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
end In 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
end It'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 |
Beta Was this translation helpful? Give feedback.
-
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
end Template: <% 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 { ... }
end With 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
end Usage: render Card.new do |card|
card.header { ... }
card.media { ... }
end
render Card.new do |card|
card.media { ... }
card.header { ... }
end Other 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
}
end Template: <% menus.each do |item| %>
<%= item %>
<% end %> Usage: render NavComponent.new do |nav|
nav.menu_item(...) { ... }
nav.menu_divider
nav.menu_item(...) { ... }
end With 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
end Usage: render Nav.new do |nav|
nav.item(...) { ... }
nav.divider
nav.item(...) { ... }
end |
Beta Was this translation helpful? Give feedback.
-
Is it possible to define different slots or "sections" to be passed to a
Phlex::View
?For example, from an ERB template:
Beta Was this translation helpful? Give feedback.
All reactions