2 * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
7 * @fileOverview Defines the {@link CKEDITOR.dom.element} class, which
8 * represents a DOM element.
12 * Represents a DOM element.
14 * // Create a new <span> element.
15 * var element = new CKEDITOR.dom.element( 'span' );
17 * // Create an element based on a native DOM element.
18 * var element = new CKEDITOR.dom.element( document.getElementById( 'myId' ) );
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
25 * @param {CKEDITOR.dom.document} [ownerDocument] The document that will contain
26 * the element in case of element creation.
28 CKEDITOR
.dom
.element = function( element
, ownerDocument
) {
29 if ( typeof element
== 'string' )
30 element
= ( ownerDocument
? ownerDocument
.$ : document
).createElement( element
);
32 // Call the base constructor (we must not call CKEDITOR.dom.node).
33 CKEDITOR
.dom
.domObject
.call( this, element
);
36 // PACKAGER_RENAME( CKEDITOR.dom.element )
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.
42 * var element = new CKEDITOR.dom.element( 'span' );
43 * alert( element == CKEDITOR.dom.element.get( element ) ); // true
45 * var element = document.getElementById( 'myElement' );
46 * alert( CKEDITOR.dom.element.get( element ).getName() ); // (e.g.) 'p'
49 * @param {String/Object} element Element's id or name or native DOM element.
50 * @returns {CKEDITOR.dom.element} The transformed element.
52 CKEDITOR
.dom
.element
.get = function( element
) {
53 var el
= typeof element
== 'string' ? document
.getElementById( element
) || document
.getElementsByName( element
)[ 0 ] : element
;
55 return el
&& ( el
.$ ? el : new CKEDITOR
.dom
.element( el
) );
58 CKEDITOR
.dom
.element
.prototype = new CKEDITOR
.dom
.node();
61 * Creates an instance of the {@link CKEDITOR.dom.element} class based on the
62 * HTML representation of an element.
64 * var element = CKEDITOR.dom.element.createFromHtml( '<strong class="anyclass">My element</strong>' );
65 * alert( element.getName() ); // 'strong'
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.
72 CKEDITOR
.dom
.element
.createFromHtml = function( html
, ownerDocument
) {
73 var temp
= new CKEDITOR
.dom
.element( 'div', ownerDocument
);
76 // When returning the node, remove it from its parent to detach it.
77 return temp
.getFirst().remove();
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.
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.
88 * CKEDITOR.dom.element.setMarker( database, element1, 'foo', 'bar' );
89 * CKEDITOR.dom.element.setMarker( database, element2, 'oof', [ 1, 2, 3 ] );
91 * element1.getCustomData( 'foo' ); // 'bar'
92 * element2.getCustomData( 'oof' ); // [ 1, 2, 3 ]
94 * CKEDITOR.dom.element.clearAllMarkers( database );
96 * element1.getCustomData( 'foo' ); // null
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.
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;
111 return element
.setCustomData( name
, value
);
115 * Removes all markers added using this database. See the {@link #setMarker} method for more information.
117 * @param {Object} database
120 CKEDITOR
.dom
.element
.clearAllMarkers = function( database
) {
121 for ( var i
in database
)
122 CKEDITOR
.dom
.element
.clearMarkers( database
, database
[ i
], 1 );
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.
130 * CKEDITOR.dom.element.setMarker( database, element1, 'foo', 'bar' );
131 * CKEDITOR.dom.element.setMarker( database, element2, 'oof', [ 1, 2, 3 ] );
133 * element1.getCustomData( 'foo' ); // 'bar'
134 * element2.getCustomData( 'oof' ); // [ 1, 2, 3 ]
136 * CKEDITOR.dom.element.clearMarkers( database, element1, true );
138 * element1.getCustomData( 'foo' ); // null
139 * element2.getCustomData( 'oof' ); // [ 1, 2, 3 ]
141 * @param {Object} database
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
];
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;
161 function hasClass( classNames
, className
) {
163 return ( ' ' + classNames
+ ' ' ).replace( rclass
, ' ' ).indexOf( ' ' + className
+ ' ' ) > -1;
166 CKEDITOR
.tools
.extend( CKEDITOR
.dom
.element
.prototype, {
168 * The node type. This is a constant value set to {@link CKEDITOR#NODE_ELEMENT}.
171 * @property {Number} [=CKEDITOR.NODE_ELEMENT]
173 type: CKEDITOR
.NODE_ELEMENT
,
176 * Adds a CSS class to the element. It appends the class to the
177 * already existing names.
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">
184 * **Note:** Since CKEditor 4.5 this method cannot be used with multiple classes (`'classA classB'`).
188 * @param {String} className The name of the class to be added.
190 addClass: supportsClassLists
?
191 function( className
) {
192 this.$.classList
.add( className
);
195 } : function( className
) {
196 var c
= this.$.className
;
198 if ( !hasClass( c
, className
) )
199 c
+= ' ' + className
;
201 this.$.className
= c
|| className
;
207 * Removes a CSS class name from the elements classes. Other classes
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>
217 * @method removeClass
218 * @param {String} className The name of the class to remove.
220 removeClass: supportsClassLists
?
221 function( className
) {
223 $.classList
.remove( className
);
226 $.removeAttribute( 'class' );
229 } : function( className
) {
230 var c
= this.getAttribute( 'class' );
231 if ( c
&& hasClass( c
, className
) ) {
233 .replace( new RegExp( '(?:^|\\s+)' + className
+ '(?=\\s|$)' ), '' )
234 .replace( /^\s+/, '' );
237 this.setAttribute( 'class', c
);
239 this.removeAttribute( 'class' );
246 * Checks if element has class name.
248 * @param {String} className
251 hasClass: function( className
) {
252 return hasClass( this.$.className
, className
);
256 * Append a node as a child of this element.
258 * var p = new CKEDITOR.dom.element( 'p' );
260 * var strong = new CKEDITOR.dom.element( 'strong' );
261 * p.append( strong );
263 * var em = p.append( 'em' );
265 * // Result: '<p><strong></strong><em></em></p>'
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.
271 append: function( node
, toStart
) {
272 if ( typeof node
== 'string' )
273 node
= this.getDocument().createElement( node
);
276 this.$.insertBefore( node
.$, this.$.firstChild
);
278 this.$.appendChild( node
.$ );
284 * Append HTML as a child(ren) of this element.
286 * @param {String} html
288 appendHtml: function( html
) {
289 if ( !this.$.childNodes
.length
)
290 this.setHtml( html
);
292 var temp
= new CKEDITOR
.dom
.element( 'div', this.getDocument() );
293 temp
.setHtml( html
);
294 temp
.moveChildren( this );
299 * Append text to this element.
301 * var p = new CKEDITOR.dom.element( 'p' );
302 * p.appendText( 'This is' );
303 * p.appendText( ' some text' );
305 * // Result: '<p>This is some text</p>'
307 * @param {String} text The text to be appended.
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 )
315 this.append( new CKEDITOR
.dom
.text( text
) );
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.
323 * @param {Boolean} [force] Append filler regardless of the environment.
325 appendBogus: function( force
) {
326 if ( !force
&& !CKEDITOR
.env
.needsBrFiller
)
329 var lastChild
= this.getLast();
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' );
337 CKEDITOR
.env
.gecko
&& bogus
.setAttribute( 'type', '_moz' );
339 this.append( bogus
);
344 * Breaks one of the ancestor element in the element position, moving
345 * this element between the broken parts.
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 );
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 );
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.
362 breakParent: function( parent
, cloneId
) {
363 var range
= new CKEDITOR
.dom
.range( this.getDocument() );
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
);
371 var docFrag
= range
.extractContents( false, cloneId
|| false );
373 // Move the element outside the broken element.
374 range
.insertNode( this.remove() );
376 // Re-insert the extracted piece after the element.
377 docFrag
.insertAfterNode( this );
381 * Checks if this element contains given node.
384 * @param {CKEDITOR.dom.node} node
387 contains: !document
.compareDocumentPosition
?
391 return node
.type
!= CKEDITOR
.NODE_ELEMENT
? $.contains( node
.getParent().$ ) : $ != node
.$ && $.contains( node
.$ );
392 } : function( node
) {
393 return !!( this.$.compareDocumentPosition( node
.$ ) & 16 );
397 * Moves the selection focus to this element.
399 * var element = CKEDITOR.document.getById( 'myTextarea' );
403 * @param {Boolean} defer Whether to asynchronously defer the
404 * execution by 100 ms.
406 focus: ( function() {
408 // IE throws error if the element is not visible.
414 return function( defer
) {
416 CKEDITOR
.tools
.setTimeout( exec
, 100, this );
423 * Gets the inner HTML of this element.
425 * var element = CKEDITOR.dom.element.createFromHtml( '<div><b>Example</b></div>' );
426 * alert( element.getHtml() ); // '<b>Example</b>'
428 * @returns {String} The inner HTML of this element.
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
;
437 * Gets the outer (inner plus tags) HTML of this element.
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>'
442 * @returns {String} The outer HTML of this element.
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( /<\?[^>]*>/, '' );
451 var tmpDiv
= this.$.ownerDocument
.createElement( 'div' );
452 tmpDiv
.appendChild( this.$.cloneNode( true ) );
453 return tmpDiv
.innerHTML
;
457 * Retrieve the bounding rectangle of the current element, in pixels,
458 * relative to the upper-left corner of the browser's client area.
460 * @returns {Object} The dimensions of the DOM element including
461 * `left`, `top`, `right`, `bottom`, `width` and `height`.
463 getClientRect: function() {
464 // http://help.dottoro.com/ljvmcrrn.php
465 var rect
= CKEDITOR
.tools
.extend( {}, this.$.getBoundingClientRect() );
467 !rect
.width
&& ( rect
.width
= rect
.right
- rect
.left
);
468 !rect
.height
&& ( rect
.height
= rect
.bottom
- rect
.top
);
474 * Sets the inner HTML of this element.
476 * var p = new CKEDITOR.dom.element( 'p' );
477 * p.setHtml( '<b>Inner</b> HTML' );
479 * // Result: '<p><b>Inner</b> HTML</p>'
482 * @param {String} html The HTML to be set for this element.
483 * @returns {String} The inserted HTML.
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.
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
);
500 var $frag
= this.getDocument()._getHtml5ShivFrag();
501 $frag
.appendChild( $ );
503 $frag
.removeChild( $ );
509 this.$.innerHTML
= '';
511 var temp
= new CKEDITOR
.dom
.element( 'body', this.getDocument() );
512 temp
.$.innerHTML
= html
;
514 var children
= temp
.getChildren();
515 while ( children
.count() )
516 this.append( children
.getItem( 0 ) );
520 } : function( html
) {
521 return ( this.$.innerHTML
= html
);
525 * Sets the element contents as plain text.
527 * var element = new CKEDITOR.dom.element( 'div' );
528 * element.setText( 'A > B & C < D' );
529 * alert( element.innerHTML ); // 'A > B & C < D'
531 * @param {String} text The text to be set.
532 * @returns {String} The inserted text.
534 setText: ( function() {
535 var supportsTextContent
= document
.createElement( 'p' );
536 supportsTextContent
.innerHTML
= 'x';
537 supportsTextContent
= supportsTextContent
.textContent
;
539 return function( text
) {
540 this.$[ supportsTextContent
? 'textContent' : 'innerText' ] = text
;
545 * Gets the value of an element attribute.
547 * var element = CKEDITOR.dom.element.createFromHtml( '<input type="text" />' );
548 * alert( element.getAttribute( 'type' ) ); // 'text'
551 * @param {String} name The attribute name.
552 * @returns {String} The attribute value or null if not defined.
554 getAttribute: ( function() {
555 var standard = function( name
) {
556 return this.$.getAttribute( name
, 2 );
559 if ( CKEDITOR
.env
.ie
&& ( CKEDITOR
.env
.ie7Compat
|| CKEDITOR
.env
.quirks
) ) {
560 return function( name
) {
574 var tabIndex
= standard
.call( this, name
);
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 )
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.
591 return attrValue
? 'checked' : null;
595 return this.$[ name
];
598 // IE does not return inline styles via getAttribute(). See #2947.
599 return this.$.style
.cssText
;
601 case 'contenteditable':
602 case 'contentEditable':
603 return this.$.attributes
.getNamedItem( 'contentEditable' ).specified
? this.$.getAttribute( 'contentEditable' ) : null;
606 return standard
.call( this, name
);
614 * Gets the nodes list containing all children of this element.
616 * @returns {CKEDITOR.dom.nodeList}
618 getChildren: function() {
619 return new CKEDITOR
.dom
.nodeList( this.$.childNodes
);
623 * Gets the current computed value of one of the element CSS style
626 * var element = new CKEDITOR.dom.element( 'span' );
627 * alert( element.getComputedStyle( 'display' ) ); // 'inline'
630 * @param {String} propertyName The style property name.
631 * @returns {String} The property value.
633 getComputedStyle: ( document
.defaultView
&& document
.defaultView
.getComputedStyle
) ?
634 function( propertyName
) {
635 var style
= this.getWindow().$.getComputedStyle( this.$, null );
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
) ];
644 * Gets the DTD entries for this element.
646 * @returns {Object} An object containing the list of elements accepted
650 var dtd
= CKEDITOR
.dtd
[ this.getName() ];
652 this.getDtd = function() {
660 * Gets all this element's descendants having given tag name.
663 * @param {String} tagName
665 getElementsByTag: CKEDITOR
.dom
.document
.prototype.getElementsByTag
,
668 * Gets the computed tabindex for this element.
670 * var element = CKEDITOR.document.getById( 'myDiv' );
671 * alert( element.getTabIndex() ); // (e.g.) '-1'
674 * @returns {Number} The tabindex value.
676 getTabIndex: function() {
677 var tabIndex
= this.$.tabIndex
;
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 )
690 * Gets the text value of this element.
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
697 * var element = CKEDITOR.dom.element.createFromHtml( '<div>Sample <i>text</i>.</div>' );
698 * alert( <b>element.getText()</b> ); // 'Sample text.'
700 * @returns {String} The text value.
702 getText: function() {
703 return this.$.textContent
|| this.$.innerText
|| '';
707 * Gets the window object that contains this element.
709 * @returns {CKEDITOR.dom.window} The window object.
711 getWindow: function() {
712 return this.getDocument().getWindow();
716 * Gets the value of the `id` attribute of this element.
718 * var element = CKEDITOR.dom.element.createFromHtml( '<p id="myId"></p>' );
719 * alert( element.getId() ); // 'myId'
721 * @returns {String} The element id, or null if not available.
724 return this.$.id
|| null;
728 * Gets the value of the `name` attribute of this element.
730 * var element = CKEDITOR.dom.element.createFromHtml( '<input name="myName"></input>' );
731 * alert( <b>element.getNameAtt()</b> ); // 'myName'
733 * @returns {String} The element name, or null if not available.
735 getNameAtt: function() {
736 return this.$.name
|| null;
740 * Gets the element name (tag name). The returned name is guaranteed to
741 * be always full lowercased.
743 * var element = new CKEDITOR.dom.element( 'span' );
744 * alert( element.getName() ); // 'span'
746 * @returns {String} The element name.
748 getName: function() {
749 // Cache the lowercased name inside a closure.
750 var nodeName
= this.$.nodeName
.toLowerCase();
752 if ( CKEDITOR
.env
.ie
&& ( document
.documentMode
<= 8 ) ) {
753 var scopeName
= this.$.scopeName
;
754 if ( scopeName
!= 'HTML' )
755 nodeName
= scopeName
.toLowerCase() + ':' + nodeName
;
758 this.getName = function() {
762 return this.getName();
766 * Gets the value set to this element. This value is usually available
767 * for form field elements.
769 * @returns {String} The element value.
771 getValue: function() {
776 * Gets the first child node of this element.
778 * var element = CKEDITOR.dom.element.createFromHtml( '<div><b>Example</b></div>' );
779 * var first = element.getFirst();
780 * alert( first.getName() ); // 'b'
782 * @param {Function} evaluator Filtering the result node.
783 * @returns {CKEDITOR.dom.node} The first child node or null if not available.
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
);
795 * See {@link #getFirst}.
797 * @param {Function} evaluator Filtering the result node.
798 * @returns {CKEDITOR.dom.node}
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
);
810 * Gets CSS style value.
812 * @param {String} name The CSS property name.
813 * @returns {String} Style value.
815 getStyle: function( name
) {
816 return this.$.style
[ CKEDITOR
.tools
.cssStyleToDomStyle( name
) ];
820 * Checks if the element name matches the specified criteria.
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
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.
833 var name
= this.getName();
835 // Check against the specified DTD liternal.
836 if ( typeof arguments
[ 0 ] == 'object' )
837 return !!arguments
[ 0 ][ name
];
839 // Check for tag names
840 for ( var i
= 0; i
< arguments
.length
; i
++ ) {
841 if ( arguments
[ i
] == name
)
848 * Decide whether one element is able to receive cursor.
850 * @param {Boolean} [textCursor=true] Only consider element that could receive text child.
852 isEditable: function( textCursor
) {
853 var name
= this.getName();
855 if ( this.isReadOnly() || this.getComputedStyle( 'display' ) == 'none' ||
856 this.getComputedStyle( 'visibility' ) == 'hidden' ||
857 CKEDITOR
.dtd
.$nonEditable
[ name
] ||
858 CKEDITOR
.dtd
.$empty
[ name
] ||
860 ( this.data( 'cke-saved-name' ) || this.hasAttribute( 'name' ) ) &&
861 !this.getChildCount()
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
[ '#' ] );
877 * Compare this element's inner html, tag name, attributes, etc. with other one.
879 * See [W3C's DOM Level 3 spec - node#isEqualNode](http://www.w3.org/TR/DOM-Level-3-Core/core.html#Node3-isEqualNode)
882 * @param {CKEDITOR.dom.element} otherElement Element to compare.
885 isIdentical: function( otherElement
) {
886 // do shallow clones, but with IDs
887 var thisEl
= this.clone( 0, 1 ),
888 otherEl
= otherElement
.clone( 0, 1 );
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' ] );
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
.$ );
901 thisEl
= thisEl
.getOuterHtml();
902 otherEl
= otherEl
.getOuterHtml();
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();
914 return thisEl
== otherEl
;
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.
923 * @returns {Boolean} True if the element is visible.
925 isVisible: function() {
926 var isVisible
= ( this.$.offsetHeight
|| this.$.offsetWidth
) && this.getComputedStyle( 'visibility' ) != 'hidden',
927 elementWindow
, elementWindowFrame
;
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();
934 if ( !elementWindow
.equals( CKEDITOR
.document
.getWindow() ) && ( elementWindowFrame
= elementWindow
.$.frameElement
) )
935 isVisible
= new CKEDITOR
.dom
.element( elementWindowFrame
).isVisible();
943 * Whether it's an empty inline elements which has no visual impact when removed.
947 isEmptyInlineRemoveable: function() {
948 if ( !CKEDITOR
.dtd
.$removeEmpty
[ this.getName() ] )
951 var children
= this.getChildren();
952 for ( var i
= 0, count
= children
.count(); i
< count
; i
++ ) {
953 var child
= children
.getItem( i
);
955 if ( child
.type
== CKEDITOR
.NODE_ELEMENT
&& child
.data( 'cke-bookmark' ) )
958 if ( child
.type
== CKEDITOR
.NODE_ELEMENT
&& !child
.isEmptyInlineRemoveable() || child
.type
== CKEDITOR
.NODE_TEXT
&& CKEDITOR
.tools
.trim( child
.getText() ) )
966 * Checks if the element has any defined attributes.
968 * var element = CKEDITOR.dom.element.createFromHtml( '<div title="Test">Example</div>' );
969 * alert( element.hasAttributes() ); // true
971 * var element = CKEDITOR.dom.element.createFromHtml( '<div>Example</div>' );
972 * alert( element.hasAttributes() ); // false
975 * @returns {Boolean} True if the element has attributes.
977 hasAttributes: CKEDITOR
.env
.ie
&& ( CKEDITOR
.env
.ie7Compat
|| CKEDITOR
.env
.quirks
) ?
979 var attributes
= this.$.attributes
;
981 for ( var i
= 0; i
< attributes
.length
; i
++ ) {
982 var attribute
= attributes
[ i
];
984 switch ( attribute
.nodeName
) {
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' ) ) {
996 // Attributes to be ignored.
998 case 'data-cke-expando':
1004 if ( attribute
.specified
) {
1012 var attrs
= this.$.attributes
,
1013 attrsNum
= attrs
.length
;
1015 // The _moz_dirty attribute might get into the element after pasting (#5455)
1016 var execludeAttrs
= { 'data-cke-expando': 1, _moz_dirty: 1 };
1018 return attrsNum
> 0 && ( attrsNum
> 2 || !execludeAttrs
[ attrs
[ 0 ].nodeName
] || ( attrsNum
== 2 && !execludeAttrs
[ attrs
[ 1 ].nodeName
] ) );
1022 * Checks if the specified attribute is defined for this element.
1025 * @param {String} name The attribute name.
1026 * @returns {Boolean} `true` if the specified attribute is defined.
1028 hasAttribute: ( function() {
1029 function ieHasAttribute( name
) {
1030 var $attr
= this.$.attributes
.getNamedItem( name
);
1032 if ( this.getName() == 'input' ) {
1035 return this.$.className
.length
> 0;
1037 return !!this.$.checked
;
1039 var type
= this.getAttribute( 'type' );
1040 return type
== 'checkbox' || type
== 'radio' ? this.$.value
!= 'on' : !!this.$.value
;
1047 return $attr
.specified
;
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
;
1059 return ieHasAttribute
.call( this, name
);
1062 return ieHasAttribute
;
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
);
1074 * Hides this element (sets `display: none`).
1076 * var element = CKEDITOR.document.getById( 'myElement' );
1080 this.setStyle( 'display', 'none' );
1084 * Moves this element's children to the target element.
1086 * @param {CKEDITOR.dom.element} target
1087 * @param {Boolean} [toStart=false] Insert moved children at the
1088 * beginning of the target element.
1090 moveChildren: function( target
, toStart
) {
1100 while ( ( child
= $.lastChild
) )
1101 target
.insertBefore( $.removeChild( child
), target
.firstChild
);
1103 while ( ( child
= $.firstChild
) )
1104 target
.appendChild( $.removeChild( child
) );
1109 * Merges sibling elements that are identical to this one.
1111 * Identical child elements are also merged. For example:
1113 * <b><i></i></b><b><i></i></b> => <b><i></i></b>
1116 * @param {Boolean} [inlineOnly=true] Allow only inline elements to be merged.
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
= [];
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
)
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();
1137 // Move pending nodes first into the target element.
1138 while ( pendingNodes
.length
)
1139 pendingNodes
.shift().move( element
, !isNext
);
1141 sibling
.moveChildren( element
, !isNext
);
1144 // Now check the last inner child (see two comments above).
1145 if ( innerSibling
&& innerSibling
.type
== CKEDITOR
.NODE_ELEMENT
)
1146 innerSibling
.mergeSiblings();
1151 return function( inlineOnly
) {
1152 // Merge empty links and anchors also. (#5567)
1153 if ( !( inlineOnly
=== false || CKEDITOR
.dtd
.$removeEmpty
[ this.getName() ] || this.is( 'a' ) ) ) {
1157 mergeElements( this, this.getNext(), true );
1158 mergeElements( this, this.getPrevious() );
1163 * Shows this element (displays it).
1165 * var element = CKEDITOR.document.getById( 'myElement' );
1176 * Sets the value of an element attribute.
1178 * var element = CKEDITOR.document.getById( 'myElement' );
1179 * element.setAttribute( 'class', 'myClass' );
1180 * element.setAttribute( 'title', 'This is an example' );
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.
1187 setAttribute: ( function() {
1188 var standard = function( name
, value
) {
1189 this.$.setAttribute( name
, value
);
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
);
1206 standard
.apply( this, arguments
);
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:\/\// ) ) {
1214 standard
.apply( this, arguments
);
1217 standard
.apply( this, arguments
);
1227 * Sets the value of several element attributes.
1229 * var element = CKEDITOR.document.getById( 'myElement' );
1230 * element.setAttributes( {
1231 * 'class': 'myClass',
1232 * title: 'This is an example'
1236 * @param {Object} attributesPairs An object containing the names and
1237 * values of the attributes.
1238 * @returns {CKEDITOR.dom.element} This element instance.
1240 setAttributes: function( attributesPairs
) {
1241 for ( var name
in attributesPairs
)
1242 this.setAttribute( name
, attributesPairs
[ name
] );
1247 * Sets the element value. This function is usually used with form
1251 * @param {String} value The element value.
1252 * @returns {CKEDITOR.dom.element} This element instance.
1254 setValue: function( value
) {
1255 this.$.value
= value
;
1260 * Removes an attribute from the element.
1262 * var element = CKEDITOR.dom.element.createFromHtml( '<div class="classA"></div>' );
1263 * element.removeAttribute( 'class' );
1266 * @param {String} name The attribute name.
1268 removeAttribute: ( function() {
1269 var standard = function( name
) {
1270 this.$.removeAttribute( name
);
1273 if ( CKEDITOR
.env
.ie
&& ( CKEDITOR
.env
.ie7Compat
|| CKEDITOR
.env
.quirks
) ) {
1274 return function( name
) {
1275 if ( name
== 'class' )
1277 else if ( name
== 'tabindex' )
1279 else if ( name
== 'contenteditable' )
1280 name
= 'contentEditable';
1281 standard
.call( this, name
);
1289 * Removes all element's attributes or just given ones.
1291 * @param {Array} [attributes] The array with attributes names.
1293 removeAttributes: function( attributes
) {
1294 if ( CKEDITOR
.tools
.isArray( attributes
) ) {
1295 for ( var i
= 0; i
< attributes
.length
; i
++ )
1296 this.removeAttribute( attributes
[ i
] );
1298 for ( var attr
in attributes
)
1299 attributes
.hasOwnProperty( attr
) && this.removeAttribute( attr
);
1304 * Removes a style from the element.
1306 * var element = CKEDITOR.dom.element.createFromHtml( '<div style="display:none"></div>' );
1307 * element.removeStyle( 'display' );
1310 * @param {String} name The style name.
1312 removeStyle: function( name
) {
1313 // Removes the specified property from the current style object.
1314 var $ = this.$.style
;
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
] );
1324 $.removeProperty
? $.removeProperty( name
) : $.removeAttribute( CKEDITOR
.tools
.cssStyleToDomStyle( name
) );
1326 // Eventually remove empty style attribute.
1327 if ( !this.$.style
.cssText
)
1328 this.removeAttribute( 'style' );
1332 * Sets the value of an element style.
1334 * var element = CKEDITOR.document.getById( 'myElement' );
1335 * element.setStyle( 'background-color', '#ff0000' );
1336 * element.setStyle( 'margin-top', '10px' );
1337 * element.setStyle( 'float', 'right' );
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.
1344 setStyle: function( name
, value
) {
1345 this.$.style
[ CKEDITOR
.tools
.cssStyleToDomStyle( name
) ] = value
;
1350 * Sets the value of several element styles.
1352 * var element = CKEDITOR.document.getById( 'myElement' );
1353 * element.setStyles( {
1354 * position: 'absolute',
1358 * @param {Object} stylesPairs An object containing the names and
1359 * values of the styles.
1360 * @returns {CKEDITOR.dom.element} This element instance.
1362 setStyles: function( stylesPairs
) {
1363 for ( var name
in stylesPairs
)
1364 this.setStyle( name
, stylesPairs
[ name
] );
1369 * Sets the opacity of an element.
1371 * var element = CKEDITOR.document.getById( 'myElement' );
1372 * element.setOpacity( 0.75 );
1374 * @param {Number} opacity A number within the range `[0.0, 1.0]`.
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
+ ')' );
1381 this.setStyle( 'opacity', opacity
);
1386 * Makes the element and its children unselectable.
1388 * var element = CKEDITOR.document.getById( 'myElement' );
1389 * element.unselectable();
1393 unselectable: function() {
1394 // CSS unselectable.
1395 this.setStyles( CKEDITOR
.tools
.cssVendorPrefix( 'user-select', 'none' ) );
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' );
1404 elements
= this.getElementsByTag( '*' );
1406 for ( var i
= 0, count
= elements
.count() ; i
< count
; i
++ ) {
1407 element
= elements
.getItem( i
);
1408 element
.setAttribute( 'unselectable', 'on' );
1414 * Gets closest positioned (`position != static`) ancestor.
1416 * @returns {CKEDITOR.dom.element} Positioned ancestor or `null`.
1418 getPositionedAncestor: function() {
1420 while ( current
.getName() != 'html' ) {
1421 if ( current
.getComputedStyle( 'position' ) != 'static' )
1424 current
= current
.getParent();
1430 * Gets this element's position in document.
1432 * @param {CKEDITOR.dom.document} [refDocument]
1433 * @returns {Object} Element's position.
1434 * @returns {Number} return.x
1435 * @returns {Number} return.y
1438 getDocumentPosition: function( refDocument
) {
1441 doc
= this.getDocument(),
1442 body
= doc
.getBody(),
1443 quirks
= doc
.$.compatMode
== 'BackCompat';
1445 if ( document
.documentElement
.getBoundingClientRect
) {
1446 var box
= this.$.getBoundingClientRect(),
1448 $docElem
= $doc
.documentElement
;
1450 var clientTop
= $docElem
.clientTop
|| body
.$.clientTop
|| 0,
1451 clientLeft
= $docElem
.clientLeft
|| body
.$.clientLeft
|| 0,
1452 needAdjustScrollAndBorders
= true;
1454 // #3804: getBoundingClientRect() works differently on IE and non-IE
1455 // browsers, regarding scroll positions.
1457 // On IE, the top position of the <html> element is always 0, no matter
1458 // how much you scrolled down.
1460 // On other browsers, the top position of the <html> element is negative
1462 if ( CKEDITOR
.env
.ie
) {
1463 var inDocElem
= doc
.getDocumentElement().contains( this ),
1464 inBody
= doc
.getBody().contains( this );
1466 needAdjustScrollAndBorders
= ( quirks
&& inBody
) || ( !quirks
&& inDocElem
);
1470 if ( needAdjustScrollAndBorders
) {
1471 var scrollRelativeLeft
,
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
;
1479 var scrollRelativeElement
= quirks
? body
.$ : $docElem
;
1481 scrollRelativeLeft
= scrollRelativeElement
.scrollLeft
;
1482 scrollRelativeTop
= scrollRelativeElement
.scrollTop
;
1485 x
= box
.left
+ scrollRelativeLeft
- clientLeft
;
1486 y
= box
.top
+ scrollRelativeTop
- clientTop
;
1492 while ( current
&& !( current
.getName() == 'body' || current
.getName() == 'html' ) ) {
1493 x
+= current
.$.offsetLeft
- current
.$.scrollLeft
;
1494 y
+= current
.$.offsetTop
- current
.$.scrollTop
;
1496 // Opera includes clientTop|Left into offsetTop|Left.
1497 if ( !current
.equals( this ) ) {
1498 x
+= ( current
.$.clientLeft
|| 0 );
1499 y
+= ( current
.$.clientTop
|| 0 );
1502 var scrollElement
= previous
;
1503 while ( scrollElement
&& !scrollElement
.equals( current
) ) {
1504 x
-= scrollElement
.$.scrollLeft
;
1505 y
-= scrollElement
.$.scrollTop
;
1506 scrollElement
= scrollElement
.getParent();
1510 current
= ( offsetParent
= current
.$.offsetParent
) ? new CKEDITOR
.dom
.element( offsetParent
) : null;
1514 if ( refDocument
) {
1515 var currentWindow
= this.getWindow(),
1516 refWindow
= refDocument
.getWindow();
1518 if ( !currentWindow
.equals( refWindow
) && currentWindow
.$.frameElement
) {
1519 var iframePosition
= ( new CKEDITOR
.dom
.element( currentWindow
.$.frameElement
) ).getDocumentPosition( refDocument
);
1521 x
+= iframePosition
.x
;
1522 y
+= iframePosition
.y
;
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;
1535 return { x: x
, y: y
};
1539 * Make any page element visible inside the browser viewport.
1541 * @param {Boolean} [alignToTop=false]
1543 scrollIntoView: function( alignToTop
) {
1544 var parent
= this.getParent();
1548 // Scroll the element into parent container from the inner out.
1550 // Check ancestors that overflows.
1552 parent
.$.clientWidth
&& parent
.$.clientWidth
< parent
.$.scrollWidth
||
1553 parent
.$.clientHeight
&& parent
.$.clientHeight
< parent
.$.scrollHeight
;
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 );
1560 // Walk across the frame.
1561 if ( parent
.is( 'html' ) ) {
1562 var win
= parent
.getWindow();
1564 // Avoid security error.
1566 var iframe
= win
.$.frameElement
;
1567 iframe
&& ( parent
= new CKEDITOR
.dom
.element( iframe
) );
1571 while ( ( parent
= parent
.getParent() ) );
1575 * Make any page element visible inside one of the ancestors by scrolling the parent.
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.
1584 scrollIntoParent: function( parent
, alignToTop
, hscroll
) {
1585 !parent
&& ( parent
= this.getWindow() );
1587 var doc
= parent
.getDocument();
1588 var isQuirks
= doc
.$.compatMode
== 'BackCompat';
1590 // On window <html> is scrolled while quirks scrolls <body>.
1591 if ( parent
instanceof CKEDITOR
.dom
.window
)
1592 parent
= isQuirks
? doc
.getBody() : doc
.getDocumentElement();
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
);
1601 parent
.$.scrollLeft
+= x
;
1602 parent
.$.scrollTop
+= y
;
1606 // Figure out the element position relative to the specified window.
1607 function screenPos( element
, refWin
) {
1608 var pos
= { x: 0, y: 0 };
1610 if ( !( element
.is( isQuirks
? 'body' : 'html' ) ) ) {
1611 var box
= element
.$.getBoundingClientRect();
1612 pos
.x
= box
.left
, pos
.y
= box
.top
;
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
;
1624 // calculated margin size.
1625 function margin( element
, side
) {
1626 return parseInt( element
.getComputedStyle( 'margin-' + side
) || 0, 10 ) || 0;
1629 var win
= parent
.getWindow();
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
,
1639 // Left-top margins.
1641 x: thisPos
.x
- margin( this, 'left' ) - parentPos
.x
|| 0,
1642 y: thisPos
.y
- margin( this, 'top' ) - parentPos
.y
|| 0
1645 // Bottom-right margins.
1647 x: thisPos
.x
+ ew
+ margin( this, 'right' ) - ( ( parentPos
.x
) + cw
) || 0,
1648 y: thisPos
.y
+ eh
+ margin( this, 'bottom' ) - ( ( parentPos
.y
) + ch
) || 0
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
);
1658 if ( hscroll
&& ( lt
.x
< 0 || br
.x
> 0 ) )
1659 scrollBy( lt
.x
< 0 ? lt
.x : br
.x
, 0 );
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}.
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>
1674 * span.setState( CKEDITOR.TRISTATE_ON, 'cke_button' );
1675 * // <span class="cke_button_on">...</span>
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.
1682 setState: function( state
, base
, useAria
) {
1683 base
= base
|| 'cke';
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' );
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' );
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' );
1713 * Returns the inner document of this `<iframe>` element.
1715 * @returns {CKEDITOR.dom.document} The inner document.
1717 getFrameDocument: function() {
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
;
1726 // Trick to solve this issue, forcing the iframe to get ready
1727 // by simply setting its "src" property.
1731 return $ && new CKEDITOR
.dom
.document( $.contentWindow
.document
);
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.
1738 * @param {CKEDITOR.dom.element} dest The destination element.
1739 * @param {Object} skipAttributes A dictionary of attributes to skip.
1741 copyAttributes: function( dest
, skipAttributes
) {
1742 var attributes
= this.$.attributes
;
1743 skipAttributes
= skipAttributes
|| {};
1745 for ( var n
= 0; n
< attributes
.length
; n
++ ) {
1746 var attribute
= attributes
[ n
];
1748 // Lowercase attribute name hard rule is broken for
1749 // some attribute on IE, e.g. CHECKED.
1750 var attrName
= attribute
.nodeName
.toLowerCase(),
1753 // We can set the type only once, so do it with the proper value, not copying it.
1754 if ( attrName
in skipAttributes
)
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
;
1766 dest
.setAttribute( attrName
, attrValue
);
1771 if ( this.$.style
.cssText
!== '' )
1772 dest
.$.style
.cssText
= this.$.style
.cssText
;
1776 * Changes the tag name of the current element.
1778 * @param {String} newTag The new tag for the element.
1780 renameNode: function( newTag
) {
1781 // If it's already correct exit here.
1782 if ( this.getName() == newTag
)
1785 var doc
= this.getDocument();
1787 // Create the new node.
1788 var newNode
= new CKEDITOR
.dom
.element( newTag
, doc
);
1790 // Copy all attributes.
1791 this.copyAttributes( newNode
);
1793 // Move children to the new node.
1794 this.moveChildren( newNode
);
1796 // Replace the node.
1797 this.getParent( true ) && this.$.parentNode
.replaceChild( newNode
.$, this.$ );
1798 newNode
.$[ 'data-cke-expando' ] = this.$[ 'data-cke-expando' ];
1800 // Bust getName's cache. (#8663)
1801 delete this.getName
;
1805 * Gets a DOM tree descendant under the current node.
1807 * var strong = p.getChild( 0 );
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.
1813 getChild: ( function() {
1814 function getChild( rawNode
, index
) {
1815 var childNodes
= rawNode
.childNodes
;
1817 if ( index
>= 0 && index
< childNodes
.length
)
1818 return childNodes
[ index
];
1821 return function( indices
) {
1822 var rawNode
= this.$;
1824 if ( !indices
.slice
)
1825 rawNode
= getChild( rawNode
, indices
);
1827 indices
= indices
.slice();
1828 while ( indices
.length
> 0 && rawNode
)
1829 rawNode
= getChild( rawNode
, indices
.shift() );
1832 return rawNode
? new CKEDITOR
.dom
.node( rawNode
) : null;
1837 * Gets number of element's children.
1841 getChildCount: function() {
1842 return this.$.childNodes
.length
;
1846 * Disables browser's context menu in this element.
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();
1855 function enablesContextMenu( node
) {
1856 return node
.type
== CKEDITOR
.NODE_ELEMENT
&& node
.hasClass( 'cke_enable_context_menu' );
1861 * Gets element's direction. Supports both CSS `direction` prop and `dir` attr.
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
||
1872 return this.getStyle( 'direction' ) || this.getAttribute( 'dir' );
1877 * Gets, sets and removes custom data to be stored as HTML5 data-* attributes.
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.
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.
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
);
1893 this.setAttribute( name
, value
);
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.
1903 * var element = new CKEDITOR.dom.element( 'div' );
1904 * element.appendTo( CKEDITOR.document.getBody() );
1905 * CKEDITOR.replace( element );
1906 * alert( element.getEditor().name ); // 'editor1'
1908 * @returns {CKEDITOR.editor} An editor instance or null if nothing has been found.
1910 getEditor: function() {
1911 var instances
= CKEDITOR
.instances
,
1914 for ( name
in instances
) {
1915 instance
= instances
[ name
];
1917 if ( instance
.element
.equals( this ) && instance
.elementMode
!= CKEDITOR
.ELEMENT_MODE_APPENDTO
)
1925 * Returns list of elements within this element that match specified `selector`.
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:
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> ]
1939 * @param {String} selector
1940 * @returns {CKEDITOR.dom.nodeList}
1942 find: function( selector
) {
1943 var removeTmpId
= createTmpId( this ),
1944 list
= new CKEDITOR
.dom
.nodeList(
1945 this.$.querySelectorAll( getContextualizedSelector( this, selector
) )
1954 * Returns first element within this element that matches specified `selector`.
1958 * * Not available in IE7.
1959 * * Unlike native `querySelectorAll` this method ensures selector contextualization. This is:
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>
1967 * @param {String} selector
1968 * @returns {CKEDITOR.dom.element}
1970 findOne: function( selector
) {
1971 var removeTmpId
= createTmpId( this ),
1972 found
= this.$.querySelector( getContextualizedSelector( this, selector
) );
1976 return found
? new CKEDITOR
.dom
.element( found
) : null;
1980 * Traverse the DOM of this element (inclusive), executing a callback for
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 );
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.
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.
2003 forEach: function( callback
, type
, skipRoot
) {
2004 if ( !skipRoot
&& ( !type
|| this.type
== type
) )
2005 var ret
= callback( this );
2007 // Do not filter children if callback returned false.
2008 if ( ret
=== false )
2011 var children
= this.getChildren(),
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
)
2026 function createTmpId( element
) {
2029 if ( !element
.$.id
) {
2030 element
.$.id
= 'cke_tmp_' + CKEDITOR
.tools
.getNextNumber();
2036 element
.removeAttribute( 'id' );
2040 function getContextualizedSelector( element
, selector
) {
2041 return '#' + element
.$.id
+ ' ' + selector
.split( /,\s*/ ).join( ', #' + element
.$.id
+ ' ' );
2045 width: [ 'border-left-width', 'border-right-width', 'padding-left', 'padding-right' ],
2046 height: [ 'border-top-width', 'border-bottom-width', 'padding-top', 'padding-bottom' ]
2049 // Generate list of specific style rules, applicable to margin/padding/border.
2050 function expandedRules( style
) {
2051 var sides
= [ 'top', 'left', 'right', 'bottom' ], components
;
2053 if ( style
== 'border' )
2054 components
= [ 'color', 'style', 'width' ];
2057 for ( var i
= 0 ; i
< sides
.length
; i
++ ) {
2060 for ( var j
= 0 ; j
< components
.length
; j
++ )
2061 styles
.push( [ style
, sides
[ i
], components
[ j
] ].join( '-' ) );
2063 styles
.push( [ style
, sides
[ i
] ].join( '-' ) );
2070 function marginAndPaddingSize( type
) {
2072 for ( var i
= 0, len
= sides
[ type
].length
; i
< len
; i
++ )
2073 adjustment
+= parseInt( this.getComputedStyle( sides
[ type
][ i
] ) || 0, 10 ) || 0;
2078 * Sets the element size considering the box model.
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.
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
);
2089 this.setStyle( type
, size
+ 'px' );
2094 * Gets the element size, possibly considering the box model.
2096 * @param {'width'/'height'} type The dimension to get.
2097 * @param {Boolean} isBorderBox Get the size based on the border box model.
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;
2103 size
-= marginAndPaddingSize
.call( this, type
);