How to dynamically update form elements in Rails using Ajax

Here’s the situation: You have two SELECT elements on a form. When the user chooses an item out of the first SELECT element, the contents of the second SELECT element need to change and show different values based on the initial selection.

This used to take quite a bit of JavaScript wizardry. But it’s pretty straightforward in Rails using a bit of Ajax. Here’s how:

First, make sure you’re including the Ajax libraries (Prototype, script.aculo.us, etc.) in your layout like so:

<html>
<head>
       <%= javascript_include_tag :defaults %>
</head>
<body>
...

Next, create a form with your initial SELECT, and empty second SELECT, and set up field observer. Here’s a really simple example:

<form>

<select id="states" name="states">
    <option value="0"></option>
    <option value="1">Colorado</option>
    <option value="2">Illinois</option>
    <option value="3">Wyoming</option>
</select>

<%= observe_field "states", :update => "cities",
:with => "city_id", :url => { :controller => "test",
:action => "get_cities" } %>

<br />

<select id="cities" name="cities">
    <option></option>
</select>

</form>

Finally, you’ll need to set up a method to handle the call from the observer and a corresponding view page to generate the HTML content that will replace everything betwen the SELECT tags in the second SELECT. Here’s an example "get_cities" controller:

def get_cities
    @cities = City.find_by_state(:all)
end

And an example "get_cities.rhtml" file:

<% for city in @cities %>
    <option value="<%= city.id %>"><%= city.name %></option>
<% end %>

And there you go. This much should dynamically update the "cities" SELECT element with new content every time the "onchange" event is fired on the initial "states" SELECT form element.

15 Comments »

  1. Good stuff
    tnx

    Comment by Scott — May 27, 2007 @ 11:54 pm
  2. Hi!
    I’m a newbie to RoR, so there may be a very simple solution to my problem. But every tutorial I read demonstrates how to update 1) one form field, or 2) several elements via its innerHTML property. But if I want to update several form elements?
    In my case, I have a listbox (say, with users) and a form with user details. When the listbox is clicked I want the form to be filled in, with no page reloading. But I cannot seem to find how to do that in the Rails paradigm (have done it myself with ASP/Javascript, but those days should be over…)

    Regards,
    Johan

    Comment by Johan Bryntesson — June 22, 2007 @ 12:06 am
  3. Johan:

    Great question! Honestly, I haven’t had much of a chance to check this out yet. Have you tried having multiple observers? Perhaps an observer for each field you’d like to update on change?

    I’ll try and check that out here soon and let you know if I can get it working.

    Best,

    Neal

    Comment by neal — June 22, 2007 @ 4:19 am
  4. Neal,
    Actually, an observer for each field (some 15) would probably clog things, since (I believe) each of them would cause a server roundtrip. What I’d like to see (and strongly suspect exist somewhere) is something that could respond to the observed change on a control with something like a hash of arbitrary size looking something like:
    {:name_of_form_control => ‘its new contents’, …}
    causing each control to receive the new value. All in one operation.
    Can’t really believe I am the first with this particular need…

    Thanks for your interest!
    /Johan

    Comment by Johan Bryntesson — June 22, 2007 @ 12:36 pm
  5. Neal,
    Found a possible way. I can make a view called edit.rjs, containing:

    page.replace_html ‘divWhereMyFormIs’, :partial => ‘form’, :o bject => @user

    If I surf directly to “localhost:3000/edit/3″ I can see that the constructed HTML is indeed sound. However, due to some strange default behavior, my app won’t do the actual replacing, unless I comment out the line

    @user = User.find(params[:id])

    which enables the above replace_html to fill the partial above with meaningful data…
    It seems I am so close to the solution, but the well-meaning Rails is stopping me…

    Best,
    Johan

    Comment by Johan Bryntesson — June 25, 2007 @ 4:10 am
  6. At last! Excuse my incessant ranting, but I hopa this will be of use to people:

    In my form I have a SELECT named user[list], and the line
    {:action => ‘updateform’}, :with => ‘id’ %>

    The trick is the :with => ‘id’, to tell the subsequent what to map the select’s value to.

    In the controller, the “updateform” action simply reads:
    @user = User.find(params[:id])

    And finally, the “updateform.rjs” consists of:
    page.replace_html ‘formpartial’, :partial => ‘form’, :o bject => @user

    Which automagically repaints the form (which itself is a partial) with the values for the @user object.

    This may be old hat for a lot of people, but I have been cruising the net for three days looking for this solution. So again: hope this helps someone!

    Best,
    Johan

    Comment by Johan Bryntesson — June 25, 2007 @ 9:00 am
  7. Sorry… the line in post #5 starting with {:action =>
    should have read:

    {:action => ‘updateform’}, :with => ‘id’ %>

    Neal, please feel free to edit this to distill out the useful parts. If any!

    Comment by Johan Bryntesson — June 25, 2007 @ 9:02 am
  8. Grrr!! It’s the form that’s editing whar I type!!!
    Again Neal, please edit…

    The line in the form, embedded in less-than + percent

    observe_field ‘user[list]’, :url => {:action => ‘updateform’}, :with => ‘id’

    Comment by Johan Bryntesson — June 25, 2007 @ 9:04 am
  9. Thanks for the walk through, saved me loads of time. And to add to it: one way to make this concept even shorter (albeit a bit more terse) is to use the options_from_collection_for_select in the “get_cities.rhtml” file. For the example above just replace the 3 lines with this one line:

    thanks again!

    Comment by matt schick — June 28, 2007 @ 3:39 pm
  10. ack, I can’t seem to get the damn line of code to post (probably should have read all the comments before posting). Here is the line of code minus the open close brackets, percent signs, and leading equal sign:

    options_from_collection_for_select @states, ‘id’, ‘name’

    Comment by matt schick — June 28, 2007 @ 3:43 pm
  11. Does this work in IE?

    Comment by Chris Ortman — July 11, 2007 @ 12:10 pm
  12. Hi. I posted a question on this to http://railsforum.com/viewtopic.php?pid=31045#p31045
    if you have any thoughts, that would be great…

    Comment by Matt Caza — July 12, 2007 @ 5:59 pm
  13. I’ve heard that IE doesn’t like to replace the contents of a , so the thing to do here is to wrap the in a span/div/whatever and replace the whole .

    Comment by Jake — August 30, 2007 @ 7:03 am
  14. LOL! Do-over:

    I’ve heard that IE doesn’t like to replace the <option> contents of a <select>, so the thing to do here is to wrap the <select> in a span/div/whatever and replace the whole <select> each time.

    Comment by Jake — August 30, 2007 @ 7:06 am
  15. This isn’t working for me…

    When I select an option from the first SELECT field, the second SELECT field just grays out. Any ideas?

    Thanks.

    Comment by Michael — September 17, 2007 @ 7:27 pm

RSS feed for comments on this post. TrackBack URI

Leave a comment

© 2007 neal enssle. all rights reserved, yo.