Server selection - Enterprise
A grid that keeps a list of selected ids is right until the rows it has seen
are a window onto a million on a server. Then "select all" has to mean a
million, the header checkbox has to say so, and a bulk edit has to reach rows
that never came down the wire. None of that fits a list of ids. It fits a
rule - "everything, except these" - which is what the
server-side row model keeps when you turn selection
on. The demo shows the rule as getSelectionState() reports it, flat and
per group, with a bulk edit the server applies by rule.
Open the live example: Server selection: select all, minus these (Server-Side Row Model)
Turning it on
const ctl = createServerRowModel(source, {
groupBy: ['region', 'country'],
aggregations: [{ col: 'amount', fn: 'sum' }, { col: 'id', fn: 'count' }],
getRowId: (r) => String(r.id),
selection: { groupSelects: 'descendants' },
grandTotalRow: 'pinnedBottom',
})
The model then supplies the grid's rowSelectionModel through rowModel:
the row checkboxes, the header checkbox and api.selectAllRows() all read
and write the rule instead of a list. Placeholder rows are skipped; there is
nothing there to select.
Two shapes of rule
groupSelects: 'self' (the default) keeps one flat rule:
{ "selectAll": true, "toggled": ["row-17", "row-90"] }
toggled are the exceptions - deselected ids under selectAll: true,
selected ids under false. A group row is a row like any other.
groupSelects: 'descendants' keeps a tree of rules, so ticking a group
selects everything beneath it and an exception inside a group is recorded
under that group:
{
"selectAllChildren": true,
"toggled": {
"EMEA": { "selectAllChildren": false, "group": true,
"toggled": { "Germany": { "selectAllChildren": true, "group": true, "toggled": {} } } }
}
}
reads as "everything, except EMEA, except Germany within it".
'filteredDescendants' is accepted as an alias: on a server the filter is
already applied, so the rule is the same.
Both shapes are plain data. ctl.getSelectionState() returns the rule and
ctl.setSelectionState(rule) restores it, so a selection survives a page
reload or travels to an endpoint. Under a descendants rule the state also
carries groupBy, the columns its levels are keyed by, which is what a
backend needs to resolve it.
An honest count
The selection bar's chip and selection.selectedCount() count what the rule
selects, not what is loaded: a million under select-all, a million minus two
exceptions, or exactly the ticked rows. Under grouping, the model reads the
total from a count aggregation on the grand total row - that is the only
place the server says how many leaves there are - and reports null (unknown)
without one, which the bar shows as the loaded count.
api.getSelectedRows() stays what it says: the selected rows the grid has
loaded. Under select-all that is a window, never the whole set.
A bulk edit by rule
ctl.bulkUpdate(patch) sends the current filter model, the patch and the
rule to the datasource's optional updateWhere(filterModel, patch, selection)
and resolves with how many rows changed, then reloads every open level. The
Enterprise selection bar's Edit fields drawer calls it when the grid runs
on a row model, so "edit 1,000,000 rows" is one request carrying one rule.
A datasource without updateWhere refuses the bulk edit rather than quietly
writing only the loaded rows. createInMemoryDataSource implements it as the
reference, resolving a nested rule level by level; createKitDataSource
sends it as one message that createKitHandlers runs through the same
authorize, validate, scope and audit hooks as a single-row update. A SQL
backend resolves a flat rule as WHERE <filters> AND id NOT IN (<toggled>)
under select-all, and a nested one by walking group values the way the grid
builds routes.
A new row's selection
A row added by a transaction under a group inherits that group's state: selected under a selected group, not under a deselected one. An indeterminate group passes on its last settled state.
More examples
Server-Side Row Model: 1,000,000 rows
Select-all across a million server-side rows with a bulk edit by rule, on the flagship demo.
Open the live example: Server-Side Row Model: 1,000,000 rows (Server-Side Row Model)
The selection bar
The bar itself, on client-side rows: the count chip, the built-in actions, the bulk-edit drawer and an app's own buttons.
Open the live example: Bulk-action bar (Selection & Clipboard)
Bulk server operations
Select rows, choose an operation, and send one request for all of them - the pattern a bulk edit by rule generalises to rows the grid never loaded.
Open the live example: Bulk server operations (Server-Side Data)
See also
- Server grouping - the model the selection belongs to.
- Server transactions - what a new row inherits.
- Selection bar - the bar, the count chip and the bulk-edit drawer.
Live examples
- Server selection: select all, minus these - The header checkbox selects every row the filter matches, loaded or not, and the selection becomes a rule: all except these ids, or per group under grouping. The panel shows getSelectionState() live, Save and Restore round-trip it, and a bulk action sends the rule to the server as one updateWhere that answers with the count it changed. The selection bar shows the server's number, not the ticks on screen.
- Server-Side Row Model: 1,000,000 rows - One grid, one rowModel prop, a million rows that stay on the server. Sort, filter, global search, grouping to any depth (Region > Country > Rep), infinite scroll or paging, inline edits applied back as transactions with the subtotal following, add and delete, select-all across rows the grid never loaded with a bulk edit by rule, failed blocks with Retry, and a request log that shows every call to the columnar warehouse behind it. The row model ships in @svgrid/enterprise; the datasource contract is free.
- Bulk-action bar - selectionBar floats a bar over the grid while rows are selected - the count, your actions, an overflow menu, and Clear - the pattern an issue tracker uses for bulk edit. position pins it to the bottom (default) or top edge. Actions receive the selected rows in display order, and hidden / disabled re-check against the live selection so buttons appear and grey out as the set changes.
- Bulk server operations - Multi-row bulk action with configurable concurrency, live progress bar, per-row outcome chip, mid-flight cancel.
Related articles
- Going AI-Native: The SvGrid MCP Server - Most AI assistants invent data grid APIs. We built an MCP server so they look up the real one instead.
- A Fill Handle (Drag to Fill) in SvGrid - Build a working spreadsheet-style fill handle on top of SvGrid's cell selection and editing - pointer tracking, range highlighting, series fill, and undo/redo integration all covered.
- Copy a Cell Range to the Clipboard in SvGrid - How SvGrid copies selected cell ranges as tab-separated values that paste cleanly into Excel and Google Sheets, plus headers, programmatic copy, and format control.