Support for ViewComponent-style collections
I've just started using Phlex on a project, I dug around a bit but I don't think there's support for anything like ViewComponent's collections (https://viewcomponent.org/guide/collections.html). I hacked something together in my ApplicationComponent file, seen below:
def self.with_collection_parameter(param)
@item_parameter_name = param.to_sym
end
def self.with_collection(items, **kwargs)
@item_parameter_name ||=
name.demodulize.underscore.chomp("_component").to_sym
CollectionComponent.new(
component: self,
item_parameter: @item_parameter_name,
items:,
kwargs:
)
end
And the following generic CollectionComponent that actually does the work.
class CollectionComponent < Phlex::HTML
def initialize(component:, items:, item_parameter:, kwargs:)
super()
@component = component
@items = items
@item_parameter = item_parameter
@kwargs = kwargs
end
def template
@items.each do |item|
render @component.new(**@kwargs, **{ @item_parameter => item })
end
end
end
Are you interested in me doing a PR, and potentially adding support for the collection counter and collection iteration context? I'm not entirely sold on their API design, but I guess making it compatible makes it easier for people to switch.
Hey @simonrussell, thanks for opening this issue. We have had something similar before. Take a look at the code removed in this PR. https://github.com/phlex-ruby/phlex/pull/465
At the time, I decided to remove it because I couldn't find one collection abstraction that was a clear best pattern. There are a number of patterns, each with its own advantages and disadvantages.
I’m not completely opposed to the idea. I just think we would need to find a pattern that was flexible enough to serve all the different needs and provide additional value through the consistency of its API across different projects and components.
Your example provides:
- Swappable item component class
- Configurable item parameter
- Argument delegation to items
- The ability to pass a collection in as an enumerable
It doesn't provide:
- A collection template method or component to wrap the items or insert things between each item
- The ability to define small collection and item templates inline as simple methods
- The ability to construct a new collection on the fly using the deferred render / “slots” interface
- The ability for the collection component to render the items more than once with different templates (e.g. for tab navigation list and tab contents)
And I’ve probably missed a bunch of other things that would need to be considered, such as counters (though I think this is best left to CSS).
If we spend some time on this and get it right, it could be a great feature. Otherwise, it's best to leave it out and let people develop their own collection abstractions.
Hi there, thanks for the feedback. I'm not sure it's that similar to the stuff removed in that PR, although just by looking at the code it's a little hard to figure out how it was used; it feels like it might address something a bit different. (I do agree that the table one is probably a step too far in terms of what you'd put in the base framework).
This really just wraps up this pattern:
@items.each do |item|
render ItemComponent.new(item:, other_param: 123)
end
which I needed almost immediately when converting partials over to components. Otherwise it's replacing a fairly short partial/collection thing with a chunk of boilerplate.
In response to your "not provide" list:
- Yes; it's a little tricky because the argument list is basically dedicated to being the constructor arguments for the item template. It's probably possible to think of a way to pack in the ability to provide a collection template.
- Yes; although possibly that would be better addressed with a helper similar to the way tags are done, which could exist next to this feature for more complicated situations (maybe it could actually be integrated with the tag helpers?)
- I'm not really sure how this one would work (I haven't used that feature), but I think maybe the answer is similar to (2)
- I think this is where you should probably just start building a custom component (that can use this internally).
Hey @simonrussell, thanks again for opening this issue. I think, for now, we want to keep Phlex as small as possible. I could see this kind of pattern being included in an extension instead.