Grid API
IGridAPI is available from React's ref/onGridReady, Angular's gridReady
output or template reference, and createGrid() in a core integration.
Reconfiguring a mounted grid
| Method | Purpose |
|---|---|
updateGridOptions(options) | Change presentation and behavior options in place, preserving scroll, selection, focus, and edit history |
getGridOption(key) | Read one option: the value last supplied (to updateGridOptions or a wrapper prop, else at creation), as given; undefined for an option never set — no defaults are filled in. pivotMode and pivotColumns read the live pivot state |
registerMenuAdapter(adapter) | Install (or remove, with null) the column-menu adapter on a mounted grid |
registerBodyMenuAdapter(adapter) | The same for the body context menu |
Only the properties present in options change; a property present with the
value undefined resets to the grid's default, which is how a callback such as
getRowStyle is removed. Object values replace wholesale — they are not
deep-merged.
api.updateGridOptions({ toolbar: { sorting: true, export: true } });
api.updateGridOptions({ zebraRows: true, rowHover: false });
api.updateGridOptions({ tooltip: { mode: "follow" } });
api.updateGridOptions({ getRowStyle: undefined }); // back to the default
This is what the React and Angular bindings do when a prop changes, so a
framework-neutral host reaches the same behavior without recreating the grid.
UpdatableGridOptions lists what can change: visual and interaction options,
toolbar / quick filter / tooltip / column panel / saved views, pagination and its
controls, row selection, theme and icons, the header heights, pinned-row bands,
row-group presentation, the server-side data source, and columnDefs.
Options that seed structure are fixed at creation and are rejected with a
warning: rowHeight (virtualization geometry),
rowNumbers and rowModelType (which columns and row model exist), and
getRowId / rowIdKey (row identity). Changing one of those means creating a
new grid.
The menu adapters are not options — they are assembly ingredients, like
columnDefs, so they have their own registration methods rather than a key in
updateGridOptions:
api.registerMenuAdapter(adapter); // effective on the next menu open
api.registerBodyMenuAdapter(null); // remove; back to the built-in body menu
Both are the deferred form of createGrid's menuAdapter / bodyMenuAdapter
options, for a host that does not know its adapter when the grid is created.
Registration needs no rebuild, and a menu that is already open keeps the items
and cleanup it was given. Reach for an adapter only when menu items are
rendered by a framework and must be unmounted again — application-owned items
belong in ColDef.columnMenu, multiColumnMenu, or bodyContextMenu, all of
which run before the adapter.
Data and columns
| Method | Purpose |
|---|---|
setRowData(rows) | Replace client-side rows |
forEachNodeAfterFilter(callback) | Visit rows that pass filtering, before client-side sorting |
forEachNodeAfterFilterAndSort(callback) | Visit rows that pass filtering in final sorted order |
applyTransaction(tx) | Add, optionally insert at source-order addIndex, update, or remove rows; returns { added, updated, removed } counts of what was actually applied (unknown row IDs are skipped and not counted). Server-side, it mirrors a change already made on the server into the loaded rows, with additions placed by route (see Transactions) |
applyTransactionAsync(tx) | Mutate rows immediately and defer model derivation/rendering into a shared batch; resolves after the flush with this call's counts |
flushAsyncTransactions() | Immediately finalize pending asynchronous transactions |
setColumnDefs(defs) | Replace column definitions (claims caller ownership of the schema — see below) |
getColumnState() | Capture width, order, pinning, visibility, and sort (sort / sortIndex, null when unsorted) |
applyColumnState(state, options?) | Merge or exactly restore state; entries with a sort field set (or, with null, clear) that column's sort |
autoSizeColumns(colIds) / autoSizeAllColumns() | Fit columns to their content, dropping user widths, with one columnWidthsChanged for the batch |
setColumnWidth(colId, widthPx) / setColumnWidths(widths) | Set widths as a resize drag would (clamped to the column's bounds, kept across autosizes); one event per call |
sizeColumnsToFit() | Scale the visible unpinned columns to fill the centre viewport exactly (pinned, resizable: false, and utility columns keep their widths) |
setColumnPinned(colIds, pinned) | Pin one column (a colId) or several to "left" / "right", or unpin with null — the column menu's Pin items; unpinnable columns are skipped; columnsChanged with reason: "pin" |
setColumnsVisible(colIds, visible) / setColumnVisible(colId, visible) | Show or hide columns; hiding skips hideable: false columns; columnsChanged with reason: "visibility", none when nothing changes |
moveColumn(colId, toIndex) / moveColumns(colIds, toIndex) | Move columns (as a block, in the order given) to toIndex of getColumnState() order — every non-internal leaf, hidden included, left → centre → right. Each column stays in its section (the index is clamped to it); lockPosition and movable: false columns are skipped, locked columns keep their edge; a group's colId moves the group. One columnsChanged with reason: "order", none when nothing moved |
getColumnGroupState() | Read every column group's expander state (open/closed) by colId, nested groups included |
setColumnGroupState(groups) | Open or close column groups by colId in one rebuild — what the header expander does; groups not named keep their state |
getColumnModel() | Read runtime columns and sections |
getColumnDefs() | The definitions as the grid holds them now, with the current width, pinning, visibility, sort, aggregate, and group open state written in — setColumnDefs(getColumnDefs()) changes nothing (see below) |
getColumn(colId) / getColumns() | One application column (tolerant id lookup), or every leaf in display order with hidden ones — the columns getColumnState() describes. Internal columns (row numbers, selection checkbox, auto-group, pivot results) are left out |
addSparklineColumn({ colIds, sparklineType, targetColId?, colId? }) | Add a transient sparkline column over colIds — the column menu's Show Sparklines; returns its colId, or null when no id names a column (Sparklines) |
refreshServerSideData(options?) | Soft-refresh or purge the server cache, or one subtree of it — groupKeys for a group path, rowId for a server-tree row |
refreshRowPresentation() | Re-evaluate row presentation for rendered body, pinned, and sticky rows after external state changes |
forEachNode(callback) | Visit every node the model holds — data rows (filtered-out and collapsed included), then group nodes |
forEachLeafNode(callback) | Visit every data (non-group) row, filtered-out and collapsed included |
Reading the definitions back. getColumnDefs() returns a fresh tree in the current
display order and structure, groups with their children. Each entry is the column's own
definition as last applied — before defaultColDef is merged in, with the same function and
object references — with the current state written over it: colId (always set), a leaf's
width, a top-level column's pinned (removed when unpinned), hidden (removed when shown),
sort / sortIndex (removed when unsorted), aggFunc for an aggregated column (its first
aggregate), and openByDefault for a group with an expander. Row-grouping columns, a column's
second and later aggregates, and sorts on the auto-group column have no place in a definition:
setColumnDefs keeps them on the same grid (it matches columns by colId), and
captureViewState() carries them to another one.
const defs = api.getColumnDefs();
api.setColumnDefs(defs.map(def => def.colId === "price" ? { ...def, label: "Unit price" } : def));
Who owns the column schema. A server-side response can carry its own columns, and the grid
arbitrates between that and the application: once the application supplies definitions through
any door — columnDefs at creation, a wrapper prop, updateGridOptions({ columnDefs }), or
setColumnDefs — server-sent schemas are ignored. For a non-empty array, setColumnDefs(defs)
and updateGridOptions({ columnDefs: defs }) are equivalent; they differ only at the edges:
setColumnDefs([]) clears the columns and keeps ownership, while updateGridOptions({ columnDefs: [] }) clears only if the application already owned the schema (a transient empty
array from a framework render must not discard a server schema). To hand the schema back to the
server, release ownership with updateGridOptions({ columnDefs: undefined }).
View and grouping
| Method | Purpose |
|---|---|
setQuickFilter(text, options?) | Apply client-side global search (options.behavior chooses filtering or finding) |
getQuickFilterText() | Current quick-filter text ("" when inactive) |
getQuickFilterBehavior() | Whether the search filters rows ("filter") or highlights matches ("find") |
getFindState() | Find behavior: { behavior, available, text, matchCount, activeIndex, activeMatch } |
findNext() / findPrevious() | Step the active find match in display order (wraps), revealing it — expanding ancestors, paging, scrolling, and moving the search widget aside when nothing else can uncover the cell |
getFilterModel() | Read the per-column filters as serializable { colId, filters, join } items |
setFilterModel(filters) | Replace all column filters (empty array clears; resets to page 1 and clears selection) |
addFilterModel(filter) | Add or replace one column's filter, keeping the others |
removeFilterModel(colId) | Remove one column's filter (no-op when it has none) |
getSetFilterValues(colId) | Set-filter columns: the value universe (null = blanks) |
getSetFilterState(colId) | Intent-level { mode, checked, unchecked }, or null when unfiltered |
checkSetFilterValue(colId, value) / uncheckSetFilterValue(colId, value) | Toggle one value by intent |
setSetFilterValues(colId, values, { mode }?) | Replace the set filter; mode pins include/exclude semantics |
setAllGroupsExpanded(expanded) | Expand/collapse all groups in one pass |
setGroupExpanded(groupIds, expanded) | Expand/collapse the named group / tree nodes in one rebuild; unknown ids are ignored, nothing happens when they are already in that state |
getGroupNodes() | Every group / tree node in the model (empty when not grouped); forEachNodeAfterFilter* walk data rows only |
getRowNode(rowId) | The node for a data row, a group / tree node (client-side; loaded rows server-side) or a pinned band row; undefined for an unknown id — a regroup retires the old group ids |
getPaginationInfo() | Current pagination state, the same object paginationChanged carries: { paginationEnabled, pageIndex, pageSize, totalRowCount, totalPageCount, totalRowCountKnown, pageSizes } (pageIndex is 0-based); counts are over the filtered view, and totalRowCountKnown is false while a server-side total is still provisional |
getCellValue(cell) | The raw value at { rowId, colId }; the read-side counterpart of setCellValue: a leaf's column value (valueGetter, else the data field), a group row's group cell value (aggregate, label, groupValueGetter); rows resolve like getRowNode; undefined for an unknown row or column |
setRowGroupColumns(colIds) / getRowGroupColumns() | Replace/read the row-grouping columns by colId (level order; empty array clears) |
setAggregates(aggregates) / getAggregates() | Replace/read the aggregate assignments as { colId, type } entries (footer totals, group totals, pivot measures; entry order sets generated pivot column order). type is a built-in AggregateType or a name from GridOptions.aggFuncs; an unknown name warns once and the column's default function stands in. Server-side, any name travels to the server unchanged. setAggregates([]) also clears what ColDef.aggFunc seeded |
setPivotMode(on) / getPivotMode() | Toggle pivot mode (client-side; off restores the pre-pivot state exactly, on reinstates the last pivot) |
isPivotUnconfigured() | Pivot mode is on with no row group, pivot column or value — the blank canvas (no columns, no rows) |
setPivotColumns(colIds) / getPivotColumns() | Replace/read the pivot columns (level order; stored even while mode is off) |
getPivotResultColumns() | Descriptors of the generated pivot value columns, in header order |
setPivotColumnOrder(order) / getPivotColumnOrder() | Replace/read the manual arrangement of the generated pivot columns (leaf order by generated colId; null = canonical). Role edits reset it |
captureViewState() | Capture columns, column-group expanders, groups, sort, filters, search (text and match settings), aggregate scope, runtime row pins, expansion, page, and the inactive pivot state layer |
applyViewState(state, options?) | Restore a captured view |
setKeyboardNavigationMode(mode) | Switch tree grid/hierarchy navigation imperatively (source: "api") |
setTreeDataKeyboardNavigationOptions(options) | Reconfigure the mode and/or the Ctrl/Cmd+Shift+Space switch (source: "options") |
Sort, filter, grouping, aggregate, and pivot methods address columns by the
public ColDef.colId.
Sorting
| Method | Purpose |
|---|---|
getSortModel() | The sort model as { colId, dir } entries in priority order (a copy) |
setSortModel(items) | Replace the whole sort model ([] clears); unknown and non-sortable columns are skipped |
api.setSortModel([{ colId: "region", dir: "asc" }, { colId: "revenue", dir: "desc" }]);
A model equal to the current one does nothing; otherwise setSortModel behaves
like a header-click sort (columnsChanged with reason: "sort", onSortChanged,
resetPageOn, selectionPersistence). See
Sorting.
Pagination
| Method | Purpose |
|---|---|
getPaginationInfo() | The current state (see View and grouping) |
setPagination({ enabled?, pageIndex?, pageSize? }) | Change any mix of the three, merged over the current state |
goToPage(pageIndex) | Go to a 0-based page |
nextPage() / previousPage() / firstPage() / lastPage() | Step through the pages |
setPageSize(pageSize) | Change the page size and return to the first page |
api.setPagination({ enabled: true, pageSize: 50 });
api.nextPage();
Page indexes are clamped to [0, totalPageCount - 1] while the total is known.
While a server-side total is still provisional (totalRowCountKnown: false, the
"N+" pager) a page past the last loaded one is accepted, and the grid snaps back
to the real last page once the count is pinned; lastPage() goes to the last
page loaded so far. A new pageSize without a pageIndex returns to page 0, as
the footer's selector does; it need not be one of pageSizes (the selector
lists only those). Turning pagination off lands on page 0, and the page helpers
do nothing while it is off. A call that leaves the state as it is does nothing;
otherwise paginationChanged fires once and the body scrolls to the top.
Displayed and rendered rows
| Method | Purpose |
|---|---|
getDisplayedRowCount() | Rows the current page displays (group rows and expanded leaves; the whole view with pagination off) |
getDisplayedRowAtIndex(viewIdx) | The node at a page-relative view index, or undefined (server-side, also for a row not loaded yet) |
getFirstDisplayedRowIndex() / getLastDisplayedRowIndex() | The rendered body window, overscan rows included; -1 when nothing is rendered |
getRenderedNodes() | The nodes rendered right now: top band, body window, bottom band |
These indexes are page-relative — the same space selectRow,
setFocusedCell, and the rendered rows use: 0 is the first row of the current
page. Add pageIndex * pageSize from getPaginationInfo() for a position in the
whole list. Application-pinned band rows are not counted by
getDisplayedRowCount().
Repainting
| Method | Purpose |
|---|---|
refreshCells({ rowIds?, colIds?, force? }?) | Re-run value getters, formatters, cell styles, and renderers of rendered cells |
redrawRows({ rowIds? }?) | Destroy and rebuild rendered rows and their renderers |
flashCells({ rowIds?, colIds?, flashDuration?, fadeDuration? }?) | Briefly highlight rendered cells, as the change-flash renderer does |
refreshCells is for state a cell reads from outside the row data — a lookup
table, a locale, a flag a renderer closes over. Nothing is re-derived (no
re-sort or re-filter) and the data is untouched; omitted lists mean every
rendered row or column. The grid does no change detection when it repaints, so
every targeted cell is re-run: a custom renderer gets new params through its
refresh(), and force: true destroys and recreates it instead. Rows that are
not rendered are skipped — they paint fresh when they scroll in.
currencyRates = nextRates;
api.refreshCells({ colIds: ["price", "total"] });
api.flashCells({ rowIds: pushedRowIds, flashDuration: 300 });
flashCells uses the change-flash renderer's neutral highlight
(--pte-cell-flash-neutral-bg-color, fading over
--pte-cell-flash-fade-duration); durations default to the cellFlashDuration
and cellFadeDuration options.
Selection and navigation
setFocusedCell, selectRange, extendRangeTo, selectRow, selectColumn,
selectAll, selectAllRows, deselectAllRows, clearSelection, and
navigate mirror rendered keyboard and pointer behavior.
navigateToCorner(corner, opts?) jumps the active cell to "topLeft" or
"bottomRight" (Ctrl+Home / Ctrl+End), optionally extending the range.
selectRowsById(ids, mode?) drives row
selection programmatically by stable row id ("set" | "add" | "remove").
getSelection, getSelectedRows, and getSelectedNodes return current state
(always copies — safe to mutate). selectAllRows/areAllRowsSelected span the
whole filtered set by default (selectAllScope option), and
getKeyboardNavigationMode() reports the live tree-data navigation mode
(non-tree grids always report "grid").
| Method | Purpose |
|---|---|
getFocusedCell() | The active cell as a CellRef (rowPinned set in a pinned band), or null when there is none — independent of DOM focus |
setFocusedCell(cell) / setFocusedCell(viewIdx, colIdx) | Make a cell active and scroll it into view, by CellRef (the only form that reaches a pinned row) or by page-relative view index and visible-leaf index; no-op for a row or column with no slot |
getRowSelectionState() / setRowSelectionState(state) | Read or replace the row selection as { selectAll, toggledRowIds } — on the server-side row model a select-all is "every row of the current query except toggledRowIds", the form to hand the server for a bulk operation (server-side select-all) |
Keyboard shortcuts
| Method | Purpose |
|---|---|
registerShortcut(shortcut) | Register an application keyboard shortcut on this grid instance; returns the disposer |
getKeyboardShortcuts() | The whole binding table — built-ins and application shortcuts — as data for a shortcut reference |
const off = api.registerShortcut({
id: "approve",
chord: "mod+shift+y", // mod = Ctrl on Windows/Linux, Cmd on macOS
label: "Approve the selected rows",
run: () => approveRows(api.getSelection()),
});
Application shortcuts resolve after every built-in (override: true registers
ahead of the non-blocking built-in scopes instead), and reserved chords are
refused with an error naming the feature that owns them: Tab and Escape always;
arrows, Home/End, Enter, Space, and PageUp/PageDown only while the surface that
claims them is on (cellSelection, headerKeyboardNavigation) — a display-only
grid frees them all. mod+alt+<printable> is refused outright (Windows AltGr
reports as Ctrl+Alt). Format a chord for the user with the exported
formatChord(chord); menu items show accelerators via MenuItem.shortcut (a
display hint) or automatically when a built-in binding shares the item's
command.
Scrolling to a row, column, or cell
| Method | Purpose |
|---|---|
ensureRowVisible(rowId, { position }?) | Scroll a row into view by stable row id |
ensureColumnVisible(colId) | Scroll a column into view horizontally |
ensureCellVisible(cell, { position }?) | Both axes at once |
scrollTo({ topPx?, leftPx? }) | Scroll the body to absolute pixel offsets; an omitted axis stays where it is, leftPx moves the centre section |
These are for "jump to row" flows — a validation error, a find-in-grid result, a
row a notification just arrived about — and they do whatever it takes to get the
row on screen first: collapsed group/tree ancestors are expanded (batched into
one repaint), and under pagination the grid pages to the row, firing
paginationChanged once. Rows are addressed by stable row id and columns by
colId, so you never compute a page or a view index yourself.
position says where the row should land: "auto" (default) scrolls the
minimum needed and leaves an already-visible row alone; "top", "middle", and
"bottom" place it deliberately even when it is already on screen — a jump the
user asked for is easier to follow when the row moves somewhere predictable.
"top" means the top of the usable viewport, below any sticky group headers
docked there.
Each method returns whether the target is visible afterwards. false means no
amount of scrolling would have shown it:
- an unknown row id or
colId; - a row the active filter excludes, or a column that is hidden (including one hidden by a collapsed column group — these do not expand column groups);
- on the server-side row model, a row that is not loaded. The server owns the row order, so the grid cannot work out which page an unloaded row is on; page or filter to it instead, then scroll.
A row mirrored into a frozen top/bottom band is already on screen, so revealing
it succeeds without moving the body (the band itself scrolls if it is taller
than the space it has). Both halves of ensureCellVisible are attempted
independently — a bad colId still gets you to the row — but it returns true
only when both succeeded.
if (!api.ensureCellVisible({ rowId: error.rowId, colId: error.field }, { position: "middle" })) {
// The row is filtered out or unloaded — clear the filter (or page to it) and try again.
}
Editing and clipboard
| Method | Purpose |
|---|---|
startEditingCell(cell) | Begin editing { rowId, colId } |
stopEditing(value) / cancelEditing() | Finish or cancel the active edit |
getEditingCell() | The cell being edited as { rowId, colId }, or null |
setCellValue(cell, value) | Write one cell without opening an editor |
setCellValues(edits) | Write many cells as one undo step (changed cells only) |
copySelection() / cutSelection() / paste() | Clipboard workflows |
fillDown() / fillRight() | Fill the selection down / right as Ctrl/Cmd+D / Ctrl/Cmd+R do (always a copy); returns the number of cells written |
canFillDown() / canFillRight() | Whether a fill has cells to write — for a toolbar button's enabled state; re-read on selectionChanged and cellsChanged |
undo() / redo() | Traverse edit history |
canUndo() / canRedo() / clearHistory() | Inspect or reset history |
getHistoryState() | { canUndo, canRedo, undoDepth, redoDepth } |
withUndoGroup(fn) | Coalesce the writes in fn into one undo step |
withoutUndoHistory(fn) | Apply the writes in fn without recording them |
A programmatic write states its own type: a string value runs through the
column's valueParser (like typed input) — or, with none configured, the
built-in parser for the column's type — and anything else is stored as the
final value. So setCellValue(cell, 99) stores the number 99,
setCellValue(cell, "99") stores 99 on a number column too, and
setCellValue(cell, "abc") on a number column is refused and writes nothing.
Floating UI and pinned rows
showTooltip, hideTooltip, openActionFrame, and closeActionFrame address
cells by stable row and column IDs; getActionFrameCell() returns the cell
with an open ActionFrame, or null. setPinnedTopRowData,
setPinnedBottomRowData, and setRowPinned update frozen row bands.
Rows supplied through pinnedTopRowData or pinnedBottomRowData stay fixed in
their band: filtering, sorting, and pagination apply only to body rows. They
are also outside row selection, so selectAllRows(), selectRowsById(), and
the selected-row APIs do not include them. Pinned cells can still be focused,
edited, and included in cell-range or column selections.
Overlays
| Method | Purpose |
|---|---|
showLoadingOverlay() | Show the loading overlay (spinner + loadingMessage) until hideOverlay(); holds the no-rows overlay back meanwhile |
showNoRowsOverlay() | Show the no-rows overlay now, whatever the row count; the next data change re-decides it |
hideOverlay() | Hide the loading overlay, and the no-rows overlay unless the view really is empty |
api.showLoadingOverlay();
const rows = await fetchOrders();
api.setRowData(rows);
api.hideOverlay();
The React and Angular loading input drives the same overlay. See
Loading and empty overlays.
Localization
| Method | Purpose |
|---|---|
getLocaleText(key, variableValues?) | One of the grid's own strings, translated the way the grid translates it (getLocaleText option, else localeText, else English), with {placeholder}s filled |
api.getLocaleText("sortAscending"); // "Sort Ascending"
api.getLocaleText("pageOfTotal", { page: 2, total: 9 }); // "Page 2 of 9"
Use it in custom header components, cell renderers, and menu items so they read like the grid around them. See Localization.
Export
exportDataAsCsv and exportDataAsExcel download files. getDataAsCsv and
getDataAsExcel return content for custom upload, encryption, or storage.
Events and cleanup
Subscribe with api.on(eventName, handler) and call the returned unsubscribe
function. destroy() releases the grid when a custom host owns its lifecycle, and
isDestroyed() reports whether it has run — already true while the teardown is in
progress, so a cell renderer's destroy can tell the grid is going away. Calls on a
destroyed grid do not throw, but they reach a detached renderer; check isDestroyed()
rather than relying on what they do.
filterChanged is the canonical "effective filter changed" signal: it fires
once for every column-filter model change (API, filter menu, dispatch), every
quick-filter change (source: "quickFilter", client-side row model only), and
any columnDefs update that drops an active filter (source: "columns") — no
need to subscribe to both columnsChanged {reason:"filter"} and
modelUpdated {reason:"filter"} (both still fire for back-compat). On the
client-side row model the handler runs after the view is re-derived, so row
counts and api.getPaginationInfo() read post-filter state. The declarative
onFilterChanged option callback wraps it.
quickFilterFindChanged is the find behavior's counterpart to filterChanged:
it fires when the search text or case setting changes (reason: "query", which
also covers switching behavior), when the user steps to another match
("navigate"), and when the rows or columns under the search move and the
matches are re-counted ("model"). Its payload is the whole
getFindState() plus that reason, so an app can render
its own "3 of 27" counter. A filtering quick filter never fires it — it
reports through filterChanged (source: "quickFilter") as before. The
declarative option callback is onQuickFilterFindChanged.
historyChanged fires whenever the undo/redo stacks move, with
{ reason, canUndo, canRedo, undoDepth, redoDepth } — enough to drive toolbar
button state without polling. It follows the write's own cellValueChanged /
cellsChanged events, so a handler reading getHistoryState() sees the value
and the history agree. Writes that never enter history — a vetoed commit, a
commit that doesn't change the stored value, anything under readOnlyEdit,
applyTransaction, or a write inside withoutUndoHistory — do not fire it,
and a whole withUndoGroup scope fires once on exit. The declarative option
callback is onHistoryChanged.
rowDoubleClicked and cellDoubleClicked are the double-click counterparts of
rowClicked and cellClicked, with the same payloads (node, isGroup, and a
group cell's aggregate or label as value). Neither fires when the double-click
opened the editor or ran the fill handle; otherwise both fire regardless of
cellSelection / editTrigger. Like cellClicked, cellDoubleClicked skips
the row-number and checkbox cells. The declarative option callbacks are
onRowDoubleClicked and onCellDoubleClicked.
columnWidthsChanged carries changedColIds (empty means every visible column)
and a final flag. Dragging a resize handle emits one event per pointer move with
final: false, so layout can follow live, then one trailing event with
final: true naming every column the drag resized. A double-click autosize and
every width change that is not a drag in progress are final: true from the
start. Persist column state only on the final event:
api.on("columnWidthsChanged", (ev) => {
if (ev.final) saveColumnState(api.getColumnState());
});
Column identity in payloads and inputs
Every colId field carried by event payloads (cellsChanged, cellValueChanged,
editingChanged, columnsChanged, columnWidthsChanged, cellClicked,
cellDoubleClicked,
filterChanged, tooltip
and focus events, onCellValueChanged, onSortChanged, selection rangeCells, …)
holds the public ColDef.colId — the id you defined. The column's internal
per-instance UUID rides alongside in a colInstanceId / colInstanceIds /
changedColInstanceIds field; you only need it to disambiguate the rare case of
split/moved column duplicates that share a colId, or to key caches that must
survive such splits.
Every column id the grid accepts (API methods, dispatched actions, CellRef
inputs) is resolved tolerantly — instance id, public colId, or key all work —
so api.startEditingCell({ rowId: "1", colId: "price" }) just works.