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/.
Perhaps we could work it through on here and then put together a
definitive tutorial?
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
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
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?
Hoorah! That worked great!
Is it possible to have the users country and city already selected when
the form is loaded?
<%= 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
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!)
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
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>
@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 :-(
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?
<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
Thanks!!
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!