Migrating from 1.x to 2.0

Improvements

    5 to 12 times better rendering performance (depending on the context)
    Change to MIT License
    Core code readability/maintainability --> Enable core contributor team growth
    Vue integration and extenability --> Modern JS workflow and increased flexibility
    Better IDE support --> Removed registry logic enables traceability
    Easier API for component development
    Improved naming schemas
    Reworked docs

License change

We decided to switch back to the MIT License in order to emphasize that we want to create an Open Source project without any commercial restrictions. We will creat a blog post about that soon!

Breaking changes

Trailblazer Cell dependency removed

We did a complete Ruby core rewrite and removed the internal dependencies from cells, haml and trailblazer-cells. We're now using pure Ruby for Matestack's HTML rendering and component based UI structuring. We removed the dependency as we realized that we're just using a small subset of Trailblazer's cell feature and are better of when implementing our own business logic. Thank you Trailblazer for bringing us so far!
We only used Trailblazer's cell API internally. If you just used the documented API of Matestack, you only need to follow the following migration steps:

Rails controller

Application helper

1
class ApplicationController < ActionController::Base
2
3
# old
4
# include Matestack::Ui::Core::ApplicationHelper
5
6
# new
7
include Matestack::Ui::Core::Helper
8
9
end
Copied!

Apps

yield_page

    Use yield instead of yield_page
1
class SomeApp < Matestack::Ui::Component
2
3
def response
4
span do
5
# old
6
# yield_page
7
8
# new
9
yield if block_given?
10
end
11
end
12
13
end
Copied!
or if you want to yield in some method other than response:
1
class SomeApp < Matestack::Ui::App
2
3
def response(&block)
4
main do
5
# old
6
# yield_page
7
8
# new
9
some_partial(&block)
10
end
11
end
12
13
def some_partial(&block)
14
div do
15
yield if block_given?
16
end
17
end
Copied!

Loading state element

1
class ExampleApp::App < Matestack::Ui::App
2
3
def response
4
#...
5
main do
6
# old
7
# yield_page slots: { loading_state: my_loading_state_slot }
8
9
# new
10
yield if block_given?
11
end
12
#...
13
end
14
15
# old
16
# def my_loading_state_slot
17
# slot do
18
# span class: "some-loading-spinner" do
19
# plain "loading..."
20
# end
21
# end
22
# end
23
24
# new
25
def loading_state_element
26
span class: "some-loading-spinner" do
27
plain "loading..."
28
end
29
end
30
31
end
Copied!

Minimal app wrapping removed

    Until 1.5.0, matestack-ui-core rendered a minimal app around pages without explicitly associated app
    This behavior is removed in 2.0.0 -> a page will be rendered without any wrapping app until an app is explicitly associated

Pages

Controller instance variables
    No more implicit controller instance variable access - > inject like you would inject options into a component
1
class SomeController < ApplicationController
2
3
include Matestack::Ui::Core::Helper
4
5
def overview
6
@foo = "bar"
7
# old
8
# render Pages::SomePage
9
10
# new
11
render Pages::SomePage, foo: @foo
12
end
13
14
end
Copied!
1
class Pages::SomePage < Matestack::Ui::Page
2
3
required :foo
4
5
def response
6
plain context.foo # "bar"
7
end
8
9
end
Copied!

Components

Registry

Definition
1
module Components::Registry
2
3
# old
4
# Matestack::Ui::Core::Component::Registry.register_components(
5
# some_component: Components::SomeComponent,
6
# # ...
7
# )
8
9
# new
10
def some_component(text=nil, options=nil, &block)
11
Components::SomeComponent.(text, options, &block)
12
end
13
14
# ...
15
16
end
Copied!
Usage
    include your registry in components, pages, and apps directly instead of including it in your controller
    create base classes for apps, pages and components inherting from Matestack's base classes including the registry(ies) and let your apps, pages and components inherit from them
As this is just a plain Ruby module, you need to include it in all contexts you want to use the alias method (unlike the registry prior to 2.0.0). It's a good idea to create your own ApplicationPage, ApplicationComponent and ApplicationApp as base classes for your pages, components ans apps. In there, you include your component registry module(s) only once and have access to the alias methods in all child classes:
1
class ApplicationPage < Matestack::Ui::Page
2
3
include Components::Registry
4
5
end
6
7
class ApplicationComponent < Matestack::Ui::Component
8
9
include Components::Registry
10
11
end
12
13
class ApplicationApp< Matestack::Ui::App
14
15
include Components::Registry
16
17
end
Copied!

Slots

1
class Components::SomeComponent < Matestack::Ui::Component
2
3
# old
4
# optional :slots
5
6
# new
7
# Do not define any slots as optional or required
8
9
def response
10
# old
11
# slot slots[:some_slot]
12
13
# new
14
slot :some_slot
15
end
16
17
end
Copied!
1
class Components::SomePage < Matestack::Ui::Page
2
3
def response
4
# old
5
# some_component slots: { some_slot: some_slot }
6
7
# new
8
some_component slots: { some_slot: method(:some_slot) }
9
end
10
11
protected
12
13
# old
14
# def some_slot
15
# slot do
16
# plain "foo #{bar}"
17
# end
18
# end
19
20
# new
21
def some_slot
22
plain "foo #{bar}"
23
end
24
25
def bar
26
plain "bar"
27
end
28
29
end
Copied!

Text options

1
class SomePage < Matestack::Ui::Page
2
3
def response
4
# old
5
# span text: "foo", class: "bar" # still suported for compatibility reasons
6
7
# new
8
span "foo", class: "bar"
9
end
10
11
end
Copied!

Required options notation

1
class Components::SomeComponent < Matestack::Ui::Component
2
3
# old
4
# requires :foo # still suported for compatibility reasons
5
6
# new
7
required :foo
8
9
end
Copied!

Required/optional options access

1
class Components::SomeComponent < Matestack::Ui::Component
2
3
required :foo
4
optional :bar
5
6
# old
7
# def response
8
# plain foo
9
# plain options[:foo]
10
# plain bar
11
# plain options[:bar]
12
# end
13
14
# new
15
def response
16
plain context.foo # options[:foo] is not available anymore!
17
plain context.bar # options[:bar] is not available anymore!
18
end
19
20
end
Copied!

Required/optional options alias

1
class Components::SomeComponent < Matestack::Ui::Component
2
3
optional class: { as: :bs_class }
4
5
# old
6
# def response
7
# span class: bs_class
8
# end
9
10
# new
11
def response
12
span class: context.bs_class
13
end
14
15
end
Copied!

HTML tag attributes

Definition and rendering on default HTML tags
    No more whitelisting through explicitly defined HTML ATTRIBUTES
    On default HTML tags, all defined options will be rendered as attributes on that tag
1
class SomePage < Matestack::Ui::Page
2
3
def response
4
# old
5
# span class: "foo", attributes: { bar: :baz, data: { hello: "world" }}
6
7
# new
8
span class: "foo", bar: :baz, data: { hello: "world" }
9
end
10
# <span class="foo" bar="baz" data-hello="world">
11
12
end
Copied!
Definition and rendering within components
    No more whitelisting through explicitly defined HTML ATTRIBUTES
    No more access through html_attributes, use simple options instead
1
class SomePage < Matestack::Ui::Page
2
3
def response
4
some_component id: 1, class: "foo", bar: :baz, data: { hello: "world" }
5
end
6
# <span id=1, class="foo bar" bar="baz" other="foo" data-hello="world" data-foo="bar">
7
8
end
Copied!
1
class Components::SomeComponent < Matestack::Ui::Component
2
3
# old
4
# def response
5
# span some_processed_attributes
6
# end
7
8
# def some_processed_attributes
9
# processed = {}.tap do |attrs|
10
# attrs[:class] = "#{options[:class]} 'bar'",
11
# attrs[:attributes] = { bar: options[:bar], other: :foo, data: options[:data].merge({ foo: "bar" }) }
12
# end
13
# html_attributes.merge(processed)
14
# end
15
16
# new
17
def response
18
span some_processed_attributes
19
end
20
21
def some_processed_attributes
22
{}.tap do |attrs|
23
attrs[:class] = "#{options[:class]} 'bar'",
24
attrs[:bar] = options[:bar]
25
attrs[:other] = :foo
26
attrs[:data] = options[:data].merge({ foo: "bar" })
27
end
28
end
29
30
end
Copied!

yield_components

1
class Components::SomeComponent < Matestack::Ui::Component
2
3
def response
4
span do
5
# old
6
# yield_components
7
# new
8
yield if block_given?
9
end
10
end
11
12
end
Copied!
or if you want to yield in some method other than response:
1
class Components::SomeComponent < Matestack::Ui::Component
2
3
def response(&block)
4
span do
5
# old
6
# yield_components
7
# new
8
some_partial(&block)
9
end
10
end
11
12
def some_partial(&block)
13
div do
14
yield if block_given?
15
end
16
end
Copied!

Vue.js

    matestack-ui-core does not include prebundle packs for Sprockets anymore
    MatestackUiCore is not available in the global window object anymore by default
    Please use the webpacker approach in order to manage and import your JavaScript assets

Vue.js integration/mounting

    Within your application pack, you now have to import and mount Vue/Vuex manually
1
// old
2
// import MatestackUiCore from 'matestack-ui-core'
3
4
// new
5
import Vue from 'vue/dist/vue.esm'
6
import Vuex from 'vuex'
7
8
import MatestackUiCore from 'matestack-ui-core'
9
10
let matestackUiApp = undefined
11
12
document.addEventListener('DOMContentLoaded', () => {
13
matestackUiApp = new Vue({
14
el: "#matestack-ui",
15
store: MatestackUiCore.store
16
})
17
})
Copied!

Vue.js component definition

1
// old
2
// MatestackUiCore.Vue.component('some-component', {
3
// mixins: [MatestackUiCore.componentMixin],
4
// data() {
5
// return {};
6
// },
7
// mounted: function() {
8
9
// },
10
// });
11
12
// new
13
import Vue from 'vue/dist/vue.esm'
14
import Vuex from 'vuex' // if required
15
import axios from 'axios' // if required
16
17
import MatestackUiCore from 'matestack-ui-core'
18
19
Vue.component('some-component', {
20
mixins: [MatestackUiCore.componentMixin],
21
data() {
22
return {};
23
},
24
mounted: function() {
25
26
},
27
});
Copied!

Vue.js component name helper

1
class Components::SomeComponent < Matestack::Ui::VueJsComponent
2
3
# old
4
# vue_js_component_name "some-component"
5
6
# new
7
vue_name "some-component"
8
9
end
Copied!

Vue.js props injection/usage

    No more implicit injection of all options into Vue.js component
    No more @component_config instance variable and setup method
    Use explicit method in order to inject specific options into Vue.js components
1
class SomePage < Matestack::Ui::Page
2
3
def response
4
some_component id: 1, class: "foo", foo: "hello" bar: "world"
5
end
6
7
end
Copied!
1
class Components::SomeComponent < Matestack::Ui::VueJsComponent
2
3
vue_name "some-component"
4
5
required :bar
6
7
# old
8
# all options are implicitly injected and used as vue props
9
# or specific vue props through setup method
10
# def setup
11
# @component_config[:baz] = "baz"
12
# end
13
14
# new
15
def vue_props
16
{}.tap do |props|
17
props[:foo] = options[:foo]
18
props[:bar] = context.bar
19
props[:baz] = "baz"
20
end
21
end
22
23
24
end
Copied!
1
Vue.component('some-component', {
2
mixins: [MatestackUiCore.componentMixin],
3
data() {
4
return {};
5
},
6
mounted: function() {
7
8
// old
9
// console.log(this.componentConfig["id"]) // undefined!
10
// console.log(this.componentConfig["foo"]) // hello
11
// console.log(this.componentConfig["bar"]) // world
12
// console.log(this.componentConfig["baz"]) // baz
13
14
// new
15
console.log(this.props["id"]) // undefined!
16
console.log(this.props["foo"]) // hello
17
console.log(this.props["bar"]) // world
18
console.log(this.props["baz"]) // baz
19
20
},
21
});
Copied!

Form components

Root form component name

    renamed form to matestack_form as form is now rendering the HTML form
    matestack_form renders the Vue.js driven form as prior to 2.0.0
1
def response
2
# old
3
# form form_config do
4
5
# new
6
matestack_form form_config do
7
form_input key: :name, type: :text, label: 'Name'
8
# ...
9
button "submit", type: :submit
10
end
11
end
Copied!

Submit

    form_submit component removed, use something like button 'submit', type: :submit instead
    get Vue.js form loading state via simple loading instead of loading()
1
def response
2
matestack_form form_config do
3
form_input key: :name, type: :text, label: 'Name'
4
# ...
5
# old
6
# form_submit do
7
# button text: "submit"
8
# end
9
10
# new
11
button "submit", type: :submit
12
13
# and optionally:
14
button "submit", type: :submit, "v-if": "!loading"
15
button "loading...", type: :submit, disabled: true, "v-if": "loading"
16
end
17
end
Copied!

Custom form components

Base class
Example for Form Input, adapt all other custom components accordingly:
1
# old
2
# class Components::MyFormInput < Matestack::Ui::Core::Form::Input::Base
3
4
# new
5
class Components::MyFormInput < Matestack::Ui::VueJs::Components::Form::Input
6
7
# old
8
# vue_js_component_name "my-form-input"
9
10
# new
11
vue_name "my-form-input"
12
13
# old
14
# def prepare
15
# # optionally add some data here, which will be accessible within your Vue.js component
16
# @component_config[:foo] = "bar"
17
# end
18
19
# new
20
def vue_props
21
{
22
foo: "bar"
23
}
24
end
25
26
def response
27
# exactly one root element is required since this is a Vue.js component template
28
div do
29
label text: "my form input"
30
input input_attributes.merge(class: "flatpickr")
31
render_errors
32
end
33
end
34
35
end
Copied!

Collection component

Helper

1
class SomePage < Matestack::Ui::Page
2
3
# old
4
# include Matestack::Ui::VueJs::Components::Collection::Helper
5
6
# new
7
include Matestack::Ui::VueJs::Components::Collection::Helper
8
9
end
Copied!

Filter

    It's now possible to use ALL form child components within a collection_filter
1
class SomePage < Matestack::Ui::Page
2
3
# old
4
# include Matestack::Ui::VueJs::Components::Collection::Helper
5
6
# new
7
include Matestack::Ui::VueJs::Components::Collection::Helper
8
9
def response
10
collection_filter @collection.config do
11
# old
12
# collection_filter_input key: :foo, type: :text, label: 'Text'
13
# collection_filter_select key: :buz, options: [1,2,3], label: 'Dropdown'
14
15
# new
16
form_input key: :foo, type: :text, label: 'Text'
17
form_checkbox key: :bar, label: 'True/False'
18
form_checkbox key: :baz, options: [1,2,3], label: 'Multi select via checkboxes'
19
form_select key: :buz, options: [1,2,3], label: 'Dropdown'
20
#...
21
22
# old
23
# collection_filter_submit do
24
# button text: 'Submit'
25
# end
26
27
# new
28
button 'Submit', type: "submit"
29
30
# same
31
collection_filter_reset do
32
button 'Reset'
33
end
34
end
35
end
36
37
end
Copied!

Onclick

    changed from rendering a div as root element to a a tag in order to have an inline root element being consistent with transition and action
this possibly breaks the appearance of your UI as we're switching from a block to an inline root element
1
onclick emit: "hello" do
2
button "Klick!"
3
end
Copied!
will now render:
1
<a href="#" class="matestack-onclick-component-root">
2
<button>Klick!</button>
3
</a>
Copied!
instead of:
1
<div class="matestack-onclick-component-root">
2
<button>Klick!</button>
3
</div>
Copied!
You can keep the block style by simply applying following styles to your application:
1
.matestack-onclick-component-root{
2
display: block;
3
}
Copied!
    link is now calling the HTML link tag instead of rendering an a tag
    use a href: ... or a path: ... instead

Isolate

isolate component doesn't raise 'not authorized' anymore. When isolate is not authorized no content is returned. Only a warning is logged to the console

Rails view

rails_view is replaced by rails_render. Use rails_render partial: '/some_partial', locals: { foo: 1 }; rails_render file: '/some_view', locals: { foo: 1 } to render a partial or a file or anything you want. Use the same as you would use rails render

Component argument

    Pass in text arguments like header 'Your headline', color: :blue
    Access it now with self.text or context.text instead of @argument

Minor HTML rendering changes

    In general: content blocks take precedence over text or :text option
    area coords will no longer be automatically joined -> html option does exactly what is expected, no magic
    italic and icon are now called with the corresponding html tag i
    a has no more option :path which can take a symbol and renders it with rails url helper. Use :href/:path with or without rails url helper instead
    unescaped renamed to unescape as it fits the naming conventions more. For example rails html_escape not html_escaped. unescaped is deprecated now
    video has no more magic creation of source tag or automatically fetch asset path to keep dsl as close to html as possible
    removed alias pg for paragraph
    transition can no longer handle symbols as path. Use rails path helper instead
    Matestack::Ui::DynamicActionviewComponent, Matestack::Ui::Core::Actionview::Dynamic and static removed -> is not needed
Last modified 6mo ago