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. Among other things, it includes the extremely neat $ (dollar) function:

  1. function $() {
  2. var elements = new Array();
  3. for (var i = 0; i < arguments.length; i++) {
  4. var element = arguments[i];
  5. if (typeof element == 'string')
  6. element = document.getElementById(element);
  7. if (arguments.length == 1)
  8. return element;
  9. elements.push(element);
  10. }
  11. return elements;
  12. }

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?

  1. function $() {
  2. var elements = new Array();
  3. for (var i=0,len=arguments.length;i<len;i++) {
  4. var element = arguments[i];
  5. if (typeof element == 'string') {
  6. var matched = document.getElementById(element);
  7. if (matched) {
  8. elements.push(matched);
  9. } else {
  10. var allels = (document.all) ? document.all : document.getElementsByTagName('*');
  11. var regexp = new RegExp('(^| )'+element+'( |$)');
  12. for (var i=0,len=allels.length;i<len;i++) if (regexp.test(allels[i].className)) elements.push(allels[i]);
  13. }
  14. if (!elements.length) elements = document.getElementsByTagName(element);
  15. if (!elements.length) {
  16. elements = new Array();
  17. var allels = (document.all) ? document.all : document.getElementsByTagName('*');
  18. for (var i=0,len=allels.length;i<len;i++) if (allels[i].getAttribute(element)) elements.push(allels[i]);
  19. }
  20. if (!elements.length) {
  21. var allels = (document.all) ? document.all : document.getElementsByTagName('*');
  22. 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]);
  23. }
  24. } else {
  25. elements.push(element);
  26. }
  27. }
  28. if (elements.length == 1) {
  29. return elements[0];
  30. } else {
  31. return elements;
  32. }
  33. }
  34.  
  35. 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.

In summary, it is a complete replacement for:

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:

Digg this article

Bookmark this article with del.icio.us

Previously: Post Christmas mash-up

Next: Hooray - @media2006 announced at last


Comments

Jens
1527 days ago
Thats great and more easy as always type the long document…

Jens
#1
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
Jim
1517 days ago
I see how it’s useful for its verastility, but how does using a $ function vs. a specific getElementById call affect performance?
#4
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