|
Use of Cascading Select in View for Managing Dependent Lists
By: Bruce Bahlmann - Contributing Author (your
feedback
is important to us!)
In rails development, when creating web forms, you often have a list of
information that is dependent on another list of information. For example
you have a school with different campuses and each campus has different
sites or communities. Ideally, you don't want users to independently select
sites/communities of the campuses these sites/communities are dependent on,
thus these lists are dependent on one another. So, the desired behavior is
to have the user first select which campus and then once selected, the user
is only shown those sites/communities that are associated with the selected
campus.
There are multiple ways to do this in Ruby on Rails (RoR), but perhaps the
simplest involves a tiny bit of JavaScript. So rather than installing a Gem
or building scopes and complicated models, the following provides one the
easiest ways to build reliable cascading selects that can be implemented
quickly and painlessly. We first start with a collection of elements (lists)
which can be provided inline or via a CONSTANT declaration [see
Select-Array] article for more information on how to do this. We will
use the CONSTANT declaration so we'll declare two related items (campuses,
sites) within our model
to account for this.
[apps/models/post.rb]
class Post < ActiveRecord::Base
CAMPUSES = {1 => { :name => "Beloit", :communities => {101 => 'Bethesda', 102 => 'Faith',
103 => 'Hill', 104 => 'Hunziker', 105 => 'Journey', 106 => 'Triumph'}
}, 2 => { :name => "Bremwood", :communities => {201 => 'Andrew', 202 => 'Banker',
203 => 'CUNA', 204 => 'Jacobson', 205 => 'North', 206 => 'Phoenix',
207 => 'South', 208 => 'Trinity', 209 => 'Waverly', 210 => 'Woodhaven'}}
}
...
end
Next we need to build the main select to display the top (deciding) level
which the follow on lists are dependent. This main select is where we
introduce our tiny bit of JavaScript to manage the show/reveal operation
that allows us to only show the secondary (cascading) list upon selection of
a valid item within this main select (list). Changing the selected item
within this main list will toggle the show/reveal to "hide" the list for the
unselected value and "show" the list for the selected value. Of course, if
the user selects no value, no secondary list be be shown (both
secondary/cascaded lists will be hidden - not shown).
[apps/views/posts/_form_post_html.erb]
class Post < ActiveRecord::Base
...
<% content_for(:javascript) do %>
<%
campus_selects = {}
Post::CAMPUSES.each_key do |k|
campus_selects[k] = options_for_select(Post::CAMPUSES[k][:communities].sort.collect {|c| c.reverse})
end
%>
// Disabled community until a campus is selected
if ($("#post_campus").val() == "") {
$("#community_field").hide();
}
campuses = <%= Post::CAMPUSES.to_json.html_safe %>;
selects = <%= campus_selects.to_json.html_safe %>;
$("#post_campus").bind('change', function() {
campus_id = $("#post_campus").val();
if(campus_id != ""){
$("#post_community").html("<option></option>"+selects[campus_id]);
$("#community_field").show();
} else {
$("#community_field").hide();
$("#post_community").html("<option></option>");
}
});
<% end %>
...
 |
 |
|
Cascading Selects allow show/reveal of
secondary selects |
With this in place, we merely need to add the code for displaying the
secondary lists. The initial if/else logic around each DIV tag allows for
the case of either adding a new entry or editing an existing entry. This is
because, a new entry would not have a value for campus (thus display: none
is used for both lists), however editing an existing entry would require
that both campus and community to have a value - so we need to turn on the
appropriate list depending on the previously stored value of campus.
[apps/views/posts/_form_post_html.erb]
class Post < ActiveRecord::Base
...
<div class="field">
<%= f.label :campus %><br />
<%= select("post", "campus",
Post::CAMPUSES.collect {|k,v| [v[:name], k]},
{:include_blank=>true}) %>
</div>
<div id="community_field" class="field">
<%= f.label :community %><br />
<% if @post.campus.nil? %>
<%= select("post", "community", {}, {:include_blank=>true}) %>
<% else %>
<%= select("post", "community",
Post::CAMPUSES[@post.campus][:communities].sort.collect {|c| c.reverse},
{:include_blank=>true}) %>
<% end %>
</div>
The code for implementing this function is a little verbose, but if this
is your first time implementing cascading selects, you might appreciate the
clarity of being verbose rather than the Rails way, which tends to be just
the opposite.
Can Birds-Eye.Net help you or your Company?
Receive your Birds-Eye.Net articles and white
papers hot off
the presses by adding our RSS feed to your reader.
|
|