8
0

emitter.js 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278
  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] The priority of this callback in relation to other callbacks to that same event.
  22. * Lower values are called first. Defaults to `10`.
  23. */
  24. on: function( event, callback, ctx, priority ) {
  25. var callbacks = getCallbacks( this, event );
  26. var wasAdded;
  27. // Priority defaults to 10.
  28. if ( typeof priority != 'number' ) {
  29. priority = 10;
  30. }
  31. callback = {
  32. callback: callback,
  33. ctx: ctx || this,
  34. priority: priority
  35. };
  36. // Add the callback to the list in the right priority position.
  37. for ( var i = 0; i < callbacks.length ; i++ ) {
  38. if ( callbacks[ i ].priority > priority ) {
  39. callbacks.splice( i, 0, callback );
  40. wasAdded = true;
  41. break;
  42. }
  43. }
  44. if ( !wasAdded ) {
  45. callbacks.push( callback );
  46. }
  47. },
  48. /**
  49. * Registers a callback function to be executed on the next time the event is fired only. This is similar to
  50. * calling {@link #on} followed by {@link #off} in the callback.
  51. *
  52. * @param {String} event The name of the event.
  53. * @param {Function} callback The function to be called on event.
  54. * @param {Object} [ctx] The object that represents `this` in the callback. Defaults to the object firing the
  55. * event.
  56. * @param {Number} [priority] The priority of this callback in relation to other callbacks to that same event.
  57. * Lower values are called first. Defaults to `10`.
  58. */
  59. once: function( event, callback, ctx, priority ) {
  60. var onceCallback = function( event ) {
  61. // Go off() at the first call.
  62. event.off();
  63. // Go with the original callback.
  64. callback.apply( this, arguments );
  65. };
  66. // Make the a similar on() call, simply replacing the callback.
  67. this.on( event, onceCallback, ctx, priority );
  68. },
  69. /**
  70. * Stops executing the callback on the given event.
  71. *
  72. * @param {String} event The name of the event.
  73. * @param {Function} callback The function to stop being called.
  74. */
  75. off: function( event, callback ) {
  76. var callbacks = getCallbacksIfAny( this, event );
  77. if ( !callbacks ) {
  78. return;
  79. }
  80. for ( var i = 0; i < callbacks.length; i++ ) {
  81. if ( callbacks[ i ].callback == callback ) {
  82. // Remove the callback from the list (fixing the next index).
  83. callbacks.splice( i, 1 );
  84. i--;
  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] The priority of this callback in relation to other callbacks to that same event.
  96. * Lower values are called first. Defaults to `10`.
  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 `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 `event`) The function be removed from the call list for the give
  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. if ( callback ) {
  156. // All params provided. off() that single callback.
  157. emitter.off( event, callback );
  158. } else if ( eventCallbacks ) {
  159. // Only emitter and event provided. off() all callbacks for that event.
  160. while ( ( callback = eventCallbacks.pop() ) ) {
  161. emitter.off( event, callback );
  162. }
  163. delete emitterInfo.callbacks[ event ];
  164. } else if ( emitterInfo ) {
  165. // Only emitter provided. off() all events for that emitter.
  166. for ( event in emitterInfo.callbacks ) {
  167. this.stopListening( emitter, event );
  168. }
  169. delete emitters[ emitterId ];
  170. } else {
  171. // No params provided. off() all emitters.
  172. for ( emitterId in emitters ) {
  173. this.stopListening( emitters[ emitterId ].emitter );
  174. }
  175. delete this._listeningTo;
  176. }
  177. },
  178. /**
  179. * Fires and event, executing all callbacks registered for it.
  180. *
  181. * The first parameter passed to callbacks is a {EventInfo} object, followed by the optional `args` provided in
  182. * the `fire()` method call.
  183. *
  184. * @param {String} event The name of the event.
  185. * @param {...*} [args] Additional arguments to be passed to the callbacks.
  186. */
  187. fire: function( event, args ) {
  188. var callbacks = getCallbacksIfAny( this, event );
  189. if ( !callbacks ) {
  190. return;
  191. }
  192. var eventInfo = new EventInfo( event );
  193. // Take the list of arguments to pass to the callbacks.
  194. args = Array.prototype.slice.call( arguments, 1 );
  195. args.unshift( eventInfo );
  196. for ( var i = 0; i < callbacks.length; i++ ) {
  197. callbacks[ i ].callback.apply( callbacks[ i ].ctx, args );
  198. if ( eventInfo.stop.called ) {
  199. break;
  200. }
  201. if ( eventInfo.off.called ) {
  202. // Remove the called mark for the next calls.
  203. delete eventInfo.off.called;
  204. // Remove the callback from the list (fixing the next index).
  205. callbacks.splice( i, 1 );
  206. i--;
  207. }
  208. }
  209. }
  210. };
  211. return EmmitterMixin;
  212. // Gets the internal events hash of a give object.
  213. function getEvents( source ) {
  214. if ( !source._events ) {
  215. Object.defineProperty( source, '_events', {
  216. value: {}
  217. } );
  218. }
  219. return source._events;
  220. }
  221. // Gets the list of callbacks for a given event.
  222. function getCallbacks( source, eventName ) {
  223. var events = getEvents( source );
  224. if ( !events[ eventName ] ) {
  225. events[ eventName ] = [];
  226. }
  227. return events[ eventName ];
  228. }
  229. // Get the list of callbacks for a given event only if there is any available.
  230. function getCallbacksIfAny( source, event ) {
  231. var callbacks;
  232. if ( !source._events || !( callbacks = source._events[ event ] ) || !callbacks.length ) {
  233. return null;
  234. }
  235. return callbacks;
  236. }
  237. } );