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. // Priority defaults to 10.
  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] The priority of this callback in relation to other callbacks to that same event.
  53. * Lower values are called first. Defaults to `10`.
  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. */
  71. off: function( event, callback ) {
  72. var callbacks = getCallbacksIfAny( this, event );
  73. if ( !callbacks ) {
  74. return;
  75. }
  76. for ( var i = 0; i < callbacks.length; i++ ) {
  77. if ( callbacks[ i ].callback == callback ) {
  78. // Remove the callback from the list (fixing the next index).
  79. callbacks.splice( i, 1 );
  80. i--;
  81. }
  82. }
  83. },
  84. /**
  85. * Registers a callback function to be executed when an event is fired in a specific (emitter) object.
  86. *
  87. * @param {Emitter} emitter The object that fires the event.
  88. * @param {String} event The name of the event.
  89. * @param {Function} callback The function to be called on event.
  90. * @param {Object} [ctx] The object that represents `this` in the callback. Defaults to `emitter`.
  91. * @param {Number} [priority] The priority of this callback in relation to other callbacks to that same event.
  92. * Lower values are called first. Defaults to `10`.
  93. */
  94. listenTo: function( emitter, event, callback, ctx, priority ) {
  95. var emitters, emitterId, emitterInfo, eventCallbacks;
  96. // _listeningTo contains a list of emitters that this object is listening to.
  97. // This list has the following format:
  98. //
  99. // _listeningTo: {
  100. // emitterId: {
  101. // emitter: emitter,
  102. // callbacks: {
  103. // event1: [ callback1, callback2, ... ]
  104. // ....
  105. // }
  106. // },
  107. // ...
  108. // }
  109. if ( !( emitters = this._listeningTo ) ) {
  110. emitters = this._listeningTo = {};
  111. }
  112. if ( !( emitterId = emitter._emitterId ) ) {
  113. emitterId = emitter._emitterId = utils.uid();
  114. }
  115. if ( !( emitterInfo = emitters[ emitterId ] ) ) {
  116. emitterInfo = emitters[ emitterId ] = {
  117. emitter: emitter,
  118. callbacks: {}
  119. };
  120. }
  121. if ( !( eventCallbacks = emitterInfo.callbacks[ event ] ) ) {
  122. eventCallbacks = emitterInfo.callbacks[ event ] = [];
  123. }
  124. eventCallbacks.push( callback );
  125. // Finally register the callback to the event.
  126. emitter.on( event, callback, ctx, priority );
  127. },
  128. /**
  129. * Stops listening for events. It can be used at different levels:
  130. *
  131. * * To stop listening to a specific callback.
  132. * * To stop listening to a specific event.
  133. * * To stop listening to all events fired by a specific object.
  134. * * To stop listening to all events fired by all object.
  135. *
  136. * @param {Emitter} [emitter] The object to stop listening to. If omitted, stops it for all objects.
  137. * @param {String} [event] (Requires the `emitter`) The name of the event to stop listening to. If omitted, stops it
  138. * for all events from `emitter`.
  139. * @param {Function} [callback] (Requires the `event`) The function to be removed from the call list for the given
  140. * `event`.
  141. */
  142. stopListening: function( emitter, event, callback ) {
  143. var emitters = this._listeningTo;
  144. var emitterId = emitter && emitter._emitterId;
  145. var emitterInfo = emitters && emitterId && emitters[ emitterId ];
  146. var eventCallbacks = emitterInfo && event && emitterInfo.callbacks[ event ];
  147. // Stop if nothing has been listened.
  148. if ( !emitters || ( emitter && !emitterInfo ) || ( event && !eventCallbacks ) ) {
  149. return;
  150. }
  151. // All params provided. off() that single callback.
  152. if ( callback ) {
  153. emitter.off( event, callback );
  154. }
  155. // Only the emitter and event provided. off() all callbacks for that event.
  156. else if ( eventCallbacks ) {
  157. while ( ( callback = eventCallbacks.pop() ) ) {
  158. emitter.off( event, callback );
  159. }
  160. delete emitterInfo.callbacks[ event ];
  161. }
  162. // Only the emitter provided. off() all events for that emitter.
  163. else if ( emitterInfo ) {
  164. for ( event in emitterInfo.callbacks ) {
  165. this.stopListening( emitter, event );
  166. }
  167. delete emitters[ emitterId ];
  168. }
  169. // No params provided. off() all emitters.
  170. else {
  171. for ( emitterId in emitters ) {
  172. this.stopListening( emitters[ emitterId ].emitter );
  173. }
  174. delete this._listeningTo;
  175. }
  176. },
  177. /**
  178. * Fires and event, executing all callbacks registered for it.
  179. *
  180. * The first parameter passed to callbacks is a {EventInfo} object, followed by the optional `args` provided in
  181. * the `fire()` method call.
  182. *
  183. * @param {String} event The name of the event.
  184. * @param {...*} [args] Additional arguments to be passed to the callbacks.
  185. */
  186. fire: function( event, args ) {
  187. var callbacks = getCallbacksIfAny( this, event );
  188. if ( !callbacks ) {
  189. return;
  190. }
  191. var eventInfo = new EventInfo( event );
  192. // Take the list of arguments to pass to the callbacks.
  193. args = Array.prototype.slice.call( arguments, 1 );
  194. args.unshift( eventInfo );
  195. for ( var i = 0; i < callbacks.length; i++ ) {
  196. callbacks[ i ].callback.apply( callbacks[ i ].ctx, args );
  197. if ( eventInfo.stop.called ) {
  198. break;
  199. }
  200. if ( eventInfo.off.called ) {
  201. // Remove the called mark for the next calls.
  202. delete eventInfo.off.called;
  203. // Remove the callback from the list (fixing the next index).
  204. callbacks.splice( i, 1 );
  205. i--;
  206. }
  207. }
  208. }
  209. };
  210. return EmmitterMixin;
  211. // Gets the internal events hash of a give object.
  212. function getEvents( source ) {
  213. if ( !source._events ) {
  214. Object.defineProperty( source, '_events', {
  215. value: {}
  216. } );
  217. }
  218. return source._events;
  219. }
  220. // Gets the list of callbacks for a given event.
  221. function getCallbacks( source, eventName ) {
  222. var events = getEvents( source );
  223. if ( !events[ eventName ] ) {
  224. events[ eventName ] = [];
  225. }
  226. return events[ eventName ];
  227. }
  228. // Get the list of callbacks for a given event only if there is any available.
  229. function getCallbacksIfAny( source, event ) {
  230. var callbacks;
  231. if ( !source._events || !( callbacks = source._events[ event ] ) || !callbacks.length ) {
  232. return null;
  233. }
  234. return callbacks;
  235. }
  236. } );