Skip to content

Column & field expressions

Genie ships an eval-free expression engine (Genie SQL-flavoured) that powers four things: grid column display/styling, editor field rules (required / disabled / hidden / security), computed values, and validation rules. The same grammar runs on the UI (live, as values change) and on the server (the engine ships a C# port of the interpreter, so field-level security, required-ness and validation are enforced, not just hinted).

Expressions are SQL-flavoured predicates evaluated against the other field values (or, for grid columns, the row’s cell values). Reference a field/column by bare name or @Name; AND/OR/NOT, LIKE, IN, arithmetic, and If(cond, a, b) are supported. Row-action visibility expressions reference the row as row.Column (see Actions & row actions).

Smart field attributes — Required / Disabled / Hidden

Section titled “Smart field attributes — Required / Disabled / Hidden”

Required / Disabled / Hidden on any editor field are “smart” attributes — each holds either a literal ("true"/"false") or a row-context expression (the merged successors of the old RequiredExpression/DisabledExpression/HiddenExpression, which no longer exist):

<Text Name="Reason" Required="@Status == 'Reject'" /> <!-- required only when rejecting -->
<Number Name="Price" Disabled="@Type == 'B'" /> <!-- read-only when Type is B -->

Required is re-validated server-side on submit — not just enforced in the UI. For value rules beyond required-ness (patterns, bounds, and author-declared assertions), see Validation.

Allow / Deny whitelist / blacklist who may edit a field. Each holds either a comma-separated role list or a row-context expression; default is editable by all. A field is editable iff (Allow empty OR allowMatches) AND NOT (Deny matches). A locked field renders read-only in the UI and — crucially — is enforced server-side on submit: a denied field’s caller-supplied value is discarded (create → DefaultValue/null, edit → the previous stored value is kept), so tampering with the raw API cannot change it.

<Number Name="CreditLimit" Allow="Admin,Manager" /> <!-- only these roles may edit -->
<Text Name="Notes" Deny="@Status == 'Closed'" /> <!-- locked once the row is closed -->

ValueExpression computes the field’s value and re-runs whenever its inputs change (make the field Disabled="true" for a read-only computed display). Beyond field references + arithmetic, the helper sumLines(cart, 'QtyColumn', 'PriceColumn') totals a <CartTable>’s line items:

<Number Name="TotalAmount" Label="Total" Disabled="true"
ValueExpression="sumLines(LineCart, 'Quantity', 'UnitPrice')" />

The price key is optional: sumLines(cart, 'Column') sums that one column — the natural fit when each line already carries a computed amount:

<Number Name="Subtotal" Label="Subtotal" Disabled="true"
ValueExpression="sumLines(Lines, 'LineAmount')" />

ValueExpression also works on a <CartTable> column, where its scope is the row’s own cells — e.g. a per-line LineTotal of Quantity * UnitPrice.

<Columns>
<Column Name="Id" Hide="true" />
<Column Name="Name" Search="true" />
<Column Name="CreditLimit" Label="Credit" StyleClasses="text-end" Export="false" />
<Column Name="RegisteredOn" Label="Registered" RelativeTime="true" />
</Columns>

Per result column: Label, Expression (or element body), StyleClasses, StyleClassesExpression, Render, Hide, Export, RolesAllowed (CSV), RelativeTime, Search, IsAttachment, Lookup, LookupKey, LookupParam.

  • Hide — when true, the column is hidden by default in the grid, but it still ships to the client (its cell values remain available for row identity, actions, and Lookup links) and it is listed in the column manager (“Columns” toolbar button) unchecked, so a user can re-enable it. This replaces the legacy Style="display:none" hack. Use it for key/id columns backing a Lookup or for secondary columns most users don’t need. Defaults to false.
  • Expression — an optional client-side display expression (Genie SQL-flavored, the same engine as StyleClassesExpression and field rules) evaluated per row by the UI against the row’s cell values; its result becomes the cell’s displayed text. It is never evaluated server-side — any computation that needs SQL (joins, aggregates, CASE/IIF, badge markup) belongs in the DataSet query instead, and styling/badges are applied client-side via StyleClasses / StyleClassesExpression.
  • IsAttachment — when true, the column’s value is treated as a set of attachment file paths (pipe/comma-separated) and the UI renders the cell as attachment tags (image thumbnails + file pills) instead of plain text. Defaults to false.
  • StyleClassesExpression — an optional client-side expression (Genie SQL-flavored, same engine as field rules) evaluated per row against the row’s cell values; its string result is appended to the cell’s CSS classes after StyleClasses — e.g. If(Status = 'Overdue', 'gx-danger', 'gx-success').
  • Render — render the cell value as rich content instead of plain text, selected by name. Built-ins: badge (one coloured chip), tags (a pipe/comma-separated value → a row of chips), bullets (→ a bulleted list), progress (a numeric 0–100 value → a progress bar with a percentage label). Any other value resolves to a host-registered renderer (see Frontend integration › Rich cell renderers), falling back to plain text when unknown. Colour/label come from StyleClasses / StyleClassesExpression — e.g. Render="badge" with StyleClassesExpression="If(Status = 'Overdue', 'gx-danger', 'gx-success')". Renderers produce React nodes (the value is auto-escaped) — no HTML is ever authored in the model or the value. The same attribute is available on a view’s <EditorField> to render its read-only value.
  • Search — marks the column for the toolbar quick-search box (see Actions & row actions).
  • Lookup / LookupKey / LookupParam — turn the column value into a lookup link. When both Lookup (a target view name) and LookupKey (a sibling column supplying the primary key) are set and the row’s key value is non-empty, the cell renders as a clickable link that opens the linked record read-only in a dialog (the target view’s full layout). The key column is typically Hide-d — its value still ships to the client for the link. LookupParam names the parameter the key is passed under (defaults to Id). The dialog has a View button that navigates to the full /object/{Lookup}?id={key} record page. Loading the linked record is gated by the View permission on the target view; if the user lacks it the dialog opens and shows the server error. Keep LookupParam as Id for the View button to round-trip (the record page reads the pk from the reserved id query param). The same link appears in a record view wherever the column is shown as a read-only field (a <ViewLayout> item, or a band’s identity, subtitle or summary). The key column need not be in the layout, because the record’s values carry every column of the form Sql.
<Columns>
<Column Name="CustomerId" Hide="true" />
<Column Name="CustomerName" Label="Customer" Lookup="Customers" LookupKey="CustomerId" />
</Columns>

Row-action visibility — DisplayExpression

Section titled “Row-action visibility — DisplayExpression”

A row <Action> uses DisplayExpression (or DisplayColumn) for conditional visibility — the same expression engine, evaluated per row against row.Column. See Actions & row actions.

Worked example: sample/Inventory/models/views/Products.view.xml — Render badges/tags/progress (plus a host-registered code renderer on Sku), StyleClassesExpression colours, Lookup links, RelativeTime, IsAttachment, RolesAllowed, plus smart Required/Hidden expressions, Allow/Deny field security, and a ValueExpression margin.