Put a dashboard on a drawn screen
Goal
Build the page a dashboard used to be — numbers, charts, a grid of records and a form to add one — as a screen you drew.
Why this exists
A dashboard is tiles, numbers, charts and a couple of embedded record surfaces. A drawn screen had the first two and none of the last two, so it now has both: a Chart control and a Table view or form control.
What you get over the dashboard artifact is that the layout is yours. The tiles are ordinary boxes, so they can be any size, any colour, and carry any drawn state; and the page can navigate, open overlays and run handlers, because it is a screen.
What you give up is the tile designer's per-widget query builder. A number here is one
loadRecords, which is the same line every other Studio screen writes.
A number is a counted query
loadRecords("par_ticket", "probe", {
top: 1,
where: { par_status: 100000002 },
countInto: "statusActive"
});
countInto is where how many rows match goes — not how many came back. top: 1 is what keeps
that honest: the count is over everything, and the page nobody reads is as small as a page can be.
probe is a state field you declare once and never look at.
Then draw a big text and bind its Text to text(state.statusActive).
Counting what is missing. where only compares for equality, so "the ones with nothing in a
field" is an option of its own, empty: a list of fields that must have no value.
loadRecords("pbo_position", "probe", {
top: 1,
where: { state: 0 },
empty: ["pbo_currentoccupant"],
countInto: "vacant"
});
That counts the open positions nobody sits in. It is the same condition as a view's is empty filter, so a tile and the list beside it agree.
A chart
Drop Components → Chart. In the App tab:
-
Chart — bars, stacked bars, line, area, pie or donut.
-
Value — a list of
{ label, value }:[{ label: "New", value: state.statusNew },{ label: "Active", value: state.statusActive },{ label: "Resolved", value: state.statusResolved }]
The chart is the platform's own, not a drawing. That matters more than it sounds: the hard part of a chart is tick selection, label collision and the data nobody designed against — a result with no rows, one outlier four hundred times the rest, a ninety-character category. Those are decided every time it renders. A chart you drew would have them decided once, by you, against the sample data in front of you: right in the designer and wrong in production.
Put the words inside the control. Whatever text you draw inside a chart becomes its accessible name — a screen reader announces "graphic" and nothing else without it. It is not drawn on top of the chart; a visible title goes in the panel above.
There is no dual axis and there will not be. Two measures on two scales in one frame invite a comparison the geometry does not support. Two measures of different sizes are two charts.
A grid, or a form, from a table
Drop Components → Table view or form. In the App tab:
| Field | Is |
|---|---|
| Shows | A view's rows, A form for a new record, or A form for one record |
| Table | picked from the environment's tables — type to filter by name or logical name |
| View / Form | picked from that table's views or forms, or The table's default |
What appears is the real thing: the model-driven grid with its own sorting, its own paging, its own column chooser and its own per-record permissions; or the table's own quick create form, with its required rules and its plugin steps. Nothing about either is re-drawn, which is why a field that is required on the table is required here too.
A form for one record takes the record from its Value binding — an id, usually from something somebody picked on the same screen.
Steps
- Draw the page: a heading, a row of tiles, panels for the charts and panels for the surfaces.
- Declare the state — one field per number, plus
probe. - Put every counting query in the screen's Loaded handler, and the same lines behind a Refresh button.
- Drop the charts and the surfaces, and fill in their fields.
- Give each object constraints so the page fills the window — the tiles spread, the charts and the surfaces hold their edges.
Import the sample first
Studio — Dashboard is this page, built. Import it, press Add 24 tickets, and look at every control in the designer beside its result in the Shell.
Troubleshooting
- The surface says it does not know which table. — the Table field is empty. Save warns about this rather than refusing it, so you can finish later.
- The surface says there is no such table. — the table has been deleted since it was picked, or the app was imported into an environment that does not have it. The Table field still shows the old name; pick one the environment has.
- The surface says this host cannot show a model-driven surface. — you are looking at the designer's Preview, which has no environment behind it. Open the app in the Shell.
- The chart is empty. — its Value is not a list of
{ label, value }. A row that is not that shape is dropped rather than charted as zero, so a wrong shape shows as a missing bar. - The numbers do not move after I add a record. — nothing re-ran the queries. Put them behind a Refresh button, or re-run them in the handler that wrote the record.
- The numbers moved but the embedded grid did not. — an embedded surface is the platform's own
grid or form, and it owns its query, so re-running your queries does not touch it. End the
handler with
reloadSurfaces(), which shows every embedded table and form on the screen again. Do it in the handler that created the records as well as in Refresh — twenty-four new rows with an empty table beside them is the most confusing thing a dashboard can do.