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