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