d37790fac1784e10ab284055525af2a2d020b118
[ckeditor.git] / _source / core / dom / domobject.js
1 /*
2 Copyright (c) 2003-2011, 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.$.attachEvent )
88 this.$.attachEvent( 'on' + eventName, listener );
89 else if ( this.$.addEventListener )
90 this.$.addEventListener( eventName, listener, !!CKEDITOR.event.useCapture );
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.$.detachEvent )
111 this.$.detachEvent( 'on' + eventName, listener );
112 else if ( this.$.removeEventListener )
113 this.$.removeEventListener( eventName, listener, false );
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 );