X-Git-Url: https://jasonwoof.com/gitweb/?a=blobdiff_plain;ds=sidebyside;f=_source%2Fcore%2Fdom%2Felement.js;h=84498ba72b4080b372777ac9cfe8155f12826e87;hb=6e682412d5cc0dfaedb376482e585bf2989c6863;hp=6c62613bc8242ef3d675fc66b053e14bccb3bbba;hpb=039a051ccf3901311661022a30afd60fc38130c9;p=ckeditor.git diff --git a/_source/core/dom/element.js b/_source/core/dom/element.js index 6c62613..84498ba 100644 --- a/_source/core/dom/element.js +++ b/_source/core/dom/element.js @@ -1,5 +1,5 @@ /* -Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. +Copyright (c) 2003-2012, CKSource - Frederico Knabben. All rights reserved. For licensing, see LICENSE.html or http://ckeditor.com/license */ @@ -307,12 +307,17 @@ CKEDITOR.tools.extend( CKEDITOR.dom.element.prototype, /** * Moves the selection focus to this element. + * @function + * @param {Boolean} defer Whether to asynchronously defer the + * execution by 100 ms. * @example * var element = CKEDITOR.document.getById( 'myTextarea' ); * element.focus(); */ - focus : function() + focus : ( function() { + function exec() + { // IE throws error if the element is not visible. try { @@ -320,7 +325,16 @@ CKEDITOR.tools.extend( CKEDITOR.dom.element.prototype, } catch (e) {} - }, + } + + return function( defer ) + { + if ( defer ) + CKEDITOR.tools.setTimeout( exec, 100, this ); + else + exec.call( this ); + }; + })(), /** * Gets the inner HTML of this element. @@ -415,6 +429,13 @@ CKEDITOR.tools.extend( CKEDITOR.dom.element.prototype, name = 'className'; break; + case 'http-equiv': + name = 'httpEquiv'; + break; + + case 'name': + return this.$.name; + case 'tabindex': var tabIndex = standard.call( this, name ); @@ -439,11 +460,17 @@ CKEDITOR.tools.extend( CKEDITOR.dom.element.prototype, } case 'hspace': - return this.$.hspace; + case 'value': + return this.$[ name ]; case 'style': // IE does not return inline styles via getAttribute(). See #2947. return this.$.style.cssText; + + case 'contenteditable': + case 'contentEditable': + return this.$.attributes.getNamedItem( 'contentEditable' ).specified ? + this.$.getAttribute( 'contentEditable' ) : null; } return standard.call( this, name ); @@ -558,7 +585,7 @@ CKEDITOR.tools.extend( CKEDITOR.dom.element.prototype, * in the future. * @returns {String} The text value. * @example - * var element = CKEDITOR.dom.element.createFromHtml( '<div>Same <i>text</i>.</div>' ); + * var element = CKEDITOR.dom.element.createFromHtml( '<div>Sample <i>text</i>.</div>' ); * alert( element.getText() ); // "Sample text." */ getText : function() @@ -613,7 +640,7 @@ CKEDITOR.tools.extend( CKEDITOR.dom.element.prototype, // Cache the lowercased name inside a closure. var nodeName = this.$.nodeName.toLowerCase(); - if ( CKEDITOR.env.ie ) + if ( CKEDITOR.env.ie && ! ( document.documentMode > 8 ) ) { var scopeName = this.$.scopeName; if ( scopeName != 'HTML' ) @@ -697,17 +724,33 @@ CKEDITOR.tools.extend( CKEDITOR.dom.element.prototype, return false; }, - isEditable : function() + /** + * Decide whether one element is able to receive cursor. + * @param {Boolean} [textCursor=true] Only consider element that could receive text child. + */ + isEditable : function( textCursor ) { - // Get the element name. var name = this.getName(); - // Get the element DTD (defaults to span for unknown elements). - var dtd = !CKEDITOR.dtd.$nonEditable[ name ] - && ( CKEDITOR.dtd[ name ] || CKEDITOR.dtd.span ); + if ( this.isReadOnly() + || this.getComputedStyle( 'display' ) == 'none' + || this.getComputedStyle( 'visibility' ) == 'hidden' + || this.is( 'a' ) && this.data( 'cke-saved-name' ) && !this.getChildCount() + || CKEDITOR.dtd.$nonEditable[ name ] + || CKEDITOR.dtd.$empty[ name ] ) + { + return false; + } + + if ( textCursor !== false ) + { + // Get the element DTD (defaults to span for unknown elements). + var dtd = CKEDITOR.dtd[ name ] || CKEDITOR.dtd.span; + // In the DTD # == text node. + return ( dtd && dtd[ '#'] ); + } - // In the DTD # == text node. - return ( dtd && dtd['#'] ); + return true; }, isIdentical : function( otherElement ) @@ -721,14 +764,14 @@ CKEDITOR.tools.extend( CKEDITOR.dom.element.prototype, var thisLength = thisAttribs.length, otherLength = otherAttribs.length; - if ( !CKEDITOR.env.ie && thisLength != otherLength ) - return false; - for ( var i = 0 ; i < thisLength ; i++ ) { var attribute = thisAttribs[ i ]; - if ( ( !CKEDITOR.env.ie || ( attribute.specified && attribute.nodeName != '_cke_expando' ) ) && attribute.nodeValue != otherElement.getAttribute( attribute.nodeName ) ) + if ( attribute.nodeName == '_moz_dirty' ) + continue; + + if ( ( !CKEDITOR.env.ie || ( attribute.specified && attribute.nodeName != 'data-cke-expando' ) ) && attribute.nodeValue != otherElement.getAttribute( attribute.nodeName ) ) return false; } @@ -739,7 +782,7 @@ CKEDITOR.tools.extend( CKEDITOR.dom.element.prototype, for ( i = 0 ; i < otherLength ; i++ ) { attribute = otherAttribs[ i ]; - if ( attribute.specified && attribute.nodeName != '_cke_expando' + if ( attribute.specified && attribute.nodeName != 'data-cke-expando' && attribute.nodeValue != this.getAttribute( attribute.nodeName ) ) return false; } @@ -756,7 +799,7 @@ CKEDITOR.tools.extend( CKEDITOR.dom.element.prototype, */ isVisible : function() { - var isVisible = !!this.$.offsetHeight && this.getComputedStyle( 'visibility' ) != 'hidden', + var isVisible = ( this.$.offsetHeight || this.$.offsetWidth ) && this.getComputedStyle( 'visibility' ) != 'hidden', elementWindow, elementWindowFrame; @@ -773,7 +816,7 @@ CKEDITOR.tools.extend( CKEDITOR.dom.element.prototype, } } - return isVisible; + return !!isVisible; }, /** @@ -789,7 +832,7 @@ CKEDITOR.tools.extend( CKEDITOR.dom.element.prototype, { var child = children.getItem( i ); - if ( child.type == CKEDITOR.NODE_ELEMENT && child.getAttribute( '_cke_bookmark' ) ) + if ( child.type == CKEDITOR.NODE_ELEMENT && child.data( 'cke-bookmark' ) ) continue; if ( child.type == CKEDITOR.NODE_ELEMENT && !child.isEmptyInlineRemoveable() @@ -802,14 +845,15 @@ CKEDITOR.tools.extend( CKEDITOR.dom.element.prototype, }, /** - * Indicates that the element has defined attributes. + * Checks if the element has any defined attributes. + * @function * @returns {Boolean} True if the element has attributes. * @example - * var element = CKEDITOR.dom.element.createFromHtml( '
true
is specified; align the bottom with viewport bottom when
+ * false
is specified. Otherwise scroll on either side with the minimum
+ * amount to show the element.
+ * @param {Boolean} [hscroll] Whether horizontal overflow should be considered.
+ */
+ scrollIntoParent : function( parent, alignToTop, hscroll )
+ {
+ !parent && ( parent = this.getWindow() );
+
+ var doc = parent.getDocument();
+ var isQuirks = doc.$.compatMode == 'BackCompat';
+
+ // On window is scrolled while quirks scrolls .
+ if ( parent instanceof CKEDITOR.dom.window )
+ parent = isQuirks ? doc.getBody() : doc.getDocumentElement();
+
+ // Scroll the parent by the specified amount.
+ function scrollBy( x, y )
+ {
+ // Webkit doesn't support "scrollTop/scrollLeft"
+ // on documentElement/body element.
+ if ( /body|html/.test( parent.getName() ) )
+ parent.getWindow().$.scrollBy( x, y );
+ else
+ {
+ parent.$[ 'scrollLeft' ] += x;
+ parent.$[ 'scrollTop' ] += y;
+ }
+ }
+
+ // Figure out the element position relative to the specified window.
+ function screenPos( element, refWin )
+ {
+ var pos = { x: 0, y: 0 };
+
+ if ( !( element.is( isQuirks ? 'body' : 'html' ) ) )
+ {
+ var box = element.$.getBoundingClientRect();
+ pos.x = box.left, pos.y = box.top;
+ }
+
+ var win = element.getWindow();
+ if ( !win.equals( refWin ) )
+ {
+ var outerPos = screenPos( CKEDITOR.dom.element.get( win.$.frameElement ), refWin );
+ pos.x += outerPos.x, pos.y += outerPos.y;
+ }
+
+ return pos;
+ }
+
+ // calculated margin size.
+ function margin( element, side )
+ {
+ return parseInt( element.getComputedStyle( 'margin-' + side ) || 0, 10 ) || 0;
+ }
+
+ var win = parent.getWindow();
+
+ var thisPos = screenPos( this, win ),
+ parentPos = screenPos( parent, win ),
+ eh = this.$.offsetHeight,
+ ew = this.$.offsetWidth,
+ ch = parent.$.clientHeight,
+ cw = parent.$.clientWidth,
+ lt,
+ br;
- // offset value might be out of range(nagative), fix it(#3692).
- offset = offset < 0 ? 0 : offset;
+ // Left-top margins.
+ lt =
+ {
+ x : thisPos.x - margin( this, 'left' ) - parentPos.x || 0,
+ y : thisPos.y - margin( this, 'top' ) - parentPos.y|| 0
+ };
+
+ // Bottom-right margins.
+ br =
+ {
+ x : thisPos.x + ew + margin( this, 'right' ) - ( ( parentPos.x ) + cw ) || 0,
+ y : thisPos.y + eh + margin( this, 'bottom' ) - ( ( parentPos.y ) + ch ) || 0
+ };
+
+ // 1. Do the specified alignment as much as possible;
+ // 2. Otherwise be smart to scroll only the minimum amount;
+ // 3. Never cut at the top;
+ // 4. DO NOT scroll when already visible.
+ if ( lt.y < 0 || br.y > 0 )
+ {
+ scrollBy( 0,
+ alignToTop === true ? lt.y :
+ alignToTop === false ? br.y :
+ lt.y < 0 ? lt.y : br.y );
+ }
- // Scroll the window to the desired position, if not already visible(#3795).
- var currentScroll = win.getScrollPosition().y;
- if ( offset > currentScroll || offset < currentScroll - winHeight )
- win.$.scrollTo( 0, offset );
+ if ( hscroll && ( lt.x < 0 || br.x > 0 ) )
+ scrollBy( lt.x < 0 ? lt.x : br.x, 0 );
},
setState : function( state )
@@ -1483,7 +1674,7 @@ CKEDITOR.tools.extend( CKEDITOR.dom.element.prototype,
// Replace the node.
this.getParent() && this.$.parentNode.replaceChild( newNode.$, this.$ );
- newNode.$._cke_expando = this.$._cke_expando;
+ newNode.$[ 'data-cke-expando' ] = this.$[ 'data-cke-expando' ];
this.$ = newNode.$;
},
@@ -1525,40 +1716,91 @@ CKEDITOR.tools.extend( CKEDITOR.dom.element.prototype,
},
/**
- * Update the element's size with box model awareness.
- * @name CKEDITOR.dom.element.setSize
- * @param {String} type [width|height]
- * @param {Number} size The length unit in px.
- * @param isBorderBox Apply the {@param width} and {@param height} based on border box model.
+ * Gets element's direction. Supports both CSS 'direction' prop and 'dir' attr.
*/
- setSize : ( function()
+ getDirection : function( useComputed )
{
- var sides = {
- width : [ "border-left-width", "border-right-width","padding-left", "padding-right" ],
- height : [ "border-top-width", "border-bottom-width", "padding-top", "padding-bottom" ]
- };
-
- return function( type, size, isBorderBox )
- {
- if ( typeof size == 'number' )
- {
- if ( isBorderBox && !( CKEDITOR.env.ie && CKEDITOR.env.quirks ) )
- {
- var adjustment = 0;
- for ( var i = 0, len = sides[ type ].length; i < len; i++ )
- adjustment += parseInt( this.getComputedStyle( sides [ type ][ i ] ) || 0, 10 ) || 0;
- size -= adjustment;
- }
- this.setStyle( type, size + 'px' );
- }
- };
- })(),
+ return useComputed ?
+ this.getComputedStyle( 'direction' )
+ // Webkit: offline element returns empty direction (#8053).
+ || this.getDirection()
+ || this.getDocument().$.dir
+ || this.getDocument().getBody().getDirection( 1 )
+ : this.getStyle( 'direction' ) || this.getAttribute( 'dir' );
+ },
/**
- * Gets element's direction. Supports both CSS 'direction' prop and 'dir' attr.
+ * Gets, sets and removes custom data to be stored as HTML5 data-* attributes.
+ * @param {String} name The name of the attribute, excluding the 'data-' part.
+ * @param {String} [value] The value to set. If set to false, the attribute will be removed.
+ * @example
+ * element.data( 'extra-info', 'test' ); // appended the attribute data-extra-info="test" to the element
+ * alert( element.data( 'extra-info' ) ); // "test"
+ * element.data( 'extra-info', false ); // remove the data-extra-info attribute from the element
*/
- getDirection : function( useComputed )
+ data : function ( name, value )
{
- return useComputed ? this.getComputedStyle( 'direction' ) : this.getStyle( 'direction' ) || this.getAttribute( 'dir' );
+ name = 'data-' + name;
+ if ( value === undefined )
+ return this.getAttribute( name );
+ else if ( value === false )
+ this.removeAttribute( name );
+ else
+ this.setAttribute( name, value );
+
+ return null;
}
});
+
+( function()
+{
+ var sides = {
+ width : [ "border-left-width", "border-right-width","padding-left", "padding-right" ],
+ height : [ "border-top-width", "border-bottom-width", "padding-top", "padding-bottom" ]
+ };
+
+ function marginAndPaddingSize( type )
+ {
+ var adjustment = 0;
+ for ( var i = 0, len = sides[ type ].length; i < len; i++ )
+ adjustment += parseInt( this.getComputedStyle( sides [ type ][ i ] ) || 0, 10 ) || 0;
+ return adjustment;
+ }
+
+ /**
+ * Sets the element size considering the box model.
+ * @name CKEDITOR.dom.element.prototype.setSize
+ * @function
+ * @param {String} type The dimension to set. It accepts "width" and "height".
+ * @param {Number} size The length unit in px.
+ * @param {Boolean} isBorderBox Apply the size based on the border box model.
+ */
+ CKEDITOR.dom.element.prototype.setSize = function( type, size, isBorderBox )
+ {
+ if ( typeof size == 'number' )
+ {
+ if ( isBorderBox && !( CKEDITOR.env.ie && CKEDITOR.env.quirks ) )
+ size -= marginAndPaddingSize.call( this, type );
+
+ this.setStyle( type, size + 'px' );
+ }
+ };
+
+ /**
+ * Gets the element size, possibly considering the box model.
+ * @name CKEDITOR.dom.element.prototype.getSize
+ * @function
+ * @param {String} type The dimension to get. It accepts "width" and "height".
+ * @param {Boolean} isBorderBox Get the size based on the border box model.
+ */
+ CKEDITOR.dom.element.prototype.getSize = function( type, isBorderBox )
+ {
+ var size = Math.max( this.$[ 'offset' + CKEDITOR.tools.capitalize( type ) ],
+ this.$[ 'client' + CKEDITOR.tools.capitalize( type ) ] ) || 0;
+
+ if ( isBorderBox )
+ size -= marginAndPaddingSize.call( this, type );
+
+ return size;
+ };
+})();