PageRenderTime 101ms CodeModel.GetById 15ms app.highlight 78ms RepoModel.GetById 1ms app.codeStats 1ms

/External.LCA_RESTRICTED/Languages/Ruby/ruby19/lib/ruby/gems/1.9.1/gems/actionpack-3.0.0/lib/action_view/helpers/form_options_helper.rb

http://github.com/IronLanguages/main
Ruby | 627 lines | 190 code | 37 blank | 400 comment | 23 complexity | 7fc275a8e0ad341f7ca19f76b99879a6 MD5 | raw file
  1require 'cgi'
  2require 'erb'
  3require 'action_view/helpers/form_helper'
  4require 'active_support/core_ext/object/blank'
  5
  6module ActionView
  7  # = Action View Form Option Helpers
  8  module Helpers
  9    # Provides a number of methods for turning different kinds of containers into a set of option tags.
 10    # == Options
 11    # The <tt>collection_select</tt>, <tt>select</tt> and <tt>time_zone_select</tt> methods take an <tt>options</tt> parameter, a hash:
 12    #
 13    # * <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.
 14    #
 15    # For example,
 16    #
 17    #   select("post", "category", Post::CATEGORIES, {:include_blank => true})
 18    #
 19    # could become:
 20    #
 21    #   <select name="post[category]">
 22    #     <option></option>
 23    #     <option>joke</option>
 24    #     <option>poem</option>
 25    #   </select>
 26    #
 27    # Another common case is a select tag for an <tt>belongs_to</tt>-associated object.
 28    #
 29    # Example with @post.person_id => 2:
 30    #
 31    #   select("post", "person_id", Person.all.collect {|p| [ p.name, p.id ] }, {:include_blank => 'None'})
 32    #
 33    # could become:
 34    #
 35    #   <select name="post[person_id]">
 36    #     <option value="">None</option>
 37    #     <option value="1">David</option>
 38    #     <option value="2" selected="selected">Sam</option>
 39    #     <option value="3">Tobias</option>
 40    #   </select>
 41    #
 42    # * <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.
 43    #
 44    # Example:
 45    #
 46    #   select("post", "person_id", Person.all.collect {|p| [ p.name, p.id ] }, {:prompt => 'Select Person'})
 47    #
 48    # could become:
 49    #
 50    #   <select name="post[person_id]">
 51    #     <option value="">Select Person</option>
 52    #     <option value="1">David</option>
 53    #     <option value="2">Sam</option>
 54    #     <option value="3">Tobias</option>
 55    #   </select>
 56    #
 57    # 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
 58    # option to be in the +html_options+ parameter.
 59    #
 60    # Example:
 61    #
 62    #   select("album[]", "genre", %w[rap rock country], {}, { :index => nil })
 63    #
 64    # becomes:
 65    #
 66    #   <select name="album[][genre]" id="album__genre">
 67    #     <option value="rap">rap</option>
 68    #     <option value="rock">rock</option>
 69    #     <option value="country">country</option>
 70    #   </select>
 71    #
 72    # * <tt>:disabled</tt> - can be a single value or an array of values that will be disabled options in the final output.
 73    #
 74    # Example:
 75    #
 76    #   select("post", "category", Post::CATEGORIES, {:disabled => 'restricted'})
 77    #
 78    # could become:
 79    #
 80    #   <select name="post[category]">
 81    #     <option></option>
 82    #     <option>joke</option>
 83    #     <option>poem</option>
 84    #     <option disabled="disabled">restricted</option>
 85    #   </select>
 86    #
 87    # 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.
 88    #
 89    # Example:
 90    #
 91    #   collection_select(:post, :category_id, Category.all, :id, :name, {:disabled => lambda{|category| category.archived? }})
 92    #
 93    # If the categories "2008 stuff" and "Christmas" return true when the method <tt>archived?</tt> is called, this would return:
 94    #   <select name="post[category_id]">
 95    #     <option value="1" disabled="disabled">2008 stuff</option>
 96    #     <option value="2" disabled="disabled">Christmas</option>
 97    #     <option value="3">Jokes</option>
 98    #     <option value="4">Poems</option>
 99    #   </select>
100    #
101    module FormOptionsHelper
102      # ERB::Util can mask some helpers like textilize. Make sure to include them.
103      include ERB::Util
104      include TextHelper
105
106      # Create a select tag and a series of contained option tags for the provided object and method.
107      # The option currently held by the object will be selected, provided that the object is available.
108      # See options_for_select for the required format of the choices parameter.
109      #
110      # Example with @post.person_id => 1:
111      #   select("post", "person_id", Person.all.collect {|p| [ p.name, p.id ] }, { :include_blank => true })
112      #
113      # could become:
114      #
115      #   <select name="post[person_id]">
116      #     <option value=""></option>
117      #     <option value="1" selected="selected">David</option>
118      #     <option value="2">Sam</option>
119      #     <option value="3">Tobias</option>
120      #   </select>
121      #
122      # This can be used to provide a default set of options in the standard way: before rendering the create form, a
123      # new model instance is assigned the default options and bound to @model_name. Usually this model is not saved
124      # to the database. Instead, a second model object is created when the create request is received.
125      # This allows the user to submit a form page more than once with the expected results of creating multiple records.
126      # In addition, this allows a single partial to be used to generate form inputs for both edit and create forms.
127      #
128      # By default, <tt>post.person_id</tt> is the selected option.  Specify <tt>:selected => value</tt> to use a different selection
129      # or <tt>:selected => nil</tt> to leave all options unselected. Similarly, you can specify values to be disabled in the option
130      # tags by specifying the <tt>:disabled</tt> option. This can either be a single value or an array of values to be disabled.
131      def select(object, method, choices, options = {}, html_options = {})
132        InstanceTag.new(object, method, self, options.delete(:object)).to_select_tag(choices, options, html_options)
133      end
134
135      # Returns <tt><select></tt> and <tt><option></tt> tags for the collection of existing return values of
136      # +method+ for +object+'s class. The value returned from calling +method+ on the instance +object+ will
137      # be selected. If calling +method+ returns +nil+, no selection is made without including <tt>:prompt</tt>
138      # or <tt>:include_blank</tt> in the +options+ hash.
139      #
140      # The <tt>:value_method</tt> and <tt>:text_method</tt> parameters are methods to be called on each member
141      # of +collection+. The return values are used as the +value+ attribute and contents of each
142      # <tt><option></tt> tag, respectively.
143      #
144      # Example object structure for use with this method:
145      #   class Post < ActiveRecord::Base
146      #     belongs_to :author
147      #   end
148      #   class Author < ActiveRecord::Base
149      #     has_many :posts
150      #     def name_with_initial
151      #       "#{first_name.first}. #{last_name}"
152      #     end
153      #   end
154      #
155      # Sample usage (selecting the associated Author for an instance of Post, <tt>@post</tt>):
156      #   collection_select(:post, :author_id, Author.all, :id, :name_with_initial, :prompt => true)
157      #
158      # If <tt>@post.author_id</tt> is already <tt>1</tt>, this would return:
159      #   <select name="post[author_id]">
160      #     <option value="">Please select</option>
161      #     <option value="1" selected="selected">D. Heinemeier Hansson</option>
162      #     <option value="2">D. Thomas</option>
163      #     <option value="3">M. Clark</option>
164      #   </select>
165      def collection_select(object, method, collection, value_method, text_method, options = {}, html_options = {})
166        InstanceTag.new(object, method, self, options.delete(:object)).to_collection_select_tag(collection, value_method, text_method, options, html_options)
167      end
168
169
170      # Returns <tt><select></tt>, <tt><optgroup></tt> and <tt><option></tt> tags for the collection of existing return values of
171      # +method+ for +object+'s class. The value returned from calling +method+ on the instance +object+ will
172      # be selected. If calling +method+ returns +nil+, no selection is made without including <tt>:prompt</tt>
173      # or <tt>:include_blank</tt> in the +options+ hash.
174      #
175      # Parameters:
176      # * +object+ - The instance of the class to be used for the select tag
177      # * +method+ - The attribute of +object+ corresponding to the select tag
178      # * +collection+ - An array of objects representing the <tt><optgroup></tt> tags.
179      # * +group_method+ - The name of a method which, when called on a member of +collection+, returns an
180      #   array of child objects representing the <tt><option></tt> tags.
181      # * +group_label_method+ - The name of a method which, when called on a member of +collection+, returns a
182      #   string to be used as the +label+ attribute for its <tt><optgroup></tt> tag.
183      # * +option_key_method+ - The name of a method which, when called on a child object of a member of
184      #   +collection+, returns a value to be used as the +value+ attribute for its <tt><option></tt> tag.
185      # * +option_value_method+ - The name of a method which, when called on a child object of a member of
186      #   +collection+, returns a value to be used as the contents of its <tt><option></tt> tag.
187      #
188      # Example object structure for use with this method:
189      #   class Continent < ActiveRecord::Base
190      #     has_many :countries
191      #     # attribs: id, name
192      #   end
193      #   class Country < ActiveRecord::Base
194      #     belongs_to :continent
195      #     # attribs: id, name, continent_id
196      #   end
197      #   class City < ActiveRecord::Base
198      #     belongs_to :country
199      #     # attribs: id, name, country_id
200      #   end
201      #
202      # Sample usage:
203      #   grouped_collection_select(:city, :country_id, @continents, :countries, :name, :id, :name)
204      #
205      # Possible output:
206      #   <select name="city[country_id]">
207      #     <optgroup label="Africa">
208      #       <option value="1">South Africa</option>
209      #       <option value="3">Somalia</option>
210      #     </optgroup>
211      #     <optgroup label="Europe">
212      #       <option value="7" selected="selected">Denmark</option>
213      #       <option value="2">Ireland</option>
214      #     </optgroup>
215      #   </select>
216      #
217      def grouped_collection_select(object, method, collection, group_method, group_label_method, option_key_method, option_value_method, options = {}, html_options = {})
218        InstanceTag.new(object, method, self, options.delete(:object)).to_grouped_collection_select_tag(collection, group_method, group_label_method, option_key_method, option_value_method, options, html_options)
219      end
220
221      # Return select and option tags for the given object and method, using
222      # #time_zone_options_for_select to generate the list of option tags.
223      #
224      # In addition to the <tt>:include_blank</tt> option documented above,
225      # this method also supports a <tt>:model</tt> option, which defaults
226      # to ActiveSupport::TimeZone. This may be used by users to specify a
227      # different time zone model object. (See +time_zone_options_for_select+
228      # for more information.)
229      #
230      # You can also supply an array of ActiveSupport::TimeZone objects
231      # as +priority_zones+, so that they will be listed above the rest of the
232      # (long) list. (You can use ActiveSupport::TimeZone.us_zones as a convenience
233      # for obtaining a list of the US time zones, or a Regexp to select the zones
234      # of your choice)
235      #
236      # Finally, this method supports a <tt>:default</tt> option, which selects
237      # a default ActiveSupport::TimeZone if the object's time zone is +nil+.
238      #
239      # Examples:
240      #   time_zone_select( "user", "time_zone", nil, :include_blank => true)
241      #
242      #   time_zone_select( "user", "time_zone", nil, :default => "Pacific Time (US & Canada)" )
243      #
244      #   time_zone_select( "user", 'time_zone', ActiveSupport::TimeZone.us_zones, :default => "Pacific Time (US & Canada)")
245      #
246      #   time_zone_select( "user", 'time_zone', [ ActiveSupport::TimeZone['Alaska'], ActiveSupport::TimeZone['Hawaii'] ])
247      #
248      #   time_zone_select( "user", 'time_zone', /Australia/)
249      #
250      #   time_zone_select( "user", "time_zone", ActiveSupport::Timezone.all.sort, :model => ActiveSupport::Timezone)
251      def time_zone_select(object, method, priority_zones = nil, options = {}, html_options = {})
252        InstanceTag.new(object, method, self,  options.delete(:object)).to_time_zone_select_tag(priority_zones, options, html_options)
253      end
254
255      # Accepts a container (hash, array, enumerable, your type) and returns a string of option tags. Given a container
256      # where the elements respond to first and last (such as a two-element array), the "lasts" serve as option values and
257      # the "firsts" as option text. Hashes are turned into this form automatically, so the keys become "firsts" and values
258      # become lasts. If +selected+ is specified, the matching "last" or element will get the selected option-tag.  +selected+
259      # may also be an array of values to be selected when using a multiple select.
260      #
261      # Examples (call, result):
262      #   options_for_select([["Dollar", "$"], ["Kroner", "DKK"]])
263      #     <option value="$">Dollar</option>\n<option value="DKK">Kroner</option>
264      #
265      #   options_for_select([ "VISA", "MasterCard" ], "MasterCard")
266      #     <option>VISA</option>\n<option selected="selected">MasterCard</option>
267      #
268      #   options_for_select({ "Basic" => "$20", "Plus" => "$40" }, "$40")
269      #     <option value="$20">Basic</option>\n<option value="$40" selected="selected">Plus</option>
270      #
271      #   options_for_select([ "VISA", "MasterCard", "Discover" ], ["VISA", "Discover"])
272      #     <option selected="selected">VISA</option>\n<option>MasterCard</option>\n<option selected="selected">Discover</option>
273      #
274      # You can optionally provide html attributes as the last element of the array.
275      #
276      # Examples:
277      #   options_for_select([ "Denmark", ["USA", {:class=>'bold'}], "Sweden" ], ["USA", "Sweden"])
278      #     <option value="Denmark">Denmark</option>\n<option value="USA" class="bold" selected="selected">USA</option>\n<option value="Sweden" selected="selected">Sweden</option>
279      #
280      #   options_for_select([["Dollar", "$", {:class=>"bold"}], ["Kroner", "DKK", {:onclick => "alert('HI');"}]])
281      #     <option value="$" class="bold">Dollar</option>\n<option value="DKK" onclick="alert('HI');">Kroner</option>
282      #
283      # If you wish to specify disabled option tags, set +selected+ to be a hash, with <tt>:disabled</tt> being either a value
284      # or array of values to be disabled. In this case, you can use <tt>:selected</tt> to specify selected option tags.
285      #
286      # Examples:
287      #   options_for_select(["Free", "Basic", "Advanced", "Super Platinum"], :disabled => "Super Platinum")
288      #     <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>
289      #
290      #   options_for_select(["Free", "Basic", "Advanced", "Super Platinum"], :disabled => ["Advanced", "Super Platinum"])
291      #     <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>
292      #
293      #   options_for_select(["Free", "Basic", "Advanced", "Super Platinum"], :selected => "Free", :disabled => "Super Platinum")
294      #     <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>
295      #
296      # NOTE: Only the option tags are returned, you have to wrap this call in a regular HTML select tag.
297      def options_for_select(container, selected = nil)
298        return container if String === container
299
300        container = container.to_a if Hash === container
301        selected, disabled = extract_selected_and_disabled(selected).map do | r |
302           Array.wrap(r).map(&:to_s)
303        end
304
305        container.map do |element|
306          html_attributes = option_html_attributes(element)
307          text, value = option_text_and_value(element).map(&:to_s)
308          selected_attribute = ' selected="selected"' if option_value_selected?(value, selected)
309          disabled_attribute = ' disabled="disabled"' if disabled && option_value_selected?(value, disabled)
310          %(<option value="#{html_escape(value)}"#{selected_attribute}#{disabled_attribute}#{html_attributes}>#{html_escape(text)}</option>)
311        end.join("\n").html_safe
312
313      end
314
315      # Returns a string of option tags that have been compiled by iterating over the +collection+ and assigning the
316      # the result of a call to the +value_method+ as the option value and the +text_method+ as the option text.
317      # Example:
318      #   options_from_collection_for_select(@people, 'id', 'name')
319      # This will output the same HTML as if you did this:
320      #   <option value="#{person.id}">#{person.name}</option>
321      #
322      # This is more often than not used inside a #select_tag like this example:
323      #   select_tag 'person', options_from_collection_for_select(@people, 'id', 'name')
324      #
325      # If +selected+ is specified as a value or array of values, the element(s) returning a match on +value_method+
326      # will be selected option tag(s).
327      #
328      # If +selected+ is specified as a Proc, those members of the collection that return true for the anonymous
329      # function are the selected values.
330      #
331      # +selected+ can also be a hash, specifying both <tt>:selected</tt> and/or <tt>:disabled</tt> values as required.
332      #
333      # Be sure to specify the same class as the +value_method+ when specifying selected or disabled options.
334      # Failure to do this will produce undesired results. Example:
335      #   options_from_collection_for_select(@people, 'id', 'name', '1')
336      # Will not select a person with the id of 1 because 1 (an Integer) is not the same as '1' (a string)
337      #   options_from_collection_for_select(@people, 'id', 'name', 1)
338      # should produce the desired results.
339      def options_from_collection_for_select(collection, value_method, text_method, selected = nil)
340        options = collection.map do |element|
341          [element.send(text_method), element.send(value_method)]
342        end
343        selected, disabled = extract_selected_and_disabled(selected)
344        select_deselect = {}
345        select_deselect[:selected] = extract_values_from_collection(collection, value_method, selected)
346        select_deselect[:disabled] = extract_values_from_collection(collection, value_method, disabled)
347
348        options_for_select(options, select_deselect)
349      end
350
351      # Returns a string of <tt><option></tt> tags, like <tt>options_from_collection_for_select</tt>, but
352      # groups them by <tt><optgroup></tt> tags based on the object relationships of the arguments.
353      #
354      # Parameters:
355      # * +collection+ - An array of objects representing the <tt><optgroup></tt> tags.
356      # * +group_method+ - The name of a method which, when called on a member of +collection+, returns an
357      #   array of child objects representing the <tt><option></tt> tags.
358      # * group_label_method+ - The name of a method which, when called on a member of +collection+, returns a
359      #   string to be used as the +label+ attribute for its <tt><optgroup></tt> tag.
360      # * +option_key_method+ - The name of a method which, when called on a child object of a member of
361      #   +collection+, returns a value to be used as the +value+ attribute for its <tt><option></tt> tag.
362      # * +option_value_method+ - The name of a method which, when called on a child object of a member of
363      #   +collection+, returns a value to be used as the contents of its <tt><option></tt> tag.
364      # * +selected_key+ - A value equal to the +value+ attribute for one of the <tt><option></tt> tags,
365      #   which will have the +selected+ attribute set. Corresponds to the return value of one of the calls
366      #   to +option_key_method+. If +nil+, no selection is made. Can also be a hash if disabled values are
367      #   to be specified.
368      #
369      # Example object structure for use with this method:
370      #   class Continent < ActiveRecord::Base
371      #     has_many :countries
372      #     # attribs: id, name
373      #   end
374      #   class Country < ActiveRecord::Base
375      #     belongs_to :continent
376      #     # attribs: id, name, continent_id
377      #   end
378      #
379      # Sample usage:
380      #   option_groups_from_collection_for_select(@continents, :countries, :name, :id, :name, 3)
381      #
382      # Possible output:
383      #   <optgroup label="Africa">
384      #     <option value="1">Egypt</option>
385      #     <option value="4">Rwanda</option>
386      #     ...
387      #   </optgroup>
388      #   <optgroup label="Asia">
389      #     <option value="3" selected="selected">China</option>
390      #     <option value="12">India</option>
391      #     <option value="5">Japan</option>
392      #     ...
393      #   </optgroup>
394      #
395      # <b>Note:</b> Only the <tt><optgroup></tt> and <tt><option></tt> tags are returned, so you still have to
396      # wrap the output in an appropriate <tt><select></tt> tag.
397      def option_groups_from_collection_for_select(collection, group_method, group_label_method, option_key_method, option_value_method, selected_key = nil)
398        collection.inject("") do |options_for_select, group|
399          group_label_string = eval("group.#{group_label_method}")
400          options_for_select += "<optgroup label=\"#{html_escape(group_label_string)}\">"
401          options_for_select += options_from_collection_for_select(eval("group.#{group_method}"), option_key_method, option_value_method, selected_key)
402          options_for_select += '</optgroup>'
403        end.html_safe
404      end
405
406      # Returns a string of <tt><option></tt> tags, like <tt>options_for_select</tt>, but
407      # wraps them with <tt><optgroup></tt> tags.
408      #
409      # Parameters:
410      # * +grouped_options+ - Accepts a nested array or hash of strings.  The first value serves as the
411      #   <tt><optgroup></tt> label while the second value must be an array of options. The second value can be a
412      #   nested array of text-value pairs. See <tt>options_for_select</tt> for more info.
413      #    Ex. ["North America",[["United States","US"],["Canada","CA"]]]
414      # * +selected_key+ - A value equal to the +value+ attribute for one of the <tt><option></tt> tags,
415      #   which will have the +selected+ attribute set. Note: It is possible for this value to match multiple options
416      #   as you might have the same option in multiple groups.  Each will then get <tt>selected="selected"</tt>.
417      # * +prompt+ - set to true or a prompt string. When the select element doesn't have a value yet, this
418      #   prepends an option with a generic prompt - "Please select" - or the given prompt string.
419      #
420      # Sample usage (Array):
421      #   grouped_options = [
422      #    ['North America',
423      #      [['United States','US'],'Canada']],
424      #    ['Europe',
425      #      ['Denmark','Germany','France']]
426      #   ]
427      #   grouped_options_for_select(grouped_options)
428      #
429      # Sample usage (Hash):
430      #   grouped_options = {
431      #    'North America' => [['United States','US], 'Canada'],
432      #    'Europe' => ['Denmark','Germany','France']
433      #   }
434      #   grouped_options_for_select(grouped_options)
435      #
436      # Possible output:
437      #   <optgroup label="Europe">
438      #     <option value="Denmark">Denmark</option>
439      #     <option value="Germany">Germany</option>
440      #     <option value="France">France</option>
441      #   </optgroup>
442      #   <optgroup label="North America">
443      #     <option value="US">United States</option>
444      #     <option value="Canada">Canada</option>
445      #   </optgroup>
446      #
447      # <b>Note:</b> Only the <tt><optgroup></tt> and <tt><option></tt> tags are returned, so you still have to
448      # wrap the output in an appropriate <tt><select></tt> tag.
449      def grouped_options_for_select(grouped_options, selected_key = nil, prompt = nil)
450        body = ''
451        body << content_tag(:option, prompt, { :value => "" }, true) if prompt
452
453        grouped_options = grouped_options.sort if grouped_options.is_a?(Hash)
454
455        grouped_options.each do |group|
456          body << content_tag(:optgroup, options_for_select(group[1], selected_key), :label => group[0])
457        end
458
459        body.html_safe
460      end
461
462      # Returns a string of option tags for pretty much any time zone in the
463      # world. Supply a ActiveSupport::TimeZone name as +selected+ to have it
464      # marked as the selected option tag. You can also supply an array of
465      # ActiveSupport::TimeZone objects as +priority_zones+, so that they will
466      # be listed above the rest of the (long) list. (You can use
467      # ActiveSupport::TimeZone.us_zones as a convenience for obtaining a list
468      # of the US time zones, or a Regexp to select the zones of your choice)
469      #
470      # The +selected+ parameter must be either +nil+, or a string that names
471      # a ActiveSupport::TimeZone.
472      #
473      # By default, +model+ is the ActiveSupport::TimeZone constant (which can
474      # be obtained in Active Record as a value object). The only requirement
475      # is that the +model+ parameter be an object that responds to +all+, and
476      # returns an array of objects that represent time zones.
477      #
478      # NOTE: Only the option tags are returned, you have to wrap this call in
479      # a regular HTML select tag.
480      def time_zone_options_for_select(selected = nil, priority_zones = nil, model = ::ActiveSupport::TimeZone)
481        zone_options = ""
482
483        zones = model.all
484        convert_zones = lambda { |list| list.map { |z| [ z.to_s, z.name ] } }
485
486        if priority_zones
487	        if priority_zones.is_a?(Regexp)
488            priority_zones = model.all.find_all {|z| z =~ priority_zones}
489	        end
490          zone_options += options_for_select(convert_zones[priority_zones], selected)
491          zone_options += "<option value=\"\" disabled=\"disabled\">-------------</option>\n"
492
493          zones = zones.reject { |z| priority_zones.include?( z ) }
494        end
495
496        zone_options += options_for_select(convert_zones[zones], selected)
497        zone_options.html_safe
498      end
499
500      private
501        def option_html_attributes(element)
502          return "" unless Array === element
503          html_attributes = []
504          element.select { |e| Hash === e }.reduce({}, :merge).each do |k, v|
505            html_attributes << " #{k}=\"#{html_escape(v.to_s)}\""
506          end
507          html_attributes.join
508        end
509
510        def option_text_and_value(option)
511          # Options are [text, value] pairs or strings used for both.
512          case
513          when Array === option
514            option = option.reject { |e| Hash === e }
515            [option.first, option.last]
516          when !option.is_a?(String) && option.respond_to?(:first) && option.respond_to?(:last)
517            [option.first, option.last]
518          else
519            [option, option]
520          end
521        end
522
523        def option_value_selected?(value, selected)
524          if selected.respond_to?(:include?) && !selected.is_a?(String)
525            selected.include? value
526          else
527            value == selected
528          end
529        end
530
531        def extract_selected_and_disabled(selected)
532          if selected.is_a?(Proc)
533            [ selected, nil ]
534          else
535            selected = Array.wrap(selected)
536            options = selected.extract_options!.symbolize_keys
537            [ options[:selected] || selected , options[:disabled] ]
538          end
539        end
540
541        def extract_values_from_collection(collection, value_method, selected)
542          if selected.is_a?(Proc)
543            collection.map do |element|
544              element.send(value_method) if selected.call(element)
545            end.compact
546          else
547            selected
548          end
549        end
550    end
551
552    class InstanceTag #:nodoc:
553      include FormOptionsHelper
554
555      def to_select_tag(choices, options, html_options)
556        html_options = html_options.stringify_keys
557        add_default_name_and_id(html_options)
558        value = value(object)
559        selected_value = options.has_key?(:selected) ? options[:selected] : value
560        disabled_value = options.has_key?(:disabled) ? options[:disabled] : nil
561        content_tag("select", add_options(options_for_select(choices, :selected => selected_value, :disabled => disabled_value), options, selected_value), html_options)
562      end
563
564      def to_collection_select_tag(collection, value_method, text_method, options, html_options)
565        html_options = html_options.stringify_keys
566        add_default_name_and_id(html_options)
567        value = value(object)
568        disabled_value = options.has_key?(:disabled) ? options[:disabled] : nil
569        selected_value = options.has_key?(:selected) ? options[:selected] : value
570        content_tag(
571          "select", add_options(options_from_collection_for_select(collection, value_method, text_method, :selected => selected_value, :disabled => disabled_value), options, value), html_options
572        )
573      end
574
575      def to_grouped_collection_select_tag(collection, group_method, group_label_method, option_key_method, option_value_method, options, html_options)
576        html_options = html_options.stringify_keys
577        add_default_name_and_id(html_options)
578        value = value(object)
579        content_tag(
580          "select", add_options(option_groups_from_collection_for_select(collection, group_method, group_label_method, option_key_method, option_value_method, value), options, value), html_options
581        )
582      end
583
584      def to_time_zone_select_tag(priority_zones, options, html_options)
585        html_options = html_options.stringify_keys
586        add_default_name_and_id(html_options)
587        value = value(object)
588        content_tag("select",
589          add_options(
590            time_zone_options_for_select(value || options[:default], priority_zones, options[:model] || ActiveSupport::TimeZone),
591            options, value
592          ), html_options
593        )
594      end
595
596      private
597        def add_options(option_tags, options, value = nil)
598          if options[:include_blank]
599            option_tags = "<option value=\"\">#{html_escape(options[:include_blank]) if options[:include_blank].kind_of?(String)}</option>\n" + option_tags
600          end
601          if value.blank? && options[:prompt]
602            prompt = options[:prompt].kind_of?(String) ? options[:prompt] : I18n.translate('helpers.select.prompt', :default => 'Please select')
603            option_tags = "<option value=\"\">#{html_escape(prompt)}</option>\n" + option_tags
604          end
605          option_tags.html_safe
606        end
607    end
608
609    class FormBuilder
610      def select(method, choices, options = {}, html_options = {})
611        @template.select(@object_name, method, choices, objectify_options(options), @default_options.merge(html_options))
612      end
613
614      def collection_select(method, collection, value_method, text_method, options = {}, html_options = {})
615        @template.collection_select(@object_name, method, collection, value_method, text_method, objectify_options(options), @default_options.merge(html_options))
616      end
617
618      def grouped_collection_select(method, collection, group_method, group_label_method, option_key_method, option_value_method, options = {}, html_options = {})
619        @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))
620      end
621
622      def time_zone_select(method, priority_zones = nil, options = {}, html_options = {})
623        @template.time_zone_select(@object_name, method, priority_zones, objectify_options(options), @default_options.merge(html_options))
624      end
625    end
626  end
627end