Cascading Selects in Rails
While I know there are resources out there for creating Cascading (dependant?) select boxes using Ruby on Rails, my decision to write about this came more from the myriad of solutions for all different versions of rails. Some of them worked and some of them didn’t. This is what ended up being my solution to the problem.
As a background, this was for a signup page which would require a state/province and a country. I wanted a user to select their country first, which would then populate the select box for the state/province so that they could choose from only relevant entries.
The model is Property and the controller is properties_controller.
The relevant part of the form looks like this:
<%= select "property", "country_id", Country.find(:all).collect {|c| [ c.name, c.id ] }, { :include_blank => true} %>
<div id="provinces">
<%= select "property", "province_id", @provinces.collect {|p| [ p.name, p.id ] }, { :include_blank => true } %></td>
</div>
<script type="text/javascript">
//<![CDATA[
new Form.Element.Observer('property_country_id', 0, function(element, value) { new Ajax.Request('/properties/select_country?country_id='+value, {asynchronous:true, evalScripts:true}); });
//]]>
</script>
Now, there is some clarification needed here. I would guess that people would argue that rather than write your own javascript in this instance, it would make more sense to use the observe_field helper. I could not for the life of me get the observe_field helper to create working javascript. If anyone has any suggestions, I’d be happy to hear them.Anyhow, the basics of the javascript is this:
Observe the field ‘property_country_id’.
When it is changed, perform the listed function.
The function creates an Ajax Request to my properties controller to it’s select_country action, and also passes in the country_id selected in the parameters.
Now let’s look at how the action works in app/controllers/properties_controller.rb.
def select_country
@country_id=params[:country_id]
if (@country_id.nil?)
@provinces=Array.new
else
@provinces = Province.find(:all, :conditions => 'country_id='+@country_id, :order=>'name')
end
end
What this does is checks the country_id and if it doesn’t exist returns a new empty array, and if it does exist, finds all the provinces (I’m Canadian so I named my model provinces) related to that country.So now we have the the list of provinces, and all thats left for us to do is repopulate the Province Select box. To do that, we use an RJS template. So we create a .rjs file in app/views/properties/select_country.rjs. We then populate it with:
page.replace_html(’provinces’, (select “property”, “province_id”, @provinces.collect {|p| [ p.name, p.id ] }, { :include_blank => true }) )
This will replace the html in the id ‘provinces’ with a select box populated with the provinces we found in the action.
That’s all there is to it!