# Collections Cleanup: Fix Alpine.js and DOMContentLoaded Issues ## Overview This guide fixes console errors caused by: 1. Dead Alpine.js exports (functions that don't exist) 2. DOMContentLoaded listeners running on wrong pages 3. Missing x-init calls in templates **Goal:** Clean, working Alpine.js integration with proper SSR architecture. --- ## Prerequisites Before starting, verify the current state: ```bash # Check current errors cd /home/nymusicman/Code/bookhoard npm run build:ts # Should see errors about: # - "addbooksToAdd is not defined" # - "removebooksToAdd is not defined" # - "toggleBookSelection is not defined" # - etc. ``` --- ## Step 1: Fix collections.ts Alpine.data Export **File:** `web/src/collections.ts` **Problem:** Alpine.data exports functions that were deleted in commit 93710a1. **Action:** Update the Alpine.data export to only include existing functions. ### Step 1.1: Read Current Alpine.data Export ```bash # Check what's currently exported tail -50 web/src/collections.ts | grep -A25 "Alpine.data" ``` You'll see something like: ```typescript Alpine.data("collections", () => ({ addbooksToAdd, // ❌ Does NOT exist - deleted backToCollections, closeCollectionModal, createRule, deleteRule, filterCollectionBooks, filterIcons, hideAddBooksModal, initCollectionDetail, // ❌ Does NOT exist - deleted initColorSelection, // ❌ Does NOT exist - deleted initIconSelection, // ❌ Does NOT exist - deleted loadCollectionRules, loadCollections, navigateToCollection, populateIconGrid, removeBook, removebooksToAdd, // ❌ Does NOT exist - deleted searchBooksForCollections, selectColor, selectIcon, showAddBooksModal, showAllIcons, // ❌ Does NOT exist - deleted setupHTMXAuth, testRule, toggleBookForRemoval, // ❌ Does NOT exist - deleted toggleBookSelection, // ❌ Does NOT exist - deleted updateSelectedCount, })); ``` ### Step 1.2: Read Current Export Statement ```bash # Check the export statement at the end of the file grep -A30 "^export {" web/src/collections.ts ``` You'll see similar dead exports. ### Step 1.3: Verify Which Functions Actually Exist ```bash # Search for function definitions grep -n "^function\|^async function" web/src/collections.ts ``` Expected output (actual existing functions): - `backToCollections` ✓ - `closeCollectionModal` ✓ - `createRule` ✓ - `deleteRule` ✓ - `filterCollectionBooks` ✓ - `filterIcons` ✓ - `hideAddBooksModal` ✓ - `loadCollectionRules` ✓ - `loadCollections` ✓ - `navigateToCollection` ✓ - `populateIconGrid` ✓ - `removeBook` ✓ - `searchBooksForCollections` ✓ - `selectColor` ✓ - `selectIcon` ✓ - `showAddBooksModal` ✓ - `setupHTMXAuth` ✓ - `testRule` ✓ - `updateSelectedCount` ✓ ### Step 1.4: Update the Export Statement **Line 407:** Change the `export` statement to only include existing functions: ```typescript export { backToCollections, closeCollectionModal, createRule, deleteRule, filterCollectionBooks, filterIcons, hideAddBooksModal, loadCollectionRules, loadCollections, navigateToCollection, populateIconGrid, removeBook, searchBooksForCollections, selectColor, selectIcon, showAddBooksModal, setupHTMXAuth, testRule, updateSelectedCount, }; ``` ### Step 1.5: Update Alpine.data Registration **Line 424:** Update Alpine.data to match the export: ```typescript Alpine.data("collections", () => ({ backToCollections, closeCollectionModal, createRule, deleteRule, filterCollectionBooks, filterIcons, hideAddBooksModal, loadCollectionRules, loadCollections, navigateToCollection, populateIconGrid, removeBook, searchBooksForCollections, selectColor, selectIcon, showAddBooksModal, setupHTMXAuth, testRule, updateSelectedCount, })); ``` ### Step 1.6: Verify the Fix ```bash # Build TypeScript npm run build:ts # Should now succeed with 0 errors ``` --- ## Step 2: Fix Template Function Calls **File:** `templates/collections.templ` **Problem:** Template calls functions that no longer exist. ### Step 2.1: Remove Dead Function Calls from Collection Detail Page **Line 226:** Remove the `removebooksToAdd` call: ```html ``` **Line 229:** Remove `addbooksToAdd` call: ```html ``` ### Step 2.2: Regenerate Templates ```bash # Generate Go template files templ generate # Should see: Complete [updates=0 duration=~40ms] ``` --- ## Step 3: Fix Other TypeScript Files (Remove DOMContentLoaded) For each file, we'll remove the `DOMContentLoaded` listener and add x-init to the template. ### Step 3.1: analytics.ts **File:** `web/src/analytics.ts` **Current:** ```typescript export { loadAnalytics }; document.addEventListener("DOMContentLoaded", loadAnalytics); Alpine.data("analytics", () => ({ loadAnalytics, })); ``` **Change to:** ```typescript export { loadAnalytics }; // REMOVE this line: // document.addEventListener("DOMContentLoaded", loadAnalytics); Alpine.data("analytics", () => ({ loadAnalytics, })); ``` **Template Update:** `templates/analytics.templ` **Line 14:** Add x-init to body tag: ```html ``` ### Step 3.2: docs.ts **File:** `web/src/docs.ts` **Current (around line 95-100):** ```typescript document.addEventListener("DOMContentLoaded", () => { initializeDocsSearch(); }); export { toggleSidebar, initializeDocsSearch }; Alpine.data("docs", () => ({ toggleSidebar, initializeSearch: initializeDocsSearch, })); ``` **Change to:** ```typescript // REMOVE this entire block: // document.addEventListener("DOMContentLoaded", () => { // initializeDocsSearch(); // }); export { toggleSidebar, initializeDocsSearch }; Alpine.data("docs", () => ({ toggleSidebar, initializeSearch: initializeDocsSearch, })); ``` **Template Update:** `templates/docs.templ` **Find the `` tag** and add x-init: ```html ``` ### Step 3.3: library.ts **File:** `web/src/library.ts` **Current (around line 655-662):** ```typescript // Initialize on DOM ready if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", initializeLibraryAdmin); } else { initializeLibraryAdmin(); } // ... later in file ... ``` **Change to:** ```typescript // REMOVE this entire block: // if (document.readyState === "loading") { // document.addEventListener("DOMContentLoaded", initializeLibraryAdmin); // } else { // initializeLibraryAdmin(); // } ``` **Template Update:** `templates/library.templ` **Find the `` tag** and add x-data and x-init: ```html ``` ### Step 3.4: dashboard.ts **File:** `web/src/dashboard.ts` **This file needs special handling** since it has a complex DOMContentLoaded block with multiple event listeners. **Current (lines 494-521):** ```typescript document.addEventListener("DOMContentLoaded", () => { initDragAndDrop(); document.addEventListener("click", (e: Event) => { // ... 70+ lines of event handling ... }); document.addEventListener("input", (e: Event) => { // ... event handling ... }); // ... more initialization ... }); ``` **Create a wrapper function at the end of the file:** **Add before the export statement (before line ~490):** ```typescript // Wrapper function for dashboard initialization function initDashboard() { initDragAndDrop(); document.addEventListener("click", (e: Event) => { const target = e.target as HTMLElement; const actionElem = target.closest("[data-action]") as HTMLElement; const action = actionElem?.getAttribute("data-action"); switch (action) { case "scroll-carousel": { const collectionId = target.dataset.direction || actionElem?.dataset.direction || "0"; if (collectionId) scrollCarousel(collectionId, direction); break; } // ... keep all existing cases ... } }); document.addEventListener("input", (e: Event) => { const target = e.target as HTMLElement; const actionElem = target.closest("[data-input-action]") as HTMLElement; const action = actionElem?.getAttribute("data-input-action"); switch (action) { case "update-items-count": { const input = target as HTMLInputElement; const displayTarget = input.getAttribute("target"); if (displayTarget) updateItemsCount(input, displayTarget); break; } } }); // Load saved library on page load const librarySelect = document.getElementById( "library-select", ) as HTMLSelectElement; if (librarySelect) { librarySelect.addEventListener("change", (e) => { const target = e.target as HTMLSelectElement; if (target.value) { switchLibrary(target.value); } }); } const savedLibrary = localStorage.getItem("selectedLibrary"); const currentLibrary = new URLSearchParams(window.location.search).get( "library_id", ); if (savedLibrary && savedLibrary !== currentLibrary) { window.location.href = `/dashboard?library_id=${savedLibrary}`; } } ``` **Remove the old DOMContentLoaded block** (delete lines 494-521): ```typescript // DELETE this entire block: // document.addEventListener("DOMContentLoaded", () => { // initDragAndDrop(); // ... all 70+ lines ... // }); ``` **Add to export statement:** ```typescript export { closeDashboardSettings, openDashboardSettings, initDashboard, // ← ADD THIS saveDashboardSettings, scrollCarousel, // ... keep all other exports ... }; ``` **Add to Alpine.data:** ```typescript Alpine.data("dashboard", () => ({ closeDashboardSettings, openDashboardSettings, initDashboard, // ← ADD THIS saveDashboardSettings, scrollCarousel, // ... keep all other exports ... })); ``` **Template Update:** `templates/dashboard.templ` **Line 19:** Add x-data and x-init to body tag: ```html ``` ### Step 3.5: Verify All TypeScript Files ```bash # Build all TypeScript npm run build:ts # Should succeed with 0 errors ``` --- ## Step 4: Regenerate Templates ```bash # Generate Go template files templ generate # Expected: Complete [updates=X duration=~40ms] # where X is the number of templates modified ``` --- ## Step 5: Build and Verify ```bash # Build the Go server go build ./cmd/server # If build succeeds, you're done! # If build fails, check the error and fix accordingly ``` --- ## Step 6: Test in Browser 1. **Start the server:** ```bash podman compose up -d ``` 2. **Open browser and test:** - Navigate to `/collections` - should work with no console errors - Navigate to `/analytics` - should load analytics data - Navigate to `/dashboard` - drag and drop should work - Navigate to `/docs` - search should work - Navigate to `/admin/library` - library management should work 3. **Check browser console:** - No "X is not defined" errors - No "n.bind is not a function" errors - No "initX is not defined" errors --- ## Summary ### Changes Made **TypeScript Files (5 files):** - `web/src/collections.ts` - Removed dead exports - `web/src/analytics.ts` - Removed DOMContentLoaded - `web/src/docs.ts` - Removed DOMContentLoaded - `web/src/library.ts` - Removed DOMContentLoaded - `web/src/dashboard.ts` - Created initDashboard wrapper, removed DOMContentLoaded **Template Files (5 files):** - `templates/analytics.templ` - Added x-init="loadAnalytics" - `templates/docs.templ` - Added x-init="initializeSearch" - `templates/dashboard.templ` - Added x-data="dashboard" x-init="initDashboard()" - `templates/library.templ` - Added x-data="library" x-init="reloadLibraries" - `templates/collections.templ` - Removed dead function calls **Generated Files:** - All `_templ.go` files regenerated ### What Was NOT Changed - **"Add Books" modal** - Still client-side Alpine.js (quick fix decision) - **WebSocket code** - Already moved to templates (previous commit) - **API endpoints** - Already exist and work correctly - **HTMX modals** - Already implemented for collection CRUD ### Result ✅ No more Alpine.js errors ✅ No more DOMContentLoaded pollution ✅ Pages run only their own initialization code ✅ Console is clean ✅ SSR architecture maintained ### Next Steps (Optional) If you want to convert the "Add Books" modal to HTMX (future enhancement), that would require: 1. Create `templates/add_books_modal.templ` 2. Add route in `internal/router/collections.go` 3. Create handler in `internal/handlers/collections.go` 4. Update `collections.templ` to use HTMX modal instead of inline 5. Remove all book selection JavaScript from `collections.ts` But for now, the client-side approach works fine.