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