Blocks, slots, & yielding
One of the most powerful features of Phlex is how well it can handle blocks of content. If you’re familiar with custom HTML elements or other component frameworks, you may have heard this referred to as “slots”.
Default block from a render
A very common way to use blocks is to render a component with a default block of content. This is done by calling the render method on the component class and passing a block of content to it. This block is called when the view_template method is invoked during the rendering process.
class Card::Component < Phlex::Component
def view_template(&block)
div(class: "card") do
h1(class: "card-title") { "Hello, world!" }
yield
end
end
end
render Card::Component.new do
p { "This is a block of content." }
end
Multiple blocks
What about components that have multiple blocks? For example, the h1 tag in the card component should accept any block of content we pass it when rendering.
class Card::Component < Phlex::Component
def view_template(&block)
div(class: "card") do
yield
end
end
def title
h1(class: "card-title") { yield }
end
end
When we call it, we can pass a block of content to the title method.
render Card::Component.new do |card|
card.title { "Custom title" }
p { "This is a block of content." }
end
Now when we render the card, the title method is called from the Card::Component class, giving us the decorated h1 tag with the content we passed it. Then the p tag is rendered with the content we passed it.
Order matters
When we render multiple blocks, the order in which they are rendered is important. The first block passed to the component will be rendered first, followed by any additional blocks.
render Card::Component.new do |card|
p { "This is a block of content." }
# The title would be on the bottom, which we might not want!
card.title { "Custom title" }
end
What do you do about content where you can’t control the order of the blocks?
Deferred rendering
Sometimes the order of items that need to be rendered isn’t known when writing a component. In other cases, you might want to enforce the order of when items are rendered, regardless of when they’re invoked from a Phlex view.
In that case, we’ll vanish the block from the view template and render it later.
class Card::Component < Phlex::Component
def view_template(&)
vanish(&)
div(class: "card") do
h1(class: "card-title", &@title)
div(class: "card-content", &@content)
end
end
def title(&block)
@title = block
end
def content(&block)
@content = block
end
end
Here’s what it looks like when we render it.
render Card::Component.new do |card|
card.title { "This is a block of content." }
card.content do
p { "Custom title" }
end
end
We can call it in whatever order we want, and the order doesn’t matter since the blocks are rendered in the order they were passed to the component.
render Card::Component.new do |card|
card.content do
p { "Custom title" }
end
card.title { "This is a block of content." }
end
