From 131afd6419fde62261e79b098f15da884cb32011 Mon Sep 17 00:00:00 2001 From: Patrick Jentsch <p.jentsch@uni-bielefeld.de> Date: Fri, 2 Sep 2022 12:56:59 +0200 Subject: [PATCH] New logic for forms including file upload. --- app/static/js/Forms/CreateCorpusFileForm.js | 18 +++ app/static/js/Forms/CreateJobForm.js | 25 ++++ app/static/js/Forms/Form.js | 141 ++++++++++++++++++++ 3 files changed, 184 insertions(+) create mode 100644 app/static/js/Forms/CreateCorpusFileForm.js create mode 100644 app/static/js/Forms/CreateJobForm.js create mode 100644 app/static/js/Forms/Form.js diff --git a/app/static/js/Forms/CreateCorpusFileForm.js b/app/static/js/Forms/CreateCorpusFileForm.js new file mode 100644 index 00000000..ae8dba3b --- /dev/null +++ b/app/static/js/Forms/CreateCorpusFileForm.js @@ -0,0 +1,18 @@ +class CreateCorpusFileForm extends Form { + static autoInit() { + let createCorpusFileFormElements = document.querySelectorAll('.create-corpus-file-form'); + for (let createCorpusFileFormElement of createCorpusFileFormElements) { + new CreateCorpusFileForm(createCorpusFileFormElement); + } + } + + constructor(formElement) { + super(formElement); + + this.addEventListener('requestLoad', (event) => { + if (event.target.status === 201) { + window.location.href = event.target.getResponseHeader('Location'); + } + }); + } +} diff --git a/app/static/js/Forms/CreateJobForm.js b/app/static/js/Forms/CreateJobForm.js new file mode 100644 index 00000000..6aa2d1b6 --- /dev/null +++ b/app/static/js/Forms/CreateJobForm.js @@ -0,0 +1,25 @@ +class CreateJobForm extends Form { + static autoInit() { + let createJobFormElements = document.querySelectorAll('.create-job-form'); + for (let createJobFormElement of createJobFormElements) { + new CreateJobForm(createJobFormElement); + } + } + + constructor(formElement) { + super(formElement); + + let versionField = this.formElement.querySelector('#create-job-form-version'); + versionField.addEventListener('change', (event) => { + let url = new URL(window.location.href); + url.search = `?version=${event.target.value}`; + window.location.href = url.toString(); + }); + + this.addEventListener('requestLoad', (event) => { + if (event.target.status === 201) { + window.location.href = event.target.getResponseHeader('Location'); + } + }); + } +} diff --git a/app/static/js/Forms/Form.js b/app/static/js/Forms/Form.js new file mode 100644 index 00000000..9a21e986 --- /dev/null +++ b/app/static/js/Forms/Form.js @@ -0,0 +1,141 @@ +class Form { + static autoInit() { + CreateCorpusFileForm.autoInit(); + CreateJobForm.autoInit(); + } + + 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.elementFromString( + ` + <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 = Utils.elementFromString( + '<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); + console.log(responseJson); + 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.elementFromString( + `<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; + } + } + } +} -- GitLab