feat(frontend): split up util registry
split up setup of utils (into (DOM) setup and (event listener) start steps); moved event listener registration of datepicker and async-table util to start method(s); small diverse fixes and refactoring. FIXME: enter in datepicker inputs still cause HTTP request loop
This commit is contained in:
parent
bfd35dbc5c
commit
67e472fa5e
@ -19,6 +19,8 @@ export class UtilRegistry {
|
||||
* element: HTMLElement | element the util is applied to
|
||||
* destroy: Function | function to destroy the util and remove any listeners
|
||||
*
|
||||
* (optional) start function for registering event listeners
|
||||
*
|
||||
* @param util Object Utility that should be added to the registry
|
||||
*/
|
||||
register(util) {
|
||||
@ -52,6 +54,7 @@ export class UtilRegistry {
|
||||
}
|
||||
|
||||
this._registeredUtils.forEach((util) => this.setup(util, scope));
|
||||
this._activeUtilInstances.forEach((instance) => typeof instance.start === 'function' && instance.start());
|
||||
}
|
||||
|
||||
setup(util, scope = document.body) {
|
||||
|
||||
@ -81,9 +81,6 @@ export class AsyncTable {
|
||||
throw new Error('Async Table cannot be set up without a scrolltable element!');
|
||||
}
|
||||
|
||||
this._setupSortableHeaders();
|
||||
this._setupPagination();
|
||||
this._setupPageSizeSelect();
|
||||
this._setupTableFilter();
|
||||
|
||||
this._processStorage();
|
||||
@ -95,11 +92,18 @@ export class AsyncTable {
|
||||
this._element.classList.add(ASYNC_TABLE_INITIALIZED_CLASS);
|
||||
}
|
||||
|
||||
start() {
|
||||
this._startSortableHeaders();
|
||||
this._startPagination();
|
||||
this._startPageSizeSelect();
|
||||
this._startTableFilter();
|
||||
}
|
||||
|
||||
destroy() {
|
||||
console.log('TBD: Destroy AsyncTable');
|
||||
}
|
||||
|
||||
_setupSortableHeaders() {
|
||||
_startSortableHeaders() {
|
||||
this._ths = Array.from(this._scrollTable.querySelectorAll('th.sortable, .course-header'))
|
||||
.map((th) => ({ element: th }));
|
||||
|
||||
@ -112,7 +116,7 @@ export class AsyncTable {
|
||||
});
|
||||
}
|
||||
|
||||
_setupPagination() {
|
||||
_startPagination() {
|
||||
const pagination = this._element.querySelector('#' + this._cssIdPrefix + this._asyncTableId + '-pagination');
|
||||
if (pagination) {
|
||||
this._pageLinks = Array.from(pagination.querySelectorAll('.page-link'))
|
||||
@ -136,7 +140,7 @@ export class AsyncTable {
|
||||
}
|
||||
}
|
||||
|
||||
_setupPageSizeSelect() {
|
||||
_startPageSizeSelect() {
|
||||
// pagesize form
|
||||
this._pagesizeForm = this._element.querySelector('#' + this._cssIdPrefix + this._asyncTableId + '-pagesize-form');
|
||||
|
||||
@ -150,6 +154,12 @@ export class AsyncTable {
|
||||
const tableFilterForm = this._element.querySelector(ASYNC_TABLE_FILTER_FORM_SELECTOR);
|
||||
if (tableFilterForm) {
|
||||
this._gatherTableFilterInputs(tableFilterForm);
|
||||
}
|
||||
}
|
||||
|
||||
_startTableFilter() {
|
||||
const tableFilterForm = this._element.querySelector(ASYNC_TABLE_FILTER_FORM_SELECTOR);
|
||||
if (tableFilterForm) {
|
||||
this._addTableFilterEventListeners(tableFilterForm);
|
||||
}
|
||||
}
|
||||
@ -170,20 +180,7 @@ export class AsyncTable {
|
||||
}
|
||||
|
||||
_addTableFilterEventListeners(tableFilterForm) {
|
||||
this._tableFilterInputs.search.forEach((input) => {
|
||||
const debouncedInput = debounce(() => {
|
||||
if (input.value.length === 0 || input.value.length > 2) {
|
||||
this._updateFromTableFilter(tableFilterForm);
|
||||
}
|
||||
}, INPUT_DEBOUNCE);
|
||||
input.addEventListener('input', debouncedInput);
|
||||
input.addEventListener('input', () => {
|
||||
// set flag to ignore any currently pending requests (not debounced)
|
||||
this._ignoreRequest = true;
|
||||
});
|
||||
});
|
||||
|
||||
this._tableFilterInputs.input.forEach((input) => {
|
||||
[...this._tableFilterInputs.search, ...this._tableFilterInputs.input].forEach((input) => {
|
||||
input.submitLockObserver = new MutationObserver((mutations, observer) => {
|
||||
for (const mutation of mutations) {
|
||||
// if the submit lock has been released, trigger an update and disconnect this observer
|
||||
@ -196,7 +193,8 @@ export class AsyncTable {
|
||||
});
|
||||
|
||||
const debouncedInput = debounce(() => {
|
||||
const submitLocked = input.getAttribute(ATTR_SUBMIT_LOCKED) === 'true';
|
||||
const submitLockedAttr = input.getAttribute(ATTR_SUBMIT_LOCKED);
|
||||
const submitLocked = submitLockedAttr === 'true' || submitLockedAttr === null;
|
||||
if (!submitLocked && (input.value.length === 0 || input.value.length > 2)) {
|
||||
this._updateFromTableFilter(tableFilterForm);
|
||||
} else if (submitLocked) {
|
||||
@ -208,6 +206,7 @@ export class AsyncTable {
|
||||
});
|
||||
}
|
||||
}, INPUT_DEBOUNCE);
|
||||
|
||||
input.addEventListener('input', debouncedInput);
|
||||
input.addEventListener('input', () => {
|
||||
// set flag to ignore any currently pending requests (not debounced)
|
||||
@ -262,7 +261,7 @@ export class AsyncTable {
|
||||
const url = new URL(this._storageManager.load('currentTableUrl') || window.location.href);
|
||||
|
||||
// create new FormData and format any date values
|
||||
const formData = Datepicker.unformatAll(this._massInputForm, new FormData(tableFilterForm));
|
||||
const formData = Datepicker.unformatAll(tableFilterForm, new FormData(tableFilterForm));
|
||||
|
||||
for (var k of url.searchParams.keys()) {
|
||||
url.searchParams.delete(k);
|
||||
|
||||
@ -100,13 +100,13 @@ export class Datepicker {
|
||||
// store the previously set type to select the input format
|
||||
this.elementType = this._element.getAttribute('type');
|
||||
|
||||
// manually set the type attribute to text because datepicker handles displaying the date
|
||||
this._element.setAttribute('type', 'text');
|
||||
|
||||
// get all relevant config options for this datepicker type
|
||||
const datepickerGlobalConfig = DATEPICKER_CONFIG['global'];
|
||||
const datepickerConfig = DATEPICKER_CONFIG[this.elementType];
|
||||
|
||||
// manually set the type attribute to text because datepicker handles displaying the date
|
||||
this._element.setAttribute('type', 'text');
|
||||
|
||||
// additional position config (optional data-datepicker-position attribute in html) that can specialize the global config
|
||||
const datepickerPosition = this._element.dataset.datepickerPosition;
|
||||
if (datepickerPosition) {
|
||||
@ -155,23 +155,9 @@ 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) => {
|
||||
for (const mutation of mutations) {
|
||||
if (!mutation.oldValue.includes(DATEPICKER_OPEN_CLASS) && this.datepickerInstance.dt.getAttribute('class').includes(DATEPICKER_OPEN_CLASS)) {
|
||||
this._element.setAttribute(ATTR_DATEPICKER_OPEN, true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
datepickerInstanceObserver.observe(this.datepickerInstance.dt, {
|
||||
attributes: true,
|
||||
attributeFilter: ['class'],
|
||||
attributeOldValue: true,
|
||||
});
|
||||
|
||||
start() {
|
||||
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]);
|
||||
@ -190,6 +176,24 @@ export class Datepicker {
|
||||
// change the selected date in the tail.datetime instance if the value of the input element is changed
|
||||
this._element.addEventListener('change', setDatepickerDate, { once: true });
|
||||
|
||||
|
||||
// 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) => {
|
||||
for (const mutation of mutations) {
|
||||
if (!mutation.oldValue.includes(DATEPICKER_OPEN_CLASS) && this.datepickerInstance.dt.getAttribute('class').includes(DATEPICKER_OPEN_CLASS)) {
|
||||
this._element.setAttribute(ATTR_DATEPICKER_OPEN, true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
datepickerInstanceObserver.observe(this.datepickerInstance.dt, {
|
||||
attributes: true,
|
||||
attributeFilter: ['class'],
|
||||
attributeOldValue: true,
|
||||
});
|
||||
|
||||
|
||||
// close the instance on focusout of any element if another input is focussed that is neither the timepicker nor _element
|
||||
window.addEventListener('focusout', event => {
|
||||
const hasFocus = event.relatedTarget !== null;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user