8
0

emittermixin.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497
  1. /**
  2. * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md.
  4. */
  5. import EventInfo from './eventinfo.js';
  6. import uid from './uid.js';
  7. import priorities from './priorities.js';
  8. /**
  9. * Mixin that injects the events API into its host.
  10. *
  11. * @mixin utils.EmitterMixin
  12. * @implements utils.Emitter
  13. */
  14. const EmitterMixin = {
  15. /**
  16. * Registers a callback function to be executed when an event is fired. Events can be grouped in namespaces using `:`.
  17. * When namespaced event is fired, it additionaly fires all callbacks for that namespace.
  18. *
  19. * myEmitter.on( 'myGroup', genericCallback );
  20. * myEmitter.on( 'myGroup:myEvent', specificCallback );
  21. * myEmitter.fire( 'myGroup' ); // genericCallback is fired.
  22. * myEmitter.fire( 'myGroup:myEvent' ); // both genericCallback and specificCallback are fired.
  23. * myEmitter.fire( 'myGroup:foo' ); // genericCallback is fired even though there are no callbacks for "foo".
  24. *
  25. * @param {String} event The name of the event.
  26. * @param {Function} callback The function to be called on event.
  27. * @param {utils.PriorityString|Number} [priority='normal'] The priority of this event callback. The higher
  28. * the priority value the sooner the callback will be fired. Events having the same priority are called in the
  29. * order they were added.
  30. * @param {Object} [ctx] The object that represents `this` in the callback. Defaults to the object firing the
  31. * event.
  32. * @method utils.EmitterMixin#on
  33. */
  34. on( event, callback, priority = 'normal', ctx = null ) {
  35. createEventNamespace( this, event );
  36. const lists = getCallbacksListsForNamespace( this, event );
  37. priority = priorities.get( priority );
  38. callback = {
  39. callback: callback,
  40. ctx: ctx || this,
  41. priority: priority
  42. };
  43. // Add the callback to all callbacks list.
  44. for ( let callbacks of lists ) {
  45. // Add the callback to the list in the right priority position.
  46. let added = false;
  47. for ( let i = 0; i < callbacks.length; i++ ) {
  48. if ( callbacks[ i ].priority < priority ) {
  49. callbacks.splice( i, 0, callback );
  50. added = true;
  51. break;
  52. }
  53. }
  54. // Add at the end, if right place was not found.
  55. if ( !added ) {
  56. callbacks.push( callback );
  57. }
  58. }
  59. },
  60. /**
  61. * Registers a callback function to be executed on the next time the event is fired only. This is similar to
  62. * calling {@link #on} followed by {@link #off} in the callback.
  63. *
  64. * @param {String} event The name of the event.
  65. * @param {Function} callback The function to be called on event.
  66. * @param {utils.EventPriority} [priority='normal'] The priority of this event callback.
  67. * @param {Object} [ctx] The object that represents `this` in the callback. Defaults to the object firing the
  68. * event.
  69. * @method utils.EmitterMixin#once
  70. */
  71. once( event, callback, priority, ctx ) {
  72. const onceCallback = function( event ) {
  73. // Go off() at the first call.
  74. event.off();
  75. // Go with the original callback.
  76. callback.apply( this, arguments );
  77. };
  78. // Make a similar on() call, simply replacing the callback.
  79. this.on( event, onceCallback, priority, ctx );
  80. },
  81. /**
  82. * Stops executing the callback on the given event.
  83. *
  84. * @param {String} event The name of the event.
  85. * @param {Function} callback The function to stop being called.
  86. * @param {Object} [ctx] The context object to be removed, pared with the given callback. To handle cases where
  87. * the same callback is used several times with different contexts.
  88. * @method utils.EmitterMixin#off
  89. */
  90. off( event, callback, ctx ) {
  91. const lists = getCallbacksListsForNamespace( this, event );
  92. for ( let callbacks of lists ) {
  93. for ( let i = 0; i < callbacks.length; i++ ) {
  94. if ( callbacks[ i ].callback == callback ) {
  95. if ( !ctx || ctx == callbacks[ i ].ctx ) {
  96. // Remove the callback from the list (fixing the next index).
  97. callbacks.splice( i, 1 );
  98. i--;
  99. }
  100. }
  101. }
  102. }
  103. },
  104. /**
  105. * Registers a callback function to be executed when an event is fired in a specific (emitter) object.
  106. *
  107. * @param {utils.Emitter} emitter The object that fires the event.
  108. * @param {String} event The name of the event.
  109. * @param {Function} callback The function to be called on event.
  110. * @param {utils.EventPriority} [priority='normal'] The priority of this event callback.
  111. * @param {Object} [ctx] The object that represents `this` in the callback. Defaults to the object firing the
  112. * event.
  113. * @method utils.EmitterMixin#listenTo
  114. */
  115. listenTo( emitter, event, callback, priority, ctx ) {
  116. let emitters, emitterId, emitterInfo, eventCallbacks;
  117. // _listeningTo contains a list of emitters that this object is listening to.
  118. // This list has the following format:
  119. //
  120. // _listeningTo: {
  121. // emitterId: {
  122. // emitter: emitter,
  123. // callbacks: {
  124. // event1: [ callback1, callback2, ... ]
  125. // ....
  126. // }
  127. // },
  128. // ...
  129. // }
  130. if ( !( emitters = this._listeningTo ) ) {
  131. emitters = this._listeningTo = {};
  132. }
  133. if ( !( emitterId = emitter._emitterId ) ) {
  134. emitterId = emitter._emitterId = uid();
  135. }
  136. if ( !( emitterInfo = emitters[ emitterId ] ) ) {
  137. emitterInfo = emitters[ emitterId ] = {
  138. emitter: emitter,
  139. callbacks: {}
  140. };
  141. }
  142. if ( !( eventCallbacks = emitterInfo.callbacks[ event ] ) ) {
  143. eventCallbacks = emitterInfo.callbacks[ event ] = [];
  144. }
  145. eventCallbacks.push( callback );
  146. // Finally register the callback to the event.
  147. emitter.on( event, callback, priority, ctx );
  148. },
  149. /**
  150. * Stops listening for events. It can be used at different levels:
  151. *
  152. * * To stop listening to a specific callback.
  153. * * To stop listening to a specific event.
  154. * * To stop listening to all events fired by a specific object.
  155. * * To stop listening to all events fired by all object.
  156. *
  157. * @param {utils.Emitter} [emitter] The object to stop listening to. If omitted, stops it for all objects.
  158. * @param {String} [event] (Requires the `emitter`) The name of the event to stop listening to. If omitted, stops it
  159. * for all events from `emitter`.
  160. * @param {Function} [callback] (Requires the `event`) The function to be removed from the call list for the given
  161. * `event`.
  162. * @method utils.EmitterMixin#stopListening
  163. */
  164. stopListening( emitter, event, callback ) {
  165. let emitters = this._listeningTo;
  166. let emitterId = emitter && emitter._emitterId;
  167. let emitterInfo = emitters && emitterId && emitters[ emitterId ];
  168. let eventCallbacks = emitterInfo && event && emitterInfo.callbacks[ event ];
  169. // Stop if nothing has been listened.
  170. if ( !emitters || ( emitter && !emitterInfo ) || ( event && !eventCallbacks ) ) {
  171. return;
  172. }
  173. // All params provided. off() that single callback.
  174. if ( callback ) {
  175. emitter.off( event, callback );
  176. }
  177. // Only `emitter` and `event` provided. off() all callbacks for that event.
  178. else if ( eventCallbacks ) {
  179. while ( ( callback = eventCallbacks.pop() ) ) {
  180. emitter.off( event, callback );
  181. }
  182. delete emitterInfo.callbacks[ event ];
  183. }
  184. // Only `emitter` provided. off() all events for that emitter.
  185. else if ( emitterInfo ) {
  186. for ( event in emitterInfo.callbacks ) {
  187. this.stopListening( emitter, event );
  188. }
  189. delete emitters[ emitterId ];
  190. }
  191. // No params provided. off() all emitters.
  192. else {
  193. for ( emitterId in emitters ) {
  194. this.stopListening( emitters[ emitterId ].emitter );
  195. }
  196. delete this._listeningTo;
  197. }
  198. },
  199. /**
  200. * Fires an event, executing all callbacks registered for it.
  201. *
  202. * The first parameter passed to callbacks is an {@link EventInfo} object, followed by the optional `args` provided in
  203. * the `fire()` method call.
  204. *
  205. * @param {String|utils.EventInfo} eventOrInfo The name of the event or `EventInfo` object if event is delegated.
  206. * @param {...*} [args] Additional arguments to be passed to the callbacks.
  207. * @method utils.EmitterMixin#fire
  208. */
  209. fire( eventOrInfo, ...args ) {
  210. const eventInfo = eventOrInfo instanceof EventInfo ? eventOrInfo : new EventInfo( this, eventOrInfo );
  211. const event = eventInfo.name;
  212. let callbacks = getCallbacksForEvent( this, event );
  213. // Record that the event passed this emitter on its path.
  214. eventInfo.path.push( this );
  215. // Handle event listener callbacks first.
  216. if ( callbacks ) {
  217. // Arguments passed to each callback.
  218. const callbackArgs = [ eventInfo, ...args ];
  219. // Copying callbacks array is the easiest and most secure way of preventing infinite loops, when event callbacks
  220. // are added while processing other callbacks. Previous solution involved adding counters (unique ids) but
  221. // failed if callbacks were added to the queue before currently processed callback.
  222. // If this proves to be too inefficient, another method is to change `.on()` so callbacks are stored if same
  223. // event is currently processed. Then, `.fire()` at the end, would have to add all stored events.
  224. callbacks = Array.from( callbacks );
  225. for ( let i = 0; i < callbacks.length; i++ ) {
  226. callbacks[ i ].callback.apply( callbacks[ i ].ctx, callbackArgs );
  227. // Remove the callback from future requests if off() has been called.
  228. if ( eventInfo.off.called ) {
  229. // Remove the called mark for the next calls.
  230. delete eventInfo.off.called;
  231. this.off( event, callbacks[ i ].callback, callbacks[ i ].ctx );
  232. }
  233. // Do not execute next callbacks if stop() was called.
  234. if ( eventInfo.stop.called ) {
  235. break;
  236. }
  237. }
  238. }
  239. // Delegate event to other emitters if needed.
  240. if ( this._delegations ) {
  241. const destinations = this._delegations.get( event );
  242. if ( destinations ) {
  243. for ( let dest of destinations ) {
  244. dest.fire( eventInfo, ...args );
  245. }
  246. }
  247. }
  248. },
  249. /**
  250. * Delegates selected events to another {@link utils.Emitter}. For instance:
  251. *
  252. * emitterA.delegate( 'eventX' ).to( emitterB );
  253. * emitterA.delegate( 'eventX', 'eventY' ).to( emitterC );
  254. *
  255. * then `eventX` is delegated (fired by) `emitterB` and `emitterC` along with `data`:
  256. *
  257. * emitterA.fire( 'eventX', data );
  258. *
  259. * and `eventY` is delegated (fired by) `emitterC` along with `data`:
  260. *
  261. * emitterA.fire( 'eventY', data );
  262. *
  263. * @method utils.EmitterMixin#delegate
  264. * @param {...String} events Event names that will be delegated to another emitter.
  265. * @returns {utils.EmitterMixin.delegate#to}
  266. */
  267. delegate( ...events ) {
  268. return {
  269. /**
  270. * Selects destination for {@link utils.EmitterMixin#delegate} events.
  271. *
  272. * @method utils.EmitterMixin.delegate#to
  273. * @param {utils.Emitter} emitter An `EmitterMixin` instance which is the destination for delegated events.
  274. */
  275. to: ( emitter ) => {
  276. if ( !this._delegations ) {
  277. this._delegations = new Map();
  278. }
  279. for ( let eventName of events ) {
  280. let destinations = this._delegations.get( eventName );
  281. if ( !destinations ) {
  282. this._delegations.set( eventName, [ emitter ] );
  283. } else {
  284. destinations.push( emitter );
  285. }
  286. }
  287. }
  288. };
  289. },
  290. /**
  291. * Stops delegating events. It can be used at different levels:
  292. *
  293. * * To stop delegating all events.
  294. * * To stop delegating a specific event to all emitters.
  295. * * To stop delegating a specific event to a specific emitter.
  296. *
  297. * @param {String} [event] The name of the event to stop delegating. If omitted, stops it all delegations.
  298. * @param {utils.Emitter} [emitter] (requires `event`) The object to stop delegating a particular event to. If omitted,
  299. * stops delegation of `event` to all emitters.
  300. * @method utils.EmitterMixin#stopDelegating
  301. */
  302. stopDelegating( event, emitter ) {
  303. if ( !this._delegations ) {
  304. return;
  305. }
  306. if ( !event ) {
  307. this._delegations.clear();
  308. } else if ( !emitter ) {
  309. this._delegations.delete( event );
  310. } else {
  311. const destinations = this._delegations.get( event );
  312. const index = destinations.indexOf( emitter );
  313. if ( index !== -1 ) {
  314. destinations.splice( index, 1 );
  315. }
  316. }
  317. }
  318. };
  319. export default EmitterMixin;
  320. // Gets the internal `_events` property of the given object.
  321. // `_events` property store all lists with callbacks for registered event names.
  322. // If there were no events registered on the object, empty `_events` object is created.
  323. function getEvents( source ) {
  324. if ( !source._events ) {
  325. Object.defineProperty( source, '_events', {
  326. value: {}
  327. } );
  328. }
  329. return source._events;
  330. }
  331. // Creates event node for generic-specific events relation architecture.
  332. function makeEventNode() {
  333. return {
  334. callbacks: [],
  335. childEvents: []
  336. };
  337. }
  338. // Creates an architecture for generic-specific events relation.
  339. // If needed, creates all events for given eventName, i.e. if the first registered event
  340. // is foo:bar:abc, it will create foo:bar:abc, foo:bar and foo event and tie them together.
  341. // It also copies callbacks from more generic events to more specific events when
  342. // specific events are created.
  343. function createEventNamespace( source, eventName ) {
  344. const events = getEvents( source );
  345. // First, check if the event we want to add to the structure already exists.
  346. if ( events[ eventName ] ) {
  347. // If it exists, we don't have to do anything.
  348. return;
  349. }
  350. // In other case, we have to create the structure for the event.
  351. // Note, that we might need to create intermediate events too.
  352. // I.e. if foo:bar:abc is being registered and we only have foo in the structure,
  353. // we need to also register foo:bar.
  354. // Currently processed event name.
  355. let name = eventName;
  356. // Name of the event that is a child event for currently processed event.
  357. let childEventName = null;
  358. // Array containing all newly created specific events.
  359. const newEventNodes = [];
  360. // While loop can't check for ':' index because we have to handle generic events too.
  361. // In each loop, we truncate event name, going from the most specific name to the generic one.
  362. // I.e. foo:bar:abc -> foo:bar -> foo.
  363. while ( name !== '' ) {
  364. if ( events[ name ] ) {
  365. // If the currently processed event name is already registered, we can be sure
  366. // that it already has all the structure created, so we can break the loop here
  367. // as no more events need to be registered.
  368. break;
  369. }
  370. // If this event is not yet registered, create a new object for it.
  371. events[ name ] = makeEventNode();
  372. // Add it to the array with newly created events.
  373. newEventNodes.push( events[ name ] );
  374. // Add previously processed event name as a child of this event.
  375. if ( childEventName ) {
  376. events[ name ].childEvents.push( childEventName );
  377. }
  378. childEventName = name;
  379. // If `.lastIndexOf()` returns -1, `.substr()` will return '' which will break the loop.
  380. name = name.substr( 0, name.lastIndexOf( ':' ) );
  381. }
  382. if ( name !== '' ) {
  383. // If name is not empty, we found an already registered event that was a parent of the
  384. // event we wanted to register.
  385. // Copy that event's callbacks to newly registered events.
  386. for ( let node of newEventNodes ) {
  387. node.callbacks = events[ name ].callbacks.slice();
  388. }
  389. // Add last newly created event to the already registered event.
  390. events[ name ].childEvents.push( childEventName );
  391. }
  392. }
  393. // Gets an array containing callbacks list for a given event and it's more specific events.
  394. // I.e. if given event is foo:bar and there is also foo:bar:abc event registered, this will
  395. // return callback list of foo:bar and foo:bar:abc (but not foo).
  396. // Returns empty array if given event has not been yet registered.
  397. function getCallbacksListsForNamespace( source, eventName ) {
  398. const eventNode = getEvents( source )[ eventName ];
  399. if ( !eventNode ) {
  400. return [];
  401. }
  402. let callbacksLists = [ eventNode.callbacks ];
  403. for ( let i = 0; i < eventNode.childEvents.length; i++ ) {
  404. let childCallbacksLists = getCallbacksListsForNamespace( source, eventNode.childEvents[ i ] );
  405. callbacksLists = callbacksLists.concat( childCallbacksLists );
  406. }
  407. return callbacksLists;
  408. }
  409. // Get the list of callbacks for a given event, but only if there any callbacks have been registered.
  410. // If there are no callbacks registered for given event, it checks if this is a specific event and looks
  411. // for callbacks for it's more generic version.
  412. function getCallbacksForEvent( source, eventName ) {
  413. let event;
  414. if ( !source._events || !( event = source._events[ eventName ] ) || !event.callbacks.length ) {
  415. // There are no callbacks registered for specified eventName.
  416. // But this could be a specific-type event that is in a namespace.
  417. if ( eventName.indexOf( ':' ) > -1 ) {
  418. // If the eventName is specific, try to find callback lists for more generic event.
  419. return getCallbacksForEvent( source, eventName.substr( 0, eventName.lastIndexOf( ':' ) ) );
  420. } else {
  421. // If this is a top-level generic event, return null;
  422. return null;
  423. }
  424. }
  425. return event.callbacks;
  426. }
  427. /**
  428. * Interface representing classes which mix in {@link utils.EmitterMixin}.
  429. *
  430. * @interface utils.Emitter
  431. */