actionview 7.0.8.7 → 7.2.2.1
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 +59 -454
- data/MIT-LICENSE +1 -1
- data/README.rdoc +1 -1
- data/app/assets/javascripts/rails-ujs.esm.js +686 -0
- data/app/assets/javascripts/rails-ujs.js +630 -0
- data/lib/action_view/base.rb +52 -14
- data/lib/action_view/buffers.rb +106 -8
- data/lib/action_view/cache_expiry.rb +44 -41
- data/lib/action_view/context.rb +1 -1
- data/lib/action_view/dependency_tracker/{ripper_tracker.rb → ruby_tracker.rb} +4 -3
- data/lib/action_view/dependency_tracker.rb +1 -1
- data/lib/action_view/deprecator.rb +7 -0
- data/lib/action_view/digestor.rb +1 -1
- data/lib/action_view/gem_version.rb +4 -4
- data/lib/action_view/helpers/active_model_helper.rb +1 -1
- data/lib/action_view/helpers/asset_tag_helper.rb +151 -55
- data/lib/action_view/helpers/asset_url_helper.rb +6 -5
- data/lib/action_view/helpers/atom_feed_helper.rb +5 -5
- data/lib/action_view/helpers/cache_helper.rb +7 -13
- data/lib/action_view/helpers/capture_helper.rb +30 -10
- data/lib/action_view/helpers/content_exfiltration_prevention_helper.rb +70 -0
- data/lib/action_view/helpers/controller_helper.rb +6 -0
- data/lib/action_view/helpers/csp_helper.rb +2 -2
- data/lib/action_view/helpers/csrf_helper.rb +3 -3
- data/lib/action_view/helpers/date_helper.rb +17 -19
- data/lib/action_view/helpers/debug_helper.rb +3 -3
- data/lib/action_view/helpers/form_helper.rb +248 -214
- data/lib/action_view/helpers/form_options_helper.rb +2 -1
- data/lib/action_view/helpers/form_tag_helper.rb +125 -58
- data/lib/action_view/helpers/javascript_helper.rb +1 -0
- data/lib/action_view/helpers/number_helper.rb +37 -330
- data/lib/action_view/helpers/output_safety_helper.rb +6 -6
- data/lib/action_view/helpers/rendering_helper.rb +1 -1
- data/lib/action_view/helpers/sanitize_helper.rb +51 -21
- data/lib/action_view/helpers/tag_helper.rb +210 -42
- data/lib/action_view/helpers/tags/base.rb +11 -52
- data/lib/action_view/helpers/tags/collection_check_boxes.rb +1 -0
- data/lib/action_view/helpers/tags/collection_radio_buttons.rb +1 -0
- data/lib/action_view/helpers/tags/collection_select.rb +3 -0
- data/lib/action_view/helpers/tags/date_field.rb +1 -1
- data/lib/action_view/helpers/tags/date_select.rb +2 -0
- data/lib/action_view/helpers/tags/datetime_field.rb +14 -6
- data/lib/action_view/helpers/tags/datetime_local_field.rb +11 -2
- data/lib/action_view/helpers/tags/grouped_collection_select.rb +3 -0
- data/lib/action_view/helpers/tags/month_field.rb +1 -1
- data/lib/action_view/helpers/tags/select.rb +3 -0
- data/lib/action_view/helpers/tags/select_renderer.rb +56 -0
- data/lib/action_view/helpers/tags/time_field.rb +1 -1
- data/lib/action_view/helpers/tags/time_zone_select.rb +3 -0
- data/lib/action_view/helpers/tags/week_field.rb +1 -1
- data/lib/action_view/helpers/tags/weekday_select.rb +3 -0
- data/lib/action_view/helpers/tags.rb +2 -0
- data/lib/action_view/helpers/text_helper.rb +157 -85
- data/lib/action_view/helpers/translation_helper.rb +3 -3
- data/lib/action_view/helpers/url_helper.rb +35 -80
- data/lib/action_view/helpers.rb +2 -0
- data/lib/action_view/layouts.rb +8 -8
- data/lib/action_view/log_subscriber.rb +57 -36
- data/lib/action_view/lookup_context.rb +29 -13
- data/lib/action_view/path_registry.rb +57 -0
- data/lib/action_view/path_set.rb +13 -14
- data/lib/action_view/railtie.rb +25 -3
- data/lib/action_view/record_identifier.rb +15 -8
- data/lib/action_view/render_parser/prism_render_parser.rb +127 -0
- data/lib/action_view/{ripper_ast_parser.rb → render_parser/ripper_render_parser.rb} +156 -13
- data/lib/action_view/render_parser.rb +21 -169
- data/lib/action_view/renderer/abstract_renderer.rb +2 -2
- data/lib/action_view/renderer/collection_renderer.rb +10 -2
- data/lib/action_view/renderer/partial_renderer/collection_caching.rb +2 -1
- data/lib/action_view/renderer/partial_renderer.rb +2 -1
- data/lib/action_view/renderer/renderer.rb +34 -38
- data/lib/action_view/renderer/streaming_template_renderer.rb +3 -2
- data/lib/action_view/renderer/template_renderer.rb +3 -2
- data/lib/action_view/rendering.rb +26 -8
- data/lib/action_view/template/error.rb +14 -1
- data/lib/action_view/template/handlers/builder.rb +4 -4
- data/lib/action_view/template/handlers/erb/erubi.rb +23 -27
- data/lib/action_view/template/handlers/erb.rb +73 -1
- data/lib/action_view/template/handlers.rb +1 -1
- data/lib/action_view/template/html.rb +1 -1
- data/lib/action_view/template/raw_file.rb +1 -1
- data/lib/action_view/template/renderable.rb +8 -2
- data/lib/action_view/template/resolver.rb +9 -3
- data/lib/action_view/template/text.rb +1 -1
- data/lib/action_view/template/types.rb +25 -34
- data/lib/action_view/template.rb +278 -55
- data/lib/action_view/template_path.rb +2 -0
- data/lib/action_view/test_case.rb +181 -28
- data/lib/action_view/unbound_template.rb +17 -7
- data/lib/action_view/version.rb +1 -1
- data/lib/action_view/view_paths.rb +15 -24
- data/lib/action_view.rb +4 -1
- metadata +26 -26
- data/lib/assets/compiled/rails-ujs.js +0 -777
@@ -1,14 +1,17 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "active_support/code_generator"
|
3
4
|
require "active_support/core_ext/enumerable"
|
4
5
|
require "active_support/core_ext/string/output_safety"
|
6
|
+
require "active_support/core_ext/string/inflections"
|
5
7
|
require "set"
|
6
8
|
require "action_view/helpers/capture_helper"
|
7
9
|
require "action_view/helpers/output_safety_helper"
|
8
10
|
|
9
11
|
module ActionView
|
10
|
-
# = Action View Tag Helpers
|
11
12
|
module Helpers # :nodoc:
|
13
|
+
# = Action View Tag \Helpers
|
14
|
+
#
|
12
15
|
# Provides methods to generate HTML tags programmatically both as a modern
|
13
16
|
# HTML5 compliant builder style and legacy XHTML compliant tags.
|
14
17
|
module TagHelper
|
@@ -45,8 +48,183 @@ module ActionView
|
|
45
48
|
include CaptureHelper
|
46
49
|
include OutputSafetyHelper
|
47
50
|
|
48
|
-
|
49
|
-
|
51
|
+
def self.define_element(name, code_generator:, method_name: name.to_s.underscore)
|
52
|
+
code_generator.define_cached_method(method_name, namespace: :tag_builder) do |batch|
|
53
|
+
batch.push(<<~RUBY) unless instance_methods.include?(method_name.to_sym)
|
54
|
+
def #{method_name}(content = nil, escape: true, **options, &block)
|
55
|
+
tag_string("#{name}", content, options, escape: escape, &block)
|
56
|
+
end
|
57
|
+
RUBY
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.define_void_element(name, code_generator:, method_name: name.to_s.underscore)
|
62
|
+
code_generator.define_cached_method(method_name, namespace: :tag_builder) do |batch|
|
63
|
+
batch.push(<<~RUBY)
|
64
|
+
def #{method_name}(content = nil, escape: true, **options, &block)
|
65
|
+
if content || block
|
66
|
+
ActionView.deprecator.warn <<~TEXT
|
67
|
+
Putting content inside a void element (#{name}) is invalid
|
68
|
+
according to the HTML5 spec, and so it is being deprecated
|
69
|
+
without replacement. In Rails 8.0, passing content as a
|
70
|
+
positional argument will raise, and using a block will have
|
71
|
+
no effect.
|
72
|
+
TEXT
|
73
|
+
tag_string("#{name}", content, options, escape: escape, &block)
|
74
|
+
else
|
75
|
+
self_closing_tag_string("#{name}", options, escape, ">")
|
76
|
+
end
|
77
|
+
end
|
78
|
+
RUBY
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def self.define_self_closing_element(name, code_generator:, method_name: name.to_s.underscore)
|
83
|
+
code_generator.define_cached_method(method_name, namespace: :tag_builder) do |batch|
|
84
|
+
batch.push(<<~RUBY)
|
85
|
+
def #{method_name}(content = nil, escape: true, **options, &block)
|
86
|
+
if content || block
|
87
|
+
tag_string("#{name}", content, options, escape: escape, &block)
|
88
|
+
else
|
89
|
+
self_closing_tag_string("#{name}", options, escape)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
RUBY
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
ActiveSupport::CodeGenerator.batch(self, __FILE__, __LINE__) do |code_generator|
|
97
|
+
define_void_element :area, code_generator: code_generator
|
98
|
+
define_void_element :base, code_generator: code_generator
|
99
|
+
define_void_element :br, code_generator: code_generator
|
100
|
+
define_void_element :col, code_generator: code_generator
|
101
|
+
define_void_element :embed, code_generator: code_generator
|
102
|
+
define_void_element :hr, code_generator: code_generator
|
103
|
+
define_void_element :img, code_generator: code_generator
|
104
|
+
define_void_element :input, code_generator: code_generator
|
105
|
+
define_void_element :keygen, code_generator: code_generator
|
106
|
+
define_void_element :link, code_generator: code_generator
|
107
|
+
define_void_element :meta, code_generator: code_generator
|
108
|
+
define_void_element :source, code_generator: code_generator
|
109
|
+
define_void_element :track, code_generator: code_generator
|
110
|
+
define_void_element :wbr, code_generator: code_generator
|
111
|
+
|
112
|
+
define_self_closing_element :animate, code_generator: code_generator
|
113
|
+
define_self_closing_element :animateMotion, code_generator: code_generator
|
114
|
+
define_self_closing_element :animateTransform, code_generator: code_generator
|
115
|
+
define_self_closing_element :circle, code_generator: code_generator
|
116
|
+
define_self_closing_element :ellipse, code_generator: code_generator
|
117
|
+
define_self_closing_element :line, code_generator: code_generator
|
118
|
+
define_self_closing_element :path, code_generator: code_generator
|
119
|
+
define_self_closing_element :polygon, code_generator: code_generator
|
120
|
+
define_self_closing_element :polyline, code_generator: code_generator
|
121
|
+
define_self_closing_element :rect, code_generator: code_generator
|
122
|
+
define_self_closing_element :set, code_generator: code_generator
|
123
|
+
define_self_closing_element :stop, code_generator: code_generator
|
124
|
+
define_self_closing_element :use, code_generator: code_generator
|
125
|
+
define_self_closing_element :view, code_generator: code_generator
|
126
|
+
|
127
|
+
define_element :a, code_generator: code_generator
|
128
|
+
define_element :abbr, code_generator: code_generator
|
129
|
+
define_element :address, code_generator: code_generator
|
130
|
+
define_element :article, code_generator: code_generator
|
131
|
+
define_element :aside, code_generator: code_generator
|
132
|
+
define_element :audio, code_generator: code_generator
|
133
|
+
define_element :b, code_generator: code_generator
|
134
|
+
define_element :bdi, code_generator: code_generator
|
135
|
+
define_element :bdo, code_generator: code_generator
|
136
|
+
define_element :blockquote, code_generator: code_generator
|
137
|
+
define_element :body, code_generator: code_generator
|
138
|
+
define_element :button, code_generator: code_generator
|
139
|
+
define_element :canvas, code_generator: code_generator
|
140
|
+
define_element :caption, code_generator: code_generator
|
141
|
+
define_element :cite, code_generator: code_generator
|
142
|
+
define_element :code, code_generator: code_generator
|
143
|
+
define_element :colgroup, code_generator: code_generator
|
144
|
+
define_element :data, code_generator: code_generator
|
145
|
+
define_element :datalist, code_generator: code_generator
|
146
|
+
define_element :dd, code_generator: code_generator
|
147
|
+
define_element :del, code_generator: code_generator
|
148
|
+
define_element :details, code_generator: code_generator
|
149
|
+
define_element :dfn, code_generator: code_generator
|
150
|
+
define_element :dialog, code_generator: code_generator
|
151
|
+
define_element :div, code_generator: code_generator
|
152
|
+
define_element :dl, code_generator: code_generator
|
153
|
+
define_element :dt, code_generator: code_generator
|
154
|
+
define_element :em, code_generator: code_generator
|
155
|
+
define_element :fieldset, code_generator: code_generator
|
156
|
+
define_element :figcaption, code_generator: code_generator
|
157
|
+
define_element :figure, code_generator: code_generator
|
158
|
+
define_element :footer, code_generator: code_generator
|
159
|
+
define_element :form, code_generator: code_generator
|
160
|
+
define_element :h1, code_generator: code_generator
|
161
|
+
define_element :h2, code_generator: code_generator
|
162
|
+
define_element :h3, code_generator: code_generator
|
163
|
+
define_element :h4, code_generator: code_generator
|
164
|
+
define_element :h5, code_generator: code_generator
|
165
|
+
define_element :h6, code_generator: code_generator
|
166
|
+
define_element :head, code_generator: code_generator
|
167
|
+
define_element :header, code_generator: code_generator
|
168
|
+
define_element :hgroup, code_generator: code_generator
|
169
|
+
define_element :html, code_generator: code_generator
|
170
|
+
define_element :i, code_generator: code_generator
|
171
|
+
define_element :iframe, code_generator: code_generator
|
172
|
+
define_element :ins, code_generator: code_generator
|
173
|
+
define_element :kbd, code_generator: code_generator
|
174
|
+
define_element :label, code_generator: code_generator
|
175
|
+
define_element :legend, code_generator: code_generator
|
176
|
+
define_element :li, code_generator: code_generator
|
177
|
+
define_element :main, code_generator: code_generator
|
178
|
+
define_element :map, code_generator: code_generator
|
179
|
+
define_element :mark, code_generator: code_generator
|
180
|
+
define_element :menu, code_generator: code_generator
|
181
|
+
define_element :meter, code_generator: code_generator
|
182
|
+
define_element :nav, code_generator: code_generator
|
183
|
+
define_element :noscript, code_generator: code_generator
|
184
|
+
define_element :object, code_generator: code_generator
|
185
|
+
define_element :ol, code_generator: code_generator
|
186
|
+
define_element :optgroup, code_generator: code_generator
|
187
|
+
define_element :option, code_generator: code_generator
|
188
|
+
define_element :output, code_generator: code_generator
|
189
|
+
define_element :p, code_generator: code_generator
|
190
|
+
define_element :picture, code_generator: code_generator
|
191
|
+
define_element :portal, code_generator: code_generator
|
192
|
+
define_element :pre, code_generator: code_generator
|
193
|
+
define_element :progress, code_generator: code_generator
|
194
|
+
define_element :q, code_generator: code_generator
|
195
|
+
define_element :rp, code_generator: code_generator
|
196
|
+
define_element :rt, code_generator: code_generator
|
197
|
+
define_element :ruby, code_generator: code_generator
|
198
|
+
define_element :s, code_generator: code_generator
|
199
|
+
define_element :samp, code_generator: code_generator
|
200
|
+
define_element :script, code_generator: code_generator
|
201
|
+
define_element :search, code_generator: code_generator
|
202
|
+
define_element :section, code_generator: code_generator
|
203
|
+
define_element :select, code_generator: code_generator
|
204
|
+
define_element :slot, code_generator: code_generator
|
205
|
+
define_element :small, code_generator: code_generator
|
206
|
+
define_element :span, code_generator: code_generator
|
207
|
+
define_element :strong, code_generator: code_generator
|
208
|
+
define_element :style, code_generator: code_generator
|
209
|
+
define_element :sub, code_generator: code_generator
|
210
|
+
define_element :summary, code_generator: code_generator
|
211
|
+
define_element :sup, code_generator: code_generator
|
212
|
+
define_element :table, code_generator: code_generator
|
213
|
+
define_element :tbody, code_generator: code_generator
|
214
|
+
define_element :td, code_generator: code_generator
|
215
|
+
define_element :template, code_generator: code_generator
|
216
|
+
define_element :textarea, code_generator: code_generator
|
217
|
+
define_element :tfoot, code_generator: code_generator
|
218
|
+
define_element :th, code_generator: code_generator
|
219
|
+
define_element :thead, code_generator: code_generator
|
220
|
+
define_element :time, code_generator: code_generator
|
221
|
+
define_element :title, code_generator: code_generator
|
222
|
+
define_element :tr, code_generator: code_generator
|
223
|
+
define_element :u, code_generator: code_generator
|
224
|
+
define_element :ul, code_generator: code_generator
|
225
|
+
define_element :var, code_generator: code_generator
|
226
|
+
define_element :video, code_generator: code_generator
|
227
|
+
end
|
50
228
|
|
51
229
|
def initialize(view_context)
|
52
230
|
@view_context = view_context
|
@@ -61,30 +239,22 @@ module ActionView
|
|
61
239
|
tag_options(attributes.to_h).to_s.strip.html_safe
|
62
240
|
end
|
63
241
|
|
64
|
-
def
|
65
|
-
|
66
|
-
end
|
242
|
+
def tag_string(name, content = nil, options, escape: true, &block)
|
243
|
+
content = @view_context.capture(self, &block) if block
|
67
244
|
|
68
|
-
|
69
|
-
|
245
|
+
content_tag_string(name, content, options, escape)
|
246
|
+
end
|
70
247
|
|
71
|
-
|
72
|
-
|
73
|
-
if (HTML_VOID_ELEMENTS.include?(name) || self_closing) && content.nil?
|
74
|
-
"<#{name.to_s.dasherize}#{tag_options(options, escape)}#{self_closing ? " />" : ">"}".html_safe
|
75
|
-
else
|
76
|
-
content_tag_string(name.to_s.dasherize, content || "", options, escape)
|
77
|
-
end
|
248
|
+
def self_closing_tag_string(name, options, escape = true, tag_suffix = " />")
|
249
|
+
"<#{name}#{tag_options(options, escape)}#{tag_suffix}".html_safe
|
78
250
|
end
|
79
251
|
|
80
252
|
def content_tag_string(name, content, options, escape = true)
|
81
253
|
tag_options = tag_options(options, escape) if options
|
82
254
|
|
83
|
-
if escape
|
84
|
-
name = ERB::Util.xml_name_escape(name)
|
255
|
+
if escape && content.present?
|
85
256
|
content = ERB::Util.unwrapped_html_escape(content)
|
86
257
|
end
|
87
|
-
|
88
258
|
"<#{name}#{tag_options}>#{PRE_CONTENT_STRINGS[name]}#{content}</#{name}>".html_safe
|
89
259
|
end
|
90
260
|
|
@@ -164,29 +334,12 @@ module ActionView
|
|
164
334
|
true
|
165
335
|
end
|
166
336
|
|
167
|
-
def
|
168
|
-
|
169
|
-
# able to warn when it is used, so we need to handle default values here.
|
170
|
-
escape_option_provided = options.has_key?(:escape)
|
171
|
-
escape_attributes_option_provided = options.has_key?(:escape_attributes)
|
172
|
-
|
173
|
-
if escape_attributes_option_provided
|
174
|
-
ActiveSupport::Deprecation.warn(<<~MSG)
|
175
|
-
Use of the option :escape_attributes is deprecated. It currently \
|
176
|
-
escapes both names and values of tags and attributes and it is \
|
177
|
-
equivalent to :escape. If any of them are enabled, the escaping \
|
178
|
-
is fully enabled.
|
179
|
-
MSG
|
180
|
-
end
|
337
|
+
def method_missing(called, *args, escape: true, **options, &block)
|
338
|
+
name = called.name.dasherize
|
181
339
|
|
182
|
-
|
183
|
-
escape_option = options.delete(:escape)
|
184
|
-
escape_attributes_option = options.delete(:escape_attributes)
|
185
|
-
escape_option || escape_attributes_option
|
186
|
-
end
|
340
|
+
TagHelper.ensure_valid_html5_tag_name(name)
|
187
341
|
|
188
|
-
|
189
|
-
tag_string(called, *args, **options, &block)
|
342
|
+
tag_string(name, *args, options, escape: escape, &block)
|
190
343
|
end
|
191
344
|
end
|
192
345
|
|
@@ -267,6 +420,14 @@ module ActionView
|
|
267
420
|
# # A void element:
|
268
421
|
# tag.br # => <br>
|
269
422
|
#
|
423
|
+
# Note that when using the block form options should be wrapped in
|
424
|
+
# parenthesis.
|
425
|
+
#
|
426
|
+
# <%= tag.a(href: "/about", class: "font-bold") do %>
|
427
|
+
# About the author
|
428
|
+
# <% end %>
|
429
|
+
# # => <a href="/about" class="font-bold">About the author</a>
|
430
|
+
#
|
270
431
|
# === Building HTML attributes
|
271
432
|
#
|
272
433
|
# Transforms a Hash into HTML attributes, ready to be interpolated into
|
@@ -283,7 +444,7 @@ module ActionView
|
|
283
444
|
#
|
284
445
|
# === Legacy syntax
|
285
446
|
#
|
286
|
-
# The following format is for legacy syntax support. It will be deprecated in future versions of Rails.
|
447
|
+
# The following format is for legacy syntax support. It will be deprecated in future versions of \Rails.
|
287
448
|
#
|
288
449
|
# tag(name, options = nil, open = false, escape = true)
|
289
450
|
#
|
@@ -332,7 +493,7 @@ module ActionView
|
|
332
493
|
if name.nil?
|
333
494
|
tag_builder
|
334
495
|
else
|
335
|
-
|
496
|
+
ensure_valid_html5_tag_name(name)
|
336
497
|
"<#{name}#{tag_builder.tag_options(options, escape) if options}#{open ? ">" : " />"}".html_safe
|
337
498
|
end
|
338
499
|
end
|
@@ -366,6 +527,8 @@ module ActionView
|
|
366
527
|
# <% end -%>
|
367
528
|
# # => <div class="strong">Hello world!</div>
|
368
529
|
def content_tag(name, content_or_options_with_block = nil, options = nil, escape = true, &block)
|
530
|
+
ensure_valid_html5_tag_name(name)
|
531
|
+
|
369
532
|
if block_given?
|
370
533
|
options = content_or_options_with_block if content_or_options_with_block.is_a?(Hash)
|
371
534
|
tag_builder.content_tag_string(name, capture(&block), options, escape)
|
@@ -386,7 +549,7 @@ module ActionView
|
|
386
549
|
# token_list(nil, false, 123, "", "foo", { bar: true })
|
387
550
|
# # => "123 foo bar"
|
388
551
|
def token_list(*args)
|
389
|
-
tokens = build_tag_values(*args).flat_map { |value| value.to_s.split(/\s+/) }.uniq
|
552
|
+
tokens = build_tag_values(*args).flat_map { |value| CGI.unescape_html(value.to_s).split(/\s+/) }.uniq
|
390
553
|
|
391
554
|
safe_join(tokens, " ")
|
392
555
|
end
|
@@ -422,6 +585,11 @@ module ActionView
|
|
422
585
|
end
|
423
586
|
|
424
587
|
private
|
588
|
+
def ensure_valid_html5_tag_name(name)
|
589
|
+
raise ArgumentError, "Invalid HTML5 tag name: #{name.inspect}" unless /\A[a-zA-Z][^\s\/>]*\z/.match?(name)
|
590
|
+
end
|
591
|
+
module_function :ensure_valid_html5_tag_name
|
592
|
+
|
425
593
|
def build_tag_values(*args)
|
426
594
|
tag_values = []
|
427
595
|
|
@@ -5,7 +5,6 @@ module ActionView
|
|
5
5
|
module Tags # :nodoc:
|
6
6
|
class Base # :nodoc:
|
7
7
|
include Helpers::ActiveModelInstanceTag, Helpers::TagHelper, Helpers::FormTagHelper
|
8
|
-
include FormOptionsHelper
|
9
8
|
|
10
9
|
attr_reader :object
|
11
10
|
|
@@ -35,22 +34,24 @@ module ActionView
|
|
35
34
|
|
36
35
|
private
|
37
36
|
def value
|
37
|
+
return unless object
|
38
|
+
|
38
39
|
if @allow_method_names_outside_object
|
39
|
-
object.public_send @method_name if object
|
40
|
+
object.public_send @method_name if object.respond_to?(@method_name)
|
40
41
|
else
|
41
|
-
object.public_send @method_name
|
42
|
+
object.public_send @method_name
|
42
43
|
end
|
43
44
|
end
|
44
45
|
|
45
46
|
def value_before_type_cast
|
46
|
-
unless object
|
47
|
-
method_before_type_cast = @method_name + "_before_type_cast"
|
47
|
+
return unless object
|
48
48
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
49
|
+
method_before_type_cast = @method_name + "_before_type_cast"
|
50
|
+
|
51
|
+
if value_came_from_user? && object.respond_to?(method_before_type_cast)
|
52
|
+
object.public_send(method_before_type_cast)
|
53
|
+
else
|
54
|
+
value
|
54
55
|
end
|
55
56
|
end
|
56
57
|
|
@@ -120,48 +121,6 @@ module ActionView
|
|
120
121
|
value.to_s.gsub(/[\s.]/, "_").gsub(/[^-[[:word:]]]/, "").downcase
|
121
122
|
end
|
122
123
|
|
123
|
-
def select_content_tag(option_tags, options, html_options)
|
124
|
-
html_options = html_options.stringify_keys
|
125
|
-
add_default_name_and_id(html_options)
|
126
|
-
|
127
|
-
if placeholder_required?(html_options)
|
128
|
-
raise ArgumentError, "include_blank cannot be false for a required field." if options[:include_blank] == false
|
129
|
-
options[:include_blank] ||= true unless options[:prompt]
|
130
|
-
end
|
131
|
-
|
132
|
-
value = options.fetch(:selected) { value() }
|
133
|
-
select = content_tag("select", add_options(option_tags, options, value), html_options)
|
134
|
-
|
135
|
-
if html_options["multiple"] && options.fetch(:include_hidden, true)
|
136
|
-
tag("input", disabled: html_options["disabled"], name: html_options["name"], type: "hidden", value: "", autocomplete: "off") + select
|
137
|
-
else
|
138
|
-
select
|
139
|
-
end
|
140
|
-
end
|
141
|
-
|
142
|
-
def placeholder_required?(html_options)
|
143
|
-
# See https://html.spec.whatwg.org/multipage/forms.html#attr-select-required
|
144
|
-
html_options["required"] && !html_options["multiple"] && html_options.fetch("size", 1).to_i == 1
|
145
|
-
end
|
146
|
-
|
147
|
-
def add_options(option_tags, options, value = nil)
|
148
|
-
if options[:include_blank]
|
149
|
-
content = (options[:include_blank] if options[:include_blank].is_a?(String))
|
150
|
-
label = (" " unless content)
|
151
|
-
option_tags = tag_builder.content_tag_string("option", content, value: "", label: label) + "\n" + option_tags
|
152
|
-
end
|
153
|
-
|
154
|
-
if value.blank? && options[:prompt]
|
155
|
-
tag_options = { value: "" }.tap do |prompt_opts|
|
156
|
-
prompt_opts[:disabled] = true if options[:disabled] == ""
|
157
|
-
prompt_opts[:selected] = true if options[:selected] == ""
|
158
|
-
end
|
159
|
-
option_tags = tag_builder.content_tag_string("option", prompt_text(options[:prompt]), tag_options) + "\n" + option_tags
|
160
|
-
end
|
161
|
-
|
162
|
-
option_tags
|
163
|
-
end
|
164
|
-
|
165
124
|
def name_and_id_index(options)
|
166
125
|
if options.key?("index")
|
167
126
|
options.delete("index") || ""
|
@@ -4,6 +4,9 @@ module ActionView
|
|
4
4
|
module Helpers
|
5
5
|
module Tags # :nodoc:
|
6
6
|
class CollectionSelect < Base # :nodoc:
|
7
|
+
include SelectRenderer
|
8
|
+
include FormOptionsHelper
|
9
|
+
|
7
10
|
def initialize(object_name, method_name, template_object, collection, value_method, text_method, options, html_options)
|
8
11
|
@collection = collection
|
9
12
|
@value_method = value_method
|
@@ -6,20 +6,28 @@ module ActionView
|
|
6
6
|
class DatetimeField < TextField # :nodoc:
|
7
7
|
def render
|
8
8
|
options = @options.stringify_keys
|
9
|
-
options["value"]
|
10
|
-
options["min"] =
|
11
|
-
options["max"] =
|
9
|
+
options["value"] = datetime_value(options["value"] || value)
|
10
|
+
options["min"] = format_datetime(parse_datetime(options["min"]))
|
11
|
+
options["max"] = format_datetime(parse_datetime(options["max"]))
|
12
12
|
@options = options
|
13
13
|
super
|
14
14
|
end
|
15
15
|
|
16
16
|
private
|
17
|
-
def
|
17
|
+
def datetime_value(value)
|
18
|
+
if value.is_a?(String)
|
19
|
+
value
|
20
|
+
else
|
21
|
+
format_datetime(value)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def format_datetime(value)
|
18
26
|
raise NotImplementedError
|
19
27
|
end
|
20
28
|
|
21
|
-
def
|
22
|
-
if value.is_a?
|
29
|
+
def parse_datetime(value)
|
30
|
+
if value.is_a?(String)
|
23
31
|
DateTime.parse(value) rescue nil
|
24
32
|
else
|
25
33
|
value
|
@@ -4,6 +4,11 @@ module ActionView
|
|
4
4
|
module Helpers
|
5
5
|
module Tags # :nodoc:
|
6
6
|
class DatetimeLocalField < DatetimeField # :nodoc:
|
7
|
+
def initialize(object_name, method_name, template_object, options = {})
|
8
|
+
@include_seconds = options.delete(:include_seconds) { true }
|
9
|
+
super
|
10
|
+
end
|
11
|
+
|
7
12
|
class << self
|
8
13
|
def field_type
|
9
14
|
@field_type ||= "datetime-local"
|
@@ -11,8 +16,12 @@ module ActionView
|
|
11
16
|
end
|
12
17
|
|
13
18
|
private
|
14
|
-
def
|
15
|
-
|
19
|
+
def format_datetime(value)
|
20
|
+
if @include_seconds
|
21
|
+
value&.strftime("%Y-%m-%dT%T")
|
22
|
+
else
|
23
|
+
value&.strftime("%Y-%m-%dT%H:%M")
|
24
|
+
end
|
16
25
|
end
|
17
26
|
end
|
18
27
|
end
|
@@ -4,6 +4,9 @@ module ActionView
|
|
4
4
|
module Helpers
|
5
5
|
module Tags # :nodoc:
|
6
6
|
class GroupedCollectionSelect < Base # :nodoc:
|
7
|
+
include SelectRenderer
|
8
|
+
include FormOptionsHelper
|
9
|
+
|
7
10
|
def initialize(object_name, method_name, template_object, collection, group_method, group_label_method, option_key_method, option_value_method, options, html_options)
|
8
11
|
@collection = collection
|
9
12
|
@group_method = group_method
|
@@ -4,6 +4,9 @@ module ActionView
|
|
4
4
|
module Helpers
|
5
5
|
module Tags # :nodoc:
|
6
6
|
class Select < Base # :nodoc:
|
7
|
+
include SelectRenderer
|
8
|
+
include FormOptionsHelper
|
9
|
+
|
7
10
|
def initialize(object_name, method_name, template_object, choices, options, html_options)
|
8
11
|
@choices = block_given? ? template_object.capture { yield || "" } : choices
|
9
12
|
@choices = @choices.to_a if @choices.is_a?(Range)
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActionView
|
4
|
+
module Helpers
|
5
|
+
module Tags # :nodoc:
|
6
|
+
module SelectRenderer # :nodoc:
|
7
|
+
private
|
8
|
+
def select_content_tag(option_tags, options, html_options)
|
9
|
+
html_options = html_options.stringify_keys
|
10
|
+
[:required, :multiple, :size].each do |prop|
|
11
|
+
html_options[prop.to_s] = options.delete(prop) if options.key?(prop) && !html_options.key?(prop.to_s)
|
12
|
+
end
|
13
|
+
|
14
|
+
add_default_name_and_id(html_options)
|
15
|
+
|
16
|
+
if placeholder_required?(html_options)
|
17
|
+
raise ArgumentError, "include_blank cannot be false for a required field." if options[:include_blank] == false
|
18
|
+
options[:include_blank] ||= true unless options[:prompt]
|
19
|
+
end
|
20
|
+
|
21
|
+
value = options.fetch(:selected) { value() }
|
22
|
+
select = content_tag("select", add_options(option_tags, options, value), html_options)
|
23
|
+
|
24
|
+
if html_options["multiple"] && options.fetch(:include_hidden, true)
|
25
|
+
tag("input", disabled: html_options["disabled"], name: html_options["name"], type: "hidden", value: "", autocomplete: "off") + select
|
26
|
+
else
|
27
|
+
select
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def placeholder_required?(html_options)
|
32
|
+
# See https://html.spec.whatwg.org/multipage/forms.html#attr-select-required
|
33
|
+
html_options["required"] && !html_options["multiple"] && html_options.fetch("size", 1).to_i == 1
|
34
|
+
end
|
35
|
+
|
36
|
+
def add_options(option_tags, options, value = nil)
|
37
|
+
if options[:include_blank]
|
38
|
+
content = (options[:include_blank] if options[:include_blank].is_a?(String))
|
39
|
+
label = (" " unless content)
|
40
|
+
option_tags = tag_builder.content_tag_string("option", content, value: "", label: label) + "\n" + option_tags
|
41
|
+
end
|
42
|
+
|
43
|
+
if value.blank? && options[:prompt]
|
44
|
+
tag_options = { value: "" }.tap do |prompt_opts|
|
45
|
+
prompt_opts[:disabled] = true if options[:disabled] == ""
|
46
|
+
prompt_opts[:selected] = true if options[:selected] == ""
|
47
|
+
end
|
48
|
+
option_tags = tag_builder.content_tag_string("option", prompt_text(options[:prompt]), tag_options) + "\n" + option_tags
|
49
|
+
end
|
50
|
+
|
51
|
+
option_tags
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -4,6 +4,9 @@ module ActionView
|
|
4
4
|
module Helpers
|
5
5
|
module Tags # :nodoc:
|
6
6
|
class TimeZoneSelect < Base # :nodoc:
|
7
|
+
include SelectRenderer
|
8
|
+
include FormOptionsHelper
|
9
|
+
|
7
10
|
def initialize(object_name, method_name, template_object, priority_zones, options, html_options)
|
8
11
|
@priority_zones = priority_zones
|
9
12
|
@html_options = html_options
|