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