ts/event/EventDispatcher.ts - StructureJS

StructureJS

0.15.2

A 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.

File: ts/event/EventDispatcher.ts

  1. import ObjectManager from '../ObjectManager';
  2. import BaseEvent from './BaseEvent';
  3. import Util from '../util/Util';
  4.  
  5. /**
  6. * EventDispatcher is the base class for all classes that dispatch events. It is the base class for the {{#crossLink "DisplayObjectContainer"}}{{/crossLink}} class.
  7. * EventDispatcher provides methods for managing prioritized queues of event listeners and dispatching events.
  8. *
  9. * @class EventDispatcher
  10. * @extends ObjectManager
  11. * @module StructureJS
  12. * @submodule event
  13. * @requires Extend
  14. * @requires ObjectManager
  15. * @requires BaseEvent
  16. * @constructor
  17. * @author Robert S. (www.codeBelt.com)
  18. * @example
  19. * // Another way to use the EventDispatcher.
  20. * let eventDispatcher = new EventDispatcher();
  21. * eventDispatcher.addEventListener('change', this._handlerMethod, this);
  22. * eventDispatcher.dispatchEvent('change');
  23. */
  24. class EventDispatcher extends ObjectManager
  25. {
  26. /**
  27. * Holds a reference to added listeners.
  28. *
  29. * @property _listeners
  30. * @type {any}
  31. * @protected
  32. */
  33. protected _listeners:any = {};
  34.  
  35. /**
  36. * Indicates the object that contains a child object. Uses the parent property
  37. * to specify a relative path to display objects that are above the current display object in the display
  38. * list hierarchy and helps facilitate event bubbling.
  39. *
  40. * @property parent
  41. * @type {any}
  42. * @public
  43. */
  44. public parent:any = null;
  45.  
  46. constructor()
  47. {
  48. super();
  49. }
  50.  
  51. /**
  52. * Registers an event listener object with an EventDispatcher object so the listener receives notification of an event.
  53. *
  54. * @method addEventListener
  55. * @param type {String} The type of event.
  56. * @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
  57. * @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.
  58. * @param [priority=0] {int} Influences the order in which the listeners are called. Listeners with lower priorities are called after ones with higher priorities.
  59. * @public
  60. * @chainable
  61. * @example
  62. * this.addEventListener(BaseEvent.CHANGE, this._handlerMethod, this);
  63. *
  64. * _handlerMethod(event) {
  65. * console.log(event.target + " sent the event.");
  66. * console.log(event.type, event.data);
  67. * }
  68. */
  69. public addEventListener(type:string, callback:Function, scope:any, priority:number = 0):EventDispatcher
  70. {
  71. // Get the list of event listeners by the associated type value that is passed in.
  72. let list = this._listeners[type];
  73. if (list == null)
  74. {
  75. // If a list of event listeners do not exist for the type value passed in then create a new empty array.
  76. this._listeners[type] = list = [];
  77. }
  78. let index:number = 0;
  79. let listener;
  80. let i:number = list.length;
  81. while (--i > -1)
  82. {
  83. listener = list[i];
  84. if (listener.callback === callback && listener.scope === scope)
  85. {
  86. // If the same callback and scope are found then remove it and add the current one below.
  87. list.splice(i, 1);
  88. }
  89. else if (index === 0 && listener.priority < priority)
  90. {
  91. index = i + 1;
  92. }
  93. }
  94. // Add the event listener to the list array at the index value.
  95. list.splice(index, 0, {callback: callback, scope: scope, priority: priority, once: false});
  96.  
  97. return this;
  98. }
  99.  
  100. /**
  101. * Registers an event listener object once with an EventDispatcher object so the listener will receive the notification of an event.
  102. *
  103. * @method addEventListenerOnce
  104. * @param type {String} The type of event.
  105. * @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
  106. * @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.
  107. * @param [priority=0] {int} Influences the order in which the listeners are called. Listeners with lower priorities are called after ones with higher priorities.
  108. * @public
  109. * @chainable
  110. * @example
  111. * this.addEventListenerOnce(BaseEvent.CHANGE, this._handlerMethod, this);
  112. *
  113. * _handlerMethod(event) {
  114. * console.log(event.target + " sent the event.");
  115. * console.log(event.type, event.data);
  116. * }
  117. */
  118. public addEventListenerOnce(type:string, callback:Function, scope:any, priority:number = 0):EventDispatcher
  119. {
  120. // Add the event listener the normal way.
  121. this.addEventListener(type, callback, scope, priority);
  122.  
  123. // Get the event listeners we just added.
  124. const list = this._listeners[type];
  125. const listener = list[0];
  126.  
  127. // Change the value to true so it will be remove after dispatchEvent is called.
  128. listener.once = true;
  129.  
  130. return this;
  131. }
  132.  
  133. /**
  134. * Removes a specified listener from the EventDispatcher object.
  135. *
  136. * @method removeEventListener
  137. * @param type {String} The type of event.
  138. * @param callback {Function} The listener object to remove.
  139. * @param scope {any} The scope of the listener object to be removed.
  140. * @hide This was added because it was needed for the {{#crossLink "EventBroker"}}{{/crossLink}} class. To keep things consistent this parameter is required.
  141. * @public
  142. * @chainable
  143. * @example
  144. * this.removeEventListener(BaseEvent.CHANGE, this._handlerMethod, this);
  145. */
  146. public removeEventListener(type:string, callback:Function, scope:any):EventDispatcher
  147. {
  148. // Get the list of event listeners by the associated type value that is passed in.
  149. const list:Array<any> = this._listeners[type];
  150. if (list !== void 0)
  151. {
  152. let i = list.length;
  153. while (--i > -1)
  154. {
  155. // If the callback and scope are the same then remove the event listener.
  156. if (list[i].callback === callback && list[i].scope === scope)
  157. {
  158. list.splice(i, 1);
  159. break;
  160. }
  161. }
  162. }
  163.  
  164. return this;
  165. }
  166.  
  167. /**
  168. * <p>Dispatches an event into the event flow. The event target is the EventDispatcher object upon which the dispatchEvent() method is called.</p>
  169. *
  170. * @method dispatchEvent
  171. * @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}}.
  172. * @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}}.
  173. * @public
  174. * @chainable
  175. * @example
  176. * this.dispatchEvent('change');
  177. *
  178. * // Example: Sending data with the event:
  179. * this.dispatchEvent('change', {some: 'data'});
  180. *
  181. * // Example: With an event object
  182. * // (event type, bubbling set to true, cancelable set to true and passing data) :
  183. * let event = new BaseEvent(BaseEvent.CHANGE, true, true, {some: 'data'});
  184. * this.dispatchEvent(event);
  185. *
  186. * // Here is a common inline event object being dispatched:
  187. * this.dispatchEvent(new BaseEvent(BaseEvent.CHANGE));
  188. */
  189. public dispatchEvent(type:any, data:any = null):EventDispatcher
  190. {
  191. let event = type;
  192.  
  193. if (typeof event === 'string')
  194. {
  195. event = new BaseEvent(type, false, true, data);
  196. }
  197.  
  198. // If target is null then set it to the object that dispatched the event.
  199. if (event.target == null)
  200. {
  201. event.target = this;
  202. event.currentTarget = this;
  203. }
  204.  
  205. // Get the list of event listener by the associated type value.
  206. const list:Array<any> = this._listeners[event.type];
  207. if (list !== void 0)
  208. {
  209. // Cache to prevent the edge case were another listener is added during the dispatch loop.
  210. const cachedList:Array<any> = list.slice();
  211.  
  212. let i:number = cachedList.length;
  213. let listener:any;
  214.  
  215. while (--i > -1)
  216. {
  217. // If cancelable and isImmediatePropagationStopped are true then break out of the while loop.
  218. if (event.cancelable === true && event.isImmediatePropagationStopped === true)
  219. {
  220. break;
  221. }
  222.  
  223. listener = cachedList[i];
  224. listener.callback.call(listener.scope, event);
  225.  
  226. // If the once value is true we want to remove the listener right after this callback was called.
  227. if (listener.once === true)
  228. {
  229. this.removeEventListener(event.type, listener.callback, listener.scope);
  230. }
  231. }
  232. }
  233.  
  234. //Dispatches up the chain of classes that have a parent.
  235. if (this.parent != null && event.bubbles === true)
  236. {
  237. // If cancelable and isPropagationStopped are true then don't dispatch the event on the parent object.
  238. if (event.cancelable === true && event.isPropagationStopped === true)
  239. {
  240. return this;
  241. }
  242.  
  243. // Assign the current object that is currently processing the event (i.e. event bubbling at).
  244. event.currentTarget = this;
  245.  
  246. // Pass the event to the parent (event bubbling).
  247. this.parent.dispatchEvent(event);
  248. }
  249.  
  250. return this;
  251. }
  252.  
  253. /**
  254. * Check if an object has a specific event listener already added.
  255. *
  256. * @method hasEventListener
  257. * @param type {String} The type of event.
  258. * @param callback {Function} The listener method to call.
  259. * @param scope {any} The scope of the listener object.
  260. * @return {boolean}
  261. * @public
  262. * @example
  263. * this.hasEventListener(BaseEvent.CHANGE, this._handlerMethod, this);
  264. */
  265. public hasEventListener(type:string, callback:Function, scope:any):boolean
  266. {
  267. if (this._listeners[type] !== void 0)
  268. {
  269. let listener:any;
  270. const numOfCallbacks:number = this._listeners[type].length;
  271. for (let i:number = 0; i < numOfCallbacks; i++)
  272. {
  273. listener = this._listeners[type][i];
  274. if (listener.callback === callback && listener.scope === scope)
  275. {
  276. return true;
  277. }
  278. }
  279. }
  280.  
  281. return false;
  282. }
  283.  
  284. /**
  285. * Returns and array of all current event types and there current listeners.
  286. *
  287. * @method getEventListeners
  288. * @return {Array<any>}
  289. * @public
  290. * @example
  291. * this.getEventListeners();
  292. */
  293. public getEventListeners():Array<any>
  294. {
  295. return this._listeners;
  296. }
  297.  
  298. /**
  299. * Prints out each event listener in the console.log
  300. *
  301. * @method print
  302. * @return {string}
  303. * @public
  304. * @example
  305. * this.printEventListeners();
  306. *
  307. * // [ClassName] is listening for the 'BaseEvent.change' event.
  308. * // [AnotherClassName] is listening for the 'BaseEvent.refresh' event.
  309. */
  310. public printEventListeners():void
  311. {
  312. let numOfCallbacks:number;
  313. let listener:any;
  314.  
  315. for (let type in this._listeners)
  316. {
  317. numOfCallbacks = this._listeners[type].length;
  318. for (let i:number = 0; i < numOfCallbacks; i++)
  319. {
  320. listener = this._listeners[type][i];
  321.  
  322. let name;
  323.  
  324. if (listener.scope)
  325. {
  326. name = '[' + Util.getName(listener.scope) + ']';
  327. }
  328. else
  329. {
  330. name ='[Unknown]';
  331. }
  332.  
  333. console.log(`${name} is listen for "${type}" event.`, listener.scope);
  334. }
  335. }
  336. }
  337.  
  338. /**
  339. * @overridden BaseObject.destroy
  340. */
  341. public destroy():void
  342. {
  343. this.disable();
  344.  
  345. super.destroy();
  346. }
  347.  
  348. }
  349.  
  350. export default EventDispatcher;
  351.