Components
Overview
Views vs. Components vs. Pages
Please note that while these doc pages will use terms like views, pages and
components to describe conceptual differences, in code all classes are
implemented in the same way, there is no difference in practice.
The Blueprint::HTML
module must be included, which uses the #blueprint
method to define an HTML structure, and #render
method accepts any class
that included Blueprint::HTML
module.
You can create reusable views using Blueprint, you just need to pass a component
instance to the #render
method.
class AlertComponent
include Blueprint::HTML
def initialize(@content : String, @type : String); end
private def blueprint
div class: "alert alert-#{@type}", role: "alert" do
@content
end
end
end
class ExamplePage
include Blueprint::HTML
private def blueprint
h1 { "Hello" }
render AlertComponent.new(content: "My alert", type: "primary")
end
end
ExamplePage.new.to_s
Output:
Passing Content
Sometimes you need to pass complex content that cannot be passed through a
constructor parameter. To accomplish this, the component's #blueprint
method needs to
yield a block. Refactoring the previous Alert component
example:
class AlertComponent
include Blueprint::HTML
def initialize(@type : String); end
private def blueprint
div class: "alert alert-#{@type}", role: "alert" do
yield
end
end
end
class ExamplePage
include Blueprint::HTML
private def blueprint
h1 { "Hello" }
render AlertComponent.new(type: "primary") do
h4(class: "alert-heading") { "My Alert" }
p { "Alert body" }
end
end
end
ExamplePage.new.to_s
Output:
<h1>Hello</h1>
<div class="alert alert-primary" role="alert">
<h4 class="alert-heading">My alert</h4>
<p>Alert body</p>
</div>
Yielding blocks
To define a method that receives a block, simply use yield inside it and the compiler will know. You can make this more evident by declaring a dummy block parameter, indicated as a last parameter prefixed with ampersand (&). eg.
Composing Components
Blueprint components can expose some predefined structure to its users. This can be accomplished by defining public instance methods that yield blocks or not. Refactoring the previous Alert component example:
class AlertComponent
include Blueprint::HTML
def initialize(@type : String); end
private def blueprint
div class: "alert alert-#{@type}", role: "alert" do
yield
end
end
def title
h4(class: "alert-heading") { yield }
end
def body
p { yield }
end
end
class ExamplePage
include Blueprint::HTML
private def blueprint
h1 { "Hello" }
render AlertComponent.new(type: "primary") do |alert|
alert.title { "My Alert" }
alert.body { "Alert body" }
end
end
end
Output:
<h1>Hello</h1>
<div class="alert alert-primary" role="alert">
<h4 class="alert-heading">My alert</h4>
<p>Alert body</p>
</div>
Registering Components
Blueprint has the register_component
macro. It is useful to avoid writing the
fully qualified name of the component class. Instead writing something like
render Views::Components::Forms::LabelComponent.new(for: "password")
you
can write just label_component(for: "password")
.
class AlertComponent
include Blueprint::HTML
def initialize(@type : String); end
private def blueprint
div class: "alert alert-#{@type}", role: "alert" do
yield
end
end
def title
h4(class: "alert-heading") { yield }
end
def body
p { yield }
end
end
class ExamplePage
include Blueprint::HTML
register_component :alert_component, AlertComponent
private def blueprint
h1 { "Hello" }
alert_component(type: "primary") do |alert|
alert.title { "My Alert" }
alert.body { "Alert body" }
end
end
end
Output: