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  * This is a base class for classes and objects that require event handling
 24  * features.
 25  * @constructor
 26  * @example
 27  */
 28 CKEDITOR.event = function()
 29 {
 30 	( this._ || ( this._ = {} ) ).events = {};
 31 };
 32
 33 CKEDITOR.event.implementOn = function( targetObject )
 34 {
 35 	CKEDITOR.event.call( targetObject );
 36
 37 	for ( var prop in CKEDITOR.event.prototype )
 38 	{
 39 		if ( targetObject[ prop ] == undefined )
 40 			targetObject[ prop ] = CKEDITOR.event.prototype[ prop ];
 41 	}
 42 };
 43
 44 CKEDITOR.event.prototype = (function()
 45 {
 46 	var eventEntry = function( eventName )
 47 	{
 48 		this.name = eventName;
 49 		this.listeners = [];
 50 	};
 51
 52 	eventEntry.prototype =
 53 	{
 54 		// Get the listener index for a specified function.
 55 		// Returns -1 if not found.
 56 		getListenerIndex : function( listenerFunction )
 57 		{
 58 			for ( var i = 0, listeners = this.listeners ; i < listeners.length ; i++ )
 59 			{
 60 				if ( listeners[i].fn == listenerFunction )
 61 					return i;
 62 			}
 63 			return -1;
 64 		}
 65 	};
 66
 67 	return /** @lends CKEDITOR.event.prototype */ {
 68 		on  : function( eventName, listenerFunction, scopeObj, listenerData, priority )
 69 		{
 70 			// Get the event entry (create it if needed).
 71 			var event = this._.events[ eventName ] || ( this._.events[ eventName ] = new eventEntry( eventName ) );
 72
 73 			if ( event.getListenerIndex( listenerFunction ) < 0 )
 74 			{
 75 				// Get the listeners.
 76 				var listeners = event.listeners;
 77
 78 				// Fill the scope.
 79 				if ( !scopeObj )
 80 					scopeObj = this;
 81
 82 				// Default the priority, if needed.
 83 				if ( isNaN( priority ) )
 84 					priority = 10;
 85
 86 				// Create the function to be fired for this listener.
 87 				var listenerFirer = function( editor, publisherData, stopFn, cancelFn )
 88 				{
 89 					var ev =
 90 					{
 91 						name : eventName,
 92 						sender : this,
 93 						editor : editor,
 94 						data : publisherData,
 95 						listenerData : listenerData,
 96 						stop : stopFn,
 97 						cancel : cancelFn
 98 					};
 99
100 					listenerFunction.call( scopeObj, ev );
101
102 					return ev.data;
103 				};
104 				listenerFirer.fn = listenerFunction;
105 				listenerFirer.priority = priority;
106
107 				// Search for the right position for this new listener, based on its
108 				// priority.
109 				for ( var i = listeners.length - 1 ; i >= 0 ; i-- )
110 				{
111 					// Find the item which should be before the new one.
112 					if ( listeners[ i ].priority <= priority )
113 					{
114 						// Insert the listener in the array.
115 						listeners.splice( i + 1, 0, listenerFirer );
116 						return;
117 					}
118 				}
119
120 				// If no position has been found (or zero length), put it in
121 				// the front of list.
122 				listeners.unshift( listenerFirer );
123 			}
124 		},
125
126 		fire : function( eventName, data, editor )
127 		{
128 			// Create the function that marks the event as stopped.
129 			var stopped = false;
130 			var stopEvent = function()
131 			{
132 				stopped = true;
133 			};
134
135 			// Create the function that marks the event as cancelled.
136 			var cancelled = false;
137 			var cancelEvent = function()
138 			{
139 				cancelled = true;
140 			};
141
142 			// Get the event entry.
143 			var event = this._.events[ eventName ];
144
145 			if ( event )
146 			{
147 				// Loop through all listeners.
148 				for ( var i = 0, listeners = event.listeners ; i < listeners.length ; i++ )
149 				{
150 					// Call the listener, passing the event data.
151 					var retData = listeners[i].call( this, editor, data, stopEvent, cancelEvent );
152
153 					if ( typeof retData != 'undefined' )
154 						data = retData;
155
156 					// No further calls is stopped or cancelled.
157 					if ( stopped || cancelled )
158 						break;
159 				}
160 			}
161
162 			return cancelled || ( typeof data == 'undefined' ? false : data );
163 		},
164
165 		fireOnce : function( eventName, data, editor )
166 		{
167 			var ret = this.fire( eventName, data, editor );
168 			delete this._.events[ eventName ];
169 			return ret;
170 		},
171
172 		removeListener : function( eventName, listenerFunction )
173 		{
174 			// Get the event entry.
175 			var event = this._.events[ eventName ];
176
177 			if ( event )
178 			{
179 				var index = event.getListenerIndex( listenerFunction );
180 				if ( index >= 0 )
181 					event.listeners.splice( index, 1 );
182 			}
183 		}
184 	};
185 })();
186
187 // The following is for documentation purposes only. It documents a virtual
188 // CKEDITOR.eventInfo class that contains the defintions of event object passed
189 // to event listeners.
190
191 /**
192  * This class is not really part of the API. It just illustrates the features
193  * of the event object passed to event listeners by a {@link CKEDITOR.event}
194  * based object.
195  * @name CKEDITOR.eventInfo
196  * @constructor
197  */
198
199 /**
200  * The event name.
201  * @name CKEDITOR.eventInfo.prototype.name
202  * @field
203  * @type String
204  */
205
206 /**
207  * The object that publishes (sends) the event.
208  * @name CKEDITOR.eventInfo.prototype.sender
209  * @field
210  * @type Object
211  */
212
213 /**
214  * The editor instance that holds the sender. May be the same as sender. May be
215  * null if the sender is not part of an editor instance, like a component
216  * running in standalone mode.
217  * @name CKEDITOR.eventInfo.prototype.editor
218  * @field
219  * @type CKEDITOR.editor
220  */
221
222 /**
223  * Any kind of additional data. Its format and usage is event dependent.
224  * @name CKEDITOR.eventInfo.prototype.data
225  * @field
226  * @type Object
227  */
228
229 /**
230  * Any extra data appended during the listener registration.
231  * @name CKEDITOR.eventInfo.prototype.listenerData
232  * @field
233  * @type Object
234  */
235
236 /**
237  * Indicates that no further listeners are to be called.
238  * @name CKEDITOR.eventInfo.prototype.stop
239  * @function
240  */
241
242 /**
243  * Indicates that the event is to be cancelled (if cancelable).
244  * @name CKEDITOR.eventInfo.prototype.cancel
245  * @function
246  */
247