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