(function() {
/**
* @typedef {'high'|'normal'} Help4.EmbeddedEvent.Priority
*/
/**
* @typedef {Object} Help4.EmbeddedEvent.EventResponse
* @property {string} type - event response type
* @property {Object} [scope] - responder
* @property {*} [response] - result value
*/
/**
* @callback Help4.EmbeddedEvent.Listener
* @param {Object} data
*/
/**
* used to embed event functionality everywhere
* @augments Help4.jscore.Base
* @property {Array<Help4.EmbeddedEvent.Listener>} _listeners - event listeners
* @property {Array<Help4.EmbeddedEvent.Priority>} _priorities - listener priorities
*/
Help4.EmbeddedEvent = class extends Help4.jscore.Base {
/** @override */
constructor() {
super({
statics: {
_listeners: {init: [], destroy: false}, // incompatible override with base class!
_priorities: {init: [], destroy: false}
}
});
}
/**
* {@link Help4.EmbeddedEvent.Priority}
* @memberof Help4.EmbeddedEvent
* @type {Object}
*/
static PRIORITY = {high: 'high', normal: 'normal'}
/** @override */
destroy() {
// as we override this._listeners in an incompatible way
// we need to prevent crashes during destroy in base class destructor
// just delete this._listeners manually here
delete this._listeners;
super.destroy();
}
/**
* returns the number of subscribed listeners
* @returns {number}
*/
countListeners() {
return this._listeners.length;
}
/**
* add a listener to the EmbeddedEvent
* @override
* @param {Help4.EmbeddedEvent.Listener} listener - the callback function
* @param {Help4.EmbeddedEvent.Priority} [priority]
* @returns {Help4.EmbeddedEvent}
*/
addListener(listener, priority) {
const {_listeners, _priorities} = this;
if (!_listeners) {
throw new Error('EmbeddedEvent has been destroyed!');
} else if (typeof listener !== 'function') {
throw new TypeError('listener has to be a function!');
} else if (_listeners.indexOf(listener) < 0) {
const {PRIORITY} = this.constructor;
_listeners.push(listener);
_priorities.push(PRIORITY[priority] || PRIORITY.normal);
}
return this;
}
/**
* remove a listener from the EmbeddedEvent
* @override
* @param {Help4.EmbeddedEvent.Listener} listener - the to be removed callback function
* @returns {Help4.EmbeddedEvent}
*/
removeListener(listener) {
if (typeof listener !== 'function') {
throw new TypeError('listener has to be a function!');
}
const {_listeners, _priorities} = this;
const index = _listeners?.indexOf(listener) ?? -1;
if (index >= 0) {
_listeners.splice(index, 1);
_priorities.splice(index, 1);
}
return this;
}
/**
* send an event to all callbacks
* @param {Object} data
* @param {Help4.EmbeddedEvent.Priority} [priority]
* @returns {Help4.EmbeddedEvent}
* @throws {Error}
*/
onEvent(data, priority) {
if (typeof data !== 'object' || !data) throw new Error('It is good practice to use an event object!');
// priority: only send items WITH a specific priority
const {PRIORITY} = this.constructor;
priority = PRIORITY[priority] || null;
const {_listeners, _priorities} = this;
(_listeners || []).forEach((listener, index) => {
if (!priority || priority === _priorities[index]) setTimeout(() => listener(data), 1);
});
return this;
}
/**
* send an event to all callbacks; delivers the return value
* @param {Object} data
* @param {Help4.EmbeddedEvent.Priority} [priority]
* @returns {Promise<Help4.EmbeddedEvent.EventResponse[]>}
* @throws {Error}
*/
async onEvent2(data, priority) {
if (typeof data !== 'object' || !data) throw new Error('It is good practice to use an event object!');
// priority: only send items WITH a specific priority
const {PRIORITY} = this.constructor;
priority = PRIORITY[priority] || null;
const result = /** @type {Help4.EmbeddedEvent.EventResponse[]} */ [];
const {_listeners, _priorities} = this;
const count = _listeners.length;
(_listeners || []).forEach((listener, index) => {
if (!priority || priority === _priorities[index]) {
setTimeout(async () => result.push(await listener(data)), 1);
}
});
return new Help4.Promise(resolve => {
const check = () => {
result.length === count
? resolve(result)
: setTimeout(check, 100);
}
check();
});
}
}
})();