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