emittermixin.js 9.1 KB

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