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:

Routing
Controller
Admin App
Products Page
Orders Page
Routing
Rails.application.routes.draw do
scope :admin do
get :products, to: 'admin#products'
get :orders, to: 'admin#orders'
end
end
Controller
class AdminController < ApplicationController
include Matestack::Ui::Core::Helper
matestack_app Admin::App
def products
render Admin::Pages::Products
end
def orders
render Admin::Pages::Orders
end
end
Admin App
class Admin::App < Matestack::Ui::App
def response
h1 "Admin App"
nav do
transition path: admin_products_page_path do
button "Products"
end
transition path: admin_orders_page_path do
button "Orders"
end
end
main do
yield
end
end
end
Products Page
class Admin::Pages::Products < Matestack::Ui::Page
def response
div class: "container" do
span id: "hello" do
plain "products!"
end
end
end
end
Orders Page
class Admin::Pages::Orders < Matestack::Ui::Page
def response
div class: "container" do
span id: "hello" do
plain "orders!"
end
end
end
end

In a file structure like:

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