/* 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
!'); } 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); }); } }