feat(util-registry): ensure specific start ordering

Fixes #587
This commit is contained in:
Gregor Kleen 2020-05-18 14:08:58 +02:00
parent 01e61f9bfd
commit baf8b18dc3
4 changed files with 115 additions and 19 deletions

View File

@ -1,9 +1,11 @@
import * as toposort from 'toposort';
const DEBUG_MODE = /localhost/.test(window.location.href) ? 2 : 0;
export class UtilRegistry {
_registeredUtils = [];
_activeUtilInstances = [];
_registeredUtils = new Array();
_activeUtilInstances = new Array();
_appInstance;
/**
@ -50,12 +52,35 @@ export class UtilRegistry {
this._appInstance = appInstance;
}
initAll(scope) {
let startedInstances = [];
initAll(scope = document.body) {
let startedInstances = new Array();
const setupInstances = this._registeredUtils.map((util) => this.setup(util, scope)).flat();
setupInstances.forEach((utilInstance) => {
const orderedInstances = setupInstances.filter(_isStartOrdered);
if (DEBUG_MODE > 3) {
console.log({ setupInstances, orderedInstances });
}
const startDependencies = new Array();
for (const utilInstance of orderedInstances) {
for (const otherInstance of setupInstances) {
const startOrder = _startOrder(utilInstance, otherInstance);
if (typeof startOrder !== 'undefined')
startDependencies.push(startOrder);
}
}
if (DEBUG_MODE > 2) {
console.log('starting instances', { setupInstances, startDependencies, order: toposort.array(setupInstances, startDependencies) });
}
toposort.array(setupInstances, startDependencies).forEach((utilInstance) => {
if (utilInstance) {
if (DEBUG_MODE > 2) {
console.log('starting utilInstance', { util: utilInstance.util.name, utilInstance });
}
const instance = utilInstance.instance;
if (instance && typeof instance.start === 'function') {
instance.start.bind(instance)();
@ -77,7 +102,7 @@ export class UtilRegistry {
console.log('setting up util', { util });
}
let instances = [];
let instances = new Array();
if (util) {
const elements = this._findUtilElements(util, scope);
@ -140,3 +165,58 @@ export class UtilRegistry {
this._activeUtilInstances = this._activeUtilInstances.filter((util) => !!util);
}
}
function _startOrder(utilInstance, otherInstance) {
if (utilInstance.element !== otherInstance.element && !(utilInstance.element.contains(otherInstance.element) || otherInstance.element.contains(utilInstance.element)))
return undefined;
if (utilInstance === otherInstance)
return undefined;
if (!_isStartOrdered(utilInstance) || !otherInstance.instance || !otherInstance.util)
return undefined;
function orderParam(name) {
if (typeof utilInstance.instance[name] === 'function')
return !!utilInstance.instance[name](otherInstance.instance);
if (typeof utilInstance.util[name] === 'function')
return !!utilInstance.util[name](otherInstance.instance);
else if (Array.isArray(utilInstance.instance[name]))
return utilInstance.instance[name].some(constr => otherInstance.util === constr);
else if (Array.isArray(utilInstance.util[name]))
return utilInstance.util[name].some(constr => otherInstance.util === constr);
return false;
}
const after = orderParam('startAfter');
const before = orderParam('startBefore');
if (DEBUG_MODE > 3) {
console.log('compared instances for ordering', { utilInstance, otherInstance }, { after, before });
}
if (after && before) {
console.error({ utilInstance, otherInstance });
throw new Error(`Incompatible start ordering: ${utilInstance.instance.constructor.name} and ${otherInstance.instance.constructor.name}`);
} else if (after)
return [otherInstance, utilInstance];
else if (before)
return [utilInstance, otherInstance];
return undefined;
}
function _isStartOrdered(utilInstance) {
if (!utilInstance || !utilInstance.instance || !utilInstance.util)
return false;
function isOrderParam(name) {
return typeof utilInstance.instance[name] === 'function' ||
typeof utilInstance.util[name] === 'function' ||
Array.isArray(utilInstance.instance[name]) ||
Array.isArray(utilInstance.util[name]);
}
return isOrderParam('startBefore') || isOrderParam('startAfter');
}

View File

@ -2,6 +2,9 @@ import { Utility } from '../../core/utility';
import { AUTO_SUBMIT_BUTTON_UTIL_SELECTOR } from './auto-submit-button';
import { AUTO_SUBMIT_INPUT_UTIL_SELECTOR } from './auto-submit-input';
import { InteractiveFieldset } from './interactive-fieldset';
import { Datepicker } from './datepicker';
/**
* Key generator from an arbitrary number of FormData objects.
* @param {...any} formDatas FormData objects
@ -31,37 +34,50 @@ export class NavigateAwayPrompt {
}
this._element = element;
this._initFormData = new FormData(this._element);
if (this._element.classList.contains(NAVIGATE_AWAY_PROMPT_INITIALIZED_CLASS)) {
return false;
return;
}
// ignore forms that get submitted automatically
if (this._element.querySelector(AUTO_SUBMIT_BUTTON_UTIL_SELECTOR) || this._element.querySelector(AUTO_SUBMIT_INPUT_UTIL_SELECTOR)) {
return false;
return;
}
if (this._element.matches(NAVIGATE_AWAY_PROMPT_UTIL_OPTOUT)) {
return false;
return;
}
window.addEventListener('beforeunload', this._beforeUnloadHandler);
// mark initialized
this._element.classList.add(NAVIGATE_AWAY_PROMPT_INITIALIZED_CLASS);
}
static startAfter = [ Datepicker, InteractiveFieldset ];
start() {
if (!this._isActive())
return;
this._initFormData = new FormData(this._element);
window.addEventListener('beforeunload', this._beforeUnloadHandler.bind(this));
this._element.addEventListener('submit', () => {
this._unloadDueToSubmit = true;
});
// mark initialized
this._element.classList.add(NAVIGATE_AWAY_PROMPT_INITIALIZED_CLASS);
}
destroy() {
window.removeEventListener('beforeunload', this._beforeUnloadHandler);
this._element.classList.remove(NAVIGATE_AWAY_PROMPT_INITIALIZED_CLASS);
}
_beforeUnloadHandler = (event) => {
_isActive() {
return this._element.classList.contains(NAVIGATE_AWAY_PROMPT_INITIALIZED_CLASS);
}
_beforeUnloadHandler(event) {
if (!this._isActive() || !this._initFormData)
return;
// compare every value of the current FormData with every corresponding value of the initial FormData and set formDataHasChanged to true if there is at least one change
const currentFormData = new FormData(this._element);
let formDataHasChanged = false;

3
package-lock.json generated
View File

@ -19553,8 +19553,7 @@
"toposort": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz",
"integrity": "sha1-riF2gXXRVZ1IvvNUILL0li8JwzA=",
"dev": true
"integrity": "sha1-riF2gXXRVZ1IvvNUILL0li8JwzA="
},
"tough-cookie": {
"version": "2.4.3",

View File

@ -125,6 +125,7 @@
"npm": "^6.14.5",
"sodium-javascript": "^0.5.6",
"tail.datetime": "git+ssh://git@gitlab2.rz.ifi.lmu.de/uni2work/tail.DateTime.git#master",
"toposort": "^2.0.2",
"whatwg-fetch": "^3.0.0"
}
}