232 lines
5.8 KiB
JavaScript

/**
* 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");
}
}
}