mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-22 13:11:12 -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:
@@ -259,60 +259,128 @@ function parkForm() {
|
||||
this.previews.splice(index, 1);
|
||||
},
|
||||
|
||||
async uploadPhotos() {
|
||||
uploadPhotos() {
|
||||
if (!this.previews.length) return true;
|
||||
|
||||
this.uploading = true;
|
||||
let allUploaded = true;
|
||||
let uploadPromises = [];
|
||||
|
||||
for (let preview of this.previews) {
|
||||
if (preview.uploaded || preview.error) continue;
|
||||
|
||||
preview.uploading = true;
|
||||
const formData = new FormData();
|
||||
formData.append('image', preview.file);
|
||||
formData.append('app_label', 'parks');
|
||||
formData.append('model', 'park');
|
||||
formData.append('object_id', '{{ park.id }}');
|
||||
|
||||
try {
|
||||
const response = await fetch('/photos/upload/', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'X-CSRFToken': document.querySelector('[name=csrfmiddlewaretoken]').value,
|
||||
},
|
||||
body: formData
|
||||
});
|
||||
|
||||
if (!response.ok) throw new Error('Upload failed');
|
||||
|
||||
const result = await response.json();
|
||||
preview.uploading = false;
|
||||
preview.uploaded = true;
|
||||
} catch (error) {
|
||||
console.error('Upload failed:', error);
|
||||
preview.uploading = false;
|
||||
preview.error = true;
|
||||
allUploaded = false;
|
||||
}
|
||||
|
||||
// Create temporary form for HTMX request
|
||||
const tempForm = document.createElement('form');
|
||||
tempForm.setAttribute('hx-post', '/photos/upload/');
|
||||
tempForm.setAttribute('hx-trigger', 'submit');
|
||||
tempForm.setAttribute('hx-swap', 'none');
|
||||
tempForm.enctype = 'multipart/form-data';
|
||||
|
||||
// Add CSRF token
|
||||
const csrfInput = document.createElement('input');
|
||||
csrfInput.type = 'hidden';
|
||||
csrfInput.name = 'csrfmiddlewaretoken';
|
||||
csrfInput.value = document.querySelector('[name=csrfmiddlewaretoken]').value;
|
||||
tempForm.appendChild(csrfInput);
|
||||
|
||||
// Add form data
|
||||
const imageInput = document.createElement('input');
|
||||
imageInput.type = 'file';
|
||||
imageInput.name = 'image';
|
||||
imageInput.files = this.createFileList([preview.file]);
|
||||
tempForm.appendChild(imageInput);
|
||||
|
||||
const appLabelInput = document.createElement('input');
|
||||
appLabelInput.type = 'hidden';
|
||||
appLabelInput.name = 'app_label';
|
||||
appLabelInput.value = 'parks';
|
||||
tempForm.appendChild(appLabelInput);
|
||||
|
||||
const modelInput = document.createElement('input');
|
||||
modelInput.type = 'hidden';
|
||||
modelInput.name = 'model';
|
||||
modelInput.value = 'park';
|
||||
tempForm.appendChild(modelInput);
|
||||
|
||||
const objectIdInput = document.createElement('input');
|
||||
objectIdInput.type = 'hidden';
|
||||
objectIdInput.name = 'object_id';
|
||||
objectIdInput.value = '{{ park.id }}';
|
||||
tempForm.appendChild(objectIdInput);
|
||||
|
||||
// Track upload completion with event listeners
|
||||
tempForm.addEventListener('htmx:afterRequest', (event) => {
|
||||
try {
|
||||
if (event.detail.xhr.status === 200) {
|
||||
const result = JSON.parse(event.detail.xhr.responseText);
|
||||
preview.uploading = false;
|
||||
preview.uploaded = true;
|
||||
} else {
|
||||
throw new Error('Upload failed');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Upload failed:', error);
|
||||
preview.uploading = false;
|
||||
preview.error = true;
|
||||
allUploaded = false;
|
||||
}
|
||||
|
||||
// Track completion
|
||||
completedUploads++;
|
||||
if (completedUploads === totalUploads) {
|
||||
this.uploading = false;
|
||||
}
|
||||
|
||||
document.body.removeChild(tempForm);
|
||||
});
|
||||
document.body.appendChild(tempForm);
|
||||
htmx.trigger(tempForm, 'submit');
|
||||
}
|
||||
|
||||
this.uploading = false;
|
||||
return allUploaded;
|
||||
// Initialize completion tracking
|
||||
let completedUploads = 0;
|
||||
const totalUploads = this.previews.filter(p => !p.uploaded && !p.error).length;
|
||||
|
||||
if (totalUploads === 0) {
|
||||
this.uploading = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
return true; // Return immediately, completion handled by event listeners
|
||||
},
|
||||
|
||||
createFileList(files) {
|
||||
const dt = new DataTransfer();
|
||||
files.forEach(file => dt.items.add(file));
|
||||
return dt.files;
|
||||
},
|
||||
|
||||
removePhoto(photoId) {
|
||||
if (confirm('Are you sure you want to remove this photo?')) {
|
||||
fetch(`/photos/${photoId}/delete/`, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'X-CSRFToken': document.querySelector('[name=csrfmiddlewaretoken]').value,
|
||||
},
|
||||
}).then(response => {
|
||||
if (response.ok) {
|
||||
// Create temporary form for HTMX request
|
||||
const tempForm = document.createElement('form');
|
||||
tempForm.setAttribute('hx-delete', `/photos/${photoId}/delete/`);
|
||||
tempForm.setAttribute('hx-trigger', 'submit');
|
||||
tempForm.setAttribute('hx-swap', 'none');
|
||||
|
||||
// Add CSRF token
|
||||
const csrfInput = document.createElement('input');
|
||||
csrfInput.type = 'hidden';
|
||||
csrfInput.name = 'csrfmiddlewaretoken';
|
||||
csrfInput.value = document.querySelector('[name=csrfmiddlewaretoken]').value;
|
||||
tempForm.appendChild(csrfInput);
|
||||
|
||||
tempForm.addEventListener('htmx:afterRequest', (event) => {
|
||||
if (event.detail.xhr.status === 200) {
|
||||
window.location.reload();
|
||||
}
|
||||
document.body.removeChild(tempForm);
|
||||
});
|
||||
|
||||
document.body.appendChild(tempForm);
|
||||
htmx.trigger(tempForm, 'submit');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -380,17 +380,28 @@ class TripPlanner {
|
||||
});
|
||||
}
|
||||
|
||||
async loadAllParks() {
|
||||
try {
|
||||
const response = await fetch('{{ map_api_urls.locations }}?types=park&limit=1000');
|
||||
const data = await response.json();
|
||||
|
||||
if (data.status === 'success' && data.data.locations) {
|
||||
this.allParks = data.data.locations;
|
||||
loadAllParks() {
|
||||
// Create temporary form for HTMX request
|
||||
const tempForm = document.createElement('form');
|
||||
tempForm.setAttribute('hx-get', '{{ map_api_urls.locations }}');
|
||||
tempForm.setAttribute('hx-vals', JSON.stringify({types: 'park', limit: 1000}));
|
||||
tempForm.setAttribute('hx-trigger', 'submit');
|
||||
tempForm.setAttribute('hx-swap', 'none');
|
||||
|
||||
tempForm.addEventListener('htmx:afterRequest', (event) => {
|
||||
try {
|
||||
const data = JSON.parse(event.detail.xhr.responseText);
|
||||
if (data.status === 'success' && data.data.locations) {
|
||||
this.allParks = data.data.locations;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to load parks:', error);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to load parks:', error);
|
||||
}
|
||||
document.body.removeChild(tempForm);
|
||||
});
|
||||
|
||||
document.body.appendChild(tempForm);
|
||||
htmx.trigger(tempForm, 'submit');
|
||||
}
|
||||
|
||||
initDragDrop() {
|
||||
@@ -570,38 +581,50 @@ class TripPlanner {
|
||||
}
|
||||
}
|
||||
|
||||
async optimizeRoute() {
|
||||
optimizeRoute() {
|
||||
if (this.tripParks.length < 2) return;
|
||||
|
||||
try {
|
||||
const parkIds = this.tripParks.map(p => p.id);
|
||||
const response = await fetch('{% url "parks:htmx_optimize_route" %}', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': '{{ csrf_token }}'
|
||||
},
|
||||
body: JSON.stringify({ park_ids: parkIds })
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.status === 'success' && data.optimized_order) {
|
||||
// Reorder parks based on optimization
|
||||
const optimizedParks = data.optimized_order.map(id =>
|
||||
this.tripParks.find(p => p.id === id)
|
||||
).filter(Boolean);
|
||||
const parkIds = this.tripParks.map(p => p.id);
|
||||
|
||||
// Create temporary form for HTMX request
|
||||
const tempForm = document.createElement('form');
|
||||
tempForm.setAttribute('hx-post', '{% url "parks:htmx_optimize_route" %}');
|
||||
tempForm.setAttribute('hx-vals', JSON.stringify({ park_ids: parkIds }));
|
||||
tempForm.setAttribute('hx-trigger', 'submit');
|
||||
tempForm.setAttribute('hx-swap', 'none');
|
||||
|
||||
// Add CSRF token
|
||||
const csrfInput = document.createElement('input');
|
||||
csrfInput.type = 'hidden';
|
||||
csrfInput.name = 'csrfmiddlewaretoken';
|
||||
csrfInput.value = '{{ csrf_token }}';
|
||||
tempForm.appendChild(csrfInput);
|
||||
|
||||
tempForm.addEventListener('htmx:afterRequest', (event) => {
|
||||
try {
|
||||
const data = JSON.parse(event.detail.xhr.responseText);
|
||||
|
||||
this.tripParks = optimizedParks;
|
||||
this.updateTripDisplay();
|
||||
this.updateTripMarkers();
|
||||
if (data.status === 'success' && data.optimized_order) {
|
||||
// Reorder parks based on optimization
|
||||
const optimizedParks = data.optimized_order.map(id =>
|
||||
this.tripParks.find(p => p.id === id)
|
||||
).filter(Boolean);
|
||||
|
||||
this.tripParks = optimizedParks;
|
||||
this.updateTripDisplay();
|
||||
this.updateTripMarkers();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Route optimization failed:', error);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Route optimization failed:', error);
|
||||
}
|
||||
document.body.removeChild(tempForm);
|
||||
});
|
||||
|
||||
document.body.appendChild(tempForm);
|
||||
htmx.trigger(tempForm, 'submit');
|
||||
}
|
||||
|
||||
async calculateRoute() {
|
||||
calculateRoute() {
|
||||
if (this.tripParks.length < 2) return;
|
||||
|
||||
// Remove existing route
|
||||
@@ -733,38 +756,49 @@ class TripPlanner {
|
||||
document.getElementById('trip-summary').classList.add('hidden');
|
||||
}
|
||||
|
||||
async saveTrip() {
|
||||
saveTrip() {
|
||||
if (this.tripParks.length === 0) return;
|
||||
|
||||
const tripName = prompt('Enter a name for this trip:');
|
||||
if (!tripName) return;
|
||||
|
||||
try {
|
||||
const response = await fetch('{% url "parks:htmx_save_trip" %}', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': '{{ csrf_token }}'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
name: tripName,
|
||||
park_ids: this.tripParks.map(p => p.id)
|
||||
})
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.status === 'success') {
|
||||
alert('Trip saved successfully!');
|
||||
// Refresh saved trips
|
||||
htmx.trigger('#saved-trips', 'refresh');
|
||||
} else {
|
||||
alert('Failed to save trip: ' + (data.message || 'Unknown error'));
|
||||
// Create temporary form for HTMX request
|
||||
const tempForm = document.createElement('form');
|
||||
tempForm.setAttribute('hx-post', '{% url "parks:htmx_save_trip" %}');
|
||||
tempForm.setAttribute('hx-vals', JSON.stringify({
|
||||
name: tripName,
|
||||
park_ids: this.tripParks.map(p => p.id)
|
||||
}));
|
||||
tempForm.setAttribute('hx-trigger', 'submit');
|
||||
tempForm.setAttribute('hx-swap', 'none');
|
||||
|
||||
// Add CSRF token
|
||||
const csrfInput = document.createElement('input');
|
||||
csrfInput.type = 'hidden';
|
||||
csrfInput.name = 'csrfmiddlewaretoken';
|
||||
csrfInput.value = '{{ csrf_token }}';
|
||||
tempForm.appendChild(csrfInput);
|
||||
|
||||
tempForm.addEventListener('htmx:afterRequest', (event) => {
|
||||
try {
|
||||
const data = JSON.parse(event.detail.xhr.responseText);
|
||||
|
||||
if (data.status === 'success') {
|
||||
alert('Trip saved successfully!');
|
||||
// Refresh saved trips using HTMX
|
||||
htmx.trigger(document.getElementById('saved-trips'), 'refresh');
|
||||
} else {
|
||||
alert('Failed to save trip: ' + (data.message || 'Unknown error'));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Save trip failed:', error);
|
||||
alert('Failed to save trip');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Save trip failed:', error);
|
||||
alert('Failed to save trip');
|
||||
}
|
||||
document.body.removeChild(tempForm);
|
||||
});
|
||||
|
||||
document.body.appendChild(tempForm);
|
||||
htmx.trigger(tempForm, 'submit');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -785,4 +819,4 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
||||
Reference in New Issue
Block a user