JasonWoof Got questions, comments, patches, etc.? Contact Jason Woofenden
c4e8876f43bf28a28cf777765bbe679455b8d107
[ckeditor.git] / _source / core / dom / element.js
1 /*\r
2 Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved.\r
3 For licensing, see LICENSE.html or http://ckeditor.com/license\r
4 */\r
5 \r
6 /**\r
7  * @fileOverview Defines the {@link CKEDITOR.dom.element} class, which\r
8  *              represents a DOM element.\r
9  */\r
10 \r
11 /**\r
12  * Represents a DOM element.\r
13  * @constructor\r
14  * @augments CKEDITOR.dom.node\r
15  * @param {Object|String} element A native DOM element or the element name for\r
16  *              new elements.\r
17  * @param {CKEDITOR.dom.document} [ownerDocument] The document that will contain\r
18  *              the element in case of element creation.\r
19  * @example\r
20  * // Create a new <span> element.\r
21  * var element = new CKEDITOR.dom.element( 'span' );\r
22  * @example\r
23  * // Create an element based on a native DOM element.\r
24  * var element = new CKEDITOR.dom.element( document.getElementById( 'myId' ) );\r
25  */\r
26 CKEDITOR.dom.element = function( element, ownerDocument )\r
27 {\r
28         if ( typeof element == 'string' )\r
29                 element = ( ownerDocument ? ownerDocument.$ : document ).createElement( element );\r
30 \r
31         // Call the base constructor (we must not call CKEDITOR.dom.node).\r
32         CKEDITOR.dom.domObject.call( this, element );\r
33 };\r
34 \r
35 // PACKAGER_RENAME( CKEDITOR.dom.element )\r
36 \r
37 /**\r
38  * The the {@link CKEDITOR.dom.element} representing and element. If the\r
39  * element is a native DOM element, it will be transformed into a valid\r
40  * CKEDITOR.dom.element object.\r
41  * @returns {CKEDITOR.dom.element} The transformed element.\r
42  * @example\r
43  * var element = new CKEDITOR.dom.element( 'span' );\r
44  * alert( element == <b>CKEDITOR.dom.element.get( element )</b> );  "true"\r
45  * @example\r
46  * var element = document.getElementById( 'myElement' );\r
47  * alert( <b>CKEDITOR.dom.element.get( element )</b>.getName() );  e.g. "p"\r
48  */\r
49 CKEDITOR.dom.element.get = function( element )\r
50 {\r
51         return element && ( element.$ ? element : new CKEDITOR.dom.element( element ) );\r
52 };\r
53 \r
54 CKEDITOR.dom.element.prototype = new CKEDITOR.dom.node();\r
55 \r
56 /**\r
57  * Creates an instance of the {@link CKEDITOR.dom.element} class based on the\r
58  * HTML representation of an element.\r
59  * @param {String} html The element HTML. It should define only one element in\r
60  *              the "root" level. The "root" element can have child nodes, but not\r
61  *              siblings.\r
62  * @returns {CKEDITOR.dom.element} The element instance.\r
63  * @example\r
64  * var element = <b>CKEDITOR.dom.element.createFromHtml( '&lt;strong class="anyclass"&gt;My element&lt;/strong&gt;' )</b>;\r
65  * alert( element.getName() );  // "strong"\r
66  */\r
67 CKEDITOR.dom.element.createFromHtml = function( html, ownerDocument )\r
68 {\r
69         var temp = new CKEDITOR.dom.element( 'div', ownerDocument );\r
70         temp.setHtml( html );\r
71 \r
72         // When returning the node, remove it from its parent to detach it.\r
73         return temp.getFirst().remove();\r
74 };\r
75 \r
76 CKEDITOR.dom.element.setMarker = function( database, element, name, value )\r
77 {\r
78         var id = element.getCustomData( 'list_marker_id' ) ||\r
79                         ( element.setCustomData( 'list_marker_id', CKEDITOR.tools.getNextNumber() ).getCustomData( 'list_marker_id' ) ),\r
80                 markerNames = element.getCustomData( 'list_marker_names' ) ||\r
81                         ( element.setCustomData( 'list_marker_names', {} ).getCustomData( 'list_marker_names' ) );\r
82         database[id] = element;\r
83         markerNames[name] = 1;\r
84 \r
85         return element.setCustomData( name, value );\r
86 };\r
87 \r
88 CKEDITOR.dom.element.clearAllMarkers = function( database )\r
89 {\r
90         for ( var i in database )\r
91                 CKEDITOR.dom.element.clearMarkers( database, database[i], true );\r
92 };\r
93 \r
94 CKEDITOR.dom.element.clearMarkers = function( database, element, removeFromDatabase )\r
95 {\r
96         var names = element.getCustomData( 'list_marker_names' ),\r
97                 id = element.getCustomData( 'list_marker_id' );\r
98         for ( var i in names )\r
99                 element.removeCustomData( i );\r
100         element.removeCustomData( 'list_marker_names' );\r
101         if ( removeFromDatabase )\r
102         {\r
103                 element.removeCustomData( 'list_marker_id' );\r
104                 delete database[id];\r
105         }\r
106 };\r
107 \r
108 CKEDITOR.tools.extend( CKEDITOR.dom.element.prototype,\r
109         /** @lends CKEDITOR.dom.element.prototype */\r
110         {\r
111                 /**\r
112                  * The node type. This is a constant value set to\r
113                  * {@link CKEDITOR.NODE_ELEMENT}.\r
114                  * @type Number\r
115                  * @example\r
116                  */\r
117                 type : CKEDITOR.NODE_ELEMENT,\r
118 \r
119                 /**\r
120                  * Adds a CSS class to the element. It appends the class to the\r
121                  * already existing names.\r
122                  * @param {String} className The name of the class to be added.\r
123                  * @example\r
124                  * var element = new CKEDITOR.dom.element( 'div' );\r
125                  * element.addClass( 'classA' );  // &lt;div class="classA"&gt;\r
126                  * element.addClass( 'classB' );  // &lt;div class="classA classB"&gt;\r
127                  * element.addClass( 'classA' );  // &lt;div class="classA classB"&gt;\r
128                  */\r
129                 addClass : function( className )\r
130                 {\r
131                         var c = this.$.className;\r
132                         if ( c )\r
133                         {\r
134                                 var regex = new RegExp( '(?:^|\\s)' + className + '(?:\\s|$)', '' );\r
135                                 if ( !regex.test( c ) )\r
136                                         c += ' ' + className;\r
137                         }\r
138                         this.$.className = c || className;\r
139                 },\r
140 \r
141                 /**\r
142                  * Removes a CSS class name from the elements classes. Other classes\r
143                  * remain untouched.\r
144                  * @param {String} className The name of the class to remove.\r
145                  * @example\r
146                  * var element = new CKEDITOR.dom.element( 'div' );\r
147                  * element.addClass( 'classA' );  // &lt;div class="classA"&gt;\r
148                  * element.addClass( 'classB' );  // &lt;div class="classA classB"&gt;\r
149                  * element.removeClass( 'classA' );  // &lt;div class="classB"&gt;\r
150                  * element.removeClass( 'classB' );  // &lt;div&gt;\r
151                  */\r
152                 removeClass : function( className )\r
153                 {\r
154                         var c = this.getAttribute( 'class' );\r
155                         if ( c )\r
156                         {\r
157                                 var regex = new RegExp( '(?:^|\\s+)' + className + '(?=\\s|$)', 'i' );\r
158                                 if ( regex.test( c ) )\r
159                                 {\r
160                                         c = c.replace( regex, '' ).replace( /^\s+/, '' );\r
161 \r
162                                         if ( c )\r
163                                                 this.setAttribute( 'class', c );\r
164                                         else\r
165                                                 this.removeAttribute( 'class' );\r
166                                 }\r
167                         }\r
168                 },\r
169 \r
170                 hasClass : function( className )\r
171                 {\r
172                         var regex = new RegExp( '(?:^|\\s+)' + className + '(?=\\s|$)', '' );\r
173                         return regex.test( this.getAttribute('class') );\r
174                 },\r
175 \r
176                 /**\r
177                  * Append a node as a child of this element.\r
178                  * @param {CKEDITOR.dom.node|String} node The node or element name to be\r
179                  *              appended.\r
180                  * @param {Boolean} [toStart] Indicates that the element is to be\r
181                  *              appended at the start.\r
182                  * @returns {CKEDITOR.dom.node} The appended node.\r
183                  * @example\r
184                  * var p = new CKEDITOR.dom.element( 'p' );\r
185                  *\r
186                  * var strong = new CKEDITOR.dom.element( 'strong' );\r
187                  * <b>p.append( strong );</b>\r
188                  *\r
189                  * var em = <b>p.append( 'em' );</b>\r
190                  *\r
191                  * // result: "&lt;p&gt;&lt;strong&gt;&lt;/strong&gt;&lt;em&gt;&lt;/em&gt;&lt;/p&gt;"\r
192                  */\r
193                 append : function( node, toStart )\r
194                 {\r
195                         if ( typeof node == 'string' )\r
196                                 node = this.getDocument().createElement( node );\r
197 \r
198                         if ( toStart )\r
199                                 this.$.insertBefore( node.$, this.$.firstChild );\r
200                         else\r
201                                 this.$.appendChild( node.$ );\r
202 \r
203                         return node;\r
204                 },\r
205 \r
206                 appendHtml : function( html )\r
207                 {\r
208                         if ( !this.$.childNodes.length )\r
209                                 this.setHtml( html );\r
210                         else\r
211                         {\r
212                                 var temp = new CKEDITOR.dom.element( 'div', this.getDocument() );\r
213                                 temp.setHtml( html );\r
214                                 temp.moveChildren( this );\r
215                         }\r
216                 },\r
217 \r
218                 /**\r
219                  * Append text to this element.\r
220                  * @param {String} text The text to be appended.\r
221                  * @returns {CKEDITOR.dom.node} The appended node.\r
222                  * @example\r
223                  * var p = new CKEDITOR.dom.element( 'p' );\r
224                  * p.appendText( 'This is' );\r
225                  * p.appendText( ' some text' );\r
226                  *\r
227                  * // result: "&lt;p&gt;This is some text&lt;/p&gt;"\r
228                  */\r
229                 appendText : function( text )\r
230                 {\r
231                         if ( this.$.text != undefined )\r
232                                 this.$.text += text;\r
233                         else\r
234                                 this.append( new CKEDITOR.dom.text( text ) );\r
235                 },\r
236 \r
237                 appendBogus : function()\r
238                 {\r
239                         var lastChild = this.getLast() ;\r
240 \r
241                         // Ignore empty/spaces text.\r
242                         while ( lastChild && lastChild.type == CKEDITOR.NODE_TEXT && !CKEDITOR.tools.rtrim( lastChild.getText() ) )\r
243                                 lastChild = lastChild.getPrevious();\r
244                         if ( !lastChild || !lastChild.is || !lastChild.is( 'br' ) )\r
245                         {\r
246                                 this.append(\r
247                                         CKEDITOR.env.opera ?\r
248                                                 this.getDocument().createText('') :\r
249                                                 this.getDocument().createElement( 'br' ) );\r
250                         }\r
251                 },\r
252 \r
253                 /**\r
254                  * Breaks one of the ancestor element in the element position, moving\r
255                  * this element between the broken parts.\r
256                  * @param {CKEDITOR.dom.element} parent The anscestor element to get broken.\r
257                  * @example\r
258                  * // Before breaking:\r
259                  * //     <b>This <i>is some<span /> sample</i> test text</b>\r
260                  * // If "element" is <span /> and "parent" is <i>:\r
261                  * //     <b>This <i>is some</i><span /><i> sample</i> test text</b>\r
262                  * element.breakParent( parent );\r
263                  * @example\r
264                  * // Before breaking:\r
265                  * //     <b>This <i>is some<span /> sample</i> test text</b>\r
266                  * // If "element" is <span /> and "parent" is <b>:\r
267                  * //     <b>This <i>is some</i></b><span /><b><i> sample</i> test text</b>\r
268                  * element.breakParent( parent );\r
269                  */\r
270                 breakParent : function( parent )\r
271                 {\r
272                         var range = new CKEDITOR.dom.range( this.getDocument() );\r
273 \r
274                         // We'll be extracting part of this element, so let's use our\r
275                         // range to get the correct piece.\r
276                         range.setStartAfter( this );\r
277                         range.setEndAfter( parent );\r
278 \r
279                         // Extract it.\r
280                         var docFrag = range.extractContents();\r
281 \r
282                         // Move the element outside the broken element.\r
283                         range.insertNode( this.remove() );\r
284 \r
285                         // Re-insert the extracted piece after the element.\r
286                         docFrag.insertAfterNode( this );\r
287                 },\r
288 \r
289                 contains :\r
290                         CKEDITOR.env.ie || CKEDITOR.env.webkit ?\r
291                                 function( node )\r
292                                 {\r
293                                         var $ = this.$;\r
294 \r
295                                         return node.type != CKEDITOR.NODE_ELEMENT ?\r
296                                                 $.contains( node.getParent().$ ) :\r
297                                                 $ != node.$ && $.contains( node.$ );\r
298                                 }\r
299                         :\r
300                                 function( node )\r
301                                 {\r
302                                         return !!( this.$.compareDocumentPosition( node.$ ) & 16 );\r
303                                 },\r
304 \r
305                 /**\r
306                  * Moves the selection focus to this element.\r
307                  * @example\r
308                  * var element = CKEDITOR.document.getById( 'myTextarea' );\r
309                  * <b>element.focus()</b>;\r
310                  */\r
311                 focus : function()\r
312                 {\r
313                         // IE throws error if the element is not visible.\r
314                         try\r
315                         {\r
316                                 this.$.focus();\r
317                         }\r
318                         catch (e)\r
319                         {}\r
320                 },\r
321 \r
322                 /**\r
323                  * Gets the inner HTML of this element.\r
324                  * @returns {String} The inner HTML of this element.\r
325                  * @example\r
326                  * var element = CKEDITOR.dom.element.createFromHtml( '&lt;div&gt;&lt;b&gt;Example&lt;/b&gt;&lt;/div&gt;' );\r
327                  * alert( <b>p.getHtml()</b> );  // "&lt;b&gt;Example&lt;/b&gt;"\r
328                  */\r
329                 getHtml : function()\r
330                 {\r
331                         return this.$.innerHTML;\r
332                 },\r
333 \r
334                 getOuterHtml : function()\r
335                 {\r
336                         if ( this.$.outerHTML )\r
337                         {\r
338                                 // IE includes the <?xml:namespace> tag in the outerHTML of\r
339                                 // namespaced element. So, we must strip it here. (#3341)\r
340                                 return this.$.outerHTML.replace( /<\?[^>]*>/, '' );\r
341                         }\r
342 \r
343                         var tmpDiv = this.$.ownerDocument.createElement( 'div' );\r
344                         tmpDiv.appendChild( this.$.cloneNode( true ) );\r
345                         return tmpDiv.innerHTML;\r
346                 },\r
347 \r
348                 /**\r
349                  * Sets the inner HTML of this element.\r
350                  * @param {String} html The HTML to be set for this element.\r
351                  * @returns {String} The inserted HTML.\r
352                  * @example\r
353                  * var p = new CKEDITOR.dom.element( 'p' );\r
354                  * <b>p.setHtml( '&lt;b&gt;Inner&lt;/b&gt; HTML' );</b>\r
355                  *\r
356                  * // result: "&lt;p&gt;&lt;b&gt;Inner&lt;/b&gt; HTML&lt;/p&gt;"\r
357                  */\r
358                 setHtml : function( html )\r
359                 {\r
360                         return ( this.$.innerHTML = html );\r
361                 },\r
362 \r
363                 /**\r
364                  * Sets the element contents as plain text.\r
365                  * @param {String} text The text to be set.\r
366                  * @returns {String} The inserted text.\r
367                  * @example\r
368                  * var element = new CKEDITOR.dom.element( 'div' );\r
369                  * element.setText( 'A > B & C < D' );\r
370                  * alert( element.innerHTML );  // "A &amp;gt; B &amp;amp; C &amp;lt; D"\r
371                  */\r
372                 setText : function( text )\r
373                 {\r
374                         CKEDITOR.dom.element.prototype.setText = ( this.$.innerText != undefined ) ?\r
375                                 function ( text )\r
376                                 {\r
377                                         return this.$.innerText = text;\r
378                                 } :\r
379                                 function ( text )\r
380                                 {\r
381                                         return this.$.textContent = text;\r
382                                 };\r
383 \r
384                         return this.setText( text );\r
385                 },\r
386 \r
387                 /**\r
388                  * Gets the value of an element attribute.\r
389                  * @function\r
390                  * @param {String} name The attribute name.\r
391                  * @returns {String} The attribute value or null if not defined.\r
392                  * @example\r
393                  * var element = CKEDITOR.dom.element.createFromHtml( '&lt;input type="text" /&gt;' );\r
394                  * alert( <b>element.getAttribute( 'type' )</b> );  // "text"\r
395                  */\r
396                 getAttribute : (function()\r
397                 {\r
398                         var standard = function( name )\r
399                         {\r
400                                 return this.$.getAttribute( name, 2 );\r
401                         };\r
402 \r
403                         if ( CKEDITOR.env.ie && ( CKEDITOR.env.ie7Compat || CKEDITOR.env.ie6Compat ) )\r
404                         {\r
405                                 return function( name )\r
406                                 {\r
407                                         switch ( name )\r
408                                         {\r
409                                                 case 'class':\r
410                                                         name = 'className';\r
411                                                         break;\r
412 \r
413                                                 case 'tabindex':\r
414                                                         var tabIndex = standard.call( this, name );\r
415 \r
416                                                         // IE returns tabIndex=0 by default for all\r
417                                                         // elements. For those elements,\r
418                                                         // getAtrribute( 'tabindex', 2 ) returns 32768\r
419                                                         // instead. So, we must make this check to give a\r
420                                                         // uniform result among all browsers.\r
421                                                         if ( tabIndex !== 0 && this.$.tabIndex === 0 )\r
422                                                                 tabIndex = null;\r
423 \r
424                                                         return tabIndex;\r
425                                                         break;\r
426 \r
427                                                 case 'checked':\r
428                                                         return this.$.checked;\r
429                                                         break;\r
430 \r
431                                                 case 'style':\r
432                                                         // IE does not return inline styles via getAttribute(). See #2947.\r
433                                                         return this.$.style.cssText;\r
434                                         }\r
435 \r
436                                         return standard.call( this, name );\r
437                                 };\r
438                         }\r
439                         else\r
440                                 return standard;\r
441                 })(),\r
442 \r
443                 getChildren : function()\r
444                 {\r
445                         return new CKEDITOR.dom.nodeList( this.$.childNodes );\r
446                 },\r
447 \r
448                 /**\r
449                  * Gets the current computed value of one of the element CSS style\r
450                  * properties.\r
451                  * @function\r
452                  * @param {String} propertyName The style property name.\r
453                  * @returns {String} The property value.\r
454                  * @example\r
455                  * var element = new CKEDITOR.dom.element( 'span' );\r
456                  * alert( <b>element.getComputedStyle( 'display' )</b> );  // "inline"\r
457                  */\r
458                 getComputedStyle :\r
459                         CKEDITOR.env.ie ?\r
460                                 function( propertyName )\r
461                                 {\r
462                                         return this.$.currentStyle[ CKEDITOR.tools.cssStyleToDomStyle( propertyName ) ];\r
463                                 }\r
464                         :\r
465                                 function( propertyName )\r
466                                 {\r
467                                         return this.getWindow().$.getComputedStyle( this.$, '' ).getPropertyValue( propertyName );\r
468                                 },\r
469 \r
470                 /**\r
471                  * Gets the DTD entries for this element.\r
472                  * @returns {Object} An object containing the list of elements accepted\r
473                  *              by this element.\r
474                  */\r
475                 getDtd : function()\r
476                 {\r
477                         var dtd = CKEDITOR.dtd[ this.getName() ];\r
478 \r
479                         this.getDtd = function()\r
480                         {\r
481                                 return dtd;\r
482                         };\r
483 \r
484                         return dtd;\r
485                 },\r
486 \r
487                 getElementsByTag : CKEDITOR.dom.document.prototype.getElementsByTag,\r
488 \r
489                 /**\r
490                  * Gets the computed tabindex for this element.\r
491                  * @function\r
492                  * @returns {Number} The tabindex value.\r
493                  * @example\r
494                  * var element = CKEDITOR.document.getById( 'myDiv' );\r
495                  * alert( <b>element.getTabIndex()</b> );  // e.g. "-1"\r
496                  */\r
497                 getTabIndex :\r
498                         CKEDITOR.env.ie ?\r
499                                 function()\r
500                                 {\r
501                                         var tabIndex = this.$.tabIndex;\r
502 \r
503                                         // IE returns tabIndex=0 by default for all elements. In\r
504                                         // those cases we must check that the element really has\r
505                                         // the tabindex attribute set to zero, or it is one of\r
506                                         // those element that should have zero by default.\r
507                                         if ( tabIndex === 0 && !CKEDITOR.dtd.$tabIndex[ this.getName() ] && parseInt( this.getAttribute( 'tabindex' ), 10 ) !== 0 )\r
508                                                 tabIndex = -1;\r
509 \r
510                                                 return tabIndex;\r
511                                 }\r
512                         : CKEDITOR.env.webkit ?\r
513                                 function()\r
514                                 {\r
515                                         var tabIndex = this.$.tabIndex;\r
516 \r
517                                         // Safari returns "undefined" for elements that should not\r
518                                         // have tabindex (like a div). So, we must try to get it\r
519                                         // from the attribute.\r
520                                         // https://bugs.webkit.org/show_bug.cgi?id=20596\r
521                                         if ( tabIndex == undefined )\r
522                                         {\r
523                                                 tabIndex = parseInt( this.getAttribute( 'tabindex' ), 10 );\r
524 \r
525                                                 // If the element don't have the tabindex attribute,\r
526                                                 // then we should return -1.\r
527                                                 if ( isNaN( tabIndex ) )\r
528                                                         tabIndex = -1;\r
529                                         }\r
530 \r
531                                         return tabIndex;\r
532                                 }\r
533                         :\r
534                                 function()\r
535                                 {\r
536                                         return this.$.tabIndex;\r
537                                 },\r
538 \r
539                 /**\r
540                  * Gets the text value of this element.\r
541                  *\r
542                  * Only in IE (which uses innerText), &lt;br&gt; will cause linebreaks,\r
543                  * and sucessive whitespaces (including line breaks) will be reduced to\r
544                  * a single space. This behavior is ok for us, for now. It may change\r
545                  * in the future.\r
546                  * @returns {String} The text value.\r
547                  * @example\r
548                  * var element = CKEDITOR.dom.element.createFromHtml( '&lt;div&gt;Same &lt;i&gt;text&lt;/i&gt;.&lt;/div&gt;' );\r
549                  * alert( <b>element.getText()</b> );  // "Sample text."\r
550                  */\r
551                 getText : function()\r
552                 {\r
553                         return this.$.textContent || this.$.innerText || '';\r
554                 },\r
555 \r
556                 /**\r
557                  * Gets the window object that contains this element.\r
558                  * @returns {CKEDITOR.dom.window} The window object.\r
559                  * @example\r
560                  */\r
561                 getWindow : function()\r
562                 {\r
563                         return this.getDocument().getWindow();\r
564                 },\r
565 \r
566                 /**\r
567                  * Gets the value of the "id" attribute of this element.\r
568                  * @returns {String} The element id, or null if not available.\r
569                  * @example\r
570                  * var element = CKEDITOR.dom.element.createFromHtml( '&lt;p id="myId"&gt;&lt;/p&gt;' );\r
571                  * alert( <b>element.getId()</b> );  // "myId"\r
572                  */\r
573                 getId : function()\r
574                 {\r
575                         return this.$.id || null;\r
576                 },\r
577 \r
578                 /**\r
579                  * Gets the value of the "name" attribute of this element.\r
580                  * @returns {String} The element name, or null if not available.\r
581                  * @example\r
582                  * var element = CKEDITOR.dom.element.createFromHtml( '&lt;input name="myName"&gt;&lt;/input&gt;' );\r
583                  * alert( <b>element.getNameAtt()</b> );  // "myName"\r
584                  */\r
585                 getNameAtt : function()\r
586                 {\r
587                         return this.$.name || null;\r
588                 },\r
589 \r
590                 /**\r
591                  * Gets the element name (tag name). The returned name is guaranteed to\r
592                  * be always full lowercased.\r
593                  * @returns {String} The element name.\r
594                  * @example\r
595                  * var element = new CKEDITOR.dom.element( 'span' );\r
596                  * alert( <b>element.getName()</b> );  // "span"\r
597                  */\r
598                 getName : function()\r
599                 {\r
600                         // Cache the lowercased name inside a closure.\r
601                         var nodeName = this.$.nodeName.toLowerCase();\r
602 \r
603                         if ( CKEDITOR.env.ie )\r
604                         {\r
605                                 var scopeName = this.$.scopeName;\r
606                                 if ( scopeName != 'HTML' )\r
607                                         nodeName = scopeName.toLowerCase() + ':' + nodeName;\r
608                         }\r
609 \r
610                         return (\r
611                         /** @ignore */\r
612                         this.getName = function()\r
613                                 {\r
614                                         return nodeName;\r
615                                 })();\r
616                 },\r
617 \r
618                 /**\r
619                  * Gets the value set to this element. This value is usually available\r
620                  * for form field elements.\r
621                  * @returns {String} The element value.\r
622                  */\r
623                 getValue : function()\r
624                 {\r
625                         return this.$.value;\r
626                 },\r
627 \r
628                 /**\r
629                  * Gets the first child node of this element.\r
630                  * @returns {CKEDITOR.dom.node} The first child node or null if not\r
631                  *              available.\r
632                  * @example\r
633                  * var element = CKEDITOR.dom.element.createFromHtml( '&lt;div&gt;&lt;b&gt;Example&lt;/b&gt;&lt;/div&gt;' );\r
634                  * var first = <b>element.getFirst()</b>;\r
635                  * alert( first.getName() );  // "b"\r
636                  */\r
637                 getFirst : function()\r
638                 {\r
639                         var $ = this.$.firstChild;\r
640                         return $ ? new CKEDITOR.dom.node( $ ) : null;\r
641                 },\r
642 \r
643                 /**\r
644                  * @param {Function} evaluator Filtering the result node.\r
645                  */\r
646                 getLast : function( evaluator )\r
647                 {\r
648                         var last = this.$.lastChild,\r
649                                 retval = last && new CKEDITOR.dom.node( last );\r
650                         if ( retval && evaluator && !evaluator( retval ) )\r
651                                 retval = retval.getPrevious( evaluator );\r
652 \r
653                         return retval;\r
654                 },\r
655 \r
656                 getStyle : function( name )\r
657                 {\r
658                         return this.$.style[ CKEDITOR.tools.cssStyleToDomStyle( name ) ];\r
659                 },\r
660 \r
661                 /**\r
662                  * Checks if the element name matches one or more names.\r
663                  * @param {String} name[,name[,...]] One or more names to be checked.\r
664                  * @returns {Boolean} true if the element name matches any of the names.\r
665                  * @example\r
666                  * var element = new CKEDITOR.element( 'span' );\r
667                  * alert( <b>element.is( 'span' )</b> );  "true"\r
668                  * alert( <b>element.is( 'p', 'span' )</b> );  "true"\r
669                  * alert( <b>element.is( 'p' )</b> );  "false"\r
670                  * alert( <b>element.is( 'p', 'div' )</b> );  "false"\r
671                  */\r
672                 is : function()\r
673                 {\r
674                         var name = this.getName();\r
675                         for ( var i = 0 ; i < arguments.length ; i++ )\r
676                         {\r
677                                 if ( arguments[ i ] == name )\r
678                                         return true;\r
679                         }\r
680                         return false;\r
681                 },\r
682 \r
683                 isEditable : function()\r
684                 {\r
685                         // Get the element name.\r
686                         var name = this.getName();\r
687 \r
688                         // Get the element DTD (defaults to span for unknown elements).\r
689                         var dtd = !CKEDITOR.dtd.$nonEditable[ name ]\r
690                                                 && ( CKEDITOR.dtd[ name ] || CKEDITOR.dtd.span );\r
691 \r
692                         // In the DTD # == text node.\r
693                         return ( dtd && dtd['#'] );\r
694                 },\r
695 \r
696                 isIdentical : function( otherElement )\r
697                 {\r
698                         if ( this.getName() != otherElement.getName() )\r
699                                 return false;\r
700 \r
701                         var thisAttribs = this.$.attributes,\r
702                                 otherAttribs = otherElement.$.attributes;\r
703 \r
704                         var thisLength = thisAttribs.length,\r
705                                 otherLength = otherAttribs.length;\r
706 \r
707                         if ( !CKEDITOR.env.ie && thisLength != otherLength )\r
708                                 return false;\r
709 \r
710                         for ( var i = 0 ; i < thisLength ; i++ )\r
711                         {\r
712                                 var attribute = thisAttribs[ i ];\r
713 \r
714                                 if ( ( !CKEDITOR.env.ie || ( attribute.specified && attribute.nodeName != '_cke_expando' ) ) && attribute.nodeValue != otherElement.getAttribute( attribute.nodeName ) )\r
715                                         return false;\r
716                         }\r
717 \r
718                         // For IE, we have to for both elements, because it's difficult to\r
719                         // know how the atttibutes collection is organized in its DOM.\r
720                         if ( CKEDITOR.env.ie )\r
721                         {\r
722                                 for ( i = 0 ; i < otherLength ; i++ )\r
723                                 {\r
724                                         attribute = otherAttribs[ i ];\r
725 \r
726                                         if ( ( !CKEDITOR.env.ie || ( attribute.specified && attribute.nodeName != '_cke_expando' ) ) && attribute.nodeValue != thisAttribs.getAttribute( attribute.nodeName ) )\r
727                                                 return false;\r
728                                 }\r
729                         }\r
730 \r
731                         return true;\r
732                 },\r
733 \r
734                 /**\r
735                  * Checks if this element is visible. May not work if the element is\r
736                  * child of an element with visibility set to "hidden", but works well\r
737                  * on the great majority of cases.\r
738                  * @return {Boolean} True if the element is visible.\r
739                  */\r
740                 isVisible : function()\r
741                 {\r
742                         return this.$.offsetWidth && ( this.$.style.visibility != 'hidden' );\r
743                 },\r
744 \r
745                 /**\r
746                  * Indicates that the element has defined attributes.\r
747                  * @returns {Boolean} True if the element has attributes.\r
748                  * @example\r
749                  * var element = CKEDITOR.dom.element.createFromHtml( '<div title="Test">Example</div>' );\r
750                  * alert( <b>element.hasAttributes()</b> );  "true"\r
751                  * @example\r
752                  * var element = CKEDITOR.dom.element.createFromHtml( '<div>Example</div>' );\r
753                  * alert( <b>element.hasAttributes()</b> );  "false"\r
754                  */\r
755                 hasAttributes :\r
756                         CKEDITOR.env.ie && ( CKEDITOR.env.ie7Compat || CKEDITOR.env.ie6Compat ) ?\r
757                                 function()\r
758                                 {\r
759                                         var attributes = this.$.attributes;\r
760 \r
761                                         for ( var i = 0 ; i < attributes.length ; i++ )\r
762                                         {\r
763                                                 var attribute = attributes[i];\r
764 \r
765                                                 switch ( attribute.nodeName )\r
766                                                 {\r
767                                                         case 'class' :\r
768                                                                 // IE has a strange bug. If calling removeAttribute('className'),\r
769                                                                 // the attributes collection will still contain the "class"\r
770                                                                 // attribute, which will be marked as "specified", even if the\r
771                                                                 // outerHTML of the element is not displaying the class attribute.\r
772                                                                 // Note : I was not able to reproduce it outside the editor,\r
773                                                                 // but I've faced it while working on the TC of #1391.\r
774                                                                 if ( this.getAttribute( 'class' ) )\r
775                                                                         return true;\r
776 \r
777                                                         // Attributes to be ignored.\r
778                                                         case '_cke_expando' :\r
779                                                                 continue;\r
780 \r
781                                                         /*jsl:fallthru*/\r
782 \r
783                                                         default :\r
784                                                                 if ( attribute.specified )\r
785                                                                         return true;\r
786                                                 }\r
787                                         }\r
788 \r
789                                         return false;\r
790                                 }\r
791                         :\r
792                                 function()\r
793                                 {\r
794                                         var attributes = this.$.attributes;\r
795                                         return ( attributes.length > 1 || ( attributes.length == 1 && attributes[0].nodeName != '_cke_expando' ) );\r
796                                 },\r
797 \r
798                 /**\r
799                  * Indicates whether a specified attribute is defined for this element.\r
800                  * @returns {Boolean} True if the specified attribute is defined.\r
801                  * @param (String) name The attribute name.\r
802                  * @example\r
803                  */\r
804                 hasAttribute : function( name )\r
805                 {\r
806                         var $attr = this.$.attributes.getNamedItem( name );\r
807                         return !!( $attr && $attr.specified );\r
808                 },\r
809 \r
810                 /**\r
811                  * Hides this element (display:none).\r
812                  * @example\r
813                  * var element = CKEDITOR.dom.element.getById( 'myElement' );\r
814                  * <b>element.hide()</b>;\r
815                  */\r
816                 hide : function()\r
817                 {\r
818                         this.setStyle( 'display', 'none' );\r
819                 },\r
820 \r
821                 moveChildren : function( target, toStart )\r
822                 {\r
823                         var $ = this.$;\r
824                         target = target.$;\r
825 \r
826                         if ( $ == target )\r
827                                 return;\r
828 \r
829                         var child;\r
830 \r
831                         if ( toStart )\r
832                         {\r
833                                 while ( ( child = $.lastChild ) )\r
834                                         target.insertBefore( $.removeChild( child ), target.firstChild );\r
835                         }\r
836                         else\r
837                         {\r
838                                 while ( ( child = $.firstChild ) )\r
839                                         target.appendChild( $.removeChild( child ) );\r
840                         }\r
841                 },\r
842 \r
843                 /**\r
844                  * Shows this element (display it).\r
845                  * @example\r
846                  * var element = CKEDITOR.dom.element.getById( 'myElement' );\r
847                  * <b>element.show()</b>;\r
848                  */\r
849                 show : function()\r
850                 {\r
851                         this.setStyles(\r
852                                 {\r
853                                         display : '',\r
854                                         visibility : ''\r
855                                 });\r
856                 },\r
857 \r
858                 /**\r
859                  * Sets the value of an element attribute.\r
860                  * @param {String} name The name of the attribute.\r
861                  * @param {String} value The value to be set to the attribute.\r
862                  * @function\r
863                  * @returns {CKEDITOR.dom.element} This element instance.\r
864                  * @example\r
865                  * var element = CKEDITOR.dom.element.getById( 'myElement' );\r
866                  * <b>element.setAttribute( 'class', 'myClass' )</b>;\r
867                  * <b>element.setAttribute( 'title', 'This is an example' )</b>;\r
868                  */\r
869                 setAttribute : (function()\r
870                 {\r
871                         var standard = function( name, value )\r
872                         {\r
873                                 this.$.setAttribute( name, value );\r
874                                 return this;\r
875                         };\r
876 \r
877                         if ( CKEDITOR.env.ie && ( CKEDITOR.env.ie7Compat || CKEDITOR.env.ie6Compat ) )\r
878                         {\r
879                                 return function( name, value )\r
880                                 {\r
881                                         if ( name == 'class' )\r
882                                                 this.$.className = value;\r
883                                         else if ( name == 'style' )\r
884                                                 this.$.style.cssText = value;\r
885                                         else if ( name == 'tabindex' )  // Case sensitive.\r
886                                                 this.$.tabIndex = value;\r
887                                         else if ( name == 'checked' )\r
888                                                 this.$.checked = value;\r
889                                         else\r
890                                                 standard.apply( this, arguments );\r
891                                         return this;\r
892                                 };\r
893                         }\r
894                         else\r
895                                 return standard;\r
896                 })(),\r
897 \r
898                 /**\r
899                  * Sets the value of several element attributes.\r
900                  * @param {Object} attributesPairs An object containing the names and\r
901                  *              values of the attributes.\r
902                  * @returns {CKEDITOR.dom.element} This element instance.\r
903                  * @example\r
904                  * var element = CKEDITOR.dom.element.getById( 'myElement' );\r
905                  * <b>element.setAttributes({\r
906                  *     'class' : 'myClass',\r
907                  *     'title' : 'This is an example' })</b>;\r
908                  */\r
909                 setAttributes : function( attributesPairs )\r
910                 {\r
911                         for ( var name in attributesPairs )\r
912                                 this.setAttribute( name, attributesPairs[ name ] );\r
913                         return this;\r
914                 },\r
915 \r
916                 /**\r
917                  * Sets the element value. This function is usually used with form\r
918                  * field element.\r
919                  * @param {String} value The element value.\r
920                  * @returns {CKEDITOR.dom.element} This element instance.\r
921                  */\r
922                 setValue : function( value )\r
923                 {\r
924                         this.$.value = value;\r
925                         return this;\r
926                 },\r
927 \r
928                 /**\r
929                  * Removes an attribute from the element.\r
930                  * @param {String} name The attribute name.\r
931                  * @function\r
932                  * @example\r
933                  * var element = CKEDITOR.dom.element.createFromHtml( '<div class="classA"></div>' );\r
934                  * element.removeAttribute( 'class' );\r
935                  */\r
936                 removeAttribute : (function()\r
937                 {\r
938                         var standard = function( name )\r
939                         {\r
940                                 this.$.removeAttribute( name );\r
941                         };\r
942 \r
943                         if ( CKEDITOR.env.ie && ( CKEDITOR.env.ie7Compat || CKEDITOR.env.ie6Compat ) )\r
944                         {\r
945                                 return function( name )\r
946                                 {\r
947                                         if ( name == 'class' )\r
948                                                 name = 'className';\r
949                                         else if ( name == 'tabindex' )\r
950                                                 name = 'tabIndex';\r
951                                         standard.call( this, name );\r
952                                 };\r
953                         }\r
954                         else\r
955                                 return standard;\r
956                 })(),\r
957 \r
958                 removeAttributes : function ( attributes )\r
959                 {\r
960                         for ( var i = 0 ; i < attributes.length ; i++ )\r
961                                 this.removeAttribute( attributes[ i ] );\r
962                 },\r
963 \r
964                 /**\r
965                  * Removes a style from the element.\r
966                  * @param {String} name The style name.\r
967                  * @function\r
968                  * @example\r
969                  * var element = CKEDITOR.dom.element.createFromHtml( '<div style="display:none"></div>' );\r
970                  * element.removeStyle( 'display' );\r
971                  */\r
972                 removeStyle : function( name )\r
973                 {\r
974                         if ( this.$.style.removeAttribute )\r
975                                 this.$.style.removeAttribute( CKEDITOR.tools.cssStyleToDomStyle( name ) );\r
976                         else\r
977                                 this.setStyle( name, '' );\r
978 \r
979                         if ( !this.$.style.cssText )\r
980                                 this.removeAttribute( 'style' );\r
981                 },\r
982 \r
983                 /**\r
984                  * Sets the value of an element style.\r
985                  * @param {String} name The name of the style. The CSS naming notation\r
986                  *              must be used (e.g. "background-color").\r
987                  * @param {String} value The value to be set to the style.\r
988                  * @returns {CKEDITOR.dom.element} This element instance.\r
989                  * @example\r
990                  * var element = CKEDITOR.dom.element.getById( 'myElement' );\r
991                  * <b>element.setStyle( 'background-color', '#ff0000' )</b>;\r
992                  * <b>element.setStyle( 'margin-top', '10px' )</b>;\r
993                  * <b>element.setStyle( 'float', 'right' )</b>;\r
994                  */\r
995                 setStyle : function( name, value )\r
996                 {\r
997                         this.$.style[ CKEDITOR.tools.cssStyleToDomStyle( name ) ] = value;\r
998                         return this;\r
999                 },\r
1000 \r
1001                 /**\r
1002                  * Sets the value of several element styles.\r
1003                  * @param {Object} stylesPairs An object containing the names and\r
1004                  *              values of the styles.\r
1005                  * @returns {CKEDITOR.dom.element} This element instance.\r
1006                  * @example\r
1007                  * var element = CKEDITOR.dom.element.getById( 'myElement' );\r
1008                  * <b>element.setStyles({\r
1009                  *     'position' : 'absolute',\r
1010                  *     'float' : 'right' })</b>;\r
1011                  */\r
1012                 setStyles : function( stylesPairs )\r
1013                 {\r
1014                         for ( var name in stylesPairs )\r
1015                                 this.setStyle( name, stylesPairs[ name ] );\r
1016                         return this;\r
1017                 },\r
1018 \r
1019                 /**\r
1020                  * Sets the opacity of an element.\r
1021                  * @param {Number} opacity A number within the range [0.0, 1.0].\r
1022                  * @example\r
1023                  * var element = CKEDITOR.dom.element.getById( 'myElement' );\r
1024                  * <b>element.setOpacity( 0.75 )</b>;\r
1025                  */\r
1026                 setOpacity : function( opacity )\r
1027                 {\r
1028                         if ( CKEDITOR.env.ie )\r
1029                         {\r
1030                                 opacity = Math.round( opacity * 100 );\r
1031                                 this.setStyle( 'filter', opacity >= 100 ? '' : 'progid:DXImageTransform.Microsoft.Alpha(opacity=' + opacity + ')' );\r
1032                         }\r
1033                         else\r
1034                                 this.setStyle( 'opacity', opacity );\r
1035                 },\r
1036 \r
1037                 /**\r
1038                  * Makes the element and its children unselectable.\r
1039                  * @function\r
1040                  * @example\r
1041                  * var element = CKEDITOR.dom.element.getById( 'myElement' );\r
1042                  * element.unselectable();\r
1043                  */\r
1044                 unselectable :\r
1045                         CKEDITOR.env.gecko ?\r
1046                                 function()\r
1047                                 {\r
1048                                         this.$.style.MozUserSelect = 'none';\r
1049                                 }\r
1050                         : CKEDITOR.env.webkit ?\r
1051                                 function()\r
1052                                 {\r
1053                                         this.$.style.KhtmlUserSelect = 'none';\r
1054                                 }\r
1055                         :\r
1056                                 function()\r
1057                                 {\r
1058                                         if ( CKEDITOR.env.ie || CKEDITOR.env.opera )\r
1059                                         {\r
1060                                                 var element = this.$,\r
1061                                                         e,\r
1062                                                         i = 0;\r
1063 \r
1064                                                 element.unselectable = 'on';\r
1065 \r
1066                                                 while ( ( e = element.all[ i++ ] ) )\r
1067                                                 {\r
1068                                                         switch ( e.tagName.toLowerCase() )\r
1069                                                         {\r
1070                                                                 case 'iframe' :\r
1071                                                                 case 'textarea' :\r
1072                                                                 case 'input' :\r
1073                                                                 case 'select' :\r
1074                                                                         /* Ignore the above tags */\r
1075                                                                         break;\r
1076                                                                 default :\r
1077                                                                         e.unselectable = 'on';\r
1078                                                         }\r
1079                                                 }\r
1080                                         }\r
1081                                 },\r
1082 \r
1083                 getPositionedAncestor : function()\r
1084                 {\r
1085                         var current = this;\r
1086                         while ( current.getName() != 'html' )\r
1087                         {\r
1088                                 if ( current.getComputedStyle( 'position' ) != 'static' )\r
1089                                         return current;\r
1090 \r
1091                                 current = current.getParent();\r
1092                         }\r
1093                         return null;\r
1094                 },\r
1095 \r
1096                 getDocumentPosition : function( refDocument )\r
1097                 {\r
1098                         var x = 0, y = 0,\r
1099                                 body = this.getDocument().getBody(),\r
1100                                 quirks = this.getDocument().$.compatMode == 'BackCompat';\r
1101 \r
1102                         var doc = this.getDocument();\r
1103 \r
1104                         if ( document.documentElement[ "getBoundingClientRect" ] )\r
1105                         {\r
1106                                 var box  = this.$.getBoundingClientRect(),\r
1107                                         $doc = doc.$,\r
1108                                         $docElem = $doc.documentElement;\r
1109 \r
1110                                 var clientTop = $docElem.clientTop || body.$.clientTop || 0,\r
1111                                         clientLeft = $docElem.clientLeft || body.$.clientLeft || 0,\r
1112                                         needAdjustScrollAndBorders = true;\r
1113 \r
1114                                 /*\r
1115                                  * #3804: getBoundingClientRect() works differently on IE and non-IE\r
1116                                  * browsers, regarding scroll positions.\r
1117                                  *\r
1118                                  * On IE, the top position of the <html> element is always 0, no matter\r
1119                                  * how much you scrolled down.\r
1120                                  *\r
1121                                  * On other browsers, the top position of the <html> element is negative\r
1122                                  * scrollTop.\r
1123                                  */\r
1124                                 if ( CKEDITOR.env.ie )\r
1125                                 {\r
1126                                         var inDocElem = doc.getDocumentElement().contains( this ),\r
1127                                                 inBody = doc.getBody().contains( this );\r
1128 \r
1129                                         needAdjustScrollAndBorders = ( quirks && inBody ) || ( !quirks && inDocElem );\r
1130                                 }\r
1131 \r
1132                                 if ( needAdjustScrollAndBorders )\r
1133                                 {\r
1134                                         x = box.left + ( !quirks && $docElem.scrollLeft || body.$.scrollLeft );\r
1135                                         x -= clientLeft;\r
1136                                         y = box.top  + ( !quirks && $docElem.scrollTop || body.$.scrollTop );\r
1137                                         y -= clientTop;\r
1138                                 }\r
1139                         }\r
1140                         else\r
1141                         {\r
1142                                 var current = this, previous = null, offsetParent;\r
1143                                 while ( current && !( current.getName() == 'body' || current.getName() == 'html' ) )\r
1144                                 {\r
1145                                         x += current.$.offsetLeft - current.$.scrollLeft;\r
1146                                         y += current.$.offsetTop - current.$.scrollTop;\r
1147 \r
1148                                         // Opera includes clientTop|Left into offsetTop|Left.\r
1149                                         if ( !current.equals( this ) )\r
1150                                         {\r
1151                                                 x += ( current.$.clientLeft || 0 );\r
1152                                                 y += ( current.$.clientTop || 0 );\r
1153                                         }\r
1154 \r
1155                                         var scrollElement = previous;\r
1156                                         while ( scrollElement && !scrollElement.equals( current ) )\r
1157                                         {\r
1158                                                 x -= scrollElement.$.scrollLeft;\r
1159                                                 y -= scrollElement.$.scrollTop;\r
1160                                                 scrollElement = scrollElement.getParent();\r
1161                                         }\r
1162 \r
1163                                         previous = current;\r
1164                                         current = ( offsetParent = current.$.offsetParent ) ?\r
1165                                                   new CKEDITOR.dom.element( offsetParent ) : null;\r
1166                                 }\r
1167                         }\r
1168 \r
1169                         if ( refDocument )\r
1170                         {\r
1171                                 var currentWindow = this.getWindow(),\r
1172                                         refWindow = refDocument.getWindow();\r
1173 \r
1174                                 if ( !currentWindow.equals( refWindow ) && currentWindow.$.frameElement )\r
1175                                 {\r
1176                                         var iframePosition = ( new CKEDITOR.dom.element( currentWindow.$.frameElement ) ).getDocumentPosition( refDocument );\r
1177 \r
1178                                         x += iframePosition.x;\r
1179                                         y += iframePosition.y;\r
1180                                 }\r
1181                         }\r
1182 \r
1183                         if ( !document.documentElement[ "getBoundingClientRect" ] )\r
1184                         {\r
1185                                 // In Firefox, we'll endup one pixel before the element positions,\r
1186                                 // so we must add it here.\r
1187                                 if ( CKEDITOR.env.gecko && !quirks )\r
1188                                 {\r
1189                                         x += this.$.clientLeft ? 1 : 0;\r
1190                                         y += this.$.clientTop ? 1 : 0;\r
1191                                 }\r
1192                         }\r
1193 \r
1194                         return { x : x, y : y };\r
1195                 },\r
1196 \r
1197                 scrollIntoView : function( alignTop )\r
1198                 {\r
1199                         // Get the element window.\r
1200                         var win = this.getWindow(),\r
1201                                 winHeight = win.getViewPaneSize().height;\r
1202 \r
1203                         // Starts from the offset that will be scrolled with the negative value of\r
1204                         // the visible window height.\r
1205                         var offset = winHeight * -1;\r
1206 \r
1207                         // Append the view pane's height if align to top.\r
1208                         // Append element height if we are aligning to the bottom.\r
1209                         if ( alignTop )\r
1210                                 offset += winHeight;\r
1211                         else\r
1212                         {\r
1213                                 offset += this.$.offsetHeight || 0;\r
1214 \r
1215                                 // Consider the margin in the scroll, which is ok for our current needs, but\r
1216                                 // needs investigation if we will be using this function in other places.\r
1217                                 offset += parseInt( this.getComputedStyle( 'marginBottom' ) || 0, 10 ) || 0;\r
1218                         }\r
1219 \r
1220                         // Append the offsets for the entire element hierarchy.\r
1221                         var elementPosition = this.getDocumentPosition();\r
1222                         offset += elementPosition.y;\r
1223 \r
1224                         // offset value might be out of range(nagative), fix it(#3692).\r
1225                         offset = offset < 0 ? 0 : offset;\r
1226 \r
1227                         // Scroll the window to the desired position, if not already visible(#3795).\r
1228                         var currentScroll = win.getScrollPosition().y;\r
1229                         if ( offset > currentScroll || offset < currentScroll - winHeight )\r
1230                                 win.$.scrollTo( 0, offset );\r
1231                 },\r
1232 \r
1233                 setState : function( state )\r
1234                 {\r
1235                         switch ( state )\r
1236                         {\r
1237                                 case CKEDITOR.TRISTATE_ON :\r
1238                                         this.addClass( 'cke_on' );\r
1239                                         this.removeClass( 'cke_off' );\r
1240                                         this.removeClass( 'cke_disabled' );\r
1241                                         break;\r
1242                                 case CKEDITOR.TRISTATE_DISABLED :\r
1243                                         this.addClass( 'cke_disabled' );\r
1244                                         this.removeClass( 'cke_off' );\r
1245                                         this.removeClass( 'cke_on' );\r
1246                                         break;\r
1247                                 default :\r
1248                                         this.addClass( 'cke_off' );\r
1249                                         this.removeClass( 'cke_on' );\r
1250                                         this.removeClass( 'cke_disabled' );\r
1251                                         break;\r
1252                         }\r
1253                 },\r
1254 \r
1255                 /**\r
1256                  * Returns the inner document of this IFRAME element.\r
1257                  * @returns {CKEDITOR.dom.document} The inner document.\r
1258                  */\r
1259                 getFrameDocument : function()\r
1260                 {\r
1261                         var $ = this.$;\r
1262 \r
1263                         try\r
1264                         {\r
1265                                 // In IE, with custom document.domain, it may happen that\r
1266                                 // the iframe is not yet available, resulting in "Access\r
1267                                 // Denied" for the following property access.\r
1268                                 $.contentWindow.document;\r
1269                         }\r
1270                         catch ( e )\r
1271                         {\r
1272                                 // Trick to solve this issue, forcing the iframe to get ready\r
1273                                 // by simply setting its "src" property.\r
1274                                 $.src = $.src;\r
1275 \r
1276                                 // In IE6 though, the above is not enough, so we must pause the\r
1277                                 // execution for a while, giving it time to think.\r
1278                                 if ( CKEDITOR.env.ie && CKEDITOR.env.version < 7 )\r
1279                                 {\r
1280                                         window.showModalDialog(\r
1281                                                 'javascript:document.write("' +\r
1282                                                         '<script>' +\r
1283                                                                 'window.setTimeout(' +\r
1284                                                                         'function(){window.close();}' +\r
1285                                                                         ',50);' +\r
1286                                                         '</script>")' );\r
1287                                 }\r
1288                         }\r
1289 \r
1290                         return $ && new CKEDITOR.dom.document( $.contentWindow.document );\r
1291                 },\r
1292 \r
1293                 /**\r
1294                  * Copy all the attributes from one node to the other, kinda like a clone\r
1295                  * skipAttributes is an object with the attributes that must NOT be copied.\r
1296                  * @param {CKEDITOR.dom.element} dest The destination element.\r
1297                  * @param {Object} skipAttributes A dictionary of attributes to skip.\r
1298                  * @example\r
1299                  */\r
1300                 copyAttributes : function( dest, skipAttributes )\r
1301                 {\r
1302                         var attributes = this.$.attributes;\r
1303                         skipAttributes = skipAttributes || {};\r
1304 \r
1305                         for ( var n = 0 ; n < attributes.length ; n++ )\r
1306                         {\r
1307                                 var attribute = attributes[n];\r
1308 \r
1309                                 // IE BUG: value attribute is never specified even if it exists.\r
1310                                 if ( attribute.specified ||\r
1311                                   ( CKEDITOR.env.ie && attribute.nodeValue && attribute.nodeName.toLowerCase() == 'value' ) )\r
1312                                 {\r
1313                                         var attrName = attribute.nodeName;\r
1314                                         // We can set the type only once, so do it with the proper value, not copying it.\r
1315                                         if ( attrName in skipAttributes )\r
1316                                                 continue;\r
1317 \r
1318                                         var attrValue = this.getAttribute( attrName );\r
1319                                         if ( attrValue === null )\r
1320                                                 attrValue = attribute.nodeValue;\r
1321 \r
1322                                         dest.setAttribute( attrName, attrValue );\r
1323                                 }\r
1324                         }\r
1325 \r
1326                         // The style:\r
1327                         if ( this.$.style.cssText !== '' )\r
1328                                 dest.$.style.cssText = this.$.style.cssText;\r
1329                 },\r
1330 \r
1331                 /**\r
1332                  * Changes the tag name of the current element.\r
1333                  * @param {String} newTag The new tag for the element.\r
1334                  */\r
1335                 renameNode : function( newTag )\r
1336                 {\r
1337                         // If it's already correct exit here.\r
1338                         if ( this.getName() == newTag )\r
1339                                 return;\r
1340 \r
1341                         var doc = this.getDocument();\r
1342 \r
1343                         // Create the new node.\r
1344                         var newNode = new CKEDITOR.dom.element( newTag, doc );\r
1345 \r
1346                         // Copy all attributes.\r
1347                         this.copyAttributes( newNode );\r
1348 \r
1349                         // Move children to the new node.\r
1350                         this.moveChildren( newNode );\r
1351 \r
1352                         // Replace the node.\r
1353                         this.$.parentNode.replaceChild( newNode.$, this.$ );\r
1354                         newNode.$._cke_expando = this.$._cke_expando;\r
1355                         this.$ = newNode.$;\r
1356                 },\r
1357 \r
1358                 /**\r
1359                  * Gets a DOM tree descendant under the current node.\r
1360                  * @param {Array|Number} indices The child index or array of child indices under the node.\r
1361                  * @returns {CKEDITOR.dom.node} The specified DOM child under the current node. Null if child does not exist.\r
1362                  * @example\r
1363                  * var strong = p.getChild(0);\r
1364                  */\r
1365                 getChild : function( indices )\r
1366                 {\r
1367                         var rawNode = this.$;\r
1368 \r
1369                         if ( !indices.slice )\r
1370                                 rawNode = rawNode.childNodes[ indices ];\r
1371                         else\r
1372                         {\r
1373                                 while ( indices.length > 0 && rawNode )\r
1374                                         rawNode = rawNode.childNodes[ indices.shift() ];\r
1375                         }\r
1376 \r
1377                         return rawNode ? new CKEDITOR.dom.node( rawNode ) : null;\r
1378                 },\r
1379 \r
1380                 getChildCount : function()\r
1381                 {\r
1382                         return this.$.childNodes.length;\r
1383                 }\r
1384         });\r