Module: DependentSelect::FormHelpers
- Defined in:
- lib/dependent_select/form_helpers.rb
Overview
Various helpers available for use in your view
Instance Method Summary collapse
-
#dependent_collection_select(object_name, method, collection, value_method, text_method, filter_method, options = {}, html_options = {}) ⇒ Object
Similar to collection_select form helper, but adds a “filter_method” parameter the generated code includes a javascript observer that modifies the select using the value of a form method.
-
#dependent_select(object_name, method, choices_with_filter, filter_method, options = {}, html_options = {}) ⇒ Object
Similar to
selectform helper, but generates javascript for filtering the results depending on the value on another field.
Instance Method Details
#dependent_collection_select(object_name, method, collection, value_method, text_method, filter_method, options = {}, html_options = {}) ⇒ Object
Similar to collection_select form helper, but adds a “filter_method” parameter the generated code includes a javascript observer that modifies the select using the value of a form method.
Parameters
+object_name+:: The name of an object being modified by this select. Example: +:employee+
+method+:: The name of a method of the object designated by "object_name" that
this select affects. Example: +:city_id+
+collection+:: The collection used for the +<>+ on the
select. Example: +@cities+
+value_method+:: The method that returns the value of each +<option>+ when
applied to each element on +collection+.
Example: +:id+ (for city.id)
+text_method+:: The method that returns the text of each +<option>+ when
applied to each to each element on +collection+.
Example: +:name+ (for city.name)
+filter_method:: The method being used for filtering. For example,
+:province_id+ will filter cities by province.
Important notice: this parameter also sets the DOM field
id that should be used for getting the filter value.
In other words, setting this to :province_id and the +object_name+ to
:employee will mean that somewhere on your form there will be a
field called "employee_province_id", used for filtering.
++:: (Optional) Usual for +collection_select+ (such as
+include_blank+) plus 3 new ones, detailed below.
++:: (Optional)The same html as +collection_select+.
They are appended to the html +select+ as attributes.
Options
In addition to all options for collection_select, several new options are available
:collapse_spaces
By default, blank spaces are collapsed on browsers when printing the select option texts For example, given the following html:
<select>
<option>This option should have lots of spaces on its text</option>
</select>
Most browsers will “collapse” the spaces, and present something like this instead:
"This option should have lots of spaces on its text"
Setting collapse_spaces to false will replace the blanks with the character. This is accomplised on the javascript function using code similar to the following
option_text.replace(/ /g, "\240"); // "\240" is the octal representation of charcode 160 (nbsp)
On the following example, a sale occurs on a store that belongs to a company. The store model has a method called name_for_selects that prints a two-column text - the first column has the company name on it, while the second has the store address. They are separated by a ‘|’ character, and padded with spaces when necesary (company name has 10 chars or less)
class Store < ActiveRecord::Model
belongs_to :company
has_many :sales
validates_presence_of :address
def name_for_selects
"#{company.name.ljust(10)} | #{address}"
end
end
Now on the edit/new sale view, we will need to use the :collapse_spaces option like this:
<%= dependent_collection_select(:sale, :store_id, @stores, :id, :name_for_selects, :company_id,
{:collapse_spaces => true}, {:class => 'monospaced'})
%>
It is recommended that you use this function in conjunction with a monospaced css style. The following rule should be available, for example on application.css:
.monospaced {
font-family: monospaced;
}
:filter_field
The javascript employed for updating the dependent select needs a field for getting the “filter value”. The default behaviour is calculating this field name by using the :object_name and :filter_value parameters. For example, on this case:
<%= dependent_collection_select(:sale, :province_id, @provinces, :id, :name, :country_id) %>
:object_name is :sale, and :filter_id is :country_id, so the javascript will look for a field called ‘sale_country_id’ on the html.
It is possible to override this default behaviour by specifying a :filter_field option. For example, in this case:
<%= dependent_collection_select(:sale, :province_id, @provinces, :id, :name,
:country_id, {:filter_field => :state_id})
%>
This will make the javascript to look for a field called ‘sale_state_id Notice that the chain ’sale_’ is still appended to the field name. It is possible to override this by using the :complete_filter_field option istead of this one.
The most common use of this property will be for dealing with multiple relationships between the same models. See the complex example below for details.
:complete_filter_field
Works the same way as :filter_field, except that it uses its value directly, instead of appending the :object_name at all. For example:
<%= dependent_collection_select(:sale, :province_id, @provinces, :id, :name,
:country_id, {:complete_filter_field => :the_province})
%>
This will make the javascript to look for a field called ‘the_province’ on the page, and use its value for filtering.
:array_name
dependent_select generates a javascript array with all the options available to the dependent select. By default, the name of that variable is automatically generated using the following formula:
js_array_name = "ds_#{dependent_field_id}_array"
This can be overriden by using the js_array_name option (its value will be used instead of the previous)
This is useful because, by default, dependant_select forms will keep track of generated arrays, and *will not* generate the same array twice. This is very useful for situations in which lots of dependent_selects have to be generated, with the same data. For example, a flight has a destination and origin city:
<%= dependent_collection_select( :flight, :origin_city_id, @cities, :id, :name, :province_id,
{ :filter_field => :origin_province_id, js_array_name => 'cities_array' }
%>
<%= dependent_collection_select( :flight, :destination_city_id, @cities, :id, :name, :province_id,
{ :filter_field => :destination_province_id, js_array_name => 'cities_array' }
%>
This example will generate the first javascript array and call it cities_array. Then the second call to dependent_select is done, the form will already know that the javascript for this script is generated, so it will not generate an array.
The :array_name option can also be used in the opposite way: to force the generation of an array. This should happen very rarely - two dependent selects generate the same object name and method but are not supposed to use the same list of values.
Examples
Example 1: A store on a City
In a form for creating a Store, the three selects used for Store, Province and City.
views/Stores/new and views/Stores/edit:
<p>
Country:
<%= collection_select :store, :country_id, @countries, :id, :name %>
</p><p>
Province:
<%= dependent_collection_select :store, :province_id, @provinces, :id, :name, :country_id %>
</p><p>
City:
<%= dependent_collection_select :store, :city_id, @cities, :id, :name, :province_id %>
</p>
Notes:
* The first helper is rail's regular +collection_select+, since countries don't
"depend" on anything on this example.
* You only need a +city_id+ on the +stores+ table (+belongs_to :city+).
You need to define methods on your model for obtaining a +province_id+ and
+country_id+. One of the possible ways is using rails' +delegate+ keyword. See
example below (and note the +:include+ clause)
class Store < ActiveRecord::Base
belongs_to :city, :include => [{:province => :country}] #:include not necessary, but nice
delegate :country, :country_id, :country_id=, :to =>:city, :allow_nil => true
delegate :province, :province_id, :province_id=, :to =>:city, :allow_nil => true
end
This delegates the province_id and country_id methods to the :city object_name. So a City must be able to handle country-related stuff too. Again, using delegate, you can do:
class City < ActiveRecord::Base
belongs_to :province, :include => [:country]
delegate :country, :country_id, :country_id=, :to =>:province, :allow_nil => true
end
Note that I’ve also delegated province, province=, country and country= . This is so I’m able to do things like puts store.country.name. This is convenient but not needed.
Example 2: Using html_options
Imagine that you want your selects to be of an specific css class, for formatting. You can accomplish this by using the html_options parameter
<p>
Country:
<%= collection_select :store, :country_id, @countries, :id, :name,
{:include_blanks => true}, {:class=>'monospaced'}
%>
</p><p>
Province:
<%= dependent_collection_select :store, :province_id, @provinces, :id, :name,
:country_id, {:include_blanks => true}, {:class=>'brightYellow'}
%>
</p><p>
City:
<%= dependent_collection_select :store, :city_id, @cities, :id, :name,
:province_id, {:include_blanks => true}, {:class=>'navyBlue'}
%>
</p>
Notice the use of {} for the :options parameter. If we wanted to include html_options but not options, we would have had to leave empty brackets.
Example 3: Multiple relations and :filter_field
Imagine that you want your stores to have two cities instead of one; One for importing and another one for exporting. Let’s call them :import_city and :export_city:
Models/Store.rb:
class Store < ActiveRecord::Base
belongs_to :import_city, :class_name => "City", :include => [{:province => :country}]
delegate :country, :country_id, :country_id=, :to =>:import_city,
:allow_nil => true, :prefix => :import
delegate :province, :province_id, :province_id=, :to =>:import_city,
:allow_nil => true, :prefix => :import
belongs_to :export_city, :class_name => "City", :include => [{:province => :country}]
delegate :country, :country_id, :country_id=, :to =>:export_city,
:allow_nil => true, :prefix => :export
delegate :province, :province_id, :province_id=, :to =>:export_city,
:allow_nil => true, :prefix => :import
end
In this case, the store doesn’t have a :city_id, :country_id or :province_id. Instead, it has :export_city_id, :export_country_id and :export_province_id, and the same for import.
We’ll have to use the :filter_field option in order to use the right field for updating the selects.
views/Stores/new and views/Stores/edit:
<p>
Import Country:
<%= collection_select :store, :import_country_id, @countries, :id, :name %>
</p><p>
Import Province:
<%= dependent_collection_select :store, :import_province_id, @provinces, :id, :name,
:country_id, :filter_field => :import_country_id, :array_name => 'provinces_array'
%>
</p><p>
Import City:
<%= dependent_collection_select :store, :import_city_id, @cities, :id, :name,
:province_id, :filter_field => :import_province_id, :array_name => 'cities_array'
%>
</p><p>
Export Country:
<%= collection_collection_select :store, :export_country_id, @countries, :id, :name %>
</p><p>
Export Province:
<%= dependent_collection_select :store, :export_province_id, @provinces, :id, :name,
:country_id, :filter_field => :export_country_id, :array_name => 'provinces_array'
%>
</p><p>
Export City:
<%= dependent_select :store, :export_city_id, @cities, :id, :name,
:province_id, :filter_field => :export_province_id, :array_name => 'cities_array'
%>
</p>
Notice the use of :array_name. This is optional, but greatly reduces the amount of js code generated
267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 |
# File 'lib/dependent_select/form_helpers.rb', line 267 def dependent_collection_select(object_name, method, collection, value_method, text_method, filter_method, = {}, = {} ) object, , = (object_name, ) initial_collection = dependent_select_initial_collection(object, method, collection, value_method) tag, dependent_field_id = dependent_collection_select_build_tag( object_name, object, method, initial_collection, value_method, text_method, , ) script = dependent_select_js_for_collection(object_name, object, method, collection, value_method, text_method, filter_method, , , , dependent_field_id) return "#{tag}\n#{script}" end |
#dependent_select(object_name, method, choices_with_filter, filter_method, options = {}, html_options = {}) ⇒ Object
Similar to select form helper, but generates javascript for filtering the results depending on the value on another field. Consider using dependent_collection_select instead of this one, it will probably help you more. And I’ll be updating that one more often.
Parameters
+object_name+:: The name of the object being modified by this select.
Example: +:employee+
+method+:: The name of the method on the object cadded "object_name" that this
will affect. Example: +:city_id+
+choices_with_filter+:: The structure is +[[opt1_txt, opt1_value, opt1_filter],
[opt2_txt, opt2_value, opt2_filter] ... ]+.
+filter_method:: The method being used for filtering. For example,
+:province_id+ will filter cities by province.
Important notice: this parameter also sets the DOM field
id that should be used for getting the filter value.
In other words, setting this to :province_id and the +object_name+ to
:employee will mean that somewhere on your form there will be a
field called "employee_province_id", used for filtering.
++ and ++:: See +dependent_collection_select+.
Examples
Example 1: Types of animals
Create an animal on a children-oriented app, where groups and subgroups are predefined constants.
models/animal.rb
class Animal < ActiveRecord::Base
GROUPS = [['Invertebrates', 1], ['Vertebrates', 2]]
SUBGROUPS = [
['Protozoa', 1, 1], ['Echinoderms',2,1], ['Annelids',3,1], ['Mollusks',4,1],
['Arthropods',5,1], ['Crustaceans',6,1], ['Arachnids',7,1], ['Insects',8,1],
['Fish',9,2], ['Amphibians',10,2], ['Reptiles',11,2], ['Birds',12,2],
['Mammals',13,2], ['Marsupials',14,2], ['Primates',15,2], ['Rodents',16,2],
['Cetaceans',17,2], ['Seals, Seal Lions and Walrus',18,2]
]
end
new/edit animal html.erb
<p>
Group: <%= select :animal, :group_id, Animal::GROUPS %>
</p><p>
Subgroup: <%= select :animal, :subgroup_id, :group, Animal::SUBGROUPS %>
</p>
333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 |
# File 'lib/dependent_select/form_helpers.rb', line 333 def dependent_select(object_name, method, choices_with_filter, filter_method, = {}, = {}) object, , = (object_name, ) initial_choices = dependent_select_initial_choices(object, method, choices_with_filter) tag, dependent_field_id = dependent_select_build_tag( object_name, object, method, initial_collection, value_method, text_method, , ) script = dependent_select_js(object_name, method, choices_with_filter, filter_method, , , ) return "#{tag}\n#{script}" end |