emittermixin.js 21 KB

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