import Check from "./Check.js"; import defined from "./defined.js"; /** * A generic utility class for managing subscribers for a particular event. * This class is usually instantiated inside of a container class and * exposed as a property for others to subscribe to. * * @alias Event * @template Listener extends (...args: any[]) => void = (...args: any[]) => void * @constructor * @example * MyObject.prototype.myListener = function(arg1, arg2) { * this.myArg1Copy = arg1; * this.myArg2Copy = arg2; * } * * const myObjectInstance = new MyObject(); * const evt = new Cesium.Event(); * evt.addEventListener(MyObject.prototype.myListener, myObjectInstance); * evt.raiseEvent('1', '2'); * evt.removeEventListener(MyObject.prototype.myListener); */ function Event() { this._listeners = []; this._scopes = []; this._toRemove = []; this._insideRaiseEvent = false; } Object.defineProperties(Event.prototype, { /** * The number of listeners currently subscribed to the event. * @memberof Event.prototype * @type {number} * @readonly */ numberOfListeners: { get: function () { return this._listeners.length - this._toRemove.length; }, }, }); /** * Registers a callback function to be executed whenever the event is raised. * An optional scope can be provided to serve as the this pointer * in which the function will execute. * * @param {Listener} listener The function to be executed when the event is raised. * @param {object} [scope] An optional object scope to serve as the this * pointer in which the listener function will execute. * @returns {Event.RemoveCallback} A function that will remove this event listener when invoked. * * @see Event#raiseEvent * @see Event#removeEventListener */ Event.prototype.addEventListener = function (listener, scope) { //>>includeStart('debug', pragmas.debug); Check.typeOf.func("listener", listener); //>>includeEnd('debug'); this._listeners.push(listener); this._scopes.push(scope); const event = this; return function () { event.removeEventListener(listener, scope); }; }; /** * Unregisters a previously registered callback. * * @param {Listener} listener The function to be unregistered. * @param {object} [scope] The scope that was originally passed to addEventListener. * @returns {boolean} true if the listener was removed; false if the listener and scope are not registered with the event. * * @see Event#addEventListener * @see Event#raiseEvent */ Event.prototype.removeEventListener = function (listener, scope) { //>>includeStart('debug', pragmas.debug); Check.typeOf.func("listener", listener); //>>includeEnd('debug'); const listeners = this._listeners; const scopes = this._scopes; let index = -1; for (let i = 0; i < listeners.length; i++) { if (listeners[i] === listener && scopes[i] === scope) { index = i; break; } } if (index !== -1) { if (this._insideRaiseEvent) { //In order to allow removing an event subscription from within //a callback, we don't actually remove the items here. Instead //remember the index they are at and undefined their value. this._toRemove.push(index); listeners[index] = undefined; scopes[index] = undefined; } else { listeners.splice(index, 1); scopes.splice(index, 1); } return true; } return false; }; function compareNumber(a, b) { return b - a; } /** * Raises the event by calling each registered listener with all supplied arguments. * * @param {...Parameters} arguments This method takes any number of parameters and passes them through to the listener functions. * * @see Event#addEventListener * @see Event#removeEventListener */ Event.prototype.raiseEvent = function () { this._insideRaiseEvent = true; let i; const listeners = this._listeners; const scopes = this._scopes; let length = listeners.length; for (i = 0; i < length; i++) { const listener = listeners[i]; if (defined(listener)) { listeners[i].apply(scopes[i], arguments); } } //Actually remove items removed in removeEventListener. const toRemove = this._toRemove; length = toRemove.length; if (length > 0) { toRemove.sort(compareNumber); for (i = 0; i < length; i++) { const index = toRemove[i]; listeners.splice(index, 1); scopes.splice(index, 1); } toRemove.length = 0; } this._insideRaiseEvent = false; }; /** * A function that removes a listener. * @callback Event.RemoveCallback */ export default Event;