From 22b3780efdabdc572d6c5a08b282bb65448bf3f1 Mon Sep 17 00:00:00 2001 From: Sarah Vaupel Date: Tue, 19 Nov 2019 17:43:41 +0100 Subject: [PATCH] feat(async-table): no submit on locked inputs --- frontend/src/utils/async-table/async-table.js | 28 +++---- frontend/src/utils/form/datepicker.js | 73 ++++++++++++++----- 2 files changed, 70 insertions(+), 31 deletions(-) diff --git a/frontend/src/utils/async-table/async-table.js b/frontend/src/utils/async-table/async-table.js index f2d1248ab..705edb656 100644 --- a/frontend/src/utils/async-table/async-table.js +++ b/frontend/src/utils/async-table/async-table.js @@ -6,6 +6,8 @@ import * as debounce from 'lodash.debounce'; import './async-table-filter.sass'; import './async-table.sass'; +const ATTR_SUBMIT_LOCKED = 'submit-locked'; + const INPUT_DEBOUNCE = 600; const HEADER_HEIGHT = 80; @@ -153,21 +155,18 @@ export class AsyncTable { } _gatherTableFilterInputs(tableFilterForm) { - Array.from(tableFilterForm.querySelectorAll('input[type="search"]')).forEach((input) => { - this._tableFilterInputs.search.push(input); + Array.from(tableFilterForm.querySelectorAll('input')).forEach((input) => { + const inputType = input.getAttribute('type'); + if (inputType === 'search') { + this._tableFilterInputs.search.push(input); + } else if (['text','date','time','datetime-local'].includes(inputType)) { + this._tableFilterInputs.input.push(input); + } else { + this._tableFilterInputs.change.push(input); + } }); - Array.from(tableFilterForm.querySelectorAll('input[type="text"]')).forEach((input) => { - this._tableFilterInputs.input.push(input); - }); - - Array.from(tableFilterForm.querySelectorAll('input:not([type="text"]):not([type="search"])')).forEach((input) => { - this._tableFilterInputs.change.push(input); - }); - - Array.from(tableFilterForm.querySelectorAll('select')).forEach((input) => { - this._tableFilterInputs.select.push(input); - }); + Array.from(tableFilterForm.querySelectorAll('select')).forEach((input) => this._tableFilterInputs.select.push(input)); } _addTableFilterEventListeners(tableFilterForm) { @@ -186,7 +185,8 @@ export class AsyncTable { this._tableFilterInputs.input.forEach((input) => { const debouncedInput = debounce(() => { - if (input.value.length === 0 || input.value.length > 2) { + const submitLocked = input.getAttribute(ATTR_SUBMIT_LOCKED); + if ((submitLocked === 'false' || submitLocked === null) && (input.value.length === 0 || input.value.length > 2)) { this._updateFromTableFilter(tableFilterForm); } }, INPUT_DEBOUNCE); diff --git a/frontend/src/utils/form/datepicker.js b/frontend/src/utils/form/datepicker.js index c4da9d381..f91ef3421 100644 --- a/frontend/src/utils/form/datepicker.js +++ b/frontend/src/utils/form/datepicker.js @@ -6,6 +6,10 @@ import moment from 'moment'; const KEYCODE_ESCAPE = 27; const Z_INDEX_MODAL = 9999; +// should be the same as ATTR_SUBMIT_LOCKED in async-table util +// TODO move to global config +const ATTR_DATEPICKER_OPEN = 'submit-locked'; + // INTERNAL (Uni2work specific) formats for formatting dates and/or times const FORM_DATE_FORMAT = { 'date': moment.HTML5_FMT.DATE, @@ -26,21 +30,10 @@ const FORM_DATE_FORMAT_MOMENT = { 'datetime-local': `${FORM_DATE_FORMAT_DATE_MOMENT} ${FORM_DATE_FORMAT_TIME_MOMENT}`, }; -/** - * Takes a string representation of a date, an input ('previous') format and a desired output format and returns a reformatted date string. - * If the date string is not valid (i.e. cannot be parsed with the given input format string), returns the original date string; - * @param {*} dateStr string representation of a date (needs to be in format formatIn) - * @param {*} formatIn input format string - * @param {*} formatOut format string of the desired output date string - */ -function reformatDateString(dateStr, formatIn, formatOut) { - const parsedMomentDate = moment(dateStr, [formatIn, formatOut]); - return parsedMomentDate.isValid() ? parsedMomentDate.format(formatOut) : dateStr; -} - const DATEPICKER_UTIL_SELECTOR = 'input[type="date"], input[type="time"], input[type="datetime-local"]'; const DATEPICKER_INITIALIZED_CLASS = 'datepicker--initialized'; +const DATEPICKER_OPEN_CLASS = 'calendar-open'; const DATEPICKER_CONFIG = { 'global': { @@ -163,6 +156,21 @@ export class Datepicker { // mark the form input element as initialized this._element.classList.add(DATEPICKER_INITIALIZED_CLASS); + // create a mutation observer that observes the datepicker instance class and sets + // the datepicker-open DOM attribute of the input element if the datepicker has been opened + const datepickerInstanceObserver = new MutationObserver((mutations) => { + mutations.forEach(mutation => { + if (!mutation.oldValue.includes(DATEPICKER_OPEN_CLASS) && this.datepickerInstance.dt.getAttribute('class').includes(DATEPICKER_OPEN_CLASS)) { + this._element.setAttribute(ATTR_DATEPICKER_OPEN, true); + } + }); + }); + datepickerInstanceObserver.observe(this.datepickerInstance.dt, { + attributes: true, + attributeFilter: ['class'], + attributeOldValue: true, + }); + const setDatepickerDate = () => { // try to parse the current input element value with fancy and internal format string const parsedMomentDate = moment(this._element.value, FORM_DATE_FORMAT_MOMENT[this.elementType]); @@ -188,7 +196,7 @@ export class Datepicker { const focussedIsNotElement = event.relatedTarget !== this._element; const focussedIsInDocument = window.document.contains(event.relatedTarget); if (hasFocus && focussedIsNotTimepicker && focussedIsNotElement && focussedIsInDocument) - this.datepickerInstance.close(); + this.closeDatepickerInstance(); }); // close the instance on click on any element outside of the datepicker (except the input element itself) @@ -198,13 +206,13 @@ export class Datepicker { const targetIsInDocument = window.document.contains(event.target); const targetIsNotElement = event.target !== this._element; if (targetIsOutside && targetIsInDocument && targetIsNotElement) - this.datepickerInstance.close(); + this.closeDatepickerInstance(); }); // close the instance on escape keydown events this._element.addEventListener('keydown', event => { if (event.keyCode === KEYCODE_ESCAPE) { - this.datepickerInstance.close(); + this.closeDatepickerInstance(); } }); @@ -216,6 +224,24 @@ export class Datepicker { this.datepickerInstance.remove(); } + + // DATEPICKER INSTANCE CONTROL + + /** + * Closes the datepicker instance, releasing the lock on the input element. + */ + closeDatepickerInstance() { + if (!this._element.datepicker-open) { + throw new Error('Cannot close already closed datepicker instance!'); + } + + this._element.setAttribute(ATTR_DATEPICKER_OPEN, false); + this.datepickerInstance.close(); + } + + + // FORMAT METHODS + /** * Formats the value of this input element from datepicker format (i.e. DATEPICKER_CONFIG.dateFormat + " " + datetime.defaults.timeFormat) to Uni2work internal date format (i.e. FORM_DATE_FORMAT) required for form submission * @param {*} toFancy optional target format switch (boolean value; default is false). If set to a truthy value, formats the element value to fancy instead of internal date format. @@ -226,8 +252,6 @@ export class Datepicker { } } - - /** * Returns a datestring in internal format from the current state of the input element value. * @param {*} toFancy Format date from internal to fancy or vice versa. When omitted, toFancy is falsy and results in fancy -> internal @@ -265,3 +289,18 @@ export class Datepicker { return formData; } } + + +// HELPER FUNCTIONS + +/** + * Takes a string representation of a date, an input ('previous') format and a desired output format and returns a reformatted date string. + * If the date string is not valid (i.e. cannot be parsed with the given input format string), returns the original date string; + * @param {*} dateStr string representation of a date (needs to be in format formatIn) + * @param {*} formatIn input format string + * @param {*} formatOut format string of the desired output date string + */ +function reformatDateString(dateStr, formatIn, formatOut) { + const parsedMomentDate = moment(dateStr, [formatIn, formatOut]); + return parsedMomentDate.isValid() ? parsedMomentDate.format(formatOut) : dateStr; +}