form_tag_helper.rb 27.2 KB
Newer Older
1
require 'cgi'
2
require 'action_view/helpers/tag_helper'
3
require 'active_support/core_ext/object/returning'
4
require 'active_support/core_ext/object/blank'
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
      # * A list of parameters to feed to the URL the form will be posted to.
S
Stefan Penner 已提交
29 30
      # * <tt>:remote</tt> - If set to true, will allow the Unobtrusive JavaScript drivers to control the 
      #   submit behaviour. By default this behaviour is an ajax submit.
31 32
      #
      # ==== Examples
33
      #   form_tag('/posts')
34 35
      #   # => <form action="/posts" method="post">
      #
36
      #   form_tag('/posts/1', :method => :put)
37 38
      #   # => <form action="/posts/1" method="put">
      #
39
      #   form_tag('/upload', :multipart => true)
40
      #   # => <form action="/upload" method="post" enctype="multipart/form-data">
41
      #
42
      #   <%= form_tag('/posts')do -%>
43 44 45
      #     <div><%= submit_tag 'Save' %></div>
      #   <% end -%>
      #   # => <form action="/posts" method="post"><div><input type="submit" name="submit" value="Save" /></div></form>
S
Stefan Penner 已提交
46
      # 
47
      #  <%= form_tag('/posts', :remote => true) %>
S
Stefan Penner 已提交
48 49
      #   # => <form action="/posts" method="post" data-remote="true">
      #   
50
      def form_tag(url_for_options = {}, options = {}, *parameters_for_url, &block)
51
        html_options = html_options_for_form(url_for_options, options, *parameters_for_url)
52
        if block_given?
53
          form_tag_in_block(html_options, &block)
54
        else
55
          form_tag_html(html_options)
56
        end
57 58
      end

59 60 61 62
      # 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
63 64 65 66 67 68
      # 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.
69
      #
70
      # ==== Examples
P
Pratik Naik 已提交
71 72
      #   select_tag "people", options_from_collection_for_select(@people, "name", "id")
      #   # <select id="people" name="people"><option value="1">David</option></select>
73
      #
74
      #   select_tag "people", "<option>David</option>"
75
      #   # => <select id="people" name="people"><option>David</option></select>
76
      #
77 78 79 80 81
      #   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
82
      #   # => <select id="colors" multiple="multiple" name="colors[]"><option>Red</option>
83 84 85 86 87 88 89
      #   #    <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'
90
      #   # => <select class="form_input" id="access" multiple="multiple" name="access[]"><option>Read</option>
91 92 93 94 95
      #   #    <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>
96
      def select_tag(name, option_tags = nil, options = {})
97 98 99 100
        if Array === option_tags
          ActiveSupport::Deprecation.warn 'Passing an array of option_tags to select_tag implicitly joins them without marking them as HTML-safe. Pass option_tags.join.html_safe instead.', caller
        end

101
        html_name = (options[:multiple] == true && !name.to_s.ends_with?("[]")) ? "#{name}[]" : name
102 103
        if blank = options.delete(:include_blank)
          if blank.kind_of?(String)
104
            option_tags = "<option value=\"\">#{blank}</option>".html_safe + option_tags
105
          else
106
            option_tags = "<option value=\"\"></option>".html_safe + option_tags
107 108
          end
        end
109
        content_tag :select, option_tags, { "name" => html_name, "id" => sanitize_to_id(name) }.update(options.stringify_keys)
110 111
      end

112 113
      # Creates a standard text field; use these text fields to input smaller chunks of text like a username
      # or a search query.
114
      #
115
      # ==== Options
116 117 118
      # * <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.
119
      # * Any other key creates standard HTML attributes for the tag.
120
      #
121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141
      # ==== 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" />
      #
      #   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" />
142
      def text_field_tag(name, value = nil, options = {})
143
        tag :input, { "type" => "text", "name" => name, "id" => sanitize_to_id(name), "value" => value }.update(options.stringify_keys)
144 145
      end

S
Stephen Celis 已提交
146
      # Creates a label element. Accepts a block.
147
      #
148
      # ==== Options
149 150 151 152 153 154 155 156 157 158 159
      # * 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>
160 161
      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 已提交
162 163 164
        options ||= {}
        options.stringify_keys!
        options["for"] = sanitize_to_id(name) unless name.blank? || options.has_key?("for")
165
        content_tag :label, content_or_options || name.to_s.humanize, options, &block
166 167
      end

168 169 170 171 172 173 174 175 176
      # 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" />
177
      #
178 179 180 181
      #   hidden_field_tag 'token', 'VUBJKB23UIVI1UU1VOBVI@'
      #   # => <input id="token" name="token" type="hidden" value="VUBJKB23UIVI1UU1VOBVI@" />
      #
      #   hidden_field_tag 'collected_input', '', :onchange => "alert('Input collected!')"
182
      #   # => <input id="collected_input" name="collected_input" onchange="alert('Input collected!')"
183
      #   #    type="hidden" value="" />
184
      def hidden_field_tag(name, value = nil, options = {})
185
        text_field_tag(name, value, options.stringify_keys.update("type" => "hidden"))
186 187
      end

188
      # Creates a file upload field.  If you are using file uploads then you will also need
189
      # to set the multipart option for the form tag:
190
      #
X
Xavier Noria 已提交
191
      #   <%= form_tag '/upload', :multipart => true do %>
192 193
      #     <label for="file">File to Upload</label> <%= file_field_tag "file" %>
      #     <%= submit_tag %>
P
Pratik Naik 已提交
194
      #   <% end %>
195
      #
196
      # The specified URL will then be passed a File object containing the selected file, or if the field
197
      # was left blank, a StringIO object.
198 199 200 201 202 203 204 205 206
      #
      # ==== 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" />
      #
207 208
      #   file_field_tag 'avatar', :class => 'profile_input'
      #   # => <input class="profile_input" id="avatar" name="avatar" type="file" />
209 210 211 212 213 214 215 216
      #
      #   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'
217
      #   # => <input accept="image/png,image/gif,image/jpeg" id="user_pic" name="user_pic" type="file" />
218 219 220
      #
      #   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" />
221
      def file_field_tag(name, options = {})
222
        text_field_tag(name, nil, options.update("type" => "file"))
223 224
      end

225
      # Creates a password field, a masked text field that will hide the users input behind a mask character.
226
      #
227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251
      # ==== 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" />
      #
252 253
      #   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" />
254
      def password_field_tag(name = "password", value = nil, options = {})
255
        text_field_tag(name, value, options.update("type" => "password"))
256 257
      end

258 259 260
      # Creates a text input area; use a textarea for longer text inputs such as blog posts or descriptions.
      #
      # ==== Options
261
      # * <tt>:size</tt> - A string specifying the dimensions (columns by rows) of the textarea (e.g., "25x10").
262 263 264
      # * <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.
265 266
      # * <tt>:escape</tt> - By default, the contents of the text input are HTML escaped.
      #   If you need unescaped contents, set this to false.
267 268 269 270 271 272 273 274
      # * 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>
275
      #
276 277 278 279 280 281 282 283 284 285 286
      #   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>
287
      def text_area_tag(name, content = nil, options = {})
288 289 290
        options.stringify_keys!

        if size = options.delete("size")
291
          options["cols"], options["rows"] = size.split("x") if size.respond_to?(:split)
292
        end
293

294 295 296
        escape = options.key?("escape") ? options.delete("escape") : true
        content = html_escape(content) if escape

297
        content_tag :textarea, content.to_s.html_safe, { "name" => name, "id" => sanitize_to_id(name) }.update(options)
298 299
      end

300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320
      # 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" />
321
      def check_box_tag(name, value = "1", checked = false, options = {})
322
        html_options = { "type" => "checkbox", "name" => name, "id" => sanitize_to_id(name), "value" => value }.update(options.stringify_keys)
323
        html_options["checked"] = "checked" if checked
324
        tag :input, html_options
325 326
      end

327
      # Creates a radio button; use groups of radio buttons named the same to allow users to
328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345
      # 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" />
346
      def radio_button_tag(name, value, checked = false, options = {})
347
        html_options = { "type" => "radio", "name" => name, "id" => "#{sanitize_to_id(name)}_#{sanitize_to_id(value)}", "value" => value }.update(options.stringify_keys)
348
        html_options["checked"] = "checked" if checked
349
        tag :input, html_options
350 351
      end

352
      # Creates a submit button with the text <tt>value</tt> as the caption.
353 354
      #
      # ==== Options
S
Stefan Penner 已提交
355 356 357
      # * <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.
P
Pratik Naik 已提交
358
      # * <tt>:disabled</tt> - If true, the user will not be able to use this input.
S
Stefan Penner 已提交
359 360 361
      # * <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.
362 363 364 365 366 367 368 369 370 371 372 373
      # * 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 已提交
374
      #
375
      #   submit_tag "Complete sale", :disable_with => "Please wait..."
S
Stefan Penner 已提交
376
      #   # => <input name="commit" data-disable-with="Please wait..."
377 378 379 380 381
      #   #    type="submit" value="Complete sale" />
      #
      #   submit_tag nil, :class => "form_submit"
      #   # => <input class="form_submit" name="commit" type="submit" />
      #
382 383
      #   submit_tag "Edit", :disable_with => "Editing...", :class => "edit_button"
      #   # => <input class="edit_button" data-disable_with="Editing..."
384
      #   #    name="commit" type="submit" value="Edit" />
S
Stefan Penner 已提交
385 386 387 388 389
      #
      #   submit_tag "Save", :confirm => "Are you sure?"
      #   # => <input name='commit' type='submit' value='Save' 
      #         data-confirm="Are you sure?" />
      #
390
      def submit_tag(value = "Save changes", options = {})
391
        options.stringify_keys!
392

393
        if disable_with = options.delete("disable_with")
J
Jeremy Kemper 已提交
394
          options["data-disable-with"] = disable_with
395
        end
396

397
        if confirm = options.delete("confirm")
398
          add_confirm_to_attributes!(options, confirm)
399
        end
400

401
        tag :input, { "type" => "submit", "name" => "commit", "value" => value }.update(options.stringify_keys)
402
      end
403

404 405
      # Displays an image which when clicked will submit the form.
      #
406
      # <tt>source</tt> is passed to AssetTagHelper#path_to_image
407 408
      #
      # ==== Options
409 410 411
      # * <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.
412 413 414 415 416 417 418
      # * <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" />
      #
419
      #   image_submit_tag("purchase.png", :disabled => true)
420 421
      #   # => <input disabled="disabled" src="/images/purchase.png" type="image" />
      #
422 423
      #   image_submit_tag("search.png", :class => 'search_button')
      #   # => <input class="search_button" src="/images/search.png" type="image" />
424
      #
425 426
      #   image_submit_tag("agree.png", :disabled => true, :class => "agree_disagree_button")
      #   # => <input class="agree_disagree_button" disabled="disabled" src="/images/agree.png" type="image" />
427
      def image_submit_tag(source, options = {})
428 429 430
        options.stringify_keys!

        if confirm = options.delete("confirm")
431
          add_confirm_to_attributes!(options, confirm)
432 433
        end

434
        tag :input, { "type" => "image", "src" => path_to_image(source) }.update(options.stringify_keys)
435
      end
436 437 438 439

      # 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 已提交
440
      # <tt>options</tt> accept the same values as tag.
441
      #
L
lifo 已提交
442
      # ==== Examples
443
      #   <%= field_set_tag do %>
444 445 446 447
      #     <p><%= text_field_tag 'name' %></p>
      #   <% end %>
      #   # => <fieldset><p><input id="name" name="name" type="text" /></p></fieldset>
      #
448
      #   <%= field_set_tag 'Your details' do %>
449 450 451
      #     <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 已提交
452
      #
453
      #   <%= field_set_tag nil, :class => 'format' do %>
A
Andrew Kaspick 已提交
454 455 456 457
      #     <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)
458
        content = capture(&block)
W
wycats 已提交
459 460 461 462
        output = tag(:fieldset, options, true)
        output.safe_concat(content_tag(:legend, legend)) unless legend.blank?
        output.concat(content)
        output.safe_concat("</fieldset>")
463
      end
464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526

      # 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
527

528 529 530 531
      private
        def html_options_for_form(url_for_options, options, *parameters_for_url)
          returning options.stringify_keys do |html_options|
            html_options["enctype"] = "multipart/form-data" if html_options.delete("multipart")
532 533
            # The following URL is unescaped, this is just a hash of options, and it is the
            # responsability of the caller to escape all the values.
534
            html_options["action"]  = url_for(url_for_options, *parameters_for_url)
W
wycats 已提交
535
            html_options["accept-charset"] = "UTF-8"
536
            html_options["data-remote"] = true if html_options.delete("remote")
537 538
          end
        end
539

540
        def extra_tags_for_form(html_options)
W
wycats 已提交
541
          snowman_tag = tag(:input, :type => "hidden",
542
                            :name => "_snowman", :value => "&#9731;".html_safe)
W
wycats 已提交
543 544 545 546

          method = html_options.delete("method").to_s

          method_tag = case method
547
            when /^get$/i # must be case-insensitive, but can't use downcase as might be nil
548 549 550 551
              html_options["method"] = "get"
              ''
            when /^post$/i, "", nil
              html_options["method"] = "post"
W
wycats 已提交
552
              token_tag
553 554
            else
              html_options["method"] = "post"
W
wycats 已提交
555
              tag(:input, :type => "hidden", :name => "_method", :value => method) + token_tag
556
          end
W
wycats 已提交
557 558 559

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

562 563
        def form_tag_html(html_options)
          extra_tags = extra_tags_for_form(html_options)
564
          (tag(:form, html_options, true) + extra_tags).html_safe
565
        end
566

567 568
        def form_tag_in_block(html_options, &block)
          content = capture(&block)
W
wycats 已提交
569 570 571 572
          output = ActiveSupport::SafeBuffer.new
          output.safe_concat(form_tag_html(html_options))
          output << content
          output.safe_concat("</form>")
573
        end
574 575

        def token_tag
576
          unless protect_against_forgery?
577 578
            ''
          else
579
            tag(:input, :type => "hidden", :name => request_forgery_protection_token.to_s, :value => form_authenticity_token)
580 581
          end
        end
582 583 584 585 586

        # 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
587 588 589
    end
  end
end