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