A better dollar function - getElementsByAnything
Anyone with more than a passing interest in modern Javascript techniques is probably aware of the prototype framework. 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;}}- Download this code: /code/extended-dollar.txt
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 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
getElementsByClassNamefunctions currently in use); - If none of those match, it will try for matching tags using the built-in
document.getElementsByTagNamemethod; - 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.
Filed under: Javascript.
Technorati tags: javascript prototype
Bookmark this article with del.icio.us
Previously: Post Christmas mash-up
Next: Hooray - @media2006 announced at last
Comments
- Jakob E
- 1519 days ago
- Hi Matthew,
@ In summary, it is a complete eplacement for:
* getElementById
* getElementsByClassName
* getElementsByTagName
If your document conatins:
<div id="p">[A]</div> <p>[B]</p> <p>[C]</p>
... how does $(‘p’) replace getElementsByTagName?
or
<div id="content">[A]</div> <div class="content">[A]</div> <div class="content">[A]</div>
... how does $(‘container’) replace getElementsByClassName?
My point is – You need to know what you’re asking for.
~ Jakob E - #2
- Matthew Pennell
- 1518 days ago
- You’re absolutely right, Jakob – if you have used identical id and class names in your document, or used any tag names as id/class names, you may have problems referencing the right elements.
Personally I would say that if you are doing that, though, you need to think a bit more about your naming conventions! - #3
- Matthew Pennell
- 1517 days ago
- Jim: I don’t know, I didn’t do any benchmark tests (and I also don’t know what the built-in functions like getElementById actually do ‘behind the scenes’).
- #5
- Jonathan Snook
- 1508 days ago
- It looks like the new version of Prototype will include a similar feature using $$:
Prototype adds CSS selector function - #6
- Matthew Pennell
- 1508 days ago
- Slightly similar, I guess, although mine is more of a bundled function than an extension of existing functionality.
- #7
Jens