PageRenderTime 170ms CodeModel.GetById 12ms RepoModel.GetById 2ms app.codeStats 0ms

/actionpack/lib/action_view/helpers/form_helper.rb

https://github.com/ghar/rails
Ruby | 1142 lines | 289 code | 60 blank | 793 comment | 52 complexity | 8925f423a9c4f14a01a06ca5d3e55b43 MD5 | raw file
  1. require 'cgi'
  2. require 'action_view/helpers/date_helper'
  3. require 'action_view/helpers/tag_helper'
  4. require 'action_view/helpers/form_tag_helper'
  5. require 'active_support/core_ext/class/attribute'
  6. require 'active_support/core_ext/hash/slice'
  7. require 'active_support/core_ext/module/method_names'
  8. require 'active_support/core_ext/object/blank'
  9. require 'active_support/core_ext/string/output_safety'
  10. require 'active_support/core_ext/array/extract_options'
  11. module ActionView
  12. # = Action View Form Helpers
  13. module Helpers
  14. # Form helpers are designed to make working with resources much easier
  15. # compared to using vanilla HTML.
  16. #
  17. # Forms for models are created with +form_for+. That method yields a form
  18. # builder that knows the model the form is about. The form builder is thus
  19. # able to generate default values for input fields that correspond to model
  20. # attributes, and also convenient names, IDs, endpoints, etc.
  21. #
  22. # Conventions in the generated field names allow controllers to receive form
  23. # data nicely structured in +params+ with no effort on your side.
  24. #
  25. # For example, to create a new person you typically set up a new instance of
  26. # +Person+ in the <tt>PeopleController#new</tt> action, <tt>@person</tt>, and
  27. # pass it to +form_for+:
  28. #
  29. # <%= form_for @person do |f| %>
  30. # <%= f.label :first_name %>:
  31. # <%= f.text_field :first_name %><br />
  32. #
  33. # <%= f.label :last_name %>:
  34. # <%= f.text_field :last_name %><br />
  35. #
  36. # <%= f.submit %>
  37. # <% end %>
  38. #
  39. # The HTML generated for this would be (modulus formatting):
  40. #
  41. # <form action="/people" class="new_person" id="new_person" method="post">
  42. # <div style="margin:0;padding:0;display:inline">
  43. # <input name="authenticity_token" type="hidden" value="NrOp5bsjoLRuK8IW5+dQEYjKGUJDe7TQoZVvq95Wteg=" />
  44. # </div>
  45. # <label for="person_first_name">First name</label>:
  46. # <input id="person_first_name" name="person[first_name]" size="30" type="text" /><br />
  47. #
  48. # <label for="person_last_name">Last name</label>:
  49. # <input id="person_last_name" name="person[last_name]" size="30" type="text" /><br />
  50. #
  51. # <input name="commit" type="submit" value="Create Person" />
  52. # </form>
  53. #
  54. # As you see, the HTML reflects knowledge about the resource in several spots,
  55. # like the path the form should be submitted to, or the names of the input fields.
  56. #
  57. # In particular, thanks to the conventions followed in the generated field names, the
  58. # controller gets a nested hash <tt>params[:person]</tt> with the person attributes
  59. # set in the form. That hash is ready to be passed to <tt>Person.create</tt>:
  60. #
  61. # if @person = Person.create(params[:person])
  62. # # success
  63. # else
  64. # # error handling
  65. # end
  66. #
  67. # Interestingly, the exact same view code in the previous example can be used to edit
  68. # a person. If <tt>@person</tt> is an existing record with name "John Smith" and ID 256,
  69. # the code above as is would yield instead:
  70. #
  71. # <form action="/people/256" class="edit_person" id="edit_person_256" method="post">
  72. # <div style="margin:0;padding:0;display:inline">
  73. # <input name="_method" type="hidden" value="put" />
  74. # <input name="authenticity_token" type="hidden" value="NrOp5bsjoLRuK8IW5+dQEYjKGUJDe7TQoZVvq95Wteg=" />
  75. # </div>
  76. # <label for="person_first_name">First name</label>:
  77. # <input id="person_first_name" name="person[first_name]" size="30" type="text" value="John" /><br />
  78. #
  79. # <label for="person_last_name">Last name</label>:
  80. # <input id="person_last_name" name="person[last_name]" size="30" type="text" value="Smith" /><br />
  81. #
  82. # <input name="commit" type="submit" value="Update Person" />
  83. # </form>
  84. #
  85. # Note that the endpoint, default values, and submit button label are tailored for <tt>@person</tt>.
  86. # That works that way because the involved helpers know whether the resource is a new record or not,
  87. # and generate HTML accordingly.
  88. #
  89. # The controller would receive the form data again in <tt>params[:person]</tt>, ready to be
  90. # passed to <tt>Person#update_attributes</tt>:
  91. #
  92. # if @person.update_attributes(params[:person])
  93. # # success
  94. # else
  95. # # error handling
  96. # end
  97. #
  98. # That's how you typically work with resources.
  99. module FormHelper
  100. extend ActiveSupport::Concern
  101. include FormTagHelper
  102. include UrlHelper
  103. # Converts the given object to an ActiveModel compliant one.
  104. def convert_to_model(object)
  105. object.respond_to?(:to_model) ? object.to_model : object
  106. end
  107. # Creates a form and a scope around a specific model object that is used
  108. # as a base for questioning about values for the fields.
  109. #
  110. # Rails provides succinct resource-oriented form generation with +form_for+
  111. # like this:
  112. #
  113. # <%= form_for @offer do |f| %>
  114. # <%= f.label :version, 'Version' %>:
  115. # <%= f.text_field :version %><br />
  116. # <%= f.label :author, 'Author' %>:
  117. # <%= f.text_field :author %><br />
  118. # <%= f.submit %>
  119. # <% end %>
  120. #
  121. # There, +form_for+ is able to generate the rest of RESTful form
  122. # parameters based on introspection on the record, but to understand what
  123. # it does we need to dig first into the alternative generic usage it is
  124. # based upon.
  125. #
  126. # === Generic form_for
  127. #
  128. # The generic way to call +form_for+ yields a form builder around a
  129. # model:
  130. #
  131. # <%= form_for :person do |f| %>
  132. # First name: <%= f.text_field :first_name %><br />
  133. # Last name : <%= f.text_field :last_name %><br />
  134. # Biography : <%= f.text_area :biography %><br />
  135. # Admin? : <%= f.check_box :admin %><br />
  136. # <%= f.submit %>
  137. # <% end %>
  138. #
  139. # There, the argument is a symbol or string with the name of the
  140. # object the form is about.
  141. #
  142. # The form builder acts as a regular form helper that somehow carries the
  143. # model. Thus, the idea is that
  144. #
  145. # <%= f.text_field :first_name %>
  146. #
  147. # gets expanded to
  148. #
  149. # <%= text_field :person, :first_name %>
  150. #
  151. # The rightmost argument to +form_for+ is an
  152. # optional hash of options:
  153. #
  154. # * <tt>:url</tt> - The URL the form is submitted to. It takes the same
  155. # fields you pass to +url_for+ or +link_to+. In particular you may pass
  156. # here a named route directly as well. Defaults to the current action.
  157. # * <tt>:html</tt> - Optional HTML attributes for the form tag.
  158. #
  159. # Also note that +form_for+ doesn't create an exclusive scope. It's still
  160. # possible to use both the stand-alone FormHelper methods and methods
  161. # from FormTagHelper. For example:
  162. #
  163. # <%= form_for @person do |f| %>
  164. # First name: <%= f.text_field :first_name %>
  165. # Last name : <%= f.text_field :last_name %>
  166. # Biography : <%= text_area :person, :biography %>
  167. # Admin? : <%= check_box_tag "person[admin]", @person.company.admin? %>
  168. # <%= f.submit %>
  169. # <% end %>
  170. #
  171. # This also works for the methods in FormOptionHelper and DateHelper that
  172. # are designed to work with an object as base, like
  173. # FormOptionHelper#collection_select and DateHelper#datetime_select.
  174. #
  175. # === Resource-oriented style
  176. #
  177. # As we said above, in addition to manually configuring the +form_for+
  178. # call, you can rely on automated resource identification, which will use
  179. # the conventions and named routes of that approach. This is the
  180. # preferred way to use +form_for+ nowadays.
  181. #
  182. # For example, if <tt>@post</tt> is an existing record you want to edit
  183. #
  184. # <%= form_for @post do |f| %>
  185. # ...
  186. # <% end %>
  187. #
  188. # is equivalent to something like:
  189. #
  190. # <%= form_for @post, :as => :post, :url => post_path(@post), :method => :put, :html => { :class => "edit_post", :id => "edit_post_45" } do |f| %>
  191. # ...
  192. # <% end %>
  193. #
  194. # And for new records
  195. #
  196. # <%= form_for(Post.new) do |f| %>
  197. # ...
  198. # <% end %>
  199. #
  200. # is equivalent to something like:
  201. #
  202. # <%= form_for @post, :as => :post, :url => posts_path, :html => { :class => "new_post", :id => "new_post" } do |f| %>
  203. # ...
  204. # <% end %>
  205. #
  206. # You can also overwrite the individual conventions, like this:
  207. #
  208. # <%= form_for(@post, :url => super_posts_path) do |f| %>
  209. # ...
  210. # <% end %>
  211. #
  212. # You can also set the answer format, like this:
  213. #
  214. # <%= form_for(@post, :format => :json) do |f| %>
  215. # ...
  216. # <% end %>
  217. #
  218. # If you have an object that needs to be represented as a different
  219. # parameter, like a Person that acts as a Client:
  220. #
  221. # <%= form_for(@person, :as => :client) do |f| %>
  222. # ...
  223. # <% end %>
  224. #
  225. # For namespaced routes, like +admin_post_url+:
  226. #
  227. # <%= form_for([:admin, @post]) do |f| %>
  228. # ...
  229. # <% end %>
  230. #
  231. # If your resource has associations defined, for example, you want to add comments
  232. # to the document given that the routes are set correctly:
  233. #
  234. # <%= form_for([@document, @comment]) do |f| %>
  235. # ...
  236. # <% end %>
  237. #
  238. # Where <tt>@document = Document.find(params[:id])</tt> and
  239. # <tt>@comment = Comment.new</tt>.
  240. #
  241. # === Setting the method
  242. #
  243. # You can force the form to use the full array of HTTP verbs by setting
  244. #
  245. # :method => (:get|:post|:put|:delete)
  246. #
  247. # in the options hash. If the verb is not GET or POST, which are natively supported by HTML forms, the
  248. # form will be set to POST and a hidden input called _method will carry the intended verb for the server
  249. # to interpret.
  250. #
  251. # === Unobtrusive JavaScript
  252. #
  253. # Specifying:
  254. #
  255. # :remote => true
  256. #
  257. # in the options hash creates a form that will allow the unobtrusive JavaScript drivers to modify its
  258. # behavior. The expected default behavior is an XMLHttpRequest in the background instead of the regular
  259. # POST arrangement, but ultimately the behavior is the choice of the JavaScript driver implementor.
  260. # Even though it's using JavaScript to serialize the form elements, the form submission will work just like
  261. # a regular submission as viewed by the receiving side (all elements available in <tt>params</tt>).
  262. #
  263. # Example:
  264. #
  265. # <%= form_for(@post, :remote => true) do |f| %>
  266. # ...
  267. # <% end %>
  268. #
  269. # The HTML generated for this would be:
  270. #
  271. # <form action='http://www.example.com' method='post' data-remote='true'>
  272. # <div style='margin:0;padding:0;display:inline'>
  273. # <input name='_method' type='hidden' value='put' />
  274. # </div>
  275. # ...
  276. # </form>
  277. #
  278. # === Removing hidden model id's
  279. #
  280. # The form_for method automatically includes the model id as a hidden field in the form.
  281. # This is used to maintain the correlation between the form data and its associated model.
  282. # Some ORM systems do not use IDs on nested models so in this case you want to be able
  283. # to disable the hidden id.
  284. #
  285. # In the following example the Post model has many Comments stored within it in a NoSQL database,
  286. # thus there is no primary key for comments.
  287. #
  288. # Example:
  289. #
  290. # <%= form_for(@post) do |f| %>
  291. # <% f.fields_for(:comments, :include_id => false) do |cf| %>
  292. # ...
  293. # <% end %>
  294. # <% end %>
  295. #
  296. # === Customized form builders
  297. #
  298. # You can also build forms using a customized FormBuilder class. Subclass
  299. # FormBuilder and override or define some more helpers, then use your
  300. # custom builder. For example, let's say you made a helper to
  301. # automatically add labels to form inputs.
  302. #
  303. # <%= form_for @person, :url => { :action => "create" }, :builder => LabellingFormBuilder do |f| %>
  304. # <%= f.text_field :first_name %>
  305. # <%= f.text_field :last_name %>
  306. # <%= f.text_area :biography %>
  307. # <%= f.check_box :admin %>
  308. # <%= f.submit %>
  309. # <% end %>
  310. #
  311. # In this case, if you use this:
  312. #
  313. # <%= render f %>
  314. #
  315. # The rendered template is <tt>people/_labelling_form</tt> and the local
  316. # variable referencing the form builder is called
  317. # <tt>labelling_form</tt>.
  318. #
  319. # The custom FormBuilder class is automatically merged with the options
  320. # of a nested fields_for call, unless it's explicitly set.
  321. #
  322. # In many cases you will want to wrap the above in another helper, so you
  323. # could do something like the following:
  324. #
  325. # def labelled_form_for(record_or_name_or_array, *args, &proc)
  326. # options = args.extract_options!
  327. # form_for(record_or_name_or_array, *(args << options.merge(:builder => LabellingFormBuilder)), &proc)
  328. # end
  329. #
  330. # If you don't need to attach a form to a model instance, then check out
  331. # FormTagHelper#form_tag.
  332. #
  333. # === Form to external resources
  334. #
  335. # When you build forms to external resources sometimes you need to set an authenticity token or just render a form
  336. # without it, for example when you submit data to a payment gateway number and types of fields could be limited.
  337. #
  338. # To set an authenticity token you need to pass an <tt>:authenticity_token</tt> parameter
  339. #
  340. # <%= form_for @invoice, :url => external_url, :authenticity_token => 'external_token' do |f|
  341. # ...
  342. # <% end %>
  343. #
  344. # If you don't want to an authenticity token field be rendered at all just pass <tt>false</tt>:
  345. #
  346. # <%= form_for @invoice, :url => external_url, :authenticity_token => false do |f|
  347. # ...
  348. # <% end %>
  349. def form_for(record, options = {}, &proc)
  350. raise ArgumentError, "Missing block" unless block_given?
  351. options[:html] ||= {}
  352. case record
  353. when String, Symbol
  354. object_name = record
  355. object = nil
  356. else
  357. object = record.is_a?(Array) ? record.last : record
  358. object_name = options[:as] || ActiveModel::Naming.param_key(object)
  359. apply_form_for_options!(record, options)
  360. end
  361. options[:html][:remote] = options.delete(:remote) if options.has_key?(:remote)
  362. options[:html][:method] = options.delete(:method) if options.has_key?(:method)
  363. options[:html][:authenticity_token] = options.delete(:authenticity_token)
  364. builder = options[:parent_builder] = instantiate_builder(object_name, object, options, &proc)
  365. fields_for = fields_for(object_name, object, options, &proc)
  366. default_options = builder.multipart? ? { :multipart => true } : {}
  367. output = form_tag(options.delete(:url) || {}, default_options.merge!(options.delete(:html)))
  368. output << fields_for
  369. output.safe_concat('</form>')
  370. end
  371. def apply_form_for_options!(object_or_array, options) #:nodoc:
  372. object = object_or_array.is_a?(Array) ? object_or_array.last : object_or_array
  373. object = convert_to_model(object)
  374. as = options[:as]
  375. action, method = object.respond_to?(:persisted?) && object.persisted? ? [:edit, :put] : [:new, :post]
  376. options[:html].reverse_merge!(
  377. :class => as ? "#{as}_#{action}" : dom_class(object, action),
  378. :id => as ? "#{as}_#{action}" : dom_id(object, action),
  379. :method => method
  380. )
  381. options[:url] ||= polymorphic_path(object_or_array, :format => options.delete(:format))
  382. end
  383. private :apply_form_for_options!
  384. # Creates a scope around a specific model object like form_for, but
  385. # doesn't create the form tags themselves. This makes fields_for suitable
  386. # for specifying additional model objects in the same form.
  387. #
  388. # === Generic Examples
  389. #
  390. # <%= form_for @person do |person_form| %>
  391. # First name: <%= person_form.text_field :first_name %>
  392. # Last name : <%= person_form.text_field :last_name %>
  393. #
  394. # <%= fields_for @person.permission do |permission_fields| %>
  395. # Admin? : <%= permission_fields.check_box :admin %>
  396. # <% end %>
  397. #
  398. # <%= f.submit %>
  399. # <% end %>
  400. #
  401. # ...or if you have an object that needs to be represented as a different
  402. # parameter, like a Client that acts as a Person:
  403. #
  404. # <%= fields_for :person, @client do |permission_fields| %>
  405. # Admin?: <%= permission_fields.check_box :admin %>
  406. # <% end %>
  407. #
  408. # ...or if you don't have an object, just a name of the parameter:
  409. #
  410. # <%= fields_for :person do |permission_fields| %>
  411. # Admin?: <%= permission_fields.check_box :admin %>
  412. # <% end %>
  413. #
  414. # Note: This also works for the methods in FormOptionHelper and
  415. # DateHelper that are designed to work with an object as base, like
  416. # FormOptionHelper#collection_select and DateHelper#datetime_select.
  417. #
  418. # === Nested Attributes Examples
  419. #
  420. # When the object belonging to the current scope has a nested attribute
  421. # writer for a certain attribute, fields_for will yield a new scope
  422. # for that attribute. This allows you to create forms that set or change
  423. # the attributes of a parent object and its associations in one go.
  424. #
  425. # Nested attribute writers are normal setter methods named after an
  426. # association. The most common way of defining these writers is either
  427. # with +accepts_nested_attributes_for+ in a model definition or by
  428. # defining a method with the proper name. For example: the attribute
  429. # writer for the association <tt>:address</tt> is called
  430. # <tt>address_attributes=</tt>.
  431. #
  432. # Whether a one-to-one or one-to-many style form builder will be yielded
  433. # depends on whether the normal reader method returns a _single_ object
  434. # or an _array_ of objects.
  435. #
  436. # ==== One-to-one
  437. #
  438. # Consider a Person class which returns a _single_ Address from the
  439. # <tt>address</tt> reader method and responds to the
  440. # <tt>address_attributes=</tt> writer method:
  441. #
  442. # class Person
  443. # def address
  444. # @address
  445. # end
  446. #
  447. # def address_attributes=(attributes)
  448. # # Process the attributes hash
  449. # end
  450. # end
  451. #
  452. # This model can now be used with a nested fields_for, like so:
  453. #
  454. # <%= form_for @person do |person_form| %>
  455. # ...
  456. # <%= person_form.fields_for :address do |address_fields| %>
  457. # Street : <%= address_fields.text_field :street %>
  458. # Zip code: <%= address_fields.text_field :zip_code %>
  459. # <% end %>
  460. # ...
  461. # <% end %>
  462. #
  463. # When address is already an association on a Person you can use
  464. # +accepts_nested_attributes_for+ to define the writer method for you:
  465. #
  466. # class Person < ActiveRecord::Base
  467. # has_one :address
  468. # accepts_nested_attributes_for :address
  469. # end
  470. #
  471. # If you want to destroy the associated model through the form, you have
  472. # to enable it first using the <tt>:allow_destroy</tt> option for
  473. # +accepts_nested_attributes_for+:
  474. #
  475. # class Person < ActiveRecord::Base
  476. # has_one :address
  477. # accepts_nested_attributes_for :address, :allow_destroy => true
  478. # end
  479. #
  480. # Now, when you use a form element with the <tt>_destroy</tt> parameter,
  481. # with a value that evaluates to +true+, you will destroy the associated
  482. # model (eg. 1, '1', true, or 'true'):
  483. #
  484. # <%= form_for @person do |person_form| %>
  485. # ...
  486. # <%= person_form.fields_for :address do |address_fields| %>
  487. # ...
  488. # Delete: <%= address_fields.check_box :_destroy %>
  489. # <% end %>
  490. # ...
  491. # <% end %>
  492. #
  493. # ==== One-to-many
  494. #
  495. # Consider a Person class which returns an _array_ of Project instances
  496. # from the <tt>projects</tt> reader method and responds to the
  497. # <tt>projects_attributes=</tt> writer method:
  498. #
  499. # class Person
  500. # def projects
  501. # [@project1, @project2]
  502. # end
  503. #
  504. # def projects_attributes=(attributes)
  505. # # Process the attributes hash
  506. # end
  507. # end
  508. #
  509. # Note that the <tt>projects_attributes=</tt> writer method is in fact
  510. # required for fields_for to correctly identify <tt>:projects</tt> as a
  511. # collection, and the correct indices to be set in the form markup.
  512. #
  513. # When projects is already an association on Person you can use
  514. # +accepts_nested_attributes_for+ to define the writer method for you:
  515. #
  516. # class Person < ActiveRecord::Base
  517. # has_many :projects
  518. # accepts_nested_attributes_for :projects
  519. # end
  520. #
  521. # This model can now be used with a nested fields_for. The block given to
  522. # the nested fields_for call will be repeated for each instance in the
  523. # collection:
  524. #
  525. # <%= form_for @person do |person_form| %>
  526. # ...
  527. # <%= person_form.fields_for :projects do |project_fields| %>
  528. # <% if project_fields.object.active? %>
  529. # Name: <%= project_fields.text_field :name %>
  530. # <% end %>
  531. # <% end %>
  532. # ...
  533. # <% end %>
  534. #
  535. # It's also possible to specify the instance to be used:
  536. #
  537. # <%= form_for @person do |person_form| %>
  538. # ...
  539. # <% @person.projects.each do |project| %>
  540. # <% if project.active? %>
  541. # <%= person_form.fields_for :projects, project do |project_fields| %>
  542. # Name: <%= project_fields.text_field :name %>
  543. # <% end %>
  544. # <% end %>
  545. # <% end %>
  546. # ...
  547. # <% end %>
  548. #
  549. # Or a collection to be used:
  550. #
  551. # <%= form_for @person do |person_form| %>
  552. # ...
  553. # <%= person_form.fields_for :projects, @active_projects do |project_fields| %>
  554. # Name: <%= project_fields.text_field :name %>
  555. # <% end %>
  556. # ...
  557. # <% end %>
  558. #
  559. # When projects is already an association on Person you can use
  560. # +accepts_nested_attributes_for+ to define the writer method for you:
  561. #
  562. # class Person < ActiveRecord::Base
  563. # has_many :projects
  564. # accepts_nested_attributes_for :projects
  565. # end
  566. #
  567. # If you want to destroy any of the associated models through the
  568. # form, you have to enable it first using the <tt>:allow_destroy</tt>
  569. # option for +accepts_nested_attributes_for+:
  570. #
  571. # class Person < ActiveRecord::Base
  572. # has_many :projects
  573. # accepts_nested_attributes_for :projects, :allow_destroy => true
  574. # end
  575. #
  576. # This will allow you to specify which models to destroy in the
  577. # attributes hash by adding a form element for the <tt>_destroy</tt>
  578. # parameter with a value that evaluates to +true+
  579. # (eg. 1, '1', true, or 'true'):
  580. #
  581. # <%= form_for @person do |person_form| %>
  582. # ...
  583. # <%= person_form.fields_for :projects do |project_fields| %>
  584. # Delete: <%= project_fields.check_box :_destroy %>
  585. # <% end %>
  586. # ...
  587. # <% end %>
  588. def fields_for(record_name, record_object = nil, options = {}, &block)
  589. builder = instantiate_builder(record_name, record_object, options, &block)
  590. output = capture(builder, &block)
  591. output.concat builder.hidden_field(:id) if output && options[:hidden_field_id] && !builder.emitted_hidden_id?
  592. output
  593. end
  594. # Returns a label tag tailored for labelling an input field for a specified attribute (identified by +method+) on an object
  595. # assigned to the template (identified by +object+). The text of label will default to the attribute name unless a translation
  596. # is found in the current I18n locale (through helpers.label.<modelname>.<attribute>) or you specify it explicitly.
  597. # Additional options on the label tag can be passed as a hash with +options+. These options will be tagged
  598. # onto the HTML as an HTML element attribute as in the example shown, except for the <tt>:value</tt> option, which is designed to
  599. # target labels for radio_button tags (where the value is used in the ID of the input tag).
  600. #
  601. # ==== Examples
  602. # label(:post, :title)
  603. # # => <label for="post_title">Title</label>
  604. #
  605. # You can localize your labels based on model and attribute names.
  606. # For example you can define the following in your locale (e.g. en.yml)
  607. #
  608. # helpers:
  609. # label:
  610. # post:
  611. # body: "Write your entire text here"
  612. #
  613. # Which then will result in
  614. #
  615. # label(:post, :body)
  616. # # => <label for="post_body">Write your entire text here</label>
  617. #
  618. # Localization can also be based purely on the translation of the attribute-name
  619. # (if you are using ActiveRecord):
  620. #
  621. # activerecord:
  622. # attributes:
  623. # post:
  624. # cost: "Total cost"
  625. #
  626. # label(:post, :cost)
  627. # # => <label for="post_cost">Total cost</label>
  628. #
  629. # label(:post, :title, "A short title")
  630. # # => <label for="post_title">A short title</label>
  631. #
  632. # label(:post, :title, "A short title", :class => "title_label")
  633. # # => <label for="post_title" class="title_label">A short title</label>
  634. #
  635. # label(:post, :privacy, "Public Post", :value => "public")
  636. # # => <label for="post_privacy_public">Public Post</label>
  637. #
  638. # label(:post, :terms) do
  639. # 'Accept <a href="/terms">Terms</a>.'
  640. # end
  641. def label(object_name, method, content_or_options = nil, options = nil, &block)
  642. content_is_options = content_or_options.is_a?(Hash)
  643. if content_is_options || block_given?
  644. options = content_or_options if content_is_options
  645. text = nil
  646. else
  647. text = content_or_options
  648. end
  649. options ||= {}
  650. InstanceTag.new(object_name, method, self, options.delete(:object)).to_label_tag(text, options, &block)
  651. end
  652. # Returns an input tag of the "text" type tailored for accessing a specified attribute (identified by +method+) on an object
  653. # assigned to the template (identified by +object+). Additional options on the input tag can be passed as a
  654. # hash with +options+. These options will be tagged onto the HTML as an HTML element attribute as in the example
  655. # shown.
  656. #
  657. # ==== Examples
  658. # text_field(:post, :title, :size => 20)
  659. # # => <input type="text" id="post_title" name="post[title]" size="20" value="#{@post.title}" />
  660. #
  661. # text_field(:post, :title, :class => "create_input")
  662. # # => <input type="text" id="post_title" name="post[title]" value="#{@post.title}" class="create_input" />
  663. #
  664. # text_field(:session, :user, :onchange => "if $('session[user]').value == 'admin' { alert('Your login can not be admin!'); }")
  665. # # => <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!'); }"/>
  666. #
  667. # text_field(:snippet, :code, :size => 20, :class => 'code_input')
  668. # # => <input type="text" id="snippet_code" name="snippet[code]" size="20" value="#{@snippet.code}" class="code_input" />
  669. #
  670. def text_field(object_name, method, options = {})
  671. InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("text", options)
  672. end
  673. # Returns an input tag of the "password" type tailored for accessing a specified attribute (identified by +method+) on an object
  674. # assigned to the template (identified by +object+). Additional options on the input tag can be passed as a
  675. # hash with +options+. These options will be tagged onto the HTML as an HTML element attribute as in the example
  676. # shown.
  677. #
  678. # ==== Examples
  679. # password_field(:login, :pass, :size => 20)
  680. # # => <input type="password" id="login_pass" name="login[pass]" size="20" />
  681. #
  682. # password_field(:account, :secret, :class => "form_input", :value => @account.secret)
  683. # # => <input type="password" id="account_secret" name="account[secret]" value="#{@account.secret}" class="form_input" />
  684. #
  685. # password_field(:user, :password, :onchange => "if $('user[password]').length > 30 { alert('Your password needs to be shorter!'); }")
  686. # # => <input type="password" id="user_password" name="user[password]" onchange = "if $('user[password]').length > 30 { alert('Your password needs to be shorter!'); }"/>
  687. #
  688. # password_field(:account, :pin, :size => 20, :class => 'form_input')
  689. # # => <input type="password" id="account_pin" name="account[pin]" size="20" class="form_input" />
  690. #
  691. def password_field(object_name, method, options = {})
  692. InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("password", { :value => nil }.merge!(options))
  693. end
  694. # Returns a hidden input tag tailored for accessing a specified attribute (identified by +method+) on an object
  695. # assigned to the template (identified by +object+). Additional options on the input tag can be passed as a
  696. # hash with +options+. These options will be tagged onto the HTML as an HTML element attribute as in the example
  697. # shown.
  698. #
  699. # ==== Examples
  700. # hidden_field(:signup, :pass_confirm)
  701. # # => <input type="hidden" id="signup_pass_confirm" name="signup[pass_confirm]" value="#{@signup.pass_confirm}" />
  702. #
  703. # hidden_field(:post, :tag_list)
  704. # # => <input type="hidden" id="post_tag_list" name="post[tag_list]" value="#{@post.tag_list}" />
  705. #
  706. # hidden_field(:user, :token)
  707. # # => <input type="hidden" id="user_token" name="user[token]" value="#{@user.token}" />
  708. def hidden_field(object_name, method, options = {})
  709. InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("hidden", options)
  710. end
  711. # Returns a file upload input tag tailored for accessing a specified attribute (identified by +method+) on an object
  712. # assigned to the template (identified by +object+). Additional options on the input tag can be passed as a
  713. # hash with +options+. These options will be tagged onto the HTML as an HTML element attribute as in the example
  714. # shown.
  715. #
  716. # Using this method inside a +form_for+ block will set the enclosing form's encoding to <tt>multipart/form-data</tt>.
  717. #
  718. # ==== Examples
  719. # file_field(:user, :avatar)
  720. # # => <input type="file" id="user_avatar" name="user[avatar]" />
  721. #
  722. # file_field(:post, :attached, :accept => 'text/html')
  723. # # => <input type="file" id="post_attached" name="post[attached]" />
  724. #
  725. # file_field(:attachment, :file, :class => 'file_input')
  726. # # => <input type="file" id="attachment_file" name="attachment[file]" class="file_input" />
  727. #
  728. def file_field(object_name, method, options = {})
  729. InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("file", options.update({:size => nil}))
  730. end
  731. # Returns a textarea opening and closing tag set tailored for accessing a specified attribute (identified by +method+)
  732. # on an object assigned to the template (identified by +object+). Additional options on the input tag can be passed as a
  733. # hash with +options+.
  734. #
  735. # ==== Examples
  736. # text_area(:post, :body, :cols => 20, :rows => 40)
  737. # # => <textarea cols="20" rows="40" id="post_body" name="post[body]">
  738. # # #{@post.body}
  739. # # </textarea>
  740. #
  741. # text_area(:comment, :text, :size => "20x30")
  742. # # => <textarea cols="20" rows="30" id="comment_text" name="comment[text]">
  743. # # #{@comment.text}
  744. # # </textarea>
  745. #
  746. # text_area(:application, :notes, :cols => 40, :rows => 15, :class => 'app_input')
  747. # # => <textarea cols="40" rows="15" id="application_notes" name="application[notes]" class="app_input">
  748. # # #{@application.notes}
  749. # # </textarea>
  750. #
  751. # text_area(:entry, :body, :size => "20x20", :disabled => 'disabled')
  752. # # => <textarea cols="20" rows="20" id="entry_body" name="entry[body]" disabled="disabled">
  753. # # #{@entry.body}
  754. # # </textarea>
  755. def text_area(object_name, method, options = {})
  756. InstanceTag.new(object_name, method, self, options.delete(:object)).to_text_area_tag(options)
  757. end
  758. # Returns a checkbox tag tailored for accessing a specified attribute (identified by +method+) on an object
  759. # assigned to the template (identified by +object+). This object must be an instance object (@object) and not a local object.
  760. # It's intended that +method+ returns an integer and if that integer is above zero, then the checkbox is checked.
  761. # Additional options on the input tag can be passed as a hash with +options+. The +checked_value+ defaults to 1
  762. # while the default +unchecked_value+ is set to 0 which is convenient for boolean values.
  763. #
  764. # ==== Gotcha
  765. #
  766. # The HTML specification says unchecked check boxes are not successful, and
  767. # thus web browsers do not send them. Unfortunately this introduces a gotcha:
  768. # if an +Invoice+ model has a +paid+ flag, and in the form that edits a paid
  769. # invoice the user unchecks its check box, no +paid+ parameter is sent. So,
  770. # any mass-assignment idiom like
  771. #
  772. # @invoice.update_attributes(params[:invoice])
  773. #
  774. # wouldn't update the flag.
  775. #
  776. # To prevent this the helper generates an auxiliary hidden field before
  777. # the very check box. The hidden field has the same name and its
  778. # attributes mimic an unchecked check box.
  779. #
  780. # This way, the client either sends only the hidden field (representing
  781. # the check box is unchecked), or both fields. Since the HTML specification
  782. # says key/value pairs have to be sent in the same order they appear in the
  783. # form, and parameters extraction gets the last occurrence of any repeated
  784. # key in the query string, that works for ordinary forms.
  785. #
  786. # Unfortunately that workaround does not work when the check box goes
  787. # within an array-like parameter, as in
  788. #
  789. # <%= fields_for "project[invoice_attributes][]", invoice, :index => nil do |form| %>
  790. # <%= form.check_box :paid %>
  791. # ...
  792. # <% end %>
  793. #
  794. # because parameter name repetition is precisely what Rails seeks to distinguish
  795. # the elements of the array. For each item with a checked check box you
  796. # get an extra ghost item with only that attribute, assigned to "0".
  797. #
  798. # In that case it is preferable to either use +check_box_tag+ or to use
  799. # hashes instead of arrays.
  800. #
  801. # ==== Examples
  802. # # Let's say that @post.validated? is 1:
  803. # check_box("post", "validated")
  804. # # => <input name="post[validated]" type="hidden" value="0" />
  805. # # <input type="checkbox" id="post_validated" name="post[validated]" value="1" />
  806. #
  807. # # Let's say that @puppy.gooddog is "no":
  808. # check_box("puppy", "gooddog", {}, "yes", "no")
  809. # # => <input name="puppy[gooddog]" type="hidden" value="no" />
  810. # # <input type="checkbox" id="puppy_gooddog" name="puppy[gooddog]" value="yes" />
  811. #
  812. # check_box("eula", "accepted", { :class => 'eula_check' }, "yes", "no")
  813. # # => <input name="eula[accepted]" type="hidden" value="no" />
  814. # # <input type="checkbox" class="eula_check" id="eula_accepted" name="eula[accepted]" value="yes" />
  815. #
  816. def check_box(object_name, method, options = {}, checked_value = "1", unchecked_value = "0")
  817. InstanceTag.new(object_name, method, self, options.delete(:object)).to_check_box_tag(options, checked_value, unchecked_value)
  818. end
  819. # Returns a radio button tag for accessing a specified attribute (identified by +method+) on an object
  820. # assigned to the template (identified by +object+). If the current value of +method+ is +tag_value+ the
  821. # radio button will be checked.
  822. #
  823. # To force the radio button to be checked pass <tt>:checked => true</tt> in the
  824. # +options+ hash. You may pass HTML options there as well.
  825. #
  826. # ==== Examples
  827. # # Let's say that @post.category returns "rails":
  828. # radio_button("post", "category", "rails")
  829. # radio_button("post", "category", "java")
  830. # # => <input type="radio" id="post_category_rails" name="post[category]" value="rails" checked="checked" />
  831. # # <input type="radio" id="post_category_java" name="post[category]" value="java" />
  832. #
  833. # radio_button("user", "receive_newsletter", "yes")
  834. # radio_button("user", "receive_newsletter", "no")
  835. # # => <input type="radio" id="user_receive_newsletter_yes" name="user[receive_newsletter]" value="yes" />
  836. # # <input type="radio" id="user_receive_newsletter_no" name="user[receive_newsletter]" value="no" checked="checked" />
  837. def radio_button(object_name, method, tag_value, options = {})
  838. InstanceTag.new(object_name, method, self, options.delete(:object)).to_radio_button_tag(tag_value, options)
  839. end
  840. # Returns an input of type "search" for accessing a specified attribute (identified by +method+) on an object
  841. # assigned to the template (identified by +object_name+). Inputs of type "search" may be styled differently by
  842. # some browsers.
  843. #
  844. # ==== Examples
  845. #
  846. # search_field(:user, :name)
  847. # # => <input id="user_name" name="user[name]" size="30" type="search" />
  848. # search_field(:user, :name, :autosave => false)
  849. # # => <input autosave="false" id="user_name" name="user[name]" size="30" type="search" />
  850. # search_field(:user, :name, :results => 3)
  851. # # => <input id="user_name" name="user[name]" results="3" size="30" type="search" />
  852. # # Assume request.host returns "www.example.com"
  853. # search_field(:user, :name, :autosave => true)
  854. # # => <input autosave="com.example.www" id="user_name" name="user[name]" results="10" size="30" type="search" />
  855. # search_field(:user, :name, :onsearch => true)
  856. # # => <input id="user_name" incremental="true" name="user[name]" onsearch="true" size="30" type="search" />
  857. # search_field(:user, :name, :autosave => false, :onsearch => true)
  858. # # => <input autosave="false" id="user_name" incremental="true" name="user[name]" onsearch="true" size="30" type="search" />
  859. # search_field(:user, :name, :autosave => true, :onsearch => true)
  860. # # => <input autosave="com.example.www" id="user_name" incremental="true" name="user[name]" onsearch="true" results="10" size="30" type="search" />
  861. #
  862. def search_field(object_name, method, options = {})
  863. options = options.stringify_keys
  864. if options["autosave"]
  865. if options["autosave"] == true
  866. options["autosave"] = request.host.split(".").reverse.join(".")
  867. end
  868. options["results"] ||= 10
  869. end
  870. if options["onsearch"]
  871. options["incremental"] = true unless options.has_key?("incremental")
  872. end
  873. InstanceTag.new(object_name, method, self, options.delete("object")).to_input_field_tag("search", options)
  874. end
  875. # Returns a text_field of type "tel".
  876. #
  877. # telephone_field("user", "phone")
  878. # # => <input id="user_phone" name="user[phone]" size="30" type="tel" />
  879. #
  880. def telephone_field(object_name, method, options = {})
  881. InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("tel", options)
  882. end
  883. alias phone_field telephone_field
  884. # Returns a text_field of type "url".
  885. #
  886. # url_field("user", "homepage")
  887. # # => <input id="user_homepage" size="30" name="user[homepage]" type="url" />
  888. #
  889. def url_field(object_name, method, options = {})
  890. InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("url", options)
  891. end
  892. # Returns a text_field of type "email".
  893. #
  894. # email_field("user", "address")
  895. # # => <input id="user_address" size="30" name="user[address]" type="email" />
  896. #
  897. def email_field(object_name, method, options = {})
  898. InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("email", options)
  899. end
  900. # Returns an input tag of type "number".
  901. #
  902. # ==== Options
  903. # * Accepts same options as number_field_tag
  904. def number_field(object_name, method, options = {})
  905. InstanceTag.new(object_name, method, self, options.delete(:object)).to_number_field_tag("number", options)
  906. end
  907. # Returns an input tag of type "range".
  908. #
  909. # ==== Options
  910. # * Accepts same options as range_field_tag
  911. def range_field(object_name, method, options = {})
  912. InstanceTag.new(object_name, method, self, options.delete(:object)).to_number_field_tag("range", options)
  913. end
  914. private
  915. def instantiate_builder(record_name, record_object, options, &block)
  916. case record_name
  917. when String, Symbol
  918. object = record_object
  919. object_name = record_name
  920. else
  921. object = record_name
  922. object_name = ActiveModel::Naming.param_key(object)
  923. end
  924. builder = options[:builder] || ActionView::Base.default_form_builder
  925. builder.new(object_name, object, self, options, block)
  926. end
  927. end
  928. class InstanceTag
  929. include Helpers::CaptureHelper, Context, Helpers::TagHelper, Helpers::FormTagHelper
  930. attr_reader :object, :method_name, :object_name
  931. DEFAULT_FIELD_OPTIONS = { "size" => 30 }
  932. DEFAULT_RADIO_OPTIONS = { }
  933. DEFAULT_TEXT_AREA_OPTIONS = { "cols" => 40, "rows" => 20 }
  934. def initialize(object_name, method_name, template_object, object = nil)
  935. @object_name, @method_name = object_name.to_s.dup, method_name.to_s.dup
  936. @template_object = template_object
  937. @object_name.sub!(/\[\]$/,"") || @object_name.sub!(/\[\]\]$/,"]")
  938. @object = retrieve_object(object)
  939. @auto_index = retrieve_autoindex(Regexp.last_match.pre_match) if Regexp.last_match
  940. end
  941. def to_label_tag(text = nil, options = {}, &block)
  942. options = options.stringify_keys
  943. tag_value = options.delete("value")
  944. name_and_id = options.dup
  945. if name_and_id["for"]
  946. name_and_id["id"] = name_and_id["for"]
  947. else
  948. name_and_id.delete("id")
  949. end
  950. add_default_name_and_id_for_value(tag_value, name_and_id)
  951. options.delete("index")
  952. options["for"] ||= name_and_id["id"]
  953. if block_given?
  954. label_tag(name_and_id["id"], options, &block)
  955. else
  956. content = if text.blank?
  957. method_and_value = tag_value.present? ? "#{method_name}.#{tag_value}" : method_name
  958. I18n.t("helpers.label.#{object_name}.#{method_and_value}", :default => "").presence
  959. else
  960. text.to_s
  961. end
  962. content ||= if object && object.class.respond_to?(:human_attribute_name)
  963. object.class.human_attribute_name(method_name)
  964. end
  965. content ||= method_name.humanize
  966. label_tag(name_and_id["id"], content, options)
  967. end
  968. end
  969. def to_input_field_tag(field_type, options = {})
  970. options = options.stringify_keys
  971. options["size"] = options["maxlength"] || DEFAULT_FIELD_OPTIONS["size"] unless options.key?("size")
  972. options = DEFAULT_FIELD_OPTIONS.merge(options)
  973. if field_type == "hidden"
  974. options.delete("size")
  975. end
  976. options["type"] ||= field_type
  977. options["value"] = options.fetch("value"){ value_before_type_cast(object) } unless field_type == "file"
  978. options["value"] &&= ERB::Util.html_escape(options["value"])
  979. add_default_name_and_id(options)
  980. tag("input", options)
  981. end
  982. def to_number_field_tag(field_type, options = {})
  983. options = options.stringify_keys
  984. if range = options.delete("in") || options.delete("within")
  985. options.update("min" => range.min, "max" => range.max)
  986. end
  987. to_input_field_tag(field_type, options)
  988. end
  989. def to_radio_button_tag(tag_value, options = {})
  990. options = DEFAULT_RADIO_OPTIONS.merge(options.stringify_keys)
  991. options["type"] = "radio"
  992. options["value"] = tag_value
  993. if options.has_key?("checked")
  994. cv = options.delete "checked"
  995. checked = cv == true || cv == "checked"
  996. else
  997. checked = self.class.radio_button_checked?(value(object), tag_value)
  998. end
  999. options["checked"] = "checked" if checked
  1000. add_default_name_and_id_for_value(tag_value, options)
  1001. tag("input", options)
  1002. end
  1003. def to_text_area_tag(options = {})
  1004. options = DEFAULT_TEXT_AREA_OPTIONS.merge(options.stringify_keys)
  1005. add_default_name_and_id(options)
  1006. if size = options.delete("size")
  1007. options["cols"], options["rows"] = size.split("x") if size.respond_to?(:split)
  1008. end
  1009. content_tag("textarea", ERB::Util.html_escape(options.delete('value') || value_before_type_cast(object)), options)
  1010. end
  1011. def to_check_box_tag(options = {}, checked_value = "1", unchecked_value = "0")
  1012. options = options.stringify_keys
  1013. options["type"] = "checkbox"
  1014. options["value"] = checked_value
  1015. if options.has_key?("checked")
  1016. cv = options.delete "checked"
  1017. checked = cv == true || cv == "checked"
  1018. else
  1019. checked = self.class.check_box_checked?(value(object), checked_value)
  1020. end
  1021. options["checked"] = "checked" if checked
  1022. if options["multiple"]
  1023. add_default_name_and_id_for_value(checked_value, options)
  1024. options.delete("multiple")
  1025. else
  1026. add_default_name_and_id(options)
  1027. end
  1028. hidden = tag("input", "name" => options["name"], "type" => "hidden", "value" => options['disabled'] && checked ? checked_value : unchecked_value)
  1029. checkbox = tag("input", options)
  1030. (hidden + checkbox).html_safe
  1031. end
  1032. def to_boolean_select_tag(options = {})
  1033. options = options.stringify_keys
  1034. add_default_name_and_id(options)
  1035. value = value(object)
  1036. tag_text = "<select"
  1037. tag_text << tag_options(options)
  1038. tag_text << "><option value=\"false\""
  1039. tag_text << " selected" if value == false
  1040. tag_text << ">False</option><option value=\"true\""
  1041. tag_text << " selected" if value
  1042. tag_text << ">True</option></select>"
  1043. end
  1044. def to_content_tag(tag_name, options = {})
  1045. content_tag(tag_name, value(object), options)
  1046. end
  1047. def retrieve_object(object)
  1048. if object
  1049. object
  1050. elsif @template_object.instance_variable_defined?("@#{@object_name}")
  1051. @template_object.instance_variable_get("@#{@object_name}")
  1052. end
  1053. rescue NameError
  1054. # As @object_name may contain the nested syntax (item[subobject]) we need to fallback to nil.
  1055. nil
  1056. end
  1057. def retrieve_autoindex(pre_match)
  1058. object = self.object || @template_object.instance_variable_get("@#{pre_match}")
  1059. if object && object.respond_to?(:to_param)
  1060. object.to_param
  1061. else
  1062. raise ArgumentError, "object[] naming but object param and @object var don't exist or don't respond to to_param: #{object.inspect}"
  1063. end
  1064. end
  1065. def value(object)
  1066. self.class.value(object, @method_name)
  1067. end
  1068. def value_before_type_cast(object)
  1069. self.class.value_before_type_cast(object, @method_name)
  1070. end
  1071. class << self
  1072. def value(object, method_name)
  1073. object.send method_name if object
  1074. end
  1075. def value_before_type_cast(object, method_name)
  1076. unless object.nil?
  1077. object.respond_to?(method_name + "_before_type_cast") ?
  1078. object.send(method_name + "_before_type_cast") :
  1079. object.send(method_name)
  1080. end
  1081. end
  1082. def check_box_checked?(value, checked_va