Migrating from 1.x to 2.0
- 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
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 create a blog post about that soon!
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 off 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 below migration steps:class ApplicationController < ActionController::Base
# old
# include Matestack::Ui::Core::ApplicationHelper
# new
include Matestack::Ui::Core::Helper
end
- Use
yield
instead ofyield_page
class SomeApp < Matestack::Ui::Component
def response
span do
# old
# yield_page
# new
yield if block_given?
end
end
end
or if you want to yield in some method other than
response
:class SomeApp < Matestack::Ui::App
def response(&block)
main do
# old
# yield_page
# new
some_partial(&block)
end
end
def some_partial(&block)
div do
yield if block_given?
end
end
class ExampleApp::App < Matestack::Ui::App
def response
#...
main do
# old
# yield_page slots: { loading_state: my_loading_state_slot }
# new
yield if block_given?
end
#...
end
# old
# def my_loading_state_slot
# slot do
# span class: "some-loading-spinner" do
# plain "loading..."
# end
# end
# end
# new
def loading_state_element
span class: "some-loading-spinner" do
plain "loading..."
end
end
end
- 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
Controller instance variables
- No more implicit controller instance variable access - > inject like you would inject options into a component
class SomeController < ApplicationController
include Matestack::Ui::Core::Helper
def overview
@foo = "bar"
# old
# render Pages::SomePage
# new
render Pages::SomePage, foo: @foo
end
end
class Pages::SomePage < Matestack::Ui::Page
required :foo
def response
plain context.foo # "bar"
end
end
Definition
Usage
module Components::Registry
# old
# Matestack::Ui::Core::Component::Registry.register_components(
# some_component: Components::SomeComponent,
# # ...
# )
# new
def some_component(text=nil, options=nil, &block)
Components::SomeComponent.(text, options, &block)
end
# ...
end
- Include your registry in components, pages, and apps directly instead of including it in your controller.
or
- 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
ApplicationVueJsComponent
and ApplicationApp
as base classes for your pages, components and apps. There, you include your component registry module(s) only once and have access to the alias methods in all child classes.
# Example base class strategy usage
class ApplicationPage < Matestack::Ui::Page
include Components::Registry
end
class ApplicationComponent < Matestack::Ui::Component
include Components::Registry
end
class ApplicationVueJsComponent < Matestack::Ui::VueJsComponent
include Components::Registry
end
class ApplicationApp < Matestack::Ui::App
include Components::Registry
end
class Components::SomeComponent < Matestack::Ui::Component
# old
# optional :slots
# new
# Do not define any slots as optional or required
def response
# old
# slot slots[:some_slot]
# new
slot :some_slot
end
end
class Components::SomePage < Matestack::Ui::Page
def response
# old
# some_component slots: { some_slot: some_slot }
# new
some_component slots: { some_slot: method(:some_slot) }
end
protected
# old
# def some_slot
# slot do
# plain "foo #{bar}"
# end
# end
# new
def some_slot
plain "foo #{bar}"
end
def bar
plain "bar"
end
end
class SomePage < Matestack::Ui::Page
def response
# old
# span text: "foo", class: "bar" # still suported for compatibility reasons
# new
span "foo", class: "bar"
end
end
class Components::SomeComponent < Matestack::Ui::Component
# old
# requires :foo # still suported for compatibility reasons
# new
required :foo
end
class Components::SomeComponent < Matestack::Ui::Component
required :foo
optional :bar
# old
# def response
# plain foo
# plain options[:foo]
# plain bar
# plain options[:bar]
# end
# new
def response
plain context.foo # options[:foo] is not available anymore!
plain context.bar # options[:bar] is not available anymore!
end
end
class Components::SomeComponent < Matestack::Ui::Component
optional class: { as: :bs_class }
# old
# def response
# span class: bs_class
# end
# new
def response
span class: context.bs_class
end
end
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
class SomePage < Matestack::Ui::Page
def response
# old
# span class: "foo", attributes: { bar: :baz, data: { hello: "world" }}
# new
span class: "foo", bar: :baz, data: { hello: "world" }
end
# <span class="foo" bar="baz" data-hello="world">
end
Definition and rendering within components
- No more whitelisting through explicitly defined HTML ATTRIBUTES
- No more access through
html_attributes
, use simpleoptions
instead
class SomePage < Matestack::Ui::Page
def response
some_component id: 1, class: "foo", bar: :baz, data: { hello: "world" }
end
# <span id=1, class="foo bar" bar="baz" other="foo" data-hello="world" data-foo="bar">
end
class Components::SomeComponent < Matestack::Ui::Component
# old
# def response
# span some_processed_attributes
# end
# def some_processed_attributes
# processed = {}.tap do |attrs|
# attrs[:class] = "#{options[:class]} 'bar'",
# attrs[:attributes] = { bar: options[:bar], other: :foo, data: options[:data].merge({ foo: "bar" }) }
# end
# html_attributes.merge(processed)
# end
# new
def response
span some_processed_attributes
end
def some_processed_attributes
{}.tap do |attrs|
attrs[:class] = "#{options[:class]} 'bar'",
attrs[:bar] = options[:bar]
attrs[:other] = :foo
attrs[:data] = options[:data].merge({ foo: "bar" })
end
end
end
class Components::SomeComponent < Matestack::Ui::Component
def response
span do
# old
# yield_components
# new
yield if block_given?
end
end
end
or if you want to yield in some method other than response:
class Components::SomeComponent < Matestack::Ui::Component
def response(&block)
span do
# old
# yield_components
# new
some_partial(&block)
end
end
def some_partial(&block)
div do
yield if block_given?
end
end
matestack-ui-core
does not include prebundle packs for Sprockets anymoreMatestackUiCore
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
- Within your application pack, you now have to import and mount Vue/Vuex manually
// old
// import MatestackUiCore from 'matestack-ui-core'
// new
import Vue from 'vue/dist/vue.esm'
import Vuex from 'vuex'
import MatestackUiCore from 'matestack-ui-core'
let matestackUiApp = undefined
document.addEventListener('DOMContentLoaded', () => {
matestackUiApp = new Vue({
el: "#matestack-ui",
store: MatestackUiCore.store
})
})
// old
// MatestackUiCore.Vue.component('some-component', {
// mixins: [MatestackUiCore.componentMixin],
// data() {
// return {};
// },
// mounted: function() {
// },
// });
// new
import Vue from 'vue/dist/vue.esm'
import Vuex from 'vuex' // if required
import axios from 'axios' // if required
import MatestackUiCore from 'matestack-ui-core'
Vue.component('some-component', {
mixins: [MatestackUiCore.componentMixin],
data() {
return {};
},
mounted: function() {
},
});
class Components::SomeComponent < Matestack::Ui::VueJsComponent
# old
# vue_js_component_name "some-component"
# new
vue_name "some-component"
end
- No more implicit injection of all options into Vue.js component
- No more
@component_config
instance variable andsetup
method - Use explicit method in order to inject specific options into Vue.js components
class SomePage < Matestack::Ui::Page
def response
some_component id: 1, class: "foo", foo: "hello" bar: "world"
end
end
class Components::SomeComponent < Matestack::Ui::VueJsComponent
vue_name "some-component"
required :bar
# old
# all options are implicitly injected and used as vue props
# or specific vue props through setup method
# def setup
# @component_config[:baz] = "baz"
# end
# new
def vue_props
{}.tap do |props|
props[:foo] = options[:foo]
props[:bar] = context.bar
props[:baz] = "baz"
end
end
end
Vue.component('some-component', {
mixins: [MatestackUiCore.componentMixin],
data() {
return {};
},
mounted: function() {
// old
// console.log(this.componentConfig["id"]) // undefined!
// console.log(this.componentConfig["foo"]) // hello
// console.log(this.componentConfig["bar"]) // world
// console.log(this.componentConfig["baz"]) // baz
// new
console.log(this.props["id"]) // undefined!
console.log(this.props["foo"]) // hello
console.log(this.props["bar"]) // world
console.log(this.props["baz"]) // baz
},
});
- renamed
form
tomatestack_form
asform
is now rendering the HTML form matestack_form
renders the Vue.js driven form as prior to 2.0.0
def response
# old
# form form_config do
# new
matestack_form form_config do
form_input key: :name, type: :text, label: 'Name'
# ...
button "submit", type: :submit
end
end
form_submit
component removed, use something likebutton 'submit', type: :submit
instead- get Vue.js form loading state via simple
loading
instead ofloading()
def response
matestack_form form_config do
form_input key: :name, type: :text, label: 'Name'
# ...
# old
# form_submit do
# button text: "submit"
# end
# new
button "submit", type: :submit
# and optionally:
button "submit", type: :submit, "v-if": "!loading"
button "loading...", type: :submit, disabled: true, "v-if": "loading"
end
end
Base class
Example for Form Input, adapt all other custom components accordingly:
# old
# class Components::MyFormInput < Matestack::Ui::Core::Form::Input::Base
# new
class Components::MyFormInput < Matestack::Ui::VueJs::Components::Form::Input
# old
# vue_js_component_name "my-form-input"
# new
vue_name "my-form-input"
# old
# def prepare
# # optionally add some data here, which will be accessible within your Vue.js component
# @component_config[:foo] = "bar"
# end
# new
def vue_props
{
foo: "bar"
}
end
def response
# exactly one root element is required since this is a Vue.js component template
div do
label text: "my form input"
input input_attributes.merge(class: "flatpickr")
render_errors
end
end
end
class SomePage < Matestack::Ui::Page
# old
# include Matestack::Ui::VueJs::Components::Collection::Helper
# new
include Matestack::Ui::VueJs::Components::Collection::Helper
end
- It's now possible to use ALL form child components within a
collection_filter
class SomePage < Matestack::Ui::Page
# old
# include Matestack::Ui::VueJs::Components::Collection::Helper
# new
include Matestack::Ui::VueJs::Components::Collection::Helper
def response
collection_filter @collection.config do
# old
# collection_filter_input key: :foo, type: :text, label: 'Text'
# collection_filter_select key: :buz, options: [1,2,3], label: 'Dropdown'
# new
form_input key: :foo, type: :text, label: 'Text'
form_checkbox key: :bar, label: 'True/False'
form_checkbox key: :baz, options: [1,2,3], label: 'Multi select via checkboxes'
form_select key: :buz, options: [1,2,3], label: 'Dropdown'
#...
# old
# collection_filter_submit do
# button text: 'Submit'
# end
# new
button 'Submit', type: "submit"
# same
collection_filter_reset do
button 'Reset'
end
end
end
end
- changed from rendering a
div
as root element to aa
tag in order to have an inline root element being consistent withtransition
andaction
this possibly breaks the appearance of your UI as we're switching from a block to an inline root element
onclick emit: "hello" do
button "Klick!"
end
will now render:
<a href="#" class="matestack-onclick-component-root">
<button>Klick!</button>
</a>
instead of:
<div class="matestack-onclick-component-root">
<button>Klick!</button>
</div>
You can keep the block style by simply applying following styles to your application:
.matestack-onclick-component-root{
display: block;
}
- link is now calling the HTML
link
tag instead of rendering ana
tag - use
a href: ...
ora path: ...
instead
isolate
component doesn't raise 'not authorized' anymore. When isolate is not authorized no content is returned. Only a warning is logged to the consolerails_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
- Pass in text arguments like
header 'Your headline', color: :blue
- Access it now with
self.text
orcontext.text
instead of@argument
- 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 magicitalic
andicon
are now called with the corresponding html tagi
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 insteadunescaped
renamed tounescape
as it fits the naming conventions more. For example rails html_escape not html_escaped.unescaped
is deprecated nowvideo
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
forparagraph
transition
can no longer handle symbols as path. Use rails path helper insteadMatestack::Ui::DynamicActionviewComponent, Matestack::Ui::Core::Actionview::Dynamic
and static removed -> is not needed
Last modified 1yr ago