#88 Dynamic Select Menus (revised)
Do you need to dynamically change one select menu based on the value of another? Here I show how to filter states/provinces by a selected country using grouped options and a dab of CoffeeScript.
- Download:
- source code
- mp4
- m4v
- webm
- ogv
You could also use
$('#person_state_id').parent().toggle(options)
Cool stuff, Ryan. I would change the end of your coffeescript a bit. It is more efficient to use chaining rather than select the #person_state_id element on back to back lines. Plus, I like that it is more concise. Like this:
Great video. I tried using this on my site but had no such luck.
I replaced Country and States to States and Cities.
I'm getting the error in my console "cities is not defined" any suggestions?
Thanks, Frank
One bug to watch out for is selecting a country and state, saving, and then changing it to a country that has no states. The old state will still persist on the object because the state select was emptied, so no value will be POSTed/PUT for state. You can either do .html($("<option>").attr('selected',true)) instead of empty() or you can have a hidden field with the same name, like Rails does for checkboxes.
Slightly OT question. Where did you get this comprehensive list of states/countries? I know many sources but they all require fair amount of work to import in rails db.
Thanks Ryan for this awesome railscast....worked like a charm in my webapp. Is it possible to order the states by name?
Try Carmen https://github.com/jim/carmen
Yes, you can create a method in Country model that sorts the states by country_name and states_name (execute a query sql), and later call it in your form.
Example: if your new method is called "states_by_country_name_and_state_name"
in the form
<%= f.grouped_collection_select :state_id, Country.order(:name), :states_by_country_name_and_state_name, :name, :id, :name, include_blank: true %>
Nice screencast Ryan. I have a question though. It seems like this method allows selection of Country only once. Since the State dropdown gets reduced to only the matched states after a Country selection, if you select a new Country that has states, you've now lost the original list of states, so the States dropdown comes up blank. This means if a user makes a mistake selecting their Country, they would need to reload the page in order to undo that mistake. Please correct me if I'm just implementing this wrong.
This is why we are saving them into a separate variable
How would you change/add to this script, if you wanted to get the respective cities of each state?
For the "grouped_collection_select", where is the ":states" parameter created? I use this one (both @sectors and @branches are filled in the controller):
<%= f.grouped_collection_select(:branch, @sectors, @branches, :sector, :id, :branch, include_blank: true) %>
But receive an error:
(eval):1: syntax error, unexpected $end group.#<ActiveRecord::Relation
Already found it, I just forgot to add "has_many :branches" to the sector model. Really stupid...
Anyone running into CSV import issues, please modify the seed.rb file as follows:
require 'csv'
puts "Importing countries..."
CSV.foreach(Rails.root.join("countries.csv"), headers: true, encoding: "ISO8859-1") do |row|
Country.create! do |country|
country.id = row[0]
country.name = row[1]
end
end
puts "Importing states..."
CSV.foreach(Rails.root.join("states.csv"), headers: true, encoding: "ISO8859-1") do |row|
State.create! do |state|
state.name = row[0]
state.country_id = row[2]
end
end
This fixes encoding issue, thanks!
But there is a problem. When try to record the data to db. There are lots of datas in ther e because of numbers of country and state field. For example you write name Bob, choose USA, and state NewYork. Datas are created many times Bob USA NewYork. Why?
I had a problem. When I changed the "country" dropdown sometimes it selected the wrong values for the "province" dropdown. Solved it like this:
Hey man, thank you, I had same problem.
When I save the data and want to edit them again, only the first select is shown.
The second select is on "style="display: none" regardless the right values is "option selected="selected".
Any suggestions?
Thanks ahead!
does this work for formtastic??? i can seem to make it work please help ..
I used this for an asset management app I'm writing.
Instead of countries/states, I use Makes/Models (which belong to an asset).
Anyway, if you are loading a record that has a make/model saved (or a country/state) then the model (state) will be hidden and you will have to change the make (country) to re-enable it. Then you will have to know it's saved value. So I changed it to only hide the model (state) if there is none saved.
I also used parent().parent() because I am using with SimpleForm
I like what you did here. This was bothering me too
The only problem is that when you show the second select, it shows all the options and not only the one it was supposed to.
I was able to "fix" the problem when editing an existing product and not having the second select or having it with all the options. It's looking pretty nasty but it works.
Does anyone how what I would do if I have multiple dynamic dropdown in the same form. Say I am allowing someone to pick country state for 3 people at the same time. Is there a way to scope the javascript to look within each person div? Otherwise, the selector would interfere with each other and nothing would work.
Did you ever find the answer for this Ben?
joining Ben & Sergioo - tried a bit with a for ... in loop on the parent element, but didn't got it yet.
Hi guys,
I'm a newbie so in my case my webpage is:
< label for="table_basket" >Basket</ label >
< select id="table_basket" name="table[basket]" >< option value="" >Please
select< /option >
< option value="1" >Awesome< /option >
< option value="2" >Normal< /option >< /select >
< label for="table_apple" >Apple< /label >
< select id="table_apple" name="table[apple]" >< option value="" >Please
select</ option >
< option value="1" >Green< /option >
< option value="1" >Red< /option >
< option value="2" >Yellow< /option >
< option value="2" >White< /option >< /select >
< input name="commit" type="submit" value="Create Table" />
in the app/views/apple/index.html.erb I have
<%= form_for @table do |f| %>
<%= f.label :basket %>
<%= f.collection_select :basket, @basket, :id, :name, {:prompt => true} %>
<%= f.label :comune %>
<%= f.collection_select :apple, @apple, :basket_id, :name, {:prompt => true} %>
<%= f.submit %>
<% end %>
And I change the apple.js.coffe file in this
jQuery ->
apple = $('#table_apple').html()
$('#table_basket').change ->
basket = $('#table_basket :selected').text()
options = $(apple).filter("[label=#{basket}]").html()
if options
$('#table_apple').html(options)
else
$('#table_apple').empty()
but it doesn't work I think the problem it's here in this line
options = $(apple).filter("[label=#{basket}]").html()
options doesn't get the 'value' of the option attribute in the select list menu
Is it a simple syntax error?
thank you in advance,
A
I followed this tutorial but am getting JQuery not defined in firebug. I looked at the application.js file and saw that the function was generated correctly. My application layout has javascript_include_tag before anything else is yielded. The funny thing is I have another Jquery function specified for another controller that's working perfectly fine. What am I doing wrong here?
Did you ever get an answer to this? I have the same situation.
Thanks.
Hello guys,
do you know how to do it with one drop down menu and hidden check boxes? When you click on "Yes" in drop down menu, it will display check boxes underneath but if you click on "never" it will not display anything underneath. Thank you
I could not get this script to work (new to javascript). I am using model User instead of Person. I copied the script as it is on this page and changed all 'person' to 'user'. I put the script is users.js.coffee in my assets/javascript folder. It seems to ignore the script. When I select a state it shows all the countries and related states instead of only displaying the states for the selected country.
I have my fields in a partial that is used both in new and edit.
Again I am new to scripting. I believe that all I have to do is to add the script and it should work. I do this when I want to add functions using Bootstrap. Any help would be appreciated.
I applied this concept to a has_many_through association. When I EDIT a page, the 'Country' is already selected but the 'State' list isn't filtered. I have to click to another 'Country' for the filter of the 'State' list to take effect.
Anyone know how to have the filtering kick-in upon page load?
Have you managed to solve this problem Faraz? I'm having the same issue..
Use #cbmeeks code above
Hey Guys i am a newbie to this...Based on this railcast i tried to do the following. Failed. Can anyone please help?
This is my View
`<%= f.association :product, :collection => Product.in_stock %>
<%= f.grouped_collection_select :batch_no, Product.in_stock, :store_opening_stocks, :title, :batch_no, :batch_no, :prompt => "Select Batch"%>`
This is Jquery.
jQuery(document).ready(function(){
var child = jQuery('.batch').html();
jQuery('.prod').change(function() {
var parent = jQuery('.prod :selected').text();
var escaped_parent = parent.replace(/([ #;&,.+*~\':"!^$[]()=>|\/@])/g, '\$1')
var options = jQuery(child).filter("optgroup[label='#{escaped_parent}']").html()
if (options) {
jQuery('.batch').html(options);
return jQuery('.batch').parent().show();
} else {
jQuery('.batch').empty();
}
});
});
Really need help been stuck for a week now. Any Help would be helpful.
You may get this error in Rails 3.2
N+1 Query detected
Country => [:states]
Add to your finder: :include => [:states]
N+1 Query method call stack
Add includes to Country as below
This isn't working for me in Internet Explorer 9...Changing the first select menu doesn't do anything. The second select menu remains hidden.
Chrome on Windows, and Safari, Firefox, and Chrome on OS X work fine. Any gotchas that you've found?
And the html...
It appears that this was an issue with
console.log
, which I now realize wasn't intended to be in the final version of the code. When IE's "F12 Developer Tools" feature was enabled, the dynamic select menus worked as intended. I removed both instances ofconsole.log
from the coffeescript file and the issue is gone, with and without F12 Developer Tools enabled. Gotcha on me.More on this here: http://stackoverflow.com/questions/11346091/i-e-craches-my-js-script-at-first-then-i-press-f12-and-it-works-beaultifully?lq=1
Great screencast. Will the grouped_collection_select work for a many to many relationship with multi-select? Any examples? Thank you.
What an awesome screencast. This also works with HABTM models as well! I wasn't expecting it to, but it worked :) Thank you!
I love Ryan's screencasts. I can't seem to get this working for me, however. The grouped_collection_select works, but my app seems to be ignoring the js.coffee file. I have the include tags in my application.html header, but still, no magic is happening. Does anyone know what I'm doing wrong?
invoices.js.coffee :
jQuery ->
$('#invoice_facility_id').parent().hide()
facilities = $('#invoice_facility_id').html()
console.log(facilities)
$('#invoice_division_id').change ->
division = $('#invoice_division_id :selected').text()
options = $(facilitys).filter("optgroup[label=#{division}]").html()
console.log(options)
if options
$('#invoice_facility_id').html(options)
$('#invoice_facility_id').parent().show()
else
$('#invoice_facility_id').empty()
$('#invoice_facility_id').parent().hide()
_form.html.erb :
<label>Division:<font color="red">*</font></b></label>
<%= f.collection_select :division_id, Division.order(:division), :id, :division, :include_blank => true %>
<label>Facility:</label>
<%= f.grouped_collection_select :facility_id, Division.order(:division), :facilities, :division, :id, :facility, :include_blank => true %>
What changes are needed so that you can select the country and then select the city, instead of the state/region
This is how to support the edit form. Notice the trigger method.
Description: Execute all handlers and behaviors attached to the matched elements for the given event type.
didn't work for me. I have a select_tag which is not part of the form. I guess that is the problem
+1 and thanks for this- (i know this is old, but think this fix is important and deserves a little more amplification)....
this version has the quotation fix for two word menu entries and initializes properly with the trigger "change"
How can I filter the parent data by id and not by text?, I have a model that has 2 records with the same name, and when I select the parent it selects the other one with the same name.
Great screencast that I just revisited.
I'm using it with Twitter Bootstrap and Simple_Form. With this in play the parent().hide() function hides the dropdown box, but does not hide the label...
Now I can get round this by created a div around both and hiding that, but that seems to be a bit cumbersome. Looking at the page source I don't see any other obvious divs to use.
Any thoughts?
With this example, if we select United States or United kingdom it won't work. reason while filter state it is not able filter the value which have space. kindly someone help to get the values
I had the same error. I am using company names instead of countries. All of my companies have a space in the name. Was wondering if you possibly found a fix.
There's a problem with the escaping in firefox 10. Apparently it will escape the escaped_country variable again:
And it will break the filter...
Any idea what browser we actually need to escape first? I've tested with IE8 and chrome and they also both work with \' in the country name without escaping.
Wondering how could this be implemented using ActiveAdmin or Rails Admin.
me too... any ideas?
This is a great railscast and I'm hoping to use this with States/Cities.
I'm still a bit new to rails and I don't understand the associations between the states and cities.
Say for instance I select the state of TX. I'd want only Houston, Austin, San Antonio, etc to show up. If I have a State and City model, how would I tie only those cities to the state.
I figured I could do
City.rb
belongs_to :state
State.rb
has_many :cities
And then put a state_id in the City model for the relationship.
But that would be a lot of manual work to get that done. Is there any way around this or a seed file that exists?
Maybe this can help those like Michael Elfassy, SMnetserv, pbaileyjr12 who have difficulty with double named countries.
On my app, everything worked fine until I accidentally 'bundle update' the gems.
After the bundle update, the double name countries (United States and United Kingdom) would not trigger and display the:
grouped_collection_select :state_id, Country.approved.includes(:states)
sidenote: bullet says to include states in the query.
I hope this helps:
I'm wondering if dynamic selects could work also when the model structure is similar to episode #196, with some fixes.
For example model like this (similar to surveysays):
table "countries"
t.string "name"
table "states"
t.integer "country_id"
t.string "name"
table "people"
t.integer "state_id"
t.string "name"
country has_many states
state belongs_to country
state has_many peoples
people belongs_to state
In this case the _form file in people view should contain a
collection_select
for countries which do not affect the database, but it is only used to populate thecollection_select
of states.This should be very useful!
Hi, I am trying to use this solution in my application but it's not working for me. I am using Mongoid so that is only difference from this example. Here is my code:
class Country
include Mongoid::Document
field :name, type: String
field :abbreviation, type: String
has_many :states
has_many :equipment
end
class State
include Mongoid::Document
field :name, type: String
field :abbreviation, type: String
belongs_to :country
has_many :equipment
end
class Equipment
include Mongoid::Document
include Mongoid::Timestamps
include Mongoid::Paperclip
field :description
field :modelNumber
field :title
field :categoryId
field :subCategoryId
field :city
field :askingPrice
belongs_to :country
belongs_to :state
has_mongoid_attached_file :photo
embeds_many :questions do
def find_by_id(question_id)
where(question_id: question_id).first
end
end
end
<%= form_for(@equipment) do |f| %>
<%= f.label :country_id, "Country" %>
<%= f.collection_select :country_id, Country.order_by([:name, :asc]), :id, :name, include_blank: true %>
<%= f.label :state_id, "State or Province" %>
<%= f.grouped_collection_select :state_id, Country.order_by([:name, :asc]), :states, :name, :id, :name, include_blank: true %>
<% end %>
My issue is the :states appears to be empty. I know this because the html output is:
All the option values that should be within the option groups are not there.
I would appreciate any assistance anyone can give. Sorry for the lengthiness but i wanted to give all the details.
Cheers,
Rich
This might be a very stupid question but unless I'm mistaken this approach requires pre-loading all the values for the dynamic select onto the client... I have too many for this and instead want to hit the server once for the relevant dynamic dropdown data once the primary selection has been made via AJAX.
Is this feasible?
+1
I have the same issue. This SO question might help. However I haven't been able to get it to work myself.
How do you add a :selected to the grouped_collection_select ???
Hi,
I need to show Country and State on a form with values coming from two separate table with association set. I don't want to hide State drop-down instead populate it with values based on Country ID 1 which is populated in country drop-down by default. Presently when I load the form, State drop-down shows all the States grouped under Country. On Country change, State filters the values and populates correctly. Issue is on form load when Jquery Country change is not triggered. How do I achieve it?
Also, based on these selections, I want to show a nested model form - lets Users from particular city based on State selection.
Any guidance in the direction will also be highly appreciated.
Regards,
Gorav
Where is the data in states.csv sourced from?
Hey Wayne,
The "states.csv" and "countries.csv" data files are available in the github hosted "Source Code" under the "Show Notes" header:
https://github.com/railscasts/088-dynamic-select-menus-revised
By using the "db/seeds.rb" file you can migrate the data into any rails app.
bundle exec rake db:seeds
I hope this helps for future use.
Where do I actually get the data he uses for countries and states, and why not, cites??
Hey Jorge,
You should be able to include cities if you had a "cities.csv" which matched the states structure.
To answer the other part of your question:
The "states.csv" and "countries.csv" data files are available in the github hosted "Source Code" under the "Show Notes" header:
https://github.com/railscasts/088-dynamic-select-menus-revised
By using the "db/seeds.rb" file you can migrate the data into any rails app.
bundle exec rake db:seeds
I hope this helps for future use.
The coffeescript in the episode didnt work for me (I was trying to have the
grouped_collection_select
in a nested form, not sure if that was part of the issue)But in any case, this did work, hopefully it helps someone: (using different id tags as the ones in my project)
Note the main difference being how I called the
filter
actionIs there a way to filter the options, but still keep a blank option?
Say I have Publisher, Series and Game selects. But if there is only one Series, then the .change -> will never get triggered for Game.
I have implemented this using the following:
jQuery ->
facilities = $('#call_transfer_from_id').html()
$('#call_region_id').change ->
region = $('#call_region_id :selected').text()
options = $(facilities).filter("optgroup[label=#{region}]").html()
This works fine when changing the region. But when I need to edit the record and the form loads and I want to change the facility, all facilities are listed grouped by region whereas I'd like to have it constrain the facilities on page load. How can I combine .change and on load or document ready within this function so when I edit a record and the page loads I only see the facilities specific to that region?
How about I have city also.
Country, State, City.
I have Country has_many :states, State belongs to :country
State has_many :cities, City belongs_to :state
My grouped collection
<%= f.collection_select :country, Country.order(:name), :id, :name, include_blank: true %>
<%= f.grouped_collection_select :state, Country.order(:name), :states, :name, :id, :name, include_blank: true %>
<%= f.grouped_collection_select :city, State.order(:name), :cities, :name, :id, :name, include_blank: true %>
My Javascript is
jQuery(function() {
$j = jQuery.noConflict();
var states;
var cities;
$j('#use_test_state_id').parent().hide();
$j('#use_test_city_id').parent().hide();
states = $j('#use_test_state').html();
cities = $j('#use_test_city').html();
return $j('#use_test_country').change(function() {
var country, escaped_country, options;
country = $j('#use_test_country :selected').text();
escaped_country = country.replace(/([ #;&,.+*~\':"!^$[]()=>|\/@])/g, '\$1');
options = $j(states).filter("optgroup[label='" + escaped_country + "']").html();
if (options) {
$j('#use_test_state').html(options);
return $j('#use_test_state_id').parent().show();
} else {
$j('#use_test_state').empty();
return $j('#use_test_state_id').parent().hide();
}
var state, escaped_state, state_options;
state = $j('#use_test_state :selected').text();
console.log(state);
escaped_state = state.replace(/([ #;&,.+*~\':"!^$[]()=>|\/@])/g, '\$1');
state_options = $j(cities).filter("optgroup[label='" + escaped_state + "']").html();
if (state_options) {
$j('#use_test_city').html(state_options);
return $j('#use_test_city_id').parent().show();
} else {
$j('#use_test_city').empty();
return $j('#use_test_city_id').parent().hide();
}
});
});
but city drop down not showing .
can you please suggest what i missing ?
any solution? I'm looking to implement the same thing
I am also looking to do the same thing. How can you link three dropdown that are dynamic?
Does not work with rails 4.2 for me.
I've setup the db and inserted the sample data but the second select field does not appear after choosing a country.
gem for dynamic country state select https://rubygems.org/gems/country_state_select/versions/2.0.0
If you´re using this with choosen-select, remember to add the trigger method:
if options
$('#person_state_id').html(options)
$('#person_state_id').parent().show()
$("#person_state_id").trigger("chosen:updated");
I guess that this approach is very bad like Backend-development
cuz its calls all the cities from the database and that is not good for the site performance !
plz if some one is not agree write a replay!
best solution is using ajax that remote trigger the controller method that find the cities of the chosen county and push it to
city select tag by js file "similar to 88"
This episode has been updated to Rails 5 as a blog post Dynamic Select Menus in Rails 5
Hey Bala
Suppose each person has the following attributes: country, state and name.
Do you know how one can create 10 people, all from the same Country and State, in the same form................at the moment using your solution and Ryan Bates' i would have to Reselect the same country and state 10 times. The only thing that is different between all 10 people are their names.
Any advice much appreciated.
rgds
Ben
In this part of the code
var states = new Array();
<% for state in @states %>
states.push(new Array(<%= state.country_id %>, '<%=h state.name %>', <%= state.id %>));
<% end %>
Exactly in the for, I am having this error: undefined method `each' for nil:NilClass
Does anybody have a java script implementation of this?
Does anybody have a java script implementation of this?