Skip to content

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:

<h1>Hello</h1>

<div class="alert alert-primary" role="alert">
  My alert
</div>

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.

def blueprint(&)
  div { yield }
end

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:

<h1>Hello</h1>

<div class="alert alert-primary" role="alert">
  <h4 class="alert-heading">My alert</h4>

  <p>Alert body</p>
</div>