Techy Treasures #4: What’s inside a dollar function?

Today’s Techy Treasure is another basic staple for me, something simple, but which I use in virtually every script I write. It’s a method for retrieving element references — either single elements, or collections of elements referenced by tag name, and optionally filtered by attribute match.

Most JavaScript libraries have a function like this, often calling it $ (dollar). And while all of them work slightly differently, they all do basically the same thing. So if you’re new to JavaScript, and been wondering what’s inside those ubiquitous dollar functions, here’s a bit of anatomy for you!

As with all the methods I’ve written about in this column, I’m demonstrating it here as a global function, to make the examples more straightforward; but in practise you’d do better to define it as a method of a custom object:

function get(find, context, conditions)
{
	if(find.indexOf('#') != -1)
	{
		return document.getElementById(find.split('#')[1]);
	}

	else 
	{
		if(typeof context == 'undefined') 
		{ 
			context = document; 
		}
		
		var nodes = [], tmp = context.getElementsByTagName(find);
		for(var i=0; i<tmp.length; i++)
		{
			nodes.push(tmp[i]);
		}
		
		if(typeof conditions == 'undefined')
		{
			return nodes;
		}
		
		var filtered = [];
		for(i=0; i<nodes.length; i++)
		{
			var add = true;
			for(var c in conditions)
			{
				if(!conditions.hasOwnProperty(c)) { continue; }
				
				var attr = c == 'class' 
					? nodes[i].className : nodes[i].getAttribute(c);
					
				if(attr == null 
					|| attr == '' 
					|| new RegExp('(' + conditions + ')', '').test(attr) == false)
				{
					add = false;
				}
			}
			if(add == true)
			{
				filtered.push(nodes[i]);
			}
		}
		return filtered;
	}
}

The get() method does three things. Firstly, it can retrieve a single element, a shorthand for getElementById:

var content = get('#content');

Secondly, it can retrieve a collection of elements, a shorthand for getElementsByTagName:

var paragraphs = get('p');

But thirdly, and most interestingly I think, it can filter a collection of elements according to attribute matches. What this feature amounts to is a getElementsByAttributeMatch() method, where multiple attributes can be matched in a single expression:

var codeblocks = get('code', document, { 
	'class':'javascript' 
	});

This third example is retrieving a collection of <code> elements which have the class name “javascript”; or to be more precise, elements where the value of the class attribute contains the string “javascript”.

In fact the value parameter of each key/value pair is evaluated as a regular expression, so you could do things like this:

var codeblocks = get('code', document, { 
	'class':'j(ava)?script|php' 
	});

You’ll notice in those examples that there’s a second argument before the condition object, which defines the context of the search. You can pass in a different document reference to retrieve a collection in that context (such as a page inside an iframe), or you can pass in an element reference to retrieve a collection of child elements within a specific element (such all list-items within a list).

Finally, what this method returns also depends on what you ask it for. For a single element it will return either that element or null; for a collection of elements (whether filtered by attribute conditions or not) it will return an array of elements (a true JavaScript array, not a DOM collection), and if no matching elements were found this array will be empty.

Free book: Jump Start HTML5 Basics

Grab a free copy of one our latest ebooks! Packed with hints and tips on HTML5's most powerful new features.

  • Lachlan

    John Resig’s Sizzle library is excellent for this, lean, fast and very readable code.

    http://github.com/jeresig/sizzle/tree/master/sizzle.js

  • Jeremy

    Lachlan hit the nail on the head; why would anyone want to use this wannabe-library with a single “get” function when every major library has a much better implementation of $ (not to mention that those libraries also have a ton of useful functions BESIDES $). However, I disagree with Lachlan on using Sizzle directly; it may be appealing because it is used by several major libraries, but the VAST majority of developers would be better of using a library that implements Sizzle rather than using Sizzle itself.

  • Lachlan

    I actually was recommending the library as a way to learn how it works (which was the point of the post), it’s very well written code.

  • http://www.brothercake.com/ brothercake

    If you’re not interested in learning, Jeremy, that’s your choice. But please don’t waste other people’s time with your dismissive comments.

  • kangax

    Interesting snippet.

    A couple of notes:
    1) The scripts doesn’t take care of some attributes conversion (it only takes care of “class” -> “className” conversion but not, say, “for” -> “htmlFor”)

    2) When creating regexp, it’s a good idea to escape meta characters, since trying to match something like “?” will throw SyntaxError. I would just treat strings as strings and allow regexp objects as values.

  • SalvatoreC

    Thanks for being so kind and writing and publishing this code!

    I recently wrote something similar but not as complex, and feel very gratefull to see someone who knows more about Javascript than I demonstrate something similar.

    Lately I’ve been thinking I’m not writing enough code to learn how JavaScript really works without even considering cross-browser issues. I must write more code! I’m looking at too much code and not writing enough examples.

    Thanks again!
    I’m learning a lot!