This post is part of a series of reviews on the book Agile Web Development With Rails. Check out the Introduction post for a full table of contents along with some initial notes.

On the last task we began to create a shopping cart for our e-commerce application, and now we’re gonna learn how to spice it up a bit by adding some AJAX. In a nutshell, AJAX is basically a way for a user to interact with the application without having to reload the page, whereas the only part that gets reloaded is the specific HTML that needs to be changed, instead of the whole page.
To prepare the stage for the AJAX implementation, we’re going to move the whole cart application to the sidebar, and to do that we need to learn the concept of partial templates. Partials can be understood as a method for a view: a chunk of code that will be displayed, which, in our case, will be the whole cart viewing code. We’ll move by steps: first let’s use a partial to invoke all the items in the cart, replacing the whole @cart.items iteration in the add_to_cart view file with a partial:

<div>Your Cart</div>
<table>
 <%= render(:partial => "cart_item" , :collection => @cart.items) %>
 <tr>
 <td colspan="2">Total</td>
 <td><%= number_to_currency(@cart.total_price) %></td>
 </tr>
</table>
<%= button_to "Empty cart" , :action => :empty_cart %>

As you can see, we added the render method, which invokes a partial passing its name, and a collection that it will iterate with, in this first case, the cart item. Now we need to actually define the partial, hence the code above is invoking nothing. Create a new view file named _cart_item.html.erb (partials need to start with an underscore and they go into the views folder, just like a view file) and fill it up with this code:

<tr>
 <td><%= cart_item.quantity %>&times;</td>
 <td><%=h cart_item.title %></td>
 <td><%= number_to_currency(cart_item.price) %></td>
</tr>

We still haven’t moved anything into the sidebar because the whole cart application is in a view file, so let’s transform it into a partial (yes, it will invoke another partial which is the _cart_item partial), it’s basically the same thing except that we’ll be using ‘cart’ instead of ‘@cart’ (the cart object will be passed by the layout, and not by the controller, so we can’t use an instance variable). Create another file named _cart.html.erb and code this in:

<div>Your Cart</div>
<table>
 <%= render(:partial => "cart_item" , :collection => cart.items) %>
 <tr>
 <td colspan="2">Total</td>
 <td><%= number_to_currency(cart.total_price) %></td>
 </tr>
</table>
<%= button_to "Empty cart" , :action => :empty_cart %>

Finally we can add it on to the sidebar in the store layout file:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd" >
<html>
<head>
<title>Pragprog Books Online Store</title>
<%= stylesheet_link_tag "depot" , :media => "all" %>
</head>
<body id="store">
 <div id="banner">
 <%= image_tag("logo.png" ) %>
 <%= @page_title || "Pragmatic Bookshelf" %>
 </div>
 <div id="columns">
 <div id="side">
 <div id="cart">
 <%= render(:partial => "cart" , :object => @cart) %>
 </div>
 <a href="http://www....">Home</a><br />
 <a href="http://www..../faq">Questions</a><br />
 <a href="http://www..../news">News</a><br />
 <a href="http://www..../contact">Contact</a><br />
 </div>
 <div id="main">
 <% if flash[:notice] -%>
 <div id="notice"><%= flash[:notice] %></div>
 <% end -%>
 <%= yield :layout %>
 </div>
 </div>
</body>
</html>

Notice that on line 14 we added a div that includes an invokation to the cart partial passing the @cart object (since this is a view file, it can manipulate instace variables, unlike partials). Now that the view is set up, we need to make it so the index action can pass that object variable to the partial, which can be done by adding one line of code to the index method in the store controller:

def index
 @products = Product.find_products_for_sale
 @cart = find_cart
end

Woot, the cart now appears at the side bar, although we still need to change the place where the ‘Add to Cart’ button redirects the user, because it’s no use redirecting to the add_to_cart.html.erb page since we have that set up at the side bar. To do that, two small changes need to be done, the first one on the add_to_cart method and the next one on the redirect_to_index method, both in the store controller:

def add_to_cart
 begin
   product = Product.find(params[:id])
 rescue ActiveRecord::RecordNotFound
   logger.error("Attempt to access invalid product #{params[:id]}" )
   redirect_to_index("Invalid product" )
 else
   @cart = find_cart
   @cart.add_product(product)
   redirect_to_index
end

Notice that the change is only made in the last line of code, where a method call to redirect_to_index is made without parameters (there’s no need to show error messages here since the page won’t be redirected to a new URL, so the user can’t mess it up). The second change mentioned is to change that redirect_to_index to accept a call without parameters:

def redirect_to_index(msg = nil)
 flash[:notice] = msg
 redirect_to :action=:index
end

The sidebard cart is all set up, now it’s time to AJAX it up. First thing that needs to be done is changing the line of code that adds the ‘Add to Cart’ button on the index view page of our store controller to this:

<% form_remote_tag :url => { :action => :add_to_cart, :id => product } do %>
<%= submit_tag "Add to Cart" %>
<% end %>

The form_remote_tag helper basically creates a form with an AJAX call to your application using the parameters you passed, which are the same as the ones we used to pass to the button_to helper, and everything inside the ruby block (do-end) is the form’s body, which in our case is just a submit button. AJAX used Javascript, and to use Javascript we need to include a library, so let’s do that in the head tag on the store layout file:

<head>
<title>Pragprog Books Online Store</title>
<%= stylesheet_link_tag "depot" , :media => "all" %>
<%= javascript_include_tag :defaults %>
</head>

Notice we added a Rails helper that includes a JS library right underneath the other helper that includes a CSS file. We now have a working AJAX request, but what good is a request without a response? The first thing we need to do to make our application respond is remove the redirect_to_index call and replace it with a method call to respond_to telling it that we want a response with a JS format.

def add_to_cart
 begin
   product = Product.find(params[:id])
 rescue ActiveRecord::RecordNotFound
   logger.error("Attempt to access invalid product #{params[:id]}" )
   redirect_to_index("Invalid product" )
 else
   @cart = find_cart
   @cart.add_product(product)
   respond_to do |format|
     format.js
   end
 end
end

Now that we have the application talking with the user, it’s time to make the magic happen by creating an RJS template, which is basically a way to add some JavaScript to the application by using Ruby code. Create an add_to_cart.js.rjs file and edit it with this line of code:

page.replace_html("cart" , :partial => "cart" , :object => @cart)

We’re basically telling the page that we want to replace the “cart” div with the content in the “cart” partial, and as said before, the partial needs something to manipulate, so we also need to pass it an instance variable. Now, by clicking ‘Add to Cart’, the sidebar should update itself with the new item you added without refreshing the page, thus, AJAX.

Next thing we need to worry about is how that AJAX is being shown to the user. Not much is happening at the moment, the data just updates itself without notice and it would be nice that something flashed to the user noticing that something new happened. The logic behind that is to tell the view file which was the last product added so it can do something with it. We’ll start by editing the cart model file with some simple changes:

 def add_product(product)
  current_item = @items.find {|item| item.product == product}
  if current_item
    current_item.increment_quantity
  else
    current_item = CartItem.new(product)
    @items << current_item
  end
  current_item
end

The method is almost the same, we just basically changed it a bit so it would return the current item being manipulated, which, in case of many, will be the last one (since the same variable gets replaced everytime). Now we move on to the controller, so that it can make that current_item an instance variable and pass it on to the view file:

def add_to_cart
  begin
    product=Product.find(params[:id])
  rescue ActiveRecord::RecordNotFound
    logger.error("Attempt to access invalid product #{params[:id]}")
    redirect_to_index("Invalidproduct")
  else
    @cart=find_cart
    @current_item=@cart.add_product(product)
    respond_to do |format|
      format.js
    end
  end
 end

Notice on line 9 that an instance variable @current_item is being set to the value returned by the add_product method which we just altered above. Now it’s time to pass on that instance variable to the view file, which, in our case, is the cart_item partial template (_cart_item.html.erb):

<%if cart_item==@current_item%>
<tr id="current_item">
<%else%>
<tr>
<%end%>
  <td><%=cart_item.quantity%>&times;</td>
  <td><%=hcart_item.title%></td>
  <td class="item-price"><%=number_to_currency(cart_item.price)%></td>
</tr>

Quite simple, actually, a comparison is made to see if the cart item being manipulated is the same as the current item instance variable, if so, then a special table row is printed, else, a normal table row is printed. That special table row will be now manipulated by a visual effect granted by the Script.aculo.us Javascript library, which flashes the content on the screen. To add that effect, let’s go back to our RJS template and edit it with a new line of code:

page.replace_html("cart",:partial => "cart", :object => @cart)
page[:current_item].visual_effect :highlight,
                                  :startcolor=> "#88ff88",
                                  :endcolor=> "#114411"

By default the highlight visual effect flashes a yellow color on the screen, so we changed it’s behavior so that it will flash a light green color, which is more compatible with our layout. Try adding something to the cart and see what happens, it’s quite amusing.

One more thing: there’s no need to show the cart if it’s empty, and making it appear out of nowhere is not cool. But then we learned that visual effects are great, aren’t they? So let’s add another one for a cart entrance event. This one is called blind down, it slowly slides the content down the screen, which, for now, is perfect for our cause. Your RJS template file should still be open in your editor, so add a new line of code to it:

page.replace_html("cart",:partial => "cart", :object => @cart)
page[:current_item].visual_effect :highlight,
                                  :startcolor => "#88ff88",
                                  :endcolor => "#114411"
page[:cart].visual_effect :blind_down if @cart.total_items == 1

We just told the template file to use the blind down effect on the “cart” div case the number of items in the cart is equal to 1, which means it will only happen on the first item added. This won’t work right away because we didn’t set the total_items method, so, on the cart model file, add this method:

def total_items
  @items.sum {|item| item.quantity}
end

To implement this the best way possible, we’re gonna be using something new: helper methods, but not the ones Rails provides us, this one we’re gonna write ourselves, and it’s recommended to do that whenever you find that you need to abstract something out of the view. Rails generates helper files that you can use to create your own helper methods, so let’s get to it, you can find the file we need at app/helpers/store_helper.rb:

module StoreHelper
  def hidden_div_if(condition,attributes={},&block)
    if condition
      attributes["style"]= "display:none"
    end
    content_tag("div",attributes,&block)
  end
end

We’re using a Rails helper named content_tag, which wraps up the output generated by a Ruby block with certain attributes. Notice the display:none attribute that will be set case the condition passed to our helper is true. Let’s see what we just did in action by inserting this chunk of code inside the “cart” div in the store layout file:

<% if @cart %>
  <% hidden_div_if(@cart.items.empty?, :id => "cart") do %>
  <%= render(:partial => "cart", :object => @cart)%>
  <% end %>
<% end %>

We set the condition to check if the cart item collection is empty, if so, the div with the “cart” ID will not display, else, it will, in this case, it will render the cart partial (which renders the cart_item partial).

Finally, let’s get rid of the empty cart flash message, there’s no need for it anymore because if the user empties the cart, it just won’t show. Editing the empty_cart method in the store controller:

def empty_cart
  session[:cart]= nil
  redirect_to_index
end

Only difference is in the redirect_to_index call, which now has no parameters, therefore, no message display.

Wait, we almost forgot about the people who don’t trust JavaScript (yes, they sure are paranoied people, but they exist). Disabling JavaScript and using our code will just print out some huge trash, so let’s make it so the non-JS user gets the old-non-AJAX treatment. The main AJAX magic happens on the add_to_cart method in our store controller, remember? So let’s edit that:

def add_to_cart
  begin
  product = Product.find(params[:id])
  rescue ActiveRecord::RecordNotFound
    logger.error("Attempt to access invalid product #{params[:id]}")
    redirect_to_index("Invalid Product")
  else
    @cart = find_cart
    @current_item = @cart.add_product(product)
    respond_to do |format|
	  format.js if request.xhr?
	  format.html {redirect_to_index}
     end
  end
end

With that code, our application will respond with HTML and a simple redirection to the index page case the XHR (XmlHTTPRequest) fails (which means the AJAX failed). On short terms, AJAX for JS people, no AJAX for non-JS people.

That’s all folks, our application now has some cool AJAX, but is also tolerant with non-JS users. Notice we did things step by step, adding the AJAX slowly, this makes it easier to debug (debugging AJAX is hard when you have a huge spider web in front of you), and also easier to globalize our application so that it will work despite the use of JavaScript.