224 lines
6.7 KiB
JavaScript
224 lines
6.7 KiB
JavaScript
import * as toposort from 'toposort';
|
|
|
|
const DEBUG_MODE = /localhost/.test(window.location.href) ? 2 : 0;
|
|
|
|
export class UtilRegistry {
|
|
|
|
_registeredUtils = new Array();
|
|
_activeUtilInstances = new Array();
|
|
_appInstance;
|
|
|
|
/**
|
|
* function registerUtil
|
|
*
|
|
* utils need to have at least these properties:
|
|
* name: string | utils name, e.g. 'example'
|
|
* selector: string | utils selector, e.g. '[uw-example]'
|
|
* setup: Function | utils setup function, see below
|
|
*
|
|
* optional util properties:
|
|
* start: Function | utils start function, see below
|
|
*
|
|
* setup function must return instance object with at least these properties:
|
|
* name: string | utils name
|
|
* 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) {
|
|
if (DEBUG_MODE > 2) {
|
|
console.log('registering util "' + util.name + '"');
|
|
console.log({ util });
|
|
}
|
|
this._registeredUtils.push(util);
|
|
}
|
|
|
|
deregister(name, destroy) {
|
|
const utilIndex = this._findUtilIndex(name);
|
|
|
|
if (utilIndex >= 0) {
|
|
if (destroy === true) {
|
|
this._destroyUtilInstances(name);
|
|
}
|
|
|
|
this._registeredUtils.splice(utilIndex, 1);
|
|
}
|
|
}
|
|
|
|
setApp(appInstance) {
|
|
this._appInstance = appInstance;
|
|
}
|
|
|
|
initAll(scope = document.body) {
|
|
let startedInstances = new Array();
|
|
const setupInstances = this._registeredUtils.map((util) => this.setup(util, scope)).flat();
|
|
|
|
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)();
|
|
startedInstances.push(instance);
|
|
}
|
|
}
|
|
});
|
|
|
|
if (DEBUG_MODE > 1) {
|
|
console.info('initialized js util instances:');
|
|
console.table(setupInstances);
|
|
}
|
|
|
|
return startedInstances;
|
|
}
|
|
|
|
setup(util, scope = document.body) {
|
|
if (DEBUG_MODE > 2) {
|
|
console.log('setting up util', { util });
|
|
}
|
|
|
|
let instances = new Array();
|
|
|
|
if (util) {
|
|
const elements = this._findUtilElements(util, scope);
|
|
|
|
elements.forEach((element) => {
|
|
let utilInstance = null;
|
|
|
|
try {
|
|
utilInstance = new util(element, this._appInstance);
|
|
} catch(err) {
|
|
if (DEBUG_MODE > 0) {
|
|
console.error('Error while trying to initialize a utility!', { util , element, err });
|
|
console.error(err.stack);
|
|
}
|
|
utilInstance = null;
|
|
}
|
|
|
|
if (utilInstance) {
|
|
if (DEBUG_MODE > 2) {
|
|
console.info('Got utility instance for utility "' + util.name + '"', { utilInstance });
|
|
}
|
|
|
|
instances.push({ util: util, scope: scope, element: element, instance: utilInstance });
|
|
}
|
|
});
|
|
}
|
|
|
|
this._activeUtilInstances.push(...instances);
|
|
return instances;
|
|
}
|
|
|
|
find(name) {
|
|
return this._registeredUtils.find((util) => util.name === name);
|
|
}
|
|
|
|
_findUtilElements(util, scope) {
|
|
if (scope && scope.matches(util.selector)) {
|
|
return [scope];
|
|
}
|
|
return Array.from(scope.querySelectorAll(util.selector));
|
|
}
|
|
|
|
_findUtilIndex(name) {
|
|
return this._registeredUtils.findIndex((util) => util.name === name);
|
|
}
|
|
|
|
_destroyUtilInstances(name) {
|
|
this._activeUtilInstances
|
|
.map((util, index) => ({
|
|
util: util,
|
|
index: index,
|
|
}))
|
|
.filter((activeUtil) => activeUtil.util.name === name)
|
|
.forEach((activeUtil) => {
|
|
// destroy util instance
|
|
activeUtil.util.destroy();
|
|
delete this._activeUtilInstances[activeUtil.index];
|
|
});
|
|
|
|
// get rid of now empty array slots
|
|
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');
|
|
}
|