emitter.js 8.5 KB

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