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 »
RSS feed for comments on this post. TrackBack URI
Good stuff
tnx
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
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
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
Neal,
Found a possible way. I can make a view called edit.rjs, containing:
page.replace_html ‘divWhereMyFormIs’, :partial => ‘form’,
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
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:
bject => @user
page.replace_html ‘formpartial’, :partial => ‘form’,
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
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!
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’
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!
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’
Does this work in IE?
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…
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 .
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.
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.