Previous Page
Next Page

Hack 58. Dynamically Generate a Selection List in a Rails Template

Generate a selection list from server-side data using Ajax and Ruby on Rails.

This hack creates a select element from server-side data using a Ruby on Rails application. Unlike the typical manner in which a web widget is generated in a web page, the user chooses the content for the pop-up, and then the pop-up appears out of the blue, without anything else on the page changing. The selections that the user sees in the pop-up derive from a server-side component; they are not just hard-coded JavaScript or HTML that is part of the web page.

This hack has the same behavior as some of our earlier hacks, but the path our code takes to produce its effects is quite different. A Rails application wraps all of the XMLHttpRequest mechanics behind its own methods. Figure 7-9 shows what the web page looks like. The user reaches this page by typing http://localhost:3000/hacks/ into a web browser's location field.

We're using the WEBrick server that comes with Ruby on Rails.

Figure 7-9. Cop your own pop-up

The user chooses either Team or Individual from the pop-up and clicks the Show Sports button, and another select list (whose values are determined by the user's choice in the first list) appears just above the button.

In a Rails application, a controller component takes charge of the views that the web user sees. The controller component is located inside the <web-app-root>/app/controllers directory. Using http://localhost:3000/hacks/ as an example, the framework looks in <web-app-root>/app/controllers/hacks_controller.rb for an action or method named index that generates the response. Actions start out as Ruby methods within a controller object (a class, in object-oriented terms) defined in this file.

The entire path on my Mac OS X machine is ~/Rails/Energy/app/controllers/hacks_controller.rb. Energy is the top-level directory of the web application.

Let's take a look at hacks_controller.rb for the index( ) method:

class HacksController < ApplicationController
    def index
#rest of class definition...

Nothing happening there; it's an empty method definition. In this case, Rails looks for a template named index.rhtml in the app/views/hacks directory.

One of the intuitive aspects of the Rails framework is that the framework maps URL path information, such as the hacks part of http://localhost:3000/hacks, to directories of the same name in sensible locations, such as views.

index.rhtml provides the template for our hack:

    <meta http-equiv="content-type" content="text/html; charset=utf-8" />
    <%= javascript_include_tag :defaults %>
    <title>Ajax Rails &amp; Select lists</title>
<%= form_remote_tag(:update => "sel_con",:url => { :action => :create_select },
:position => "top",:success => "$('sel_con').innerHTML=''" ) %>
Please select a sports category:
<%= select_tag "categories",
"<option>Team</option><option>Individual</option>" %>
<div id="sel_con"></div>
<%= submit_tag "Show Sports" %>
<%= end_form_tag %>

This template calls the form_remote_tag( ) method to update the div positioned beneath the form tag. This method wraps all of the Ajax- and request objectrelated functionality, updating the div with server data and positioning any more information on top of or before any existing div content (as specified by the :position => "top" parameter). In other words, when the user clicks the Show Sports button, an XMLHttpRequest object is created, and its send( ) method sends a request to a Rails action named create_select. This action is defined as a Ruby method in the hacks_controller.rb file we peeked at before.

The create_select action renders the HTTP response, which specifies the tags and content of a new pop-up or select list. Figure 7-10 shows the result of clicking the Show Sports button.

Figure 7-10. Voil\x88 , up pops a select list

How is that response rendered, anyway? Let's look at that part of the controller object:

class HacksController < ApplicationController
    def index
    def create_select
        indArr=["Nordic Skiing", "Inline Skating","Tennis",
"Triathlon","Road Racing","Figure Skating","Weight Lifting",
"Speed Skating","Snowboarding"]; teamArr=["Soccer","Basketball","Football","Hockey",
"Baseball","Lacrosse"]; str=""; if params[:categories].index('Team') != nil render :partial => "options", :locals => { :sports => teamArr,:sptype => "team"} elsif params[:categories].index('Individual') != nil render :partial => "options", :locals => { :sports => indArr, :sptype => "individual" } else str="<select id='individual' name='individual'> <option>unknown</option></select>"; render :text => str; end #end method end #end class definition end

This controller object stores two Ruby arrays in the variables indArr and teamArr (these values could alternatively be generated from a database). Remember the web page's existing select list that gives the user a choice of Team or Individual? This is a select element with the name categories. The browser submits this value as a form parameter to the Rails action. The code uses the syntax params[:categories] to get this parameter's value. Then things get interesting, in a Rails kind of way. The server still has to provide an HTTP response to the Ajax request object.

The action uses the RoR render( ) method to send the HTML for a select list back to our application, with the array values as the select list contents:

render :partial => "options", 
:locals => { :sports => teamArr,:sptype => "team"}

render( ) specifies in its first parameter a partial named options. In Rails lingo, a partial is just a template that contains a chunk of content that can be used over and over againfor example, one or a few HTML tags. Using the Rails naming convention, the partial (really a text file) is placed in the app/views/hacks directory with its name preceded by an underscore, as in _options.rhtml. So, in plain English, the method call declares, "Render the response as the _options partial content, and hand the partial two local variables, :sports and :sptype."

Don't forget the : before the variable names!

A Little Partial Pizzazz

The partial needs the array values to build the select list; these are stored in the teamArr variable. Rails uses its built-in naming convention for partials (the underscore requirement) to find the correct content and render it as the response. Let's look at the partial _options.rhtml file:

<select id="<%= sptype %>" name="<%= sptype %>">
<% sports.each do |sport| %>
<option><%= sport %></option>
<% end %>

A little embedded Ruby code gives the select element its id and name. The embedded code then iterates through the array (passed into the partial by the render( ) method) and creates an option element for each array member, as in <option>hockey</option>. Although a little server-side Ruby code was involved, the code did not have to touch the request object or deal with fetching or massaging the return value.

Cleanup Code

The only bit of trickery involved is making sure that the application does not append one new select list after another inside the div as the user clicks the button. We want all the existing selects in that div to be replaced by any new ones. Therefore, we include a little cleanup code that empties the div before it's updated with a new select list:

<%= form_remote_tag(:update => "sel_con",:url => { :action => :create_select },
:position => "top",success => "$('sel_con').innerHTML=''" ) %>

success sequentially precedes the complete stage, so this code sets the div's content to the empty string before the new select list appears.

To handle any request failures, such as a downed web server, add this code from a prior hack as a form_remote_tag( ) parameter:

#$('failure') is Prototype's shortcut for
:failure => "$('failure').innerHTML='Failure;
request status='+request.status"

Previous Page
Next Page