JasonWoof Got questions, comments, patches, etc.? Contact Jason Woofenden
vanilla ckeditor-3.1
[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.\r
81                          * @param {Object} [scopeObj] The object used to scope the listener\r
82                          *              call (the this object. If omitted, the current object is used.\r
83                          * @param {Object} [listenerData] Data to be sent as the\r
84                          *              {@link CKEDITOR.eventInfo#listenerData} when calling the\r
85                          *              listener.\r
86                          * @param {Number} [priority] The listener priority. Lower priority\r
87                          *              listeners are called first. Listeners with the same priority\r
88                          *              value are called in registration order. Defaults to 10.\r
89                          * @example\r
90                          * someObject.on( 'someEvent', function()\r
91                          *     {\r
92                          *         alert( this == someObject );  // "true"\r
93                          *     });\r
94                          * @example\r
95                          * someObject.on( 'someEvent', function()\r
96                          *     {\r
97                          *         alert( this == anotherObject );  // "true"\r
98                          *     }\r
99                          *     , anotherObject );\r
100                          * @example\r
101                          * someObject.on( 'someEvent', function( event )\r
102                          *     {\r
103                          *         alert( event.listenerData );  // "Example"\r
104                          *     }\r
105                          *     , null, 'Example' );\r
106                          * @example\r
107                          * someObject.on( 'someEvent', function() { ... } );                   // 2nd called\r
108                          * someObject.on( 'someEvent', function() { ... }, null, null, 100 );  // 3rd called\r
109                          * someObject.on( 'someEvent', function() { ... }, null, null, 1 );    // 1st called\r
110                          */\r
111                         on : function( eventName, listenerFunction, scopeObj, listenerData, priority )\r
112                         {\r
113                                 // Get the event entry (create it if needed).\r
114                                 var events = getPrivate( this ),\r
115                                         event = events[ eventName ] || ( events[ eventName ] = new eventEntry( eventName ) );\r
116 \r
117                                 if ( event.getListenerIndex( listenerFunction ) < 0 )\r
118                                 {\r
119                                         // Get the listeners.\r
120                                         var listeners = event.listeners;\r
121 \r
122                                         // Fill the scope.\r
123                                         if ( !scopeObj )\r
124                                                 scopeObj = this;\r
125 \r
126                                         // Default the priority, if needed.\r
127                                         if ( isNaN( priority ) )\r
128                                                 priority = 10;\r
129 \r
130                                         var me = this;\r
131 \r
132                                         // Create the function to be fired for this listener.\r
133                                         var listenerFirer = function( editor, publisherData, stopFn, cancelFn )\r
134                                         {\r
135                                                 var ev =\r
136                                                 {\r
137                                                         name : eventName,\r
138                                                         sender : this,\r
139                                                         editor : editor,\r
140                                                         data : publisherData,\r
141                                                         listenerData : listenerData,\r
142                                                         stop : stopFn,\r
143                                                         cancel : cancelFn,\r
144                                                         removeListener : function()\r
145                                                         {\r
146                                                                 me.removeListener( eventName, listenerFunction );\r
147                                                         }\r
148                                                 };\r
149 \r
150                                                 listenerFunction.call( scopeObj, ev );\r
151 \r
152                                                 return ev.data;\r
153                                         };\r
154                                         listenerFirer.fn = listenerFunction;\r
155                                         listenerFirer.priority = priority;\r
156 \r
157                                         // Search for the right position for this new listener, based on its\r
158                                         // priority.\r
159                                         for ( var i = listeners.length - 1 ; i >= 0 ; i-- )\r
160                                         {\r
161                                                 // Find the item which should be before the new one.\r
162                                                 if ( listeners[ i ].priority <= priority )\r
163                                                 {\r
164                                                         // Insert the listener in the array.\r
165                                                         listeners.splice( i + 1, 0, listenerFirer );\r
166                                                         return;\r
167                                                 }\r
168                                         }\r
169 \r
170                                         // If no position has been found (or zero length), put it in\r
171                                         // the front of list.\r
172                                         listeners.unshift( listenerFirer );\r
173                                 }\r
174                         },\r
175 \r
176                         /**\r
177                          * Fires an specific event in the object. All registered listeners are\r
178                          * called at this point.\r
179                          * @function\r
180                          * @param {String} eventName The event name to fire.\r
181                          * @param {Object} [data] Data to be sent as the\r
182                          *              {@link CKEDITOR.eventInfo#data} when calling the\r
183                          *              listeners.\r
184                          * @param {CKEDITOR.editor} [editor] The editor instance to send as the\r
185                          *              {@link CKEDITOR.eventInfo#editor} when calling the\r
186                          *              listener.\r
187                          * @returns {Boolean|Object} A booloan indicating that the event is to be\r
188                          *              canceled, or data returned by one of the listeners.\r
189                          * @example\r
190                          * someObject.on( 'someEvent', function() { ... } );\r
191                          * someObject.on( 'someEvent', function() { ... } );\r
192                          * <b>someObject.fire( 'someEvent' )</b>;  // both listeners are called\r
193                          * @example\r
194                          * someObject.on( 'someEvent', function( event )\r
195                          *     {\r
196                          *         alert( event.data );  // "Example"\r
197                          *     });\r
198                          * <b>someObject.fire( 'someEvent', 'Example' )</b>;\r
199                          */\r
200                         fire : (function()\r
201                         {\r
202                                 // Create the function that marks the event as stopped.\r
203                                 var stopped = false;\r
204                                 var stopEvent = function()\r
205                                 {\r
206                                         stopped = true;\r
207                                 };\r
208 \r
209                                 // Create the function that marks the event as canceled.\r
210                                 var canceled = false;\r
211                                 var cancelEvent = function()\r
212                                 {\r
213                                         canceled = true;\r
214                                 };\r
215 \r
216                                 return function( eventName, data, editor )\r
217                                 {\r
218                                         // Get the event entry.\r
219                                         var event = getPrivate( this )[ eventName ];\r
220 \r
221                                         // Save the previous stopped and cancelled states. We may\r
222                                         // be nesting fire() calls.\r
223                                         var previousStopped = stopped,\r
224                                                 previousCancelled = canceled;\r
225 \r
226                                         // Reset the stopped and canceled flags.\r
227                                         stopped = canceled = false;\r
228 \r
229                                         if ( event )\r
230                                         {\r
231                                                 var listeners = event.listeners;\r
232 \r
233                                                 if ( listeners.length )\r
234                                                 {\r
235                                                         // As some listeners may remove themselves from the\r
236                                                         // event, the original array length is dinamic. So,\r
237                                                         // let's make a copy of all listeners, so we are\r
238                                                         // sure we'll call all of them.\r
239                                                         listeners = listeners.slice( 0 );\r
240 \r
241                                                         // Loop through all listeners.\r
242                                                         for ( var i = 0 ; i < listeners.length ; i++ )\r
243                                                         {\r
244                                                                 // Call the listener, passing the event data.\r
245                                                                 var retData = listeners[i].call( this, editor, data, stopEvent, cancelEvent );\r
246 \r
247                                                                 if ( typeof retData != 'undefined' )\r
248                                                                         data = retData;\r
249 \r
250                                                                 // No further calls is stopped or canceled.\r
251                                                                 if ( stopped || canceled )\r
252                                                                         break;\r
253                                                         }\r
254                                                 }\r
255                                         }\r
256 \r
257                                         var ret = canceled || ( typeof data == 'undefined' ? false : data );\r
258 \r
259                                         // Restore the previous stopped and canceled states.\r
260                                         stopped = previousStopped;\r
261                                         canceled = previousCancelled;\r
262 \r
263                                         return ret;\r
264                                 };\r
265                         })(),\r
266 \r
267                         /**\r
268                          * Fires an specific event in the object, releasing all listeners\r
269                          * registered to that event. The same listeners are not called again on\r
270                          * successive calls of it or of {@link #fire}.\r
271                          * @param {String} eventName The event name to fire.\r
272                          * @param {Object} [data] Data to be sent as the\r
273                          *              {@link CKEDITOR.eventInfo#data} when calling the\r
274                          *              listeners.\r
275                          * @param {CKEDITOR.editor} [editor] The editor instance to send as the\r
276                          *              {@link CKEDITOR.eventInfo#editor} when calling the\r
277                          *              listener.\r
278                          * @returns {Boolean|Object} A booloan indicating that the event is to be\r
279                          *              canceled, or data returned by one of the listeners.\r
280                          * @example\r
281                          * someObject.on( 'someEvent', function() { ... } );\r
282                          * someObject.fire( 'someEvent' );  // above listener called\r
283                          * <b>someObject.fireOnce( 'someEvent' )</b>;  // above listener called\r
284                          * someObject.fire( 'someEvent' );  // no listeners called\r
285                          */\r
286                         fireOnce : function( eventName, data, editor )\r
287                         {\r
288                                 var ret = this.fire( eventName, data, editor );\r
289                                 delete getPrivate( this )[ eventName ];\r
290                                 return ret;\r
291                         },\r
292 \r
293                         /**\r
294                          * Unregisters a listener function from being called at the specified\r
295                          *              event. No errors are thrown if the listener has not been\r
296                          *              registered previously.\r
297                          * @param {String} eventName The event name.\r
298                          * @param {Function} listenerFunction The listener function to unregister.\r
299                          * @example\r
300                          * var myListener = function() { ... };\r
301                          * someObject.on( 'someEvent', myListener );\r
302                          * someObject.fire( 'someEvent' );  // myListener called\r
303                          * <b>someObject.removeListener( 'someEvent', myListener )</b>;\r
304                          * someObject.fire( 'someEvent' );  // myListener not called\r
305                          */\r
306                         removeListener : function( eventName, listenerFunction )\r
307                         {\r
308                                 // Get the event entry.\r
309                                 var event = getPrivate( this )[ eventName ];\r
310 \r
311                                 if ( event )\r
312                                 {\r
313                                         var index = event.getListenerIndex( listenerFunction );\r
314                                         if ( index >= 0 )\r
315                                                 event.listeners.splice( index, 1 );\r
316                                 }\r
317                         },\r
318 \r
319                         /**\r
320                          * Checks if there is any listener registered to a given event.\r
321                          * @param {String} eventName The event name.\r
322                          * @example\r
323                          * var myListener = function() { ... };\r
324                          * someObject.on( 'someEvent', myListener );\r
325                          * alert( someObject.<b>hasListeners( 'someEvent' )</b> );  // "true"\r
326                          * alert( someObject.<b>hasListeners( 'noEvent' )</b> );    // "false"\r
327                          */\r
328                         hasListeners : function( eventName )\r
329                         {\r
330                                 var event = getPrivate( this )[ eventName ];\r
331                                 return ( event && event.listeners.length > 0 ) ;\r
332                         }\r
333                 };\r
334         })();\r
335 }\r