20 Oct
Posted by ProCOM
on October 20, 2007 – 1:23 pm - 1,383 views
If you're new here, you may want to subscribe to my RSS feed. So that you can read the latest updates about Web2.0 tools, Making Money Online, Tips in SEO, Ajax and many more. Thanks for visiting ProgramimiCOM!
In this first article in a two-part series, you will learn how Ruby on Rails implements Ajax. We will use the specific example of a slideshow to demonstrate our points. This article is excerpted from chapter six of the book Ruby on Rails: Up and Running, written by Bruce A. Tate and Curt Hibbs (O’Reilly, 2006; ISBN: 0596101325). Copyright © 2006 O’Reilly Media, Inc. All rights reserved. Used with permission from the publisher. Available from booksellers or direct from O’Reilly Media.
Ajax is one of the most important emerging trends in web applications. Web sites like Google Maps and Gmail dramatically demonstrate that web applications do not have to be slow, clunky, page-at-a-time web forms. Ajax techniques can reclaim some of the fluidity and responsiveness that was lost when we moved from desktop applications to web applications.
Ajax (which stands for the cryptic “Asynchronous JavaScript and XML”) is a technique for building web pages that are more interactive, exciting, and dynamic. Ajax is asynchronous: JavaScript libraries can communicate with the server at any time, and the web page need not be frozen while waiting for a response. Ajax uses JavaScript on the browser, any language on the server, and XML to specify messages.
When you use this emerging technique, a web page can communicate with the server at any time, updating only those portions of the display that need it. Users experience more responsive web pages, with immediate feedback. Even though using Ajax techniques usually requires significantly more sophisticated design and implementation skills, the benefits to the end user are so great that Ajax-enabled web applications will soon become the rule, not the exception. Fortunately, Rails makes Ajax so simple that, for typical cases, using Ajax is almost as easy as not using it.
How Rails Implements Ajax
Rails has a simple, consistent model for how it implements Ajax operations. Once the browser has rendered and displayed the initial web page, different user actions cause it to display a new web page (like any traditional web application) or trigger an Ajax operation:
Some trigger fires
This trigger could be the user clicking on a button or link, the user making changes to the data on a form or in a field, or just a periodic trigger (based on a timer).
The web client calls the server
A JavaScript method, XMLHttpRequest, sends data associated with the trigger to an action handler on the server. The data might be the ID of a checkbox, the text in an entry field, or a whole form.
The server does something
The server-side action handler–a Rails controller action (for our purposes)–does something with the data and returns an HTML fragment to the web client.
The client receives the response
The client-side JavaScript, which Rails creates automatically, receives the HTML fragment and uses it to update a specified part of the current pages HTML, often the content of a <div> tag.
These steps are the simplest way to use Ajax in a Rails application, but with a little extra work, you can have the server return any kind of data in response to an Ajax request, and you can create custom JavaScript in the browser to perform more involved interactions. We’ll stick to HTML fragments in this chapter.
Rails uses the Prototype and script.aculo.us JavaScript libraries to implement browser support for Ajax. You can use these libraries independently of Rails, but with their seamless integration with Rails, you probably wont want to. Throughout this chapter, we’ll exploit the Ajax and special-effects capabilities that come with Rails to implement missing features in our Photo Share application.
Playing a Slideshow
Let’s see what happens when we try to play a slideshow. Browse to http://127.0.0.1: 3000/slideshows/list, and click the Play link for our only slideshow. As you can see in Figure 6-1, this URL invokes the show action on the slideshow controller, but the action is still using the scaffold code.
We need to change this page to actually “play” the slideshow by sequentially displaying the pictures contained in the slideshow. To do this, we will initially display the first picture in the slideshow; then, once every two seconds, we’ll make an Ajax call to get and display the next picture.
The controller sets up all the slides in a slideshow for playback. You need to start @slideshow with the current slideshow set to play. You also need to put the current slide (initially, 0) and the whole slideshow into a holding area called the session, so you won’t have to read from the database each time you play a new slide. Edit the slideshows controller (app/controllers/slideshows_controller.rb), and modify the show method to look like this:
def show
@slideshow = Slideshow.find(params[:id])
session[:slideshow] = @slideshow
session[:slide_index] = 0
Figure 6-1. Playing a slideshow that still uses scaffolding
@slide = @slideshow.slides[0]
end
Every two seconds in this code, the browser sends an Ajax request to get the next slide. You can’t use instance variables to keep track of where you are in the slideshow because instance variables exist only until you finish processing the current request. Use the Rails-provided session object instead, which is persistent across requests. Let’s look at this code in a little more detail.
session[:slideshow] = @slideshow stores a reference to the current slideshow in the session hash at the key :slideshow. We do the same thing with the index of the current slide that is being played. We initially set the slide_index to zero to point to the first slide, and our Ajax request increments the index by one as it displays each slide. We can retrieve these values from the session hash during the Ajax requests for the next slide.
Now, edit the view template (app/views/slideshows/show.rhtml) to look like this:
<p><i><%= @slideshow.name %></i></p>
<div id=”slides”>
<%= render :partial => “show_slide” %>
</div>
<%= periodically_call_remote :update => ’slides’,
:url => { :action => :show_slide },
:frequency => 2.0 %>
This RHTML template contains three things: a title line, the div that displays the current slide, and a magic Ajax incantation that we will now pick apart.
The periodically_call_remote Rails helper function creates JavaScript that periodically sends a request to the server and uses the HTML fragment that is returned to replace the content of the update target. In this case, the update target is an HTML element with an ID of ’slides’, which is a <div> tag. The returned HTML fragment replaces the contents of this <div> tag. The URL that makes the request is constructed to ensure that it will be routed to the show_slide method of the current controller (the slideshows controller). Finally, the frequency parameter makes the call once every two seconds. All Ajax help functions take their parameters in key/value pairs, so you can list the parameters in any order.
We need to display each slide as it comes back to the client; Rails uses a partial HTML template to do this work. To create the partial view template, place the following contents in a new file called app/views/slideshows/_show_slide.rhtml:
<%= image_tag “photos/#{@slide.photo.filename}” %>
<p><%= @slide.photo.filename %></p>
And its controller method in app/controllers/slideshows_controller.rb:
def show_slide
@slideshow = session[:slideshow]
session[:slide_index] += 1
@slide = @slideshow.slides[session[:slide_index]]
if @slide == nil
session[:slide_index] = 0
@slide = @slideshow.slides[0]
end
render :partial => “show_slide”
end
This method retrieves the slideshow information from the session, moves to the next slide (or back to the beginning if at the end), and then explicitly renders the partial view template show_slide. You need to render a partial view or render with the option :render_layout => false . Otherwise, Rails tries to render a full template, including layout. As our page already has a layout, simply render a partial template, consisting of an image tag for the slide, and its name.
Finally, you need to update your standard layout template to include script tags for the Prototype JavaScript library because the client-side JavaScript code that Rails creates for you uses them, so in app/views/layouts/standard.rhtml, insert this line immediately after the title tags:
<%= javascript_include_tag ‘prototype’, ‘effects’, ‘dragdrop’ %>
This line includes three JavaScript files that are shipped with Rails: prototype.js and two Script.aculos.us files, effects.js and dragdrop.js. We will use these last two shortly.
Now show a slideshow by loading slideshows/list and clicking Show, and you will see the actual pictures in the slideshow, changing every two seconds.
Using Drag-and-Drop to Reorder Slides
The scaffolding we have for editing a slideshow shows just the slideshow attributes that are stored directly in the slideshows table: the slideshows name and the date on which it was created. The most important part is missing: the photos that are part of the slideshow!
By now, you’ve probably realized that this is because the scaffolding code deals with only one database table: the slideshows table. The relationship data about which photos are assigned to a slideshow and their order in the slideshow are stored in the slides table. Scaffolding does not handle relationships, so you have to write the code to edit this relationship data.
We’re going to display a list of thumbnails of all the photos that are in a slideshow, and then let the user reorder them using drag-and-drop. If you’ve had to struggle through implementing drag-and-drop before, you’re not going to believe how easy this is going to be. Here’s a hint: this will take a total 34 additional lines of Ruby, CSS, and RHTML template!
Let’s start by reviewing the current implementation of the edit action in the slideshow controller:
def edit
@slideshow = Slideshow.find(params[:id])
end
This action expects to find the ID of the slideshow to edit passed in as the id parameter, which is normally decoded from the URL. You find the slideshow with that ID and assign that slideshow object to the instance variable @slideshow, so that it can be accessed in the view template.
That is really all that’s needed here, so you won’t have to add any code to this method. The changes will start with the edit view template, so edit the template photos/app/views/slideshows/edit.rhtml and make it look like this (the changes are in bold):
<h1>Editing slideshow</h1>
<%= link_to ‘Play this Slideshow’,
:action => ’show’, :id => @slideshow %>
<div id=’slideshow-contents’>
<%= render :partial => ’show_slides_draggable’ %>
</div>
<div id=’slideshow-attributes’>
<%= start_form_tag :action => ‘update’, :id => @slideshow %>
<%= render :partial => ‘form’ %>
<%= submit_tag ‘Save Attributes’ %>
<%= end_form_tag %>
</div>
Notice that the existing <%= render :partial => ‘form’ %> is wrapped in a <div> tag with an id attribute of slideshow-attributes. You will use this name in one of your CSS files to control how this section is displayed.
There is also a completely new section that displays thumbnails of the photos in the slideshow:
<div id=’slideshow-contents’>
<%= render :partial => ’show_slides_draggable’ %>
</div>
This code also uses a <div> tag with an id attribute, for the same reason: to use a CSS file to control its appearance. This div also renders a new partial view template named show_slides_draggable, which we will create next.
Create the file photos/app/views/slideshows/_show_slides_draggable.rhtml with the following contents:
<ol id=’sortable_thumbs’>
<% for slide in @slideshow.slides %>
<li id=’thumbs_<%= slide.id %>’ class=’slides’>
<%= thumbnail_tag slide %>
</li>
<% end %>
</ol>
<%= sortable_element(’sortable_thumbs’,
:url => {:action => ‘update_slide_order’}) %>
The first part is pretty standard stuff. We’re creating an HTML ordered list, in which each list item is a thumbnail image of one of the photos in the slideshow (note that the thumbnail_tag helper function that was created earlier). However, it’s the last two lines that do the heavy lifting.
sortable_element is a helper function that generates the JavaScript code that turns our list into a user-sortable, drag-and-drop-capable list. It wraps this list an HTML form, and the :url option specifies the URL to post to the server whenever the user changes the order of the list. In this case, it calls the action method update_slide_order in our slideshow controller. This call works in the background using an Ajax call.
The update_slide_order method is pretty simple as well. Edit photos/app/controllers/ slideshows_controller.rb, and add this method:
def update_slide_order
params[:sortable_thumbs].each_with_index do |id, position|
Slide.update(id, :position => position)
end
end
This method iterates through each slide in the list, extracting its ID and position in the list, and uses this information to update that slide’s database row with its new position. Let’s walk through this code in a little more detail:
We’re almost ready to give it a try, but first let’s edit photos/public/stylesheets/slideshows.css and add some formatting instructions for the two div IDs we created. Add the following at the end of the file:
#slideshow-contents {
float: left;
width: 11em;
padding: 0.50em;
text-align: center;
border-right: thin solid #bbb;
padding: 0.50em;
padding-bottom: 10em;
}
#slideshow-attributes {
margin-left: 23em;
padding-left: 1.5em;
padding-top: 1.5em;
}
This causes the contents of the slideshow (which will be a list of thumbnail images) to be displayed down the left side of the page, and the slideshow’s attributes will be displayed immediately to the right of the thumbnails.
Let’s see how this looks. Browse to http://127.0.0.1:3000/slideshows/list, and click the edit link for our one and only slideshow. It will look like Figure 6-2.
Click on one of the photos, and try dragging it around. When you drop it into a new location, update_slide_order is called to write the new order to the database.
Lets fix one minor thing here before we move on. Wouldn’t it be better to see the number of each photo appear vertically aligned in the middle of the thumbnail instead of at the bottom? Because the HTML for each thumbnail image is created by…
Figure 6-2. A drag-and-drop list of photos
…
our own helper function, thumbnail_tag, we just need to edit that function and add a vertical-align style attribute.
First, edit photos/app/helpers/slideshows_helper.rb, and add the code shown in bold:
module SlideshowsHelper
def thumbnail_tag(slide)
image_tag(”photos/#{slide.photo.thumbnail}”,
:style=>”vertical-align:middle”) if slide
end
end
Now, refresh your browser: the list numbers are nicely centered, as you can see in Figure 6-3.
With a very small amount of code, we added a very nice drag-and-drop user interface for reordering the slides in a slideshow. But we’re just getting started with our Ajax-enabled user interface.
Figure 6-3. Nicely centered list numbers
Please check back tomorrow for the conclusion to this article.
—
by O’Reilly Media
Print This Post
Email This Post
Comments RSS
TrackBack Identifier URI
You must be logged in to post a comment.