Country-State Select Using Carmen and jQuery

I’ve been wanting to find a way to use drop down menus for countries and their states when they exist. But keeping a list on my own would have been a huge pain in the ass. So rather reinvent the wheel, I found the Carmen plugin for Rails. All I have to do is keep the plugin updated and my list of countries and their states will be kept updated as well.

How do I do all this with unobtrusive Javascript and Rails you ask? Good question. Let me show you. Don’t forget to install the plugin (or use the gem).

Let’s start out by adding the drop down menu to our view. In my case I have it in a partial for the address part of a form. You’ll have to modify this slightly to pick up the values of the form if the partial handles edits as well. This one is just for a new method as it uses a default country of US and its states. Note the div ID here of addressStates; we will be using this later in the javascript.

< % fields_for "address" do |addr_form| %>
<p>
  Country<br />
  < %= addr_form.select :country, Carmen::country_names, {:selected => 'United States'} %>
</p>
<div id="addressStates">
  < %= render :partial => 'addresses/states',
    :locals => {:states => Carmen::states('US'), :form => addr_form} %>
</div>
< % end %>

Since we are rendering a partial, we should add that partial as well. Create the file app/views/addresses/_states.html.erb and add the code below. You’ll notice that if no set of states exist in the country selected, I render a text_field instead. This is obviously optional and could potentially lead to issues if the list of states for a particular country is updated and the values in your DB don’t match.

<p>State or Territory<br />
< % fields_for "address" do |addr_form| %>
  < % if !states.blank? %>
    < %= addr_form.select :state, states.collect{|s| [s[0],s[0]]}, {:prompt => 'Select State'},
        {:style => 'width: 180px'} %>
  < % else %>
    < %= addr_form.text_field :state %>
  < % end %>
< % end %>
</p>

Now, let’s update our application.js file. Because of the way the form is laid out, my element ID works out to be address_country. Make sure you use the correct element ID otherwise the action will not fire. (Thanks to Joel for helping with the javascript).

// Select the state after the country has been selected
jQuery('select#address_country').change(function() {
  var country = 'country=' + $('select#address_country :selected').text();
  jQuery.get('/addresses/update_state_select?' + country, function(data){
    jQuery("div#addressStates").html( "<script type='text/javascript'>" + data + "</script>" );
  })
  return false;
});

You’ll notice that when we populate the div, we wrap it in javascript <script> tags. This is because the response we get is an AJAX response. In order for the browser to render it properly, we need to wrap it in script tags.

Before the above Javascript will work, we have to setup a route specifically to handle this request. So add the following code to your config/routes.rb. I chose to put this in the addresses controller (since it’s a functional part of an address, it just seemed logical), but feel free to put it wherever you see fit. Just make sure you then add the method to the proper controller.

map.states '/addresses/update_state_select', :controller => 'addresses', :action => 'update_state_select'

Now we need to add the method to our controller. As I said, I chose the addresses controller in my project because its logical. So I opened up the app/controllers/addresses_controller.rb and added the following code.

def update_state_select
  states = Carmen::states( Carmen::country_code(params[:country]) ) || []
  render :update do |page|
    page.replace_html("div#addressStates",
      :partial => "states",
      :locals => { :states => states }
    )
  end
end

And there you have it. A straightforward way to keep your list of countries and states updated and consistent. Hope this works for you as well as it does for me.

Posted in Rails. Tags: . 4 Comments »
  • Pingback: Tweets that mention Country-State Select Using Carmen and jQuery | Eric's Tech Blog -- Topsy.com

  • http://twitter.com/Alir3z4 Amu Alireza

    AweSome!

  • alansp

    Firstly, thanks for this post, it’s close* to being very helpful for me…

    It’s probably not cool to be posting a question on such an old post but I’m a bit desperate so here goes. I find that the onchange function doesn’t fire unless the script is defined after the country dropdown on the page. ie: If I have application.js load in the header it doesn’t fire. If I load it after the footer it fires without any issue. Am I missing something fundamental?

    I’m using Rails 3 and these are my header script includes:

    Thanks,

    Alan.

  • alansp

    // Figured it out. I needed to use “ready” to make sure that the DOM was loaded

    jQuery(document).ready(function() {
    jQuery(‘#location_country_code’).change(function() {
    var country = ‘country=’ + $(‘select#address_country :selected’).text();
    jQuery.get(‘/addresses/update_state_select?’ + country, function(data){
    jQuery(“div#addressStates”).html( “” + data + “” );
    })
    return false;
    });
    });