Jun 24, 2008 ... JavaScript libraries, you will see how to use the Scriptaculous ... This article uses
the latest version of Scriptaculous, version 1.8.1 (see ...
Develop Ajax applications like the pros, Part 2: Using the Prototype JavaScript Framework and script.aculo.us Skill Level: Intermediate Michael Galpin (
[email protected]) Software architect eBay
24 Jun 2008 Are you building a Web application? Is it supposed to look more like cragislist or flickr? If the answer is the former, then you can probably skip this article. Still reading? Well you are in luck. In this article, Part 2 of a three-part series on JavaScript libraries, you will see how to use the Scriptaculous JavaScript library to enhance your Web applications. 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.
Introduction to Scriptaculous 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
Using the Prototype JavaScript Framework and script.aculo.us © Copyright IBM Corporation 1994, 2008. All rights reserved.
Page 1 of 13
developerWorks®
ibm.com/developerWorks
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.
Drag-and-drop 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. Photo organizer 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 Using the Prototype JavaScript Framework and script.aculo.us Page 2 of 13
© Copyright IBM Corporation 1994, 2008. All rights reserved.
ibm.com/developerWorks
developerWorks®
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.html format.xml format.json end end
|format| # index.html.erb { render :xml => @photos } { render :json => @photos }
# GET /photos/1 # GET /photos/1.xml def show @photo = Photo.find(params[:id]) respond_to do format.html format.xml format.json end end
|format| # show.html.erb { render :xml => @photo } { render :json => @photo }
# 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
Using the Prototype JavaScript Framework and script.aculo.us © Copyright IBM Corporation 1994, 2008. All rights reserved.
Page 3 of 13
developerWorks®
ibm.com/developerWorks
# 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
Using the Prototype JavaScript Framework and script.aculo.us Page 4 of 13
© Copyright IBM Corporation 1994, 2008. All rights reserved.
ibm.com/developerWorks
developerWorks®
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. Photo organizer front end 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 Photo Organizer
Organize Your Photos
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",
Using the Prototype JavaScript Framework and script.aculo.us © Copyright IBM Corporation 1994, 2008. All rights reserved.
Page 5 of 13
developerWorks®
ibm.com/developerWorks
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
Using the Prototype JavaScript Framework and script.aculo.us Page 6 of 13
© Copyright IBM Corporation 1994, 2008. All rights reserved.
ibm.com/developerWorks
developerWorks®
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 Using the Prototype JavaScript Framework and script.aculo.us © Copyright IBM Corporation 1994, 2008. All rights reserved.
Page 7 of 13
developerWorks®
ibm.com/developerWorks
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.
Using the Prototype JavaScript Framework and script.aculo.us Page 8 of 13
© Copyright IBM Corporation 1994, 2008. All rights reserved.
ibm.com/developerWorks
developerWorks®
Running the example 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
Using the Prototype JavaScript Framework and script.aculo.us © Copyright IBM Corporation 1994, 2008. All rights reserved.
Page 9 of 13
developerWorks®
ibm.com/developerWorks
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.
Summary The developerWorks Ajax resource center Check out the Ajax resource center, your one-stop shop for free tools, code, and information on developing Ajax applications. The active Ajax community forum, hosted by Ajax expert Jack Herrington, will connect you with peers who might just have the answers you're looking for right now.
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.
Using the Prototype JavaScript Framework and script.aculo.us Page 10 of 13
© Copyright IBM Corporation 1994, 2008. All rights reserved.
ibm.com/developerWorks
developerWorks®
Downloads Description
Name
Size
Sample code
wa-aj-ajaxpro2.zip170KB
Download method HTTP
Information about download methods
Using the Prototype JavaScript Framework and script.aculo.us © Copyright IBM Corporation 1994, 2008. All rights reserved.
Page 11 of 13
developerWorks®
ibm.com/developerWorks
Resources 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.
Using the Prototype JavaScript Framework and script.aculo.us Page 12 of 13
© Copyright IBM Corporation 1994, 2008. All rights reserved.
ibm.com/developerWorks
developerWorks®
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 • Participate in the discussion forum for this content.
About the author Michael Galpin Michael Galpin has been developing Web applications since the late 1990's. He holds a degree in mathematics from the California Institute of Technology and is an architect at eBay in San Jose, CA.
Using the Prototype JavaScript Framework and script.aculo.us © Copyright IBM Corporation 1994, 2008. All rights reserved.
Page 13 of 13