diff options
Diffstat (limited to 'core/event.js')
| -rw-r--r-- | core/event.js | 387 |
1 files changed, 387 insertions, 0 deletions
diff --git a/core/event.js b/core/event.js new file mode 100644 index 0000000..fb4c541 --- /dev/null +++ b/core/event.js @@ -0,0 +1,387 @@ +/** + * @license Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.html or http://ckeditor.com/license + */ + +/** + * @fileOverview Defines the {@link CKEDITOR.event} class, which serves as the + * base for classes and objects that require event handling features. + */ + +if ( !CKEDITOR.event ) { + /** + * Creates an event class instance. This constructor is rearely used, being + * the {@link #implementOn} function used in class prototypes directly + * instead. + * + * This is a base class for classes and objects that require event + * handling features. + * + * Do not confuse this class with {@link CKEDITOR.dom.event} which is + * instead used for DOM events. The CKEDITOR.event class implements the + * internal event system used by the CKEditor to fire API related events. + * + * @class + * @constructor Creates an event class instance. + */ + CKEDITOR.event = function() {}; + + /** + * Implements the {@link CKEDITOR.event} features in an object. + * + * var myObject = { message: 'Example' }; + * CKEDITOR.event.implementOn( myObject ); + * + * myObject.on( 'testEvent', function() { + * alert( this.message ); + * } ); + * myObject.fire( 'testEvent' ); // 'Example' + * + * @static + * @param {Object} targetObject The object into which implement the features. + */ + CKEDITOR.event.implementOn = function( targetObject ) { + var eventProto = CKEDITOR.event.prototype; + + for ( var prop in eventProto ) { + if ( targetObject[ prop ] == undefined ) + targetObject[ prop ] = eventProto[ prop ]; + } + }; + + CKEDITOR.event.prototype = (function() { + // Returns the private events object for a given object. + var getPrivate = function( obj ) { + var _ = ( obj.getPrivate && obj.getPrivate() ) || obj._ || ( obj._ = {} ); + return _.events || ( _.events = {} ); + }; + + var eventEntry = function( eventName ) { + this.name = eventName; + this.listeners = []; + }; + + eventEntry.prototype = { + // Get the listener index for a specified function. + // Returns -1 if not found. + getListenerIndex: function( listenerFunction ) { + for ( var i = 0, listeners = this.listeners; i < listeners.length; i++ ) { + if ( listeners[ i ].fn == listenerFunction ) + return i; + } + return -1; + } + }; + + // Retrieve the event entry on the event host (create it if needed). + function getEntry( name ) { + // Get the event entry (create it if needed). + var events = getPrivate( this ); + return events[ name ] || ( events[ name ] = new eventEntry( name ) ); + } + + return { + /** + * Predefine some intrinsic properties on a specific event name. + * + * @param {String} name The event name + * @param meta + * @param [meta.errorProof=false] Whether the event firing should catch error thrown from a per listener call. + */ + define: function( name, meta ) { + var entry = getEntry.call( this, name ); + CKEDITOR.tools.extend( entry, meta, true ); + }, + + /** + * Registers a listener to a specific event in the current object. + * + * someObject.on( 'someEvent', function() { + * alert( this == someObject ); // true + * } ); + * + * someObject.on( 'someEvent', function() { + * alert( this == anotherObject ); // true + * }, anotherObject ); + * + * someObject.on( 'someEvent', function( event ) { + * alert( event.listenerData ); // 'Example' + * }, null, 'Example' ); + * + * someObject.on( 'someEvent', function() { ... } ); // 2nd called + * someObject.on( 'someEvent', function() { ... }, null, null, 100 ); // 3rd called + * someObject.on( 'someEvent', function() { ... }, null, null, 1 ); // 1st called + * + * @param {String} eventName The event name to which listen. + * @param {Function} listenerFunction The function listening to the + * event. A single {@link CKEDITOR.eventInfo} object instanced + * is passed to this function containing all the event data. + * @param {Object} [scopeObj] The object used to scope the listener + * call (the `this` object). If omitted, the current object is used. + * @param {Object} [listenerData] Data to be sent as the + * {@link CKEDITOR.eventInfo#listenerData} when calling the + * listener. + * @param {Number} [priority=10] The listener priority. Lower priority + * listeners are called first. Listeners with the same priority + * value are called in registration order. + * @returns {Object} An object containing the `removeListener` + * function, which can be used to remove the listener at any time. + */ + on: function( eventName, listenerFunction, scopeObj, listenerData, priority ) { + // Create the function to be fired for this listener. + function listenerFirer( editor, publisherData, stopFn, cancelFn ) { + var ev = { + name: eventName, + sender: this, + editor: editor, + data: publisherData, + listenerData: listenerData, + stop: stopFn, + cancel: cancelFn, + removeListener: removeListener + }; + + var ret = listenerFunction.call( scopeObj, ev ); + + return ret === false ? false : ev.data; + } + + function removeListener() { + me.removeListener( eventName, listenerFunction ); + } + + var event = getEntry.call( this, eventName ); + + if ( event.getListenerIndex( listenerFunction ) < 0 ) { + // Get the listeners. + var listeners = event.listeners; + + // Fill the scope. + if ( !scopeObj ) + scopeObj = this; + + // Default the priority, if needed. + if ( isNaN( priority ) ) + priority = 10; + + var me = this; + + listenerFirer.fn = listenerFunction; + listenerFirer.priority = priority; + + // Search for the right position for this new listener, based on its + // priority. + for ( var i = listeners.length - 1; i >= 0; i-- ) { + // Find the item which should be before the new one. + if ( listeners[ i ].priority <= priority ) { + // Insert the listener in the array. + listeners.splice( i + 1, 0, listenerFirer ); + return { removeListener: removeListener }; + } + } + + // If no position has been found (or zero length), put it in + // the front of list. + listeners.unshift( listenerFirer ); + } + + return { removeListener: removeListener }; + }, + + /** + * Similiar with {@link #on} but the listener will be called only once upon the next event firing. + * + * @see CKEDITOR.event#on + */ + once: function() { + var fn = arguments[ 1 ]; + + arguments[ 1 ] = function( evt ) { + evt.removeListener(); + return fn.apply( this, arguments ); + }; + + return this.on.apply( this, arguments ); + }, + + /** + * @static + * @property {Boolean} useCapture + * @todo + */ + + /** + * Register event handler under the capturing stage on supported target. + */ + capture: function() { + CKEDITOR.event.useCapture = 1; + var retval = this.on.apply( this, arguments ); + CKEDITOR.event.useCapture = 0; + return retval; + }, + + /** + * Fires an specific event in the object. All registered listeners are + * called at this point. + * + * someObject.on( 'someEvent', function() { ... } ); + * someObject.on( 'someEvent', function() { ... } ); + * someObject.fire( 'someEvent' ); // Both listeners are called. + * + * someObject.on( 'someEvent', function( event ) { + * alert( event.data ); // 'Example' + * } ); + * someObject.fire( 'someEvent', 'Example' ); + * + * @method + * @param {String} eventName The event name to fire. + * @param {Object} [data] Data to be sent as the + * {@link CKEDITOR.eventInfo#data} when calling the listeners. + * @param {CKEDITOR.editor} [editor] The editor instance to send as the + * {@link CKEDITOR.eventInfo#editor} when calling the listener. + * @returns {Boolean/Object} A boolean indicating that the event is to be + * canceled, or data returned by one of the listeners. + */ + fire: (function() { + // Create the function that marks the event as stopped. + var stopped = 0; + var stopEvent = function() { + stopped = 1; + }; + + // Create the function that marks the event as canceled. + var canceled = 0; + var cancelEvent = function() { + canceled = 1; + }; + + return function( eventName, data, editor ) { + // Get the event entry. + var event = getPrivate( this )[ eventName ]; + + // Save the previous stopped and cancelled states. We may + // be nesting fire() calls. + var previousStopped = stopped, + previousCancelled = canceled; + + // Reset the stopped and canceled flags. + stopped = canceled = 0; + + if ( event ) { + var listeners = event.listeners; + + if ( listeners.length ) { + // As some listeners may remove themselves from the + // event, the original array length is dinamic. So, + // let's make a copy of all listeners, so we are + // sure we'll call all of them. + listeners = listeners.slice( 0 ); + + var retData; + // Loop through all listeners. + for ( var i = 0; i < listeners.length; i++ ) { + // Call the listener, passing the event data. + if ( event.errorProof ) { + try { + retData = listeners[ i ].call( this, editor, data, stopEvent, cancelEvent ); + } catch ( er ) {} + } else + retData = listeners[ i ].call( this, editor, data, stopEvent, cancelEvent ); + + if ( retData === false ) + canceled = 1; + else if ( typeof retData != 'undefined' ) + data = retData; + + // No further calls is stopped or canceled. + if ( stopped || canceled ) + break; + } + } + } + + var ret = canceled ? false : ( typeof data == 'undefined' ? true : data ); + + // Restore the previous stopped and canceled states. + stopped = previousStopped; + canceled = previousCancelled; + + return ret; + }; + })(), + + /** + * Fires an specific event in the object, releasing all listeners + * registered to that event. The same listeners are not called again on + * successive calls of it or of {@link #fire}. + * + * someObject.on( 'someEvent', function() { ... } ); + * someObject.fire( 'someEvent' ); // Above listener called. + * someObject.fireOnce( 'someEvent' ); // Above listener called. + * someObject.fire( 'someEvent' ); // No listeners called. + * + * @param {String} eventName The event name to fire. + * @param {Object} [data] Data to be sent as the + * {@link CKEDITOR.eventInfo#data} when calling the listeners. + * @param {CKEDITOR.editor} [editor] The editor instance to send as the + * {@link CKEDITOR.eventInfo#editor} when calling the listener. + * @returns {Boolean/Object} A booloan indicating that the event is to be + * canceled, or data returned by one of the listeners. + */ + fireOnce: function( eventName, data, editor ) { + var ret = this.fire( eventName, data, editor ); + delete getPrivate( this )[ eventName ]; + return ret; + }, + + /** + * Unregisters a listener function from being called at the specified + * event. No errors are thrown if the listener has not been registered previously. + * + * var myListener = function() { ... }; + * someObject.on( 'someEvent', myListener ); + * someObject.fire( 'someEvent' ); // myListener called. + * someObject.removeListener( 'someEvent', myListener ); + * someObject.fire( 'someEvent' ); // myListener not called. + * + * @param {String} eventName The event name. + * @param {Function} listenerFunction The listener function to unregister. + */ + removeListener: function( eventName, listenerFunction ) { + // Get the event entry. + var event = getPrivate( this )[ eventName ]; + + if ( event ) { + var index = event.getListenerIndex( listenerFunction ); + if ( index >= 0 ) + event.listeners.splice( index, 1 ); + } + }, + + /** + * Remove all existing listeners on this object, for cleanup purpose. + */ + removeAllListeners: function() { + var events = getPrivate( this ); + for ( var i in events ) + delete events[ i ]; + }, + + /** + * Checks if there is any listener registered to a given event. + * + * var myListener = function() { ... }; + * someObject.on( 'someEvent', myListener ); + * alert( someObject.hasListeners( 'someEvent' ) ); // true + * alert( someObject.hasListeners( 'noEvent' ) ); // false + * + * @param {String} eventName The event name. + * @returns {Boolean} + */ + hasListeners: function( eventName ) { + var event = getPrivate( this )[ eventName ]; + return ( event && event.listeners.length > 0 ); + } + }; + })(); +} |
