omg-actionview 8.0.0.alpha1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +25 -0
- data/MIT-LICENSE +21 -0
- data/README.rdoc +40 -0
- 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 +316 -0
- data/lib/action_view/buffers.rb +165 -0
- data/lib/action_view/cache_expiry.rb +69 -0
- data/lib/action_view/context.rb +32 -0
- data/lib/action_view/dependency_tracker/erb_tracker.rb +159 -0
- 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 +41 -0
- data/lib/action_view/deprecator.rb +7 -0
- data/lib/action_view/digestor.rb +130 -0
- data/lib/action_view/flows.rb +75 -0
- data/lib/action_view/gem_version.rb +17 -0
- data/lib/action_view/helpers/active_model_helper.rb +54 -0
- data/lib/action_view/helpers/asset_tag_helper.rb +680 -0
- data/lib/action_view/helpers/asset_url_helper.rb +473 -0
- data/lib/action_view/helpers/atom_feed_helper.rb +205 -0
- data/lib/action_view/helpers/cache_helper.rb +315 -0
- data/lib/action_view/helpers/capture_helper.rb +236 -0
- data/lib/action_view/helpers/content_exfiltration_prevention_helper.rb +70 -0
- data/lib/action_view/helpers/controller_helper.rb +42 -0
- data/lib/action_view/helpers/csp_helper.rb +26 -0
- data/lib/action_view/helpers/csrf_helper.rb +35 -0
- data/lib/action_view/helpers/date_helper.rb +1266 -0
- data/lib/action_view/helpers/debug_helper.rb +38 -0
- data/lib/action_view/helpers/form_helper.rb +2765 -0
- data/lib/action_view/helpers/form_options_helper.rb +927 -0
- data/lib/action_view/helpers/form_tag_helper.rb +1088 -0
- data/lib/action_view/helpers/javascript_helper.rb +96 -0
- data/lib/action_view/helpers/number_helper.rb +165 -0
- data/lib/action_view/helpers/output_safety_helper.rb +70 -0
- data/lib/action_view/helpers/rendering_helper.rb +218 -0
- data/lib/action_view/helpers/sanitize_helper.rb +201 -0
- data/lib/action_view/helpers/tag_helper.rb +621 -0
- data/lib/action_view/helpers/tags/base.rb +138 -0
- data/lib/action_view/helpers/tags/check_box.rb +65 -0
- data/lib/action_view/helpers/tags/checkable.rb +18 -0
- data/lib/action_view/helpers/tags/collection_check_boxes.rb +37 -0
- data/lib/action_view/helpers/tags/collection_helpers.rb +118 -0
- data/lib/action_view/helpers/tags/collection_radio_buttons.rb +31 -0
- data/lib/action_view/helpers/tags/collection_select.rb +33 -0
- data/lib/action_view/helpers/tags/color_field.rb +26 -0
- data/lib/action_view/helpers/tags/date_field.rb +14 -0
- data/lib/action_view/helpers/tags/date_select.rb +75 -0
- data/lib/action_view/helpers/tags/datetime_field.rb +39 -0
- data/lib/action_view/helpers/tags/datetime_local_field.rb +29 -0
- data/lib/action_view/helpers/tags/datetime_select.rb +10 -0
- data/lib/action_view/helpers/tags/email_field.rb +10 -0
- data/lib/action_view/helpers/tags/file_field.rb +26 -0
- data/lib/action_view/helpers/tags/grouped_collection_select.rb +34 -0
- data/lib/action_view/helpers/tags/hidden_field.rb +14 -0
- data/lib/action_view/helpers/tags/label.rb +84 -0
- data/lib/action_view/helpers/tags/month_field.rb +14 -0
- data/lib/action_view/helpers/tags/number_field.rb +20 -0
- data/lib/action_view/helpers/tags/password_field.rb +14 -0
- data/lib/action_view/helpers/tags/placeholderable.rb +24 -0
- data/lib/action_view/helpers/tags/radio_button.rb +32 -0
- data/lib/action_view/helpers/tags/range_field.rb +10 -0
- data/lib/action_view/helpers/tags/search_field.rb +27 -0
- data/lib/action_view/helpers/tags/select.rb +45 -0
- data/lib/action_view/helpers/tags/select_renderer.rb +56 -0
- data/lib/action_view/helpers/tags/tel_field.rb +10 -0
- data/lib/action_view/helpers/tags/text_area.rb +24 -0
- data/lib/action_view/helpers/tags/text_field.rb +33 -0
- data/lib/action_view/helpers/tags/time_field.rb +23 -0
- data/lib/action_view/helpers/tags/time_select.rb +10 -0
- data/lib/action_view/helpers/tags/time_zone_select.rb +25 -0
- data/lib/action_view/helpers/tags/translator.rb +39 -0
- data/lib/action_view/helpers/tags/url_field.rb +10 -0
- data/lib/action_view/helpers/tags/week_field.rb +14 -0
- data/lib/action_view/helpers/tags/weekday_select.rb +31 -0
- data/lib/action_view/helpers/tags.rb +47 -0
- data/lib/action_view/helpers/text_helper.rb +568 -0
- data/lib/action_view/helpers/translation_helper.rb +161 -0
- data/lib/action_view/helpers/url_helper.rb +812 -0
- data/lib/action_view/helpers.rb +68 -0
- data/lib/action_view/layouts.rb +434 -0
- data/lib/action_view/locale/en.yml +56 -0
- data/lib/action_view/log_subscriber.rb +132 -0
- data/lib/action_view/lookup_context.rb +299 -0
- data/lib/action_view/model_naming.rb +14 -0
- data/lib/action_view/path_registry.rb +57 -0
- data/lib/action_view/path_set.rb +84 -0
- data/lib/action_view/railtie.rb +132 -0
- data/lib/action_view/record_identifier.rb +118 -0
- data/lib/action_view/render_parser/prism_render_parser.rb +139 -0
- data/lib/action_view/render_parser/ripper_render_parser.rb +350 -0
- data/lib/action_view/render_parser.rb +40 -0
- data/lib/action_view/renderer/abstract_renderer.rb +186 -0
- data/lib/action_view/renderer/collection_renderer.rb +204 -0
- data/lib/action_view/renderer/object_renderer.rb +34 -0
- data/lib/action_view/renderer/partial_renderer/collection_caching.rb +120 -0
- data/lib/action_view/renderer/partial_renderer.rb +267 -0
- data/lib/action_view/renderer/renderer.rb +107 -0
- data/lib/action_view/renderer/streaming_template_renderer.rb +107 -0
- data/lib/action_view/renderer/template_renderer.rb +115 -0
- data/lib/action_view/rendering.rb +190 -0
- data/lib/action_view/routing_url_for.rb +149 -0
- data/lib/action_view/tasks/cache_digests.rake +25 -0
- data/lib/action_view/template/error.rb +264 -0
- data/lib/action_view/template/handlers/builder.rb +25 -0
- data/lib/action_view/template/handlers/erb/erubi.rb +85 -0
- data/lib/action_view/template/handlers/erb.rb +157 -0
- data/lib/action_view/template/handlers/html.rb +11 -0
- data/lib/action_view/template/handlers/raw.rb +11 -0
- data/lib/action_view/template/handlers.rb +66 -0
- data/lib/action_view/template/html.rb +33 -0
- data/lib/action_view/template/inline.rb +22 -0
- data/lib/action_view/template/raw_file.rb +25 -0
- data/lib/action_view/template/renderable.rb +30 -0
- data/lib/action_view/template/resolver.rb +212 -0
- data/lib/action_view/template/sources/file.rb +17 -0
- data/lib/action_view/template/sources.rb +13 -0
- data/lib/action_view/template/text.rb +32 -0
- data/lib/action_view/template/types.rb +50 -0
- data/lib/action_view/template.rb +580 -0
- data/lib/action_view/template_details.rb +66 -0
- data/lib/action_view/template_path.rb +66 -0
- data/lib/action_view/test_case.rb +449 -0
- data/lib/action_view/testing/resolvers.rb +44 -0
- data/lib/action_view/unbound_template.rb +67 -0
- data/lib/action_view/version.rb +10 -0
- data/lib/action_view/view_paths.rb +117 -0
- data/lib/action_view.rb +104 -0
- metadata +275 -0
@@ -0,0 +1,621 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/code_generator"
|
4
|
+
require "active_support/core_ext/enumerable"
|
5
|
+
require "active_support/core_ext/string/output_safety"
|
6
|
+
require "active_support/core_ext/string/inflections"
|
7
|
+
require "set"
|
8
|
+
require "action_view/helpers/capture_helper"
|
9
|
+
require "action_view/helpers/output_safety_helper"
|
10
|
+
|
11
|
+
module ActionView
|
12
|
+
module Helpers # :nodoc:
|
13
|
+
# = Action View Tag \Helpers
|
14
|
+
#
|
15
|
+
# Provides methods to generate HTML tags programmatically both as a modern
|
16
|
+
# HTML5 compliant builder style and legacy XHTML compliant tags.
|
17
|
+
module TagHelper
|
18
|
+
include CaptureHelper
|
19
|
+
include OutputSafetyHelper
|
20
|
+
|
21
|
+
BOOLEAN_ATTRIBUTES = %w(allowfullscreen allowpaymentrequest async autofocus
|
22
|
+
autoplay checked compact controls declare default
|
23
|
+
defaultchecked defaultmuted defaultselected defer
|
24
|
+
disabled enabled formnovalidate hidden indeterminate
|
25
|
+
inert ismap itemscope loop multiple muted nohref
|
26
|
+
nomodule noresize noshade novalidate nowrap open
|
27
|
+
pauseonexit playsinline readonly required reversed
|
28
|
+
scoped seamless selected sortable truespeed
|
29
|
+
typemustmatch visible).to_set
|
30
|
+
|
31
|
+
BOOLEAN_ATTRIBUTES.merge(BOOLEAN_ATTRIBUTES.map(&:to_sym))
|
32
|
+
BOOLEAN_ATTRIBUTES.freeze
|
33
|
+
|
34
|
+
ARIA_PREFIXES = ["aria", :aria].to_set.freeze
|
35
|
+
DATA_PREFIXES = ["data", :data].to_set.freeze
|
36
|
+
|
37
|
+
TAG_TYPES = {}
|
38
|
+
TAG_TYPES.merge! BOOLEAN_ATTRIBUTES.index_with(:boolean)
|
39
|
+
TAG_TYPES.merge! DATA_PREFIXES.index_with(:data)
|
40
|
+
TAG_TYPES.merge! ARIA_PREFIXES.index_with(:aria)
|
41
|
+
TAG_TYPES.freeze
|
42
|
+
|
43
|
+
PRE_CONTENT_STRINGS = Hash.new { "" }
|
44
|
+
PRE_CONTENT_STRINGS[:textarea] = "\n"
|
45
|
+
PRE_CONTENT_STRINGS["textarea"] = "\n"
|
46
|
+
|
47
|
+
class TagBuilder # :nodoc:
|
48
|
+
include CaptureHelper
|
49
|
+
include OutputSafetyHelper
|
50
|
+
|
51
|
+
def deprecated_void_content(name)
|
52
|
+
ActionView.deprecator.warn <<~TEXT
|
53
|
+
Putting content inside a void element (#{name}) is invalid
|
54
|
+
according to the HTML5 spec, and so it is being deprecated
|
55
|
+
without replacement. In Rails 8.0, passing content as a
|
56
|
+
positional argument will raise, and using a block will have
|
57
|
+
no effect.
|
58
|
+
TEXT
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.define_element(name, code_generator:, method_name: name)
|
62
|
+
return if method_defined?(name)
|
63
|
+
|
64
|
+
code_generator.class_eval do |batch|
|
65
|
+
batch << "\n" <<
|
66
|
+
"def #{method_name}(content = nil, escape: true, **options, &block)" <<
|
67
|
+
" tag_string(#{name.inspect}, content, options, escape: escape, &block)" <<
|
68
|
+
"end"
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def self.define_void_element(name, code_generator:, method_name: name)
|
73
|
+
code_generator.class_eval do |batch|
|
74
|
+
batch << "\n" <<
|
75
|
+
"def #{method_name}(content = nil, escape: true, **options, &block)" <<
|
76
|
+
" if content || block" <<
|
77
|
+
" deprecated_void_content(#{name.inspect})" <<
|
78
|
+
" tag_string(#{name.inspect}, content, options, escape: escape, &block)" <<
|
79
|
+
" else" <<
|
80
|
+
" self_closing_tag_string(#{name.inspect}, options, escape, '>')" <<
|
81
|
+
" end" <<
|
82
|
+
"end"
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def self.define_self_closing_element(name, code_generator:, method_name: name)
|
87
|
+
code_generator.class_eval do |batch|
|
88
|
+
batch << "\n" <<
|
89
|
+
"def #{method_name}(content = nil, escape: true, **options, &block)" <<
|
90
|
+
" if content || block" <<
|
91
|
+
" tag_string(#{name.inspect}, content, options, escape: escape, &block)" <<
|
92
|
+
" else" <<
|
93
|
+
" self_closing_tag_string(#{name.inspect}, options, escape)" <<
|
94
|
+
" end" <<
|
95
|
+
"end"
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
ActiveSupport::CodeGenerator.batch(self, __FILE__, __LINE__) do |code_generator|
|
100
|
+
define_void_element :area, code_generator: code_generator
|
101
|
+
define_void_element :base, code_generator: code_generator
|
102
|
+
define_void_element :br, code_generator: code_generator
|
103
|
+
define_void_element :col, code_generator: code_generator
|
104
|
+
define_void_element :embed, code_generator: code_generator
|
105
|
+
define_void_element :hr, code_generator: code_generator
|
106
|
+
define_void_element :img, code_generator: code_generator
|
107
|
+
define_void_element :input, code_generator: code_generator
|
108
|
+
define_void_element :keygen, code_generator: code_generator
|
109
|
+
define_void_element :link, code_generator: code_generator
|
110
|
+
define_void_element :meta, code_generator: code_generator
|
111
|
+
define_void_element :source, code_generator: code_generator
|
112
|
+
define_void_element :track, code_generator: code_generator
|
113
|
+
define_void_element :wbr, code_generator: code_generator
|
114
|
+
|
115
|
+
define_self_closing_element :animate, code_generator: code_generator
|
116
|
+
define_self_closing_element :animateMotion, code_generator: code_generator, method_name: :animate_motion
|
117
|
+
define_self_closing_element :animateTransform, code_generator: code_generator, method_name: :animate_transform
|
118
|
+
define_self_closing_element :circle, code_generator: code_generator
|
119
|
+
define_self_closing_element :ellipse, code_generator: code_generator
|
120
|
+
define_self_closing_element :line, code_generator: code_generator
|
121
|
+
define_self_closing_element :path, code_generator: code_generator
|
122
|
+
define_self_closing_element :polygon, code_generator: code_generator
|
123
|
+
define_self_closing_element :polyline, code_generator: code_generator
|
124
|
+
define_self_closing_element :rect, code_generator: code_generator
|
125
|
+
define_self_closing_element :set, code_generator: code_generator
|
126
|
+
define_self_closing_element :stop, code_generator: code_generator
|
127
|
+
define_self_closing_element :use, code_generator: code_generator
|
128
|
+
define_self_closing_element :view, code_generator: code_generator
|
129
|
+
|
130
|
+
define_element :a, code_generator: code_generator
|
131
|
+
define_element :abbr, code_generator: code_generator
|
132
|
+
define_element :address, code_generator: code_generator
|
133
|
+
define_element :article, code_generator: code_generator
|
134
|
+
define_element :aside, code_generator: code_generator
|
135
|
+
define_element :audio, code_generator: code_generator
|
136
|
+
define_element :b, code_generator: code_generator
|
137
|
+
define_element :bdi, code_generator: code_generator
|
138
|
+
define_element :bdo, code_generator: code_generator
|
139
|
+
define_element :blockquote, code_generator: code_generator
|
140
|
+
define_element :body, code_generator: code_generator
|
141
|
+
define_element :button, code_generator: code_generator
|
142
|
+
define_element :canvas, code_generator: code_generator
|
143
|
+
define_element :caption, code_generator: code_generator
|
144
|
+
define_element :cite, code_generator: code_generator
|
145
|
+
define_element :code, code_generator: code_generator
|
146
|
+
define_element :colgroup, code_generator: code_generator
|
147
|
+
define_element :data, code_generator: code_generator
|
148
|
+
define_element :datalist, code_generator: code_generator
|
149
|
+
define_element :dd, code_generator: code_generator
|
150
|
+
define_element :del, code_generator: code_generator
|
151
|
+
define_element :details, code_generator: code_generator
|
152
|
+
define_element :dfn, code_generator: code_generator
|
153
|
+
define_element :dialog, code_generator: code_generator
|
154
|
+
define_element :div, code_generator: code_generator
|
155
|
+
define_element :dl, code_generator: code_generator
|
156
|
+
define_element :dt, code_generator: code_generator
|
157
|
+
define_element :em, code_generator: code_generator
|
158
|
+
define_element :fieldset, code_generator: code_generator
|
159
|
+
define_element :figcaption, code_generator: code_generator
|
160
|
+
define_element :figure, code_generator: code_generator
|
161
|
+
define_element :footer, code_generator: code_generator
|
162
|
+
define_element :form, code_generator: code_generator
|
163
|
+
define_element :h1, code_generator: code_generator
|
164
|
+
define_element :h2, code_generator: code_generator
|
165
|
+
define_element :h3, code_generator: code_generator
|
166
|
+
define_element :h4, code_generator: code_generator
|
167
|
+
define_element :h5, code_generator: code_generator
|
168
|
+
define_element :h6, code_generator: code_generator
|
169
|
+
define_element :head, code_generator: code_generator
|
170
|
+
define_element :header, code_generator: code_generator
|
171
|
+
define_element :hgroup, code_generator: code_generator
|
172
|
+
define_element :html, code_generator: code_generator
|
173
|
+
define_element :i, code_generator: code_generator
|
174
|
+
define_element :iframe, code_generator: code_generator
|
175
|
+
define_element :ins, code_generator: code_generator
|
176
|
+
define_element :kbd, code_generator: code_generator
|
177
|
+
define_element :label, code_generator: code_generator
|
178
|
+
define_element :legend, code_generator: code_generator
|
179
|
+
define_element :li, code_generator: code_generator
|
180
|
+
define_element :main, code_generator: code_generator
|
181
|
+
define_element :map, code_generator: code_generator
|
182
|
+
define_element :mark, code_generator: code_generator
|
183
|
+
define_element :menu, code_generator: code_generator
|
184
|
+
define_element :meter, code_generator: code_generator
|
185
|
+
define_element :nav, code_generator: code_generator
|
186
|
+
define_element :noscript, code_generator: code_generator
|
187
|
+
define_element :object, code_generator: code_generator
|
188
|
+
define_element :ol, code_generator: code_generator
|
189
|
+
define_element :optgroup, code_generator: code_generator
|
190
|
+
define_element :option, code_generator: code_generator
|
191
|
+
define_element :output, code_generator: code_generator
|
192
|
+
define_element :p, code_generator: code_generator
|
193
|
+
define_element :picture, code_generator: code_generator
|
194
|
+
define_element :portal, code_generator: code_generator
|
195
|
+
define_element :pre, code_generator: code_generator
|
196
|
+
define_element :progress, code_generator: code_generator
|
197
|
+
define_element :q, code_generator: code_generator
|
198
|
+
define_element :rp, code_generator: code_generator
|
199
|
+
define_element :rt, code_generator: code_generator
|
200
|
+
define_element :ruby, code_generator: code_generator
|
201
|
+
define_element :s, code_generator: code_generator
|
202
|
+
define_element :samp, code_generator: code_generator
|
203
|
+
define_element :script, code_generator: code_generator
|
204
|
+
define_element :search, code_generator: code_generator
|
205
|
+
define_element :section, code_generator: code_generator
|
206
|
+
define_element :select, code_generator: code_generator
|
207
|
+
define_element :slot, code_generator: code_generator
|
208
|
+
define_element :small, code_generator: code_generator
|
209
|
+
define_element :span, code_generator: code_generator
|
210
|
+
define_element :strong, code_generator: code_generator
|
211
|
+
define_element :style, code_generator: code_generator
|
212
|
+
define_element :sub, code_generator: code_generator
|
213
|
+
define_element :summary, code_generator: code_generator
|
214
|
+
define_element :sup, code_generator: code_generator
|
215
|
+
define_element :table, code_generator: code_generator
|
216
|
+
define_element :tbody, code_generator: code_generator
|
217
|
+
define_element :td, code_generator: code_generator
|
218
|
+
define_element :template, code_generator: code_generator
|
219
|
+
define_element :textarea, code_generator: code_generator
|
220
|
+
define_element :tfoot, code_generator: code_generator
|
221
|
+
define_element :th, code_generator: code_generator
|
222
|
+
define_element :thead, code_generator: code_generator
|
223
|
+
define_element :time, code_generator: code_generator
|
224
|
+
define_element :title, code_generator: code_generator
|
225
|
+
define_element :tr, code_generator: code_generator
|
226
|
+
define_element :u, code_generator: code_generator
|
227
|
+
define_element :ul, code_generator: code_generator
|
228
|
+
define_element :var, code_generator: code_generator
|
229
|
+
define_element :video, code_generator: code_generator
|
230
|
+
end
|
231
|
+
|
232
|
+
def initialize(view_context)
|
233
|
+
@view_context = view_context
|
234
|
+
end
|
235
|
+
|
236
|
+
# Transforms a Hash into HTML Attributes, ready to be interpolated into
|
237
|
+
# ERB.
|
238
|
+
#
|
239
|
+
# <input <%= tag.attributes(type: :text, aria: { label: "Search" }) %> >
|
240
|
+
# # => <input type="text" aria-label="Search">
|
241
|
+
def attributes(attributes)
|
242
|
+
tag_options(attributes.to_h).to_s.strip.html_safe
|
243
|
+
end
|
244
|
+
|
245
|
+
def tag_string(name, content = nil, options, escape: true, &block)
|
246
|
+
content = @view_context.capture(self, &block) if block
|
247
|
+
|
248
|
+
content_tag_string(name, content, options, escape)
|
249
|
+
end
|
250
|
+
|
251
|
+
def self_closing_tag_string(name, options, escape = true, tag_suffix = " />")
|
252
|
+
"<#{name}#{tag_options(options, escape)}#{tag_suffix}".html_safe
|
253
|
+
end
|
254
|
+
|
255
|
+
def content_tag_string(name, content, options, escape = true)
|
256
|
+
tag_options = tag_options(options, escape) if options
|
257
|
+
|
258
|
+
if escape && content.present?
|
259
|
+
content = ERB::Util.unwrapped_html_escape(content)
|
260
|
+
end
|
261
|
+
"<#{name}#{tag_options}>#{PRE_CONTENT_STRINGS[name]}#{content}</#{name}>".html_safe
|
262
|
+
end
|
263
|
+
|
264
|
+
def tag_options(options, escape = true)
|
265
|
+
return if options.blank?
|
266
|
+
output = +""
|
267
|
+
sep = " "
|
268
|
+
options.each_pair do |key, value|
|
269
|
+
type = TAG_TYPES[key]
|
270
|
+
if type == :data && value.is_a?(Hash)
|
271
|
+
value.each_pair do |k, v|
|
272
|
+
next if v.nil?
|
273
|
+
output << sep
|
274
|
+
output << prefix_tag_option(key, k, v, escape)
|
275
|
+
end
|
276
|
+
elsif type == :aria && value.is_a?(Hash)
|
277
|
+
value.each_pair do |k, v|
|
278
|
+
next if v.nil?
|
279
|
+
|
280
|
+
case v
|
281
|
+
when Array, Hash
|
282
|
+
tokens = TagHelper.build_tag_values(v)
|
283
|
+
next if tokens.none?
|
284
|
+
|
285
|
+
v = safe_join(tokens, " ")
|
286
|
+
else
|
287
|
+
v = v.to_s
|
288
|
+
end
|
289
|
+
|
290
|
+
output << sep
|
291
|
+
output << prefix_tag_option(key, k, v, escape)
|
292
|
+
end
|
293
|
+
elsif type == :boolean
|
294
|
+
if value
|
295
|
+
output << sep
|
296
|
+
output << boolean_tag_option(key)
|
297
|
+
end
|
298
|
+
elsif !value.nil?
|
299
|
+
output << sep
|
300
|
+
output << tag_option(key, value, escape)
|
301
|
+
end
|
302
|
+
end
|
303
|
+
output unless output.empty?
|
304
|
+
end
|
305
|
+
|
306
|
+
def boolean_tag_option(key)
|
307
|
+
%(#{key}="#{key}")
|
308
|
+
end
|
309
|
+
|
310
|
+
def tag_option(key, value, escape)
|
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
|
321
|
+
end
|
322
|
+
value = value.gsub('"', """) if value.include?('"')
|
323
|
+
|
324
|
+
%(#{key}="#{value}")
|
325
|
+
end
|
326
|
+
|
327
|
+
private
|
328
|
+
def prefix_tag_option(prefix, key, value, escape)
|
329
|
+
key = "#{prefix}-#{key.to_s.dasherize}"
|
330
|
+
unless value.is_a?(String) || value.is_a?(Symbol) || value.is_a?(BigDecimal)
|
331
|
+
value = value.to_json
|
332
|
+
end
|
333
|
+
tag_option(key, value, escape)
|
334
|
+
end
|
335
|
+
|
336
|
+
def respond_to_missing?(*args)
|
337
|
+
true
|
338
|
+
end
|
339
|
+
|
340
|
+
def method_missing(called, *args, escape: true, **options, &block)
|
341
|
+
name = called.name.dasherize
|
342
|
+
|
343
|
+
TagHelper.ensure_valid_html5_tag_name(name)
|
344
|
+
|
345
|
+
tag_string(name, *args, options, escape: escape, &block)
|
346
|
+
end
|
347
|
+
end
|
348
|
+
|
349
|
+
# Returns an HTML tag.
|
350
|
+
#
|
351
|
+
# === Building HTML tags
|
352
|
+
#
|
353
|
+
# Builds HTML5 compliant tags with a tag proxy. Every tag can be built with:
|
354
|
+
#
|
355
|
+
# tag.<tag name>(optional content, options)
|
356
|
+
#
|
357
|
+
# where tag name can be e.g. br, div, section, article, or any tag really.
|
358
|
+
#
|
359
|
+
# ==== Passing content
|
360
|
+
#
|
361
|
+
# Tags can pass content to embed within it:
|
362
|
+
#
|
363
|
+
# tag.h1 'All titles fit to print' # => <h1>All titles fit to print</h1>
|
364
|
+
#
|
365
|
+
# tag.div tag.p('Hello world!') # => <div><p>Hello world!</p></div>
|
366
|
+
#
|
367
|
+
# Content can also be captured with a block, which is useful in templates:
|
368
|
+
#
|
369
|
+
# <%= tag.p do %>
|
370
|
+
# The next great American novel starts here.
|
371
|
+
# <% end %>
|
372
|
+
# # => <p>The next great American novel starts here.</p>
|
373
|
+
#
|
374
|
+
# ==== Options
|
375
|
+
#
|
376
|
+
# Use symbol keyed options to add attributes to the generated tag.
|
377
|
+
#
|
378
|
+
# tag.section class: %w( kitties puppies )
|
379
|
+
# # => <section class="kitties puppies"></section>
|
380
|
+
#
|
381
|
+
# tag.section id: dom_id(@post)
|
382
|
+
# # => <section id="<generated dom id>"></section>
|
383
|
+
#
|
384
|
+
# Pass +true+ for any attributes that can render with no values, like +disabled+ and +readonly+.
|
385
|
+
#
|
386
|
+
# tag.input type: 'text', disabled: true
|
387
|
+
# # => <input type="text" disabled="disabled">
|
388
|
+
#
|
389
|
+
# HTML5 <tt>data-*</tt> and <tt>aria-*</tt> attributes can be set with a
|
390
|
+
# single +data+ or +aria+ key pointing to a hash of sub-attributes.
|
391
|
+
#
|
392
|
+
# To play nicely with JavaScript conventions, sub-attributes are dasherized.
|
393
|
+
#
|
394
|
+
# tag.article data: { user_id: 123 }
|
395
|
+
# # => <article data-user-id="123"></article>
|
396
|
+
#
|
397
|
+
# Thus <tt>data-user-id</tt> can be accessed as <tt>dataset.userId</tt>.
|
398
|
+
#
|
399
|
+
# Data attribute values are encoded to JSON, with the exception of strings, symbols, and
|
400
|
+
# BigDecimals.
|
401
|
+
# This may come in handy when using jQuery's HTML5-aware <tt>.data()</tt>
|
402
|
+
# from 1.4.3.
|
403
|
+
#
|
404
|
+
# tag.div data: { city_state: %w( Chicago IL ) }
|
405
|
+
# # => <div data-city-state="["Chicago","IL"]"></div>
|
406
|
+
#
|
407
|
+
# The generated tag names and attributes are escaped by default. This can be disabled using
|
408
|
+
# +escape+.
|
409
|
+
#
|
410
|
+
# tag.img src: 'open & shut.png'
|
411
|
+
# # => <img src="open & shut.png">
|
412
|
+
#
|
413
|
+
# tag.img src: 'open & shut.png', escape: false
|
414
|
+
# # => <img src="open & shut.png">
|
415
|
+
#
|
416
|
+
# The tag builder respects
|
417
|
+
# {HTML5 void elements}[https://www.w3.org/TR/html5/syntax.html#void-elements]
|
418
|
+
# if no content is passed, and omits closing tags for those elements.
|
419
|
+
#
|
420
|
+
# # A standard element:
|
421
|
+
# tag.div # => <div></div>
|
422
|
+
#
|
423
|
+
# # A void element:
|
424
|
+
# tag.br # => <br>
|
425
|
+
#
|
426
|
+
# Note that when using the block form options should be wrapped in
|
427
|
+
# parenthesis.
|
428
|
+
#
|
429
|
+
# <%= tag.a(href: "/about", class: "font-bold") do %>
|
430
|
+
# About the author
|
431
|
+
# <% end %>
|
432
|
+
# # => <a href="/about" class="font-bold">About the author</a>
|
433
|
+
#
|
434
|
+
# === Building HTML attributes
|
435
|
+
#
|
436
|
+
# Transforms a Hash into HTML attributes, ready to be interpolated into
|
437
|
+
# ERB. Includes or omits boolean attributes based on their truthiness.
|
438
|
+
# Transforms keys nested within
|
439
|
+
# <tt>aria:</tt> or <tt>data:</tt> objects into <tt>aria-</tt> and <tt>data-</tt>
|
440
|
+
# prefixed attributes:
|
441
|
+
#
|
442
|
+
# <input <%= tag.attributes(type: :text, aria: { label: "Search" }) %>>
|
443
|
+
# # => <input type="text" aria-label="Search">
|
444
|
+
#
|
445
|
+
# <button <%= tag.attributes id: "call-to-action", disabled: false, aria: { expanded: false } %> class="primary">Get Started!</button>
|
446
|
+
# # => <button id="call-to-action" aria-expanded="false" class="primary">Get Started!</button>
|
447
|
+
#
|
448
|
+
# === Legacy syntax
|
449
|
+
#
|
450
|
+
# The following format is for legacy syntax support. It will be deprecated in future versions of \Rails.
|
451
|
+
#
|
452
|
+
# tag(name, options = nil, open = false, escape = true)
|
453
|
+
#
|
454
|
+
# It returns an empty HTML tag of type +name+ which by default is XHTML
|
455
|
+
# compliant. Set +open+ to true to create an open tag compatible
|
456
|
+
# with HTML 4.0 and below. Add HTML attributes by passing an attributes
|
457
|
+
# hash to +options+. Set +escape+ to false to disable attribute value
|
458
|
+
# escaping.
|
459
|
+
#
|
460
|
+
# ==== Options
|
461
|
+
#
|
462
|
+
# You can use symbols or strings for the attribute names.
|
463
|
+
#
|
464
|
+
# Use +true+ with boolean attributes that can render with no value, like
|
465
|
+
# +disabled+ and +readonly+.
|
466
|
+
#
|
467
|
+
# HTML5 <tt>data-*</tt> attributes can be set with a single +data+ key
|
468
|
+
# pointing to a hash of sub-attributes.
|
469
|
+
#
|
470
|
+
# ==== Examples
|
471
|
+
#
|
472
|
+
# tag("br")
|
473
|
+
# # => <br />
|
474
|
+
#
|
475
|
+
# tag("br", nil, true)
|
476
|
+
# # => <br>
|
477
|
+
#
|
478
|
+
# tag("input", type: 'text', disabled: true)
|
479
|
+
# # => <input type="text" disabled="disabled" />
|
480
|
+
#
|
481
|
+
# tag("input", type: 'text', class: ["strong", "highlight"])
|
482
|
+
# # => <input class="strong highlight" type="text" />
|
483
|
+
#
|
484
|
+
# tag("img", src: "open & shut.png")
|
485
|
+
# # => <img src="open & shut.png" />
|
486
|
+
#
|
487
|
+
# tag("img", { src: "open & shut.png" }, false, false)
|
488
|
+
# # => <img src="open & shut.png" />
|
489
|
+
#
|
490
|
+
# tag("div", data: { name: 'Stephen', city_state: %w(Chicago IL) })
|
491
|
+
# # => <div data-name="Stephen" data-city-state="["Chicago","IL"]" />
|
492
|
+
#
|
493
|
+
# tag("div", class: { highlight: current_user.admin? })
|
494
|
+
# # => <div class="highlight" />
|
495
|
+
def tag(name = nil, options = nil, open = false, escape = true)
|
496
|
+
if name.nil?
|
497
|
+
tag_builder
|
498
|
+
else
|
499
|
+
ensure_valid_html5_tag_name(name)
|
500
|
+
"<#{name}#{tag_builder.tag_options(options, escape) if options}#{open ? ">" : " />"}".html_safe
|
501
|
+
end
|
502
|
+
end
|
503
|
+
|
504
|
+
# Returns an HTML block tag of type +name+ surrounding the +content+. Add
|
505
|
+
# HTML attributes by passing an attributes hash to +options+.
|
506
|
+
# Instead of passing the content as an argument, you can also use a block
|
507
|
+
# in which case, you pass your +options+ as the second parameter.
|
508
|
+
# Set escape to false to disable escaping.
|
509
|
+
# Note: this is legacy syntax, see +tag+ method description for details.
|
510
|
+
#
|
511
|
+
# ==== Options
|
512
|
+
# The +options+ hash can be used with attributes with no value like (<tt>disabled</tt> and
|
513
|
+
# <tt>readonly</tt>), which you can give a value of true in the +options+ hash. You can use
|
514
|
+
# symbols or strings for the attribute names.
|
515
|
+
#
|
516
|
+
# ==== Examples
|
517
|
+
# content_tag(:p, "Hello world!")
|
518
|
+
# # => <p>Hello world!</p>
|
519
|
+
# content_tag(:div, content_tag(:p, "Hello world!"), class: "strong")
|
520
|
+
# # => <div class="strong"><p>Hello world!</p></div>
|
521
|
+
# content_tag(:div, "Hello world!", class: ["strong", "highlight"])
|
522
|
+
# # => <div class="strong highlight">Hello world!</div>
|
523
|
+
# content_tag(:div, "Hello world!", class: ["strong", { highlight: current_user.admin? }])
|
524
|
+
# # => <div class="strong highlight">Hello world!</div>
|
525
|
+
# content_tag("select", options, multiple: true)
|
526
|
+
# # => <select multiple="multiple">...options...</select>
|
527
|
+
#
|
528
|
+
# <%= content_tag :div, class: "strong" do -%>
|
529
|
+
# Hello world!
|
530
|
+
# <% end -%>
|
531
|
+
# # => <div class="strong">Hello world!</div>
|
532
|
+
def content_tag(name, content_or_options_with_block = nil, options = nil, escape = true, &block)
|
533
|
+
ensure_valid_html5_tag_name(name)
|
534
|
+
|
535
|
+
if block_given?
|
536
|
+
options = content_or_options_with_block if content_or_options_with_block.is_a?(Hash)
|
537
|
+
tag_builder.content_tag_string(name, capture(&block), options, escape)
|
538
|
+
else
|
539
|
+
tag_builder.content_tag_string(name, content_or_options_with_block, options, escape)
|
540
|
+
end
|
541
|
+
end
|
542
|
+
|
543
|
+
# Returns a string of tokens built from +args+.
|
544
|
+
#
|
545
|
+
# ==== Examples
|
546
|
+
# token_list("foo", "bar")
|
547
|
+
# # => "foo bar"
|
548
|
+
# token_list("foo", "foo bar")
|
549
|
+
# # => "foo bar"
|
550
|
+
# token_list({ foo: true, bar: false })
|
551
|
+
# # => "foo"
|
552
|
+
# token_list(nil, false, 123, "", "foo", { bar: true })
|
553
|
+
# # => "123 foo bar"
|
554
|
+
def token_list(*args)
|
555
|
+
tokens = build_tag_values(*args).flat_map { |value| CGI.unescape_html(value.to_s).split(/\s+/) }.uniq
|
556
|
+
|
557
|
+
safe_join(tokens, " ")
|
558
|
+
end
|
559
|
+
alias_method :class_names, :token_list
|
560
|
+
|
561
|
+
# Returns a CDATA section with the given +content+. CDATA sections
|
562
|
+
# are used to escape blocks of text containing characters which would
|
563
|
+
# otherwise be recognized as markup. CDATA sections begin with the string
|
564
|
+
# <tt><![CDATA[</tt> and end with (and may not contain) the string <tt>]]></tt>.
|
565
|
+
#
|
566
|
+
# cdata_section("<hello world>")
|
567
|
+
# # => <![CDATA[<hello world>]]>
|
568
|
+
#
|
569
|
+
# cdata_section(File.read("hello_world.txt"))
|
570
|
+
# # => <![CDATA[<hello from a text file]]>
|
571
|
+
#
|
572
|
+
# cdata_section("hello]]>world")
|
573
|
+
# # => <![CDATA[hello]]]]><![CDATA[>world]]>
|
574
|
+
def cdata_section(content)
|
575
|
+
splitted = content.to_s.gsub(/\]\]>/, "]]]]><![CDATA[>")
|
576
|
+
"<![CDATA[#{splitted}]]>".html_safe
|
577
|
+
end
|
578
|
+
|
579
|
+
# Returns an escaped version of +html+ without affecting existing escaped entities.
|
580
|
+
#
|
581
|
+
# escape_once("1 < 2 & 3")
|
582
|
+
# # => "1 < 2 & 3"
|
583
|
+
#
|
584
|
+
# escape_once("<< Accept & Checkout")
|
585
|
+
# # => "<< Accept & Checkout"
|
586
|
+
def escape_once(html)
|
587
|
+
ERB::Util.html_escape_once(html)
|
588
|
+
end
|
589
|
+
|
590
|
+
private
|
591
|
+
def ensure_valid_html5_tag_name(name)
|
592
|
+
raise ArgumentError, "Invalid HTML5 tag name: #{name.inspect}" unless /\A[a-zA-Z][^\s\/>]*\z/.match?(name)
|
593
|
+
end
|
594
|
+
module_function :ensure_valid_html5_tag_name
|
595
|
+
|
596
|
+
def build_tag_values(*args)
|
597
|
+
tag_values = []
|
598
|
+
|
599
|
+
args.each do |tag_value|
|
600
|
+
case tag_value
|
601
|
+
when Hash
|
602
|
+
tag_value.each do |key, val|
|
603
|
+
tag_values << key.to_s if val && key.present?
|
604
|
+
end
|
605
|
+
when Array
|
606
|
+
tag_values.concat build_tag_values(*tag_value)
|
607
|
+
else
|
608
|
+
tag_values << tag_value.to_s if tag_value.present?
|
609
|
+
end
|
610
|
+
end
|
611
|
+
|
612
|
+
tag_values
|
613
|
+
end
|
614
|
+
module_function :build_tag_values
|
615
|
+
|
616
|
+
def tag_builder
|
617
|
+
@tag_builder ||= TagBuilder.new(self)
|
618
|
+
end
|
619
|
+
end
|
620
|
+
end
|
621
|
+
end
|