Skip to content

Pages and components

A page is what a route renders. It has up to two parts, and you can author each one separately:

PartWhat it saysAuthored as
Layout (the definition)what the screen looks like: a tree of componentsa YAML file under specs/ui/, the visual editor, or a component tree built in code
View model (optional)what the screen does: state, actions, validation, data accessa class in Java, C# or Python

A page can have only a layout. That page runs entirely in the browser, which is what a static bundle ships. It can have only a view model, in which case Mateu derives the layout from the class’s fields and methods. Or it can have both: a YAML layout bound to a class by the route, which is described in Mixing YAML and code.

This page is one of the building blocks. The running example is the product catalogue.

A definition is one component at the root, with the components it contains nested under it:

# specs/ui/product.yaml — the record page, route products/:id
type: Form
title: ${state.name}
subtitle: Product
toolbar:
- type: Button
label: Edit
buttonStyle: primary
actionable:
type: RouteLink
route: products/${state.id}/edit
content:
- type: FormLayout
content:
- {type: FormField, id: name, label: Name, readOnly: true}
- type: Partial
ref: price-block # price + status, shared with the edit page
buttons:
- type: Button
label: Discontinue
actionId: discontinue # from this page's actions:, or actions.yaml
actions:
- id: discontinue
confirmationRequired: true
restAction:
source: {ref: product-discontinue}
successMessage: Discontinued

The route supplies the record. data: product on the products/:id entry fetches it from the data source into the page state, so the fields show state.name, state.price and the rest.

Beside the component itself, a definition can carry:

KeyMeaning
actionsActions this page owns: flows (steps) and REST calls (restAction). See Actions.
triggersActions that run without a click. See triggers.
viewModelThe class this layout binds to. Omit it on a layout shared by several routes, and let each route entry bind its own.
layout / layoutDeltalayout: wraps the component when you want the keys above beside it. layoutDelta: records human edits on top of a layout Mateu still infers from the view model. See Pages in YAML.

In Java, C# and Python the view model is the page, and Mateu derives the layout from it:

@Title("Product")
public class ProductPage {
String id; // from the :id path parameter
@NotEmpty String name;
@Stereotype(FieldStereotype.money) double price;
ProductStatus status;
@Button
public Message discontinue() {
ProductStore.INSTANCE.discontinue(id);
return new Message("Discontinued");
}
}

The fields become a form, the method becomes a button, and the bean validation becomes validation in both the browser and the server. When you need full control, the same class can implement ComponentTreeSupplier and return the component tree itself, using the same components a YAML file names. Archetypes such as AutoCrud, Wizard and Dashboard give you whole page families from one base class. See The Mateu Way and choosing a page template.

C# and Python have the same shape: [UI] / @ui view models, Crud<T> / Crud[T], and the Language Rosetta maps the annotations.

Every layout, whether it is written in YAML, built fluently in code or derived from a class, is made of components from one catalogue: layouts (VerticalLayout, FormLayout, TabLayout…), fields (FormField), listings (Listing, GridColumn), display components (Text, Badge, MetricCard…), overlays (Dialog, Drawer) and navigation (RouteLink, Menu…). The type: in YAML is the record’s class name, and the catalogue’s JSON Schema is generated from those records. Because of that, the schema, the visual editor’s palette and the renderers can’t disagree.

The page has…With a serverAs a static bundle
only a layoutrendered through the normal sync pathpre-rendered at build time, or shipped raw (specsOnly) and expanded in the browser
a view modelinstantiated fresh on every requestneeds a server. A staticOnly build refuses it