actionview 6.1.7.9 → 7.0.8.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +299 -247
- data/MIT-LICENSE +1 -0
- data/README.rdoc +2 -2
- data/lib/action_view/base.rb +4 -7
- data/lib/action_view/buffers.rb +2 -2
- data/lib/action_view/cache_expiry.rb +46 -32
- data/lib/action_view/dependency_tracker/erb_tracker.rb +154 -0
- data/lib/action_view/dependency_tracker/ripper_tracker.rb +59 -0
- data/lib/action_view/dependency_tracker.rb +6 -147
- data/lib/action_view/digestor.rb +7 -4
- data/lib/action_view/flows.rb +4 -4
- data/lib/action_view/gem_version.rb +5 -5
- data/lib/action_view/helpers/active_model_helper.rb +2 -2
- data/lib/action_view/helpers/asset_tag_helper.rb +95 -39
- data/lib/action_view/helpers/asset_url_helper.rb +16 -16
- data/lib/action_view/helpers/atom_feed_helper.rb +3 -4
- data/lib/action_view/helpers/cache_helper.rb +52 -3
- data/lib/action_view/helpers/capture_helper.rb +4 -4
- data/lib/action_view/helpers/controller_helper.rb +2 -2
- data/lib/action_view/helpers/csp_helper.rb +1 -1
- data/lib/action_view/helpers/csrf_helper.rb +2 -2
- data/lib/action_view/helpers/date_helper.rb +111 -43
- data/lib/action_view/helpers/debug_helper.rb +3 -1
- data/lib/action_view/helpers/form_helper.rb +211 -84
- data/lib/action_view/helpers/form_options_helper.rb +70 -33
- data/lib/action_view/helpers/form_tag_helper.rb +150 -53
- data/lib/action_view/helpers/javascript_helper.rb +3 -5
- data/lib/action_view/helpers/number_helper.rb +17 -16
- data/lib/action_view/helpers/output_safety_helper.rb +4 -4
- data/lib/action_view/helpers/rendering_helper.rb +5 -6
- data/lib/action_view/helpers/sanitize_helper.rb +3 -3
- data/lib/action_view/helpers/tag_helper.rb +37 -8
- data/lib/action_view/helpers/tags/base.rb +5 -25
- data/lib/action_view/helpers/tags/check_box.rb +1 -1
- data/lib/action_view/helpers/tags/collection_select.rb +1 -1
- data/lib/action_view/helpers/tags/file_field.rb +16 -0
- data/lib/action_view/helpers/tags/select.rb +1 -1
- data/lib/action_view/helpers/tags/time_field.rb +10 -1
- data/lib/action_view/helpers/tags/weekday_select.rb +28 -0
- data/lib/action_view/helpers/tags.rb +3 -2
- data/lib/action_view/helpers/text_helper.rb +25 -14
- data/lib/action_view/helpers/translation_helper.rb +12 -43
- data/lib/action_view/helpers/url_helper.rb +194 -123
- data/lib/action_view/helpers.rb +25 -25
- data/lib/action_view/layouts.rb +7 -4
- data/lib/action_view/lookup_context.rb +33 -52
- data/lib/action_view/model_naming.rb +2 -2
- data/lib/action_view/path_set.rb +16 -22
- data/lib/action_view/railtie.rb +19 -7
- data/lib/action_view/record_identifier.rb +1 -1
- data/lib/action_view/render_parser.rb +188 -0
- data/lib/action_view/renderer/abstract_renderer.rb +2 -2
- data/lib/action_view/renderer/partial_renderer/collection_caching.rb +20 -3
- data/lib/action_view/renderer/partial_renderer.rb +1 -35
- data/lib/action_view/renderer/renderer.rb +4 -4
- data/lib/action_view/renderer/streaming_template_renderer.rb +3 -3
- data/lib/action_view/renderer/template_renderer.rb +6 -2
- data/lib/action_view/rendering.rb +3 -3
- data/lib/action_view/ripper_ast_parser.rb +198 -0
- data/lib/action_view/routing_url_for.rb +8 -5
- data/lib/action_view/template/error.rb +108 -13
- data/lib/action_view/template/handlers/erb.rb +6 -0
- data/lib/action_view/template/handlers.rb +3 -3
- data/lib/action_view/template/html.rb +3 -3
- data/lib/action_view/template/inline.rb +3 -3
- data/lib/action_view/template/raw_file.rb +3 -3
- data/lib/action_view/template/resolver.rb +89 -314
- data/lib/action_view/template/text.rb +3 -3
- data/lib/action_view/template/types.rb +14 -12
- data/lib/action_view/template.rb +18 -2
- data/lib/action_view/template_details.rb +66 -0
- data/lib/action_view/template_path.rb +64 -0
- data/lib/action_view/test_case.rb +7 -3
- data/lib/action_view/testing/resolvers.rb +11 -12
- data/lib/action_view/unbound_template.rb +33 -7
- data/lib/action_view/version.rb +1 -1
- data/lib/action_view/view_paths.rb +4 -4
- data/lib/action_view.rb +2 -3
- metadata +20 -13
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
require "cgi"
|
4
4
|
require "action_view/helpers/date_helper"
|
5
|
-
require "action_view/helpers/
|
5
|
+
require "action_view/helpers/url_helper"
|
6
6
|
require "action_view/helpers/form_tag_helper"
|
7
7
|
require "action_view/helpers/active_model_helper"
|
8
8
|
require "action_view/model_naming"
|
@@ -11,11 +11,10 @@ require "active_support/core_ext/module/attribute_accessors"
|
|
11
11
|
require "active_support/core_ext/hash/slice"
|
12
12
|
require "active_support/core_ext/string/output_safety"
|
13
13
|
require "active_support/core_ext/string/inflections"
|
14
|
-
require "active_support/core_ext/symbol/starts_ends_with"
|
15
14
|
|
16
15
|
module ActionView
|
17
16
|
# = Action View Form Helpers
|
18
|
-
module Helpers
|
17
|
+
module Helpers # :nodoc:
|
19
18
|
# Form helpers are designed to make working with resources much easier
|
20
19
|
# compared to using vanilla HTML.
|
21
20
|
#
|
@@ -283,6 +282,12 @@ module ActionView
|
|
283
282
|
# ...
|
284
283
|
# <% end %>
|
285
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
|
+
#
|
286
291
|
# You can also set the answer format, like this:
|
287
292
|
#
|
288
293
|
# <%= form_for(@post, format: :json) do |f| %>
|
@@ -427,50 +432,46 @@ module ActionView
|
|
427
432
|
# <% end %>
|
428
433
|
def form_for(record, options = {}, &block)
|
429
434
|
raise ArgumentError, "Missing block" unless block_given?
|
430
|
-
html_options = options[:html] ||= {}
|
431
435
|
|
432
436
|
case record
|
433
437
|
when String, Symbol
|
438
|
+
model = nil
|
434
439
|
object_name = record
|
435
|
-
object = nil
|
436
440
|
else
|
437
|
-
|
441
|
+
model = convert_to_model(record)
|
442
|
+
object = _object_for_form_builder(record)
|
438
443
|
raise ArgumentError, "First argument in form cannot contain nil or be empty" unless object
|
439
444
|
object_name = options[:as] || model_name_from_record_or_class(object).param_key
|
440
|
-
apply_form_for_options!(
|
445
|
+
apply_form_for_options!(object, options)
|
441
446
|
end
|
442
447
|
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
+
remote = options.delete(:remote)
|
449
|
+
|
450
|
+
if remote && !embed_authenticity_token_in_remote_forms && options[:authenticity_token].blank?
|
451
|
+
options[:authenticity_token] = false
|
452
|
+
end
|
448
453
|
|
449
|
-
|
450
|
-
|
451
|
-
|
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)
|
452
459
|
|
453
|
-
|
454
|
-
form_tag_with_body(html_options, output)
|
460
|
+
form_with(**options, &block)
|
455
461
|
end
|
456
462
|
|
457
|
-
def apply_form_for_options!(
|
463
|
+
def apply_form_for_options!(object, options) # :nodoc:
|
458
464
|
object = convert_to_model(object)
|
459
465
|
|
460
466
|
as = options[:as]
|
461
467
|
namespace = options[:namespace]
|
462
468
|
action, method = object.respond_to?(:persisted?) && object.persisted? ? [:edit, :patch] : [:new, :post]
|
469
|
+
options[:html] ||= {}
|
463
470
|
options[:html].reverse_merge!(
|
464
471
|
class: as ? "#{action}_#{as}" : dom_class(object, action),
|
465
472
|
id: (as ? [namespace, action, as] : [namespace, dom_id(object, action)]).compact.join("_").presence,
|
466
|
-
method: method
|
467
473
|
)
|
468
|
-
|
469
|
-
options[:url] ||= if options.key?(:format)
|
470
|
-
polymorphic_path(record, format: options.delete(:format))
|
471
|
-
else
|
472
|
-
polymorphic_path(record, {})
|
473
|
-
end
|
474
|
+
options[:method] ||= method
|
474
475
|
end
|
475
476
|
private :apply_form_for_options!
|
476
477
|
|
@@ -478,6 +479,8 @@ module ActionView
|
|
478
479
|
|
479
480
|
mattr_accessor :form_with_generates_ids, default: false
|
480
481
|
|
482
|
+
mattr_accessor :multiple_file_field_include_hidden, default: false
|
483
|
+
|
481
484
|
# Creates a form tag based on mixing URLs, scopes, or models.
|
482
485
|
#
|
483
486
|
# # Using just a URL:
|
@@ -485,7 +488,16 @@ module ActionView
|
|
485
488
|
# <%= form.text_field :title %>
|
486
489
|
# <% end %>
|
487
490
|
# # =>
|
488
|
-
# <form action="/posts" method="post"
|
491
|
+
# <form action="/posts" method="post">
|
492
|
+
# <input type="text" name="title">
|
493
|
+
# </form>
|
494
|
+
#
|
495
|
+
# # With an intentionally empty URL:
|
496
|
+
# <%= form_with url: false do |form| %>
|
497
|
+
# <%= form.text_field :title %>
|
498
|
+
# <% end %>
|
499
|
+
# # =>
|
500
|
+
# <form method="post" data-remote="true">
|
489
501
|
# <input type="text" name="title">
|
490
502
|
# </form>
|
491
503
|
#
|
@@ -494,7 +506,7 @@ module ActionView
|
|
494
506
|
# <%= form.text_field :title %>
|
495
507
|
# <% end %>
|
496
508
|
# # =>
|
497
|
-
# <form action="/posts" method="post"
|
509
|
+
# <form action="/posts" method="post">
|
498
510
|
# <input type="text" name="post[title]">
|
499
511
|
# </form>
|
500
512
|
#
|
@@ -503,7 +515,7 @@ module ActionView
|
|
503
515
|
# <%= form.text_field :title %>
|
504
516
|
# <% end %>
|
505
517
|
# # =>
|
506
|
-
# <form action="/posts" method="post"
|
518
|
+
# <form action="/posts" method="post">
|
507
519
|
# <input type="text" name="post[title]">
|
508
520
|
# </form>
|
509
521
|
#
|
@@ -512,7 +524,7 @@ module ActionView
|
|
512
524
|
# <%= form.text_field :title %>
|
513
525
|
# <% end %>
|
514
526
|
# # =>
|
515
|
-
# <form action="/posts/1" method="post"
|
527
|
+
# <form action="/posts/1" method="post">
|
516
528
|
# <input type="hidden" name="_method" value="patch">
|
517
529
|
# <input type="text" name="post[title]" value="<the title of the post>">
|
518
530
|
# </form>
|
@@ -523,7 +535,7 @@ module ActionView
|
|
523
535
|
# <%= form.text_field :but_in_forms_they_can %>
|
524
536
|
# <% end %>
|
525
537
|
# # =>
|
526
|
-
# <form action="/cats" method="post"
|
538
|
+
# <form action="/cats" method="post">
|
527
539
|
# <input type="text" name="cat[cats_dont_have_gills]">
|
528
540
|
# <input type="text" name="cat[but_in_forms_they_can]">
|
529
541
|
# </form>
|
@@ -604,10 +616,16 @@ module ActionView
|
|
604
616
|
# This is helpful when fragment-caching the form. Remote forms
|
605
617
|
# get the authenticity token from the <tt>meta</tt> tag, so embedding is
|
606
618
|
# unnecessary unless you support browsers without JavaScript.
|
607
|
-
# * <tt>:local</tt> -
|
608
|
-
#
|
609
|
-
#
|
610
|
-
#
|
619
|
+
# * <tt>:local</tt> - Whether to use standard HTTP form submission.
|
620
|
+
# When set to <tt>true</tt>, the form is submitted via standard HTTP.
|
621
|
+
# When set to <tt>false</tt>, the form is submitted as a "remote form", which
|
622
|
+
# is handled by Rails UJS as an XHR. When unspecified, the behavior is derived
|
623
|
+
# from <tt>config.action_view.form_with_generates_remote_forms</tt> where the
|
624
|
+
# config's value is actually the inverse of what <tt>local</tt>'s value would be.
|
625
|
+
# As of Rails 6.1, that configuration option defaults to <tt>false</tt>
|
626
|
+
# (which has the equivalent effect of passing <tt>local: true</tt>).
|
627
|
+
# In previous versions of Rails, that configuration option defaults to
|
628
|
+
# <tt>true</tt> (the equivalent of passing <tt>local: false</tt>).
|
611
629
|
# * <tt>:skip_enforcing_utf8</tt> - If set to true, a hidden input with name
|
612
630
|
# utf8 is not output.
|
613
631
|
# * <tt>:builder</tt> - Override the object used to build the form.
|
@@ -735,13 +753,14 @@ module ActionView
|
|
735
753
|
# form_with(**options.merge(builder: LabellingFormBuilder), &block)
|
736
754
|
# end
|
737
755
|
def form_with(model: nil, scope: nil, url: nil, format: nil, **options, &block)
|
738
|
-
options
|
739
|
-
options[:skip_default_ids] = !form_with_generates_ids
|
756
|
+
options = { allow_method_names_outside_object: true, skip_default_ids: !form_with_generates_ids }.merge!(options)
|
740
757
|
|
741
758
|
if model
|
742
|
-
url
|
759
|
+
if url != false
|
760
|
+
url ||= polymorphic_path(model, format: format)
|
761
|
+
end
|
743
762
|
|
744
|
-
model = model
|
763
|
+
model = _object_for_form_builder(model)
|
745
764
|
scope ||= model_name_from_record_or_class(model).param_key
|
746
765
|
end
|
747
766
|
|
@@ -996,12 +1015,14 @@ module ActionView
|
|
996
1015
|
# <% end %>
|
997
1016
|
#
|
998
1017
|
# Note that fields_for will automatically generate a hidden field
|
999
|
-
# to store the ID of the record
|
1000
|
-
# hidden field is not needed and you
|
1001
|
-
# to prevent fields_for from
|
1018
|
+
# to store the ID of the record if it responds to <tt>persisted?</tt>.
|
1019
|
+
# There are circumstances where this hidden field is not needed and you
|
1020
|
+
# can pass <tt>include_id: false</tt> to prevent fields_for from
|
1021
|
+
# rendering it automatically.
|
1002
1022
|
def fields_for(record_name, record_object = nil, options = {}, &block)
|
1003
|
-
|
1004
|
-
|
1023
|
+
options = { model: record_object, allow_method_names_outside_object: false, skip_default_ids: false }.merge!(options)
|
1024
|
+
|
1025
|
+
fields(record_name, **options, &block)
|
1005
1026
|
end
|
1006
1027
|
|
1007
1028
|
# Scopes input fields with either an explicit scope or model.
|
@@ -1050,10 +1071,10 @@ module ActionView
|
|
1050
1071
|
# to work with an object as a base, like
|
1051
1072
|
# FormOptionsHelper#collection_select and DateHelper#datetime_select.
|
1052
1073
|
def fields(scope = nil, model: nil, **options, &block)
|
1053
|
-
options
|
1054
|
-
options[:skip_default_ids] = !form_with_generates_ids
|
1074
|
+
options = { allow_method_names_outside_object: true, skip_default_ids: !form_with_generates_ids }.merge!(options)
|
1055
1075
|
|
1056
1076
|
if model
|
1077
|
+
model = _object_for_form_builder(model)
|
1057
1078
|
scope ||= model_name_from_record_or_class(model).param_key
|
1058
1079
|
end
|
1059
1080
|
|
@@ -1063,7 +1084,7 @@ module ActionView
|
|
1063
1084
|
|
1064
1085
|
# Returns a label tag tailored for labelling an input field for a specified attribute (identified by +method+) on an object
|
1065
1086
|
# assigned to the template (identified by +object+). The text of label will default to the attribute name unless a translation
|
1066
|
-
# is found in the current I18n locale (through helpers.label.<modelname>.<attribute>) or you specify it explicitly.
|
1087
|
+
# is found in the current I18n locale (through <tt>helpers.label.<modelname>.<attribute></tt>) or you specify it explicitly.
|
1067
1088
|
# Additional options on the label tag can be passed as a hash with +options+. These options will be tagged
|
1068
1089
|
# onto the HTML as an HTML element attribute as in the example shown, except for the <tt>:value</tt> option, which is designed to
|
1069
1090
|
# target labels for radio_button tags (where the value is used in the ID of the input tag).
|
@@ -1197,6 +1218,7 @@ module ActionView
|
|
1197
1218
|
# * Creates standard HTML attributes for the tag.
|
1198
1219
|
# * <tt>:disabled</tt> - If set to true, the user will not be able to use this input.
|
1199
1220
|
# * <tt>:multiple</tt> - If set to true, *in most updated browsers* the user will be allowed to select multiple files.
|
1221
|
+
# * <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.
|
1200
1222
|
# * <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.
|
1201
1223
|
#
|
1202
1224
|
# ==== Examples
|
@@ -1215,6 +1237,8 @@ module ActionView
|
|
1215
1237
|
# file_field(:attachment, :file, class: 'file_input')
|
1216
1238
|
# # => <input type="file" id="attachment_file" name="attachment[file]" class="file_input" />
|
1217
1239
|
def file_field(object_name, method, options = {})
|
1240
|
+
options = { include_hidden: multiple_file_field_include_hidden }.merge!(options)
|
1241
|
+
|
1218
1242
|
Tags::FileField.new(object_name, method, self, convert_direct_upload_option_to_url(options.dup)).render
|
1219
1243
|
end
|
1220
1244
|
|
@@ -1252,6 +1276,12 @@ module ActionView
|
|
1252
1276
|
# Additional options on the input tag can be passed as a hash with +options+. The +checked_value+ defaults to 1
|
1253
1277
|
# while the default +unchecked_value+ is set to 0 which is convenient for boolean values.
|
1254
1278
|
#
|
1279
|
+
# ==== Options
|
1280
|
+
#
|
1281
|
+
# * Any standard HTML attributes for the tag can be passed in, for example +:class+.
|
1282
|
+
# * <tt>:checked</tt> - +true+ or +false+ forces the state of the checkbox to be checked or not.
|
1283
|
+
# * <tt>:include_hidden</tt> - If set to false, the auxiliary hidden field described below will not be generated.
|
1284
|
+
#
|
1255
1285
|
# ==== Gotcha
|
1256
1286
|
#
|
1257
1287
|
# The HTML specification says unchecked check boxes are not successful, and
|
@@ -1265,7 +1295,7 @@ module ActionView
|
|
1265
1295
|
# wouldn't update the flag.
|
1266
1296
|
#
|
1267
1297
|
# To prevent this the helper generates an auxiliary hidden field before
|
1268
|
-
#
|
1298
|
+
# every check box. The hidden field has the same name and its
|
1269
1299
|
# attributes mimic an unchecked check box.
|
1270
1300
|
#
|
1271
1301
|
# This way, the client either sends only the hidden field (representing
|
@@ -1289,6 +1319,8 @@ module ActionView
|
|
1289
1319
|
# In that case it is preferable to either use +check_box_tag+ or to use
|
1290
1320
|
# hashes instead of arrays.
|
1291
1321
|
#
|
1322
|
+
# ==== Examples
|
1323
|
+
#
|
1292
1324
|
# # Let's say that @post.validated? is 1:
|
1293
1325
|
# check_box("post", "validated")
|
1294
1326
|
# # => <input name="post[validated]" type="hidden" value="0" />
|
@@ -1403,13 +1435,16 @@ module ActionView
|
|
1403
1435
|
# Returns a text_field of type "time".
|
1404
1436
|
#
|
1405
1437
|
# The default value is generated by trying to call +strftime+ with "%T.%L"
|
1406
|
-
# on the object's value.
|
1407
|
-
# by
|
1438
|
+
# on the object's value. If you pass <tt>include_seconds: false</tt>, it will be
|
1439
|
+
# formatted by trying to call +strftime+ with "%H:%M" on the object's value.
|
1440
|
+
# It is also possible to override this by passing the "value" option.
|
1441
|
+
#
|
1442
|
+
# ==== Options
|
1443
|
+
#
|
1444
|
+
# Supports the same options as FormTagHelper#time_field_tag.
|
1408
1445
|
#
|
1409
|
-
#
|
1410
|
-
# * Accepts same options as time_field_tag
|
1446
|
+
# ==== Examples
|
1411
1447
|
#
|
1412
|
-
# === Example
|
1413
1448
|
# time_field("task", "started_at")
|
1414
1449
|
# # => <input id="task_started_at" name="task[started_at]" type="time" />
|
1415
1450
|
#
|
@@ -1425,6 +1460,12 @@ module ActionView
|
|
1425
1460
|
# time_field("task", "started_at", min: "01:00:00")
|
1426
1461
|
# # => <input id="task_started_at" name="task[started_at]" type="time" min="01:00:00.000" />
|
1427
1462
|
#
|
1463
|
+
# By default, provided times will be formatted including seconds. You can render just the hour
|
1464
|
+
# and minute by passing <tt>include_seconds: false</tt>. Some browsers will render a simpler UI
|
1465
|
+
# if you exclude seconds in the timestamp format.
|
1466
|
+
#
|
1467
|
+
# time_field("task", "started_at", value: Time.now, include_seconds: false)
|
1468
|
+
# # => <input id="task_started_at" name="task[started_at]" type="time" value="01:00" />
|
1428
1469
|
def time_field(object_name, method, options = {})
|
1429
1470
|
Tags::TimeField.new(object_name, method, self, options).render
|
1430
1471
|
end
|
@@ -1515,7 +1556,8 @@ module ActionView
|
|
1515
1556
|
# Returns an input tag of type "number".
|
1516
1557
|
#
|
1517
1558
|
# ==== Options
|
1518
|
-
#
|
1559
|
+
#
|
1560
|
+
# Supports the same options as FormTagHelper#number_field_tag.
|
1519
1561
|
def number_field(object_name, method, options = {})
|
1520
1562
|
Tags::NumberField.new(object_name, method, self, options).render
|
1521
1563
|
end
|
@@ -1523,39 +1565,30 @@ module ActionView
|
|
1523
1565
|
# Returns an input tag of type "range".
|
1524
1566
|
#
|
1525
1567
|
# ==== Options
|
1526
|
-
#
|
1568
|
+
#
|
1569
|
+
# Supports the same options as FormTagHelper#range_field_tag.
|
1527
1570
|
def range_field(object_name, method, options = {})
|
1528
1571
|
Tags::RangeField.new(object_name, method, self, options).render
|
1529
1572
|
end
|
1530
1573
|
|
1574
|
+
def _object_for_form_builder(object) # :nodoc:
|
1575
|
+
object.is_a?(Array) ? object.last : object
|
1576
|
+
end
|
1577
|
+
|
1531
1578
|
private
|
1532
1579
|
def html_options_for_form_with(url_for_options = nil, model = nil, html: {}, local: !form_with_generates_remote_forms,
|
1533
1580
|
skip_enforcing_utf8: nil, **options)
|
1534
|
-
html_options = options.slice(:id, :class, :multipart, :method, :data).merge(html)
|
1581
|
+
html_options = options.slice(:id, :class, :multipart, :method, :data, :authenticity_token).merge!(html)
|
1582
|
+
html_options[:remote] = html.delete(:remote) || !local
|
1535
1583
|
html_options[:method] ||= :patch if model.respond_to?(:persisted?) && model.persisted?
|
1536
|
-
|
1537
|
-
|
1538
|
-
|
1539
|
-
|
1540
|
-
|
1541
|
-
|
1542
|
-
html_options[:action] = url_for(url_for_options || {})
|
1543
|
-
html_options[:"accept-charset"] = "UTF-8"
|
1544
|
-
html_options[:"data-remote"] = true unless local
|
1545
|
-
|
1546
|
-
html_options[:authenticity_token] = options.delete(:authenticity_token)
|
1547
|
-
|
1548
|
-
if !local && html_options[:authenticity_token].blank?
|
1549
|
-
html_options[:authenticity_token] = embed_authenticity_token_in_remote_forms
|
1550
|
-
end
|
1551
|
-
|
1552
|
-
if html_options[:authenticity_token] == true
|
1553
|
-
# Include the default authenticity_token, which is only generated when it's set to nil,
|
1554
|
-
# but we needed the true value to override the default of no authenticity_token on data-remote.
|
1555
|
-
html_options[:authenticity_token] = nil
|
1584
|
+
if skip_enforcing_utf8.nil?
|
1585
|
+
if options.key?(:enforce_utf8)
|
1586
|
+
html_options[:enforce_utf8] = options[:enforce_utf8]
|
1587
|
+
end
|
1588
|
+
else
|
1589
|
+
html_options[:enforce_utf8] = !skip_enforcing_utf8
|
1556
1590
|
end
|
1557
|
-
|
1558
|
-
html_options.stringify_keys!
|
1591
|
+
html_options_for_form(url_for_options.nil? ? {} : url_for_options, html_options)
|
1559
1592
|
end
|
1560
1593
|
|
1561
1594
|
def instantiate_builder(record_name, record_object, options)
|
@@ -1685,6 +1718,69 @@ module ActionView
|
|
1685
1718
|
@index = options[:index] || options[:child_index]
|
1686
1719
|
end
|
1687
1720
|
|
1721
|
+
# Generate an HTML <tt>id</tt> attribute value.
|
1722
|
+
#
|
1723
|
+
# return the <tt><form></tt> element's <tt>id</tt> attribute.
|
1724
|
+
#
|
1725
|
+
# <%= form_for @post do |f| %>
|
1726
|
+
# <%# ... %>
|
1727
|
+
#
|
1728
|
+
# <% content_for :sticky_footer do %>
|
1729
|
+
# <%= form.button(form: f.id) %>
|
1730
|
+
# <% end %>
|
1731
|
+
# <% end %>
|
1732
|
+
#
|
1733
|
+
# In the example above, the <tt>:sticky_footer</tt> content area will
|
1734
|
+
# exist outside of the <tt><form></tt> element. By declaring the
|
1735
|
+
# <tt>form</tt> HTML attribute, we hint to the browser that the generated
|
1736
|
+
# <tt><button></tt> element should be treated as the <tt><form></tt>
|
1737
|
+
# element's submit button, regardless of where it exists in the DOM.
|
1738
|
+
def id
|
1739
|
+
options.dig(:html, :id) || options[:id]
|
1740
|
+
end
|
1741
|
+
|
1742
|
+
# Generate an HTML <tt>id</tt> attribute value for the given field
|
1743
|
+
#
|
1744
|
+
# Return the value generated by the <tt>FormBuilder</tt> for the given
|
1745
|
+
# attribute name.
|
1746
|
+
#
|
1747
|
+
# <%= form_for @post do |f| %>
|
1748
|
+
# <%= f.label :title %>
|
1749
|
+
# <%= f.text_field :title, aria: { describedby: f.field_id(:title, :error) } %>
|
1750
|
+
# <%= tag.span("is blank", id: f.field_id(:title, :error) %>
|
1751
|
+
# <% end %>
|
1752
|
+
#
|
1753
|
+
# In the example above, the <tt><input type="text"></tt> element built by
|
1754
|
+
# the call to <tt>FormBuilder#text_field</tt> declares an
|
1755
|
+
# <tt>aria-describedby</tt> attribute referencing the <tt><span></tt>
|
1756
|
+
# element, sharing a common <tt>id</tt> root (<tt>post_title</tt>, in this
|
1757
|
+
# case).
|
1758
|
+
def field_id(method, *suffixes, namespace: @options[:namespace], index: @options[:index])
|
1759
|
+
@template.field_id(@object_name, method, *suffixes, namespace: namespace, index: index)
|
1760
|
+
end
|
1761
|
+
|
1762
|
+
# Generate an HTML <tt>name</tt> attribute value for the given name and
|
1763
|
+
# field combination
|
1764
|
+
#
|
1765
|
+
# Return the value generated by the <tt>FormBuilder</tt> for the given
|
1766
|
+
# attribute name.
|
1767
|
+
#
|
1768
|
+
# <%= form_for @post do |f| %>
|
1769
|
+
# <%= f.text_field :title, name: f.field_name(:title, :subtitle) %>
|
1770
|
+
# <%# => <input type="text" name="post[title][subtitle]">
|
1771
|
+
# <% end %>
|
1772
|
+
#
|
1773
|
+
# <%= form_for @post do |f| %>
|
1774
|
+
# <%= f.field_tag :tag, name: f.field_name(:tag, multiple: true) %>
|
1775
|
+
# <%# => <input type="text" name="post[tag][]">
|
1776
|
+
# <% end %>
|
1777
|
+
#
|
1778
|
+
def field_name(method, *methods, multiple: false, index: @options[:index])
|
1779
|
+
object_name = @options.fetch(:as) { @object_name }
|
1780
|
+
|
1781
|
+
@template.field_name(object_name, method, *methods, index: index, multiple: multiple)
|
1782
|
+
end
|
1783
|
+
|
1688
1784
|
##
|
1689
1785
|
# :method: text_field
|
1690
1786
|
#
|
@@ -2171,7 +2267,7 @@ module ActionView
|
|
2171
2267
|
return fields_for_with_nested_attributes(record_name, record_object, fields_options, block)
|
2172
2268
|
end
|
2173
2269
|
else
|
2174
|
-
record_object =
|
2270
|
+
record_object = @template._object_for_form_builder(record_name)
|
2175
2271
|
record_name = model_name_from_record_or_class(record_object).param_key
|
2176
2272
|
end
|
2177
2273
|
|
@@ -2195,7 +2291,7 @@ module ActionView
|
|
2195
2291
|
@template.fields_for(record_name, record_object, fields_options, &block)
|
2196
2292
|
end
|
2197
2293
|
|
2198
|
-
# See the docs for the
|
2294
|
+
# See the docs for the ActionView::Helpers::FormHelper#fields helper method.
|
2199
2295
|
def fields(scope = nil, model: nil, **options, &block)
|
2200
2296
|
options[:allow_method_names_outside_object] = true
|
2201
2297
|
options[:skip_default_ids] = !FormHelper.form_with_generates_ids
|
@@ -2207,7 +2303,7 @@ module ActionView
|
|
2207
2303
|
|
2208
2304
|
# Returns a label tag tailored for labelling an input field for a specified attribute (identified by +method+) on an object
|
2209
2305
|
# assigned to the template (identified by +object+). The text of label will default to the attribute name unless a translation
|
2210
|
-
# is found in the current I18n locale (through helpers.label.<modelname>.<attribute>) or you specify it explicitly.
|
2306
|
+
# is found in the current I18n locale (through <tt>helpers.label.<modelname>.<attribute></tt>) or you specify it explicitly.
|
2211
2307
|
# Additional options on the label tag can be passed as a hash with +options+. These options will be tagged
|
2212
2308
|
# onto the HTML as an HTML element attribute as in the example shown, except for the <tt>:value</tt> option, which is designed to
|
2213
2309
|
# target labels for radio_button tags (where the value is used in the ID of the input tag).
|
@@ -2281,6 +2377,12 @@ module ActionView
|
|
2281
2377
|
# Additional options on the input tag can be passed as a hash with +options+. The +checked_value+ defaults to 1
|
2282
2378
|
# while the default +unchecked_value+ is set to 0 which is convenient for boolean values.
|
2283
2379
|
#
|
2380
|
+
# ==== Options
|
2381
|
+
#
|
2382
|
+
# * Any standard HTML attributes for the tag can be passed in, for example +:class+.
|
2383
|
+
# * <tt>:checked</tt> - +true+ or +false+ forces the state of the checkbox to be checked or not.
|
2384
|
+
# * <tt>:include_hidden</tt> - If set to false, the auxiliary hidden field described below will not be generated.
|
2385
|
+
#
|
2284
2386
|
# ==== Gotcha
|
2285
2387
|
#
|
2286
2388
|
# The HTML specification says unchecked check boxes are not successful, and
|
@@ -2294,7 +2396,7 @@ module ActionView
|
|
2294
2396
|
# wouldn't update the flag.
|
2295
2397
|
#
|
2296
2398
|
# To prevent this the helper generates an auxiliary hidden field before
|
2297
|
-
#
|
2399
|
+
# every check box. The hidden field has the same name and its
|
2298
2400
|
# attributes mimic an unchecked check box.
|
2299
2401
|
#
|
2300
2402
|
# This way, the client either sends only the hidden field (representing
|
@@ -2318,6 +2420,8 @@ module ActionView
|
|
2318
2420
|
# In that case it is preferable to either use +check_box_tag+ or to use
|
2319
2421
|
# hashes instead of arrays.
|
2320
2422
|
#
|
2423
|
+
# ==== Examples
|
2424
|
+
#
|
2321
2425
|
# # Let's say that @post.validated? is 1:
|
2322
2426
|
# check_box("validated")
|
2323
2427
|
# # => <input name="post[validated]" type="hidden" value="0" />
|
@@ -2386,12 +2490,13 @@ module ActionView
|
|
2386
2490
|
# hash with +options+. These options will be tagged onto the HTML as an HTML element attribute as in the example
|
2387
2491
|
# shown.
|
2388
2492
|
#
|
2389
|
-
# Using this method inside a +
|
2493
|
+
# Using this method inside a +form_with+ block will set the enclosing form's encoding to <tt>multipart/form-data</tt>.
|
2390
2494
|
#
|
2391
2495
|
# ==== Options
|
2392
2496
|
# * Creates standard HTML attributes for the tag.
|
2393
2497
|
# * <tt>:disabled</tt> - If set to true, the user will not be able to use this input.
|
2394
2498
|
# * <tt>:multiple</tt> - If set to true, *in most updated browsers* the user will be allowed to select multiple files.
|
2499
|
+
# * <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.
|
2395
2500
|
# * <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.
|
2396
2501
|
#
|
2397
2502
|
# ==== Examples
|
@@ -2483,6 +2588,9 @@ module ActionView
|
|
2483
2588
|
# button("Create post")
|
2484
2589
|
# # => <button name='button' type='submit'>Create post</button>
|
2485
2590
|
#
|
2591
|
+
# button(:draft, value: true)
|
2592
|
+
# # => <button id="post_draft" name="post[draft]" value="true" type="submit">Create post</button>
|
2593
|
+
#
|
2486
2594
|
# button do
|
2487
2595
|
# content_tag(:strong, 'Ask me!')
|
2488
2596
|
# end
|
@@ -2497,14 +2605,31 @@ module ActionView
|
|
2497
2605
|
# # <strong>Create post</strong>
|
2498
2606
|
# # </button>
|
2499
2607
|
#
|
2608
|
+
# button(:draft, value: true) do
|
2609
|
+
# content_tag(:strong, "Save as draft")
|
2610
|
+
# end
|
2611
|
+
# # => <button id="post_draft" name="post[draft]" value="true" type="submit">
|
2612
|
+
# # <strong>Save as draft</strong>
|
2613
|
+
# # </button>
|
2614
|
+
#
|
2500
2615
|
def button(value = nil, options = {}, &block)
|
2501
|
-
|
2616
|
+
case value
|
2617
|
+
when Hash
|
2618
|
+
value, options = nil, value
|
2619
|
+
when Symbol
|
2620
|
+
value, options = nil, { name: field_name(value), id: field_id(value) }.merge!(options.to_h)
|
2621
|
+
end
|
2502
2622
|
value ||= submit_default_value
|
2503
2623
|
|
2504
2624
|
if block_given?
|
2505
2625
|
value = @template.capture { yield(value) }
|
2506
2626
|
end
|
2507
2627
|
|
2628
|
+
formmethod = options[:formmethod]
|
2629
|
+
if formmethod.present? && !/post|get/i.match?(formmethod) && !options.key?(:name) && !options.key?(:value)
|
2630
|
+
options.merge! formmethod: :post, name: "_method", value: formmethod
|
2631
|
+
end
|
2632
|
+
|
2508
2633
|
@template.button_tag(value, options)
|
2509
2634
|
end
|
2510
2635
|
|
@@ -2565,7 +2690,9 @@ module ActionView
|
|
2565
2690
|
else
|
2566
2691
|
options[:child_index] = nested_child_index(name)
|
2567
2692
|
end
|
2568
|
-
|
2693
|
+
if content = fields_for_nested_model("#{name}[#{options[:child_index]}]", child, options, block)
|
2694
|
+
output << content
|
2695
|
+
end
|
2569
2696
|
end
|
2570
2697
|
output
|
2571
2698
|
elsif association
|