2004-11-23 20:04:44 -05:00
require 'cgi'
require 'erb'
2007-01-28 02:16:55 -05:00
require 'action_view/helpers/form_helper'
2010-03-28 08:15:02 -04:00
require 'active_support/core_ext/object/blank'
2010-10-18 11:58:57 -04:00
require 'active_support/core_ext/string/output_safety'
2004-11-23 20:04:44 -05:00
module ActionView
2010-06-16 14:17:49 -04:00
# = Action View Form Option Helpers
2004-11-23 20:04:44 -05:00
module Helpers
2005-01-15 13:57:17 -05:00
# Provides a number of methods for turning different kinds of containers into a set of option tags.
2005-02-17 14:38:02 -05:00
# == Options
2009-02-13 19:47:22 -05:00
# The <tt>collection_select</tt>, <tt>select</tt> and <tt>time_zone_select</tt> methods take an <tt>options</tt> parameter, a hash:
2005-02-17 14:38:02 -05:00
#
2007-05-18 01:29:22 -04:00
# * <tt>:include_blank</tt> - set to true or a prompt string if the first option element of the select element is a blank. Useful if there is not a default value required for the select element.
#
# For example,
2005-04-19 06:59:55 -04:00
#
2005-02-17 14:38:02 -05:00
# select("post", "category", Post::CATEGORIES, {:include_blank => true})
#
2005-04-19 06:59:55 -04:00
# could become:
2005-02-17 14:38:02 -05:00
#
# <select name="post[category]">
# <option></option>
# <option>joke</option>
# <option>poem</option>
# </select>
2005-04-19 06:59:55 -04:00
#
2007-05-18 01:29:22 -04:00
# Another common case is a select tag for an <tt>belongs_to</tt>-associated object.
#
# Example with @post.person_id => 2:
#
2009-02-13 19:47:22 -05:00
# select("post", "person_id", Person.all.collect {|p| [ p.name, p.id ] }, {:include_blank => 'None'})
2007-05-18 01:29:22 -04:00
#
# could become:
#
# <select name="post[person_id]">
# <option value="">None</option>
# <option value="1">David</option>
# <option value="2" selected="selected">Sam</option>
# <option value="3">Tobias</option>
# </select>
#
2005-07-03 08:23:16 -04:00
# * <tt>:prompt</tt> - set to true or a prompt string. When the select element doesn't have a value yet, this prepends an option with a generic prompt -- "Please select" -- or the given prompt string.
#
2007-05-18 01:29:22 -04:00
# Example:
2005-04-19 06:59:55 -04:00
#
2009-02-13 19:47:22 -05:00
# select("post", "person_id", Person.all.collect {|p| [ p.name, p.id ] }, {:prompt => 'Select Person'})
2005-04-19 06:59:55 -04:00
#
# could become:
#
# <select name="post[person_id]">
2007-05-18 01:29:22 -04:00
# <option value="">Select Person</option>
2005-04-19 06:59:55 -04:00
# <option value="1">David</option>
# <option value="2">Sam</option>
# <option value="3">Tobias</option>
# </select>
2010-08-14 01:13:00 -04:00
#
# Like the other form helpers, +select+ can accept an <tt>:index</tt> option to manually set the ID used in the resulting output. Unlike other helpers, +select+ expects this
2008-03-19 22:15:29 -04:00
# option to be in the +html_options+ parameter.
2010-08-14 01:13:00 -04:00
#
# Example:
#
2008-03-19 22:15:29 -04:00
# select("album[]", "genre", %w[rap rock country], {}, { :index => nil })
2010-08-14 01:13:00 -04:00
#
2008-03-19 22:15:29 -04:00
# becomes:
2010-08-14 01:13:00 -04:00
#
2008-03-19 22:15:29 -04:00
# <select name="album[][genre]" id="album__genre">
# <option value="rap">rap</option>
# <option value="rock">rock</option>
# <option value="country">country</option>
# </select>
2009-02-13 19:47:22 -05:00
#
# * <tt>:disabled</tt> - can be a single value or an array of values that will be disabled options in the final output.
#
# Example:
#
# select("post", "category", Post::CATEGORIES, {:disabled => 'restricted'})
#
# could become:
#
# <select name="post[category]">
# <option></option>
# <option>joke</option>
# <option>poem</option>
# <option disabled="disabled">restricted</option>
# </select>
#
# When used with the <tt>collection_select</tt> helper, <tt>:disabled</tt> can also be a Proc that identifies those options that should be disabled.
#
# Example:
#
# collection_select(:post, :category_id, Category.all, :id, :name, {:disabled => lambda{|category| category.archived? }})
#
# If the categories "2008 stuff" and "Christmas" return true when the method <tt>archived?</tt> is called, this would return:
# <select name="post[category_id]">
# <option value="1" disabled="disabled">2008 stuff</option>
# <option value="2" disabled="disabled">Christmas</option>
# <option value="3">Jokes</option>
# <option value="4">Poems</option>
# </select>
#
2004-11-23 20:04:44 -05:00
module FormOptionsHelper
2010-03-10 02:41:39 -05:00
# ERB::Util can mask some helpers like textilize. Make sure to include them.
include TextHelper
2004-12-15 06:49:23 -05:00
# Create a select tag and a series of contained option tags for the provided object and method.
2005-02-07 09:15:53 -05:00
# The option currently held by the object will be selected, provided that the object is available.
2011-06-14 15:45:22 -04:00
#
# There are two possible formats for the choices parameter, corresponding to other helpers' output:
# * A flat collection: see options_for_select
# * A nested collection: see grouped_options_for_select
2005-04-19 06:59:55 -04:00
#
# Example with @post.person_id => 1:
2009-02-13 19:47:22 -05:00
# select("post", "person_id", Person.all.collect {|p| [ p.name, p.id ] }, { :include_blank => true })
2005-04-19 06:59:55 -04:00
#
# could become:
#
2005-11-23 16:59:20 -05:00
# <select name="post[person_id]">
2007-05-18 01:29:22 -04:00
# <option value=""></option>
2005-04-19 06:59:55 -04:00
# <option value="1" selected="selected">David</option>
# <option value="2">Sam</option>
# <option value="3">Tobias</option>
# </select>
2005-03-06 06:50:41 -05:00
#
2004-12-15 06:49:23 -05:00
# This can be used to provide a default set of options in the standard way: before rendering the create form, a
# new model instance is assigned the default options and bound to @model_name. Usually this model is not saved
# to the database. Instead, a second model object is created when the create request is received.
# This allows the user to submit a form page more than once with the expected results of creating multiple records.
# In addition, this allows a single partial to be used to generate form inputs for both edit and create forms.
2005-11-23 16:59:20 -05:00
#
2011-05-23 19:22:33 -04:00
# By default, <tt>post.person_id</tt> is the selected option. Specify <tt>:selected => value</tt> to use a different selection
2009-02-13 19:47:22 -05:00
# or <tt>:selected => nil</tt> to leave all options unselected. Similarly, you can specify values to be disabled in the option
# tags by specifying the <tt>:disabled</tt> option. This can either be a single value or an array of values to be disabled.
2011-06-08 05:59:54 -04:00
#
# ==== Gotcha
#
2012-01-05 15:59:25 -05:00
# The HTML specification says when +multiple+ parameter passed to select and all options got deselected
2011-06-08 05:59:54 -04:00
# web browsers do not send any value to server. Unfortunately this introduces a gotcha:
# if an +User+ model has many +roles+ and have +role_ids+ accessor, and in the form that edits roles of the user
# the user deselects all roles from +role_ids+ multiple select box, no +role_ids+ parameter is sent. So,
# any mass-assignment idiom like
#
# @user.update_attributes(params[:user])
#
# wouldn't update roles.
#
# To prevent this the helper generates an auxiliary hidden field before
# every multiple select. The hidden field has the same name as multiple select and blank value.
#
# This way, the client either sends only the hidden field (representing
# the deselected multiple select box), or both fields. Since the HTML specification
# says key/value pairs have to be sent in the same order they appear in the
# form, and parameters extraction gets the last occurrence of any repeated
# key in the query string, that works for ordinary forms.
#
2004-11-23 20:04:44 -05:00
def select ( object , method , choices , options = { } , html_options = { } )
2012-01-17 13:07:05 -05:00
Tags :: Select . new ( object , method , self , choices , options , html_options ) . render
2004-11-23 20:04:44 -05:00
end
2005-03-06 06:50:41 -05:00
2007-08-04 21:13:44 -04:00
# Returns <tt><select></tt> and <tt><option></tt> tags for the collection of existing return values of
# +method+ for +object+'s class. The value returned from calling +method+ on the instance +object+ will
# be selected. If calling +method+ returns +nil+, no selection is made without including <tt>:prompt</tt>
# or <tt>:include_blank</tt> in the +options+ hash.
#
# The <tt>:value_method</tt> and <tt>:text_method</tt> parameters are methods to be called on each member
# of +collection+. The return values are used as the +value+ attribute and contents of each
# <tt><option></tt> tag, respectively.
2010-08-14 01:13:00 -04:00
#
2007-08-04 21:13:44 -04:00
# Example object structure for use with this method:
# class Post < ActiveRecord::Base
# belongs_to :author
# end
# class Author < ActiveRecord::Base
# has_many :posts
# def name_with_initial
# "#{first_name.first}. #{last_name}"
# end
# end
#
2008-05-25 07:29:00 -04:00
# Sample usage (selecting the associated Author for an instance of Post, <tt>@post</tt>):
2010-01-28 05:18:32 -05:00
# collection_select(:post, :author_id, Author.all, :id, :name_with_initial, :prompt => true)
2007-08-04 21:13:44 -04:00
#
# If <tt>@post.author_id</tt> is already <tt>1</tt>, this would return:
# <select name="post[author_id]">
# <option value="">Please select</option>
# <option value="1" selected="selected">D. Heinemeier Hansson</option>
# <option value="2">D. Thomas</option>
# <option value="3">M. Clark</option>
# </select>
2004-11-23 20:04:44 -05:00
def collection_select ( object , method , collection , value_method , text_method , options = { } , html_options = { } )
2012-01-17 13:07:05 -05:00
Tags :: CollectionSelect . new ( object , method , self , collection , value_method , text_method , options , html_options ) . render
2004-11-23 20:04:44 -05:00
end
2005-03-06 06:50:41 -05:00
2009-08-09 22:18:01 -04:00
# Returns <tt><select></tt>, <tt><optgroup></tt> and <tt><option></tt> tags for the collection of existing return values of
# +method+ for +object+'s class. The value returned from calling +method+ on the instance +object+ will
# be selected. If calling +method+ returns +nil+, no selection is made without including <tt>:prompt</tt>
# or <tt>:include_blank</tt> in the +options+ hash.
#
# Parameters:
# * +object+ - The instance of the class to be used for the select tag
# * +method+ - The attribute of +object+ corresponding to the select tag
# * +collection+ - An array of objects representing the <tt><optgroup></tt> tags.
# * +group_method+ - The name of a method which, when called on a member of +collection+, returns an
# array of child objects representing the <tt><option></tt> tags.
# * +group_label_method+ - The name of a method which, when called on a member of +collection+, returns a
# string to be used as the +label+ attribute for its <tt><optgroup></tt> tag.
# * +option_key_method+ - The name of a method which, when called on a child object of a member of
# +collection+, returns a value to be used as the +value+ attribute for its <tt><option></tt> tag.
# * +option_value_method+ - The name of a method which, when called on a child object of a member of
# +collection+, returns a value to be used as the contents of its <tt><option></tt> tag.
#
# Example object structure for use with this method:
# class Continent < ActiveRecord::Base
# has_many :countries
# # attribs: id, name
# end
# class Country < ActiveRecord::Base
# belongs_to :continent
# # attribs: id, name, continent_id
# end
# class City < ActiveRecord::Base
# belongs_to :country
# # attribs: id, name, country_id
# end
#
# Sample usage:
# grouped_collection_select(:city, :country_id, @continents, :countries, :name, :id, :name)
#
# Possible output:
# <select name="city[country_id]">
# <optgroup label="Africa">
# <option value="1">South Africa</option>
# <option value="3">Somalia</option>
# </optgroup>
# <optgroup label="Europe">
# <option value="7" selected="selected">Denmark</option>
# <option value="2">Ireland</option>
# </optgroup>
# </select>
#
def grouped_collection_select ( object , method , collection , group_method , group_label_method , option_key_method , option_value_method , options = { } , html_options = { } )
2012-01-17 13:07:05 -05:00
Tags :: GroupedCollectionSelect . new ( object , method , self , collection , group_method , group_label_method , option_key_method , option_value_method , options , html_options ) . render
2009-08-09 22:18:01 -04:00
end
2005-02-23 07:54:58 -05:00
# Return select and option tags for the given object and method, using
# #time_zone_options_for_select to generate the list of option tags.
#
# In addition to the <tt>:include_blank</tt> option documented above,
# this method also supports a <tt>:model</tt> option, which defaults
2009-07-25 11:03:58 -04:00
# to ActiveSupport::TimeZone. This may be used by users to specify a
# different time zone model object. (See +time_zone_options_for_select+
# for more information.)
2008-05-18 11:25:29 -04:00
#
2009-07-25 11:03:58 -04:00
# You can also supply an array of ActiveSupport::TimeZone objects
2008-05-18 11:25:29 -04:00
# as +priority_zones+, so that they will be listed above the rest of the
2009-07-25 11:03:58 -04:00
# (long) list. (You can use ActiveSupport::TimeZone.us_zones as a convenience
# for obtaining a list of the US time zones, or a Regexp to select the zones
2008-06-28 22:27:32 -04:00
# of your choice)
2008-05-18 11:25:29 -04:00
#
2007-12-21 17:18:07 -05:00
# Finally, this method supports a <tt>:default</tt> option, which selects
2009-07-25 11:03:58 -04:00
# a default ActiveSupport::TimeZone if the object's time zone is +nil+.
2007-12-21 17:18:07 -05:00
#
# Examples:
# time_zone_select( "user", "time_zone", nil, :include_blank => true)
#
# time_zone_select( "user", "time_zone", nil, :default => "Pacific Time (US & Canada)" )
#
2009-07-25 11:03:58 -04:00
# time_zone_select( "user", 'time_zone', ActiveSupport::TimeZone.us_zones, :default => "Pacific Time (US & Canada)")
2007-12-21 17:18:07 -05:00
#
2009-07-25 11:03:58 -04:00
# time_zone_select( "user", 'time_zone', [ ActiveSupport::TimeZone['Alaska'], ActiveSupport::TimeZone['Hawaii'] ])
2008-05-18 11:25:29 -04:00
#
2008-06-28 22:27:32 -04:00
# time_zone_select( "user", 'time_zone', /Australia/)
#
2010-10-15 13:43:53 -04:00
# time_zone_select( "user", "time_zone", ActiveSupport::TimeZone.all.sort, :model => ActiveSupport::TimeZone)
2005-02-23 07:54:58 -05:00
def time_zone_select ( object , method , priority_zones = nil , options = { } , html_options = { } )
2012-01-17 13:07:05 -05:00
Tags :: TimeZoneSelect . new ( object , method , self , priority_zones , options , html_options ) . render
2005-02-23 07:54:58 -05:00
end
2005-03-06 06:50:41 -05:00
# Accepts a container (hash, array, enumerable, your type) and returns a string of option tags. Given a container
2004-11-23 20:04:44 -05:00
# where the elements respond to first and last (such as a two-element array), the "lasts" serve as option values and
# the "firsts" as option text. Hashes are turned into this form automatically, so the keys become "firsts" and values
2011-05-23 19:22:33 -04:00
# become lasts. If +selected+ is specified, the matching "last" or element will get the selected option-tag. +selected+
2005-03-06 06:50:41 -05:00
# may also be an array of values to be selected when using a multiple select.
2004-11-23 20:04:44 -05:00
#
# Examples (call, result):
# options_for_select([["Dollar", "$"], ["Kroner", "DKK"]])
# <option value="$">Dollar</option>\n<option value="DKK">Kroner</option>
#
2005-02-07 09:15:53 -05:00
# options_for_select([ "VISA", "MasterCard" ], "MasterCard")
# <option>VISA</option>\n<option selected="selected">MasterCard</option>
2004-11-23 20:04:44 -05:00
#
# options_for_select({ "Basic" => "$20", "Plus" => "$40" }, "$40")
# <option value="$20">Basic</option>\n<option value="$40" selected="selected">Plus</option>
#
2005-02-07 09:15:53 -05:00
# options_for_select([ "VISA", "MasterCard", "Discover" ], ["VISA", "Discover"])
# <option selected="selected">VISA</option>\n<option>MasterCard</option>\n<option selected="selected">Discover</option>
2005-02-19 07:22:13 -05:00
#
2010-05-15 05:53:59 -04:00
# You can optionally provide html attributes as the last element of the array.
#
# Examples:
2011-05-10 19:55:31 -04:00
# options_for_select([ "Denmark", ["USA", {:class => 'bold'}], "Sweden" ], ["USA", "Sweden"])
2010-05-15 05:53:59 -04:00
# <option value="Denmark">Denmark</option>\n<option value="USA" class="bold" selected="selected">USA</option>\n<option value="Sweden" selected="selected">Sweden</option>
#
2011-05-10 19:55:31 -04:00
# options_for_select([["Dollar", "$", {:class => "bold"}], ["Kroner", "DKK", {:onclick => "alert('HI');"}]])
2010-05-15 05:53:59 -04:00
# <option value="$" class="bold">Dollar</option>\n<option value="DKK" onclick="alert('HI');">Kroner</option>
#
2009-02-13 19:47:22 -05:00
# If you wish to specify disabled option tags, set +selected+ to be a hash, with <tt>:disabled</tt> being either a value
# or array of values to be disabled. In this case, you can use <tt>:selected</tt> to specify selected option tags.
#
# Examples:
# options_for_select(["Free", "Basic", "Advanced", "Super Platinum"], :disabled => "Super Platinum")
# <option value="Free">Free</option>\n<option value="Basic">Basic</option>\n<option value="Advanced">Advanced</option>\n<option value="Super Platinum" disabled="disabled">Super Platinum</option>
#
# options_for_select(["Free", "Basic", "Advanced", "Super Platinum"], :disabled => ["Advanced", "Super Platinum"])
# <option value="Free">Free</option>\n<option value="Basic">Basic</option>\n<option value="Advanced" disabled="disabled">Advanced</option>\n<option value="Super Platinum" disabled="disabled">Super Platinum</option>
#
# options_for_select(["Free", "Basic", "Advanced", "Super Platinum"], :selected => "Free", :disabled => "Super Platinum")
# <option value="Free" selected="selected">Free</option>\n<option value="Basic">Basic</option>\n<option value="Advanced">Advanced</option>\n<option value="Super Platinum" disabled="disabled">Super Platinum</option>
#
2005-02-19 07:22:13 -05:00
# NOTE: Only the option tags are returned, you have to wrap this call in a regular HTML select tag.
2004-11-23 20:04:44 -05:00
def options_for_select ( container , selected = nil )
2009-04-01 06:44:56 -04:00
return container if String === container
2012-01-05 15:18:54 -05:00
selected , disabled = extract_selected_and_disabled ( selected ) . map do | r |
Array ( r ) . map { | item | item . to_s }
2010-08-14 10:55:51 -04:00
end
2005-03-06 06:50:41 -05:00
2010-08-14 10:55:51 -04:00
container . map do | element |
2010-05-15 05:53:59 -04:00
html_attributes = option_html_attributes ( element )
2011-07-27 10:12:22 -04:00
text , value = option_text_and_value ( element ) . map { | item | item . to_s }
2007-03-11 09:14:53 -04:00
selected_attribute = ' selected="selected"' if option_value_selected? ( value , selected )
2009-02-13 19:37:24 -05:00
disabled_attribute = ' disabled="disabled"' if disabled && option_value_selected? ( value , disabled )
2010-10-18 11:58:57 -04:00
%( <option value=" #{ ERB :: Util . html_escape ( value ) } " #{ selected_attribute } #{ disabled_attribute } #{ html_attributes } > #{ ERB :: Util . html_escape ( text ) } </option> )
2010-08-14 10:55:51 -04:00
end . join ( " \n " ) . html_safe
2004-11-23 20:04:44 -05:00
end
2012-01-25 17:12:01 -05:00
# Returns a string of option tags that have been compiled by iterating over the +collection+ and assigning
2004-11-23 20:04:44 -05:00
# the result of a call to the +value_method+ as the option value and the +text_method+ as the option text.
2008-12-19 09:27:43 -05:00
# Example:
# options_from_collection_for_select(@people, 'id', 'name')
# This will output the same HTML as if you did this:
# <option value="#{person.id}">#{person.name}</option>
2004-11-23 20:04:44 -05:00
#
2008-12-19 09:27:43 -05:00
# This is more often than not used inside a #select_tag like this example:
# select_tag 'person', options_from_collection_for_select(@people, 'id', 'name')
2005-02-19 07:22:13 -05:00
#
2009-02-13 19:47:22 -05:00
# If +selected+ is specified as a value or array of values, the element(s) returning a match on +value_method+
# will be selected option tag(s).
#
# If +selected+ is specified as a Proc, those members of the collection that return true for the anonymous
# function are the selected values.
#
# +selected+ can also be a hash, specifying both <tt>:selected</tt> and/or <tt>:disabled</tt> values as required.
#
# Be sure to specify the same class as the +value_method+ when specifying selected or disabled options.
2008-12-19 09:27:43 -05:00
# Failure to do this will produce undesired results. Example:
# options_from_collection_for_select(@people, 'id', 'name', '1')
# Will not select a person with the id of 1 because 1 (an Integer) is not the same as '1' (a string)
# options_from_collection_for_select(@people, 'id', 'name', 1)
# should produce the desired results.
2007-03-11 09:14:53 -04:00
def options_from_collection_for_select ( collection , value_method , text_method , selected = nil )
options = collection . map do | element |
[ element . send ( text_method ) , element . send ( value_method ) ]
end
2009-02-13 19:37:24 -05:00
selected , disabled = extract_selected_and_disabled ( selected )
2012-01-31 15:58:31 -05:00
select_deselect = {
:selected = > extract_values_from_collection ( collection , value_method , selected ) ,
:disabled = > extract_values_from_collection ( collection , value_method , disabled )
}
2009-02-13 19:37:24 -05:00
options_for_select ( options , select_deselect )
2004-11-23 20:04:44 -05:00
end
2008-05-25 07:29:00 -04:00
# Returns a string of <tt><option></tt> tags, like <tt>options_from_collection_for_select</tt>, but
2007-08-04 21:13:44 -04:00
# groups them by <tt><optgroup></tt> tags based on the object relationships of the arguments.
#
# Parameters:
2008-05-25 07:29:00 -04:00
# * +collection+ - An array of objects representing the <tt><optgroup></tt> tags.
# * +group_method+ - The name of a method which, when called on a member of +collection+, returns an
# array of child objects representing the <tt><option></tt> tags.
# * group_label_method+ - The name of a method which, when called on a member of +collection+, returns a
# string to be used as the +label+ attribute for its <tt><optgroup></tt> tag.
# * +option_key_method+ - The name of a method which, when called on a child object of a member of
# +collection+, returns a value to be used as the +value+ attribute for its <tt><option></tt> tag.
# * +option_value_method+ - The name of a method which, when called on a child object of a member of
# +collection+, returns a value to be used as the contents of its <tt><option></tt> tag.
# * +selected_key+ - A value equal to the +value+ attribute for one of the <tt><option></tt> tags,
# which will have the +selected+ attribute set. Corresponds to the return value of one of the calls
2009-02-13 19:47:22 -05:00
# to +option_key_method+. If +nil+, no selection is made. Can also be a hash if disabled values are
# to be specified.
2007-08-04 21:13:44 -04:00
#
# Example object structure for use with this method:
# class Continent < ActiveRecord::Base
# has_many :countries
# # attribs: id, name
# end
# class Country < ActiveRecord::Base
# belongs_to :continent
# # attribs: id, name, continent_id
# end
#
# Sample usage:
# option_groups_from_collection_for_select(@continents, :countries, :name, :id, :name, 3)
#
# Possible output:
# <optgroup label="Africa">
# <option value="1">Egypt</option>
# <option value="4">Rwanda</option>
# ...
# </optgroup>
# <optgroup label="Asia">
# <option value="3" selected="selected">China</option>
# <option value="12">India</option>
# <option value="5">Japan</option>
# ...
# </optgroup>
#
# <b>Note:</b> Only the <tt><optgroup></tt> and <tt><option></tt> tags are returned, so you still have to
# wrap the output in an appropriate <tt><select></tt> tag.
def option_groups_from_collection_for_select ( collection , group_method , group_label_method , option_key_method , option_value_method , selected_key = nil )
2010-09-28 14:22:16 -04:00
collection . map do | group |
2012-01-31 15:38:46 -05:00
option_tags = options_from_collection_for_select (
group . send ( group_method ) , option_key_method , option_value_method , selected_key )
content_tag ( :optgroup , option_tags , :label = > group . send ( group_label_method ) )
2010-09-28 14:22:16 -04:00
end . join . html_safe
2005-03-06 06:50:41 -05:00
end
2009-01-29 12:59:44 -05:00
# Returns a string of <tt><option></tt> tags, like <tt>options_for_select</tt>, but
# wraps them with <tt><optgroup></tt> tags.
#
# Parameters:
2011-05-23 19:22:33 -04:00
# * +grouped_options+ - Accepts a nested array or hash of strings. The first value serves as the
2009-01-29 12:59:44 -05:00
# <tt><optgroup></tt> label while the second value must be an array of options. The second value can be a
# nested array of text-value pairs. See <tt>options_for_select</tt> for more info.
# Ex. ["North America",[["United States","US"],["Canada","CA"]]]
# * +selected_key+ - A value equal to the +value+ attribute for one of the <tt><option></tt> tags,
# which will have the +selected+ attribute set. Note: It is possible for this value to match multiple options
2011-05-23 19:22:33 -04:00
# as you might have the same option in multiple groups. Each will then get <tt>selected="selected"</tt>.
2010-06-11 06:15:34 -04:00
# * +prompt+ - set to true or a prompt string. When the select element doesn't have a value yet, this
# prepends an option with a generic prompt - "Please select" - or the given prompt string.
2009-01-29 12:59:44 -05:00
#
# Sample usage (Array):
# grouped_options = [
# ['North America',
# [['United States','US'],'Canada']],
# ['Europe',
# ['Denmark','Germany','France']]
# ]
# grouped_options_for_select(grouped_options)
#
# Sample usage (Hash):
# grouped_options = {
2011-05-26 09:24:33 -04:00
# 'North America' => [['United States','US'], 'Canada'],
2009-01-29 12:59:44 -05:00
# 'Europe' => ['Denmark','Germany','France']
# }
# grouped_options_for_select(grouped_options)
#
# Possible output:
# <optgroup label="Europe">
# <option value="Denmark">Denmark</option>
# <option value="Germany">Germany</option>
# <option value="France">France</option>
# </optgroup>
# <optgroup label="North America">
# <option value="US">United States</option>
# <option value="Canada">Canada</option>
# </optgroup>
#
# <b>Note:</b> Only the <tt><optgroup></tt> and <tt><option></tt> tags are returned, so you still have to
# wrap the output in an appropriate <tt><select></tt> tag.
def grouped_options_for_select ( grouped_options , selected_key = nil , prompt = nil )
body = ''
2010-07-14 02:23:41 -04:00
body << content_tag ( :option , prompt , { :value = > " " } , true ) if prompt
2009-01-29 12:59:44 -05:00
grouped_options = grouped_options . sort if grouped_options . is_a? ( Hash )
grouped_options . each do | group |
body << content_tag ( :optgroup , options_for_select ( group [ 1 ] , selected_key ) , :label = > group [ 0 ] )
end
2010-04-04 00:31:52 -04:00
body . html_safe
2009-01-29 12:59:44 -05:00
end
2005-02-23 07:54:58 -05:00
# Returns a string of option tags for pretty much any time zone in the
2009-07-25 11:03:58 -04:00
# world. Supply a ActiveSupport::TimeZone name as +selected+ to have it
# marked as the selected option tag. You can also supply an array of
# ActiveSupport::TimeZone objects as +priority_zones+, so that they will
# be listed above the rest of the (long) list. (You can use
# ActiveSupport::TimeZone.us_zones as a convenience for obtaining a list
# of the US time zones, or a Regexp to select the zones of your choice)
2005-02-23 07:54:58 -05:00
#
2005-02-23 18:55:59 -05:00
# The +selected+ parameter must be either +nil+, or a string that names
2009-07-25 11:03:58 -04:00
# a ActiveSupport::TimeZone.
2005-02-23 18:55:59 -05:00
#
2009-07-25 11:03:58 -04:00
# By default, +model+ is the ActiveSupport::TimeZone constant (which can
# be obtained in Active Record as a value object). The only requirement
# is that the +model+ parameter be an object that responds to +all+, and
# returns an array of objects that represent time zones.
2005-02-23 07:54:58 -05:00
#
# NOTE: Only the option tags are returned, you have to wrap this call in
# a regular HTML select tag.
2008-06-11 00:13:27 -04:00
def time_zone_options_for_select ( selected = nil , priority_zones = nil , model = :: ActiveSupport :: TimeZone )
2005-02-23 07:54:58 -05:00
zone_options = " "
2005-02-23 18:55:59 -05:00
zones = model . all
convert_zones = lambda { | list | list . map { | z | [ z . to_s , z . name ] } }
2005-02-23 07:54:58 -05:00
if priority_zones
2012-01-05 15:59:25 -05:00
if priority_zones . is_a? ( Regexp )
2008-06-28 22:27:32 -04:00
priority_zones = model . all . find_all { | z | z =~ priority_zones }
2012-01-05 15:59:25 -05:00
end
2005-02-23 18:55:59 -05:00
zone_options += options_for_select ( convert_zones [ priority_zones ] , selected )
2007-12-04 00:30:28 -05:00
zone_options += " <option value= \" \" disabled= \" disabled \" >-------------</option> \n "
2005-02-23 07:54:58 -05:00
2005-02-23 18:55:59 -05:00
zones = zones . reject { | z | priority_zones . include? ( z ) }
2005-02-23 07:54:58 -05:00
end
2005-02-23 18:55:59 -05:00
zone_options += options_for_select ( convert_zones [ zones ] , selected )
2010-08-15 08:36:29 -04:00
zone_options . html_safe
2005-02-23 07:54:58 -05:00
end
2004-11-23 20:04:44 -05:00
2012-02-01 14:28:50 -05:00
# Returns radio button tags for the collection of existing return values of +method+ for
# +object+'s class. The value returned from calling +method+ on the instance +object+ will
# be selected. If calling +method+ returns +nil+, no selection is made.
#
# The <tt>:value_method</tt> and <tt>:text_method</tt> parameters are methods to be called on each member
# of +collection+. The return values are used as the +value+ attribute and contents of each
# radio button tag, respectively.
#
# Example object structure for use with this method:
# class Post < ActiveRecord::Base
# belongs_to :author
# end
# class Author < ActiveRecord::Base
# has_many :posts
# def name_with_initial
# "#{first_name.first}. #{last_name}"
# end
# end
#
# Sample usage (selecting the associated Author for an instance of Post, <tt>@post</tt>):
# collection_radio_buttons(:post, :author_id, Author.all, :id, :name_with_initial)
#
# If <tt>@post.author_id</tt> is already <tt>1</tt>, this would return:
# <input id="post_author_id_1" name="post[author_id]" type="radio" value="1" checked="checked" />
# <label class="collection_radio_buttons" for="post_author_id_1">D. Heinemeier Hansson</label>
# <input id="post_author_id_2" name="post[author_id]" type="radio" value="2" />
# <label class="collection_radio_buttons" for="post_author_id_2">D. Thomas</label>
# <input id="post_author_id_3" name="post[author_id]" type="radio" value="3" />
# <label class="collection_radio_buttons" for="post_author_id_3">M. Clark</label>
def collection_radio_buttons ( object , method , collection , value_method , text_method , options = { } , html_options = { } , & block )
Tags :: CollectionRadioButtons . new ( object , method , self , collection , value_method , text_method , options , html_options ) . render ( & block )
end
# Returns check box tags for the collection of existing return values of +method+ # for
# +object+'s class. The value returned from calling +method+ on the instance +object+ will
# be selected. If calling +method+ returns +nil+, no selection is made.
#
# The <tt>:value_method</tt> and <tt>:text_method</tt> parameters are methods to be called on each member
# of +collection+. The return values are used as the +value+ attribute and contents of each
# check box tag, respectively.
#
# Example object structure for use with this method:
# class Post < ActiveRecord::Base
# has_and_belongs_to :author
# end
# class Author < ActiveRecord::Base
# has_and_belongs_to :posts
# def name_with_initial
# "#{first_name.first}. #{last_name}"
# end
# end
#
# Sample usage (selecting the associated Author for an instance of Post, <tt>@post</tt>):
# collection_check_boxes(:post, :author_ids, Author.all, :id, :name_with_initial)
#
# If <tt>@post.author_ids</tt> is already <tt>[1]</tt>, this would return:
# <input id="post_author_ids_1" name="post[author_ids][]" type="checkbox" value="1" checked="checked" />
# <label class="collection_check_boxes" for="post_author_ids_1">D. Heinemeier Hansson</label>
# <input id="post_author_ids_1" name="post[author_ids][]" type="checkbox" value="2" />
# <label class="collection_check_boxes" for="post_author_ids_1">D. Thomas</label>
# <input id="post_author_ids_3" name="post[author_ids][]" type="checkbox" value="3" />
# <label class="collection_check_boxes" for="post_author_ids_3">M. Clark</label>
# <input name="post[author_ids][]" type="hidden" value="" />
def collection_check_boxes ( object , method , collection , value_method , text_method , options = { } , html_options = { } , & block )
Tags :: CollectionCheckBoxes . new ( object , method , self , collection , value_method , text_method , options , html_options ) . render ( & block )
end
2004-11-23 20:04:44 -05:00
private
2010-05-15 05:53:59 -04:00
def option_html_attributes ( element )
return " " unless Array === element
2012-01-31 15:58:31 -05:00
element . select { | e | Hash === e } . reduce ( { } , :merge ) . map do | k , v |
" #{ k } = \" #{ ERB :: Util . html_escape ( v . to_s ) } \" "
end . join
2010-05-15 05:53:59 -04:00
end
2007-03-11 09:14:53 -04:00
def option_text_and_value ( option )
# Options are [text, value] pairs or strings used for both.
2010-05-15 05:53:59 -04:00
case
when Array === option
option = option . reject { | e | Hash === e }
[ option . first , option . last ]
when ! option . is_a? ( String ) && option . respond_to? ( :first ) && option . respond_to? ( :last )
2007-03-11 09:14:53 -04:00
[ option . first , option . last ]
else
[ option , option ]
end
end
def option_value_selected? ( value , selected )
if selected . respond_to? ( :include? ) && ! selected . is_a? ( String )
selected . include? value
else
value == selected
end
end
2009-02-13 19:37:24 -05:00
def extract_selected_and_disabled ( selected )
2010-08-14 10:55:51 -04:00
if selected . is_a? ( Proc )
2012-01-31 15:58:31 -05:00
[ selected , nil ]
2009-02-13 19:37:24 -05:00
else
2010-08-14 10:55:51 -04:00
selected = Array . wrap ( selected )
2012-01-31 15:58:31 -05:00
options = selected . extract_options! . symbolize_keys
selected_items = options . fetch ( :selected , selected )
[ selected_items , options [ :disabled ] ]
2009-02-13 19:37:24 -05:00
end
end
def extract_values_from_collection ( collection , value_method , selected )
if selected . is_a? ( Proc )
collection . map do | element |
element . send ( value_method ) if selected . call ( element )
end . compact
else
selected
end
end
2004-11-23 20:04:44 -05:00
end
2005-11-13 06:13:11 -05:00
class FormBuilder
def select ( method , choices , options = { } , html_options = { } )
2008-07-13 19:55:57 -04:00
@template . select ( @object_name , method , choices , objectify_options ( options ) , @default_options . merge ( html_options ) )
2005-11-13 06:13:11 -05:00
end
def collection_select ( method , collection , value_method , text_method , options = { } , html_options = { } )
2008-07-13 19:55:57 -04:00
@template . collection_select ( @object_name , method , collection , value_method , text_method , objectify_options ( options ) , @default_options . merge ( html_options ) )
2005-11-13 06:13:11 -05:00
end
2009-08-09 22:18:01 -04:00
def grouped_collection_select ( method , collection , group_method , group_label_method , option_key_method , option_value_method , options = { } , html_options = { } )
@template . grouped_collection_select ( @object_name , method , collection , group_method , group_label_method , option_key_method , option_value_method , objectify_options ( options ) , @default_options . merge ( html_options ) )
end
2005-11-13 06:13:11 -05:00
def time_zone_select ( method , priority_zones = nil , options = { } , html_options = { } )
2008-07-13 19:55:57 -04:00
@template . time_zone_select ( @object_name , method , priority_zones , objectify_options ( options ) , @default_options . merge ( html_options ) )
2005-11-13 06:13:11 -05:00
end
2012-02-01 14:28:50 -05:00
def collection_check_boxes ( method , collection , value_method , text_method , options = { } , html_options = { } )
@template . collection_check_boxes ( @object_name , method , collection , value_method , text_method , objectify_options ( options ) , @default_options . merge ( html_options ) )
end
def collection_radio_buttons ( method , collection , value_method , text_method , options = { } , html_options = { } )
@template . collection_radio_buttons ( @object_name , method , collection , value_method , text_method , objectify_options ( options ) , @default_options . merge ( html_options ) )
end
2005-11-13 06:13:11 -05:00
end
2004-11-23 20:04:44 -05:00
end
2005-02-23 18:55:59 -05:00
end