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 /**
 28  * This is a base class for classes and objects that require event handling
 29  * features.
 30  * @constructor
 31  * @example
 32  */
 33 CKEDITOR.event = function()
 34 {
 35 	( this._ || ( this._ = {} ) ).events = {};
 36 };
 37
 38 /**
 39  * Implements the {@link CKEDITOR.event} features in an object.
 40  * @param {Object} targetObject The object in which implement the features.
 41  * @type undefined
 42  * @example
 43  * var myObject = { message : 'Example' };
 44  * <b>CKEDITOR.event.implementOn( myObject }</b>;
 45  * myObject.on( 'testevent', function()
 46  *     {
 47  *         alert( this.message );  // "Example"
 48  *     });
 49  * myObject.fire( 'testevent' );
 50  */
 51 CKEDITOR.event.implementOn = function( targetObject )
 52 {
 53 	CKEDITOR.event.call( targetObject );
 54
 55 	for ( var prop in CKEDITOR.event.prototype )
 56 	{
 57 		if ( targetObject[ prop ] == undefined )
 58 			targetObject[ prop ] = CKEDITOR.event.prototype[ prop ];
 59 	}
 60 };
 61
 62 CKEDITOR.event.prototype = (function()
 63 {
 64 	var eventEntry = function( eventName )
 65 	{
 66 		this.name = eventName;
 67 		this.listeners = [];
 68 	};
 69
 70 	eventEntry.prototype =
 71 	{
 72 		// Get the listener index for a specified function.
 73 		// Returns -1 if not found.
 74 		getListenerIndex : function( listenerFunction )
 75 		{
 76 			for ( var i = 0, listeners = this.listeners ; i < listeners.length ; i++ )
 77 			{
 78 				if ( listeners[i].fn == listenerFunction )
 79 					return i;
 80 			}
 81 			return -1;
 82 		}
 83 	};
 84
 85 	return /** @lends CKEDITOR.event.prototype */ {
 86 		/**
 87 		 * Registers a listener to a specific event in the current object.
 88 		 * @param {String} eventName The event name to which listen.
 89 		 * @param {Function} listenerFunction The function listening to the
 90 		 *		event.
 91 		 * @param {Object} [scopeObj] The object used to scope the listener
 92 		 *		call (the this object. If omitted, the current object is used.
 93 		 * @param {Object} [listenerData] Data to be sent as the
 94 		 *		{@link CKEDITOR.eventInfo#listenerData} when calling the
 95 		 *		listener.
 96 		 * @param {Number} [priority] The listener priority. Lower priority
 97 		 *		listeners are called first. Listeners with the same priority
 98 		 *		value are called in registration order. Defaults to 10.
 99 		 * @type undefined
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 		 * @type undefined
275 		 * @example
276 		 * var myListener = function() { ... };
277 		 * someObject.on( 'someevent', myListener );
278 		 * someObject.fire( 'someevent' );  // myListener called
279 		 * <b>someObject.removeListener( 'someevent', myListener )</b>;
280 		 * someObject.fire( 'someevent' );  // myListener not called
281 		 */
282 		removeListener : function( eventName, listenerFunction )
283 		{
284 			// Get the event entry.
285 			var event = this._.events[ eventName ];
286
287 			if ( event )
288 			{
289 				var index = event.getListenerIndex( listenerFunction );
290 				if ( index >= 0 )
291 					event.listeners.splice( index, 1 );
292 			}
293 		}
294 	};
295 })();
296