StructureJS
0.15.2A class based utility library for building modular and scalable web platform applications. Features opt-in classes and utilities which provide a solid foundation and toolset to build your next project.
import ObjectManager from '../ObjectManager'; import BaseEvent from './BaseEvent'; import Util from '../util/Util'; /** * EventDispatcher is the base class for all classes that dispatch events. It is the base class for the {{#crossLink "DisplayObjectContainer"}}{{/crossLink}} class. * EventDispatcher provides methods for managing prioritized queues of event listeners and dispatching events. * * @class EventDispatcher * @extends ObjectManager * @module StructureJS * @submodule event * @requires Extend * @requires ObjectManager * @requires BaseEvent * @constructor * @author Robert S. (www.codeBelt.com) * @example * // Another way to use the EventDispatcher. * let eventDispatcher = new EventDispatcher(); * eventDispatcher.addEventListener('change', this._handlerMethod, this); * eventDispatcher.dispatchEvent('change'); */ class EventDispatcher extends ObjectManager { /** * Holds a reference to added listeners. * * @property _listeners * @type {any} * @protected */ protected _listeners:any = {}; /** * Indicates the object that contains a child object. Uses the parent property * to specify a relative path to display objects that are above the current display object in the display * list hierarchy and helps facilitate event bubbling. * * @property parent * @type {any} * @public */ public parent:any = null; constructor() { super(); } /** * Registers an event listener object with an EventDispatcher object so the listener receives notification of an event. * * @method addEventListener * @param type {String} The type of event. * @param callback {Function} The listener function that processes the event. This function must accept an Event object as its only parameter and must return nothing, as this example shows. @example function(event:Event):void * @param scope {any} Binds the scope to a particular object (scope is basically what "this" refers to in your function). This can be very useful in JavaScript because scope isn't generally maintained. * @param [priority=0] {int} Influences the order in which the listeners are called. Listeners with lower priorities are called after ones with higher priorities. * @public * @chainable * @example * this.addEventListener(BaseEvent.CHANGE, this._handlerMethod, this); * * _handlerMethod(event) { * console.log(event.target + " sent the event."); * console.log(event.type, event.data); * } */ public addEventListener(type:string, callback:Function, scope:any, priority:number = 0):EventDispatcher { // Get the list of event listeners by the associated type value that is passed in. let list = this._listeners[type]; if (list == null) { // If a list of event listeners do not exist for the type value passed in then create a new empty array. this._listeners[type] = list = []; } let index:number = 0; let listener; let i:number = list.length; while (--i > -1) { listener = list[i]; if (listener.callback === callback && listener.scope === scope) { // If the same callback and scope are found then remove it and add the current one below. list.splice(i, 1); } else if (index === 0 && listener.priority < priority) { index = i + 1; } } // Add the event listener to the list array at the index value. list.splice(index, 0, {callback: callback, scope: scope, priority: priority, once: false}); return this; } /** * Registers an event listener object once with an EventDispatcher object so the listener will receive the notification of an event. * * @method addEventListenerOnce * @param type {String} The type of event. * @param callback {Function} The listener function that processes the event. This function must accept an Event object as its only parameter and must return nothing, as this example shows. @example function(event:Event):void * @param scope {any} Binds the scope to a particular object (scope is basically what "this" refers to in your function). This can be very useful in JavaScript because scope isn't generally maintained. * @param [priority=0] {int} Influences the order in which the listeners are called. Listeners with lower priorities are called after ones with higher priorities. * @public * @chainable * @example * this.addEventListenerOnce(BaseEvent.CHANGE, this._handlerMethod, this); * * _handlerMethod(event) { * console.log(event.target + " sent the event."); * console.log(event.type, event.data); * } */ public addEventListenerOnce(type:string, callback:Function, scope:any, priority:number = 0):EventDispatcher { // Add the event listener the normal way. this.addEventListener(type, callback, scope, priority); // Get the event listeners we just added. const list = this._listeners[type]; const listener = list[0]; // Change the value to true so it will be remove after dispatchEvent is called. listener.once = true; return this; } /** * Removes a specified listener from the EventDispatcher object. * * @method removeEventListener * @param type {String} The type of event. * @param callback {Function} The listener object to remove. * @param scope {any} The scope of the listener object to be removed. * @hide This was added because it was needed for the {{#crossLink "EventBroker"}}{{/crossLink}} class. To keep things consistent this parameter is required. * @public * @chainable * @example * this.removeEventListener(BaseEvent.CHANGE, this._handlerMethod, this); */ public removeEventListener(type:string, callback:Function, scope:any):EventDispatcher { // Get the list of event listeners by the associated type value that is passed in. const list:Array<any> = this._listeners[type]; if (list !== void 0) { let i = list.length; while (--i > -1) { // If the callback and scope are the same then remove the event listener. if (list[i].callback === callback && list[i].scope === scope) { list.splice(i, 1); break; } } } return this; } /** * <p>Dispatches an event into the event flow. The event target is the EventDispatcher object upon which the dispatchEvent() method is called.</p> * * @method dispatchEvent * @param event {string|BaseEvent} The Event object or event type string you want to dispatch. You can create custom events, the only requirement is all events must extend {{#crossLink "BaseEvent"}}{{/crossLink}}. * @param [data=null] {any} The optional data you want to send with the event. Do not use this parameter if you are passing in a {{#crossLink "BaseEvent"}}{{/crossLink}}. * @public * @chainable * @example * this.dispatchEvent('change'); * * // Example: Sending data with the event: * this.dispatchEvent('change', {some: 'data'}); * * // Example: With an event object * // (event type, bubbling set to true, cancelable set to true and passing data) : * let event = new BaseEvent(BaseEvent.CHANGE, true, true, {some: 'data'}); * this.dispatchEvent(event); * * // Here is a common inline event object being dispatched: * this.dispatchEvent(new BaseEvent(BaseEvent.CHANGE)); */ public dispatchEvent(type:any, data:any = null):EventDispatcher { let event = type; if (typeof event === 'string') { event = new BaseEvent(type, false, true, data); } // If target is null then set it to the object that dispatched the event. if (event.target == null) { event.target = this; event.currentTarget = this; } // Get the list of event listener by the associated type value. const list:Array<any> = this._listeners[event.type]; if (list !== void 0) { // Cache to prevent the edge case were another listener is added during the dispatch loop. const cachedList:Array<any> = list.slice(); let i:number = cachedList.length; let listener:any; while (--i > -1) { // If cancelable and isImmediatePropagationStopped are true then break out of the while loop. if (event.cancelable === true && event.isImmediatePropagationStopped === true) { break; } listener = cachedList[i]; listener.callback.call(listener.scope, event); // If the once value is true we want to remove the listener right after this callback was called. if (listener.once === true) { this.removeEventListener(event.type, listener.callback, listener.scope); } } } //Dispatches up the chain of classes that have a parent. if (this.parent != null && event.bubbles === true) { // If cancelable and isPropagationStopped are true then don't dispatch the event on the parent object. if (event.cancelable === true && event.isPropagationStopped === true) { return this; } // Assign the current object that is currently processing the event (i.e. event bubbling at). event.currentTarget = this; // Pass the event to the parent (event bubbling). this.parent.dispatchEvent(event); } return this; } /** * Check if an object has a specific event listener already added. * * @method hasEventListener * @param type {String} The type of event. * @param callback {Function} The listener method to call. * @param scope {any} The scope of the listener object. * @return {boolean} * @public * @example * this.hasEventListener(BaseEvent.CHANGE, this._handlerMethod, this); */ public hasEventListener(type:string, callback:Function, scope:any):boolean { if (this._listeners[type] !== void 0) { let listener:any; const numOfCallbacks:number = this._listeners[type].length; for (let i:number = 0; i < numOfCallbacks; i++) { listener = this._listeners[type][i]; if (listener.callback === callback && listener.scope === scope) { return true; } } } return false; } /** * Returns and array of all current event types and there current listeners. * * @method getEventListeners * @return {Array<any>} * @public * @example * this.getEventListeners(); */ public getEventListeners():Array<any> { return this._listeners; } /** * Prints out each event listener in the console.log * * @method print * @return {string} * @public * @example * this.printEventListeners(); * * // [ClassName] is listening for the 'BaseEvent.change' event. * // [AnotherClassName] is listening for the 'BaseEvent.refresh' event. */ public printEventListeners():void { let numOfCallbacks:number; let listener:any; for (let type in this._listeners) { numOfCallbacks = this._listeners[type].length; for (let i:number = 0; i < numOfCallbacks; i++) { listener = this._listeners[type][i]; let name; if (listener.scope) { name = '[' + Util.getName(listener.scope) + ']'; } else { name ='[Unknown]'; } console.log(`${name} is listen for "${type}" event.`, listener.scope); } } } /** * @overridden BaseObject.destroy */ public destroy():void { this.disable(); super.destroy(); } } export default EventDispatcher;