Skip to content

Handles the typical country-province-city dependent select problem

License

Notifications You must be signed in to change notification settings

splendeo/dependent_select

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

20 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

This package extends rails with some helpers that allow “selects that update dynamically depending on other fields”

Demo application can be found in github.com/splendeo/dependent_select

Mailing list on groups.google.com/group/dependent_select/

On your layout:

<%= javascript_include_tag :defaults %>
<%= dependent_select_includes %>

On your new/edit views:

<% form_for :store do |f| %>
  Country:<br/>
  <% f.collection_select :country_id, @countries, :id, :name, :include_blanks => true %> <br />
  Province:<br/>
  <% f.dependent_collection_select :province_id, @provinces, :id, :name, :country_id, :include_blanks => true %> <br />
  City:<br/>
  <% f.dependent_collection_select :city_id, @cities, :id, :name, :province_id, :include_blanks => true %> <br />
<% end %>

In order for this to work properly, the Store model must have methods for country_id and store_id. The best way I’ve found for implementing this is by using delegate. So the model would be:

class Store < ActiveRecord::Base
  belongs_to :city, :include => [{:province => :country}] #useful to preload these
  delegate :country, :country_id, :country_id=, :to =>:city
  delegate :province, :province_id, :province_id=, :to =>:city
end

Notice that I’ve delegated the country to the city - so the city should probably have another delegate line:

class City < ActiveRecord::Base
  belongs_to :province, :include => [:country] #again, useful but not needed
  delegate :country, :country_id, :country_id=, :to =>:province
end

Finally, the controller might look like this:

class StoresController < ApplicationController
  before_filter :fill_selects :only => [:new, :edit, :update, :create]

  {...} # Standard scaffold-generated methods

  protected
  def fill_selects
    @countries = Country.find(:all, :order => 'name ASC')
    @provinces = Province.find(:all, :order => 'name ASC') # all provinces for all countries
    @cities = City.find(:all, :order => 'name ASC') # all cities for all provinces
  end
end

This will generate a regular collection_select for the country and a dependent_collection_select for province. The later will be a regular collection_select followed by a js +<script>+ tag that:

  • Will create an array with all the provinces. (+var array=[province1, province2…];+)

  • Will place a listeners on the country_id select in order to update the provinces select if the countries select is modified

  • Fill up the provinces select with appropiate values.

*Note that this will not work if you haven’t followed the installation procedure - see below*

There’s a more complex example at the end of this document.

Copy this on config/environment.erb, inside the gems section

config.gem "dependent_select"

Then execute

rake gems:install

The first time you initialize your server after this (presumably with script/server) the necesary javascript files and css will be copied to the public/ directory.

I actually haven’t tried this, sorry I don’t know how to do it.

Several steps are needed:

  • It is recommended that you uninstall the gem before installing a new version.

  • You must remove this file: public/javascripts/dependent_select/dependent_select.js

  • And then install the new version

In other words:

sudo gem uninstall dependent_select
rm public/javascripts/dependent_select/dependent_select.js
rake gems:install

I haven’t looked into that yet.

No AJAX for now, sorry. Just plain old javascript.

However, it might interest you that you’ll be generating this:

<script>
  var whatever = [['opt1',1,1],['opt2',2,1],['opt3',3,1]...];
</script>

Instead of this :

<option value='1'>opt1</option>
<option value='2'>opt2</option>
<option value='3'>opt3</option>

In our tests, generating information for 8000 cities took arond 20k - the size of a small image.

Make the test and then decide. It will not take you more than 10 minutes.

On this case we have an employee model with 2 relationships with cities. So the employee model might look like the one below. Notice that the delegates get a bit more complicated.

class Employee < ActiveRecord::Base

  belongs_to :home_city, :class_name => "City", :include => [{:province => :country}]
  delegate :country, :country_id, :country_id=, :to =>:home_city,
    :allow_nil => true, :prefix => :home
  delegate :province, :province_id, :province_id=, :to =>:home_city,
    :allow_nil => true, :prefix => :home

  belongs_to :work_city, :class_name => "City", :include => [{:province => :country}]
  delegate :country, :country_id, :country_id=, :to =>:work_city,
    :allow_nil => true, :prefix => :work
  delegate :province, :province_id, :province_id=, :to =>:work_city,
    :allow_nil => true, :prefix => :home

end

On your layout:

<%= javascript_include_tag :defaults %>
<%= dependent_select_includes %>

On your new/edit views, the “filter” for provinces isn’t :country_id any more, but :home_country_id or :work_country_id. The same happens with the cities and the provinces. You have to tell the selects where to find the right filter fields, using the filter_field option.

<% form_for :employee do |f| %>
  Home Country:<br/>
  <% f.collection_select :home_country_id, @countries, :id, :name, :include_blanks => true %> <br />
  Home Province:<br/>
  <% f.dependent_collection_select :home_province_id, @provinces, :id, :name, :country_id, 
    :filter_field => :home_country_id,
    :include_blanks => true %> <br />
  Home City:<br/>
  <% f.dependent_collection_select :home_city_id, @cities, :id, :name, :city_id, 
    :filter_field => :home_province_id,
    :include_blanks => true %> <br />
  Work Country:<br/>
  <% f.collection_select :work_country_id, @countries, :id, :name, :include_blanks => true %> <br />
  Work Province:<br/>
  <% f.dependent_collection_select :work_province_id, @provinces, :id, :name, :country_id, 
    :filter_field => :work_country_id,
    :include_blanks => true %> <br />
  Work City:<br/>
  <% f.dependent_collection_select :work_city_id, @cities, :id, :name, :city_id, 
    :filter_field => :work_province_id,
    :include_blanks => true %> <br />
<% end %>

On your controller:

class EmployeesController < ApplicationController
  before_filter :fill_selects :only => [:new, :edit, :update, :create]

  {...} # Standard scaffold-generated methods

  protected
  def fill_selects
    @countries = Country.find(:all, :order => 'name ASC')
    @provinces = Province.find(:all, :order => 'name ASC') # all provinces for all countries
    @cities = City.find(:all, :order => 'name ASC') # all cities for all provinces
  end
end

About

Handles the typical country-province-city dependent select problem

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published