From 5b07cf117d256f0dc4d17d7a605bf08721da6bfa Mon Sep 17 00:00:00 2001 From: Nick Coyne Date: Fri, 1 Nov 2024 06:04:18 +1300 Subject: [PATCH] Allow assigning a spacer component between items in a rendered collection (#2137) * Allow rendering of spacer component between collection items * Add changelog entry * Keep in sync * Update to accept component instance * Apply suggestions from code review --------- Co-authored-by: Joel Hawksley Co-authored-by: Joel Hawksley --- docs/CHANGELOG.md | 4 ++++ docs/guide/collections.md | 13 +++++++++++++ lib/view_component/base.rb | 5 +++-- lib/view_component/collection.rb | 14 ++++++++++++-- test/sandbox/test/collection_test.rb | 11 +++++++++++ test/sandbox/test/rendering_test.rb | 2 +- 6 files changed, 44 insertions(+), 5 deletions(-) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index a4db567ed..60d432c99 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -10,6 +10,10 @@ nav_order: 5 ## main +* Allow rendering `with_collection` to accept an optional `spacer_component` to be rendered between each item. + + *Nick Coyne* + * Remove OpenStruct from codebase. *Oleksii Vasyliev* diff --git a/docs/guide/collections.md b/docs/guide/collections.md index 48ae219cf..91ad90e3c 100644 --- a/docs/guide/collections.md +++ b/docs/guide/collections.md @@ -110,3 +110,16 @@ class ProductComponent < ViewComponent::Base end end ``` + +## Spacer components + +Since 3.20.0 +{: .label } + +Set `:spacer_component` as an instantiated component to render between items: + +```erb +<%= render(ProductComponent.with_collection(@products, spacer_component: SpacerComponent.new)) %> +``` + +Which will render the SpacerComponent component between `ProductComponent`s. diff --git a/lib/view_component/base.rb b/lib/view_component/base.rb index 8d9ecde03..958f057fd 100644 --- a/lib/view_component/base.rb +++ b/lib/view_component/base.rb @@ -509,9 +509,10 @@ def sidecar_files(extensions) # ``` # # @param collection [Enumerable] A list of items to pass the ViewComponent one at a time. + # @param spacer_component [ViewComponent::Base] Component instance to be rendered between items. # @param args [Arguments] Arguments to pass to the ViewComponent every time. - def with_collection(collection, **args) - Collection.new(self, collection, **args) + def with_collection(collection, spacer_component: nil, **args) + Collection.new(self, collection, spacer_component, **args) end # @private diff --git a/lib/view_component/collection.rb b/lib/view_component/collection.rb index e4082dc0d..798e38c25 100644 --- a/lib/view_component/collection.rb +++ b/lib/view_component/collection.rb @@ -19,7 +19,7 @@ def render_in(view_context, &block) components.map do |component| component.set_original_view_context(__vc_original_view_context) component.render_in(view_context, &block) - end.join.html_safe + end.join(rendered_spacer(view_context)).html_safe end def components @@ -48,9 +48,10 @@ def format private - def initialize(component, object, **options) + def initialize(component, object, spacer_component, **options) @component = component @collection = collection_variable(object || []) + @spacer_component = spacer_component @options = options end @@ -69,5 +70,14 @@ def component_options(item, iterator) @options.merge(item_options) end + + def rendered_spacer(view_context) + if @spacer_component + @spacer_component.set_original_view_context(__vc_original_view_context) + @spacer_component.render_in(view_context) + else + "" + end + end end end diff --git a/test/sandbox/test/collection_test.rb b/test/sandbox/test/collection_test.rb index 75eb42a4f..7d1b56d1c 100644 --- a/test/sandbox/test/collection_test.rb +++ b/test/sandbox/test/collection_test.rb @@ -16,6 +16,12 @@ def call end end + class SpacerComponent < ViewComponent::Base + def call + "
".html_safe + end + end + def setup @products = [Product.new(name: "Radio clock"), Product.new(name: "Mints")] @collection = ProductComponent.with_collection(@products, notice: "secondhand") @@ -35,5 +41,10 @@ def test_supports_components_with_keyword_args assert_selector("*[data-name='#{@products.first.name}']", text: @products.first.name) assert_selector("*[data-name='#{@products.last.name}']", text: @products.last.name) end + + def test_supports_collection_with_spacer_component + render_inline(ProductComponent.with_collection(@products, spacer_component: SpacerComponent.new)) + assert_selector("hr", count: 1) + end end end diff --git a/test/sandbox/test/rendering_test.rb b/test/sandbox/test/rendering_test.rb index 6f22d551a..80fd7b924 100644 --- a/test/sandbox/test/rendering_test.rb +++ b/test/sandbox/test/rendering_test.rb @@ -15,7 +15,7 @@ def test_render_inline_allocations ViewComponent::CompileCache.cache.delete(MyComponent) MyComponent.ensure_compiled - assert_allocations("3.4.0" => 110, "3.3.5" => 116, "3.3.0" => 129, "3.2.5" => 115, "3.1.6" => 115, "3.0.7" => 125) do + assert_allocations("3.4.0" => 110, "3.3.5" => 116, "3.3.0" => 129, "3.2.6" => 115, "3.1.6" => 115, "3.0.7" => 125) do render_inline(MyComponent.new) end