SitePoint Sponsor
Article
Dan Webb
Dan is a rehabilitated DHTML hacker turned web application developer for Vivabit,
organisers of the @media conference. His current weapons of choice are
Ruby on Rails and modern, unobtrusive DOM scripting. On those occasions
he's not programming, he enjoys blogging on Vivabit's blog, (The Web's Bollocks), hording vast amounts of vinyl and eating cheese slices.
Painless JavaScript Using Prototype
By Dan Webb
February 22nd 2006
Reader Rating: Not rated yet
Prototype is an object oriented JavaScript library (written by Sam Stephenson and friends) that makes JavaScript fun. So it says on the site, anyway. Those of you who are familiar with the open source community's latest and greatest application framework, Rails, may recognise Prototype as it actually forms the backbone of Rails' JavaScript helper. However, Prototype can be used independently of Rails to aid the coding of many JavaScript doodads and Web 2.0 thingy wangles.
Personally, I think the jury's out on the whole 'fun' JavaScript thing, but nevertheless Prototype is a really well executed JavaScript library which, although the situation has been improving of late, has had notoriously sparse documentation. This article provides a whirlwind tour of the whole library. It aims to give you enough examples and resources to get started using Prototype in your DOM scripting projects.
First, we'll examine the basic building blocks of Prototype: its $ functions, its additions to the String, Number, Array and Function objects, its form handling capabilities and its DOM functions. Then, we'll move on to look at Prototype's well-known AJAX helpers. Finally, we'll finish with a brief discussion of other interesting projects that are based on it.
I'd like to note that the current stable version of Prototype at time of writing is 1.4.0. I have a feeling that the library will change quite quickly in response to Rails' lightning-quick development cycle, so things will change. The final boring note is that at this time Prototype only supports the newer browsers -- as you might expect of a DOM and XMLHttpRequest based library. See the Prototype site for details of browser support.
Getting Started
The latest version of Prototype can be downloaded from the prototype site. Simply download prototype.js and link it to your pages with a <script> tag:
<script type="text/javascript" src="path/to/prototype.js"></script>
If you're on Rails, you don't need to download Prototype: it's
included in the distribution. You can include it into your views by
putting this into the <head> of your pages:
<%= javascript_include_tag 'prototype' %>
Now, let's get into it!
Prototype's Little Helpers
One of the really nice things about using Prototype is the deadly
simple helper functions that it provides for very common scripting
tasks. The $ function has already been getting some attention. Give it one or more element IDs, and it'll return references to them:
// reference to the element with the ID 'nav'
$("nav")
// an array of element references
$("img1", "img2", "img3")
It's like a souped-up document.getElementById and it's amazing how much more convenient coding seems when you use it.
Another incredibly useful function is document.getElementsByClassName, which does what it says on the tin: it takes a CSS class name and returns a list of all elements with that class:
// all elements with class 'navlink'
document.getElementsByClassName("navlink")
// all elements with class navlink and inside the element with ID 'nav'
document.getElementByClassName("navlink", $("nav"))
Also, as this article was being written, Prototype version 1.5.0_rc0 gained the powerful $$ function, which allows you to select elements using standard CSS selector syntax:
// an array of all input elements inside 'commentform'
$$("#commentform input")
// an array of all links with the class 'external'
$$("a.external")
Please note that, at the time of writing, unless you download the very latest version of Prototype from Subversion, this function won't be available to you.
$F takes an ID and returns the value of any form field, for instance, a select box like this:
<select name="country" id="country">
<option selected="selected" value="UK">United Kingdom</option>
<option value="FR">France</option>
...
</select>
$F('country') // 'UK'
Making JavaScript Suck Less
Oops, I've stolen another JavaScript library's tag line. JavaScript library developers just can't seem to keep from trying to make JavaScript be like another language. The Mochikit guys want JavaScript to be Python, countless programmers have tried to make JavaScript like Java, and Prototype tries to make it like Ruby. Prototype makes extensions to the core of JavaScript that can (if you choose to use them) have a dramatic effect on your approach to coding JavaScript. Depending on your background and the way your brain works, this may or may not be of help to you.
OO the Ruby(ish) way: Class.create and Object.extend
The Class.create method allows you to define classes in
a more Ruby-like way, although this is purely aesthetic as it
essentially just calls the initialize method you define as the
constructor, rather than the taking the traditional JavaScript approach
of creating objects with constructor functions.
var DOMTable = Class.create();
DOMTable.prototype = {
initialize : function(el) {
this.el = el;
},
...
}
However, much more powerful is the stupidly simple but effective Object.extend method. All it does is copy one object's properties and methods to another object, but its uses are many. Here's a quick taster:
// make a (shallow) copy of obj1
var obj2 = Object.extend({}, obj1);
var options = {
method : "post",
args : ""
};
// merges in the given options object to the default options object
Object.extend(options, {
args : "data=454",
onComplete : function() { alert("done!"); }
});
options.method // "post"
options.args // "ata=454"
options.onComplete // function() { alert("done!"); }
It's most commonly used to "mix in" methods from one object with another. For instance, you could create a set of functions that make certain DOM elements sortable:
var Sortable = {
sortBy : function(func) {
...
},
sortByReversed : function(func) {
...
},
reset : function() {
...
}
};
Then, if we wanted to make our DOMTable from above sortable, we could mix in these methods to the DOMTable object:
var myTable = new DOMTable("table-id");
Object.extend(myTable, Sortable);
Now we can call those methods on the table:
// sort the table using the given function
myTable.sortBy(function (itemA, itemB) { ... });
Function Binding
Prototype also adds to the Function object two really useful methods: bind and bindAsEventListener. These are used mainly to bind a function to a particular object so that the this
keyword points to that object. This is incredibly useful when you're
setting event handler functions. Imagine you try something like this:
var myObject = new Object();
myObject.message = "Hello!";
myObject.eventHandler = function() {
alert(this.message);
}
$("mydiv").onmouseover = myObject.eventHandler;
Traditionally, you'd get an error because, when the event triggers the handler function, this refers to the mydiv element, not myObject, so this.message is undefined. You can solve this problem using the bind method like so:
$("mydiv").onmouseover = myObject.eventHandler.bind(myObject);
Now it all works fine, because the this keyword is bound to myObject. Further to that, bindAsEventListener
does that same thing, though it passes the event object through to your
function in a cross-browser compatible way, so you no longer need to
worry about window.event in IE. Try this:
myObject.eventHandler = function(event) {
alert(event.srcElement.nodeName);
}
$("mydiv").onmouseover = myObject.eventHandler.bindAsEventListener(myObject);
Now our eventHandler function has access to the event object. Much more detail on these two methods is available at their creator's site.
New String and Number Methods
Prototype has added an enormous number of useful methods to the built in String object. Let's have a quick look at some of the best.
// "backgroundColor"
"background-color".camelize()
camelize turns hyphenated strings to camel case strings that you can use to work with CSS properties.
// "I am a piece of HTML"
"I am a piece of <strong>HTML</strong>".striptTags()
// {a : 10, b: "thing"}
"a=10&b=thing".toQueryParams()
Prototype adds a great method to Number, too. Say goodbye to your for loops!
// alerts "1", "2", "3" ... "50"
50.times(function(n) {
alert(n);
}};
Here, the times method takes a function that will be called the given number of times, and passes in the current iteration number as an argument. This use of an iterator function is common when using Enumerable, which we'll discuss next.
Iterating the Ruby way: Enumerable and Hash
One of the hidden gems of Prototype is the Enumerable mix-in and the Hash object, which have been poached straight out of Ruby. If you're not familiar with Ruby, don't worry. I'll explain it all here.
We'll start with Enumerable. In short, when we add Enumerable to an object using Object.extend, it gives the object in question lots of really useful functions for working with its properties. Enumerable has been added to Array's prototype, so any array has these new methods. Here are a few examples of what you can do with the new "enumerated" arrays:
// alerts "a is at 0" then "b is at 1" then "c is at 2"
["a", "b", "c"].each(function(item, index) {
alert(item + " is at " + index);
});
// [80,50]
[1, 80, 3, 50].select(function(item) {
return (item > 20);
});
select creates a new array that contains only the elements that make the function return true.
// ["A", "B", "C"]
["a", "b", "c"].invoke("toUpperCase");
invoke calls the specified method of each element of the array and returns the resulting array.
// ["cat", "rat"]
["cat", "dog", "rat", "mouse",].grep(/at/);
grep returns all elements that match the given regular expression.
Enumerable offers a large number of incredibly powerful
functions that can make many tedious DOM scripting tasks a breeze. I
strongly suggest you have a good look at the Enumerable methods in Sergio Pereira's extremely useful developer notes.
There's a small problem here, though. In JavaScript, you can come
across many types of objects that, to all intents and purposes, act
like arrays but aren't Array objects. Objects such as DOM NodeLists and function arguments won't have Enumerable available to them automatically. This is easy to rectify, though; to add the Enumerable functions to any array-like object, use $A:
// add Enumerable to childNodes
var children = $A($("mydiv").childNodes);
// sets class="highlighted" for all child nodes of "mydiv"
children.each(function(child) {
child.setAttribute("class", "highlighted");
});
To create a hash, call the magic function $H on any object. This turns all the properties of the object into a set of key-value pairs with Enumerable mixed in. Let's take hashes for a spin:
// create a hash by feeding an object to $H
var contact = $H({
name : "Dan Webb",
email : "dan@danwebb.net",
address : "None of your Business, London",
postcode : "a111111"
});
// ["name", "email", "address", "postcode"]
contact.keys()
// ["Dan Webb", "dan@danwebb.net","None of your Business, London", "a111111"]
contact.values()
// "name=Dan Webb&email=..."
contact.toQueryString()
Hash extends Enumerable as well, so all those useful methods are also available...
// alerts "name contains Dan Webb" and so on
contact.each(function(item) {
alert(item.key + " contains " + item.value);
});
At first, if you're not a Rubyist, Enumerable and Hash
may seem a bit of a hassle but I can assure you, once you start using
them, you'll wonder why you ever bothered getting RSI writing all those
for loops! When you use one or more of them together, you'll realise
the massive power of these new methods. You can read about Enumerable and Hash in more detail at Encyte Media.
The Event object helps to provide what, to many, is the holy grail of JavaScript: simple, cross-browser event handling:
function eventHandlerFunction(e) {
// the element that triggered the event
var element = Event.element(e);
// gets the mouse position
var mouseX = Event.pointerX(e),
mouseY = Event.pointerY(e);
// stop default behaviour and event propagation
Event.stop(e);
}
// register eventHandlerFunction to the onclick of myObject
Event.observe(myObject, "click", eventHandlerFunction, false);
// removes the event handler
Event.stopObserving(myObject, "click", eventHandlerFunction, false);
In a rather pleasant way, Prototype tries to avoid those pesky memory leaks in IE by automatically removing every observer when the page unloads.
In my opinion, though, this is a rather under-developed event
handling solution at the moment, so it might be worth considering using
something a bit richer like Dean Edwards's addEvent for the time being.
SitePoint Marketplace
SitePoint Marketplace
Buy and sell Websites, templates, domain names, hosting, graphics and more.
SitePoint Software

Now even the CEO can edit the company Website!
Never mind showing people how to use HTML or XML to markup their
Website content. Download Editize, drop it into your CMS and forget
about it...
SitePoint Kits
NEW! - December 2005
Run Your Own Web Server Using Linux & Apache
Setup, optimize and secure your own Web Server!
Download the FREE sample chapters
NEW! - September 2005
Build Your Own Standards Compliant Website Using Dreamweaver 8
Create accessible, standards compliant Websites today!
Download the FREE sample chapters
SitePoint Books
Download sample chapters of any of our popular books.

