This repository has been archived on 2024-10-24. You can view files and clone it, but cannot push or open issues or pull requests.
fradrive-old/frontend/src/utils/mass-input/mass-input.js
2020-04-19 18:20:40 +02:00

268 lines
8.4 KiB
JavaScript

/* global global:writable */
import { Utility } from '../../core/utility';
import { Datepicker } from '../form/datepicker';
import './mass-input.sass';
const MASS_INPUT_CELL_SELECTOR = '.massinput__cell';
const MASS_INPUT_ADD_CELL_SELECTOR = '.massinput__cell--add';
const MASS_INPUT_SUBMIT_BUTTON_CLASS = 'massinput__submit-button';
const MASS_INPUT_INITIALIZED_CLASS = 'mass-input--initialized';
const MASS_INPUT_ADD_CHANGE_FIELD_SELECTOR = 'select, input[type=radio]';
// const MASS_INPUT_SAFETY_SUBMITTED_CLASS = 'massinput--safety-submitted';
// const MASS_INPUT_SAFETY_SUBMITTED_TIMEOUT = 1000;
@Utility({
selector: '[uw-mass-input]',
})
export class MassInput {
_element;
_app;
_global;
_massInputId;
_massInputFormSubmitHandler;
_massInputForm;
_changedAdd = new Array();
constructor(element, app) {
if (!element) {
throw new Error('Mass Input utility cannot be setup without an element!');
}
this._element = element;
this._app = app;
if (global !== undefined)
this._global = global;
else if (window !== undefined)
this._global = window;
else
throw new Error('Cannot setup Mass Input utility without window or global');
if (this._element.classList.contains(MASS_INPUT_INITIALIZED_CLASS)) {
return false;
}
this._massInputId = this._element.dataset.massInputIdent || '_';
this._massInputForm = this._element.closest('form');
if (!this._massInputForm) {
throw new Error('Mass Input utility cannot be setup without being wrapped in a <form>!');
}
this._massInputFormSubmitHandler = this._makeSubmitHandler();
// setup submit buttons inside this massinput so browser
// uses correct submit button for form submission.
const buttons = this._getMassInputSubmitButtons();
buttons.forEach((button) => {
this._setupSubmitButton(button);
});
this._massInputForm.addEventListener('submit', this._massInputFormSubmitHandler.bind(this));
this._massInputForm.addEventListener('keypress', this._keypressHandler.bind(this));
Array.from(this._element.querySelectorAll(MASS_INPUT_ADD_CELL_SELECTOR)).forEach(this._setupChangedHandlers.bind(this));
// mark initialized
this._element.classList.add(MASS_INPUT_INITIALIZED_CLASS);
}
destroy() {
this._reset();
}
_setupChangedHandlers(addCell) {
Array.from(addCell.querySelectorAll(MASS_INPUT_ADD_CHANGE_FIELD_SELECTOR)).forEach(inputElem => {
if (inputElem.closest('[uw-mass-input]') !== this._element)
return;
inputElem.addEventListener('change', () => { this._changedAdd.push(addCell); });
});
}
_unsafeAddCells() {
let changedAdd = this._changedAdd;
Array.from(this._element.querySelectorAll(MASS_INPUT_ADD_CELL_SELECTOR)).forEach(addCell => addCell.querySelectorAll('input:not([type=checkbox]):not([type=radio])').forEach(inputElem => {
if (inputElem.closest('[uw-mass-input]') === this._element && inputElem.value !== '' && (inputElem.defaultValue || inputElem.getAttribute('value')) !== inputElem.value)
changedAdd.push(addCell);
}));
return changedAdd;
}
_makeSubmitHandler() {
const method = this._massInputForm.getAttribute('method') || 'POST';
const url = this._massInputForm.getAttribute('action') || window.location.href;
const enctype = this._massInputForm.getAttribute('enctype') || 'application/json';
let requestFn;
if (this._app.httpClient[method.toLowerCase()]) {
requestFn = this._app.httpClient[method.toLowerCase()].bind(this._app.httpClient);
}
return (event) => {
let submitButton;
let isAddCell;
let isMassInputSubmit = (() => {
let activeElement;
// check if event occured from either a mass input add/delete button or
// from inside one of massinput's inputs (i.e. a child is focused/active)
activeElement = this._element.querySelector(':focus, :active');
if (!activeElement) {
return false;
}
// find the according massinput cell thats hosts the element that triggered the submit
const massInputCell = activeElement.closest(MASS_INPUT_CELL_SELECTOR);
if (!massInputCell) {
return false;
}
submitButton = massInputCell.querySelector('.' + MASS_INPUT_SUBMIT_BUTTON_CLASS);
if (!submitButton) {
return false;
}
isAddCell = massInputCell.matches(MASS_INPUT_ADD_CELL_SELECTOR);
const submitButtonIsActive = submitButton.matches(':focus, :active');
// if the cell is not an add cell the active element must at least be the cells submit button
if (!isAddCell && !submitButtonIsActive) {
return false;
}
return true;
})();
let unsafeAddCells = this._unsafeAddCells();
if (unsafeAddCells.length > 0 && !isMassInputSubmit) {
let addButtons = Array.from(unsafeAddCells[0].querySelectorAll('.' + MASS_INPUT_SUBMIT_BUTTON_CLASS)).filter(addButton => addButton.closest('[uw-mass-input]') === this._element);
if (addButtons.length > 0) {
submitButton = addButtons[0];
isMassInputSubmit = true;
isAddCell = false;
this._element.scrollIntoView();
// this._element.classList.add(MASS_INPUT_SAFETY_SUBMITTED_CLASS);
// this._global.setTimeout(() => { this._element.classList.remove(MASS_INPUT_SAFETY_SUBMITTED_CLASS) }, MASS_INPUT_SAFETY_SUBMITTED_TIMEOUT)
}
}
if (!isMassInputSubmit) {
return false;
}
event.preventDefault();
const requestBody = this._serializeForm(submitButton, enctype);
if (requestFn && requestBody) {
const headers = {'Mass-Input-Shortcircuit': this._massInputId};
if (enctype !== 'multipart/form-data') {
headers['Content-Type'] = enctype;
}
requestFn({
url: url,
headers: headers,
body: requestBody,
}).then((response) => {
return this._app.htmlHelpers.parseResponse(response);
}).then((response) => {
this._processResponse(response.element);
if (isAddCell) {
this._reFocusAddCell();
}
});
}
};
}
_keypressHandler = (event) => {
if (event.keyCode !== 13) {
return false;
}
if (this._massInputFormSubmitHandler) {
return this._massInputFormSubmitHandler(event);
}
}
_getMassInputSubmitButtons() {
return Array.from(this._element.querySelectorAll('button[type="submit"][name][value], .' + MASS_INPUT_SUBMIT_BUTTON_CLASS));
}
_setupSubmitButton(button) {
button.setAttribute('type', 'button');
button.classList.add(MASS_INPUT_SUBMIT_BUTTON_CLASS);
button.addEventListener('click', this._massInputFormSubmitHandler);
}
_resetSubmitButton(button) {
button.setAttribute('type', 'submit');
button.classList.remove(MASS_INPUT_SUBMIT_BUTTON_CLASS);
button.removeEventListener('click', this._massInputFormSubmitHandler);
}
_processResponse(responseElement) {
this._element.innerHTML = '';
this._element.appendChild(responseElement);
this._reset();
this._app.utilRegistry.initAll(this._element);
}
_serializeForm(submitButton, enctype) {
// create new FormData and format any date values
const formData = Datepicker.unformatAll(this._massInputForm, new FormData(this._massInputForm));
// manually add name and value of submit button to formData
formData.append(submitButton.name, submitButton.value);
if (enctype === 'application/x-www-form-urlencoded') {
return new URLSearchParams(formData);
} else if (enctype === 'multipart/form-data') {
return formData;
} else {
throw new Error('Unsupported form enctype: ' + enctype);
}
}
_reFocusAddCell() {
const addCell = this._element.querySelector(MASS_INPUT_ADD_CELL_SELECTOR);
if (!addCell) {
return false;
}
const addCellInput = addCell.querySelector('input:not([type="hidden"])');
if (addCellInput) {
// Clearing of add-inputs is done in the backend
addCellInput.focus();
}
}
_reset() {
this._element.classList.remove(MASS_INPUT_INITIALIZED_CLASS);
this._massInputForm.removeEventListener('submit', this._massInputFormSubmitHandler);
this._massInputForm.removeEventListener('keypress', this._keypressHandler);
const buttons = this._getMassInputSubmitButtons();
buttons.forEach((button) => {
this._resetSubmitButton(button);
});
}
}