The Rails old shopping cart demo ported to the Padrino framework
Switch branches/tags
Nothing to show
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Failed to load latest commit information.
.bundle
app
config
public
spec
.components
.gitignore
Gemfile
README.textile
config.ru

README.textile

Overview

This tutorial will show you step-by-step how to create an Ajax-powered Padrino application in minutes.

Introduction

Adding items to a shopping cart in common e-commerce applications isn’t very close to the actual “add to cart” metaphor, since it requires clicking an “add to cart” button, watch a new page (the shopping cart), and then go back to the shop or checkout with buttons.

Ajax allows to get closer to the cart metaphor, by enabling drag-and-drop interactions and giving immediate visual feedback, without leaving the shop.

The target application of this tutorial will be a Padrino ported version of the shopping cart demo published by script.aculo.us in Rails. It uses the prototype JavaScript framework (bundled with Padrino) and some script.aculo.us JavaScript that is the core of the JavaScript helpers.

Application Setup

First, create a shopping-cart project and cart controller:


$ cd /home/cored
$ padrino-gen project shopping-cart -e erb -s prototype
$ cd shopping-cart
$ bundle install
$ padrino-gen controller Cart get:index

The Main Page

First, you need to create a list of items to be purchased. To keep the project simple, the element list is accessed via an array. The shopping cart is a simple parameter of the session hash. Modify the shopping-cart/app/controllers/cart.rb to:


ShoppingCart.controllers :cart do
  get :index, :map => '/' do
  	session[:cart] = []
  	@products = ['iPod black', 'iMac', 'iMac RC', 'iPod']
  	render 'cart/index'
  end
end

The main page of the cart controller will contain a list of items, and a zone to drag items to. This zone is the shopping cart. So create the view for the index in shoppng-cart/app/views/cart/index.erb and write in:


<h1>padrino Apple store demo</h1>
 
<div id="shopping_cart">
 
  <h2>Products:</h2>
 
  <div id="product_list">
    <% @products.each_with_index do |product, index| %>
      <%= image_tag "product#{index}.png", :class => 'products', :id => "product_#{index}" %>
    <% end %>
  </div>
 
  <h2>Cart:</h2>
 
  <div id="cart" class="cart">
    <div style="clear: both"></div>
  </div>
  
</div>

You can see that products are shown as images. Use the images available in the public/images directory in the project’s repository, and put them in the shopping-cart/public/images/ directory. In addition, part of the styling was done for you, so it is recommended that you upload this stylesheet to the shopping-cart/public/stylesheets/ directory and add a stylesheet_link_tag in the top of the shopping-cart/app/views/cart/index.erb view:


<%= stylesheet_link_tag 'cart' %>

Now from your project root directory start padrino web server and point your browser to http://localhost:3000


$ padrino start 

Focus on the Cart

The cart content will change as you drag items to it. This means that the content of the cart in the template must be in an independent file. Use the partial helper for that. The items in the shopping cart will be stored in divs with float:left style, so a clearing div is necessary after the container. So change the end of the index.erb view to:


  <div id="cart" class="cart">
    <div id="items">
      <% partial 'cart/cart', :collection => @products %>
    </div>
    <div style="clear: both"></div>
  </div>
</div>

The partial helper will include a _cart.erb file, and look for this file in the shopping-cart/app/views/cart/ directory. Create it with the following content:


<% session[:cart].each_with_index do |product, quantity| %>
	<div>
		<% 1.upto(quantity) do |i| %>
			<%= image_tag "product#{quantity}.png", :class => 'cart-items', 
			:id => "item_#{quantity}_#{i}",
			:style => 'position:relative' 
			%>
		<% end %>
		<%= "#{quantity} #{@products[quantity]} "%>
	</div>
	<div id="shopping_cart_text"></div>
<% end %>

<% if session[:cart].empty? %>
nothing yet in your shopping cart.	
<% end %>

If the cart contains items, they appear as images, as many times as they are added; the quantity is be displayed after each series.

Now watch again the shopping cart at:

http://localhost:3000

Well, there is not much change, it is still very empty… It’s time to make things AJAX.

Add JavaScript Behaviors

Edit the index.erb view and add the following lines to the bottom:


<%= javascript_include_tag 'protopak' %>
<%= javascript_include_tag 'application' %>

Make the images draggable by adding the following code to shopping-cart/public/javascript/application.js script:


for (i = 0; i < 4; i++) {
	new Draggable('product_'+i, { revert: true });
}

This adds a ‘draggable’ behavior to each of the images of the list of products. The revert option will make images go back to their origin position when released (unless received by a receiving element).

Now, define the cart as a receiving element. You just need to define which part of the view will have to be updated when the event occurs, which action will be called for its content, and which type of draggable elements can be dragged into it. Put the following code inside the application.js script:


Droppables.add('cart', {
    accept: 'products',
    onDrop: function(element) {
        new Ajax.Updater('items', $('shopping_cart').readAttribute('data-url'), { method: 'get', parameters: { id: element.id }});
    }
});

Now try again, and move the products to the cart: it works. When a draggable item is dragged to the receiving element, an XMLHTTPRequest is sent to the add action, and the result is displayed in the items div. The thing is, the add action of the cart module is not defined yet…

Define the Updating Action

Edit the shopping-cart/app/controllers/cart.rb to add an add action:


   get :add, :map => '/add' do
     product_id = params[:id].at(-1).to_i
     cart = session[:cart] || {}
     cart[product_id] ? cart[product_id] += 1 : cart[product_id] = 1
     session[:cart] = cart
     render 'cart/add'
   end

This action looks for the parameter sent by the JavaScript (the id of the dragged item) and adds it to the cart.

The result of the add action will be the add.erb view. It is a simple inclusion of the _cart.erb partial, but this time it is necessary to pass the products as a parameter :


#shopping-cart/app/views/add.erb

<% partial 'cart/cart', :collection => @products %>

Try it on: you can now add items to the cart by dragging them

Focus on Usability

You could stop now, but this shopping cart has a big default: while the cart is updated, the interface doesn’t change and the user might be disoriented. This is a general issue of asynchronous requests: an indicator zone has to be added to show that the request is being processed. In addition, nothing tells the user when the dragged item is considered accepted by the cart, so the hover style of the cart div also has to be defined.

To do that, edit the index.erb template and write in:


<div style="height:20px">
  <p id="indicator" style="display:none">
    <%= image_tag 'indicator.gif' %> updating cart...
  </p>
</div>

Save the ‘indicator.gif’ image file to your shopping-cart/public/images/ directory. You can find this file in the github repo.

Now, modify the Javascript in shopping-cart/public/javascript/application.js to show this new indicator while requests are processed and declare the hover style:


Ajax.Responders.register({
    onCreate: function() {
        if($('indicator') && Ajax.activeRequestCount>0)
            Element.show('indicator');
    },
    onComplete: function() {
        if($('indicator') && Ajax.activeRequestCount==0)
            Element.hide('indicator');
    }
});

Droppables.add('cart', {
    accept: 'products',
    onDrop: function(element) {
        new Ajax.Updater('items', $('shopping_cart').readAttribute('data-url'), { method: 'get', parameters: { id: element.id }});
    }, 
    hoverclass: "cart-active"
});

Conclusion

I hope you enjoy the ride through some of the features of the Padrino web framework. Any suggestion just send me an email to george.rafael at gmail.com