A better dollar function - getElementsByAnything
|
|
|
| 5.0/5.0 (5 votes total) |
|
|
|
Matthew Pennell July 27, 2006
|
Matthew Pennell |
Matthew is 31, tall and skinny, married, and is employed as a Senior Web Designer. His favourite colour is green, and the way he spells ‘favourite’ and ‘colour’ should tell you that he is from the UK. thewatchmakerproject.com
|
Matthew Pennell
has written 1 articles for JavaScriptSearch. |
View all articles by Matthew Pennell... |
A better dollar function - getElementsByAnything
Jan 03 2006
Anyone with more than a passing interest in modern Javascript techniques is probably aware of the Prototype framework (http://prototype.conio.net) . Among other things, it includes the extremely neat $ (dollar) function:
function $() {
var elements = new Array();
for (var i = 0; i < arguments.length; i++) {
var element = arguments[i];
if (typeof element == 'string')
element = document.getElementById(element);
if (arguments.length == 1)
return element;
elements.push(element);
}
return elements;
}
Simply pass it one or more strings representing element ids, and get back an array of all those elements (if only one string is passed, a single object reference is returned). So instead of laboriously typing var el = document.getElementById('myobj'); we have the much neater and quicker
var el = $('myobj'); .
Extended remix
But there are several other long functions that get used all the time – getElementsByTagName , getElementsByClassName – which we still have to type out long-hand… why not combine all of those into the one simple function?
function $() {
var elements = new Array();
for (var i=0,len=arguments.length;i<len;i++) {
var element = arguments[i];
if (typeof element == 'string') {
var matched = document.getElementById(element);
if (matched) {
elements.push(matched);
} else {
var allels = (document.all) ? document.all : document.getElementsByTagName('*');
var regexp = new RegExp('(^| )'+element+'( |$)');
for (var i=0,len=allels.length;i<len;i++) if (regexp.test(allels[i].className)) elements.push(allels[i]);
}
if (!elements.length) elements = document.getElementsByTagName(element);
if (!elements.length) {
elements = new Array();
var allels = (document.all) ? document.all : document.getElementsByTagName('*');
for (var i=0,len=allels.length;i<len;i++) if (allels[i].getAttribute(element)) elements.push(allels[i]);
}
if (!elements.length) {
var allels = (document.all) ? document.all : document.getElementsByTagName('*');
for (var i=0,len=allels.length;i<len;i++) if (allels[i].attributes) for (var j=0,lenn=allels[i].attributes.length;j<lenn;j++) if (allels[i].attributes[j].specified) if (allels[i].attributes[j].nodeValue == element) elements.push(allels[i]);
}
} else {
elements.push(element);
}
}
if (elements.length == 1) {
return elements[0];
} else {
return elements;
}
}
So, what does it do?
Pseudo-specificity
This function works in exactly the same way as the “prototype.js” $ function by accepting a comma-separated list of strings, but it also emulates CSS specificity (see http://www.stuffandnonsense.co.uk/archives/css_specificity_wars.html) in its matching.
- It will first attempt to find an element with a matching
id ;
- If none are found, it will next look for matching class names (so replacing any of the existing
getElementsByClassName functions currently in use);
- If none of those match, it will try for matching tags using the built-in
document.getElementsByTagName method;
- Next, it gives up on CSS specificity and tries to match any other attribute (e.g.
rel , type , title , etc.);
- Finally, if none of those attributes were matched it checks the actual values of element attributes.
In summary, it is a complete replacement for:
- getElementById
- getElementsByClassName
- getElementsByTagName
Plus some extra bits thrown in. The function will only keep looking until it finds a match, so the more inefficient loops (checking every attribute value on the page!) are only run if none of the other checks matched.
Very nice, but what’s it for?
Aside from reducing the typing needed for common method calls, this function can be used to combine several things into one line. Say we wanted to grab all the form elements (inputs, selects and textareas) on a page into one array – we might do it like this:
var inputs = document.getElementsByTagName('input');
var selects = document.getElementsByTagName('select');
var ta = document.getElementsByTagName('textarea');
var elements = inputs.concat(selects.concat(ta));
How much easier would it be to do it like this:
var elements = $('input','select','textarea');
As the function arguments can be a combination of id, class, tagname, attribute or value (or an object reference), quite complex arrays of objects can be built quickly and easily.
Summary
I’ve tested the code in Firefox 1.0 and IE6 – if anyone finds that it doesn’t work correctly on other browsers/OSes, please let me know in the comments on my website at thewatchmakerproject.com.
|