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 	 * @type undefined
 44 	 * @example
 45 	 * var myObject = { message : 'Example' };
 46 	 * <b>CKEDITOR.event.implementOn( myObject }</b>;
 47 	 * myObject.on( 'testevent', function()
 48 	 *     {
 49 	 *         alert( this.message );  // "Example"
 50 	 *     });
 51 	 * myObject.fire( 'testevent' );
 52 	 */
 53 	CKEDITOR.event.implementOn = function( targetObject )
 54 	{
 55 		CKEDITOR.event.call( targetObject );
 56
 57 		for ( var prop in CKEDITOR.event.prototype )
 58 		{
 59 			if ( targetObject[ prop ] == undefined )
 60 				targetObject[ prop ] = CKEDITOR.event.prototype[ prop ];
 61 		}
 62 	};
 63
 64 	CKEDITOR.event.prototype = (function()
 65 	{
 66 		var eventEntry = function( eventName )
 67 		{
 68 			this.name = eventName;
 69 			this.listeners = [];
 70 		};
 71
 72 		eventEntry.prototype =
 73 		{
 74 			// Get the listener index for a specified function.
 75 			// Returns -1 if not found.
 76 			getListenerIndex : function( listenerFunction )
 77 			{
 78 				for ( var i = 0, listeners = this.listeners ; i < listeners.length ; i++ )
 79 				{
 80 					if ( listeners[i].fn == listenerFunction )
 81 						return i;
 82 				}
 83 				return -1;
 84 			}
 85 		};
 86
 87 		return /** @lends CKEDITOR.event.prototype */ {
 88 			/**
 89 			 * Registers a listener to a specific event in the current object.
 90 			 * @param {String} eventName The event name to which listen.
 91 			 * @param {Function} listenerFunction The function listening to the
 92 			 *		event.
 93 			 * @param {Object} [scopeObj] The object used to scope the listener
 94 			 *		call (the this object. If omitted, the current object is used.
 95 			 * @param {Object} [listenerData] Data to be sent as the
 96 			 *		{@link CKEDITOR.eventInfo#listenerData} when calling the
 97 			 *		listener.
 98 			 * @param {Number} [priority] The listener priority. Lower priority
 99 			 *		listeners are called first. Listeners with the same priority
100 			 *		value are called in registration order. Defaults to 10.
101 			 * @type undefined
102 			 * @example
103 			 * someObject.on( 'someevent', function()
104 			 *     {
105 			 *         alert( this == someObject );  // "true"
106 			 *     });
107 			 * @example
108 			 * someObject.on( 'someevent', function()
109 			 *     {
110 			 *         alert( this == anotherObject );  // "true"
111 			 *     }
112 			 *     , anotherObject );
113 			 * @example
114 			 * someObject.on( 'someevent', function( event )
115 			 *     {
116 			 *         alert( event.listenerData );  // "Example"
117 			 *     }
118 			 *     , null, 'Example' );
119 			 * @example
120 			 * someObject.on( 'someevent', function() { ... } );                   // 2nd called
121 			 * someObject.on( 'someevent', function() { ... }, null, null, 100 );  // 3rd called
122 			 * someObject.on( 'someevent', function() { ... }, null, null, 1 );    // 1st called
123 			 */
124 			on  : function( eventName, listenerFunction, scopeObj, listenerData, priority )
125 			{
126 				// Get the event entry (create it if needed).
127 				var event = this._.events[ eventName ] || ( this._.events[ eventName ] = new eventEntry( eventName ) );
128
129 				if ( event.getListenerIndex( listenerFunction ) < 0 )
130 				{
131 					// Get the listeners.
132 					var listeners = event.listeners;
133
134 					// Fill the scope.
135 					if ( !scopeObj )
136 						scopeObj = this;
137
138 					// Default the priority, if needed.
139 					if ( isNaN( priority ) )
140 						priority = 10;
141
142 					// Create the function to be fired for this listener.
143 					var listenerFirer = function( editor, publisherData, stopFn, cancelFn )
144 					{
145 						var ev =
146 						{
147 							name : eventName,
148 							sender : this,
149 							editor : editor,
150 							data : publisherData,
151 							listenerData : listenerData,
152 							stop : stopFn,
153 							cancel : cancelFn
154 						};
155
156 						listenerFunction.call( scopeObj, ev );
157
158 						return ev.data;
159 					};
160 					listenerFirer.fn = listenerFunction;
161 					listenerFirer.priority = priority;
162
163 					// Search for the right position for this new listener, based on its
164 					// priority.
165 					for ( var i = listeners.length - 1 ; i >= 0 ; i-- )
166 					{
167 						// Find the item which should be before the new one.
168 						if ( listeners[ i ].priority <= priority )
169 						{
170 							// Insert the listener in the array.
171 							listeners.splice( i + 1, 0, listenerFirer );
172 							return;
173 						}
174 					}
175
176 					// If no position has been found (or zero length), put it in
177 					// the front of list.
178 					listeners.unshift( listenerFirer );
179 				}
180 			},
181
182 			/**
183 			 * Fires an specific event in the object. All registered listeners are
184 			 * called at this point.
185 			 * @param {String} eventName The event name to fire.
186 			 * @param {Object} [data] Data to be sent as the
187 			 *		{@link CKEDITOR.eventInfo#data} when calling the
188 			 *		listeners.
189 			 * @param {CKEDITOR.editor} [editor] The editor instance to send as the
190 			 *		{@link CKEDITOR.eventInfo#editor} when calling the
191 			 *		listener.
192 			 * @returns {Boolean|Object} A booloan indicating that the event is to be
193 			 *		cancelled, or data returned by one of the listeners.
194 			 * @example
195 			 * someObject.on( 'someevent', function() { ... } );
196 			 * someObject.on( 'someevent', function() { ... } );
197 			 * <b>someObject.fire( 'someevent' )</b>;  // both listeners are called
198 			 * @example
199 			 * someObject.on( 'someevent', function( event )
200 			 *     {
201 			 *         alert( event.data );  // "Example"
202 			 *     });
203 			 * <b>someObject.fire( 'someevent', 'Example' )</b>;
204 			 */
205 			fire : function( eventName, data, editor )
206 			{
207 				// Create the function that marks the event as stopped.
208 				var stopped = false;
209 				var stopEvent = function()
210 				{
211 					stopped = true;
212 				};
213
214 				// Create the function that marks the event as cancelled.
215 				var cancelled = false;
216 				var cancelEvent = function()
217 				{
218 					cancelled = true;
219 				};
220
221 				// Get the event entry.
222 				var event = this._.events[ eventName ];
223
224 				if ( event )
225 				{
226 					// Loop through all listeners.
227 					for ( var i = 0, listeners = event.listeners ; i < listeners.length ; i++ )
228 					{
229 						// Call the listener, passing the event data.
230 						var retData = listeners[i].call( this, editor, data, stopEvent, cancelEvent );
231
232 						if ( typeof retData != 'undefined' )
233 							data = retData;
234
235 						// No further calls is stopped or cancelled.
236 						if ( stopped || cancelled )
237 							break;
238 					}
239 				}
240
241 				return cancelled || ( typeof data == 'undefined' ? false : data );
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 			 *		cancelled, 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 			 * @type undefined
277 			 * @example
278 			 * var myListener = function() { ... };
279 			 * someObject.on( 'someevent', myListener );
280 			 * someObject.fire( 'someevent' );  // myListener called
281 			 * <b>someObject.removeListener( 'someevent', myListener )</b>;
282 			 * someObject.fire( 'someevent' );  // myListener not called
283 			 */
284 			removeListener : function( eventName, listenerFunction )
285 			{
286 				// Get the event entry.
287 				var event = this._.events[ eventName ];
288
289 				if ( event )
290 				{
291 					var index = event.getListenerIndex( listenerFunction );
292 					if ( index >= 0 )
293 						event.listeners.splice( index, 1 );
294 				}
295 			}
296 		};
297 	})();
298 }
299