Observe_field Location select (help b4 I go mad)

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 :slight_smile:

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?

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

containing your Cities select.

hth,
Bill

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” %>


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

Paul C. wrote:

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” %>


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) %>

Hey.

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

I assume i need to include…

in my head?

Yes, absolutely. Hope it works for you…

Paul C. 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?

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) %>

-Paul

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

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

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

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

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

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!)

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

Country


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

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

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

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

Paul C. wrote:

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

Bugger!

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

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?

I’ve just got around to updating my code and it works a treat!

Thanks!!

It fails for me in IE as well. What I had to do to get this to work was
to wrap the tag for city in a

and tell “observe_field”
to update the div rather than the select. This means changing some
stuff. First off, change your view from:


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

to:

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

Then change the _city_options partial to:


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

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

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… :slight_smile: 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! :slight_smile:

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!