JasonWoof Got questions, comments, patches, etc.? Contact Jason Woofenden
vanilla ckeditor-3.6.2
[ckeditor.git] / _source / core / dom / element.js
1 /*\r
2 Copyright (c) 2003-2011, 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], 1 );\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                                 var bogus = CKEDITOR.env.opera ?\r
247                                                 this.getDocument().createText('') :\r
248                                                 this.getDocument().createElement( 'br' );\r
249 \r
250                                 CKEDITOR.env.gecko && bogus.setAttribute( 'type', '_moz' );\r
251 \r
252                                 this.append( bogus );\r
253                         }\r
254                 },\r
255 \r
256                 /**\r
257                  * Breaks one of the ancestor element in the element position, moving\r
258                  * this element between the broken parts.\r
259                  * @param {CKEDITOR.dom.element} parent The anscestor element to get broken.\r
260                  * @example\r
261                  * // Before breaking:\r
262                  * //     &lt;b&gt;This &lt;i&gt;is some&lt;span /&gt; sample&lt;/i&gt; test text&lt;/b&gt;\r
263                  * // If "element" is &lt;span /&gt; and "parent" is &lt;i&gt;:\r
264                  * //     &lt;b&gt;This &lt;i&gt;is some&lt;/i&gt;&lt;span /&gt;&lt;i&gt; sample&lt;/i&gt; test text&lt;/b&gt;\r
265                  * element.breakParent( parent );\r
266                  * @example\r
267                  * // Before breaking:\r
268                  * //     &lt;b&gt;This &lt;i&gt;is some&lt;span /&gt; sample&lt;/i&gt; test text&lt;/b&gt;\r
269                  * // If "element" is &lt;span /&gt; and "parent" is &lt;b&gt;:\r
270                  * //     &lt;b&gt;This &lt;i&gt;is some&lt;/i&gt;&lt;/b&gt;&lt;span /&gt;&lt;b&gt;&lt;i&gt; sample&lt;/i&gt; test text&lt;/b&gt;\r
271                  * element.breakParent( parent );\r
272                  */\r
273                 breakParent : function( parent )\r
274                 {\r
275                         var range = new CKEDITOR.dom.range( this.getDocument() );\r
276 \r
277                         // We'll be extracting part of this element, so let's use our\r
278                         // range to get the correct piece.\r
279                         range.setStartAfter( this );\r
280                         range.setEndAfter( parent );\r
281 \r
282                         // Extract it.\r
283                         var docFrag = range.extractContents();\r
284 \r
285                         // Move the element outside the broken element.\r
286                         range.insertNode( this.remove() );\r
287 \r
288                         // Re-insert the extracted piece after the element.\r
289                         docFrag.insertAfterNode( this );\r
290                 },\r
291 \r
292                 contains :\r
293                         CKEDITOR.env.ie || CKEDITOR.env.webkit ?\r
294                                 function( node )\r
295                                 {\r
296                                         var $ = this.$;\r
297 \r
298                                         return node.type != CKEDITOR.NODE_ELEMENT ?\r
299                                                 $.contains( node.getParent().$ ) :\r
300                                                 $ != node.$ && $.contains( node.$ );\r
301                                 }\r
302                         :\r
303                                 function( node )\r
304                                 {\r
305                                         return !!( this.$.compareDocumentPosition( node.$ ) & 16 );\r
306                                 },\r
307 \r
308                 /**\r
309                  * Moves the selection focus to this element.\r
310                  * @function\r
311                  * @param  {Boolean} defer Whether to asynchronously defer the\r
312                  *              execution by 100 ms.\r
313                  * @example\r
314                  * var element = CKEDITOR.document.getById( 'myTextarea' );\r
315                  * <b>element.focus()</b>;\r
316                  */\r
317                 focus : ( function()\r
318                 {\r
319                         function exec()\r
320                         {\r
321                         // IE throws error if the element is not visible.\r
322                         try\r
323                         {\r
324                                 this.$.focus();\r
325                         }\r
326                         catch (e)\r
327                         {}\r
328                         }\r
329 \r
330                         return function( defer )\r
331                         {\r
332                                 if ( defer )\r
333                                         CKEDITOR.tools.setTimeout( exec, 100, this );\r
334                                 else\r
335                                         exec.call( this );\r
336                         };\r
337                 })(),\r
338 \r
339                 /**\r
340                  * Gets the inner HTML of this element.\r
341                  * @returns {String} The inner HTML of this element.\r
342                  * @example\r
343                  * var element = CKEDITOR.dom.element.createFromHtml( '&lt;div&gt;&lt;b&gt;Example&lt;/b&gt;&lt;/div&gt;' );\r
344                  * alert( <b>p.getHtml()</b> );  // "&lt;b&gt;Example&lt;/b&gt;"\r
345                  */\r
346                 getHtml : function()\r
347                 {\r
348                         var retval = this.$.innerHTML;\r
349                         // Strip <?xml:namespace> tags in IE. (#3341).\r
350                         return CKEDITOR.env.ie ? retval.replace( /<\?[^>]*>/g, '' ) : retval;\r
351                 },\r
352 \r
353                 getOuterHtml : function()\r
354                 {\r
355                         if ( this.$.outerHTML )\r
356                         {\r
357                                 // IE includes the <?xml:namespace> tag in the outerHTML of\r
358                                 // namespaced element. So, we must strip it here. (#3341)\r
359                                 return this.$.outerHTML.replace( /<\?[^>]*>/, '' );\r
360                         }\r
361 \r
362                         var tmpDiv = this.$.ownerDocument.createElement( 'div' );\r
363                         tmpDiv.appendChild( this.$.cloneNode( true ) );\r
364                         return tmpDiv.innerHTML;\r
365                 },\r
366 \r
367                 /**\r
368                  * Sets the inner HTML of this element.\r
369                  * @param {String} html The HTML to be set for this element.\r
370                  * @returns {String} The inserted HTML.\r
371                  * @example\r
372                  * var p = new CKEDITOR.dom.element( 'p' );\r
373                  * <b>p.setHtml( '&lt;b&gt;Inner&lt;/b&gt; HTML' );</b>\r
374                  *\r
375                  * // result: "&lt;p&gt;&lt;b&gt;Inner&lt;/b&gt; HTML&lt;/p&gt;"\r
376                  */\r
377                 setHtml : function( html )\r
378                 {\r
379                         return ( this.$.innerHTML = html );\r
380                 },\r
381 \r
382                 /**\r
383                  * Sets the element contents as plain text.\r
384                  * @param {String} text The text to be set.\r
385                  * @returns {String} The inserted text.\r
386                  * @example\r
387                  * var element = new CKEDITOR.dom.element( 'div' );\r
388                  * element.setText( 'A > B & C < D' );\r
389                  * alert( element.innerHTML );  // "A &amp;gt; B &amp;amp; C &amp;lt; D"\r
390                  */\r
391                 setText : function( text )\r
392                 {\r
393                         CKEDITOR.dom.element.prototype.setText = ( this.$.innerText != undefined ) ?\r
394                                 function ( text )\r
395                                 {\r
396                                         return this.$.innerText = text;\r
397                                 } :\r
398                                 function ( text )\r
399                                 {\r
400                                         return this.$.textContent = text;\r
401                                 };\r
402 \r
403                         return this.setText( text );\r
404                 },\r
405 \r
406                 /**\r
407                  * Gets the value of an element attribute.\r
408                  * @function\r
409                  * @param {String} name The attribute name.\r
410                  * @returns {String} The attribute value or null if not defined.\r
411                  * @example\r
412                  * var element = CKEDITOR.dom.element.createFromHtml( '&lt;input type="text" /&gt;' );\r
413                  * alert( <b>element.getAttribute( 'type' )</b> );  // "text"\r
414                  */\r
415                 getAttribute : (function()\r
416                 {\r
417                         var standard = function( name )\r
418                         {\r
419                                 return this.$.getAttribute( name, 2 );\r
420                         };\r
421 \r
422                         if ( CKEDITOR.env.ie && ( CKEDITOR.env.ie7Compat || CKEDITOR.env.ie6Compat ) )\r
423                         {\r
424                                 return function( name )\r
425                                 {\r
426                                         switch ( name )\r
427                                         {\r
428                                                 case 'class':\r
429                                                         name = 'className';\r
430                                                         break;\r
431 \r
432                                                 case 'http-equiv':\r
433                                                         name = 'httpEquiv';\r
434                                                         break;\r
435 \r
436                                                 case 'name':\r
437                                                         return this.$.name;\r
438 \r
439                                                 case 'tabindex':\r
440                                                         var tabIndex = standard.call( this, name );\r
441 \r
442                                                         // IE returns tabIndex=0 by default for all\r
443                                                         // elements. For those elements,\r
444                                                         // getAtrribute( 'tabindex', 2 ) returns 32768\r
445                                                         // instead. So, we must make this check to give a\r
446                                                         // uniform result among all browsers.\r
447                                                         if ( tabIndex !== 0 && this.$.tabIndex === 0 )\r
448                                                                 tabIndex = null;\r
449 \r
450                                                         return tabIndex;\r
451                                                         break;\r
452 \r
453                                                 case 'checked':\r
454                                                 {\r
455                                                         var attr = this.$.attributes.getNamedItem( name ),\r
456                                                                 attrValue = attr.specified ? attr.nodeValue     // For value given by parser.\r
457                                                                                                                          : this.$.checked;  // For value created via DOM interface.\r
458 \r
459                                                         return attrValue ? 'checked' : null;\r
460                                                 }\r
461 \r
462                                                 case 'hspace':\r
463                                                 case 'value':\r
464                                                         return this.$[ name ];\r
465 \r
466                                                 case 'style':\r
467                                                         // IE does not return inline styles via getAttribute(). See #2947.\r
468                                                         return this.$.style.cssText;\r
469                                         }\r
470 \r
471                                         return standard.call( this, name );\r
472                                 };\r
473                         }\r
474                         else\r
475                                 return standard;\r
476                 })(),\r
477 \r
478                 getChildren : function()\r
479                 {\r
480                         return new CKEDITOR.dom.nodeList( this.$.childNodes );\r
481                 },\r
482 \r
483                 /**\r
484                  * Gets the current computed value of one of the element CSS style\r
485                  * properties.\r
486                  * @function\r
487                  * @param {String} propertyName The style property name.\r
488                  * @returns {String} The property value.\r
489                  * @example\r
490                  * var element = new CKEDITOR.dom.element( 'span' );\r
491                  * alert( <b>element.getComputedStyle( 'display' )</b> );  // "inline"\r
492                  */\r
493                 getComputedStyle :\r
494                         CKEDITOR.env.ie ?\r
495                                 function( propertyName )\r
496                                 {\r
497                                         return this.$.currentStyle[ CKEDITOR.tools.cssStyleToDomStyle( propertyName ) ];\r
498                                 }\r
499                         :\r
500                                 function( propertyName )\r
501                                 {\r
502                                         return this.getWindow().$.getComputedStyle( this.$, '' ).getPropertyValue( propertyName );\r
503                                 },\r
504 \r
505                 /**\r
506                  * Gets the DTD entries for this element.\r
507                  * @returns {Object} An object containing the list of elements accepted\r
508                  *              by this element.\r
509                  */\r
510                 getDtd : function()\r
511                 {\r
512                         var dtd = CKEDITOR.dtd[ this.getName() ];\r
513 \r
514                         this.getDtd = function()\r
515                         {\r
516                                 return dtd;\r
517                         };\r
518 \r
519                         return dtd;\r
520                 },\r
521 \r
522                 getElementsByTag : CKEDITOR.dom.document.prototype.getElementsByTag,\r
523 \r
524                 /**\r
525                  * Gets the computed tabindex for this element.\r
526                  * @function\r
527                  * @returns {Number} The tabindex value.\r
528                  * @example\r
529                  * var element = CKEDITOR.document.getById( 'myDiv' );\r
530                  * alert( <b>element.getTabIndex()</b> );  // e.g. "-1"\r
531                  */\r
532                 getTabIndex :\r
533                         CKEDITOR.env.ie ?\r
534                                 function()\r
535                                 {\r
536                                         var tabIndex = this.$.tabIndex;\r
537 \r
538                                         // IE returns tabIndex=0 by default for all elements. In\r
539                                         // those cases we must check that the element really has\r
540                                         // the tabindex attribute set to zero, or it is one of\r
541                                         // those element that should have zero by default.\r
542                                         if ( tabIndex === 0 && !CKEDITOR.dtd.$tabIndex[ this.getName() ] && parseInt( this.getAttribute( 'tabindex' ), 10 ) !== 0 )\r
543                                                 tabIndex = -1;\r
544 \r
545                                                 return tabIndex;\r
546                                 }\r
547                         : CKEDITOR.env.webkit ?\r
548                                 function()\r
549                                 {\r
550                                         var tabIndex = this.$.tabIndex;\r
551 \r
552                                         // Safari returns "undefined" for elements that should not\r
553                                         // have tabindex (like a div). So, we must try to get it\r
554                                         // from the attribute.\r
555                                         // https://bugs.webkit.org/show_bug.cgi?id=20596\r
556                                         if ( tabIndex == undefined )\r
557                                         {\r
558                                                 tabIndex = parseInt( this.getAttribute( 'tabindex' ), 10 );\r
559 \r
560                                                 // If the element don't have the tabindex attribute,\r
561                                                 // then we should return -1.\r
562                                                 if ( isNaN( tabIndex ) )\r
563                                                         tabIndex = -1;\r
564                                         }\r
565 \r
566                                         return tabIndex;\r
567                                 }\r
568                         :\r
569                                 function()\r
570                                 {\r
571                                         return this.$.tabIndex;\r
572                                 },\r
573 \r
574                 /**\r
575                  * Gets the text value of this element.\r
576                  *\r
577                  * Only in IE (which uses innerText), &lt;br&gt; will cause linebreaks,\r
578                  * and sucessive whitespaces (including line breaks) will be reduced to\r
579                  * a single space. This behavior is ok for us, for now. It may change\r
580                  * in the future.\r
581                  * @returns {String} The text value.\r
582                  * @example\r
583                  * var element = CKEDITOR.dom.element.createFromHtml( '&lt;div&gt;Same &lt;i&gt;text&lt;/i&gt;.&lt;/div&gt;' );\r
584                  * alert( <b>element.getText()</b> );  // "Sample text."\r
585                  */\r
586                 getText : function()\r
587                 {\r
588                         return this.$.textContent || this.$.innerText || '';\r
589                 },\r
590 \r
591                 /**\r
592                  * Gets the window object that contains this element.\r
593                  * @returns {CKEDITOR.dom.window} The window object.\r
594                  * @example\r
595                  */\r
596                 getWindow : function()\r
597                 {\r
598                         return this.getDocument().getWindow();\r
599                 },\r
600 \r
601                 /**\r
602                  * Gets the value of the "id" attribute of this element.\r
603                  * @returns {String} The element id, or null if not available.\r
604                  * @example\r
605                  * var element = CKEDITOR.dom.element.createFromHtml( '&lt;p id="myId"&gt;&lt;/p&gt;' );\r
606                  * alert( <b>element.getId()</b> );  // "myId"\r
607                  */\r
608                 getId : function()\r
609                 {\r
610                         return this.$.id || null;\r
611                 },\r
612 \r
613                 /**\r
614                  * Gets the value of the "name" attribute of this element.\r
615                  * @returns {String} The element name, or null if not available.\r
616                  * @example\r
617                  * var element = CKEDITOR.dom.element.createFromHtml( '&lt;input name="myName"&gt;&lt;/input&gt;' );\r
618                  * alert( <b>element.getNameAtt()</b> );  // "myName"\r
619                  */\r
620                 getNameAtt : function()\r
621                 {\r
622                         return this.$.name || null;\r
623                 },\r
624 \r
625                 /**\r
626                  * Gets the element name (tag name). The returned name is guaranteed to\r
627                  * be always full lowercased.\r
628                  * @returns {String} The element name.\r
629                  * @example\r
630                  * var element = new CKEDITOR.dom.element( 'span' );\r
631                  * alert( <b>element.getName()</b> );  // "span"\r
632                  */\r
633                 getName : function()\r
634                 {\r
635                         // Cache the lowercased name inside a closure.\r
636                         var nodeName = this.$.nodeName.toLowerCase();\r
637 \r
638                         if ( CKEDITOR.env.ie && ! ( document.documentMode > 8 ) )\r
639                         {\r
640                                 var scopeName = this.$.scopeName;\r
641                                 if ( scopeName != 'HTML' )\r
642                                         nodeName = scopeName.toLowerCase() + ':' + nodeName;\r
643                         }\r
644 \r
645                         return (\r
646                         this.getName = function()\r
647                                 {\r
648                                         return nodeName;\r
649                                 })();\r
650                 },\r
651 \r
652                 /**\r
653                  * Gets the value set to this element. This value is usually available\r
654                  * for form field elements.\r
655                  * @returns {String} The element value.\r
656                  */\r
657                 getValue : function()\r
658                 {\r
659                         return this.$.value;\r
660                 },\r
661 \r
662                 /**\r
663                  * Gets the first child node of this element.\r
664                  * @param {Function} evaluator Filtering the result node.\r
665                  * @returns {CKEDITOR.dom.node} The first child node or null if not\r
666                  *              available.\r
667                  * @example\r
668                  * var element = CKEDITOR.dom.element.createFromHtml( '&lt;div&gt;&lt;b&gt;Example&lt;/b&gt;&lt;/div&gt;' );\r
669                  * var first = <b>element.getFirst()</b>;\r
670                  * alert( first.getName() );  // "b"\r
671                  */\r
672                 getFirst : function( evaluator )\r
673                 {\r
674                         var first = this.$.firstChild,\r
675                                 retval = first && new CKEDITOR.dom.node( first );\r
676                         if ( retval && evaluator && !evaluator( retval ) )\r
677                                 retval = retval.getNext( evaluator );\r
678 \r
679                         return retval;\r
680                 },\r
681 \r
682                 /**\r
683                  * @param {Function} evaluator Filtering the result node.\r
684                  */\r
685                 getLast : function( evaluator )\r
686                 {\r
687                         var last = this.$.lastChild,\r
688                                 retval = last && new CKEDITOR.dom.node( last );\r
689                         if ( retval && evaluator && !evaluator( retval ) )\r
690                                 retval = retval.getPrevious( evaluator );\r
691 \r
692                         return retval;\r
693                 },\r
694 \r
695                 getStyle : function( name )\r
696                 {\r
697                         return this.$.style[ CKEDITOR.tools.cssStyleToDomStyle( name ) ];\r
698                 },\r
699 \r
700                 /**\r
701                  * Checks if the element name matches one or more names.\r
702                  * @param {String} name[,name[,...]] One or more names to be checked.\r
703                  * @returns {Boolean} true if the element name matches any of the names.\r
704                  * @example\r
705                  * var element = new CKEDITOR.element( 'span' );\r
706                  * alert( <b>element.is( 'span' )</b> );  "true"\r
707                  * alert( <b>element.is( 'p', 'span' )</b> );  "true"\r
708                  * alert( <b>element.is( 'p' )</b> );  "false"\r
709                  * alert( <b>element.is( 'p', 'div' )</b> );  "false"\r
710                  */\r
711                 is : function()\r
712                 {\r
713                         var name = this.getName();\r
714                         for ( var i = 0 ; i < arguments.length ; i++ )\r
715                         {\r
716                                 if ( arguments[ i ] == name )\r
717                                         return true;\r
718                         }\r
719                         return false;\r
720                 },\r
721 \r
722                 /**\r
723                  * Decide whether one element is able to receive cursor.\r
724                  * @param {Boolean} [textCursor=true] Only consider element that could receive text child.\r
725                  */\r
726                 isEditable : function( textCursor )\r
727                 {\r
728                         var name = this.getName();\r
729 \r
730                         if ( this.isReadOnly()\r
731                                         || this.getComputedStyle( 'display' ) == 'none'\r
732                                         || this.getComputedStyle( 'visibility' ) == 'hidden'\r
733                                         || CKEDITOR.dtd.$nonEditable[ name ] )\r
734                         {\r
735                                 return false;\r
736                         }\r
737 \r
738                         if ( textCursor !== false )\r
739                         {\r
740                                 // Get the element DTD (defaults to span for unknown elements).\r
741                                 var dtd = CKEDITOR.dtd[ name ] || CKEDITOR.dtd.span;\r
742                                 // In the DTD # == text node.\r
743                                 return ( dtd && dtd[ '#'] );\r
744                         }\r
745 \r
746                         return true;\r
747                 },\r
748 \r
749                 isIdentical : function( otherElement )\r
750                 {\r
751                         if ( this.getName() != otherElement.getName() )\r
752                                 return false;\r
753 \r
754                         var thisAttribs = this.$.attributes,\r
755                                 otherAttribs = otherElement.$.attributes;\r
756 \r
757                         var thisLength = thisAttribs.length,\r
758                                 otherLength = otherAttribs.length;\r
759 \r
760                         for ( var i = 0 ; i < thisLength ; i++ )\r
761                         {\r
762                                 var attribute = thisAttribs[ i ];\r
763 \r
764                                 if ( attribute.nodeName == '_moz_dirty' )\r
765                                         continue;\r
766 \r
767                                 if ( ( !CKEDITOR.env.ie || ( attribute.specified && attribute.nodeName != 'data-cke-expando' ) ) && attribute.nodeValue != otherElement.getAttribute( attribute.nodeName ) )\r
768                                         return false;\r
769                         }\r
770 \r
771                         // For IE, we have to for both elements, because it's difficult to\r
772                         // know how the atttibutes collection is organized in its DOM.\r
773                         if ( CKEDITOR.env.ie )\r
774                         {\r
775                                 for ( i = 0 ; i < otherLength ; i++ )\r
776                                 {\r
777                                         attribute = otherAttribs[ i ];\r
778                                         if ( attribute.specified && attribute.nodeName != 'data-cke-expando'\r
779                                                         && attribute.nodeValue != this.getAttribute( attribute.nodeName ) )\r
780                                                 return false;\r
781                                 }\r
782                         }\r
783 \r
784                         return true;\r
785                 },\r
786 \r
787                 /**\r
788                  * Checks if this element is visible. May not work if the element is\r
789                  * child of an element with visibility set to "hidden", but works well\r
790                  * on the great majority of cases.\r
791                  * @return {Boolean} True if the element is visible.\r
792                  */\r
793                 isVisible : function()\r
794                 {\r
795                         var isVisible = ( this.$.offsetHeight || this.$.offsetWidth ) && this.getComputedStyle( 'visibility' ) != 'hidden',\r
796                                 elementWindow,\r
797                                 elementWindowFrame;\r
798 \r
799                         // Webkit and Opera report non-zero offsetHeight despite that\r
800                         // element is inside an invisible iframe. (#4542)\r
801                         if ( isVisible && ( CKEDITOR.env.webkit || CKEDITOR.env.opera ) )\r
802                         {\r
803                                 elementWindow = this.getWindow();\r
804 \r
805                                 if ( !elementWindow.equals( CKEDITOR.document.getWindow() )\r
806                                                 && ( elementWindowFrame = elementWindow.$.frameElement ) )\r
807                                 {\r
808                                         isVisible = new CKEDITOR.dom.element( elementWindowFrame ).isVisible();\r
809                                 }\r
810                         }\r
811 \r
812                         return !!isVisible;\r
813                 },\r
814 \r
815                 /**\r
816                  * Whether it's an empty inline elements which has no visual impact when removed.\r
817                  */\r
818                 isEmptyInlineRemoveable : function()\r
819                 {\r
820                         if ( !CKEDITOR.dtd.$removeEmpty[ this.getName() ] )\r
821                                 return false;\r
822 \r
823                         var children = this.getChildren();\r
824                         for ( var i = 0, count = children.count(); i < count; i++ )\r
825                         {\r
826                                 var child = children.getItem( i );\r
827 \r
828                                 if ( child.type == CKEDITOR.NODE_ELEMENT && child.data( 'cke-bookmark' ) )\r
829                                         continue;\r
830 \r
831                                 if ( child.type == CKEDITOR.NODE_ELEMENT && !child.isEmptyInlineRemoveable()\r
832                                         || child.type == CKEDITOR.NODE_TEXT && CKEDITOR.tools.trim( child.getText() ) )\r
833                                 {\r
834                                         return false;\r
835                                 }\r
836                         }\r
837                         return true;\r
838                 },\r
839 \r
840                 /**\r
841                  * Checks if the element has any defined attributes.\r
842                  * @function\r
843                  * @returns {Boolean} True if the element has attributes.\r
844                  * @example\r
845                  * var element = CKEDITOR.dom.element.createFromHtml( '&lt;div title="Test"&gt;Example&lt;/div&gt;' );\r
846                  * alert( <b>element.hasAttributes()</b> );  // "true"\r
847                  * @example\r
848                  * var element = CKEDITOR.dom.element.createFromHtml( '&lt;div&gt;Example&lt;/div&gt;' );\r
849                  * alert( <b>element.hasAttributes()</b> );  // "false"\r
850                  */\r
851                 hasAttributes :\r
852                         CKEDITOR.env.ie && ( CKEDITOR.env.ie7Compat || CKEDITOR.env.ie6Compat ) ?\r
853                                 function()\r
854                                 {\r
855                                         var attributes = this.$.attributes;\r
856 \r
857                                         for ( var i = 0 ; i < attributes.length ; i++ )\r
858                                         {\r
859                                                 var attribute = attributes[i];\r
860 \r
861                                                 switch ( attribute.nodeName )\r
862                                                 {\r
863                                                         case 'class' :\r
864                                                                 // IE has a strange bug. If calling removeAttribute('className'),\r
865                                                                 // the attributes collection will still contain the "class"\r
866                                                                 // attribute, which will be marked as "specified", even if the\r
867                                                                 // outerHTML of the element is not displaying the class attribute.\r
868                                                                 // Note : I was not able to reproduce it outside the editor,\r
869                                                                 // but I've faced it while working on the TC of #1391.\r
870                                                                 if ( this.getAttribute( 'class' ) )\r
871                                                                         return true;\r
872 \r
873                                                         // Attributes to be ignored.\r
874                                                         case 'data-cke-expando' :\r
875                                                                 continue;\r
876 \r
877                                                         /*jsl:fallthru*/\r
878 \r
879                                                         default :\r
880                                                                 if ( attribute.specified )\r
881                                                                         return true;\r
882                                                 }\r
883                                         }\r
884 \r
885                                         return false;\r
886                                 }\r
887                         :\r
888                                 function()\r
889                                 {\r
890                                         var attrs = this.$.attributes,\r
891                                                 attrsNum = attrs.length;\r
892 \r
893                                         // The _moz_dirty attribute might get into the element after pasting (#5455)\r
894                                         var execludeAttrs = { 'data-cke-expando' : 1, _moz_dirty : 1 };\r
895 \r
896                                         return attrsNum > 0 &&\r
897                                                 ( attrsNum > 2 ||\r
898                                                         !execludeAttrs[ attrs[0].nodeName ] ||\r
899                                                         ( attrsNum == 2 && !execludeAttrs[ attrs[1].nodeName ] ) );\r
900                                 },\r
901 \r
902                 /**\r
903                  * Checks if the specified attribute is defined for this element.\r
904                  * @returns {Boolean} True if the specified attribute is defined.\r
905                  * @param {String} name The attribute name.\r
906                  * @example\r
907                  */\r
908                 hasAttribute : (function()\r
909                 {\r
910                         function standard( name )\r
911                         {\r
912                                 var $attr = this.$.attributes.getNamedItem( name );\r
913                                 return !!( $attr && $attr.specified );\r
914                         }\r
915 \r
916                         return ( CKEDITOR.env.ie && CKEDITOR.env.version < 8 ) ?\r
917                                         function( name )\r
918                                         {\r
919                                                 // On IE < 8 the name attribute cannot be retrieved\r
920                                                 // right after the element creation and setting the\r
921                                                 // name with setAttribute.\r
922                                                 if ( name == 'name' )\r
923                                                         return !!this.$.name;\r
924 \r
925                                                 return standard.call( this, name );\r
926                                         }\r
927                                 :\r
928                                         standard;\r
929                 })(),\r
930 \r
931                 /**\r
932                  * Hides this element (display:none).\r
933                  * @example\r
934                  * var element = CKEDITOR.dom.element.getById( 'myElement' );\r
935                  * <b>element.hide()</b>;\r
936                  */\r
937                 hide : function()\r
938                 {\r
939                         this.setStyle( 'display', 'none' );\r
940                 },\r
941 \r
942                 moveChildren : function( target, toStart )\r
943                 {\r
944                         var $ = this.$;\r
945                         target = target.$;\r
946 \r
947                         if ( $ == target )\r
948                                 return;\r
949 \r
950                         var child;\r
951 \r
952                         if ( toStart )\r
953                         {\r
954                                 while ( ( child = $.lastChild ) )\r
955                                         target.insertBefore( $.removeChild( child ), target.firstChild );\r
956                         }\r
957                         else\r
958                         {\r
959                                 while ( ( child = $.firstChild ) )\r
960                                         target.appendChild( $.removeChild( child ) );\r
961                         }\r
962                 },\r
963 \r
964                 /**\r
965                  * Merges sibling elements that are identical to this one.<br>\r
966                  * <br>\r
967                  * Identical child elements are also merged. For example:<br>\r
968                  * &lt;b&gt;&lt;i&gt;&lt;/i&gt;&lt;/b&gt;&lt;b&gt;&lt;i&gt;&lt;/i&gt;&lt;/b&gt; =&gt; &lt;b&gt;&lt;i&gt;&lt;/i&gt;&lt;/b&gt;\r
969                  * @function\r
970                  * @param {Boolean} [inlineOnly] Allow only inline elements to be merged. Defaults to "true".\r
971                  */\r
972                 mergeSiblings : ( function()\r
973                 {\r
974                         function mergeElements( element, sibling, isNext )\r
975                         {\r
976                                 if ( sibling && sibling.type == CKEDITOR.NODE_ELEMENT )\r
977                                 {\r
978                                         // Jumping over bookmark nodes and empty inline elements, e.g. <b><i></i></b>,\r
979                                         // queuing them to be moved later. (#5567)\r
980                                         var pendingNodes = [];\r
981 \r
982                                         while ( sibling.data( 'cke-bookmark' )\r
983                                                 || sibling.isEmptyInlineRemoveable() )\r
984                                         {\r
985                                                 pendingNodes.push( sibling );\r
986                                                 sibling = isNext ? sibling.getNext() : sibling.getPrevious();\r
987                                                 if ( !sibling || sibling.type != CKEDITOR.NODE_ELEMENT )\r
988                                                         return;\r
989                                         }\r
990 \r
991                                         if ( element.isIdentical( sibling ) )\r
992                                         {\r
993                                                 // Save the last child to be checked too, to merge things like\r
994                                                 // <b><i></i></b><b><i></i></b> => <b><i></i></b>\r
995                                                 var innerSibling = isNext ? element.getLast() : element.getFirst();\r
996 \r
997                                                 // Move pending nodes first into the target element.\r
998                                                 while( pendingNodes.length )\r
999                                                         pendingNodes.shift().move( element, !isNext );\r
1000 \r
1001                                                 sibling.moveChildren( element, !isNext );\r
1002                                                 sibling.remove();\r
1003 \r
1004                                                 // Now check the last inner child (see two comments above).\r
1005                                                 if ( innerSibling && innerSibling.type == CKEDITOR.NODE_ELEMENT )\r
1006                                                         innerSibling.mergeSiblings();\r
1007                                         }\r
1008                                 }\r
1009                         }\r
1010 \r
1011                         return function( inlineOnly )\r
1012                                 {\r
1013                                         if ( ! ( inlineOnly === false\r
1014                                                         || CKEDITOR.dtd.$removeEmpty[ this.getName() ]\r
1015                                                         || this.is( 'a' ) ) )   // Merge empty links and anchors also. (#5567)\r
1016                                         {\r
1017                                                 return;\r
1018                                         }\r
1019 \r
1020                                         mergeElements( this, this.getNext(), true );\r
1021                                         mergeElements( this, this.getPrevious() );\r
1022                                 };\r
1023                 } )(),\r
1024 \r
1025                 /**\r
1026                  * Shows this element (display it).\r
1027                  * @example\r
1028                  * var element = CKEDITOR.dom.element.getById( 'myElement' );\r
1029                  * <b>element.show()</b>;\r
1030                  */\r
1031                 show : function()\r
1032                 {\r
1033                         this.setStyles(\r
1034                                 {\r
1035                                         display : '',\r
1036                                         visibility : ''\r
1037                                 });\r
1038                 },\r
1039 \r
1040                 /**\r
1041                  * Sets the value of an element attribute.\r
1042                  * @param {String} name The name of the attribute.\r
1043                  * @param {String} value The value to be set to the attribute.\r
1044                  * @function\r
1045                  * @returns {CKEDITOR.dom.element} This element instance.\r
1046                  * @example\r
1047                  * var element = CKEDITOR.dom.element.getById( 'myElement' );\r
1048                  * <b>element.setAttribute( 'class', 'myClass' )</b>;\r
1049                  * <b>element.setAttribute( 'title', 'This is an example' )</b>;\r
1050                  */\r
1051                 setAttribute : (function()\r
1052                 {\r
1053                         var standard = function( name, value )\r
1054                         {\r
1055                                 this.$.setAttribute( name, value );\r
1056                                 return this;\r
1057                         };\r
1058 \r
1059                         if ( CKEDITOR.env.ie && ( CKEDITOR.env.ie7Compat || CKEDITOR.env.ie6Compat ) )\r
1060                         {\r
1061                                 return function( name, value )\r
1062                                 {\r
1063                                         if ( name == 'class' )\r
1064                                                 this.$.className = value;\r
1065                                         else if ( name == 'style' )\r
1066                                                 this.$.style.cssText = value;\r
1067                                         else if ( name == 'tabindex' )  // Case sensitive.\r
1068                                                 this.$.tabIndex = value;\r
1069                                         else if ( name == 'checked' )\r
1070                                                 this.$.checked = value;\r
1071                                         else\r
1072                                                 standard.apply( this, arguments );\r
1073                                         return this;\r
1074                                 };\r
1075                         }\r
1076                         else if ( CKEDITOR.env.ie8Compat && CKEDITOR.env.secure )\r
1077                         {\r
1078                                 return function( name, value )\r
1079                                 {\r
1080                                         // IE8 throws error when setting src attribute to non-ssl value. (#7847)\r
1081                                         if ( name == 'src' && value.match( /^http:\/\// ) )\r
1082                                                 try { standard.apply( this, arguments ); } catch( e ){}\r
1083                                         else\r
1084                                                 standard.apply( this, arguments );\r
1085                                         return this;\r
1086                                 };\r
1087                         }\r
1088                         else\r
1089                                 return standard;\r
1090                 })(),\r
1091 \r
1092                 /**\r
1093                  * Sets the value of several element attributes.\r
1094                  * @param {Object} attributesPairs An object containing the names and\r
1095                  *              values of the attributes.\r
1096                  * @returns {CKEDITOR.dom.element} This element instance.\r
1097                  * @example\r
1098                  * var element = CKEDITOR.dom.element.getById( 'myElement' );\r
1099                  * <b>element.setAttributes({\r
1100                  *     'class' : 'myClass',\r
1101                  *     'title' : 'This is an example' })</b>;\r
1102                  */\r
1103                 setAttributes : function( attributesPairs )\r
1104                 {\r
1105                         for ( var name in attributesPairs )\r
1106                                 this.setAttribute( name, attributesPairs[ name ] );\r
1107                         return this;\r
1108                 },\r
1109 \r
1110                 /**\r
1111                  * Sets the element value. This function is usually used with form\r
1112                  * field element.\r
1113                  * @param {String} value The element value.\r
1114                  * @returns {CKEDITOR.dom.element} This element instance.\r
1115                  */\r
1116                 setValue : function( value )\r
1117                 {\r
1118                         this.$.value = value;\r
1119                         return this;\r
1120                 },\r
1121 \r
1122                 /**\r
1123                  * Removes an attribute from the element.\r
1124                  * @param {String} name The attribute name.\r
1125                  * @function\r
1126                  * @example\r
1127                  * var element = CKEDITOR.dom.element.createFromHtml( '<div class="classA"></div>' );\r
1128                  * element.removeAttribute( 'class' );\r
1129                  */\r
1130                 removeAttribute : (function()\r
1131                 {\r
1132                         var standard = function( name )\r
1133                         {\r
1134                                 this.$.removeAttribute( name );\r
1135                         };\r
1136 \r
1137                         if ( CKEDITOR.env.ie && ( CKEDITOR.env.ie7Compat || CKEDITOR.env.ie6Compat ) )\r
1138                         {\r
1139                                 return function( name )\r
1140                                 {\r
1141                                         if ( name == 'class' )\r
1142                                                 name = 'className';\r
1143                                         else if ( name == 'tabindex' )\r
1144                                                 name = 'tabIndex';\r
1145                                         standard.call( this, name );\r
1146                                 };\r
1147                         }\r
1148                         else\r
1149                                 return standard;\r
1150                 })(),\r
1151 \r
1152                 removeAttributes : function ( attributes )\r
1153                 {\r
1154                         if ( CKEDITOR.tools.isArray( attributes ) )\r
1155                         {\r
1156                                 for ( var i = 0 ; i < attributes.length ; i++ )\r
1157                                         this.removeAttribute( attributes[ i ] );\r
1158                         }\r
1159                         else\r
1160                         {\r
1161                                 for ( var attr in attributes )\r
1162                                         attributes.hasOwnProperty( attr ) && this.removeAttribute( attr );\r
1163                         }\r
1164                 },\r
1165 \r
1166                 /**\r
1167                  * Removes a style from the element.\r
1168                  * @param {String} name The style name.\r
1169                  * @function\r
1170                  * @example\r
1171                  * var element = CKEDITOR.dom.element.createFromHtml( '<div style="display:none"></div>' );\r
1172                  * element.removeStyle( 'display' );\r
1173                  */\r
1174                 removeStyle : function( name )\r
1175                 {\r
1176                         this.setStyle( name, '' );\r
1177                         if ( this.$.style.removeAttribute )\r
1178                                 this.$.style.removeAttribute( CKEDITOR.tools.cssStyleToDomStyle( name ) );\r
1179 \r
1180                         if ( !this.$.style.cssText )\r
1181                                 this.removeAttribute( 'style' );\r
1182                 },\r
1183 \r
1184                 /**\r
1185                  * Sets the value of an element style.\r
1186                  * @param {String} name The name of the style. The CSS naming notation\r
1187                  *              must be used (e.g. "background-color").\r
1188                  * @param {String} value The value to be set to the style.\r
1189                  * @returns {CKEDITOR.dom.element} This element instance.\r
1190                  * @example\r
1191                  * var element = CKEDITOR.dom.element.getById( 'myElement' );\r
1192                  * <b>element.setStyle( 'background-color', '#ff0000' )</b>;\r
1193                  * <b>element.setStyle( 'margin-top', '10px' )</b>;\r
1194                  * <b>element.setStyle( 'float', 'right' )</b>;\r
1195                  */\r
1196                 setStyle : function( name, value )\r
1197                 {\r
1198                         this.$.style[ CKEDITOR.tools.cssStyleToDomStyle( name ) ] = value;\r
1199                         return this;\r
1200                 },\r
1201 \r
1202                 /**\r
1203                  * Sets the value of several element styles.\r
1204                  * @param {Object} stylesPairs An object containing the names and\r
1205                  *              values of the styles.\r
1206                  * @returns {CKEDITOR.dom.element} This element instance.\r
1207                  * @example\r
1208                  * var element = CKEDITOR.dom.element.getById( 'myElement' );\r
1209                  * <b>element.setStyles({\r
1210                  *     'position' : 'absolute',\r
1211                  *     'float' : 'right' })</b>;\r
1212                  */\r
1213                 setStyles : function( stylesPairs )\r
1214                 {\r
1215                         for ( var name in stylesPairs )\r
1216                                 this.setStyle( name, stylesPairs[ name ] );\r
1217                         return this;\r
1218                 },\r
1219 \r
1220                 /**\r
1221                  * Sets the opacity of an element.\r
1222                  * @param {Number} opacity A number within the range [0.0, 1.0].\r
1223                  * @example\r
1224                  * var element = CKEDITOR.dom.element.getById( 'myElement' );\r
1225                  * <b>element.setOpacity( 0.75 )</b>;\r
1226                  */\r
1227                 setOpacity : function( opacity )\r
1228                 {\r
1229                         if ( CKEDITOR.env.ie )\r
1230                         {\r
1231                                 opacity = Math.round( opacity * 100 );\r
1232                                 this.setStyle( 'filter', opacity >= 100 ? '' : 'progid:DXImageTransform.Microsoft.Alpha(opacity=' + opacity + ')' );\r
1233                         }\r
1234                         else\r
1235                                 this.setStyle( 'opacity', opacity );\r
1236                 },\r
1237 \r
1238                 /**\r
1239                  * Makes the element and its children unselectable.\r
1240                  * @function\r
1241                  * @example\r
1242                  * var element = CKEDITOR.dom.element.getById( 'myElement' );\r
1243                  * element.unselectable();\r
1244                  */\r
1245                 unselectable :\r
1246                         CKEDITOR.env.gecko ?\r
1247                                 function()\r
1248                                 {\r
1249                                         this.$.style.MozUserSelect = 'none';\r
1250                                         this.on( 'dragstart', function( evt ) { evt.data.preventDefault(); } );\r
1251                                 }\r
1252                         : CKEDITOR.env.webkit ?\r
1253                                 function()\r
1254                                 {\r
1255                                         this.$.style.KhtmlUserSelect = 'none';\r
1256                                         this.on( 'dragstart', function( evt ) { evt.data.preventDefault(); } );\r
1257                                 }\r
1258                         :\r
1259                                 function()\r
1260                                 {\r
1261                                         if ( CKEDITOR.env.ie || CKEDITOR.env.opera )\r
1262                                         {\r
1263                                                 var element = this.$,\r
1264                                                         e,\r
1265                                                         i = 0;\r
1266 \r
1267                                                 element.unselectable = 'on';\r
1268 \r
1269                                                 while ( ( e = element.all[ i++ ] ) )\r
1270                                                 {\r
1271                                                         switch ( e.tagName.toLowerCase() )\r
1272                                                         {\r
1273                                                                 case 'iframe' :\r
1274                                                                 case 'textarea' :\r
1275                                                                 case 'input' :\r
1276                                                                 case 'select' :\r
1277                                                                         /* Ignore the above tags */\r
1278                                                                         break;\r
1279                                                                 default :\r
1280                                                                         e.unselectable = 'on';\r
1281                                                         }\r
1282                                                 }\r
1283                                         }\r
1284                                 },\r
1285 \r
1286                 getPositionedAncestor : function()\r
1287                 {\r
1288                         var current = this;\r
1289                         while ( current.getName() != 'html' )\r
1290                         {\r
1291                                 if ( current.getComputedStyle( 'position' ) != 'static' )\r
1292                                         return current;\r
1293 \r
1294                                 current = current.getParent();\r
1295                         }\r
1296                         return null;\r
1297                 },\r
1298 \r
1299                 getDocumentPosition : function( refDocument )\r
1300                 {\r
1301                         var x = 0, y = 0,\r
1302                                 doc = this.getDocument(),\r
1303                                 body = doc.getBody(),\r
1304                                 quirks = doc.$.compatMode == 'BackCompat';\r
1305 \r
1306                         if ( document.documentElement[ "getBoundingClientRect" ] )\r
1307                         {\r
1308                                 var box  = this.$.getBoundingClientRect(),\r
1309                                         $doc = doc.$,\r
1310                                         $docElem = $doc.documentElement;\r
1311 \r
1312                                 var clientTop = $docElem.clientTop || body.$.clientTop || 0,\r
1313                                         clientLeft = $docElem.clientLeft || body.$.clientLeft || 0,\r
1314                                         needAdjustScrollAndBorders = true;\r
1315 \r
1316                                 /*\r
1317                                  * #3804: getBoundingClientRect() works differently on IE and non-IE\r
1318                                  * browsers, regarding scroll positions.\r
1319                                  *\r
1320                                  * On IE, the top position of the <html> element is always 0, no matter\r
1321                                  * how much you scrolled down.\r
1322                                  *\r
1323                                  * On other browsers, the top position of the <html> element is negative\r
1324                                  * scrollTop.\r
1325                                  */\r
1326                                 if ( CKEDITOR.env.ie )\r
1327                                 {\r
1328                                         var inDocElem = doc.getDocumentElement().contains( this ),\r
1329                                                 inBody = doc.getBody().contains( this );\r
1330 \r
1331                                         needAdjustScrollAndBorders = ( quirks && inBody ) || ( !quirks && inDocElem );\r
1332                                 }\r
1333 \r
1334                                 if ( needAdjustScrollAndBorders )\r
1335                                 {\r
1336                                         x = box.left + ( !quirks && $docElem.scrollLeft || body.$.scrollLeft );\r
1337                                         x -= clientLeft;\r
1338                                         y = box.top  + ( !quirks && $docElem.scrollTop || body.$.scrollTop );\r
1339                                         y -= clientTop;\r
1340                                 }\r
1341                         }\r
1342                         else\r
1343                         {\r
1344                                 var current = this, previous = null, offsetParent;\r
1345                                 while ( current && !( current.getName() == 'body' || current.getName() == 'html' ) )\r
1346                                 {\r
1347                                         x += current.$.offsetLeft - current.$.scrollLeft;\r
1348                                         y += current.$.offsetTop - current.$.scrollTop;\r
1349 \r
1350                                         // Opera includes clientTop|Left into offsetTop|Left.\r
1351                                         if ( !current.equals( this ) )\r
1352                                         {\r
1353                                                 x += ( current.$.clientLeft || 0 );\r
1354                                                 y += ( current.$.clientTop || 0 );\r
1355                                         }\r
1356 \r
1357                                         var scrollElement = previous;\r
1358                                         while ( scrollElement && !scrollElement.equals( current ) )\r
1359                                         {\r
1360                                                 x -= scrollElement.$.scrollLeft;\r
1361                                                 y -= scrollElement.$.scrollTop;\r
1362                                                 scrollElement = scrollElement.getParent();\r
1363                                         }\r
1364 \r
1365                                         previous = current;\r
1366                                         current = ( offsetParent = current.$.offsetParent ) ?\r
1367                                                   new CKEDITOR.dom.element( offsetParent ) : null;\r
1368                                 }\r
1369                         }\r
1370 \r
1371                         if ( refDocument )\r
1372                         {\r
1373                                 var currentWindow = this.getWindow(),\r
1374                                         refWindow = refDocument.getWindow();\r
1375 \r
1376                                 if ( !currentWindow.equals( refWindow ) && currentWindow.$.frameElement )\r
1377                                 {\r
1378                                         var iframePosition = ( new CKEDITOR.dom.element( currentWindow.$.frameElement ) ).getDocumentPosition( refDocument );\r
1379 \r
1380                                         x += iframePosition.x;\r
1381                                         y += iframePosition.y;\r
1382                                 }\r
1383                         }\r
1384 \r
1385                         if ( !document.documentElement[ "getBoundingClientRect" ] )\r
1386                         {\r
1387                                 // In Firefox, we'll endup one pixel before the element positions,\r
1388                                 // so we must add it here.\r
1389                                 if ( CKEDITOR.env.gecko && !quirks )\r
1390                                 {\r
1391                                         x += this.$.clientLeft ? 1 : 0;\r
1392                                         y += this.$.clientTop ? 1 : 0;\r
1393                                 }\r
1394                         }\r
1395 \r
1396                         return { x : x, y : y };\r
1397                 },\r
1398 \r
1399                 scrollIntoView : function( alignTop )\r
1400                 {\r
1401                         // Get the element window.\r
1402                         var win = this.getWindow(),\r
1403                                 winHeight = win.getViewPaneSize().height;\r
1404 \r
1405                         // Starts from the offset that will be scrolled with the negative value of\r
1406                         // the visible window height.\r
1407                         var offset = winHeight * -1;\r
1408 \r
1409                         // Append the view pane's height if align to top.\r
1410                         // Append element height if we are aligning to the bottom.\r
1411                         if ( alignTop )\r
1412                                 offset += winHeight;\r
1413                         else\r
1414                         {\r
1415                                 offset += this.$.offsetHeight || 0;\r
1416 \r
1417                                 // Consider the margin in the scroll, which is ok for our current needs, but\r
1418                                 // needs investigation if we will be using this function in other places.\r
1419                                 offset += parseInt( this.getComputedStyle( 'marginBottom' ) || 0, 10 ) || 0;\r
1420                         }\r
1421 \r
1422                         // Append the offsets for the entire element hierarchy.\r
1423                         var elementPosition = this.getDocumentPosition();\r
1424                         offset += elementPosition.y;\r
1425 \r
1426                         // offset value might be out of range(nagative), fix it(#3692).\r
1427                         offset = offset < 0 ? 0 : offset;\r
1428 \r
1429                         // Scroll the window to the desired position, if not already visible(#3795).\r
1430                         var currentScroll = win.getScrollPosition().y;\r
1431                         if ( offset > currentScroll || offset < currentScroll - winHeight )\r
1432                                 win.$.scrollTo( 0, offset );\r
1433                 },\r
1434 \r
1435                 setState : function( state )\r
1436                 {\r
1437                         switch ( state )\r
1438                         {\r
1439                                 case CKEDITOR.TRISTATE_ON :\r
1440                                         this.addClass( 'cke_on' );\r
1441                                         this.removeClass( 'cke_off' );\r
1442                                         this.removeClass( 'cke_disabled' );\r
1443                                         break;\r
1444                                 case CKEDITOR.TRISTATE_DISABLED :\r
1445                                         this.addClass( 'cke_disabled' );\r
1446                                         this.removeClass( 'cke_off' );\r
1447                                         this.removeClass( 'cke_on' );\r
1448                                         break;\r
1449                                 default :\r
1450                                         this.addClass( 'cke_off' );\r
1451                                         this.removeClass( 'cke_on' );\r
1452                                         this.removeClass( 'cke_disabled' );\r
1453                                         break;\r
1454                         }\r
1455                 },\r
1456 \r
1457                 /**\r
1458                  * Returns the inner document of this IFRAME element.\r
1459                  * @returns {CKEDITOR.dom.document} The inner document.\r
1460                  */\r
1461                 getFrameDocument : function()\r
1462                 {\r
1463                         var $ = this.$;\r
1464 \r
1465                         try\r
1466                         {\r
1467                                 // In IE, with custom document.domain, it may happen that\r
1468                                 // the iframe is not yet available, resulting in "Access\r
1469                                 // Denied" for the following property access.\r
1470                                 $.contentWindow.document;\r
1471                         }\r
1472                         catch ( e )\r
1473                         {\r
1474                                 // Trick to solve this issue, forcing the iframe to get ready\r
1475                                 // by simply setting its "src" property.\r
1476                                 $.src = $.src;\r
1477 \r
1478                                 // In IE6 though, the above is not enough, so we must pause the\r
1479                                 // execution for a while, giving it time to think.\r
1480                                 if ( CKEDITOR.env.ie && CKEDITOR.env.version < 7 )\r
1481                                 {\r
1482                                         window.showModalDialog(\r
1483                                                 'javascript:document.write("' +\r
1484                                                         '<script>' +\r
1485                                                                 'window.setTimeout(' +\r
1486                                                                         'function(){window.close();}' +\r
1487                                                                         ',50);' +\r
1488                                                         '</script>")' );\r
1489                                 }\r
1490                         }\r
1491 \r
1492                         return $ && new CKEDITOR.dom.document( $.contentWindow.document );\r
1493                 },\r
1494 \r
1495                 /**\r
1496                  * Copy all the attributes from one node to the other, kinda like a clone\r
1497                  * skipAttributes is an object with the attributes that must NOT be copied.\r
1498                  * @param {CKEDITOR.dom.element} dest The destination element.\r
1499                  * @param {Object} skipAttributes A dictionary of attributes to skip.\r
1500                  * @example\r
1501                  */\r
1502                 copyAttributes : function( dest, skipAttributes )\r
1503                 {\r
1504                         var attributes = this.$.attributes;\r
1505                         skipAttributes = skipAttributes || {};\r
1506 \r
1507                         for ( var n = 0 ; n < attributes.length ; n++ )\r
1508                         {\r
1509                                 var attribute = attributes[n];\r
1510 \r
1511                                 // Lowercase attribute name hard rule is broken for\r
1512                                 // some attribute on IE, e.g. CHECKED.\r
1513                                 var attrName = attribute.nodeName.toLowerCase(),\r
1514                                         attrValue;\r
1515 \r
1516                                 // We can set the type only once, so do it with the proper value, not copying it.\r
1517                                 if ( attrName in skipAttributes )\r
1518                                         continue;\r
1519 \r
1520                                 if ( attrName == 'checked' && ( attrValue = this.getAttribute( attrName ) ) )\r
1521                                         dest.setAttribute( attrName, attrValue );\r
1522                                 // IE BUG: value attribute is never specified even if it exists.\r
1523                                 else if ( attribute.specified ||\r
1524                                   ( CKEDITOR.env.ie && attribute.nodeValue && attrName == 'value' ) )\r
1525                                 {\r
1526                                         attrValue = this.getAttribute( attrName );\r
1527                                         if ( attrValue === null )\r
1528                                                 attrValue = attribute.nodeValue;\r
1529 \r
1530                                         dest.setAttribute( attrName, attrValue );\r
1531                                 }\r
1532                         }\r
1533 \r
1534                         // The style:\r
1535                         if ( this.$.style.cssText !== '' )\r
1536                                 dest.$.style.cssText = this.$.style.cssText;\r
1537                 },\r
1538 \r
1539                 /**\r
1540                  * Changes the tag name of the current element.\r
1541                  * @param {String} newTag The new tag for the element.\r
1542                  */\r
1543                 renameNode : function( newTag )\r
1544                 {\r
1545                         // If it's already correct exit here.\r
1546                         if ( this.getName() == newTag )\r
1547                                 return;\r
1548 \r
1549                         var doc = this.getDocument();\r
1550 \r
1551                         // Create the new node.\r
1552                         var newNode = new CKEDITOR.dom.element( newTag, doc );\r
1553 \r
1554                         // Copy all attributes.\r
1555                         this.copyAttributes( newNode );\r
1556 \r
1557                         // Move children to the new node.\r
1558                         this.moveChildren( newNode );\r
1559 \r
1560                         // Replace the node.\r
1561                         this.getParent() && this.$.parentNode.replaceChild( newNode.$, this.$ );\r
1562                         newNode.$[ 'data-cke-expando' ] = this.$[ 'data-cke-expando' ];\r
1563                         this.$ = newNode.$;\r
1564                 },\r
1565 \r
1566                 /**\r
1567                  * Gets a DOM tree descendant under the current node.\r
1568                  * @param {Array|Number} indices The child index or array of child indices under the node.\r
1569                  * @returns {CKEDITOR.dom.node} The specified DOM child under the current node. Null if child does not exist.\r
1570                  * @example\r
1571                  * var strong = p.getChild(0);\r
1572                  */\r
1573                 getChild : function( indices )\r
1574                 {\r
1575                         var rawNode = this.$;\r
1576 \r
1577                         if ( !indices.slice )\r
1578                                 rawNode = rawNode.childNodes[ indices ];\r
1579                         else\r
1580                         {\r
1581                                 while ( indices.length > 0 && rawNode )\r
1582                                         rawNode = rawNode.childNodes[ indices.shift() ];\r
1583                         }\r
1584 \r
1585                         return rawNode ? new CKEDITOR.dom.node( rawNode ) : null;\r
1586                 },\r
1587 \r
1588                 getChildCount : function()\r
1589                 {\r
1590                         return this.$.childNodes.length;\r
1591                 },\r
1592 \r
1593                 disableContextMenu : function()\r
1594                 {\r
1595                         this.on( 'contextmenu', function( event )\r
1596                                 {\r
1597                                         // Cancel the browser context menu.\r
1598                                         if ( !event.data.getTarget().hasClass( 'cke_enable_context_menu' ) )\r
1599                                                 event.data.preventDefault();\r
1600                                 } );\r
1601                 },\r
1602 \r
1603                 /**\r
1604                  * Gets element's direction. Supports both CSS 'direction' prop and 'dir' attr.\r
1605                  */\r
1606                 getDirection : function( useComputed )\r
1607                 {\r
1608                         return useComputed ?\r
1609                                 this.getComputedStyle( 'direction' )\r
1610                                         // Webkit: offline element returns empty direction (#8053).\r
1611                                         || this.getDirection()\r
1612                                         || this.getDocument().$.dir\r
1613                                         || this.getDocument().getBody().getDirection( 1 )\r
1614                                 : this.getStyle( 'direction' ) || this.getAttribute( 'dir' );\r
1615                 },\r
1616 \r
1617                 /**\r
1618                  * Gets, sets and removes custom data to be stored as HTML5 data-* attributes.\r
1619                  * @param {String} name The name of the attribute, excluding the 'data-' part.\r
1620                  * @param {String} [value] The value to set. If set to false, the attribute will be removed.\r
1621                  * @example\r
1622                  * element.data( 'extra-info', 'test' );   // appended the attribute data-extra-info="test" to the element\r
1623                  * alert( element.data( 'extra-info' ) );  // "test"\r
1624                  * element.data( 'extra-info', false );    // remove the data-extra-info attribute from the element\r
1625                  */\r
1626                 data : function ( name, value )\r
1627                 {\r
1628                         name = 'data-' + name;\r
1629                         if ( value === undefined )\r
1630                                 return this.getAttribute( name );\r
1631                         else if ( value === false )\r
1632                                 this.removeAttribute( name );\r
1633                         else\r
1634                                 this.setAttribute( name, value );\r
1635 \r
1636                         return null;\r
1637                 }\r
1638         });\r
1639 \r
1640 ( function()\r
1641 {\r
1642         var sides = {\r
1643                 width : [ "border-left-width", "border-right-width","padding-left", "padding-right" ],\r
1644                 height : [ "border-top-width", "border-bottom-width", "padding-top",  "padding-bottom" ]\r
1645         };\r
1646 \r
1647         function marginAndPaddingSize( type )\r
1648         {\r
1649                 var adjustment = 0;\r
1650                 for ( var i = 0, len = sides[ type ].length; i < len; i++ )\r
1651                         adjustment += parseInt( this.getComputedStyle( sides [ type ][ i ] ) || 0, 10 ) || 0;\r
1652                 return adjustment;\r
1653         }\r
1654 \r
1655         /**\r
1656          * Sets the element size considering the box model.\r
1657          * @name CKEDITOR.dom.element.prototype.setSize\r
1658          * @function\r
1659          * @param {String} type The dimension to set. It accepts "width" and "height".\r
1660          * @param {Number} size The length unit in px.\r
1661          * @param {Boolean} isBorderBox Apply the size based on the border box model.\r
1662          */\r
1663         CKEDITOR.dom.element.prototype.setSize = function( type, size, isBorderBox )\r
1664                 {\r
1665                         if ( typeof size == 'number' )\r
1666                         {\r
1667                                 if ( isBorderBox && !( CKEDITOR.env.ie && CKEDITOR.env.quirks ) )\r
1668                                         size -= marginAndPaddingSize.call( this, type );\r
1669 \r
1670                                 this.setStyle( type, size + 'px' );\r
1671                         }\r
1672                 };\r
1673 \r
1674         /**\r
1675          * Gets the element size, possibly considering the box model.\r
1676          * @name CKEDITOR.dom.element.prototype.getSize\r
1677          * @function\r
1678          * @param {String} type The dimension to get. It accepts "width" and "height".\r
1679          * @param {Boolean} isBorderBox Get the size based on the border box model.\r
1680          */\r
1681         CKEDITOR.dom.element.prototype.getSize = function( type, isBorderBox )\r
1682                 {\r
1683                         var size = Math.max( this.$[ 'offset' + CKEDITOR.tools.capitalize( type )  ],\r
1684                                 this.$[ 'client' + CKEDITOR.tools.capitalize( type )  ] ) || 0;\r
1685 \r
1686                         if ( isBorderBox )\r
1687                                 size -= marginAndPaddingSize.call( this, type );\r
1688 \r
1689                         return size;\r
1690                 };\r
1691 })();\r