mirror of
https://github.com/Karaka-Management/jsOMS.git
synced 2026-01-10 17:38:41 +00:00
220 lines
7.1 KiB
JavaScript
220 lines
7.1 KiB
JavaScript
import { jsOMS } from '../Utils/oLib.js';
|
|
import { Logger } from '../Log/Logger.js';
|
|
|
|
/**
|
|
* Action manager class.
|
|
*
|
|
* @copyright Dennis Eichhorn
|
|
* @license OMS License 2.2
|
|
* @version 1.0.0
|
|
* @since 1.0.0
|
|
*
|
|
* @todo Karaka/jsOMS#26
|
|
* Sync/Async events
|
|
* Events so far can be created sync and async depending on the implementation.
|
|
* It would be better to make it sync/async depending on a option flag.
|
|
*
|
|
* @todo Karaka/jsOMS#35
|
|
* Template actions cannot be overwritten
|
|
* Templates by nature get added and removed from a page (often in order to reuse existing html to minimize the html load).
|
|
* The problem with templates is that they need to register in the ActionManager.js.
|
|
* A listener currently is only registered once per id.
|
|
* Since templates often keep the same id for some elements this results in a problem because the new template will not register a new listener.
|
|
* Possible solutions:
|
|
* 1. Force unique ids for templates during setup (pro: fast and easy initial solution, con: action event pollution not solved)
|
|
* 2. Whenever a dom element with action elements is removed, also unregister the listeners (pro: clean solution, con: difficult to implement)
|
|
* Solution 2 will be implemented.
|
|
* Maybe this can be performed in the dom removing action events or a dom listener would be required to listen for these dom elements.
|
|
*/
|
|
export class ActionManager
|
|
{
|
|
/**
|
|
* @constructor
|
|
*
|
|
* @param {Object} app Application
|
|
*
|
|
* @since 1.0.0
|
|
*/
|
|
constructor (app)
|
|
{
|
|
this.logger = Logger.getInstance();
|
|
this.app = app;
|
|
this.actions = {};
|
|
};
|
|
|
|
/**
|
|
* Bind element.
|
|
*
|
|
* @param {null|string} [id] Element id (optional)
|
|
*
|
|
* @return {void}
|
|
*
|
|
* @since 1.0.0
|
|
*/
|
|
bind (id = null)
|
|
{
|
|
const uiElements = id == null ? document.querySelectorAll('[data-action]') : (typeof id.length !== 'undefined' ? id : [id]);
|
|
const length = uiElements.length;
|
|
|
|
for (let i = 0; i < length; ++i) {
|
|
if (uiElements[i] !== null && uiElements[i].hasAttribute('data-action')) {
|
|
this.bindElement(uiElements[i]);
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Bind element.
|
|
*
|
|
* @param {Element} e Element to bind
|
|
*
|
|
* @return {void}
|
|
*
|
|
* @since 1.0.0
|
|
*/
|
|
bindElement (e)
|
|
{
|
|
if (!jsOMS.isValidJson(e.getAttribute('data-action'))) {
|
|
this.logger.error('Invalid json string: \'' + e.getAttribute('data-action') + '\'');
|
|
|
|
return;
|
|
}
|
|
|
|
const listeners = JSON.parse(e.getAttribute('data-action'));
|
|
const listenerLength = listeners.length;
|
|
const self = this;
|
|
|
|
// For every action an event is registered
|
|
for (let i = 0; i < listenerLength; ++i) {
|
|
let c = [e];
|
|
let hasSelector = false;
|
|
|
|
// the selector must be a child of e!!!
|
|
if (Object.prototype.hasOwnProperty.call(listeners[i], 'selector')) {
|
|
c = document.querySelectorAll(listeners[i].selector);
|
|
hasSelector = true;
|
|
}
|
|
|
|
const childLength = c.length;
|
|
for (let j = 0; j < childLength; ++j) {
|
|
this.bindListener(c[j].id, listeners[i]);
|
|
}
|
|
|
|
/**
|
|
* @todo Karaka/jsOMS#69
|
|
* If a element has a selector defined this means the action is defined for all child elements of this selector.
|
|
* This usually is done in order to avoid defining the same behavior multiple times for similar elements (e.g. elements in a list).
|
|
* However, in this case it's not unusual that the child elements get changed dynamically (added, changed, removed).
|
|
* In this case the child element listeners need to be updated on change/addition/removal.
|
|
*/
|
|
const observeConfig = { childList: false, attributes: true, subtree: false };
|
|
|
|
if (hasSelector) {
|
|
this.app.eventManager.attach(e.id + '-childList', function (data) {
|
|
const length = data.addedNodes.length;
|
|
|
|
for (let j = 0; j < length; ++j) {
|
|
self.bindListener(
|
|
data.addedNodes[j].id,
|
|
listeners[i],
|
|
typeof listeners[i].autoremove !== 'undefined' ? listeners[i].autoremove : false
|
|
);
|
|
}
|
|
});
|
|
|
|
observeConfig.childList = true;
|
|
observeConfig.subtree = true;
|
|
}
|
|
|
|
this.app.eventManager.attach(e.id + '-attributes', function (data) {});
|
|
this.app.uiManager.getDOMObserver().observe(e, observeConfig);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Bind listener for object
|
|
*
|
|
* @param {string} id Element to bind
|
|
* @param {Object} listener Listener object
|
|
*
|
|
* @return {void}
|
|
*
|
|
* @since 1.0.0
|
|
*/
|
|
bindListener (id, listener, removable = false)
|
|
{
|
|
const self = this;
|
|
const actionLength = listener.action.length;
|
|
|
|
for (let j = 1; j < actionLength; ++j) {
|
|
if (typeof id === 'undefined' || typeof listener.key === 'undefined') {
|
|
this.logger.error('Invalid element id/key: ' + id + '/' + listener.key);
|
|
return;
|
|
}
|
|
|
|
if (this.app.eventManager.isAttached(id + '-' + listener.key + '-' + listener.action[j - 1].key)) {
|
|
return;
|
|
}
|
|
|
|
this.app.eventManager.attach(id + '-' + listener.key + '-' + listener.action[j - 1].key, function (data)
|
|
{
|
|
self.runAction(id, listener, listener.action[j], data);
|
|
}, removable, true);
|
|
}
|
|
|
|
// Register event for first action
|
|
document.getElementById(id).addEventListener(listener.listener, function (event)
|
|
{
|
|
if (listener.preventDefault) {
|
|
jsOMS.preventAll(event);
|
|
}
|
|
|
|
self.runAction(this.id, listener, listener.action[0], event);
|
|
}, false);
|
|
};
|
|
|
|
/**
|
|
* Run event action.
|
|
*
|
|
* @param {string} id Element
|
|
* @param {Object} listener Listener
|
|
* @param {Object} action Action
|
|
* @param {Object} data Data for the next action
|
|
*
|
|
* @return {void}
|
|
*
|
|
* @since 1.0.0
|
|
*/
|
|
runAction (id, listener, action, data)
|
|
{
|
|
const self = this;
|
|
|
|
if (!Object.prototype.hasOwnProperty.call(this.actions, action.type)) {
|
|
this.logger.warning('Undefined action ' + action.type);
|
|
return;
|
|
}
|
|
|
|
action.data = data;
|
|
|
|
this.actions[action.type](action, function (data)
|
|
{
|
|
self.app.eventManager.trigger(id + '-' + listener.key + '-' + action.key, id, data);
|
|
}, id);
|
|
};
|
|
|
|
/**
|
|
* Add action callback.
|
|
*
|
|
* @param {string} name Action identifier
|
|
* @param {function} callback Action callback
|
|
*
|
|
* @return {void}
|
|
*
|
|
* @since 1.0.0
|
|
*/
|
|
add (name, callback)
|
|
{
|
|
this.actions[name] = callback;
|
|
};
|
|
};
|