view_component 2.34.0 → 2.38.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/view_components/_preview_source.html.erb +17 -0
- data/app/views/view_components/preview.html.erb +6 -2
- data/{CHANGELOG.md → docs/CHANGELOG.md} +145 -2
- data/lib/rails/generators/abstract_generator.rb +46 -0
- data/lib/rails/generators/component/component_generator.rb +9 -5
- data/lib/rails/generators/component/templates/component.rb.tt +1 -1
- data/lib/rails/generators/erb/component_generator.rb +12 -12
- data/lib/rails/generators/erb/templates/component.html.erb.tt +1 -1
- data/lib/rails/generators/haml/component_generator.rb +6 -16
- data/lib/rails/generators/slim/component_generator.rb +6 -16
- data/lib/rails/generators/stimulus/component_generator.rb +26 -0
- data/lib/rails/generators/stimulus/templates/component_controller.js.tt +7 -0
- data/lib/view_component.rb +1 -0
- data/lib/view_component/base.rb +144 -84
- 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 +19 -2
- data/lib/view_component/instrumentation.rb +5 -1
- 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,46 @@
|
|
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
|
+
File.join(destination_directory, "#{destination_file_name}.html.#{engine_name}")
|
15
|
+
end
|
16
|
+
|
17
|
+
def destination_directory
|
18
|
+
if options["sidecar"]
|
19
|
+
File.join(component_path, class_path, destination_file_name)
|
20
|
+
else
|
21
|
+
File.join(component_path, class_path)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def destination_file_name
|
26
|
+
"#{file_name}_component"
|
27
|
+
end
|
28
|
+
|
29
|
+
def file_name
|
30
|
+
@_file_name ||= super.sub(/_component\z/i, "")
|
31
|
+
end
|
32
|
+
|
33
|
+
def component_path
|
34
|
+
ViewComponent::Base.view_component_path
|
35
|
+
end
|
36
|
+
|
37
|
+
def stimulus_controller
|
38
|
+
if options["stimulus"]
|
39
|
+
File.join(destination_directory, destination_file_name).
|
40
|
+
sub("#{component_path}/", "").
|
41
|
+
gsub("_", "-").
|
42
|
+
gsub("/", "--")
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -1,32 +1,36 @@
|
|
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"
|
9
13
|
check_class_collision suffix: "Component"
|
10
14
|
class_option :inline, type: :boolean, default: false
|
15
|
+
class_option :stimulus, type: :boolean, default: ViewComponent::Base.generate_stimulus_controller
|
16
|
+
class_option :sidecar, type: :boolean, default: false
|
11
17
|
|
12
18
|
def create_component_file
|
13
|
-
template "component.rb", File.join(
|
19
|
+
template "component.rb", File.join(component_path, class_path, "#{file_name}_component.rb")
|
14
20
|
end
|
15
21
|
|
16
22
|
hook_for :test_framework
|
17
23
|
|
18
24
|
hook_for :preview, type: :boolean
|
19
25
|
|
26
|
+
hook_for :stimulus, type: :boolean
|
27
|
+
|
20
28
|
hook_for :template_engine do |instance, template_engine|
|
21
29
|
instance.invoke template_engine, [instance.name]
|
22
30
|
end
|
23
31
|
|
24
32
|
private
|
25
33
|
|
26
|
-
def file_name
|
27
|
-
@_file_name ||= super.sub(/_component\z/i, "")
|
28
|
-
end
|
29
|
-
|
30
34
|
def parent_class
|
31
35
|
defined?(ApplicationComponent) ? "ApplicationComponent" : "ViewComponent::Base"
|
32
36
|
end
|
@@ -8,7 +8,7 @@ class <%= class_name %>Component < <%= parent_class %>
|
|
8
8
|
<%- end -%>
|
9
9
|
<%- if initialize_call_method_for_inline? -%>
|
10
10
|
def call
|
11
|
-
content_tag :h1, "Hello world!"
|
11
|
+
content_tag :h1, "Hello world!"<%= ", data: { controller: \"#{stimulus_controller}\" }" if options["stimulus"] %>
|
12
12
|
end
|
13
13
|
<%- end -%>
|
14
14
|
|
@@ -1,33 +1,33 @@
|
|
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
|
14
|
+
class_option :stimulus, type: :boolean, default: false
|
15
|
+
|
16
|
+
def engine_name
|
17
|
+
"erb"
|
18
|
+
end
|
11
19
|
|
12
20
|
def copy_view_file
|
13
|
-
|
14
|
-
template "component.html.erb", destination
|
15
|
-
end
|
21
|
+
super
|
16
22
|
end
|
17
23
|
|
18
24
|
private
|
19
25
|
|
20
|
-
def
|
21
|
-
if options["
|
22
|
-
|
23
|
-
else
|
24
|
-
File.join("app/components", class_path, "#{file_name}_component.html.erb")
|
26
|
+
def data_attributes
|
27
|
+
if options["stimulus"]
|
28
|
+
" data-controller=\"#{stimulus_controller}\""
|
25
29
|
end
|
26
30
|
end
|
27
|
-
|
28
|
-
def file_name
|
29
|
-
@_file_name ||= super.sub(/_component\z/i, "")
|
30
|
-
end
|
31
31
|
end
|
32
32
|
end
|
33
33
|
end
|
@@ -1 +1 @@
|
|
1
|
-
<div
|
1
|
+
<div<%= data_attributes %>>Add <%= class_name %> template here</div>
|
@@ -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
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Stimulus
|
4
|
+
module Generators
|
5
|
+
class ComponentGenerator < ::Rails::Generators::NamedBase
|
6
|
+
include ViewComponent::AbstractGenerator
|
7
|
+
|
8
|
+
source_root File.expand_path("templates", __dir__)
|
9
|
+
class_option :sidecar, type: :boolean, default: false
|
10
|
+
|
11
|
+
def create_stimulus_controller
|
12
|
+
template "component_controller.js", destination
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def destination
|
18
|
+
if options["sidecar"]
|
19
|
+
File.join(component_path, class_path, "#{file_name}_component", "#{file_name}_component_controller.js")
|
20
|
+
else
|
21
|
+
File.join(component_path, class_path, "#{file_name}_component_controller.js")
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
data/lib/view_component.rb
CHANGED
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
|
@@ -146,20 +155,42 @@ module ViewComponent
|
|
146
155
|
end
|
147
156
|
end
|
148
157
|
|
149
|
-
# 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.
|
150
160
|
#
|
151
161
|
# @return [ActionController::Base]
|
152
162
|
def controller
|
153
|
-
|
154
|
-
|
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
|
155
175
|
end
|
156
176
|
|
157
|
-
# 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.
|
158
179
|
#
|
159
180
|
# @return [ActionView::Base]
|
160
181
|
def helpers
|
161
|
-
|
162
|
-
|
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
|
163
194
|
end
|
164
195
|
|
165
196
|
# Exposes .virtual_path as an instance method
|
@@ -180,38 +211,23 @@ module ViewComponent
|
|
180
211
|
# @private
|
181
212
|
def format
|
182
213
|
# Ruby 2.6 throws a warning without checking `defined?`, 2.7 does not
|
183
|
-
if defined?(@
|
184
|
-
@
|
214
|
+
if defined?(@__vc_variant)
|
215
|
+
@__vc_variant
|
185
216
|
end
|
186
217
|
end
|
187
218
|
|
188
|
-
# Assign the provided content to the content area accessor
|
189
|
-
#
|
190
|
-
# @private
|
191
|
-
def with(area, content = nil, &block)
|
192
|
-
unless content_areas.include?(area)
|
193
|
-
raise ArgumentError.new "Unknown content_area '#{area}' - expected one of '#{content_areas}'"
|
194
|
-
end
|
195
|
-
|
196
|
-
if block_given?
|
197
|
-
content = view_context.capture(&block)
|
198
|
-
end
|
199
|
-
|
200
|
-
instance_variable_set("@#{area}".to_sym, content)
|
201
|
-
nil
|
202
|
-
end
|
203
|
-
|
204
219
|
# Use the provided variant instead of the one determined by the current request.
|
205
220
|
#
|
206
221
|
# @param variant [Symbol] The variant to be used by the component.
|
207
222
|
# @return [self]
|
208
223
|
def with_variant(variant)
|
209
|
-
@
|
224
|
+
@__vc_variant = variant
|
210
225
|
|
211
226
|
self
|
212
227
|
end
|
213
228
|
|
214
|
-
# 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.
|
215
231
|
#
|
216
232
|
# @return [ActionDispatch::Request]
|
217
233
|
def request
|
@@ -223,31 +239,62 @@ module ViewComponent
|
|
223
239
|
attr_reader :view_context
|
224
240
|
|
225
241
|
def content
|
226
|
-
|
227
|
-
@
|
228
|
-
|
229
|
-
@
|
230
|
-
view_context
|
231
|
-
|
232
|
-
@
|
233
|
-
|
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
|
234
251
|
end
|
235
252
|
|
236
253
|
def content_evaluated?
|
237
|
-
@
|
254
|
+
@__vc_content_evaluated
|
238
255
|
end
|
239
256
|
|
240
|
-
#
|
241
|
-
#
|
242
|
-
#
|
243
|
-
#
|
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
|
+
#
|
244
264
|
mattr_accessor :test_controller
|
245
265
|
@@test_controller = "ApplicationController"
|
246
266
|
|
247
|
-
#
|
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
|
+
#
|
248
271
|
mattr_accessor :render_monkey_patch_enabled, instance_writer: false, default: true
|
249
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
|
+
# Always generate a Stimulus controller alongside the component:
|
282
|
+
#
|
283
|
+
# config.view_component.generate_stimulus_controller = true
|
284
|
+
#
|
285
|
+
# Defaults to `false`.
|
286
|
+
#
|
287
|
+
mattr_accessor :generate_stimulus_controller, instance_writer: false, default: false
|
288
|
+
|
289
|
+
# Path for component files
|
290
|
+
#
|
291
|
+
# config.view_component.view_component_path = "app/my_components"
|
292
|
+
#
|
293
|
+
# Defaults to "app/components".
|
294
|
+
mattr_accessor :view_component_path, instance_writer: false, default: "app/components"
|
295
|
+
|
250
296
|
class << self
|
297
|
+
# @private
|
251
298
|
attr_accessor :source_location, :virtual_path
|
252
299
|
|
253
300
|
# EXPERIMENTAL: This API is experimental and may be removed at any time.
|
@@ -257,6 +304,7 @@ module ViewComponent
|
|
257
304
|
# strings starting without the "dot", example: `["erb", "haml"]`.
|
258
305
|
#
|
259
306
|
# For example, one might collect sidecar CSS files that need to be compiled.
|
307
|
+
# @private TODO: add documentation
|
260
308
|
def _sidecar_files(extensions)
|
261
309
|
return [] unless source_location
|
262
310
|
|
@@ -277,11 +325,12 @@ module ViewComponent
|
|
277
325
|
# end
|
278
326
|
#
|
279
327
|
# Without this, `MyOtherComponent` will not look for `my_component/my_other_component.html.erb`
|
280
|
-
nested_component_files =
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
328
|
+
nested_component_files =
|
329
|
+
if name.include?("::") && component_name != filename
|
330
|
+
Dir["#{directory}/#{filename}/#{component_name}.*{#{extensions}}"]
|
331
|
+
else
|
332
|
+
[]
|
333
|
+
end
|
285
334
|
|
286
335
|
# view files in the same directory as the component
|
287
336
|
sidecar_files = Dir["#{directory}/#{component_name}.*{#{extensions}}"]
|
@@ -291,16 +340,24 @@ module ViewComponent
|
|
291
340
|
(sidecar_files - [source_location] + sidecar_directory_files + nested_component_files).uniq
|
292
341
|
end
|
293
342
|
|
294
|
-
# Render a component collection
|
343
|
+
# Render a component for each element in a collection ([documentation](/guide/collections)):
|
344
|
+
#
|
345
|
+
# render(ProductsComponent.with_collection(@products, foo: :bar))
|
346
|
+
#
|
347
|
+
# @param collection [Enumerable] A list of items to pass the ViewComponent one at a time.
|
348
|
+
# @param args [Arguments] Arguments to pass to the ViewComponent every time.
|
295
349
|
def with_collection(collection, **args)
|
296
350
|
Collection.new(self, collection, **args)
|
297
351
|
end
|
298
352
|
|
299
353
|
# Provide identifier for ActionView template annotations
|
354
|
+
#
|
355
|
+
# @private
|
300
356
|
def short_identifier
|
301
357
|
@short_identifier ||= defined?(Rails.root) ? source_location.sub("#{Rails.root}/", "") : source_location
|
302
358
|
end
|
303
359
|
|
360
|
+
# @private
|
304
361
|
def inherited(child)
|
305
362
|
# Compile so child will inherit compiled `call_*` template methods that
|
306
363
|
# `compile` defines
|
@@ -318,11 +375,14 @@ module ViewComponent
|
|
318
375
|
child.source_location = caller_locations(1, 10).reject { |l| l.label == "inherited" }[0].absolute_path
|
319
376
|
|
320
377
|
# Removes the first part of the path and the extension.
|
321
|
-
child.virtual_path = child.source_location.gsub(
|
378
|
+
child.virtual_path = child.source_location.gsub(
|
379
|
+
%r{(.*#{Regexp.quote(ViewComponent::Base.view_component_path)})|(\.rb)}, ""
|
380
|
+
)
|
322
381
|
|
323
382
|
super
|
324
383
|
end
|
325
384
|
|
385
|
+
# @private
|
326
386
|
def compiled?
|
327
387
|
compiler.compiled?
|
328
388
|
end
|
@@ -331,50 +391,39 @@ module ViewComponent
|
|
331
391
|
#
|
332
392
|
# Do as much work as possible in this step, as doing so reduces the amount
|
333
393
|
# of work done each time a component is rendered.
|
394
|
+
# @private
|
334
395
|
def compile(raise_errors: false)
|
335
396
|
compiler.compile(raise_errors: raise_errors)
|
336
397
|
end
|
337
398
|
|
399
|
+
# @private
|
338
400
|
def compiler
|
339
|
-
@
|
401
|
+
@__vc_compiler ||= Compiler.new(self)
|
340
402
|
end
|
341
403
|
|
342
404
|
# we'll eventually want to update this to support other types
|
405
|
+
# @private
|
343
406
|
def type
|
344
407
|
"text/html"
|
345
408
|
end
|
346
409
|
|
410
|
+
# @private
|
347
411
|
def format
|
348
412
|
:html
|
349
413
|
end
|
350
414
|
|
415
|
+
# @private
|
351
416
|
def identifier
|
352
417
|
source_location
|
353
418
|
end
|
354
419
|
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
raise ArgumentError.new ":content is a reserved content area name. Please use another name, such as ':body'"
|
363
|
-
end
|
364
|
-
|
365
|
-
areas.each do |area|
|
366
|
-
define_method area.to_sym do
|
367
|
-
content unless content_evaluated? # ensure content is loaded so content_areas will be defined
|
368
|
-
instance_variable_get(:"@#{area}") if instance_variable_defined?(:"@#{area}")
|
369
|
-
end
|
370
|
-
end
|
371
|
-
|
372
|
-
self.content_areas = areas
|
373
|
-
end
|
374
|
-
|
375
|
-
# Support overriding collection parameter name
|
376
|
-
def with_collection_parameter(param)
|
377
|
-
@provided_collection_parameter = param
|
420
|
+
# Set the parameter name used when rendering elements of a collection ([documentation](/guide/collections)):
|
421
|
+
#
|
422
|
+
# with_collection_parameter :item
|
423
|
+
#
|
424
|
+
# @param parameter [Symbol] The parameter name used when rendering elements of a collection.
|
425
|
+
def with_collection_parameter(parameter)
|
426
|
+
@provided_collection_parameter = parameter
|
378
427
|
end
|
379
428
|
|
380
429
|
# Ensure the component initializer accepts the
|
@@ -382,6 +431,7 @@ module ViewComponent
|
|
382
431
|
# validate that the default parameter name
|
383
432
|
# is accepted, as support for collection
|
384
433
|
# rendering is optional.
|
434
|
+
# @private TODO: add documentation
|
385
435
|
def validate_collection_parameter!(validate_default: false)
|
386
436
|
parameter = validate_default ? collection_parameter : provided_collection_parameter
|
387
437
|
|
@@ -393,29 +443,35 @@ module ViewComponent
|
|
393
443
|
# the component.
|
394
444
|
if initialize_parameters.empty?
|
395
445
|
raise ArgumentError.new(
|
396
|
-
"#{self} initializer is empty or invalid."
|
446
|
+
"The #{self} initializer is empty or invalid." \
|
447
|
+
"It must accept the parameter `#{parameter}` 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."
|
397
450
|
)
|
398
451
|
end
|
399
452
|
|
400
453
|
raise ArgumentError.new(
|
401
|
-
"#{self}
|
402
|
-
"
|
454
|
+
"The initializer for #{self} does not accept the parameter `#{parameter}`, " \
|
455
|
+
"which is required in order to render it as a collection.\n\n" \
|
456
|
+
"To fix this issue, update the initializer to accept `#{parameter}`.\n\n" \
|
457
|
+
"See https://viewcomponent.org/guide/collections.html for more information on rendering collections."
|
403
458
|
)
|
404
459
|
end
|
405
460
|
|
406
461
|
# Ensure the component initializer does not define
|
407
462
|
# invalid parameters that could override the framework's
|
408
463
|
# methods.
|
464
|
+
# @private TODO: add documentation
|
409
465
|
def validate_initialization_parameters!
|
410
466
|
return unless initialize_parameter_names.include?(RESERVED_PARAMETER)
|
411
467
|
|
412
468
|
raise ViewComponent::ComponentError.new(
|
413
|
-
"#{self} initializer cannot
|
414
|
-
"
|
415
|
-
"public ViewComponent method."
|
469
|
+
"#{self} initializer cannot accept the parameter `#{RESERVED_PARAMETER}`, as it will override a " \
|
470
|
+
"public ViewComponent method. To fix this issue, rename the parameter."
|
416
471
|
)
|
417
472
|
end
|
418
473
|
|
474
|
+
# @private
|
419
475
|
def collection_parameter
|
420
476
|
if provided_collection_parameter
|
421
477
|
provided_collection_parameter
|
@@ -424,18 +480,22 @@ module ViewComponent
|
|
424
480
|
end
|
425
481
|
end
|
426
482
|
|
483
|
+
# @private
|
427
484
|
def collection_counter_parameter
|
428
485
|
"#{collection_parameter}_counter".to_sym
|
429
486
|
end
|
430
487
|
|
488
|
+
# @private
|
431
489
|
def counter_argument_present?
|
432
490
|
initialize_parameter_names.include?(collection_counter_parameter)
|
433
491
|
end
|
434
492
|
|
493
|
+
# @private
|
435
494
|
def collection_iteration_parameter
|
436
495
|
"#{collection_parameter}_iteration".to_sym
|
437
496
|
end
|
438
497
|
|
498
|
+
# @private
|
439
499
|
def iteration_argument_present?
|
440
500
|
initialize_parameter_names.include?(collection_iteration_parameter)
|
441
501
|
end
|