Events & callbacks
Every interaction has a hook. In Dash the grid pushes state out through props you wire as callback
Inputs (and reads control props back); in the adapters the same signals arrive as on* callback
props (React / Vanilla) or @-events (Vue). State flows one direction: the frontend owns view state
(expansion, selection, pivot layout) and pushes it out; the backend owns data and pushes it in.
This surface is best seen wired into a running app. The core hooks per framework:
from dash import Input, Output, callbackfrom dash_tensor_grid import TensorGrid
grid = TensorGrid(id="grid", row_data=..., column_defs=..., enable_cell_click=True)
# Interaction outputs — use any as a callback Input:# expanded_state – True | { node_id: bool }, pushed on every expand/collapse# selected_rows – [{id, depth, grouped, data}]# cell_edited – {row_id, column, old_value, new_value, row, path, n}# cell_clicked – {row_id, column, value, row, path, n}# action_triggered– {row_id, action, n, row, path} (or {scope:'column', column, action, n})# pivot_state – {rows, columns, values} (two-way with enable_pivot_ui)# rows_request – {start_row, end_row, sort, filters, global_filter, n} (infinite mode)# grid_event – {type, payload, n} (ready, sortChanged, filterChanged, columnResized, …)
@callback(Output("out", "children"), Input("grid", "cell_edited"), prevent_initial_call=True)def on_edit(e): return f"{e['column']}: {e['old_value']} -> {e['new_value']}"import { TensorGrid } from '@tensorgrid/react';
<TensorGrid data={data} rows={['region', 'country']} measures={{ revenue: 'sum' }} masterDetail editable onSelectionChange={(ids) => {}} onCellEdit={(edit) => {}} // { path, rowIndex, column, oldValue, newValue } onEditRejected={(d) => {}} // { column, rowIndex, code, message } onColumnReorder={(order) => {}} onColumnResize={(widths) => {}} onColumnVisibilityChange={(hidden) => {}} onDensityChange={(level) => {}} onExport={(csv) => {}} onCopy={(csv) => {}}/>;<TensorGrid :data="data" :rows="['region', 'country']" :measures="{ revenue: 'sum' }" master-detail editable @selection-change="(ids) => {}" @cell-edit="(edit) => {}" @edit-rejected="(d) => {}" @column-reorder="(order) => {}" @column-resize="(widths) => {}" @density-change="(level) => {}" @export="(csv) => {}" @copy="(csv) => {}"/>import { mountTensorGrid } from '@tensorgrid/vanilla';
const grid = mountTensorGrid(document.getElementById('grid'), { data, rows: ['region', 'country'], measures: { revenue: 'sum' }, masterDetail: true, editable: true, onCellEdit: (edit) => {}, onEditRejected: (d) => {}, onColumnReorder: (order) => {}, onColumnResize: (widths) => {}, onDensityChange: (level) => {}, onExport: (csv) => {}, onCopy: (csv) => {},});
// Selection is pull-model in vanilla (no push callback): read it on demand.grid.getSelected();- One-directional state. The frontend owns
expanded_state/pivot_state/ selection and pushes them out; the backend ownsrow_data/column_defs/rows_responseand pushes them in. Don’t echo view state back — it causes update loops. - Edits are optimistic.
cell_edited/onCellEditfire immediately; feed refreshedrow_databack to persist, and handleonEditRejectedfor validation failures — see source-row editing. - Infinite mode.
rows_request→ serve a block withengine.get_rows_window(...)→rows_response; recompute facets only when the filter model changes. See virtualization. - Pivot.
pivot_stateis two-way — seed the layout and read the drag-and-drop configurator’s changes back; see pivot basics.