observe_field Location select (help b4 I go mad)

43 views
Skip to first unread message

James Kelly

unread,
Dec 22, 2006, 4:20:48 PM12/22/06
to rubyonra...@googlegroups.com
Hey all,

I've been trying for days, without much success to add a 'Select
Location' option to my site. I want to use observe_field so that a user
can select a Country, and then another list is populated with the
appropriate cities.

This must be something that is needed all the time, but I just can't
seem to find a decent tutorial (i'm a pretty new programmer!)

Here are my models...

lu_countries
id
country


lu_cities
id
city
country_id


Can anyone help me?

Thanks guys!

Ps. Happy Holidays from here in London :-)

--
Posted via http://www.ruby-forum.com/.

Scott Holland

unread,
Dec 23, 2006, 11:23:58 AM12/23/06
to rubyonra...@googlegroups.com
This is exactly what I've been working on, but I just can't seem to get
my head around it either.

Perhaps we could work it through on here and then put together a
definitive tutorial?

Bill Walton

unread,
Dec 23, 2006, 11:41:10 AM12/23/06
to rubyonra...@googlegroups.com
Hi James,

James Kelly wrote:
>
> I've been trying for days, without much success to add a 'Select Location'
> option to my site. I want to use observe_field so that a user
> can select a Country, and then another list is populated with the
> appropriate cities.

> This must be something that is needed all the time, but I just can't seem
> to find a decent tutorial (i'm a pretty new programmer!)

If you search the archives you'll find numerous discussions of this over the
last year.

In general, the easiest way to accomplish this is to set the observe_field
on your Country select so that it sends back the selected value using :with
=> whatever_you_want_the_param_to_be_named, then in the controller do a find
on your Cities using the Country passed back in that param, then in your rjs
template replace the content of the <div> containing your Cities select.

hth,
Bill


Paul Corcoran

unread,
Dec 23, 2006, 3:52:54 PM12/23/06
to Ruby on Rails: Talk
Based on your example, try this:

In your models (PS: I stripped the lu_ prefix on the class in my
example so I explicitly reference the proper table based on your
example):

class Country < ActiveRecord::Base
has_many :cities
def self.table_name() "lu_countries" end
end

class City < ActiveRecord::Base
belongs_to :country
def self.table_name() "lu_cities" end
end

In your controller, somewhere appropriate, populate @countries:

@countries = Country.find(:all)

Also, add a new method to your controller to respond to the
"observe_field" callback:

def country_changed
@cities = Country.find(params[:country_id]).cities
render :partial => 'layouts/city_options', :layout => false
end

In your view:

<select name="country_id" id="country_id">
<%= render(:partial => 'layouts/country_options', :layout =>
false) %>
<%= observe_field "country_id",
:url => {:controller => "YOUR_CONTROLLER", :action =>
"country_changed"},
:with => "country_id",
:update => "city_id" %>
</select>
<select name="city_id" id="city_id">
</select>

Create two partial views (I have them in the /layouts subdirectory):

_country_options.rhtml
<%= options_from_collection_for_select(@countries, :id, :country) %>

_city_options.rhtml
<%= options_from_collection_for_select(@cities, :id, :city) %>

The way I have it currently setup, the cities drop down will only be
populated when you select a country. But you could set the country to a
default for the user, populate the @cities variable as I'm doing in the
country_changed method, and then render the city_options partial in the
view.

-Paul

Scott Holland

unread,
Dec 24, 2006, 6:51:39 AM12/24/06
to rubyonra...@googlegroups.com

Hey.

Thanks for that. I tried it, but the second select list just won't
update.

I assume i need to include..

<script src="/javascripts/prototype.js" type="text/javascript"></script>

in my head?

Paul Corcoran

unread,
Dec 24, 2006, 9:12:54 AM12/24/06
to Ruby on Rails: Talk
Yes, absolutely. Hope it works for you...

Scott Holland

unread,
Dec 24, 2006, 2:28:03 PM12/24/06
to rubyonra...@googlegroups.com
Paul Corcoran wrote:
> Yes, absolutely. Hope it works for you...


Hoorah! That worked great!

Is it possible to have the users country and city already selected when
the form is loaded?

Paul Corcoran

unread,
Dec 24, 2006, 3:19:58 PM12/24/06
to Ruby on Rails: Talk
Yes. Go ahead and populate @countries as I showed you but also set some
variable to the id of the user's country like @user_country_id. Also,
populate the @cities variable as well based on the users country like
we were doing in the country_changed method. Then change
_country_options.rhtml as follows:

<%= options_from_collection_for_select(@countries, :id, :country,
selected_value = @user_country_id) %>

Or if you had a @user object that had the country_id you could code it
as "selected_value = @user.country_id". The important thing is that it
is the "id" of the country you need to specify in the selected_value
parameter. Do something similar for the city.

Also, in my first example I wasn't populating the city drop down so you
would need to do that in your view now as well:

<select name="city_id" id="city_id">

<%= render(:partial => 'layouts/city_options', :layout =>
false) %>
</select>

-Paul

Scott Holland

unread,
Dec 24, 2006, 4:12:59 PM12/24/06
to rubyonra...@googlegroups.com
Hey Paul,

Thanks for such a quick response!

The "selected_value = @user_country_id" works great - when the page is
loaded, the users country is selected!

However, the city list remains blank, and I'm a little confused as to
what you are saying with regards to populating that.


Here is my code so far...


****ProfileController****
def edit
user = session[:user_id]
@profile = Profile.find(:first, :conditions => ["user_id = ?",
user])
@countries = Country.find(:all)
@user_country_id = @profile.country_id
end

def country_changed
@cities = Country.find(params[:country_id]).cities

end

****Partials****

_country_options.rhtml


<%= options_from_collection_for_select(@countries, :id, :country,
selected_value = @user_country_id) %>

_city_options.rhtml
<%= options_from_collection_for_select(@cities, :id, :city) %>


****View****

<select name="profile[country_id]" id="profile_country_id">
<%= render(:partial => 'country_options', :layout => false) %>
</select>

<%= observe_field "profile_country_id", :url => {:controller =>
"profile", :action => "country_changed"}, :with => "country_id", :update
=> "profile_city_id" %>

<select name="profile[city_id]" id="profile_city_id">
<%= render(:partial => 'city_options', :layout => false) %>
</select>


The error message I get is..

You have a nil object when you didn't expect it!
You might have expected an instance of Array.
The error occured while evaluating nil.inject

Any ideas?

Thanks again (really appreciate it!)

Paul Corcoran

unread,
Dec 25, 2006, 12:06:55 AM12/25/06
to Ruby on Rails: Talk
Hi Scott,

To populate the cities list you need to add a line (2nd to the last
line) to your "edit" method as follows:

def edit
user = session[:user_id]
@profile = Profile.find(:first, :conditions => ["user_id = ?",
user])
@countries = Country.find(:all)
@user_country_id = @profile.country_id

@cities = Country.find(@profile.country_id).cities
@user_city_id = @profile.city_id
end

Additionally, if you wanted to select the users city, you would need to
set a variable like @user_city_id (see last line) and add ",
selected_value = @user_city_id" to the _city_options.rhtml partial like
we did with the countries.

-Paul

Scott Holland

unread,
Dec 25, 2006, 7:33:39 AM12/25/06
to rubyonra...@googlegroups.com
Fantastic! That all works now. Just what I wanted - Thank you!

And for the others, here is my final code...

This code is for an "update my profile" page which is part of
the"profile" controller


****Head of page****

<%= javascript_include_tag "prototype" %>


****ProfileController****


def edit
user = session[:user_id]
@profile = Profile.find(:first, :conditions => ["user_id =

?",user])


@countries = Country.find(:all)
@user_country_id = @profile.country_id
@cities = Country.find(@profile.country_id).cities
@user_city_id = @profile.city_id
end

def country_changed


@cities = Country.find(params[:country_id]).cities

render(:partial => 'city_options', :layout => false)

end

****Partials****

_country_options.rhtml
<%= options_from_collection_for_select(@countries, :id, :country,

selected_value = @user_country_id ) %>


_city_options.rhtml
<%= options_from_collection_for_select(@cities, :id, :city,
selected_value = @user_city_id) %>


****View****

<p><label for="profile_country_id">Country</label><br/>

<select name="profile[country_id]" id="profile_country_id"

style="width: 200px">


<%= render(:partial => 'country_options', :layout => false) %>

</select>
<%= observe_field "profile_country_id", :url => {:controller =>
"profile", :action => "country_changed"}, :with => "country_id", :update

=> "profile_city_id", :frequency => 0.25 %>

<p><label for="profile_city_id">City</label><br/>
<select name="profile[city_id]" id="profile_city_id" style="width:
200px">


<%= render(:partial => 'city_options', :layout => false) %>
</select>

Paul Corcoran

unread,
Dec 25, 2006, 9:20:56 AM12/25/06
to Ruby on Rails: Talk
Thats great! Now that I see your Profile class, there is one last thing
you could do to simplify your code just a bit. Since you have the
country_id and city_id available in the @profile variable, there really
is no need to put them into the varaibles @user_country_id and
@user_city_id. We can simply retrieve that data from the @profile
variable when needed. So you could remove the following 2 lines from
the "edit" method:

@user_country_id = @profile.country_id
@user_city_id = @profile.city_id

And then replace @user_country_id with @profile.country_id in
_country_options.rhtml and @user_city_id with @profile.city_id in
_city_options.rhtml.

-Paul

Scott Holland

unread,
Dec 25, 2006, 1:50:15 PM12/25/06
to rubyonra...@googlegroups.com

Bugger!

Even though it works fine on FF and Safari, I've just tested it on IE 7
and it doesn't work :-(

When the page loads, the second box is loaded with the correct city
options, but when I change the country the city list goes blank.

Any ideas?

Paul Corcoran

unread,
Dec 25, 2006, 3:37:59 PM12/25/06
to Ruby on Rails: Talk
It fails for me in IE as well. What I had to do to get this to work was
to wrap the <select> tag for city in a <div> and tell "observe_field"
to update the div rather than the select. This means changing some
stuff. First off, change your view from:

<select name="profile[city_id]" id="profile_city_id" style="width:
200px">
<%= render(:partial => 'city_options', :layout => false) %>
</select>

to:
<div id="city_select_div">


<%= render(:partial => 'city_options', :layout => false) %>

</div>

Then change the _city_options partial to:

<select name="profile[city_id]" id="profile_city_id" style="width:
200px">

<%= options_from_collection_for_select(@cities, :id, :city,
selected_value = @user_city_id) %>

</select>

And finally, change the update parameter of "observe_field" from
:update => "profile_city_id" to :update => "city_select_div".

That should take care of it. I am begining to come to the realization
that ajax updates work better when the tag is wrapped in a div. Being a
block level statement, I think it forces the browser to "work harder"
when the DOM changes. I have yet to see someone make a blanket
recommendation to this effect but I am starting to lean that way.

-Paul

Scott Holland

unread,
Dec 27, 2006, 8:34:50 AM12/27/06
to rubyonra...@googlegroups.com
I've just got around to updating my code and it works a treat!

Thanks!!

Olie D.

unread,
Oct 26, 2007, 11:43:35 PM10/26/07
to rubyonra...@googlegroups.com
Just discovered this very useful thread and am curious (I'll go try it
in a bit but, when encountering problems, it's always nice to know if
it's SUPPOSED to work... :) if these things can be daisy chained.

That is, can I expect to be able to expand the above example to have

kingdom ->
phylum ->
class ->
order ->
species

(It's just an example, please ignore the "old" hierarchy and discussion
of The New Taxonomy! :)

where each popup triggers a callback to the next, which is pre-filled
with a default value (likely the 1st item on the list), so the last
popup is adjusted according to changes in any of the ones above it?

Will that work?

Thanks!

Reply all
Reply to author
Forward
0 new messages