var Forms = {};
Forms.autoInit = () => {
for (let propertyName in Forms) {
let property = Forms[propertyName];
// Call autoInit of all properties that are subclasses of Forms.BaseForm.
// This does not include Forms.BaseForm itself.
if (property.prototype instanceof Forms.BaseForm) {
// Check if the static htmlClass property is defined.
if (property.htmlClass === undefined) {return;}
// Gather all HTML elements that have the `this.htmlClass` class
// and do not have the no-autoinit class.
let formElements = document.querySelectorAll(`.${property.htmlClass}:not(.no-autoinit)`);
// Create an instance of this class for each form element.
for (let formElement of formElements) {new property(formElement);}
}
}
};
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 = Utils.HTMLToElement(
`
`
);
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 = Utils.HTMLToElement(
'Please select an option.'
);
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 = Utils.HTMLToElement(
`${inputError}`
);
inputFieldElement.appendChild(errorHelperTextElement);
}
}
}
if (request.status === 500) {
app.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;
}
}
}
};