view_component 3.23.2 → 4.0.0
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 +404 -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 +154 -157
- data/lib/view_component/collection.rb +11 -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 +30 -558
- 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
@@ -1,10 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "
|
3
|
+
require "generators/view_component/abstract_generator"
|
4
4
|
|
5
|
-
module
|
5
|
+
module ViewComponent
|
6
6
|
module Generators
|
7
|
-
class
|
7
|
+
class LocaleGenerator < ::Rails::Generators::NamedBase
|
8
8
|
include ViewComponent::AbstractGenerator
|
9
9
|
|
10
10
|
source_root File.expand_path("templates", __dir__)
|
@@ -1,8 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
module
|
3
|
+
module ViewComponent
|
4
4
|
module Generators
|
5
|
-
class
|
5
|
+
class PreviewGenerator < ::Rails::Generators::NamedBase
|
6
6
|
source_root File.expand_path("templates", __dir__)
|
7
7
|
class_option :preview_path, type: :string, desc: "Path for previews, required when multiple preview paths are configured", default: ViewComponent::Base.config.generate.preview_path
|
8
8
|
|
@@ -10,7 +10,7 @@ module Preview
|
|
10
10
|
check_class_collision suffix: "ComponentPreview"
|
11
11
|
|
12
12
|
def create_preview_file
|
13
|
-
preview_paths = ViewComponent::Base.config.
|
13
|
+
preview_paths = ViewComponent::Base.config.previews.paths
|
14
14
|
optional_path = options[:preview_path]
|
15
15
|
return if preview_paths.count > 1 && optional_path.blank?
|
16
16
|
|
@@ -1,10 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "
|
3
|
+
require "generators/view_component/abstract_generator"
|
4
4
|
|
5
|
-
module
|
5
|
+
module ViewComponent
|
6
6
|
module Generators
|
7
|
-
class
|
7
|
+
class RspecGenerator < ::Rails::Generators::NamedBase
|
8
8
|
include ViewComponent::AbstractGenerator
|
9
9
|
|
10
10
|
source_root File.expand_path("templates", __dir__)
|
@@ -1,10 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "
|
3
|
+
require "generators/view_component/erb/erb_generator"
|
4
4
|
|
5
|
-
module
|
5
|
+
module ViewComponent
|
6
6
|
module Generators
|
7
|
-
class
|
7
|
+
class SlimGenerator < ViewComponent::Generators::ErbGenerator
|
8
8
|
include ViewComponent::AbstractGenerator
|
9
9
|
|
10
10
|
source_root File.expand_path("templates", __dir__)
|
@@ -1,10 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "
|
3
|
+
require "generators/view_component/abstract_generator"
|
4
4
|
|
5
|
-
module
|
5
|
+
module ViewComponent
|
6
6
|
module Generators
|
7
|
-
class
|
7
|
+
class StimulusGenerator < ::Rails::Generators::NamedBase
|
8
8
|
include ViewComponent::AbstractGenerator
|
9
9
|
|
10
10
|
source_root File.expand_path("templates", __dir__)
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "generators/view_component/erb/erb_generator"
|
4
|
+
|
5
|
+
module ViewComponent
|
6
|
+
module Generators
|
7
|
+
class TailwindcssGenerator < ViewComponent::Generators::ErbGenerator
|
8
|
+
source_root File.expand_path("templates", __dir__)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -1,8 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
module
|
3
|
+
module ViewComponent
|
4
4
|
module Generators
|
5
|
-
class
|
5
|
+
class TestUnitGenerator < ::Rails::Generators::NamedBase
|
6
6
|
source_root File.expand_path("templates", __dir__)
|
7
7
|
check_class_collision suffix: "ComponentTest"
|
8
8
|
|
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,13 +316,6 @@ module ViewComponent
|
|
274
316
|
[]
|
275
317
|
end
|
276
318
|
|
277
|
-
# For caching, such as #cache_if
|
278
|
-
#
|
279
|
-
# @private
|
280
|
-
def format
|
281
|
-
@__vc_variant if defined?(@__vc_variant)
|
282
|
-
end
|
283
|
-
|
284
319
|
# The current request. Use sparingly as doing so introduces coupling that
|
285
320
|
# inhibits encapsulation & reuse, often making testing difficult.
|
286
321
|
#
|
@@ -289,10 +324,9 @@ module ViewComponent
|
|
289
324
|
__vc_request
|
290
325
|
end
|
291
326
|
|
292
|
-
# Enables consumers to override request/@request
|
293
|
-
#
|
294
327
|
# @private
|
295
328
|
def __vc_request
|
329
|
+
# The current request (if present, as mailers/jobs/etc do not have a request)
|
296
330
|
@__vc_request ||= controller.request if controller.respond_to?(:request)
|
297
331
|
end
|
298
332
|
|
@@ -305,7 +339,9 @@ module ViewComponent
|
|
305
339
|
|
306
340
|
@__vc_content =
|
307
341
|
if __vc_render_in_block_provided?
|
308
|
-
|
342
|
+
with_original_virtual_path do
|
343
|
+
view_context.capture(self, &@__vc_render_in_block)
|
344
|
+
end
|
309
345
|
elsif __vc_content_set_by_with_content_defined?
|
310
346
|
@__vc_content_set_by_with_content
|
311
347
|
end
|
@@ -318,6 +354,14 @@ module ViewComponent
|
|
318
354
|
__vc_render_in_block_provided? || __vc_content_set_by_with_content_defined?
|
319
355
|
end
|
320
356
|
|
357
|
+
# @private
|
358
|
+
def with_original_virtual_path
|
359
|
+
@view_context.instance_variable_set(:@virtual_path, @old_virtual_path)
|
360
|
+
yield
|
361
|
+
ensure
|
362
|
+
@view_context.instance_variable_set(:@virtual_path, virtual_path)
|
363
|
+
end
|
364
|
+
|
321
365
|
private
|
322
366
|
|
323
367
|
attr_reader :view_context
|
@@ -330,12 +374,8 @@ module ViewComponent
|
|
330
374
|
defined?(@__vc_content_set_by_with_content)
|
331
375
|
end
|
332
376
|
|
333
|
-
def
|
334
|
-
|
335
|
-
end
|
336
|
-
|
337
|
-
def maybe_escape_html(text)
|
338
|
-
return text if __vc_request && !__vc_request.format.html?
|
377
|
+
def __vc_maybe_escape_html(text)
|
378
|
+
return text if @current_template && !@current_template.html?
|
339
379
|
return text if text.blank?
|
340
380
|
|
341
381
|
if text.html_safe?
|
@@ -346,54 +386,18 @@ module ViewComponent
|
|
346
386
|
end
|
347
387
|
end
|
348
388
|
|
349
|
-
def
|
350
|
-
|
389
|
+
def __vc_safe_output_preamble
|
390
|
+
__vc_maybe_escape_html(output_preamble) do
|
351
391
|
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
392
|
end
|
353
393
|
end
|
354
394
|
|
355
|
-
def
|
356
|
-
|
395
|
+
def __vc_safe_output_postamble
|
396
|
+
__vc_maybe_escape_html(output_postamble) do
|
357
397
|
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
398
|
end
|
359
399
|
end
|
360
400
|
|
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
401
|
# Configuration for generators.
|
398
402
|
#
|
399
403
|
# All options under this namespace default to `false` unless otherwise
|
@@ -451,6 +455,18 @@ module ViewComponent
|
|
451
455
|
# ```
|
452
456
|
#
|
453
457
|
# Defaults to `false`.
|
458
|
+
#
|
459
|
+
# #### ßparent_class
|
460
|
+
#
|
461
|
+
# Parent class for generated components
|
462
|
+
#
|
463
|
+
# ```ruby
|
464
|
+
# config.view_component.generate.parent_class = "MyBaseComponent"
|
465
|
+
# ```
|
466
|
+
#
|
467
|
+
# Defaults to nil. If this is falsy, generators will use
|
468
|
+
# "ApplicationComponent" if defined, "ViewComponent::Base" otherwise.
|
469
|
+
#
|
454
470
|
|
455
471
|
class << self
|
456
472
|
# The file path of the component Ruby file.
|
@@ -519,50 +535,43 @@ module ViewComponent
|
|
519
535
|
Collection.new(self, collection, spacer_component, **args)
|
520
536
|
end
|
521
537
|
|
538
|
+
# @private
|
539
|
+
def __vc_compile(raise_errors: false, force: false)
|
540
|
+
__vc_compiler.compile(raise_errors: raise_errors, force: force)
|
541
|
+
end
|
542
|
+
|
522
543
|
# @private
|
523
544
|
def inherited(child)
|
524
545
|
# Compile so child will inherit compiled `call_*` template methods that
|
525
546
|
# `compile` defines
|
526
|
-
|
547
|
+
__vc_compile
|
527
548
|
|
528
549
|
# Give the child its own personal #render_template_for to protect against the case when
|
529
550
|
# eager loading is disabled and the parent component is rendered before the child. In
|
530
551
|
# such a scenario, the parent will override ViewComponent::Base#render_template_for,
|
531
552
|
# 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.
|
553
|
+
if !child.instance_methods(false).include?(:render_template_for) && !child.__vc_compiled?
|
533
554
|
child.class_eval <<~RUBY, __FILE__, __LINE__ + 1
|
534
|
-
def render_template_for(
|
555
|
+
def render_template_for(requested_details)
|
535
556
|
# Force compilation here so the compiler always redefines render_template_for.
|
536
557
|
# This is mostly a safeguard to prevent infinite recursion.
|
537
|
-
self.class.
|
538
|
-
# .
|
539
|
-
render_template_for(
|
558
|
+
self.class.__vc_compile(raise_errors: true, force: true)
|
559
|
+
# .__vc_compile replaces this method; call the new one
|
560
|
+
render_template_for(requested_details)
|
540
561
|
end
|
541
562
|
RUBY
|
542
563
|
end
|
543
564
|
|
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
565
|
# Derive the source location of the component Ruby file from the call stack.
|
551
566
|
# We need to ignore `inherited` frames here as they indicate that `inherited`
|
552
567
|
# has been re-defined by the consuming application, likely in ApplicationComponent.
|
553
568
|
# We use `base_label` method here instead of `label` to avoid cases where the method
|
554
569
|
# owner is included in a prefix like `ApplicationComponent.inherited`.
|
555
570
|
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
|
571
|
+
child.virtual_path = child.name&.underscore
|
563
572
|
|
564
573
|
# Set collection parameter to the extended component
|
565
|
-
child.with_collection_parameter
|
574
|
+
child.with_collection_parameter(__vc_provided_collection_parameter)
|
566
575
|
|
567
576
|
if instance_methods(false).include?(:render_template_for)
|
568
577
|
vc_ancestor_calls = defined?(@__vc_ancestor_calls) ? @__vc_ancestor_calls.dup : []
|
@@ -575,22 +584,17 @@ module ViewComponent
|
|
575
584
|
end
|
576
585
|
|
577
586
|
# @private
|
578
|
-
def
|
579
|
-
|
580
|
-
end
|
581
|
-
|
582
|
-
# @private
|
583
|
-
def ensure_compiled
|
584
|
-
compile unless compiled?
|
587
|
+
def __vc_compiled?
|
588
|
+
__vc_compiler.compiled?
|
585
589
|
end
|
586
590
|
|
587
591
|
# @private
|
588
|
-
def
|
589
|
-
|
592
|
+
def __vc_ensure_compiled
|
593
|
+
__vc_compile unless __vc_compiled?
|
590
594
|
end
|
591
595
|
|
592
596
|
# @private
|
593
|
-
def
|
597
|
+
def __vc_compiler
|
594
598
|
@__vc_compiler ||= Compiler.new(self)
|
595
599
|
end
|
596
600
|
|
@@ -602,8 +606,8 @@ module ViewComponent
|
|
602
606
|
#
|
603
607
|
# @param parameter [Symbol] The parameter name used when rendering elements of a collection.
|
604
608
|
def with_collection_parameter(parameter)
|
605
|
-
@
|
606
|
-
@
|
609
|
+
@__vc_provided_collection_parameter = parameter
|
610
|
+
@__vc_initialize_parameters = nil
|
607
611
|
end
|
608
612
|
|
609
613
|
# Strips trailing whitespace from templates before compiling them.
|
@@ -632,18 +636,11 @@ module ViewComponent
|
|
632
636
|
# is accepted, as support for collection
|
633
637
|
# rendering is optional.
|
634
638
|
# @private
|
635
|
-
def
|
636
|
-
parameter = validate_default ?
|
639
|
+
def __vc_validate_collection_parameter!(validate_default: false)
|
640
|
+
parameter = validate_default ? __vc_collection_parameter : __vc_provided_collection_parameter
|
637
641
|
|
638
642
|
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
|
643
|
+
return if __vc_initialize_parameter_names.include?(parameter) || __vc_splatted_keyword_argument_present?
|
647
644
|
|
648
645
|
raise MissingCollectionArgumentError.new(name, parameter)
|
649
646
|
end
|
@@ -652,58 +649,58 @@ module ViewComponent
|
|
652
649
|
# invalid parameters that could override the framework's
|
653
650
|
# methods.
|
654
651
|
# @private
|
655
|
-
def
|
656
|
-
return unless
|
652
|
+
def __vc_validate_initialization_parameters!
|
653
|
+
return unless __vc_initialize_parameter_names.include?(:content)
|
657
654
|
|
658
|
-
raise ReservedParameterError.new(name,
|
655
|
+
raise ReservedParameterError.new(name, :content)
|
659
656
|
end
|
660
657
|
|
661
658
|
# @private
|
662
|
-
def
|
663
|
-
|
659
|
+
def __vc_collection_parameter
|
660
|
+
@__vc_provided_collection_parameter ||= name && name.demodulize.underscore.chomp("_component").to_sym
|
664
661
|
end
|
665
662
|
|
666
663
|
# @private
|
667
|
-
def
|
668
|
-
:"#{
|
664
|
+
def __vc_collection_counter_parameter
|
665
|
+
@__vc_collection_counter_parameter ||= :"#{__vc_collection_parameter}_counter"
|
669
666
|
end
|
670
667
|
|
671
668
|
# @private
|
672
|
-
def
|
673
|
-
|
669
|
+
def __vc_counter_argument_present?
|
670
|
+
__vc_initialize_parameter_names.include?(__vc_collection_counter_parameter)
|
674
671
|
end
|
675
672
|
|
676
673
|
# @private
|
677
|
-
def
|
678
|
-
:"#{
|
674
|
+
def __vc_collection_iteration_parameter
|
675
|
+
@__vc_collection_iteration_parameter ||= :"#{__vc_collection_parameter}_iteration"
|
679
676
|
end
|
680
677
|
|
681
678
|
# @private
|
682
|
-
def
|
683
|
-
|
679
|
+
def __vc_iteration_argument_present?
|
680
|
+
__vc_initialize_parameter_names.include?(__vc_collection_iteration_parameter)
|
684
681
|
end
|
685
682
|
|
686
683
|
private
|
687
684
|
|
688
|
-
def
|
689
|
-
|
690
|
-
!initialize_parameters.include?([:keyrest, :**]) # Un-named splatted keyword args don't count!
|
685
|
+
def __vc_splatted_keyword_argument_present?
|
686
|
+
__vc_initialize_parameters.flatten.include?(:keyrest)
|
691
687
|
end
|
692
688
|
|
693
|
-
def
|
694
|
-
|
695
|
-
|
696
|
-
|
697
|
-
|
698
|
-
|
689
|
+
def __vc_initialize_parameter_names
|
690
|
+
@__vc_initialize_parameter_names ||=
|
691
|
+
if respond_to?(:attribute_names)
|
692
|
+
attribute_names.map(&:to_sym)
|
693
|
+
else
|
694
|
+
__vc_initialize_parameters.map(&:last)
|
695
|
+
end
|
699
696
|
end
|
700
697
|
|
701
|
-
def
|
702
|
-
@
|
698
|
+
def __vc_initialize_parameters
|
699
|
+
@__vc_initialize_parameters ||= instance_method(:initialize).parameters
|
703
700
|
end
|
704
701
|
|
705
|
-
def
|
706
|
-
@
|
702
|
+
def __vc_provided_collection_parameter
|
703
|
+
@__vc_provided_collection_parameter ||= nil
|
707
704
|
end
|
708
705
|
end
|
709
706
|
|