fa19060376276b950637ab37cad04222c7263cd2
[ckeditor.git] / skins / ckeditor / _source / core / event.js
1 /*
2 Copyright (c) 2003-2011, 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 * Creates an event class instance. This constructor is rearely used, being
15 * the {@link #.implementOn} function used in class prototypes directly
16 * instead.
17 * @class This is a base class for classes and objects that require event
18 * handling features.<br />
19 * <br />
20 * Do not confuse this class with {@link CKEDITOR.dom.event} which is
21 * instead used for DOM events. The CKEDITOR.event class implements the
22 * internal event system used by the CKEditor to fire API related events.
23 * @example
24 */
25 CKEDITOR.event = function()
26 {};
27
28 /**
29 * Implements the {@link CKEDITOR.event} features in an object.
30 * @param {Object} targetObject The object into which implement the features.
31 * @example
32 * var myObject = { message : 'Example' };
33 * <b>CKEDITOR.event.implementOn( myObject }</b>;
34 * myObject.on( 'testEvent', function()
35 * {
36 * alert( this.message ); // "Example"
37 * });
38 * myObject.fire( 'testEvent' );
39 */
40 CKEDITOR.event.implementOn = function( targetObject )
41 {
42 var eventProto = CKEDITOR.event.prototype;
43
44 for ( var prop in eventProto )
45 {
46 if ( targetObject[ prop ] == undefined )
47 targetObject[ prop ] = eventProto[ prop ];
48 }
49 };
50
51 CKEDITOR.event.prototype = (function()
52 {
53 // Returns the private events object for a given object.
54 var getPrivate = function( obj )
55 {
56 var _ = ( obj.getPrivate && obj.getPrivate() ) || obj._ || ( obj._ = {} );
57 return _.events || ( _.events = {} );
58 };
59
60 var eventEntry = function( eventName )
61 {
62 this.name = eventName;
63 this.listeners = [];
64 };
65
66 eventEntry.prototype =
67 {
68 // Get the listener index for a specified function.
69 // Returns -1 if not found.
70 getListenerIndex : function( listenerFunction )
71 {
72 for ( var i = 0, listeners = this.listeners ; i < listeners.length ; i++ )
73 {
74 if ( listeners[i].fn == listenerFunction )
75 return i;
76 }
77 return -1;
78 }
79 };
80
81 return /** @lends CKEDITOR.event.prototype */ {
82 /**
83 * Registers a listener to a specific event in the current object.
84 * @param {String} eventName The event name to which listen.
85 * @param {Function} listenerFunction The function listening to the
86 * event. A single {@link CKEDITOR.eventInfo} object instanced
87 * is passed to this function containing all the event data.
88 * @param {Object} [scopeObj] The object used to scope the listener
89 * call (the this object. If omitted, the current object is used.
90 * @param {Object} [listenerData] Data to be sent as the
91 * {@link CKEDITOR.eventInfo#listenerData} when calling the
92 * listener.
93 * @param {Number} [priority] The listener priority. Lower priority
94 * listeners are called first. Listeners with the same priority
95 * value are called in registration order. Defaults to 10.
96 * @example
97 * someObject.on( 'someEvent', function()
98 * {
99 * alert( this == someObject ); // "true"
100 * });
101 * @example
102 * someObject.on( 'someEvent', function()
103 * {
104 * alert( this == anotherObject ); // "true"
105 * }
106 * , anotherObject );
107 * @example
108 * someObject.on( 'someEvent', function( event )
109 * {
110 * alert( event.listenerData ); // "Example"
111 * }
112 * , null, 'Example' );
113 * @example
114 * someObject.on( 'someEvent', function() { ... } ); // 2nd called
115 * someObject.on( 'someEvent', function() { ... }, null, null, 100 ); // 3rd called
116 * someObject.on( 'someEvent', function() { ... }, null, null, 1 ); // 1st called
117 */
118 on : function( eventName, listenerFunction, scopeObj, listenerData, priority )
119 {
120 // Get the event entry (create it if needed).
121 var events = getPrivate( this ),
122 event = events[ eventName ] || ( events[ eventName ] = new eventEntry( eventName ) );
123
124 if ( event.getListenerIndex( listenerFunction ) < 0 )
125 {
126 // Get the listeners.
127 var listeners = event.listeners;
128
129 // Fill the scope.
130 if ( !scopeObj )
131 scopeObj = this;
132
133 // Default the priority, if needed.
134 if ( isNaN( priority ) )
135 priority = 10;
136
137 var me = this;
138
139 // Create the function to be fired for this listener.
140 var listenerFirer = function( editor, publisherData, stopFn, cancelFn )
141 {
142 var ev =
143 {
144 name : eventName,
145 sender : this,
146 editor : editor,
147 data : publisherData,
148 listenerData : listenerData,
149 stop : stopFn,
150 cancel : cancelFn,
151 removeListener : function()
152 {
153 me.removeListener( eventName, listenerFunction );
154 }
155 };
156
157 listenerFunction.call( scopeObj, ev );
158
159 return ev.data;
160 };
161 listenerFirer.fn = listenerFunction;
162 listenerFirer.priority = priority;
163
164 // Search for the right position for this new listener, based on its
165 // priority.
166 for ( var i = listeners.length - 1 ; i >= 0 ; i-- )
167 {
168 // Find the item which should be before the new one.
169 if ( listeners[ i ].priority <= priority )
170 {
171 // Insert the listener in the array.
172 listeners.splice( i + 1, 0, listenerFirer );
173 return;
174 }
175 }
176
177 // If no position has been found (or zero length), put it in
178 // the front of list.
179 listeners.unshift( listenerFirer );
180 }
181 },
182
183 /**
184 * Fires an specific event in the object. All registered listeners are
185 * called at this point.
186 * @function
187 * @param {String} eventName The event name to fire.
188 * @param {Object} [data] Data to be sent as the
189 * {@link CKEDITOR.eventInfo#data} when calling the
190 * listeners.
191 * @param {CKEDITOR.editor} [editor] The editor instance to send as the
192 * {@link CKEDITOR.eventInfo#editor} when calling the
193 * listener.
194 * @returns {Boolean|Object} A booloan indicating that the event is to be
195 * canceled, or data returned by one of the listeners.
196 * @example
197 * someObject.on( 'someEvent', function() { ... } );
198 * someObject.on( 'someEvent', function() { ... } );
199 * <b>someObject.fire( 'someEvent' )</b>; // both listeners are called
200 * @example
201 * someObject.on( 'someEvent', function( event )
202 * {
203 * alert( event.data ); // "Example"
204 * });
205 * <b>someObject.fire( 'someEvent', 'Example' )</b>;
206 */
207 fire : (function()
208 {
209 // Create the function that marks the event as stopped.
210 var stopped = false;
211 var stopEvent = function()
212 {
213 stopped = true;
214 };
215
216 // Create the function that marks the event as canceled.
217 var canceled = false;
218 var cancelEvent = function()
219 {
220 canceled = true;
221 };
222
223 return function( eventName, data, editor )
224 {
225 // Get the event entry.
226 var event = getPrivate( this )[ eventName ];
227
228 // Save the previous stopped and cancelled states. We may
229 // be nesting fire() calls.
230 var previousStopped = stopped,
231 previousCancelled = canceled;
232
233 // Reset the stopped and canceled flags.
234 stopped = canceled = false;
235
236 if ( event )
237 {
238 var listeners = event.listeners;
239
240 if ( listeners.length )
241 {
242 // As some listeners may remove themselves from the
243 // event, the original array length is dinamic. So,
244 // let's make a copy of all listeners, so we are
245 // sure we'll call all of them.
246 listeners = listeners.slice( 0 );
247
248 // Loop through all listeners.
249 for ( var i = 0 ; i < listeners.length ; i++ )
250 {
251 // Call the listener, passing the event data.
252 var retData = listeners[i].call( this, editor, data, stopEvent, cancelEvent );
253
254 if ( typeof retData != 'undefined' )
255 data = retData;
256
257 // No further calls is stopped or canceled.
258 if ( stopped || canceled )
259 break;
260 }
261 }
262 }
263
264 var ret = canceled || ( typeof data == 'undefined' ? false : data );
265
266 // Restore the previous stopped and canceled states.
267 stopped = previousStopped;
268 canceled = previousCancelled;
269
270 return ret;
271 };
272 })(),
273
274 /**
275 * Fires an specific event in the object, releasing all listeners
276 * registered to that event. The same listeners are not called again on
277 * successive calls of it or of {@link #fire}.
278 * @param {String} eventName The event name to fire.
279 * @param {Object} [data] Data to be sent as the
280 * {@link CKEDITOR.eventInfo#data} when calling the
281 * listeners.
282 * @param {CKEDITOR.editor} [editor] The editor instance to send as the
283 * {@link CKEDITOR.eventInfo#editor} when calling the
284 * listener.
285 * @returns {Boolean|Object} A booloan indicating that the event is to be
286 * canceled, or data returned by one of the listeners.
287 * @example
288 * someObject.on( 'someEvent', function() { ... } );
289 * someObject.fire( 'someEvent' ); // above listener called
290 * <b>someObject.fireOnce( 'someEvent' )</b>; // above listener called
291 * someObject.fire( 'someEvent' ); // no listeners called
292 */
293 fireOnce : function( eventName, data, editor )
294 {
295 var ret = this.fire( eventName, data, editor );
296 delete getPrivate( this )[ eventName ];
297 return ret;
298 },
299
300 /**
301 * Unregisters a listener function from being called at the specified
302 * event. No errors are thrown if the listener has not been
303 * registered previously.
304 * @param {String} eventName The event name.
305 * @param {Function} listenerFunction The listener function to unregister.
306 * @example
307 * var myListener = function() { ... };
308 * someObject.on( 'someEvent', myListener );
309 * someObject.fire( 'someEvent' ); // myListener called
310 * <b>someObject.removeListener( 'someEvent', myListener )</b>;
311 * someObject.fire( 'someEvent' ); // myListener not called
312 */
313 removeListener : function( eventName, listenerFunction )
314 {
315 // Get the event entry.
316 var event = getPrivate( this )[ eventName ];
317
318 if ( event )
319 {
320 var index = event.getListenerIndex( listenerFunction );
321 if ( index >= 0 )
322 event.listeners.splice( index, 1 );
323 }
324 },
325
326 /**
327 * Checks if there is any listener registered to a given event.
328 * @param {String} eventName The event name.
329 * @example
330 * var myListener = function() { ... };
331 * someObject.on( 'someEvent', myListener );
332 * alert( someObject.<b>hasListeners( 'someEvent' )</b> ); // "true"
333 * alert( someObject.<b>hasListeners( 'noEvent' )</b> ); // "false"
334 */
335 hasListeners : function( eventName )
336 {
337 var event = getPrivate( this )[ eventName ];
338 return ( event && event.listeners.length > 0 ) ;
339 }
340 };
341 })();
342 }