diff --git a/docs/developer/alpine-patterns.md b/docs/developer/alpine-patterns.md new file mode 100644 index 0000000..be622e0 --- /dev/null +++ b/docs/developer/alpine-patterns.md @@ -0,0 +1,92 @@ +# Alpine.js Patterns in Bookhoard +## Book Picker Modal Pattern (SSR + HTMX + Alpine.store) +For modals that need state persistence across HTMX swaps: +### TypeScript +```typescript +// web/src/bookPicker.ts +Alpine.store("bookPicker", { + isOpen: false, + selectedBooks: new Set(), + + open() { + this.isOpen = true; + this.selectedBooks.clear(); + this.loadBooks(); + }, + + close() { + this.isOpen = false; + this.selectedBooks.clear(); + }, + + toggleBook(id: string) { + if (this.selectedBooks.has(id)) { + this.selectedBooks.delete(id); + } else { + this.selectedBooks.add(id); + } + // Trigger reactivity + this.selectedBooks = new Set(this.selectedBooks); + }, + + isSelected(id: string): boolean { + return this.selectedBooks.has(id); + }, + + get selectedCount(): number { + return this.selectedBooks.size; + } +}); +Template + + + +
+ + + + + +
+ +
+ + + + + + +
+Key Principles +- ✅ Alpine.store persists state across HTMX DOM swaps +- ✅ Server returns HTML with checkbox :checked attributes +- ✅ No client-side rendering or manual DOM manipulation +- ✅ HTMX handles dynamic content updates +- ✅ Alpine manages UI state only +Dropdown Pattern (Local x-data) +
+ +
+ +
+
+Best Practices +- ✅ State lives in template (x-data) +- ✅ UI updates automatically (x-show) +- ✅ No manual DOM manipulation in TypeScript +- ✅ Pure business logic only in TypeScript functions +- ✅ Use Alpine.store for global state +- ✅ Use local x-data for component state +- ✅ Use x-init for setup only (no data fetch for SSR pages) +- ❌ Never use classList in TypeScript +- ❌ Never use getElementById for show/hide +- ❌ Never fetch data in x-init (for SSR pages)