emittermixin.js 18 KB

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