About this entry




Introduction to AJAX in Ruby on Rails

The following article introduces how to add AJAX support to Ruby on Rails applications.

Liked it? !

The following posts are based on a Ruby on Rails workshop I am teaching in Thimphu, Bhutan. It is not a complete tutorial since it does not include explanations. It may in the future but I do not know when I will have time to add them, so I am publishing it now as is to make it available to others that may find it useful. The sketches describe how to progressively build an Image Management System from the ground-up.

Before reading this article it is recommended that you read this first: Introduction to Active Support.

Index

  1. Modules
  2. Rendering Partial Templates
  3. link_to_remote

 

Modules

In the lib folder you can place code that you want available from several controllers, models, or views belonging to different controllers. Using modules is a nice way to extend classes adding access to attributes or methods. Create a file under lib/util.rb with the following content:

module Util MARGIN = " " * 5 end

This module will be automatically included and available from views, models, and controllers. You can access the constant by writing Util::MARGIN.

Rendering Partial Templates

When listing the administrative units in the app/views/countries/show.rhtml view, we will put the rendering of the units in a separate template. Update app/views/countries/show.rhtml to:

<%= javascript_include_tag 'prototype' %> <p> <b>Country Title:</b> <%=h @country.title %> </p> <p><b>Administrative Levels</b> (<%= link_to 'New administrative level', new_administrative_level_path(:country_id => @country) %>)</p> <ul> <% for level in @administrative_levels %> <li><%= link_to h(level.title), administrative_level_path(level) %> (<%= level.level %>)</li> <% end %> </ul> <% if !@first_level.nil? %> <p><b><%= @first_level.title.pluralize %></b> (<%= link_to "New #{@first_level.title}", new_administrative_unit_path(:administrative_level_id => @first_level) %>)</p> <p> <% @margin_depth = 0 for @unit in @first_level_units %> <div id="<%= @unit.id %>_div"> <%= render :partial => 'administrative_units/contracted_unit' %> </div> <% end %> </p> <% end %> <p> <%= link_to 'Edit', edit_country_path(@country) %> | <%= link_to 'Destroy', country_path(@country), :confirm => 'Are you sure?', :method => :delete %> | <%= link_to 'Back', countries_path %> </p>

Note that we have included a javascript include tag for the prototype library, which is needed for AJAX to work. We send information to the partial templates from other templates in the same way that we do from the actions to the views: through local variables. In this case we are sending @margin and @unit to the contracted_unit template in the administrative_units view folder. Notice that what the template will render is within a <div> tag with an id corresponding to the administrative unit id that is being rendered. The naming convention for the partial templates is for its name to start with an underscore (_). Create a file called app/views/administrative_units/_contracted_unit.rhtml with the following content:

<%= Util::MARGIN * @margin_depth %> <% if !@unit.administrativeLevel.next_level.nil? %> <%= link_to_remote '+', :update => "#{@unit.id}_div", :url => {:controller => 'administrative_units', :action => 'expand_unit', :id => @unit.id, :margin_depth => @margin_depth } %> <% end %> <%= link_to h(@unit.title), administrative_unit_path(@unit) %><br/>

link_to_remote

The key statement in the previous partial template is the link_to_remote call that substitutes the conventional link_to helper. The link_to_remote helper will instead of causing a whole page to load in the browser will simply substitute the html tag with the id specified by :update by the view rendered by the action specified in the :url. For the action expand_unit, add to the administrative_units controller:

def expand_unit @unit = AdministrativeUnit.find(params[:id]) @margin_depth = params[:margin_depth].to_i render :partial => 'expanded_unit' end

The @margin_depth global variable will specify how much to indent the lower levels. This action will render the partial template in app/views/administrative_units/_expanded_unit.rhtml:

<%= Util::MARGIN * @margin_depth %> <%= link_to_remote '-', :update => "#{@unit.id}_div", :url => {:controller => 'administrative_units', :action => 'contract_unit', :id => @unit.id, :margin_depth => @margin_depth } %> <%= link_to h(@unit.title), administrative_unit_path(@unit) %> <% next_level = @unit.administrativeLevel.next_level if !next_level.nil? %> (<%= link_to "New #{next_level.title}", new_administrative_unit_path(:administrative_level_id => next_level, :parent_administrative_unit_id => @unit) %>) <% end %> <br/> <% @margin_depth = @margin_depth + 1 children = @unit.childrenUnits for @unit in children %> <div id="<%= @unit.id %>_div"> <%= render :partial => 'administrative_units/contracted_unit' %> </div> <% end %>

The contract_unit action in the administrative_units controller invoked about is:

def contract_unit @unit = AdministrativeUnit.find(params[:id]) @margin_depth = params[:margin_depth].to_i render :partial => 'contracted_unit' end

Finally, we would also like this dynamic tree browsing for the show view in the administrative_units controller. Update app/views/administrative_units/show.rhtml to

<%= javascript_include_tag 'prototype' %> <p> <b><%=h @administrative_level.title %> Title:</b> <%=h @administrative_unit.title %> </p> <p> <% options = {:administrative_level_id => @administrative_level} if !@parent_unit.nil? options[:parent_administrative_unit_id] = @parent_unit %> <b>Parent <%=h @parent_unit.administrativeLevel.title %>:</b> <%= link_to h(@parent_unit.title), administrative_unit_path(@parent_unit) %> <% else country = @administrative_level.country %> <b>Parent Country:</b> <%= link_to h(country.title), country_path(country) %> <% end %> </p> <% if !@next_level.nil? %> <p><b><%=h @next_level.title.pluralize %></b> (<%= link_to "New #{@next_level.title}", new_administrative_unit_path(:administrative_level_id => @next_level, :parent_administrative_unit_id => @administrative_unit) %>)</p> <p> <% @margin_depth = 0 for @unit in @administrative_unit.childrenUnits %> <div id="<%= @unit.id %>_div"> <%= render :partial => 'contracted_unit' %> </div> <% end %> </p> <% end %> <p> <%= link_to 'Edit', edit_administrative_unit_path(@administrative_unit) %> | <%= link_to 'Destroy', administrative_unit_path(@administrative_unit), :confirm => 'Are you sure?', :method => :delete %> | <%= link_to 'Back', administrative_units_path(options) %> </p>
Technorati tags: , , ,

Liked it? !

Posted on February 21st | 0 comments | Filed Under: Ruby on Rails