diff options
Diffstat (limited to 'actionpack/lib/action_view/helpers/form_options_helper.rb')
-rw-r--r-- | actionpack/lib/action_view/helpers/form_options_helper.rb | 107 |
1 files changed, 92 insertions, 15 deletions
diff --git a/actionpack/lib/action_view/helpers/form_options_helper.rb b/actionpack/lib/action_view/helpers/form_options_helper.rb index 54c82cbd1d..6b385ef77d 100644 --- a/actionpack/lib/action_view/helpers/form_options_helper.rb +++ b/actionpack/lib/action_view/helpers/form_options_helper.rb @@ -6,9 +6,7 @@ module ActionView module Helpers # Provides a number of methods for turning different kinds of containers into a set of option tags. # == Options - # The <tt>collection_select</tt>, <tt>country_select</tt>, <tt>select</tt>, - # and <tt>time_zone_select</tt> methods take an <tt>options</tt> parameter, - # a hash. + # The <tt>collection_select</tt>, <tt>select</tt> and <tt>time_zone_select</tt> methods take an <tt>options</tt> parameter, a hash: # # * <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. # @@ -28,7 +26,7 @@ module ActionView # # Example with @post.person_id => 2: # - # select("post", "person_id", Person.find(:all).collect {|p| [ p.name, p.id ] }, {:include_blank => 'None'}) + # select("post", "person_id", Person.all.collect {|p| [ p.name, p.id ] }, {:include_blank => 'None'}) # # could become: # @@ -43,7 +41,7 @@ module ActionView # # Example: # - # select("post", "person_id", Person.find(:all).collect {|p| [ p.name, p.id ] }, {:prompt => 'Select Person'}) + # select("post", "person_id", Person.all.collect {|p| [ p.name, p.id ] }, {:prompt => 'Select Person'}) # # could become: # @@ -68,6 +66,36 @@ module ActionView # <option value="rock">rock</option> # <option value="country">country</option> # </select> + # + # * <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> + # module FormOptionsHelper include ERB::Util @@ -76,7 +104,7 @@ module ActionView # See options_for_select for the required format of the choices parameter. # # Example with @post.person_id => 1: - # select("post", "person_id", Person.find(:all).collect {|p| [ p.name, p.id ] }, { :include_blank => true }) + # select("post", "person_id", Person.all.collect {|p| [ p.name, p.id ] }, { :include_blank => true }) # # could become: # @@ -94,7 +122,8 @@ module ActionView # In addition, this allows a single partial to be used to generate form inputs for both edit and create forms. # # By default, <tt>post.person_id</tt> is the selected option. Specify <tt>:selected => value</tt> to use a different selection - # or <tt>:selected => nil</tt> to leave all options unselected. + # 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. def select(object, method, choices, options = {}, html_options = {}) InstanceTag.new(object, method, self, options.delete(:object)).to_select_tag(choices, options, html_options) end @@ -120,7 +149,7 @@ module ActionView # end # # Sample usage (selecting the associated Author for an instance of Post, <tt>@post</tt>): - # collection_select(:post, :author_id, Author.find(:all), :id, :name_with_initial, {:prompt => true}) + # collection_select(:post, :author_id, Author.all, :id, :name_with_initial, {:prompt => true}) # # If <tt>@post.author_id</tt> is already <tt>1</tt>, this would return: # <select name="post[author_id]"> @@ -186,14 +215,29 @@ module ActionView # options_for_select([ "VISA", "MasterCard", "Discover" ], ["VISA", "Discover"]) # <option selected="selected">VISA</option>\n<option>MasterCard</option>\n<option selected="selected">Discover</option> # + # 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> + # # NOTE: Only the option tags are returned, you have to wrap this call in a regular HTML select tag. def options_for_select(container, selected = nil) container = container.to_a if Hash === container + selected, disabled = extract_selected_and_disabled(selected) options_for_select = container.inject([]) do |options, element| text, value = option_text_and_value(element) selected_attribute = ' selected="selected"' if option_value_selected?(value, selected) - options << %(<option value="#{html_escape(value.to_s)}"#{selected_attribute}>#{html_escape(text.to_s)}</option>) + disabled_attribute = ' disabled="disabled"' if disabled && option_value_selected?(value, disabled) + options << %(<option value="#{html_escape(value.to_s)}"#{selected_attribute}#{disabled_attribute}>#{html_escape(text.to_s)}</option>) end options_for_select.join("\n") @@ -209,8 +253,15 @@ module ActionView # 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') # - # If +selected+ is specified, the element returning a match on +value_method+ will get the selected option tag. - # Be sure to specify the same class as the +value_method+ when specifying a selected option. + # 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. # 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) @@ -220,7 +271,12 @@ module ActionView options = collection.map do |element| [element.send(text_method), element.send(value_method)] end - options_for_select(options, selected) + selected, disabled = extract_selected_and_disabled(selected) + select_deselect = {} + select_deselect[:selected] = extract_values_from_collection(collection, value_method, selected) + select_deselect[:disabled] = extract_values_from_collection(collection, value_method, disabled) + + options_for_select(options, select_deselect) end # Returns a string of <tt><option></tt> tags, like <tt>options_from_collection_for_select</tt>, but @@ -238,7 +294,8 @@ module ActionView # +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 - # to +option_key_method+. If +nil+, no selection is made. + # to +option_key_method+. If +nil+, no selection is made. Can also be a hash if disabled values are + # to be specified. # # Example object structure for use with this method: # class Continent < ActiveRecord::Base @@ -388,6 +445,24 @@ module ActionView value == selected end end + + def extract_selected_and_disabled(selected) + if selected.is_a?(Hash) + [selected[:selected], selected[:disabled]] + else + [selected, nil] + 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 end class InstanceTag #:nodoc: @@ -398,16 +473,18 @@ module ActionView add_default_name_and_id(html_options) value = value(object) selected_value = options.has_key?(:selected) ? options[:selected] : value - content_tag("select", add_options(options_for_select(choices, selected_value), options, selected_value), html_options) + disabled_value = options.has_key?(:disabled) ? options[:disabled] : nil + content_tag("select", add_options(options_for_select(choices, :selected => selected_value, :disabled => disabled_value), options, selected_value), html_options) end def to_collection_select_tag(collection, value_method, text_method, options, html_options) html_options = html_options.stringify_keys add_default_name_and_id(html_options) value = value(object) + disabled_value = options.has_key?(:disabled) ? options[:disabled] : nil selected_value = options.has_key?(:selected) ? options[:selected] : value content_tag( - "select", add_options(options_from_collection_for_select(collection, value_method, text_method, selected_value), options, value), html_options + "select", add_options(options_from_collection_for_select(collection, value_method, text_method, :selected => selected_value, :disabled => disabled_value), options, value), html_options ) end |