form_tag_helper.rb 30.3 KB
Newer Older
1
require 'cgi'
2
require 'action_view/helpers/tag_helper'
3
require 'active_support/core_ext/object/blank'
4
require 'active_support/core_ext/string/output_safety'
5 6

module ActionView
7
  # = Action View Form Tag Helpers
8
  module Helpers
P
Pratik Naik 已提交
9
    # Provides a number of methods for creating form tags that doesn't rely on an Active Record object assigned to the template like
10
    # FormHelper does. Instead, you provide the names and values manually.
11
    #
12
    # NOTE: The HTML options <tt>disabled</tt>, <tt>readonly</tt>, and <tt>multiple</tt> can all be treated as booleans. So specifying
13
    # <tt>:disabled => true</tt> will give <tt>disabled="disabled"</tt>.
14
    module FormTagHelper
W
wycats 已提交
15 16 17 18 19
      extend ActiveSupport::Concern

      include UrlHelper
      include TextHelper

20
      # Starts a form tag that points the action to an url configured with <tt>url_for_options</tt> just like
21 22
      # ActionController::Base#url_for. The method for the form defaults to POST.
      #
23
      # ==== Options
24
      # * <tt>:multipart</tt> - If set to true, the enctype is set to "multipart/form-data".
P
Pratik Naik 已提交
25 26 27
      # * <tt>:method</tt> - The method to use when submitting the form, usually either "get" or "post".
      #   If "put", "delete", or another verb is used, a hidden input with name <tt>_method</tt>
      #   is added to simulate the verb over post.
28 29 30
      # * <tt>:authenticity_token</tt> - Authenticity token to use in the form. Use only if you need to
      #   pass custom authenticity token string, or to not add authenticity_token field at all
      #   (by passing <tt>false</tt>).
31
      # * A list of parameters to feed to the URL the form will be posted to.
32
      # * <tt>:remote</tt> - If set to true, will allow the Unobtrusive JavaScript drivers to control the
S
Stefan Penner 已提交
33
      #   submit behaviour. By default this behaviour is an ajax submit.
34 35
      #
      # ==== Examples
36
      #   form_tag('/posts')
37 38
      #   # => <form action="/posts" method="post">
      #
39
      #   form_tag('/posts/1', :method => :put)
40 41
      #   # => <form action="/posts/1" method="put">
      #
42
      #   form_tag('/upload', :multipart => true)
43
      #   # => <form action="/upload" method="post" enctype="multipart/form-data">
44
      #
A
Akira Matsuda 已提交
45
      #   <%= form_tag('/posts') do -%>
46 47 48
      #     <div><%= submit_tag 'Save' %></div>
      #   <% end -%>
      #   # => <form action="/posts" method="post"><div><input type="submit" name="submit" value="Save" /></div></form>
49
      #
50
      #  <%= form_tag('/posts', :remote => true) %>
S
Stefan Penner 已提交
51
      #   # => <form action="/posts" method="post" data-remote="true">
52
      #
53 54 55 56 57 58
      #   form_tag('http://far.away.com/form', :authenticity_token => false)
      #   # form without authenticity token
      #
      #   form_tag('http://far.away.com/form', :authenticity_token => "cf50faa3fe97702ca1ae")
      #   # form with custom authenticity token
      #
59
      def form_tag(url_for_options = {}, options = {}, *parameters_for_url, &block)
60
        html_options = html_options_for_form(url_for_options, options, *parameters_for_url)
61
        if block_given?
62
          form_tag_in_block(html_options, &block)
63
        else
64
          form_tag_html(html_options)
65
        end
66 67
      end

68 69 70 71
      # Creates a dropdown selection box, or if the <tt>:multiple</tt> option is set to true, a multiple
      # choice selection box.
      #
      # Helpers::FormOptions can be used to create common select boxes such as countries, time zones, or
72 73 74 75 76 77
      # associated records. <tt>option_tags</tt> is a string containing the option tags for the select box.
      #
      # ==== Options
      # * <tt>:multiple</tt> - If set to true the selection will allow multiple choices.
      # * <tt>:disabled</tt> - If set to true, the user will not be able to use this input.
      # * Any other key creates standard HTML attributes for the tag.
78
      #
79
      # ==== Examples
80
      #   select_tag "people", options_from_collection_for_select(@people, "id", "name")
P
Pratik Naik 已提交
81
      #   # <select id="people" name="people"><option value="1">David</option></select>
82
      #
83
      #   select_tag "people", "<option>David</option>"
84
      #   # => <select id="people" name="people"><option>David</option></select>
85
      #
86 87 88 89 90
      #   select_tag "count", "<option>1</option><option>2</option><option>3</option><option>4</option>"
      #   # => <select id="count" name="count"><option>1</option><option>2</option>
      #   #    <option>3</option><option>4</option></select>
      #
      #   select_tag "colors", "<option>Red</option><option>Green</option><option>Blue</option>", :multiple => true
91
      #   # => <select id="colors" multiple="multiple" name="colors[]"><option>Red</option>
92 93 94 95 96 97 98
      #   #    <option>Green</option><option>Blue</option></select>
      #
      #   select_tag "locations", "<option>Home</option><option selected="selected">Work</option><option>Out</option>"
      #   # => <select id="locations" name="locations"><option>Home</option><option selected='selected'>Work</option>
      #   #    <option>Out</option></select>
      #
      #   select_tag "access", "<option>Read</option><option>Write</option>", :multiple => true, :class => 'form_input'
99
      #   # => <select class="form_input" id="access" multiple="multiple" name="access[]"><option>Read</option>
100 101 102 103 104
      #   #    <option>Write</option></select>
      #
      #   select_tag "destination", "<option>NYC</option><option>Paris</option><option>Rome</option>", :disabled => true
      #   # => <select disabled="disabled" id="destination" name="destination"><option>NYC</option>
      #   #    <option>Paris</option><option>Rome</option></select>
105
      def select_tag(name, option_tags = nil, options = {})
106
        html_name = (options[:multiple] == true && !name.to_s.ends_with?("[]")) ? "#{name}[]" : name
107 108
        if blank = options.delete(:include_blank)
          if blank.kind_of?(String)
109
            option_tags = "<option value=\"\">#{blank}</option>".html_safe + option_tags
110
          else
111
            option_tags = "<option value=\"\"></option>".html_safe + option_tags
112 113
          end
        end
114
        content_tag :select, option_tags, { "name" => html_name, "id" => sanitize_to_id(name) }.update(options.stringify_keys)
115 116
      end

117 118
      # Creates a standard text field; use these text fields to input smaller chunks of text like a username
      # or a search query.
119
      #
120
      # ==== Options
121 122 123
      # * <tt>:disabled</tt> - If set to true, the user will not be able to use this input.
      # * <tt>:size</tt> - The number of visible characters that will fit in the input.
      # * <tt>:maxlength</tt> - The maximum number of characters that the browser will allow the user to enter.
124
      # * <tt>:placeholder</tt> - The text contained in the field by default which is removed when the field receives focus.
125
      # * Any other key creates standard HTML attributes for the tag.
126
      #
127 128 129 130 131 132 133
      # ==== Examples
      #   text_field_tag 'name'
      #   # => <input id="name" name="name" type="text" />
      #
      #   text_field_tag 'query', 'Enter your search query here'
      #   # => <input id="query" name="query" type="text" value="Enter your search query here" />
      #
134 135 136
      #   text_field_tag 'search', nil, :placeholder => 'Enter search term...'
      #   # => <input id="search" name="search" placeholder="Enter search term..." type="text" />
      #
137 138 139 140 141 142 143 144 145 146 147 148 149 150
      #   text_field_tag 'request', nil, :class => 'special_input'
      #   # => <input class="special_input" id="request" name="request" type="text" />
      #
      #   text_field_tag 'address', '', :size => 75
      #   # => <input id="address" name="address" size="75" type="text" value="" />
      #
      #   text_field_tag 'zip', nil, :maxlength => 5
      #   # => <input id="zip" maxlength="5" name="zip" type="text" />
      #
      #   text_field_tag 'payment_amount', '$0.00', :disabled => true
      #   # => <input disabled="disabled" id="payment_amount" name="payment_amount" type="text" value="$0.00" />
      #
      #   text_field_tag 'ip', '0.0.0.0', :maxlength => 15, :size => 20, :class => "ip-input"
      #   # => <input class="ip-input" id="ip" maxlength="15" name="ip" size="20" type="text" value="0.0.0.0" />
151
      def text_field_tag(name, value = nil, options = {})
152
        tag :input, { "type" => "text", "name" => name, "id" => sanitize_to_id(name), "value" => value }.update(options.stringify_keys)
153 154
      end

S
Stephen Celis 已提交
155
      # Creates a label element. Accepts a block.
156
      #
157
      # ==== Options
158 159 160 161 162 163 164 165 166 167 168
      # * Creates standard HTML attributes for the tag.
      #
      # ==== Examples
      #   label_tag 'name'
      #   # => <label for="name">Name</label>
      #
      #   label_tag 'name', 'Your name'
      #   # => <label for="name">Your Name</label>
      #
      #   label_tag 'name', nil, :class => 'small_label'
      #   # => <label for="name" class="small_label">Name</label>
169 170
      def label_tag(name = nil, content_or_options = nil, options = nil, &block)
        options = content_or_options if block_given? && content_or_options.is_a?(Hash)
S
Stephen Celis 已提交
171 172 173
        options ||= {}
        options.stringify_keys!
        options["for"] = sanitize_to_id(name) unless name.blank? || options.has_key?("for")
174
        content_tag :label, content_or_options || name.to_s.humanize, options, &block
175 176
      end

177 178 179 180 181 182 183 184 185
      # Creates a hidden form input field used to transmit data that would be lost due to HTTP's statelessness or
      # data that should be hidden from the user.
      #
      # ==== Options
      # * Creates standard HTML attributes for the tag.
      #
      # ==== Examples
      #   hidden_field_tag 'tags_list'
      #   # => <input id="tags_list" name="tags_list" type="hidden" />
186
      #
187 188 189 190
      #   hidden_field_tag 'token', 'VUBJKB23UIVI1UU1VOBVI@'
      #   # => <input id="token" name="token" type="hidden" value="VUBJKB23UIVI1UU1VOBVI@" />
      #
      #   hidden_field_tag 'collected_input', '', :onchange => "alert('Input collected!')"
191
      #   # => <input id="collected_input" name="collected_input" onchange="alert('Input collected!')"
192
      #   #    type="hidden" value="" />
193
      def hidden_field_tag(name, value = nil, options = {})
194
        text_field_tag(name, value, options.stringify_keys.update("type" => "hidden"))
195 196
      end

197
      # Creates a file upload field.  If you are using file uploads then you will also need
198
      # to set the multipart option for the form tag:
199
      #
X
Xavier Noria 已提交
200
      #   <%= form_tag '/upload', :multipart => true do %>
201 202
      #     <label for="file">File to Upload</label> <%= file_field_tag "file" %>
      #     <%= submit_tag %>
P
Pratik Naik 已提交
203
      #   <% end %>
204
      #
205
      # The specified URL will then be passed a File object containing the selected file, or if the field
206
      # was left blank, a StringIO object.
207 208 209 210 211 212 213 214 215
      #
      # ==== Options
      # * Creates standard HTML attributes for the tag.
      # * <tt>:disabled</tt> - If set to true, the user will not be able to use this input.
      #
      # ==== Examples
      #   file_field_tag 'attachment'
      #   # => <input id="attachment" name="attachment" type="file" />
      #
216 217
      #   file_field_tag 'avatar', :class => 'profile_input'
      #   # => <input class="profile_input" id="avatar" name="avatar" type="file" />
218 219 220 221 222 223 224 225
      #
      #   file_field_tag 'picture', :disabled => true
      #   # => <input disabled="disabled" id="picture" name="picture" type="file" />
      #
      #   file_field_tag 'resume', :value => '~/resume.doc'
      #   # => <input id="resume" name="resume" type="file" value="~/resume.doc" />
      #
      #   file_field_tag 'user_pic', :accept => 'image/png,image/gif,image/jpeg'
226
      #   # => <input accept="image/png,image/gif,image/jpeg" id="user_pic" name="user_pic" type="file" />
227 228 229
      #
      #   file_field_tag 'file', :accept => 'text/html', :class => 'upload', :value => 'index.html'
      #   # => <input accept="text/html" class="upload" id="file" name="file" type="file" value="index.html" />
230
      def file_field_tag(name, options = {})
231
        text_field_tag(name, nil, options.update("type" => "file"))
232 233
      end

234
      # Creates a password field, a masked text field that will hide the users input behind a mask character.
235
      #
236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260
      # ==== Options
      # * <tt>:disabled</tt> - If set to true, the user will not be able to use this input.
      # * <tt>:size</tt> - The number of visible characters that will fit in the input.
      # * <tt>:maxlength</tt> - The maximum number of characters that the browser will allow the user to enter.
      # * Any other key creates standard HTML attributes for the tag.
      #
      # ==== Examples
      #   password_field_tag 'pass'
      #   # => <input id="pass" name="pass" type="password" />
      #
      #   password_field_tag 'secret', 'Your secret here'
      #   # => <input id="secret" name="secret" type="password" value="Your secret here" />
      #
      #   password_field_tag 'masked', nil, :class => 'masked_input_field'
      #   # => <input class="masked_input_field" id="masked" name="masked" type="password" />
      #
      #   password_field_tag 'token', '', :size => 15
      #   # => <input id="token" name="token" size="15" type="password" value="" />
      #
      #   password_field_tag 'key', nil, :maxlength => 16
      #   # => <input id="key" maxlength="16" name="key" type="password" />
      #
      #   password_field_tag 'confirm_pass', nil, :disabled => true
      #   # => <input disabled="disabled" id="confirm_pass" name="confirm_pass" type="password" />
      #
261 262
      #   password_field_tag 'pin', '1234', :maxlength => 4, :size => 6, :class => "pin_input"
      #   # => <input class="pin_input" id="pin" maxlength="4" name="pin" size="6" type="password" value="1234" />
263
      def password_field_tag(name = "password", value = nil, options = {})
264
        text_field_tag(name, value, options.update("type" => "password"))
265 266
      end

267 268 269
      # Creates a text input area; use a textarea for longer text inputs such as blog posts or descriptions.
      #
      # ==== Options
270
      # * <tt>:size</tt> - A string specifying the dimensions (columns by rows) of the textarea (e.g., "25x10").
271 272 273
      # * <tt>:rows</tt> - Specify the number of rows in the textarea
      # * <tt>:cols</tt> - Specify the number of columns in the textarea
      # * <tt>:disabled</tt> - If set to true, the user will not be able to use this input.
274 275
      # * <tt>:escape</tt> - By default, the contents of the text input are HTML escaped.
      #   If you need unescaped contents, set this to false.
276 277 278 279 280 281 282 283
      # * Any other key creates standard HTML attributes for the tag.
      #
      # ==== Examples
      #   text_area_tag 'post'
      #   # => <textarea id="post" name="post"></textarea>
      #
      #   text_area_tag 'bio', @user.bio
      #   # => <textarea id="bio" name="bio">This is my biography.</textarea>
284
      #
285 286 287 288 289 290 291 292 293 294 295
      #   text_area_tag 'body', nil, :rows => 10, :cols => 25
      #   # => <textarea cols="25" id="body" name="body" rows="10"></textarea>
      #
      #   text_area_tag 'body', nil, :size => "25x10"
      #   # => <textarea name="body" id="body" cols="25" rows="10"></textarea>
      #
      #   text_area_tag 'description', "Description goes here.", :disabled => true
      #   # => <textarea disabled="disabled" id="description" name="description">Description goes here.</textarea>
      #
      #   text_area_tag 'comment', nil, :class => 'comment_input'
      #   # => <textarea class="comment_input" id="comment" name="comment"></textarea>
296
      def text_area_tag(name, content = nil, options = {})
297 298 299
        options.stringify_keys!

        if size = options.delete("size")
300
          options["cols"], options["rows"] = size.split("x") if size.respond_to?(:split)
301
        end
302

303
        escape = options.key?("escape") ? options.delete("escape") : true
304
        content = ERB::Util.html_escape(content) if escape
305

306
        content_tag :textarea, content.to_s.html_safe, { "name" => name, "id" => sanitize_to_id(name) }.update(options)
307 308
      end

309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329
      # Creates a check box form input tag.
      #
      # ==== Options
      # * <tt>:disabled</tt> - If set to true, the user will not be able to use this input.
      # * Any other key creates standard HTML options for the tag.
      #
      # ==== Examples
      #   check_box_tag 'accept'
      #   # => <input id="accept" name="accept" type="checkbox" value="1" />
      #
      #   check_box_tag 'rock', 'rock music'
      #   # => <input id="rock" name="rock" type="checkbox" value="rock music" />
      #
      #   check_box_tag 'receive_email', 'yes', true
      #   # => <input checked="checked" id="receive_email" name="receive_email" type="checkbox" value="yes" />
      #
      #   check_box_tag 'tos', 'yes', false, :class => 'accept_tos'
      #   # => <input class="accept_tos" id="tos" name="tos" type="checkbox" value="yes" />
      #
      #   check_box_tag 'eula', 'accepted', false, :disabled => true
      #   # => <input disabled="disabled" id="eula" name="eula" type="checkbox" value="accepted" />
330
      def check_box_tag(name, value = "1", checked = false, options = {})
331
        html_options = { "type" => "checkbox", "name" => name, "id" => sanitize_to_id(name), "value" => value }.update(options.stringify_keys)
332
        html_options["checked"] = "checked" if checked
333
        tag :input, html_options
334 335
      end

336
      # Creates a radio button; use groups of radio buttons named the same to allow users to
337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354
      # select from a group of options.
      #
      # ==== Options
      # * <tt>:disabled</tt> - If set to true, the user will not be able to use this input.
      # * Any other key creates standard HTML options for the tag.
      #
      # ==== Examples
      #   radio_button_tag 'gender', 'male'
      #   # => <input id="gender_male" name="gender" type="radio" value="male" />
      #
      #   radio_button_tag 'receive_updates', 'no', true
      #   # => <input checked="checked" id="receive_updates_no" name="receive_updates" type="radio" value="no" />
      #
      #   radio_button_tag 'time_slot', "3:00 p.m.", false, :disabled => true
      #   # => <input disabled="disabled" id="time_slot_300_pm" name="time_slot" type="radio" value="3:00 p.m." />
      #
      #   radio_button_tag 'color', "green", true, :class => "color_input"
      #   # => <input checked="checked" class="color_input" id="color_green" name="color" type="radio" value="green" />
355
      def radio_button_tag(name, value, checked = false, options = {})
356
        html_options = { "type" => "radio", "name" => name, "id" => "#{sanitize_to_id(name)}_#{sanitize_to_id(value)}", "value" => value }.update(options.stringify_keys)
357
        html_options["checked"] = "checked" if checked
358
        tag :input, html_options
359 360
      end

361
      # Creates a submit button with the text <tt>value</tt> as the caption.
362 363
      #
      # ==== Options
364 365
      # * <tt>:confirm => 'question?'</tt> - If present the unobtrusive JavaScript
      #   drivers will provide a prompt with the question specified. If the user accepts,
S
Stefan Penner 已提交
366
      #   the form is processed normally, otherwise no action is taken.
P
Pratik Naik 已提交
367
      # * <tt>:disabled</tt> - If true, the user will not be able to use this input.
368 369
      # * <tt>:disable_with</tt> - Value of this parameter will be used as the value for a
      #   disabled version of the submit button when the form is submitted. This feature is
S
Stefan Penner 已提交
370
      #   provided by the unobtrusive JavaScript driver.
371 372 373 374 375 376 377 378 379 380 381 382
      # * Any other key creates standard HTML options for the tag.
      #
      # ==== Examples
      #   submit_tag
      #   # => <input name="commit" type="submit" value="Save changes" />
      #
      #   submit_tag "Edit this article"
      #   # => <input name="commit" type="submit" value="Edit this article" />
      #
      #   submit_tag "Save edits", :disabled => true
      #   # => <input disabled="disabled" name="commit" type="submit" value="Save edits" />
      #
S
Stefan Penner 已提交
383
      #
384
      #   submit_tag "Complete sale", :disable_with => "Please wait..."
S
Stefan Penner 已提交
385
      #   # => <input name="commit" data-disable-with="Please wait..."
386 387 388 389 390
      #   #    type="submit" value="Complete sale" />
      #
      #   submit_tag nil, :class => "form_submit"
      #   # => <input class="form_submit" name="commit" type="submit" />
      #
391 392
      #   submit_tag "Edit", :disable_with => "Editing...", :class => "edit_button"
      #   # => <input class="edit_button" data-disable_with="Editing..."
393
      #   #    name="commit" type="submit" value="Edit" />
S
Stefan Penner 已提交
394 395
      #
      #   submit_tag "Save", :confirm => "Are you sure?"
396
      #   # => <input name='commit' type='submit' value='Save'
S
Stefan Penner 已提交
397 398
      #         data-confirm="Are you sure?" />
      #
399
      def submit_tag(value = "Save changes", options = {})
400
        options.stringify_keys!
401

402
        if disable_with = options.delete("disable_with")
J
Jeremy Kemper 已提交
403
          options["data-disable-with"] = disable_with
404
        end
405

406
        if confirm = options.delete("confirm")
407
          options["data-confirm"] = confirm
408
        end
409

410
        tag :input, { "type" => "submit", "name" => "commit", "value" => value }.update(options.stringify_keys)
411
      end
412

R
Rizwan Reza 已提交
413 414 415 416
      # Creates a button element that defines a <tt>submit</tt> button,
      # <tt>reset</tt>button or a generic button which can be used in
      # JavaScript, for example. You can use the button tag as a regular
      # submit tag but it isn't supported in legacy browsers. However,
417 418
      # the button tag allows richer labels such as images and emphasis,
      # so this helper will also accept a block.
R
Rizwan Reza 已提交
419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434
      #
      # ==== Options
      # * <tt>:confirm => 'question?'</tt> - If present, the
      #   unobtrusive JavaScript drivers will provide a prompt with
      #   the question specified. If the user accepts, the form is
      #   processed normally, otherwise no action is taken.
      # * <tt>:disabled</tt> - If true, the user will not be able to
      #   use this input.
      # * <tt>:disable_with</tt> - Value of this parameter will be
      #   used as the value for a disabled version of the submit
      #   button when the form is submitted. This feature is provided
      #   by the unobtrusive JavaScript driver.
      # * Any other key creates standard HTML options for the tag.
      #
      # ==== Examples
      #   button_tag
435
      #   # => <button name="button" type="submit">Button</button>
R
Rizwan Reza 已提交
436
      #
437 438 439
      #   button_tag(:type => 'button') do
      #     content_tag(:strong, 'Ask me!')
      #   end
R
Rizwan Reza 已提交
440 441 442 443 444 445
      #   # => <button name="button" type="button">
      #          <strong>Ask me!</strong>
      #        </button>
      #
      #   button_tag "Checkout", :disable_with => "Please wait..."
      #   # => <button data-disable-with="Please wait..." name="button"
446
      #                type="submit">Checkout</button>
R
Rizwan Reza 已提交
447
      #
448 449 450
      def button_tag(content_or_options = nil, options = nil, &block)
        options = content_or_options if block_given? && content_or_options.is_a?(Hash)
        options ||= {}
R
Rizwan Reza 已提交
451 452 453 454 455 456 457 458 459 460
        options.stringify_keys!

        if disable_with = options.delete("disable_with")
          options["data-disable-with"] = disable_with
        end

        if confirm = options.delete("confirm")
          options["data-confirm"] = confirm
        end

461
        options.reverse_merge! 'name' => 'button', 'type' => 'submit'
R
Rizwan Reza 已提交
462

463
        content_tag :button, content_or_options || 'Button', options, &block
R
Rizwan Reza 已提交
464 465
      end

466 467
      # Displays an image which when clicked will submit the form.
      #
468
      # <tt>source</tt> is passed to AssetTagHelper#path_to_image
469 470
      #
      # ==== Options
471 472 473
      # * <tt>:confirm => 'question?'</tt> - This will add a JavaScript confirm
      #   prompt with the question specified. If the user accepts, the form is
      #   processed normally, otherwise no action is taken.
474 475 476 477 478 479 480
      # * <tt>:disabled</tt> - If set to true, the user will not be able to use this input.
      # * Any other key creates standard HTML options for the tag.
      #
      # ==== Examples
      #   image_submit_tag("login.png")
      #   # => <input src="/images/login.png" type="image" />
      #
481
      #   image_submit_tag("purchase.png", :disabled => true)
482 483
      #   # => <input disabled="disabled" src="/images/purchase.png" type="image" />
      #
484 485
      #   image_submit_tag("search.png", :class => 'search_button')
      #   # => <input class="search_button" src="/images/search.png" type="image" />
486
      #
487 488
      #   image_submit_tag("agree.png", :disabled => true, :class => "agree_disagree_button")
      #   # => <input class="agree_disagree_button" disabled="disabled" src="/images/agree.png" type="image" />
489
      def image_submit_tag(source, options = {})
490 491 492
        options.stringify_keys!

        if confirm = options.delete("confirm")
493
          options["data-confirm"] = confirm
494 495
        end

496
        tag :input, { "type" => "image", "src" => path_to_image(source) }.update(options.stringify_keys)
497
      end
498 499 500 501

      # Creates a field set for grouping HTML form elements.
      #
      # <tt>legend</tt> will become the fieldset's title (optional as per W3C).
A
Andrew Kaspick 已提交
502
      # <tt>options</tt> accept the same values as tag.
503
      #
L
lifo 已提交
504
      # ==== Examples
505
      #   <%= field_set_tag do %>
506 507 508 509
      #     <p><%= text_field_tag 'name' %></p>
      #   <% end %>
      #   # => <fieldset><p><input id="name" name="name" type="text" /></p></fieldset>
      #
510
      #   <%= field_set_tag 'Your details' do %>
511 512 513
      #     <p><%= text_field_tag 'name' %></p>
      #   <% end %>
      #   # => <fieldset><legend>Your details</legend><p><input id="name" name="name" type="text" /></p></fieldset>
A
Andrew Kaspick 已提交
514
      #
515
      #   <%= field_set_tag nil, :class => 'format' do %>
A
Andrew Kaspick 已提交
516 517 518 519
      #     <p><%= text_field_tag 'name' %></p>
      #   <% end %>
      #   # => <fieldset class="format"><p><input id="name" name="name" type="text" /></p></fieldset>
      def field_set_tag(legend = nil, options = nil, &block)
520
        content = capture(&block)
W
wycats 已提交
521 522 523 524
        output = tag(:fieldset, options, true)
        output.safe_concat(content_tag(:legend, legend)) unless legend.blank?
        output.concat(content)
        output.safe_concat("</fieldset>")
525
      end
526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588

      # Creates a text field of type "search".
      #
      # ==== Options
      # * Accepts the same options as text_field_tag.
      def search_field_tag(name, value = nil, options = {})
        text_field_tag(name, value, options.stringify_keys.update("type" => "search"))
      end

      # Creates a text field of type "tel".
      #
      # ==== Options
      # * Accepts the same options as text_field_tag.
      def telephone_field_tag(name, value = nil, options = {})
        text_field_tag(name, value, options.stringify_keys.update("type" => "tel"))
      end
      alias phone_field_tag telephone_field_tag

      # Creates a text field of type "url".
      #
      # ==== Options
      # * Accepts the same options as text_field_tag.
      def url_field_tag(name, value = nil, options = {})
        text_field_tag(name, value, options.stringify_keys.update("type" => "url"))
      end

      # Creates a text field of type "email".
      #
      # ==== Options
      # * Accepts the same options as text_field_tag.
      def email_field_tag(name, value = nil, options = {})
        text_field_tag(name, value, options.stringify_keys.update("type" => "email"))
      end

      # Creates a number field.
      #
      # ==== Options
      # * <tt>:min</tt> - The minimum acceptable value.
      # * <tt>:max</tt> - The maximum acceptable value.
      # * <tt>:in</tt> - A range specifying the <tt>:min</tt> and
      #   <tt>:max</tt> values.
      # * <tt>:step</tt> - The acceptable value granularity.
      # * Otherwise accepts the same options as text_field_tag.
      #
      # ==== Examples
      #   number_field_tag 'quantity', nil, :in => 1...10
      #   => <input id="quantity" name="quantity" min="1" max="9" />
      def number_field_tag(name, value = nil, options = {})
        options = options.stringify_keys
        options["type"] ||= "number"
        if range = options.delete("in") || options.delete("within")
          options.update("min" => range.min, "max" => range.max)
        end
        text_field_tag(name, value, options)
      end

      # Creates a range form element.
      #
      # ==== Options
      # * Accepts the same options as number_field_tag.
      def range_field_tag(name, value = nil, options = {})
        number_field_tag(name, value, options.stringify_keys.update("type" => "range"))
      end
589

590 591
      private
        def html_options_for_form(url_for_options, options, *parameters_for_url)
S
Santiago Pastorino 已提交
592
          options.stringify_keys.tap do |html_options|
593
            html_options["enctype"] = "multipart/form-data" if html_options.delete("multipart")
594
            # The following URL is unescaped, this is just a hash of options, and it is the
R
R.T. Lechow 已提交
595
            # responsibility of the caller to escape all the values.
596
            html_options["action"]  = url_for(url_for_options, *parameters_for_url)
W
wycats 已提交
597
            html_options["accept-charset"] = "UTF-8"
598
            html_options["data-remote"] = true if html_options.delete("remote")
599
            html_options["authenticity_token"] = html_options.delete("authenticity_token") if html_options.has_key?("authenticity_token")
600 601
          end
        end
602

603
        def extra_tags_for_form(html_options)
W
wycats 已提交
604
          snowman_tag = tag(:input, :type => "hidden",
W
wycats 已提交
605
                            :name => "utf8", :value => "&#x2713;".html_safe)
W
wycats 已提交
606

607
          authenticity_token = html_options.delete("authenticity_token")
W
wycats 已提交
608 609 610
          method = html_options.delete("method").to_s

          method_tag = case method
611
            when /^get$/i # must be case-insensitive, but can't use downcase as might be nil
612 613 614 615
              html_options["method"] = "get"
              ''
            when /^post$/i, "", nil
              html_options["method"] = "post"
616
              token_tag(authenticity_token)
617 618
            else
              html_options["method"] = "post"
619
              tag(:input, :type => "hidden", :name => "_method", :value => method) + token_tag(authenticity_token)
620
          end
W
wycats 已提交
621 622 623

          tags = snowman_tag << method_tag
          content_tag(:div, tags, :style => 'margin:0;padding:0;display:inline')
624
        end
625

626 627
        def form_tag_html(html_options)
          extra_tags = extra_tags_for_form(html_options)
628
          (tag(:form, html_options, true) + extra_tags).html_safe
629
        end
630

631 632
        def form_tag_in_block(html_options, &block)
          content = capture(&block)
W
wycats 已提交
633 634 635 636
          output = ActiveSupport::SafeBuffer.new
          output.safe_concat(form_tag_html(html_options))
          output << content
          output.safe_concat("</form>")
637
        end
638

639 640
        def token_tag(token)
          if token == false || !protect_against_forgery?
641 642
            ''
          else
643 644
            token = form_authenticity_token if token.nil?
            tag(:input, :type => "hidden", :name => request_forgery_protection_token.to_s, :value => token)
645 646
          end
        end
647 648 649 650 651

        # see http://www.w3.org/TR/html4/types.html#type-name
        def sanitize_to_id(name)
          name.to_s.gsub(']','').gsub(/[^-a-zA-Z0-9:.]/, "_")
        end
652 653 654
    end
  end
end