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