2
require 'action_view/helpers/date_helper'
3
require 'action_view/helpers/tag_helper'
4
require 'action_view/helpers/form_tag_helper'
8
# Form helpers are designed to make working with models much easier
9
# compared to using just standard HTML elements by providing a set of
10
# methods for creating forms based on your models. This helper generates
11
# the HTML for forms, providing a method for each sort of input
12
# (e.g., text, password, select, and so on). When the form is submitted
13
# (i.e., when the user hits the submit button or <tt>form.submit</tt> is
14
# called via JavaScript), the form inputs will be bundled into the
15
# <tt>params</tt> object and passed back to the controller.
17
# There are two types of form helpers: those that specifically work with
18
# model attributes and those that don't. This helper deals with those that
19
# work with model attributes; to see an example of form helpers that don't
20
# work with model attributes, check the ActionView::Helpers::FormTagHelper
23
# The core method of this helper, form_for, gives you the ability to create
24
# a form for a model instance; for example, let's say that you have a model
25
# <tt>Person</tt> and want to create a new instance of it:
27
# # Note: a @person variable will have been created in the controller.
28
# # For example: @person = Person.new
29
# <% form_for :person, @person, :url => { :action => "create" } do |f| %>
30
# <%= f.text_field :first_name %>
31
# <%= f.text_field :last_name %>
32
# <%= submit_tag 'Create' %>
35
# The HTML generated for this would be:
37
# <form action="/persons/create" method="post">
38
# <input id="person_first_name" name="person[first_name]" size="30" type="text" />
39
# <input id="person_last_name" name="person[last_name]" size="30" type="text" />
40
# <input name="commit" type="submit" value="Create" />
43
# If you are using a partial for your form fields, you can use this shortcut:
45
# <% form_for :person, @person, :url => { :action => "create" } do |f| %>
46
# <%= render :partial => f %>
47
# <%= submit_tag 'Create' %>
50
# This example will render the <tt>people/_form</tt> partial, setting a
51
# local variable called <tt>form</tt> which references the yielded
52
# FormBuilder. The <tt>params</tt> object created when this form is
53
# submitted would look like:
55
# {"action"=>"create", "controller"=>"persons", "person"=>{"first_name"=>"William", "last_name"=>"Smith"}}
57
# The params hash has a nested <tt>person</tt> value, which can therefore
58
# be accessed with <tt>params[:person]</tt> in the controller. If were
59
# editing/updating an instance (e.g., <tt>Person.find(1)</tt> rather than
60
# <tt>Person.new</tt> in the controller), the objects attribute values are
61
# filled into the form (e.g., the <tt>person_first_name</tt> field would
62
# have that person's first name in it).
64
# If the object name contains square brackets the id for the object will be
65
# inserted. For example:
67
# <%= text_field "person[]", "name" %>
69
# ...will generate the following ERb.
71
# <input type="text" id="person_<%= @person.id %>_name" name="person[<%= @person.id %>][name]" value="<%= @person.name %>" />
73
# If the helper is being used to generate a repetitive sequence of similar
74
# form elements, for example in a partial used by
75
# <tt>render_collection_of_partials</tt>, the <tt>index</tt> option may
76
# come in handy. Example:
78
# <%= text_field "person", "name", "index" => 1 %>
82
# <input type="text" id="person_1_name" name="person[1][name]" value="<%= @person.name %>" />
84
# An <tt>index</tt> option may also be passed to <tt>form_for</tt> and
85
# <tt>fields_for</tt>. This automatically applies the <tt>index</tt> to
86
# all the nested fields.
88
# There are also methods for helping to build form tags in
89
# link:classes/ActionView/Helpers/FormOptionsHelper.html,
90
# link:classes/ActionView/Helpers/DateHelper.html, and
91
# link:classes/ActionView/Helpers/ActiveRecordHelper.html
93
# Creates a form and a scope around a specific model object that is used
94
# as a base for questioning about values for the fields.
96
# Rails provides succinct resource-oriented form generation with +form_for+
99
# <% form_for @offer do |f| %>
100
# <%= f.label :version, 'Version' %>:
101
# <%= f.text_field :version %><br />
102
# <%= f.label :author, 'Author' %>:
103
# <%= f.text_field :author %><br />
106
# There, +form_for+ is able to generate the rest of RESTful form
107
# parameters based on introspection on the record, but to understand what
108
# it does we need to dig first into the alternative generic usage it is
111
# === Generic form_for
113
# The generic way to call +form_for+ yields a form builder around a
116
# <% form_for :person, :url => { :action => "update" } do |f| %>
117
# <%= f.error_messages %>
118
# First name: <%= f.text_field :first_name %><br />
119
# Last name : <%= f.text_field :last_name %><br />
120
# Biography : <%= f.text_area :biography %><br />
121
# Admin? : <%= f.check_box :admin %><br />
124
# There, the first argument is a symbol or string with the name of the
125
# object the form is about, and also the name of the instance variable
126
# the object is stored in.
128
# The form builder acts as a regular form helper that somehow carries the
129
# model. Thus, the idea is that
131
# <%= f.text_field :first_name %>
135
# <%= text_field :person, :first_name %>
137
# If the instance variable is not <tt>@person</tt> you can pass the actual
138
# record as the second argument:
140
# <% form_for :person, person, :url => { :action => "update" } do |f| %>
144
# In that case you can think
146
# <%= f.text_field :first_name %>
150
# <%= text_field :person, :first_name, :object => person %>
152
# You can even display error messages of the wrapped model this way:
154
# <%= f.error_messages %>
156
# In any of its variants, the rightmost argument to +form_for+ is an
157
# optional hash of options:
159
# * <tt>:url</tt> - The URL the form is submitted to. It takes the same
160
# fields you pass to +url_for+ or +link_to+. In particular you may pass
161
# here a named route directly as well. Defaults to the current action.
162
# * <tt>:html</tt> - Optional HTML attributes for the form tag.
164
# Worth noting is that the +form_for+ tag is called in a ERb evaluation
165
# block, not an ERb output block. So that's <tt><% %></tt>, not
168
# Also note that +form_for+ doesn't create an exclusive scope. It's still
169
# possible to use both the stand-alone FormHelper methods and methods
170
# from FormTagHelper. For example:
172
# <% form_for :person, @person, :url => { :action => "update" } do |f| %>
173
# First name: <%= f.text_field :first_name %>
174
# Last name : <%= f.text_field :last_name %>
175
# Biography : <%= text_area :person, :biography %>
176
# Admin? : <%= check_box_tag "person[admin]", @person.company.admin? %>
179
# This also works for the methods in FormOptionHelper and DateHelper that
180
# are designed to work with an object as base, like
181
# FormOptionHelper#collection_select and DateHelper#datetime_select.
183
# === Resource-oriented style
185
# As we said above, in addition to manually configuring the +form_for+
186
# call, you can rely on automated resource identification, which will use
187
# the conventions and named routes of that approach. This is the
188
# preferred way to use +form_for+ nowadays.
190
# For example, if <tt>@post</tt> is an existing record you want to edit
192
# <% form_for @post do |f| %>
196
# is equivalent to something like:
198
# <% form_for :post, @post, :url => post_path(@post), :html => { :method => :put, :class => "edit_post", :id => "edit_post_45" } do |f| %>
202
# And for new records
204
# <% form_for(Post.new) do |f| %>
210
# <% form_for :post, Post.new, :url => posts_path, :html => { :class => "new_post", :id => "new_post" } do |f| %>
214
# You can also overwrite the individual conventions, like this:
216
# <% form_for(@post, :url => super_post_path(@post)) do |f| %>
220
# And for namespaced routes, like +admin_post_url+:
222
# <% form_for([:admin, @post]) do |f| %>
226
# === Customized form builders
228
# You can also build forms using a customized FormBuilder class. Subclass
229
# FormBuilder and override or define some more helpers, then use your
230
# custom builder. For example, let's say you made a helper to
231
# automatically add labels to form inputs.
233
# <% form_for :person, @person, :url => { :action => "update" }, :builder => LabellingFormBuilder do |f| %>
234
# <%= f.text_field :first_name %>
235
# <%= f.text_field :last_name %>
236
# <%= text_area :person, :biography %>
237
# <%= check_box_tag "person[admin]", @person.company.admin? %>
240
# In this case, if you use this:
242
# <%= render :partial => f %>
244
# The rendered template is <tt>people/_labelling_form</tt> and the local
245
# variable referencing the form builder is called
246
# <tt>labelling_form</tt>.
248
# The custom FormBuilder class is automatically merged with the options
249
# of a nested fields_for call, unless it's explicitely set.
251
# In many cases you will want to wrap the above in another helper, so you
252
# could do something like the following:
254
# def labelled_form_for(record_or_name_or_array, *args, &proc)
255
# options = args.extract_options!
256
# form_for(record_or_name_or_array, *(args << options.merge(:builder => LabellingFormBuilder)), &proc)
259
# If you don't need to attach a form to a model instance, then check out
260
# FormTagHelper#form_tag.
261
def form_for(record_or_name_or_array, *args, &proc)
262
raise ArgumentError, "Missing block" unless block_given?
264
options = args.extract_options!
266
case record_or_name_or_array
268
object_name = record_or_name_or_array
270
object = record_or_name_or_array.last
271
object_name = ActionController::RecordIdentifier.singular_class_name(object)
272
apply_form_for_options!(record_or_name_or_array, options)
275
object = record_or_name_or_array
276
object_name = ActionController::RecordIdentifier.singular_class_name(object)
277
apply_form_for_options!([object], options)
281
concat(form_tag(options.delete(:url) || {}, options.delete(:html) || {}))
282
fields_for(object_name, *(args << options), &proc)
283
concat('</form>'.html_safe!)
286
def apply_form_for_options!(object_or_array, options) #:nodoc:
287
object = object_or_array.is_a?(Array) ? object_or_array.last : object_or_array
290
if object.respond_to?(:new_record?) && object.new_record?
291
{ :class => dom_class(object, :new), :id => dom_id(object), :method => :post }
293
{ :class => dom_class(object, :edit), :id => dom_id(object, :edit), :method => :put }
296
options[:html] ||= {}
297
options[:html].reverse_merge!(html_options)
298
options[:url] ||= polymorphic_path(object_or_array)
301
# Creates a scope around a specific model object like form_for, but
302
# doesn't create the form tags themselves. This makes fields_for suitable
303
# for specifying additional model objects in the same form.
305
# === Generic Examples
307
# <% form_for @person, :url => { :action => "update" } do |person_form| %>
308
# First name: <%= person_form.text_field :first_name %>
309
# Last name : <%= person_form.text_field :last_name %>
311
# <% fields_for @person.permission do |permission_fields| %>
312
# Admin? : <%= permission_fields.check_box :admin %>
316
# ...or if you have an object that needs to be represented as a different
317
# parameter, like a Client that acts as a Person:
319
# <% fields_for :person, @client do |permission_fields| %>
320
# Admin?: <%= permission_fields.check_box :admin %>
323
# ...or if you don't have an object, just a name of the parameter:
325
# <% fields_for :person do |permission_fields| %>
326
# Admin?: <%= permission_fields.check_box :admin %>
329
# Note: This also works for the methods in FormOptionHelper and
330
# DateHelper that are designed to work with an object as base, like
331
# FormOptionHelper#collection_select and DateHelper#datetime_select.
333
# === Nested Attributes Examples
335
# When the object belonging to the current scope has a nested attribute
336
# writer for a certain attribute, fields_for will yield a new scope
337
# for that attribute. This allows you to create forms that set or change
338
# the attributes of a parent object and its associations in one go.
340
# Nested attribute writers are normal setter methods named after an
341
# association. The most common way of defining these writers is either
342
# with +accepts_nested_attributes_for+ in a model definition or by
343
# defining a method with the proper name. For example: the attribute
344
# writer for the association <tt>:address</tt> is called
345
# <tt>address_attributes=</tt>.
347
# Whether a one-to-one or one-to-many style form builder will be yielded
348
# depends on whether the normal reader method returns a _single_ object
349
# or an _array_ of objects.
353
# Consider a Person class which returns a _single_ Address from the
354
# <tt>address</tt> reader method and responds to the
355
# <tt>address_attributes=</tt> writer method:
362
# def address_attributes=(attributes)
363
# # Process the attributes hash
367
# This model can now be used with a nested fields_for, like so:
369
# <% form_for @person, :url => { :action => "update" } do |person_form| %>
371
# <% person_form.fields_for :address do |address_fields| %>
372
# Street : <%= address_fields.text_field :street %>
373
# Zip code: <%= address_fields.text_field :zip_code %>
377
# When address is already an association on a Person you can use
378
# +accepts_nested_attributes_for+ to define the writer method for you:
380
# class Person < ActiveRecord::Base
382
# accepts_nested_attributes_for :address
385
# If you want to destroy the associated model through the form, you have
386
# to enable it first using the <tt>:allow_destroy</tt> option for
387
# +accepts_nested_attributes_for+:
389
# class Person < ActiveRecord::Base
391
# accepts_nested_attributes_for :address, :allow_destroy => true
394
# Now, when you use a form element with the <tt>_delete</tt> parameter,
395
# with a value that evaluates to +true+, you will destroy the associated
396
# model (eg. 1, '1', true, or 'true'):
398
# <% form_for @person, :url => { :action => "update" } do |person_form| %>
400
# <% person_form.fields_for :address do |address_fields| %>
402
# Delete: <%= address_fields.check_box :_delete %>
408
# Consider a Person class which returns an _array_ of Project instances
409
# from the <tt>projects</tt> reader method and responds to the
410
# <tt>projects_attributes=</tt> writer method:
414
# [@project1, @project2]
417
# def projects_attributes=(attributes)
418
# # Process the attributes hash
422
# This model can now be used with a nested fields_for. The block given to
423
# the nested fields_for call will be repeated for each instance in the
426
# <% form_for @person, :url => { :action => "update" } do |person_form| %>
428
# <% person_form.fields_for :projects do |project_fields| %>
429
# <% if project_fields.object.active? %>
430
# Name: <%= project_fields.text_field :name %>
435
# It's also possible to specify the instance to be used:
437
# <% form_for @person, :url => { :action => "update" } do |person_form| %>
439
# <% @person.projects.each do |project| %>
440
# <% if project.active? %>
441
# <% person_form.fields_for :projects, project do |project_fields| %>
442
# Name: <%= project_fields.text_field :name %>
448
# Or a collection to be used:
450
# <% form_for @person, :url => { :action => "update" } do |person_form| %>
452
# <% person_form.fields_for :projects, @active_projects do |project_fields| %>
453
# Name: <%= project_fields.text_field :name %>
457
# When projects is already an association on Person you can use
458
# +accepts_nested_attributes_for+ to define the writer method for you:
460
# class Person < ActiveRecord::Base
462
# accepts_nested_attributes_for :projects
465
# If you want to destroy any of the associated models through the
466
# form, you have to enable it first using the <tt>:allow_destroy</tt>
467
# option for +accepts_nested_attributes_for+:
469
# class Person < ActiveRecord::Base
471
# accepts_nested_attributes_for :projects, :allow_destroy => true
474
# This will allow you to specify which models to destroy in the
475
# attributes hash by adding a form element for the <tt>_delete</tt>
476
# parameter with a value that evaluates to +true+
477
# (eg. 1, '1', true, or 'true'):
479
# <% form_for @person, :url => { :action => "update" } do |person_form| %>
481
# <% person_form.fields_for :projects do |project_fields| %>
482
# Delete: <%= project_fields.check_box :_delete %>
485
def fields_for(record_or_name_or_array, *args, &block)
486
raise ArgumentError, "Missing block" unless block_given?
487
options = args.extract_options!
489
case record_or_name_or_array
491
object_name = record_or_name_or_array
494
object = record_or_name_or_array
495
object_name = ActionController::RecordIdentifier.singular_class_name(object)
498
builder = options[:builder] || ActionView::Base.default_form_builder
499
yield builder.new(object_name, object, self, options, block)
502
# Returns a label tag tailored for labelling an input field for a specified attribute (identified by +method+) on an object
503
# assigned to the template (identified by +object+). The text of label will default to the attribute name unless you specify
504
# it explicitly. Additional options on the label tag can be passed as a hash with +options+. These options will be tagged
505
# onto the HTML as an HTML element attribute as in the example shown, except for the <tt>:value</tt> option, which is designed to
506
# target labels for radio_button tags (where the value is used in the ID of the input tag).
509
# label(:post, :title)
510
# # => <label for="post_title">Title</label>
512
# label(:post, :title, "A short title")
513
# # => <label for="post_title">A short title</label>
515
# label(:post, :title, "A short title", :class => "title_label")
516
# # => <label for="post_title" class="title_label">A short title</label>
518
# label(:post, :privacy, "Public Post", :value => "public")
519
# # => <label for="post_privacy_public">Public Post</label>
521
def label(object_name, method, text = nil, options = {})
522
InstanceTag.new(object_name, method, self, options.delete(:object)).to_label_tag(text, options)
525
# Returns an input tag of the "text" type tailored for accessing a specified attribute (identified by +method+) on an object
526
# assigned to the template (identified by +object+). Additional options on the input tag can be passed as a
527
# hash with +options+. These options will be tagged onto the HTML as an HTML element attribute as in the example
531
# text_field(:post, :title, :size => 20)
532
# # => <input type="text" id="post_title" name="post[title]" size="20" value="#{@post.title}" />
534
# text_field(:post, :title, :class => "create_input")
535
# # => <input type="text" id="post_title" name="post[title]" value="#{@post.title}" class="create_input" />
537
# text_field(:session, :user, :onchange => "if $('session[user]').value == 'admin' { alert('Your login can not be admin!'); }")
538
# # => <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!'); }"/>
540
# text_field(:snippet, :code, :size => 20, :class => 'code_input')
541
# # => <input type="text" id="snippet_code" name="snippet[code]" size="20" value="#{@snippet.code}" class="code_input" />
543
def text_field(object_name, method, options = {})
544
InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("text", options)
547
# Returns an input tag of the "password" type tailored for accessing a specified attribute (identified by +method+) on an object
548
# assigned to the template (identified by +object+). Additional options on the input tag can be passed as a
549
# hash with +options+. These options will be tagged onto the HTML as an HTML element attribute as in the example
553
# password_field(:login, :pass, :size => 20)
554
# # => <input type="text" id="login_pass" name="login[pass]" size="20" value="#{@login.pass}" />
556
# password_field(:account, :secret, :class => "form_input")
557
# # => <input type="text" id="account_secret" name="account[secret]" value="#{@account.secret}" class="form_input" />
559
# password_field(:user, :password, :onchange => "if $('user[password]').length > 30 { alert('Your password needs to be shorter!'); }")
560
# # => <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!'); }"/>
562
# password_field(:account, :pin, :size => 20, :class => 'form_input')
563
# # => <input type="text" id="account_pin" name="account[pin]" size="20" value="#{@account.pin}" class="form_input" />
565
def password_field(object_name, method, options = {})
566
InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("password", options)
569
# Returns a hidden input tag tailored for accessing a specified attribute (identified by +method+) on an object
570
# assigned to the template (identified by +object+). Additional options on the input tag can be passed as a
571
# hash with +options+. These options will be tagged onto the HTML as an HTML element attribute as in the example
575
# hidden_field(:signup, :pass_confirm)
576
# # => <input type="hidden" id="signup_pass_confirm" name="signup[pass_confirm]" value="#{@signup.pass_confirm}" />
578
# hidden_field(:post, :tag_list)
579
# # => <input type="hidden" id="post_tag_list" name="post[tag_list]" value="#{@post.tag_list}" />
581
# hidden_field(:user, :token)
582
# # => <input type="hidden" id="user_token" name="user[token]" value="#{@user.token}" />
583
def hidden_field(object_name, method, options = {})
584
InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("hidden", options)
587
# Returns an file upload input tag tailored for accessing a specified attribute (identified by +method+) on an object
588
# assigned to the template (identified by +object+). Additional options on the input tag can be passed as a
589
# hash with +options+. These options will be tagged onto the HTML as an HTML element attribute as in the example
593
# file_field(:user, :avatar)
594
# # => <input type="file" id="user_avatar" name="user[avatar]" />
596
# file_field(:post, :attached, :accept => 'text/html')
597
# # => <input type="file" id="post_attached" name="post[attached]" />
599
# file_field(:attachment, :file, :class => 'file_input')
600
# # => <input type="file" id="attachment_file" name="attachment[file]" class="file_input" />
602
def file_field(object_name, method, options = {})
603
InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("file", options)
606
# Returns a textarea opening and closing tag set tailored for accessing a specified attribute (identified by +method+)
607
# on an object assigned to the template (identified by +object+). Additional options on the input tag can be passed as a
608
# hash with +options+.
611
# text_area(:post, :body, :cols => 20, :rows => 40)
612
# # => <textarea cols="20" rows="40" id="post_body" name="post[body]">
616
# text_area(:comment, :text, :size => "20x30")
617
# # => <textarea cols="20" rows="30" id="comment_text" name="comment[text]">
621
# text_area(:application, :notes, :cols => 40, :rows => 15, :class => 'app_input')
622
# # => <textarea cols="40" rows="15" id="application_notes" name="application[notes]" class="app_input">
623
# # #{@application.notes}
626
# text_area(:entry, :body, :size => "20x20", :disabled => 'disabled')
627
# # => <textarea cols="20" rows="20" id="entry_body" name="entry[body]" disabled="disabled">
630
def text_area(object_name, method, options = {})
631
InstanceTag.new(object_name, method, self, options.delete(:object)).to_text_area_tag(options)
634
# Returns a checkbox tag tailored for accessing a specified attribute (identified by +method+) on an object
635
# assigned to the template (identified by +object+). This object must be an instance object (@object) and not a local object.
636
# It's intended that +method+ returns an integer and if that integer is above zero, then the checkbox is checked.
637
# Additional options on the input tag can be passed as a hash with +options+. The +checked_value+ defaults to 1
638
# while the default +unchecked_value+ is set to 0 which is convenient for boolean values.
642
# The HTML specification says unchecked check boxes are not successful, and
643
# thus web browsers do not send them. Unfortunately this introduces a gotcha:
644
# if an Invoice model has a +paid+ flag, and in the form that edits a paid
645
# invoice the user unchecks its check box, no +paid+ parameter is sent. So,
646
# any mass-assignment idiom like
648
# @invoice.update_attributes(params[:invoice])
650
# wouldn't update the flag.
652
# To prevent this the helper generates a hidden field with the same name as
653
# the checkbox after the very check box. So, the client either sends only the
654
# hidden field (representing the check box is unchecked), or both fields.
655
# Since the HTML specification says key/value pairs have to be sent in the
656
# same order they appear in the form and Rails parameters extraction always
657
# gets the first occurrence of any given key, that works in ordinary forms.
659
# Unfortunately that workaround does not work when the check box goes
660
# within an array-like parameter, as in
662
# <% fields_for "project[invoice_attributes][]", invoice, :index => nil do |form| %>
663
# <%= form.check_box :paid %>
667
# because parameter name repetition is precisely what Rails seeks to distinguish
668
# the elements of the array.
671
# # Let's say that @post.validated? is 1:
672
# check_box("post", "validated")
673
# # => <input type="checkbox" id="post_validated" name="post[validated]" value="1" />
674
# # <input name="post[validated]" type="hidden" value="0" />
676
# # Let's say that @puppy.gooddog is "no":
677
# check_box("puppy", "gooddog", {}, "yes", "no")
678
# # => <input type="checkbox" id="puppy_gooddog" name="puppy[gooddog]" value="yes" />
679
# # <input name="puppy[gooddog]" type="hidden" value="no" />
681
# check_box("eula", "accepted", { :class => 'eula_check' }, "yes", "no")
682
# # => <input type="checkbox" class="eula_check" id="eula_accepted" name="eula[accepted]" value="yes" />
683
# # <input name="eula[accepted]" type="hidden" value="no" />
685
def check_box(object_name, method, options = {}, checked_value = "1", unchecked_value = "0")
686
InstanceTag.new(object_name, method, self, options.delete(:object)).to_check_box_tag(options, checked_value, unchecked_value)
689
# Returns a radio button tag for accessing a specified attribute (identified by +method+) on an object
690
# assigned to the template (identified by +object+). If the current value of +method+ is +tag_value+ the
691
# radio button will be checked.
693
# To force the radio button to be checked pass <tt>:checked => true</tt> in the
694
# +options+ hash. You may pass HTML options there as well.
697
# # Let's say that @post.category returns "rails":
698
# radio_button("post", "category", "rails")
699
# radio_button("post", "category", "java")
700
# # => <input type="radio" id="post_category_rails" name="post[category]" value="rails" checked="checked" />
701
# # <input type="radio" id="post_category_java" name="post[category]" value="java" />
703
# radio_button("user", "receive_newsletter", "yes")
704
# radio_button("user", "receive_newsletter", "no")
705
# # => <input type="radio" id="user_receive_newsletter_yes" name="user[receive_newsletter]" value="yes" />
706
# # <input type="radio" id="user_receive_newsletter_no" name="user[receive_newsletter]" value="no" checked="checked" />
707
def radio_button(object_name, method, tag_value, options = {})
708
InstanceTag.new(object_name, method, self, options.delete(:object)).to_radio_button_tag(tag_value, options)
712
class InstanceTag #:nodoc:
713
include Helpers::TagHelper, Helpers::FormTagHelper
715
attr_reader :method_name, :object_name
717
DEFAULT_FIELD_OPTIONS = { "size" => 30 }.freeze unless const_defined?(:DEFAULT_FIELD_OPTIONS)
718
DEFAULT_RADIO_OPTIONS = { }.freeze unless const_defined?(:DEFAULT_RADIO_OPTIONS)
719
DEFAULT_TEXT_AREA_OPTIONS = { "cols" => 40, "rows" => 20 }.freeze unless const_defined?(:DEFAULT_TEXT_AREA_OPTIONS)
721
def initialize(object_name, method_name, template_object, object = nil)
722
@object_name, @method_name = object_name.to_s.dup, method_name.to_s.dup
723
@template_object = template_object
725
if @object_name.sub!(/\[\]$/,"") || @object_name.sub!(/\[\]\]$/,"]")
726
if (object ||= @template_object.instance_variable_get("@#{Regexp.last_match.pre_match}")) && object.respond_to?(:to_param)
727
@auto_index = object.to_param
729
raise ArgumentError, "object[] naming but object param and @object var don't exist or don't respond to to_param: #{object.inspect}"
734
def to_label_tag(text = nil, options = {})
735
options = options.stringify_keys
736
tag_value = options.delete("value")
737
name_and_id = options.dup
738
name_and_id["id"] = name_and_id["for"]
739
add_default_name_and_id_for_value(tag_value, name_and_id)
740
options.delete("index")
741
options["for"] ||= name_and_id["id"]
742
content = (text.blank? ? nil : text.to_s) || method_name.humanize
743
label_tag(name_and_id["id"], content, options)
746
def to_input_field_tag(field_type, options = {})
747
options = options.stringify_keys
748
options["size"] = options["maxlength"] || DEFAULT_FIELD_OPTIONS["size"] unless options.key?("size")
749
options = DEFAULT_FIELD_OPTIONS.merge(options)
750
if field_type == "hidden"
751
options.delete("size")
753
options["type"] = field_type
754
options["value"] ||= value_before_type_cast(object) unless field_type == "file"
755
options["value"] &&= html_escape(options["value"])
756
add_default_name_and_id(options)
757
tag("input", options)
760
def to_radio_button_tag(tag_value, options = {})
761
options = DEFAULT_RADIO_OPTIONS.merge(options.stringify_keys)
762
options["type"] = "radio"
763
options["value"] = tag_value
764
if options.has_key?("checked")
765
cv = options.delete "checked"
766
checked = cv == true || cv == "checked"
768
checked = self.class.radio_button_checked?(value(object), tag_value)
770
options["checked"] = "checked" if checked
771
add_default_name_and_id_for_value(tag_value, options)
772
tag("input", options)
775
def to_text_area_tag(options = {})
776
options = DEFAULT_TEXT_AREA_OPTIONS.merge(options.stringify_keys)
777
add_default_name_and_id(options)
779
if size = options.delete("size")
780
options["cols"], options["rows"] = size.split("x") if size.respond_to?(:split)
783
content_tag("textarea", html_escape(options.delete('value') || value_before_type_cast(object)), options)
786
def to_check_box_tag(options = {}, checked_value = "1", unchecked_value = "0")
787
options = options.stringify_keys
788
options["type"] = "checkbox"
789
options["value"] = checked_value
790
if options.has_key?("checked")
791
cv = options.delete "checked"
792
checked = cv == true || cv == "checked"
794
checked = self.class.check_box_checked?(value(object), checked_value)
796
options["checked"] = "checked" if checked
797
add_default_name_and_id(options)
798
hidden = tag("input", "name" => options["name"], "type" => "hidden", "value" => options['disabled'] && checked ? checked_value : unchecked_value)
799
checkbox = tag("input", options)
800
(hidden + checkbox).html_safe!
803
def to_boolean_select_tag(options = {})
804
options = options.stringify_keys
805
add_default_name_and_id(options)
806
value = value(object)
808
tag_text << tag_options(options)
809
tag_text << "><option value=\"false\""
810
tag_text << " selected" if value == false
811
tag_text << ">False</option><option value=\"true\""
812
tag_text << " selected" if value
813
tag_text << ">True</option></select>"
816
def to_content_tag(tag_name, options = {})
817
content_tag(tag_name, value(object), options)
821
@object || @template_object.instance_variable_get("@#{@object_name}")
823
# As @object_name may contain the nested syntax (item[subobject]) we
824
# need to fallback to nil.
829
self.class.value(object, @method_name)
832
def value_before_type_cast(object)
833
self.class.value_before_type_cast(object, @method_name)
837
def value(object, method_name)
838
object.send method_name unless object.nil?
841
def value_before_type_cast(object, method_name)
843
object.respond_to?(method_name + "_before_type_cast") ?
844
object.send(method_name + "_before_type_cast") :
845
object.send(method_name)
849
def check_box_checked?(value, checked_value)
851
when TrueClass, FalseClass
858
value == checked_value
860
value.include?(checked_value)
866
def radio_button_checked?(value, checked_value)
867
value.to_s == checked_value.to_s
872
def add_default_name_and_id_for_value(tag_value, options)
873
unless tag_value.nil?
874
pretty_tag_value = tag_value.to_s.gsub(/\s/, "_").gsub(/\W/, "").downcase
875
specified_id = options["id"]
876
add_default_name_and_id(options)
877
options["id"] += "_#{pretty_tag_value}" unless specified_id
879
add_default_name_and_id(options)
883
def add_default_name_and_id(options)
884
if options.has_key?("index")
885
options["name"] ||= tag_name_with_index(options["index"])
886
options["id"] ||= tag_id_with_index(options["index"])
887
options.delete("index")
888
elsif defined?(@auto_index)
889
options["name"] ||= tag_name_with_index(@auto_index)
890
options["id"] ||= tag_id_with_index(@auto_index)
892
options["name"] ||= tag_name + (options.has_key?('multiple') ? '[]' : '')
893
options["id"] ||= tag_id
898
"#{@object_name}[#{sanitized_method_name}]"
901
def tag_name_with_index(index)
902
"#{@object_name}[#{index}][#{sanitized_method_name}]"
906
"#{sanitized_object_name}_#{sanitized_method_name}"
909
def tag_id_with_index(index)
910
"#{sanitized_object_name}_#{index}_#{sanitized_method_name}"
913
def sanitized_object_name
914
@sanitized_object_name ||= @object_name.gsub(/\]\[|[^-a-zA-Z0-9:.]/, "_").sub(/_$/, "")
917
def sanitized_method_name
918
@sanitized_method_name ||= @method_name.sub(/\?$/,"")
922
class FormBuilder #:nodoc:
923
# The methods which wrap a form helper call.
924
class_inheritable_accessor :field_helpers
925
self.field_helpers = (FormHelper.instance_methods - ['form_for'])
927
attr_accessor :object_name, :object, :options
929
def initialize(object_name, object, template, options, proc)
930
@nested_child_index = {}
931
@object_name, @object, @template, @options, @proc = object_name, object, template, options, proc
932
@default_options = @options ? @options.slice(:index) : {}
933
if @object_name.to_s.match(/\[\]$/)
934
if object ||= @template.instance_variable_get("@#{Regexp.last_match.pre_match}") and object.respond_to?(:to_param)
935
@auto_index = object.to_param
937
raise ArgumentError, "object[] naming but object param and @object var don't exist or don't respond to to_param: #{object.inspect}"
942
(field_helpers - %w(label check_box radio_button fields_for hidden_field)).each do |selector|
944
def #{selector}(method, options = {}) # def text_field(method, options = {})
945
@template.send( # @template.send(
946
#{selector.inspect}, # "text_field",
947
@object_name, # @object_name,
949
objectify_options(options)) # objectify_options(options))
952
class_eval src, __FILE__, __LINE__
955
def fields_for(record_or_name_or_array, *args, &block)
956
if options.has_key?(:index)
957
index = "[#{options[:index]}]"
958
elsif defined?(@auto_index)
959
self.object_name = @object_name.to_s.sub(/\[\]$/,"")
960
index = "[#{@auto_index}]"
966
args << {} unless args.last.is_a?(Hash)
967
args.last[:builder] ||= options[:builder]
970
case record_or_name_or_array
972
if nested_attributes_association?(record_or_name_or_array)
973
return fields_for_with_nested_attributes(record_or_name_or_array, args, block)
975
name = "#{object_name}#{index}[#{record_or_name_or_array}]"
978
object = record_or_name_or_array.last
979
name = "#{object_name}#{index}[#{ActionController::RecordIdentifier.singular_class_name(object)}]"
982
object = record_or_name_or_array
983
name = "#{object_name}#{index}[#{ActionController::RecordIdentifier.singular_class_name(object)}]"
987
@template.fields_for(name, *args, &block)
990
def label(method, text = nil, options = {})
991
@template.label(@object_name, method, text, objectify_options(options))
994
def check_box(method, options = {}, checked_value = "1", unchecked_value = "0")
995
@template.check_box(@object_name, method, objectify_options(options), checked_value, unchecked_value)
998
def radio_button(method, tag_value, options = {})
999
@template.radio_button(@object_name, method, tag_value, objectify_options(options))
1002
def hidden_field(method, options = {})
1003
@emitted_hidden_id = true if method == :id
1004
@template.hidden_field(@object_name, method, objectify_options(options))
1007
def error_message_on(method, *args)
1008
@template.error_message_on(@object, method, *args)
1011
def error_messages(options = {})
1012
@template.error_messages_for(@object_name, objectify_options(options))
1015
def submit(value = "Save changes", options = {})
1016
@template.submit_tag(value, options.reverse_merge(:id => "#{object_name}_submit"))
1019
def emitted_hidden_id?
1024
def objectify_options(options)
1025
@default_options.merge(options.merge(:object => @object))
1028
def nested_attributes_association?(association_name)
1029
@object.respond_to?("#{association_name}_attributes=")
1032
def fields_for_with_nested_attributes(association_name, args, block)
1033
name = "#{object_name}[#{association_name}_attributes]"
1034
association = args.first
1036
if association.respond_to?(:new_record?)
1037
association = [association] if @object.send(association_name).is_a?(Array)
1038
elsif !association.is_a?(Array)
1039
association = @object.send(association_name)
1042
if association.is_a?(Array)
1043
explicit_child_index = args.last[:child_index] if args.last.is_a?(Hash)
1044
association.map do |child|
1045
fields_for_nested_model("#{name}[#{explicit_child_index || nested_child_index(name)}]", child, args, block)
1048
fields_for_nested_model(name, association, args, block)
1052
def fields_for_nested_model(name, object, args, block)
1053
if object.new_record?
1054
@template.fields_for(name, object, *args, &block)
1056
@template.fields_for(name, object, *args) do |builder|
1058
@template.concat builder.hidden_field(:id) unless builder.emitted_hidden_id?
1063
def nested_child_index(name)
1064
@nested_child_index[name] ||= -1
1065
@nested_child_index[name] += 1
1071
cattr_accessor :default_form_builder
1072
self.default_form_builder = ::ActionView::Helpers::FormBuilder