2 Copyright (c) 2003-2011, CKSource - Frederico Knabben. All rights reserved.
3 For licensing, see LICENSE.html 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 * @augments CKEDITOR.dom.node
15 * @param {Object|String} element A native DOM element or the element name for
17 * @param {CKEDITOR.dom.document} [ownerDocument] The document that will contain
18 * the element in case of element creation.
20 * // Create a new <span> element.
21 * var element = new CKEDITOR.dom.element( 'span' );
23 * // Create an element based on a native DOM element.
24 * var element = new CKEDITOR.dom.element( document.getElementById( 'myId' ) );
26 CKEDITOR
.dom
.element = function( element
, ownerDocument
)
28 if ( typeof element
== 'string' )
29 element
= ( ownerDocument
? ownerDocument
.$ : document
).createElement( element
);
31 // Call the base constructor (we must not call CKEDITOR.dom.node).
32 CKEDITOR
.dom
.domObject
.call( this, element
);
35 // 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.
41 * @returns {CKEDITOR.dom.element} The transformed element.
43 * var element = new CKEDITOR.dom.element( 'span' );
44 * alert( element == <b>CKEDITOR.dom.element.get( element )</b> ); "true"
46 * var element = document.getElementById( 'myElement' );
47 * alert( <b>CKEDITOR.dom.element.get( element )</b>.getName() ); e.g. "p"
49 CKEDITOR
.dom
.element
.get = function( element
)
51 return element
&& ( element
.$ ? element
: new CKEDITOR
.dom
.element( element
) );
54 CKEDITOR
.dom
.element
.prototype = new CKEDITOR
.dom
.node();
57 * Creates an instance of the {@link CKEDITOR.dom.element} class based on the
58 * HTML representation of an element.
59 * @param {String} html The element HTML. It should define only one element in
60 * the "root" level. The "root" element can have child nodes, but not
62 * @returns {CKEDITOR.dom.element} The element instance.
64 * var element = <b>CKEDITOR.dom.element.createFromHtml( '<strong class="anyclass">My element</strong>' )</b>;
65 * alert( element.getName() ); // "strong"
67 CKEDITOR
.dom
.element
.createFromHtml = function( html
, ownerDocument
)
69 var temp
= new CKEDITOR
.dom
.element( 'div', ownerDocument
);
72 // When returning the node, remove it from its parent to detach it.
73 return temp
.getFirst().remove();
76 CKEDITOR
.dom
.element
.setMarker = function( database
, element
, name
, value
)
78 var id
= element
.getCustomData( 'list_marker_id' ) ||
79 ( element
.setCustomData( 'list_marker_id', CKEDITOR
.tools
.getNextNumber() ).getCustomData( 'list_marker_id' ) ),
80 markerNames
= element
.getCustomData( 'list_marker_names' ) ||
81 ( element
.setCustomData( 'list_marker_names', {} ).getCustomData( 'list_marker_names' ) );
82 database
[id
] = element
;
83 markerNames
[name
] = 1;
85 return element
.setCustomData( name
, value
);
88 CKEDITOR
.dom
.element
.clearAllMarkers = function( database
)
90 for ( var i
in database
)
91 CKEDITOR
.dom
.element
.clearMarkers( database
, database
[i
], 1 );
94 CKEDITOR
.dom
.element
.clearMarkers = function( database
, element
, removeFromDatabase
)
96 var names
= element
.getCustomData( 'list_marker_names' ),
97 id
= element
.getCustomData( 'list_marker_id' );
98 for ( var i
in names
)
99 element
.removeCustomData( i
);
100 element
.removeCustomData( 'list_marker_names' );
101 if ( removeFromDatabase
)
103 element
.removeCustomData( 'list_marker_id' );
108 CKEDITOR
.tools
.extend( CKEDITOR
.dom
.element
.prototype,
109 /** @lends CKEDITOR.dom.element.prototype */
112 * The node type. This is a constant value set to
113 * {@link CKEDITOR.NODE_ELEMENT}.
117 type
: CKEDITOR
.NODE_ELEMENT
,
120 * Adds a CSS class to the element. It appends the class to the
121 * already existing names.
122 * @param {String} className The name of the class to be added.
124 * var element = new CKEDITOR.dom.element( 'div' );
125 * element.addClass( 'classA' ); // <div class="classA">
126 * element.addClass( 'classB' ); // <div class="classA classB">
127 * element.addClass( 'classA' ); // <div class="classA classB">
129 addClass : function( className
)
131 var c
= this.$.className
;
134 var regex
= new RegExp( '(?:^|\\s)' + className
+ '(?:\\s|$)', '' );
135 if ( !regex
.test( c
) )
136 c
+= ' ' + className
;
138 this.$.className
= c
|| className
;
142 * Removes a CSS class name from the elements classes. Other classes
144 * @param {String} className The name of the class to remove.
146 * var element = new CKEDITOR.dom.element( 'div' );
147 * element.addClass( 'classA' ); // <div class="classA">
148 * element.addClass( 'classB' ); // <div class="classA classB">
149 * element.removeClass( 'classA' ); // <div class="classB">
150 * element.removeClass( 'classB' ); // <div>
152 removeClass : function( className
)
154 var c
= this.getAttribute( 'class' );
157 var regex
= new RegExp( '(?:^|\\s+)' + className
+ '(?=\\s|$)', 'i' );
158 if ( regex
.test( c
) )
160 c
= c
.replace( regex
, '' ).replace( /^\s+/, '' );
163 this.setAttribute( 'class', c
);
165 this.removeAttribute( 'class' );
170 hasClass : function( className
)
172 var regex
= new RegExp( '(?:^|\\s+)' + className
+ '(?=\\s|$)', '' );
173 return regex
.test( this.getAttribute('class') );
177 * Append a node as a child of this element.
178 * @param {CKEDITOR.dom.node|String} node The node or element name to be
180 * @param {Boolean} [toStart] Indicates that the element is to be
181 * appended at the start.
182 * @returns {CKEDITOR.dom.node} The appended node.
184 * var p = new CKEDITOR.dom.element( 'p' );
186 * var strong = new CKEDITOR.dom.element( 'strong' );
187 * <b>p.append( strong );</b>
189 * var em = <b>p.append( 'em' );</b>
191 * // result: "<p><strong></strong><em></em></p>"
193 append : function( node
, toStart
)
195 if ( typeof node
== 'string' )
196 node
= this.getDocument().createElement( node
);
199 this.$.insertBefore( node
.$, this.$.firstChild
);
201 this.$.appendChild( node
.$ );
206 appendHtml : function( html
)
208 if ( !this.$.childNodes
.length
)
209 this.setHtml( html
);
212 var temp
= new CKEDITOR
.dom
.element( 'div', this.getDocument() );
213 temp
.setHtml( html
);
214 temp
.moveChildren( this );
219 * Append text to this element.
220 * @param {String} text The text to be appended.
221 * @returns {CKEDITOR.dom.node} The appended node.
223 * var p = new CKEDITOR.dom.element( 'p' );
224 * p.appendText( 'This is' );
225 * p.appendText( ' some text' );
227 * // result: "<p>This is some text</p>"
229 appendText : function( text
)
231 if ( this.$.text
!= undefined )
234 this.append( new CKEDITOR
.dom
.text( text
) );
237 appendBogus : function()
239 var lastChild
= this.getLast() ;
241 // Ignore empty/spaces text.
242 while ( lastChild
&& lastChild
.type
== CKEDITOR
.NODE_TEXT
&& !CKEDITOR
.tools
.rtrim( lastChild
.getText() ) )
243 lastChild
= lastChild
.getPrevious();
244 if ( !lastChild
|| !lastChild
.is
|| !lastChild
.is( 'br' ) )
246 var bogus
= CKEDITOR
.env
.opera
?
247 this.getDocument().createText('') :
248 this.getDocument().createElement( 'br' );
250 CKEDITOR
.env
.gecko
&& bogus
.setAttribute( 'type', '_moz' );
252 this.append( bogus
);
257 * Breaks one of the ancestor element in the element position, moving
258 * this element between the broken parts.
259 * @param {CKEDITOR.dom.element} parent The anscestor element to get broken.
261 * // Before breaking:
262 * // <b>This <i>is some<span /> sample</i> test text</b>
263 * // If "element" is <span /> and "parent" is <i>:
264 * // <b>This <i>is some</i><span /><i> sample</i> test text</b>
265 * element.breakParent( parent );
267 * // Before breaking:
268 * // <b>This <i>is some<span /> sample</i> test text</b>
269 * // If "element" is <span /> and "parent" is <b>:
270 * // <b>This <i>is some</i></b><span /><b><i> sample</i> test text</b>
271 * element.breakParent( parent );
273 breakParent : function( parent
)
275 var range
= new CKEDITOR
.dom
.range( this.getDocument() );
277 // We'll be extracting part of this element, so let's use our
278 // range to get the correct piece.
279 range
.setStartAfter( this );
280 range
.setEndAfter( parent
);
283 var docFrag
= range
.extractContents();
285 // Move the element outside the broken element.
286 range
.insertNode( this.remove() );
288 // Re-insert the extracted piece after the element.
289 docFrag
.insertAfterNode( this );
293 CKEDITOR
.env
.ie
|| CKEDITOR
.env
.webkit
?
298 return node
.type
!= CKEDITOR
.NODE_ELEMENT
?
299 $.contains( node
.getParent().$ ) :
300 $ != node
.$ && $.contains( node
.$ );
305 return !!( this.$.compareDocumentPosition( node
.$ ) & 16 );
309 * Moves the selection focus to this element.
311 * @param {Boolean} defer Whether to asynchronously defer the
312 * execution by 100 ms.
314 * var element = CKEDITOR.document.getById( 'myTextarea' );
315 * <b>element.focus()</b>;
321 // IE throws error if the element is not visible.
330 return function( defer
)
333 CKEDITOR
.tools
.setTimeout( exec
, 100, this );
340 * Gets the inner HTML of this element.
341 * @returns {String} The inner HTML of this element.
343 * var element = CKEDITOR.dom.element.createFromHtml( '<div><b>Example</b></div>' );
344 * alert( <b>p.getHtml()</b> ); // "<b>Example</b>"
348 var retval
= this.$.innerHTML
;
349 // Strip <?xml:namespace> tags in IE. (#3341).
350 return CKEDITOR
.env
.ie
? retval
.replace( /<\?[^>]*>/g, '' ) : retval
;
353 getOuterHtml : function()
355 if ( this.$.outerHTML
)
357 // IE includes the <?xml:namespace> tag in the outerHTML of
358 // namespaced element. So, we must strip it here. (#3341)
359 return this.$.outerHTML
.replace( /<\?[^>]*>/, '' );
362 var tmpDiv
= this.$.ownerDocument
.createElement( 'div' );
363 tmpDiv
.appendChild( this.$.cloneNode( true ) );
364 return tmpDiv
.innerHTML
;
368 * Sets the inner HTML of this element.
369 * @param {String} html The HTML to be set for this element.
370 * @returns {String} The inserted HTML.
372 * var p = new CKEDITOR.dom.element( 'p' );
373 * <b>p.setHtml( '<b>Inner</b> HTML' );</b>
375 * // result: "<p><b>Inner</b> HTML</p>"
377 setHtml : function( html
)
379 return ( this.$.innerHTML
= html
);
383 * Sets the element contents as plain text.
384 * @param {String} text The text to be set.
385 * @returns {String} The inserted text.
387 * var element = new CKEDITOR.dom.element( 'div' );
388 * element.setText( 'A > B & C < D' );
389 * alert( element.innerHTML ); // "A &gt; B &amp; C &lt; D"
391 setText : function( text
)
393 CKEDITOR
.dom
.element
.prototype.setText
= ( this.$.innerText
!= undefined ) ?
396 return this.$.innerText
= text
;
400 return this.$.textContent
= text
;
403 return this.setText( text
);
407 * Gets the value of an element attribute.
409 * @param {String} name The attribute name.
410 * @returns {String} The attribute value or null if not defined.
412 * var element = CKEDITOR.dom.element.createFromHtml( '<input type="text" />' );
413 * alert( <b>element.getAttribute( 'type' )</b> ); // "text"
415 getAttribute
: (function()
417 var standard = function( name
)
419 return this.$.getAttribute( name
, 2 );
422 if ( CKEDITOR
.env
.ie
&& ( CKEDITOR
.env
.ie7Compat
|| CKEDITOR
.env
.ie6Compat
) )
424 return function( name
)
440 var tabIndex
= standard
.call( this, name
);
442 // IE returns tabIndex=0 by default for all
443 // elements. For those elements,
444 // getAtrribute( 'tabindex', 2 ) returns 32768
445 // instead. So, we must make this check to give a
446 // uniform result among all browsers.
447 if ( tabIndex
!== 0 && this.$.tabIndex
=== 0 )
455 var attr
= this.$.attributes
.getNamedItem( name
),
456 attrValue
= attr
.specified
? attr
.nodeValue
// For value given by parser.
457 : this.$.checked
; // For value created via DOM interface.
459 return attrValue
? 'checked' : null;
464 return this.$[ name
];
467 // IE does not return inline styles via getAttribute(). See #2947.
468 return this.$.style
.cssText
;
471 return standard
.call( this, name
);
478 getChildren : function()
480 return new CKEDITOR
.dom
.nodeList( this.$.childNodes
);
484 * Gets the current computed value of one of the element CSS style
487 * @param {String} propertyName The style property name.
488 * @returns {String} The property value.
490 * var element = new CKEDITOR.dom.element( 'span' );
491 * alert( <b>element.getComputedStyle( 'display' )</b> ); // "inline"
495 function( propertyName
)
497 return this.$.currentStyle
[ CKEDITOR
.tools
.cssStyleToDomStyle( propertyName
) ];
500 function( propertyName
)
502 return this.getWindow().$.getComputedStyle( this.$, '' ).getPropertyValue( propertyName
);
506 * Gets the DTD entries for this element.
507 * @returns {Object} An object containing the list of elements accepted
512 var dtd
= CKEDITOR
.dtd
[ this.getName() ];
514 this.getDtd = function()
522 getElementsByTag
: CKEDITOR
.dom
.document
.prototype.getElementsByTag
,
525 * Gets the computed tabindex for this element.
527 * @returns {Number} The tabindex value.
529 * var element = CKEDITOR.document.getById( 'myDiv' );
530 * alert( <b>element.getTabIndex()</b> ); // e.g. "-1"
536 var tabIndex
= this.$.tabIndex
;
538 // IE returns tabIndex=0 by default for all elements. In
539 // those cases we must check that the element really has
540 // the tabindex attribute set to zero, or it is one of
541 // those element that should have zero by default.
542 if ( tabIndex
=== 0 && !CKEDITOR
.dtd
.$tabIndex
[ this.getName() ] && parseInt( this.getAttribute( 'tabindex' ), 10 ) !== 0 )
547 : CKEDITOR
.env
.webkit
?
550 var tabIndex
= this.$.tabIndex
;
552 // Safari returns "undefined" for elements that should not
553 // have tabindex (like a div). So, we must try to get it
554 // from the attribute.
555 // https://bugs.webkit.org/show_bug.cgi?id=20596
556 if ( tabIndex
== undefined )
558 tabIndex
= parseInt( this.getAttribute( 'tabindex' ), 10 );
560 // If the element don't have the tabindex attribute,
561 // then we should return -1.
562 if ( isNaN( tabIndex
) )
571 return this.$.tabIndex
;
575 * Gets the text value of this element.
577 * Only in IE (which uses innerText), <br> will cause linebreaks,
578 * and sucessive whitespaces (including line breaks) will be reduced to
579 * a single space. This behavior is ok for us, for now. It may change
581 * @returns {String} The text value.
583 * var element = CKEDITOR.dom.element.createFromHtml( '<div>Same <i>text</i>.</div>' );
584 * alert( <b>element.getText()</b> ); // "Sample text."
588 return this.$.textContent
|| this.$.innerText
|| '';
592 * Gets the window object that contains this element.
593 * @returns {CKEDITOR.dom.window} The window object.
596 getWindow : function()
598 return this.getDocument().getWindow();
602 * Gets the value of the "id" attribute of this element.
603 * @returns {String} The element id, or null if not available.
605 * var element = CKEDITOR.dom.element.createFromHtml( '<p id="myId"></p>' );
606 * alert( <b>element.getId()</b> ); // "myId"
610 return this.$.id
|| null;
614 * Gets the value of the "name" attribute of this element.
615 * @returns {String} The element name, or null if not available.
617 * var element = CKEDITOR.dom.element.createFromHtml( '<input name="myName"></input>' );
618 * alert( <b>element.getNameAtt()</b> ); // "myName"
620 getNameAtt : function()
622 return this.$.name
|| null;
626 * Gets the element name (tag name). The returned name is guaranteed to
627 * be always full lowercased.
628 * @returns {String} The element name.
630 * var element = new CKEDITOR.dom.element( 'span' );
631 * alert( <b>element.getName()</b> ); // "span"
635 // Cache the lowercased name inside a closure.
636 var nodeName
= this.$.nodeName
.toLowerCase();
638 if ( CKEDITOR
.env
.ie
&& ! ( document
.documentMode
> 8 ) )
640 var scopeName
= this.$.scopeName
;
641 if ( scopeName
!= 'HTML' )
642 nodeName
= scopeName
.toLowerCase() + ':' + nodeName
;
646 this.getName = function()
653 * Gets the value set to this element. This value is usually available
654 * for form field elements.
655 * @returns {String} The element value.
657 getValue : function()
663 * Gets the first child node of this element.
664 * @param {Function} evaluator Filtering the result node.
665 * @returns {CKEDITOR.dom.node} The first child node or null if not
668 * var element = CKEDITOR.dom.element.createFromHtml( '<div><b>Example</b></div>' );
669 * var first = <b>element.getFirst()</b>;
670 * alert( first.getName() ); // "b"
672 getFirst : function( evaluator
)
674 var first
= this.$.firstChild
,
675 retval
= first
&& new CKEDITOR
.dom
.node( first
);
676 if ( retval
&& evaluator
&& !evaluator( retval
) )
677 retval
= retval
.getNext( evaluator
);
683 * @param {Function} evaluator Filtering the result node.
685 getLast : function( evaluator
)
687 var last
= this.$.lastChild
,
688 retval
= last
&& new CKEDITOR
.dom
.node( last
);
689 if ( retval
&& evaluator
&& !evaluator( retval
) )
690 retval
= retval
.getPrevious( evaluator
);
695 getStyle : function( name
)
697 return this.$.style
[ CKEDITOR
.tools
.cssStyleToDomStyle( name
) ];
701 * Checks if the element name matches one or more names.
702 * @param {String} name[,name[,...]] One or more names to be checked.
703 * @returns {Boolean} true if the element name matches any of the names.
705 * var element = new CKEDITOR.element( 'span' );
706 * alert( <b>element.is( 'span' )</b> ); "true"
707 * alert( <b>element.is( 'p', 'span' )</b> ); "true"
708 * alert( <b>element.is( 'p' )</b> ); "false"
709 * alert( <b>element.is( 'p', 'div' )</b> ); "false"
713 var name
= this.getName();
714 for ( var i
= 0 ; i
< arguments
.length
; i
++ )
716 if ( arguments
[ i
] == name
)
722 isEditable : function()
724 if ( this.isReadOnly() )
727 // Get the element name.
728 var name
= this.getName();
730 // Get the element DTD (defaults to span for unknown elements).
731 var dtd
= !CKEDITOR
.dtd
.$nonEditable
[ name
]
732 && ( CKEDITOR
.dtd
[ name
] || CKEDITOR
.dtd
.span
);
734 // In the DTD # == text node.
735 return ( dtd
&& dtd
['#'] );
738 isIdentical : function( otherElement
)
740 if ( this.getName() != otherElement
.getName() )
743 var thisAttribs
= this.$.attributes
,
744 otherAttribs
= otherElement
.$.attributes
;
746 var thisLength
= thisAttribs
.length
,
747 otherLength
= otherAttribs
.length
;
749 for ( var i
= 0 ; i
< thisLength
; i
++ )
751 var attribute
= thisAttribs
[ i
];
753 if ( attribute
.nodeName
== '_moz_dirty' )
756 if ( ( !CKEDITOR
.env
.ie
|| ( attribute
.specified
&& attribute
.nodeName
!= 'data-cke-expando' ) ) && attribute
.nodeValue
!= otherElement
.getAttribute( attribute
.nodeName
) )
760 // For IE, we have to for both elements, because it's difficult to
761 // know how the atttibutes collection is organized in its DOM.
762 if ( CKEDITOR
.env
.ie
)
764 for ( i
= 0 ; i
< otherLength
; i
++ )
766 attribute
= otherAttribs
[ i
];
767 if ( attribute
.specified
&& attribute
.nodeName
!= 'data-cke-expando'
768 && attribute
.nodeValue
!= this.getAttribute( attribute
.nodeName
) )
777 * Checks if this element is visible. May not work if the element is
778 * child of an element with visibility set to "hidden", but works well
779 * on the great majority of cases.
780 * @return {Boolean} True if the element is visible.
782 isVisible : function()
784 var isVisible
= !!this.$.offsetHeight
&& this.getComputedStyle( 'visibility' ) != 'hidden',
788 // Webkit and Opera report non-zero offsetHeight despite that
789 // element is inside an invisible iframe. (#4542)
790 if ( isVisible
&& ( CKEDITOR
.env
.webkit
|| CKEDITOR
.env
.opera
) )
792 elementWindow
= this.getWindow();
794 if ( !elementWindow
.equals( CKEDITOR
.document
.getWindow() )
795 && ( elementWindowFrame
= elementWindow
.$.frameElement
) )
797 isVisible
= new CKEDITOR
.dom
.element( elementWindowFrame
).isVisible();
805 * Whether it's an empty inline elements which has no visual impact when removed.
807 isEmptyInlineRemoveable : function()
809 if ( !CKEDITOR
.dtd
.$removeEmpty
[ this.getName() ] )
812 var children
= this.getChildren();
813 for ( var i
= 0, count
= children
.count(); i
< count
; i
++ )
815 var child
= children
.getItem( i
);
817 if ( child
.type
== CKEDITOR
.NODE_ELEMENT
&& child
.data( 'cke-bookmark' ) )
820 if ( child
.type
== CKEDITOR
.NODE_ELEMENT
&& !child
.isEmptyInlineRemoveable()
821 || child
.type
== CKEDITOR
.NODE_TEXT
&& CKEDITOR
.tools
.trim( child
.getText() ) )
830 * Checks if the element has any defined attributes.
832 * @returns {Boolean} True if the element has attributes.
834 * var element = CKEDITOR.dom.element.createFromHtml( '<div title="Test">Example</div>' );
835 * alert( <b>element.hasAttributes()</b> ); // "true"
837 * var element = CKEDITOR.dom.element.createFromHtml( '<div>Example</div>' );
838 * alert( <b>element.hasAttributes()</b> ); // "false"
841 CKEDITOR
.env
.ie
&& ( CKEDITOR
.env
.ie7Compat
|| CKEDITOR
.env
.ie6Compat
) ?
844 var attributes
= this.$.attributes
;
846 for ( var i
= 0 ; i
< attributes
.length
; i
++ )
848 var attribute
= attributes
[i
];
850 switch ( attribute
.nodeName
)
853 // IE has a strange bug. If calling removeAttribute('className'),
854 // the attributes collection will still contain the "class"
855 // attribute, which will be marked as "specified", even if the
856 // outerHTML of the element is not displaying the class attribute.
857 // Note : I was not able to reproduce it outside the editor,
858 // but I've faced it while working on the TC of #1391.
859 if ( this.getAttribute( 'class' ) )
862 // Attributes to be ignored.
863 case 'data-cke-expando' :
869 if ( attribute
.specified
)
879 var attrs
= this.$.attributes
,
880 attrsNum
= attrs
.length
;
882 // The _moz_dirty attribute might get into the element after pasting (#5455)
883 var execludeAttrs
= { 'data-cke-expando' : 1, _moz_dirty
: 1 };
885 return attrsNum
> 0 &&
887 !execludeAttrs
[ attrs
[0].nodeName
] ||
888 ( attrsNum
== 2 && !execludeAttrs
[ attrs
[1].nodeName
] ) );
892 * Checks if the specified attribute is defined for this element.
893 * @returns {Boolean} True if the specified attribute is defined.
894 * @param {String} name The attribute name.
897 hasAttribute
: (function()
899 function standard( name
)
901 var $attr
= this.$.attributes
.getNamedItem( name
);
902 return !!( $attr
&& $attr
.specified
);
905 return ( CKEDITOR
.env
.ie
&& CKEDITOR
.env
.version
< 8 ) ?
908 // On IE < 8 the name attribute cannot be retrieved
909 // right after the element creation and setting the
910 // name with setAttribute.
911 if ( name
== 'name' )
912 return !!this.$.name
;
914 return standard
.call( this, name
);
921 * Hides this element (display:none).
923 * var element = CKEDITOR.dom.element.getById( 'myElement' );
924 * <b>element.hide()</b>;
928 this.setStyle( 'display', 'none' );
931 moveChildren : function( target
, toStart
)
943 while ( ( child
= $.lastChild
) )
944 target
.insertBefore( $.removeChild( child
), target
.firstChild
);
948 while ( ( child
= $.firstChild
) )
949 target
.appendChild( $.removeChild( child
) );
954 * Merges sibling elements that are identical to this one.<br>
956 * Identical child elements are also merged. For example:<br>
957 * <b><i></i></b><b><i></i></b> => <b><i></i></b>
959 * @param {Boolean} [inlineOnly] Allow only inline elements to be merged. Defaults to "true".
961 mergeSiblings
: ( function()
963 function mergeElements( element
, sibling
, isNext
)
965 if ( sibling
&& sibling
.type
== CKEDITOR
.NODE_ELEMENT
)
967 // Jumping over bookmark nodes and empty inline elements, e.g. <b><i></i></b>,
968 // queuing them to be moved later. (#5567)
969 var pendingNodes
= [];
971 while ( sibling
.data( 'cke-bookmark' )
972 || sibling
.isEmptyInlineRemoveable() )
974 pendingNodes
.push( sibling
);
975 sibling
= isNext
? sibling
.getNext() : sibling
.getPrevious();
976 if ( !sibling
|| sibling
.type
!= CKEDITOR
.NODE_ELEMENT
)
980 if ( element
.isIdentical( sibling
) )
982 // Save the last child to be checked too, to merge things like
983 // <b><i></i></b><b><i></i></b> => <b><i></i></b>
984 var innerSibling
= isNext
? element
.getLast() : element
.getFirst();
986 // Move pending nodes first into the target element.
987 while( pendingNodes
.length
)
988 pendingNodes
.shift().move( element
, !isNext
);
990 sibling
.moveChildren( element
, !isNext
);
993 // Now check the last inner child (see two comments above).
994 if ( innerSibling
&& innerSibling
.type
== CKEDITOR
.NODE_ELEMENT
)
995 innerSibling
.mergeSiblings();
1000 return function( inlineOnly
)
1002 if ( ! ( inlineOnly
=== false
1003 || CKEDITOR
.dtd
.$removeEmpty
[ this.getName() ]
1004 || this.is( 'a' ) ) ) // Merge empty links and anchors also. (#5567)
1009 mergeElements( this, this.getNext(), true );
1010 mergeElements( this, this.getPrevious() );
1015 * Shows this element (display it).
1017 * var element = CKEDITOR.dom.element.getById( 'myElement' );
1018 * <b>element.show()</b>;
1030 * Sets the value of an element attribute.
1031 * @param {String} name The name of the attribute.
1032 * @param {String} value The value to be set to the attribute.
1034 * @returns {CKEDITOR.dom.element} This element instance.
1036 * var element = CKEDITOR.dom.element.getById( 'myElement' );
1037 * <b>element.setAttribute( 'class', 'myClass' )</b>;
1038 * <b>element.setAttribute( 'title', 'This is an example' )</b>;
1040 setAttribute
: (function()
1042 var standard = function( name
, value
)
1044 this.$.setAttribute( name
, value
);
1048 if ( CKEDITOR
.env
.ie
&& ( CKEDITOR
.env
.ie7Compat
|| CKEDITOR
.env
.ie6Compat
) )
1050 return function( name
, value
)
1052 if ( name
== 'class' )
1053 this.$.className
= value
;
1054 else if ( name
== 'style' )
1055 this.$.style
.cssText
= value
;
1056 else if ( name
== 'tabindex' ) // Case sensitive.
1057 this.$.tabIndex
= value
;
1058 else if ( name
== 'checked' )
1059 this.$.checked
= value
;
1061 standard
.apply( this, arguments
);
1065 else if ( CKEDITOR
.env
.ie8Compat
&& CKEDITOR
.env
.secure
)
1067 return function( name
, value
)
1069 // IE8 throws error when setting src attribute to non-ssl value. (#7847)
1070 if ( name
== 'src' && value
.match( /^http:\/\// ) )
1071 try { standard
.apply( this, arguments
); } catch( e
){}
1073 standard
.apply( this, arguments
);
1082 * Sets the value of several element attributes.
1083 * @param {Object} attributesPairs An object containing the names and
1084 * values of the attributes.
1085 * @returns {CKEDITOR.dom.element} This element instance.
1087 * var element = CKEDITOR.dom.element.getById( 'myElement' );
1088 * <b>element.setAttributes({
1089 * 'class' : 'myClass',
1090 * 'title' : 'This is an example' })</b>;
1092 setAttributes : function( attributesPairs
)
1094 for ( var name
in attributesPairs
)
1095 this.setAttribute( name
, attributesPairs
[ name
] );
1100 * Sets the element value. This function is usually used with form
1102 * @param {String} value The element value.
1103 * @returns {CKEDITOR.dom.element} This element instance.
1105 setValue : function( value
)
1107 this.$.value
= value
;
1112 * Removes an attribute from the element.
1113 * @param {String} name The attribute name.
1116 * var element = CKEDITOR.dom.element.createFromHtml( '<div class="classA"></div>' );
1117 * element.removeAttribute( 'class' );
1119 removeAttribute
: (function()
1121 var standard = function( name
)
1123 this.$.removeAttribute( name
);
1126 if ( CKEDITOR
.env
.ie
&& ( CKEDITOR
.env
.ie7Compat
|| CKEDITOR
.env
.ie6Compat
) )
1128 return function( name
)
1130 if ( name
== 'class' )
1132 else if ( name
== 'tabindex' )
1134 standard
.call( this, name
);
1141 removeAttributes : function ( attributes
)
1143 if ( CKEDITOR
.tools
.isArray( attributes
) )
1145 for ( var i
= 0 ; i
< attributes
.length
; i
++ )
1146 this.removeAttribute( attributes
[ i
] );
1150 for ( var attr
in attributes
)
1151 attributes
.hasOwnProperty( attr
) && this.removeAttribute( attr
);
1156 * Removes a style from the element.
1157 * @param {String} name The style name.
1160 * var element = CKEDITOR.dom.element.createFromHtml( '<div style="display:none"></div>' );
1161 * element.removeStyle( 'display' );
1163 removeStyle : function( name
)
1165 this.setStyle( name
, '' );
1166 if ( this.$.style
.removeAttribute
)
1167 this.$.style
.removeAttribute( CKEDITOR
.tools
.cssStyleToDomStyle( name
) );
1169 if ( !this.$.style
.cssText
)
1170 this.removeAttribute( 'style' );
1174 * Sets the value of an element style.
1175 * @param {String} name The name of the style. The CSS naming notation
1176 * must be used (e.g. "background-color").
1177 * @param {String} value The value to be set to the style.
1178 * @returns {CKEDITOR.dom.element} This element instance.
1180 * var element = CKEDITOR.dom.element.getById( 'myElement' );
1181 * <b>element.setStyle( 'background-color', '#ff0000' )</b>;
1182 * <b>element.setStyle( 'margin-top', '10px' )</b>;
1183 * <b>element.setStyle( 'float', 'right' )</b>;
1185 setStyle : function( name
, value
)
1187 this.$.style
[ CKEDITOR
.tools
.cssStyleToDomStyle( name
) ] = value
;
1192 * Sets the value of several element styles.
1193 * @param {Object} stylesPairs An object containing the names and
1194 * values of the styles.
1195 * @returns {CKEDITOR.dom.element} This element instance.
1197 * var element = CKEDITOR.dom.element.getById( 'myElement' );
1198 * <b>element.setStyles({
1199 * 'position' : 'absolute',
1200 * 'float' : 'right' })</b>;
1202 setStyles : function( stylesPairs
)
1204 for ( var name
in stylesPairs
)
1205 this.setStyle( name
, stylesPairs
[ name
] );
1210 * Sets the opacity of an element.
1211 * @param {Number} opacity A number within the range [0.0, 1.0].
1213 * var element = CKEDITOR.dom.element.getById( 'myElement' );
1214 * <b>element.setOpacity( 0.75 )</b>;
1216 setOpacity : function( opacity
)
1218 if ( CKEDITOR
.env
.ie
)
1220 opacity
= Math
.round( opacity
* 100 );
1221 this.setStyle( 'filter', opacity
>= 100 ? '' : 'progid:DXImageTransform.Microsoft.Alpha(opacity=' + opacity
+ ')' );
1224 this.setStyle( 'opacity', opacity
);
1228 * Makes the element and its children unselectable.
1231 * var element = CKEDITOR.dom.element.getById( 'myElement' );
1232 * element.unselectable();
1235 CKEDITOR
.env
.gecko
?
1238 this.$.style
.MozUserSelect
= 'none';
1239 this.on( 'dragstart', function( evt
) { evt
.data
.preventDefault(); } );
1241 : CKEDITOR
.env
.webkit
?
1244 this.$.style
.KhtmlUserSelect
= 'none';
1245 this.on( 'dragstart', function( evt
) { evt
.data
.preventDefault(); } );
1250 if ( CKEDITOR
.env
.ie
|| CKEDITOR
.env
.opera
)
1252 var element
= this.$,
1256 element
.unselectable
= 'on';
1258 while ( ( e
= element
.all
[ i
++ ] ) )
1260 switch ( e
.tagName
.toLowerCase() )
1266 /* Ignore the above tags */
1269 e
.unselectable
= 'on';
1275 getPositionedAncestor : function()
1278 while ( current
.getName() != 'html' )
1280 if ( current
.getComputedStyle( 'position' ) != 'static' )
1283 current
= current
.getParent();
1288 getDocumentPosition : function( refDocument
)
1291 body
= this.getDocument().getBody(),
1292 quirks
= this.getDocument().$.compatMode
== 'BackCompat';
1294 var doc
= this.getDocument();
1296 if ( document
.documentElement
[ "getBoundingClientRect" ] )
1298 var box
= this.$.getBoundingClientRect(),
1300 $docElem
= $doc
.documentElement
;
1302 var clientTop
= $docElem
.clientTop
|| body
.$.clientTop
|| 0,
1303 clientLeft
= $docElem
.clientLeft
|| body
.$.clientLeft
|| 0,
1304 needAdjustScrollAndBorders
= true;
1307 * #3804: getBoundingClientRect() works differently on IE and non-IE
1308 * browsers, regarding scroll positions.
1310 * On IE, the top position of the <html> element is always 0, no matter
1311 * how much you scrolled down.
1313 * On other browsers, the top position of the <html> element is negative
1316 if ( CKEDITOR
.env
.ie
)
1318 var inDocElem
= doc
.getDocumentElement().contains( this ),
1319 inBody
= doc
.getBody().contains( this );
1321 needAdjustScrollAndBorders
= ( quirks
&& inBody
) || ( !quirks
&& inDocElem
);
1324 if ( needAdjustScrollAndBorders
)
1326 x
= box
.left
+ ( !quirks
&& $docElem
.scrollLeft
|| body
.$.scrollLeft
);
1328 y
= box
.top
+ ( !quirks
&& $docElem
.scrollTop
|| body
.$.scrollTop
);
1334 var current
= this, previous
= null, offsetParent
;
1335 while ( current
&& !( current
.getName() == 'body' || current
.getName() == 'html' ) )
1337 x
+= current
.$.offsetLeft
- current
.$.scrollLeft
;
1338 y
+= current
.$.offsetTop
- current
.$.scrollTop
;
1340 // Opera includes clientTop|Left into offsetTop|Left.
1341 if ( !current
.equals( this ) )
1343 x
+= ( current
.$.clientLeft
|| 0 );
1344 y
+= ( current
.$.clientTop
|| 0 );
1347 var scrollElement
= previous
;
1348 while ( scrollElement
&& !scrollElement
.equals( current
) )
1350 x
-= scrollElement
.$.scrollLeft
;
1351 y
-= scrollElement
.$.scrollTop
;
1352 scrollElement
= scrollElement
.getParent();
1356 current
= ( offsetParent
= current
.$.offsetParent
) ?
1357 new CKEDITOR
.dom
.element( offsetParent
) : null;
1363 var currentWindow
= this.getWindow(),
1364 refWindow
= refDocument
.getWindow();
1366 if ( !currentWindow
.equals( refWindow
) && currentWindow
.$.frameElement
)
1368 var iframePosition
= ( new CKEDITOR
.dom
.element( currentWindow
.$.frameElement
) ).getDocumentPosition( refDocument
);
1370 x
+= iframePosition
.x
;
1371 y
+= iframePosition
.y
;
1375 if ( !document
.documentElement
[ "getBoundingClientRect" ] )
1377 // In Firefox, we'll endup one pixel before the element positions,
1378 // so we must add it here.
1379 if ( CKEDITOR
.env
.gecko
&& !quirks
)
1381 x
+= this.$.clientLeft
? 1 : 0;
1382 y
+= this.$.clientTop
? 1 : 0;
1386 return { x
: x
, y
: y
};
1389 scrollIntoView : function( alignTop
)
1391 // Get the element window.
1392 var win
= this.getWindow(),
1393 winHeight
= win
.getViewPaneSize().height
;
1395 // Starts from the offset that will be scrolled with the negative value of
1396 // the visible window height.
1397 var offset
= winHeight
* -1;
1399 // Append the view pane's height if align to top.
1400 // Append element height if we are aligning to the bottom.
1402 offset
+= winHeight
;
1405 offset
+= this.$.offsetHeight
|| 0;
1407 // Consider the margin in the scroll, which is ok for our current needs, but
1408 // needs investigation if we will be using this function in other places.
1409 offset
+= parseInt( this.getComputedStyle( 'marginBottom' ) || 0, 10 ) || 0;
1412 // Append the offsets for the entire element hierarchy.
1413 var elementPosition
= this.getDocumentPosition();
1414 offset
+= elementPosition
.y
;
1416 // offset value might be out of range(nagative), fix it(#3692).
1417 offset
= offset
< 0 ? 0 : offset
;
1419 // Scroll the window to the desired position, if not already visible(#3795).
1420 var currentScroll
= win
.getScrollPosition().y
;
1421 if ( offset
> currentScroll
|| offset
< currentScroll
- winHeight
)
1422 win
.$.scrollTo( 0, offset
);
1425 setState : function( state
)
1429 case CKEDITOR
.TRISTATE_ON
:
1430 this.addClass( 'cke_on' );
1431 this.removeClass( 'cke_off' );
1432 this.removeClass( 'cke_disabled' );
1434 case CKEDITOR
.TRISTATE_DISABLED
:
1435 this.addClass( 'cke_disabled' );
1436 this.removeClass( 'cke_off' );
1437 this.removeClass( 'cke_on' );
1440 this.addClass( 'cke_off' );
1441 this.removeClass( 'cke_on' );
1442 this.removeClass( 'cke_disabled' );
1448 * Returns the inner document of this IFRAME element.
1449 * @returns {CKEDITOR.dom.document} The inner document.
1451 getFrameDocument : function()
1457 // In IE, with custom document.domain, it may happen that
1458 // the iframe is not yet available, resulting in "Access
1459 // Denied" for the following property access.
1460 $.contentWindow
.document
;
1464 // Trick to solve this issue, forcing the iframe to get ready
1465 // by simply setting its "src" property.
1468 // In IE6 though, the above is not enough, so we must pause the
1469 // execution for a while, giving it time to think.
1470 if ( CKEDITOR
.env
.ie
&& CKEDITOR
.env
.version
< 7 )
1472 window
.showModalDialog(
1473 'javascript:document.write("' +
1475 'window.setTimeout(' +
1476 'function(){window.close();}' +
1482 return $ && new CKEDITOR
.dom
.document( $.contentWindow
.document
);
1486 * Copy all the attributes from one node to the other, kinda like a clone
1487 * skipAttributes is an object with the attributes that must NOT be copied.
1488 * @param {CKEDITOR.dom.element} dest The destination element.
1489 * @param {Object} skipAttributes A dictionary of attributes to skip.
1492 copyAttributes : function( dest
, skipAttributes
)
1494 var attributes
= this.$.attributes
;
1495 skipAttributes
= skipAttributes
|| {};
1497 for ( var n
= 0 ; n
< attributes
.length
; n
++ )
1499 var attribute
= attributes
[n
];
1501 // Lowercase attribute name hard rule is broken for
1502 // some attribute on IE, e.g. CHECKED.
1503 var attrName
= attribute
.nodeName
.toLowerCase(),
1506 // We can set the type only once, so do it with the proper value, not copying it.
1507 if ( attrName
in skipAttributes
)
1510 if ( attrName
== 'checked' && ( attrValue
= this.getAttribute( attrName
) ) )
1511 dest
.setAttribute( attrName
, attrValue
);
1512 // IE BUG: value attribute is never specified even if it exists.
1513 else if ( attribute
.specified
||
1514 ( CKEDITOR
.env
.ie
&& attribute
.nodeValue
&& attrName
== 'value' ) )
1516 attrValue
= this.getAttribute( attrName
);
1517 if ( attrValue
=== null )
1518 attrValue
= attribute
.nodeValue
;
1520 dest
.setAttribute( attrName
, attrValue
);
1525 if ( this.$.style
.cssText
!== '' )
1526 dest
.$.style
.cssText
= this.$.style
.cssText
;
1530 * Changes the tag name of the current element.
1531 * @param {String} newTag The new tag for the element.
1533 renameNode : function( newTag
)
1535 // If it's already correct exit here.
1536 if ( this.getName() == newTag
)
1539 var doc
= this.getDocument();
1541 // Create the new node.
1542 var newNode
= new CKEDITOR
.dom
.element( newTag
, doc
);
1544 // Copy all attributes.
1545 this.copyAttributes( newNode
);
1547 // Move children to the new node.
1548 this.moveChildren( newNode
);
1550 // Replace the node.
1551 this.getParent() && this.$.parentNode
.replaceChild( newNode
.$, this.$ );
1552 newNode
.$[ 'data-cke-expando' ] = this.$[ 'data-cke-expando' ];
1557 * Gets a DOM tree descendant under the current node.
1558 * @param {Array|Number} indices The child index or array of child indices under the node.
1559 * @returns {CKEDITOR.dom.node} The specified DOM child under the current node. Null if child does not exist.
1561 * var strong = p.getChild(0);
1563 getChild : function( indices
)
1565 var rawNode
= this.$;
1567 if ( !indices
.slice
)
1568 rawNode
= rawNode
.childNodes
[ indices
];
1571 while ( indices
.length
> 0 && rawNode
)
1572 rawNode
= rawNode
.childNodes
[ indices
.shift() ];
1575 return rawNode
? new CKEDITOR
.dom
.node( rawNode
) : null;
1578 getChildCount : function()
1580 return this.$.childNodes
.length
;
1583 disableContextMenu : function()
1585 this.on( 'contextmenu', function( event
)
1587 // Cancel the browser context menu.
1588 if ( !event
.data
.getTarget().hasClass( 'cke_enable_context_menu' ) )
1589 event
.data
.preventDefault();
1594 * Gets element's direction. Supports both CSS 'direction' prop and 'dir' attr.
1596 getDirection : function( useComputed
)
1598 return useComputed
?
1599 this.getComputedStyle( 'direction' )
1600 // Webkit: offline element returns empty direction (#8053).
1601 || this.getDirection()
1602 || this.getDocument().$.dir
1603 || this.getDocument().getBody().getDirection( 1 )
1604 : this.getStyle( 'direction' ) || this.getAttribute( 'dir' );
1608 * Gets, sets and removes custom data to be stored as HTML5 data-* attributes.
1609 * @param {String} name The name of the attribute, excluding the 'data-' part.
1610 * @param {String} [value] The value to set. If set to false, the attribute will be removed.
1612 * element.data( 'extra-info', 'test' ); // appended the attribute data-extra-info="test" to the element
1613 * alert( element.data( 'extra-info' ) ); // "test"
1614 * element.data( 'extra-info', false ); // remove the data-extra-info attribute from the element
1616 data : function ( name
, value
)
1618 name
= 'data-' + name
;
1619 if ( value
=== undefined )
1620 return this.getAttribute( name
);
1621 else if ( value
=== false )
1622 this.removeAttribute( name
);
1624 this.setAttribute( name
, value
);
1633 width
: [ "border-left-width", "border-right-width","padding-left", "padding-right" ],
1634 height
: [ "border-top-width", "border-bottom-width", "padding-top", "padding-bottom" ]
1637 function marginAndPaddingSize( type
)
1640 for ( var i
= 0, len
= sides
[ type
].length
; i
< len
; i
++ )
1641 adjustment
+= parseInt( this.getComputedStyle( sides
[ type
][ i
] ) || 0, 10 ) || 0;
1646 * Sets the element size considering the box model.
1647 * @name CKEDITOR.dom.element.prototype.setSize
1649 * @param {String} type The dimension to set. It accepts "width" and "height".
1650 * @param {Number} size The length unit in px.
1651 * @param {Boolean} isBorderBox Apply the size based on the border box model.
1653 CKEDITOR
.dom
.element
.prototype.setSize = function( type
, size
, isBorderBox
)
1655 if ( typeof size
== 'number' )
1657 if ( isBorderBox
&& !( CKEDITOR
.env
.ie
&& CKEDITOR
.env
.quirks
) )
1658 size
-= marginAndPaddingSize
.call( this, type
);
1660 this.setStyle( type
, size
+ 'px' );
1665 * Gets the element size, possibly considering the box model.
1666 * @name CKEDITOR.dom.element.prototype.getSize
1668 * @param {String} type The dimension to get. It accepts "width" and "height".
1669 * @param {Boolean} isBorderBox Get the size based on the border box model.
1671 CKEDITOR
.dom
.element
.prototype.getSize = function( type
, isBorderBox
)
1673 var size
= Math
.max( this.$[ 'offset' + CKEDITOR
.tools
.capitalize( type
) ],
1674 this.$[ 'client' + CKEDITOR
.tools
.capitalize( type
) ] ) || 0;
1677 size
-= marginAndPaddingSize
.call( this, type
);