Summary
You can do far more with Prototype than what I’ve just described, but the functions in
this chapter are the ones you’ll use most often. And although they solve common prob-
lems, they also form the foundation for a general scripting philosophy: one that espouses
fewer lines of code, separation of content and behavior, and the principle of least sur-
prise. Later on, you’ll learn how to use these functions within a set of conventions to
make your DOM scripting experience far more pleasant.
CHAPTER 2 ■ PROTOTYPE BASICS 29
Collections (Or, Never Write a
for Loop Again)
Collections are at the heart of DOM scripting—arrays, hashes, DOM NodeLists, and
various other groups of items. Nearly all your scripts will do some form of iteration over
an array. So why is iteration so bland in JavaScript?
Prototype sports a robust library for dealing with collections. It makes arrays
astoundingly flexible (and invents
Hash, a subclass of Object, for key/value pairs), but
can also be integrated into any collections you use in your own scripts.
The Traditional for Loop
Amazingly, the first version of JavaScript didn’t even support arrays. They were added
soon after, but with only one real enhancement over a vanilla
Object—a magic length
property that would count the number of numeric keys in the array. For example
var threeStooges = new Array();
threeStooges[0] = "Larry";
threeStooges[1] = "Curly";
threeStooges[2] = "Moe";
console.log(threeStooges.length);
//-> 3
The length property and the ubiquitous for looping construct result in a simple, low-
tech way to loop over an array’s values: start at
0 and count up to the value of length.
types can be done with functions.
This enables a different approach to iteration: since functions can be passed as
arguments to other functions, you can define a function for iterating over an array.
Again, this is easier to explain with code than with words:
function each(collection, iterator) {
for(vari=0;i<collection.length; i++)
iterator(collection[i]);
}
CHAPTER 3 ■ COLLECTIONS (OR, NEVER WRITE A FOR LOOP AGAIN)32
The iterator argument is a function. The each method we just wrote will loop over an
array’s indices and call
iterator on each, passing into it the current item in the array.
Now we can iterate thusly:
var paragraphs = $$('p');
each(paragraphs, makeTextRed);
Also remember from Chapter 1 that functions have a literal notation—you don’t have
to name a function before you use it. If we won’t use the
makeTextRed function anywhere
else in the code, then there’s no reason to define it beforehand.
each(paragraphs, function(element) {
element.style.color = "red";
});
We can make one more improvement to our code. Since each is a method made to
act on arrays, let’s make it an instance method of all arrays:
Array.prototype.each = function(iterator) {
for (var i = 0; i < this.length; i++)
iterator(this[i]);
};
Remember that this refers to the execution scope of the function—in this case, it’s
the array itself. Now we can write the following:
Using Enumerable#each
Enumerable#each is the foundation that the rest of Enumerable relies upon, so let’s take
a closer look at it.
I’ve been defaming
for loops for several pages now, but they do have one critical
advantage over functional iteration: they let you short-circuit the iteration flow by
using the keywords
break (abort the loop) and continue (skip to the next item in the
loop). We need a way to emulate these keywords if we want to match the feature set
of traditional loops.
var elements = $$('.menu-item');
// find the element whose text content contains "weblog"
for (var i = 0, element; element = elements[i]; i++) {
if (!element.id) continue;
if (element.innerHTML.include('weblog')) break;
}
Simulating continue is easy enough—an empty return within a function will do
the trick:
var elements = $$('.menu-item'), weblogElement;
elements.each( function(element) {
if (!element.id) return;
/* */
});
But a break equivalent takes a bit of voodoo. Prototype makes smart use of excep-
tions to pull this off. It creates a
$break object that can be thrown within loops to exit
immediately.
CHAPTER 3 ■ COLLECTIONS (OR, NEVER WRITE A FOR LOOP AGAIN)34
var elements = $$('.menu-item'), weblogElement;
elements.each( function(element) {
function isEven(number) {
return number % 2 == 0;
}
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10].detect(isEven);
//-> 2
If there ar
e no matches,
detect will return false.
CHAPTER 3 ■ COLLECTIONS (OR, NEVER WRITE A FOR LOOP AGAIN) 35