Overview

HTML Rendering

Matestack’s rendering mechanism takes care of converting Ruby into HTML:

div class: "card shadow-sm border-0 bg-light", foo: "bar" do
  img path: "...", class: "w-100"
  div class: "card-body" do
    h5 "foo", class: "card-title"
    paragraph "bar", class: "card-text"
  end
end

will be rendered to:

<div class="card shadow-sm border-0 bg-light" foo="bar">
  <img src="..." class="w-100">
  <div class="card-body">
    <h5 class="card-title">foo</h5>
    <p class="card-text">bar</p>
  </div>
</div>

As you can see, Ruby method calls like div, img ... are mapped to simple HTML tags. matestack-ui-core support all kinds of standard HTML tags enabling you to build a well known DOM structure while writing and utilizing pure Ruby!

As you can see, you can add CSS classes and ids as well as custom tag attributes. This way matestack-ui-core can be combined with various CSS frameworks or your custom styles. It’s already fun to write pure Ruby instead of HTML or any other templating engine syntax but this approach is really paying of, when you start using Ruby's language features in order to split your UI implementation into various small chunks, organized through included modules, class inheritance or simply multiple Ruby methods within one class!

The above shown Ruby code lives in Ruby classes inheriting from Matestack::Ui::Component, Matestack::Ui::Page or Matestack::Ui::App

They are described in the next section:

Basic UI Building Blocks

Depending on your desired Rails integration mode (see below), you might only need a subset of the now presented building blocks

Matestack’s basic UI building blocks are called apps, pages and components:

An app can be compared with a Rails layout, a page can be compared with a Rails view and a component can be best compared with a Rails partial.

Apps, pages and components are Ruby classes, implementing a response method which will then define specific parts of the UI using pure Ruby methods. We will see in a bit how this looks like.

Components

Components use Matestack's HTML rendering mechanism in a response method and may additionally call other components in order to define a specific UI. Components can be used on apps, pages and other components. Additionally they can be used on Rails views (see below)

app/matestack/components/card.rb
class Components::Card < Matestack::Ui::Component

  requires :body
  optional :title
  optional :image

  def response
    div class: "card shadow-sm border-0 bg-light" do
      img path: context.image, class: "w-100" if context.image.present?
      div class: "card-body" do
        heading size: 5, text: context.title if context.title.present?
        paragraph class: "card-text", text: context.body
      end
    end
  end

end

On the above shown example, a reusable card component was created, which can be used across the whole application taking multiple required or optional options. A component may also take blocks or named slots for flexible markup injection.

Components can be called directly like Components::Card.(title: "foo", body: "bar") which will return the desired HTML string. If desired, you can create alias methods in order to avoid the class call syntax:

app/matestack/components/registry.rb
module Components::Registry

  def card(text=nil, options=nil, &block)
    Components::Card.(text, options, &block)
  end

  #...

end

which then allows you to call the card component like card(title: "foo", body: "bar") if the above shown module is included properly.

Learn more about components:

Pages

As said, a Matestack page can be compared to a Rails view and might be yielded within a layout provided by an associated Matestack app (see below). The page itself uses Matestack's HTML rendering mechanism in a response method and may additionally call other components in order to define a specific UI.

app/matestack/pages/some:page.rb
class Pages::SomePage < Matestack::Ui::Page

  # optional if you want to use alias methods in order to call your components
  # can be placed in a ApplicationPage class once in order not to include it
  # on every page
  include Components::Registry

  def response
    div class: "container" do
      span id: "hello" do
        plain "hello world!"
      end
      Components::Card.(title: "foo", body: "bar")
      # or (if you created and included the registry module):
      card(title: "foo", body: "bar")
    end
  end

end

In this basic example the page is using the Ruby methods div, span and plain in order to create the desired UI and call the above defined component Components::Card

Pages are used as Rails view substitutes and therefore called in a Rails controller action:

app/controllers/some:controller.rb
class SomeController < ApplicationController

  include Matestack::Ui::Core::Helper

  def overview
    render Pages::SomePage
  end

end

Learn more about Pages:

Apps

An app uses components in order to define the layout of your application. It might implement a header and a footer for example. Just like a Rails layout would yield a Rails view, an app yields a page. The app uses Matestack's HTML rendering mechanism in a response method and may additionally call other components in order to define a specific UI.

app/matestack/some:app/app.rb
class SomeApp::App < Matestack::Ui::App

  def response
    h1 "Some App"
    main do
      yield
    end
  end

end

In this basic example the app is using the methods h1 and main in order to create the markup as well as a yield in order to yield a page on a specific position.

Usually an app implies a specific context of your application. Multiple pages are then scoped within that context, which could lead to a file structure like:

app/matestack/
|
└───some_app/
      app.rb
   └───pages/
         page1.rb
         page2.rb
         page3.rb

and then used in a controller like this:

app/controllers/some:controller.rb
class SomeController < ApplicationController

  include Matestack::Ui::Core::Helper

  matestack_app SomeApp::App

  def page_1
    render SomeApp::Pages::Page1
  end

  def page_2
    render SomeApp::Pages::Page2
  end

  def page_3
    render SomeApp::Pages::Page3, matestack_app: false # skip app layout on this page
  end

end

See below (Full Matestack) for a more concrete example!

Learn more about Apps:

Rails Integration Modes

There are several ways to use the presented building blocks in your Rails app. matestack-ui-core is designed to be progressively integrated into existing Rails apps and views. Leveraging the full power and beauty is best done when going full Matestack though!

  1. Matestack components on Rails views

    → Only components are used here and there

  2. Full Matestack

    → Apps, pages and components used together as a Rails view layer substitute

Matestack components on Rails views

If you already have plenty of Rails views (ERB, Haml or Slim) and want to start creating small UI components in pure Ruby, you are able to use components on these existing views.

app/matestack/components/products/teaser.rb
class Components::Products::Teaser < Matestack::Ui::Component

  requires :product

  def response
    a path: product_path(context.product), class: 'product-teaser' do
      div do
        h2 context.product.name
        paragraph conext.product.description
        b context.product.price
      end
    end
  end

end

The class is then called on your Rails view, in this case an ERB view:

<%= @products.each do |product| %>
  <%= Components::Products::Teaser.(product: product) %>
<% end %>

This approach is suitable for existing apps and a good idea to migrate to Matestack step by step. If you start with a blank Rails app, we recommend to go full Matestack right away**!**

Full Matestack

Going full Matestack means, using pages, components and apps as a (scoped) complete substitute for the Rails view layer. We're not using Rails views anymore but Matestack pages instead. This behavior is mainly managed within Rails controllers.

It's totally valid to serve multiple scopes within one Rails app. Think of a web shop Rails app, consisting of a client-facing storefront UI and a backoffice admin UI. You're free to use full Matestack on one scope and a completely different view layer on the other.

Let’s review the classic Rails request/response cycle:

A request is coming in and Rails routing is calling the specified Rails controller action. Nothing new here. Within the Rails action we’re now telling the controller not to render a Rails view wrapped in a Rails layout but instead render a Matestack page wrapped in a Matestack app. All other controller based business logic stays untouched. This is why gems like devise or pundit for example can be used in harmony with matestack-ui-core

Thinking of the describe example of a Webshop backoffice admin UI, the implementation with dynamic page transitions may look like this:

Rails.application.routes.draw do

  scope :admin do
    get :products, to: 'admin#products'
    get :orders, to: 'admin#orders'
  end

end

In a file structure like:

app/matestack/
|
└───admin/
      app.rb
   └───pages/
         products.rb
         orders.rb

Last updated