]> git.immae.eu Git - perso/Immae/Projets/packagist/connexionswing-ckeditor-component.git/blob - sources/core/dom/element.js
Initial commit
[perso/Immae/Projets/packagist/connexionswing-ckeditor-component.git] / sources / core / dom / element.js
1 /**
2 * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5
6 /**
7 * @fileOverview Defines the {@link CKEDITOR.dom.element} class, which
8 * represents a DOM element.
9 */
10
11 /**
12 * Represents a DOM element.
13 *
14 * // Create a new <span> element.
15 * var element = new CKEDITOR.dom.element( 'span' );
16 *
17 * // Create an element based on a native DOM element.
18 * var element = new CKEDITOR.dom.element( document.getElementById( 'myId' ) );
19 *
20 * @class
21 * @extends CKEDITOR.dom.node
22 * @constructor Creates an element class instance.
23 * @param {Object/String} element A native DOM element or the element name for
24 * new elements.
25 * @param {CKEDITOR.dom.document} [ownerDocument] The document that will contain
26 * the element in case of element creation.
27 */
28 CKEDITOR.dom.element = function( element, ownerDocument ) {
29 if ( typeof element == 'string' )
30 element = ( ownerDocument ? ownerDocument.$ : document ).createElement( element );
31
32 // Call the base constructor (we must not call CKEDITOR.dom.node).
33 CKEDITOR.dom.domObject.call( this, element );
34 };
35
36 // PACKAGER_RENAME( CKEDITOR.dom.element )
37 /**
38 * The the {@link CKEDITOR.dom.element} representing and element. If the
39 * element is a native DOM element, it will be transformed into a valid
40 * CKEDITOR.dom.element object.
41 *
42 * var element = new CKEDITOR.dom.element( 'span' );
43 * alert( element == CKEDITOR.dom.element.get( element ) ); // true
44 *
45 * var element = document.getElementById( 'myElement' );
46 * alert( CKEDITOR.dom.element.get( element ).getName() ); // (e.g.) 'p'
47 *
48 * @static
49 * @param {String/Object} element Element's id or name or native DOM element.
50 * @returns {CKEDITOR.dom.element} The transformed element.
51 */
52 CKEDITOR.dom.element.get = function( element ) {
53 var el = typeof element == 'string' ? document.getElementById( element ) || document.getElementsByName( element )[ 0 ] : element;
54
55 return el && ( el.$ ? el : new CKEDITOR.dom.element( el ) );
56 };
57
58 CKEDITOR.dom.element.prototype = new CKEDITOR.dom.node();
59
60 /**
61 * Creates an instance of the {@link CKEDITOR.dom.element} class based on the
62 * HTML representation of an element.
63 *
64 * var element = CKEDITOR.dom.element.createFromHtml( '<strong class="anyclass">My element</strong>' );
65 * alert( element.getName() ); // 'strong'
66 *
67 * @static
68 * @param {String} html The element HTML. It should define only one element in
69 * the "root" level. The "root" element can have child nodes, but not siblings.
70 * @returns {CKEDITOR.dom.element} The element instance.
71 */
72 CKEDITOR.dom.element.createFromHtml = function( html, ownerDocument ) {
73 var temp = new CKEDITOR.dom.element( 'div', ownerDocument );
74 temp.setHtml( html );
75
76 // When returning the node, remove it from its parent to detach it.
77 return temp.getFirst().remove();
78 };
79
80 /**
81 * Sets {@link CKEDITOR.dom.element#setCustomData custom data} on an element in a way that it is later
82 * possible to {@link #clearAllMarkers clear all data} set on all elements sharing the same database.
83 *
84 * This mechanism is very useful when processing some portion of DOM. All markers can later be removed
85 * by calling the {@link #clearAllMarkers} method, hence markers will not leak to second pass of this algorithm.
86 *
87 * var database = {};
88 * CKEDITOR.dom.element.setMarker( database, element1, 'foo', 'bar' );
89 * CKEDITOR.dom.element.setMarker( database, element2, 'oof', [ 1, 2, 3 ] );
90 *
91 * element1.getCustomData( 'foo' ); // 'bar'
92 * element2.getCustomData( 'oof' ); // [ 1, 2, 3 ]
93 *
94 * CKEDITOR.dom.element.clearAllMarkers( database );
95 *
96 * element1.getCustomData( 'foo' ); // null
97 *
98 * @static
99 * @param {Object} database
100 * @param {CKEDITOR.dom.element} element
101 * @param {String} name
102 * @param {Object} value
103 * @returns {CKEDITOR.dom.element} The element.
104 */
105 CKEDITOR.dom.element.setMarker = function( database, element, name, value ) {
106 var id = element.getCustomData( 'list_marker_id' ) || ( element.setCustomData( 'list_marker_id', CKEDITOR.tools.getNextNumber() ).getCustomData( 'list_marker_id' ) ),
107 markerNames = element.getCustomData( 'list_marker_names' ) || ( element.setCustomData( 'list_marker_names', {} ).getCustomData( 'list_marker_names' ) );
108 database[ id ] = element;
109 markerNames[ name ] = 1;
110
111 return element.setCustomData( name, value );
112 };
113
114 /**
115 * Removes all markers added using this database. See the {@link #setMarker} method for more information.
116 *
117 * @param {Object} database
118 * @static
119 */
120 CKEDITOR.dom.element.clearAllMarkers = function( database ) {
121 for ( var i in database )
122 CKEDITOR.dom.element.clearMarkers( database, database[ i ], 1 );
123 };
124
125 /**
126 * Removes all markers added to this element and removes it from the database if
127 * `removeFromDatabase` was passed. See the {@link #setMarker} method for more information.
128 *
129 * var database = {};
130 * CKEDITOR.dom.element.setMarker( database, element1, 'foo', 'bar' );
131 * CKEDITOR.dom.element.setMarker( database, element2, 'oof', [ 1, 2, 3 ] );
132 *
133 * element1.getCustomData( 'foo' ); // 'bar'
134 * element2.getCustomData( 'oof' ); // [ 1, 2, 3 ]
135 *
136 * CKEDITOR.dom.element.clearMarkers( database, element1, true );
137 *
138 * element1.getCustomData( 'foo' ); // null
139 * element2.getCustomData( 'oof' ); // [ 1, 2, 3 ]
140 *
141 * @param {Object} database
142 * @static
143 */
144 CKEDITOR.dom.element.clearMarkers = function( database, element, removeFromDatabase ) {
145 var names = element.getCustomData( 'list_marker_names' ),
146 id = element.getCustomData( 'list_marker_id' );
147 for ( var i in names )
148 element.removeCustomData( i );
149 element.removeCustomData( 'list_marker_names' );
150 if ( removeFromDatabase ) {
151 element.removeCustomData( 'list_marker_id' );
152 delete database[ id ];
153 }
154 };
155
156 ( function() {
157 var elementsClassList = document.createElement( '_' ).classList,
158 supportsClassLists = typeof elementsClassList !== 'undefined' && String( elementsClassList.add ).match( /\[Native code\]/gi ) !== null,
159 rclass = /[\n\t\r]/g;
160
161 function hasClass( classNames, className ) {
162 // Source: jQuery.
163 return ( ' ' + classNames + ' ' ).replace( rclass, ' ' ).indexOf( ' ' + className + ' ' ) > -1;
164 }
165
166 CKEDITOR.tools.extend( CKEDITOR.dom.element.prototype, {
167 /**
168 * The node type. This is a constant value set to {@link CKEDITOR#NODE_ELEMENT}.
169 *
170 * @readonly
171 * @property {Number} [=CKEDITOR.NODE_ELEMENT]
172 */
173 type: CKEDITOR.NODE_ELEMENT,
174
175 /**
176 * Adds a CSS class to the element. It appends the class to the
177 * already existing names.
178 *
179 * var element = new CKEDITOR.dom.element( 'div' );
180 * element.addClass( 'classA' ); // <div class="classA">
181 * element.addClass( 'classB' ); // <div class="classA classB">
182 * element.addClass( 'classA' ); // <div class="classA classB">
183 *
184 * **Note:** Since CKEditor 4.5 this method cannot be used with multiple classes (`'classA classB'`).
185 *
186 * @chainable
187 * @method addClass
188 * @param {String} className The name of the class to be added.
189 */
190 addClass: supportsClassLists ?
191 function( className ) {
192 this.$.classList.add( className );
193
194 return this;
195 } : function( className ) {
196 var c = this.$.className;
197 if ( c ) {
198 if ( !hasClass( c, className ) )
199 c += ' ' + className;
200 }
201 this.$.className = c || className;
202
203 return this;
204 },
205
206 /**
207 * Removes a CSS class name from the elements classes. Other classes
208 * remain untouched.
209 *
210 * var element = new CKEDITOR.dom.element( 'div' );
211 * element.addClass( 'classA' ); // <div class="classA">
212 * element.addClass( 'classB' ); // <div class="classA classB">
213 * element.removeClass( 'classA' ); // <div class="classB">
214 * element.removeClass( 'classB' ); // <div>
215 *
216 * @chainable
217 * @method removeClass
218 * @param {String} className The name of the class to remove.
219 */
220 removeClass: supportsClassLists ?
221 function( className ) {
222 var $ = this.$;
223 $.classList.remove( className );
224
225 if ( !$.className )
226 $.removeAttribute( 'class' );
227
228 return this;
229 } : function( className ) {
230 var c = this.getAttribute( 'class' );
231 if ( c && hasClass( c, className ) ) {
232 c = c
233 .replace( new RegExp( '(?:^|\\s+)' + className + '(?=\\s|$)' ), '' )
234 .replace( /^\s+/, '' );
235
236 if ( c )
237 this.setAttribute( 'class', c );
238 else
239 this.removeAttribute( 'class' );
240 }
241
242 return this;
243 },
244
245 /**
246 * Checks if element has class name.
247 *
248 * @param {String} className
249 * @returns {Boolean}
250 */
251 hasClass: function( className ) {
252 return hasClass( this.$.className, className );
253 },
254
255 /**
256 * Append a node as a child of this element.
257 *
258 * var p = new CKEDITOR.dom.element( 'p' );
259 *
260 * var strong = new CKEDITOR.dom.element( 'strong' );
261 * p.append( strong );
262 *
263 * var em = p.append( 'em' );
264 *
265 * // Result: '<p><strong></strong><em></em></p>'
266 *
267 * @param {CKEDITOR.dom.node/String} node The node or element name to be appended.
268 * @param {Boolean} [toStart=false] Indicates that the element is to be appended at the start.
269 * @returns {CKEDITOR.dom.node} The appended node.
270 */
271 append: function( node, toStart ) {
272 if ( typeof node == 'string' )
273 node = this.getDocument().createElement( node );
274
275 if ( toStart )
276 this.$.insertBefore( node.$, this.$.firstChild );
277 else
278 this.$.appendChild( node.$ );
279
280 return node;
281 },
282
283 /**
284 * Append HTML as a child(ren) of this element.
285 *
286 * @param {String} html
287 */
288 appendHtml: function( html ) {
289 if ( !this.$.childNodes.length )
290 this.setHtml( html );
291 else {
292 var temp = new CKEDITOR.dom.element( 'div', this.getDocument() );
293 temp.setHtml( html );
294 temp.moveChildren( this );
295 }
296 },
297
298 /**
299 * Append text to this element.
300 *
301 * var p = new CKEDITOR.dom.element( 'p' );
302 * p.appendText( 'This is' );
303 * p.appendText( ' some text' );
304 *
305 * // Result: '<p>This is some text</p>'
306 *
307 * @param {String} text The text to be appended.
308 */
309 appendText: function( text ) {
310 // On IE8 it is impossible to append node to script tag, so we use its text.
311 // On the contrary, on Safari the text property is unpredictable in links. (#13232)
312 if ( this.$.text != null && CKEDITOR.env.ie && CKEDITOR.env.version < 9 )
313 this.$.text += text;
314 else
315 this.append( new CKEDITOR.dom.text( text ) );
316 },
317
318 /**
319 * Appends a `<br>` filler element to this element if the filler is not present already.
320 * By default filler is appended only if {@link CKEDITOR.env#needsBrFiller} is `true`,
321 * however when `force` is set to `true` filler will be appended regardless of the environment.
322 *
323 * @param {Boolean} [force] Append filler regardless of the environment.
324 */
325 appendBogus: function( force ) {
326 if ( !force && !CKEDITOR.env.needsBrFiller )
327 return;
328
329 var lastChild = this.getLast();
330
331 // Ignore empty/spaces text.
332 while ( lastChild && lastChild.type == CKEDITOR.NODE_TEXT && !CKEDITOR.tools.rtrim( lastChild.getText() ) )
333 lastChild = lastChild.getPrevious();
334 if ( !lastChild || !lastChild.is || !lastChild.is( 'br' ) ) {
335 var bogus = this.getDocument().createElement( 'br' );
336
337 CKEDITOR.env.gecko && bogus.setAttribute( 'type', '_moz' );
338
339 this.append( bogus );
340 }
341 },
342
343 /**
344 * Breaks one of the ancestor element in the element position, moving
345 * this element between the broken parts.
346 *
347 * // Before breaking:
348 * // <b>This <i>is some<span /> sample</i> test text</b>
349 * // If "element" is <span /> and "parent" is <i>:
350 * // <b>This <i>is some</i><span /><i> sample</i> test text</b>
351 * element.breakParent( parent );
352 *
353 * // Before breaking:
354 * // <b>This <i>is some<span /> sample</i> test text</b>
355 * // If "element" is <span /> and "parent" is <b>:
356 * // <b>This <i>is some</i></b><span /><b><i> sample</i> test text</b>
357 * element.breakParent( parent );
358 *
359 * @param {CKEDITOR.dom.element} parent The anscestor element to get broken.
360 * @param {Boolean} [cloneId=false] Whether to preserve ancestor ID attributes while breaking.
361 */
362 breakParent: function( parent, cloneId ) {
363 var range = new CKEDITOR.dom.range( this.getDocument() );
364
365 // We'll be extracting part of this element, so let's use our
366 // range to get the correct piece.
367 range.setStartAfter( this );
368 range.setEndAfter( parent );
369
370 // Extract it.
371 var docFrag = range.extractContents( false, cloneId || false );
372
373 // Move the element outside the broken element.
374 range.insertNode( this.remove() );
375
376 // Re-insert the extracted piece after the element.
377 docFrag.insertAfterNode( this );
378 },
379
380 /**
381 * Checks if this element contains given node.
382 *
383 * @method
384 * @param {CKEDITOR.dom.node} node
385 * @returns {Boolean}
386 */
387 contains: !document.compareDocumentPosition ?
388 function( node ) {
389 var $ = this.$;
390
391 return node.type != CKEDITOR.NODE_ELEMENT ? $.contains( node.getParent().$ ) : $ != node.$ && $.contains( node.$ );
392 } : function( node ) {
393 return !!( this.$.compareDocumentPosition( node.$ ) & 16 );
394 },
395
396 /**
397 * Moves the selection focus to this element.
398 *
399 * var element = CKEDITOR.document.getById( 'myTextarea' );
400 * element.focus();
401 *
402 * @method
403 * @param {Boolean} defer Whether to asynchronously defer the
404 * execution by 100 ms.
405 */
406 focus: ( function() {
407 function exec() {
408 // IE throws error if the element is not visible.
409 try {
410 this.$.focus();
411 } catch ( e ) {}
412 }
413
414 return function( defer ) {
415 if ( defer )
416 CKEDITOR.tools.setTimeout( exec, 100, this );
417 else
418 exec.call( this );
419 };
420 } )(),
421
422 /**
423 * Gets the inner HTML of this element.
424 *
425 * var element = CKEDITOR.dom.element.createFromHtml( '<div><b>Example</b></div>' );
426 * alert( element.getHtml() ); // '<b>Example</b>'
427 *
428 * @returns {String} The inner HTML of this element.
429 */
430 getHtml: function() {
431 var retval = this.$.innerHTML;
432 // Strip <?xml:namespace> tags in IE. (#3341).
433 return CKEDITOR.env.ie ? retval.replace( /<\?[^>]*>/g, '' ) : retval;
434 },
435
436 /**
437 * Gets the outer (inner plus tags) HTML of this element.
438 *
439 * var element = CKEDITOR.dom.element.createFromHtml( '<div class="bold"><b>Example</b></div>' );
440 * alert( element.getOuterHtml() ); // '<div class="bold"><b>Example</b></div>'
441 *
442 * @returns {String} The outer HTML of this element.
443 */
444 getOuterHtml: function() {
445 if ( this.$.outerHTML ) {
446 // IE includes the <?xml:namespace> tag in the outerHTML of
447 // namespaced element. So, we must strip it here. (#3341)
448 return this.$.outerHTML.replace( /<\?[^>]*>/, '' );
449 }
450
451 var tmpDiv = this.$.ownerDocument.createElement( 'div' );
452 tmpDiv.appendChild( this.$.cloneNode( true ) );
453 return tmpDiv.innerHTML;
454 },
455
456 /**
457 * Retrieve the bounding rectangle of the current element, in pixels,
458 * relative to the upper-left corner of the browser's client area.
459 *
460 * @returns {Object} The dimensions of the DOM element including
461 * `left`, `top`, `right`, `bottom`, `width` and `height`.
462 */
463 getClientRect: function() {
464 // http://help.dottoro.com/ljvmcrrn.php
465 var rect = CKEDITOR.tools.extend( {}, this.$.getBoundingClientRect() );
466
467 !rect.width && ( rect.width = rect.right - rect.left );
468 !rect.height && ( rect.height = rect.bottom - rect.top );
469
470 return rect;
471 },
472
473 /**
474 * Sets the inner HTML of this element.
475 *
476 * var p = new CKEDITOR.dom.element( 'p' );
477 * p.setHtml( '<b>Inner</b> HTML' );
478 *
479 * // Result: '<p><b>Inner</b> HTML</p>'
480 *
481 * @method
482 * @param {String} html The HTML to be set for this element.
483 * @returns {String} The inserted HTML.
484 */
485 setHtml: ( CKEDITOR.env.ie && CKEDITOR.env.version < 9 ) ?
486 // old IEs throws error on HTML manipulation (through the "innerHTML" property)
487 // on the element which resides in an DTD invalid position, e.g. <span><div></div></span>
488 // fortunately it can be worked around with DOM manipulation.
489 function( html ) {
490 try {
491 var $ = this.$;
492
493 // Fix the case when setHtml is called on detached element.
494 // HTML5 shiv used for document in which this element was created
495 // won't affect that detached element. So get document fragment with
496 // all HTML5 elements enabled and set innerHTML while this element is appended to it.
497 if ( this.getParent() )
498 return ( $.innerHTML = html );
499 else {
500 var $frag = this.getDocument()._getHtml5ShivFrag();
501 $frag.appendChild( $ );
502 $.innerHTML = html;
503 $frag.removeChild( $ );
504
505 return html;
506 }
507 }
508 catch ( e ) {
509 this.$.innerHTML = '';
510
511 var temp = new CKEDITOR.dom.element( 'body', this.getDocument() );
512 temp.$.innerHTML = html;
513
514 var children = temp.getChildren();
515 while ( children.count() )
516 this.append( children.getItem( 0 ) );
517
518 return html;
519 }
520 } : function( html ) {
521 return ( this.$.innerHTML = html );
522 },
523
524 /**
525 * Sets the element contents as plain text.
526 *
527 * var element = new CKEDITOR.dom.element( 'div' );
528 * element.setText( 'A > B & C < D' );
529 * alert( element.innerHTML ); // 'A &gt; B &amp; C &lt; D'
530 *
531 * @param {String} text The text to be set.
532 * @returns {String} The inserted text.
533 */
534 setText: ( function() {
535 var supportsTextContent = document.createElement( 'p' );
536 supportsTextContent.innerHTML = 'x';
537 supportsTextContent = supportsTextContent.textContent;
538
539 return function( text ) {
540 this.$[ supportsTextContent ? 'textContent' : 'innerText' ] = text;
541 };
542 } )(),
543
544 /**
545 * Gets the value of an element attribute.
546 *
547 * var element = CKEDITOR.dom.element.createFromHtml( '<input type="text" />' );
548 * alert( element.getAttribute( 'type' ) ); // 'text'
549 *
550 * @method
551 * @param {String} name The attribute name.
552 * @returns {String} The attribute value or null if not defined.
553 */
554 getAttribute: ( function() {
555 var standard = function( name ) {
556 return this.$.getAttribute( name, 2 );
557 };
558
559 if ( CKEDITOR.env.ie && ( CKEDITOR.env.ie7Compat || CKEDITOR.env.quirks ) ) {
560 return function( name ) {
561 switch ( name ) {
562 case 'class':
563 name = 'className';
564 break;
565
566 case 'http-equiv':
567 name = 'httpEquiv';
568 break;
569
570 case 'name':
571 return this.$.name;
572
573 case 'tabindex':
574 var tabIndex = standard.call( this, name );
575
576 // IE returns tabIndex=0 by default for all
577 // elements. For those elements,
578 // getAtrribute( 'tabindex', 2 ) returns 32768
579 // instead. So, we must make this check to give a
580 // uniform result among all browsers.
581 if ( tabIndex !== 0 && this.$.tabIndex === 0 )
582 tabIndex = null;
583
584 return tabIndex;
585
586 case 'checked':
587 var attr = this.$.attributes.getNamedItem( name ),
588 attrValue = attr.specified ? attr.nodeValue // For value given by parser.
589 : this.$.checked; // For value created via DOM interface.
590
591 return attrValue ? 'checked' : null;
592
593 case 'hspace':
594 case 'value':
595 return this.$[ name ];
596
597 case 'style':
598 // IE does not return inline styles via getAttribute(). See #2947.
599 return this.$.style.cssText;
600
601 case 'contenteditable':
602 case 'contentEditable':
603 return this.$.attributes.getNamedItem( 'contentEditable' ).specified ? this.$.getAttribute( 'contentEditable' ) : null;
604 }
605
606 return standard.call( this, name );
607 };
608 } else {
609 return standard;
610 }
611 } )(),
612
613 /**
614 * Gets the nodes list containing all children of this element.
615 *
616 * @returns {CKEDITOR.dom.nodeList}
617 */
618 getChildren: function() {
619 return new CKEDITOR.dom.nodeList( this.$.childNodes );
620 },
621
622 /**
623 * Gets the current computed value of one of the element CSS style
624 * properties.
625 *
626 * var element = new CKEDITOR.dom.element( 'span' );
627 * alert( element.getComputedStyle( 'display' ) ); // 'inline'
628 *
629 * @method
630 * @param {String} propertyName The style property name.
631 * @returns {String} The property value.
632 */
633 getComputedStyle: ( document.defaultView && document.defaultView.getComputedStyle ) ?
634 function( propertyName ) {
635 var style = this.getWindow().$.getComputedStyle( this.$, null );
636
637 // Firefox may return null if we call the above on a hidden iframe. (#9117)
638 return style ? style.getPropertyValue( propertyName ) : '';
639 } : function( propertyName ) {
640 return this.$.currentStyle[ CKEDITOR.tools.cssStyleToDomStyle( propertyName ) ];
641 },
642
643 /**
644 * Gets the DTD entries for this element.
645 *
646 * @returns {Object} An object containing the list of elements accepted
647 * by this element.
648 */
649 getDtd: function() {
650 var dtd = CKEDITOR.dtd[ this.getName() ];
651
652 this.getDtd = function() {
653 return dtd;
654 };
655
656 return dtd;
657 },
658
659 /**
660 * Gets all this element's descendants having given tag name.
661 *
662 * @method
663 * @param {String} tagName
664 */
665 getElementsByTag: CKEDITOR.dom.document.prototype.getElementsByTag,
666
667 /**
668 * Gets the computed tabindex for this element.
669 *
670 * var element = CKEDITOR.document.getById( 'myDiv' );
671 * alert( element.getTabIndex() ); // (e.g.) '-1'
672 *
673 * @method
674 * @returns {Number} The tabindex value.
675 */
676 getTabIndex: function() {
677 var tabIndex = this.$.tabIndex;
678
679 // IE returns tabIndex=0 by default for all elements. In
680 // those cases we must check that the element really has
681 // the tabindex attribute set to zero, or it is one of
682 // those element that should have zero by default.
683 if ( tabIndex === 0 && !CKEDITOR.dtd.$tabIndex[ this.getName() ] && parseInt( this.getAttribute( 'tabindex' ), 10 ) !== 0 )
684 return -1;
685
686 return tabIndex;
687 },
688
689 /**
690 * Gets the text value of this element.
691 *
692 * Only in IE (which uses innerText), `<br>` will cause linebreaks,
693 * and sucessive whitespaces (including line breaks) will be reduced to
694 * a single space. This behavior is ok for us, for now. It may change
695 * in the future.
696 *
697 * var element = CKEDITOR.dom.element.createFromHtml( '<div>Sample <i>text</i>.</div>' );
698 * alert( <b>element.getText()</b> ); // 'Sample text.'
699 *
700 * @returns {String} The text value.
701 */
702 getText: function() {
703 return this.$.textContent || this.$.innerText || '';
704 },
705
706 /**
707 * Gets the window object that contains this element.
708 *
709 * @returns {CKEDITOR.dom.window} The window object.
710 */
711 getWindow: function() {
712 return this.getDocument().getWindow();
713 },
714
715 /**
716 * Gets the value of the `id` attribute of this element.
717 *
718 * var element = CKEDITOR.dom.element.createFromHtml( '<p id="myId"></p>' );
719 * alert( element.getId() ); // 'myId'
720 *
721 * @returns {String} The element id, or null if not available.
722 */
723 getId: function() {
724 return this.$.id || null;
725 },
726
727 /**
728 * Gets the value of the `name` attribute of this element.
729 *
730 * var element = CKEDITOR.dom.element.createFromHtml( '<input name="myName"></input>' );
731 * alert( <b>element.getNameAtt()</b> ); // 'myName'
732 *
733 * @returns {String} The element name, or null if not available.
734 */
735 getNameAtt: function() {
736 return this.$.name || null;
737 },
738
739 /**
740 * Gets the element name (tag name). The returned name is guaranteed to
741 * be always full lowercased.
742 *
743 * var element = new CKEDITOR.dom.element( 'span' );
744 * alert( element.getName() ); // 'span'
745 *
746 * @returns {String} The element name.
747 */
748 getName: function() {
749 // Cache the lowercased name inside a closure.
750 var nodeName = this.$.nodeName.toLowerCase();
751
752 if ( CKEDITOR.env.ie && ( document.documentMode <= 8 ) ) {
753 var scopeName = this.$.scopeName;
754 if ( scopeName != 'HTML' )
755 nodeName = scopeName.toLowerCase() + ':' + nodeName;
756 }
757
758 this.getName = function() {
759 return nodeName;
760 };
761
762 return this.getName();
763 },
764
765 /**
766 * Gets the value set to this element. This value is usually available
767 * for form field elements.
768 *
769 * @returns {String} The element value.
770 */
771 getValue: function() {
772 return this.$.value;
773 },
774
775 /**
776 * Gets the first child node of this element.
777 *
778 * var element = CKEDITOR.dom.element.createFromHtml( '<div><b>Example</b></div>' );
779 * var first = element.getFirst();
780 * alert( first.getName() ); // 'b'
781 *
782 * @param {Function} evaluator Filtering the result node.
783 * @returns {CKEDITOR.dom.node} The first child node or null if not available.
784 */
785 getFirst: function( evaluator ) {
786 var first = this.$.firstChild,
787 retval = first && new CKEDITOR.dom.node( first );
788 if ( retval && evaluator && !evaluator( retval ) )
789 retval = retval.getNext( evaluator );
790
791 return retval;
792 },
793
794 /**
795 * See {@link #getFirst}.
796 *
797 * @param {Function} evaluator Filtering the result node.
798 * @returns {CKEDITOR.dom.node}
799 */
800 getLast: function( evaluator ) {
801 var last = this.$.lastChild,
802 retval = last && new CKEDITOR.dom.node( last );
803 if ( retval && evaluator && !evaluator( retval ) )
804 retval = retval.getPrevious( evaluator );
805
806 return retval;
807 },
808
809 /**
810 * Gets CSS style value.
811 *
812 * @param {String} name The CSS property name.
813 * @returns {String} Style value.
814 */
815 getStyle: function( name ) {
816 return this.$.style[ CKEDITOR.tools.cssStyleToDomStyle( name ) ];
817 },
818
819 /**
820 * Checks if the element name matches the specified criteria.
821 *
822 * var element = new CKEDITOR.element( 'span' );
823 * alert( element.is( 'span' ) ); // true
824 * alert( element.is( 'p', 'span' ) ); // true
825 * alert( element.is( 'p' ) ); // false
826 * alert( element.is( 'p', 'div' ) ); // false
827 * alert( element.is( { p:1,span:1 } ) ); // true
828 *
829 * @param {String.../Object} name One or more names to be checked, or a {@link CKEDITOR.dtd} object.
830 * @returns {Boolean} `true` if the element name matches any of the names.
831 */
832 is: function() {
833 var name = this.getName();
834
835 // Check against the specified DTD liternal.
836 if ( typeof arguments[ 0 ] == 'object' )
837 return !!arguments[ 0 ][ name ];
838
839 // Check for tag names
840 for ( var i = 0; i < arguments.length; i++ ) {
841 if ( arguments[ i ] == name )
842 return true;
843 }
844 return false;
845 },
846
847 /**
848 * Decide whether one element is able to receive cursor.
849 *
850 * @param {Boolean} [textCursor=true] Only consider element that could receive text child.
851 */
852 isEditable: function( textCursor ) {
853 var name = this.getName();
854
855 if ( this.isReadOnly() || this.getComputedStyle( 'display' ) == 'none' ||
856 this.getComputedStyle( 'visibility' ) == 'hidden' ||
857 CKEDITOR.dtd.$nonEditable[ name ] ||
858 CKEDITOR.dtd.$empty[ name ] ||
859 ( this.is( 'a' ) &&
860 ( this.data( 'cke-saved-name' ) || this.hasAttribute( 'name' ) ) &&
861 !this.getChildCount()
862 ) ) {
863 return false;
864 }
865
866 if ( textCursor !== false ) {
867 // Get the element DTD (defaults to span for unknown elements).
868 var dtd = CKEDITOR.dtd[ name ] || CKEDITOR.dtd.span;
869 // In the DTD # == text node.
870 return !!( dtd && dtd[ '#' ] );
871 }
872
873 return true;
874 },
875
876 /**
877 * Compare this element's inner html, tag name, attributes, etc. with other one.
878 *
879 * See [W3C's DOM Level 3 spec - node#isEqualNode](http://www.w3.org/TR/DOM-Level-3-Core/core.html#Node3-isEqualNode)
880 * for more details.
881 *
882 * @param {CKEDITOR.dom.element} otherElement Element to compare.
883 * @returns {Boolean}
884 */
885 isIdentical: function( otherElement ) {
886 // do shallow clones, but with IDs
887 var thisEl = this.clone( 0, 1 ),
888 otherEl = otherElement.clone( 0, 1 );
889
890 // Remove distractions.
891 thisEl.removeAttributes( [ '_moz_dirty', 'data-cke-expando', 'data-cke-saved-href', 'data-cke-saved-name' ] );
892 otherEl.removeAttributes( [ '_moz_dirty', 'data-cke-expando', 'data-cke-saved-href', 'data-cke-saved-name' ] );
893
894 // Native comparison available.
895 if ( thisEl.$.isEqualNode ) {
896 // Styles order matters.
897 thisEl.$.style.cssText = CKEDITOR.tools.normalizeCssText( thisEl.$.style.cssText );
898 otherEl.$.style.cssText = CKEDITOR.tools.normalizeCssText( otherEl.$.style.cssText );
899 return thisEl.$.isEqualNode( otherEl.$ );
900 } else {
901 thisEl = thisEl.getOuterHtml();
902 otherEl = otherEl.getOuterHtml();
903
904 // Fix tiny difference between link href in older IEs.
905 if ( CKEDITOR.env.ie && CKEDITOR.env.version < 9 && this.is( 'a' ) ) {
906 var parent = this.getParent();
907 if ( parent.type == CKEDITOR.NODE_ELEMENT ) {
908 var el = parent.clone();
909 el.setHtml( thisEl ), thisEl = el.getHtml();
910 el.setHtml( otherEl ), otherEl = el.getHtml();
911 }
912 }
913
914 return thisEl == otherEl;
915 }
916 },
917
918 /**
919 * Checks if this element is visible. May not work if the element is
920 * child of an element with visibility set to `hidden`, but works well
921 * on the great majority of cases.
922 *
923 * @returns {Boolean} True if the element is visible.
924 */
925 isVisible: function() {
926 var isVisible = ( this.$.offsetHeight || this.$.offsetWidth ) && this.getComputedStyle( 'visibility' ) != 'hidden',
927 elementWindow, elementWindowFrame;
928
929 // Webkit and Opera report non-zero offsetHeight despite that
930 // element is inside an invisible iframe. (#4542)
931 if ( isVisible && CKEDITOR.env.webkit ) {
932 elementWindow = this.getWindow();
933
934 if ( !elementWindow.equals( CKEDITOR.document.getWindow() ) && ( elementWindowFrame = elementWindow.$.frameElement ) )
935 isVisible = new CKEDITOR.dom.element( elementWindowFrame ).isVisible();
936
937 }
938
939 return !!isVisible;
940 },
941
942 /**
943 * Whether it's an empty inline elements which has no visual impact when removed.
944 *
945 * @returns {Boolean}
946 */
947 isEmptyInlineRemoveable: function() {
948 if ( !CKEDITOR.dtd.$removeEmpty[ this.getName() ] )
949 return false;
950
951 var children = this.getChildren();
952 for ( var i = 0, count = children.count(); i < count; i++ ) {
953 var child = children.getItem( i );
954
955 if ( child.type == CKEDITOR.NODE_ELEMENT && child.data( 'cke-bookmark' ) )
956 continue;
957
958 if ( child.type == CKEDITOR.NODE_ELEMENT && !child.isEmptyInlineRemoveable() || child.type == CKEDITOR.NODE_TEXT && CKEDITOR.tools.trim( child.getText() ) )
959 return false;
960
961 }
962 return true;
963 },
964
965 /**
966 * Checks if the element has any defined attributes.
967 *
968 * var element = CKEDITOR.dom.element.createFromHtml( '<div title="Test">Example</div>' );
969 * alert( element.hasAttributes() ); // true
970 *
971 * var element = CKEDITOR.dom.element.createFromHtml( '<div>Example</div>' );
972 * alert( element.hasAttributes() ); // false
973 *
974 * @method
975 * @returns {Boolean} True if the element has attributes.
976 */
977 hasAttributes: CKEDITOR.env.ie && ( CKEDITOR.env.ie7Compat || CKEDITOR.env.quirks ) ?
978 function() {
979 var attributes = this.$.attributes;
980
981 for ( var i = 0; i < attributes.length; i++ ) {
982 var attribute = attributes[ i ];
983
984 switch ( attribute.nodeName ) {
985 case 'class':
986 // IE has a strange bug. If calling removeAttribute('className'),
987 // the attributes collection will still contain the "class"
988 // attribute, which will be marked as "specified", even if the
989 // outerHTML of the element is not displaying the class attribute.
990 // Note : I was not able to reproduce it outside the editor,
991 // but I've faced it while working on the TC of #1391.
992 if ( this.getAttribute( 'class' ) ) {
993 return true;
994 }
995
996 // Attributes to be ignored.
997 /* falls through */
998 case 'data-cke-expando':
999 continue;
1000
1001
1002 /* falls through */
1003 default:
1004 if ( attribute.specified ) {
1005 return true;
1006 }
1007 }
1008 }
1009
1010 return false;
1011 } : function() {
1012 var attrs = this.$.attributes,
1013 attrsNum = attrs.length;
1014
1015 // The _moz_dirty attribute might get into the element after pasting (#5455)
1016 var execludeAttrs = { 'data-cke-expando': 1, _moz_dirty: 1 };
1017
1018 return attrsNum > 0 && ( attrsNum > 2 || !execludeAttrs[ attrs[ 0 ].nodeName ] || ( attrsNum == 2 && !execludeAttrs[ attrs[ 1 ].nodeName ] ) );
1019 },
1020
1021 /**
1022 * Checks if the specified attribute is defined for this element.
1023 *
1024 * @method
1025 * @param {String} name The attribute name.
1026 * @returns {Boolean} `true` if the specified attribute is defined.
1027 */
1028 hasAttribute: ( function() {
1029 function ieHasAttribute( name ) {
1030 var $attr = this.$.attributes.getNamedItem( name );
1031
1032 if ( this.getName() == 'input' ) {
1033 switch ( name ) {
1034 case 'class':
1035 return this.$.className.length > 0;
1036 case 'checked':
1037 return !!this.$.checked;
1038 case 'value':
1039 var type = this.getAttribute( 'type' );
1040 return type == 'checkbox' || type == 'radio' ? this.$.value != 'on' : !!this.$.value;
1041 }
1042 }
1043
1044 if ( !$attr )
1045 return false;
1046
1047 return $attr.specified;
1048 }
1049
1050 if ( CKEDITOR.env.ie ) {
1051 if ( CKEDITOR.env.version < 8 ) {
1052 return function( name ) {
1053 // On IE < 8 the name attribute cannot be retrieved
1054 // right after the element creation and setting the
1055 // name with setAttribute.
1056 if ( name == 'name' )
1057 return !!this.$.name;
1058
1059 return ieHasAttribute.call( this, name );
1060 };
1061 } else {
1062 return ieHasAttribute;
1063 }
1064 } else {
1065 return function( name ) {
1066 // On other browsers specified property is deprecated and return always true,
1067 // but fortunately $.attributes contains only specified attributes.
1068 return !!this.$.attributes.getNamedItem( name );
1069 };
1070 }
1071 } )(),
1072
1073 /**
1074 * Hides this element (sets `display: none`).
1075 *
1076 * var element = CKEDITOR.document.getById( 'myElement' );
1077 * element.hide();
1078 */
1079 hide: function() {
1080 this.setStyle( 'display', 'none' );
1081 },
1082
1083 /**
1084 * Moves this element's children to the target element.
1085 *
1086 * @param {CKEDITOR.dom.element} target
1087 * @param {Boolean} [toStart=false] Insert moved children at the
1088 * beginning of the target element.
1089 */
1090 moveChildren: function( target, toStart ) {
1091 var $ = this.$;
1092 target = target.$;
1093
1094 if ( $ == target )
1095 return;
1096
1097 var child;
1098
1099 if ( toStart ) {
1100 while ( ( child = $.lastChild ) )
1101 target.insertBefore( $.removeChild( child ), target.firstChild );
1102 } else {
1103 while ( ( child = $.firstChild ) )
1104 target.appendChild( $.removeChild( child ) );
1105 }
1106 },
1107
1108 /**
1109 * Merges sibling elements that are identical to this one.
1110 *
1111 * Identical child elements are also merged. For example:
1112 *
1113 * <b><i></i></b><b><i></i></b> => <b><i></i></b>
1114 *
1115 * @method
1116 * @param {Boolean} [inlineOnly=true] Allow only inline elements to be merged.
1117 */
1118 mergeSiblings: ( function() {
1119 function mergeElements( element, sibling, isNext ) {
1120 if ( sibling && sibling.type == CKEDITOR.NODE_ELEMENT ) {
1121 // Jumping over bookmark nodes and empty inline elements, e.g. <b><i></i></b>,
1122 // queuing them to be moved later. (#5567)
1123 var pendingNodes = [];
1124
1125 while ( sibling.data( 'cke-bookmark' ) || sibling.isEmptyInlineRemoveable() ) {
1126 pendingNodes.push( sibling );
1127 sibling = isNext ? sibling.getNext() : sibling.getPrevious();
1128 if ( !sibling || sibling.type != CKEDITOR.NODE_ELEMENT )
1129 return;
1130 }
1131
1132 if ( element.isIdentical( sibling ) ) {
1133 // Save the last child to be checked too, to merge things like
1134 // <b><i></i></b><b><i></i></b> => <b><i></i></b>
1135 var innerSibling = isNext ? element.getLast() : element.getFirst();
1136
1137 // Move pending nodes first into the target element.
1138 while ( pendingNodes.length )
1139 pendingNodes.shift().move( element, !isNext );
1140
1141 sibling.moveChildren( element, !isNext );
1142 sibling.remove();
1143
1144 // Now check the last inner child (see two comments above).
1145 if ( innerSibling && innerSibling.type == CKEDITOR.NODE_ELEMENT )
1146 innerSibling.mergeSiblings();
1147 }
1148 }
1149 }
1150
1151 return function( inlineOnly ) {
1152 // Merge empty links and anchors also. (#5567)
1153 if ( !( inlineOnly === false || CKEDITOR.dtd.$removeEmpty[ this.getName() ] || this.is( 'a' ) ) ) {
1154 return;
1155 }
1156
1157 mergeElements( this, this.getNext(), true );
1158 mergeElements( this, this.getPrevious() );
1159 };
1160 } )(),
1161
1162 /**
1163 * Shows this element (displays it).
1164 *
1165 * var element = CKEDITOR.document.getById( 'myElement' );
1166 * element.show();
1167 */
1168 show: function() {
1169 this.setStyles( {
1170 display: '',
1171 visibility: ''
1172 } );
1173 },
1174
1175 /**
1176 * Sets the value of an element attribute.
1177 *
1178 * var element = CKEDITOR.document.getById( 'myElement' );
1179 * element.setAttribute( 'class', 'myClass' );
1180 * element.setAttribute( 'title', 'This is an example' );
1181 *
1182 * @method
1183 * @param {String} name The name of the attribute.
1184 * @param {String} value The value to be set to the attribute.
1185 * @returns {CKEDITOR.dom.element} This element instance.
1186 */
1187 setAttribute: ( function() {
1188 var standard = function( name, value ) {
1189 this.$.setAttribute( name, value );
1190 return this;
1191 };
1192
1193 if ( CKEDITOR.env.ie && ( CKEDITOR.env.ie7Compat || CKEDITOR.env.quirks ) ) {
1194 return function( name, value ) {
1195 if ( name == 'class' )
1196 this.$.className = value;
1197 else if ( name == 'style' )
1198 this.$.style.cssText = value;
1199 else if ( name == 'tabindex' ) // Case sensitive.
1200 this.$.tabIndex = value;
1201 else if ( name == 'checked' )
1202 this.$.checked = value;
1203 else if ( name == 'contenteditable' )
1204 standard.call( this, 'contentEditable', value );
1205 else
1206 standard.apply( this, arguments );
1207 return this;
1208 };
1209 } else if ( CKEDITOR.env.ie8Compat && CKEDITOR.env.secure ) {
1210 return function( name, value ) {
1211 // IE8 throws error when setting src attribute to non-ssl value. (#7847)
1212 if ( name == 'src' && value.match( /^http:\/\// ) ) {
1213 try {
1214 standard.apply( this, arguments );
1215 } catch ( e ) {}
1216 } else {
1217 standard.apply( this, arguments );
1218 }
1219 return this;
1220 };
1221 } else {
1222 return standard;
1223 }
1224 } )(),
1225
1226 /**
1227 * Sets the value of several element attributes.
1228 *
1229 * var element = CKEDITOR.document.getById( 'myElement' );
1230 * element.setAttributes( {
1231 * 'class': 'myClass',
1232 * title: 'This is an example'
1233 * } );
1234 *
1235 * @chainable
1236 * @param {Object} attributesPairs An object containing the names and
1237 * values of the attributes.
1238 * @returns {CKEDITOR.dom.element} This element instance.
1239 */
1240 setAttributes: function( attributesPairs ) {
1241 for ( var name in attributesPairs )
1242 this.setAttribute( name, attributesPairs[ name ] );
1243 return this;
1244 },
1245
1246 /**
1247 * Sets the element value. This function is usually used with form
1248 * field element.
1249 *
1250 * @chainable
1251 * @param {String} value The element value.
1252 * @returns {CKEDITOR.dom.element} This element instance.
1253 */
1254 setValue: function( value ) {
1255 this.$.value = value;
1256 return this;
1257 },
1258
1259 /**
1260 * Removes an attribute from the element.
1261 *
1262 * var element = CKEDITOR.dom.element.createFromHtml( '<div class="classA"></div>' );
1263 * element.removeAttribute( 'class' );
1264 *
1265 * @method
1266 * @param {String} name The attribute name.
1267 */
1268 removeAttribute: ( function() {
1269 var standard = function( name ) {
1270 this.$.removeAttribute( name );
1271 };
1272
1273 if ( CKEDITOR.env.ie && ( CKEDITOR.env.ie7Compat || CKEDITOR.env.quirks ) ) {
1274 return function( name ) {
1275 if ( name == 'class' )
1276 name = 'className';
1277 else if ( name == 'tabindex' )
1278 name = 'tabIndex';
1279 else if ( name == 'contenteditable' )
1280 name = 'contentEditable';
1281 standard.call( this, name );
1282 };
1283 } else {
1284 return standard;
1285 }
1286 } )(),
1287
1288 /**
1289 * Removes all element's attributes or just given ones.
1290 *
1291 * @param {Array} [attributes] The array with attributes names.
1292 */
1293 removeAttributes: function( attributes ) {
1294 if ( CKEDITOR.tools.isArray( attributes ) ) {
1295 for ( var i = 0; i < attributes.length; i++ )
1296 this.removeAttribute( attributes[ i ] );
1297 } else {
1298 for ( var attr in attributes )
1299 attributes.hasOwnProperty( attr ) && this.removeAttribute( attr );
1300 }
1301 },
1302
1303 /**
1304 * Removes a style from the element.
1305 *
1306 * var element = CKEDITOR.dom.element.createFromHtml( '<div style="display:none"></div>' );
1307 * element.removeStyle( 'display' );
1308 *
1309 * @method
1310 * @param {String} name The style name.
1311 */
1312 removeStyle: function( name ) {
1313 // Removes the specified property from the current style object.
1314 var $ = this.$.style;
1315
1316 // "removeProperty" need to be specific on the following styles.
1317 if ( !$.removeProperty && ( name == 'border' || name == 'margin' || name == 'padding' ) ) {
1318 var names = expandedRules( name );
1319 for ( var i = 0 ; i < names.length ; i++ )
1320 this.removeStyle( names[ i ] );
1321 return;
1322 }
1323
1324 $.removeProperty ? $.removeProperty( name ) : $.removeAttribute( CKEDITOR.tools.cssStyleToDomStyle( name ) );
1325
1326 // Eventually remove empty style attribute.
1327 if ( !this.$.style.cssText )
1328 this.removeAttribute( 'style' );
1329 },
1330
1331 /**
1332 * Sets the value of an element style.
1333 *
1334 * var element = CKEDITOR.document.getById( 'myElement' );
1335 * element.setStyle( 'background-color', '#ff0000' );
1336 * element.setStyle( 'margin-top', '10px' );
1337 * element.setStyle( 'float', 'right' );
1338 *
1339 * @param {String} name The name of the style. The CSS naming notation
1340 * must be used (e.g. `background-color`).
1341 * @param {String} value The value to be set to the style.
1342 * @returns {CKEDITOR.dom.element} This element instance.
1343 */
1344 setStyle: function( name, value ) {
1345 this.$.style[ CKEDITOR.tools.cssStyleToDomStyle( name ) ] = value;
1346 return this;
1347 },
1348
1349 /**
1350 * Sets the value of several element styles.
1351 *
1352 * var element = CKEDITOR.document.getById( 'myElement' );
1353 * element.setStyles( {
1354 * position: 'absolute',
1355 * float: 'right'
1356 * } );
1357 *
1358 * @param {Object} stylesPairs An object containing the names and
1359 * values of the styles.
1360 * @returns {CKEDITOR.dom.element} This element instance.
1361 */
1362 setStyles: function( stylesPairs ) {
1363 for ( var name in stylesPairs )
1364 this.setStyle( name, stylesPairs[ name ] );
1365 return this;
1366 },
1367
1368 /**
1369 * Sets the opacity of an element.
1370 *
1371 * var element = CKEDITOR.document.getById( 'myElement' );
1372 * element.setOpacity( 0.75 );
1373 *
1374 * @param {Number} opacity A number within the range `[0.0, 1.0]`.
1375 */
1376 setOpacity: function( opacity ) {
1377 if ( CKEDITOR.env.ie && CKEDITOR.env.version < 9 ) {
1378 opacity = Math.round( opacity * 100 );
1379 this.setStyle( 'filter', opacity >= 100 ? '' : 'progid:DXImageTransform.Microsoft.Alpha(opacity=' + opacity + ')' );
1380 } else {
1381 this.setStyle( 'opacity', opacity );
1382 }
1383 },
1384
1385 /**
1386 * Makes the element and its children unselectable.
1387 *
1388 * var element = CKEDITOR.document.getById( 'myElement' );
1389 * element.unselectable();
1390 *
1391 * @method
1392 */
1393 unselectable: function() {
1394 // CSS unselectable.
1395 this.setStyles( CKEDITOR.tools.cssVendorPrefix( 'user-select', 'none' ) );
1396
1397 // For IE/Opera which doesn't support for the above CSS style,
1398 // the unselectable="on" attribute only specifies the selection
1399 // process cannot start in the element itself, and it doesn't inherit.
1400 if ( CKEDITOR.env.ie ) {
1401 this.setAttribute( 'unselectable', 'on' );
1402
1403 var element,
1404 elements = this.getElementsByTag( '*' );
1405
1406 for ( var i = 0, count = elements.count() ; i < count ; i++ ) {
1407 element = elements.getItem( i );
1408 element.setAttribute( 'unselectable', 'on' );
1409 }
1410 }
1411 },
1412
1413 /**
1414 * Gets closest positioned (`position != static`) ancestor.
1415 *
1416 * @returns {CKEDITOR.dom.element} Positioned ancestor or `null`.
1417 */
1418 getPositionedAncestor: function() {
1419 var current = this;
1420 while ( current.getName() != 'html' ) {
1421 if ( current.getComputedStyle( 'position' ) != 'static' )
1422 return current;
1423
1424 current = current.getParent();
1425 }
1426 return null;
1427 },
1428
1429 /**
1430 * Gets this element's position in document.
1431 *
1432 * @param {CKEDITOR.dom.document} [refDocument]
1433 * @returns {Object} Element's position.
1434 * @returns {Number} return.x
1435 * @returns {Number} return.y
1436 * @todo refDocument
1437 */
1438 getDocumentPosition: function( refDocument ) {
1439 var x = 0,
1440 y = 0,
1441 doc = this.getDocument(),
1442 body = doc.getBody(),
1443 quirks = doc.$.compatMode == 'BackCompat';
1444
1445 if ( document.documentElement.getBoundingClientRect ) {
1446 var box = this.$.getBoundingClientRect(),
1447 $doc = doc.$,
1448 $docElem = $doc.documentElement;
1449
1450 var clientTop = $docElem.clientTop || body.$.clientTop || 0,
1451 clientLeft = $docElem.clientLeft || body.$.clientLeft || 0,
1452 needAdjustScrollAndBorders = true;
1453
1454 // #3804: getBoundingClientRect() works differently on IE and non-IE
1455 // browsers, regarding scroll positions.
1456 //
1457 // On IE, the top position of the <html> element is always 0, no matter
1458 // how much you scrolled down.
1459 //
1460 // On other browsers, the top position of the <html> element is negative
1461 // scrollTop.
1462 if ( CKEDITOR.env.ie ) {
1463 var inDocElem = doc.getDocumentElement().contains( this ),
1464 inBody = doc.getBody().contains( this );
1465
1466 needAdjustScrollAndBorders = ( quirks && inBody ) || ( !quirks && inDocElem );
1467 }
1468
1469 // #12747.
1470 if ( needAdjustScrollAndBorders ) {
1471 var scrollRelativeLeft,
1472 scrollRelativeTop;
1473
1474 // See #12758 to know more about document.(documentElement|body).scroll(Left|Top) in Webkit.
1475 if ( CKEDITOR.env.webkit || ( CKEDITOR.env.ie && CKEDITOR.env.version >= 12 ) ) {
1476 scrollRelativeLeft = body.$.scrollLeft || $docElem.scrollLeft;
1477 scrollRelativeTop = body.$.scrollTop || $docElem.scrollTop;
1478 } else {
1479 var scrollRelativeElement = quirks ? body.$ : $docElem;
1480
1481 scrollRelativeLeft = scrollRelativeElement.scrollLeft;
1482 scrollRelativeTop = scrollRelativeElement.scrollTop;
1483 }
1484
1485 x = box.left + scrollRelativeLeft - clientLeft;
1486 y = box.top + scrollRelativeTop - clientTop;
1487 }
1488 } else {
1489 var current = this,
1490 previous = null,
1491 offsetParent;
1492 while ( current && !( current.getName() == 'body' || current.getName() == 'html' ) ) {
1493 x += current.$.offsetLeft - current.$.scrollLeft;
1494 y += current.$.offsetTop - current.$.scrollTop;
1495
1496 // Opera includes clientTop|Left into offsetTop|Left.
1497 if ( !current.equals( this ) ) {
1498 x += ( current.$.clientLeft || 0 );
1499 y += ( current.$.clientTop || 0 );
1500 }
1501
1502 var scrollElement = previous;
1503 while ( scrollElement && !scrollElement.equals( current ) ) {
1504 x -= scrollElement.$.scrollLeft;
1505 y -= scrollElement.$.scrollTop;
1506 scrollElement = scrollElement.getParent();
1507 }
1508
1509 previous = current;
1510 current = ( offsetParent = current.$.offsetParent ) ? new CKEDITOR.dom.element( offsetParent ) : null;
1511 }
1512 }
1513
1514 if ( refDocument ) {
1515 var currentWindow = this.getWindow(),
1516 refWindow = refDocument.getWindow();
1517
1518 if ( !currentWindow.equals( refWindow ) && currentWindow.$.frameElement ) {
1519 var iframePosition = ( new CKEDITOR.dom.element( currentWindow.$.frameElement ) ).getDocumentPosition( refDocument );
1520
1521 x += iframePosition.x;
1522 y += iframePosition.y;
1523 }
1524 }
1525
1526 if ( !document.documentElement.getBoundingClientRect ) {
1527 // In Firefox, we'll endup one pixel before the element positions,
1528 // so we must add it here.
1529 if ( CKEDITOR.env.gecko && !quirks ) {
1530 x += this.$.clientLeft ? 1 : 0;
1531 y += this.$.clientTop ? 1 : 0;
1532 }
1533 }
1534
1535 return { x: x, y: y };
1536 },
1537
1538 /**
1539 * Make any page element visible inside the browser viewport.
1540 *
1541 * @param {Boolean} [alignToTop=false]
1542 */
1543 scrollIntoView: function( alignToTop ) {
1544 var parent = this.getParent();
1545 if ( !parent )
1546 return;
1547
1548 // Scroll the element into parent container from the inner out.
1549 do {
1550 // Check ancestors that overflows.
1551 var overflowed =
1552 parent.$.clientWidth && parent.$.clientWidth < parent.$.scrollWidth ||
1553 parent.$.clientHeight && parent.$.clientHeight < parent.$.scrollHeight;
1554
1555 // Skip body element, which will report wrong clientHeight when containing
1556 // floated content. (#9523)
1557 if ( overflowed && !parent.is( 'body' ) )
1558 this.scrollIntoParent( parent, alignToTop, 1 );
1559
1560 // Walk across the frame.
1561 if ( parent.is( 'html' ) ) {
1562 var win = parent.getWindow();
1563
1564 // Avoid security error.
1565 try {
1566 var iframe = win.$.frameElement;
1567 iframe && ( parent = new CKEDITOR.dom.element( iframe ) );
1568 } catch ( er ) {}
1569 }
1570 }
1571 while ( ( parent = parent.getParent() ) );
1572 },
1573
1574 /**
1575 * Make any page element visible inside one of the ancestors by scrolling the parent.
1576 *
1577 * @param {CKEDITOR.dom.element/CKEDITOR.dom.window} parent The container to scroll into.
1578 * @param {Boolean} [alignToTop] Align the element's top side with the container's
1579 * when `true` is specified; align the bottom with viewport bottom when
1580 * `false` is specified. Otherwise scroll on either side with the minimum
1581 * amount to show the element.
1582 * @param {Boolean} [hscroll] Whether horizontal overflow should be considered.
1583 */
1584 scrollIntoParent: function( parent, alignToTop, hscroll ) {
1585 !parent && ( parent = this.getWindow() );
1586
1587 var doc = parent.getDocument();
1588 var isQuirks = doc.$.compatMode == 'BackCompat';
1589
1590 // On window <html> is scrolled while quirks scrolls <body>.
1591 if ( parent instanceof CKEDITOR.dom.window )
1592 parent = isQuirks ? doc.getBody() : doc.getDocumentElement();
1593
1594 // Scroll the parent by the specified amount.
1595 function scrollBy( x, y ) {
1596 // Webkit doesn't support "scrollTop/scrollLeft"
1597 // on documentElement/body element.
1598 if ( /body|html/.test( parent.getName() ) )
1599 parent.getWindow().$.scrollBy( x, y );
1600 else {
1601 parent.$.scrollLeft += x;
1602 parent.$.scrollTop += y;
1603 }
1604 }
1605
1606 // Figure out the element position relative to the specified window.
1607 function screenPos( element, refWin ) {
1608 var pos = { x: 0, y: 0 };
1609
1610 if ( !( element.is( isQuirks ? 'body' : 'html' ) ) ) {
1611 var box = element.$.getBoundingClientRect();
1612 pos.x = box.left, pos.y = box.top;
1613 }
1614
1615 var win = element.getWindow();
1616 if ( !win.equals( refWin ) ) {
1617 var outerPos = screenPos( CKEDITOR.dom.element.get( win.$.frameElement ), refWin );
1618 pos.x += outerPos.x, pos.y += outerPos.y;
1619 }
1620
1621 return pos;
1622 }
1623
1624 // calculated margin size.
1625 function margin( element, side ) {
1626 return parseInt( element.getComputedStyle( 'margin-' + side ) || 0, 10 ) || 0;
1627 }
1628
1629 var win = parent.getWindow();
1630
1631 var thisPos = screenPos( this, win ),
1632 parentPos = screenPos( parent, win ),
1633 eh = this.$.offsetHeight,
1634 ew = this.$.offsetWidth,
1635 ch = parent.$.clientHeight,
1636 cw = parent.$.clientWidth,
1637 lt, br;
1638
1639 // Left-top margins.
1640 lt = {
1641 x: thisPos.x - margin( this, 'left' ) - parentPos.x || 0,
1642 y: thisPos.y - margin( this, 'top' ) - parentPos.y || 0
1643 };
1644
1645 // Bottom-right margins.
1646 br = {
1647 x: thisPos.x + ew + margin( this, 'right' ) - ( ( parentPos.x ) + cw ) || 0,
1648 y: thisPos.y + eh + margin( this, 'bottom' ) - ( ( parentPos.y ) + ch ) || 0
1649 };
1650
1651 // 1. Do the specified alignment as much as possible;
1652 // 2. Otherwise be smart to scroll only the minimum amount;
1653 // 3. Never cut at the top;
1654 // 4. DO NOT scroll when already visible.
1655 if ( lt.y < 0 || br.y > 0 )
1656 scrollBy( 0, alignToTop === true ? lt.y : alignToTop === false ? br.y : lt.y < 0 ? lt.y : br.y );
1657
1658 if ( hscroll && ( lt.x < 0 || br.x > 0 ) )
1659 scrollBy( lt.x < 0 ? lt.x : br.x, 0 );
1660 },
1661
1662 /**
1663 * Switch the `class` attribute to reflect one of the triple states of an
1664 * element in one of {@link CKEDITOR#TRISTATE_ON}, {@link CKEDITOR#TRISTATE_OFF}
1665 * or {@link CKEDITOR#TRISTATE_DISABLED}.
1666 *
1667 * link.setState( CKEDITOR.TRISTATE_ON );
1668 * // <a class="cke_on" aria-pressed="true">...</a>
1669 * link.setState( CKEDITOR.TRISTATE_OFF );
1670 * // <a class="cke_off">...</a>
1671 * link.setState( CKEDITOR.TRISTATE_DISABLED );
1672 * // <a class="cke_disabled" aria-disabled="true">...</a>
1673 *
1674 * span.setState( CKEDITOR.TRISTATE_ON, 'cke_button' );
1675 * // <span class="cke_button_on">...</span>
1676 *
1677 * @param {Number} state Indicate the element state. One of {@link CKEDITOR#TRISTATE_ON},
1678 * {@link CKEDITOR#TRISTATE_OFF}, {@link CKEDITOR#TRISTATE_DISABLED}.
1679 * @param [base='cke'] The prefix apply to each of the state class name.
1680 * @param [useAria=true] Whether toggle the ARIA state attributes besides of class name change.
1681 */
1682 setState: function( state, base, useAria ) {
1683 base = base || 'cke';
1684
1685 switch ( state ) {
1686 case CKEDITOR.TRISTATE_ON:
1687 this.addClass( base + '_on' );
1688 this.removeClass( base + '_off' );
1689 this.removeClass( base + '_disabled' );
1690 useAria && this.setAttribute( 'aria-pressed', true );
1691 useAria && this.removeAttribute( 'aria-disabled' );
1692 break;
1693
1694 case CKEDITOR.TRISTATE_DISABLED:
1695 this.addClass( base + '_disabled' );
1696 this.removeClass( base + '_off' );
1697 this.removeClass( base + '_on' );
1698 useAria && this.setAttribute( 'aria-disabled', true );
1699 useAria && this.removeAttribute( 'aria-pressed' );
1700 break;
1701
1702 default:
1703 this.addClass( base + '_off' );
1704 this.removeClass( base + '_on' );
1705 this.removeClass( base + '_disabled' );
1706 useAria && this.removeAttribute( 'aria-pressed' );
1707 useAria && this.removeAttribute( 'aria-disabled' );
1708 break;
1709 }
1710 },
1711
1712 /**
1713 * Returns the inner document of this `<iframe>` element.
1714 *
1715 * @returns {CKEDITOR.dom.document} The inner document.
1716 */
1717 getFrameDocument: function() {
1718 var $ = this.$;
1719
1720 try {
1721 // In IE, with custom document.domain, it may happen that
1722 // the iframe is not yet available, resulting in "Access
1723 // Denied" for the following property access.
1724 $.contentWindow.document;
1725 } catch ( e ) {
1726 // Trick to solve this issue, forcing the iframe to get ready
1727 // by simply setting its "src" property.
1728 $.src = $.src;
1729 }
1730
1731 return $ && new CKEDITOR.dom.document( $.contentWindow.document );
1732 },
1733
1734 /**
1735 * Copy all the attributes from one node to the other, kinda like a clone
1736 * skipAttributes is an object with the attributes that must **not** be copied.
1737 *
1738 * @param {CKEDITOR.dom.element} dest The destination element.
1739 * @param {Object} skipAttributes A dictionary of attributes to skip.
1740 */
1741 copyAttributes: function( dest, skipAttributes ) {
1742 var attributes = this.$.attributes;
1743 skipAttributes = skipAttributes || {};
1744
1745 for ( var n = 0; n < attributes.length; n++ ) {
1746 var attribute = attributes[ n ];
1747
1748 // Lowercase attribute name hard rule is broken for
1749 // some attribute on IE, e.g. CHECKED.
1750 var attrName = attribute.nodeName.toLowerCase(),
1751 attrValue;
1752
1753 // We can set the type only once, so do it with the proper value, not copying it.
1754 if ( attrName in skipAttributes )
1755 continue;
1756
1757 if ( attrName == 'checked' && ( attrValue = this.getAttribute( attrName ) ) )
1758 dest.setAttribute( attrName, attrValue );
1759 // IE contains not specified attributes in $.attributes so we need to check
1760 // if elements attribute is specified using hasAttribute.
1761 else if ( !CKEDITOR.env.ie || this.hasAttribute( attrName ) ) {
1762 attrValue = this.getAttribute( attrName );
1763 if ( attrValue === null )
1764 attrValue = attribute.nodeValue;
1765
1766 dest.setAttribute( attrName, attrValue );
1767 }
1768 }
1769
1770 // The style:
1771 if ( this.$.style.cssText !== '' )
1772 dest.$.style.cssText = this.$.style.cssText;
1773 },
1774
1775 /**
1776 * Changes the tag name of the current element.
1777 *
1778 * @param {String} newTag The new tag for the element.
1779 */
1780 renameNode: function( newTag ) {
1781 // If it's already correct exit here.
1782 if ( this.getName() == newTag )
1783 return;
1784
1785 var doc = this.getDocument();
1786
1787 // Create the new node.
1788 var newNode = new CKEDITOR.dom.element( newTag, doc );
1789
1790 // Copy all attributes.
1791 this.copyAttributes( newNode );
1792
1793 // Move children to the new node.
1794 this.moveChildren( newNode );
1795
1796 // Replace the node.
1797 this.getParent( true ) && this.$.parentNode.replaceChild( newNode.$, this.$ );
1798 newNode.$[ 'data-cke-expando' ] = this.$[ 'data-cke-expando' ];
1799 this.$ = newNode.$;
1800 // Bust getName's cache. (#8663)
1801 delete this.getName;
1802 },
1803
1804 /**
1805 * Gets a DOM tree descendant under the current node.
1806 *
1807 * var strong = p.getChild( 0 );
1808 *
1809 * @method
1810 * @param {Array/Number} indices The child index or array of child indices under the node.
1811 * @returns {CKEDITOR.dom.node} The specified DOM child under the current node. Null if child does not exist.
1812 */
1813 getChild: ( function() {
1814 function getChild( rawNode, index ) {
1815 var childNodes = rawNode.childNodes;
1816
1817 if ( index >= 0 && index < childNodes.length )
1818 return childNodes[ index ];
1819 }
1820
1821 return function( indices ) {
1822 var rawNode = this.$;
1823
1824 if ( !indices.slice )
1825 rawNode = getChild( rawNode, indices );
1826 else {
1827 indices = indices.slice();
1828 while ( indices.length > 0 && rawNode )
1829 rawNode = getChild( rawNode, indices.shift() );
1830 }
1831
1832 return rawNode ? new CKEDITOR.dom.node( rawNode ) : null;
1833 };
1834 } )(),
1835
1836 /**
1837 * Gets number of element's children.
1838 *
1839 * @returns {Number}
1840 */
1841 getChildCount: function() {
1842 return this.$.childNodes.length;
1843 },
1844
1845 /**
1846 * Disables browser's context menu in this element.
1847 */
1848 disableContextMenu: function() {
1849 this.on( 'contextmenu', function( evt ) {
1850 // Cancel the browser context menu.
1851 if ( !evt.data.getTarget().getAscendant( enablesContextMenu, true ) )
1852 evt.data.preventDefault();
1853 } );
1854
1855 function enablesContextMenu( node ) {
1856 return node.type == CKEDITOR.NODE_ELEMENT && node.hasClass( 'cke_enable_context_menu' );
1857 }
1858 },
1859
1860 /**
1861 * Gets element's direction. Supports both CSS `direction` prop and `dir` attr.
1862 */
1863 getDirection: function( useComputed ) {
1864 if ( useComputed ) {
1865 return this.getComputedStyle( 'direction' ) ||
1866 this.getDirection() ||
1867 this.getParent() && this.getParent().getDirection( 1 ) ||
1868 this.getDocument().$.dir ||
1869 'ltr';
1870 }
1871 else {
1872 return this.getStyle( 'direction' ) || this.getAttribute( 'dir' );
1873 }
1874 },
1875
1876 /**
1877 * Gets, sets and removes custom data to be stored as HTML5 data-* attributes.
1878 *
1879 * element.data( 'extra-info', 'test' ); // Appended the attribute data-extra-info="test" to the element.
1880 * alert( element.data( 'extra-info' ) ); // 'test'
1881 * element.data( 'extra-info', false ); // Remove the data-extra-info attribute from the element.
1882 *
1883 * @param {String} name The name of the attribute, excluding the `data-` part.
1884 * @param {String} [value] The value to set. If set to false, the attribute will be removed.
1885 */
1886 data: function( name, value ) {
1887 name = 'data-' + name;
1888 if ( value === undefined )
1889 return this.getAttribute( name );
1890 else if ( value === false )
1891 this.removeAttribute( name );
1892 else
1893 this.setAttribute( name, value );
1894
1895 return null;
1896 },
1897
1898 /**
1899 * Retrieves an editor instance which is based on this element (if any).
1900 * It basically loops over {@link CKEDITOR#instances} in search for an instance
1901 * that uses the element.
1902 *
1903 * var element = new CKEDITOR.dom.element( 'div' );
1904 * element.appendTo( CKEDITOR.document.getBody() );
1905 * CKEDITOR.replace( element );
1906 * alert( element.getEditor().name ); // 'editor1'
1907 *
1908 * @returns {CKEDITOR.editor} An editor instance or null if nothing has been found.
1909 */
1910 getEditor: function() {
1911 var instances = CKEDITOR.instances,
1912 name, instance;
1913
1914 for ( name in instances ) {
1915 instance = instances[ name ];
1916
1917 if ( instance.element.equals( this ) && instance.elementMode != CKEDITOR.ELEMENT_MODE_APPENDTO )
1918 return instance;
1919 }
1920
1921 return null;
1922 },
1923
1924 /**
1925 * Returns list of elements within this element that match specified `selector`.
1926 *
1927 * **Notes:**
1928 *
1929 * * Not available in IE7.
1930 * * Returned list is not a live collection (like a result of native `querySelectorAll`).
1931 * * Unlike native `querySelectorAll` this method ensures selector contextualization. This is:
1932 *
1933 * HTML: '<body><div><i>foo</i></div></body>'
1934 * Native: div.querySelectorAll( 'body i' ) // -> [ <i>foo</i> ]
1935 * Method: div.find( 'body i' ) // -> []
1936 * div.find( 'i' ) // -> [ <i>foo</i> ]
1937 *
1938 * @since 4.3
1939 * @param {String} selector
1940 * @returns {CKEDITOR.dom.nodeList}
1941 */
1942 find: function( selector ) {
1943 var removeTmpId = createTmpId( this ),
1944 list = new CKEDITOR.dom.nodeList(
1945 this.$.querySelectorAll( getContextualizedSelector( this, selector ) )
1946 );
1947
1948 removeTmpId();
1949
1950 return list;
1951 },
1952
1953 /**
1954 * Returns first element within this element that matches specified `selector`.
1955 *
1956 * **Notes:**
1957 *
1958 * * Not available in IE7.
1959 * * Unlike native `querySelectorAll` this method ensures selector contextualization. This is:
1960 *
1961 * HTML: '<body><div><i>foo</i></div></body>'
1962 * Native: div.querySelector( 'body i' ) // -> <i>foo</i>
1963 * Method: div.findOne( 'body i' ) // -> null
1964 * div.findOne( 'i' ) // -> <i>foo</i>
1965 *
1966 * @since 4.3
1967 * @param {String} selector
1968 * @returns {CKEDITOR.dom.element}
1969 */
1970 findOne: function( selector ) {
1971 var removeTmpId = createTmpId( this ),
1972 found = this.$.querySelector( getContextualizedSelector( this, selector ) );
1973
1974 removeTmpId();
1975
1976 return found ? new CKEDITOR.dom.element( found ) : null;
1977 },
1978
1979 /**
1980 * Traverse the DOM of this element (inclusive), executing a callback for
1981 * each node.
1982 *
1983 * var element = CKEDITOR.dom.element.createFromHtml( '<div><p>foo<b>bar</b>bom</p></div>' );
1984 * element.forEach( function( node ) {
1985 * console.log( node );
1986 * } );
1987 * // Will log:
1988 * // 1. <div> element,
1989 * // 2. <p> element,
1990 * // 3. "foo" text node,
1991 * // 4. <b> element,
1992 * // 5. "bar" text node,
1993 * // 6. "bom" text node.
1994 *
1995 * @since 4.3
1996 * @param {Function} callback Function to be executed on every node.
1997 * If `callback` returns `false` descendants of the node will be ignored.
1998 * @param {CKEDITOR.htmlParser.node} callback.node Node passed as argument.
1999 * @param {Number} [type] If specified `callback` will be executed only on
2000 * nodes of this type.
2001 * @param {Boolean} [skipRoot] Don't execute `callback` on this element.
2002 */
2003 forEach: function( callback, type, skipRoot ) {
2004 if ( !skipRoot && ( !type || this.type == type ) )
2005 var ret = callback( this );
2006
2007 // Do not filter children if callback returned false.
2008 if ( ret === false )
2009 return;
2010
2011 var children = this.getChildren(),
2012 node,
2013 i = 0;
2014
2015 // We do not cache the size, because the live list of nodes may be changed by the callback.
2016 for ( ; i < children.count(); i++ ) {
2017 node = children.getItem( i );
2018 if ( node.type == CKEDITOR.NODE_ELEMENT )
2019 node.forEach( callback, type );
2020 else if ( !type || node.type == type )
2021 callback( node );
2022 }
2023 }
2024 } );
2025
2026 function createTmpId( element ) {
2027 var hadId = true;
2028
2029 if ( !element.$.id ) {
2030 element.$.id = 'cke_tmp_' + CKEDITOR.tools.getNextNumber();
2031 hadId = false;
2032 }
2033
2034 return function() {
2035 if ( !hadId )
2036 element.removeAttribute( 'id' );
2037 };
2038 }
2039
2040 function getContextualizedSelector( element, selector ) {
2041 return '#' + element.$.id + ' ' + selector.split( /,\s*/ ).join( ', #' + element.$.id + ' ' );
2042 }
2043
2044 var sides = {
2045 width: [ 'border-left-width', 'border-right-width', 'padding-left', 'padding-right' ],
2046 height: [ 'border-top-width', 'border-bottom-width', 'padding-top', 'padding-bottom' ]
2047 };
2048
2049 // Generate list of specific style rules, applicable to margin/padding/border.
2050 function expandedRules( style ) {
2051 var sides = [ 'top', 'left', 'right', 'bottom' ], components;
2052
2053 if ( style == 'border' )
2054 components = [ 'color', 'style', 'width' ];
2055
2056 var styles = [];
2057 for ( var i = 0 ; i < sides.length ; i++ ) {
2058
2059 if ( components ) {
2060 for ( var j = 0 ; j < components.length ; j++ )
2061 styles.push( [ style, sides[ i ], components[ j ] ].join( '-' ) );
2062 } else {
2063 styles.push( [ style, sides[ i ] ].join( '-' ) );
2064 }
2065 }
2066
2067 return styles;
2068 }
2069
2070 function marginAndPaddingSize( type ) {
2071 var adjustment = 0;
2072 for ( var i = 0, len = sides[ type ].length; i < len; i++ )
2073 adjustment += parseInt( this.getComputedStyle( sides[ type ][ i ] ) || 0, 10 ) || 0;
2074 return adjustment;
2075 }
2076
2077 /**
2078 * Sets the element size considering the box model.
2079 *
2080 * @param {'width'/'height'} type The dimension to set.
2081 * @param {Number} size The length unit in px.
2082 * @param {Boolean} isBorderBox Apply the size based on the border box model.
2083 */
2084 CKEDITOR.dom.element.prototype.setSize = function( type, size, isBorderBox ) {
2085 if ( typeof size == 'number' ) {
2086 if ( isBorderBox && !( CKEDITOR.env.ie && CKEDITOR.env.quirks ) )
2087 size -= marginAndPaddingSize.call( this, type );
2088
2089 this.setStyle( type, size + 'px' );
2090 }
2091 };
2092
2093 /**
2094 * Gets the element size, possibly considering the box model.
2095 *
2096 * @param {'width'/'height'} type The dimension to get.
2097 * @param {Boolean} isBorderBox Get the size based on the border box model.
2098 */
2099 CKEDITOR.dom.element.prototype.getSize = function( type, isBorderBox ) {
2100 var size = Math.max( this.$[ 'offset' + CKEDITOR.tools.capitalize( type ) ], this.$[ 'client' + CKEDITOR.tools.capitalize( type ) ] ) || 0;
2101
2102 if ( isBorderBox )
2103 size -= marginAndPaddingSize.call( this, type );
2104
2105 return size;
2106 };
2107 } )();