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