RFC: Building the Perfect Tabbed Pane (an tutorial article)

P

Peter Michaux

I'm writing a blog article about building a decent tabbed pane widget
for a web page on the general web (anyone with a browser and internet
connection has access). I've never seen an article that deals with all
of the feature testing and sequential rendering issues and I think an
article like this is needed. If a copy of the article would be agreed
upon and beneficial to the jibbering site then I would support that.
It isn't exactly an easy task to write a widget for thousands of
possible browser versions and configurations. I can understand why
people give up when handed this task and only support recent browsers
and use sniffing. More written about building widgets in such a harsh
environment would help, I am sure.

Below I've included the four files involved in the example:

- index.html
- tabbedPane.js
- tabbedPane.css
- lib.js

The example is also temporarily available on the web

<URL: http://peter.michaux.ca/temp/tabbedPane/index.html>

The example code is not finished. Any feedback would be greatly
appreciated.

The goal of the HTML mark up was to make it as easy as possible for
the content author. I don't like how many tabbed pane widgets require
a ul element at the top and then the content author must connect the
tab with the pane using some convention for element id attributes.
That is far more difficult than it should be for the content author. I
know first hand that making it easier for them is greatly appreciated
and also easier on me.

I've feature tested almost maximally. All host objects are tested and
any language feature that isn't ancient is tested. The objective is to
have all the code run in IE4+ and NN4+ browsers without throwing any
errors: syntax or runtime.

I've tried to be relatively modern by including a css selector query
function. Surely this task could be done without such a function but
to a certain degree I am trying to simulate how developers use
libraries. When more widgets are in a page the cost of the library
size is not so great.

The events part of the library is a problem area for several reasons.
A big reason is due to my ignorance about screen readers. If a blind
visitor with a browser that has JavaScript enabled is using a screen
reader, do the tab elements need to have anchor elements so that the
screen reader reads the tabs. If anchor elements are required then I
want to use preventDefault to stop the browser from changing the URL
and even potentially reloading the page when a tab is clicked. Safari
1.9 and early v2 releases didn't support preventDefault when the
callback function is attached with addEventListener. If DOM0 handler
is used as a workaround I need to know that it will work before
manipulating the DOM into a tabbed pane. A feature test for DOM0 is
not so easy (although David Mark suggested one that works in at least
Firefox.)

There are many ways to "architect" the actual mechanics that run the
tabbed pane after it is all set up. I decided to go with a closure
system. This was just a choice. The mechanics that run this example
feel super light weight to me: just a little closure for each tab that
knows it's tab and pane and the current tab and pane. It feels like
the sports car version to me. OOP versions are good for some project
specs but feel like tanks. I may write and article that looks at
different code designs for different design requirements.

I look forward to any suggestions.

Thanks,
Peter


// index.html --------------------------------------------------

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
"http://www.w3.org/TR/html4/strict.dtd">

<html>
<head>

<title>Page Title</title>

<script src="lib.js" type="text/javascript"></script>
<script src="tabbedPane.js" type="text/javascript"></script>

</head>
<body>

<div class="sections">

<div class="section first">
<h2>One</h2>
<p>
Lorem ipsum dolor sit amet, consectetur adipisicing elit,
sed do eiusmod tempor incididunt ut labore et dolore magna
aliqua. Ut enim ad minim veniam, quis nostrud exercitation.
</p>
</div>

<div class="section">
<h2>Two</h2>
<p>
Duis aute irure dolor in reprehenderit in voluptate
velit esse cillum dolore eu fugiat nulla pariatur.
</p>
</div>

<div class="section">
<h2>Three</h2>
<p>
Excepteur sint occaecat cupidatat non proident, sunt
in culpa qui officia deserunt mollit anim id est laborum.
</p>
</div>

</div>

</body>
</html>


// tabbedPane.js --------------------------------------------------

// Test what can be tested as soon as possible
// Check that the library loaded
if (typeof LIB != 'undefined' &&
// Test for library functions I use directly
LIB.getAnElement &&
LIB.getDocumentElement &&
LIB.isRealObjectProperty &&
LIB.isHostMethod &&
LIB.arrayFilter &&
LIB.arrayForEach &&
LIB.addListener &&
LIB.querySelector &&
LIB.hasClass &&
LIB.removeClass &&
LIB.addClass &&
// Test for host objects and methods I use directly
LIB.isRealObjectProperty(this, 'window') &&
LIB.isRealObjectProperty(this, 'document') &&
LIB.isHostMethod(this.document, 'write') &&
LIB.isHostMethod(this.document, 'createElement') &&
(function() {
var el = LIB.getAnElement();
return LIB.isHostMethod(el, 'appendChild') &&
LIB.isHostMethod(el, 'insertBefore') &&
LIB.isRealObjectProperty(el, 'firstChild') &&
LIB.isRealObjectProperty(el, 'childNodes') &&
typeof el.innerHTML == 'string';
})()) {

(function() {

var doc = this.document;

var cc = 'current'; // TODO

var enliven = function(c, t, p) {
LIB.addListener(t, 'click', function() {
// TODO prevent default and use anchor elements for tabs
// TODO what about keyboard accessibility?

// avoid potential flicker if user clicks current tab
if (t == c.t) {
return;
}
LIB.removeClass(c.t, cc);
LIB.removeClass(c.p, cc);
c.t = t;
c.p = p;
LIB.addClass(t, cc);
LIB.addClass(p, cc);
});
};

var init = function(w) {
var ts = doc.createElement('ul'),
first = true,
c,
t,
h,
t;
LIB.addClass(ts, 'tabs');
LIB.arrayForEach(
LIB.arrayFilter(
w.childNodes,
function(s) {
return LIB.hasClass(s, 'section');
}),
function(p) {
t = doc.createElement('li');
if (first) {
c = {t:t, p:p};
LIB.addClass(t, cc);
LIB.addClass(p, cc);
first = false;
}
enliven(c, t, p);
h = LIB.querySelector('h2', p)[0];
t.innerHTML = h ? h.innerHTML : 'tab';
ts.appendChild(t);
});
w.insertBefore(ts, w.firstChild);
};

// Test that a pane really is out of the page
// when it is not current and that it has some
// height when it is current. This is the critical
// test to make sure the tabbed pane will work.
var supportsDisplayCss = function() {
var middle = doc.createElement('div');

if (LIB.isRealObjectProperty(doc, 'body') &&
LIB.isHostMethod(doc.body, 'removeChild') &&
typeof middle.offsetHeight == 'number') {

var outer = doc.createElement('div'),
inner = doc.createElement('div');

LIB.addClass(outer, 'tabbedPanesEnabled');
LIB.addClass(middle, 'sections');
LIB.addClass(inner, 'section');
inner.innerHTML = '.';
middle.appendChild(inner);
outer.appendChild(middle);
doc.body.appendChild(outer);
var h = outer.offsetHeight;
LIB.addClass(inner, 'current')
var doesSupport = (h == 0 && outer.offsetHeight > 0);
doc.body.removeChild(outer);
return doesSupport;
}
return false;
};

// We don't know for sure at this point that the tabbed pane
// will work. We have to wait for window.onload to finish
// the tests. We do know we can give the pages some style to use
// during the page load because we can "get out of trouble"
// when window.onload fires. This is
// because the functions used to get out of trouble
// have been feature tested.
doc.write('<link href="tabbedPane.css"'+
' rel="stylesheet" type="text/css">');

LIB.addListener(this.window, 'load', function() {
// Cannot test that CSS support works until window.onload.
// This also checks that the stylesheet loaded
if (supportsDisplayCss()) {
LIB.arrayForEach(LIB.querySelector('.sections'), init);
LIB.addClass(LIB.getDocumentElement(),
'tabbedPanesEnabled');
}
else {
// "get out of trouble"
LIB.addClass(LIB.getDocumentElement(),
'tabbedPanesDisabled');
}
});

})();

}

// tabbedPane.css ----------------------------------------------

/* styles for use until window.onload if
the browser is "tentatively" capable */
..sections .section {
display:none;
}
..sections .first {
display:block;
}

/* if feature tests for tabbed panes fail */
..tabbedPanesDisabled .section {
display:block;
}

/* if feature tests for for tabbed panes pass */
..tabbedPanesEnabled li.current {
background:red;
}
..tabbedPanesEnabled .sections .section, .displayTestElement {
display:none;
}
..tabbedPanesEnabled .sections div.current {
display:block;
}


// 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
// to keep library interdependencies low.
// 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.
LIB.arrayFilter = function(a, f) {
var rs = [];
for (var i=0, ilen=a.length; i<ilen; i++) {
if (f(a)) {
rs[rs.length] = a;
}
}
return rs;
};

LIB.arrayForEach = function(a, f) {
for (var i=0, ilen=a.length; i<ilen; i++) {
f(a);
}
};

// ---------------------------------------------------

// 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?
if (e.cancelBubble !== undefined){
e.returnValue = false;
return;
}
};


// ---------------------------------------------------

(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;

if (isRealObjectProperty(doc, 'documentElement')) {
var getAnElement = function(d) {
return (d || doc).documentElement;
};
LIB.getAnElement = getAnElement;
LIB.getDocumentElement = getAnElement;
}

// Test both interfaces specified in the DOM
if (isHostMethod(doc, 'getElementsByTagName') &&
typeof getAnElement == 'function' &&
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')) {
els = root.all;
}
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' &&
(function() {
var el = getAnElement();
return typeof el.nodeType == 'number' &&
typeof el.tagName == 'string' &&
typeof el.className == 'string' &&
typeof el.id == 'string'
})()) {

// One possible selector compiler implementation
// that can handle selectors with a tag name,
// class name and id.
//
// use memoization for efficiency
var cache = {};
var compile = function(s) {
if (cache) {
return cache;
}

var m, // regexp matches
tn, // tagName in s
id, // id in s
cn, // className in s
f; // the function body

m = s.match(/^([^#\.]+)/);
tn = m ? m[1] : null;

m = s.match(/#([^\.]+)/);
id = m ? m[1] : null;

m = s.match(/\.([^#]+)/);
cn = m ? m[1] : null;

f = 'var i,els,el,m,ns=[];';
if (id) {
f += 'if (!d||(d.nodeType==9||(!d.nodeType&&!d.tagName))){'+
'els=((el=LIB.getEBI("'+id+'",d))?[el]:[]);' +
((!cn&&!tn)?'return els;':'') +
'}else{' +
'els=LIB.getEBTN("'+(tn||'*')+'",d);' +
'}';
}
else {
f += 'els=LIB.getEBTN("'+(tn||'*')+'",d);';
}

if (id || cn) {
f += 'i=els.length;' +
'while(i--){' +
'el=els;' +
'if(';
if (id) {
f += 'el.id=="'+id+'"';
}
if ((cn||tn) && id) {
f += '&&';
}
if (tn) {
f += 'el.tagName.toLowerCase()=="' + tn + '"';
}
if (cn && tn) {
f += '&&';
}
if (cn) {
f += '((m=el.className)&&' +
'(" "+m+" ").indexOf(" '+cn+' ")>-1)';
}
f += '){' +
'ns[ns.length]=el;' +
'}' +
'}';

f += 'return ns.reverse()';
}
else {
f += 'return els;';
}

// http://elfz.laacz.lv/beautify/
//console.log('function f(d) {' + f + '}');

f = new Function('d', f);
cache = f;
return f;
}

LIB.querySelector = function(selector, rootEl) {
return (compile(selector))(rootEl);
};

}

})();

// ------------------------------------------

if (typeof LIB.getAnElement != 'undefined' &&
typeof LIB.getAnElement().className == 'string') {

// 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);
}
};

}
 
D

David Mark

I'm writing a blog article about building a decent tabbed pane widget
for a web page on the general web (anyone with a browser and internet
connection has access). I've never seen an article that deals with all
of the feature testing and sequential rendering issues and I think an
article like this is needed. If a copy of the article would be agreed
upon and beneficial to the jibbering site then I would support that.
It isn't exactly an easy task to write a widget for thousands of
possible browser versions and configurations. I can understand why
people give up when handed this task and only support recent browsers
and use sniffing. More written about building widgets in such a harsh
environment would help, I am sure.

Below I've included the four files involved in the example:

- index.html
- tabbedPane.js
- tabbedPane.css
- lib.js

The example is also temporarily available on the web

<URL:http://peter.michaux.ca/temp/tabbedPane/index.html>

Did you mean to leave the UL unstyled (other than the background color
of the selected item?) It is possible to make list items look just
like the tabs in a property sheet.
The example code is not finished. Any feedback would be greatly
appreciated.

The goal of the HTML mark up was to make it as easy as possible for
the content author. I don't like how many tabbed pane widgets require
a ul element at the top and then the content author must connect the
tab with the pane using some convention for element id attributes.

Certainly I don't like anything that relies on an ID (or className)
convention to define element relationships.
That is far more difficult than it should be for the content author. I
know first hand that making it easier for them is greatly appreciated
and also easier on me.

I've feature tested almost maximally. All host objects are tested and
any language feature that isn't ancient is tested. The objective is to
have all the code run in IE4+ and NN4+ browsers without throwing any
errors: syntax or runtime.

I've tried to be relatively modern by including a css selector query
function. Surely this task could be done without such a function but
to a certain degree I am trying to simulate how developers use
libraries. When more widgets are in a page the cost of the library
size is not so great.

The events part of the library is a problem area for several reasons.
A big reason is due to my ignorance about screen readers. If a blind

There is no way to predict what screen readers and aural browsers will
do with dynamic content. Some newer ones try to deal with the issue
with varying results. Some aural browsers follow the aural style
rules, but screen readers typically read what they "see" on the
screen. Some applications are hard to pigeon-hold. Is Opera Voice an
aural browser? It follows some of the aural style rules, but it won't
read anything that isn't visible on the screen. It would be simple
enough for the sight-impaired to disable scripting, but of course that
breaks most of the current Web as most Web developers consider anyone
who disables scripting to be unworthy of their content.
visitor with a browser that has JavaScript enabled is using a screen
reader, do the tab elements need to have anchor elements so that the
screen reader reads the tabs. If anchor elements are required then I

Regardless of the state of scripting, the tabs must be accessible by
keyboard. The text in the list items should be wrapped in anchors or
buttons. Buttons make more sense, but are impossible to style in some
browsers.
want to use preventDefault to stop the browser from changing the URL
and even potentially reloading the page when a tab is clicked. Safari
1.9 and early v2 releases didn't support preventDefault when the
callback function is attached with addEventListener. If DOM0 handler
is used as a workaround I need to know that it will work before
manipulating the DOM into a tabbed pane. A feature test for DOM0 is
not so easy (although David Mark suggested one that works in at least
Firefox.)

It should work in all browsers that support setAttribute. As for
browsers like IE4/NN4, you can try to check for something other than
undefined in the onclick (or whatever) property of an arbitrary
element and then decide what your default assumption should be for the
rest (it seems likely that browsers with script support that predate
addEventListener/attachEvent will have the DOM0 event model.)
There are many ways to "architect" the actual mechanics that run the
tabbed pane after it is all set up. I decided to go with a closure
system. This was just a choice. The mechanics that run this example
feel super light weight to me: just a little closure for each tab that
knows it's tab and pane and the current tab and pane. It feels like

That's what I did with my popup menu script.
the sports car version to me. OOP versions are good for some project
specs but feel like tanks. I may write and article that looks at
Exactly.

different code designs for different design requirements.

I look forward to any suggestions.
[snip]

// index.html --------------------------------------------------

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
"http://www.w3.org/TR/html4/strict.dtd">

<html>
<head>

<title>Page Title</title>

<script src="lib.js" type="text/javascript"></script>
<script src="tabbedPane.js" type="text/javascript"></script>

</head>
<body>

<div class="sections">

<div class="section first">
<h2>One</h2>
<p>
Lorem ipsum dolor sit amet, consectetur adipisicing elit,
sed do eiusmod tempor incididunt ut labore et dolore magna
aliqua. Ut enim ad minim veniam, quis nostrud exercitation.
</p>
</div>

<div class="section">
<h2>Two</h2>
<p>
Duis aute irure dolor in reprehenderit in voluptate
velit esse cillum dolore eu fugiat nulla pariatur.
</p>
</div>

<div class="section">
<h2>Three</h2>
<p>
Excepteur sint occaecat cupidatat non proident, sunt
in culpa qui officia deserunt mollit anim id est laborum.
</p>
</div>

</div>

</body>
</html>

// tabbedPane.js --------------------------------------------------

// Test what can be tested as soon as possible
// Check that the library loaded
if (typeof LIB != 'undefined' &&
// Test for library functions I use directly

I would test if it is an object and that it is truthy (to exclude
null.) You can't trust global variables, so it is best to interrogate
them as thoroughly as possible.
LIB.getAnElement &&
LIB.getDocumentElement &&
LIB.isRealObjectProperty &&
LIB.isHostMethod &&
LIB.arrayFilter &&
LIB.arrayForEach &&
LIB.addListener &&
LIB.querySelector &&
LIB.hasClass &&
LIB.removeClass &&
LIB.addClass &&
// Test for host objects and methods I use directly
LIB.isRealObjectProperty(this, 'window') &&

I don't do this test as I never reference the window object. It is
faster to reference the global object directly.
LIB.isRealObjectProperty(this, 'document') &&
LIB.isHostMethod(this.document, 'write') &&
LIB.isHostMethod(this.document, 'createElement') &&
(function() {
var el = LIB.getAnElement();
return LIB.isHostMethod(el, 'appendChild') &&
LIB.isHostMethod(el, 'insertBefore') &&
LIB.isRealObjectProperty(el, 'firstChild') &&
LIB.isRealObjectProperty(el, 'childNodes') &&

Careful with this one. I use isHostMethod to test it as I do for
document.images, document.forms, etc. (Safari makes some host objects
that take indeces as arguments callable.)
typeof el.innerHTML == 'string';
})()) {

(function() {

var doc = this.document;

var cc = 'current'; // TODO

What is there to do here?
var enliven = function(c, t, p) {
LIB.addListener(t, 'click', function() {

I typically create "shortcuts" to API properties. It speeds things
property access and does wonders for (aggressive) minification. I
know you don't care for obfuscation, due to the possibility of
introducing bugs, but recently I have been using the YUI
"compressor" (misnomer) on a 7000+ line project that is teeming with
closures and it has never burned me. Granted, I always seek JSLint's
approval before running my build process.
// TODO prevent default and use anchor elements for tabs
// TODO what about keyboard accessibility?

You answered the second question.

<h2><a name="two">Two</a></h2>

Then turn it into a link when you create the widget.

As for Safari 1.x., if the dynamically created href is "#", the worst
case is that it will scroll to the top on click. It may not do
anything but tack an empty hash onto the address.
// avoid potential flicker if user clicks current tab
if (t == c.t) {
return;
}
LIB.removeClass(c.t, cc);
LIB.removeClass(c.p, cc);
c.t = t;
c.p = p;
LIB.addClass(t, cc);
LIB.addClass(p, cc);
});
};

I find this hard to follow. What are c, t and p? I think this is
another reason to minify aggressively (it allows you to use long,
descriptive variable names with impunity.) Of course, it won't help
with the t and p properties of c.
var init = function(w) {
var ts = doc.createElement('ul'),
first = true,
c,
t,
h,
t;

Make sure ts is truthy (may be overly paranoid.)
LIB.addClass(ts, 'tabs');
LIB.arrayForEach(
LIB.arrayFilter(
w.childNodes,
function(s) {
return LIB.hasClass(s, 'section');
}),
function(p) {
t = doc.createElement('li');

Okay, it appears t is for tab and p is for pane and cc is class added
to indicate that the tab is selected.
if (first) {
c = {t:t, p:p};
LIB.addClass(t, cc);
LIB.addClass(p, cc);

I assume you are adding and removing panes from the layout by adding
and removing the "current" class. I would set the display style
directly. Fiddling with a widget's accompanying style sheet should
never have the potential to break the its behavior.
first = false;
}
enliven(c, t, p);
h = LIB.querySelector('h2', p)[0];

You should generalize this to use any level headline. And why not
getEBTN for this (seems like a more direct approach.)
t.innerHTML = h ? h.innerHTML : 'tab';

I would not use innerHTML for this as you have appendChild at your
disposal.
ts.appendChild(t);
});
w.insertBefore(ts, w.firstChild);
};

// Test that a pane really is out of the page
// when it is not current and that it has some
// height when it is current. This is the critical
// test to make sure the tabbed pane will work.
var supportsDisplayCss = function() {
var middle = doc.createElement('div');

Should use the createElement wrapper (2 lines IIRC) to allow for XHTML
support.
if (LIB.isRealObjectProperty(doc, 'body') &&

This will fail in some browsers when using XHTML (see getBodyElement.)
LIB.isHostMethod(doc.body, 'removeChild') &&
typeof middle.offsetHeight == 'number') {

var outer = doc.createElement('div'),
inner = doc.createElement('div');

LIB.addClass(outer, 'tabbedPanesEnabled');
LIB.addClass(middle, 'sections');
LIB.addClass(inner, 'section');
inner.innerHTML = '.';
middle.appendChild(inner);
outer.appendChild(middle);
doc.body.appendChild(outer);
var h = outer.offsetHeight;
LIB.addClass(inner, 'current')
var doesSupport = (h == 0 && outer.offsetHeight > 0);

Looks good (presuming all browsers behave as expected with
offsetHeight and display:none. It seems you are checking if the style
sheet loaded, as well as if style is enabled and that user style
sheets are not interfering. Good deal.

I wrote a generalized "cssEnabled" function a while back that used
similar logic, but have never used it for anything in production. It
seemed to me at the time that I needed widgets to work even if CSS was
toggled after the page was loaded. I do like the specific test for
this widget as I have never dealt with the issue of a (lunatic) user
adding an !important display rule.
doc.body.removeChild(outer);
return doesSupport;
}
return false;
};

// We don't know for sure at this point that the tabbed pane
// will work. We have to wait for window.onload to finish
// the tests. We do know we can give the pages some style to use
// during the page load because we can "get out of trouble"
// when window.onload fires. This is
// because the functions used to get out of trouble
// have been feature tested.

You can skip all of this unless this test passes:

var el = getAnElement();
if (el && el.style && typeof el.style.display == 'string') {
// Add style rule(s)
// Attach load listener
}

doc.write('<link href="tabbedPane.css"'+
' rel="stylesheet" type="text/css">');

IIRC, this will crash some (or all) revisions of NN4.x. It also will
not work with XHTML (I know it is a dead language on the Web, but it
is useful for Intranets.) Better to add the needed rules via DOM
manipulation. Of course, that logic must reside in a script block
outside of the head element, else you risk the dreaded "Operation
Aborted" error in IE.
LIB.addListener(this.window, 'load', function() {

I think "this" is sufficient as this.window points back to the global
object.
// Cannot test that CSS support works until window.onload.
// This also checks that the stylesheet loaded
if (supportsDisplayCss()) {
LIB.arrayForEach(LIB.querySelector('.sections'), init);
LIB.addClass(LIB.getDocumentElement(),
'tabbedPanesEnabled');
}
else {
// "get out of trouble"
LIB.addClass(LIB.getDocumentElement(),
'tabbedPanesDisabled');
}

Interesting that you add classes to the documentElement. I haven't
had occasion to do that.
});

})();

}

// tabbedPane.css ----------------------------------------------

/* styles for use until window.onload if
the browser is "tentatively" capable */
.sections .section {
display:none;}

.sections .first {
display:block;

}

As mentioned, I would add these rules directly and have one less style
sheet to download.
/* if feature tests for tabbed panes fail */
.tabbedPanesDisabled .section {
display:block;

}

Same here. Then you don't need to add a class to the documentElement.
/* if feature tests for for tabbed panes pass */
.tabbedPanesEnabled li.current {
background:red;}

.tabbedPanesEnabled .sections .section, .displayTestElement {
display:none;}

.tabbedPanesEnabled .sections div.current {
display:block;

}

As mentioned, I don't think these display rules belong in a style
sheet. They shouldn't be exposed to authors as fiddling with them
will only break the behavior of the widget.
// 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
// to keep library interdependencies low.
// 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.
LIB.arrayFilter = function(a, f) {
var rs = [];
for (var i=0, ilen=a.length; i<ilen; i++) {
if (f(a)) {


You can skip the call to f if a is undefined. This speeds things
up for sparsely populated arrays.
rs[rs.length] = a;
}
}
return rs;

};

LIB.arrayForEach = function(a, f) {
for (var i=0, ilen=a.length; i<ilen; i++) {
f(a);


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 think I submitted a CWR ticket with wrappers for these that take
advantage of the native methods when available (which are obviously
faster.)
// ---------------------------------------------------

// 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?
if (e.cancelBubble !== undefined){

I would skip this inference.
e.returnValue = false;
return;

return false;
}

};

// ---------------------------------------------------

(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.
if (isRealObjectProperty(doc, 'documentElement')) {

IIRC, this excludes IE4.
var getAnElement = function(d) {
return (d || doc).documentElement;
};

The slightly longer version of this will support IE4 (and virtually
anything else.)
LIB.getAnElement = getAnElement;
LIB.getDocumentElement = getAnElement;
}

// Test both interfaces specified in the DOM
if (isHostMethod(doc, 'getElementsByTagName') &&
typeof getAnElement == 'function' &&

&& getAnElement

You know that the getAnElement variable exists.
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.
}
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.
(function() {
var el = getAnElement();
return typeof el.nodeType == 'number' &&

IIRC, this excludes IE5.0 and under.
typeof el.tagName == 'string' &&
typeof el.className == 'string' &&
typeof el.id == 'string'
})()) {

// One possible selector compiler implementation
// that can handle selectors with a tag name,
// class name and id.
//
// use memoization for efficiency
var cache = {};
var compile = function(s) {
if (cache) {
return cache;
}

var m, // regexp matches
tn, // tagName in s
id, // id in s
cn, // className in s
f; // the function body

m = s.match(/^([^#\.]+)/);
tn = m ? m[1] : null;

m = s.match(/#([^\.]+)/);
id = m ? m[1] : null;

m = s.match(/\.([^#]+)/);
cn = m ? m[1] : null;

f = 'var i,els,el,m,ns=[];';
if (id) {
f += 'if (!d||(d.nodeType==9||(!d.nodeType&&!d.tagName))){'+
'els=((el=LIB.getEBI("'+id+'",d))?[el]:[]);' +


You really shouldn't reference LIB internally (other than to populate
it) for performance and style reasons.
((!cn&&!tn)?'return els;':'') +
'}else{' +
'els=LIB.getEBTN("'+(tn||'*')+'",d);' +
'}';
}
else {
f += 'els=LIB.getEBTN("'+(tn||'*')+'",d);';
}

if (id || cn) {
f += 'i=els.length;' +
'while(i--){' +
'el=els;' +
'if(';
if (id) {
f += 'el.id=="'+id+'"';
}
if ((cn||tn) && id) {
f += '&&';
}
if (tn) {
f += 'el.tagName.toLowerCase()=="' + tn + '"';
}
if (cn && tn) {
f += '&&';
}
if (cn) {
f += '((m=el.className)&&' +
'(" "+m+" ").indexOf(" '+cn+' ")>-1)';
}
f += '){' +
'ns[ns.length]=el;' +
'}' +
'}';

f += 'return ns.reverse()';
}
else {
f += 'return els;';
}

//http://elfz.laacz.lv/beautify/
//console.log('function f(d) {' + f + '}');

f = new Function('d', f);
cache = f;
return f;
}

LIB.querySelector = function(selector, rootEl) {
return (compile(selector))(rootEl);
};


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.
}

})();

// ------------------------------------------

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,
it would be a good idea to test its result.
// 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?

Despite the verbosity of my comments, most of them are quibbles. This
is certainly the right way to create a widget. Unfortunately, it
seems that most Web developers follow a far different path:

1. Download Prototype, jQuery or some other amateurish garbage, along
with assorted plug-ins
2. Write widget code that is married to the library in terms of
syntax, as well as (typically ineffectual) feature testing
3. Put a "Best viewed in three or four modern browsers" warning on
pages that use the widget
4. Download patches to the library for eternity, ignoring the fact
that the library authors will never really get it right
5. Upload widget to library-specific repository so developers with
similar delusions can break their sites as well
6. Tell anyone who complains that they aren't living in the "real
world" (which apparently doesn't have mobile devices, set top boxes or
off-brand browsers of any kind)
7. Exhort everyone else to follow their lead as their widget code is
so simple and compact (never mind that it has 150K of dependencies and
is prone to catastrophic failure)

But hey, it works for Google. Unfortunately for the rest of us, the
equation is not:

incompetence = Web success

But rather:

incompetence + lots of cash from investors = Web success

But I digress. For those who need an example of how to write widgets
for applications on the public Internet, this is a good example. With
some minor modifications, it could be a great example.
 
P

Peter Michaux

David said:
I'm writing a blog article about building a decent tabbed pane widget
[snip]

Did you mean to leave the UL unstyled (other than the background color
of the selected item?)

For now yes. I don't want to cloud what is related to scripting and
what is only for looks.


[snip]

Regardless of the state of scripting, the tabs must be accessible by
keyboard. The text in the list items should be wrapped in anchors or
buttons. Buttons make more sense, but are impossible to style in some
browsers.

You are right. I need to make this keyboard accessible. Given how so
many websites are not navigable by keyboard, there must be a way for
people to move their cursor around with the keyboard to simulate a
mouse.

[snip]
I would test if it is an object and that it is truthy (to exclude
null.) You can't trust global variables, so it is best to interrogate
them as thoroughly as possible.

Even more specific would be to test that it is an object with

typeof LIB == 'object'


I don't do this test as I never reference the window object. It is
faster to reference the global object directly.

Interesting. Thanks. I will do that.

Careful with this one. I use isHostMethod to test it as I do for
document.images, document.forms, etc. (Safari makes some host objects
that take indeces as arguments callable.)

Are you only referring to the tests for firstChild and childNodes?

For the sake of curiosity, why would I want to call them?

What is there to do here?

Sorry. I meant to trim that TODO out. I was going to change the name
and may.
I typically create "shortcuts" to API properties. It speeds things
property access

I tested this just a few days ago. Richard Cornford has referred to
these as something like "silly namespaces with performance penalties".
He is right, of course, but I don't think the single namespace is
hideous. For 10000 property lookups the version that had the shortcut
was about 30ms and the version with one "namespace" like above was
about 50 ms. I was convinced this is not worth stressing about. Ten
thousand is a lot!

More importantly, seeing as this is intended as an article, I don't
want to go to any extra length to complicate things. I think it will
be plenty complicated enough with concepts alone for most readers.
and does wonders for (aggressive) minification. I
know you don't care for obfuscation, due to the possibility of
introducing bugs, but recently I have been using the YUI
"compressor" (misnomer) on a 7000+ line project that is teeming with
closures and it has never burned me. Granted, I always seek JSLint's
approval before running my build process.


You answered the second question.

<h2><a name="two">Two</a></h2>

Then turn it into a link when you create the widget.

As for Safari 1.x., if the dynamically created href is "#", the worst
case is that it will scroll to the top on click. It may not do
anything but tack an empty hash onto the address.

I think something along these lines is the right idea.

I find this hard to follow. What are c, t and p? I think this is
another reason to minify aggressively (it allows you to use long,
descriptive variable names with impunity.) Of course, it won't help
with the t and p properties of c.

I'll expand these names for the sake of the article
Make sure ts is truthy (may be overly paranoid.)

If execution has reached this stage, then doc.createElement should be
tested to work. I have a feature test at the top for the existence of
document.createElement. I could add a test there that it also works
and a comment that it may be overly paranoid. I'm not being that
paranoid in other cases. I'd like the article to point out that you
can actually test things work as well as just testing they exist so
this may make a good example.

Okay, it appears t is for tab and p is for pane and cc is class added
to indicate that the tab is selected.

Yes. Sorry to make you muddle through that.

I assume you are adding and removing panes from the layout by adding
and removing the "current" class.
Yes.

I would set the display style
directly. Fiddling with a widget's accompanying style sheet should
never have the potential to break the its behavior.

I've debated exactly this in my head. I'm not sure which is better.

The separatists argue that all styling should be in the stylesheet.
This breaks down the minute they want an animation.

I think it is fair to put a bit of css in the stylesheet that has a
comment /* for javascript */; however, some other rule could then
override it. If it is a direct manipulation of element.style.display
it wins the cascade for sure.

Thanks.

first = false;
}
enliven(c, t, p);
h = LIB.querySelector('h2', p)[0];

You should generalize this to use any level headline.

I will mention in the article that this could be done many ways. I
don't want to bloat the actual mechanics of the widget with options. I
think that just one way is good enough for an article.

And why not
getEBTN for this (seems like a more direct approach.)

Given the implementation, there is really no effective difference. The
compiled function just calls getEBTN. I think that these querySelector
functions are the way of the future (there is a standard now and it is
in Safari 3, I believe) and provide a nice uniform API. Just one
function that does it all.


I would not use innerHTML for this as you have appendChild at your
disposal.

The innerHTML may be more than one element and that would require a
loop. I could add a restriction to the design spec that the h2
contents can only be one text element.

I'm no longer afraid of innerHTML. It works far faster and more
predictably than the DOM functions across the various browsers. I have
a work project that builds a table with 250 rows, 7 cells per row and
some images in each row. The only way to get acceptable performance is
by building the table with HTML and inserting with innerHTML. Using
createElement, appendChild is *really* slow even if the final
attachment to the DOM happens in one appendChild.

Should use the createElement wrapper (2 lines IIRC) to allow for XHTML
support.

If I do that then I have to stop using innerHTML for consistency.

Personally I don't care one bit about XHTML. Given the XHTML
implementations and what happens with a page is malformed, it is a
total waste of time, in my opinion.

This will fail in some browsers when using XHTML (see getBodyElement.)

For consistency, I considered writing

LIB.querySelector('body')

which would have used your fallback in getBodyElement.

Looks good (presuming all browsers behave as expected with
offsetHeight and display:none. It seems you are checking if the style
sheet loaded, as well as if style is enabled and that user style
sheets are not interfering. Good deal.

Even though the test seems a bit long, I thought it was pretty
economical for all that it is testing.

I think this is the kind of test that is going to shock people. I
don't think many people actually test that the widget will work by
making a fake little one and trying it.
I wrote a generalized "cssEnabled" function a while back that used
similar logic, but have never used it for anything in production. It
seemed to me at the time that I needed widgets to work even if CSS was
toggled after the page was loaded.

If some user toggles the CSS after the page is loaded is on his own. I
will mention this however.

It is almost just as easy that some user might type this into his
browser URL bar

javascript:LIB.addClass = null;

There are certain things like this that someone really has to go out
of his way to break the page.

I do like the specific test for
this widget as I have never dealt with the issue of a (lunatic) user
adding an !important display rule.


You can skip all of this unless this test passes:

var el = getAnElement();
if (el && el.style && typeof el.style.display == 'string') {
// Add style rule(s)
// Attach load listener
}

Skip all of what?

I did have this test at one point but since I put the styling in a CSS
sheet and only manipulated the classes, I figured I didn't need the
above test as I don't directly manipulate the el.style.display value.
I may switch back to the direct manipulation.

IIRC, this will crash some (or all) revisions of NN4.x.

Crap.

It works for me in NN4.0 and NN4.5 with no problems.

This is actually a very important part of what I want to be able to do
with widgets and part of the reason for the article.

It also will
not work with XHTML (I know it is a dead language on the Web, but it
is useful for Intranets.)

I'm ok with this not working with XHTML.

Better to add the needed rules via DOM
manipulation. Of course, that logic must reside in a script block
outside of the head element, else you risk the dreaded "Operation
Aborted" error in IE.

Officially, the DOM is supposed to be read only until window.onload. I
don't want to touch it in any way. I like following the rules. I'm
sure the people that started seeing that Operation Aborted error
didn't know what to do for a long time and the only reason they saw it
was because they didn't follow the rules.

The only sanctioned thing that can be done while the page is loading
is document.write to dynamically generate parts of the page.

So if XHTML is to be used then having the transitional CSS that makes
the page somewhat acceptable to the user would not be possible.

I think "this" is sufficient as this.window points back to the global
object.


Interesting that you add classes to the documentElement. I haven't
had occasion to do that.

I haven't used it before but it seems to work. I have added classes to
document.body to do what I'm trying to do here because I've assumed
document.body exists.

The reason I added to document.documentElement here is because if I am
"getting out of trouble", I can only use what I knew existed when the
first set of feature tests ran. That means I know the
document.documentElement exists but I do not know that document.body
exists. I am trying to build a consistent line of logic for what's
been tested and what is being used.

[snip]

[I'll slit the comments for the lib.js into another reply.]

Peter
 
P

Peter Michaux

David said:
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
// to keep library interdependencies low.
// 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.
LIB.arrayFilter = function(a, f) {
var rs = [];
for (var i=0, ilen=a.length; i<ilen; i++) {
if (f(a)) {


You can skip the call to f if a 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);


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
LIB.preventDefault section shouldn't be defined unless it can work.
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.

[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
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.

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.


[snip]

&& getAnElement

You know that the getAnElement variable exists.

getAnElement is defined in an if statement above.

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.


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.

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.
IIRC, this excludes IE5.0 and under.

It does work in my multi-IE install of IE5.01 but not in IE4.

[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.

[snip]
if (LIB.getAnElement &&


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.

I'm missing something here. Can you elaborate on this?

"foo foo".replace(/(^|\s+)foo(\s+|$)/g, ' ');

returns

"foo"

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.

This is code I got from YUI.


Despite the verbosity of my comments, most of them are quibbles. This
is certainly the right way to create a widget. Unfortunately, it
seems that most Web developers follow a far different path:

1. Download Prototype, jQuery or some other amateurish garbage, along
with assorted plug-ins
2. Write widget code that is married to the library in terms of
syntax, as well as (typically ineffectual) feature testing
3. Put a "Best viewed in three or four modern browsers" warning on
pages that use the widget
4. Download patches to the library for eternity, ignoring the fact
that the library authors will never really get it right
5. Upload widget to library-specific repository so developers with
similar delusions can break their sites as well
6. Tell anyone who complains that they aren't living in the "real
world" (which apparently doesn't have mobile devices, set top boxes or
off-brand browsers of any kind)
7. Exhort everyone else to follow their lead as their widget code is
so simple and compact (never mind that it has 150K of dependencies and
is prone to catastrophic failure)

But hey, it works for Google. Unfortunately for the rest of us, the
equation is not:

incompetence = Web success

But rather:

incompetence + lots of cash from investors = Web success
:)


But I digress. For those who need an example of how to write widgets
for applications on the public Internet, this is a good example. With
some minor modifications, it could be a great example.

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.

Peter
 
D

David Mark

David said:
I'm writing a blog article about building a decent tabbed pane widget
[snip]

Did you mean to leave the UL unstyled (other than the background color
of the selected item?)

For now yes. I don't want to cloud what is related to scripting and
what is only for looks.

[snip]
Regardless of the state of scripting, the tabs must be accessible by
keyboard.  The text in the list items should be wrapped in anchors or
buttons.  Buttons make more sense, but are impossible to style in some
browsers.

You are right. I need to make this keyboard accessible. Given how so
many websites are not navigable by keyboard, there must be a way for
people to move their cursor around with the keyboard to simulate a
mouse.

I imagine such devices/drivers exist, but certainly they should not be
necessary to navigate the Web. The best way to test keyboard
accessibility is to unplug the mouse. If your page isn't 100%
functional without it, there is a problem with the design.
[snip]
I would test if it is an object and that it is truthy (to exclude
null.)  You can't trust global variables, so it is best to interrogate
them as thoroughly as possible.

Even more specific would be to test that it is an object with

typeof LIB == 'object'

What I proposed translates to:

typeof LIB == 'object' && LIB

Remember that due to a mistake by the creators of JavaScript, typeof
null == 'object'.
Interesting. Thanks. I will do that.



Are you only referring to the tests for firstChild and childNodes?

Just childNodes.
For the sake of curiosity, why would I want to call them?

You wouldn't. The point is that isRealObjectProperty will return
false for a callable property (typeof o[p] == 'function'.) So these
would fail in Safari:

isRealObjectProperty(document, 'images')
isRealObjectProperty(document, 'forms')
isRealObjectProperty(document, 'all')

And possibly childNodes if the pattern holds. The isHostMethod
function allows for 'object', 'function' or 'unknown' types. The
isRealObjectProperty allows only for a truthy object.
Sorry. I meant to trim that TODO out. I was going to change the name
and may.



I tested this just a few days ago. Richard Cornford has referred to
these as something like "silly namespaces with performance penalties".

These are penalties that can be easily avoided. Granted, "deep"
namespaces like YUI's are silly.
He is right, of course, but I don't think the single namespace is
hideous. For 10000 property lookups the version that had the shortcut
was about 30ms and the version with one "namespace" like above was
about 50 ms. I was convinced this is not worth stressing about. Ten
thousand is a lot!

More importantly, seeing as this is intended as an article, I don't
want to go to any extra length to complicate things. I think it will
be plenty complicated enough with concepts alone for most readers.






I think something along these lines is the right idea.



I'll expand these names for the sake of the article



If execution has reached this stage, then doc.createElement should be
tested to work. I have a feature test at the top for the existence of
document.createElement. I could add a test there that it also works
and a comment that it may be overly paranoid. I'm not being that
paranoid in other cases. I'd like the article to point out that you
can actually test things work as well as just testing they exist so
this may make a good example.

By habit, I have always tested the return value of createElement. It
may not be necessary. My original thinking was along the lines of a
browser that is out of resources and cannot create a new element.
Perhaps it would return null in that case (or perhaps it would just
blow up.)
Yes. Sorry to make you muddle through that.



I've debated exactly this in my head. I'm not sure which is better.

The separatists argue that all styling should be in the stylesheet.

If the styles are for appearance only then yes. If they are part of
the mechanics of the widget, then definitely not. Display,
visibility, cursors, etc. fall into the latter category. You should
not be able to cause problems with the widget's behavior by
customizing its style sheet. Also, imagine if you want to create
alternate style sheets. You wouldn't want to repeat the display rules
in those.
This breaks down the minute they want an animation.

I think it is fair to put a bit of css in the stylesheet that has a
comment /* for javascript */; however, some other rule could then
override it. If it is a direct manipulation of element.style.display
it wins the cascade for sure.

Exactly. A (very strange) user could override with an !important rule
in their local style sheet, but you covered that in your feature
testing.
Thanks.
                first = false;
            }
            enliven(c, t, p);
            h = LIB.querySelector('h2', p)[0];
You should generalize this to use any level headline.

I will mention in the article that this could be done many ways. I
don't want to bloat the actual mechanics of the widget with options. I
think that just one way is good enough for an article.
And why not
getEBTN for this (seems like a more direct approach.)

Given the implementation, there is really no effective difference. The
compiled function just calls getEBTN. I think that these querySelector
functions are the way of the future (there is a standard now and it is
in Safari 3, I believe) and provide a nice uniform API. Just one
function that does it all.

I've never had a need for them, but indeed there is a proposed
standard to query by CSS selectors.
The innerHTML may be more than one element and that would require a
loop. I could add a restriction to the design spec that the h2
contents can only be one text element.

What's wrong with a loop?
I'm no longer afraid of innerHTML. It works far faster and more
predictably than the DOM functions across the various browsers. I have
a work project that builds a table with 250 rows, 7 cells per row and
some images in each row. The only way to get acceptable performance is
by building the table with HTML and inserting with innerHTML. Using
createElement, appendChild is *really* slow even if the final
attachment to the DOM happens in one appendChild.

In such an extreme case, I would agree that innerHTML is the way to
go.
If I do that then I have to stop using innerHTML for consistency.

I don't think that's a problem for the contents of a headline.
Personally I don't care one bit about XHTML. Given the XHTML
implementations and what happens with a page is malformed, it is a
total waste of time, in my opinion.

A good solution is to make your pages well-formed. However, it is a
waste of time on the public Internet. Another caveat is that some
browsers have buggy implementations. You saw the XHTML/HTML test page
on my builder site (I assume) and I had never had a problem with it
got very long (i.e. when every module is selected.) Recently, after
adding several new modules and their accompanying unit tests, I was
flabbergasted to see Opera 9 throw an obscure error (document ended
unexpectedly or something) on page load. Hitting refresh made it go
away. Refreshing repeatedly wouldn't bring it back, but I eventually
saw it again. The error always points to the same line in the markup,
which is certainly well-formed. That's enough to make me forget XHTML
on the public Internet. I am not going to remove support for it from
the library as it can be useful for Intranet applications (and only
adds a couple dozen lines to the 7000 or so that make up the complete
build.)
For consistency, I considered writing

LIB.querySelector('body')

which would have used your fallback in getBodyElement.



Even though the test seems a bit long, I thought it was pretty
economical for all that it is testing.

I think this is the kind of test that is going to shock people. I
don't think many people actually test that the widget will work by
making a fake little one and trying it.

I do a lot of this sort of testing in my library. The offset position
function looks for about a dozen quirks in similar fashion. I've
never done it at the widget level, but it makes sense as user-defined
rules or a CSS file that fails to download can render them useless.
If some user toggles the CSS after the page is loaded is on his own. I
will mention this however.

It is almost just as easy that some user might type this into his
browser URL bar

javascript:LIB.addClass = null;

There are certain things like this that someone really has to go out
of his way to break the page.

Yes, I don't think it makes sense to worry about either case.
Skip all of what?

The whole widget. Regardless of how you change the display style,
agents that fail the above test will not reflect those changes in the
layout. AFAIK, this is a small group that includes Opera 6 and some
outdated phones (which were probably running an old version of Opera
Mini.)
I did have this test at one point but since I put the styling in a CSS
sheet and only manipulated the classes, I figured I didn't need the
above test as I don't directly manipulate the el.style.display value.
I may switch back to the direct manipulation.

Your test is fine. All I meant was that you could skip everything if
the initial display test failed.
Crap.

It works for me in NN4.0 and NN4.5 with no problems.

Perhaps I am mistaken. I could have sworn there was an issue with
using document.write in the head. Personally, I would forget NN4 as
it was never a viable browser. You could say the same thing about
NN6.0 (though 6.2 is fairly easy to support.)
This is actually a very important part of what I want to be able to do
with widgets and part of the reason for the article.

You have to do it, but you don't have to use document.write.
I'm ok with this not working with XHTML.


Officially, the DOM is supposed to be read only until window.onload. I

According to whom? Personally, I don't touch the body element until
the document has completed parsing, but adding style elements to the
head shouldn't be an issue as long as the script is after the closing
head tag.
don't want to touch it in any way. I like following the rules. I'm
sure the people that started seeing that Operation Aborted error
didn't know what to do for a long time and the only reason they saw it
was because they didn't follow the rules.

No, it is a documented bug in IE6. I don't know if it exists in IE7
or not. I've never run into it, except when testing third-party
scripts. It is caused by attempting to manipulate elements that
haven't been fully parsed.
The only sanctioned thing that can be done while the page is loading
is document.write to dynamically generate parts of the page.

I've never heard of such a rule and certainly nobody conforms to it
(or enforces it.) What would be the point of DOMContentLoaded if you
had to wait for the load event?
So if XHTML is to be used then having the transitional CSS that makes
the page somewhat acceptable to the user would not be possible.

That is incorrect. You can certainly add style elements to the head
of an XHTML document while it is loading. I do it all the time. The
flash of fallback content is less pronounced with XHTML as there is no
incremental rendering going on during the load, but I can't stand even
the slightest unnecessary twitch.

Or at least until the document is parsed.
I haven't used it before but it seems to work. I have added classes to

Certainly it should work.
document.body to do what I'm trying to do here because I've assumed
document.body exists.

Which is a fairly good assumption unless you use XHTML. I haven't had
occasion to add classes to the body either. Either should work, but
there are certainly other ways to go.
The reason I added to document.documentElement here is because if I am
"getting out of trouble", I can only use what I knew existed when the
first set of feature tests ran. That means I know the
document.documentElement exists but I do not know that document.body

Right.
 
D

David Mark

David said:
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.

That too. I made all of the array stuff optional in my builder.

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)) {

You can skip the call to f if a 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);

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?
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]
&& 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.)
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.
But getAnElement may not be defined.

But it is certainly declared at this point, which is all that matters.
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 &&
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.
"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?
 
P

Peter Michaux

[snip]
If execution has reached this stage, then doc.createElement should be
tested to work. I have a feature test at the top for the existence of
document.createElement. I could add a test there that it also works
and a comment that it may be overly paranoid. I'm not being that
paranoid in other cases. I'd like the article to point out that you
can actually test things work as well as just testing they exist so
this may make a good example.

By habit, I have always tested the return value of createElement. It
may not be necessary. My original thinking was along the lines of a
browser that is out of resources and cannot create a new element.
Perhaps it would return null in that case (or perhaps it would just
blow up.)

That is overly paranoid! ;-)

I suppose the test could go with the cssDisplaySupport, because if the
browser has decided this late in the game that it isn't going to make
any more elements then that time for the check is before any elements
for the tabbed pane are created.

There are quite a few kinds of widgets that, when passed the point of
no return, require successful createElement calls to be successful.
There is almost no point in testing the calls worked at that late
stage in the game. I suppose an attempt to back out of the widget to
the static version could be made. An error message saying "you
computer has very little memory...upgrade." might help.

[snip]
According to whom?

The only reference I can find right now is

<URL: http://www.devguru.com/technologies/xmldom/quickref/document_readyState.html>

I don't see anything on the Microsoft site about the DOM being read-
only in this state. I may just be wrong about this.

Personally, I don't touch the body element until
the document has completed parsing, but adding style elements to the
head shouldn't be an issue as long as the script is after the closing
head tag.


No, it is a documented bug in IE6. I don't know if it exists in IE7
or not.

I believe IE7 does have it and that I've seen it. That was quite a
while ago so my memory may be wrong.

I've never run into it, except when testing third-party
scripts. It is caused by attempting to manipulate elements that
haven't been fully parsed.




I've never heard of such a rule and certainly nobody conforms to it
(or enforces it.) What would be the point of DOMContentLoaded if you
had to wait for the load event?

I always figured that DOMContentLoaded was a proprietary extension
that was early permission to the developer that even though the
standard says to wait, it is actually ok to start manipulating now. It
looks like I was wrong about the read-only part, however.

Peter
 
P

Peter Michaux


This seems like a very ad hoc set of rules...driven by a very ad hoc
browser.


[snip]

I think it would be more appropriate and a more future-proof API to
make a new test. The rules for the test may diverge and having the
user specifying the parameters of such a test is dangerous. The user
shouldn't have to know the mechanics of the test.

var isHostObject = function(o, m) {
var t = typeof(o[m]);
return !!((t=='function' || t=='object') && o[m]);
};
LIB.isHostObject = isHostObject;

[snip]
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.

What did you find?


[snip]
How did [blogs] ever supplant Usenet?

They are shinier.

Peter
 
P

Peter Michaux

var el = getAnElement();
if (el && el.style && typeof el.style.display == 'string') {
// Add style rule(s)
// Attach load listener

Can you think of any reason to do some indirect test that style object
exists?

isRealObjectProperty(el, 'style');

-----

I'm making a slew of changes to the code now (not the event stuff yet)
and will post a new example.

Peter
 
D

dhtml

David said:
I'm writing a blog article about building a decent tabbed pane widget
[snip]
[snip]


If I do that then I have to stop using innerHTML for consistency.

Personally I don't care one bit about XHTML. Given the XHTML
implementations and what happens with a page is malformed, it is a
total waste of time, in my opinion.
This will fail in some browsers when using XHTML (see getBodyElement.)
Only old versions of FF, I think.

For consistency, I considered writing

LIB.querySelector('body')

Why not just document.body?

which would have used your fallback in getBodyElement.

I think this creates what MS call a cross-page leak.

The only issues with display: none is with Safari 2. Safari 2 I won't
send form elements with display: none, and I think it sets the value
to "". And there's that issue with getComputedStyle with display:
none John brought up.

Why wait for onload?

The reason I added to document.documentElement here is because if I am
"getting out of trouble", I can only use what I knew existed when the
first set of feature tests ran. That means I know the
document.documentElement exists but I do not know that document.body
exists. I am trying to build a consistent line of logic for what's
been tested and what is being used.

It works, but it's not valid to have class on HTML.

[snip]

[I'll slit the comments for the lib.js into another reply.]
I would design the code with separated modules.

'LIB' does not describe anything. It's like a kitchen sink.

As you build more widgets, they'll also want to reuse stuff in 'LIB'.
If anyone wants to use a LIB widget, they need LIB. It is my opinion
that the core library should be minimalistic.

Garrett
 
D

David Mark

David Mark wrote:
I'm writing a blog article about building a decent tabbed pane widget
[snip]




      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.

This seems like a very ad hoc set of rules...driven by a very ad hoc
browser.
Indeed.

It is worth noting that unlike el.href for a news links or
el.getAttribute for an XML element or el.offsetParent for an element
that is not in a document or various methods of window.external, this
one will likely never throw an error on [[Get]]. The reason I bought
it up is that it just doesn't make logical sense use a test that
allows "unknown" types and then turn around and evaluate the property
(something that is known to blow up in IE.)
[snip]

I think it would be more appropriate and a more future-proof API to
make a new test. The rules for the test may diverge and having the
user specifying the parameters of such a test is dangerous. The user
shouldn't have to know the mechanics of the test.

var isHostObject = function(o, m) {
  var t = typeof(o[m]);
  return !!((t=='function' || t=='object') && o[m]);};

LIB.isHostObject = isHostObject;

Works for me. Of course, once the library gets extensive enough, most
authors won't need to call these low-level feature test functions. I
think I am at that point with mine (at least for most applications.)
[snip]
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.

What did you find?

I forgot to look. I will try to remember next time I am near the
multi-IE box.
[snip]
How did [blogs] ever supplant Usenet?

They are shinier.

With all of the Web-based front-ends to Usenet, that shouldn't be the
case. Of course, Google Groups is one of the most popular and it is
perhaps the worst Web-based discussion forum ever designed. When I
read posts using it, I never see the last word of each sentence
(unless I reduce text size to the point where I can't read anything.)
I once ran into one of their "usability experts" in one of the Google
development forums and he said that he hadn't heard too many
complaints. (!) That was followed by a deluge of "you've been using
it a long time, you must like it" responses from fanboys. Of course,
most of my posts at the time were via NNTP.

Oh well, last I heard, blog-based discussions were on the way out. I
have no idea what is on the way in though.
 
D

David Mark

Can you think of any reason to do some indirect test that style object
exists?

isRealObjectProperty(el, 'style');

This is as it should be. Testing a property of an element by type
conversion is too dangerous. If href can blow up, anything can.
 
D

David Mark

I'm writing a blog article about building a decent tabbed pane widget

[snip]



If I do that then I have to stop using innerHTML for consistency.
Personally I don't care one bit about XHTML. Given the XHTML
implementations and what happens with a page is malformed, it is a
total waste of time, in my opinion.

Only old versions of FF, I think.

IIRC, Safari 3 for Windows doesn't have it either.
Why not just document.body?

If not using XHTML, then that will do.
I think this creates what MS call a cross-page leak.

So did I at one time, but it was pointed out to me that there example
of that in MSDN creates an element with an intrinsic event handler.
The only issues with display: none is with Safari 2. Safari 2 I won't
send form elements with display: none, and I think it sets the value
to "".  And there's that issue with getComputedStyle with display:
none John brought up.

John who? Hopefully everybody knows that you can't reliably query
style rules of elements that aren't part of the layout. It makes no
logical sense to try. It has been brought up recently that some
libraries provide a useless workaround for this, but what a waste of
time and code.
Why wait for onload?

Because DOM ready logic is beyond the scope of this article. There is
a ticket proposing such code in CWR.
It works, but it's not valid to have class on HTML.

That I didn't know. Good thing I never tried it. Not valid markup?
[I'll slit the comments for the lib.js into another reply.]

I would design the code with separated modules.

'LIB' does not describe anything. It's like a kitchen sink.

I use API. What do you propose?
As you build more widgets, they'll also want to reuse stuff in 'LIB'.
If anyone wants to use a LIB widget, they need LIB. It is my opinion

I don't follow exactly.
that the core library should be minimalistic.

If it is modular and has a build process, it can be as small or large
as the author requires. Mine ranges from 1K to well over 100K,
depending on the modules selected.
 
P

Peter Michaux

If not using XHTML, then that will do.

This article is about building widgets for the general web. XHTML is
not something to be used on the general web. I will switch to just
document.body.

So did I at one time, but it was pointed out to me that there example
of that in MSDN creates an element with an intrinsic event handler.

What is a "cross-page leak"?


Because DOM ready logic is beyond the scope of this article. There is
a ticket proposing such code in CWR.

This article could leave the door open by having a wrapper for DOM
ready. I am very skeptical there is a solution that works in all
browsers and that some browsers will still need to use window.onload.



The HTML 4.01 spec says no

<URL: http://www.w3.org/TR/REC-html40/struct/global.html#h-7.3>

The DOM 2 spec says yes

http://www.w3.org/TR/2003/REC-DOM-Level-2-HTML-20030109/html.html#ID-58190037

So since this className is being added using the DOM it should be ok
to do so. It doesn't necessarily make it a good idea, however.

[snip]
[I'll slit the comments for the lib.js into another reply.]
I would design the code with separated modules.
'LIB' does not describe anything. It's like a kitchen sink.

I use API. What do you propose?

How about...

ca.michaux.peter.javascript.example.library.dom.classAttribute.addClass()

:D

I don't follow exactly.

The library for the example only has the code necessary to make the
example run.

If it is modular and has a build process, it can be as small or large
as the author requires. Mine ranges from 1K to well over 100K,
depending on the modules selected.

This article is not about the library's modularization. I don't even
believe in this anymore. One namespace is plenty for all of a library.
The library is just for illustrative purposes. I will type that in
bold all-caps in the article so people don't focus on that. (Just
imagine that all of the emacs lisp code is in the single global
namespace.)

Peter
 
D

David Mark

[snip]
What did you find?

My multi IE setup has 5.01, which has nodeType, nodeName, childNodes,
etc. It appears to be a valid test environment as it does not have
Arrary.prototype.splice, push, etc.

I don't know what property I was thinking of and since PPK has purged
the IE5 data from quirksmode.org, I can't find out without using the
Internet wayback machine (I'm not that curious at the moment.) I am
sure there was some common node-related property that was missing in
IE5, but added in IE5.5, but can't remember which. As I don't design
anything for a specific subset of browsers, I don't have to clutter my
head with such minutiae.
 
P

Peter Michaux

This is as it should be. Testing a property of an element by type
conversion is too dangerous. If href can blow up, anything can.

I suppose this should be the following so the name of the test
function is appropriate.

isHostObject(el, 'style')

Perhaps some browser will make el.style callable or some other weird
contortion.

Even though I just wrote something to the contrary, shouldn't it be
the following for consistency?

isHostObject(doc, 'body')

rather than just doc.body.

Peter
 
P

Peter Michaux

On Feb 13, 4:49 am, David Mark <[email protected]> wrote:
[snip]
How did [blogs] ever supplant Usenet?
They are shinier.

With all of the Web-based front-ends to Usenet, that shouldn't be the
case. Of course, Google Groups is one of the most popular and it is
perhaps the worst Web-based discussion forum ever designed.

I use, like and don't ever have problems with the Google Groups
interface.

Oh well, last I heard, blog-based discussions were on the way out. I
have no idea what is on the way in though.

Micro blogging.

http://twitter.com/

Why anyone would need to know what I'm doing every moment and why I
would feel the need to broadcast that information is beyond me.

Peter
 
D

David Mark

This article is about building widgets for the general web. XHTML is
not something to be used on the general web. I will switch to just
document.body.

For safety, make sure you test it with isRealObjectProperty. Somebody
could drop the lib and example into an XHTML page.
What is a "cross-page leak"?
http://msdn2.microsoft.com/en-us/library/bb250448.aspx



This article could leave the door open by having a wrapper for DOM
ready. I am very skeptical there is a solution that works in all
browsers and that some browsers will still need to use window.onload.

My solution does indeed use window.onload as a last resort. It
listens for that event in all browsers, but bails out immediately if
the DOM ready code has been run by the DOMContentLoaded listener or a
direct call from a script at the bottom of the page. It works
flawlessly in every browser I have tested.
The HTML 4.01 spec says no

Back then the HTML element wasn't part of the layout.

That makes sense.
http://www.w3.org/TR/2003/REC-DOM-Level-2-HTML-20030109/html.html#ID-...

So since this className is being added using the DOM it should be ok
to do so. It doesn't necessarily make it a good idea, however.

It's not something I have ever found a need for. Same for the body.
[snip]
[I'll slit the comments for the lib.js into another reply.]
I would design the code with separated modules.
'LIB' does not describe anything. It's like a kitchen sink.
I use API.  What do you propose?

How about...

ca.michaux.peter.javascript.example.library.dom.classAttribute.addClass()

How very YUI-esque. I have a theory that Yahoo does that to make
their project seem bigger than it is. Granted the code is bloated,
but its functionality is hardly in need of such structure.
:D



The library for the example only has the code necessary to make the
example run.


This article is not about the library's modularization. I don't even
believe in this anymore. One namespace is plenty for all of a library.

Don't believe in what? And yes, a single flat namespace is plenty.
Its only purpose is to keep the global namespace clean.
 
P

Peter Michaux

For safety, make sure you test it with isRealObjectProperty. Somebody
could drop the lib and example into an XHTML page.





http://msdn2.microsoft.com/en-us/library/bb250448.aspx

Reading that description it doesn't say anything about JavaScript. It
does talk a lot about "scope". It seems to support what Garrett wrote.

It is not a good idea to do their solution of adding elements only to
elements already in the DOM. It is much better for rendering if the
subtree is created and then added to the DOM in one final appendChild.


How very YUI-esque.

It is really much more Java-esque.
I have a theory that Yahoo does that to make
their project seem bigger than it is.

I don't think there is any evidence for that.

Don't believe in what? And yes, a single flat namespace is plenty.
Its only purpose is to keep the global namespace clean.

I don't believe in having multiple namespaces within a library. There
is no need for LIB.Dom.addClass and LIB.Event.addListener. If I'm
going to end up with a namespace collision because I want two
LIB.addClass, then I need to rethink something. I do think the single
namespace is a great way to keep the library from colliding with the
app-specific code. This can be done with LIB_addClass also.

Peter
 
D

David Mark

Reading that description it doesn't say anything about JavaScript. It
does talk a lot about "scope". It seems to support what Garrett wrote.

This is from the example in question:

var parentDiv = document.createElement("<div onClick='foo()'>");
var childDiv = document.createElement("<div onClick='foo()'>");

// This will leak a temporary object
parentDiv.appendChild(childDiv);
hostElement.appendChild(parentDiv);
hostElement.removeChild(parentDiv);
parentDiv.removeChild(childDiv);
parentDiv = null;
childDiv = null;

Below this example they say:

"The key points to understand about the leak are that DOM elements are
being created with scripts already attached."

Of course, as with virtually anything MS says (even about their own
software), it is questionable as to whether it is a fact or guesswork.
It is not a good idea to do their solution of adding elements only to
elements already in the DOM. It is much better for rendering if the
subtree is created and then added to the DOM in one final appendChild.
Agreed.



It is really much more Java-esque.

I'm sure that is where YUI got the idea.
I don't think there is any evidence for that.

It's just a theory. They seem to be saying that their library is so
expansive that every module needs its own space, else how would you
ever keep track of it all. Then again, maybe it was just an
unfortunate design decision.
I don't believe in having multiple namespaces within a library. There
is no need for LIB.Dom.addClass and LIB.Event.addListener. If I'm
going to end up with a namespace collision because I want two
LIB.addClass, then I need to rethink something. I do think the single
namespace is a great way to keep the library from colliding with the
app-specific code. This can be done with LIB_addClass also.

Agreed, but I wouldn't want to tack hundreds of LIB_ properties onto
the global object. Speaking of globals, Opera is known to be slow in
referencing them. They have an article on their Website about that.
So in your widget code, calling API.whatever (as opposed to using a
direct reference to the whatever function) will be slower for two
reasons (at least in Opera.)
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,744
Messages
2,569,483
Members
44,903
Latest member
orderPeak8CBDGummies

Latest Threads

Top