API

Response

Use the response method to define the UI of the component by using Matestack's HTML rendering or calling components.
1
class SomeComponent < Matestack::Ui::Component
2
3
def response
4
div id: "my-component" do
5
plain "hello world!"
6
end
7
SomeOtherComponent.()
8
end
9
10
end
Copied!
1
class ExamplePage < Matestack::Ui::Page
2
3
def response
4
div id: "div-on-page" do
5
SomeComponent.()
6
end
7
end
8
9
end
Copied!

Partials and helper methods

Use partials to keep the code dry and indentation layers manageable!

Local partials on component level

In the component definition, see how this time from inside the response, the my_partial method below is called:
1
class SomeComponent < Matestack::Ui::Component
2
3
def response
4
div id: "my-component" do
5
my_partial "foo from component"
6
end
7
end
8
9
private # optionally mark your partials as private
10
11
def my_partial text
12
div class: "nested"
13
plain text
14
end
15
end
16
17
end
Copied!

Partials defined in modules

Extract code snippets to modules for an even better project structure. First, create a module:
1
module MySharedPartials
2
3
def my_partial text
4
div class: "nested"
5
plain text
6
end
7
end
8
9
end
Copied!
Include the module in the component:
1
class SomeComponent < Matestack::Ui::Component
2
3
include MySharedPartials
4
5
def response
6
div id: "my-component" do
7
my_partial "foo from component"
8
end
9
end
10
11
end
Copied!

Helper methods

Not only can instance methods be used as "partials" but as general helpers performing any kind of logic or data resolving:
1
class SomeComponent < Matestack::Ui::Component
2
3
def response
4
div id: "my-component" do
5
if is_admin?
6
latest_users.each do |user|
7
div do
8
plain user.name # ...
9
end
10
end
11
else
12
plain "not authorized"
13
end
14
end
15
end
16
17
private # optionally mark your helper methods as private
18
19
def is_admin?
20
true # some crazy Ruby logic!
21
end
22
23
def latest_users
24
User.last(10) # calling ActiveRecord models for example
25
end
26
27
end
Copied!

Render?

Use the render? method to conditionally render the component based on custom rules:
1
class AdminComponent < Matestack::Ui::Component
2
required :user
3
4
def render?
5
context.user.admin?
6
end
7
8
def response
9
div id: "admin-component" do
10
plain "This component should only get rendered for admins"
11
end
12
end
13
end
Copied!
This is particularly useful to avoid plastering your views with conditional statements like if and unless.
Instead of:
1
<% if current_user.admin? %>
2
<%= Components::AdminComponent.(user: current_user) %>
3
<% end %>
Copied!
You can just use:
1
<%= Components::AdminComponent.(user: current_user) %>
Copied!

Prepare

Use a prepare method to resolve instance variables before rendering a component if required.
1
class SomeComponent < Matestack::Ui::Component
2
3
def prepare
4
@some_data = "some data"
5
end
6
7
def response
8
div id: "my-component" do
9
plain @some_data
10
end
11
end
12
13
end
Copied!
1
class ExamplePage < Matestack::Ui::Page
2
3
def response
4
div id: "div-on-page" do
5
SomeComponent.()
6
end
7
end
8
9
end
Copied!
This is the HTML which gets created:
1
<div id="div-on-page">
2
<div id="my-component">
3
some data
4
</div>
5
</div>
Copied!

Params access

A component can access request information, e.g. url query params, by calling the params method:
1
class SomeComponent < Matestack::Ui::Component
2
3
def response
4
div id: "my-component" do
5
plain params[:foo]
6
end
7
end
8
9
end
Copied!
On the example page, reference the component as usual.
1
class ExamplePage < Matestack::Ui::Page
2
3
def response
4
div id: "div-on-page" do
5
SomeComponent.()
6
end
7
end
8
9
end
Copied!
Now, visiting the respective route to the page, e.g. via /xyz?foo=bar, the component reads the [:foo] from the params and displays it like so:
1
<div id="div-on-page">
2
<div id="my-component">
3
bar
4
</div>
5
</div>
Copied!

Passing data to components

You often need to pass data into your component to make them reusable. You have multiple possibilities to do that:

General options access

If you pass in a hash to a component...
1
class ExamplePage < Matestack::Ui::Page
2
3
def response
4
div id: "div-on-page" do
5
SomeComponent.(foo: "bar")
6
end
7
end
8
9
end
Copied!
...this hash is accessible via options in the component:
1
class Some::Component < Matestack::Ui::Component
2
3
def response
4
div id: "my-component" do
5
plain options[:foo]
6
end
7
end
8
9
end
Copied!

Optional and required options

Matestack components give you the possibility to define an explicit API for your component describing required and optional options for a component. Using this approach, it's way easier to understand what data can be processed by your component.
required and optional options will be deleted from the options hash and are only available via the context object.

Required options

Required options are required for your component to work, like the name suggests. If at least one required option is missing, an Exception is raised.
Declare your required options by calling required as follows:
1
class SomeComponent < Matestack::Ui::Component
2
3
required :some_property, :some_other
4
5
end
Copied!
You then can use these options simply by calling the provided OpenStruct object context, which includes the injected options with their name:
1
class SomeComponent < Matestack::Ui::Component
2
3
required :some_property, :some_other
4
5
def response
6
# display some_property plain inside a div and some_other property inside a paragraph beneath it
7
div do
8
plain context.some_property
9
end
10
paragraph text: context.some_other
11
end
12
13
end
Copied!

Optional options

To define optional options you can use the same syntax as required. Just use optional instead of required. Optional options are optional and not validated for presence like required options.
1
class SomeComponent < Matestack::Ui::Component
2
3
optional :optional_property, :other_optional_property # optional properties could be empty
4
5
def response
6
div do
7
plain context.optional_property
8
end
9
paragraph context.other_optional_property
10
end
11
12
end
Copied!

Passing more complex data structures to components

You can pass any object you like and use it in the component with the helper.
1
class SomeComponent < Matestack::Ui::Component
2
3
required :some_option,
4
optional :some_other
5
6
def response
7
div do
8
plain context.some_option
9
end
10
if context.some_other.present?
11
paragraph context.some_other[:option]
12
end
13
end
14
15
end
Copied!
Use it in the example page and pass in one option as a hash
1
class ExamplePage < Matestack::Ui::Page
2
3
def prepare
4
@hello = "hello!"
5
end
6
7
def response
8
div id: "div-on-page" do
9
SomeComponent.(some_option: @hello, some_other: { option: "world!" })
10
end
11
end
12
13
end
Copied!

Alias properties

It's not possible to overwrite core methods of the OpenStruct object context
1
[:!, :!=, :!~, :<=>, :==, :===, :=~, :[], :[]=, :__id__, :__send__, :acts_like?, :as_json, :blank?, :byebug, :class, :class_eval, :clone, :dclone, :debugger, :deep_dup, :define_singleton_method, :delete_field, :dig, :display, :dup, :duplicable?, :each_pair, :enum_for, :eql?, :equal?, :extend, :freeze, :frozen?, :gem, :hash, :html_safe?, :in?, :inspect, :instance_eval, :instance_exec, :instance_of?, :instance_values, :instance_variable_defined?, :instance_variable_get, :instance_variable_names, :instance_variable_set, :instance_variables, :is_a?, :itself, :kind_of?, :load_dependency, :marshal_dump, :marshal_load, :method, :method_missing, :methods, :nil?, :object_id, :presence, :presence_in, :present?, :pretty_inspect, :pretty_print, :pretty_print_cycle, :pretty_print_inspect, :pretty_print_instance_variables, :private_methods, :protected_methods, :public_method, :public_methods, :public_send, :remote_byebug, :remove_instance_variable, :require_dependency, :require_or_load, :respond_to?, :send, :singleton_class, :singleton_method, :singleton_methods, :table, :table!, :taint, :tainted?, :tap, :then, :to_enum, :to_h, :to_json, :to_param, :to_query, :to_s, :to_yaml, :trust, :try, :try!, :unloadable, :untaint, :untrust, :untrusted?, :with_options, :yield_self]
Copied!
If you somehow want to inject options with a key matching a core method of the OpenStruct object, simply provide an alias name with the as: option. You can then use the alias accordingly. A popular example would be the option called class
1
class SomeComponent < Matestack::Ui::Component
2
3
required :foo, :bar, class: { as: :my_class }
4
5
def response
6
div class: context.my_class do
7
plain "#{context.foo} - #{context.bar}"
8
end
9
end
10
11
end
Copied!

Text argument

Sometimes you just want to pass in a simple (text) argument rather than a hash with multiple keys:
1
class ExamplePage < Matestack::Ui::Page
2
3
def response
4
div id: "div-on-page" do
5
# simply pass in a string here
6
SomeComponent.("foo from page")
7
end
8
end
9
10
end
Copied!
A component can access this text argument in various ways:
1
class Some::Component < Matestack::Ui::Component
2
3
def response
4
div id: "my-component" do
5
plain self.text # because such an argument is almost always a string
6
# or
7
plain context.text # for compatibility with older releases
8
end
9
end
10
11
end
Copied!
This approach can be combined with injecting Hashes to components:
1
class ExamplePage < Matestack::Ui::Page
2
3
def response
4
div id: "div-on-page" do
5
# simply pass in a string here
6
Some::Component.("foo from page", { foo: "bar" })
7
end
8
end
9
10
end
Copied!
1
class Some::Component < Matestack::Ui::Component
2
3
optional :foo
4
5
def response
6
div id: "my-component" do
7
plain self.text # because such an argument is almost always a string
8
# or
9
plain context.text # for compatibility with older releases
10
plain context.foo
11
end
12
end
13
14
end
Copied!

Yielding inside components

Components can yield a block with access to scope, where a block is defined.
1
class SomeComponent < Matestack::Ui::Component
2
3
def response
4
div id: "my-component" do
5
yield
6
end
7
end
8
9
end
Copied!
Pass a block to a component on the page as shown below:
1
class ExamplePage < Matestack::Ui::Page
2
3
def prepare
4
@foo = "foo from page"
5
end
6
7
def response
8
div id: "div-on-page" do
9
SomeComponent.() do
10
plain @foo
11
end
12
end
13
end
14
15
end
Copied!
Not a fancy example, but this is the result:
1
<div id="div-on-page">
2
<div id="my-component">
3
foo from page
4
</div>
5
</div>
Copied!

Slots

Slots in Matestack allow us to inject whole UI snippets into the component. It's a more specific yielding mechanism as you will yield multiple "named blocks" into the component. Each of these blocks can be referenced and positioned independently in the component,

Slots on the page instance scope

Define the slots within the component file as shown below. Please make sure to inject slots within a hash slots: { ... } into the component.
1
class SomeComponent < Matestack::Ui::Component
2
3
def prepare
4
@foo = "foo from component"
5
end
6
7
def response
8
div id: "my-component" do
9
slot :my_first_slot
10
br
11
slot :my_second_slot
12
end
13
end
14
15
end
Copied!
Slots have access to the scope of the class, where they are defined. In this case @foo
1
class ExamplePage < Matestack::Ui::Page
2
3
def prepare
4
@foo = "foo from page"
5
end
6
7
def response
8
div do
9
some_component slots: {
10
my_first_slot: method(:my_simple_slot),
11
my_second_slot: method(:my_second_simple_slot)
12
}
13
end
14
end
15
16
def my_simple_slot
17
span id: "my_simple_slot" do
18
plain "some content"
19
end
20
end
21
22
def my_second_simple_slot
23
span id: "my_simple_slot" do
24
plain @foo
25
end
26
end
27
28
end
Copied!
This gets rendered into HTML as shown below. Notice that the @foo from the component configuration got overwritten by the page's local @foo!
1
<div>
2
<div id="my-component">
3
<span id="my_simple_slot">
4
some content
5
</span>
6
<br/>
7
<span id="my_simple_slot">
8
foo from page
9
</span>
10
</div>
11
</div>
Copied!

Using slots of components within components

To use component instance scope slots, first define slots within a static component:
1
class Other::Component < Matestack::Ui::Component
2
3
def prepare
4
@foo = "foo from other component"
5
end
6
7
def response
8
div id: "my-other-component" do
9
slot :my_slot_from_component
10
br
11
slot :my_slot_from_page
12
br
13
plain @foo
14
end
15
end
16
17
end
Copied!
and also in some component:
1
class Some::Component < Matestack::Ui::Component
2
3
def prepare
4
@foo = "foo from component"
5
end
6
7
def response
8
div id: "my-component" do
9
other_component slots: {
10
my_slot_from_component: method(:my_slot_from_component),
11
my_slot_from_page: slots[:my_slot_from_page]
12
}
13
end
14
end
15
16
def my_slot_from_component
17
span id: "my-slot-from-component" do
18
plain @foo
19
end
20
end
21
22
end
Copied!
Then, put both components (note that some component uses other component so that's how they're both in here) to use on the example page:
1
class ExamplePage < Matestack::Ui::Page
2
3
def prepare
4
@foo = "foo from page"
5
end
6
7
def response
8
div id: "page-div" do
9
some_component slots: { my_slot_from_page: method(:my_slot_from_page) }
10
end
11
end
12
13
def my_slot_from_page
14
span id: "my-slot-from-page" do
15
plain @foo
16
end
17
end
18
19
end
Copied!
This gets rendered into the HTML below:
1
<div id="page-div">
2
<div id="my-component">
3
<div id="my-other-component">
4
<span id="my-slot-from-component">
5
foo from component
6
</span>
7
<br/>
8
<span id="my-slot-from-page">
9
foo from page
10
</span>
11
<br/>
12
foo from other component
13
</div>
14
</div>
15
</div>
Copied!

Calling slots with params

Sometimes it's necessary to call a slot with params:
1
class SomeComponent < Matestack::Ui::Component
2
3
def response
4
div id: "my-component" do
5
User.last(10).each do |user|
6
slot(:user_card, user)
7
end
8
end
9
end
10
11
end
Copied!
1
class ExamplePage < Matestack::Ui::Page
2
3
def response
4
div do
5
some_component slots: {
6
user_card: method(:user_card)
7
}
8
end
9
end
10
11
def user_card user
12
div class: "card" do
13
plain user.name
14
end
15
end
16
17
end
Copied!