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;