mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-20 11:11:10 -05:00
Refactor photo management and upload functionality to use HTMX for asynchronous requests
- Updated photo upload handling in `photo_manager.html` and `photo_upload.html` to utilize HTMX for file uploads, improving user experience and reducing reliance on Promises. - Refactored caption update and primary photo toggle methods to leverage HTMX for state updates without full page reloads. - Enhanced error handling and success notifications using HTMX events. - Replaced fetch API calls with HTMX forms in various templates, including `homepage.html`, `park_form.html`, and `roadtrip_planner.html`, to streamline AJAX interactions. - Improved search suggestion functionality in `search_script.html` by implementing HTMX for fetching suggestions, enhancing performance and user experience. - Updated designer, manufacturer, and ride model forms to handle responses with HTMX, ensuring better integration and user feedback.
This commit is contained in:
@@ -12,14 +12,22 @@
|
||||
headers: {
|
||||
'X-CSRFToken': document.querySelector('[name=csrfmiddlewaretoken]').value
|
||||
}
|
||||
}).then(response => {
|
||||
if (response.detail) {
|
||||
const data = JSON.parse(response.detail.xhr.response);
|
||||
selectDesigner(data.id, data.name);
|
||||
});
|
||||
|
||||
// Handle HTMX response using event listeners
|
||||
document.addEventListener('htmx:afterRequest', function handleResponse(event) {
|
||||
if (event.detail.pathInfo.requestPath === '/rides/designers/create/') {
|
||||
document.removeEventListener('htmx:afterRequest', handleResponse);
|
||||
|
||||
if (event.detail.xhr.status >= 200 && event.detail.xhr.status < 300) {
|
||||
const data = JSON.parse(event.detail.xhr.response);
|
||||
if (typeof selectDesigner === 'function') {
|
||||
selectDesigner(data.id, data.name);
|
||||
}
|
||||
$dispatch('close-designer-modal');
|
||||
}
|
||||
submitting = false;
|
||||
}
|
||||
$dispatch('close-designer-modal');
|
||||
}).finally(() => {
|
||||
submitting = false;
|
||||
});
|
||||
}">
|
||||
{% csrf_token %}
|
||||
|
||||
@@ -12,14 +12,22 @@
|
||||
headers: {
|
||||
'X-CSRFToken': document.querySelector('[name=csrfmiddlewaretoken]').value
|
||||
}
|
||||
}).then(response => {
|
||||
if (response.detail) {
|
||||
const data = JSON.parse(response.detail.xhr.response);
|
||||
selectManufacturer(data.id, data.name);
|
||||
});
|
||||
|
||||
// Handle HTMX response using event listeners
|
||||
document.addEventListener('htmx:afterRequest', function handleResponse(event) {
|
||||
if (event.detail.pathInfo.requestPath === '/rides/manufacturers/create/') {
|
||||
document.removeEventListener('htmx:afterRequest', handleResponse);
|
||||
|
||||
if (event.detail.xhr.status >= 200 && event.detail.xhr.status < 300) {
|
||||
const data = JSON.parse(event.detail.xhr.response);
|
||||
if (typeof selectManufacturer === 'function') {
|
||||
selectManufacturer(data.id, data.name);
|
||||
}
|
||||
$dispatch('close-manufacturer-modal');
|
||||
}
|
||||
submitting = false;
|
||||
}
|
||||
$dispatch('close-manufacturer-modal');
|
||||
}).finally(() => {
|
||||
submitting = false;
|
||||
});
|
||||
}">
|
||||
{% csrf_token %}
|
||||
|
||||
@@ -24,20 +24,28 @@
|
||||
headers: {
|
||||
'X-CSRFToken': document.querySelector('[name=csrfmiddlewaretoken]').value
|
||||
}
|
||||
}).then(response => {
|
||||
if (response.detail) {
|
||||
const data = JSON.parse(response.detail.xhr.response);
|
||||
selectRideModel(data.id, data.name);
|
||||
}
|
||||
const parentForm = document.querySelector('[x-data]');
|
||||
if (parentForm) {
|
||||
const parentData = Alpine.$data(parentForm);
|
||||
if (parentData && parentData.setRideModelModal) {
|
||||
parentData.setRideModelModal(false);
|
||||
});
|
||||
|
||||
// Handle HTMX response using event listeners
|
||||
document.addEventListener('htmx:afterRequest', function handleResponse(event) {
|
||||
if (event.detail.pathInfo.requestPath === '/rides/models/create/') {
|
||||
document.removeEventListener('htmx:afterRequest', handleResponse);
|
||||
|
||||
if (event.detail.xhr.status >= 200 && event.detail.xhr.status < 300) {
|
||||
const data = JSON.parse(event.detail.xhr.response);
|
||||
if (typeof selectRideModel === 'function') {
|
||||
selectRideModel(data.id, data.name);
|
||||
}
|
||||
const parentForm = document.querySelector('[x-data]');
|
||||
if (parentForm) {
|
||||
const parentData = Alpine.$data(parentForm);
|
||||
if (parentData && parentData.setRideModelModal) {
|
||||
parentData.setRideModelModal(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
submitting = false;
|
||||
}
|
||||
}).finally(() => {
|
||||
submitting = false;
|
||||
});
|
||||
}">
|
||||
{% csrf_token %}
|
||||
|
||||
@@ -98,7 +98,7 @@ document.addEventListener('alpine:init', () => {
|
||||
lastRequestId: 0,
|
||||
currentRequest: null,
|
||||
|
||||
async getSearchSuggestions() {
|
||||
getSearchSuggestions() {
|
||||
if (this.searchQuery.length < 2) {
|
||||
this.showSuggestions = false;
|
||||
return;
|
||||
@@ -115,40 +115,79 @@ document.addEventListener('alpine:init', () => {
|
||||
|
||||
const timeoutId = setTimeout(() => controller.abort(), 5000); // 5s timeout
|
||||
|
||||
try {
|
||||
const response = await this.fetchSuggestions(controller, requestId);
|
||||
await this.handleSuggestionResponse(response, requestId);
|
||||
} catch (error) {
|
||||
this.handleSuggestionError(error, requestId);
|
||||
} finally {
|
||||
this.fetchSuggestions(controller, requestId, () => {
|
||||
clearTimeout(timeoutId);
|
||||
if (this.currentRequest === controller) {
|
||||
this.currentRequest = null;
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
async fetchSuggestions(controller, requestId) {
|
||||
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 queryParams = {
|
||||
q: this.searchQuery
|
||||
};
|
||||
if (parkSlug) {
|
||||
queryParams.park_slug = parkSlug;
|
||||
}
|
||||
|
||||
const response = await fetch(url, {
|
||||
signal: controller.signal,
|
||||
headers: {
|
||||
'X-Request-ID': requestId.toString()
|
||||
// Create temporary form for HTMX request
|
||||
const tempForm = document.createElement('form');
|
||||
tempForm.setAttribute('hx-get', '/rides/search-suggestions/');
|
||||
tempForm.setAttribute('hx-vals', JSON.stringify(queryParams));
|
||||
tempForm.setAttribute('hx-trigger', 'submit');
|
||||
tempForm.setAttribute('hx-swap', 'none');
|
||||
|
||||
// Add request ID header simulation
|
||||
tempForm.setAttribute('hx-headers', JSON.stringify({
|
||||
'X-Request-ID': requestId.toString()
|
||||
}));
|
||||
|
||||
// Handle abort signal
|
||||
if (controller.signal.aborted) {
|
||||
this.handleSuggestionError(new Error('AbortError'), requestId);
|
||||
return;
|
||||
}
|
||||
|
||||
const abortHandler = () => {
|
||||
if (document.body.contains(tempForm)) {
|
||||
document.body.removeChild(tempForm);
|
||||
}
|
||||
this.handleSuggestionError(new Error('AbortError'), requestId);
|
||||
};
|
||||
controller.signal.addEventListener('abort', abortHandler);
|
||||
|
||||
tempForm.addEventListener('htmx:afterRequest', (event) => {
|
||||
controller.signal.removeEventListener('abort', abortHandler);
|
||||
|
||||
if (event.detail.xhr.status >= 200 && event.detail.xhr.status < 300) {
|
||||
this.handleSuggestionResponse(event.detail.xhr, requestId);
|
||||
} else {
|
||||
this.handleSuggestionError(new Error(`HTTP error! status: ${event.detail.xhr.status}`), requestId);
|
||||
}
|
||||
|
||||
if (document.body.contains(tempForm)) {
|
||||
document.body.removeChild(tempForm);
|
||||
}
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
return response;
|
||||
tempForm.addEventListener('htmx:error', (event) => {
|
||||
controller.signal.removeEventListener('abort', abortHandler);
|
||||
this.handleSuggestionError(new Error(`HTTP error! status: ${event.detail.xhr.status || 'unknown'}`), requestId);
|
||||
|
||||
if (document.body.contains(tempForm)) {
|
||||
document.body.removeChild(tempForm);
|
||||
}
|
||||
});
|
||||
|
||||
document.body.appendChild(tempForm);
|
||||
htmx.trigger(tempForm, 'submit');
|
||||
},
|
||||
|
||||
async handleSuggestionResponse(response, requestId) {
|
||||
const html = await response.text();
|
||||
|
||||
handleSuggestionResponse(xhr, requestId) {
|
||||
if (requestId === this.lastRequestId && this.searchQuery === document.getElementById('search').value) {
|
||||
const html = xhr.responseText || '';
|
||||
const suggestionsEl = document.getElementById('search-suggestions');
|
||||
suggestionsEl.innerHTML = html;
|
||||
this.showSuggestions = Boolean(html.trim());
|
||||
@@ -187,7 +226,7 @@ document.addEventListener('alpine:init', () => {
|
||||
},
|
||||
|
||||
// Handle input changes with debounce
|
||||
async handleInput() {
|
||||
handleInput() {
|
||||
clearTimeout(this.suggestionTimeout);
|
||||
this.suggestionTimeout = setTimeout(() => {
|
||||
this.getSearchSuggestions();
|
||||
@@ -413,4 +452,4 @@ window.addEventListener('beforeunload', () => {
|
||||
timeouts.forEach(timeoutId => clearTimeout(timeoutId));
|
||||
timeouts.clear();
|
||||
});
|
||||
</script>
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user