Summary#
A grid renders each cell as text. That is right for a name or a price, and wrong for a status — which wants a coloured pill — or a person, which wants an avatar.
A cell template is how you take a column over. Inside the control's call, write slot <Field> { row => … } and
that column's cells render your content instead of text. Everything else about the grid still works: the column
still sorts, the table still becomes cards on a narrow screen, the row still raises its selection event.
Signature#
DataGrid(rows: <list>, columns: [ … ]) {
slot <Field> { <row> => <content> } // <Field> is a property of the row; <row> binds that row
}<Field> names a property of the row type. If it names something that is not a property — a typo, a column you
renamed — that is a compile error, not a cell that quietly renders nothing.
Description#
A status pill#
The classic case: the status column should be a pill, tinted by what the status is.
enum OrgStatus {
Active,
[Label("Suspended")] Suspended,
}
entity Organization {
[Required, MaxLength(100)] string Name;
OrgStatus Status = OrgStatus.Active;
}
class GridColumn { public string Key; public string Label; }
control DataGrid<T> {
contractVersion "1.0"
participation headless
props {
T[] rows;
GridColumn[] columns;
}
events { rowSelected(T row); }
}
theme Admin {
Colors {
Surface1 = "#fbfbfa";
TextSecondary = "#5f5f5a";
BgSuccess = "#eaf3de";
TextSuccess = "#3b6d11";
BgWarning = "#faeeda";
TextWarning = "#854f0b";
}
Radius { Pill = "999px"; }
}
enum Tone { Neutral, Success, Warning }
/// The pill itself — an ordinary component. Its label is CONTENT, so it can hold an enum-typed field, which renders
/// as that member's label.
[Composable] component Badge(Tone tone) {
variants {
base { Display = Display.InlineFlex; Px = "10px"; Rounded = Radius.Pill; }
tone {
Neutral { Bg = Colors.Surface1; Color = Colors.TextSecondary; }
Success { Bg = Colors.BgSuccess; Color = Colors.TextSuccess; }
Warning { Bg = Colors.BgWarning; Color = Colors.TextWarning; }
}
}
render { Row(align: Align.Center) { Slot; } }
}
[Page("/orgs")]
[Render(CSR)]
component OrganizationsPage() {
var orgs = Organization.ToList();
render {
DataGrid(rows: orgs, columns: [
new GridColumn { Key = "Name", Label = "Organization" },
new GridColumn { Key = "Status", Label = "Status" }
]) {
slot Status { o =>
Row(align: Align.Center) {
if (o.Status == OrgStatus.Active) { Badge(Tone.Success) { Text(o.Status); } }
else { Badge(Tone.Warning) { Text(o.Status); } }
}
}
}
}
}Three things are worth naming, because each is a decision you are making:
- The pill is yours. It is an ordinary component, not something the grid provides. Restyle it, or replace it with something else entirely, and no control has to know.
- The colour is a UI decision, so it lives in the UI.
Activeis green because this screen says so. Nothing about the colour belongs on the enum — an enum is a set of values, not a palette. Text(o.Status)shows the member's label, because it reads an enum-typed field directly. See [Label], [Icon], [Tone] — what a human reads.
What the { o => … } parameter binds to#
{ o => … } binds that row, typed as the row's entity — so o.Status, o.Name and any other property type-check.
You can read whatever the row has, not just the column you are rendering:
slot Name { o => Row(gap: 2) { Avatar(o.AvatarUrl); Text(o.Name); } }Columns you do not template are unchanged#
Only the columns you write a slot for change. Everything else keeps rendering as text, with the alignment and
formatting the column declared. Start by templating the one column that needs it.
See also#
- control — foreign UI controls (charts, grids, maps) — the controls a cell template applies to, and how a control is declared
- [Label], [Icon], [Tone] — what a human reads — why
Text(o.Status)reads as "Suspended" and not a stored number - Slot (child content) — the
slotconstruct in its other role: filling a component's named slot