Skip to content

Actions & row actions

A view can carry toolbar actions (buttons above the grid), row actions (per-row buttons or menu items), a quick-search box, and pre-execute filters. This page covers all four, plus the CSP-safe dispatch envelope that lets server SQL drive the client.

<Actions>
<Action Name="ExportCsv" Icon="fa fa-file-csv" ExportHandler="Csv">exportTable('Customer');</Action>
</Actions>
<RowActions>
<Action Name="Deactivate" Icon="fa fa-ban" Color="warning" Confirm="Sure?" DisplayExpression="row.IsActive == true">
<Sql>UPDATE Sales.Customers SET IsActive = 0 WHERE Id = @Id;</Sql>
</Action>
<Action Name="AdjustCredit" Icon="fa fa-sliders">
<Sql>UPDATE Sales.Customers SET CreditLimit = @NewLimit WHERE Id = @Id;</Sql>
<Prompt Message="New credit limit" ConfirmLabel="Apply">
<Number Name="NewLimit" Label="New Credit Limit" Required="true" MinValue="0" />
</Prompt>
</Action>
<Action Name="Site" Icon="fa fa-globe" Url="{Website}" Target="_blank" DisplayColumn="Website" />
</RowActions>

A toolbar <Action> carries an OnClick script (element body), optional Icon, Confirm, ExportHandler. A row <Action> resolves its body by precedence <Sql> > Url (link) > OnClick/script, plus Name, Icon, Color, Confirm, Target, Permission, DisplayExpression/DisplayColumn (conditional visibility — see expressions), and an optional <Prompt> (Message, ConfirmLabel + editor fields collected before the action runs).

A Url makes the action a link. {Column} tokens are substituted from the row ({Id} included), and tokens landing in the query string are percent-encoded, so a value containing &, # or = can’t corrupt the parameters after it.

Target decides where it opens:

Target Behaviour
omitted / _self An internal path navigates in-app (SPA route); an external URL opens in the same tab.
_blank (or any anchor target) Opens a window. Note a new tab carries no bearer token, so don’t point it at an [Authorize] endpoint.
Modal Opens the document viewer in a dialog, without leaving the grid. Only applies to a /report-view or /file-view URL; anything else falls back to navigation. Superseded by Type="Report" below, which does the same by default.

Type="Report" is the preferred way to open a report or stored file, and it opens in a dialog by default. It is the one Type an author may declare: Sql, Link and Script are inferred from the action’s body, so naming them is rejected rather than silently ignored. A Report still needs a Url — the viewer URL it opens.

Mode then decides where, and is only valid alongside Type="Report":

Mode Behaviour
omitted / Dialog The default. Opens the document viewer in a dialog over the grid, keeping the list, its scroll position and its pre-execute filters underneath. Right for a document that is glanced at and dismissed.
Redirected Opens the viewer in a new tab with the sidebar minified, for a document somebody sits and reads. Safe despite the _blank warning above, because the tab loads the SPA viewer page — not the [Authorize] endpoint — and same-origin tabs share the bearer token.

A Report whose Url is not a viewer path has no dialog to open, so it falls back to navigation rather than producing a dead button.

<!-- Opens the document dialog — Mode defaults to Dialog -->
<Action Name="Invoice" Icon="fa fa-file-pdf" Type="Report"
Url="/report-view?report=sales.invoice&amp;title=Invoice&amp;invoiceId={Id}" />
<!-- Same document, in its own tab with the sidebar minified -->
<Action Name="InvoiceTab" Icon="fa fa-up-right-from-square" Type="Report" Mode="Redirected"
Url="/report-view?report=sales.invoice&amp;title=Invoice&amp;invoiceId={Id}" />
<!-- A stored file (images preview; every other type downloads) -->
<Action Name="Contract" Icon="fa fa-file" Type="Report"
Url="/file-view?path={ContractDocument}&amp;title=Contract" DisplayColumn="ContractDocument" />
<!-- Plain link: no Type, so it navigates to the viewer page in place -->
<Action Name="InvoicePage" Icon="fa fa-file-pdf"
Url="/report-view?report=sales.invoice&amp;title=Invoice&amp;invoiceId={Id}" />

Target="Modal" predates Type="Report" and still works, so existing views need no edit — but prefer Type="Report" in new ones: it says what the action is rather than overloading the anchor target, and it is what Mode attaches to.

A <Sql> block (in a row action, submit, delete, import AfterSql, or an RPC) can drive an action instead of just returning a value. Return a result set whose first column is named FunctionName; each row becomes one callback, with the remaining columns as positional params (Param1, Param2, … in column order, SQL NULL → null). Names must match ^[A-Za-z_][A-Za-z0-9_]*$, and rows run in sequence, each awaited, so a callback completes before the next row runs.

Callbacks come in two kinds, and the split matters:

  • Server callbacks — starting or advancing a workflow. The engine runs these itself, inside the request that produced them, and strips them from the response. They never reach the browser, so backend operations aren’t exposed as public endpoints.
  • UI callbacks — everything in the table below. These are returned to the React client, which runs each row through a closed registry — no eval, no inline JS.

A single result set can mix them; the server rows execute, the rest are handed on:

SELECT 'startWorkflow' FunctionName, 'OrderApproval', 'Orders', @Id -- runs on the server
UNION ALL
SELECT 'showSuccess', 'Submitted', 'Sent for approval.', NULL; -- runs in the browser
-- Validation message that keeps the form/grid open:
SELECT 'showAlert' FunctionName, 'error', 'Invalid Work Package Dates',
'Each work package must stay within project start/end dates.';
-- Refresh the grid after a successful update:
UPDATE … ; SELECT 'refreshTable' FunctionName;

Supported UI FunctionName values and their positional params (the server callbacks are documented in Workflow callbacks):

FunctionName Params Effect
refreshTable — Reload the surrounding grid/record
showAlert severity, title, message Blocking dialog; severity = error/success/info/warning
showSuccess title, message Blocking success dialog
showError title, message Blocking error dialog
swal title, message, severity Legacy alias for showAlert (severity defaults to error)
closeModal — Dismiss the surrounding modal
navigate url Same-origin redirect (internal routes navigate in-app; javascript: refused)
historyBack — Browser back
openWindow url, target, features window.open (defaults _blank, noopener,noreferrer; javascript: refused)

The client registry lives in src/ui/genie-engine-ui/src/components/genieActions.ts; it is wired into every mutation handler so any <Sql> that returns this shape is dispatched automatically. An unknown FunctionName reaching the client surfaces an “Unknown action” dialog rather than failing silently.

Marking one or more columns Search="true" enables a search box in the table toolbar. On submit (Enter or the Search button) the typed term is applied server-side as a single LIKE '%term%' predicate OR-gated across all searchable columns, then AND-combined with any active column filters — e.g. (Name LIKE @t OR Email LIKE @t) AND Status = @s. The box appears only when the view declares at least one searchable column; the term itself is always parameterized and the searchable set is derived from the view config server-side (the client never picks the columns). SQL-backed views only.

<Filters Toggle="Collapsed"> <!-- Active | Collapsed -->
<Select Name="CustomerType" Label="Type"><DataSet Type="static"> … </DataSet></Select>
<Date Name="RegisteredFrom" Label="Registered from" />
</Filters>

Filter fields become @parameters available to <Sql> (guard with (@Param IS NULL OR …)). Children are editor fields (or <Filter Name="" Type="" /> shorthand); an optional <Layout> lays them out. Filter <Select>s use the same dataset system as editor fields. A DateRange filter arrives as two parameters, @{Name}From and @{Name}To.

Worked example: sample/Inventory/models/views/Products.view.xml — toolbar CSV export, row actions with <Sql> + a <Prompt> + refreshTable/showAlert dispatch, quick-search columns, and model/sql/static <Filters>.