]>
Commit | Line | Data |
---|---|---|
3332bebe | 1 | /** |
317f8f8f | 2 | * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved. |
3332bebe IB |
3 | * For licensing, see LICENSE.md 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 | * Creates an event class instance. This constructor is rarely used, being | |
14 | * the {@link #implementOn} function used in class prototypes directly | |
15 | * instead. | |
16 | * | |
17 | * This is a base class for classes and objects that require event | |
18 | * handling features. | |
19 | * | |
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 | * | |
24 | * @class | |
25 | * @constructor Creates an event class instance. | |
26 | */ | |
27 | CKEDITOR.event = function() {}; | |
28 | ||
29 | /** | |
30 | * Implements the {@link CKEDITOR.event} features in an object. | |
31 | * | |
32 | * var myObject = { message: 'Example' }; | |
33 | * CKEDITOR.event.implementOn( myObject ); | |
34 | * | |
35 | * myObject.on( 'testEvent', function() { | |
36 | * alert( this.message ); | |
37 | * } ); | |
38 | * myObject.fire( 'testEvent' ); // 'Example' | |
39 | * | |
40 | * @static | |
41 | * @param {Object} targetObject The object into which implement the features. | |
42 | */ | |
43 | CKEDITOR.event.implementOn = function( targetObject ) { | |
44 | var eventProto = CKEDITOR.event.prototype; | |
45 | ||
46 | for ( var prop in eventProto ) { | |
47 | if ( targetObject[ prop ] == null ) | |
48 | targetObject[ prop ] = eventProto[ prop ]; | |
49 | } | |
50 | }; | |
51 | ||
52 | CKEDITOR.event.prototype = ( function() { | |
53 | // Returns the private events object for a given object. | |
54 | var getPrivate = function( obj ) { | |
55 | var _ = ( obj.getPrivate && obj.getPrivate() ) || obj._ || ( obj._ = {} ); | |
56 | return _.events || ( _.events = {} ); | |
57 | }; | |
58 | ||
59 | var eventEntry = function( eventName ) { | |
60 | this.name = eventName; | |
61 | this.listeners = []; | |
62 | }; | |
63 | ||
64 | eventEntry.prototype = { | |
65 | // Get the listener index for a specified function. | |
66 | // Returns -1 if not found. | |
67 | getListenerIndex: function( listenerFunction ) { | |
68 | for ( var i = 0, listeners = this.listeners; i < listeners.length; i++ ) { | |
69 | if ( listeners[ i ].fn == listenerFunction ) | |
70 | return i; | |
71 | } | |
72 | return -1; | |
73 | } | |
74 | }; | |
75 | ||
76 | // Retrieve the event entry on the event host (create it if needed). | |
77 | function getEntry( name ) { | |
78 | // Get the event entry (create it if needed). | |
79 | var events = getPrivate( this ); | |
80 | return events[ name ] || ( events[ name ] = new eventEntry( name ) ); | |
81 | } | |
82 | ||
83 | return { | |
84 | /** | |
85 | * Predefine some intrinsic properties on a specific event name. | |
86 | * | |
87 | * @param {String} name The event name | |
88 | * @param meta | |
89 | * @param [meta.errorProof=false] Whether the event firing should catch error thrown from a per listener call. | |
90 | */ | |
91 | define: function( name, meta ) { | |
92 | var entry = getEntry.call( this, name ); | |
93 | CKEDITOR.tools.extend( entry, meta, true ); | |
94 | }, | |
95 | ||
96 | /** | |
97 | * Registers a listener to a specific event in the current object. | |
98 | * | |
99 | * someObject.on( 'someEvent', function() { | |
100 | * alert( this == someObject ); // true | |
101 | * } ); | |
102 | * | |
103 | * someObject.on( 'someEvent', function() { | |
104 | * alert( this == anotherObject ); // true | |
105 | * }, anotherObject ); | |
106 | * | |
107 | * someObject.on( 'someEvent', function( event ) { | |
108 | * alert( event.listenerData ); // 'Example' | |
109 | * }, null, 'Example' ); | |
110 | * | |
111 | * someObject.on( 'someEvent', function() { ... } ); // 2nd called | |
112 | * someObject.on( 'someEvent', function() { ... }, null, null, 100 ); // 3rd called | |
113 | * someObject.on( 'someEvent', function() { ... }, null, null, 1 ); // 1st called | |
114 | * | |
115 | * @param {String} eventName The event name to which listen. | |
116 | * @param {Function} listenerFunction The function listening to the | |
117 | * event. A single {@link CKEDITOR.eventInfo} object instanced | |
118 | * is passed to this function containing all the event data. | |
119 | * @param {Object} [scopeObj] The object used to scope the listener | |
120 | * call (the `this` object). If omitted, the current object is used. | |
121 | * @param {Object} [listenerData] Data to be sent as the | |
122 | * {@link CKEDITOR.eventInfo#listenerData} when calling the | |
123 | * listener. | |
124 | * @param {Number} [priority=10] The listener priority. Lower priority | |
125 | * listeners are called first. Listeners with the same priority | |
126 | * value are called in registration order. | |
127 | * @returns {Object} An object containing the `removeListener` | |
128 | * function, which can be used to remove the listener at any time. | |
129 | */ | |
130 | on: function( eventName, listenerFunction, scopeObj, listenerData, priority ) { | |
131 | // Create the function to be fired for this listener. | |
132 | function listenerFirer( editor, publisherData, stopFn, cancelFn ) { | |
133 | var ev = { | |
134 | name: eventName, | |
135 | sender: this, | |
136 | editor: editor, | |
137 | data: publisherData, | |
138 | listenerData: listenerData, | |
139 | stop: stopFn, | |
140 | cancel: cancelFn, | |
141 | removeListener: removeListener | |
142 | }; | |
143 | ||
144 | var ret = listenerFunction.call( scopeObj, ev ); | |
145 | ||
146 | return ret === false ? false : ev.data; | |
147 | } | |
148 | ||
149 | function removeListener() { | |
150 | me.removeListener( eventName, listenerFunction ); | |
151 | } | |
152 | ||
153 | var event = getEntry.call( this, eventName ); | |
154 | ||
155 | if ( event.getListenerIndex( listenerFunction ) < 0 ) { | |
156 | // Get the listeners. | |
157 | var listeners = event.listeners; | |
158 | ||
159 | // Fill the scope. | |
160 | if ( !scopeObj ) | |
161 | scopeObj = this; | |
162 | ||
163 | // Default the priority, if needed. | |
164 | if ( isNaN( priority ) ) | |
165 | priority = 10; | |
166 | ||
167 | var me = this; | |
168 | ||
169 | listenerFirer.fn = listenerFunction; | |
170 | listenerFirer.priority = priority; | |
171 | ||
172 | // Search for the right position for this new listener, based on its | |
173 | // priority. | |
174 | for ( var i = listeners.length - 1; i >= 0; i-- ) { | |
175 | // Find the item which should be before the new one. | |
176 | if ( listeners[ i ].priority <= priority ) { | |
177 | // Insert the listener in the array. | |
178 | listeners.splice( i + 1, 0, listenerFirer ); | |
179 | return { removeListener: removeListener }; | |
180 | } | |
181 | } | |
182 | ||
183 | // If no position has been found (or zero length), put it in | |
184 | // the front of list. | |
185 | listeners.unshift( listenerFirer ); | |
186 | } | |
187 | ||
188 | return { removeListener: removeListener }; | |
189 | }, | |
190 | ||
191 | /** | |
192 | * Similiar with {@link #on} but the listener will be called only once upon the next event firing. | |
193 | * | |
194 | * @see CKEDITOR.event#on | |
195 | */ | |
196 | once: function() { | |
197 | var args = Array.prototype.slice.call( arguments ), | |
198 | fn = args[ 1 ]; | |
199 | ||
200 | args[ 1 ] = function( evt ) { | |
201 | evt.removeListener(); | |
202 | return fn.apply( this, arguments ); | |
203 | }; | |
204 | ||
205 | return this.on.apply( this, args ); | |
206 | }, | |
207 | ||
208 | /** | |
209 | * @static | |
210 | * @property {Boolean} useCapture | |
211 | * @todo | |
212 | */ | |
213 | ||
214 | /** | |
215 | * Register event handler under the capturing stage on supported target. | |
216 | */ | |
217 | capture: function() { | |
218 | CKEDITOR.event.useCapture = 1; | |
219 | var retval = this.on.apply( this, arguments ); | |
220 | CKEDITOR.event.useCapture = 0; | |
221 | return retval; | |
222 | }, | |
223 | ||
224 | /** | |
225 | * Fires an specific event in the object. All registered listeners are | |
226 | * called at this point. | |
227 | * | |
228 | * someObject.on( 'someEvent', function() { ... } ); | |
229 | * someObject.on( 'someEvent', function() { ... } ); | |
230 | * someObject.fire( 'someEvent' ); // Both listeners are called. | |
231 | * | |
232 | * someObject.on( 'someEvent', function( event ) { | |
233 | * alert( event.data ); // 'Example' | |
234 | * } ); | |
235 | * someObject.fire( 'someEvent', 'Example' ); | |
236 | * | |
237 | * @method | |
238 | * @param {String} eventName The event name to fire. | |
239 | * @param {Object} [data] Data to be sent as the | |
240 | * {@link CKEDITOR.eventInfo#data} when calling the listeners. | |
241 | * @param {CKEDITOR.editor} [editor] The editor instance to send as the | |
242 | * {@link CKEDITOR.eventInfo#editor} when calling the listener. | |
243 | * @returns {Boolean/Object} A boolean indicating that the event is to be | |
244 | * canceled, or data returned by one of the listeners. | |
245 | */ | |
246 | fire: ( function() { | |
247 | // Create the function that marks the event as stopped. | |
248 | var stopped = 0; | |
249 | var stopEvent = function() { | |
250 | stopped = 1; | |
251 | }; | |
252 | ||
253 | // Create the function that marks the event as canceled. | |
254 | var canceled = 0; | |
255 | var cancelEvent = function() { | |
256 | canceled = 1; | |
257 | }; | |
258 | ||
259 | return function( eventName, data, editor ) { | |
260 | // Get the event entry. | |
261 | var event = getPrivate( this )[ eventName ]; | |
262 | ||
263 | // Save the previous stopped and cancelled states. We may | |
264 | // be nesting fire() calls. | |
265 | var previousStopped = stopped, | |
266 | previousCancelled = canceled; | |
267 | ||
268 | // Reset the stopped and canceled flags. | |
269 | stopped = canceled = 0; | |
270 | ||
271 | if ( event ) { | |
272 | var listeners = event.listeners; | |
273 | ||
274 | if ( listeners.length ) { | |
275 | // As some listeners may remove themselves from the | |
276 | // event, the original array length is dinamic. So, | |
277 | // let's make a copy of all listeners, so we are | |
278 | // sure we'll call all of them. | |
279 | listeners = listeners.slice( 0 ); | |
280 | ||
281 | var retData; | |
282 | // Loop through all listeners. | |
283 | for ( var i = 0; i < listeners.length; i++ ) { | |
284 | // Call the listener, passing the event data. | |
285 | if ( event.errorProof ) { | |
286 | try { | |
287 | retData = listeners[ i ].call( this, editor, data, stopEvent, cancelEvent ); | |
288 | } catch ( er ) {} | |
289 | } else { | |
290 | retData = listeners[ i ].call( this, editor, data, stopEvent, cancelEvent ); | |
291 | } | |
292 | ||
293 | if ( retData === false ) | |
294 | canceled = 1; | |
295 | else if ( typeof retData != 'undefined' ) | |
296 | data = retData; | |
297 | ||
298 | // No further calls is stopped or canceled. | |
299 | if ( stopped || canceled ) | |
300 | break; | |
301 | } | |
302 | } | |
303 | } | |
304 | ||
305 | var ret = canceled ? false : ( typeof data == 'undefined' ? true : data ); | |
306 | ||
307 | // Restore the previous stopped and canceled states. | |
308 | stopped = previousStopped; | |
309 | canceled = previousCancelled; | |
310 | ||
311 | return ret; | |
312 | }; | |
313 | } )(), | |
314 | ||
315 | /** | |
316 | * Fires an specific event in the object, releasing all listeners | |
317 | * registered to that event. The same listeners are not called again on | |
318 | * successive calls of it or of {@link #fire}. | |
319 | * | |
320 | * someObject.on( 'someEvent', function() { ... } ); | |
321 | * someObject.fire( 'someEvent' ); // Above listener called. | |
322 | * someObject.fireOnce( 'someEvent' ); // Above listener called. | |
323 | * someObject.fire( 'someEvent' ); // No listeners called. | |
324 | * | |
325 | * @param {String} eventName The event name to fire. | |
326 | * @param {Object} [data] Data to be sent as the | |
327 | * {@link CKEDITOR.eventInfo#data} when calling the listeners. | |
328 | * @param {CKEDITOR.editor} [editor] The editor instance to send as the | |
329 | * {@link CKEDITOR.eventInfo#editor} when calling the listener. | |
330 | * @returns {Boolean/Object} A booloan indicating that the event is to be | |
331 | * canceled, or data returned by one of the listeners. | |
332 | */ | |
333 | fireOnce: function( eventName, data, editor ) { | |
334 | var ret = this.fire( eventName, data, editor ); | |
335 | delete getPrivate( this )[ eventName ]; | |
336 | return ret; | |
337 | }, | |
338 | ||
339 | /** | |
340 | * Unregisters a listener function from being called at the specified | |
341 | * event. No errors are thrown if the listener has not been registered previously. | |
342 | * | |
343 | * var myListener = function() { ... }; | |
344 | * someObject.on( 'someEvent', myListener ); | |
345 | * someObject.fire( 'someEvent' ); // myListener called. | |
346 | * someObject.removeListener( 'someEvent', myListener ); | |
347 | * someObject.fire( 'someEvent' ); // myListener not called. | |
348 | * | |
349 | * @param {String} eventName The event name. | |
350 | * @param {Function} listenerFunction The listener function to unregister. | |
351 | */ | |
352 | removeListener: function( eventName, listenerFunction ) { | |
353 | // Get the event entry. | |
354 | var event = getPrivate( this )[ eventName ]; | |
355 | ||
356 | if ( event ) { | |
357 | var index = event.getListenerIndex( listenerFunction ); | |
358 | if ( index >= 0 ) | |
359 | event.listeners.splice( index, 1 ); | |
360 | } | |
361 | }, | |
362 | ||
363 | /** | |
364 | * Remove all existing listeners on this object, for cleanup purpose. | |
365 | */ | |
366 | removeAllListeners: function() { | |
367 | var events = getPrivate( this ); | |
368 | for ( var i in events ) | |
369 | delete events[ i ]; | |
370 | }, | |
371 | ||
372 | /** | |
373 | * Checks if there is any listener registered to a given event. | |
374 | * | |
375 | * var myListener = function() { ... }; | |
376 | * someObject.on( 'someEvent', myListener ); | |
377 | * alert( someObject.hasListeners( 'someEvent' ) ); // true | |
378 | * alert( someObject.hasListeners( 'noEvent' ) ); // false | |
379 | * | |
380 | * @param {String} eventName The event name. | |
381 | * @returns {Boolean} | |
382 | */ | |
383 | hasListeners: function( eventName ) { | |
384 | var event = getPrivate( this )[ eventName ]; | |
385 | return ( event && event.listeners.length > 0 ); | |
386 | } | |
387 | }; | |
388 | } )(); | |
389 | } |