Fieldia

The page format

A page is one JSON document with four parts: what it is about (data), what it holds (fields), how it is laid out (layout), and its name and title. Nothing in it is code, so a page can be stored, sent, generated or edited by a designer.

{
  "fieldia": "0.1",
  "id": "customer",
  "title": "Customer",
  "description": "Optional text under the title",
  "data": { "kind": "record", "model": "partner" },
  "fields": { "...": "..." },
  "layout": { "type": "sheet", "id": "sheet", "children": [] }
}

data: a record or responses

The model is only a name your data source understands; Fieldia passes it through.

fields: what the page holds

Each field is declared once, by name. The layout then points at it, so the same field can be described in one place and placed anywhere.

"fields": {
  "email": {
    "type": "char",
    "label": "Email",
    "help": "We send the joining details here.",
    "required": true,
    "pattern": "^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$"
  },
  "country_id": { "type": "many2one", "label": "Country", "relation": "country" }
}

Every field has a type and a label, and may have help, required, readonly and a default. Each type adds its own options — options for a selection, relation for a link to another record, min and max for a number. Fields lists them all.

layout: how it is arranged

A page's layout is one of four kinds:

LayoutFor
sectionsA form in titled sections, each with 1 to 4 columns. Sign-ups, settings, simple records.
sheetA business record: a title (with an optional photo), a statusbar, buttons, stat buttons, a ribbon, alerts, then sections and tabs, and an optional side panel.
tabsTabs at the top level, each holding sections and fields.
wizardSteps, one at a time, with a progress bar. A step whose condition is false is skipped — that is how a survey branches.

Inside them, these nodes can be nested freely:

NodeWhat it is
fieldA field, by name: { "type": "field", "id": "email", "field": "email" }. It can override the label, choose a widget (such as radio or tags), set a placeholder, span colspan columns, and carry conditions.
sectionA titled group with columns (1–4) and a description.
tabsTabs, each a tab with a label and children.
buttonA button that names an action. Fieldia hands the press to your app with the record; your app decides what it does. It can ask to confirm first.
textA heading, a paragraph or a note.
slotA named place your app fills with its own content — an activity feed, a map, a chart.

Every node has an id, unique in the page. Ids are what a designer, a test or your app use to find a node.

Conditions: invisible, readonly, required

A field node, a section, a tab, a step or a button can be hidden, locked or made required by a condition written in the page. A condition is true, false, or an expression over the record's values:

{ "type": "field", "id": "other_role", "field": "other_role",
  "invisible": "role != 'other'",
  "required": true }

{ "type": "field", "id": "discount", "field": "discount",
  "readonly": "state in ('done', 'cancel') or not is_company" }

Expressions read like Python: == != < > <= >=, in and not in with a list, and or not, True False None, numbers and quoted strings. An empty value is false. Conditions are checked again on every change, and a hidden field's value is left out of what is saved or submitted.

A condition that names a field the page does not have is refused when the page is checked, not when someone fills it in.

Checking a page

import { validatePage } from '@fieldia/core';

const checked = validatePage(json);
if (!checked.ok) console.log(checked.issues);   // [{ path: 'layout.children[0]...', message: '...' }]

validatePage checks the shape, that every id is unique, that every field a node or a condition names exists, and that every condition parses. The viewer runs it too, and refuses a page that fails with the same list of issues. For editors and other languages the format is also a JSON Schema: @fieldia/core/page.schema.json.