actionview 7.1.5.2 → 8.1.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 +96 -436
- data/README.rdoc +1 -1
- data/lib/action_view/base.rb +29 -11
- data/lib/action_view/buffers.rb +1 -1
- data/lib/action_view/cache_expiry.rb +9 -3
- data/lib/action_view/dependency_tracker/erb_tracker.rb +37 -28
- data/lib/action_view/dependency_tracker/ruby_tracker.rb +43 -0
- data/lib/action_view/dependency_tracker/wildcard_resolver.rb +32 -0
- data/lib/action_view/dependency_tracker.rb +8 -2
- data/lib/action_view/digestor.rb +6 -2
- data/lib/action_view/gem_version.rb +3 -3
- data/lib/action_view/helpers/asset_tag_helper.rb +39 -8
- data/lib/action_view/helpers/atom_feed_helper.rb +1 -3
- data/lib/action_view/helpers/cache_helper.rb +10 -2
- data/lib/action_view/helpers/capture_helper.rb +2 -2
- data/lib/action_view/helpers/controller_helper.rb +6 -2
- data/lib/action_view/helpers/csrf_helper.rb +1 -1
- data/lib/action_view/helpers/date_helper.rb +28 -4
- data/lib/action_view/helpers/form_helper.rb +277 -272
- data/lib/action_view/helpers/form_options_helper.rb +39 -35
- data/lib/action_view/helpers/form_tag_helper.rb +115 -72
- data/lib/action_view/helpers/javascript_helper.rb +5 -1
- data/lib/action_view/helpers/number_helper.rb +14 -0
- data/lib/action_view/helpers/output_safety_helper.rb +5 -6
- data/lib/action_view/helpers/rendering_helper.rb +160 -50
- data/lib/action_view/helpers/sanitize_helper.rb +6 -0
- data/lib/action_view/helpers/tag_helper.rb +225 -48
- data/lib/action_view/helpers/tags/base.rb +11 -9
- data/lib/action_view/helpers/tags/check_box.rb +9 -3
- data/lib/action_view/helpers/tags/collection_check_boxes.rb +4 -3
- data/lib/action_view/helpers/tags/collection_helpers.rb +2 -1
- data/lib/action_view/helpers/tags/datetime_field.rb +1 -1
- data/lib/action_view/helpers/tags/file_field.rb +7 -2
- data/lib/action_view/helpers/tags/hidden_field.rb +1 -1
- data/lib/action_view/helpers/tags/label.rb +3 -10
- data/lib/action_view/helpers/tags/radio_button.rb +1 -1
- data/lib/action_view/helpers/tags/select.rb +6 -1
- data/lib/action_view/helpers/tags/select_renderer.rb +6 -4
- data/lib/action_view/helpers/tags/text_area.rb +1 -1
- data/lib/action_view/helpers/tags/text_field.rb +1 -1
- data/lib/action_view/helpers/text_helper.rb +11 -4
- data/lib/action_view/helpers/translation_helper.rb +6 -1
- data/lib/action_view/helpers/url_helper.rb +42 -90
- data/lib/action_view/layouts.rb +9 -11
- data/lib/action_view/locale/en.yml +3 -0
- data/lib/action_view/log_subscriber.rb +9 -8
- data/lib/action_view/railtie.rb +12 -2
- data/lib/action_view/record_identifier.rb +22 -1
- data/lib/action_view/render_parser/prism_render_parser.rb +139 -0
- data/lib/action_view/{ripper_ast_parser.rb → render_parser/ripper_render_parser.rb} +162 -10
- data/lib/action_view/render_parser.rb +21 -169
- data/lib/action_view/renderer/abstract_renderer.rb +1 -1
- data/lib/action_view/renderer/partial_renderer.rb +18 -2
- data/lib/action_view/renderer/renderer.rb +32 -38
- data/lib/action_view/renderer/streaming_template_renderer.rb +8 -2
- data/lib/action_view/renderer/template_renderer.rb +3 -3
- data/lib/action_view/rendering.rb +6 -7
- data/lib/action_view/structured_event_subscriber.rb +97 -0
- data/lib/action_view/template/error.rb +18 -3
- data/lib/action_view/template/handlers/erb/erubi.rb +1 -1
- data/lib/action_view/template/handlers/erb.rb +77 -44
- data/lib/action_view/template/raw_file.rb +4 -0
- data/lib/action_view/template/renderable.rb +7 -1
- data/lib/action_view/template/resolver.rb +0 -3
- data/lib/action_view/template.rb +39 -12
- data/lib/action_view/test_case.rb +57 -62
- data/lib/action_view.rb +4 -0
- metadata +17 -14
- data/lib/action_view/dependency_tracker/ripper_tracker.rb +0 -59
|
@@ -1,8 +1,9 @@
|
|
|
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"
|
|
5
|
-
require "
|
|
6
|
+
require "active_support/core_ext/string/inflections"
|
|
6
7
|
require "action_view/helpers/capture_helper"
|
|
7
8
|
require "action_view/helpers/output_safety_helper"
|
|
8
9
|
|
|
@@ -43,11 +44,171 @@ module ActionView
|
|
|
43
44
|
PRE_CONTENT_STRINGS["textarea"] = "\n"
|
|
44
45
|
|
|
45
46
|
class TagBuilder # :nodoc:
|
|
46
|
-
|
|
47
|
-
|
|
47
|
+
def self.define_element(name, code_generator:, method_name: name)
|
|
48
|
+
return if method_defined?(name)
|
|
49
|
+
|
|
50
|
+
code_generator.class_eval do |batch|
|
|
51
|
+
batch << "\n" <<
|
|
52
|
+
"def #{method_name}(content = nil, escape: true, **options, &block)" <<
|
|
53
|
+
" tag_string(#{name.inspect}, content, options, escape: escape, &block)" <<
|
|
54
|
+
"end"
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def self.define_void_element(name, code_generator:, method_name: name)
|
|
59
|
+
code_generator.class_eval do |batch|
|
|
60
|
+
batch << "\n" <<
|
|
61
|
+
"def #{method_name}(escape: true, **options, &block)" <<
|
|
62
|
+
" self_closing_tag_string(#{name.inspect}, options, escape, '>')" <<
|
|
63
|
+
"end"
|
|
64
|
+
end
|
|
65
|
+
end
|
|
48
66
|
|
|
49
|
-
|
|
50
|
-
|
|
67
|
+
def self.define_self_closing_element(name, code_generator:, method_name: name)
|
|
68
|
+
code_generator.class_eval do |batch|
|
|
69
|
+
batch << "\n" <<
|
|
70
|
+
"def #{method_name}(content = nil, escape: true, **options, &block)" <<
|
|
71
|
+
" if content || block" <<
|
|
72
|
+
" tag_string(#{name.inspect}, content, options, escape: escape, &block)" <<
|
|
73
|
+
" else" <<
|
|
74
|
+
" self_closing_tag_string(#{name.inspect}, options, escape)" <<
|
|
75
|
+
" end" <<
|
|
76
|
+
"end"
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
ActiveSupport::CodeGenerator.batch(self, __FILE__, __LINE__) do |code_generator|
|
|
81
|
+
define_void_element :area, code_generator: code_generator
|
|
82
|
+
define_void_element :base, code_generator: code_generator
|
|
83
|
+
define_void_element :br, code_generator: code_generator
|
|
84
|
+
define_void_element :col, code_generator: code_generator
|
|
85
|
+
define_void_element :embed, code_generator: code_generator
|
|
86
|
+
define_void_element :hr, code_generator: code_generator
|
|
87
|
+
define_void_element :img, code_generator: code_generator
|
|
88
|
+
define_void_element :input, code_generator: code_generator
|
|
89
|
+
define_void_element :keygen, code_generator: code_generator
|
|
90
|
+
define_void_element :link, code_generator: code_generator
|
|
91
|
+
define_void_element :meta, code_generator: code_generator
|
|
92
|
+
define_void_element :source, code_generator: code_generator
|
|
93
|
+
define_void_element :track, code_generator: code_generator
|
|
94
|
+
define_void_element :wbr, code_generator: code_generator
|
|
95
|
+
|
|
96
|
+
define_self_closing_element :animate, code_generator: code_generator
|
|
97
|
+
define_self_closing_element :animateMotion, code_generator: code_generator, method_name: :animate_motion
|
|
98
|
+
define_self_closing_element :animateTransform, code_generator: code_generator, method_name: :animate_transform
|
|
99
|
+
define_self_closing_element :circle, code_generator: code_generator
|
|
100
|
+
define_self_closing_element :ellipse, code_generator: code_generator
|
|
101
|
+
define_self_closing_element :line, code_generator: code_generator
|
|
102
|
+
define_self_closing_element :path, code_generator: code_generator
|
|
103
|
+
define_self_closing_element :polygon, code_generator: code_generator
|
|
104
|
+
define_self_closing_element :polyline, code_generator: code_generator
|
|
105
|
+
define_self_closing_element :rect, code_generator: code_generator
|
|
106
|
+
define_self_closing_element :set, code_generator: code_generator
|
|
107
|
+
define_self_closing_element :stop, code_generator: code_generator
|
|
108
|
+
define_self_closing_element :use, code_generator: code_generator
|
|
109
|
+
define_self_closing_element :view, code_generator: code_generator
|
|
110
|
+
|
|
111
|
+
define_element :a, code_generator: code_generator
|
|
112
|
+
define_element :abbr, code_generator: code_generator
|
|
113
|
+
define_element :address, code_generator: code_generator
|
|
114
|
+
define_element :article, code_generator: code_generator
|
|
115
|
+
define_element :aside, code_generator: code_generator
|
|
116
|
+
define_element :audio, code_generator: code_generator
|
|
117
|
+
define_element :b, code_generator: code_generator
|
|
118
|
+
define_element :bdi, code_generator: code_generator
|
|
119
|
+
define_element :bdo, code_generator: code_generator
|
|
120
|
+
define_element :blockquote, code_generator: code_generator
|
|
121
|
+
define_element :body, code_generator: code_generator
|
|
122
|
+
define_element :button, code_generator: code_generator
|
|
123
|
+
define_element :canvas, code_generator: code_generator
|
|
124
|
+
define_element :caption, code_generator: code_generator
|
|
125
|
+
define_element :cite, code_generator: code_generator
|
|
126
|
+
define_element :code, code_generator: code_generator
|
|
127
|
+
define_element :colgroup, code_generator: code_generator
|
|
128
|
+
define_element :data, code_generator: code_generator
|
|
129
|
+
define_element :datalist, code_generator: code_generator
|
|
130
|
+
define_element :dd, code_generator: code_generator
|
|
131
|
+
define_element :del, code_generator: code_generator
|
|
132
|
+
define_element :details, code_generator: code_generator
|
|
133
|
+
define_element :dfn, code_generator: code_generator
|
|
134
|
+
define_element :dialog, code_generator: code_generator
|
|
135
|
+
define_element :div, code_generator: code_generator
|
|
136
|
+
define_element :dl, code_generator: code_generator
|
|
137
|
+
define_element :dt, code_generator: code_generator
|
|
138
|
+
define_element :em, code_generator: code_generator
|
|
139
|
+
define_element :fieldset, code_generator: code_generator
|
|
140
|
+
define_element :figcaption, code_generator: code_generator
|
|
141
|
+
define_element :figure, code_generator: code_generator
|
|
142
|
+
define_element :footer, code_generator: code_generator
|
|
143
|
+
define_element :form, code_generator: code_generator
|
|
144
|
+
define_element :h1, code_generator: code_generator
|
|
145
|
+
define_element :h2, code_generator: code_generator
|
|
146
|
+
define_element :h3, code_generator: code_generator
|
|
147
|
+
define_element :h4, code_generator: code_generator
|
|
148
|
+
define_element :h5, code_generator: code_generator
|
|
149
|
+
define_element :h6, code_generator: code_generator
|
|
150
|
+
define_element :head, code_generator: code_generator
|
|
151
|
+
define_element :header, code_generator: code_generator
|
|
152
|
+
define_element :hgroup, code_generator: code_generator
|
|
153
|
+
define_element :html, code_generator: code_generator
|
|
154
|
+
define_element :i, code_generator: code_generator
|
|
155
|
+
define_element :iframe, code_generator: code_generator
|
|
156
|
+
define_element :ins, code_generator: code_generator
|
|
157
|
+
define_element :kbd, code_generator: code_generator
|
|
158
|
+
define_element :label, code_generator: code_generator
|
|
159
|
+
define_element :legend, code_generator: code_generator
|
|
160
|
+
define_element :li, code_generator: code_generator
|
|
161
|
+
define_element :main, code_generator: code_generator
|
|
162
|
+
define_element :map, code_generator: code_generator
|
|
163
|
+
define_element :mark, code_generator: code_generator
|
|
164
|
+
define_element :menu, code_generator: code_generator
|
|
165
|
+
define_element :meter, code_generator: code_generator
|
|
166
|
+
define_element :nav, code_generator: code_generator
|
|
167
|
+
define_element :noscript, code_generator: code_generator
|
|
168
|
+
define_element :object, code_generator: code_generator
|
|
169
|
+
define_element :ol, code_generator: code_generator
|
|
170
|
+
define_element :optgroup, code_generator: code_generator
|
|
171
|
+
define_element :option, code_generator: code_generator
|
|
172
|
+
define_element :output, code_generator: code_generator
|
|
173
|
+
define_element :p, code_generator: code_generator
|
|
174
|
+
define_element :picture, code_generator: code_generator
|
|
175
|
+
define_element :portal, code_generator: code_generator
|
|
176
|
+
define_element :pre, code_generator: code_generator
|
|
177
|
+
define_element :progress, code_generator: code_generator
|
|
178
|
+
define_element :q, code_generator: code_generator
|
|
179
|
+
define_element :rp, code_generator: code_generator
|
|
180
|
+
define_element :rt, code_generator: code_generator
|
|
181
|
+
define_element :ruby, code_generator: code_generator
|
|
182
|
+
define_element :s, code_generator: code_generator
|
|
183
|
+
define_element :samp, code_generator: code_generator
|
|
184
|
+
define_element :script, code_generator: code_generator
|
|
185
|
+
define_element :search, code_generator: code_generator
|
|
186
|
+
define_element :section, code_generator: code_generator
|
|
187
|
+
define_element :select, code_generator: code_generator
|
|
188
|
+
define_element :slot, code_generator: code_generator
|
|
189
|
+
define_element :small, code_generator: code_generator
|
|
190
|
+
define_element :span, code_generator: code_generator
|
|
191
|
+
define_element :strong, code_generator: code_generator
|
|
192
|
+
define_element :style, code_generator: code_generator
|
|
193
|
+
define_element :sub, code_generator: code_generator
|
|
194
|
+
define_element :summary, code_generator: code_generator
|
|
195
|
+
define_element :sup, code_generator: code_generator
|
|
196
|
+
define_element :table, code_generator: code_generator
|
|
197
|
+
define_element :tbody, code_generator: code_generator
|
|
198
|
+
define_element :td, code_generator: code_generator
|
|
199
|
+
define_element :template, code_generator: code_generator
|
|
200
|
+
define_element :textarea, code_generator: code_generator
|
|
201
|
+
define_element :tfoot, code_generator: code_generator
|
|
202
|
+
define_element :th, code_generator: code_generator
|
|
203
|
+
define_element :thead, code_generator: code_generator
|
|
204
|
+
define_element :time, code_generator: code_generator
|
|
205
|
+
define_element :title, code_generator: code_generator
|
|
206
|
+
define_element :tr, code_generator: code_generator
|
|
207
|
+
define_element :u, code_generator: code_generator
|
|
208
|
+
define_element :ul, code_generator: code_generator
|
|
209
|
+
define_element :var, code_generator: code_generator
|
|
210
|
+
define_element :video, code_generator: code_generator
|
|
211
|
+
end
|
|
51
212
|
|
|
52
213
|
def initialize(view_context)
|
|
53
214
|
@view_context = view_context
|
|
@@ -62,53 +223,40 @@ module ActionView
|
|
|
62
223
|
tag_options(attributes.to_h).to_s.strip.html_safe
|
|
63
224
|
end
|
|
64
225
|
|
|
65
|
-
def
|
|
66
|
-
tag_string(:p, *arguments, **options, &block)
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
def tag_string(name, content = nil, escape: true, **options, &block)
|
|
70
|
-
content = @view_context.capture(self, &block) if block_given?
|
|
71
|
-
self_closing = SVG_SELF_CLOSING_ELEMENTS.include?(name)
|
|
72
|
-
if (HTML_VOID_ELEMENTS.include?(name) || self_closing) && content.nil?
|
|
73
|
-
"<#{name.to_s.dasherize}#{tag_options(options, escape)}#{self_closing ? " />" : ">"}".html_safe
|
|
74
|
-
else
|
|
75
|
-
content_tag_string(name.to_s.dasherize, content || "", options, escape)
|
|
76
|
-
end
|
|
77
|
-
end
|
|
78
|
-
|
|
79
|
-
def content_tag_string(name, content, options, escape = true)
|
|
226
|
+
def content_tag_string(name, content, options, escape = true) # :nodoc:
|
|
80
227
|
tag_options = tag_options(options, escape) if options
|
|
81
228
|
|
|
82
|
-
if escape
|
|
83
|
-
name = ERB::Util.xml_name_escape(name)
|
|
229
|
+
if escape && content.present?
|
|
84
230
|
content = ERB::Util.unwrapped_html_escape(content)
|
|
85
231
|
end
|
|
86
|
-
|
|
87
232
|
"<#{name}#{tag_options}>#{PRE_CONTENT_STRINGS[name]}#{content}</#{name}>".html_safe
|
|
88
233
|
end
|
|
89
234
|
|
|
90
|
-
def tag_options(options, escape = true)
|
|
235
|
+
def tag_options(options, escape = true) # :nodoc:
|
|
91
236
|
return if options.blank?
|
|
92
237
|
output = +""
|
|
93
238
|
sep = " "
|
|
94
239
|
options.each_pair do |key, value|
|
|
240
|
+
next if key.blank?
|
|
241
|
+
|
|
95
242
|
type = TAG_TYPES[key]
|
|
96
243
|
if type == :data && value.is_a?(Hash)
|
|
97
244
|
value.each_pair do |k, v|
|
|
98
|
-
next if v.nil?
|
|
245
|
+
next if k.blank? || v.nil?
|
|
246
|
+
|
|
99
247
|
output << sep
|
|
100
248
|
output << prefix_tag_option(key, k, v, escape)
|
|
101
249
|
end
|
|
102
250
|
elsif type == :aria && value.is_a?(Hash)
|
|
103
251
|
value.each_pair do |k, v|
|
|
104
|
-
next if v.nil?
|
|
252
|
+
next if k.blank? || v.nil?
|
|
105
253
|
|
|
106
254
|
case v
|
|
107
255
|
when Array, Hash
|
|
108
256
|
tokens = TagHelper.build_tag_values(v)
|
|
109
257
|
next if tokens.none?
|
|
110
258
|
|
|
111
|
-
v = safe_join(tokens, " ")
|
|
259
|
+
v = @view_context.safe_join(tokens, " ")
|
|
112
260
|
else
|
|
113
261
|
v = v.to_s
|
|
114
262
|
end
|
|
@@ -129,28 +277,38 @@ module ActionView
|
|
|
129
277
|
output unless output.empty?
|
|
130
278
|
end
|
|
131
279
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
280
|
+
private
|
|
281
|
+
def tag_string(name, content = nil, options, escape: true, &block)
|
|
282
|
+
content = @view_context.capture(self, &block) if block
|
|
135
283
|
|
|
136
|
-
|
|
137
|
-
key = ERB::Util.xml_name_escape(key) if escape
|
|
138
|
-
|
|
139
|
-
case value
|
|
140
|
-
when Array, Hash
|
|
141
|
-
value = TagHelper.build_tag_values(value) if key.to_s == "class"
|
|
142
|
-
value = escape ? safe_join(value, " ") : value.join(" ")
|
|
143
|
-
when Regexp
|
|
144
|
-
value = escape ? ERB::Util.unwrapped_html_escape(value.source) : value.source
|
|
145
|
-
else
|
|
146
|
-
value = escape ? ERB::Util.unwrapped_html_escape(value) : value.to_s
|
|
284
|
+
content_tag_string(name, content, options, escape)
|
|
147
285
|
end
|
|
148
|
-
value = value.gsub('"', """) if value.include?('"')
|
|
149
286
|
|
|
150
|
-
|
|
151
|
-
|
|
287
|
+
def self_closing_tag_string(name, options, escape = true, tag_suffix = " />")
|
|
288
|
+
"<#{name}#{tag_options(options, escape)}#{tag_suffix}".html_safe
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
def boolean_tag_option(key)
|
|
292
|
+
%(#{key}="#{key}")
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
def tag_option(key, value, escape)
|
|
296
|
+
key = ERB::Util.xml_name_escape(key) if escape
|
|
297
|
+
|
|
298
|
+
case value
|
|
299
|
+
when Array, Hash
|
|
300
|
+
value = TagHelper.build_tag_values(value) if key.to_s == "class"
|
|
301
|
+
value = escape ? @view_context.safe_join(value, " ") : value.join(" ")
|
|
302
|
+
when Regexp
|
|
303
|
+
value = escape ? ERB::Util.unwrapped_html_escape(value.source) : value.source
|
|
304
|
+
else
|
|
305
|
+
value = escape ? ERB::Util.unwrapped_html_escape(value) : value.to_s
|
|
306
|
+
end
|
|
307
|
+
value = value.gsub('"', """) if value.include?('"')
|
|
308
|
+
|
|
309
|
+
%(#{key}="#{value}")
|
|
310
|
+
end
|
|
152
311
|
|
|
153
|
-
private
|
|
154
312
|
def prefix_tag_option(prefix, key, value, escape)
|
|
155
313
|
key = "#{prefix}-#{key.to_s.dasherize}"
|
|
156
314
|
unless value.is_a?(String) || value.is_a?(Symbol) || value.is_a?(BigDecimal)
|
|
@@ -163,8 +321,12 @@ module ActionView
|
|
|
163
321
|
true
|
|
164
322
|
end
|
|
165
323
|
|
|
166
|
-
def method_missing(called, *args, **options, &block)
|
|
167
|
-
|
|
324
|
+
def method_missing(called, *args, escape: true, **options, &block)
|
|
325
|
+
name = called.name.dasherize
|
|
326
|
+
|
|
327
|
+
TagHelper.ensure_valid_html5_tag_name(name)
|
|
328
|
+
|
|
329
|
+
tag_string(name, *args, options, escape: escape, &block)
|
|
168
330
|
end
|
|
169
331
|
end
|
|
170
332
|
|
|
@@ -245,6 +407,14 @@ module ActionView
|
|
|
245
407
|
# # A void element:
|
|
246
408
|
# tag.br # => <br>
|
|
247
409
|
#
|
|
410
|
+
# Note that when using the block form options should be wrapped in
|
|
411
|
+
# parenthesis.
|
|
412
|
+
#
|
|
413
|
+
# <%= tag.a(href: "/about", class: "font-bold") do %>
|
|
414
|
+
# About the author
|
|
415
|
+
# <% end %>
|
|
416
|
+
# # => <a href="/about" class="font-bold">About the author</a>
|
|
417
|
+
#
|
|
248
418
|
# === Building HTML attributes
|
|
249
419
|
#
|
|
250
420
|
# Transforms a Hash into HTML attributes, ready to be interpolated into
|
|
@@ -310,7 +480,7 @@ module ActionView
|
|
|
310
480
|
if name.nil?
|
|
311
481
|
tag_builder
|
|
312
482
|
else
|
|
313
|
-
|
|
483
|
+
ensure_valid_html5_tag_name(name)
|
|
314
484
|
"<#{name}#{tag_builder.tag_options(options, escape) if options}#{open ? ">" : " />"}".html_safe
|
|
315
485
|
end
|
|
316
486
|
end
|
|
@@ -344,6 +514,8 @@ module ActionView
|
|
|
344
514
|
# <% end -%>
|
|
345
515
|
# # => <div class="strong">Hello world!</div>
|
|
346
516
|
def content_tag(name, content_or_options_with_block = nil, options = nil, escape = true, &block)
|
|
517
|
+
ensure_valid_html5_tag_name(name)
|
|
518
|
+
|
|
347
519
|
if block_given?
|
|
348
520
|
options = content_or_options_with_block if content_or_options_with_block.is_a?(Hash)
|
|
349
521
|
tag_builder.content_tag_string(name, capture(&block), options, escape)
|
|
@@ -400,6 +572,11 @@ module ActionView
|
|
|
400
572
|
end
|
|
401
573
|
|
|
402
574
|
private
|
|
575
|
+
def ensure_valid_html5_tag_name(name)
|
|
576
|
+
raise ArgumentError, "Invalid HTML5 tag name: #{name.inspect}" unless /\A[a-zA-Z][^\s\/>]*\z/.match?(name)
|
|
577
|
+
end
|
|
578
|
+
module_function :ensure_valid_html5_tag_name
|
|
579
|
+
|
|
403
580
|
def build_tag_values(*args)
|
|
404
581
|
tag_values = []
|
|
405
582
|
|
|
@@ -80,30 +80,32 @@ module ActionView
|
|
|
80
80
|
end
|
|
81
81
|
end
|
|
82
82
|
|
|
83
|
-
def
|
|
83
|
+
def add_default_name_and_field_for_value(tag_value, options, field = "id")
|
|
84
84
|
if tag_value.nil?
|
|
85
|
-
|
|
85
|
+
add_default_name_and_field(options, field)
|
|
86
86
|
else
|
|
87
|
-
|
|
88
|
-
|
|
87
|
+
specified_field = options[field]
|
|
88
|
+
add_default_name_and_field(options, field)
|
|
89
89
|
|
|
90
|
-
if
|
|
91
|
-
options[
|
|
90
|
+
if specified_field.blank? && options[field].present?
|
|
91
|
+
options[field] += "_#{sanitized_value(tag_value)}"
|
|
92
92
|
end
|
|
93
93
|
end
|
|
94
94
|
end
|
|
95
|
+
alias_method :add_default_name_and_id_for_value, :add_default_name_and_field_for_value
|
|
95
96
|
|
|
96
|
-
def
|
|
97
|
+
def add_default_name_and_field(options, field = "id")
|
|
97
98
|
index = name_and_id_index(options)
|
|
98
99
|
options["name"] = options.fetch("name") { tag_name(options["multiple"], index) }
|
|
99
100
|
|
|
100
101
|
if generate_ids?
|
|
101
|
-
options[
|
|
102
|
+
options[field] = options.fetch(field) { tag_id(index, options.delete("namespace")) }
|
|
102
103
|
if namespace = options.delete("namespace")
|
|
103
|
-
options[
|
|
104
|
+
options[field] = options[field] ? "#{namespace}_#{options[field]}" : namespace
|
|
104
105
|
end
|
|
105
106
|
end
|
|
106
107
|
end
|
|
108
|
+
alias_method :add_default_name_and_id, :add_default_name_and_field
|
|
107
109
|
|
|
108
110
|
def tag_name(multiple = false, index = nil)
|
|
109
111
|
@template_object.field_name(@object_name, sanitized_method_name, multiple: multiple, index: index)
|
|
@@ -21,10 +21,10 @@ module ActionView
|
|
|
21
21
|
options["checked"] = "checked" if input_checked?(options)
|
|
22
22
|
|
|
23
23
|
if options["multiple"]
|
|
24
|
-
|
|
24
|
+
add_default_name_and_field_for_value(@checked_value, options)
|
|
25
25
|
options.delete("multiple")
|
|
26
26
|
else
|
|
27
|
-
|
|
27
|
+
add_default_name_and_field(options)
|
|
28
28
|
end
|
|
29
29
|
|
|
30
30
|
include_hidden = options.delete("include_hidden") { true }
|
|
@@ -57,7 +57,13 @@ module ActionView
|
|
|
57
57
|
end
|
|
58
58
|
|
|
59
59
|
def hidden_field_for_checkbox(options)
|
|
60
|
-
|
|
60
|
+
if @unchecked_value
|
|
61
|
+
tag_options = options.slice("name", "disabled", "form").merge!("type" => "hidden", "value" => @unchecked_value)
|
|
62
|
+
tag_options["autocomplete"] = "off" unless ActionView::Base.remove_hidden_field_autocomplete
|
|
63
|
+
tag("input", tag_options)
|
|
64
|
+
else
|
|
65
|
+
"".html_safe
|
|
66
|
+
end
|
|
61
67
|
end
|
|
62
68
|
end
|
|
63
69
|
end
|
|
@@ -10,12 +10,13 @@ module ActionView
|
|
|
10
10
|
include FormOptionsHelper
|
|
11
11
|
|
|
12
12
|
class CheckBoxBuilder < Builder # :nodoc:
|
|
13
|
-
def
|
|
13
|
+
def checkbox(extra_html_options = {})
|
|
14
14
|
html_options = extra_html_options.merge(@input_html_options)
|
|
15
15
|
html_options[:multiple] = true
|
|
16
16
|
html_options[:skip_default_ids] = false
|
|
17
|
-
@template_object.
|
|
17
|
+
@template_object.checkbox(@object_name, @method_name, html_options, @value, nil)
|
|
18
18
|
end
|
|
19
|
+
alias_method :check_box, :checkbox
|
|
19
20
|
end
|
|
20
21
|
|
|
21
22
|
def render(&block)
|
|
@@ -24,7 +25,7 @@ module ActionView
|
|
|
24
25
|
|
|
25
26
|
private
|
|
26
27
|
def render_component(builder)
|
|
27
|
-
builder.
|
|
28
|
+
builder.checkbox + builder.label
|
|
28
29
|
end
|
|
29
30
|
|
|
30
31
|
def hidden_field_name
|
|
@@ -106,7 +106,8 @@ module ActionView
|
|
|
106
106
|
|
|
107
107
|
def hidden_field
|
|
108
108
|
hidden_name = @html_options[:name] || hidden_field_name
|
|
109
|
-
|
|
109
|
+
options = { id: nil, form: @html_options[:form] }
|
|
110
|
+
@template_object.hidden_field_tag(hidden_name, "", options)
|
|
110
111
|
end
|
|
111
112
|
|
|
112
113
|
def hidden_field_name
|
|
@@ -6,7 +6,7 @@ module ActionView
|
|
|
6
6
|
class DatetimeField < TextField # :nodoc:
|
|
7
7
|
def render
|
|
8
8
|
options = @options.stringify_keys
|
|
9
|
-
options["value"] = datetime_value(options
|
|
9
|
+
options["value"] = datetime_value(options.fetch("value", value))
|
|
10
10
|
options["min"] = format_datetime(parse_datetime(options["min"]))
|
|
11
11
|
options["max"] = format_datetime(parse_datetime(options["max"]))
|
|
12
12
|
@options = options
|
|
@@ -6,8 +6,11 @@ module ActionView
|
|
|
6
6
|
class FileField < TextField # :nodoc:
|
|
7
7
|
def render
|
|
8
8
|
include_hidden = @options.delete(:include_hidden)
|
|
9
|
+
if @options[:accept].is_a?(Array)
|
|
10
|
+
@options[:accept] = @options[:accept].join(",")
|
|
11
|
+
end
|
|
9
12
|
options = @options.stringify_keys
|
|
10
|
-
|
|
13
|
+
add_default_name_and_field(options)
|
|
11
14
|
|
|
12
15
|
if options["multiple"] && include_hidden
|
|
13
16
|
hidden_field_for_multiple_file(options) + super
|
|
@@ -18,7 +21,9 @@ module ActionView
|
|
|
18
21
|
|
|
19
22
|
private
|
|
20
23
|
def hidden_field_for_multiple_file(options)
|
|
21
|
-
|
|
24
|
+
tag_options = { "name" => options["name"], "type" => "hidden", "value" => "" }
|
|
25
|
+
tag_options["autocomplete"] = "off" unless ActionView::Base.remove_hidden_field_autocomplete
|
|
26
|
+
tag("input", tag_options)
|
|
22
27
|
end
|
|
23
28
|
end
|
|
24
29
|
end
|
|
@@ -48,18 +48,11 @@ module ActionView
|
|
|
48
48
|
def render(&block)
|
|
49
49
|
options = @options.stringify_keys
|
|
50
50
|
tag_value = options.delete("value")
|
|
51
|
-
name_and_id = options.dup
|
|
52
51
|
|
|
53
|
-
|
|
54
|
-
name_and_id["id"] = name_and_id["for"]
|
|
55
|
-
else
|
|
56
|
-
name_and_id.delete("id")
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
add_default_name_and_id_for_value(tag_value, name_and_id)
|
|
52
|
+
add_default_name_and_field_for_value(tag_value, options, "for")
|
|
60
53
|
options.delete("index")
|
|
54
|
+
options.delete("name")
|
|
61
55
|
options.delete("namespace")
|
|
62
|
-
options["for"] = name_and_id["id"] unless options.key?("for")
|
|
63
56
|
|
|
64
57
|
builder = LabelBuilder.new(@template_object, @object_name, @method_name, @object, tag_value)
|
|
65
58
|
|
|
@@ -71,7 +64,7 @@ module ActionView
|
|
|
71
64
|
render_component(builder)
|
|
72
65
|
end
|
|
73
66
|
|
|
74
|
-
label_tag(
|
|
67
|
+
label_tag(options["for"], content, options)
|
|
75
68
|
end
|
|
76
69
|
|
|
77
70
|
private
|
|
@@ -18,7 +18,7 @@ module ActionView
|
|
|
18
18
|
options["type"] = "radio"
|
|
19
19
|
options["value"] = @tag_value
|
|
20
20
|
options["checked"] = "checked" if input_checked?(options)
|
|
21
|
-
|
|
21
|
+
add_default_name_and_field_for_value(@tag_value, options)
|
|
22
22
|
tag("input", options)
|
|
23
23
|
end
|
|
24
24
|
|
|
@@ -37,7 +37,12 @@ module ActionView
|
|
|
37
37
|
# [nil, []]
|
|
38
38
|
# { nil => [] }
|
|
39
39
|
def grouped_choices?
|
|
40
|
-
|
|
40
|
+
return false if @choices.blank?
|
|
41
|
+
|
|
42
|
+
first_choice = @choices.first
|
|
43
|
+
return false unless first_choice.is_a?(Enumerable)
|
|
44
|
+
|
|
45
|
+
first_choice.second.is_a?(Array)
|
|
41
46
|
end
|
|
42
47
|
end
|
|
43
48
|
end
|
|
@@ -11,7 +11,7 @@ module ActionView
|
|
|
11
11
|
html_options[prop.to_s] = options.delete(prop) if options.key?(prop) && !html_options.key?(prop.to_s)
|
|
12
12
|
end
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
add_default_name_and_field(html_options)
|
|
15
15
|
|
|
16
16
|
if placeholder_required?(html_options)
|
|
17
17
|
raise ArgumentError, "include_blank cannot be false for a required field." if options[:include_blank] == false
|
|
@@ -22,7 +22,9 @@ module ActionView
|
|
|
22
22
|
select = content_tag("select", add_options(option_tags, options, value), html_options)
|
|
23
23
|
|
|
24
24
|
if html_options["multiple"] && options.fetch(:include_hidden, true)
|
|
25
|
-
|
|
25
|
+
tag_options = { disabled: html_options["disabled"], name: html_options["name"], type: "hidden", value: "" }
|
|
26
|
+
tag_options[:autocomplete] = "off" unless ActionView::Base.remove_hidden_field_autocomplete
|
|
27
|
+
tag("input", tag_options) + select
|
|
26
28
|
else
|
|
27
29
|
select
|
|
28
30
|
end
|
|
@@ -37,7 +39,7 @@ module ActionView
|
|
|
37
39
|
if options[:include_blank]
|
|
38
40
|
content = (options[:include_blank] if options[:include_blank].is_a?(String))
|
|
39
41
|
label = (" " unless content)
|
|
40
|
-
option_tags = tag_builder.
|
|
42
|
+
option_tags = tag_builder.option(content, value: "", label: label) + "\n" + option_tags
|
|
41
43
|
end
|
|
42
44
|
|
|
43
45
|
if value.blank? && options[:prompt]
|
|
@@ -45,7 +47,7 @@ module ActionView
|
|
|
45
47
|
prompt_opts[:disabled] = true if options[:disabled] == ""
|
|
46
48
|
prompt_opts[:selected] = true if options[:selected] == ""
|
|
47
49
|
end
|
|
48
|
-
option_tags = tag_builder.
|
|
50
|
+
option_tags = tag_builder.option(prompt_text(options[:prompt]), **tag_options) + "\n" + option_tags
|
|
49
51
|
end
|
|
50
52
|
|
|
51
53
|
option_tags
|
|
@@ -10,7 +10,7 @@ module ActionView
|
|
|
10
10
|
|
|
11
11
|
def render
|
|
12
12
|
options = @options.stringify_keys
|
|
13
|
-
|
|
13
|
+
add_default_name_and_field(options)
|
|
14
14
|
|
|
15
15
|
if size = options.delete("size")
|
|
16
16
|
options["cols"], options["rows"] = size.split("x") if size.respond_to?(:split)
|
|
@@ -13,7 +13,7 @@ module ActionView
|
|
|
13
13
|
options["size"] = options["maxlength"] unless options.key?("size")
|
|
14
14
|
options["type"] ||= field_type
|
|
15
15
|
options["value"] = options.fetch("value") { value_before_type_cast } unless field_type == "file"
|
|
16
|
-
|
|
16
|
+
add_default_name_and_field(options)
|
|
17
17
|
tag("input", options)
|
|
18
18
|
end
|
|
19
19
|
|