Add detailed step-by-step guide for fixing console errors in collections and cleaning up DOMContentLoaded listeners across multiple TypeScript files. COLLECTIONS_CLEANUP_GUIDE.md provides: - Complete analysis of what was broken and why - Line-by-line instructions for fixing collections.ts Alpine.data exports - Step-by-step guide for removing DOMContentLoaded from 5 TypeScript files - Template x-init additions for proper Alpine.js initialization - Verification and testing steps This guide documents the fix for: - Dead Alpine.js exports (addbooksToAdd, removebooksToAdd, toggleBookSelection, etc.) - DOMContentLoaded listeners running on wrong pages (analytics, docs, library, dashboard, admin) - Missing x-init calls in templates (analytics, docs, dashboard, library) - Template cleanup (removing dead function calls in collections.templ) The guide follows PROJECT_GUIDELINES.md standards with clear code examples, file paths, and verification steps. It serves as both implementation guide and documentation for the cleanup effort.
604 lines
14 KiB
Markdown
604 lines
14 KiB
Markdown
# 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
|
||
<!-- BEFORE -->
|
||
<button
|
||
id="bulk-remove-btn"
|
||
@click="removebooksToAdd"
|
||
disabled
|
||
class="btn-danger px-4 py-2 rounded-lg disabled:opacity-50 disabled:cursor-not-allowed"
|
||
>
|
||
🗑️ Remove Selected
|
||
</button>
|
||
|
||
<!-- AFTER -->
|
||
<button
|
||
id="bulk-remove-btn"
|
||
disabled
|
||
class="btn-danger px-4 py-2 rounded-lg disabled:opacity-50 disabled:cursor-not-allowed"
|
||
>
|
||
🗑️ Remove Selected
|
||
</button>
|
||
```
|
||
|
||
**Line 229:** Remove `addbooksToAdd` call:
|
||
|
||
```html
|
||
<!-- BEFORE -->
|
||
<button @click="addbooksToAdd" class="btn-primary px-4 py-2 rounded-lg">
|
||
➕ Add Selected Books
|
||
</button>
|
||
|
||
<!-- AFTER -->
|
||
<button type="button" class="btn-primary px-4 py-2 rounded-lg" style="opacity: 0.5; cursor: not-allowed;" disabled>
|
||
➕ Add Selected Books
|
||
</button>
|
||
```
|
||
|
||
### 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
|
||
<!-- BEFORE -->
|
||
<body x-data="analytics" class="theme-{ user.Theme }">
|
||
|
||
<!-- AFTER -->
|
||
<body x-data="analytics" x-init="loadAnalytics" class="theme-{ user.Theme }">
|
||
```
|
||
|
||
### 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 `<body>` tag** and add x-init:
|
||
|
||
```html
|
||
<!-- BEFORE -->
|
||
<body x-data="docs" class="theme-{ user.Theme }">
|
||
|
||
<!-- AFTER -->
|
||
<body x-data="docs" x-init="initializeSearch" class="theme-{ user.Theme }">
|
||
```
|
||
|
||
### 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 `<body>` tag** and add x-data and x-init:
|
||
|
||
```html
|
||
<!-- BEFORE -->
|
||
<body class="theme-{ user.Theme }">
|
||
|
||
<!-- AFTER -->
|
||
<body class="theme-{ user.Theme }" x-data="library" x-init="reloadLibraries">
|
||
```
|
||
|
||
### 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
|
||
<!-- BEFORE -->
|
||
<body class="theme-{ user.Theme }">
|
||
|
||
<!-- AFTER -->
|
||
<body class="theme-{ user.Theme }" x-data="dashboard" x-init="initDashboard()">
|
||
```
|
||
|
||
### 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.
|