Appearance
How does a page get its data?
There are exactly three ways data reaches a page. Knowing which is which stops the "0 rows, no error" confusion.
1. The grid — via metadata.dataSource
A core.grid block has no data configuration on it. It renders whatever the page's top-level metadata.dataSource resolves to:
page.metadata.dataSource ──▶ a metadata/provider/*.json whose `name` matches
──▶ that provider's basePath (e.g. /api/v1/entities/oeq_equipment/records)
──▶ the Entity Engine serves rows at basePath + /queryThe grid's externalFilter binding supplies query filters; refreshTrigger re-runs the query. This is the only mechanism for core.grid. See Wire a page's data.
2. Page state — via action chains
Everything that isn't the grid — KPI numbers, a detail panel, a form's current values, dialog open/closed flags — lives in page state, a bag of key/value pairs. You:
- write it with
setValueactions:setValue page.kpiTotal = ${kpi.results.total.value} - read it with bindings:
{ "source": "binding", "binding": { "scope": "page", "key": "kpiTotal" } }
An action chain is an ordered list of actions on a block event (mounted, clicked, committed, rowClicked). The typical loader chain:
mounted:
callApi POST /api/v1/data-services/oeq-equipment-kpis/execute → output "kpi"
setValue page.kpiTotal = ${kpi.results.total.value}
setValue page.kpiAvailable = ${kpi.results.available.value}${kpi....} references the output name of an earlier callApi in the same chain. ${event.new} (in a committed handler) is the value the user entered. ${event.id} (in rowClicked) is the clicked row's id.
3. A lookup field — via optionsSourceKey
A core.lookup block calls a search Data Service named by its optionsSourceKey, passing the user's keystrokes as the search parameter, and expects back { value, label } rows. Its committed event fires with event.new = the picked record's id. See the lookup recipe.
Data Service response shapes (the part everyone gets wrong once)
operation | /execute returns | bind |
|---|---|---|
count | { "value": 12 } | ${out.value} |
search / get | { "items": [...] } (or { "records": [...] } for a dataView source) | ${out.items} |
composite | { "results": { "total": { "value": 12 }, ... } } | ${out.results.total.value} |
A parameterized Data Service is called with args nested one level deeper: { "params": { "parameters": { "search": "lap" } } }.
Putting it together — the tutorial's Equipment Register page
mount loader block → callApi oeq-equipment-kpis → setValue page.kpiTotal / kpiAvailable / kpiAssigned
KPI cards → bind page.kpiTotal / ...
search + status inputs → committed → setValue page.gridExternalFilter.search / .status
grid → page.metadata.dataSource = "oeq-equipment-provider"; externalFilter = page.gridExternalFilter
"New" button → setValue page.form* = null/defaults; setValue page.formDialogOpen = true
dialog inputs → committed → setValue page.form<Field> = ${event.new}
"Save" button → callApi POST /records; setValue page.formDialogOpen=false; setValue page.gridRefreshTick=${!page.gridRefreshTick}