actionview 7.2.3.1 → 8.1.4.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 +198 -109
- data/lib/action_view/base.rb +5 -2
- data/lib/action_view/buffers.rb +1 -1
- data/lib/action_view/dependency_tracker/erb_tracker.rb +37 -28
- data/lib/action_view/dependency_tracker/ruby_tracker.rb +3 -20
- data/lib/action_view/dependency_tracker/wildcard_resolver.rb +32 -0
- data/lib/action_view/dependency_tracker.rb +7 -1
- data/lib/action_view/gem_version.rb +3 -3
- data/lib/action_view/helpers/asset_tag_helper.rb +21 -2
- data/lib/action_view/helpers/atom_feed_helper.rb +0 -2
- data/lib/action_view/helpers/cache_helper.rb +8 -0
- 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/date_helper.rb +20 -3
- data/lib/action_view/helpers/form_helper.rb +77 -77
- data/lib/action_view/helpers/form_options_helper.rb +42 -36
- data/lib/action_view/helpers/form_tag_helper.rb +35 -25
- 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/rendering_helper.rb +160 -50
- data/lib/action_view/helpers/sanitize_helper.rb +12 -1
- data/lib/action_view/helpers/tag_helper.rb +70 -77
- 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 +11 -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/tags/week_field.rb +1 -1
- data/lib/action_view/helpers/text_helper.rb +23 -7
- data/lib/action_view/helpers/translation_helper.rb +6 -1
- data/lib/action_view/helpers/url_helper.rb +39 -13
- data/lib/action_view/layouts.rb +1 -1
- data/lib/action_view/locale/en.yml +3 -0
- data/lib/action_view/log_subscriber.rb +1 -4
- data/lib/action_view/railtie.rb +12 -1
- data/lib/action_view/record_identifier.rb +22 -1
- data/lib/action_view/render_parser/prism_render_parser.rb +13 -1
- data/lib/action_view/render_parser/ripper_render_parser.rb +10 -1
- data/lib/action_view/renderer/partial_renderer/collection_caching.rb +5 -1
- data/lib/action_view/renderer/partial_renderer.rb +16 -0
- data/lib/action_view/renderer/streaming_template_renderer.rb +8 -2
- data/lib/action_view/rendering.rb +2 -3
- data/lib/action_view/structured_event_subscriber.rb +97 -0
- data/lib/action_view/template/error.rb +7 -3
- data/lib/action_view/template/handlers/erb/erubi.rb +1 -1
- data/lib/action_view/template/handlers/erb.rb +37 -12
- data/lib/action_view/template/raw_file.rb +4 -0
- data/lib/action_view/template/resolver.rb +0 -1
- data/lib/action_view/template.rb +12 -6
- data/lib/action_view/test_case.rb +68 -55
- data/lib/action_view.rb +16 -0
- metadata +20 -26
|
@@ -1,32 +1,140 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
# :markup: markdown
|
|
4
|
+
|
|
3
5
|
module ActionView
|
|
4
6
|
module Helpers # :nodoc:
|
|
5
|
-
#
|
|
7
|
+
# # Action View Rendering Helpers
|
|
6
8
|
#
|
|
7
|
-
# Implements methods that allow rendering from a view context.
|
|
8
|
-
#
|
|
9
|
-
#
|
|
9
|
+
# Implements methods that allow rendering from a view context. In order to use
|
|
10
|
+
# this module, all you need is to implement view_renderer that returns an
|
|
11
|
+
# ActionView::Renderer object.
|
|
10
12
|
module RenderingHelper
|
|
11
|
-
#
|
|
13
|
+
# Renders a template and returns the result.
|
|
14
|
+
#
|
|
15
|
+
# Pass the template to render as the first argument. This is shorthand
|
|
16
|
+
# syntax for partial rendering, so the template filename should be
|
|
17
|
+
# prefixed with an underscore. The partial renderer looks for the partial
|
|
18
|
+
# template in the directory of the calling template first.
|
|
19
|
+
#
|
|
20
|
+
# <% # app/views/posts/new.html.erb %>
|
|
21
|
+
# <%= render "form" %>
|
|
22
|
+
# # => renders app/views/posts/_form.html.erb
|
|
23
|
+
#
|
|
24
|
+
# Use the complete view path to render a partial from another directory.
|
|
25
|
+
#
|
|
26
|
+
# <% # app/views/posts/show.html.erb %>
|
|
27
|
+
# <%= render "comments/form" %>
|
|
28
|
+
# # => renders app/views/comments/_form.html.erb
|
|
29
|
+
#
|
|
30
|
+
# Without the rendering mode, the second argument can be a Hash of local
|
|
31
|
+
# variable assignments for the template.
|
|
32
|
+
#
|
|
33
|
+
# <% # app/views/posts/new.html.erb %>
|
|
34
|
+
# <%= render "form", post: Post.new %>
|
|
35
|
+
# # => renders app/views/posts/_form.html.erb
|
|
36
|
+
#
|
|
37
|
+
# If the first argument responds to `render_in`, the template will be rendered
|
|
38
|
+
# by calling `render_in` with the current view context.
|
|
39
|
+
#
|
|
40
|
+
# class Greeting
|
|
41
|
+
# def render_in(view_context)
|
|
42
|
+
# view_context.render html: "<h1>Hello, World</h1>"
|
|
43
|
+
# end
|
|
44
|
+
#
|
|
45
|
+
# def format
|
|
46
|
+
# :html
|
|
47
|
+
# end
|
|
48
|
+
# end
|
|
49
|
+
#
|
|
50
|
+
# <%= render Greeting.new %>
|
|
51
|
+
# # => "<h1>Hello, World</h1>"
|
|
52
|
+
#
|
|
53
|
+
# #### Rendering Mode
|
|
54
|
+
#
|
|
55
|
+
# Pass the rendering mode as first argument to override it.
|
|
56
|
+
#
|
|
57
|
+
# `:partial`
|
|
58
|
+
# : See ActionView::PartialRenderer for details.
|
|
59
|
+
#
|
|
60
|
+
# <%= render partial: "form", locals: { post: Post.new } %>
|
|
61
|
+
# # => renders app/views/posts/_form.html.erb
|
|
62
|
+
#
|
|
63
|
+
# `:file`
|
|
64
|
+
# : Renders the contents of a file. This option should **not** be used with
|
|
65
|
+
# unsanitized user input.
|
|
66
|
+
#
|
|
67
|
+
# <%= render file: "/path/to/some/file" %>
|
|
68
|
+
# # => renders /path/to/some/file
|
|
69
|
+
#
|
|
70
|
+
# `:inline`
|
|
71
|
+
# : Renders an ERB template string.
|
|
72
|
+
#
|
|
73
|
+
# <% name = "World" %>
|
|
74
|
+
# <%= render inline: "<h1>Hello, <%= name %>!</h1>" %>
|
|
75
|
+
# # => renders "<h1>Hello, World!</h1>"
|
|
76
|
+
#
|
|
77
|
+
# `:body`
|
|
78
|
+
# : Renders the provided text, and sets the format as `:text`.
|
|
79
|
+
#
|
|
80
|
+
# <%= render body: "Hello, World!" %>
|
|
81
|
+
# # => renders "Hello, World!"
|
|
82
|
+
#
|
|
83
|
+
# `:plain`
|
|
84
|
+
# : Renders the provided text, and sets the format as `:text`.
|
|
85
|
+
#
|
|
86
|
+
# <%= render plain: "Hello, World!" %>
|
|
87
|
+
# # => renders "Hello, World!"
|
|
88
|
+
#
|
|
89
|
+
# `:html`
|
|
90
|
+
# : Renders the provided HTML string, and sets the format as
|
|
91
|
+
# `:html`. If the string is not `html_safe?`, performs HTML escaping on
|
|
92
|
+
# the string before rendering.
|
|
93
|
+
#
|
|
94
|
+
# <%= render html: "<h1>Hello, World!</h1>".html_safe %>
|
|
95
|
+
# # => renders "<h1>Hello, World!</h1>"
|
|
96
|
+
#
|
|
97
|
+
# <%= render html: "<h1>Hello, World!</h1>" %>
|
|
98
|
+
# # => renders "<h1>Hello, World!</h1>"
|
|
99
|
+
#
|
|
100
|
+
# `:renderable`
|
|
101
|
+
# : Renders the provided object by calling `render_in` with the current view
|
|
102
|
+
# context. The format is determined by calling `format` on the
|
|
103
|
+
# renderable if it responds to `format`, falling back to `:html` by
|
|
104
|
+
# default.
|
|
105
|
+
#
|
|
106
|
+
# <%= render renderable: Greeting.new %>
|
|
107
|
+
# # => renders "<h1>Hello, World</h1>"
|
|
108
|
+
#
|
|
109
|
+
#
|
|
110
|
+
# #### Options
|
|
111
|
+
#
|
|
112
|
+
# `:locals`
|
|
113
|
+
# : Hash of local variable assignments for the template.
|
|
114
|
+
#
|
|
115
|
+
# <%= render inline: "<h1>Hello, <%= name %>!</h1>", locals: { name: "World" } %>
|
|
116
|
+
# # => renders "<h1>Hello, World!</h1>"
|
|
117
|
+
#
|
|
118
|
+
# `:formats`
|
|
119
|
+
# : Override the current format to render a template for a different format.
|
|
120
|
+
#
|
|
121
|
+
# <% # app/views/posts/show.html.erb %>
|
|
122
|
+
# <%= render template: "posts/content", formats: [:text] %>
|
|
123
|
+
# # => renders app/views/posts/content.text.erb
|
|
12
124
|
#
|
|
13
|
-
#
|
|
14
|
-
#
|
|
15
|
-
# * <tt>:inline</tt> - Renders an inline template similar to how it's done in the controller.
|
|
16
|
-
# * <tt>:plain</tt> - Renders the text passed in out. Setting the content
|
|
17
|
-
# type as <tt>text/plain</tt>.
|
|
18
|
-
# * <tt>:html</tt> - Renders the HTML safe string passed in out, otherwise
|
|
19
|
-
# performs HTML escape on the string first. Setting the content type as
|
|
20
|
-
# <tt>text/html</tt>.
|
|
21
|
-
# * <tt>:body</tt> - Renders the text passed in, and inherits the content
|
|
22
|
-
# type of <tt>text/plain</tt> from ActionDispatch::Response object.
|
|
125
|
+
# `:variants`
|
|
126
|
+
# : Render a template for a different variant.
|
|
23
127
|
#
|
|
24
|
-
#
|
|
128
|
+
# <% # app/views/posts/show.html.erb %>
|
|
129
|
+
# <%= render template: "posts/content", variants: [:tablet] %>
|
|
130
|
+
# # => renders app/views/posts/content.html+tablet.erb
|
|
25
131
|
#
|
|
26
|
-
#
|
|
27
|
-
#
|
|
132
|
+
# `:handlers`
|
|
133
|
+
# : Render a template for a different handler.
|
|
28
134
|
#
|
|
29
|
-
#
|
|
135
|
+
# <% # app/views/posts/show.html.erb %>
|
|
136
|
+
# <%= render template: "posts/content", handlers: [:builder] %>
|
|
137
|
+
# # => renders app/views/posts/content.html.builder
|
|
30
138
|
def render(options = {}, locals = {}, &block)
|
|
31
139
|
case options
|
|
32
140
|
when Hash
|
|
@@ -47,52 +155,54 @@ module ActionView
|
|
|
47
155
|
end
|
|
48
156
|
|
|
49
157
|
# Overrides _layout_for in the context object so it supports the case a block is
|
|
50
|
-
# passed to a partial. Returns the contents that are yielded to a layout, given
|
|
51
|
-
# name or a block.
|
|
158
|
+
# passed to a partial. Returns the contents that are yielded to a layout, given
|
|
159
|
+
# a name or a block.
|
|
52
160
|
#
|
|
53
|
-
# You can think of a layout as a method that is called with a block. If the user
|
|
54
|
-
#
|
|
55
|
-
# If the user calls simply
|
|
161
|
+
# You can think of a layout as a method that is called with a block. If the user
|
|
162
|
+
# calls `yield :some_name`, the block, by default, returns
|
|
163
|
+
# `content_for(:some_name)`. If the user calls simply `yield`, the default block
|
|
164
|
+
# returns `content_for(:layout)`.
|
|
56
165
|
#
|
|
57
166
|
# The user can override this default by passing a block to the layout:
|
|
58
167
|
#
|
|
59
|
-
#
|
|
60
|
-
#
|
|
61
|
-
#
|
|
62
|
-
#
|
|
168
|
+
# # The template
|
|
169
|
+
# <%= render layout: "my_layout" do %>
|
|
170
|
+
# Content
|
|
171
|
+
# <% end %>
|
|
63
172
|
#
|
|
64
|
-
#
|
|
65
|
-
#
|
|
66
|
-
#
|
|
67
|
-
#
|
|
173
|
+
# # The layout
|
|
174
|
+
# <html>
|
|
175
|
+
# <%= yield %>
|
|
176
|
+
# </html>
|
|
68
177
|
#
|
|
69
|
-
# In this case, instead of the default block, which would return
|
|
70
|
-
# this method returns the block that was passed in to
|
|
178
|
+
# In this case, instead of the default block, which would return `content_for(:layout)`,
|
|
179
|
+
# this method returns the block that was passed in to `render :layout`, and the response
|
|
71
180
|
# would be
|
|
72
181
|
#
|
|
73
|
-
#
|
|
74
|
-
#
|
|
75
|
-
#
|
|
182
|
+
# <html>
|
|
183
|
+
# Content
|
|
184
|
+
# </html>
|
|
76
185
|
#
|
|
77
|
-
# Finally, the block can take block arguments, which can be passed in by
|
|
186
|
+
# Finally, the block can take block arguments, which can be passed in by
|
|
187
|
+
# `yield`:
|
|
78
188
|
#
|
|
79
|
-
#
|
|
80
|
-
#
|
|
81
|
-
#
|
|
82
|
-
#
|
|
189
|
+
# # The template
|
|
190
|
+
# <%= render layout: "my_layout" do |customer| %>
|
|
191
|
+
# Hello <%= customer.name %>
|
|
192
|
+
# <% end %>
|
|
83
193
|
#
|
|
84
|
-
#
|
|
85
|
-
#
|
|
86
|
-
#
|
|
87
|
-
#
|
|
194
|
+
# # The layout
|
|
195
|
+
# <html>
|
|
196
|
+
# <%= yield Struct.new(:name).new("David") %>
|
|
197
|
+
# </html>
|
|
88
198
|
#
|
|
89
|
-
# In this case, the layout would receive the block passed into
|
|
199
|
+
# In this case, the layout would receive the block passed into `render :layout`,
|
|
90
200
|
# and the struct specified would be passed into the block as an argument. The result
|
|
91
201
|
# would be
|
|
92
202
|
#
|
|
93
|
-
#
|
|
94
|
-
#
|
|
95
|
-
#
|
|
203
|
+
# <html>
|
|
204
|
+
# Hello David
|
|
205
|
+
# </html>
|
|
96
206
|
#
|
|
97
207
|
def _layout_for(*args, &block)
|
|
98
208
|
name = args.first
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "active_support/core_ext/string/output_safety"
|
|
3
4
|
require "rails-html-sanitizer"
|
|
4
5
|
|
|
5
6
|
module ActionView
|
|
@@ -24,9 +25,18 @@ module ActionView
|
|
|
24
25
|
#
|
|
25
26
|
# Custom sanitization rules can also be provided.
|
|
26
27
|
#
|
|
28
|
+
# <b>Warning</b>: Adding disallowed tags or attributes to the allowlists may introduce
|
|
29
|
+
# vulnerabilities into your application. Please rely on the default allowlists whenever
|
|
30
|
+
# possible, because they are curated to maintain security and safety. If you think that the
|
|
31
|
+
# default allowlists should be expanded, please {open an issue on the rails-html-sanitizer
|
|
32
|
+
# project}[https://github.com/rails/rails-html-sanitizer/issues].
|
|
33
|
+
#
|
|
27
34
|
# Please note that sanitizing user-provided text does not guarantee that the
|
|
28
35
|
# resulting markup is valid or even well-formed.
|
|
29
36
|
#
|
|
37
|
+
# The output is markup, which is safe only as element content. Do not use it in an attribute
|
|
38
|
+
# value; use +strip_tags+ or a tag helper such as <tt>tag.a(title: ...)</tt> instead.
|
|
39
|
+
#
|
|
30
40
|
# ==== Options
|
|
31
41
|
#
|
|
32
42
|
# [+:tags+]
|
|
@@ -131,7 +141,8 @@ module ActionView
|
|
|
131
141
|
# strip_tags("> A quote from Smith & Wesson")
|
|
132
142
|
# # => > A quote from Smith & Wesson
|
|
133
143
|
def strip_tags(html)
|
|
134
|
-
self.class.full_sanitizer.sanitize(html)
|
|
144
|
+
stripped = self.class.full_sanitizer.sanitize(html)
|
|
145
|
+
ERB::Util.html_escape_once(stripped) if stripped
|
|
135
146
|
end
|
|
136
147
|
|
|
137
148
|
# Strips all link tags from +html+ leaving just the link text.
|
|
@@ -4,7 +4,6 @@ require "active_support/code_generator"
|
|
|
4
4
|
require "active_support/core_ext/enumerable"
|
|
5
5
|
require "active_support/core_ext/string/output_safety"
|
|
6
6
|
require "active_support/core_ext/string/inflections"
|
|
7
|
-
require "set"
|
|
8
7
|
require "action_view/helpers/capture_helper"
|
|
9
8
|
require "action_view/helpers/output_safety_helper"
|
|
10
9
|
|
|
@@ -45,51 +44,36 @@ module ActionView
|
|
|
45
44
|
PRE_CONTENT_STRINGS["textarea"] = "\n"
|
|
46
45
|
|
|
47
46
|
class TagBuilder # :nodoc:
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
end
|
|
57
|
-
RUBY
|
|
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"
|
|
58
55
|
end
|
|
59
56
|
end
|
|
60
57
|
|
|
61
|
-
def self.define_void_element(name, code_generator:, method_name: name
|
|
62
|
-
code_generator.
|
|
63
|
-
batch
|
|
64
|
-
def #{method_name}(
|
|
65
|
-
|
|
66
|
-
|
|
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
|
|
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"
|
|
79
64
|
end
|
|
80
65
|
end
|
|
81
66
|
|
|
82
|
-
def self.define_self_closing_element(name, code_generator:, method_name: name
|
|
83
|
-
code_generator.
|
|
84
|
-
batch
|
|
85
|
-
def #{method_name}(content = nil, escape: true, **options, &block)
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
end
|
|
92
|
-
RUBY
|
|
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"
|
|
93
77
|
end
|
|
94
78
|
end
|
|
95
79
|
|
|
@@ -110,8 +94,8 @@ module ActionView
|
|
|
110
94
|
define_void_element :wbr, code_generator: code_generator
|
|
111
95
|
|
|
112
96
|
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
|
|
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
|
|
115
99
|
define_self_closing_element :circle, code_generator: code_generator
|
|
116
100
|
define_self_closing_element :ellipse, code_generator: code_generator
|
|
117
101
|
define_self_closing_element :line, code_generator: code_generator
|
|
@@ -239,17 +223,7 @@ module ActionView
|
|
|
239
223
|
tag_options(attributes.to_h).to_s.strip.html_safe
|
|
240
224
|
end
|
|
241
225
|
|
|
242
|
-
def
|
|
243
|
-
content = @view_context.capture(self, &block) if block
|
|
244
|
-
|
|
245
|
-
content_tag_string(name, content, options, escape)
|
|
246
|
-
end
|
|
247
|
-
|
|
248
|
-
def self_closing_tag_string(name, options, escape = true, tag_suffix = " />")
|
|
249
|
-
"<#{name}#{tag_options(options, escape)}#{tag_suffix}".html_safe
|
|
250
|
-
end
|
|
251
|
-
|
|
252
|
-
def content_tag_string(name, content, options, escape = true)
|
|
226
|
+
def content_tag_string(name, content, options, escape = true) # :nodoc:
|
|
253
227
|
tag_options = tag_options(options, escape) if options
|
|
254
228
|
|
|
255
229
|
if escape && content.present?
|
|
@@ -258,73 +232,92 @@ module ActionView
|
|
|
258
232
|
"<#{name}#{tag_options}>#{PRE_CONTENT_STRINGS[name]}#{content}</#{name}>".html_safe
|
|
259
233
|
end
|
|
260
234
|
|
|
261
|
-
def tag_options(options, escape = true)
|
|
235
|
+
def tag_options(options, escape = true) # :nodoc:
|
|
262
236
|
return if options.blank?
|
|
263
237
|
output = +""
|
|
264
238
|
sep = " "
|
|
265
239
|
options.each_pair do |key, value|
|
|
266
240
|
next if key.blank?
|
|
267
241
|
|
|
242
|
+
name = Symbol === key ? key.name : key.to_s
|
|
243
|
+
next if name.blank?
|
|
244
|
+
|
|
268
245
|
type = TAG_TYPES[key]
|
|
269
246
|
if type == :data && value.is_a?(Hash)
|
|
270
247
|
value.each_pair do |k, v|
|
|
271
248
|
next if k.blank? || v.nil?
|
|
272
249
|
|
|
250
|
+
nested_name = Symbol === k ? k.name : k.to_s
|
|
251
|
+
next if nested_name.blank?
|
|
252
|
+
|
|
273
253
|
output << sep
|
|
274
|
-
output << prefix_tag_option(
|
|
254
|
+
output << prefix_tag_option(name, nested_name, v, escape)
|
|
275
255
|
end
|
|
276
256
|
elsif type == :aria && value.is_a?(Hash)
|
|
277
257
|
value.each_pair do |k, v|
|
|
278
258
|
next if k.blank? || v.nil?
|
|
279
259
|
|
|
260
|
+
nested_name = Symbol === k ? k.name : k.to_s
|
|
261
|
+
next if nested_name.blank?
|
|
262
|
+
|
|
280
263
|
case v
|
|
281
264
|
when Array, Hash
|
|
282
265
|
tokens = TagHelper.build_tag_values(v)
|
|
283
266
|
next if tokens.none?
|
|
284
267
|
|
|
285
|
-
v = safe_join(tokens, " ")
|
|
268
|
+
v = @view_context.safe_join(tokens, " ")
|
|
286
269
|
else
|
|
287
270
|
v = v.to_s
|
|
288
271
|
end
|
|
289
272
|
|
|
290
273
|
output << sep
|
|
291
|
-
output << prefix_tag_option(
|
|
274
|
+
output << prefix_tag_option(name, nested_name, v, escape)
|
|
292
275
|
end
|
|
293
276
|
elsif type == :boolean
|
|
294
277
|
if value
|
|
295
278
|
output << sep
|
|
296
|
-
output << boolean_tag_option(
|
|
279
|
+
output << boolean_tag_option(name)
|
|
297
280
|
end
|
|
298
281
|
elsif !value.nil?
|
|
299
282
|
output << sep
|
|
300
|
-
output << tag_option(
|
|
283
|
+
output << tag_option(name, value, escape)
|
|
301
284
|
end
|
|
302
285
|
end
|
|
303
286
|
output unless output.empty?
|
|
304
287
|
end
|
|
305
288
|
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
289
|
+
private
|
|
290
|
+
def tag_string(name, content = nil, options, escape: true, &block)
|
|
291
|
+
content = @view_context.capture(self, &block) if block
|
|
309
292
|
|
|
310
|
-
|
|
311
|
-
key = ERB::Util.xml_name_escape(key) if escape
|
|
312
|
-
|
|
313
|
-
case value
|
|
314
|
-
when Array, Hash
|
|
315
|
-
value = TagHelper.build_tag_values(value) if key.to_s == "class"
|
|
316
|
-
value = escape ? safe_join(value, " ") : value.join(" ")
|
|
317
|
-
when Regexp
|
|
318
|
-
value = escape ? ERB::Util.unwrapped_html_escape(value.source) : value.source
|
|
319
|
-
else
|
|
320
|
-
value = escape ? ERB::Util.unwrapped_html_escape(value) : value.to_s
|
|
293
|
+
content_tag_string(name, content, options, escape)
|
|
321
294
|
end
|
|
322
|
-
value = value.gsub('"', """) if value.include?('"')
|
|
323
295
|
|
|
324
|
-
|
|
325
|
-
|
|
296
|
+
def self_closing_tag_string(name, options, escape = true, tag_suffix = " />")
|
|
297
|
+
"<#{name}#{tag_options(options, escape)}#{tag_suffix}".html_safe
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
def boolean_tag_option(key)
|
|
301
|
+
%(#{key}="#{key}")
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
def tag_option(key, value, escape)
|
|
305
|
+
key = ERB::Util.xml_name_escape(key) if escape
|
|
306
|
+
|
|
307
|
+
case value
|
|
308
|
+
when Array, Hash
|
|
309
|
+
value = TagHelper.build_tag_values(value) if key.to_s == "class"
|
|
310
|
+
value = escape ? @view_context.safe_join(value, " ") : value.join(" ")
|
|
311
|
+
when Regexp
|
|
312
|
+
value = escape ? ERB::Util.unwrapped_html_escape(value.source) : value.source
|
|
313
|
+
else
|
|
314
|
+
value = escape ? ERB::Util.unwrapped_html_escape(value) : value.to_s
|
|
315
|
+
end
|
|
316
|
+
value = value.gsub('"', """) if value.include?('"')
|
|
317
|
+
|
|
318
|
+
%(#{key}="#{value}")
|
|
319
|
+
end
|
|
326
320
|
|
|
327
|
-
private
|
|
328
321
|
def prefix_tag_option(prefix, key, value, escape)
|
|
329
322
|
key = "#{prefix}-#{key.to_s.dasherize}"
|
|
330
323
|
unless value.is_a?(String) || value.is_a?(Symbol) || value.is_a?(BigDecimal)
|
|
@@ -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
|
|
@@ -68,8 +68,18 @@ module ActionView
|
|
|
68
68
|
html_options
|
|
69
69
|
end
|
|
70
70
|
|
|
71
|
+
# Sanitize the attribute name used for the label's `for` attribute
|
|
72
|
+
# in collection helpers. When the value is nil or results in an
|
|
73
|
+
# empty sanitized string, skip the trailing underscore to match the
|
|
74
|
+
# id generated by add_default_name_and_id_for_value.
|
|
71
75
|
def sanitize_attribute_name(value)
|
|
72
|
-
|
|
76
|
+
sanitized = sanitized_value(value)
|
|
77
|
+
|
|
78
|
+
if sanitized.empty?
|
|
79
|
+
sanitized_method_name.dup
|
|
80
|
+
else
|
|
81
|
+
"#{sanitized_method_name}_#{sanitized}"
|
|
82
|
+
end
|
|
73
83
|
end
|
|
74
84
|
|
|
75
85
|
def render_collection
|
|
@@ -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
|