ViewComponent-like slots? #267
Replies: 2 comments 1 reply
|
When you In the simplest case, those methods can just create tags: class Card < Phlex::View
def template(&)
article(class: "card", &)
end
def body(&)
div(class: "body", &)
end
def actions(&)
div(class: "actions", &)
end
endIn this example, I capture the yielded card as class Example < Phlex::View
def template
render Card.new do |c|
c.body do
h1 { "My Card" }
p { "Hello world" }
end
c.actions do
button(type: "submit") { "Submit" }
button { "Cancel" }
end
end
end
endIt's up to the calling template to compose these methods correctly, calling them in the correct order the correct number of times. I think that's usually fine. If you need the card component to have more control over how it it's used, for example by requiring the body and actions slots and ensuring they're only set once, you can can yield early and use public methods to save the slots and then render a template based on the saved values. Here's an example of that with the same interface. class Card
def template(&)
yield(self) and validate_slots
article class: "card" do
div(class: "body", &@body)
div(class: "actions", &@actions)
end
end
def body(&block)
raise ArgumentError, "You already set the body slot." if @body
@body = block
end
def actions(&block)
raise ArgumentError, "You already set the actions slot." if @actions
@actions = block
end
private
def validate_slots
unless @body && @actions
raise ArgumentError, "You must set `body` and `actions` slots."
end
end
end |
|
Phlex is great because I can define the render order or the "slot" types without problems. We are using view components at my job, and we built a card with header and media, but the header can be used before or after media. Example: class CardComponent < ViewComponent::Base
renders_one :header, Card::HeaderComponent
renders_one :media, Card::MediaComponent
def initialize(media_first: false)
@media_first = media_first
end
endTemplate: <% if @media_first %>
<%= media %>
<%= header %>
<% else %>
<%= header %>
<%= media %>
<% end %>Usage: render CardComponent.new do |card|
card.header { ... }
card.media { ... }
end
render CardComponent.new(media_first: true) do |card|
card.header { ... }
card.media { ... }
endWith Phlex: class Card < Phlex::View
def template(&content)
div(&content)
end
def header(&content)
render Card::Header.new(&content)
end
def media(&content)
render Card::Media.new(&content)
end
endUsage: render Card.new do |card|
card.header { ... }
card.media { ... }
end
render Card.new do |card|
card.media { ... }
card.header { ... }
endOther problem we had with view components, our nav can receive items or dividers: class NavComponent < ViewComponent::Base
renders_many :menus, types: {
item: Nav::ItemComponent,
divider: Nav::DividerComponent
}
endTemplate: <% menus.each do |item| %>
<%= item %>
<% end %>Usage: render NavComponent.new do |nav|
nav.menu_item(...) { ... }
nav.menu_divider
nav.menu_item(...) { ... }
endWith phlex: class Nav < Phlex::View
def template(&content)
div(&content)
end
def item(**args, &content)
render Nav::Item.new(**args, &content)
end
def divider
render Nav::Divider.new
end
endUsage: render Nav.new do |nav|
nav.item(...) { ... }
nav.divider
nav.item(...) { ... }
end |
Uh oh!
There was an error while loading. Please reload this page.
Is it possible to define different slots or "sections" to be passed to a
Phlex::View?For example, from an ERB template:
All reactions