See below for an overview of the various possibilities Matestack provides for component implementation:
Use the response
method to define the UI of the component by using other components.
class Some::Component < Matestack::Ui::Component​def responsediv id: "my-component" doplain "hello world!"endend​end
class ExamplePage < Matestack::Ui::Page​def responsediv id: "div-on-page" dosome_componentendend​end
This is the HTML which gets created:
<div id="div-on-page"><div id="my-component">hello world!</div></div>
If no response
method is defined, matestack will look for a corresponding HAML
template file lying next to Ruby component file.
Use a prepare method to resolve data before rendering a component!
class Some::Component < Matestack::Ui::Component​def prepare@some_data = "some data"end​def responsediv id: "my-component" doplain @some_dataendend​end
class ExamplePage < Matestack::Ui::Page​def responsediv id: "div-on-page" dosome_componentendend​end
This is the HTML which gets created:
<div id="div-on-page"><div id="my-component">some data</div></div>
The prepare method comes in handy to read from the database or to resolve content before displaying it!
A component can access request information, e.g. url query params, by calling the params
method:
class Some::Component < Matestack::Ui::Component​def responsediv id: "my-component" doplain params[:foo]endend​end
On the example page, reference the component as usual.
class ExamplePage < Matestack::Ui::Page​def responsediv id: "div-on-page" dosome_componentendend​end
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:
<div id="div-on-page"><div id="my-component">bar</div></div>
Matestack components give you the option to define required and optional properties for a component. It creates helpers for these properties automatically.
Required properties are required for your component to work, like the name suggests. If at least one required property is missing a Matestack::Ui::Core::Properties::PropertyMissingException
is raised.
Declare your required properties by calling requires
as follows:
class SomeComponent < Matestack::Ui::Component​requires :some_property, :some_other​end
You then can use these properties simply by calling the provided helper method, which is generated for you. The helper method name corresponds to the passed property name.
class SomeComponent < Matestack::Ui::Component​requires :some_property, :some_other​def response# display some_property plain inside a div and some_other property inside a paragraph beneath itdiv doplain some_propertyendparagraph text: some_otherend​end
To define optional attributes you can use the same syntax as requires
. Just use optional
instead of requires
. Optional attributes are optional and not validated for presence like required attributes.
class SomeComponent < Matestack::Ui::Component​optional :optional_property, :other_optional_property optional properties could be empty​def response# display optional_property plain inside a div and other_optional_property property inside a paragraph beneath itdiv doplain optional_propertyendparagraph text: other_optional_propertyend​end
Pass the properties as a hash directly to the component when calling it. You can pass any object you like and use it in the component with the helper.
class SomeComponent < Matestack::Ui::Component​requires :some_option,optional :some_other optional properties could be empty​def responsediv doplain some_optionendif some_other.present?paragraph text: some_other[:option]endend​end
Use it in the example page and pass in the properties as a hash
class ExamplePage < Matestack::Ui::Page​def prepare@hello = "hello!"end​def responsediv id: "div-on-page" dosome_component some_option: @hello, some_other: { option: "world!" }endend​end
The outcome is quite as expected:
<div id="div-on-page"><div>hello!</div><p>world!</p></div>
Matestack tries to prevent overriding existing methods while creating helpers. If you pass a property with a name that matches any instance method of your component matestack will raise a Matestack::Ui::Core::Properties::PropertyOverwritingExistingMethodException
. To use property names that would raise this exception, simply provide an alias name with the as:
option. You can then use the alias accordingly.
class SomeComponent < Matestack::Ui::Componentrequires :foo, :bar, method: { as: :my_method }optional response: { as: :my_response }​def responsediv doplain "#{foo} - #{bar} - #{my_method}" string concatenation of properties foo, bar, and method aliased as my_methodendparagraph my_response if my_response.present? response property aliased as my_response inside a paragraph if it is presentendend
Some common names that could not be used as properties:
:method,:params
If no hash was given, a component can also access/accept a simple argument!
class Components::Some::Component < Matestack::Ui::Component​def responsediv id: "my-component" doplain @argumentendend​end
Just make sure to pass an argument on the example page:
class ExamplePage < Matestack::Ui::Page​def responsediv id: "div-on-page" dosimply pass a string heresome_component "foo from page"endend​end
No miracle to find here, just what was expected!
<div id="div-on-page"><div id="my-component">foo from page</div></div>
Components can yield a block with access to scope, where a block is defined. This works the way yield usually works in Ruby. But make sure to explicitly call 'yield_components' within the component response!
class Some::Component < Matestack::Ui::Component​def responsediv id: "my-component" doyield_componentsendend​end
Pass a block to a component on the page as shown below:
class ExamplePage < Matestack::Ui::Page​def prepare@foo = "foo from page"end​def responsediv id: "div-on-page" dosome_component doplain @fooendendend​end
Not a fancy example, but this is the result:
<div id="div-on-page"><div id="my-component">foo from page</div></div>
Use partials to keep the code dry and indentation layers manageable!
In the component definition, see how this time from inside the response, the my_partial
method below is called:
class Some::Component < Matestack::Ui::Component​def responsediv id: "my-component" domy_partial "foo from component"endend​def my_partial textplain textend​end
As everything is already defined in the component, calling the some_component
on the example page is all there is to do:
class ExamplePage < Matestack::Ui::Page​def responsediv id: "div-on-page" docustom_some_componentendend​end
The outcome is the usual, boring HTML response. Below the HTML snippet, a more exciting example of partial usage is waiting!
<div id="div-on-page"><div id="my-component">foo from component</div></div>
Extract code snippets to modules for an even better project structure. First, create a module:
module MySharedPartials​def my_partial textplain textend​end
Include the module in the component:
class Some::Component < Matestack::Ui::Component​include MySharedPartials​def responsediv id: "my-component" domy_partial "foo from component"endend​end
Then reference the component on the example page as before:
class ExamplePage < Matestack::Ui::Page​def responsediv id: "div-on-page" dosome_componentendend​end
The output is unspectacular in this example, but more complex codebases will greatly benefit from this refactoring option!
<div id="div-on-page"><div id="my-component">foo from component</div></div>
Try combining partials with options and slots (see below) for maximum readability, dryness and fun!
Similar to named slots in Vue.js, slots in Matestack allows 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,
Define the slots within the component file as shown below. Please make sure to inject slots within a hash slots: { ... }
into the component.
class Some::Component < Matestack::Ui::Component​requires :slots​def prepare@foo = "foo from component"end​def responsediv id: "my-component" doslot slots[:my_first_slot]brslot slots[:my_second_slot]endend​end
Slots have access to the scope of the class, where they are defined. In this case @foo
class ExamplePage < Matestack::Ui::Page​def prepare@foo = "foo from page"end​def responsediv dosome_component slots: {my_first_slot: my_simple_slot,my_second_slot: my_second_simple_slot}endend​def my_simple_slotslot dospan id: "my_simple_slot" doplain "some content"endendend​def my_second_simple_slotslot dospan id: "my_simple_slot" doplain @fooendendend​end
This gets rendered into HTML as shown below. Notice that the @foo
from the component configuration got overwritten by the page's local @foo
!
<div><div id="my-component"><span id="my_simple_slot">some content</span><br/><span id="my_simple_slot">foo from page</span></div></div>
To use component instance scope slots, first define slots within a static component:
class Other::Component < Matestack::Ui::Component​requires :slots​def prepare@foo = "foo from other component"end​def responsediv id: "my-other-component" doslot slots[:my_slot_from_component]brslot slots[:my_slot_from_page]brplain @fooendend​end
and also in some component:
class Some::Component < Matestack::Ui::Component​requires :slots​def prepare@foo = "foo from component"end​def responsediv id: "my-component" doother_component slots: {my_slot_from_component: my_slot_from_component,my_slot_from_page: slots[:my_slot_from_page]}endend​def my_slot_from_componentslot dospan id: "my-slot-from-component" doplain @fooendendend​end
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:
class ExamplePage < Matestack::Ui::Page​def prepare@foo = "foo from page"end​def responsediv id: "page-div" dosome_component slots: { my_slot_from_page: my_slot_from_page }endend​def my_slot_from_pageslot dospan id: "my-slot-from-page" doplain @fooendendend​end
This gets rendered into the HTML below:
<div id="page-div"><div id="my-component"><div id="my-other-component"><span id="my-slot-from-component">foo from component</span><br/><span id="my-slot-from-page">foo from page</span><br/>foo from other component</div></div></div>
This may seem complicated at first, but it can provide valuable freedom of configuration and great fallbacks in more complex scenarios!