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