This is the second article of a three-part series on popular JavaScript libraries that you can use to create Ajax-powered applications. In Part 1, you learned about the Prototype library to build a Web application for managing songs. In this article, you will use the Scriptaculous library to build a Web application for managing photos.
This article uses the latest version of Scriptaculous, version 1.8.1 (see Resources for a link). Scriptaculous uses the Prototype 1.6 library. Familiarity with JavaScript, HTML, and CSS are a must. This article demonstrates the use of Scriptaculous with Ajax. For the back end, a combination of Ruby on Rails 2.0 and MySQL 5.0.4 were used (see Resources). You can definitely substitute the back-end technologies of your choice with just a few minor adjustments.
The Scriptaculous JavaScript library is one of the most popular libraries available. It is used to add rich interaction to HTML-based Web sites. It provides numerous visual effects and behaviors that you can pick from to add interactivity to your Web application. Scriptaculous builds on top of the Prototype library.
Figure 1. The Scriptaculous and Prototype stack
If you read Part 1, then you saw examples of the Ajax abstractions provided by Prototype. Instead of creating its own similar functionality, Scriptaculous simply uses Prototype and adds effects and behaviors on top of it. Scriptaculous provides numerous rich controls such as drag-and-drop elements. It also provides stunning visual effects that you can use in combination with the controls.
One of the most useful and visually compelling features you can add to your application is drag-and-drop. The drag-and-drop feature is something we have come to expect out of desktop applications, but it is not seen as often in Web applications. Adding it to your Web application can provide a rich user experience. It may seem like a daunting task, but it is surprisingly easy with Scriptaculous. To demonstrate this, we will take an example application and dissect it to see the benefits of using Scriptaculous.
Example application: The photo organizer
For our example application, we will create an application for managing photos. Our application will let us add photos to a set or delete the photos, all using drag and drop metaphors. To create the back end, we will use Ruby on Rails and MySQL. Ruby on Rails actually includes Scriptaculous by default and provides APIs for using it automatically in your application. We will not use those features from Rails; we will just use it to provide a data service back end.
We will not spend too much time on the back end — just enough to understand it so the operations on the front end will make sense. All of the code is included in the Download section of this article. First, we create the Rails application using rails pix. Next, we need to create a database called pix_development, per the Rails naming convention. You need to modify the config/database.yml file to modify the configuration parameters (host, name, password) to the database.
The most important step is next, which involves creating a scaffolding for the
application. Normally, you create scaffolding just to get you started, but in this
case we will use it to create a RESTful interface that we can call using Ajax. The command follows: ruby script/generate scaffold photo thumb :string caption :string inSet :boolean. This creates our model with a thumb field (for a thumbnail picture), a caption, and a Boolean indicating if it is in our hypothetical set. Now the command rake db:migrate will create the tables we need in the database. The scaffold generation command also creates a controller for our application. We need to modify this to provide a JSON version of the RESTful service that Rails creates automatically. The modified controller is shown in Listing 1:
Listing 1. The photos Web controller
class PhotosController < ApplicationController
# GET /photos
# GET /photos.xml
protect_from_forgery :only => [:create]
def index
@photos = Photo.find(:all)
respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => @photos }
format.json { render :json => @photos }
end
end
# GET /photos/1
# GET /photos/1.xml
def show
@photo = Photo.find(params[:id])
respond_to do |format|
format.html # show.html.erb
format.xml { render :xml => @photo }
format.json { render :json => @photo }
end
end
# GET /photos/new
# GET /photos/new.xml
def new
@photo = Photo.new
respond_to do |format|
format.html # new.html.erb
format.xml { render :xml => @photo }
format.json { render :json => @photo }
end
end
# GET /photos/1/edit
def edit
@photo = Photo.find(params[:id])
end
# POST /photos
# POST /photos.xml
def create
@photo = Photo.new(params[:photo])
respond_to do |format|
if @photo.save
flash[:notice] = 'Photo was successfully created.'
format.html { redirect_to(@photo) }
format.xml { render :xml => @photo, :status => :created,
:location => @photo }
format.json { render :json => @photo, :status => :created,
:location => @photo }
else
format.html { render :action => "new" }
format.xml { render :xml => @photo.errors, :status =>
:unprocessable_entity }
format.json { render :json => @photo.errors, :status =>
:unprocessable_entity }
end
end
end
# PUT /photos/1
# PUT /photos/1.xml
def update
@photo = Photo.find(params[:id])
respond_to do |format|
if @photo.update_attributes(params[:photo])
flash[:notice] = 'Photo was successfully updated.'
format.html { redirect_to(@photo) }
format.xml { head :ok }
format.json { head :ok }
else
format.html { render :action => "edit" }
format.xml { render :xml => @photo.errors, :status =>
:unprocessable_entity }
format.json { render :json => @photo.errors, :status =>
:unprocessable_entity }
end
end
end
# DELETE /photos/1
# DELETE /photos/1.xml
def destroy
@photo = Photo.find(params[:id])
@photo.destroy
respond_to do |format|
format.html { redirect_to(photos_url) }
format.xml { head :ok }
format.json { head :ok }
end
end
end
|
Notice a few elements here. First, the protect_from_forgery
command allows for everything but the create operation to be accessible as a Web
service. Next, notice in the respond_to do |format| blocks
that we have added format.json { render :json ... }. This
uses RoR's built-in support for JSON to respond to a URL that ends with .json. So a
/photos.json will call the index method with all the data serialized as JSON. As we
saw in the first article in
this series, this is a very convenient way to pass data to Prototype. Now we see how
the data looks on the back end and how it gets served. Now let's look at the front end and how Scriptaculous is used there.
To understand the front end, let's look at the code and examine how it works. The front end is a simple HTML page, as shown in Listing 2:
Listing 2. The organize.html Web page
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN"
"http://www.w3.org/TR/html4/strict.dtd">
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Photo Organizer</title>
<script type="text/javascript" src="javascripts/prototype.js"></script>
<script type="text/javascript" src="javascripts/effects.js"></script>
<script type="text/javascript" src="javascripts/dragdrop.js"></script>
<script type="text/javascript" src="javascripts/builder.js"></script>
<script type="text/javascript" src="javascripts/organizer.js"></script>
<script type="text/javascript" src="javascripts/organize_page.js"></script>
<link rel="stylesheet" href="stylesheets/organizer.css"/>
</head>
<body onLoad="initPage()">
<div id="pageTitle">Organize Your Photos</div>
<div id="allPics">
<span id="setPics"></span>
<span id="availablePics"></span>
</div>
<div id="trash"> </div>
</body>
</html>
|
This looks fairly simple, right? Note that there are several JavaScript files we
loaded. We had to load prototype.js because Scriptaculous uses it (and we will use it
as well). Then we used three libraries that are part of Scriptaculous: effects.js,
dragdrop.js, and builder.js. Scriptaculous is fairly modular, allowing you to only use
what you need. However, the dragdrop.js does have a dependency on effects.js, so make sure you always include them together (if you are using drag-and-drop that is). Of course the most interesting code is in the two JavaScript files that are specific to this application. When the page loads, it calls the initPage function in organize_page.js as shown in Listing 3:
Listing 3. The page initialization JavaScript
// the mode for the page
var model = {};
// page initialization
function initPage(){
var options = {
method : "get",
onSuccess : initUi
}
new Ajax.Request("photos.json",options);
}
function initUi(xhr){
model = new Organizer(xhr.responseJSON);
var list = Builder.node("span", {id: "inList"});
var list2 = Builder.node("span", {id: "outList"});
var pix = model.pixList;
for (var i=0; i < pix.length; i++){
var pic = buildPicUi(pix[i]);
if (pix[i].inSet){
list.appendChild(pic);
} else {
list2.appendChild(pic);
}
}
$("setPics").appendChild(list);
$("availablePics").appendChild(list2);
// setup drag-and-drop
makeDraggables();
initDropZones();
}
|
The first function, initPage, should look familiar if you have used Prototype before. It simply makes a request back to the server for a list of photos rendered as JSON. It sets the initUi function as the function to call to handle the response from the server. Let's take a look at that function next.
The initUi function takes the data from the server and
creates an Organizer object. This object is contained in a separate JavaScript file and acts as a model for our application. You can take a look at that code, which is available in the source code for this article. For now, we will concentrate on the UI components. We dynamically create visual components for two groupings of photos: the ones that are "in" the set and the ones that are not. We use Scriptaculous's Builder library to create the DOM elements for this dynamically. Next we iterate over the photos in our model and create visual representations for them using the buildPicUi function. This function is shown in Listing 4:
Listing 4. The
buildPicUi JavaScript function
function buildPicUi(pic){
var picId = "pic" + pic.id;
var img = Builder.node("img", {src : "images/"+pic.thumb, alt: pic.caption});
var picNode = Builder.node("div",{id : picId, class: "pic"},img);
return picNode;
}
|
Again, we make use of the Builder library to more easily create our DOM elements. In
this case, we create an image (img tag) inside of a div. Now if we go back to the initUi function, we see that we call two other functions, makeDraggables and initDropZones. These functions are shown in Listing 5:
Listing 5. Enabling drag-and-drop
function makeDraggables(){
for (var picId in model.pixMap){
new Draggable(picId, {revert:true});
}
}
function initDropZones(){
Droppables.add("setPics",{onDrop: addToSet, accept: "pic"});
Droppables.add("availablePics",{onDrop: removeFromSet, accept: "pic"});
Droppables.add("trash",{onDrop: addToTrash, accept: "pic"});
}
|
These two functions enable all of the drag-and-drop functionality in the application. The makeDraggables function simply iterates over all of the pictures and creates a Draggable object for each. All that is needed is the DOM ID of the picture (see the buildPicUi function in Listing 4) and then the options. In this case, the only option we have is revert = true. This says that the picture should revert back to its original position unless it lands in a drop zone. The drop zones are created by initDropZones.
The initDropZones takes three areas on the page: the
in-list, the out-list, and an area at the bottom designated as trash. For each one, it
sets a handler (onDrop) to be called when an object is
dropped into the drop zone. It also sets a filter of what kind of objects can be
dropped in it. In each case, the drop zone will only accept objects with class "pic."
This is the class we set for each picture in the buildPicUi function in Listing 4. So, only pictures can be dropped in our drop zones, and the pictures can only be dragged into a drop zone that will accept them. Each zone has a different handler function. Let's look at the adToTrash handler, shown in Listing 6:
Listing 6. The
addToTrash JavaScript function
function addToTrash(pic){
pic.style.left = "";
pic.style.top = "";
$("trash").appendChild(pic);
Effect.Puff(pic.id, {duration: 0.8});
model.deletePic(pic.id);
}
|
This function does several things. First, it cleans up some of the CSS that gets
created when you drag an object. Then we add the picture to the "trash" zone. Notice how we used Prototype's convenience notation $("trash"). Next, we use the Scriptaculous effect called Puff. This is a visual effect that shows the picture disappearing to indicate that the picture has been deleted. It is what Scriptaculous calls a combination effect: it uses a combination of the library's core effects. (You could use numerous other effects as well.) Last, we make a call to our model (the Organizer object) to make an Ajax call to delete the object from the database. The Organizer object is shown in Listing 7:
Listing 7.
Organizer class
var Organizer = Class.create({
initialize : function(pix){
this.pixMap = {};
this.pixList = pix;
pix.each(function(pic){
this.pixMap["pic"+pic.id] = pic;
}.bind(this));
},
updatePic : function(picId, data){
var photo = this.pixMap[picId];
params = [];
// Rails uses _method since browsers
// do not properly send HTTP PUT and DELETE
params["_method"] = "put";
// Rails uses className[propertyName] for
// request parameters to auto-bind them to
// object properties
$H(data).each(function(pair){
params["photo["+pair.key+"]"] = pair.value;
});
var options = {
method : "post",
parameters : params
};
new Ajax.Request("photos/"+photo.id,options);
},
deletePic : function(picId){
var photo = this.pixMap[picId];
var params = {};
params["_method"] = "delete";
var options = {
method : "post",
parameters : params
};
new Ajax.Request("photos/"+photo.id,options);
}
});
|
This class has a lot of interesting things going on. It, like Scriptaculous, makes
heavy use of Prototype. It uses Prototype for creating a class (Class.create(...)). Take a look at the initialize function. This is called when you create an Organizer
instance. It uses the each function that Prototype adds to
JavaScript arrays and objects. This is a familiar construct found in many programming
languages but missing in JavaScript. In our example, it is combined with Prototype's
bind function. This is a function that Prototype adds to
function object in JavaScript. It essentially binds the
current context to the function. In this case, we want to be able to alter the Organizer's pixMap field during the execution of the anonymous function being called by the each function. Without the bind, this.pixMap would have no meaning. Also notice that the updatePic function uses another Prototype syntactical shortcut, the $H() notation. This creates a Prototype Hash object that adds things like the eachfunction to our object. Again, this is a familiar construct borrowed from other languages that Prototype brings to JavaScript. Finally, both the updatePic and deletePic once again use Prototype's Ajax.Request function to create a cross-browser-compliant Ajax request to our server.
To start the example, you just start up Rails: ruby script/server start for example. Now you can bring it up in your browser as shown in Figure 2:
Figure 2. The photo organizer example
When the page loads, you can use a tool like Firebug to watch the asynchronous loading of data, as shown in Figure 3:
Figure 3. Initial data load shown in Firebug
When you drag pictures from one list to another or to the trash at the bottom of the screen, you should see another Ajax request going out, as shown in Figure 4:
Figure 4. Ajax request triggered by drag-and-drop
Now you have a Web application that has a lot of desktop-like functionality. It was built using advanced controls and effects from Scriptaculous, along with simplified Ajax courtesy of Prototype.
In this article, Part 2 of this three-part series on JavaScript libraries, we have explored some of the controls and effects provided by the Scriptaculous JavaScript library. We have seen how Scriptaculous builds on top of Prototype to make Ajax development easy. We have also seen how easy it is to combine Scriptaculous controls and effects with Prototype's Ajax to create a rich user experience. We have only scratched the surface, though. Scriptaculous has several other controls and many more effects that you can use in your Web applications, so it's worth it to take the time to explore it further.
| Description | Name | Size | Download method |
|---|---|---|---|
| Sample code | wa-aj-ajaxpro2.zip | 170KB | HTTP |
Information about download methods
Learn
-
Visit the Script.aculo.us wiki for Script.aculo.us documentation.
-
Scriptaculous is a key component in the Ajax support in Ruby on Rails. Read about it in
the article "Crossing
borders: Ajax on Rails" (Bruce Tate, developerWorks, December 2006).
-
Scriptaculous can be combined with many other JavaScript libraries. See how its effects
can be used with the Google Web Toolkit in the article "Ajax for Java Developers: Exploring the Google Web Toolkit" (Philip McCarthy, developerWorks, June 2006).
-
Check out the in-place editor control in Scriptaculous in the article "Ajax and XML: Five cool Ajax widgets" (Jack Herrington, developerWorks, June 2007).
-
Get a survey of popular Ajax libraries, including Scriptaculous, in the article "Ajax -- a guide for the perplexed" (Gal Shachor et al., developerWorks, July 2007).
-
Find out more about some of the Ajax patterns mentioned here in the article "Ajax and XML: Five common Ajax patterns" (Jack Herrington, developerWorks, March 2007).
-
"Discover the Ajax
Toolkit Framework for Eclipse" (Tim McIntire, developerWorks, November 2006): Extend the Eclipse Web Tools Platform when you add support for open-source Ajax tool kits such as Dojo, Zimbra, and Rico.
-
See more articles and tutorials on the Prototype JavaScript library.
-
The server scripts used in this article conform to the REST protocol. Learn more about
using REST and Ajax together in the developerWorks article "RESTful Web services and their Ajax-based clients" (Shailesh K. Mishra, developerWorks, July 2007).
-
Read Simon Willison's A (Re)-Introduction to JavaScript.
-
Check out IBM developerWorks' Ajax Resource Center.
-
W3Schools provides online reference information for all core Ajax technologies (JavaScript, CSS, HTML, DOM, XML, and so on).
-
To listen to interesting interviews and discussions for software developers, check out developerWorks podcasts.
-
developerWorks technical events and webcasts: Stay current with developerWorks technical events and webcasts.
Get products and technologies
-
Prototype is a JavaScript library that introduces powerful functions to help simplify Ajax programming.
-
Download Script.aculo.us 1.8.1.
-
Get the Firebug extension for Firefox.
-
Ruby on Rails: Download the open source Ruby on Rails Web framework.
Discuss
Comments (Undergoing maintenance)





