Writing Custom Iterators For Prototype
Traversing the DOM can be a painful beast at times, but we can remove a lot of that pain by writing a simple set of iterators that will allow us to pick it apart at will.
When you ask for the childNodes of an element, most of the time you’re just wanting the element nodes only and you’d like to completely ignore the text nodes. Using Prototype, you could write something like the code below:
element = $(element);
element.cleanWhitespace();
$A(element.childNodes).each(function(element) {
element.setStyle({color: '#ccc'});
});
That would be a quick fix, but remember, we’re not supposed to be repeating ourselves. I find myself wanting to perform a task like this time and time again. Basically the code above just grabs a parent element, cleans the whitespace from it (removes the text nodes) and gives us back only the elements which we wanted to begin with. It’s much simpler to DRY up this process by implementing our own custom iterator.
The eachElement Method
We can easily reuse the code above by appending an eachElement method to the Element.Methods object.
Element.addMethods({
eachElement: function(element, iterator) {
element = $(element);
element.cleanWhitespace();
$A(element.childNodes).each(iterator);
}
});
Now that we’ve got this in place, it’ll make our life a lot easier, our code a lot less redundant, and anyone who reads our code will probably have a better understanding of what we’re doing. Never mind the fact it just rocks being able to do something like this.
$('options').eachElement(function(element) {
console.log(element);
});
Tag-based Iterators
The eachElement method is very nice by itself, but what if we’re still repeating ourselves? Maybe we only want the form elements inside a div? We could use our eachElement method and filter out elements by their tag name.
$('options').eachElement(function(element) {
if(element.tagName.toLowerCase() == 'form')
//Do stuff with forms
});
We don’t want to have to write these conditional checks inside our eachElement method every time we want to filter out a certain tag, but at the same time, we don’t want to write a method for every tag available to an HTML document, nor do we want to have a switch statement that checks for every tag. With this in mind, what’s the most effective way to achieve tag-based iterators? We’ll dynamically add methods at runtime.
Basically, we want to write a method that writes methods and we want this method to execute as soon as our Javascript is loaded. We want to dynamically create methods such as eachDiv, eachLi, eachSelect, etc. So lets look at the code to make this possible:
var Iterators = function() {
var tags = "div p span ul ol li span form input select textarea h1 h2 h3 h4 h5 h6 dl dt em strong";
var methods = {};
$A(tags.split(' ')).each(function(tag) {
methods["each" + tag.charAt(0).toUpperCase() + tag.substring(1)] = function(element, iterator) {
element = $(element);
element.cleanWhitespace();
$A(element.getElementsByTagName(tag)).each(iterator);
}
});
Element.addMethods(methods);
}();
If you look at the code, you’ll notice the list of tags we have. Now I don’t have all the tags here, but enough for you to get the point. After we have our tag list, we can create an object to hold our methods, and then dynamically start building our iterators.
The line below is where the magic happens:
methods["each" + tag.charAt(0).toUpperCase() + tag.substring(1)] = ...
Considering I want to follow the camelCase nature of Javascript convention, I’m breaking off the first character and up-casing it. So instead of creating methods like eachselect, I’ll get eachSelect. Prototype has a camelize method of the String object, but it doesn’t work unless there is an underscore or dash in the name.
When we’ve built our method object up, we can now assign these methods to the Element.Methods object using Element.addMethods. Once this is run we can now do some pretty cool stuff like:
$('list').eachLi(function(item) {
item.setStyle({color: '#ccc'});
});
The one funky thing you might notice is the last line. By adding the parentheses we create a self-invoking function. This function automatically executes after it’s loaded, thus creating our methods before we need to use them.
So there it is, pretty short, pretty sweet, extremely handy. You could certainly build some really cool stuff using this type of dynamic method generation. Until next time!
Sorry, comments are closed for this article.



Discussion
and use it like this:
Or have aeachElement()method that could accept a tag as argument, otherwise it gets all elements.Wow, that’s just about the slickest thing I’ve seen that you can do with Prototype!
One question, this:$A(element.childNodes).each(iterator);Is there a hidden return going on there?
I really wouldn’t spend this effort just to overwrite existing DOM statements.
We already can iterate this way:
But I suppose the Iterators argument is for you just the occasion for introducing dynamic method generation, that is far more interesting. And this is a great tutorial about it.
Why not just jump the hoops and get Ruby into Firefox, Opera, and IE?
I do appreciate that JavaScript is turning away from Java (as some lunatics had envisioned it could resemble) and in the direction of Ruby.
I’m not sure that cleanWhitespace() does what you say it does. It will only remove text nodes that are pure whitespace. Depending on your markup you will probably have text nodes with real content. Here might be an alternate implementation that will do what you want.
@Justin: There isn’t a secret return. If your confused by the short hand syntax, your allowed to pass in a method that accepts whats being iterated as it’s first argument. It will automatically be passed as an argument to the iterator. Here is a more common example:
@Eric, your absolutely right. Thanks for catching that.
Thanks Justin, I was actually confused because I thought we had a little ruby thing going on there (the last statement eval’d is returned), but now that I take a closer look I see that’s not going on at all.
Thanks for clarifying :)
Excellent post. I’m still amazed at how good Prototype actually is.
Hi Justin,
Reading your code and especially, the following lines:
- the like of which I’ve seen around elsewhere quite recently -I thaught (after discussion on irc) that Prototype could perhaps beneficiate from even more syntactic sugar by adding the following ruby flavored function:making the following possible:
The only caveat being, of course, speed… any benchmarks around anyone?
On a completely different topic: how’s your book going? Any deadlines for the first beta issue? I’m really looking forward to reading it.
Another great article btw.
Regards,
Tobie
I added a “capitalize” method to
String.prototypea while back to solve this problem:undefinedto the varibleIteratorsbecause your function returns no value. Surely this assignment serves no purpose?henrah: You’ll get a syntax error if you don’t assign the function to a variable name.