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