Skip to content
Permalink
Browse files

Fix #10877. Make outerWidth/Height a setter. Closes gh-783.

  • Loading branch information...
mikesherov authored and dmethvin committed May 21, 2012
1 parent 978acb9 commit e0151e5827d7091f311c82d9f951aaaa2688ba8c
Showing with 130 additions and 92 deletions.
  1. +55 −39 src/css.js
  2. +21 −21 src/dimensions.js
  3. +8 −10 test/unit/css.js
  4. +46 −22 test/unit/dimensions.js
@@ -1,5 +1,6 @@
(function( jQuery ) {

// order is important!
jQuery.cssExpand = [ "Top", "Right", "Bottom", "Left" ];

var ralpha = /alpha\([^)]*\)/i,
@@ -13,7 +14,6 @@ var ralpha = /alpha\([^)]*\)/i,

cssShow = { position: "absolute", visibility: "hidden", display: "block" },

// order is important!
cssExpand = jQuery.cssExpand,
cssPrefixes = [ "Webkit", "O", "Moz", "ms" ],

@@ -127,7 +127,7 @@ jQuery.extend({
}

// If a hook was provided, use that value, otherwise just set the specified value
if ( !hooks || !("set" in hooks) || (value = hooks.set( elem, value )) !== undefined ) {
if ( !hooks || !("set" in hooks) || (value = hooks.set( elem, value, extra )) !== undefined ) {
// Wrapped to prevent IE from throwing errors when 'invalid' values are provided
// Fixes bug #5509
try {
@@ -266,19 +266,57 @@ if ( document.defaultView && document.defaultView.getComputedStyle ) {
};
}

function setPositiveNumber( elem, value ) {
function setPositiveNumber( elem, value, subtract ) {
var matches = rnumsplit.exec( value );
return matches ?
Math.max( 0, matches[ 1 ] ) + ( matches [ 2 ] || "px" )
Math.max( 0, +matches[ 1 ] - ( subtract || 0 ) ) + ( matches [ 2 ] || "px" )
: value;
}

function augmentWidthOrHeight( name, elem, extra, isBorderBox ) {
var val = 0,
i = name === "width" ? 1 : 0;

// if the measurement we need is already represented by the measurement
// there's no need to augment further
if ( extra !== (isBorderBox ? "border" : "content") ) {
for ( ; i < 4; i += 2 ) {
// both box models exclude margin, so add it if we want it
if ( extra === "margin" ) {
// we use jQuery.css instead of curCSS here
// because of the reliableMarginRight CSS hook!
val += parseFloat( jQuery.css( elem, extra + cssExpand[ i ] ) ) || 0;
}

if ( isBorderBox ) {
// border-box includes padding, so remove it if we want content
if ( extra === "content" ) {
val -= parseFloat( curCSS( elem, "padding" + cssExpand[ i ] ) ) || 0;
}

// at this point, extra isnt border nor margin, so remove border
if ( extra !== "margin" ) {
val -= parseFloat( curCSS( elem, "border" + cssExpand[ i ] + "Width" ) ) || 0;
}
} else {
// at this point, extra isnt content, so add padding
val += parseFloat( curCSS( elem, "padding" + cssExpand[ i ] ) ) || 0;

// at this point, extra isnt content nor padding, so add border
if ( extra !== "padding" ) {
val += parseFloat( curCSS( elem, "border" + cssExpand[ i ] + "Width" ) ) || 0;
}
}
}
}

return val;
}

function getWidthOrHeight( elem, name, extra ) {

// Start with offset property, which is equivalent to the border-box value
var val = name === "width" ? elem.offsetWidth : elem.offsetHeight,
i = name === "width" ? 1 : 0,
len = 4,
valueIsBorderBox = true,
isBorderBox = jQuery.support.boxSizing && jQuery.css( elem, "boxSizing" ) === "border-box";

@@ -307,38 +345,7 @@ function getWidthOrHeight( elem, name, extra ) {
extra = isBorderBox ? "border" : "content";
}

// if the measurement we need is already represented by the retrieved width
// there's no need to augment further
if ( extra !== (valueIsBorderBox ? "border" : "content") ) {
for ( ; i < len; i += 2 ) {
// both box models exclude margin, so add it if we want it
if ( extra === "margin" ) {
// we use jQuery.css instead of curCSS here
// because of the reliableMarginRight CSS hook!
val += parseFloat( jQuery.css( elem, extra + cssExpand[ i ] ) ) || 0;
}

if ( valueIsBorderBox ) {
// border-box includes padding, so remove it if we want content
if ( extra === "content" ) {
val -= parseFloat( curCSS( elem, "padding" + cssExpand[ i ] ) ) || 0;
}

// at this point, extra isnt border nor margin, so remove border
if ( extra !== "margin" ) {
val -= parseFloat( curCSS( elem, "border" + cssExpand[ i ] + "Width" ) ) || 0;
}
} else {
// at this point, extra isnt content, so add padding
val += parseFloat( curCSS( elem, "padding" + cssExpand[ i ] ) ) || 0;

// at this point, extra isnt content nor padding, so add border
if ( extra !== "padding" ) {
val += parseFloat( curCSS( elem, "border" + cssExpand[ i ] + "Width" ) ) || 0;
}
}
}
}
val += augmentWidthOrHeight( name, elem, extra, valueIsBorderBox );

return val + "px";
}
@@ -357,7 +364,16 @@ jQuery.each([ "height", "width" ], function( i, name ) {
}
},

set: setPositiveNumber
set: function( elem, value, extra ) {
return setPositiveNumber( elem, value, extra ?
augmentWidthOrHeight(
name,
elem,
extra,
jQuery.support.boxSizing && jQuery.css( elem, "boxSizing" ) === "border-box"
) : 0
);
}
};
});

@@ -6,27 +6,27 @@ jQuery.each( { Height: "height", Width: "width" }, function( name, type ) {
scrollProp = "scroll" + name,
offsetProp = "offset" + name;

// innerHeight and innerWidth
jQuery.fn[ "inner" + name ] = function() {
var elem = this[0];
return elem ?
elem.style ?
parseFloat( jQuery.css( elem, type, "padding" ) ) :
this[ type ]() :
null;
};
// height, width, innerHeight and innerWidth
jQuery.each( { padding: "inner" + name, content: type }, function( extra, funcName ) {
jQuery.fn[ funcName ] = function( value ) {
var args = [ type, extra ];
if ( arguments.length ) {
args.push( value );
}
return getDimension.apply( this, args );
};
});

// outerHeight and outerWidth
jQuery.fn[ "outer" + name ] = function( margin ) {
var elem = this[0];
return elem ?
elem.style ?
parseFloat( jQuery.css( elem, type, margin ? "margin" : "border" ) ) :
this[ type ]() :
null;
jQuery.fn[ "outer" + name ] = function( margin, value ) {
var args = [ type, ( margin === true || value === true ) ? "margin" : "border" ];
if ( arguments.length && typeof margin !== "boolean" ) {
args.push( margin );
}
return getDimension.apply( this, args );
};

jQuery.fn[ type ] = function( value ) {
function getDimension( type, extra, value ) {
return jQuery.access( this, function( elem, type, value ) {
var doc, orig, ret;

@@ -58,15 +58,15 @@ jQuery.each( { Height: "height", Width: "width" }, function( name, type ) {

// Get width or height on the element
if ( value === undefined ) {
orig = jQuery.css( elem, type, "content" );
orig = jQuery.css( elem, type, extra );
ret = parseFloat( orig );
return jQuery.isNumeric( ret ) ? ret : orig;
}

// Set the width or height on the element
jQuery.style( elem, type, value );
}, type, value, arguments.length, null );
};
jQuery.style( elem, type, value, extra );
}, type, value, arguments.length > 2, null );
}
});

})( jQuery );
@@ -555,16 +555,14 @@ test("outerWidth(true) and css('margin') returning % instead of px in Webkit, se
equal( el.outerWidth(true), 400, "outerWidth(true) and css('margin') returning % instead of px in Webkit, see #10639" );
});

test("css('width') should respect box-sizing, see #11004", function() {
var el_disconnected = jQuery("<div style='width:300px;margin:2px;padding:2px;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box;'>test</div>"),
el = el_disconnected.clone().appendTo("#qunit-fixture"),
width_initial = el.css("width"),
width_roundtrip = el.css("width", el.css("width")).css("width"),
width_initial_disconnected = el_disconnected.css("width"),
width_roundtrip_disconnected = el_disconnected.css("width", el_disconnected.css("width")).css("width");

equal( width_roundtrip, width_initial, "css('width') is not respecting box-sizing, see #11004");
equal( width_roundtrip_disconnected, width_initial_disconnected, "css('width') is not respecting box-sizing for disconnected element, see #11004");
test("css('width') and css('height') should respect box-sizing, see #11004", function() {
var el_dis = jQuery("<div style='width:300px;height:300px;margin:2px;padding:2px;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box;'>test</div>"),
el = el_dis.clone().appendTo("#qunit-fixture");

equal( el.css("width"), el.css("width", el.css("width")).css("width"), "css('width') is not respecting box-sizing, see #11004");
equal( el_dis.css("width"), el_dis.css("width", el_dis.css("width")).css("width"), "css('width') is not respecting box-sizing for disconnected element, see #11004");
equal( el.css("height"), el.css("height", el.css("height")).css("height"), "css('height') is not respecting box-sizing, see #11004");
equal( el_dis.css("height"), el_dis.css("height", el_dis.css("height")).css("height"), "css('height') is not respecting box-sizing for disconnected element, see #11004");
});

test( "cssHooks - expand", function() {
@@ -43,11 +43,6 @@ test("width()", function() {
testWidth( pass );
});

test("width(undefined)", function() {
expect(1);
equal(jQuery("#nothiddendiv").width(30).width(undefined).width(), 30, ".width(undefined) is chainable (#5571)");
});

test("width(Function)", function() {
testWidth( fn );
});
@@ -99,11 +94,6 @@ test("height()", function() {
testHeight( pass );
});

test("height(undefined)", function() {
expect(1);
equal(jQuery("#nothiddendiv").height(30).height(undefined).height(), 30, ".height(undefined) is chainable (#5571)");
});

test("height(Function)", function() {
testHeight( fn );
});
@@ -121,16 +111,13 @@ test("height(Function(args))", function() {
});

test("innerWidth()", function() {
expect(8);
expect(6);

var winWidth = jQuery( window ).width(),
docWidth = jQuery( document ).width();

equal(jQuery(window).innerWidth(), winWidth, "Test on window without margin option");
equal(jQuery(window).innerWidth(true), winWidth, "Test on window with margin option");

equal(jQuery(document).innerWidth(), docWidth, "Test on document without margin option");
equal(jQuery(document).innerWidth(true), docWidth, "Test on document with margin option");
equal(jQuery(window).innerWidth(), winWidth, "Test on window");
equal(jQuery(document).innerWidth(), docWidth, "Test on document");

var $div = jQuery("#nothiddendiv");
// set styles
@@ -159,16 +146,13 @@ test("innerWidth()", function() {
});

test("innerHeight()", function() {
expect(8);
expect(6);

var winHeight = jQuery( window ).height(),
docHeight = jQuery( document ).height();

equal(jQuery(window).innerHeight(), winHeight, "Test on window without margin option");
equal(jQuery(window).innerHeight(true), winHeight, "Test on window with margin option");

equal(jQuery(document).innerHeight(), docHeight, "Test on document without margin option");
equal(jQuery(document).innerHeight(true), docHeight, "Test on document with margin option");
equal(jQuery(window).innerHeight(), winHeight, "Test on window");
equal(jQuery(document).innerHeight(), docHeight, "Test on document");

var $div = jQuery("#nothiddendiv");
// set styles
@@ -369,6 +353,46 @@ test("outerHeight()", function() {
jQuery.removeData($div[0], "olddisplay", true);
});

test("passing undefined is a setter #5571", function() {
expect(4);
equal(jQuery("#nothiddendiv").height(30).height(undefined).height(), 30, ".height(undefined) is chainable (#5571)");
equal(jQuery("#nothiddendiv").height(30).innerHeight(undefined).height(), 30, ".innerHeight(undefined) is chainable (#5571)");
equal(jQuery("#nothiddendiv").height(30).outerHeight(undefined).height(), 30, ".outerHeight(undefined) is chainable (#5571)");
equal(jQuery("#nothiddendiv").width(30).width(undefined).width(), 30, ".width(undefined) is chainable (#5571)");
});

test("setters with and without box-sizing:border-box", function(){
expect(20);

var el_bb = jQuery("<div style='width:114px;height:114px;margin:5px;padding:3px;border:4px solid white;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box;'>test</div>").appendTo("#qunit-fixture"),
el = jQuery("<div style='width:100px;height:100px;margin:5px;padding:3px;border:4px solid white;'>test</div>").appendTo("#qunit-fixture"),
expected = 100;

equal( el_bb.width( 101 ).width(), expected + 1, "test border-box width(int) by roundtripping" );
equal( el_bb.innerWidth( 108 ).width(), expected + 2, "test border-box innerWidth(int) by roundtripping" );
equal( el_bb.outerWidth( 117 ).width(), expected + 3, "test border-box outerWidth(int) by roundtripping" );
equal( el_bb.outerWidth( 118, false ).width(), expected + 4, "test border-box outerWidth(int, false) by roundtripping" );
equal( el_bb.outerWidth( 129, true ).width(), expected + 5, "test border-box innerWidth(int, true) by roundtripping" );

equal( el_bb.height( 101 ).height(), expected + 1, "test border-box height(int) by roundtripping" );
equal( el_bb.innerHeight( 108 ).height(), expected + 2, "test border-box innerHeight(int) by roundtripping" );
equal( el_bb.outerHeight( 117 ).height(), expected + 3, "test border-box outerHeight(int) by roundtripping" );
equal( el_bb.outerHeight( 118, false ).height(), expected + 4, "test border-box outerHeight(int, false) by roundtripping" );
equal( el_bb.outerHeight( 129, true ).height(), expected + 5, "test border-box innerHeight(int, true) by roundtripping" );

equal( el.width( 101 ).width(), expected + 1, "test border-box width(int) by roundtripping" );
equal( el.innerWidth( 108 ).width(), expected + 2, "test border-box innerWidth(int) by roundtripping" );
equal( el.outerWidth( 117 ).width(), expected + 3, "test border-box outerWidth(int) by roundtripping" );
equal( el.outerWidth( 118, false ).width(), expected + 4, "test border-box outerWidth(int, false) by roundtripping" );
equal( el.outerWidth( 129, true ).width(), expected + 5, "test border-box innerWidth(int, true) by roundtripping" );

equal( el.height( 101 ).height(), expected + 1, "test border-box height(int) by roundtripping" );
equal( el.innerHeight( 108 ).height(), expected + 2, "test border-box innerHeight(int) by roundtripping" );
equal( el.outerHeight( 117 ).height(), expected + 3, "test border-box outerHeight(int) by roundtripping" );
equal( el.outerHeight( 118, false ).height(), expected + 4, "test border-box outerHeight(int, false) by roundtripping" );
equal( el.outerHeight( 129, true ).height(), expected + 5, "test border-box innerHeight(int, true) by roundtripping" );
});

testIframe("dimensions/documentSmall", "window vs. small document", function( jQuery, window, document ) {
expect(2);

13 comments on commit e0151e5

@justinbmeyer

This comment has been minimized.

Copy link
Contributor

justinbmeyer replied May 21, 2012

@justinbmeyer

This comment has been minimized.

Copy link
Contributor

justinbmeyer replied May 21, 2012

Actually, is there anyway to detect this? We have a bunch of widgets that still work on older jQuery versions that depend on a setter. I'd like them to check if this is available before grabbing our plugin. Thanks!

@scottgonzalez

This comment has been minimized.

Copy link
Member

scottgonzalez replied May 21, 2012

The test I added in jQuery UI is $( "<a>" ).outerWidth( 1 ).jquery. If it's chainable, then setters are implemented.

@justinbmeyer

This comment has been minimized.

Copy link
Contributor

justinbmeyer replied May 21, 2012

One more question, does this work with animation? It doesn't seem so. It can be very useful to animate outerWidth / innerWidth. We've done that here:

https://github.com/jupiterjs/jquerypp/blob/master/dom/dimensions/dimensions.js#L162

@mikesherov

This comment has been minimized.

Copy link
Member Author

mikesherov replied May 21, 2012

@justinbmeyer, the fastest way to animate .outerWidth() is to set the elem to box-sizing:border-box, and then just animate .css('width')

@justinbmeyer

This comment has been minimized.

Copy link
Contributor

justinbmeyer replied May 21, 2012

@scottgonzalez thanks!

@mikesherov thanks, but unfortunately that doesn't work in ie 6+7 and supporting that is still a reality for me as long as it is a reality for jQuery.

@mikesherov

This comment has been minimized.

Copy link
Member Author

mikesherov replied May 21, 2012

@justinbmeyer: https://github.com/Schepp/box-sizing-polyfill

But also, you can use jQuery.css(elem, name, extra) for those animations. Of course, this is undocumented, but works for now. For example:

  • jQuery.fn.width(value) => jQuery.style(elem, "width", value, "content")
  • jQuery.fn.innerWidth(value) => jQuery.style(elem, "width", value, "padding")
  • jQuery.fn.outerWidth(value) => jQuery.style(elem, "width", value, "border")
  • jQuery.fn.width(value, true) => jQuery.style(elem, "width", value, "margin")
@justinbmeyer

This comment has been minimized.

Copy link
Contributor

justinbmeyer replied May 21, 2012

You're talking about using jQuery.css inside my animate/step methods to set the value?

jQuery.style(elem, "width", value, "padding")

sets an element's width, but somehow accounts for padding?

@mikesherov

This comment has been minimized.

Copy link
Member Author

mikesherov replied May 21, 2012

@justinbmeyer: Yes, and even corrects for whichever box-sizing you're using :-)

@mikesherov

This comment has been minimized.

Copy link
Member Author

mikesherov replied May 21, 2012

@justinbmeyer

This comment has been minimized.

Copy link
Contributor

justinbmeyer replied May 21, 2012

Cool, but I'd still like to see jQuery able to easily animate these properties. We have a lot of widgets that accept any users styles, but animates outerWidth. We can't control their box model. Plus, if people are going to be using innerWidth outerWidth as setters, it's probably likely they will try using them as animate props.

If performance is a concern, maybe jQuery could look into bringing our "group styles" plugin:

https://github.com/jupiterjs/jquerypp/blob/master/dom/styles/styles.js

That can read multiple properties from getComputedStyle at once and has considerable (2x-3x) performance improvements in naive implementations of width / height.

@mikesherov

This comment has been minimized.

Copy link
Member Author

mikesherov replied May 21, 2012

Yeah, I was going to enhance the performance of the augmentWidthorHeight function by doing exactly what you have there. Having to call getComputedStyle multiple times sucks. I need it to also grab the box-sizing as well, so it needs to be a bit less naive, and I have to worry about size for 1.8. It's coming though :-)

@mikesherov

This comment has been minimized.

Copy link
Member Author

mikesherov replied May 21, 2012

@justinbmeyer, with regards to "easily" animate these properties, you can always use a propHook in effects.js:

Tween.propHooks = {

Please sign in to comment.
You can’t perform that action at this time.