Home » Guides Advanced Article

A better dollar function - getElementsByAnything

5.0/5.0 (5 votes total)
Rate:

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:

  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. }

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.


Add commentAdd comment (Comments: 1)  
Title: Nice Work! May 2, 2007
Comment by James Andrews

It seemed to me that the dollar function ${} was a little overrated. I am building a javascript library and hunting for candidates to include and was not inclined to include ${}. However, the getelementbyanything idea would be worth it. Indeed some elements don't have ids, but have classes. Some only have attributes, and some are plain tags and are only accessed to change style. So you see, I can remodel even old pages with lame inline styling or no ids, pretty easily with this enhanced ${}. The only improvement I can think of would be to permit the passing of an object reference as well as a string (using typeof test).
Anyway, this is pretty good.

Advertisement

Partners

Related Resources

Other Resources

arrow