require 'cgi' require 'erb' require 'action_view/helpers/form_helper' module ActionView module Helpers # Provides a number of methods for turning different kinds of containers into a set of option tags. # == Options # The collection_select, country_select, select, # and time_zone_select methods take an options parameter, # a hash. # # * :include_blank - 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, # # select("post", "category", Post::CATEGORIES, {:include_blank => true}) # # could become: # # # # Another common case is a select tag for an belongs_to-associated object. # # Example with @post.person_id => 2: # # select("post", "person_id", Person.find(:all).collect {|p| [ p.name, p.id ] }, {:include_blank => 'None'}) # # could become: # # # # * :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. # # Example: # # select("post", "person_id", Person.find(:all).collect {|p| [ p.name, p.id ] }, {:prompt => 'Select Person'}) # # could become: # # module FormOptionsHelper include ERB::Util # Create a select tag and a series of contained option tags for the provided object and method. # The option currently held by the object will be selected, provided that the object is available. # 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 }) # # could become: # # # # 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. # # By default, post.person_id is the selected option. Specify :selected => value to use a different selection # or :selected => nil to leave all options unselected. def select(object, method, choices, options = {}, html_options = {}) InstanceTag.new(object, method, self, nil, options.delete(:object)).to_select_tag(choices, options, html_options) end # Returns # # # # # def collection_select(object, method, collection, value_method, text_method, options = {}, html_options = {}) InstanceTag.new(object, method, self, nil, options.delete(:object)).to_collection_select_tag(collection, value_method, text_method, options, html_options) end # Return select and option tags for the given object and method, using country_options_for_select to generate the list of option tags. def country_select(object, method, priority_countries = nil, options = {}, html_options = {}) InstanceTag.new(object, method, self, nil, options.delete(:object)).to_country_select_tag(priority_countries, options, html_options) end # 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 :include_blank option documented above, # this method also supports a :model option, which defaults # to TimeZone. This may be used by users to specify a different time # zone model object. (See #time_zone_options_for_select for more # information.) def time_zone_select(object, method, priority_zones = nil, options = {}, html_options = {}) InstanceTag.new(object, method, self, nil, options.delete(:object)).to_time_zone_select_tag(priority_zones, options, html_options) end # Accepts a container (hash, array, enumerable, your type) and returns a string of option tags. Given a container # 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 # become lasts. If +selected+ is specified, the matching "last" or element will get the selected option-tag. +Selected+ # may also be an array of values to be selected when using a multiple select. # # Examples (call, result): # options_for_select([["Dollar", "$"], ["Kroner", "DKK"]]) # \n # # options_for_select([ "VISA", "MasterCard" ], "MasterCard") # \n # # options_for_select({ "Basic" => "$20", "Plus" => "$40" }, "$40") # \n # # options_for_select([ "VISA", "MasterCard", "Discover" ], ["VISA", "Discover"]) # \n\n # # 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 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 << %() end options_for_select.join("\n") end # Returns a string of option tags that have been compiled by iterating over the +collection+ and assigning the # the result of a call to the +value_method+ as the option value and the +text_method+ as the option text. # If +selected+ is specified, the element returning a match on +value_method+ will get the selected option tag. # # Example (call, result). Imagine a loop iterating over each +person+ in @project.people to generate an input tag: # options_from_collection_for_select(@project.people, "id", "name") # # # NOTE: Only the option tags are returned, you have to wrap this call in a regular HTML select tag. 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 options_for_select(options, selected) end # Returns a string of tags, like #options_from_collection_for_select, but # groups them by tags based on the object relationships of the arguments. # # Parameters: # +collection+:: An array of objects representing the tags # +group_method+:: The name of a method which, when called on a member of +collection+, returns an # array of child objects representing the 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 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 # 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 # tag # +selected_key+:: A value equal to the +value+ attribute for one of the 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. # # 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: # # # # ... # # # # # # ... # # # Note: Only the and tags are returned, so you still have to # wrap the output in an appropriate