Best way to client-side load or hide state/province drop down when country is changed?

Maybe I’m just trying too hard, because I have done this plenty of
times with plain old javascript before Rails entered my life. But now
that I’m trying to work within the Rails system of form_for and such,
I am finding that what should be a simple task has become arduous
beyond compare. I’m ready to tear out what little hair I have left,
and I have yet to find anything online that addresses what I thought
would be a common scenario.

So, the scenario:
A user create/edit page. When a user selects US or Canada, the drop
down loads accordingly. If he selects a different country, the
dropdown disappears. When the form is submitted, the user and his
address are saved accordingly.

User: has_one :address
Address: has_one :state, has_one :country


<% form_for :user, :url => {:action => “update”, :id => @user} do |f|
<%= render :partial => “user_form”, :locals => {:f => f} %>
<% end %>


First name: <%= f.text_field :first_name %>

Last name: <%= f.text_field :first_name %>

<% fields_for :address do |a| %>

City: <%= a.text_field :city %>

Country: <%= :country_id, COUNTRIES %>

State: <%= :state_id, STATES %>

<% end %>

At first, I tried putting the state selector and text in its own
partial, thinking that via a custom onchange event on the country
selector and by using RJS I would be able to reload the state selector
from a controller method or hide it altogether. But in practice it
can’t be done because the state selector needs a form reference and
the controller can’t pass that in. It got ugly real fast.

So where I stand now, I’m looking at writing a custom javascript
method, fired from an onchange event on the country selector, that
would reload the contents of the state selector. Have I missed some
magical rails ingredient or helper that would make this better? Has it
just been too long a day? Any help appreciated.

I got it working by foregoing a form_for selector for this attribute,
and using a manually loaded html selector instead. Seems to work as
desired, though it feels a bit hackish.

i dont think that’s ‘hackish’.
Basically you’re foregoing some automated magic for more control and
which in this case you needed.

True true! Just happy to get it working.

Yuval wrote:

True true! Just happy to get it working.

yuval raz?

If you want a solution that is more Rails-centric…

First, I created some helper methods just for convenience. (I only
needed the state_options one but created the others for testing this
solution). The xxx_options simply creates an acceptable collection
for populating a Rails select. The xxx_option_tags return a string of
option tags with the line breaks removed. That’s necessary for their
inclusion in a js function below:

in application_helper.rb:

def state_options
Address::STATES.collect{|state| [state, state]}

def province_options
Address::PROVINCES.collect{|state| [state, state]}

def state_option_tags
options_for_select(state_options).gsub("\n", “”)

def province_option_tags
options_for_select(province_options).gsub("\n", “”)

I’ve got an address partial because I will capture addresses for all
kinds of different things (people, customers, businesses, etc). My
address partial just consists of a normal ‘fields_for :address’.
Here’s the part relevant to the discussion:

<%= :state, state_options %>
<%= f.text_field :postal_code %>
<%= f.country_select :country, ['United States',

‘Canada’], :selected=>‘United States’ %>
<%= observe_field :address_country,
:function=> “switch(value){ case ‘United
States’: $(‘address_state’).show();$
(‘address_state’).update(’#{state_option_tags}’); break; case
‘Canada’: $(‘address_state’).show();$
(‘address_state’).update(’#{province_option_tags}’); break; default: $
(‘address_state’).hide();}” %>

Okay, so what’s going on… I’m making use of the prototype library
packaged with Rails and the baked-in “observe_field” helper. This
helper observes the country select field and fires on a change (the
country select is rendered with US and Canada at the top before an
alphabetical listing of countries, and US is selected by default).
The :function=>… part is passing a js-function that will become the
method responsible for handling the change in the observe field. It
will be wrapped in a function block that looks like: function(object,
value) {}. That’s where the ‘value’ field comes from. The rest of
the string is just a switch-statement that loads the appropriate
values into the select box or hides it all together.

The #{state_option_tag} is where the application_helper.rb comes into
play. That is evaluated at run time to give the select options you
need. The net effect is that the js swaps out the tags in
the address_select field.