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