actionview 7.0.0.alpha2 → 7.0.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of actionview might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +156 -0
- data/lib/action_view/base.rb +1 -4
- data/lib/action_view/cache_expiry.rb +1 -1
- data/lib/action_view/gem_version.rb +1 -1
- data/lib/action_view/helpers/active_model_helper.rb +1 -1
- data/lib/action_view/helpers/asset_tag_helper.rb +5 -5
- data/lib/action_view/helpers/asset_url_helper.rb +2 -2
- data/lib/action_view/helpers/cache_helper.rb +9 -8
- data/lib/action_view/helpers/date_helper.rb +57 -2
- data/lib/action_view/helpers/form_helper.rb +127 -66
- data/lib/action_view/helpers/form_options_helper.rb +6 -3
- data/lib/action_view/helpers/form_tag_helper.rb +58 -14
- data/lib/action_view/helpers/number_helper.rb +1 -0
- data/lib/action_view/helpers/tag_helper.rb +17 -2
- data/lib/action_view/helpers/tags/base.rb +5 -13
- data/lib/action_view/helpers/tags/check_box.rb +1 -1
- data/lib/action_view/helpers/tags/file_field.rb +16 -0
- data/lib/action_view/helpers/tags/hidden_field.rb +4 -0
- data/lib/action_view/helpers/tags/weekday_select.rb +2 -1
- data/lib/action_view/helpers/translation_helper.rb +9 -39
- data/lib/action_view/helpers/url_helper.rb +56 -10
- data/lib/action_view/model_naming.rb +1 -1
- data/lib/action_view/railtie.rb +5 -6
- data/lib/action_view/template.rb +8 -1
- metadata +15 -14
@@ -282,6 +282,12 @@ module ActionView
|
|
282
282
|
# ...
|
283
283
|
# <% end %>
|
284
284
|
#
|
285
|
+
# You can omit the <tt>action</tt> attribute by passing <tt>url: false</tt>:
|
286
|
+
#
|
287
|
+
# <%= form_for(@post, url: false) do |f| %>
|
288
|
+
# ...
|
289
|
+
# <% end %>
|
290
|
+
#
|
285
291
|
# You can also set the answer format, like this:
|
286
292
|
#
|
287
293
|
# <%= form_for(@post, format: :json) do |f| %>
|
@@ -426,50 +432,45 @@ module ActionView
|
|
426
432
|
# <% end %>
|
427
433
|
def form_for(record, options = {}, &block)
|
428
434
|
raise ArgumentError, "Missing block" unless block_given?
|
429
|
-
html_options = options[:html] ||= {}
|
430
435
|
|
431
436
|
case record
|
432
437
|
when String, Symbol
|
438
|
+
model = nil
|
433
439
|
object_name = record
|
434
|
-
object = nil
|
435
440
|
else
|
436
|
-
|
441
|
+
model = record
|
442
|
+
object = _object_for_form_builder(record)
|
437
443
|
raise ArgumentError, "First argument in form cannot contain nil or be empty" unless object
|
438
444
|
object_name = options[:as] || model_name_from_record_or_class(object).param_key
|
439
|
-
apply_form_for_options!(
|
445
|
+
apply_form_for_options!(object, options)
|
440
446
|
end
|
441
447
|
|
442
|
-
|
443
|
-
html_options[:remote] = options.delete(:remote) if options.has_key?(:remote)
|
444
|
-
html_options[:method] = options.delete(:method) if options.has_key?(:method)
|
445
|
-
html_options[:enforce_utf8] = options.delete(:enforce_utf8) if options.has_key?(:enforce_utf8)
|
446
|
-
html_options[:authenticity_token] = options.delete(:authenticity_token)
|
448
|
+
remote = options.delete(:remote)
|
447
449
|
|
448
|
-
|
449
|
-
|
450
|
-
|
450
|
+
if remote && !embed_authenticity_token_in_remote_forms && options[:authenticity_token].blank?
|
451
|
+
options[:authenticity_token] = false
|
452
|
+
end
|
453
|
+
|
454
|
+
options[:model] = model
|
455
|
+
options[:scope] = object_name
|
456
|
+
options[:local] = !remote
|
457
|
+
options[:skip_default_ids] = false
|
458
|
+
options[:allow_method_names_outside_object] = options.fetch(:allow_method_names_outside_object, false)
|
451
459
|
|
452
|
-
|
453
|
-
form_tag_with_body(html_options, output)
|
460
|
+
form_with(**options, &block)
|
454
461
|
end
|
455
462
|
|
456
|
-
def apply_form_for_options!(
|
463
|
+
def apply_form_for_options!(object, options) # :nodoc:
|
457
464
|
object = convert_to_model(object)
|
458
465
|
|
459
466
|
as = options[:as]
|
460
467
|
namespace = options[:namespace]
|
461
|
-
action
|
468
|
+
action = object.respond_to?(:persisted?) && object.persisted? ? :edit : :new
|
469
|
+
options[:html] ||= {}
|
462
470
|
options[:html].reverse_merge!(
|
463
471
|
class: as ? "#{action}_#{as}" : dom_class(object, action),
|
464
472
|
id: (as ? [namespace, action, as] : [namespace, dom_id(object, action)]).compact.join("_").presence,
|
465
|
-
method: method
|
466
473
|
)
|
467
|
-
|
468
|
-
options[:url] ||= if options.key?(:format)
|
469
|
-
polymorphic_path(record, format: options.delete(:format))
|
470
|
-
else
|
471
|
-
polymorphic_path(record, {})
|
472
|
-
end
|
473
474
|
end
|
474
475
|
private :apply_form_for_options!
|
475
476
|
|
@@ -477,6 +478,8 @@ module ActionView
|
|
477
478
|
|
478
479
|
mattr_accessor :form_with_generates_ids, default: false
|
479
480
|
|
481
|
+
mattr_accessor :multiple_file_field_include_hidden, default: false
|
482
|
+
|
480
483
|
# Creates a form tag based on mixing URLs, scopes, or models.
|
481
484
|
#
|
482
485
|
# # Using just a URL:
|
@@ -484,7 +487,16 @@ module ActionView
|
|
484
487
|
# <%= form.text_field :title %>
|
485
488
|
# <% end %>
|
486
489
|
# # =>
|
487
|
-
# <form action="/posts" method="post"
|
490
|
+
# <form action="/posts" method="post">
|
491
|
+
# <input type="text" name="title">
|
492
|
+
# </form>
|
493
|
+
#
|
494
|
+
# # With an intentionally empty URL:
|
495
|
+
# <%= form_with url: false do |form| %>
|
496
|
+
# <%= form.text_field :title %>
|
497
|
+
# <% end %>
|
498
|
+
# # =>
|
499
|
+
# <form method="post" data-remote="true">
|
488
500
|
# <input type="text" name="title">
|
489
501
|
# </form>
|
490
502
|
#
|
@@ -493,7 +505,7 @@ module ActionView
|
|
493
505
|
# <%= form.text_field :title %>
|
494
506
|
# <% end %>
|
495
507
|
# # =>
|
496
|
-
# <form action="/posts" method="post"
|
508
|
+
# <form action="/posts" method="post">
|
497
509
|
# <input type="text" name="post[title]">
|
498
510
|
# </form>
|
499
511
|
#
|
@@ -502,7 +514,7 @@ module ActionView
|
|
502
514
|
# <%= form.text_field :title %>
|
503
515
|
# <% end %>
|
504
516
|
# # =>
|
505
|
-
# <form action="/posts" method="post"
|
517
|
+
# <form action="/posts" method="post">
|
506
518
|
# <input type="text" name="post[title]">
|
507
519
|
# </form>
|
508
520
|
#
|
@@ -511,7 +523,7 @@ module ActionView
|
|
511
523
|
# <%= form.text_field :title %>
|
512
524
|
# <% end %>
|
513
525
|
# # =>
|
514
|
-
# <form action="/posts/1" method="post"
|
526
|
+
# <form action="/posts/1" method="post">
|
515
527
|
# <input type="hidden" name="_method" value="patch">
|
516
528
|
# <input type="text" name="post[title]" value="<the title of the post>">
|
517
529
|
# </form>
|
@@ -522,7 +534,7 @@ module ActionView
|
|
522
534
|
# <%= form.text_field :but_in_forms_they_can %>
|
523
535
|
# <% end %>
|
524
536
|
# # =>
|
525
|
-
# <form action="/cats" method="post"
|
537
|
+
# <form action="/cats" method="post">
|
526
538
|
# <input type="text" name="cat[cats_dont_have_gills]">
|
527
539
|
# <input type="text" name="cat[but_in_forms_they_can]">
|
528
540
|
# </form>
|
@@ -740,13 +752,14 @@ module ActionView
|
|
740
752
|
# form_with(**options.merge(builder: LabellingFormBuilder), &block)
|
741
753
|
# end
|
742
754
|
def form_with(model: nil, scope: nil, url: nil, format: nil, **options, &block)
|
743
|
-
options
|
744
|
-
options[:skip_default_ids] = !form_with_generates_ids
|
755
|
+
options = { allow_method_names_outside_object: true, skip_default_ids: !form_with_generates_ids }.merge!(options)
|
745
756
|
|
746
757
|
if model
|
747
|
-
url
|
758
|
+
if url != false
|
759
|
+
url ||= polymorphic_path(model, format: format)
|
760
|
+
end
|
748
761
|
|
749
|
-
model = model
|
762
|
+
model = _object_for_form_builder(model)
|
750
763
|
scope ||= model_name_from_record_or_class(model).param_key
|
751
764
|
end
|
752
765
|
|
@@ -1005,8 +1018,9 @@ module ActionView
|
|
1005
1018
|
# hidden field is not needed and you can pass <tt>include_id: false</tt>
|
1006
1019
|
# to prevent fields_for from rendering it automatically.
|
1007
1020
|
def fields_for(record_name, record_object = nil, options = {}, &block)
|
1008
|
-
|
1009
|
-
|
1021
|
+
options = { model: record_object, allow_method_names_outside_object: false, skip_default_ids: false }.merge!(options)
|
1022
|
+
|
1023
|
+
fields(record_name, **options, &block)
|
1010
1024
|
end
|
1011
1025
|
|
1012
1026
|
# Scopes input fields with either an explicit scope or model.
|
@@ -1055,10 +1069,10 @@ module ActionView
|
|
1055
1069
|
# to work with an object as a base, like
|
1056
1070
|
# FormOptionsHelper#collection_select and DateHelper#datetime_select.
|
1057
1071
|
def fields(scope = nil, model: nil, **options, &block)
|
1058
|
-
options
|
1059
|
-
options[:skip_default_ids] = !form_with_generates_ids
|
1072
|
+
options = { allow_method_names_outside_object: true, skip_default_ids: !form_with_generates_ids }.merge!(options)
|
1060
1073
|
|
1061
1074
|
if model
|
1075
|
+
model = _object_for_form_builder(model)
|
1062
1076
|
scope ||= model_name_from_record_or_class(model).param_key
|
1063
1077
|
end
|
1064
1078
|
|
@@ -1202,6 +1216,7 @@ module ActionView
|
|
1202
1216
|
# * Creates standard HTML attributes for the tag.
|
1203
1217
|
# * <tt>:disabled</tt> - If set to true, the user will not be able to use this input.
|
1204
1218
|
# * <tt>:multiple</tt> - If set to true, *in most updated browsers* the user will be allowed to select multiple files.
|
1219
|
+
# * <tt>:include_hidden</tt> - When <tt>multiple: true</tt> and <tt>include_hidden: true</tt>, the field will be prefixed with an <tt><input type="hidden"></tt> field with an empty value to support submitting an empty collection of files.
|
1205
1220
|
# * <tt>:accept</tt> - If set to one or multiple mime-types, the user will be suggested a filter when choosing a file. You still need to set up model validations.
|
1206
1221
|
#
|
1207
1222
|
# ==== Examples
|
@@ -1220,7 +1235,9 @@ module ActionView
|
|
1220
1235
|
# file_field(:attachment, :file, class: 'file_input')
|
1221
1236
|
# # => <input type="file" id="attachment_file" name="attachment[file]" class="file_input" />
|
1222
1237
|
def file_field(object_name, method, options = {})
|
1223
|
-
|
1238
|
+
options = { include_hidden: multiple_file_field_include_hidden }.merge!(options)
|
1239
|
+
|
1240
|
+
Tags::FileField.new(object_name, method, self, convert_direct_upload_option_to_url(method, options)).render
|
1224
1241
|
end
|
1225
1242
|
|
1226
1243
|
# Returns a textarea opening and closing tag set tailored for accessing a specified attribute (identified by +method+)
|
@@ -1257,6 +1274,12 @@ module ActionView
|
|
1257
1274
|
# Additional options on the input tag can be passed as a hash with +options+. The +checked_value+ defaults to 1
|
1258
1275
|
# while the default +unchecked_value+ is set to 0 which is convenient for boolean values.
|
1259
1276
|
#
|
1277
|
+
# ==== Options
|
1278
|
+
#
|
1279
|
+
# * Any standard HTML attributes for the tag can be passed in, for example +:class+.
|
1280
|
+
# * <tt>:checked</tt> - +true+ or +false+ forces the state of the checkbox to be checked or not.
|
1281
|
+
# * <tt>:include_hidden</tt> - If set to false, the auxiliary hidden field described below will not be generated.
|
1282
|
+
#
|
1260
1283
|
# ==== Gotcha
|
1261
1284
|
#
|
1262
1285
|
# The HTML specification says unchecked check boxes are not successful, and
|
@@ -1270,7 +1293,7 @@ module ActionView
|
|
1270
1293
|
# wouldn't update the flag.
|
1271
1294
|
#
|
1272
1295
|
# To prevent this the helper generates an auxiliary hidden field before
|
1273
|
-
#
|
1296
|
+
# every check box. The hidden field has the same name and its
|
1274
1297
|
# attributes mimic an unchecked check box.
|
1275
1298
|
#
|
1276
1299
|
# This way, the client either sends only the hidden field (representing
|
@@ -1294,6 +1317,8 @@ module ActionView
|
|
1294
1317
|
# In that case it is preferable to either use +check_box_tag+ or to use
|
1295
1318
|
# hashes instead of arrays.
|
1296
1319
|
#
|
1320
|
+
# ==== Examples
|
1321
|
+
#
|
1297
1322
|
# # Let's say that @post.validated? is 1:
|
1298
1323
|
# check_box("post", "validated")
|
1299
1324
|
# # => <input name="post[validated]" type="hidden" value="0" />
|
@@ -1540,34 +1565,24 @@ module ActionView
|
|
1540
1565
|
Tags::RangeField.new(object_name, method, self, options).render
|
1541
1566
|
end
|
1542
1567
|
|
1568
|
+
def _object_for_form_builder(object) # :nodoc:
|
1569
|
+
object.is_a?(Array) ? object.last : object
|
1570
|
+
end
|
1571
|
+
|
1543
1572
|
private
|
1544
1573
|
def html_options_for_form_with(url_for_options = nil, model = nil, html: {}, local: !form_with_generates_remote_forms,
|
1545
1574
|
skip_enforcing_utf8: nil, **options)
|
1546
|
-
html_options = options.slice(:id, :class, :multipart, :method, :data).merge(html)
|
1575
|
+
html_options = options.slice(:id, :class, :multipart, :method, :data, :authenticity_token).merge!(html)
|
1576
|
+
html_options[:remote] = html.delete(:remote) || !local
|
1547
1577
|
html_options[:method] ||= :patch if model.respond_to?(:persisted?) && model.persisted?
|
1548
|
-
|
1549
|
-
|
1550
|
-
|
1551
|
-
|
1552
|
-
|
1553
|
-
|
1554
|
-
html_options[:action] = url_for(url_for_options || {})
|
1555
|
-
html_options[:"accept-charset"] = "UTF-8"
|
1556
|
-
html_options[:"data-remote"] = true unless local
|
1557
|
-
|
1558
|
-
html_options[:authenticity_token] = options.delete(:authenticity_token)
|
1559
|
-
|
1560
|
-
if !local && html_options[:authenticity_token].blank?
|
1561
|
-
html_options[:authenticity_token] = embed_authenticity_token_in_remote_forms
|
1562
|
-
end
|
1563
|
-
|
1564
|
-
if html_options[:authenticity_token] == true
|
1565
|
-
# Include the default authenticity_token, which is only generated when it's set to nil,
|
1566
|
-
# but we needed the true value to override the default of no authenticity_token on data-remote.
|
1567
|
-
html_options[:authenticity_token] = nil
|
1578
|
+
if skip_enforcing_utf8.nil?
|
1579
|
+
if options.key?(:enforce_utf8)
|
1580
|
+
html_options[:enforce_utf8] = options[:enforce_utf8]
|
1581
|
+
end
|
1582
|
+
else
|
1583
|
+
html_options[:enforce_utf8] = !skip_enforcing_utf8
|
1568
1584
|
end
|
1569
|
-
|
1570
|
-
html_options.stringify_keys!
|
1585
|
+
html_options_for_form(url_for_options.nil? ? {} : url_for_options, html_options)
|
1571
1586
|
end
|
1572
1587
|
|
1573
1588
|
def instantiate_builder(record_name, record_object, options)
|
@@ -1734,8 +1749,30 @@ module ActionView
|
|
1734
1749
|
# <tt>aria-describedby</tt> attribute referencing the <tt><span></tt>
|
1735
1750
|
# element, sharing a common <tt>id</tt> root (<tt>post_title</tt>, in this
|
1736
1751
|
# case).
|
1737
|
-
def field_id(method, *suffixes, index: @index)
|
1738
|
-
@template.field_id(@object_name, method, *suffixes, index: index)
|
1752
|
+
def field_id(method, *suffixes, namespace: @options[:namespace], index: @index)
|
1753
|
+
@template.field_id(@object_name, method, *suffixes, namespace: namespace, index: index)
|
1754
|
+
end
|
1755
|
+
|
1756
|
+
# Generate an HTML <tt>name</tt> attribute value for the given name and
|
1757
|
+
# field combination
|
1758
|
+
#
|
1759
|
+
# Return the value generated by the <tt>FormBuilder</tt> for the given
|
1760
|
+
# attribute name.
|
1761
|
+
#
|
1762
|
+
# <%= form_for @post do |f| %>
|
1763
|
+
# <%= f.text_field :title, name: f.field_name(:title, :subtitle) %>
|
1764
|
+
# <%# => <input type="text" name="post[title][subtitle]">
|
1765
|
+
# <% end %>
|
1766
|
+
#
|
1767
|
+
# <%= form_for @post do |f| %>
|
1768
|
+
# <%= f.field_tag :tag, name: f.field_name(:tag, multiple: true) %>
|
1769
|
+
# <%# => <input type="text" name="post[tag][]">
|
1770
|
+
# <% end %>
|
1771
|
+
#
|
1772
|
+
def field_name(method, *methods, multiple: false, index: @index)
|
1773
|
+
object_name = @options.fetch(:as) { @object_name }
|
1774
|
+
|
1775
|
+
@template.field_name(object_name, method, *methods, index: index, multiple: multiple)
|
1739
1776
|
end
|
1740
1777
|
|
1741
1778
|
##
|
@@ -2224,7 +2261,7 @@ module ActionView
|
|
2224
2261
|
return fields_for_with_nested_attributes(record_name, record_object, fields_options, block)
|
2225
2262
|
end
|
2226
2263
|
else
|
2227
|
-
record_object =
|
2264
|
+
record_object = @template._object_for_form_builder(record_name)
|
2228
2265
|
record_name = model_name_from_record_or_class(record_object).param_key
|
2229
2266
|
end
|
2230
2267
|
|
@@ -2334,6 +2371,12 @@ module ActionView
|
|
2334
2371
|
# Additional options on the input tag can be passed as a hash with +options+. The +checked_value+ defaults to 1
|
2335
2372
|
# while the default +unchecked_value+ is set to 0 which is convenient for boolean values.
|
2336
2373
|
#
|
2374
|
+
# ==== Options
|
2375
|
+
#
|
2376
|
+
# * Any standard HTML attributes for the tag can be passed in, for example +:class+.
|
2377
|
+
# * <tt>:checked</tt> - +true+ or +false+ forces the state of the checkbox to be checked or not.
|
2378
|
+
# * <tt>:include_hidden</tt> - If set to false, the auxiliary hidden field described below will not be generated.
|
2379
|
+
#
|
2337
2380
|
# ==== Gotcha
|
2338
2381
|
#
|
2339
2382
|
# The HTML specification says unchecked check boxes are not successful, and
|
@@ -2347,7 +2390,7 @@ module ActionView
|
|
2347
2390
|
# wouldn't update the flag.
|
2348
2391
|
#
|
2349
2392
|
# To prevent this the helper generates an auxiliary hidden field before
|
2350
|
-
#
|
2393
|
+
# every check box. The hidden field has the same name and its
|
2351
2394
|
# attributes mimic an unchecked check box.
|
2352
2395
|
#
|
2353
2396
|
# This way, the client either sends only the hidden field (representing
|
@@ -2371,6 +2414,8 @@ module ActionView
|
|
2371
2414
|
# In that case it is preferable to either use +check_box_tag+ or to use
|
2372
2415
|
# hashes instead of arrays.
|
2373
2416
|
#
|
2417
|
+
# ==== Examples
|
2418
|
+
#
|
2374
2419
|
# # Let's say that @post.validated? is 1:
|
2375
2420
|
# check_box("validated")
|
2376
2421
|
# # => <input name="post[validated]" type="hidden" value="0" />
|
@@ -2445,6 +2490,7 @@ module ActionView
|
|
2445
2490
|
# * Creates standard HTML attributes for the tag.
|
2446
2491
|
# * <tt>:disabled</tt> - If set to true, the user will not be able to use this input.
|
2447
2492
|
# * <tt>:multiple</tt> - If set to true, *in most updated browsers* the user will be allowed to select multiple files.
|
2493
|
+
# * <tt>:include_hidden</tt> - When <tt>multiple: true</tt> and <tt>include_hidden: true</tt>, the field will be prefixed with an <tt><input type="hidden"></tt> field with an empty value to support submitting an empty collection of files.
|
2448
2494
|
# * <tt>:accept</tt> - If set to one or multiple mime-types, the user will be suggested a filter when choosing a file. You still need to set up model validations.
|
2449
2495
|
#
|
2450
2496
|
# ==== Examples
|
@@ -2536,6 +2582,9 @@ module ActionView
|
|
2536
2582
|
# button("Create post")
|
2537
2583
|
# # => <button name='button' type='submit'>Create post</button>
|
2538
2584
|
#
|
2585
|
+
# button(:draft, value: true)
|
2586
|
+
# # => <button id="post_draft" name="post[draft]" value="true" type="submit">Create post</button>
|
2587
|
+
#
|
2539
2588
|
# button do
|
2540
2589
|
# content_tag(:strong, 'Ask me!')
|
2541
2590
|
# end
|
@@ -2550,8 +2599,20 @@ module ActionView
|
|
2550
2599
|
# # <strong>Create post</strong>
|
2551
2600
|
# # </button>
|
2552
2601
|
#
|
2602
|
+
# button(:draft, value: true) do
|
2603
|
+
# content_tag(:strong, "Save as draft")
|
2604
|
+
# end
|
2605
|
+
# # => <button id="post_draft" name="post[draft]" value="true" type="submit">
|
2606
|
+
# # <strong>Save as draft</strong>
|
2607
|
+
# # </button>
|
2608
|
+
#
|
2553
2609
|
def button(value = nil, options = {}, &block)
|
2554
|
-
|
2610
|
+
case value
|
2611
|
+
when Hash
|
2612
|
+
value, options = nil, value
|
2613
|
+
when Symbol
|
2614
|
+
value, options = nil, { name: field_name(value), id: field_id(value) }.merge!(options.to_h)
|
2615
|
+
end
|
2555
2616
|
value ||= submit_default_value
|
2556
2617
|
|
2557
2618
|
if block_given?
|
@@ -597,15 +597,18 @@ module ActionView
|
|
597
597
|
# Returns a string of option tags for the days of the week.
|
598
598
|
#
|
599
599
|
# Options:
|
600
|
-
# * <tt>:index_as_value</tt> - Defaults to false, set to true to use the
|
600
|
+
# * <tt>:index_as_value</tt> - Defaults to false, set to true to use the indexes from
|
601
|
+
# `I18n.translate("date.day_names")` as the values. By default, Sunday is always 0.
|
601
602
|
# * <tt>:day_format</tt> - The I18n key of the array to use for the weekday options.
|
602
603
|
# Defaults to :day_names, set to :abbr_day_names for abbreviations.
|
604
|
+
# * <tt>:beginning_of_week</tt> - Defaults to Date.beginning_of_week.
|
603
605
|
#
|
604
606
|
# NOTE: Only the option tags are returned, you have to wrap this call in
|
605
607
|
# a regular HTML select tag.
|
606
|
-
def weekday_options_for_select(selected = nil, index_as_value: false, day_format: :day_names)
|
608
|
+
def weekday_options_for_select(selected = nil, index_as_value: false, day_format: :day_names, beginning_of_week: Date.beginning_of_week)
|
607
609
|
day_names = I18n.translate("date.#{day_format}")
|
608
|
-
day_names = day_names.map.with_index.
|
610
|
+
day_names = day_names.map.with_index.to_a if index_as_value
|
611
|
+
day_names = day_names.rotate(Date::DAYS_INTO_WEEK.fetch(beginning_of_week))
|
609
612
|
|
610
613
|
options_for_select(day_names, selected)
|
611
614
|
end
|
@@ -62,6 +62,9 @@ module ActionView
|
|
62
62
|
#
|
63
63
|
# <%= form_tag('/posts', remote: true) %>
|
64
64
|
# # => <form action="/posts" method="post" data-remote="true">
|
65
|
+
|
66
|
+
# form_tag(false, method: :get)
|
67
|
+
# # => <form method="get">
|
65
68
|
#
|
66
69
|
# form_tag('http://far.away.com/form', authenticity_token: false)
|
67
70
|
# # form without authenticity token
|
@@ -93,7 +96,7 @@ module ActionView
|
|
93
96
|
# <tt>aria-describedby</tt> attribute referencing the <tt><span></tt>
|
94
97
|
# element, sharing a common <tt>id</tt> root (<tt>post_title</tt>, in this
|
95
98
|
# case).
|
96
|
-
def field_id(object_name, method_name, *suffixes, index: nil)
|
99
|
+
def field_id(object_name, method_name, *suffixes, index: nil, namespace: nil)
|
97
100
|
if object_name.respond_to?(:model_name)
|
98
101
|
object_name = object_name.model_name.singular
|
99
102
|
end
|
@@ -102,15 +105,38 @@ module ActionView
|
|
102
105
|
|
103
106
|
sanitized_method_name = method_name.to_s.delete_suffix("?")
|
104
107
|
|
108
|
+
[
|
109
|
+
namespace,
|
110
|
+
sanitized_object_name.presence,
|
111
|
+
(index unless sanitized_object_name.empty?),
|
112
|
+
sanitized_method_name,
|
113
|
+
*suffixes,
|
114
|
+
].tap(&:compact!).join("_")
|
115
|
+
end
|
116
|
+
|
117
|
+
# Generate an HTML <tt>name</tt> attribute value for the given name and
|
118
|
+
# field combination
|
119
|
+
#
|
120
|
+
# Return the value generated by the <tt>FormBuilder</tt> for the given
|
121
|
+
# attribute name.
|
122
|
+
#
|
123
|
+
# <%= text_field_tag :post, :title, name: field_name(:post, :title, :subtitle) %>
|
124
|
+
# <%# => <input type="text" name="post[title][subtitle]">
|
125
|
+
#
|
126
|
+
# <%= text_field_tag :post, :tag, name: field_name(:post, :tag, multiple: true) %>
|
127
|
+
# <%# => <input type="text" name="post[tag][]">
|
128
|
+
#
|
129
|
+
def field_name(object_name, method_name, *method_names, multiple: false, index: nil)
|
130
|
+
names = method_names.map! { |name| "[#{name}]" }.join
|
131
|
+
|
105
132
|
# a little duplication to construct fewer strings
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
"#{sanitized_object_name}_#{index}_#{sanitized_method_name}"
|
133
|
+
case
|
134
|
+
when object_name.empty?
|
135
|
+
"#{method_name}#{names}#{multiple ? "[]" : ""}"
|
136
|
+
when index
|
137
|
+
"#{object_name}[#{index}][#{method_name}]#{names}#{multiple ? "[]" : ""}"
|
112
138
|
else
|
113
|
-
"#{
|
139
|
+
"#{object_name}[#{method_name}]#{names}#{multiple ? "[]" : ""}"
|
114
140
|
end
|
115
141
|
end
|
116
142
|
|
@@ -277,7 +303,7 @@ module ActionView
|
|
277
303
|
# # => <input id="collected_input" name="collected_input" onchange="alert('Input collected!')"
|
278
304
|
# # type="hidden" value="" />
|
279
305
|
def hidden_field_tag(name, value = nil, options = {})
|
280
|
-
text_field_tag(name, value, options.merge(type: :hidden))
|
306
|
+
text_field_tag(name, value, options.merge(type: :hidden, autocomplete: "off"))
|
281
307
|
end
|
282
308
|
|
283
309
|
# Creates a file upload field. If you are using file uploads then you will also need
|
@@ -316,7 +342,7 @@ module ActionView
|
|
316
342
|
# file_field_tag 'file', accept: 'text/html', class: 'upload', value: 'index.html'
|
317
343
|
# # => <input accept="text/html" class="upload" id="file" name="file" type="file" value="index.html" />
|
318
344
|
def file_field_tag(name, options = {})
|
319
|
-
text_field_tag(name, nil, convert_direct_upload_option_to_url(options.merge(type: :file)))
|
345
|
+
text_field_tag(name, nil, convert_direct_upload_option_to_url(name, options.merge(type: :file)))
|
320
346
|
end
|
321
347
|
|
322
348
|
# Creates a password field, a masked text field that will hide the users input behind a mask character.
|
@@ -866,7 +892,7 @@ module ActionView
|
|
866
892
|
# Use raw HTML to ensure the value is written as an HTML entity; it
|
867
893
|
# needs to be the right character regardless of which encoding the
|
868
894
|
# browser infers.
|
869
|
-
'<input name="utf8" type="hidden" value="✓" />'.html_safe
|
895
|
+
'<input name="utf8" type="hidden" value="✓" autocomplete="off" />'.html_safe
|
870
896
|
end
|
871
897
|
|
872
898
|
private
|
@@ -875,13 +901,17 @@ module ActionView
|
|
875
901
|
html_options["enctype"] = "multipart/form-data" if html_options.delete("multipart")
|
876
902
|
# The following URL is unescaped, this is just a hash of options, and it is the
|
877
903
|
# responsibility of the caller to escape all the values.
|
878
|
-
html_options["action"]
|
904
|
+
if url_for_options == false || html_options["action"] == false
|
905
|
+
html_options.delete("action")
|
906
|
+
else
|
907
|
+
html_options["action"] = url_for(url_for_options)
|
908
|
+
end
|
879
909
|
html_options["accept-charset"] = "UTF-8"
|
880
910
|
|
881
911
|
html_options["data-remote"] = true if html_options.delete("remote")
|
882
912
|
|
883
913
|
if html_options["data-remote"] &&
|
884
|
-
|
914
|
+
embed_authenticity_token_in_remote_forms == false &&
|
885
915
|
html_options["authenticity_token"].blank?
|
886
916
|
# The authenticity token is taken from the meta tag in this case
|
887
917
|
html_options["authenticity_token"] = false
|
@@ -954,9 +984,23 @@ module ActionView
|
|
954
984
|
tag_options.delete("data-disable-with")
|
955
985
|
end
|
956
986
|
|
957
|
-
def convert_direct_upload_option_to_url(options)
|
987
|
+
def convert_direct_upload_option_to_url(name, options)
|
958
988
|
if options.delete(:direct_upload) && respond_to?(:rails_direct_uploads_url)
|
959
989
|
options["data-direct-upload-url"] = rails_direct_uploads_url
|
990
|
+
|
991
|
+
if options[:object] && options[:object].class.respond_to?(:reflect_on_attachment)
|
992
|
+
attachment_reflection = options[:object].class.reflect_on_attachment(name)
|
993
|
+
|
994
|
+
class_with_attachment = "#{options[:object].class.name.underscore}##{name}"
|
995
|
+
options["data-direct-upload-attachment-name"] = class_with_attachment
|
996
|
+
|
997
|
+
service_name = attachment_reflection.options[:service_name] || ActiveStorage::Blob.service.name
|
998
|
+
options["data-direct-upload-token"] = ActiveStorage::DirectUploadToken.generate_direct_upload_token(
|
999
|
+
class_with_attachment,
|
1000
|
+
service_name,
|
1001
|
+
session
|
1002
|
+
)
|
1003
|
+
end
|
960
1004
|
end
|
961
1005
|
options
|
962
1006
|
end
|
@@ -45,7 +45,8 @@ module ActionView
|
|
45
45
|
include CaptureHelper
|
46
46
|
include OutputSafetyHelper
|
47
47
|
|
48
|
-
|
48
|
+
HTML_VOID_ELEMENTS = %i(area base br col circle embed hr img input keygen link meta param source track wbr).to_set
|
49
|
+
SVG_VOID_ELEMENTS = %i(animate animateMotion animateTransform circle ellipse line path polygon polyline rect set stop use view).to_set
|
49
50
|
|
50
51
|
def initialize(view_context)
|
51
52
|
@view_context = view_context
|
@@ -66,7 +67,7 @@ module ActionView
|
|
66
67
|
|
67
68
|
def tag_string(name, content = nil, escape_attributes: true, **options, &block)
|
68
69
|
content = @view_context.capture(self, &block) if block_given?
|
69
|
-
if
|
70
|
+
if (HTML_VOID_ELEMENTS.include?(name) || SVG_VOID_ELEMENTS.include?(name)) && content.nil?
|
70
71
|
"<#{name.to_s.dasherize}#{tag_options(options, escape_attributes)}>".html_safe
|
71
72
|
else
|
72
73
|
content_tag_string(name.to_s.dasherize, content || "", options, escape_attributes)
|
@@ -234,6 +235,20 @@ module ActionView
|
|
234
235
|
# # A void element:
|
235
236
|
# tag.br # => <br>
|
236
237
|
#
|
238
|
+
# === Building HTML attributes
|
239
|
+
#
|
240
|
+
# Transforms a Hash into HTML attributes, ready to be interpolated into
|
241
|
+
# ERB. Includes or omits boolean attributes based on their truthiness.
|
242
|
+
# Transforms keys nested within
|
243
|
+
# <tt>aria:</tt> or <tt>data:</tt> objects into `aria-` and `data-`
|
244
|
+
# prefixed attributes:
|
245
|
+
#
|
246
|
+
# <input <%= tag.attributes(type: :text, aria: { label: "Search" }) %>>
|
247
|
+
# # => <input type="text" aria-label="Search">
|
248
|
+
#
|
249
|
+
# <button <%= tag.attributes id: "call-to-action", disabled: false, aria: { expanded: false } %> class="primary">Get Started!</button>
|
250
|
+
# # => <button id="call-to-action" aria-expanded="false" class="primary">Get Started!</button>
|
251
|
+
#
|
237
252
|
# === Legacy syntax
|
238
253
|
#
|
239
254
|
# The following format is for legacy syntax support. It will be deprecated in future versions of Rails.
|
@@ -97,7 +97,7 @@ module ActionView
|
|
97
97
|
options["name"] = options.fetch("name") { tag_name(options["multiple"], index) }
|
98
98
|
|
99
99
|
if generate_ids?
|
100
|
-
options["id"] = options.fetch("id") { tag_id(index) }
|
100
|
+
options["id"] = options.fetch("id") { tag_id(index, options.delete("namespace")) }
|
101
101
|
if namespace = options.delete("namespace")
|
102
102
|
options["id"] = options["id"] ? "#{namespace}_#{options['id']}" : namespace
|
103
103
|
end
|
@@ -105,19 +105,11 @@ module ActionView
|
|
105
105
|
end
|
106
106
|
|
107
107
|
def tag_name(multiple = false, index = nil)
|
108
|
-
|
109
|
-
case
|
110
|
-
when @object_name.empty?
|
111
|
-
"#{sanitized_method_name}#{multiple ? "[]" : ""}"
|
112
|
-
when index
|
113
|
-
"#{@object_name}[#{index}][#{sanitized_method_name}]#{multiple ? "[]" : ""}"
|
114
|
-
else
|
115
|
-
"#{@object_name}[#{sanitized_method_name}]#{multiple ? "[]" : ""}"
|
116
|
-
end
|
108
|
+
@template_object.field_name(@object_name, sanitized_method_name, multiple: multiple, index: index)
|
117
109
|
end
|
118
110
|
|
119
|
-
def tag_id(index = nil)
|
120
|
-
@template_object.field_id(@object_name, @method_name, index: index)
|
111
|
+
def tag_id(index = nil, namespace = nil)
|
112
|
+
@template_object.field_id(@object_name, @method_name, index: index, namespace: namespace)
|
121
113
|
end
|
122
114
|
|
123
115
|
def sanitized_method_name
|
@@ -141,7 +133,7 @@ module ActionView
|
|
141
133
|
select = content_tag("select", add_options(option_tags, options, value), html_options)
|
142
134
|
|
143
135
|
if html_options["multiple"] && options.fetch(:include_hidden, true)
|
144
|
-
tag("input", disabled: html_options["disabled"], name: html_options["name"], type: "hidden", value: "") + select
|
136
|
+
tag("input", disabled: html_options["disabled"], name: html_options["name"], type: "hidden", value: "", autocomplete: "off") + select
|
145
137
|
else
|
146
138
|
select
|
147
139
|
end
|
@@ -57,7 +57,7 @@ module ActionView
|
|
57
57
|
end
|
58
58
|
|
59
59
|
def hidden_field_for_checkbox(options)
|
60
|
-
@unchecked_value ? tag("input", options.slice("name", "disabled", "form").merge!("type" => "hidden", "value" => @unchecked_value)) : "".html_safe
|
60
|
+
@unchecked_value ? tag("input", options.slice("name", "disabled", "form").merge!("type" => "hidden", "value" => @unchecked_value, "autocomplete" => "off")) : "".html_safe
|
61
61
|
end
|
62
62
|
end
|
63
63
|
end
|