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