feat(async-table): no submit on locked inputs

This commit is contained in:
Sarah Vaupel 2019-11-19 17:43:41 +01:00 committed by Gregor Kleen
parent eeabdb1b2b
commit 22b3780efd
2 changed files with 70 additions and 31 deletions

View File

@ -6,6 +6,8 @@ import * as debounce from 'lodash.debounce';
import './async-table-filter.sass'; import './async-table-filter.sass';
import './async-table.sass'; import './async-table.sass';
const ATTR_SUBMIT_LOCKED = 'submit-locked';
const INPUT_DEBOUNCE = 600; const INPUT_DEBOUNCE = 600;
const HEADER_HEIGHT = 80; const HEADER_HEIGHT = 80;
@ -153,21 +155,18 @@ export class AsyncTable {
} }
_gatherTableFilterInputs(tableFilterForm) { _gatherTableFilterInputs(tableFilterForm) {
Array.from(tableFilterForm.querySelectorAll('input[type="search"]')).forEach((input) => { Array.from(tableFilterForm.querySelectorAll('input')).forEach((input) => {
this._tableFilterInputs.search.push(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) => { Array.from(tableFilterForm.querySelectorAll('select')).forEach((input) => this._tableFilterInputs.select.push(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);
});
} }
_addTableFilterEventListeners(tableFilterForm) { _addTableFilterEventListeners(tableFilterForm) {
@ -186,7 +185,8 @@ export class AsyncTable {
this._tableFilterInputs.input.forEach((input) => { this._tableFilterInputs.input.forEach((input) => {
const debouncedInput = debounce(() => { 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); this._updateFromTableFilter(tableFilterForm);
} }
}, INPUT_DEBOUNCE); }, INPUT_DEBOUNCE);

View File

@ -6,6 +6,10 @@ import moment from 'moment';
const KEYCODE_ESCAPE = 27; const KEYCODE_ESCAPE = 27;
const Z_INDEX_MODAL = 9999; 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 // INTERNAL (Uni2work specific) formats for formatting dates and/or times
const FORM_DATE_FORMAT = { const FORM_DATE_FORMAT = {
'date': moment.HTML5_FMT.DATE, '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}`, '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_UTIL_SELECTOR = 'input[type="date"], input[type="time"], input[type="datetime-local"]';
const DATEPICKER_INITIALIZED_CLASS = 'datepicker--initialized'; const DATEPICKER_INITIALIZED_CLASS = 'datepicker--initialized';
const DATEPICKER_OPEN_CLASS = 'calendar-open';
const DATEPICKER_CONFIG = { const DATEPICKER_CONFIG = {
'global': { 'global': {
@ -163,6 +156,21 @@ export class Datepicker {
// mark the form input element as initialized // mark the form input element as initialized
this._element.classList.add(DATEPICKER_INITIALIZED_CLASS); 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 = () => { const setDatepickerDate = () => {
// try to parse the current input element value with fancy and internal format string // 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]); 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 focussedIsNotElement = event.relatedTarget !== this._element;
const focussedIsInDocument = window.document.contains(event.relatedTarget); const focussedIsInDocument = window.document.contains(event.relatedTarget);
if (hasFocus && focussedIsNotTimepicker && focussedIsNotElement && focussedIsInDocument) 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) // 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 targetIsInDocument = window.document.contains(event.target);
const targetIsNotElement = event.target !== this._element; const targetIsNotElement = event.target !== this._element;
if (targetIsOutside && targetIsInDocument && targetIsNotElement) if (targetIsOutside && targetIsInDocument && targetIsNotElement)
this.datepickerInstance.close(); this.closeDatepickerInstance();
}); });
// close the instance on escape keydown events // close the instance on escape keydown events
this._element.addEventListener('keydown', event => { this._element.addEventListener('keydown', event => {
if (event.keyCode === KEYCODE_ESCAPE) { if (event.keyCode === KEYCODE_ESCAPE) {
this.datepickerInstance.close(); this.closeDatepickerInstance();
} }
}); });
@ -216,6 +224,24 @@ export class Datepicker {
this.datepickerInstance.remove(); 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 * 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. * @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. * 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 * @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; 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;
}