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