In case you haven't already used it, prototype.js is a JavaScript library written by Sam Stephenson. This amazingly well thought and well written piece of standards-compliant code takes a lot of the burden associated with creating rich, highly interactive web pages that characterize the Web 2.0 off your back.
If you tried to use this library recently, you probably noticed that documentation is not one of its strongest points. As many other developers before me, I got my head around prototype.js by reading the source code and experimenting with it. I thought it would be nice to take notes while I learned and share with everybody else.
As you read the examples and the reference, developers familiar with the Ruby programming language will notice an intentional similarity between Ruby's built-in classes and many of the extensions implemented by this library.
The $() function is a handy shortcut to the all-too-frequent document.getElementById() function of the DOM. Like the DOM function, this one returns the element that has the id passed as an argument.
Unlike the DOM function, though, this one goes further. You can pass more than one id and $() will return an Array object with all the requested elements. The example below should illustrate this.
This is a paragraph
This is another paragraph
Another nice thing about this function is that you can pass either the id string or the element object itself, which makes this function very useful when creating other functions that can also take either form of argument.
The $F() function is a another welcome shortcut. It returns the value of any field input control, like text boxes or drop-down lists. The function can take as argument either the element id or the element object itself.
The $A() function converts the single argument it receives into an Array object.
This function, combined with the extensions for the Array class, makes it easier to convert or copy any enumerable list into an Array object. One suggested use is to convert DOM NodeLists into regular arrays, which can be traversed more efficiently. See example below.
The $H() function converts objects into enumerable Hash objects that resemble associative arrays.
We are all familiar with for-loops. You know, create yourself an array, populate it with elements of the same kind, create a loop control structure (for, foreach, while, repeat, etc,) access each element sequentially, by its numeric index, and do something with the element.
When you come to think about it, almost every time you have an array in your code it means that you'll be using that array in a loop sooner or later. Wouldn't it be nice if the array objects had more functionality to deal with these iterations? Yes, it would, and many programming languages provide such functionality in their arrays or equivalent structures (like collections and lists.)
Well, it turns out that prototype.js gives us the Enumerable object, which implements a plethora of tricks for us to use when dealing with iterable data. The prototype.js library goes one step further and extends the Array class with all the methods of Enumerable.
In standard javascript, if you wanted to sequentially display the elements of an array, you could very well write something like this.
With our new best friend, prototype.js, we can rewrite this loop like this.
You are probably thinking "big freaking deal...just a weird syntax for the same old thing." Well, in the above example, yes, there's nothing too earth shattering going on. After all, there's not much to be changed in such a drop-dead-simple example. But keep reading, nonetheless.
Before we move on. Do you see this function that is being passed as an argument to the each method? Let's start referring to it as an iterator function.
Like we mentioned above, it's very common for all the elements in your array to be of the same kind, with the same properties and methods. Let's see how we can take advantage of iterator functions with our new souped-up arrays.
Finding an element according to a criteria.
Now let's kick it up another notch. See how we can filter out items in arrays, then retrieve just a desired member from each element.
This <a href="http://othersite.com/page.html">text</a> has
a <a href="#localAnchor">lot</a> of
<a href="#otherAnchor">links</a>. Some are
<a href="http://wherever.com/page.html">external</a>
and some are <a href="#someAnchor">local</a>
It takes just a little bit of practice to get completely addicted to this syntax. Next we will go through the available functions with the following example.
I used to find myself writing a lot of for loops. Although,
Prototype does not by any means eliminate the need to do for-loops,
it does give you access to what I consider to be a cleaner, easier to read method in each.
The each function allows us to iterate over these objects Ruby style.
The each function takes one argument, an iterator function. This iterator is invoked once for every item in the array, and that item along with the optional index is passed to the iterator. So if we also needed the index we could do something like the code below.
Hashes can be created by wrapping an Object (associative array) in $H() and can have their key/value pairs exposed.
We can also directly access the keys and values of a Hash without iterating over it.
The collect function allows you to iterate over an Array and return the results as a new array. Each item returned as a result of the iteration will be pushed onto the end of the new array.
You can even join on the end of the block.
The include function allows you to check if a value is included in an array and returns true or false depending on if a match was made. Assuming I put up a form asking the user to name some artist in my iTunes playlist, we could do something like the code below. Prime candidate for some conditional madness.
The inject function is good for getting a collective sum from an array of values. For instance, to add up all the numbers.
The first argument to inject is just an initial value that would be added to the sum, so if we added 1 instead of 0, the output would be 162.
When given an Array, the findAll function will return an array of items for which the iterator evaluated to true. Basically, it allows you to build a new array of values based on some search criteria. If we wanted to find all products whose type was “E-Commerce” we could do something like the code below.
Note that even if only one match is made, just as in this case, the result is still returned as an array. In that case, ecom.company would return undefined.
Unlike the findAll function, the detect function will only return the first item for which the expression inside the iterator is true. So, if we wanted to find the first number that was greater than 5 we’d do something like the code below.
Even though, there are other numbers above 5 in our array, detect only gives us the first match back.
The invoke function allows us to pass a method as a string and have that method invoked. For instance, if we wanted to sort our array of artists we’d do something like this:
Why not just use F.Artists.sort? Well, for the example above we could do just that, but here is where invoke shines.
So we invoked sort for each sub-array. Note that the code below will not work.
The reason this will not work is because it is taking each item in that array and trying to apply sort to it, thus if we wrote it outright, it would look something like this:
We could however do something like this:
Now, what about passing arguments to the invoke function? The first argument passed to invoke is the method to be invoked, and any other arguments beyond that will be passed as arguments to the invoked method.