/** * Form utilities for handling form submissions with progress tracking */ class FormHandler { constructor(formId, options = {}) { this.form = document.getElementById(formId); this.options = { progressModalId: "progressModal", progressBarId: "progressBar", progressStatusId: "progressStatus", statusCheckInterval: 1000, onSuccess: null, onError: null, onProgress: null, ...options, }; this.progressModal = null; this.progressBar = null; this.progressStatus = null; this.submitButton = null; this.initElements(); this.initEventListeners(); } /** * Initialize DOM elements */ initElements() { if (this.options.progressModalId) { const modalElement = document.getElementById( this.options.progressModalId ); if (modalElement && typeof bootstrap !== "undefined") { this.progressModal = new bootstrap.Modal(modalElement); } } this.progressBar = document.getElementById(this.options.progressBarId); this.progressStatus = document.getElementById( this.options.progressStatusId ); this.submitButton = this.form?.querySelector('button[type="submit"]'); } /** * Initialize event listeners */ initEventListeners() { if (this.form) { this.form.addEventListener("submit", (e) => this.handleSubmit(e)); } } /** * Handle form submission * @param {Event} e - Form submit event */ async handleSubmit(e) { e.preventDefault(); // Show progress modal this.showProgress(); this.updateProgress(5, "Starting..."); // Disable submit button if (this.submitButton) { this.submitButton.disabled = true; } const formData = new FormData(this.form); try { const response = await fetch(this.form.action, { method: "POST", body: formData, }); const data = await response.json(); if (data.error) { this.handleError(data.error); return; } // Start polling for task status if task_id is provided if (data.task_id) { this.pollTaskStatus(data.task_id); } else { // Handle immediate response this.handleSuccess(data); } } catch (error) { console.error("Form submission failed:", error); this.handleError("Form submission failed. Please try again."); } } /** * Poll task status for long-running operations * @param {string} taskId - Task ID to poll */ async pollTaskStatus(taskId) { const checkStatus = async () => { try { // Construct status URL - this should be customizable const statusUrl = this.options.statusUrlTemplate ? this.options.statusUrlTemplate.replace("{taskId}", taskId) : `/upload/task_status/${taskId}`; const response = await fetch(statusUrl); const status = await response.json(); console.log("Task status:", status); if (status.state === "SUCCESS") { this.updateProgress(100, "Completed!"); setTimeout(() => { this.hideProgress(); this.handleSuccess(status.result); }, 1000); } else if (status.state === "FAILURE") { this.updateProgress(100, "Failed!", true); setTimeout(() => { this.hideProgress(); this.handleError(status.error || "Unknown error occurred"); }, 1000); } else { // Update progress const progress = status.progress || 0; this.updateProgress(progress, `Processing... (${status.state})`); // Continue polling setTimeout(checkStatus, this.options.statusCheckInterval); } } catch (error) { console.error("Failed to check task status:", error); // Continue polling on error setTimeout(checkStatus, this.options.statusCheckInterval); } }; checkStatus(); } /** * Show progress modal */ showProgress() { if (this.progressModal) { this.progressModal.show(); } } /** * Hide progress modal */ hideProgress() { if (this.progressModal) { this.progressModal.hide(); } } /** * Update progress display * @param {number} percentage - Progress percentage (0-100) * @param {string} message - Status message * @param {boolean} isError - Whether this is an error state */ updateProgress(percentage, message, isError = false) { if (this.progressBar) { this.progressBar.style.width = `${percentage}%`; this.progressBar.textContent = `${percentage}%`; if (isError) { this.progressBar.classList.add("bg-danger"); } } if (this.progressStatus) { this.progressStatus.textContent = message; } // Call custom progress callback if (this.options.onProgress) { this.options.onProgress(percentage, message, isError); } } /** * Handle successful form submission * @param {object} result - Success result data */ handleSuccess(result) { // Re-enable submit button if (this.submitButton) { this.submitButton.disabled = false; } // Call custom success callback if (this.options.onSuccess) { this.options.onSuccess(result); } else { // Default success handling showFlashMessage("Operation completed successfully!", "success"); } } /** * Handle form submission error * @param {string} error - Error message */ handleError(error) { this.hideProgress(); // Re-enable submit button if (this.submitButton) { this.submitButton.disabled = false; } // Call custom error callback if (this.options.onError) { this.options.onError(error); } else { // Default error handling showFlashMessage(`Error: ${error}`, "error"); } } }