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