A frequent need in building web site application is to have users select one value and then, based on that value, select another value. Real world needs for related values might be : Select a Country and then State or Province; Select a Car Manufacture and then a Car Model. Generically it’s about selecting some Category or Section and then selecting the Sub Category or sub Section, the selection of one field cascades the results in another related field.
Also called Related Drop Down fields or Dependant Drop Down lists or Dynamic Drop Downs or Dependent Drop Downs.
What you want to have happen is something that will look and act like this
If you select Veg’s

and then if you select Meat :

Also, in the underlying html select tag code, you want values stored and names displayed so you can a)change or correct the names, b) display the names in a different languages but keep the underlying key values.
In the bad old days you might have them select the first value and the go to a new page for the related values, or at best refresh the entire page. Thankfully this is the days of shinny and we haz ajax!! But we still need to get the ducks lined up and quacking in order for this to work. This is my code to do that.
Note that this solution has been tested with RoR 2.3.5 and jQuery 1.4.2 (standard flavours as of early 2010)
you need to have jQuery loaded on the page and the easest way (althought not always the best way) is to pull all the script for m the public/javascrip direcroty of your application with :
1 | < %= javascript_include_tag :defaults %> |
in the sample I assuming a Section field and a dependent Sub-Section field. From a data model perspective the Section mode has a Id and a Name and has_one :sub_section; and the SubSection has a Id, a Name and a Section Id and belongs_to :section
in the sub_sections controller
1 2 3 4 5 6 | def for_sectionid @subsections = SubSection.find( :all, :conditions => [" section_id = ?", params[:id]] ).sort_by{ |k| k['name'] } respond_to do |format| format.json { render :json => @subsections } end end |
this is a pretty straight forward piece of code that uses an id passed in the parameters, returns all the subsection objects for that section_id, sorts that collection of subsections by the subsection.name, and renders that collection as a json object.
As per the notes on the rails wiki for SQL Injection, you need to sanitize the variables being passed as a parameter, and Ruby on Rails has a built in filter for special SQL characters which you need to apply, as above. :conditions => [" section_id = ?", params[:id]]
Note, you could also use a Dynamic attribute-based finders to get the subsection object :
1 | @subsection = SubSection.find_all_by_section_id( params[:id]).sort_by{ |k| k['name'] } |
which is shorter code (less change of a syntax error?) and, as a bonus, automatically applies the sanitize countermeasure.
Further, you might want to create a custom json view for this routine with, only the sub section name and sub section id, if you a) have values want to keep private, or b) the SubSection objection has a lot of data (since we are only interested in 2 fields). That’s the reason its in the SubSections Controller, to keep related stuff together.
in the new or edit view for (in this case) gallery
1 2 3 4 5 6 7 8 9 10 11 12 | < % form_for(@gallery) do |f| %> ... <p> < %= f.label :section_id %><br /> < %= collection_select(:gallery, :section_id, Section.all, :id, :name , options ={:prompt => ""} ) %> </p> <p> < %= f.label :sub_section_id %><br /> < %= collection_select(:gallery, :sub_section_id, SubSection.find_all_by_section_id(@gallery.section_id), :id, :name, options ={:prompt => ""}) %> </p> .. < % end %> |
Again, a fairly typical piece of Rails ERB code for display a html select tag used to create a select list (or drop-down list).
The collection_select help used for the Section field displays all the valid value Name’s and stores the id’s, notes the selected value (as needed in a edit) and a empty string prompt (as needed in a new).
In the case of the Sub Section field the “extra” stuff is to populate the drop down for an edit of an exiting value.
That one line “< %= collection_select(:gallery, :section_id, Section.all, :id, :name , options ={:prompt => “”} ) %>” replaces all of my 2005 posting Building a Better Drop Down Selection List for Ruby on Rails written for Rails 1.x. That’s progress!!
There’s the magic sauce for all of this, assuming that you are including the jQuery javascript library (tested with version jQuery 1.4.2) on the page. I have this in a partial used in the new and edit pages for the gallery.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | <script type="text/javascript"> $(document).ready(function(){ $("select#gallery_section_id").change(function(){ var id_value_string = $(this).val(); if (id_value_string == "") { // if the id is empty remove all the sub_selection options from being selectable and do not do any ajax $("select#gallery_sub_section_id option").remove(); var row = "<option value=\"" + "" + "\">" + "" + "</option>"; $(row).appendTo("select#gallery_sub_section_id"); } else { // Send the request and update sub category dropdown $.ajax({ dataType: "json", cache: false, url: '/sub_sections/for_sectionid/' + id_value_string, timeout: 2000, error: function(XMLHttpRequest, errorTextStatus, error){ alert("Failed to submit : "+ errorTextStatus+" ;"+error); }, success: function(data){ // Clear all options from sub category select $("select#gallery_sub_section_id option").remove(); //put in a empty default line var row = "<option value=\"" + "" + "\">" + "" + "</option>"; $(row).appendTo("select#gallery_sub_section_id"); // Fill sub category select $.each(data, function(i, j){ row = "<option value=\"" + j.sub_section.id + "\">" + j.sub_section.name + "</option>"; $(row).appendTo("select#gallery_sub_section_id"); }); } }); }; }); }); </script> |
The Code is wrapped up in standard jQuery code for doing unobtrusive javascript, waiting for the DOM to finish load and then watching for changes on the select tag with the gallery_section_id tag id.
The first section of code (lines 5-9) just reacts if the Section field is set to my default prompt of blank, in which case it blanks out the sub section values and does nothing else.
The next piece (line 14-16) is the meat of the $.ajax request: setting the dataType to “json” (which ensures the controller routine renders as expected), doing “cache: false” is best for development but is someting to look at in production if your supporting data is very very static. setting the url is Key! in this case ” url: ‘/sub_sections/for_sectionid/’ + id_value_string ” so that it calls the “sub_sections” controller and the “for_sectionid” routine and passes the id string.
If the ajax returns successfully, then the code removes the exiting option’s (important! it works on the option part of the DOM) from the sub_section select tag, adds my default blank line, and than append the id and name values from the returned json object.
Using Firefox firebug extension will make your life much easier in debug this, in particular confirming the ajax request fires off the way (and to the url) you expected, and returns what you expect.
Update : thanks to Tom Meinlschmidt’s feedback I’ve added “.sort_by{ |k| k['name'] }” to the function in the sub_sections controller to return the results in order by the sub section name, as it should have been. (and I fixed up a typo!)
Subscribe by RSS
Subscribe by Email
Follow Me on Twitter
my Del.icio.us Bookmarks
my Flickr Pictures




3 Comments
hmm, using @subsections = SubSection.find( :all, :conditions => “section_id = #{(params[:id]}” ) is a bit obsolete, better to use named_scopes or as you’re using later than, find_all_by_section_id …
btw – valid :condition code is “:conditions => {:section_id => params[:id]}
and names_scope in model could be:
rder => :name
named_scope :by_section, lambda {|id| {:conditions => {:section_id => id}}
and optionally add some sorting support
named_scope :sort_by_name,
and use as SubSection.by_section(params[:id]).sort_by_name
This is exactly what I am looking to do. Will you please provide a full sample where the mvc is provided?
It’s all there!
One Trackback
[...] Down Selection List for Ruby on Rails with jQuery Ajax, for Rails 2.3.x. Related Posts:Building a Cascading Drop Down Selection List for Ruby on Rails with jQuery AjaxAgile Web Development with Rails, erratumRails 1.0: Party like it’s one oh oh!JavaScript for a [...]