1 /*
  2 Copyright (c) 2003-2012, 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.editor} class, which is the base
  8  *		for other classes representing DOM objects.
  9  */
 10 
 11 /**
 12  * Represents a DOM object. This class is not intended to be used directly. It
 13  * serves as the base class for other classes representing specific DOM
 14  * objects.
 15  * @constructor
 16  * @param {Object} nativeDomObject A native DOM object.
 17  * @augments CKEDITOR.event
 18  * @example
 19  */
 20 CKEDITOR.dom.domObject = function( nativeDomObject )
 21 {
 22 	if ( nativeDomObject )
 23 	{
 24 		/**
 25 		 * The native DOM object represented by this class instance.
 26 		 * @type Object
 27 		 * @example
 28 		 * var element = new CKEDITOR.dom.element( 'span' );
 29 		 * alert( element.$.nodeType );  // "1"
 30 		 */
 31 		this.$ = nativeDomObject;
 32 	}
 33 };
 34 
 35 CKEDITOR.dom.domObject.prototype = (function()
 36 {
 37 	// Do not define other local variables here. We want to keep the native
 38 	// listener closures as clean as possible.
 39 
 40 	var getNativeListener = function( domObject, eventName )
 41 	{
 42 		return function( domEvent )
 43 		{
 44 			// In FF, when reloading the page with the editor focused, it may
 45 			// throw an error because the CKEDITOR global is not anymore
 46 			// available. So, we check it here first. (#2923)
 47 			if ( typeof CKEDITOR != 'undefined' )
 48 				domObject.fire( eventName, new CKEDITOR.dom.event( domEvent ) );
 49 		};
 50 	};
 51 
 52 	return /** @lends CKEDITOR.dom.domObject.prototype */ {
 53 
 54 		getPrivate : function()
 55 		{
 56 			var priv;
 57 
 58 			// Get the main private function from the custom data. Create it if not
 59 			// defined.
 60 			if ( !( priv = this.getCustomData( '_' ) ) )
 61 				this.setCustomData( '_', ( priv = {} ) );
 62 
 63 			return priv;
 64 		},
 65 
 66 		/** @ignore */
 67 		on  : function( eventName )
 68 		{
 69 			// We customize the "on" function here. The basic idea is that we'll have
 70 			// only one listener for a native event, which will then call all listeners
 71 			// set to the event.
 72 
 73 			// Get the listeners holder object.
 74 			var nativeListeners = this.getCustomData( '_cke_nativeListeners' );
 75 
 76 			if ( !nativeListeners )
 77 			{
 78 				nativeListeners = {};
 79 				this.setCustomData( '_cke_nativeListeners', nativeListeners );
 80 			}
 81 
 82 			// Check if we have a listener for that event.
 83 			if ( !nativeListeners[ eventName ] )
 84 			{
 85 				var listener = nativeListeners[ eventName ] = getNativeListener( this, eventName );
 86 
 87 				if ( this.$.addEventListener )
 88 					this.$.addEventListener( eventName, listener, !!CKEDITOR.event.useCapture );
 89 				else if ( this.$.attachEvent )
 90 					this.$.attachEvent( 'on' + eventName, listener );
 91 			}
 92 
 93 			// Call the original implementation.
 94 			return CKEDITOR.event.prototype.on.apply( this, arguments );
 95 		},
 96 
 97 		/** @ignore */
 98 		removeListener : function( eventName )
 99 		{
100 			// Call the original implementation.
101 			CKEDITOR.event.prototype.removeListener.apply( this, arguments );
102 
103 			// If we don't have listeners for this event, clean the DOM up.
104 			if ( !this.hasListeners( eventName ) )
105 			{
106 				var nativeListeners = this.getCustomData( '_cke_nativeListeners' );
107 				var listener = nativeListeners && nativeListeners[ eventName ];
108 				if ( listener )
109 				{
110 					if ( this.$.removeEventListener )
111 						this.$.removeEventListener( eventName, listener, false );
112 					else if ( this.$.detachEvent )
113 						this.$.detachEvent( 'on' + eventName, listener );
114 
115 					delete nativeListeners[ eventName ];
116 				}
117 			}
118 		},
119 
120 		/**
121 		 * Removes any listener set on this object.
122 		 * To avoid memory leaks we must assure that there are no
123 		 * references left after the object is no longer needed.
124 		 */
125 		removeAllListeners : function()
126 		{
127 			var nativeListeners = this.getCustomData( '_cke_nativeListeners' );
128 			for ( var eventName in nativeListeners )
129 			{
130 				var listener = nativeListeners[ eventName ];
131 				if ( this.$.detachEvent )
132 					this.$.detachEvent( 'on' + eventName, listener );
133 				else if ( this.$.removeEventListener )
134 					this.$.removeEventListener( eventName, listener, false );
135 
136 				delete nativeListeners[ eventName ];
137 			}
138 		}
139 	};
140 })();
141 
142 (function( domObjectProto )
143 {
144 	var customData = {};
145 
146 	CKEDITOR.on( 'reset', function()
147 		{
148 			customData = {};
149 		});
150 
151 	/**
152 	 * Determines whether the specified object is equal to the current object.
153 	 * @name CKEDITOR.dom.domObject.prototype.equals
154 	 * @function
155 	 * @param {Object} object The object to compare with the current object.
156 	 * @returns {Boolean} "true" if the object is equal.
157 	 * @example
158 	 * var doc = new CKEDITOR.dom.document( document );
159 	 * alert( doc.equals( CKEDITOR.document ) );  // "true"
160 	 * alert( doc == CKEDITOR.document );         // "false"
161 	 */
162 	domObjectProto.equals = function( object )
163 	{
164 		return ( object && object.$ === this.$ );
165 	};
166 
167 	/**
168 	 * Sets a data slot value for this object. These values are shared by all
169 	 * instances pointing to that same DOM object.
170 	 * <strong>Note:</strong> The created data slot is only guarantied to be available on this unique dom node,
171 	 * thus any wish to continue access it from other element clones (either created by clone node or from innerHtml)
172 	 * will fail, for such usage, please use {@link CKEDITOR.dom.element::setAttribute} instead.
173 	 * @name CKEDITOR.dom.domObject.prototype.setCustomData
174 	 * @function
175 	 * @param {String} key A key used to identify the data slot.
176 	 * @param {Object} value The value to set to the data slot.
177 	 * @returns {CKEDITOR.dom.domObject} This DOM object instance.
178 	 * @see CKEDITOR.dom.domObject.prototype.getCustomData
179 	 * @example
180 	 * var element = new CKEDITOR.dom.element( 'span' );
181 	 * element.setCustomData( 'hasCustomData', true );
182 	 */
183 	domObjectProto.setCustomData = function( key, value )
184 	{
185 		var expandoNumber = this.getUniqueId(),
186 			dataSlot = customData[ expandoNumber ] || ( customData[ expandoNumber ] = {} );
187 
188 		dataSlot[ key ] = value;
189 
190 		return this;
191 	};
192 
193 	/**
194 	 * Gets the value set to a data slot in this object.
195 	 * @name CKEDITOR.dom.domObject.prototype.getCustomData
196 	 * @function
197 	 * @param {String} key The key used to identify the data slot.
198 	 * @returns {Object} This value set to the data slot.
199 	 * @see CKEDITOR.dom.domObject.prototype.setCustomData
200 	 * @example
201 	 * var element = new CKEDITOR.dom.element( 'span' );
202 	 * alert( element.getCustomData( 'hasCustomData' ) );  // e.g. 'true'
203 	 */
204 	domObjectProto.getCustomData = function( key )
205 	{
206 		var expandoNumber = this.$[ 'data-cke-expando' ],
207 			dataSlot = expandoNumber && customData[ expandoNumber ];
208 
209 		return dataSlot && dataSlot[ key ];
210 	};
211 
212 	/**
213 	 * @name CKEDITOR.dom.domObject.prototype.removeCustomData
214 	 */
215 	domObjectProto.removeCustomData = function( key )
216 	{
217 		var expandoNumber = this.$[ 'data-cke-expando' ],
218 			dataSlot = expandoNumber && customData[ expandoNumber ],
219 			retval = dataSlot && dataSlot[ key ];
220 
221 		if ( typeof retval != 'undefined' )
222 			delete dataSlot[ key ];
223 
224 		return retval || null;
225 	};
226 
227 	/**
228 	 * Removes any data stored on this object.
229 	 * To avoid memory leaks we must assure that there are no
230 	 * references left after the object is no longer needed.
231 	 * @name CKEDITOR.dom.domObject.prototype.clearCustomData
232 	 * @function
233 	 */
234 	domObjectProto.clearCustomData = function()
235 	{
236 		// Clear all event listeners
237 		this.removeAllListeners();
238 
239 		var expandoNumber = this.$[ 'data-cke-expando' ];
240 		expandoNumber && delete customData[ expandoNumber ];
241 	};
242 
243 	/**
244 	 * Gets an ID that can be used to identiquely identify this DOM object in
245 	 * the running session.
246 	 * @name CKEDITOR.dom.domObject.prototype.getUniqueId
247 	 * @function
248 	 * @returns {Number} A unique ID.
249 	 */
250 	domObjectProto.getUniqueId = function()
251 	{
252 		return this.$[ 'data-cke-expando' ] || ( this.$[ 'data-cke-expando' ] = CKEDITOR.tools.getNextNumber() );
253 	};
254 
255 	// Implement CKEDITOR.event.
256 	CKEDITOR.event.implementOn( domObjectProto );
257 
258 })( CKEDITOR.dom.domObject.prototype );
259