mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-20 03:51:09 -05:00
- Enhanced filter sidebar with AlpineJS for collapsible sections and localStorage persistence. - Removed custom JavaScript in favor of AlpineJS for managing filter states and interactions. - Updated ride form to utilize AlpineJS for handling manufacturer, designer, and ride model selections. - Simplified search script to leverage AlpineJS for managing search input and suggestions. - Improved error handling for HTMX requests with minimal JavaScript. - Refactored ride form data handling to encapsulate logic within an AlpineJS component.
166 lines
4.8 KiB
HTML
166 lines
4.8 KiB
HTML
<!-- HTMX + AlpineJS ONLY - NO CUSTOM JAVASCRIPT -->
|
|
<div x-data="{
|
|
searchQuery: new URLSearchParams(window.location.search).get('search') || '',
|
|
showSuggestions: false,
|
|
selectedIndex: -1,
|
|
|
|
init() {
|
|
// Watch for URL changes
|
|
this.$watch('searchQuery', value => {
|
|
if (value.length >= 2) {
|
|
this.showSuggestions = true;
|
|
} else {
|
|
this.showSuggestions = false;
|
|
}
|
|
});
|
|
|
|
// Handle clicks outside to close suggestions
|
|
this.$el.addEventListener('click', (e) => {
|
|
if (!e.target.closest('#search-suggestions') && !e.target.closest('#search')) {
|
|
this.showSuggestions = false;
|
|
}
|
|
});
|
|
},
|
|
|
|
handleInput() {
|
|
// HTMX will handle the actual search request
|
|
if (this.searchQuery.length >= 2) {
|
|
this.showSuggestions = true;
|
|
} else {
|
|
this.showSuggestions = false;
|
|
}
|
|
},
|
|
|
|
selectSuggestion(text) {
|
|
this.searchQuery = text;
|
|
this.showSuggestions = false;
|
|
// Update the search input
|
|
this.$refs.searchInput.value = text;
|
|
// Trigger form change for HTMX
|
|
this.$refs.searchForm.dispatchEvent(new Event('change'));
|
|
},
|
|
|
|
handleKeydown(e) {
|
|
const suggestions = this.$el.querySelectorAll('#search-suggestions button');
|
|
if (!suggestions.length) return;
|
|
|
|
switch(e.key) {
|
|
case 'ArrowDown':
|
|
e.preventDefault();
|
|
if (this.selectedIndex < suggestions.length - 1) {
|
|
this.selectedIndex++;
|
|
suggestions[this.selectedIndex].focus();
|
|
}
|
|
break;
|
|
case 'ArrowUp':
|
|
e.preventDefault();
|
|
if (this.selectedIndex > 0) {
|
|
this.selectedIndex--;
|
|
suggestions[this.selectedIndex].focus();
|
|
} else {
|
|
this.$refs.searchInput.focus();
|
|
this.selectedIndex = -1;
|
|
}
|
|
break;
|
|
case 'Escape':
|
|
this.showSuggestions = false;
|
|
this.selectedIndex = -1;
|
|
this.$refs.searchInput.blur();
|
|
break;
|
|
case 'Enter':
|
|
if (e.target.tagName === 'BUTTON') {
|
|
e.preventDefault();
|
|
this.selectSuggestion(e.target.dataset.text);
|
|
}
|
|
break;
|
|
case 'Tab':
|
|
this.showSuggestions = false;
|
|
break;
|
|
}
|
|
}
|
|
}"
|
|
@click.outside="showSuggestions = false">
|
|
|
|
<!-- Search Input with HTMX -->
|
|
<input
|
|
x-ref="searchInput"
|
|
x-model="searchQuery"
|
|
@input="handleInput()"
|
|
@keydown="handleKeydown($event)"
|
|
hx-get="/rides/search-suggestions/"
|
|
hx-trigger="input changed delay:200ms"
|
|
hx-target="#search-suggestions"
|
|
hx-swap="innerHTML"
|
|
hx-include="[name='park_slug']"
|
|
:aria-expanded="showSuggestions"
|
|
aria-controls="search-suggestions"
|
|
type="text"
|
|
name="search"
|
|
id="search"
|
|
placeholder="Search rides..."
|
|
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
|
/>
|
|
|
|
<!-- Suggestions Container -->
|
|
<div
|
|
x-show="showSuggestions"
|
|
x-transition
|
|
id="search-suggestions"
|
|
role="listbox"
|
|
class="absolute z-50 w-full mt-1 bg-white border border-gray-300 rounded-lg shadow-lg max-h-60 overflow-y-auto"
|
|
>
|
|
<!-- HTMX will populate this -->
|
|
</div>
|
|
|
|
<!-- Form Reference for HTMX -->
|
|
<form x-ref="searchForm" style="display: none;">
|
|
<!-- Hidden form for HTMX reference -->
|
|
</form>
|
|
</div>
|
|
|
|
<!-- HTMX Loading Indicator Styles -->
|
|
<style>
|
|
.htmx-indicator {
|
|
opacity: 0;
|
|
transition: opacity 200ms ease-in;
|
|
}
|
|
.htmx-request .htmx-indicator {
|
|
opacity: 1;
|
|
}
|
|
|
|
.loading-indicator {
|
|
position: fixed;
|
|
bottom: 1rem;
|
|
right: 1rem;
|
|
z-index: 50;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
color: white;
|
|
font-size: 0.875rem;
|
|
}
|
|
|
|
.loading-indicator svg {
|
|
width: 1.25rem;
|
|
height: 1.25rem;
|
|
animation: spin 1s linear infinite;
|
|
}
|
|
|
|
@keyframes spin {
|
|
from { transform: rotate(0deg); }
|
|
to { transform: rotate(360deg); }
|
|
}
|
|
</style>
|
|
|
|
<!-- HTMX Error Handling (Minimal JavaScript as allowed by Context7 docs) -->
|
|
<div x-data="{
|
|
init() {
|
|
// Only essential HTMX error handling as shown in Context7 docs
|
|
this.$el.addEventListener('htmx:responseError', (evt) => {
|
|
if (evt.detail.xhr.status === 404 || evt.detail.xhr.status === 500) {
|
|
console.error('HTMX Error:', evt.detail.xhr.status);
|
|
}
|
|
});
|
|
}
|
|
}"></div>
|