Row Nodes

Every row in a GridStorm grid is represented by a RowNode<TData> object. Row nodes wrap your data objects with metadata needed for rendering, selection, grouping, aggregation, and virtual scrolling.

interface RowNode<TData = any> {
  id: string;
  data: TData | undefined;
  sourceIndex: number;
  displayIndex: number;
  level: number;
  rowHeight: number;
  rowTop: number;
  parent: RowNode<TData> | null;
  children: RowNode<TData>[] | null;
  expanded: boolean;
  group: boolean;
  groupField: string | null;
  groupValue: any;
  leafChildrenCount: number;
  aggData: Record<string, any> | null;
  selected: boolean;
  selectable: boolean;
  detail: boolean;
  rowPinned: 'top' | 'bottom' | null;
  version: number;
}

Identity Fields

Field Type Description
id string Unique row identifier. Generated by GridConfig.getRowId or defaults to the array index. Used as the key in GridState.rowNodes and for selection tracking.
data TData | undefined Reference to the original row data object. undefined for group rows, filler rows, and loading placeholder rows.
sourceIndex number Index in the original (unfiltered, unsorted) data array. -1 for group rows and other synthetic nodes.
displayIndex number Index in the displayed (post-filter/sort/group/pagination) row list. Corresponds to the visual position in the grid. Used by the virtual scroller.

Nesting

Field Type Description
level number Nesting depth for tree/group data. 0 for top-level rows. Incremented by 1 for each grouping level.

Display Geometry

Field Type Description
rowHeight number Height of this row in pixels, as resolved from GridConfig.rowHeight.
rowTop number Vertical offset in pixels from the top of the virtual scroll container. Used by the virtual scroller to position row elements via CSS transform.

Tree and Group Fields

Field Type Description
parent RowNode<TData> | null Parent group node. null for top-level rows.
children RowNode<TData>[] | null Child row nodes when this is a group row. null for leaf (non-group) rows.
expanded boolean Whether this group row is currently expanded (children visible). Always false for non-group rows. Default: false.
group boolean true if this node represents a group row rather than a data row. Group rows are created by the grouping plugin.
groupField string | null The column field that this group row represents. null for non-group rows.
groupValue any The grouping value for this group row (e.g., the department name). undefined for non-group rows.
leafChildrenCount number Total number of leaf (non-group) descendant rows under this group. 0 for non-group rows.

Aggregation Fields

Field Type Description
aggData Record<string, any> | null Aggregated data for group rows, keyed by column ID. Populated by the aggregation plugin when ColumnDef.aggFunc is configured. null when no aggregation is active or for non-group rows.
// For a group row with aggFunc: 'sum' on salary, aggFunc: 'avg' on age:
node.aggData // { salary: 450000, age: 32.5 }

Selection Fields

Field Type Description
selected boolean Whether this row is currently selected.
selectable boolean Whether this row can be selected. Set to false to prevent selection of specific rows. Default: true.

Detail Fields

Field Type Description
detail boolean true if this is a detail row in a master-detail layout. Detail rows expand below their master row and can contain nested grids or custom content.

Pinning Fields

Field Type Description
rowPinned 'top' | 'bottom' | null Indicates whether this row is pinned. 'top' from GridConfig.pinnedTopRowData, 'bottom' from GridConfig.pinnedBottomRowData, null for normal scrolling rows.

Version Tracking

Field Type Description
version number Incremented each time the node's properties change. Used by the renderer for targeted re-renders. Comparing versions is cheaper than deep equality checks on the entire node.

Row Types

Data Rows

Standard rows that hold your data objects:

node.group === false
node.data !== undefined
node.children === null
node.rowPinned === null

Group Rows

Created by the grouping plugin when ColumnDef.rowGroup is enabled:

node.group === true
node.data === undefined
node.children !== null      // array of child RowNodes
node.groupField === 'department'
node.groupValue === 'Engineering'
node.leafChildrenCount === 15
node.expanded === true      // or false

Pinned Rows

Created from pinnedTopRowData or pinnedBottomRowData. Excluded from sorting, filtering, and pagination:

node.rowPinned === 'top'    // or 'bottom'
node.data !== undefined     // holds the pinned row data

Detail Rows

Used in master-detail layouts for expandable sub-content:

node.detail === true

Accessing Row Nodes

By ID

const node = api.getRowNode('emp-123');
if (node) {
  console.log(node.data?.name, node.selected);
}

By Display Index

const firstRow = api.getDisplayedRowAtIndex(0);
const lastRow = api.getDisplayedRowAtIndex(api.getDisplayedRowCount() - 1);

Iterating All Nodes

api.forEachNode((node, index) => {
  if (node.selected) {
    console.log('Selected:', node.id, node.data?.name);
  }
});

From Selected Rows

const selectedNodes = api.getSelectedNodes();
const selectedData = api.getSelectedRows(); // returns TData[]

From Grid State

const state = api.getState();
const totalNodes = state.rowNodes.size;           // Map<string, RowNode>
const displayedCount = state.displayedRowIds.length; // string[]

// Look up a specific node from state
const node = state.rowNodes.get('emp-123');

Row Node Events

Many grid events include the affected row node in their payload. Subscribe via api.addEventListener.

Event Payload Fields Description
row:clicked { node, event } Fired when a row is clicked.
row:doubleClicked { node, event } Fired when a row is double-clicked.
cell:clicked { node, colId, value, event } Fired when a cell is clicked.
cell:doubleClicked { node, colId, value, event } Fired when a cell is double-clicked.
cell:editingStarted { node, colId, value } Fired when a cell enters edit mode.
cell:editingStopped { node, colId, oldValue, newValue, cancelled } Fired when editing completes or is cancelled.
cell:valueChanged { node, colId, oldValue, newValue } Fired after a cell value is committed.
row:groupOpened { node, expanded } Fired when a group row is expanded or collapsed.
rowNode:updated { node } Fired when a row node's properties are updated.
selection:changed { selectedNodes, source } Fired when selected rows change.
row:moved { rowId, fromIndex, toIndex } Fired when a row is moved to a new position.
api.addEventListener('cell:clicked', (event) => {
  console.log('Clicked row:', event.node.id, 'column:', event.colId);
});

api.addEventListener('row:groupOpened', (event) => {
  console.log('Group', event.node.groupValue, 'expanded:', event.expanded);
});

api.addEventListener('cell:valueChanged', (event) => {
  console.log('Row', event.node.id, 'changed:', event.oldValue, '->', event.newValue);
});

Row Node Lifecycle

Row nodes go through several stages during their lifetime in the grid:

1. Creation

Row nodes are created when data is provided to the grid via GridConfig.rowData, api.setRowData(), or api.addRows(). The grid engine calls createRowNodes() which:

2. Processing

After creation, the row processing pipeline runs:

  1. Filtering -- Nodes not matching the filter model or quick filter are excluded from display.
  2. Sorting -- Remaining nodes are sorted according to the sort model.
  3. Grouping -- If grouping is active, group nodes are created and data nodes are nested under them.
  4. Display assignment -- Each visible node receives a displayIndex and rowTop value.

3. Mutation

Row nodes use a mutable internal design for performance. Properties that change at runtime:

Property Changed By Description
selected Selection plugin Updated when rows are selected or deselected.
expanded Grouping plugin / API Updated when group rows are expanded or collapsed.
displayIndex Row processing pipeline Reassigned after sort, filter, or group changes.
rowTop Row processing pipeline Recalculated based on display position and row heights.
aggData Aggregation plugin Recomputed when group membership or values change.
version Any mutation Incremented after any property change to signal the renderer.

4. Update

To update a cell value, use the transaction API or direct mutation:

api.updateRows([
  { id: 'emp-123', data: { salary: 95000 } },
]);
const node = api.getRowNode('emp-123');
if (node?.data) {
  node.data.salary = 95000;
  node.version++;
  api.refreshCells({ rowIds: ['emp-123'], colIds: ['salary'] });
}

:::caution Direct mutation bypasses the command bus and event system. For tracked, auditable changes, use api.updateRows() or the editing system instead. :::

5. Removal

Nodes are removed via api.removeRows() or replaced entirely via api.setRowData(). Removed nodes are deleted from GridState.rowNodes and from the selection set.

api.removeRows(['emp-123', 'emp-456']);

GridState Row Collections

The grid state contains two row-related collections:

Collection Type Description
rowNodes Map<string, RowNode<TData>> All row nodes keyed by ID. Includes data rows, group rows, and pinned rows.
displayedRowIds string[] Ordered array of row IDs currently visible after filter, sort, group, and pagination.
const state = api.getState();

// All nodes
for (const [id, node] of state.rowNodes) {
  console.log(id, node.group ? 'group' : 'data');
}

// Displayed nodes in order
for (const id of state.displayedRowIds) {
  const node = state.rowNodes.get(id);
  console.log(node?.displayIndex, node?.data);
}

Related Types

GetRowIdParams

interface GetRowIdParams<TData> {
  data: TData;           // The row data object
  index: number;         // Zero-based index in the input data array
  parentKeys?: string[]; // Parent group keys for grouped/tree data
}

RowModelType

type RowModelType = 'client' | 'server' | 'infinite' | 'viewport';

SelectionSource

type SelectionSource = 'api' | 'checkbox' | 'click' | 'keyboard' | 'selectAll';