D
David Mark
Defending the recent decision to further tangle up the attr method by
hard-wiring it to jQuery method calls for attribute names that match
jQuery method names (e.g. height), Resig offered this explanation:-
"...for example, we definitely do want to get the height value as
reported by.height(), especially since it's likely to be more accurate
than trying to get elem.height."
http://groups.google.com/group/jquery-dev/browse_thread/thread/baef5e91bd714033
More accurate than what? The value returned by jQuery's height method
is going to mean something quite different than - for example - the
height property of an image element.
<img height="32" width="32" src="..." style="border:solid 10px;padding:
10px">
There are a number of contexts where you would want to retrieve the
image height of 32. For instance, if you wanted to zoom 2X, you could
easily calculate the zoomed height as:-
img.height * 2 // 64
It used to be that jQuery could do this with its attr method:-
attr('height') * 2 // 64
Simple, concise, 100% accurate, works in virtually any agent, but
apparently not "cool" enough for the code monkeys.
I imagine one of their "thoughts" is "what if the display style is
none?". Wouldn't it be cool if they added it to the layout real fast,
measured and removed it? Well, of course not. That would just step
on more significant details and create more confusion (at least for
those paying attention).
So what will this new "more accurate" rendition do besides cross more
wires under the hood?
This is the height method:-
jQuery.fn[ type ] = function( size ) {
// Get window width or height
var elem = this[0];
if ( !elem ) { return null; }
return ("scrollTo" in elem && elem.document) ? // does it walk and
quack like a window?
There's the result of the ridiculous "overload"-happy design, which
doesn't really work in JS. So this thing measures windows too. I
suppose it is predictable as this object model uses one object to
represent a single element, multiple elements, documents, windows,
etc.
// Everyone else use document.documentElement or document.body
depending on Quirks vs Standards mode
What?
elem.document.compatMode === "CSS1Compat" &&
elem.document.documentElement[ "client" + name ] ||
elem.document.body[ "client" + name ] :
Well, we know that's bullshit.
http://www.cinsoft.net/viewport.asp
// Get document width or height
(elem.nodeType === 9) ? // is it a document
// Either scroll[Width/Height] or offset[Width/Height], whichever
is greater
Math.max(
elem.documentElement["client" + name],
elem.body["scroll" + name], elem.documentElement["scroll" +
name],
elem.body["offset" + name], elem.documentElement["offset" + name]
) :
Sure, scrollHeight and offsetHeight mean about the same thing for
documents objects. Whichever is bigger.
// Get or set width or height on the element
Finally.
size === undefined ?
// Get width or height on the element
jQuery.css( elem, type ) :
Dammit, another detour (and I don't like where this is going).
css: function( elem, name, force, extra ) {
if ( name === "width" || name === "height" ) {
var val, props = { position: "absolute", visibility: "hidden",
display:"block" }, which = name === "width" ? [ "Left", "Right" ] :
[ "Top", "Bottom" ];
function getWH() {
val = name === "width" ? elem.offsetWidth : elem.offsetHeight;
if ( extra === "border" ) { return; }
Didn't pass any "extra", so proceed...
jQuery.each( which, function() {
if ( !extra ) {
val -= parseFloat(jQuery.curCSS( elem, "padding" + this, true))
|| 0;
}
Subtracts padding.
if ( extra === "margin" ) {
val += parseFloat(jQuery.curCSS( elem, "margin" + this, true))
|| 0;
} else {
val -= parseFloat(jQuery.curCSS( elem, "border" + this +
"Width", true)) || 0;
Subtracts border.
}
});
}
if ( elem.offsetWidth !== 0 ) {
getWH();
There it is.
} else {
jQuery.swap( elem, props, getWH );
}
return Math.max(0, Math.round(val));
}
return jQuery.curCSS( elem, name, force );
},
Okay, that was a wild ride (and imagine a UA doing it on every call to
attr). I want to duplicate the following design, which reads the
height of an image and then puts it back. The image's height should
remain the same.
var height = img.height; // 32
img.height = var; .// 32 (all is well)
There's one glaring issue with the jQuery way and that's curCSS. A
quote from the inline comments of that method says it all:-
// From the awesome hack by Dean Edwards
(!)
// If we're not dealing with a regular pixel number
// but a number that has a weird ending, we need to convert it to
pixels
This is from the IE fork. The hack used is complete bullshit, but
doesn't matter for this example.
Still, this is definitely going to be a "weird ending".
In standards mode, in most browsers, Resig's concoction will spit out:
32. Cool! And only took 10,000 times the effort. It's even (sort
of) symmetrical as the reverse process ends up setting the CSS height
to '32px'.
<img height="32" width="32" src="..." style="border:solid 10px;padding:
10px;height:32px">
In quirks mode, in some browsers (e.g. IE), or if the box model for
the image has been changed through CSS, the result is also 32.
Unfortunately, setting the CSS height to '32px' will be a catastrophe.
In fact, it's not even possible as the borders and padding take up
40px (and that's all that will be left of the image).
The confusion is evident in these methods as well:-
// innerHeight and innerWidth
jQuery.fn["inner" + name] = function(){
return this[0] ?
jQuery.css( this[0], type, false, "padding" ) :
null;
};
// outerHeight and outerWidth
jQuery.fn["outer" + name] = function(margin) {
return this[0] ?
jQuery.css( this[0], type, false, margin ? "margin" : "border" ) :
null;
};
What's wrong with those pictures?
So, even if there were no such things as quirks mode or alternate box
models and every agent supported computed styles (lets out IE), this
would sort of work, but would still be a ridiculous design.
What's the answer to all of this? It's very simple. Use more than
one function. One gets the CSS dimensions, one gets the offset
dimensions (and neither is tangled up with attribute or property
wrappers). A symmetrical setter for the former is trivial and the
latter is do-able as well. There is no need to manually subtract
borders and padding at all and the box model is irrelevant (GIGO).
See My Library (or the upcoming "B is for Box Model" article) for
examples.
Can't measure windows, document or elements; can't query elements by
attribute, can't get/set attributes or properties in any sort of
predictable way and as a bonus, if you act now...
From the creators of isArray and isFunction comes...
isObjectLiteral: function( obj ) {
Yes friends, you too can detect object literals.
if ( toString.call(obj) !== "[object Object]" || typeof obj.nodeType
=== "number" ) {
return false;
}
LOL. I've got to figure they know they screwed up massively with the
initial "overloaded" design, but I guess they are stuck with it.
// not own constructor property must be Object
if ( obj.constructor
&& !hasOwnProperty.call(obj, "constructor")
Oops. So Safari 2 and the like just blow up in the middle of
this.
&& !hasOwnProperty.call(obj.constructor.prototype,
"isPrototypeOf") ) {
return false;
}
//own properties are iterated firstly,
Says who?
//so to speed up, we can test last one if it is own or not
var key;
for ( key in obj ) {}
return key === undefined || hasOwnProperty.call( obj, key );
},
WTF? I've got a great idea to "speed up" this mess. Lose it
altogether.
hard-wiring it to jQuery method calls for attribute names that match
jQuery method names (e.g. height), Resig offered this explanation:-
"...for example, we definitely do want to get the height value as
reported by.height(), especially since it's likely to be more accurate
than trying to get elem.height."
http://groups.google.com/group/jquery-dev/browse_thread/thread/baef5e91bd714033
More accurate than what? The value returned by jQuery's height method
is going to mean something quite different than - for example - the
height property of an image element.
<img height="32" width="32" src="..." style="border:solid 10px;padding:
10px">
There are a number of contexts where you would want to retrieve the
image height of 32. For instance, if you wanted to zoom 2X, you could
easily calculate the zoomed height as:-
img.height * 2 // 64
It used to be that jQuery could do this with its attr method:-
attr('height') * 2 // 64
Simple, concise, 100% accurate, works in virtually any agent, but
apparently not "cool" enough for the code monkeys.
I imagine one of their "thoughts" is "what if the display style is
none?". Wouldn't it be cool if they added it to the layout real fast,
measured and removed it? Well, of course not. That would just step
on more significant details and create more confusion (at least for
those paying attention).
So what will this new "more accurate" rendition do besides cross more
wires under the hood?
This is the height method:-
jQuery.fn[ type ] = function( size ) {
// Get window width or height
var elem = this[0];
if ( !elem ) { return null; }
return ("scrollTo" in elem && elem.document) ? // does it walk and
quack like a window?
There's the result of the ridiculous "overload"-happy design, which
doesn't really work in JS. So this thing measures windows too. I
suppose it is predictable as this object model uses one object to
represent a single element, multiple elements, documents, windows,
etc.
// Everyone else use document.documentElement or document.body
depending on Quirks vs Standards mode
What?
elem.document.compatMode === "CSS1Compat" &&
elem.document.documentElement[ "client" + name ] ||
elem.document.body[ "client" + name ] :
Well, we know that's bullshit.
http://www.cinsoft.net/viewport.asp
// Get document width or height
(elem.nodeType === 9) ? // is it a document
// Either scroll[Width/Height] or offset[Width/Height], whichever
is greater
Math.max(
elem.documentElement["client" + name],
elem.body["scroll" + name], elem.documentElement["scroll" +
name],
elem.body["offset" + name], elem.documentElement["offset" + name]
) :
Sure, scrollHeight and offsetHeight mean about the same thing for
documents objects. Whichever is bigger.
// Get or set width or height on the element
Finally.
size === undefined ?
// Get width or height on the element
jQuery.css( elem, type ) :
Dammit, another detour (and I don't like where this is going).
css: function( elem, name, force, extra ) {
if ( name === "width" || name === "height" ) {
var val, props = { position: "absolute", visibility: "hidden",
display:"block" }, which = name === "width" ? [ "Left", "Right" ] :
[ "Top", "Bottom" ];
function getWH() {
val = name === "width" ? elem.offsetWidth : elem.offsetHeight;
if ( extra === "border" ) { return; }
Didn't pass any "extra", so proceed...
jQuery.each( which, function() {
if ( !extra ) {
val -= parseFloat(jQuery.curCSS( elem, "padding" + this, true))
|| 0;
}
Subtracts padding.
if ( extra === "margin" ) {
val += parseFloat(jQuery.curCSS( elem, "margin" + this, true))
|| 0;
} else {
val -= parseFloat(jQuery.curCSS( elem, "border" + this +
"Width", true)) || 0;
Subtracts border.
}
});
}
if ( elem.offsetWidth !== 0 ) {
getWH();
There it is.
} else {
jQuery.swap( elem, props, getWH );
}
return Math.max(0, Math.round(val));
}
return jQuery.curCSS( elem, name, force );
},
Okay, that was a wild ride (and imagine a UA doing it on every call to
attr). I want to duplicate the following design, which reads the
height of an image and then puts it back. The image's height should
remain the same.
var height = img.height; // 32
img.height = var; .// 32 (all is well)
There's one glaring issue with the jQuery way and that's curCSS. A
quote from the inline comments of that method says it all:-
// From the awesome hack by Dean Edwards
(!)
// If we're not dealing with a regular pixel number
// but a number that has a weird ending, we need to convert it to
pixels
This is from the IE fork. The hack used is complete bullshit, but
doesn't matter for this example.
Still, this is definitely going to be a "weird ending".
In standards mode, in most browsers, Resig's concoction will spit out:
32. Cool! And only took 10,000 times the effort. It's even (sort
of) symmetrical as the reverse process ends up setting the CSS height
to '32px'.
<img height="32" width="32" src="..." style="border:solid 10px;padding:
10px;height:32px">
In quirks mode, in some browsers (e.g. IE), or if the box model for
the image has been changed through CSS, the result is also 32.
Unfortunately, setting the CSS height to '32px' will be a catastrophe.
In fact, it's not even possible as the borders and padding take up
40px (and that's all that will be left of the image).
The confusion is evident in these methods as well:-
// innerHeight and innerWidth
jQuery.fn["inner" + name] = function(){
return this[0] ?
jQuery.css( this[0], type, false, "padding" ) :
null;
};
// outerHeight and outerWidth
jQuery.fn["outer" + name] = function(margin) {
return this[0] ?
jQuery.css( this[0], type, false, margin ? "margin" : "border" ) :
null;
};
What's wrong with those pictures?
So, even if there were no such things as quirks mode or alternate box
models and every agent supported computed styles (lets out IE), this
would sort of work, but would still be a ridiculous design.
What's the answer to all of this? It's very simple. Use more than
one function. One gets the CSS dimensions, one gets the offset
dimensions (and neither is tangled up with attribute or property
wrappers). A symmetrical setter for the former is trivial and the
latter is do-able as well. There is no need to manually subtract
borders and padding at all and the box model is irrelevant (GIGO).
See My Library (or the upcoming "B is for Box Model" article) for
examples.
Can't measure windows, document or elements; can't query elements by
attribute, can't get/set attributes or properties in any sort of
predictable way and as a bonus, if you act now...
From the creators of isArray and isFunction comes...
isObjectLiteral: function( obj ) {
Yes friends, you too can detect object literals.
if ( toString.call(obj) !== "[object Object]" || typeof obj.nodeType
=== "number" ) {
return false;
}
LOL. I've got to figure they know they screwed up massively with the
initial "overloaded" design, but I guess they are stuck with it.
// not own constructor property must be Object
if ( obj.constructor
&& !hasOwnProperty.call(obj, "constructor")
Oops. So Safari 2 and the like just blow up in the middle of
this.
&& !hasOwnProperty.call(obj.constructor.prototype,
"isPrototypeOf") ) {
return false;
}
//own properties are iterated firstly,
Says who?
//so to speed up, we can test last one if it is own or not
var key;
for ( key in obj ) {}
return key === undefined || hasOwnProperty.call( obj, key );
},
WTF? I've got a great idea to "speed up" this mess. Lose it
altogether.