2023-11-09 13:29:01 +00:00
|
|
|
nopaque.forms.BaseForm = class BaseForm {
|
2023-10-10 09:06:44 +00:00
|
|
|
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();
|
2023-11-09 13:29:01 +00:00
|
|
|
let modalElement = nopaque.Utils.HTMLToElement(
|
2023-10-10 09:06:44 +00:00
|
|
|
`
|
|
|
|
<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');
|
2023-11-09 13:29:01 +00:00
|
|
|
let errorHelperTextElement = nopaque.Utils.HTMLToElement(
|
2023-10-10 09:06:44 +00:00
|
|
|
'<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) {
|
2023-11-09 13:29:01 +00:00
|
|
|
let errorHelperTextElement = nopaque.Utils.HTMLToElement(
|
2023-10-10 09:06:44 +00:00
|
|
|
`<span class="helper-text error-color-text" data-helper-type="error">${inputError}</span>`
|
|
|
|
);
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|