Velocity Reviews - Computer Hardware Reviews

Velocity Reviews > Newsgroups > Programming > Javascript > Various DOM-related wrappers (Code Worth Recommending Project)

Reply
Thread Tools

Various DOM-related wrappers (Code Worth Recommending Project)

 
 
David Mark
Guest
Posts: n/a
 
      12-08-2007
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(documentElementsByTagNam e)?
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[i]] = typeof(html.style[styles[i]]) ==
'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);
};
})();
}
})();
}
 
Reply With Quote
 
 
 
 
David Mark
Guest
Posts: n/a
 
      12-08-2007
On Dec 8, 8:37 am, David Mark <(E-Mail Removed)> wrote:

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.)
 
Reply With Quote
 
 
 
 
Peter Michaux
Guest
Posts: n/a
 
      12-09-2007
On Dec 8, 5:37 am, David Mark <(E-Mail Removed)> wrote:
> 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.

--
Peter
Code Worth Recommending Project
http://cljs.michaux.ca
 
Reply With Quote
 
AKS
Guest
Posts: n/a
 
      12-09-2007
On 8 ΔΕΛ, 23:46, David Mark <(E-Mail Removed)> wrote:
> On Dec 8, 8:37 am, David Mark <(E-Mail Removed)> wrote:


> var l = a.length
> ...
> if (typeof(l) != 'undefined')


What this checking is necessary for? The index of an array can be type
"undefined"?
 
Reply With Quote
 
Peter Michaux
Guest
Posts: n/a
 
      12-09-2007
On Dec 8, 5:37 am, David Mark <(E-Mail Removed)> wrote:

[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(documentElementsByTagNam e)?
> 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[i];
// 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.)

--
Peter
Code Worth Recommending Project
http://cljs.michaux.ca

 
Reply With Quote
 
Peter Michaux
Guest
Posts: n/a
 
      12-09-2007
On Dec 8, 5:32 pm, Peter Michaux <(E-Mail Removed)> wrote:
> On Dec 8, 5:37 am, David Mark <(E-Mail Removed)> 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);
}

}
})();

--
Peter
Code Worth Recommending Project
http://cljs.michaux.ca
 
Reply With Quote
 
Evertjan.
Guest
Posts: n/a
 
      12-09-2007
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);
};


--
Evertjan.
The Netherlands.
(Please change the x'es to dots in my emailaddress)
 
Reply With Quote
 
Thomas 'PointedEars' Lahn
Guest
Posts: n/a
 
      12-09-2007
Peter Michaux wrote:
> 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
 
Reply With Quote
 
VK
Guest
Posts: n/a
 
      12-09-2007
On Dec 9, 4:32 am, Peter Michaux <(E-Mail Removed)> wrote:
> 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.
 
Reply With Quote
 
David Mark
Guest
Posts: n/a
 
      12-09-2007
On Dec 9, 12:00 am, AKS <(E-Mail Removed)> wrote:
> On 8 ΔΕΛ, 23:46, David Mark <(E-Mail Removed)> wrote:
>
> > On Dec 8, 8:37 am, David Mark <(E-Mail Removed)> wrote:
> > var l = a.length
> > ...
> > if (typeof(l) != 'undefined')

>
> What this checking is necessary for? The index of an array can be type
> "undefined"?


Typo. Should have been checking a[l].
 
Reply With Quote
 
 
 
Reply

Thread Tools

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off
Trackbacks are On
Pingbacks are On
Refbacks are Off


Similar Threads
Thread Thread Starter Forum Replies Last Post
status and plan (Code Worth Recommending Project) Peter Michaux Javascript 0 12-13-2007 03:11 AM
isFunction (Code Worth Recommending Project) David Mark Javascript 25 12-12-2007 01:56 AM
form serialization (Code Worth Recommending Project) Peter Michaux Javascript 45 12-07-2007 11:38 PM
url encoding string (Code Worth Recommending Project) Peter Michaux Javascript 16 11-26-2007 07:44 PM



Advertisments