The YAML format
The Modular framework provides tools for creating knowledge apps by combining existing modules. App creators can combine these modules and describe the visual structure of their apps by writing YAML files that follow a specific format. This YAML format exposes the framework API and provides helpers to write these apps' descriptions more easily. This document is meant to explain the format of this YAML file. Here's the first example:
---
root:
type: Controller.Mesh
slots:
window:
type: Window.Simple
slots:
content:
type: ContentGroup.StaticText
properties:
label: Hello World
This is the simplest working app that can be described using the YAML format.
The app description follows a tree-like structure, always starting from a root
node.
Each node represents a module of the framework.
The most basic components of a node are:
- the
type
of the module to be used in that node. - the
properties
of the module. - the
slots
of that module.
A real example:
---
root:
type: Controller.Mesh
slots:
window:
type: Window.Simple
slots:
content:
type: Pager.Simple
slots:
home-page:
type: ContentGroup.ContentGroup
properties:
expand: true
slots:
arrangement:
type: Arrangement.List
properties:
expand: true
slots:
card:
type: Card.List
properties:
expand: true
selection:
type: Selection.All
slots:
filter:
type: Filter.Articles
order:
type: Order.Sequence
This example expands the previous one. Now the app is capable of handling pages and displays a list of articles sorted in a sequence, on its home page. Extracting one of the nodes to see its components:
arrangement:
type: Arrangement.List
properties:
expand: true
slots:
card:
This is a node named arrangement
for a module type Arrangement.List, which sets a property named expand
, with only one slot named card
.
The slots
key is used to add sub-nodes, and to construct the tree.
The list of available properties
and slots
depends on each type of module.
As shown in the full example above, we can create a wide variety of apps by combining and constructing module trees.
Features
Shortdef
To write more readable and compact apps' descriptions, the format includes a shortdef
key.
The example below is identical to the previous one, except that it uses the key:
---
root:
type: Controller.Mesh
slots:
window:
type: Window.Simple
slots:
content:
type: Pager.Simple
slots:
home-page:
shortdef: 'ContentGroup.ContentGroup(expand: true)'
slots:
arrangement:
shortdef: 'Arrangement.List(expand: true)'
slots:
card: 'Card.List(expand: true)'
selection:
type: Selection.All
slots:
filter: Filter.Articles
order: Order.Sequence
In this example, the type
and properties
keys are combined into single lines with the help of the shortdef
key, e.g. shortdef: 'Arrangement.List(expand: true)'
.
When no slots are needed, the shortdef
key can be omitted, using the short description directly on the slot name, e.g. card: 'Card.List(expand: true)'
.
References
Some modules need access to other modules' properties.
To do this, the format includes the id
and references
keys, see the example below:
---
root:
type: Controller.Mesh
slots:
window:
type: Window.Simple
slots:
content:
type: Pager.Simple
slots:
home-page:
shortdef: 'Layout.InfiniteScrolling(expand: true)'
references:
lazy-load: all-articles
slots:
content:
shortdef: 'ContentGroup.ContentGroup(expand: true)'
slots:
arrangement:
shortdef: 'Arrangement.List(expand: true)'
slots:
card: 'Card.List(expand: true)'
selection:
type: Selection.All
id: all-articles
slots:
filter: Filter.Articles
order: Order.Sequence
In this example a Layout.InfiniteScrolling module is added, which needs access to the Selection.All module.
To do this, the selection module defines its identifier all-articles
using the id
key.
Then, the scrolling module references
the selection using the identifier.
The lazy-load
reference name is what the scrolling module uses to access this reference.
The list of available references names depends on each type of module.
Styles
App creators will likely need to style specific nodes and, to avoid complex CSS selectors, the easiest way to do it is by adding custom CSS classes.
The format includes the styles
key to add one or more CSS classes.
See the example below:
---
root:
type: Controller.Mesh
slots:
window:
type: Window.Simple
slots:
content:
type: Pager.Simple
slots:
home-page:
shortdef: 'Layout.InfiniteScrolling(expand: true)'
references:
lazy-load: all-articles
slots:
content:
shortdef: 'ContentGroup.ContentGroup(expand: true)'
styles:
- ContentGroup--articles
slots:
arrangement:
shortdef: 'Arrangement.List(expand: true)'
slots:
card: 'Card.List(expand: true)'
selection:
type: Selection.All
id: all-articles
slots:
filter: Filter.Articles
order: Order.Sequence
In this example, a ContentGroup--articles
BEM-style CSS class is added to a node.
After that, the CSS class can be used from the app's theme to style that specific node.
Imports
Each app must include its own app description, but there is no need to write it from scratch.
The framework provides pre-defined apps' descriptions or presets, for different kinds of app experiences.
The format includes the !import
tag to use these presets.
See the example below:
---
!import 'news'
In this example the app includes its own complete copy of the news
preset, provided by the framework.
The copy occurs while building the app.
Overrides
Using a preset can be limiting, as app creators often want to customize things. One way to overcome this limitation is to manually copy and modify the preset, but forking the preset makes the maintenance of the apps harder. Overrides provides a better way of handling this situation, by allowing app creators to customize specific parts of a preset without forking it. The format includes two different override methods: variables overrides and custom overrides.
Variables and Overrides
The YAML format allows to add customization points to the presets, by declaring variables in a vars
section of the preset.
Each variable is defined with a default value and the variables can be referenced from within the preset using the refvar
key.
See the example below:
---
vars:
home-arrangement:
shortdef: 'Arrangement.List(expand: true)'
home-card:
shortdef: 'Card.List(expand: true)'
root:
type: Controller.Mesh
slots:
window:
type: Window.Simple
slots:
content:
type: Pager.Simple
slots:
home-page:
shortdef: 'Layout.InfiniteScrolling(expand: true)'
references:
lazy-load: all-articles
slots:
content:
shortdef: 'ContentGroup.ContentGroup(expand: true)'
styles:
- ContentGroup--articles
slots:
arrangement:
refvar: $home-arrangement
slots:
card: $home-card
selection:
type: Selection.All
id: all-articles
slots:
filter: Filter.Articles
order: Order.Sequence
In this example, two variables home-arrangement
and home-card
were added to the example from before.
These variables are referenced from within the preset using the refvar
key.
Note that a $
prefix is needed for referencing variable names.
A short form is also available when referencing variables from a node name, e.g. card: $home-card
.
Once variables are available in a preset, the app creators can use and customize these presets by overriding the defaults.
To override the preset defaults an overrides
section must be added to the app description.
See the example below:
---
overrides:
home-arrangement:
shortdef: 'Arrangement.Grid(expand: true)'
home-card:
shortdef: 'Card.Default(expand: true)'
---
!import 'example_preset'
In this example, the preset is imported and these two variables are overridden.
Note that this requires to split the same YAML file in two documents, adding the YAML ---
separator.
The benefit of using variables overrides is that these are more likely to be kept in future versions of the framework.
If a variable is removed from the framework, the override will be ignored.
Custom Overrides
There are other cases where app creators need to customize parts of a preset that are not exposed with variables. In these cases, custom overrides can be used as the example below:
---
overrides:
home-arrangement:
shortdef: 'Arrangement.Grid(expand: true)'
home-card:
shortdef: 'Card.Default(expand: true)'
root.window.content:
type: Pager.ParallaxBackground
---
!import 'example_preset'
In this example, a custom override was added to the overrides section to customize a node that is not exposed with a variable.
Instead of a variable name, the node identifier is composed of the names of the nodes from the root node to the target node, forming a path, e.g. root.window.content
.
The benefit of this feature is that app creators can modify any portion of the preset. But power comes with a price, these paths can change if the preset changes its structure, e.g. during a refactor of the preset. In that case, the custom override will be ignored. Therefore, use this override method with care and always double check when porting apps to newer major versions of the framework.
The results of the search are