mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-20 05:51:08 -05:00
Implement RideUpdateView and refactor search suggestion handling for improved modularity and error management
This commit is contained in:
@@ -1,4 +1,17 @@
|
||||
# Keep all imports and previous classes up to RideCreateView
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.urls import reverse
|
||||
from django.views.generic import UpdateView
|
||||
from companies.models import Manufacturer
|
||||
from designers.models import Designer
|
||||
from moderation.mixins import EditSubmissionMixin
|
||||
from apps.parks.mixins.base import ParkContextRequired # type: ignore
|
||||
from moderation.models import EditSubmission
|
||||
from parks.models import Park
|
||||
from rides.forms import RideForm # type: ignore
|
||||
from .models import Ride, RideModel
|
||||
|
||||
class RideUpdateView(LoginRequiredMixin, ParkContextRequired, EditSubmissionMixin, UpdateView):
|
||||
"""View for updating an existing ride"""
|
||||
|
||||
@@ -116,53 +116,10 @@ document.addEventListener('alpine:init', () => {
|
||||
const timeoutId = setTimeout(() => controller.abort(), 5000); // 5s timeout
|
||||
|
||||
try {
|
||||
const parkSlug = document.querySelector('input[name="park_slug"]')?.value;
|
||||
const url = `/rides/search-suggestions/?q=${encodeURIComponent(this.searchQuery)}${parkSlug ? '&park_slug=' + parkSlug : ''}`;
|
||||
|
||||
const response = await fetch(url, {
|
||||
signal: controller.signal,
|
||||
headers: {
|
||||
'X-Request-ID': requestId.toString()
|
||||
}
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
const html = await response.text();
|
||||
|
||||
// Only update if this is still the most recent request
|
||||
if (requestId === this.lastRequestId && this.searchQuery === document.getElementById('search').value) {
|
||||
const suggestionsEl = document.getElementById('search-suggestions');
|
||||
suggestionsEl.innerHTML = html;
|
||||
this.showSuggestions = html.trim() ? true : false;
|
||||
|
||||
// Set proper ARIA attributes
|
||||
const searchInput = document.getElementById('search');
|
||||
searchInput.setAttribute('aria-expanded', this.showSuggestions.toString());
|
||||
searchInput.setAttribute('aria-controls', 'search-suggestions');
|
||||
if (this.showSuggestions) {
|
||||
suggestionsEl.setAttribute('role', 'listbox');
|
||||
suggestionsEl.querySelectorAll('button').forEach(btn => {
|
||||
btn.setAttribute('role', 'option');
|
||||
});
|
||||
}
|
||||
}
|
||||
const response = await this.fetchSuggestions(controller, requestId);
|
||||
await this.handleSuggestionResponse(response, requestId);
|
||||
} catch (error) {
|
||||
if (error.name === 'AbortError') {
|
||||
console.warn('Search suggestion request timed out or cancelled');
|
||||
} else {
|
||||
console.error('Error fetching suggestions:', error);
|
||||
if (requestId === this.lastRequestId) {
|
||||
const suggestionsEl = document.getElementById('search-suggestions');
|
||||
suggestionsEl.innerHTML = `
|
||||
<div class="p-2 text-sm text-red-600 dark:text-red-400" role="alert">
|
||||
Failed to load suggestions. Please try again.
|
||||
</div>`;
|
||||
this.showSuggestions = true;
|
||||
}
|
||||
}
|
||||
this.handleSuggestionError(error, requestId);
|
||||
} finally {
|
||||
clearTimeout(timeoutId);
|
||||
if (this.currentRequest === controller) {
|
||||
@@ -171,6 +128,64 @@ document.addEventListener('alpine:init', () => {
|
||||
}
|
||||
},
|
||||
|
||||
async fetchSuggestions(controller, requestId) {
|
||||
const parkSlug = document.querySelector('input[name="park_slug"]')?.value;
|
||||
const url = `/rides/search-suggestions/?q=${encodeURIComponent(this.searchQuery)}${parkSlug ? '&park_slug=' + parkSlug : ''}`;
|
||||
|
||||
const response = await fetch(url, {
|
||||
signal: controller.signal,
|
||||
headers: {
|
||||
'X-Request-ID': requestId.toString()
|
||||
}
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
return response;
|
||||
},
|
||||
|
||||
async handleSuggestionResponse(response, requestId) {
|
||||
const html = await response.text();
|
||||
|
||||
if (requestId === this.lastRequestId && this.searchQuery === document.getElementById('search').value) {
|
||||
const suggestionsEl = document.getElementById('search-suggestions');
|
||||
suggestionsEl.innerHTML = html;
|
||||
this.showSuggestions = Boolean(html.trim());
|
||||
|
||||
this.updateAriaAttributes(suggestionsEl);
|
||||
}
|
||||
},
|
||||
|
||||
updateAriaAttributes(suggestionsEl) {
|
||||
const searchInput = document.getElementById('search');
|
||||
searchInput.setAttribute('aria-expanded', this.showSuggestions.toString());
|
||||
searchInput.setAttribute('aria-controls', 'search-suggestions');
|
||||
if (this.showSuggestions) {
|
||||
suggestionsEl.setAttribute('role', 'listbox');
|
||||
suggestionsEl.querySelectorAll('button').forEach(btn => {
|
||||
btn.setAttribute('role', 'option');
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
handleSuggestionError(error, requestId) {
|
||||
if (error.name === 'AbortError') {
|
||||
console.warn('Search suggestion request timed out or cancelled');
|
||||
return;
|
||||
}
|
||||
|
||||
console.error('Error fetching suggestions:', error);
|
||||
if (requestId === this.lastRequestId) {
|
||||
const suggestionsEl = document.getElementById('search-suggestions');
|
||||
suggestionsEl.innerHTML = `
|
||||
<div class="p-2 text-sm text-red-600 dark:text-red-400" role="alert">
|
||||
Failed to load suggestions. Please try again.
|
||||
</div>`;
|
||||
this.showSuggestions = true;
|
||||
}
|
||||
},
|
||||
|
||||
// Handle input changes with debounce
|
||||
async handleInput() {
|
||||
clearTimeout(this.suggestionTimeout);
|
||||
@@ -277,24 +292,6 @@ document.addEventListener('alpine:init', () => {
|
||||
}
|
||||
}));
|
||||
});
|
||||
</script>
|
||||
|
||||
<!-- HTMX Loading Indicator Styles -->
|
||||
<style>
|
||||
.htmx-indicator {
|
||||
opacity: 0;
|
||||
transition: opacity 200ms ease-in;
|
||||
}
|
||||
.htmx-request .htmx-indicator {
|
||||
opacity: 1
|
||||
}
|
||||
|
||||
/* Enhanced Loading Indicator */
|
||||
.loading-indicator {
|
||||
position: fixed;
|
||||
bottom: 1rem;
|
||||
right: 1rem;
|
||||
z-index: 50;
|
||||
},
|
||||
|
||||
performCleanup() {
|
||||
@@ -324,6 +321,23 @@ document.addEventListener('alpine:init', () => {
|
||||
}));
|
||||
});
|
||||
</script>
|
||||
|
||||
<!-- HTMX Loading Indicator Styles -->
|
||||
<style>
|
||||
.htmx-indicator {
|
||||
opacity: 0;
|
||||
transition: opacity 200ms ease-in;
|
||||
}
|
||||
.htmx-request .htmx-indicator {
|
||||
opacity: 1
|
||||
}
|
||||
|
||||
/* Enhanced Loading Indicator */
|
||||
.loading-indicator {
|
||||
position: fixed;
|
||||
bottom: 1rem;
|
||||
right: 1rem;
|
||||
z-index: 50;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
|
||||
Reference in New Issue
Block a user