Skip to content
This repository
Fetching contributors…

Octocat-spinner-32-eaf2f5

Cannot retrieve contributors at this time

file 238 lines (221 sloc) 11.194 kb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238
module Formtastic
  module Inputs
    # A select input is used to render a `<select>` tag with a series of options to choose from.
    # It works for both single selections (like a `belongs_to` relationship, or "yes/no" boolean),
    # as well as multiple selections (like a `has_and_belongs_to_many`/`has_many` relationship,
    # for assigning many genres to a song, for example).
    #
    # This is the default input choice when:
    #
    # * the database column type is an `:integer` and there is an association (`belongs_to`)
    # * the database column type is a `:string` and the `:collection` option is used
    # * there an object with an association, but no database column on the object (`has_many`, etc)
    # * there is no object and the `:collection` option is used
    #
    # The flexibility of the `:collection` option (see examples) makes the :select input viable as
    # an alternative for many other input types. For example, instead of...
    #
    # * a `:string` input (where you want to force the user to choose from a few specific strings rather than entering anything)
    # * a `:boolean` checkbox input (where the user could choose yes or no, rather than checking a box)
    # * a `:date`, `:time` or `:datetime` input (where the user could choose from pre-selected dates)
    # * a `:number` input (where the user could choose from a set of pre-defined numbers)
    # * a `:time_zone` input (where you want to provide your own set of choices instead of relying on Rails)
    # * a `:country` input (no need for a plugin really)
    #
    # Within the standard `<li>` wrapper, the output is a `<label>` tag followed by a `<select>`
    # tag containing `<option>` tags.
    #
    # For inputs that map to associations on the object model, Formtastic will automatically load
    # in a collection of objects on the association as options to choose from. This might be an
    # `Author.all` on a `Post` form with an input for a `belongs_to :user` association, or a
    # `Tag.all` for a `Post` form with an input for a `has_and_belongs_to_many :tags` association.
    # You can override or customise this collection and the `<option>` tags it will render through
    # the `:collection` option (see examples).
    #
    # The way on which Formtastic renders the `value` attribute and content of each `<option>` tag
    # is customisable through the `:member_label` and `:member_value` options. When not provided,
    # we fall back to a list of methods to try on each object such as `:to_label`, `:name` and
    # `:to_s`, which are defined in the configurations `collection_label_methods` and
    # `collection_value_methods` (see examples below).
    #
    # @example Basic `belongs_to` example with full form context
    #
    # <%= semantic_form_for @post do |f| %>
    # <%= f.inputs do %>
    # <%= f.input :author, :as => :select %>
    # <% end %>
    # <% end %>
    #
    # <form...>
    # <fieldset>
    # <ol>
    # <li class='select'>
    # <label for="post_author_id">Author</label>
    # <select id="post_author_id" name="post[post_author_id]">
    # <option value=""></option>
    # <option value="1">Justin</option>
    # <option value="3">Kate</option>
    # <option value="2">Amelia</option>
    # </select>
    # </li>
    # </ol>
    # </fieldset>
    # </form>
    #
    # @example Basic `has_many` or `has_and_belongs_to_many` example with full form context
    #
    # <%= semantic_form_for @post do |f| %>
    # <%= f.inputs do %>
    # <%= f.input :tags, :as => :select %>
    # <% end %>
    # <% end %>
    #
    # <form...>
    # <fieldset>
    # <ol>
    # <li class='select'>
    # <label for="post_tag_ids">Author</label>
    # <select id="post_tag_ids" name="post[tag_ids]" multiple="true">
    # <option value="1">Ruby</option>
    # <option value="6">Rails</option>
    # <option value="3">Forms</option>
    # <option value="4">Awesome</option>
    # </select>
    # </li>
    # </ol>
    # </fieldset>
    # </form>
    #
    # @example Override Formtastic's assumption on when you need a multi select
    # <%= f.input :authors, :as => :select, :input_html => { :multiple => true } %>
    # <%= f.input :authors, :as => :select, :input_html => { :multiple => false } %>
    #
    # @example The `:collection` option can be used to customize the choices
    # <%= f.input :author, :as => :select, :collection => @authors %>
    # <%= f.input :author, :as => :select, :collection => Author.all %>
    # <%= f.input :author, :as => :select, :collection => Author.some_named_scope %>
    # <%= f.input :author, :as => :select, :collection => [Author.find_by_login("justin"), Category.find_by_name("kate")] %>
    # <%= f.input :author, :as => :select, :collection => ["Justin", "Kate"] %>
    # <%= f.input :author, :as => :select, :collection => [["Justin", "justin"], ["Kate", "kate"]] %>
    # <%= f.input :author, :as => :select, :collection => [["Justin", "1"], ["Kate", "3"]] %>
    # <%= f.input :author, :as => :select, :collection => [["Justin", 1], ["Kate", 3]] %>
    # <%= f.input :author, :as => :select, :collection => 1..5 %>
    # <%= f.input :author, :as => :select, :collection => "<option>your own options HTML string</option>" %>
    # <%= f.input :author, :as => :select, :collection => options_for_select(...) %>
    # <%= f.input :author, :as => :select, :collection => options_from_collection_for_select(...) %>
    # <%= f.input :author, :as => :select, :collection => grouped_options_for_select(...) %>
    # <%= f.input :author, :as => :select, :collection => time_zone_options_for_select(...) %>
    #
    # @example The `:member_label` can be used to call a different method (or a Proc) on each object in the collection for rendering the label text (it'll try the methods like `to_s` in `collection_label_methods` config by default)
    # <%= f.input :author, :as => :select, :member_label => :name %>
    # <%= f.input :author, :as => :select, :member_label => :name_with_post_count %>
    # <%= f.input :author, :as => :select, :member_label => Proc.new { |a| "#{c.name} (#{pluralize("post", a.posts.count)})" } %>
    #
    # @example The `:member_value` can be used to call a different method (or a Proc) on each object in the collection for rendering the value for each checkbox (it'll try the methods like `id` in `collection_value_methods` config by default)
    # <%= f.input :author, :as => :select, :member_value => :login %>
    # <%= f.input :author, :as => :select, :member_value => Proc.new { |c| c.full_name.downcase.underscore } %>
    #
    # @example Set HTML attributes on the `<select>` tag with `:input_html`
    # <%= f.input :authors, :as => :select, :input_html => { :size => 20, :multiple => true, :class => "special" } %>
    #
    # @example Set HTML attributes on the `<li>` wrapper with `:wrapper_html`
    # <%= f.input :authors, :as => :select, :wrapper_html => { :class => "special" } %>
    #
    # @example Exclude, include, or customize the blank option at the top of the select. Always shown, even if the field already has a value. Suitable for optional inputs.
    # <%= f.input :author, :as => :select, :include_blank => false %>
    # <%= f.input :author, :as => :select, :include_blank => true %> => <option value=""></option>
    # <%= f.input :author, :as => :select, :include_blank => "No author" %>
    #
    # @example Exclude, include, or customize the prompt at the top of the select. Only shown if the field does not have a value. Suitable for required inputs.
    # <%= f.input :author, :as => :select, :prompt => false %>
    # <%= f.input :author, :as => :select, :prompt => true %> => <option value="">Please select</option>
    # <%= f.input :author, :as => :select, :prompt => "Please select an author" %>
    #
    #
    # @example Group options an `<optgroup>` with the `:group_by` and `:group_label` options (`belongs_to` associations only)
    # <%= f.input :author, :as => :select, :group_by => :continent %>
    #
    # @see Formtastic::Helpers::InputsHelper#input InputsHelper#input for full documentation of all possible options.
    # @see Formtastic::Inputs::CheckBoxesInput CheckBoxesInput as an alternative for `has_many` and `has_and_belongs_to_many` associations
    # @see Formtastic::Inputs::RadioInput RadioInput as an alternative for `belongs_to` associations
    #
    # @todo Do/can we support the per-item HTML options like RadioInput?
    class SelectInput
      include Base
      include Base::Collections
      include Base::GroupedCollections

      def to_html
        input_wrapping do
          hidden_input <<
          label_html <<
          (options[:group_by] ? grouped_select_html : select_html)
        end
      end

      def select_html
        builder.select(input_name, collection, input_options, input_html_options)
      end

      def grouped_select_html
        builder.grouped_collection_select(
          input_name,
          grouped_collection,
          group_association,
          group_label_method,
          value_method,
          label_method,
          input_options,
          input_html_options
        )
      end

      def include_blank
        options.key?(:include_blank) ? options[:include_blank] : (single? && builder.include_blank_for_select_by_default)
      end
      
      def hidden_input
        if multiple?
          template.hidden_field_tag(input_html_options_name_multiple, '', :id => nil)
        else
          "".html_safe
        end
      end

      def prompt?
        !!options[:prompt]
      end

      def label_html_options
        super.merge(:for => input_html_options[:id])
      end

      def input_options
        super.merge :include_blank => (include_blank unless prompt?)
      end

      def input_html_options
        extra_input_html_options.merge(super)
      end
      
      def extra_input_html_options
        {
          :multiple => multiple?,
          :name => multiple? ? input_html_options_name_multiple : input_html_options_name
        }
      end
      
      def input_html_options_name
        if builder.options.key?(:index)
          "#{object_name}[#{builder.options[:index]}][#{association_primary_key}]"
        else
          "#{object_name}[#{association_primary_key}]"
        end
      end

      def input_html_options_name_multiple
        input_html_options_name + "[]"
      end

      def multiple_by_association?
        reflection && [ :has_many, :has_and_belongs_to_many ].include?(reflection.macro)
      end

      def multiple_by_options?
        options[:multiple] || (options[:input_html] && options[:input_html][:multiple])
      end

      def multiple?
        multiple_by_options? || multiple_by_association?
      end

      def single?
        !multiple?
      end

    end
  end
end
Something went wrong with that request. Please try again.