nopaque/app/static/js/forms/base-form.js
2024-12-02 09:34:17 +01:00

139 lines
4.6 KiB
JavaScript

nopaque.forms.BaseForm = class BaseForm {
static htmlClass;
constructor(formElement) {
this.formElement = formElement;
this.eventListeners = {
'requestLoad': []
};
this.afterRequestListeners = [];
for (let selectElement of this.formElement.querySelectorAll('select')) {
selectElement.removeAttribute('required');
}
this.formElement.addEventListener('submit', (event) => {
event.preventDefault();
this.submit(event);
});
}
addEventListener(eventType, listener) {
if (eventType in this.eventListeners) {
this.eventListeners[eventType].push(listener);
} else {
throw `Unknown event type ${eventType}`;
}
}
submit(event) {
let request = new XMLHttpRequest();
let modalElement = nopaque.Utils.HTMLToElement(
`
<div class="modal">
<div class="modal-content">
<h4><i class="material-icons left">file_upload</i>Submitting...</h4>
<div class="progress">
<div class="determinate" style="width: 0%"></div>
</div>
</div>
<div class="modal-footer">
<a class="action-button btn red waves-effect waves-light modal-close" data-action="cancel">Cancel</a>
</div>
</div>
`
);
document.querySelector('#modals').appendChild(modalElement);
let modal = M.Modal.init(
modalElement,
{
dismissible: false,
onCloseEnd: () => {
modal.destroy();
modalElement.remove();
}
}
);
modal.open();
// Remove all previous helper text elements that indicate errors
let errorHelperTextElements = this.formElement
.querySelectorAll('.helper-text[data-helper-text-type="error"]');
for (let errorHelperTextElement of errorHelperTextElements) {
errorHelperTextElement.remove();
}
// Check if select elements are filled out properly
for (let selectElement of this.formElement.querySelectorAll('select')) {
if (selectElement.value === '') {
let inputFieldElement = selectElement.closest('.input-field');
let errorHelperTextElement = nopaque.Utils.HTMLToElement(
'<span class="helper-text error-color-text" data-helper-text-type="error">Please select an option.</span>'
);
inputFieldElement.appendChild(errorHelperTextElement);
inputFieldElement.querySelector('.select-dropdown').classList.add('invalid');
modal.close();
return;
}
}
// Setup abort handling
let cancelElement = modalElement.querySelector('.action-button[data-action="cancel"]');
cancelElement.addEventListener('click', (event) => {request.abort();});
// Setup load handling (after the request completed)
request.addEventListener('load', (event) => {
for (let listener of this.eventListeners['requestLoad']) {
listener(event);
}
if (request.status === 400) {
let responseJson = JSON.parse(request.responseText);
for (let [inputName, inputErrors] of Object.entries(responseJson.errors)) {
let inputFieldElement = this.formElement
.querySelector(`input[name$="${inputName}"], select[name$="${inputName}"]`)
.closest('.input-field');
for (let inputError of inputErrors) {
let errorHelperTextElement = nopaque.Utils.HTMLToElement(
`<span class="helper-text error-color-text" data-helper-type="error">${inputError}</span>`
);
inputFieldElement.appendChild(errorHelperTextElement);
}
}
}
if (request.status === 500) {
app.ui.flash('Internal Server Error', 'error');
}
modal.close();
});
// Setup progress handling
let progressBarElement = modalElement.querySelector('.progress > .determinate');
request.upload.addEventListener('progress', (event) => {
let progress = Math.floor(100 * event.loaded / event.total);
progressBarElement.style.width = `${progress}%`;
});
request.open(this.formElement.method, this.formElement.action);
request.setRequestHeader('Accept', 'application/json');
let formData = new FormData(this.formElement);
switch (this.formElement.enctype) {
case 'application/x-www-form-urlencoded': {
let urlSearchParams = new URLSearchParams(formData);
request.send(urlSearchParams);
break;
}
case 'multipart/form-data': {
request.send(formData);
break;
}
case 'text/plain': {
throw 'enctype "text/plain" is not supported';
break;
}
default: {
break;
}
}
}
};