developer guide
Tree data and master-detail: JavaScript Data Grid Guide
A hierarchy from a parent reference or a path, orphans and cycles absorbed rather than dropped, lazy children, and rows that expand into a nested grid.
Developer guide › Tree data and master-detail
Tree data
Rows that sit under one another rather than in a flat list. Two shapes, and they answer different questions about where the hierarchy lives.
The row names its parent
tree: {
parentKey: 'parentId', // a field, or a function of the row
label: 'name', // what the tree column shows
orphans: 'Unassigned', // or 'root', the default
}
The row carries its own ancestry
tree: { path: (row) => row.hierarchy } // ['EMEA', 'UK', 'Colchester']
Parent-reference is what a join or a document store produces. Every node is a real row. A row whose parent is not in the data (filtered away, not loaded, or simply wrong) is an orphan: it goes to the root by default, or into a named bucket. It is never dropped, because hiding a record over a bad reference loses data the user can see in a flat view.
Path-based rows describe their own place, so intermediate levels may have no
row at all: EMEA/UK/Colchester with no EMEA/UK row still needs a
UK node to sit under. Those are synthesised, and render as group rows: a heading
over the rows beneath it with no record of its own. A real row arriving later for a level
already synthesised fills that node rather than appearing beside it.
A parent cycle is not a hierarchy and cannot be walked. It is reported once and cut, with the rows shown at the root: wrong place beats vanished.
The grid generates a tree column to carry the expander and the indent, on the
same terms as the auto-group, selection and detail columns: pinned to the start, and absent
from columns.visible(), saved views, exports and the tool panel. Its text comes
from tree.label; without one it falls back to your first visible column, which
then appears twice until you hide it, the grid does not remove a column you did not ask it
to remove.
Loading a branch on demand
Children fetched when the node is opened
tree: {
parentKey: 'parentId',
hasChildren: (data) => data.childCount > 0,
loadChildren: (row, signal) => api.children(row.data.id, { signal }),
}
hasChildren lets a row declare children it does not hold, so the expander is
there before anything is fetched: without it there is nothing to click and the branch can
never load. Such a node reads as closed even though tree nodes are otherwise expanded
by default, because an open branch with nothing under it leaves no gesture to load it.
The rows that arrive are added to the data set, so the hierarchy rebuilds through the same
pipeline as everything else and the new rows sort, filter and export like any other. A branch
is fetched once however often it is toggled; closing it before the rows arrive aborts the
request through the signal. A rejection is reported and leaves the branch
unloaded, so reopening tries again rather than showing an empty node for good.
tree:loading, tree:loaded and tree:loadFailed are on the
event bus.
Expansion is the same state group expansion uses, so rows.expand,
rows.collapse, expandAll, collapseAll and saved views
all work on it. A collapsed branch is skipped rather than hidden, so it costs nothing.
Grouping and tree together is not a combination: the grouping wins and says so
once, because two expanders in one row would be two hierarchies claiming the same rows.
Master-detail
A master row expands to reveal a detail region: by default a nested grid over whatever
detail.rows(row) returns, which may be a promise. The grid adds an expander
column while the feature is on, on the same terms as the auto-group and selection columns:
pinned to the start, and absent from columns.visible(), saved views, exports and
the tool panel.