Re: RFC: Building the Perfect Tabbed Pane (an tutorial article)
- From: David Mark <dmark.cinsoft@xxxxxxxxx>
- Date: Wed, 13 Feb 2008 04:49:22 -0800 (PST)
On Feb 13, 3:46 am, Peter Michaux <petermich...@xxxxxxxxx> wrote:
David Mark wrote:
On Feb 12, 10:26 pm, Peter Michaux <petermich...@xxxxxxxxx> wrote:
I'm writing a blog article about building a decent tabbed pane widget
[snip]
// lib.js ------------------------------------------------------
// test any JavaScript features
// new in NN4+, IE4+, ES3+
// or known to have a bug in some browser
// test all host objects
var LIB = {};
// Some array extras for the app developer.
// These are not used within the library
And certainly shouldn't be for performance reasons.
// to keep library interdependencies low.
That too. I made all of the array stuff optional in my builder.
// These extras don't use the optional
// thisObject argument of native JavaScript 1.6
// Array.prototype.filter but that can easily
// be added here at the cost of some file size.
I think I covered that in the versions I posted to CWR. If not, see
the versions on my site.
LIB.arrayFilter = function(a, f) {
var rs = [];
for (var i=0, ilen=a.length; i<ilen; i++) {
if (f(a[i])) {
You can skip the call to f if a[i] is undefined. This speeds things
up for sparsely populated arrays.
I will add that check and the use of the native filter if it is
available.
[snip]
LIB.arrayForEach = function(a, f) {
for (var i=0, ilen=a.length; i<ilen; i++) {
f(a[i]);
Same here. I think Mozilla's documentation has the source for some
(or all) of these 1.6 array methods as implemented in their engine.
You can't use them verbatim though as they use the in operator.
I'll look at yours more closely.
[snip]
// TODO, feature testing and multiple handlers
LIB.addListener = function(element, eventType, callback) {
element['on'+eventType] = callback;
};
LIB.preventDefault = function(e) {
if (e.preventDefault) {
e.preventDefault();
return;
}
// can't test for returnValue directly?
Why test it at all?
This whole (two function) event section is lame. The
Clearly. I assumed they were just placeholders.
LIB.preventDefault section shouldn't be defined unless it can work.
There's really no telling. This is what I use:
cancelDefault = function(e) {
if (e.preventDefault) { e.preventDefault(); }
if (global.event) { global.event.returnValue = false; }
return false;
};
Regardless of the event model used, a listener can return
cancelDefault(e) and it will work (except click/dblclick in Safari 1.x
as I do use addEventListener when available.)
This function came from another library and is really not up to snuff.
A group discussion about events sooner than later would be good. It is
not an easy problem in general and it is the most important library.
Agreed.
[snip]
// ---------------------------------------------------
(function() {
var isRealObjectProperty = function(o, p) {
return !!(typeof(o[p]) == 'object' && o[p]);
};
LIB.isRealObjectProperty = isRealObjectProperty;
var isHostMethod = function(o, m) {
var t = typeof(o[m]);
return !!(((t=='function' || t=='object') && o[m]) ||
t == 'unknown');
};
LIB.isHostMethod = isHostMethod;
if (!(isRealObjectProperty(this, 'document'))) {
return;
}
var doc = this.document;
I would set this to null once feature testing is complete. I
reference the default document as global.document thereafter. It
isn't a big concern here as you didn't define your addListener
function inside here.
Ahh, circular memory leaks? I was thinking about this recently. All
functions have "document" in their closure which references
document.body which has all the document elements as descendants. This
The issue I was referring to was related to using DOM0 or attachEvent
on the document. I don't think the descendants enter into it, but as
body is a property of document, attaching a listener to it would
create this circular reference:
body => body.onclick => document => body
As my whole library resides in a single closure, I needed to set doc
to null after feature testing to avoid this issue.
will include whichever element a function is observing. There is
always a very indirect circle. For some reason, do these indirect
circles not cause the IE memory leak. I think they should.
I don't think so. Just the document and the body as it is referenced
by a property of the document. Then again, it is IE, so who knows for
sure?
if (isRealObjectProperty(doc, 'documentElement')) {
IIRC, this excludes IE4.
That is ok. For this article, as long as IE4/NN4 don't syntax or
runtime error that is ok. They may not even have enough CSS support to
make a tabbed pane look any good unless table layouts are used.
Perhaps not.
[snip]
// Test both interfaces specified in the DOM
if (isHostMethod(doc, 'getElementsByTagName') &&
typeof getAnElement == 'function' &&
&& getAnElement
You know that the getAnElement variable exists.
getAnElement is defined in an if statement above.
Whether it is defined or not is irrelevant. The issue is whether the
variable exists. It exists as it was created as soon as the
containing function was entered and can therefore be tested by boolean
type conversion. This is why I don't like declaring variables inside
blocks (it is misleading.)
isHostMethod(getAnElement(), 'getElementsByTagName')) {
// One possible implementation for developers
// in a situation where it is not a problem that
// IE5 thinks doctype and comments are elements.
LIB.getEBTN = function(tag, root) {
root = root || doc;
var els = root.getElementsByTagName(tag);
if (tag == '*' &&
!els.length &&
isHostMethod(root, 'all')) {
There is a potential problem here (and I made the same mistake several
times in my library.) The isHostMethod function should only be used
to test properties that will be called. It allows for properties of
type "unknown", which are safe to call, but unsafe to evaluate.
els = root.all;
Here is where it would blow up if some future version of IE implements
documents as ActiveX objects (or whatever the hell it is that returns
"unknown" for typeof operations.) You can't use isRealObjectProperty
either as Safari makes document.all callable (typeof root.all ==
'function'.) I think I mentioned this in one of the tickets. We need
a way a parameter to tell isHostMethod to exclude "unknown" properties
when appropriate. This is a perfect example.
Crap. I cannot blame people for using the mainstream libraries. It
doesn't mean their code is good but they don't want to worry about
this stuff. It is not fun.
The mainstream libraries offer zero protection from such issues.
Regardless, it is a simple matter of adding an additional parameter to
isHostMethod to exclude "unknown" types. This parameter should be set
if the property in question will be evaluated rather than called (as
in this case.) Document it as such and you are done.
}
return els;
};
}
if (isHostMethod(doc, 'getElementById')) {
// One possible implementation for developers
// not troubled by the name and id attribute
// conflict in IE
LIB.getEBI = function(id, d) {
return (d || doc).getElementById(id);
};
}
if (LIB.getEBTN &&
LIB.getEBI &&
typeof getAnElement == 'function' &&
getAnElement &&
You can skip the typeof operation for library functions in other
modules as well, as long as they are dependencies that are
automatically inserted by the build process.
But getAnElement may not be defined.
But it is certainly declared at this point, which is all that matters.
(function() {
var el = getAnElement();
return typeof el.nodeType == 'number' &&
IIRC, this excludes IE5.0 and under.
It does work in my multi-IE install of IE5.01 but not in IE4.
I could have sworn that IE5.0 did not support nodeType. Either I am
mistaken of the multi-IE install is faulty. I would check
quirksmode.org, but they have inexplicably removed most of the IE5
data at this point. I don't have a stand-alone IE5 install to test,
but I am pretty sure my multi-IE install is sound. I'll check it when
I get a chance.
[snip]
I really like the XPath-enabled version(s) better. The simple version
isn't that much more code and is considerably faster in browsers that
support document.evaluate.
There are so many ways to implement these things. The library here
just has to be correct for the sake of article completeness. It
doesn't have to be optimal. In fact, less concepts are better for an
article.
Agreed.
[snip]
if (typeof LIB.getAnElement != 'undefined' &&
if (LIB.getAnElement &&
typeof LIB.getAnElement().className == 'string') {
When you add the more robust version of getAnElement to support IE4,
I'm not going to add that version of getAnElement here.
it would be a good idea to test its result.
Why? If getAnElement is defined then it should work. Part of defining
it is testing that it will work.
In some cases it is not possible to know for sure. But in this case,
you are correct as all it does is return the first element it finds.
We know the document that contains the script has at least one
element. If you pass a different document to it, that would be
another story.
// The RegExp support need here
// has been available since NN4 & IE4
LIB.hasClass = function(el, className) {
return (new RegExp(
'(^|\\s+)' + className + '(\\s+|$)')).test(el.className);
};
LIB.addClass = function(el, className) {
if (LIB.hasClass(el, className)) {
return;
}
el.className = el.className + ' ' + className;
};
LIB.removeClass = function(el, className) {
el.className = el.className.replace(
new RegExp('(^|\\s+)' + className + '(\\s+|$)', 'g'), ' ');
// in case of multiple adjacent with a single space
if ( LIB.hasClass(el, className) ) {
arguments.callee(el, className);
}
I'm missing something here. Can you elaborate on this?
"foo foo".replace(/(^|\s+)foo(\s+|$)/g, ' ');
returns
"foo"
I would consider that a valid result for data that has clearly been
corrupted by another process. I don't think I would fix it here and
shield the developer from a mistake that happened elsewhere.
It is something to do with how regular expressions are matched
internally. The space after the first foo is associated with the first
foo. That means the second foo doesn't have either the string start or
space before it.
Right.
This is code I got from YUI.
YUI is a poor source of code. I took a little time and read through
some of it recently. To be fair, I don't know what version it was as
I just stumbled across the archive. Regardless, it was God-awful.
IIRC, the event handling portion was full of browser sniffing (and
that is the part touted as its best bit.) Certainly it is much better
code than that found in Prototype or jQuery, but that isn't saying
much. When I get a chance, I will check out the latest version and if
it hasn't improved (or if God forbid I was actually looking at the
latest version), I will give it the jQuery treatment (dissection and
condemnation.)
[snip]
Thanks for all your detailed comments. I am glad there is somewhere
like c.l.js where a response like this is possible. Part of the reason
for this article is to bottle up some of the discussion for
consumption.
Yes, the Web would be better place if more developers realized that
blogs make terrible discussion forums (particularly for technical
topics.) It seems obvious considering they don't even support
threads. I've seen several JS-related blogs that don't support
posting code, cropping it at the first instance of ">".
@scriptkiddie: Your code didn't post.
@somebodyelse: Thanks!
@blogmaven: You are wrong.
[Comments closed]
How did those things ever supplant Usenet?
.
- Follow-Ups:
- Re: RFC: Building the Perfect Tabbed Pane (an tutorial article)
- From: Peter Michaux
- Re: RFC: Building the Perfect Tabbed Pane (an tutorial article)
- References:
- RFC: Building the Perfect Tabbed Pane (an tutorial article)
- From: Peter Michaux
- Re: RFC: Building the Perfect Tabbed Pane (an tutorial article)
- From: David Mark
- Re: RFC: Building the Perfect Tabbed Pane (an tutorial article)
- From: Peter Michaux
- RFC: Building the Perfect Tabbed Pane (an tutorial article)
- Prev by Date: Re: Submiting a form using javascript
- Next by Date: How to call global function having its name?
- Previous by thread: Re: RFC: Building the Perfect Tabbed Pane (an tutorial article)
- Next by thread: Re: RFC: Building the Perfect Tabbed Pane (an tutorial article)
- Index(es):
Relevant Pages
|
Loading