view_component 2.33.0 → 2.37.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.
Potentially problematic release.
This version of view_component might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/README.md +1 -1
- data/app/assets/vendor/prism.css +196 -0
- data/app/assets/vendor/prism.min.js +12 -0
- data/app/controllers/view_components_controller.rb +1 -1
- data/app/helpers/preview_helper.rb +19 -0
- data/app/views/test_mailer/test_email.html.erb +1 -0
- data/app/views/view_components/_preview_source.html.erb +17 -0
- data/app/views/view_components/preview.html.erb +6 -2
- data/{CHANGELOG.md → docs/CHANGELOG.md} +151 -1
- data/lib/rails/generators/abstract_generator.rb +29 -0
- data/lib/rails/generators/component/component_generator.rb +5 -5
- data/lib/rails/generators/erb/component_generator.rb +7 -16
- data/lib/rails/generators/haml/component_generator.rb +6 -16
- data/lib/rails/generators/slim/component_generator.rb +6 -16
- data/lib/view_component.rb +2 -0
- data/lib/view_component/base.rb +144 -85
- data/lib/view_component/collection.rb +6 -2
- data/lib/view_component/compile_cache.rb +1 -0
- data/lib/view_component/compiler.rb +87 -53
- data/lib/view_component/content_areas.rb +57 -0
- data/lib/view_component/engine.rb +31 -3
- data/lib/view_component/instrumentation.rb +21 -0
- data/lib/view_component/preview.rb +19 -8
- data/lib/view_component/previewable.rb +16 -18
- data/lib/view_component/slot_v2.rb +34 -27
- data/lib/view_component/slotable.rb +2 -1
- data/lib/view_component/slotable_v2.rb +58 -24
- data/lib/view_component/test_helpers.rb +7 -1
- data/lib/view_component/translatable.rb +6 -5
- data/lib/view_component/version.rb +1 -1
- data/lib/view_component/with_content_helper.rb +5 -2
- data/lib/yard/mattr_accessor_handler.rb +19 -0
- metadata +76 -39
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ViewComponent
|
4
|
+
module AbstractGenerator
|
5
|
+
def copy_view_file
|
6
|
+
unless options["inline"]
|
7
|
+
template "component.html.#{engine_name}", destination
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def destination
|
14
|
+
if options["sidecar"]
|
15
|
+
File.join(component_path, class_path, "#{file_name}_component", "#{file_name}_component.html.#{engine_name}")
|
16
|
+
else
|
17
|
+
File.join(component_path, class_path, "#{file_name}_component.html.#{engine_name}")
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def file_name
|
22
|
+
@_file_name ||= super.sub(/_component\z/i, "")
|
23
|
+
end
|
24
|
+
|
25
|
+
def component_path
|
26
|
+
ViewComponent::Base.view_component_path
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -1,8 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "rails/generators/abstract_generator"
|
4
|
+
|
3
5
|
module Rails
|
4
6
|
module Generators
|
5
7
|
class ComponentGenerator < Rails::Generators::NamedBase
|
8
|
+
include ViewComponent::AbstractGenerator
|
9
|
+
|
6
10
|
source_root File.expand_path("templates", __dir__)
|
7
11
|
|
8
12
|
argument :attributes, type: :array, default: [], banner: "attribute"
|
@@ -10,7 +14,7 @@ module Rails
|
|
10
14
|
class_option :inline, type: :boolean, default: false
|
11
15
|
|
12
16
|
def create_component_file
|
13
|
-
template "component.rb", File.join(
|
17
|
+
template "component.rb", File.join(component_path, class_path, "#{file_name}_component.rb")
|
14
18
|
end
|
15
19
|
|
16
20
|
hook_for :test_framework
|
@@ -23,10 +27,6 @@ module Rails
|
|
23
27
|
|
24
28
|
private
|
25
29
|
|
26
|
-
def file_name
|
27
|
-
@_file_name ||= super.sub(/_component\z/i, "")
|
28
|
-
end
|
29
|
-
|
30
30
|
def parent_class
|
31
31
|
defined?(ApplicationComponent) ? "ApplicationComponent" : "ViewComponent::Base"
|
32
32
|
end
|
@@ -1,32 +1,23 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "rails/generators/erb"
|
4
|
+
require "rails/generators/abstract_generator"
|
4
5
|
|
5
6
|
module Erb
|
6
7
|
module Generators
|
7
8
|
class ComponentGenerator < Base
|
9
|
+
include ViewComponent::AbstractGenerator
|
10
|
+
|
8
11
|
source_root File.expand_path("templates", __dir__)
|
9
12
|
class_option :sidecar, type: :boolean, default: false
|
10
13
|
class_option :inline, type: :boolean, default: false
|
11
14
|
|
12
|
-
def
|
13
|
-
|
14
|
-
template "component.html.erb", destination
|
15
|
-
end
|
16
|
-
end
|
17
|
-
|
18
|
-
private
|
19
|
-
|
20
|
-
def destination
|
21
|
-
if options["sidecar"]
|
22
|
-
File.join("app/components", class_path, "#{file_name}_component", "#{file_name}_component.html.erb")
|
23
|
-
else
|
24
|
-
File.join("app/components", class_path, "#{file_name}_component.html.erb")
|
25
|
-
end
|
15
|
+
def engine_name
|
16
|
+
"erb"
|
26
17
|
end
|
27
18
|
|
28
|
-
def
|
29
|
-
|
19
|
+
def copy_view_file
|
20
|
+
super
|
30
21
|
end
|
31
22
|
end
|
32
23
|
end
|
@@ -5,27 +5,17 @@ require "rails/generators/erb/component_generator"
|
|
5
5
|
module Haml
|
6
6
|
module Generators
|
7
7
|
class ComponentGenerator < Erb::Generators::ComponentGenerator
|
8
|
+
include ViewComponent::AbstractGenerator
|
9
|
+
|
8
10
|
source_root File.expand_path("templates", __dir__)
|
9
11
|
class_option :sidecar, type: :boolean, default: false
|
10
12
|
|
11
|
-
def
|
12
|
-
|
13
|
-
template "component.html.haml", destination
|
14
|
-
end
|
15
|
-
end
|
16
|
-
|
17
|
-
private
|
18
|
-
|
19
|
-
def destination
|
20
|
-
if options["sidecar"]
|
21
|
-
File.join("app/components", class_path, "#{file_name}_component", "#{file_name}_component.html.haml")
|
22
|
-
else
|
23
|
-
File.join("app/components", class_path, "#{file_name}_component.html.haml")
|
24
|
-
end
|
13
|
+
def engine_name
|
14
|
+
"haml"
|
25
15
|
end
|
26
16
|
|
27
|
-
def
|
28
|
-
|
17
|
+
def copy_view_file
|
18
|
+
super
|
29
19
|
end
|
30
20
|
end
|
31
21
|
end
|
@@ -5,27 +5,17 @@ require "rails/generators/erb/component_generator"
|
|
5
5
|
module Slim
|
6
6
|
module Generators
|
7
7
|
class ComponentGenerator < Erb::Generators::ComponentGenerator
|
8
|
+
include ViewComponent::AbstractGenerator
|
9
|
+
|
8
10
|
source_root File.expand_path("templates", __dir__)
|
9
11
|
class_option :sidecar, type: :boolean, default: false
|
10
12
|
|
11
|
-
def
|
12
|
-
|
13
|
-
template "component.html.slim", destination
|
14
|
-
end
|
15
|
-
end
|
16
|
-
|
17
|
-
private
|
18
|
-
|
19
|
-
def destination
|
20
|
-
if options["sidecar"]
|
21
|
-
File.join("app/components", class_path, "#{file_name}_component", "#{file_name}_component.html.slim")
|
22
|
-
else
|
23
|
-
File.join("app/components", class_path, "#{file_name}_component.html.slim")
|
24
|
-
end
|
13
|
+
def engine_name
|
14
|
+
"slim"
|
25
15
|
end
|
26
16
|
|
27
|
-
def
|
28
|
-
|
17
|
+
def copy_view_file
|
18
|
+
super
|
29
19
|
end
|
30
20
|
end
|
31
21
|
end
|
data/lib/view_component.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
require "action_view"
|
3
4
|
require "active_support/dependencies/autoload"
|
4
5
|
|
@@ -8,6 +9,7 @@ module ViewComponent
|
|
8
9
|
autoload :Base
|
9
10
|
autoload :Compiler
|
10
11
|
autoload :ComponentError
|
12
|
+
autoload :Instrumentation
|
11
13
|
autoload :Preview
|
12
14
|
autoload :PreviewTemplateError
|
13
15
|
autoload :TestHelpers
|
data/lib/view_component/base.rb
CHANGED
@@ -4,6 +4,7 @@ require "action_view"
|
|
4
4
|
require "active_support/configurable"
|
5
5
|
require "view_component/collection"
|
6
6
|
require "view_component/compile_cache"
|
7
|
+
require "view_component/content_areas"
|
7
8
|
require "view_component/previewable"
|
8
9
|
require "view_component/slotable"
|
9
10
|
require "view_component/slotable_v2"
|
@@ -12,6 +13,7 @@ require "view_component/with_content_helper"
|
|
12
13
|
module ViewComponent
|
13
14
|
class Base < ActionView::Base
|
14
15
|
include ActiveSupport::Configurable
|
16
|
+
include ViewComponent::ContentAreas
|
15
17
|
include ViewComponent::Previewable
|
16
18
|
include ViewComponent::SlotableV2
|
17
19
|
include ViewComponent::WithContentHelper
|
@@ -76,22 +78,28 @@ module ViewComponent
|
|
76
78
|
@virtual_path ||= virtual_path
|
77
79
|
|
78
80
|
# For template variants (+phone, +desktop, etc.)
|
79
|
-
@
|
81
|
+
@__vc_variant ||= @lookup_context.variants.first
|
80
82
|
|
81
83
|
# For caching, such as #cache_if
|
82
84
|
@current_template = nil unless defined?(@current_template)
|
83
85
|
old_current_template = @current_template
|
84
86
|
@current_template = self
|
85
87
|
|
86
|
-
|
88
|
+
if block && defined?(@__vc_content_set_by_with_content)
|
89
|
+
raise ArgumentError.new(
|
90
|
+
"It looks like a block was provided after calling `with_content` on #{self.class.name}, " \
|
91
|
+
"which means that ViewComponent doesn't know which content to use.\n\n" \
|
92
|
+
"To fix this issue, use either `with_content` or a block."
|
93
|
+
)
|
94
|
+
end
|
87
95
|
|
88
|
-
@
|
89
|
-
@
|
96
|
+
@__vc_content_evaluated = false
|
97
|
+
@__vc_render_in_block = block
|
90
98
|
|
91
99
|
before_render
|
92
100
|
|
93
101
|
if render?
|
94
|
-
render_template_for(@
|
102
|
+
render_template_for(@__vc_variant).to_s + _output_postamble
|
95
103
|
else
|
96
104
|
""
|
97
105
|
end
|
@@ -106,7 +114,8 @@ module ViewComponent
|
|
106
114
|
""
|
107
115
|
end
|
108
116
|
|
109
|
-
# Called before rendering the component. Override to perform operations that
|
117
|
+
# Called before rendering the component. Override to perform operations that
|
118
|
+
# depend on having access to the view context, such as helpers.
|
110
119
|
#
|
111
120
|
# @return [void]
|
112
121
|
def before_render
|
@@ -115,7 +124,7 @@ module ViewComponent
|
|
115
124
|
|
116
125
|
# Called after rendering the component.
|
117
126
|
#
|
118
|
-
# @deprecated Use
|
127
|
+
# @deprecated Use `#before_render` instead. Will be removed in v3.0.0.
|
119
128
|
# @return [void]
|
120
129
|
def before_render_check
|
121
130
|
# noop
|
@@ -136,6 +145,7 @@ module ViewComponent
|
|
136
145
|
# This prevents an exception when rendering a partial inside of a component that has also been rendered outside
|
137
146
|
# of the component. This is due to the partials compiled template method existing in the parent `view_context`,
|
138
147
|
# and not the component's `view_context`.
|
148
|
+
#
|
139
149
|
# @private
|
140
150
|
def render(options = {}, args = {}, &block)
|
141
151
|
if options.is_a? ViewComponent::Base
|
@@ -145,23 +155,46 @@ module ViewComponent
|
|
145
155
|
end
|
146
156
|
end
|
147
157
|
|
148
|
-
# The current controller. Use sparingly as doing so introduces coupling
|
158
|
+
# The current controller. Use sparingly as doing so introduces coupling
|
159
|
+
# that inhibits encapsulation & reuse, often making testing difficult.
|
149
160
|
#
|
150
161
|
# @return [ActionController::Base]
|
151
162
|
def controller
|
152
|
-
|
153
|
-
|
163
|
+
if view_context.nil?
|
164
|
+
raise(
|
165
|
+
ViewContextCalledBeforeRenderError,
|
166
|
+
"`#controller` cannot be used during initialization, as it depends " \
|
167
|
+
"on the view context that only exists once a ViewComponent is passed to " \
|
168
|
+
"the Rails render pipeline.\n\n" \
|
169
|
+
"It's sometimes possible to fix this issue by moving code dependent on " \
|
170
|
+
"`#controller` to a `#before_render` method: https://viewcomponent.org/api.html#before_render--void."
|
171
|
+
)
|
172
|
+
end
|
173
|
+
|
174
|
+
@__vc_controller ||= view_context.controller
|
154
175
|
end
|
155
176
|
|
156
|
-
# A proxy through which to access helpers. Use sparingly as doing so introduces
|
177
|
+
# A proxy through which to access helpers. Use sparingly as doing so introduces
|
178
|
+
# coupling that inhibits encapsulation & reuse, often making testing difficult.
|
157
179
|
#
|
158
180
|
# @return [ActionView::Base]
|
159
181
|
def helpers
|
160
|
-
|
161
|
-
|
182
|
+
if view_context.nil?
|
183
|
+
raise(
|
184
|
+
ViewContextCalledBeforeRenderError,
|
185
|
+
"`#helpers` cannot be used during initialization, as it depends " \
|
186
|
+
"on the view context that only exists once a ViewComponent is passed to " \
|
187
|
+
"the Rails render pipeline.\n\n" \
|
188
|
+
"It's sometimes possible to fix this issue by moving code dependent on " \
|
189
|
+
"`#helpers` to a `#before_render` method: https://viewcomponent.org/api.html#before_render--void."
|
190
|
+
)
|
191
|
+
end
|
192
|
+
|
193
|
+
@__vc_helpers ||= controller.view_context
|
162
194
|
end
|
163
195
|
|
164
196
|
# Exposes .virtual_path as an instance method
|
197
|
+
#
|
165
198
|
# @private
|
166
199
|
def virtual_path
|
167
200
|
self.class.virtual_path
|
@@ -174,41 +207,31 @@ module ViewComponent
|
|
174
207
|
end
|
175
208
|
|
176
209
|
# For caching, such as #cache_if
|
210
|
+
#
|
177
211
|
# @private
|
178
212
|
def format
|
179
213
|
# Ruby 2.6 throws a warning without checking `defined?`, 2.7 does not
|
180
|
-
if defined?(@
|
181
|
-
@
|
214
|
+
if defined?(@__vc_variant)
|
215
|
+
@__vc_variant
|
182
216
|
end
|
183
217
|
end
|
184
218
|
|
185
|
-
#
|
186
|
-
#
|
187
|
-
|
188
|
-
|
189
|
-
raise ArgumentError.new "Unknown content_area '#{area}' - expected one of '#{content_areas}'"
|
190
|
-
end
|
191
|
-
|
192
|
-
if block_given?
|
193
|
-
content = view_context.capture(&block)
|
194
|
-
end
|
195
|
-
|
196
|
-
instance_variable_set("@#{area}".to_sym, content)
|
197
|
-
nil
|
198
|
-
end
|
199
|
-
|
200
|
-
# @private TODO: add documentation
|
219
|
+
# Use the provided variant instead of the one determined by the current request.
|
220
|
+
#
|
221
|
+
# @param variant [Symbol] The variant to be used by the component.
|
222
|
+
# @return [self]
|
201
223
|
def with_variant(variant)
|
202
|
-
@
|
224
|
+
@__vc_variant = variant
|
203
225
|
|
204
226
|
self
|
205
227
|
end
|
206
228
|
|
207
|
-
# The current request. Use sparingly as doing so introduces coupling that
|
229
|
+
# The current request. Use sparingly as doing so introduces coupling that
|
230
|
+
# inhibits encapsulation & reuse, often making testing difficult.
|
208
231
|
#
|
209
232
|
# @return [ActionDispatch::Request]
|
210
233
|
def request
|
211
|
-
@request ||= controller.request
|
234
|
+
@request ||= controller.request if controller.respond_to?(:request)
|
212
235
|
end
|
213
236
|
|
214
237
|
private
|
@@ -216,31 +239,54 @@ module ViewComponent
|
|
216
239
|
attr_reader :view_context
|
217
240
|
|
218
241
|
def content
|
219
|
-
|
220
|
-
@
|
221
|
-
|
222
|
-
@
|
223
|
-
view_context
|
224
|
-
|
225
|
-
@
|
226
|
-
|
242
|
+
@__vc_content_evaluated = true
|
243
|
+
return @__vc_content if defined?(@__vc_content)
|
244
|
+
|
245
|
+
@__vc_content =
|
246
|
+
if @view_context && @__vc_render_in_block
|
247
|
+
view_context.capture(self, &@__vc_render_in_block)
|
248
|
+
elsif defined?(@__vc_content_set_by_with_content)
|
249
|
+
@__vc_content_set_by_with_content
|
250
|
+
end
|
227
251
|
end
|
228
252
|
|
229
253
|
def content_evaluated?
|
230
|
-
@
|
254
|
+
@__vc_content_evaluated
|
231
255
|
end
|
232
256
|
|
233
|
-
#
|
234
|
-
#
|
235
|
-
#
|
236
|
-
#
|
257
|
+
# Set the controller used for testing components:
|
258
|
+
#
|
259
|
+
# config.view_component.test_controller = "MyTestController"
|
260
|
+
#
|
261
|
+
# Defaults to ApplicationController. Can also be configured on a per-test
|
262
|
+
# basis using `with_controller_class`.
|
263
|
+
#
|
237
264
|
mattr_accessor :test_controller
|
238
265
|
@@test_controller = "ApplicationController"
|
239
266
|
|
240
|
-
#
|
267
|
+
# Set if render monkey patches should be included or not in Rails <6.1:
|
268
|
+
#
|
269
|
+
# config.view_component.render_monkey_patch_enabled = false
|
270
|
+
#
|
241
271
|
mattr_accessor :render_monkey_patch_enabled, instance_writer: false, default: true
|
242
272
|
|
273
|
+
# Enable or disable source code previews in component previews:
|
274
|
+
#
|
275
|
+
# config.view_component.show_previews_source = true
|
276
|
+
#
|
277
|
+
# Defaults to `false`.
|
278
|
+
#
|
279
|
+
mattr_accessor :show_previews_source, instance_writer: false, default: false
|
280
|
+
|
281
|
+
# Path for component files
|
282
|
+
#
|
283
|
+
# config.view_component.view_component_path = "app/my_components"
|
284
|
+
#
|
285
|
+
# Defaults to "app/components".
|
286
|
+
mattr_accessor :view_component_path, instance_writer: false, default: "app/components"
|
287
|
+
|
243
288
|
class << self
|
289
|
+
# @private
|
244
290
|
attr_accessor :source_location, :virtual_path
|
245
291
|
|
246
292
|
# EXPERIMENTAL: This API is experimental and may be removed at any time.
|
@@ -250,6 +296,7 @@ module ViewComponent
|
|
250
296
|
# strings starting without the "dot", example: `["erb", "haml"]`.
|
251
297
|
#
|
252
298
|
# For example, one might collect sidecar CSS files that need to be compiled.
|
299
|
+
# @private TODO: add documentation
|
253
300
|
def _sidecar_files(extensions)
|
254
301
|
return [] unless source_location
|
255
302
|
|
@@ -270,11 +317,12 @@ module ViewComponent
|
|
270
317
|
# end
|
271
318
|
#
|
272
319
|
# Without this, `MyOtherComponent` will not look for `my_component/my_other_component.html.erb`
|
273
|
-
nested_component_files =
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
320
|
+
nested_component_files =
|
321
|
+
if name.include?("::") && component_name != filename
|
322
|
+
Dir["#{directory}/#{filename}/#{component_name}.*{#{extensions}}"]
|
323
|
+
else
|
324
|
+
[]
|
325
|
+
end
|
278
326
|
|
279
327
|
# view files in the same directory as the component
|
280
328
|
sidecar_files = Dir["#{directory}/#{component_name}.*{#{extensions}}"]
|
@@ -284,16 +332,24 @@ module ViewComponent
|
|
284
332
|
(sidecar_files - [source_location] + sidecar_directory_files + nested_component_files).uniq
|
285
333
|
end
|
286
334
|
|
287
|
-
# Render a component collection
|
335
|
+
# Render a component for each element in a collection ([documentation](/guide/collections)):
|
336
|
+
#
|
337
|
+
# render(ProductsComponent.with_collection(@products, foo: :bar))
|
338
|
+
#
|
339
|
+
# @param collection [Enumerable] A list of items to pass the ViewComponent one at a time.
|
340
|
+
# @param args [Arguments] Arguments to pass to the ViewComponent every time.
|
288
341
|
def with_collection(collection, **args)
|
289
342
|
Collection.new(self, collection, **args)
|
290
343
|
end
|
291
344
|
|
292
345
|
# Provide identifier for ActionView template annotations
|
346
|
+
#
|
347
|
+
# @private
|
293
348
|
def short_identifier
|
294
349
|
@short_identifier ||= defined?(Rails.root) ? source_location.sub("#{Rails.root}/", "") : source_location
|
295
350
|
end
|
296
351
|
|
352
|
+
# @private
|
297
353
|
def inherited(child)
|
298
354
|
# Compile so child will inherit compiled `call_*` template methods that
|
299
355
|
# `compile` defines
|
@@ -311,11 +367,14 @@ module ViewComponent
|
|
311
367
|
child.source_location = caller_locations(1, 10).reject { |l| l.label == "inherited" }[0].absolute_path
|
312
368
|
|
313
369
|
# Removes the first part of the path and the extension.
|
314
|
-
child.virtual_path = child.source_location.gsub(
|
370
|
+
child.virtual_path = child.source_location.gsub(
|
371
|
+
%r{(.*#{Regexp.quote(ViewComponent::Base.view_component_path)})|(\.rb)}, ""
|
372
|
+
)
|
315
373
|
|
316
374
|
super
|
317
375
|
end
|
318
376
|
|
377
|
+
# @private
|
319
378
|
def compiled?
|
320
379
|
compiler.compiled?
|
321
380
|
end
|
@@ -324,50 +383,39 @@ module ViewComponent
|
|
324
383
|
#
|
325
384
|
# Do as much work as possible in this step, as doing so reduces the amount
|
326
385
|
# of work done each time a component is rendered.
|
386
|
+
# @private
|
327
387
|
def compile(raise_errors: false)
|
328
388
|
compiler.compile(raise_errors: raise_errors)
|
329
389
|
end
|
330
390
|
|
391
|
+
# @private
|
331
392
|
def compiler
|
332
|
-
@
|
393
|
+
@__vc_compiler ||= Compiler.new(self)
|
333
394
|
end
|
334
395
|
|
335
396
|
# we'll eventually want to update this to support other types
|
397
|
+
# @private
|
336
398
|
def type
|
337
399
|
"text/html"
|
338
400
|
end
|
339
401
|
|
402
|
+
# @private
|
340
403
|
def format
|
341
404
|
:html
|
342
405
|
end
|
343
406
|
|
407
|
+
# @private
|
344
408
|
def identifier
|
345
409
|
source_location
|
346
410
|
end
|
347
411
|
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
raise ArgumentError.new ":content is a reserved content area name. Please use another name, such as ':body'"
|
356
|
-
end
|
357
|
-
|
358
|
-
areas.each do |area|
|
359
|
-
define_method area.to_sym do
|
360
|
-
content unless content_evaluated? # ensure content is loaded so content_areas will be defined
|
361
|
-
instance_variable_get(:"@#{area}") if instance_variable_defined?(:"@#{area}")
|
362
|
-
end
|
363
|
-
end
|
364
|
-
|
365
|
-
self.content_areas = areas
|
366
|
-
end
|
367
|
-
|
368
|
-
# Support overriding collection parameter name
|
369
|
-
def with_collection_parameter(param)
|
370
|
-
@provided_collection_parameter = param
|
412
|
+
# Set the parameter name used when rendering elements of a collection ([documentation](/guide/collections)):
|
413
|
+
#
|
414
|
+
# with_collection_parameter :item
|
415
|
+
#
|
416
|
+
# @param parameter [Symbol] The parameter name used when rendering elements of a collection.
|
417
|
+
def with_collection_parameter(parameter)
|
418
|
+
@provided_collection_parameter = parameter
|
371
419
|
end
|
372
420
|
|
373
421
|
# Ensure the component initializer accepts the
|
@@ -375,6 +423,7 @@ module ViewComponent
|
|
375
423
|
# validate that the default parameter name
|
376
424
|
# is accepted, as support for collection
|
377
425
|
# rendering is optional.
|
426
|
+
# @private TODO: add documentation
|
378
427
|
def validate_collection_parameter!(validate_default: false)
|
379
428
|
parameter = validate_default ? collection_parameter : provided_collection_parameter
|
380
429
|
|
@@ -386,29 +435,35 @@ module ViewComponent
|
|
386
435
|
# the component.
|
387
436
|
if initialize_parameters.empty?
|
388
437
|
raise ArgumentError.new(
|
389
|
-
"#{self} initializer is empty or invalid."
|
438
|
+
"The #{self} initializer is empty or invalid." \
|
439
|
+
"It must accept the parameter `#{parameter}` to render it as a collection.\n\n" \
|
440
|
+
"To fix this issue, update the initializer to accept `#{parameter}`.\n\n" \
|
441
|
+
"See https://viewcomponent.org/guide/collections.html for more information on rendering collections."
|
390
442
|
)
|
391
443
|
end
|
392
444
|
|
393
445
|
raise ArgumentError.new(
|
394
|
-
"#{self}
|
395
|
-
"
|
446
|
+
"The initializer for #{self} does not accept the parameter `#{parameter}`, " \
|
447
|
+
"which is required in order to render it as a collection.\n\n" \
|
448
|
+
"To fix this issue, update the initializer to accept `#{parameter}`.\n\n" \
|
449
|
+
"See https://viewcomponent.org/guide/collections.html for more information on rendering collections."
|
396
450
|
)
|
397
451
|
end
|
398
452
|
|
399
453
|
# Ensure the component initializer does not define
|
400
454
|
# invalid parameters that could override the framework's
|
401
455
|
# methods.
|
456
|
+
# @private TODO: add documentation
|
402
457
|
def validate_initialization_parameters!
|
403
458
|
return unless initialize_parameter_names.include?(RESERVED_PARAMETER)
|
404
459
|
|
405
460
|
raise ViewComponent::ComponentError.new(
|
406
|
-
"#{self} initializer cannot
|
407
|
-
"
|
408
|
-
"public ViewComponent method."
|
461
|
+
"#{self} initializer cannot accept the parameter `#{RESERVED_PARAMETER}`, as it will override a " \
|
462
|
+
"public ViewComponent method. To fix this issue, rename the parameter."
|
409
463
|
)
|
410
464
|
end
|
411
465
|
|
466
|
+
# @private
|
412
467
|
def collection_parameter
|
413
468
|
if provided_collection_parameter
|
414
469
|
provided_collection_parameter
|
@@ -417,18 +472,22 @@ module ViewComponent
|
|
417
472
|
end
|
418
473
|
end
|
419
474
|
|
475
|
+
# @private
|
420
476
|
def collection_counter_parameter
|
421
477
|
"#{collection_parameter}_counter".to_sym
|
422
478
|
end
|
423
479
|
|
480
|
+
# @private
|
424
481
|
def counter_argument_present?
|
425
482
|
initialize_parameter_names.include?(collection_counter_parameter)
|
426
483
|
end
|
427
484
|
|
485
|
+
# @private
|
428
486
|
def collection_iteration_parameter
|
429
487
|
"#{collection_parameter}_iteration".to_sym
|
430
488
|
end
|
431
489
|
|
490
|
+
# @private
|
432
491
|
def iteration_argument_present?
|
433
492
|
initialize_parameter_names.include?(collection_iteration_parameter)
|
434
493
|
end
|