event.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389
  1. /**
  2. * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md or http://ckeditor.com/license
  4. */
  5. /**
  6. * @fileOverview Defines the {@link CKEDITOR.event} class, which serves as the
  7. * base for classes and objects that require event handling features.
  8. */
  9. if ( !CKEDITOR.event ) {
  10. /**
  11. * Creates an event class instance. This constructor is rarely used, being
  12. * the {@link #implementOn} function used in class prototypes directly
  13. * instead.
  14. *
  15. * This is a base class for classes and objects that require event
  16. * handling features.
  17. *
  18. * Do not confuse this class with {@link CKEDITOR.dom.event} which is
  19. * instead used for DOM events. The CKEDITOR.event class implements the
  20. * internal event system used by the CKEditor to fire API related events.
  21. *
  22. * @class
  23. * @constructor Creates an event class instance.
  24. */
  25. CKEDITOR.event = function() {};
  26. /**
  27. * Implements the {@link CKEDITOR.event} features in an object.
  28. *
  29. * var myObject = { message: 'Example' };
  30. * CKEDITOR.event.implementOn( myObject );
  31. *
  32. * myObject.on( 'testEvent', function() {
  33. * alert( this.message );
  34. * } );
  35. * myObject.fire( 'testEvent' ); // 'Example'
  36. *
  37. * @static
  38. * @param {Object} targetObject The object into which implement the features.
  39. */
  40. CKEDITOR.event.implementOn = function( targetObject ) {
  41. var eventProto = CKEDITOR.event.prototype;
  42. for ( var prop in eventProto ) {
  43. if ( targetObject[ prop ] == null )
  44. targetObject[ prop ] = eventProto[ prop ];
  45. }
  46. };
  47. CKEDITOR.event.prototype = ( function() {
  48. // Returns the private events object for a given object.
  49. var getPrivate = function( obj ) {
  50. var _ = ( obj.getPrivate && obj.getPrivate() ) || obj._ || ( obj._ = {} );
  51. return _.events || ( _.events = {} );
  52. };
  53. var eventEntry = function( eventName ) {
  54. this.name = eventName;
  55. this.listeners = [];
  56. };
  57. eventEntry.prototype = {
  58. // Get the listener index for a specified function.
  59. // Returns -1 if not found.
  60. getListenerIndex: function( listenerFunction ) {
  61. for ( var i = 0, listeners = this.listeners; i < listeners.length; i++ ) {
  62. if ( listeners[ i ].fn == listenerFunction )
  63. return i;
  64. }
  65. return -1;
  66. }
  67. };
  68. // Retrieve the event entry on the event host (create it if needed).
  69. function getEntry( name ) {
  70. // Get the event entry (create it if needed).
  71. var events = getPrivate( this );
  72. return events[ name ] || ( events[ name ] = new eventEntry( name ) );
  73. }
  74. return {
  75. /**
  76. * Predefine some intrinsic properties on a specific event name.
  77. *
  78. * @param {String} name The event name
  79. * @param meta
  80. * @param [meta.errorProof=false] Whether the event firing should catch error thrown from a per listener call.
  81. */
  82. define: function( name, meta ) {
  83. var entry = getEntry.call( this, name );
  84. CKEDITOR.tools.extend( entry, meta, true );
  85. },
  86. /**
  87. * Registers a listener to a specific event in the current object.
  88. *
  89. * someObject.on( 'someEvent', function() {
  90. * alert( this == someObject ); // true
  91. * } );
  92. *
  93. * someObject.on( 'someEvent', function() {
  94. * alert( this == anotherObject ); // true
  95. * }, anotherObject );
  96. *
  97. * someObject.on( 'someEvent', function( event ) {
  98. * alert( event.listenerData ); // 'Example'
  99. * }, null, 'Example' );
  100. *
  101. * someObject.on( 'someEvent', function() { ... } ); // 2nd called
  102. * someObject.on( 'someEvent', function() { ... }, null, null, 100 ); // 3rd called
  103. * someObject.on( 'someEvent', function() { ... }, null, null, 1 ); // 1st called
  104. *
  105. * @param {String} eventName The event name to which listen.
  106. * @param {Function} listenerFunction The function listening to the
  107. * event. A single {@link CKEDITOR.eventInfo} object instanced
  108. * is passed to this function containing all the event data.
  109. * @param {Object} [scopeObj] The object used to scope the listener
  110. * call (the `this` object). If omitted, the current object is used.
  111. * @param {Object} [listenerData] Data to be sent as the
  112. * {@link CKEDITOR.eventInfo#listenerData} when calling the
  113. * listener.
  114. * @param {Number} [priority=10] The listener priority. Lower priority
  115. * listeners are called first. Listeners with the same priority
  116. * value are called in registration order.
  117. * @returns {Object} An object containing the `removeListener`
  118. * function, which can be used to remove the listener at any time.
  119. */
  120. on: function( eventName, listenerFunction, scopeObj, listenerData, priority ) {
  121. // Create the function to be fired for this listener.
  122. function listenerFirer( editor, publisherData, stopFn, cancelFn ) {
  123. var ev = {
  124. name: eventName,
  125. sender: this,
  126. editor: editor,
  127. data: publisherData,
  128. listenerData: listenerData,
  129. stop: stopFn,
  130. cancel: cancelFn,
  131. removeListener: removeListener
  132. };
  133. var ret = listenerFunction.call( scopeObj, ev );
  134. return ret === false ? false : ev.data;
  135. }
  136. function removeListener() {
  137. me.removeListener( eventName, listenerFunction );
  138. }
  139. var event = getEntry.call( this, eventName );
  140. if ( event.getListenerIndex( listenerFunction ) < 0 ) {
  141. // Get the listeners.
  142. var listeners = event.listeners;
  143. // Fill the scope.
  144. if ( !scopeObj )
  145. scopeObj = this;
  146. // Default the priority, if needed.
  147. if ( isNaN( priority ) )
  148. priority = 10;
  149. var me = this;
  150. listenerFirer.fn = listenerFunction;
  151. listenerFirer.priority = priority;
  152. // Search for the right position for this new listener, based on its
  153. // priority.
  154. for ( var i = listeners.length - 1; i >= 0; i-- ) {
  155. // Find the item which should be before the new one.
  156. if ( listeners[ i ].priority <= priority ) {
  157. // Insert the listener in the array.
  158. listeners.splice( i + 1, 0, listenerFirer );
  159. return { removeListener: removeListener };
  160. }
  161. }
  162. // If no position has been found (or zero length), put it in
  163. // the front of list.
  164. listeners.unshift( listenerFirer );
  165. }
  166. return { removeListener: removeListener };
  167. },
  168. /**
  169. * Similiar with {@link #on} but the listener will be called only once upon the next event firing.
  170. *
  171. * @see CKEDITOR.event#on
  172. */
  173. once: function() {
  174. var args = Array.prototype.slice.call( arguments ),
  175. fn = args[ 1 ];
  176. args[ 1 ] = function( evt ) {
  177. evt.removeListener();
  178. return fn.apply( this, arguments );
  179. };
  180. return this.on.apply( this, args );
  181. },
  182. /**
  183. * @static
  184. * @property {Boolean} useCapture
  185. * @todo
  186. */
  187. /**
  188. * Register event handler under the capturing stage on supported target.
  189. */
  190. capture: function() {
  191. CKEDITOR.event.useCapture = 1;
  192. var retval = this.on.apply( this, arguments );
  193. CKEDITOR.event.useCapture = 0;
  194. return retval;
  195. },
  196. /**
  197. * Fires an specific event in the object. All registered listeners are
  198. * called at this point.
  199. *
  200. * someObject.on( 'someEvent', function() { ... } );
  201. * someObject.on( 'someEvent', function() { ... } );
  202. * someObject.fire( 'someEvent' ); // Both listeners are called.
  203. *
  204. * someObject.on( 'someEvent', function( event ) {
  205. * alert( event.data ); // 'Example'
  206. * } );
  207. * someObject.fire( 'someEvent', 'Example' );
  208. *
  209. * @method
  210. * @param {String} eventName The event name to fire.
  211. * @param {Object} [data] Data to be sent as the
  212. * {@link CKEDITOR.eventInfo#data} when calling the listeners.
  213. * @param {CKEDITOR.editor} [editor] The editor instance to send as the
  214. * {@link CKEDITOR.eventInfo#editor} when calling the listener.
  215. * @returns {Boolean/Object} A boolean indicating that the event is to be
  216. * canceled, or data returned by one of the listeners.
  217. */
  218. fire: ( function() {
  219. // Create the function that marks the event as stopped.
  220. var stopped = 0;
  221. var stopEvent = function() {
  222. stopped = 1;
  223. };
  224. // Create the function that marks the event as canceled.
  225. var canceled = 0;
  226. var cancelEvent = function() {
  227. canceled = 1;
  228. };
  229. return function( eventName, data, editor ) {
  230. // Get the event entry.
  231. var event = getPrivate( this )[ eventName ];
  232. // Save the previous stopped and cancelled states. We may
  233. // be nesting fire() calls.
  234. var previousStopped = stopped,
  235. previousCancelled = canceled;
  236. // Reset the stopped and canceled flags.
  237. stopped = canceled = 0;
  238. if ( event ) {
  239. var listeners = event.listeners;
  240. if ( listeners.length ) {
  241. // As some listeners may remove themselves from the
  242. // event, the original array length is dinamic. So,
  243. // let's make a copy of all listeners, so we are
  244. // sure we'll call all of them.
  245. listeners = listeners.slice( 0 );
  246. var retData;
  247. // Loop through all listeners.
  248. for ( var i = 0; i < listeners.length; i++ ) {
  249. // Call the listener, passing the event data.
  250. if ( event.errorProof ) {
  251. try {
  252. retData = listeners[ i ].call( this, editor, data, stopEvent, cancelEvent );
  253. } catch ( er ) {}
  254. } else {
  255. retData = listeners[ i ].call( this, editor, data, stopEvent, cancelEvent );
  256. }
  257. if ( retData === false )
  258. canceled = 1;
  259. else if ( typeof retData != 'undefined' )
  260. data = retData;
  261. // No further calls is stopped or canceled.
  262. if ( stopped || canceled )
  263. break;
  264. }
  265. }
  266. }
  267. var ret = canceled ? false : ( typeof data == 'undefined' ? true : data );
  268. // Restore the previous stopped and canceled states.
  269. stopped = previousStopped;
  270. canceled = previousCancelled;
  271. return ret;
  272. };
  273. } )(),
  274. /**
  275. * Fires an specific event in the object, releasing all listeners
  276. * registered to that event. The same listeners are not called again on
  277. * successive calls of it or of {@link #fire}.
  278. *
  279. * someObject.on( 'someEvent', function() { ... } );
  280. * someObject.fire( 'someEvent' ); // Above listener called.
  281. * someObject.fireOnce( 'someEvent' ); // Above listener called.
  282. * someObject.fire( 'someEvent' ); // No listeners called.
  283. *
  284. * @param {String} eventName The event name to fire.
  285. * @param {Object} [data] Data to be sent as the
  286. * {@link CKEDITOR.eventInfo#data} when calling the listeners.
  287. * @param {CKEDITOR.editor} [editor] The editor instance to send as the
  288. * {@link CKEDITOR.eventInfo#editor} when calling the listener.
  289. * @returns {Boolean/Object} A booloan indicating that the event is to be
  290. * canceled, or data returned by one of the listeners.
  291. */
  292. fireOnce: function( eventName, data, editor ) {
  293. var ret = this.fire( eventName, data, editor );
  294. delete getPrivate( this )[ eventName ];
  295. return ret;
  296. },
  297. /**
  298. * Unregisters a listener function from being called at the specified
  299. * event. No errors are thrown if the listener has not been registered previously.
  300. *
  301. * var myListener = function() { ... };
  302. * someObject.on( 'someEvent', myListener );
  303. * someObject.fire( 'someEvent' ); // myListener called.
  304. * someObject.removeListener( 'someEvent', myListener );
  305. * someObject.fire( 'someEvent' ); // myListener not called.
  306. *
  307. * @param {String} eventName The event name.
  308. * @param {Function} listenerFunction The listener function to unregister.
  309. */
  310. removeListener: function( eventName, listenerFunction ) {
  311. // Get the event entry.
  312. var event = getPrivate( this )[ eventName ];
  313. if ( event ) {
  314. var index = event.getListenerIndex( listenerFunction );
  315. if ( index >= 0 )
  316. event.listeners.splice( index, 1 );
  317. }
  318. },
  319. /**
  320. * Remove all existing listeners on this object, for cleanup purpose.
  321. */
  322. removeAllListeners: function() {
  323. var events = getPrivate( this );
  324. for ( var i in events )
  325. delete events[ i ];
  326. },
  327. /**
  328. * Checks if there is any listener registered to a given event.
  329. *
  330. * var myListener = function() { ... };
  331. * someObject.on( 'someEvent', myListener );
  332. * alert( someObject.hasListeners( 'someEvent' ) ); // true
  333. * alert( someObject.hasListeners( 'noEvent' ) ); // false
  334. *
  335. * @param {String} eventName The event name.
  336. * @returns {Boolean}
  337. */
  338. hasListeners: function( eventName ) {
  339. var event = getPrivate( this )[ eventName ];
  340. return ( event && event.listeners.length > 0 );
  341. }
  342. };
  343. } )();
  344. }