Various DOM-related wrappers (Code Worth Recommending Project)

D

David Mark

After a some reworking to support the "*" parameter properly in IE5
(my previous version failed to filter non-element nodes), here are my
proposals for various low-level DOM-related functions and the feature
tests that enable them to be created without issue. I welcome any
comments.

I just noticed that I left some of the function declarations as
variable declarations. There wasn't any specific reason for this,
they were just created differently in my framework.

var allElements, commonElementsByTagName, createElement,
documentElementsByTagName, element, elementElementsByTagName, doc,
headElement, html, htmlElement, i, xmlParseMode;
var filter, filterLegacy;
var canAdjustStyle, isStyleCapable, styles;
var reFeaturedMethod = new RegExp('^function|object$', 'i');
var global = this;

// Test for properties of host objects known not to be callable (e.g.
document nodes, elements)

var isRealObjectProperty = function(o, p) {
return !!(typeof(o[p]) == 'object' && o[p]);
};

// Test for host object properties that are typically callable (e.g.
all, getElementById),
// which may be of type function, object (IE and possibly others) or
unknown (IE ActiveX methods)

var isFeaturedMethod = function(o, m) {
var t = typeof(o[m]);
return !!((reFeaturedMethod.test(t) && o[m]) || t == 'unknown');
};

// Filter wrapper, which is included at this stage as it is needed for
the gEBTN workaround
// (all returns non-element nodes.)
// Standard filter wrapper is not useful for IE5.0 as it requires
Function.prototype.call.

if (Array.prototype.filter) {
filter = function(a, fn, context) { return a.filter(fn, context); };
}
else {
// Note that Array.prototype.reverse is not tested as it is from JS
1.1
if (Function.prototype.call) {
filter = function(a, fn, context) {
var l = a.length, r = [], c = 0;
context = context || global;
// Didn't want to use in operator and for in loop does not preserve
order
while (l--) {
if (typeof(l) != 'undefined') {
if (fn.call(context, a[l], a)) { r[c++] = a[l]; }
}
}
return r.reverse();
};
}
else {
// No simulated filter possible, so add a fallback with a slightly
different interface
// Parameter fn can be a string (method name) or function
// Context ignored unless fn is a string
filterLegacy = function(a, fn, context) {
var l = a.length, r = [], c = 0;
context = context || global;
fn = (typeof(fn) == 'string')?context[fn]:fn;
while (l--) {
if (typeof(l) != 'undefined') {
if (fn(a[l], a)) { r[c++] = a[l]; }
}
}
return r.reverse();
};
}
}


function elementFilter(o) {
// IE5 thinks comments and docTypes are elements.
// Second conjunction is for agents that don't support nodeType.
return (o.nodeType == 1 && o.tagName != '!') || (!o.nodeType &&
o.tagName);
}

// Used to convert array-like host objects to arrays
// IIRC, Array.prototype.slice didn't work with node lists
function toArray(o) {
var a = [];
var l = o.length;
while (l--) { a[l] = o[l]; }
return a;
}

if (isRealObjectProperty(this, 'document')) {
doc = this.document;

// gEBI wrapper
element = (function() {
function idCheck(el, id) {
return (el && el.id == id)?el:null;
}
if (isFeaturedMethod(doc, 'getElementById')) {
return function(id, docNode) { return idCheck((docNode ||
doc).getElementById(id), id); };
}
if (isFeaturedMethod(doc, 'all')) {
return function(id, docNode) { return idCheck((docNode ||
doc).all[id], id); };
}
})();


if (isFeaturedMethod(doc, 'all')) {
// Internal in my framework, called by: commonElementsByTagName and
htmlElement
allElements = (function() {
var fnFilter = filter || filterLegacy;
return function(el, bFilter) {
return (bFilter)?fnFilter(toArray(el.all), elementFilter):el.all;
};
})();
}

// Called by both gEBTN wrappers
commonElementsByTagName = (function() {
if (isFeaturedMethod(doc, 'all') && allElements) {
return function (el, t) {
return(t == '*' && el.all)?allElements(el,
true):el.getElementsByTagName(t);
};
}
return function (el, t) { return el.getElementsByTagName(t); };
})();

// Defined only if document nodes feature gEBTN or all.
// Returns an array or array-like host object.
documentElementsByTagName = (function() {
if (isFeaturedMethod(doc, 'getElementsByTagName')) {
return function(t, docNode) {
return commonElementsByTagName(docNode || doc, t);
};
}
if (isFeaturedMethod(doc, 'all') && isFeaturedMethod(doc.all,
'tags')) {
return function(t, docNode) {
return (docNode || doc).all.tags(t);
};
}
})();

// Returns the HTML element by default or optionally the first
element it finds.
htmlElement = function(docNode, bAnyElement) {
var html, all;
docNode = docNode || doc;
html = isRealObjectProperty(docNode, 'documentElement')?
docNode.documentElement:((documentElementsByTagName)?
documentElementsByTagName('html', docNode)[0]:null);
if (!html && allElements) {
all = allElements(docNode); // Don't bother to filter for this
html = all[(all[0].tagName == '!')?1:0];
if (html && !bAnyElement && html.tagName.toLowerCase() != 'html')
{ html = null; }
}
return html;
};

// Retrieve any element (what follows doesn't care which one)
html = htmlElement(doc, true);

// Note that the bodyElement function is not included yet as events
module is needed first
// That function (among others) is defined after the document is
ready.
if (documentElementsByTagName) {
headElement = function(docNode) {
return documentElementsByTagName('head', docNode || doc)[0] ||
null;
};
}

if (html) {
// Defined only if element nodes feature gEBTN or all.
// Returns an array or array-like host object.
elementElementsByTagName = (function() {
if (isFeaturedMethod(html, 'getElementsByTagName')) {
return commonElementsByTagName;
}
if (isFeaturedMethod(html, 'all') && isFeaturedMethod(html.all,
'tags')) {
return function(el, t) { return el.all.tags(t); };
}
})();

// MS has been known to implement elements as ActiveX objects
// (e.g. anchors that link to news resources)
isStyleCapable = isRealObjectProperty(html, 'style');

// These flags dictate which style-related functions should be
initialized
if (isStyleCapable) {
canAdjustStyle = {};
styles = ['display', 'visibility', 'position'];
i = 3;
while (i--) {
canAdjustStyle[styles] = typeof(html.style[styles]) ==
'string';
}
}
}

// This is called in two places,
// createElement (below) and elements (an XPath wrapper.)
// Each adds one additional line to work with XHTML (regardless of
MIME type.)
xmlParseMode = function(docNode) {
docNode = docNode || doc;
if (typeof(docNode.contentType) == 'string') {
return docNode.contentType.indexOf('xml') != -1;
}
else {
return typeof(docNode.body) == 'undefined';
}
};

// Included at this stage as it will be needed for feature testing
shortly
createElement = (function() {
if (doc.createElement) {
return (function() {
if (xmlParseMode() && doc.createElementNS) {
return function(tag, docNode) {
return (docNode || doc).createElementNS('http://www.w3.org/1999/
xhtml', 'html:' + tag);
};
}
return function(tag, docNode) {
return (docNode || doc).createElement(tag);
};
})();
}
})();
}
 
D

David Mark

Correction:

filterLegacy = function(a, fn, context) {
var l = a.length, r = [], c = 0;
context = context || global;
while (l--) {
if (typeof(l) != 'undefined') {
if (((typeof(fn) == 'string')?context[fn]:fn)(a[l], a)) { r[c++] =
a[l]; }
}
}
return r.reverse();
};

I'm thinking this fallback doesn't really need to support context. It
is only used for one thing at the moment (the gEBTN "*" fix.)
 
P

Peter Michaux

After a some reworking to support the "*" parameter properly in IE5
(my previous version failed to filter non-element nodes), here are my
proposals for various low-level DOM-related functions and the feature
tests that enable them to be created without issue. I welcome any
comments.

[nitpick mode on]

Please use a 70 character line length to avoid wrapping. Usenet wraps
at 72 so a 70 character line allows one reply without wrapping.

A 2 space "tab stop" conserves line width.

Neither of these are my personal preferences but are appropriate to
code that will be posted to Usenet.

<URL: http://cljs.michaux.ca/trac/wiki/DesignGuidelines>

[nitpick mode off]

[code quoted below reformatted and reduced to just that related to the
gEBI wrapper]
var element,
doc,
i;
var reFeaturedMethod = new RegExp('^function|object$', 'i');
var global = this;

// Test for host object properties that are typically callable
// (e.g. all, getElementById),
// which may be of type function, object (IE and possibly others)
// or unknown (IE ActiveX methods)

var isFeaturedMethod = function(o, m) {
var t = typeof(o[m]);
return !!((reFeaturedMethod.test(t) && o[m]) || t == 'unknown');
};


if (isRealObjectProperty(this, 'document')) {
doc = this.document;

// gEBI wrapper
element = (function() {

function idCheck(el, id) {
return (el && el.id == id) ? el : null;
}

if (isFeaturedMethod(doc, 'getElementById')) {
return function(id, docNode) {
return idCheck((docNode || doc).getElementById(id), id);
};
}

if (isFeaturedMethod(doc, 'all')) {
return function(id, docNode) {
return idCheck((docNode || doc).all[id], id);
};
}

})();

}

Based on the repository design guidelines, the above could be
substantially reduced to just

if (document.getElementById) {
var getEBI = function(id, d) {
var el = (d||document).getElementById(id);
if (el.id == id) {
return el;
}
};
}

Notes:

There is no need to check for the existence of "document" as no
browser, NN4+ and IE4+, missing "document".

There is no need to check that document.getElementById is a callable
since there has never been a known implementation where
document.getElementById that is not callable.

Since this is a getter the name must start with "get". I'm tempted to
call it "getElementById" since that won't clash with
document.getElementById. "getEBI" is shorter.

It is clear that supporting IE4 requires quite a bit more code which
must be downloaded by everyone. I don't mean this to start a big
discussion about supporting IE4. It is just quite obvious in this
example.
 
P

Peter Michaux

[code quoted below reformatted and reduced to just that related to the
gEBTN wrapper]
var allElements, commonElementsByTagName,
documentElementsByTagName, elementElementsByTagName,
doc, html, htmlElement, i;
var filter, filterLegacy;
var reFeaturedMethod = new RegExp('^function|object$', 'i');
var global = this;

// Test for properties of host objects known not to be callable
// (e.g.document nodes, elements)

var isRealObjectProperty = function(o, p) {
return !!(typeof(o[p]) == 'object' && o[p]);
};


// Test for host object properties that are typically callable
// (e.g. all, getElementById),
// which may be of type function, object (IE and possibly others)
// or unknown (IE ActiveX methods)

var isFeaturedMethod = function(o, m) {
var t = typeof(o[m]);
return !!((reFeaturedMethod.test(t) && o[m]) || t == 'unknown');
};


// Filter wrapper, which is included at this stage as it is needed
// for the gEBTN workaround (all returns non-element nodes.)
// Standard filter wrapper is not useful for IE5.0 as it requires
// Function.prototype.call.

if (Array.prototype.filter) {
filter = function(a, fn, context) {
return a.filter(fn, context);
};
}
else {
// Note that Array.prototype.reverse is not tested as it is
// from JS 1.1
if (Function.prototype.call) {
filter = function(a, fn, context) {
var l = a.length,
r = [],
c = 0;
context = context || global;
// Didn't want to use in operator and for in loop does
// not preserve order
while (l--) {
if (typeof(l) != 'undefined') {
if (fn.call(context, a[l], a)) {
r[c++] = a[l];
}
}
}
return r.reverse();
};
}
else {
// No simulated filter possible, so add a fallback
// with a slightly different interface
// Parameter fn can be a string (method name) or function
// Context ignored unless fn is a string
filterLegacy = function(a, fn, context) {
var l = a.length, r = [], c = 0;
context = context || global;
fn = (typeof(fn) == 'string')?context[fn]:fn;
while (l--) {
if (typeof(l) != 'undefined') {
if (fn(a[l], a)) { r[c++] = a[l]; }
}
}
return r.reverse();
};
}
}

function elementFilter(o) {
// IE5 thinks comments and docTypes are elements.
// Second conjunction is for agents that don't support nodeType.
return (o.nodeType == 1 && o.tagName != '!') ||
(!o.nodeType && o.tagName);
}

// Used to convert array-like host objects to arrays
// IIRC, Array.prototype.slice didn't work with node lists
function toArray(o) {
var a = [];
var l = o.length;
while (l--) { a[l] = o[l]; }
return a;
}

if (isRealObjectProperty(this, 'document')) {
doc = this.document;

if (isFeaturedMethod(doc, 'all')) {
// Internal in my framework, called by:
// commonElementsByTagName and htmlElement
allElements = (function() {
var fnFilter = filter || filterLegacy;
return function(el, bFilter) {
return (bFilter) ?
fnFilter(toArray(el.all), elementFilter) :
el.all;
};
})();
}

// Called by both gEBTN wrappers
commonElementsByTagName = (function() {
if (isFeaturedMethod(doc, 'all') && allElements) {
return function(el, t) {
return (t == '*' && el.all) ?
allElements(el, true) :
el.getElementsByTagName(t);
};
}
return function(el, t) {
return el.getElementsByTagName(t);
};
})();

// Defined only if document nodes feature gEBTN or all.
// Returns an array or array-like host object.
documentElementsByTagName = (function() {
if (isFeaturedMethod(doc, 'getElementsByTagName')) {
return function(t, docNode) {
return commonElementsByTagName(docNode || doc, t);
};
}
if (isFeaturedMethod(doc, 'all') &&
isFeaturedMethod(doc.all, 'tags')) {
return function(t, docNode) {
return (docNode || doc).all.tags(t);
};
}
})();

// Returns the HTML element by default or optionally
// the first element it finds.
htmlElement = function(docNode, bAnyElement) {
var html, all;
docNode = docNode || doc;
html = isRealObjectProperty(docNode, 'documentElement') ?
docNode.documentElement:((documentElementsByTagName)?
documentElementsByTagName('html', docNode)[0] : null);
if (!html && allElements) {
all = allElements(docNode);//Don't bother to filter for this
html = all[(all[0].tagName == '!')?1:0];
if (html &&
!bAnyElement &&
html.tagName.toLowerCase() != 'html') {
html = null;
}
}
return html;
};

// Retrieve any element (what follows doesn't care which one)
html = htmlElement(doc, true);

if (html) {
// Defined only if element nodes feature gEBTN or all.
// Returns an array or array-like host object.
elementElementsByTagName = (function() {
if (isFeaturedMethod(html, 'getElementsByTagName')) {
return commonElementsByTagName;
}
if (isFeaturedMethod(html, 'all') &&
isFeaturedMethod(html.all, 'tags')) {
return function(el, t) { return el.all.tags(t); };
}
})();

}

}

Whoa, that is a lot of code!

I didn't make a time test but I think the simulation of
Array.prototype.filter will be very slow. I made a CSS selector
function one time (something like jQuery has) and I found that any
code that will operate on any medium sized DOM needs to be very fast.
I also made a test between just simply looping over an array verses
using a function that takes another function and iterates over all of
an array. A simple loop wins by a long shot.

For the repository, the above code could be substantially reduced to
something like the following which will run much faster.

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

// from another file in the respository
if (document.documentElement) {
var getAnElement = function(d) {
return (d || document).documentElement;
};
}

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

// One implementation for developers not concerned with IE5
// problem of the browser thinking doctype and comments
// are elements.

if (document.getElementsByTagName &&
typeof getAnElement != 'undefined' &&
getAnElement().getElementsByTagName) {

var getEBTN = function(tag, root) {
var els = root.getElementsByTagName(tag);
if (tag == '*' && !els.length && root.all) {
els = root.all;
}
return els;
};

}

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

// Another implementation for developers concerned with
// the IE5 problem about doctype and comments

if (document.getElementsByTagName &&
typeof getAnElement != 'undefined' &&
getAnElement().getElementsByTagName) {

var getEBTN = function(tag, root) {
var els = root.getElementsByTagName(tag);

if (tag == '*' && !els.length && root.all) {
var all = root.all;
els = [];
for (var i=0, ilen=all.length; i<ilen; i++) {
var el = all;
// The following conditional could be factored out
// if necessary.
if ((el.nodeType == 1 && el.tagName != '!') ||
(!el.nodeType && el.tagName)) {
els[els.length] = el;
}
}
}

return els;
};
}

I haven't actually verified the IE5 bug yet and tested the above in a
wide set of browsers. I'm just looking at the packaging of the
concepts.

I think there is no need for two getEBTN wrappers because, I think,
all of the known browsers that have document.getElementByTagName also
have element.getElementByTagName. It seems overly paranoid to think
that sometime in a future such a browser will be created. The reason
for two feature tests is because document.getElementsByTagName is
defined in the DOM2 standard as part of Document and the other
getElementsByTagName is defined for Element so a test only for one
could be construed as object inference.

Even though the two getEBTN wrappers use different fallbacks for IE4
it looks like they could be combined into one just like I've done here
with no real performance penalty. If some browsers had document.all
and not element.all then that would be a problem but has that ever
been the case?

The API should be considered here. A getEBTN wrapper function like the
above could be called "getByCssSelector" and the calls to getEBTN
would become a subset of the possible calls to "getByCssSelector".
There is a standard in the works for something like
"document.getByCssSelector". So eventually there would be a very fast
host object that could be wrapped.

I think that having getEBTN as it's own function is good and both
getEBID and getEBTN could be wrapped in a function getByCssSelector.
This is something I'd like to add to the repository eventually. I did
a couple weeks of work on these types of functions a while ago and it
is very handy for enlivening pages for unobtrusive JavaScript. (Cue
"PointedEars" for a "lemmings" comment.)
 
P

Peter Michaux

On Dec 8, 5:37 am, David Mark <[email protected]> wrote:

[snip about gEBI wrappers for the repository]
if (document.getElementById) {
var getEBI = function(id, d) {
var el = (d||document).getElementById(id);
if (el.id == id) {
return el;
}
};

}

// the simplest possible implementation

if (document.getElementById) {
var getEBI = function(id, d) {
return (d||document).getElementById(id);
};
}

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

// an implementation that *fixes* the IE name/id bug
// I don't know what happens in the "hunt" if there
// are frames, iframes, or object elements in the DOM

var getEBI = (function() {

var el;

if (document.getElementById &&
typeof getAnElement != 'undefined' &&
((el=getAnElement()).firstChild || el.firstChild === null) &&
(el.nextSibling || el.nextSibling === null)) {

return function(id, d) {
var el = (d || document).getElementById(id);
if (el && el.id == id) {
return el;
}

function hunt(node, id) {
if (node.id == id) {
return node;
}
node = node.firstChild;

while (node) {
var el = hunt(node, id);
if (el) {
return el;
}
node = node.nextSibling;
}
}

return hunt(d, id);
}

}
})();
 
E

Evertjan.

Peter Michaux wrote on 09 dec 2007 in comp.lang.javascript:
// the simplest possible implementation

if (document.getElementById) {
var getEBI = function(id, d) {
return (d||document).getElementById(id);
};
}

What is the use of testing for getElementById,
as the js code will error anyway
when getEBI() is subsequently called while not defined?

// the simplest possible implementation

var getEBI = function(id, d) {
return (d||document).getElementById(id);
};
 
T

Thomas 'PointedEars' Lahn

Peter said:
Based on the repository design guidelines, the above could be
substantially reduced to just
[...]
Notes:

There is no need to check for the existence of "document" as no
browser, NN4+ and IE4+, missing "document".

There is no need to check that document.getElementById is a callable
since there has never been a known implementation where
document.getElementById that is not callable.

If these are the guidelines for your project, to produce code that is not
interoperable and inherently unreliable, I don't want to contribute.


PointedEars
 
V

VK

Based on the repository design guidelines, the above could be
substantially reduced to just

if (document.getElementById) {
var getEBI = function(id, d) {
var el = (d||document).getElementById(id);
if (el.id == id) {
return el;
}
};

}

That is an absolutely terrible way to implement a _runtime_ method.
DOM interface calls are the most used in any script and they have the
greatest impact to the performance. JS <=> DOM calls already much
slower then JS <=> JS calls and cutting their performance even further
for any bit is a crime to be punished :) :-|

Features/environment sniffing has to be done only _once_ on the
instantiation stage and _never_ in a loop on each invocation. So if
anyone still really cares of document.getElementById support - nobody
does in the world except some people in this NG but let's play
academics - then it must be function reference conditionally set once
for the identifier:
if (condition_1) {
$ = function(id){/* do this */};
}
else if (condition_2) {
$ = function(id){/* do that */};
}
else {
$ = function(id){/* do something */};
}
so on the further run $(id) will call the appropriate direct method.
 
D

David Mark

After a some reworking to support the "*" parameter properly in IE5
(my previous version failed to filter non-element nodes), here are my
proposals for various low-level DOM-related functions and the feature
tests that enable them to be created without issue. I welcome any
comments.

[nitpick mode on]

Please use a 70 character line length to avoid wrapping. Usenet wraps
at 72 so a 70 character line allows one reply without wrapping.

A 2 space "tab stop" conserves line width.

Neither of these are my personal preferences but are appropriate to
code that will be posted to Usenet.

<URL:http://cljs.michaux.ca/trac/wiki/DesignGuidelines>

[nitpick mode off]

[code quoted below reformatted and reduced to just that related to the
gEBI wrapper]




var element,
doc,
i;
var reFeaturedMethod = new RegExp('^function|object$', 'i');
var global = this;
// Test for host object properties that are typically callable
// (e.g. all, getElementById),
// which may be of type function, object (IE and possibly others)
// or unknown (IE ActiveX methods)
var isFeaturedMethod = function(o, m) {
var t = typeof(o[m]);
return !!((reFeaturedMethod.test(t) && o[m]) || t == 'unknown');
};
if (isRealObjectProperty(this, 'document')) {
doc = this.document;
// gEBI wrapper
element = (function() {
function idCheck(el, id) {
return (el && el.id == id) ? el : null;
}
if (isFeaturedMethod(doc, 'getElementById')) {
return function(id, docNode) {
return idCheck((docNode || doc).getElementById(id), id);
};
}
if (isFeaturedMethod(doc, 'all')) {
return function(id, docNode) {
return idCheck((docNode || doc).all[id], id);
};
}

}

Based on the repository design guidelines, the above could be
substantially reduced to just

if (document.getElementById) {
var getEBI = function(id, d) {
var el = (d||document).getElementById(id);
if (el.id == id) {
return el;
}
};

}

Notes:

There is no need to check for the existence of "document" as no
browser, NN4+ and IE4+, missing "document".

There is no need to check that document.getElementById is a callable
since there has never been a known implementation where
document.getElementById that is not callable.

Since this is a getter the name must start with "get". I'm tempted to
call it "getElementById" since that won't clash with
document.getElementById. "getEBI" is shorter.

It is clear that supporting IE4 requires quite a bit more code which
must be downloaded by everyone. I don't mean this to start a big
discussion about supporting IE4. It is just quite obvious in this
example.

A lot of the extra code is for the IE5 problem with the "*" parameter
(gEBTN.)
 
T

Thomas 'PointedEars' Lahn

VK said:
That is an absolutely terrible way to implement a _runtime_ method.
DOM interface calls are the most used in any script and they have the
greatest impact to the performance. JS <=> DOM calls already much
slower then JS <=> JS calls and cutting their performance even further
for any bit is a crime to be punished :) :-|

I have to agree with VK here. Lacking necessary feature testing (instead
of mere property true-value testing) aside, it is not necessary and it is
inefficient to do the test every time the feature is used.

That is why I refactored my dhtml.js recently to use factory functions to
assign suitable function references to the wrapper object's properties.


PointedEars
 
D

David Mark

Whoa, that is a lot of code!

I thought it best to start with every possibility and whittle it down
from there.
I didn't make a time test but I think the simulation of
Array.prototype.filter will be very slow. I made a CSS selector

Slow is relative. I don't know of any way to simulate filter any
faster. That's why I never use the "*" selector for gEBTN. I
included it only to illustrate what is required to work around that
problem.
function one time (something like jQuery has) and I found that any

I have found that a combination of gEBI and gEBTN is all I ever need
to home in on whatever elements I need to manipulate.
code that will operate on any medium sized DOM needs to be very fast.

As long as you don't use "*" with gEBTN, then these functions perform
well.
I also made a test between just simply looping over an array verses
using a function that takes another function and iterates over all of
an array. A simple loop wins by a long shot.

Without question. It will create longer functions that duplicate
code, but that may be the way to go in this case.
For the repository, the above code could be substantially reduced to
something like the following which will run much faster.

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

// from another file in the respository
if (document.documentElement) {
var getAnElement = function(d) {
return (d || document).documentElement;
};

}

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

// One implementation for developers not concerned with IE5
// problem of the browser thinking doctype and comments
// are elements.

if (document.getElementsByTagName &&
typeof getAnElement != 'undefined' &&
getAnElement().getElementsByTagName) {

var getEBTN = function(tag, root) {
var els = root.getElementsByTagName(tag);
if (tag == '*' && !els.length && root.all) {
els = root.all;
}
return els;
};

}

That works for that case.
// -----------------------

// Another implementation for developers concerned with
// the IE5 problem about doctype and comments

if (document.getElementsByTagName &&
typeof getAnElement != 'undefined' &&
getAnElement().getElementsByTagName) {

var getEBTN = function(tag, root) {
var els = root.getElementsByTagName(tag);

if (tag == '*' && !els.length && root.all) {
var all = root.all;
els = [];
for (var i=0, ilen=all.length; i<ilen; i++) {
var el = all;


I prefer not to declare variables inside of loops. And for long
loops, a while that counts down to 0 will be faster. Granted, you
have to reverse the results at the end.
// The following conditional could be factored out
// if necessary.
if ((el.nodeType == 1 && el.tagName != '!') ||
(!el.nodeType && el.tagName)) {
els[els.length] = el;
}
}
}

return els;
};

}

I haven't actually verified the IE5 bug yet and tested the above in a
wide set of browsers. I'm just looking at the packaging of the
concepts.

It isn't actually a bug, but a limitation of IE5's implementation of
gEBTN. It just doens't handle "*".
I think there is no need for two getEBTN wrappers because, I think,
all of the known browsers that have document.getElementByTagName also
have element.getElementByTagName. It seems overly paranoid to think
that sometime in a future such a browser will be created. The reason

I was actually more concerned with past browsers that may have
incomplete implementations of gEBTN.
for two feature tests is because document.getElementsByTagName is
defined in the DOM2 standard as part of Document and the other
getElementsByTagName is defined for Element so a test only for one
could be construed as object inference.

Right. That's why I avoided it.
Even though the two getEBTN wrappers use different fallbacks for IE4
it looks like they could be combined into one just like I've done here
with no real performance penalty. If some browsers had document.all
and not element.all then that would be a problem but has that ever
been the case?

That one isn't likely. It was the gEBTN inference that I wanted to
avoid. The previous discussion about this stemmed from the fact that
most libraries make similar inferences about addEventListener (which
requires three implementations: window, document and element.) It was
noted that agents do exist that support that method for elements, but
not for window.
The API should be considered here. A getEBTN wrapper function like the
above could be called "getByCssSelector" and the calls to getEBTN
would become a subset of the possible calls to "getByCssSelector".

If you feel we must have a getByCssSelector, then that makes sense. I
can't imagine a scenario where I would use such a thing.
There is a standard in the works for something like
"document.getByCssSelector". So eventually there would be a very fast
host object that could be wrapped.
Right.


I think that having getEBTN as it's own function is good and both
getEBID and getEBTN could be wrapped in a function getByCssSelector.

That is similar to what I do, except they are both wrapped in one
branch of an XPath wrapper (for browsers that don't support XPath.)
This is something I'd like to add to the repository eventually. I did
a couple weeks of work on these types of functions a while ago and it
is very handy for enlivening pages for unobtrusive JavaScript. (Cue
"PointedEars" for a "lemmings" comment.)

I haven't found a need for such queries and I agree with "PointedEars"
in that event delegation is often the best way to go anyway.
 
D

David Mark

On Dec 8, 5:37 am, David Mark <[email protected]> wrote:

[snip about gEBI wrappers for the repository]
if (document.getElementById) {
var getEBI = function(id, d) {
var el = (d||document).getElementById(id);
if (el.id == id) {
return el;
}
};

// the simplest possible implementation

if (document.getElementById) {
var getEBI = function(id, d) {
return (d||document).getElementById(id);
};

}

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

// an implementation that *fixes* the IE name/id bug
// I don't know what happens in the "hunt" if there
// are frames, iframes, or object elements in the DOM

Frames and IFrames won't enter into it.
var getEBI = (function() {

var el;

if (document.getElementById &&
typeof getAnElement != 'undefined' &&
((el=getAnElement()).firstChild || el.firstChild === null) &&
(el.nextSibling || el.nextSibling === null)) {

return function(id, d) {
var el = (d || document).getElementById(id);
if (el && el.id == id) {
return el;
}

function hunt(node, id) {
if (node.id == id) {
return node;
}
node = node.firstChild;

while (node) {
var el = hunt(node, id);
if (el) {
return el;
}
node = node.nextSibling;
}
}

return hunt(d, id);
}

}

})();

I thought about adding this, but decided it was too much extra code to
enable authors to write poor markup. It might be better to have the
function return null in such a case as it will clue the author that
something is wrong.
 
D

David Mark

Peter Michaux wrote on 09 dec 2007 in comp.lang.javascript:



What is the use of testing for getElementById,
as the js code will error anyway
when getEBI() is subsequently called while not defined?

The idea is that applications can more simply feature test API
abstractions than the individual features abstracted. Granted, this
particular example is too simple to illustrate this concept.
// the simplest possible implementation

var getEBI = function(id, d) {
return (d||document).getElementById(id);

};

In this particular case, that would be just as good (it isn't any more
complex to test getElementById.) But as soon as additional features
are abstracted by getEBI (e.g. document.all), it becomes simpler to
test the wrapper.
 
D

David Mark

Peter said:
Based on the repository design guidelines, the above could be
substantially reduced to just
[...]
Notes:
There is no need to check for the existence of "document" as no
browser, NN4+ and IE4+, missing "document".
There is no need to check that document.getElementById is a callable
since there has never been a known implementation where
document.getElementById that is not callable.

If these are the guidelines for your project, to produce code that is not
interoperable and inherently unreliable, I don't want to contribute.

I somewhat agree with that sentiment, which is why I presented both
tests, though not contributing at all would seem an extreme position.
 
D

David Mark

That is an absolutely terrible way to implement a _runtime_ method.
DOM interface calls are the most used in any script and they have the
greatest impact to the performance. JS <=> DOM calls already much
slower then JS <=> JS calls and cutting their performance even further
for any bit is a crime to be punished :) :-|

Features/environment sniffing has to be done only _once_ on the
instantiation stage and _never_ in a loop on each invocation.

Read it again. It is done only _once_.
 
D

David Mark

[snip]
I didn't make a time test but I think the simulation of
Array.prototype.filter will be very slow. I made a CSS selector

Considering that the use of the filter wrapper only helps IE5 and
under, it does make sense to duplicate its functionality in the gEBTN
wrapper. But that function and other JS1.6 array method wrappers are
useful for other purposes where speed is less of an issue. I think
that we should deal with those shortly.

Looking at the rest of my "base" module, I think these topics should
be considered next (in no particular order.)

These extend what is currently being discussed:
- Get child elements
- Get first and last child element (with optionally specified tagName)
- Get HTML and head elements

These are needed to complete basic feature testing support:
- Style support detection
- createElement and XML parse mode detection
- Element parent and owner document
- Element "canHaveChildren" test

Array and object testing, traversal and mutation:
- isArray
- push/pop
- 1.6 Array method wrappers (filter, map, some, every, forEach, etc.)
- For in filter.

Events (needed to support more advanced feature testing):
- addEventListener wrapper
- DOMContentLoaded simulation

Miscellaneous:
- Get and set element text
- Cookies (with optional support for ASP's keyed values.)
- Plug-in detection

I realize that the task of finalizing, adding and documenting each is
a bottleneck, but perhaps we should start discussing some of these in
parallel. Everybody has their own areas of interest, so this may
increase overall participation in the project.
 
P

Peter Michaux

Peter Michaux wrote on 09 dec 2007 in comp.lang.javascript:



What is the use of testing for getElementById,
as the js code will error anyway
when getEBI() is subsequently called while not defined?

The idea is to avoid errors.

A function that calls getEBI should only test for the existance of
gEBI and not document.getElementById. This is because the
implementation of getEBI may be changed and may not use
document.getElementById.
 
P

Peter Michaux

Peter said:
Based on the repository design guidelines, the above could be
substantially reduced to just
[...]
Notes:
There is no need to check for the existence of "document" as no
browser, NN4+ and IE4+, missing "document".
There is no need to check that document.getElementById is a callable
since there has never been a known implementation where
document.getElementById that is not callable.

If these are the guidelines for your project, to produce code that is not
interoperable and inherently unreliable,

Do you feature test everything?

Do you feature test that alert is callable?

Do you feature test that string concatenation works?

Do you feature test that float division works (e.g. 1/2 is 0.5)?

Do you feature test for the bugs in the implementation of feature "x"
in browser "y" that you have never seen? You could almost do this by
an insanely laborious process of checking things like float division
in 100 different combinations before doing float division.

Hopefully you answer "no" to at least one of those question. If so
then you see that the line has to be drawn somewhere.
 

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

No members online now.

Forum statistics

Threads
473,989
Messages
2,570,207
Members
46,782
Latest member
ThomasGex

Latest Threads

Top