UNPKG

zombiebox

Version:

ZombieBox is a JavaScript framework for development of Smart TV and STB applications

238 lines (203 loc) 5.5 kB
/* * This file is part of the ZombieBox package. * * Copyright © 2012-2019, Interfaced * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ import {info} from '../console/console'; import IEventPublisher from './interfaces/i-event-publisher'; /** * @implements {IEventPublisher} */ export default class EventPublisher { /** */ constructor() { /** * Event listeners * @type {!Object<string, Array<Listener>>} * @protected */ this._listeners = {}; /** * Event callbacks that being called by wrappers * @type {!Object<string, Array<Listener>>} * @protected */ this._onceMapCallbacks = {}; /** * Event wrappers that call their callbacks * @type {!Object<string, Array<Listener>>} * @protected */ this._onceMapWrappers = {}; /** * Current executing event * @type {?string} * @protected */ this._executingEvent = null; /** * Warning: use this event only for development purposes * Fired with: nothing * @const {string} */ this.EVENT_ANY = 'any'; } /** * Add event listener * @param {string} event * @param {Listener} callback */ on(event, callback) { if (!this._listeners.hasOwnProperty(event)) { this._listeners[event] = []; } this._cloneListenersOnWrite(event); this._listeners[event].push(callback); } /** * Add disposable event listener * @param {string} event * @param {Listener} callback */ once(event, callback) { const wrapper = (...args) => { this.off(event, wrapper); callback(...args); }; if (!this._onceMapCallbacks.hasOwnProperty(event)) { this._onceMapCallbacks[event] = []; } if (!this._onceMapWrappers.hasOwnProperty(event)) { this._onceMapWrappers[event] = []; } this._onceMapCallbacks[event].push(callback); this._onceMapWrappers[event].push(wrapper); this.on(event, wrapper); } /** * Remove event listener * @param {string} event * @param {Listener} callback */ off(event, callback) { if (!this._listeners.hasOwnProperty(event)) { return; } const eventOnceWrappers = this._onceMapWrappers[event] || []; const eventOnceCallbacks = this._onceMapCallbacks[event] || []; const i = this._listeners[event].indexOf(callback); if (i !== -1) { const onceWrapperIndex = eventOnceWrappers.indexOf(callback); this._cloneListenersOnWrite(event, {logInfo: onceWrapperIndex === -1}); this._listeners[event].splice(i, 1); if (onceWrapperIndex !== -1) { eventOnceWrappers.splice(onceWrapperIndex, 1); eventOnceCallbacks.splice(onceWrapperIndex, 1); } } let onceCallbackIndex; while ((onceCallbackIndex = eventOnceCallbacks.indexOf(callback)) !== -1) { this.off(event, eventOnceWrappers[onceCallbackIndex]); } } /** * Remove all event listeners * @param {string=} event */ removeAllListeners(event) { const allListeners = this._listeners; const eventsToRemove = event ? [event] : Object.keys(allListeners); eventsToRemove.forEach((event) => { // "off" mutates listeners, so use a copy instead of an original array const eventListeners = allListeners[event].slice(); eventListeners.forEach((listener) => { this.off(event, listener); }); }); } /** * @param {string} event * @param {{ * logInfo: (boolean|undefined) * }=} options * @protected */ _cloneListenersOnWrite(event, {logInfo = true} = {}) { if (this._executingEvent === event) { const oldListeners = this._listeners[event]; let newListeners = []; if (oldListeners && oldListeners.length) { newListeners = oldListeners.slice(0); } this._listeners[event] = newListeners; if (logInfo) { info( `Clone listeners for event "${event}" during the execution phase. ` + `This may lead to a low-predictable order of callbacks executing and performance degradation ` + `when event has a large number of listeners.` ); } } } /** * Trigger all subscribed event listeners * @param {string} event * @param {...*} args * @protected */ _fireEvent(event, ...args) { // Save refs to prevent mutation during event execution const eventListeners = this._listeners[event]; const anyEventListeners = this._listeners[this.EVENT_ANY]; this._executingEvent = event; this._callListeners(eventListeners, event, args); this._executingEvent = this.EVENT_ANY; this._callListeners(anyEventListeners, event, args); this._executingEvent = null; } /** * @param {Array<Listener>|undefined} listeners * @param {string} event * @param {Array<*>} args * @protected */ _callListeners(listeners, event, args) { if (listeners) { for (let i = 0, len = listeners.length; i < len; i++) { const listener = listeners[i]; switch (args.length) { // Fast cases case 0: listener.call(null, event); break; case 1: listener.call(null, event, args[0]); break; case 2: listener.call(null, event, args[0], args[1]); break; case 3: listener.call(null, event, args[0], args[1], args[2]); break; case 4: listener.call(null, event, args[0], args[1], args[2], args[3]); break; case 5: listener.call(null, event, args[0], args[1], args[2], args[3], args[4]); break; // Slower default: listener(...[event].concat(args)); } } } } } /** * @typedef {function(string, ...?)} */ export let Listener;