268 lines
8.4 KiB
JavaScript
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);
|
|
});
|
|
}
|
|
}
|