form_helper.rb 34.4 KB
Newer Older
D
Initial  
David Heinemeier Hansson 已提交
1
require 'cgi'
2 3
require 'action_view/helpers/date_helper'
require 'action_view/helpers/tag_helper'
4
require 'action_view/helpers/form_tag_helper'
D
Initial  
David Heinemeier Hansson 已提交
5 6 7

module ActionView
  module Helpers
8 9 10 11
    # Form helpers are designed to make working with models much easier compared to using just standard HTML
    # elements by providing a set of methods for creating forms based on your models. This helper generates the HTML
    # for forms, providing a method for each sort of input (e.g., text, password, select, and so on). When the form
    # is submitted (i.e., when the user hits the submit button or <tt>form.submit</tt> is called via JavaScript), the form inputs will be bundled into the <tt>params</tt> object and passed back to the controller.
D
David Heinemeier Hansson 已提交
12
    #
13
    # There are two types of form helpers: those that specifically work with model attributes and those that don't.
14 15
    # This helper deals with those that work with model attributes; to see an example of form helpers that don't work
    # with model attributes, check the ActionView::Helpers::FormTagHelper documentation.
D
David Heinemeier Hansson 已提交
16
    #
17
    # The core method of this helper, form_for, gives you the ability to create a form for a model instance;
18
    # for example, let's say that you have a model <tt>Person</tt> and want to create a new instance of it:
D
Initial  
David Heinemeier Hansson 已提交
19
    #
20
    #     # Note: a @person variable will have been created in the controller.
21 22
    #     # For example: @person = Person.new
    #     <% form_for :person, @person, :url => { :action => "create" } do |f| %>
D
David Heinemeier Hansson 已提交
23 24
    #       <%= f.text_field :first_name %>
    #       <%= f.text_field :last_name %>
25
    #       <%= submit_tag 'Create' %>
D
David Heinemeier Hansson 已提交
26
    #     <% end %>
D
Initial  
David Heinemeier Hansson 已提交
27
    #
28
    # The HTML generated for this would be:
D
Initial  
David Heinemeier Hansson 已提交
29
    #
30
    #     <form action="/persons/create" method="post">
D
David Heinemeier Hansson 已提交
31 32
    #       <input id="person_first_name" name="person[first_name]" size="30" type="text" />
    #       <input id="person_last_name" name="person[last_name]" size="30" type="text" />
33
    #       <input name="commit" type="submit" value="Create" />
D
David Heinemeier Hansson 已提交
34
    #     </form>
D
Initial  
David Heinemeier Hansson 已提交
35
    #
36 37 38 39 40 41 42 43 44
    # If you are using a partial for your form fields, you can use this shortcut:
    #
    #     <% form_for :person, @person, :url => { :action => "create" } do |f| %>
    #       <%= render :partial => f %>
    #       <%= submit_tag 'Create' %>
    #     <% end %>
    #
    # This example will render the <tt>people/_form</tt> partial, setting a local variable called <tt>form</tt> which references the yielded FormBuilder.
    #
45
    # The <tt>params</tt> object created when this form is submitted would look like:
D
Initial  
David Heinemeier Hansson 已提交
46
    #
47
    #     {"action"=>"create", "controller"=>"persons", "person"=>{"first_name"=>"William", "last_name"=>"Smith"}}
48
    #
49 50 51
    # The params hash has a nested <tt>person</tt> value, which can therefore be accessed with <tt>params[:person]</tt> in the controller.
    # If were editing/updating an instance (e.g., <tt>Person.find(1)</tt> rather than <tt>Person.new</tt> in the controller), the objects
    # attribute values are filled into the form (e.g., the <tt>person_first_name</tt> field would have that person's first name in it).
52
    #
53
    # If the object name contains square brackets the id for the object will be inserted. For example:
54
    #
55
    #   <%= text_field "person[]", "name" %>
56
    #
57
    # ...will generate the following ERb.
58 59 60
    #
    #   <input type="text" id="person_<%= @person.id %>_name" name="person[<%= @person.id %>][name]" value="<%= @person.name %>" />
    #
61
    # If the helper is being used to generate a repetitive sequence of similar form elements, for example in a partial
62
    # used by <tt>render_collection_of_partials</tt>, the <tt>index</tt> option may come in handy. Example:
63 64 65
    #
    #   <%= text_field "person", "name", "index" => 1 %>
    #
66
    # ...becomes...
67
    #
68 69
    #   <input type="text" id="person_1_name" name="person[1][name]" value="<%= @person.name %>" />
    #
70 71 72
    # An <tt>index</tt> option may also be passed to <tt>form_for</tt> and <tt>fields_for</tt>.  This automatically applies
    # the <tt>index</tt> to all the nested fields.
    #
D
David Heinemeier Hansson 已提交
73
    # There are also methods for helping to build form tags in link:classes/ActionView/Helpers/FormOptionsHelper.html,
D
Initial  
David Heinemeier Hansson 已提交
74 75
    # link:classes/ActionView/Helpers/DateHelper.html, and link:classes/ActionView/Helpers/ActiveRecordHelper.html
    module FormHelper
76 77
      # Creates a form and a scope around a specific model object that is used as
      # a base for questioning about values for the fields.
78
      #
79 80 81 82 83 84 85 86
      # Rails provides succint resource-oriented form generation with +form_for+
      # like this:
      #
      #   <% form_for @offer do |f| %>
      #     <%= f.label :version, 'Version' %>:
      #     <%= f.text_field :version %><br />
      #     <%= f.label :author, 'Author' %>:
      #     <%= f.text_field :author %><br />
87 88
      #   <% end %>
      #
P
Pratik Naik 已提交
89
      # There, +form_for+ is able to generate the rest of RESTful form parameters
90 91
      # based on introspection on the record, but to understand what it does we
      # need to dig first into the alternative generic usage it is based upon.
92
      #
93 94
      # === Generic form_for
      #
P
Pratik Naik 已提交
95
      # The generic way to call +form_for+ yields a form builder around a model:
96
      #
P
Pratik Naik 已提交
97
      #   <% form_for :person, :url => { :action => "update" } do |f| %>
98 99 100 101 102 103
      #     <%= f.error_messages %>
      #     First name: <%= f.text_field :first_name %><br />
      #     Last name : <%= f.text_field :last_name %><br />
      #     Biography : <%= f.text_area :biography %><br />
      #     Admin?    : <%= f.check_box :admin %><br />
      #   <% end %>
104
      #
P
Pratik Naik 已提交
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
      # There, the first argument is a symbol or string with the name of the
      # object the form is about, and also the name of the instance variable the
      # object is stored in.
      #
      # The form builder acts as a regular form helper that somehow carries the
      # model. Thus, the idea is that
      #
      #   <%= f.text_field :first_name %>
      #
      # gets expanded to
      #
      #   <%= text_field :person, :first_name %>
      #
      # If the instance variable is not <tt>@person</tt> you can pass the actual
      # record as the second argument:
      #
      #   <% form_for :person, person, :url => { :action => "update" } do |f| %>
      #     ...
      #   <% end %>
      #
      # In that case you can think
      #
      #   <%= f.text_field :first_name %>
      #
      # gets expanded to
      #
      #   <%= text_field :person, :first_name, :object => person %>
      #
      # You can even display error messages of the wrapped model this way:
      #
      #   <%= f.error_messages %>
      #
      # In any of its variants, the rightmost argument to +form_for+ is an
      # optional hash of options:
      #
      # * <tt>:url</tt> - The URL the form is submitted to. It takes the same fields
      #   you pass to +url_for+ or +link_to+. In particular you may pass here a
      #   named route directly as well. Defaults to the current action.
      # * <tt>:html</tt> - Optional HTML attributes for the form tag.
      #
145
      # Worth noting is that the +form_for+ tag is called in a ERb evaluation block,
P
Pratik Naik 已提交
146
      # not an ERb output block. So that's <tt><% %></tt>, not <tt><%= %></tt>.
147 148 149 150
      #
      # Also note that +form_for+ doesn't create an exclusive scope. It's still
      # possible to use both the stand-alone FormHelper methods and methods from
      # FormTagHelper. For example:
151
      #
152
      #   <% form_for :person, @person, :url => { :action => "update" } do |f| %>
153 154 155 156 157 158
      #     First name: <%= f.text_field :first_name %>
      #     Last name : <%= f.text_field :last_name %>
      #     Biography : <%= text_area :person, :biography %>
      #     Admin?    : <%= check_box_tag "person[admin]", @person.company.admin? %>
      #   <% end %>
      #
159 160 161
      # This also works for the methods in FormOptionHelper and DateHelper that are
      # designed to work with an object as base, like FormOptionHelper#collection_select
      # and DateHelper#datetime_select.
162
      #
P
Pratik Naik 已提交
163
      # === Resource-oriented style
164
      #
165
      # As we said above, in addition to manually configuring the +form_for+ call,
P
Pratik Naik 已提交
166 167 168 169 170
      # you can rely on automated resource identification, which will use the conventions
      # and named routes of that approach. This is the preferred way to use +form_for+
      # nowadays.
      #
      # For example, if <tt>@post</tt> is an existing record you want to edit
171
      #
P
Pratik Naik 已提交
172
      #   <% form_for @post do |f| %>
173 174 175
      #     ...
      #   <% end %>
      #
P
Pratik Naik 已提交
176
      # is equivalent to something like:
177 178 179 180 181
      #
      #   <% form_for :post, @post, :url => post_path(@post), :html => { :method => :put, :class => "edit_post", :id => "edit_post_45" } do |f| %>
      #     ...
      #   <% end %>
      #
P
Pratik Naik 已提交
182
      # And for new records
183 184 185 186 187
      #
      #   <% form_for(Post.new) do |f| %>
      #     ...
      #   <% end %>
      #
P
Pratik Naik 已提交
188
      # expands to
189
      #
190
      #   <% form_for :post, Post.new, :url => posts_path, :html => { :class => "new_post", :id => "new_post" } do |f| %>
191 192 193 194 195 196 197 198 199
      #     ...
      #   <% end %>
      #
      # You can also overwrite the individual conventions, like this:
      #
      #   <% form_for(@post, :url => super_post_path(@post)) do |f| %>
      #     ...
      #   <% end %>
      #
200
      # And for namespaced routes, like +admin_post_url+:
201 202 203 204 205
      #
      #   <% form_for([:admin, @post]) do |f| %>
      #    ...
      #   <% end %>
      #
206 207
      # === Customized form builders
      #
208
      # You can also build forms using a customized FormBuilder class. Subclass FormBuilder and override or define some more helpers,
209 210
      # then use your custom builder. For example, let's say you made a helper to automatically add labels to form inputs.
      #
211 212 213 214 215 216
      #   <% form_for :person, @person, :url => { :action => "update" }, :builder => LabellingFormBuilder do |f| %>
      #     <%= f.text_field :first_name %>
      #     <%= f.text_field :last_name %>
      #     <%= text_area :person, :biography %>
      #     <%= check_box_tag "person[admin]", @person.company.admin? %>
      #   <% end %>
217
      #
218 219 220 221 222 223
      # In this case, if you use this:
      #
      #   <%= render :partial => f %>
      #
      # The rendered template is <tt>people/_labelling_form</tt> and the local variable referencing the form builder is called <tt>labelling_form</tt>.
      #
224
      # In many cases you will want to wrap the above in another helper, so you could do something like the following:
225
      #
226 227
      #   def labelled_form_for(record_or_name_or_array, *args, &proc)
      #     options = args.extract_options!
228
      #     form_for(record_or_name_or_array, *(args << options.merge(:builder => LabellingFormBuilder)), &proc)
229 230
      #   end
      #
231
      # If you don't need to attach a form to a model instance, then check out FormTagHelper#form_tag.
232
      def form_for(record_or_name_or_array, *args, &proc)
233
        raise ArgumentError, "Missing block" unless block_given?
234

235
        options = args.extract_options!
236

237
        case record_or_name_or_array
238
        when String, Symbol
239
          object_name = record_or_name_or_array
240
        when Array
241
          object = record_or_name_or_array.last
242
          object_name = ActionController::RecordIdentifier.singular_class_name(object)
243
          apply_form_for_options!(record_or_name_or_array, options)
244
          args.unshift object
245
        else
246
          object = record_or_name_or_array
247
          object_name = ActionController::RecordIdentifier.singular_class_name(object)
248
          apply_form_for_options!([object], options)
249
          args.unshift object
250 251
        end

252
        concat(form_tag(options.delete(:url) || {}, options.delete(:html) || {}))
253
        fields_for(object_name, *(args << options), &proc)
254
        concat('</form>')
255
      end
256

257 258
      def apply_form_for_options!(object_or_array, options) #:nodoc:
        object = object_or_array.is_a?(Array) ? object_or_array.last : object_or_array
259

260 261 262 263 264 265 266
        html_options =
          if object.respond_to?(:new_record?) && object.new_record?
            { :class  => dom_class(object, :new),  :id => dom_id(object), :method => :post }
          else
            { :class  => dom_class(object, :edit), :id => dom_id(object, :edit), :method => :put }
          end

267 268
        options[:html] ||= {}
        options[:html].reverse_merge!(html_options)
269
        options[:url] ||= polymorphic_path(object_or_array)
270
      end
271 272

      # Creates a scope around a specific model object like form_for, but doesn't create the form tags themselves. This makes
273
      # fields_for suitable for specifying additional model objects in the same form:
274
      #
275
      # ==== Examples
276
      #   <% form_for @person, :url => { :action => "update" } do |person_form| %>
277 278
      #     First name: <%= person_form.text_field :first_name %>
      #     Last name : <%= person_form.text_field :last_name %>
279
      #
280
      #     <% fields_for @person.permission do |permission_fields| %>
281 282 283 284
      #       Admin?  : <%= permission_fields.check_box :admin %>
      #     <% end %>
      #   <% end %>
      #
285 286 287 288 289 290 291 292 293 294 295 296
      # ...or if you have an object that needs to be represented as a different parameter, like a Client that acts as a Person:
      #
      #   <% fields_for :person, @client do |permission_fields| %>
      #     Admin?: <%= permission_fields.check_box :admin %>
      #   <% end %>
      #
      # ...or if you don't have an object, just a name of the parameter
      #
      #   <% fields_for :person do |permission_fields| %>
      #     Admin?: <%= permission_fields.check_box :admin %>
      #   <% end %>
      #
297 298
      # Note: This also works for the methods in FormOptionHelper and DateHelper that are designed to work with an object as base,
      # like FormOptionHelper#collection_select and DateHelper#datetime_select.
299
      def fields_for(record_or_name_or_array, *args, &block)
300
        raise ArgumentError, "Missing block" unless block_given?
301
        options = args.extract_options!
302 303 304 305 306 307 308 309 310 311 312 313 314

        case record_or_name_or_array
        when String, Symbol
          object_name = record_or_name_or_array
          object = args.first
        when Array
          object = record_or_name_or_array.last
          object_name = ActionController::RecordIdentifier.singular_class_name(object)
          apply_form_for_options!(record_or_name_or_array, options)
        else
          object = record_or_name_or_array
          object_name = ActionController::RecordIdentifier.singular_class_name(object)
        end
315 316 317

        builder = options[:builder] || ActionView::Base.default_form_builder
        yield builder.new(object_name, object, self, options, block)
318 319
      end

320 321 322
      # Returns a label tag tailored for labelling an input field for a specified attribute (identified by +method+) on an object
      # assigned to the template (identified by +object+). The text of label will default to the attribute name unless you specify
      # it explicitly. Additional options on the label tag can be passed as a hash with +options+. These options will be tagged
323
      # onto the HTML as an HTML element attribute as in the example shown.
324 325 326
      #
      # ==== Examples
      #   label(:post, :title)
P
Pratik Naik 已提交
327
      #   # => <label for="post_title">Title</label>
328 329
      #
      #   label(:post, :title, "A short title")
P
Pratik Naik 已提交
330
      #   # => <label for="post_title">A short title</label>
331 332
      #
      #   label(:post, :title, "A short title", :class => "title_label")
P
Pratik Naik 已提交
333
      #   # => <label for="post_title" class="title_label">A short title</label>
334 335 336 337 338
      #
      def label(object_name, method, text = nil, options = {})
        InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_label_tag(text, options)
      end

D
Initial  
David Heinemeier Hansson 已提交
339 340
      # Returns an input tag of the "text" type tailored for accessing a specified attribute (identified by +method+) on an object
      # assigned to the template (identified by +object+). Additional options on the input tag can be passed as a
341
      # hash with +options+. These options will be tagged onto the HTML as an HTML element attribute as in the example
D
David Heinemeier Hansson 已提交
342
      # shown.
D
Initial  
David Heinemeier Hansson 已提交
343
      #
344
      # ==== Examples
D
David Heinemeier Hansson 已提交
345
      #   text_field(:post, :title, :size => 20)
346 347 348 349 350 351 352 353 354 355 356
      #   # => <input type="text" id="post_title" name="post[title]" size="20" value="#{@post.title}" />
      #
      #   text_field(:post, :title, :class => "create_input")
      #   # => <input type="text" id="post_title" name="post[title]" value="#{@post.title}" class="create_input" />
      #
      #   text_field(:session, :user, :onchange => "if $('session[user]').value == 'admin' { alert('Your login can not be admin!'); }")
      #   # => <input type="text" id="session_user" name="session[user]" value="#{@session.user}" onchange = "if $('session[user]').value == 'admin' { alert('Your login can not be admin!'); }"/>
      #
      #   text_field(:snippet, :code, :size => 20, :class => 'code_input')
      #   # => <input type="text" id="snippet_code" name="snippet[code]" size="20" value="#{@snippet.code}" class="code_input" />
      #
357 358
      def text_field(object_name, method, options = {})
        InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_input_field_tag("text", options)
D
Initial  
David Heinemeier Hansson 已提交
359 360
      end

361 362
      # Returns an input tag of the "password" type tailored for accessing a specified attribute (identified by +method+) on an object
      # assigned to the template (identified by +object+). Additional options on the input tag can be passed as a
363
      # hash with +options+. These options will be tagged onto the HTML as an HTML element attribute as in the example
364 365 366 367 368 369 370 371 372 373 374 375 376 377 378
      # shown.
      #
      # ==== Examples
      #   password_field(:login, :pass, :size => 20)
      #   # => <input type="text" id="login_pass" name="login[pass]" size="20" value="#{@login.pass}" />
      #
      #   password_field(:account, :secret, :class => "form_input")
      #   # => <input type="text" id="account_secret" name="account[secret]" value="#{@account.secret}" class="form_input" />
      #
      #   password_field(:user, :password, :onchange => "if $('user[password]').length > 30 { alert('Your password needs to be shorter!'); }")
      #   # => <input type="text" id="user_password" name="user[password]" value="#{@user.password}" onchange = "if $('user[password]').length > 30 { alert('Your password needs to be shorter!'); }"/>
      #
      #   password_field(:account, :pin, :size => 20, :class => 'form_input')
      #   # => <input type="text" id="account_pin" name="account[pin]" size="20" value="#{@account.pin}" class="form_input" />
      #
379 380
      def password_field(object_name, method, options = {})
        InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_input_field_tag("password", options)
D
Initial  
David Heinemeier Hansson 已提交
381 382
      end

383 384
      # Returns a hidden input tag tailored for accessing a specified attribute (identified by +method+) on an object
      # assigned to the template (identified by +object+). Additional options on the input tag can be passed as a
385
      # hash with +options+. These options will be tagged onto the HTML as an HTML element attribute as in the example
386 387
      # shown.
      #
388
      # ==== Examples
389 390 391 392 393 394 395
      #   hidden_field(:signup, :pass_confirm)
      #   # => <input type="hidden" id="signup_pass_confirm" name="signup[pass_confirm]" value="#{@signup.pass_confirm}" />
      #
      #   hidden_field(:post, :tag_list)
      #   # => <input type="hidden" id="post_tag_list" name="post[tag_list]" value="#{@post.tag_list}" />
      #
      #   hidden_field(:user, :token)
396
      #   # => <input type="hidden" id="user_token" name="user[token]" value="#{@user.token}" />
397
      def hidden_field(object_name, method, options = {})
398
        InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_input_field_tag("hidden", options)
D
Initial  
David Heinemeier Hansson 已提交
399 400
      end

401 402
      # Returns an file upload input tag tailored for accessing a specified attribute (identified by +method+) on an object
      # assigned to the template (identified by +object+). Additional options on the input tag can be passed as a
403
      # hash with +options+. These options will be tagged onto the HTML as an HTML element attribute as in the example
404 405 406 407 408 409 410 411 412 413 414 415
      # shown.
      #
      # ==== Examples
      #   file_field(:user, :avatar)
      #   # => <input type="file" id="user_avatar" name="user[avatar]" />
      #
      #   file_field(:post, :attached, :accept => 'text/html')
      #   # => <input type="file" id="post_attached" name="post[attached]" />
      #
      #   file_field(:attachment, :file, :class => 'file_input')
      #   # => <input type="file" id="attachment_file" name="attachment[file]" class="file_input" />
      #
416 417
      def file_field(object_name, method, options = {})
        InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_input_field_tag("file", options)
418 419
      end

D
Initial  
David Heinemeier Hansson 已提交
420 421 422 423
      # Returns a textarea opening and closing tag set tailored for accessing a specified attribute (identified by +method+)
      # on an object assigned to the template (identified by +object+). Additional options on the input tag can be passed as a
      # hash with +options+.
      #
424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443
      # ==== Examples
      #   text_area(:post, :body, :cols => 20, :rows => 40)
      #   # => <textarea cols="20" rows="40" id="post_body" name="post[body]">
      #   #      #{@post.body}
      #   #    </textarea>
      #
      #   text_area(:comment, :text, :size => "20x30")
      #   # => <textarea cols="20" rows="30" id="comment_text" name="comment[text]">
      #   #      #{@comment.text}
      #   #    </textarea>
      #
      #   text_area(:application, :notes, :cols => 40, :rows => 15, :class => 'app_input')
      #   # => <textarea cols="40" rows="15" id="application_notes" name="application[notes]" class="app_input">
      #   #      #{@application.notes}
      #   #    </textarea>
      #
      #   text_area(:entry, :body, :size => "20x20", :disabled => 'disabled')
      #   # => <textarea cols="20" rows="20" id="entry_body" name="entry[body]" disabled="disabled">
      #   #      #{@entry.body}
      #   #    </textarea>
444 445
      def text_area(object_name, method, options = {})
        InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_text_area_tag(options)
D
Initial  
David Heinemeier Hansson 已提交
446
      end
447

D
Initial  
David Heinemeier Hansson 已提交
448 449 450 451
      # Returns a checkbox tag tailored for accessing a specified attribute (identified by +method+) on an object
      # assigned to the template (identified by +object+). It's intended that +method+ returns an integer and if that
      # integer is above zero, then the checkbox is checked. Additional options on the input tag can be passed as a
      # hash with +options+. The +checked_value+ defaults to 1 while the default +unchecked_value+
452 453
      # is set to 0 which is convenient for boolean values. Since HTTP standards say that unchecked checkboxes don't post anything,
      # we add a hidden value with the same name as the checkbox as a work around.
D
Initial  
David Heinemeier Hansson 已提交
454
      #
455
      # ==== Examples
456
      #   # Let's say that @post.validated? is 1:
D
Initial  
David Heinemeier Hansson 已提交
457
      #   check_box("post", "validated")
P
Pratik Naik 已提交
458
      #   # => <input type="checkbox" id="post_validated" name="post[validated]" value="1" />
459
      #   #    <input name="post[validated]" type="hidden" value="0" />
D
Initial  
David Heinemeier Hansson 已提交
460
      #
461
      #   # Let's say that @puppy.gooddog is "no":
D
Initial  
David Heinemeier Hansson 已提交
462
      #   check_box("puppy", "gooddog", {}, "yes", "no")
463 464 465
      #   # => <input type="checkbox" id="puppy_gooddog" name="puppy[gooddog]" value="yes" />
      #   #    <input name="puppy[gooddog]" type="hidden" value="no" />
      #
P
Pratik Naik 已提交
466 467
      #   check_box("eula", "accepted", { :class => 'eula_check' }, "yes", "no")
      #   # => <input type="checkbox" class="eula_check" id="eula_accepted" name="eula[accepted]" value="yes" />
468 469
      #   #    <input name="eula[accepted]" type="hidden" value="no" />
      #
470 471
      def check_box(object_name, method, options = {}, checked_value = "1", unchecked_value = "0")
        InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_check_box_tag(options, checked_value, unchecked_value)
D
Initial  
David Heinemeier Hansson 已提交
472
      end
473 474 475 476

      # Returns a radio button tag for accessing a specified attribute (identified by +method+) on an object
      # assigned to the template (identified by +object+). If the current value of +method+ is +tag_value+ the
      # radio button will be checked. Additional options on the input tag can be passed as a
477
      # hash with +options+.
478 479 480
      #
      # ==== Examples
      #   # Let's say that @post.category returns "rails":
481 482
      #   radio_button("post", "category", "rails")
      #   radio_button("post", "category", "java")
P
Pratik Naik 已提交
483 484
      #   # => <input type="radio" id="post_category_rails" name="post[category]" value="rails" checked="checked" />
      #   #    <input type="radio" id="post_category_java" name="post[category]" value="java" />
485
      #
486 487
      #   radio_button("user", "receive_newsletter", "yes")
      #   radio_button("user", "receive_newsletter", "no")
P
Pratik Naik 已提交
488 489
      #   # => <input type="radio" id="user_receive_newsletter_yes" name="user[receive_newsletter]" value="yes" />
      #   #    <input type="radio" id="user_receive_newsletter_no" name="user[receive_newsletter]" value="no" checked="checked" />
490 491
      def radio_button(object_name, method, tag_value, options = {})
        InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_radio_button_tag(tag_value, options)
492
      end
D
Initial  
David Heinemeier Hansson 已提交
493 494 495
    end

    class InstanceTag #:nodoc:
496
      include Helpers::TagHelper, Helpers::FormTagHelper
D
Initial  
David Heinemeier Hansson 已提交
497 498

      attr_reader :method_name, :object_name
499 500

      DEFAULT_FIELD_OPTIONS     = { "size" => 30 }.freeze unless const_defined?(:DEFAULT_FIELD_OPTIONS)
501
      DEFAULT_RADIO_OPTIONS     = { }.freeze unless const_defined?(:DEFAULT_RADIO_OPTIONS)
502
      DEFAULT_TEXT_AREA_OPTIONS = { "cols" => 40, "rows" => 20 }.freeze unless const_defined?(:DEFAULT_TEXT_AREA_OPTIONS)
D
Initial  
David Heinemeier Hansson 已提交
503

504
      def initialize(object_name, method_name, template_object, local_binding = nil, object = nil)
505
        @object_name, @method_name = object_name.to_s.dup, method_name.to_s.dup
D
Initial  
David Heinemeier Hansson 已提交
506
        @template_object, @local_binding = template_object, local_binding
507
        @object = object
508
        if @object_name.sub!(/\[\]$/,"")
509 510
          if object ||= @template_object.instance_variable_get("@#{Regexp.last_match.pre_match}") and object.respond_to?(:to_param)
            @auto_index = object.to_param
511
          else
512
            raise ArgumentError, "object[] naming but object param and @object var don't exist or don't respond to to_param: #{object.inspect}"
513
          end
514
        end
D
Initial  
David Heinemeier Hansson 已提交
515
      end
516

517
      def to_label_tag(text = nil, options = {})
518
        options = options.stringify_keys
519 520
        name_and_id = options.dup
        add_default_name_and_id(name_and_id)
521
        options.delete("index")
522
        options["for"] ||= name_and_id["id"]
523
        content = (text.blank? ? nil : text.to_s) || method_name.humanize
524
        label_tag(name_and_id["id"], content, options)
525 526
      end

D
Initial  
David Heinemeier Hansson 已提交
527
      def to_input_field_tag(field_type, options = {})
528
        options = options.stringify_keys
529
        options["size"] = options["maxlength"] || DEFAULT_FIELD_OPTIONS["size"] unless options.key?("size")
530
        options = DEFAULT_FIELD_OPTIONS.merge(options)
531 532 533 534
        if field_type == "hidden"
          options.delete("size")
        end
        options["type"] = field_type
535
        options["value"] ||= value_before_type_cast(object) unless field_type == "file"
536
        options["value"] &&= html_escape(options["value"])
537 538 539 540 541
        add_default_name_and_id(options)
        tag("input", options)
      end

      def to_radio_button_tag(tag_value, options = {})
542
        options = DEFAULT_RADIO_OPTIONS.merge(options.stringify_keys)
543 544
        options["type"]     = "radio"
        options["value"]    = tag_value
545 546 547 548 549 550
        if options.has_key?("checked")
          cv = options.delete "checked"
          checked = cv == true || cv == "checked"
        else
          checked = self.class.radio_button_checked?(value(object), tag_value)
        end
551
        options["checked"]  = "checked" if checked
552
        pretty_tag_value    = tag_value.to_s.gsub(/\s/, "_").gsub(/\W/, "").downcase
553
        options["id"]     ||= defined?(@auto_index) ?
554 555
          "#{tag_id_with_index(@auto_index)}_#{pretty_tag_value}" :
          "#{tag_id}_#{pretty_tag_value}"
556 557 558 559
        add_default_name_and_id(options)
        tag("input", options)
      end

D
Initial  
David Heinemeier Hansson 已提交
560
      def to_text_area_tag(options = {})
561
        options = DEFAULT_TEXT_AREA_OPTIONS.merge(options.stringify_keys)
D
Initial  
David Heinemeier Hansson 已提交
562
        add_default_name_and_id(options)
563 564

        if size = options.delete("size")
565
          options["cols"], options["rows"] = size.split("x") if size.respond_to?(:split)
566 567
        end

568
        content_tag("textarea", html_escape(options.delete('value') || value_before_type_cast(object)), options)
D
Initial  
David Heinemeier Hansson 已提交
569 570 571
      end

      def to_check_box_tag(options = {}, checked_value = "1", unchecked_value = "0")
572 573 574
        options = options.stringify_keys
        options["type"]     = "checkbox"
        options["value"]    = checked_value
575 576 577
        if options.has_key?("checked")
          cv = options.delete "checked"
          checked = cv == true || cv == "checked"
578
        else
579
          checked = self.class.check_box_checked?(value(object), checked_value)
580
        end
581
        options["checked"] = "checked" if checked
D
Initial  
David Heinemeier Hansson 已提交
582
        add_default_name_and_id(options)
583
        tag("input", options) << tag("input", "name" => options["name"], "type" => "hidden", "value" => options['disabled'] && checked ? checked_value : unchecked_value)
D
Initial  
David Heinemeier Hansson 已提交
584 585 586
      end

      def to_boolean_select_tag(options = {})
587
        options = options.stringify_keys
D
Initial  
David Heinemeier Hansson 已提交
588
        add_default_name_and_id(options)
589
        value = value(object)
D
Initial  
David Heinemeier Hansson 已提交
590 591 592 593 594 595 596 597
        tag_text = "<select"
        tag_text << tag_options(options)
        tag_text << "><option value=\"false\""
        tag_text << " selected" if value == false
        tag_text << ">False</option><option value=\"true\""
        tag_text << " selected" if value
        tag_text << ">True</option></select>"
      end
598

599
      def to_content_tag(tag_name, options = {})
600
        content_tag(tag_name, value(object), options)
601
      end
602

D
Initial  
David Heinemeier Hansson 已提交
603
      def object
604
        @object || (@template_object.instance_variable_get("@#{@object_name}") rescue nil)
D
Initial  
David Heinemeier Hansson 已提交
605 606
      end

607 608
      def value(object)
        self.class.value(object, @method_name)
D
Initial  
David Heinemeier Hansson 已提交
609 610
      end

611 612 613
      def value_before_type_cast(object)
        self.class.value_before_type_cast(object, @method_name)
      end
614

615 616 617 618
      class << self
        def value(object, method_name)
          object.send method_name unless object.nil?
        end
619

620 621 622 623 624 625 626
        def value_before_type_cast(object, method_name)
          unless object.nil?
            object.respond_to?(method_name + "_before_type_cast") ?
            object.send(method_name + "_before_type_cast") :
            object.send(method_name)
          end
        end
627

628 629 630 631 632 633 634 635 636 637
        def check_box_checked?(value, checked_value)
          case value
          when TrueClass, FalseClass
            value
          when NilClass
            false
          when Integer
            value != 0
          when String
            value == checked_value
638 639
          when Array
            value.include?(checked_value)
640 641 642 643
          else
            value.to_i != 0
          end
        end
644

645 646
        def radio_button_checked?(value, checked_value)
          value.to_s == checked_value.to_s
647
        end
648 649
      end

D
Initial  
David Heinemeier Hansson 已提交
650 651
      private
        def add_default_name_and_id(options)
652 653 654
          if options.has_key?("index")
            options["name"] ||= tag_name_with_index(options["index"])
            options["id"]   ||= tag_id_with_index(options["index"])
655
            options.delete("index")
656
          elsif defined?(@auto_index)
657 658
            options["name"] ||= tag_name_with_index(@auto_index)
            options["id"]   ||= tag_id_with_index(@auto_index)
659
          else
660
            options["name"] ||= tag_name + (options.has_key?('multiple') ? '[]' : '')
661
            options["id"]   ||= tag_id
662
          end
D
Initial  
David Heinemeier Hansson 已提交
663
        end
664

D
Initial  
David Heinemeier Hansson 已提交
665
        def tag_name
666
          "#{@object_name}[#{sanitized_method_name}]"
D
Initial  
David Heinemeier Hansson 已提交
667
        end
668

669
        def tag_name_with_index(index)
670
          "#{@object_name}[#{index}][#{sanitized_method_name}]"
671
        end
D
Initial  
David Heinemeier Hansson 已提交
672 673

        def tag_id
674
          "#{sanitized_object_name}_#{sanitized_method_name}"
D
Initial  
David Heinemeier Hansson 已提交
675
        end
676 677

        def tag_id_with_index(index)
678
          "#{sanitized_object_name}_#{index}_#{sanitized_method_name}"
679 680 681
        end

        def sanitized_object_name
682 683 684 685 686
          @sanitized_object_name ||= @object_name.gsub(/[^-a-zA-Z0-9:.]/, "_").sub(/_$/, "")
        end

        def sanitized_method_name
          @sanitized_method_name ||= @method_name.sub(/\?$/,"")
687
        end
D
Initial  
David Heinemeier Hansson 已提交
688
    end
689

690
    class FormBuilder #:nodoc:
691 692 693
      # The methods which wrap a form helper call.
      class_inheritable_accessor :field_helpers
      self.field_helpers = (FormHelper.instance_methods - ['form_for'])
694

695
      attr_accessor :object_name, :object, :options
696

697
      def initialize(object_name, object, template, options, proc)
698
        @object_name, @object, @template, @options, @proc = object_name, object, template, options, proc
699
        @default_options = @options ? @options.slice(:index) : {}
700
      end
701

702
      (field_helpers - %w(label check_box radio_button fields_for)).each do |selector|
703 704
        src = <<-end_src
          def #{selector}(method, options = {})
705
            @template.send(#{selector.inspect}, @object_name, method, objectify_options(options))
706 707 708 709
          end
        end_src
        class_eval src, __FILE__, __LINE__
      end
710

711 712 713 714 715 716 717 718 719 720 721 722 723
      def fields_for(record_or_name_or_array, *args, &block)
        case record_or_name_or_array
        when String, Symbol
          name = "#{object_name}[#{record_or_name_or_array}]"
        when Array
          object = record_or_name_or_array.last
          name = "#{object_name}[#{ActionController::RecordIdentifier.singular_class_name(object)}]"
          args.unshift(object)
        else
          object = record_or_name_or_array
          name = "#{object_name}[#{ActionController::RecordIdentifier.singular_class_name(object)}]"
          args.unshift(object)
        end
724

725 726
        @template.fields_for(name, *args, &block)
      end
727 728

      def label(method, text = nil, options = {})
729
        @template.label(@object_name, method, text, objectify_options(options))
730
      end
731

732
      def check_box(method, options = {}, checked_value = "1", unchecked_value = "0")
733
        @template.check_box(@object_name, method, objectify_options(options), checked_value, unchecked_value)
734
      end
735

736
      def radio_button(method, tag_value, options = {})
737
        @template.radio_button(@object_name, method, tag_value, objectify_options(options))
738
      end
739

740
      def error_message_on(method, prepend_text = "", append_text = "", css_class = "formError")
741
        @template.error_message_on(@object, method, prepend_text, append_text, css_class)
742
      end
743 744

      def error_messages(options = {})
745
        @template.error_messages_for(@object_name, objectify_options(options))
746
      end
747

748 749 750
      def submit(value = "Save changes", options = {})
        @template.submit_tag(value, options.reverse_merge(:id => "#{object_name}_submit"))
      end
751 752 753 754 755

      private
        def objectify_options(options)
          @default_options.merge(options.merge(:object => @object))
        end
756
    end
D
Initial  
David Heinemeier Hansson 已提交
757
  end
758 759 760 761 762

  class Base
    cattr_accessor :default_form_builder
    self.default_form_builder = ::ActionView::Helpers::FormBuilder
  end
763
end