emittermixin.js 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306
  1. /**
  2. * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md.
  4. */
  5. 'use strict';
  6. import EventInfo from './eventinfo.js';
  7. import utils from './utils.js';
  8. // Saves how many callbacks has been already added. Does not decrement when callback is removed.
  9. // Used internally as a unique id for a callback.
  10. let eventsCounter = 0;
  11. /**
  12. * Mixin that injects the events API into its host.
  13. *
  14. * @singleton
  15. * @class core.EmitterMixin
  16. * @implementes core.Emitter
  17. */
  18. const EmitterMixin = {
  19. /**
  20. * Registers a callback function to be executed when an event is fired.
  21. *
  22. * @param {String} event The name of the event.
  23. * @param {Function} callback The function to be called on event.
  24. * @param {Object} [ctx] The object that represents `this` in the callback. Defaults to the object firing the
  25. * event.
  26. * @param {Number} [priority=10] The priority of this callback in relation to other callbacks to that same event.
  27. * Lower values are called first.
  28. */
  29. on( event, callback, ctx, priority ) {
  30. const callbacks = getCallbacks( this, event );
  31. // Set the priority defaults.
  32. if ( typeof priority != 'number' ) {
  33. priority = 10;
  34. }
  35. callback = {
  36. callback: callback,
  37. ctx: ctx || this,
  38. priority: priority,
  39. // Save counter value as unique id.
  40. counter: ++eventsCounter
  41. };
  42. // Add the callback to the list in the right priority position.
  43. for ( let i = callbacks.length - 1; i >= 0; i-- ) {
  44. if ( callbacks[ i ].priority <= priority ) {
  45. callbacks.splice( i + 1, 0, callback );
  46. return;
  47. }
  48. }
  49. callbacks.unshift( callback );
  50. },
  51. /**
  52. * Registers a callback function to be executed on the next time the event is fired only. This is similar to
  53. * calling {@link #on} followed by {@link #off} in the callback.
  54. *
  55. * @param {String} event The name of the event.
  56. * @param {Function} callback The function to be called on event.
  57. * @param {Object} [ctx] The object that represents `this` in the callback. Defaults to the object firing the
  58. * event.
  59. * @param {Number} [priority=10] The priority of this callback in relation to other callbacks to that same event.
  60. * Lower values are called first.
  61. */
  62. once( event, callback, ctx, priority ) {
  63. const onceCallback = function( event ) {
  64. // Go off() at the first call.
  65. event.off();
  66. // Go with the original callback.
  67. callback.apply( this, arguments );
  68. };
  69. // Make the a similar on() call, simply replacing the callback.
  70. this.on( event, onceCallback, ctx, priority );
  71. },
  72. /**
  73. * Stops executing the callback on the given event.
  74. *
  75. * @param {String} event The name of the event.
  76. * @param {Function} callback The function to stop being called.
  77. * @param {Object} [ctx] The context object to be removed, pared with the given callback. To handle cases where
  78. * the same callback is used several times with different contexts.
  79. */
  80. off( event, callback, ctx ) {
  81. const callbacks = getCallbacksIfAny( this, event );
  82. if ( !callbacks ) {
  83. return;
  84. }
  85. for ( let i = 0; i < callbacks.length; i++ ) {
  86. if ( callbacks[ i ].callback == callback ) {
  87. if ( !ctx || ctx == callbacks[ i ].ctx ) {
  88. // Remove the callback from the list (fixing the next index).
  89. callbacks.splice( i, 1 );
  90. i--;
  91. }
  92. }
  93. }
  94. },
  95. /**
  96. * Registers a callback function to be executed when an event is fired in a specific (emitter) object.
  97. *
  98. * @param {core.Emitter} emitter The object that fires the event.
  99. * @param {String} event The name of the event.
  100. * @param {Function} callback The function to be called on event.
  101. * @param {Object} [ctx] The object that represents `this` in the callback. Defaults to `emitter`.
  102. * @param {Number} [priority=10] The priority of this callback in relation to other callbacks to that same event.
  103. * Lower values are called first.
  104. */
  105. listenTo( emitter, event, callback, ctx, priority ) {
  106. let emitters, emitterId, emitterInfo, eventCallbacks;
  107. // _listeningTo contains a list of emitters that this object is listening to.
  108. // This list has the following format:
  109. //
  110. // _listeningTo: {
  111. // emitterId: {
  112. // emitter: emitter,
  113. // callbacks: {
  114. // event1: [ callback1, callback2, ... ]
  115. // ....
  116. // }
  117. // },
  118. // ...
  119. // }
  120. if ( !( emitters = this._listeningTo ) ) {
  121. emitters = this._listeningTo = {};
  122. }
  123. if ( !( emitterId = emitter._emitterId ) ) {
  124. emitterId = emitter._emitterId = utils.uid();
  125. }
  126. if ( !( emitterInfo = emitters[ emitterId ] ) ) {
  127. emitterInfo = emitters[ emitterId ] = {
  128. emitter: emitter,
  129. callbacks: {}
  130. };
  131. }
  132. if ( !( eventCallbacks = emitterInfo.callbacks[ event ] ) ) {
  133. eventCallbacks = emitterInfo.callbacks[ event ] = [];
  134. }
  135. eventCallbacks.push( callback );
  136. // Finally register the callback to the event.
  137. emitter.on( event, callback, ctx, priority );
  138. },
  139. /**
  140. * Stops listening for events. It can be used at different levels:
  141. *
  142. * * To stop listening to a specific callback.
  143. * * To stop listening to a specific event.
  144. * * To stop listening to all events fired by a specific object.
  145. * * To stop listening to all events fired by all object.
  146. *
  147. * @param {core.Emitter} [emitter] The object to stop listening to. If omitted, stops it for all objects.
  148. * @param {String} [event] (Requires the `emitter`) The name of the event to stop listening to. If omitted, stops it
  149. * for all events from `emitter`.
  150. * @param {Function} [callback] (Requires the `event`) The function to be removed from the call list for the given
  151. * `event`.
  152. */
  153. stopListening( emitter, event, callback ) {
  154. let emitters = this._listeningTo;
  155. let emitterId = emitter && emitter._emitterId;
  156. let emitterInfo = emitters && emitterId && emitters[ emitterId ];
  157. let eventCallbacks = emitterInfo && event && emitterInfo.callbacks[ event ];
  158. // Stop if nothing has been listened.
  159. if ( !emitters || ( emitter && !emitterInfo ) || ( event && !eventCallbacks ) ) {
  160. return;
  161. }
  162. // All params provided. off() that single callback.
  163. if ( callback ) {
  164. emitter.off( event, callback );
  165. }
  166. // Only `emitter` and `event` provided. off() all callbacks for that event.
  167. else if ( eventCallbacks ) {
  168. while ( ( callback = eventCallbacks.pop() ) ) {
  169. emitter.off( event, callback );
  170. }
  171. delete emitterInfo.callbacks[ event ];
  172. }
  173. // Only `emitter` provided. off() all events for that emitter.
  174. else if ( emitterInfo ) {
  175. for ( event in emitterInfo.callbacks ) {
  176. this.stopListening( emitter, event );
  177. }
  178. delete emitters[ emitterId ];
  179. }
  180. // No params provided. off() all emitters.
  181. else {
  182. for ( emitterId in emitters ) {
  183. this.stopListening( emitters[ emitterId ].emitter );
  184. }
  185. delete this._listeningTo;
  186. }
  187. },
  188. /**
  189. * Fires an event, executing all callbacks registered for it.
  190. *
  191. * The first parameter passed to callbacks is an {@link EventInfo} object, followed by the optional `args` provided in
  192. * the `fire()` method call.
  193. *
  194. * @param {String} event The name of the event.
  195. * @param {...*} [args] Additional arguments to be passed to the callbacks.
  196. */
  197. fire( event, args ) {
  198. const callbacks = getCallbacksIfAny( this, event );
  199. if ( !callbacks ) {
  200. return;
  201. }
  202. let eventInfo = new EventInfo( this, event );
  203. // Take the list of arguments to pass to the callbacks.
  204. args = Array.prototype.slice.call( arguments, 1 );
  205. args.unshift( eventInfo );
  206. // Save how many callbacks were added at the moment when the event has been fired.
  207. const counter = eventsCounter;
  208. for ( let i = 0; i < callbacks.length; i++ ) {
  209. // Filter out callbacks that have been added after event has been fired.
  210. if ( callbacks[ i ].counter > counter ) {
  211. continue;
  212. }
  213. callbacks[ i ].callback.apply( callbacks[ i ].ctx, args );
  214. // Remove the callback from future requests if off() has been called.
  215. if ( eventInfo.off.called ) {
  216. // Remove the called mark for the next calls.
  217. delete eventInfo.off.called;
  218. // Remove the callback from the list (fixing the next index).
  219. callbacks.splice( i, 1 );
  220. i--;
  221. }
  222. // Do not execute next callbacks if stop() was called.
  223. if ( eventInfo.stop.called ) {
  224. break;
  225. }
  226. }
  227. }
  228. };
  229. export default EmitterMixin;
  230. // Gets the internal events hash of a give object.
  231. function getEvents( source ) {
  232. if ( !source._events ) {
  233. Object.defineProperty( source, '_events', {
  234. value: {}
  235. } );
  236. }
  237. return source._events;
  238. }
  239. // Gets the list of callbacks for a given event.
  240. function getCallbacks( source, eventName ) {
  241. const events = getEvents( source );
  242. if ( !events[ eventName ] ) {
  243. events[ eventName ] = [];
  244. }
  245. return events[ eventName ];
  246. }
  247. // Get the list of callbacks for a given event only if there is any available.
  248. function getCallbacksIfAny( source, event ) {
  249. let callbacks;
  250. if ( !source._events || !( callbacks = source._events[ event ] ) || !callbacks.length ) {
  251. return null;
  252. }
  253. return callbacks;
  254. }
  255. /**
  256. * Interface representing classes which mix in {@link core.EmitterMixin}.
  257. *
  258. * @interface core.Emitter
  259. */