view_component 3.23.2 → 4.0.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/app/controllers/concerns/view_component/preview_actions.rb +11 -14
- data/app/controllers/view_components_system_test_controller.rb +15 -20
- data/app/views/test_mailer/test_asset_email.html.erb +1 -0
- data/app/views/test_mailer/test_url_email.html.erb +1 -0
- data/app/views/view_components/preview.html.erb +1 -9
- data/docs/CHANGELOG.md +415 -0
- data/lib/{rails/generators → generators/view_component}/abstract_generator.rb +2 -2
- data/lib/{rails/generators → generators/view_component}/component/component_generator.rb +16 -3
- data/lib/{rails/generators → generators/view_component}/component/templates/component.rb.tt +6 -1
- data/lib/{rails/generators/erb/component_generator.rb → generators/view_component/erb/erb_generator.rb} +4 -3
- data/lib/{rails/generators/haml/component_generator.rb → generators/view_component/haml/haml_generator.rb} +3 -3
- data/lib/{rails/generators/locale/component_generator.rb → generators/view_component/locale/locale_generator.rb} +3 -3
- data/lib/{rails/generators/preview/component_generator.rb → generators/view_component/preview/preview_generator.rb} +3 -3
- data/lib/{rails/generators/rspec/component_generator.rb → generators/view_component/rspec/rspec_generator.rb} +3 -3
- data/lib/{rails/generators/slim/component_generator.rb → generators/view_component/slim/slim_generator.rb} +3 -3
- data/lib/{rails/generators/stimulus/component_generator.rb → generators/view_component/stimulus/stimulus_generator.rb} +3 -3
- data/lib/generators/view_component/tailwindcss/tailwindcss_generator.rb +11 -0
- data/lib/{rails/generators/test_unit/component_generator.rb → generators/view_component/test_unit/test_unit_generator.rb} +2 -2
- data/lib/view_component/base.rb +160 -155
- data/lib/view_component/collection.rb +19 -25
- data/lib/view_component/compiler.rb +52 -79
- data/lib/view_component/config.rb +51 -85
- data/lib/view_component/configurable.rb +1 -1
- data/lib/view_component/deprecation.rb +1 -1
- data/lib/view_component/engine.rb +37 -107
- data/lib/view_component/errors.rb +16 -34
- data/lib/view_component/inline_template.rb +3 -4
- data/lib/view_component/instrumentation.rb +4 -10
- data/lib/view_component/preview.rb +4 -11
- data/lib/view_component/request_details.rb +30 -0
- data/lib/view_component/slot.rb +6 -13
- data/lib/view_component/slotable.rb +82 -77
- data/lib/view_component/system_spec_helpers.rb +11 -0
- data/lib/view_component/system_test_helpers.rb +1 -2
- data/lib/view_component/template.rb +106 -83
- data/lib/view_component/test_helpers.rb +37 -44
- data/lib/view_component/translatable.rb +33 -32
- data/lib/view_component/version.rb +3 -3
- data/lib/view_component.rb +8 -6
- metadata +31 -559
- data/app/assets/vendor/prism.css +0 -4
- data/app/assets/vendor/prism.min.js +0 -12
- data/app/helpers/preview_helper.rb +0 -85
- data/app/views/view_components/_preview_source.html.erb +0 -17
- data/lib/rails/generators/tailwindcss/component_generator.rb +0 -11
- data/lib/view_component/capture_compatibility.rb +0 -44
- data/lib/view_component/component_error.rb +0 -6
- data/lib/view_component/rails/tasks/view_component.rake +0 -20
- data/lib/view_component/render_component_helper.rb +0 -10
- data/lib/view_component/render_component_to_string_helper.rb +0 -9
- data/lib/view_component/render_monkey_patch.rb +0 -13
- data/lib/view_component/render_to_string_monkey_patch.rb +0 -13
- data/lib/view_component/rendering_component_helper.rb +0 -9
- data/lib/view_component/rendering_monkey_patch.rb +0 -13
- data/lib/view_component/slotable_default.rb +0 -20
- data/lib/view_component/use_helpers.rb +0 -42
- /data/lib/{rails/generators → generators/view_component}/erb/templates/component.html.erb.tt +0 -0
- /data/lib/{rails/generators → generators/view_component}/haml/templates/component.html.haml.tt +0 -0
- /data/lib/{rails/generators → generators/view_component}/preview/templates/component_preview.rb.tt +0 -0
- /data/lib/{rails/generators → generators/view_component}/rspec/templates/component_spec.rb.tt +0 -0
- /data/lib/{rails/generators → generators/view_component}/slim/templates/component.html.slim.tt +0 -0
- /data/lib/{rails/generators → generators/view_component}/stimulus/templates/component_controller.js.tt +0 -0
- /data/lib/{rails/generators → generators/view_component}/stimulus/templates/component_controller.ts.tt +0 -0
- /data/lib/{rails/generators → generators/view_component}/tailwindcss/templates/component.html.erb.tt +0 -0
- /data/lib/{rails/generators → generators/view_component}/test_unit/templates/component_test.rb.tt +0 -0
data/lib/view_component/base.rb
CHANGED
@@ -9,15 +9,27 @@ require "view_component/config"
|
|
9
9
|
require "view_component/errors"
|
10
10
|
require "view_component/inline_template"
|
11
11
|
require "view_component/preview"
|
12
|
+
require "view_component/request_details"
|
12
13
|
require "view_component/slotable"
|
13
|
-
require "view_component/slotable_default"
|
14
14
|
require "view_component/template"
|
15
15
|
require "view_component/translatable"
|
16
16
|
require "view_component/with_content_helper"
|
17
|
-
|
17
|
+
|
18
|
+
module ActionView
|
19
|
+
class OutputBuffer
|
20
|
+
def with_buffer(buf = nil)
|
21
|
+
new_buffer = buf || +""
|
22
|
+
old_buffer, @raw_buffer = @raw_buffer, new_buffer
|
23
|
+
yield
|
24
|
+
new_buffer
|
25
|
+
ensure
|
26
|
+
@raw_buffer = old_buffer
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
18
30
|
|
19
31
|
module ViewComponent
|
20
|
-
class Base
|
32
|
+
class Base
|
21
33
|
class << self
|
22
34
|
delegate(*ViewComponent::Config.defaults.keys, to: :config)
|
23
35
|
|
@@ -34,26 +46,33 @@ module ViewComponent
|
|
34
46
|
end
|
35
47
|
end
|
36
48
|
|
49
|
+
include ActionView::Helpers
|
50
|
+
include Rails.application.routes.url_helpers if defined?(Rails) && Rails.application
|
51
|
+
include ERB::Escape
|
52
|
+
include ActiveSupport::CoreExt::ERBUtil
|
53
|
+
|
37
54
|
include ViewComponent::InlineTemplate
|
38
|
-
include ViewComponent::UseHelpers
|
39
55
|
include ViewComponent::Slotable
|
40
56
|
include ViewComponent::Translatable
|
41
57
|
include ViewComponent::WithContentHelper
|
42
58
|
|
43
|
-
RESERVED_PARAMETER = :content
|
44
|
-
VC_INTERNAL_DEFAULT_FORMAT = :html
|
45
|
-
|
46
59
|
# For CSRF authenticity tokens in forms
|
47
60
|
delegate :form_authenticity_token, :protect_against_forgery?, :config, to: :helpers
|
48
61
|
|
62
|
+
# HTML construction methods
|
63
|
+
delegate :output_buffer, :lookup_context, :view_renderer, :view_flow, to: :helpers
|
64
|
+
|
65
|
+
# For Turbo::StreamsHelper
|
66
|
+
delegate :formats, :formats=, to: :helpers
|
67
|
+
|
49
68
|
# For Content Security Policy nonces
|
50
69
|
delegate :content_security_policy_nonce, to: :helpers
|
51
70
|
|
52
71
|
# Config option that strips trailing whitespace in templates before compiling them.
|
53
|
-
class_attribute :__vc_strip_trailing_whitespace, instance_accessor: false, instance_predicate: false
|
54
|
-
self.__vc_strip_trailing_whitespace = false # class_attribute:default doesn't work until Rails 5.2
|
72
|
+
class_attribute :__vc_strip_trailing_whitespace, instance_accessor: false, instance_predicate: false, default: false
|
55
73
|
|
56
74
|
attr_accessor :__vc_original_view_context
|
75
|
+
attr_reader :current_template
|
57
76
|
|
58
77
|
# Components render in their own view context. Helpers and other functionality
|
59
78
|
# require a reference to the original Rails view context, an instance of
|
@@ -65,7 +84,14 @@ module ViewComponent
|
|
65
84
|
# @param view_context [ActionView::Base] The original view context.
|
66
85
|
# @return [void]
|
67
86
|
def set_original_view_context(view_context)
|
68
|
-
|
87
|
+
# noop
|
88
|
+
end
|
89
|
+
|
90
|
+
using RequestDetails
|
91
|
+
|
92
|
+
# Including `Rails.application.routes.url_helpers` defines an initializer that accepts (...),
|
93
|
+
# so we have to define our own empty initializer to overwrite it.
|
94
|
+
def initialize
|
69
95
|
end
|
70
96
|
|
71
97
|
# Entrypoint for rendering components.
|
@@ -77,31 +103,28 @@ module ViewComponent
|
|
77
103
|
#
|
78
104
|
# @return [String]
|
79
105
|
def render_in(view_context, &block)
|
80
|
-
self.class.
|
106
|
+
self.class.__vc_compile(raise_errors: true)
|
81
107
|
|
82
108
|
@view_context = view_context
|
109
|
+
@old_virtual_path = view_context.instance_variable_get(:@virtual_path)
|
83
110
|
self.__vc_original_view_context ||= view_context
|
84
111
|
|
85
|
-
@output_buffer =
|
112
|
+
@output_buffer = view_context.output_buffer
|
86
113
|
|
87
114
|
@lookup_context ||= view_context.lookup_context
|
88
115
|
|
89
|
-
# required for path helpers in older Rails versions
|
90
|
-
@view_renderer ||= view_context.view_renderer
|
91
|
-
|
92
116
|
# For content_for
|
93
117
|
@view_flow ||= view_context.view_flow
|
94
118
|
|
95
119
|
# For i18n
|
96
120
|
@virtual_path ||= virtual_path
|
97
121
|
|
98
|
-
#
|
99
|
-
@
|
122
|
+
# Describes the inferred request constraints (locales, formats, variants)
|
123
|
+
@__vc_requested_details ||= @lookup_context.vc_requested_details
|
100
124
|
|
101
125
|
# For caching, such as #cache_if
|
102
126
|
@current_template = nil unless defined?(@current_template)
|
103
127
|
old_current_template = @current_template
|
104
|
-
@current_template = self
|
105
128
|
|
106
129
|
if block && defined?(@__vc_content_set_by_with_content)
|
107
130
|
raise DuplicateContentError.new(self.class.name)
|
@@ -113,18 +136,35 @@ module ViewComponent
|
|
113
136
|
before_render
|
114
137
|
|
115
138
|
if render?
|
116
|
-
|
139
|
+
value = nil
|
140
|
+
|
141
|
+
@output_buffer.with_buffer do
|
142
|
+
@view_context.instance_variable_set(:@virtual_path, virtual_path)
|
143
|
+
|
144
|
+
rendered_template =
|
145
|
+
around_render do
|
146
|
+
render_template_for(@__vc_requested_details).to_s
|
147
|
+
end
|
148
|
+
|
149
|
+
# Avoid allocating new string when output_preamble and output_postamble are blank
|
150
|
+
value = if output_preamble.blank? && output_postamble.blank?
|
151
|
+
rendered_template
|
152
|
+
else
|
153
|
+
__vc_safe_output_preamble + rendered_template + __vc_safe_output_postamble
|
154
|
+
end
|
155
|
+
end
|
117
156
|
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
else
|
122
|
-
safe_output_preamble + rendered_template + safe_output_postamble
|
157
|
+
if ActionView::Base.annotate_rendered_view_with_filenames && current_template.inline_call? && request&.format == :html
|
158
|
+
identifier = defined?(Rails.root) ? self.class.identifier.sub("#{Rails.root}/", "") : self.class.identifier
|
159
|
+
value = "<!-- BEGIN #{identifier} -->".html_safe + value + "<!-- END #{identifier} -->".html_safe
|
123
160
|
end
|
161
|
+
|
162
|
+
value
|
124
163
|
else
|
125
164
|
""
|
126
165
|
end
|
127
166
|
ensure
|
167
|
+
view_context.instance_variable_set(:@virtual_path, @old_virtual_path)
|
128
168
|
@current_template = old_current_template
|
129
169
|
end
|
130
170
|
|
@@ -161,7 +201,7 @@ module ViewComponent
|
|
161
201
|
target_render = self.class.instance_variable_get(:@__vc_ancestor_calls)[@__vc_parent_render_level]
|
162
202
|
@__vc_parent_render_level += 1
|
163
203
|
|
164
|
-
target_render.bind_call(self, @
|
204
|
+
target_render.bind_call(self, @__vc_requested_details)
|
165
205
|
ensure
|
166
206
|
@__vc_parent_render_level -= 1
|
167
207
|
end
|
@@ -189,6 +229,14 @@ module ViewComponent
|
|
189
229
|
# noop
|
190
230
|
end
|
191
231
|
|
232
|
+
# Called around rendering the component. Override to wrap the rendering of a
|
233
|
+
# component in custom instrumentation, etc.
|
234
|
+
#
|
235
|
+
# @return [void]
|
236
|
+
def around_render
|
237
|
+
yield
|
238
|
+
end
|
239
|
+
|
192
240
|
# Override to determine whether the ViewComponent should render.
|
193
241
|
#
|
194
242
|
# @return [Boolean]
|
@@ -196,23 +244,17 @@ module ViewComponent
|
|
196
244
|
true
|
197
245
|
end
|
198
246
|
|
199
|
-
# Override the ActionView::Base initializer so that components
|
200
|
-
# do not need to define their own initializers.
|
201
|
-
# @private
|
202
|
-
def initialize(*)
|
203
|
-
end
|
204
|
-
|
205
247
|
# Re-use original view_context if we're not rendering a component.
|
206
248
|
#
|
207
249
|
# This prevents an exception when rendering a partial inside of a component that has also been rendered outside
|
208
250
|
# of the component. This is due to the partials compiled template method existing in the parent `view_context`,
|
209
|
-
#
|
251
|
+
# and not the component's `view_context`.
|
210
252
|
#
|
211
253
|
# @private
|
212
254
|
def render(options = {}, args = {}, &block)
|
213
255
|
if options.respond_to?(:set_original_view_context)
|
214
256
|
options.set_original_view_context(self.__vc_original_view_context)
|
215
|
-
|
257
|
+
@view_context.render(options, args, &block)
|
216
258
|
else
|
217
259
|
__vc_original_view_context.render(options, args, &block)
|
218
260
|
end
|
@@ -274,11 +316,12 @@ module ViewComponent
|
|
274
316
|
[]
|
275
317
|
end
|
276
318
|
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
319
|
+
if Rails::VERSION::MAJOR == 7 && Rails::VERSION::MINOR == 1
|
320
|
+
# Rails expects us to define `format` on all renderables,
|
321
|
+
# but we do not know the `format` of a ViewComponent until runtime.
|
322
|
+
def format
|
323
|
+
nil
|
324
|
+
end
|
282
325
|
end
|
283
326
|
|
284
327
|
# The current request. Use sparingly as doing so introduces coupling that
|
@@ -289,10 +332,9 @@ module ViewComponent
|
|
289
332
|
__vc_request
|
290
333
|
end
|
291
334
|
|
292
|
-
# Enables consumers to override request/@request
|
293
|
-
#
|
294
335
|
# @private
|
295
336
|
def __vc_request
|
337
|
+
# The current request (if present, as mailers/jobs/etc do not have a request)
|
296
338
|
@__vc_request ||= controller.request if controller.respond_to?(:request)
|
297
339
|
end
|
298
340
|
|
@@ -305,7 +347,9 @@ module ViewComponent
|
|
305
347
|
|
306
348
|
@__vc_content =
|
307
349
|
if __vc_render_in_block_provided?
|
308
|
-
|
350
|
+
with_original_virtual_path do
|
351
|
+
view_context.capture(self, &@__vc_render_in_block)
|
352
|
+
end
|
309
353
|
elsif __vc_content_set_by_with_content_defined?
|
310
354
|
@__vc_content_set_by_with_content
|
311
355
|
end
|
@@ -318,6 +362,14 @@ module ViewComponent
|
|
318
362
|
__vc_render_in_block_provided? || __vc_content_set_by_with_content_defined?
|
319
363
|
end
|
320
364
|
|
365
|
+
# @private
|
366
|
+
def with_original_virtual_path
|
367
|
+
@view_context.instance_variable_set(:@virtual_path, @old_virtual_path)
|
368
|
+
yield
|
369
|
+
ensure
|
370
|
+
@view_context.instance_variable_set(:@virtual_path, virtual_path)
|
371
|
+
end
|
372
|
+
|
321
373
|
private
|
322
374
|
|
323
375
|
attr_reader :view_context
|
@@ -330,12 +382,8 @@ module ViewComponent
|
|
330
382
|
defined?(@__vc_content_set_by_with_content)
|
331
383
|
end
|
332
384
|
|
333
|
-
def
|
334
|
-
|
335
|
-
end
|
336
|
-
|
337
|
-
def maybe_escape_html(text)
|
338
|
-
return text if __vc_request && !__vc_request.format.html?
|
385
|
+
def __vc_maybe_escape_html(text)
|
386
|
+
return text if @current_template && !@current_template.html?
|
339
387
|
return text if text.blank?
|
340
388
|
|
341
389
|
if text.html_safe?
|
@@ -346,54 +394,18 @@ module ViewComponent
|
|
346
394
|
end
|
347
395
|
end
|
348
396
|
|
349
|
-
def
|
350
|
-
|
397
|
+
def __vc_safe_output_preamble
|
398
|
+
__vc_maybe_escape_html(output_preamble) do
|
351
399
|
Kernel.warn("WARNING: The #{self.class} component was provided an HTML-unsafe preamble. The preamble will be automatically escaped, but you may want to investigate.")
|
352
400
|
end
|
353
401
|
end
|
354
402
|
|
355
|
-
def
|
356
|
-
|
403
|
+
def __vc_safe_output_postamble
|
404
|
+
__vc_maybe_escape_html(output_postamble) do
|
357
405
|
Kernel.warn("WARNING: The #{self.class} component was provided an HTML-unsafe postamble. The postamble will be automatically escaped, but you may want to investigate.")
|
358
406
|
end
|
359
407
|
end
|
360
408
|
|
361
|
-
# Set the controller used for testing components:
|
362
|
-
#
|
363
|
-
# ```ruby
|
364
|
-
# config.view_component.test_controller = "MyTestController"
|
365
|
-
# ```
|
366
|
-
#
|
367
|
-
# Defaults to `nil`. If this is falsy, `"ApplicationController"` is used. Can also be
|
368
|
-
# configured on a per-test basis using `with_controller_class`.
|
369
|
-
#
|
370
|
-
|
371
|
-
# Set if render monkey patches should be included or not in Rails <6.1:
|
372
|
-
#
|
373
|
-
# ```ruby
|
374
|
-
# config.view_component.render_monkey_patch_enabled = false
|
375
|
-
# ```
|
376
|
-
#
|
377
|
-
|
378
|
-
# Path for component files
|
379
|
-
#
|
380
|
-
# ```ruby
|
381
|
-
# config.view_component.view_component_path = "app/my_components"
|
382
|
-
# ```
|
383
|
-
#
|
384
|
-
# Defaults to `nil`. If this is falsy, `app/components` is used.
|
385
|
-
#
|
386
|
-
|
387
|
-
# Parent class for generated components
|
388
|
-
#
|
389
|
-
# ```ruby
|
390
|
-
# config.view_component.component_parent_class = "MyBaseComponent"
|
391
|
-
# ```
|
392
|
-
#
|
393
|
-
# Defaults to nil. If this is falsy, generators will use
|
394
|
-
# "ApplicationComponent" if defined, "ViewComponent::Base" otherwise.
|
395
|
-
#
|
396
|
-
|
397
409
|
# Configuration for generators.
|
398
410
|
#
|
399
411
|
# All options under this namespace default to `false` unless otherwise
|
@@ -451,6 +463,18 @@ module ViewComponent
|
|
451
463
|
# ```
|
452
464
|
#
|
453
465
|
# Defaults to `false`.
|
466
|
+
#
|
467
|
+
# #### ßparent_class
|
468
|
+
#
|
469
|
+
# Parent class for generated components
|
470
|
+
#
|
471
|
+
# ```ruby
|
472
|
+
# config.view_component.generate.parent_class = "MyBaseComponent"
|
473
|
+
# ```
|
474
|
+
#
|
475
|
+
# Defaults to nil. If this is falsy, generators will use
|
476
|
+
# "ApplicationComponent" if defined, "ViewComponent::Base" otherwise.
|
477
|
+
#
|
454
478
|
|
455
479
|
class << self
|
456
480
|
# The file path of the component Ruby file.
|
@@ -519,50 +543,43 @@ module ViewComponent
|
|
519
543
|
Collection.new(self, collection, spacer_component, **args)
|
520
544
|
end
|
521
545
|
|
546
|
+
# @private
|
547
|
+
def __vc_compile(raise_errors: false, force: false)
|
548
|
+
__vc_compiler.compile(raise_errors: raise_errors, force: force)
|
549
|
+
end
|
550
|
+
|
522
551
|
# @private
|
523
552
|
def inherited(child)
|
524
553
|
# Compile so child will inherit compiled `call_*` template methods that
|
525
554
|
# `compile` defines
|
526
|
-
|
555
|
+
__vc_compile
|
527
556
|
|
528
557
|
# Give the child its own personal #render_template_for to protect against the case when
|
529
558
|
# eager loading is disabled and the parent component is rendered before the child. In
|
530
559
|
# such a scenario, the parent will override ViewComponent::Base#render_template_for,
|
531
560
|
# meaning it will not be called for any children and thus not compile their templates.
|
532
|
-
if !child.instance_methods(false).include?(:render_template_for) && !child.
|
561
|
+
if !child.instance_methods(false).include?(:render_template_for) && !child.__vc_compiled?
|
533
562
|
child.class_eval <<~RUBY, __FILE__, __LINE__ + 1
|
534
|
-
def render_template_for(
|
563
|
+
def render_template_for(requested_details)
|
535
564
|
# Force compilation here so the compiler always redefines render_template_for.
|
536
565
|
# This is mostly a safeguard to prevent infinite recursion.
|
537
|
-
self.class.
|
538
|
-
# .
|
539
|
-
render_template_for(
|
566
|
+
self.class.__vc_compile(raise_errors: true, force: true)
|
567
|
+
# .__vc_compile replaces this method; call the new one
|
568
|
+
render_template_for(requested_details)
|
540
569
|
end
|
541
570
|
RUBY
|
542
571
|
end
|
543
572
|
|
544
|
-
# If Rails application is loaded, add application url_helpers to the component context
|
545
|
-
# we need to check this to use this gem as a dependency
|
546
|
-
if defined?(Rails) && Rails.application && !(child < Rails.application.routes.url_helpers)
|
547
|
-
child.include Rails.application.routes.url_helpers
|
548
|
-
end
|
549
|
-
|
550
573
|
# Derive the source location of the component Ruby file from the call stack.
|
551
574
|
# We need to ignore `inherited` frames here as they indicate that `inherited`
|
552
575
|
# has been re-defined by the consuming application, likely in ApplicationComponent.
|
553
576
|
# We use `base_label` method here instead of `label` to avoid cases where the method
|
554
577
|
# owner is included in a prefix like `ApplicationComponent.inherited`.
|
555
578
|
child.identifier = caller_locations(1, 10).reject { |l| l.base_label == "inherited" }[0].path
|
556
|
-
|
557
|
-
# If Rails application is loaded, removes the first part of the path and the extension.
|
558
|
-
if defined?(Rails) && Rails.application
|
559
|
-
child.virtual_path = child.identifier.gsub(
|
560
|
-
/(.*#{Regexp.quote(ViewComponent::Base.config.view_component_path)})|(\.rb)/, ""
|
561
|
-
)
|
562
|
-
end
|
579
|
+
child.virtual_path = child.name&.underscore
|
563
580
|
|
564
581
|
# Set collection parameter to the extended component
|
565
|
-
child.with_collection_parameter
|
582
|
+
child.with_collection_parameter(__vc_provided_collection_parameter)
|
566
583
|
|
567
584
|
if instance_methods(false).include?(:render_template_for)
|
568
585
|
vc_ancestor_calls = defined?(@__vc_ancestor_calls) ? @__vc_ancestor_calls.dup : []
|
@@ -575,22 +592,17 @@ module ViewComponent
|
|
575
592
|
end
|
576
593
|
|
577
594
|
# @private
|
578
|
-
def
|
579
|
-
|
580
|
-
end
|
581
|
-
|
582
|
-
# @private
|
583
|
-
def ensure_compiled
|
584
|
-
compile unless compiled?
|
595
|
+
def __vc_compiled?
|
596
|
+
__vc_compiler.compiled?
|
585
597
|
end
|
586
598
|
|
587
599
|
# @private
|
588
|
-
def
|
589
|
-
|
600
|
+
def __vc_ensure_compiled
|
601
|
+
__vc_compile unless __vc_compiled?
|
590
602
|
end
|
591
603
|
|
592
604
|
# @private
|
593
|
-
def
|
605
|
+
def __vc_compiler
|
594
606
|
@__vc_compiler ||= Compiler.new(self)
|
595
607
|
end
|
596
608
|
|
@@ -602,8 +614,8 @@ module ViewComponent
|
|
602
614
|
#
|
603
615
|
# @param parameter [Symbol] The parameter name used when rendering elements of a collection.
|
604
616
|
def with_collection_parameter(parameter)
|
605
|
-
@
|
606
|
-
@
|
617
|
+
@__vc_provided_collection_parameter = parameter
|
618
|
+
@__vc_initialize_parameters = nil
|
607
619
|
end
|
608
620
|
|
609
621
|
# Strips trailing whitespace from templates before compiling them.
|
@@ -632,18 +644,11 @@ module ViewComponent
|
|
632
644
|
# is accepted, as support for collection
|
633
645
|
# rendering is optional.
|
634
646
|
# @private
|
635
|
-
def
|
636
|
-
parameter = validate_default ?
|
647
|
+
def __vc_validate_collection_parameter!(validate_default: false)
|
648
|
+
parameter = validate_default ? __vc_collection_parameter : __vc_provided_collection_parameter
|
637
649
|
|
638
650
|
return unless parameter
|
639
|
-
return if
|
640
|
-
|
641
|
-
# If Ruby can't parse the component class, then the initialize
|
642
|
-
# parameters will be empty and ViewComponent will not be able to render
|
643
|
-
# the component.
|
644
|
-
if initialize_parameters.empty?
|
645
|
-
raise EmptyOrInvalidInitializerError.new(name, parameter)
|
646
|
-
end
|
651
|
+
return if __vc_initialize_parameter_names.include?(parameter) || __vc_splatted_keyword_argument_present?
|
647
652
|
|
648
653
|
raise MissingCollectionArgumentError.new(name, parameter)
|
649
654
|
end
|
@@ -652,58 +657,58 @@ module ViewComponent
|
|
652
657
|
# invalid parameters that could override the framework's
|
653
658
|
# methods.
|
654
659
|
# @private
|
655
|
-
def
|
656
|
-
return unless
|
660
|
+
def __vc_validate_initialization_parameters!
|
661
|
+
return unless __vc_initialize_parameter_names.include?(:content)
|
657
662
|
|
658
|
-
raise ReservedParameterError.new(name,
|
663
|
+
raise ReservedParameterError.new(name, :content)
|
659
664
|
end
|
660
665
|
|
661
666
|
# @private
|
662
|
-
def
|
663
|
-
|
667
|
+
def __vc_collection_parameter
|
668
|
+
@__vc_provided_collection_parameter ||= name && name.demodulize.underscore.chomp("_component").to_sym
|
664
669
|
end
|
665
670
|
|
666
671
|
# @private
|
667
|
-
def
|
668
|
-
:"#{
|
672
|
+
def __vc_collection_counter_parameter
|
673
|
+
@__vc_collection_counter_parameter ||= :"#{__vc_collection_parameter}_counter"
|
669
674
|
end
|
670
675
|
|
671
676
|
# @private
|
672
|
-
def
|
673
|
-
|
677
|
+
def __vc_counter_argument_present?
|
678
|
+
__vc_initialize_parameter_names.include?(__vc_collection_counter_parameter)
|
674
679
|
end
|
675
680
|
|
676
681
|
# @private
|
677
|
-
def
|
678
|
-
:"#{
|
682
|
+
def __vc_collection_iteration_parameter
|
683
|
+
@__vc_collection_iteration_parameter ||= :"#{__vc_collection_parameter}_iteration"
|
679
684
|
end
|
680
685
|
|
681
686
|
# @private
|
682
|
-
def
|
683
|
-
|
687
|
+
def __vc_iteration_argument_present?
|
688
|
+
__vc_initialize_parameter_names.include?(__vc_collection_iteration_parameter)
|
684
689
|
end
|
685
690
|
|
686
691
|
private
|
687
692
|
|
688
|
-
def
|
689
|
-
|
690
|
-
!initialize_parameters.include?([:keyrest, :**]) # Un-named splatted keyword args don't count!
|
693
|
+
def __vc_splatted_keyword_argument_present?
|
694
|
+
__vc_initialize_parameters.flatten.include?(:keyrest)
|
691
695
|
end
|
692
696
|
|
693
|
-
def
|
694
|
-
|
695
|
-
|
696
|
-
|
697
|
-
|
698
|
-
|
697
|
+
def __vc_initialize_parameter_names
|
698
|
+
@__vc_initialize_parameter_names ||=
|
699
|
+
if respond_to?(:attribute_names)
|
700
|
+
attribute_names.map(&:to_sym)
|
701
|
+
else
|
702
|
+
__vc_initialize_parameters.map(&:last)
|
703
|
+
end
|
699
704
|
end
|
700
705
|
|
701
|
-
def
|
702
|
-
@
|
706
|
+
def __vc_initialize_parameters
|
707
|
+
@__vc_initialize_parameters ||= instance_method(:initialize).parameters
|
703
708
|
end
|
704
709
|
|
705
|
-
def
|
706
|
-
@
|
710
|
+
def __vc_provided_collection_parameter
|
711
|
+
@__vc_provided_collection_parameter ||= nil
|
707
712
|
end
|
708
713
|
end
|
709
714
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "action_view/renderer/collection_renderer"
|
3
|
+
require "action_view/renderer/collection_renderer"
|
4
4
|
|
5
5
|
module ViewComponent
|
6
6
|
class Collection
|
@@ -9,25 +9,32 @@ module ViewComponent
|
|
9
9
|
|
10
10
|
delegate :size, to: :@collection
|
11
11
|
|
12
|
-
attr_accessor :__vc_original_view_context
|
13
|
-
|
14
|
-
def set_original_view_context(view_context)
|
15
|
-
self.__vc_original_view_context = view_context
|
16
|
-
end
|
17
|
-
|
18
12
|
def render_in(view_context, &block)
|
19
13
|
components.map do |component|
|
20
|
-
component.set_original_view_context(__vc_original_view_context)
|
21
14
|
component.render_in(view_context, &block)
|
22
15
|
end.join(rendered_spacer(view_context)).html_safe
|
23
16
|
end
|
24
17
|
|
18
|
+
def each(&block)
|
19
|
+
components.each(&block)
|
20
|
+
end
|
21
|
+
|
22
|
+
if Rails::VERSION::MAJOR == 7 && Rails::VERSION::MINOR == 1
|
23
|
+
# Rails expects us to define `format` on all renderables,
|
24
|
+
# but we do not know the `format` of a ViewComponent until runtime.
|
25
|
+
def format
|
26
|
+
nil
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
25
32
|
def components
|
26
33
|
return @components if defined? @components
|
27
34
|
|
28
35
|
iterator = ActionView::PartialIteration.new(@collection.size)
|
29
36
|
|
30
|
-
component.
|
37
|
+
component.__vc_validate_collection_parameter!(validate_default: true)
|
31
38
|
|
32
39
|
@components = @collection.map do |item|
|
33
40
|
component.new(**component_options(item, iterator)).tap do |component|
|
@@ -36,18 +43,6 @@ module ViewComponent
|
|
36
43
|
end
|
37
44
|
end
|
38
45
|
|
39
|
-
def each(&block)
|
40
|
-
components.each(&block)
|
41
|
-
end
|
42
|
-
|
43
|
-
# Rails expects us to define `format` on all renderables,
|
44
|
-
# but we do not know the `format` of a ViewComponent until runtime.
|
45
|
-
def format
|
46
|
-
nil
|
47
|
-
end
|
48
|
-
|
49
|
-
private
|
50
|
-
|
51
46
|
def initialize(component, object, spacer_component, **options)
|
52
47
|
@component = component
|
53
48
|
@collection = collection_variable(object || [])
|
@@ -64,16 +59,15 @@ module ViewComponent
|
|
64
59
|
end
|
65
60
|
|
66
61
|
def component_options(item, iterator)
|
67
|
-
item_options = {component.
|
68
|
-
item_options[component.
|
69
|
-
item_options[component.
|
62
|
+
item_options = {component.__vc_collection_parameter => item}
|
63
|
+
item_options[component.__vc_collection_counter_parameter] = iterator.index if component.__vc_counter_argument_present?
|
64
|
+
item_options[component.__vc_collection_iteration_parameter] = iterator.dup if component.__vc_iteration_argument_present?
|
70
65
|
|
71
66
|
@options.merge(item_options)
|
72
67
|
end
|
73
68
|
|
74
69
|
def rendered_spacer(view_context)
|
75
70
|
if @spacer_component
|
76
|
-
@spacer_component.set_original_view_context(__vc_original_view_context)
|
77
71
|
@spacer_component.render_in(view_context)
|
78
72
|
else
|
79
73
|
""
|