Lattice Grid Buy a licence

api reference

Tree API Reference

createTree: a standalone tree over a real grid, with expand and collapse, selection, tri-state checkboxes and drag to reparent.

API reference › The tree module

All 29 pages Everything on one page → Developer guide →

The tree module

modules/tree is an opt-in, standalone tree - expand, select, tri-state checkboxes and drag to reparent - built on the grid's own tree mode (see Tree data) rather than a hierarchy of its own: createTree configures one generated column, no header, no grid lines, its single visible column drawing the expander, an optional per-node icon, an optional checkbox and the label. Expansion, selection and drag-to-reparent are the grid's; this module's own surface is the data shape (flat parentKey rows or nested items), the tri-state checkbox rollup, and the thin tree.on event vocabulary a standalone tree is expected to speak.

import { createTree } from '@toclocoinc/lattice-grid/modules/tree';

const tree = createTree(document.querySelector('#tree'), {
  data: [
    { id: 1, label: 'Documents' },
    { id: 2, parentKey: 1, label: 'Report.docx' },
  ],
  checkboxes: true,
  selection: 'single',
  dragMode: 'both',
  open: [1],
});

Two data shapes, the grid's own. data is flat rows each naming its parent by parentKey (default field name 'parentKey', default id field 'id'); items is the same tree nested under a childrenField array (default 'children') and is flattened once, on construction. A node's label comes from labelField (default 'label') or a template(node) function, which wins when given.

Checkboxes are tri-state and roll up. With checkboxes: true, checking or unchecking a node checks or unchecks every node under it - the usual tree-checkbox convention - and every ancestor's own box repaints to 'checked' when every descendant leaf is, 'unchecked' when none are, and 'mixed' (drawn indeterminate) otherwise. getChecked() lists the ids whose rolled-up state is fully checked; a mixed branch is never among them.

Drag to reparent is two already-shipped grid primitives, not a third drag implementation. dragMode ('source', 'target' or 'both'; false, the default, leaves the handle undrawn) turns on the grid's own rowReorder over a field-form tree.parentKey, which drops on the upper or lower quarter of a node as a new sibling before or after it, and on the middle half as that node's new last child - see Drag to reparent for the mechanism itself, shared with a plain grid's tree. A node cannot be dropped into one of its own descendants; the drop is refused outright, before any data changes.

Lazy children. hasChildren(node) draws an expander for a branch not yet in the data, and loadChildren(node, signal) fetches it the first time it is opened - fetched once, aborted through signal if closed before it resolves, the same contract as the grid's own tree.loadChildren.

MemberDescription
createTree(el, config)Create the tree.
data.add(item, parentId, index) / data.remove(id)Add a node under a parent (root when omitted) at a display index (default the end); remove a node and its whole subtree.
data.update(id, changes) / data.getItem(id)Shallow-merge fields into a node, leaving its place in the hierarchy alone; read a node's own data.
data.getParentId(id) / data.getChildren(id?)A node's parent id (null for a root); a node's direct children's ids (the roots when omitted).
data.serialize(id?)The whole tree, or one subtree, nested under childrenField.
expand(id) / collapse(id) / toggle(id) / expandAll() / collapseAll()Open or close one node, flip it, or open or close every node.
select(id) / getSelected()Select a node (adds to the selection under 'multi'); the selected ids, always an array.
check(id) / uncheck(id) / getChecked()Check or uncheck a node and everything under it; the ids whose rolled-up state is fully checked.
focusItem(id)Move keyboard focus to a node.
on(name, fn)Events: itemClick, itemDoubleClick, expand, collapse, selectionChange, check, the cancellable beforeDrop, afterDrop. Every one carries the node id.
gridThe grid the tree is built on, for anything this API does not cover.
destroy()Destroy the inner grid and release every handler. The host still owns the element.

The data API, selection and the checkbox rollup, executed

A four-node tree, a collapse/re-expand, a selection, a checkbox rollup through a mixed state to fully checked, an added node read back through every data method, and a removal that leaves the hierarchy consistent - run headless on every build with the suite's own DOM test double, since this module always needs a real element and createGrid behind it.

const { createTestDom } = await import('../packages/dom/src/renderer/testdom.js');
const { createGrid } = await import('../packages/dom/src/index.js');
const { createTree } = await import('../packages/modules/tree/index.js');

const seen = [];
const host = createTestDom().root;
const tree = createTree(host, {
  data: [
    { id: '1', label: 'Documents' },
    { id: '2', parentKey: '1', label: 'Report.docx' },
    { id: '3', parentKey: '1', label: 'Budget.xlsx' },
    { id: '4', label: 'Photos' },
  ],
  checkboxes: true,
  selection: 'single',
  open: ['1'],
});
tree.on('expand', (e) => seen.push(`expand:${e.id}`));
tree.on('collapse', (e) => seen.push(`collapse:${e.id}`));
tree.on('selectionChange', (e) => seen.push(`sel:${e.ids.join(',')}`));
tree.on('check', (e) => seen.push(`check:${e.id}:${e.checked}`));

tree.collapse('1');
tree.expand('1');
tree.select('2');
const selected = tree.getSelected().join(',');

tree.check('2');
const mixedCheck = tree.getChecked().join(',');            // '2' -- '1' is mixed, '3' unchecked
tree.check('3');
const rolledUp = tree.getChecked().join(',');             // '1,2,3' -- both children checked rolls '1' up

tree.data.add({ id: '5', label: 'Appendix.docx' }, '2');
const childrenOf2 = tree.data.getChildren('2').join(',');
tree.data.update('5', { label: 'Appendix (final).docx' });
const updatedLabel = tree.data.getItem('5').label;
const parentOf5 = tree.data.getParentId('5');

tree.data.remove('3');
const rootsNow = tree.data.getChildren().join(',');
const nested = tree.data.serialize('1').map((n) => n.id).join(',');

tree.destroy();
return [selected, mixedCheck, rolledUp, childrenOf2, updatedLabel, parentOf5, rootsNow, nested, seen.join('|')].join(' || ');

Nested items, a label template, a per-node icon and initial checks, executed

The other data shape: items nested under childrenField, flattened once on construction. template wins over labelField when both are given; icon and dragMode are per-node and drag config a headless run cannot show on screen, so this only proves they are accepted and read.

const { createTestDom } = await import('../packages/dom/src/renderer/testdom.js');
const { createGrid } = await import('../packages/dom/src/index.js');
const { createTree } = await import('../packages/modules/tree/index.js');

const host = createTestDom().root;
const tree = createTree(host, {
  items: [
    { id: 'a', name: 'Root', children: [{ id: 'b', name: 'Child' }] },
  ],
  childrenField: 'children',
  labelField: 'name',           // loses to template, below, when both are given
  template: (node) => `#${node.id}`,
  icon: (node) => (node.id === 'a' ? 'folder' : 'file'),
  checked: ['b'],                // rolls 'a' up to fully checked too
  dragMode: 'source',
});

const nestedBack = tree.data.serialize().map((n) => n.name).join(',');   // Root
const childChecked = tree.getChecked().join(',');                        // a,b
tree.destroy();
return `${nestedBack} | ${childChecked}`;

Type reference

Generated from the type declarations, so it always matches the release. Each surface lists its properties, its methods and the events it raises as three tables; an option or value type lists its members once.

The tree module

TreeModuleConfig

The configuration for {@link createTree}. Built on the grid's own tree mode (one generated column, no header, no grid lines) rather than a hierarchy of its own - `data`/`items`, `key`, `parentKey` and `childrenField` describe the same two row shapes `GridConfig.tree` reads from a plain grid.

PropertyTypeDescription
dataTreeNode[]Flat rows, each naming its parent by `parentKey` (or none, for a root). (optional)
itemsTreeNode[]Nested nodes, each with a `childrenField` array, instead of `data`. Takes priority over `data` when both are given. (optional)
keystringThe field holding a node's own identity. Default `'id'`. (optional)
parentKeystringThe field a flat row names its parent in. Default `'parentKey'`. (optional)
childrenFieldstringThe array property nesting a node's children, for `items`. Default `'children'`. (optional)
labelFieldstringThe field a node's label comes from, when `template` is not given. Default `'label'`. (optional)
iconClassstring | ((node: TreeNode) => string | null | undefined)An icon-font class (or classes) per node, drawn as an `<i>` in the icon slot alongside the registered-SVG icon from `icon`. A string applies to every node; a function reads it per node. (optional)
openArray<string | number>The ids open at start. Default none - every other branch starts closed, however many children it already has. (optional)
selectionfalse | 'single' | 'multi'Selection mode. Default `false`. (optional)
checkboxesbooleanDraw a tri-state checkbox per node, checked/unchecked/mixed rolled up from its children. Default `false`. (optional)
checkedArray<string | number>The ids checked at start. Default none. (optional)
dragModefalse | 'source' | 'target' | 'both'This tree's role in a drag: reorder and reparent within itself either way; the split is the host's own declaration of intent, since a standalone tree has nothing else to send to or receive from. Default `false`. (optional)
contextMenubooleanThe grid's own cell right-click menu (Paste, Clear, Fill down). Default `false` - a right-click on a node is `itemContextMenu`, not a menu meant for spreadsheet cells. (optional)
MethodSignatureParametersReturnsDescription
template(node: TreeNode) => string | HTMLElementnode: TreeNode=> string | HTMLElementDraw a node's label yourself; wins over `labelField`. A string is HTML sanitised by the shared sanitiser (`modules/shared/sanitise-html.js`): script elements, `on*` attributes and `javascript:` URLs removed, everything else - including an icon-font element's classes - kept. An element is mounted as is. (optional)
icon(node: TreeNode) => string | null | undefinednode: TreeNode=> string | null | undefinedAn icon name per node, or none. (optional)
hasChildren(node: TreeNode) => booleannode: TreeNode=> booleanSay that a node has children not yet in `data`/`items`, so it draws an expander before anything is fetched. (optional)
loadChildren(node: TreeNode, signal: AbortSignal) => Promise<TreeNode[]>node: TreeNode
signal: AbortSignal
=> Promise<TreeNode[]>Fetch a branch's children when it is first opened. Fetched once; a rejection leaves the branch unloaded. (optional)

TreeDataApi

`tree.data`: add, remove, change and read nodes, and read the hierarchy back out.

MethodSignatureParametersReturnsDescription
add(item: TreeNode, parentId?: unknown, index?: number): voiditem: TreeNode
parentId?: unknown
index?: number
voidAdd a node. `parentId` omitted (or null) makes it a root; `index` omitted appends among its new siblings.
remove(id: unknown): voidid: unknownvoidRemove a node and its whole subtree.
update(id: unknown, changes: Partial<TreeNode>): voidid: unknown
changes: Partial<TreeNode>
voidShallow-merge `changes` into a node's own fields, leaving its place in the hierarchy alone.
getItem(id: unknown): TreeNode | nullid: unknownTreeNode | nullA node's own data, or null when not found.
getParentId(id: unknown): string | nullid: unknownstring | nullA node's parent id, or null for a root.
getChildren(id?: unknown): string[]id?: unknownstring[]A node's direct children's ids; the roots when `id` is omitted.
serialize(id?: unknown): TreeNode[]id?: unknownTreeNode[]The whole tree (or, with `id`, one subtree), nested under `childrenField`.

TreeItemEvent

The payload `itemClick`, `itemDoubleClick`, `expand` and `collapse` all carry.

PropertyTypeDescription
idstringThe node's id.

TreeCheckEvent

The `check` payload: a node's own checkbox was explicitly set, independently of what its rolled-up state then reads as.

PropertyTypeDescription
idstringThe node's id.
checkedbooleanThe state it was set to.

TreeSelectionChangeEvent

The `selectionChange` payload.

PropertyTypeDescription
idsstring[]The selected ids. Always an array, regardless of `TreeModuleConfig.selection` mode.

TreeBeforeDropEvent

The cancellable `beforeDrop` payload: a node is about to be dropped before, after or inside another. Call `preventDefault(reason?)` (or return `false`) to veto, leaving the node exactly where it was.

PropertyTypeDescription
idstringThe id of the node being dropped.
parentunknownThe parent id the node will have after the drop.
indexnumberThe index the node will land at.
position'before' | 'after' | 'inside'Which band of the target node the drop landed in.
targetstringThe id of the node the drag was dropped onto.
MethodSignatureParametersReturnsDescription
preventDefault(reason?: string): voidreason?: stringvoidVeto the drop, with an optional reason.

TreeAfterDropEvent

The `afterDrop` payload: a drop that was not vetoed has committed.

PropertyTypeDescription
idstringThe id of the node that was dropped.
parentunknownThe parent id the node now has.
indexnumberThe index the node now occupies.
position'before' | 'after' | 'inside'Which band of the target node the drop landed in.
targetstringThe id of the node the drag was dropped onto.

TreeItemContextMenuEvent

The `itemContextMenu` payload: a right-click on a node (and the context-menu key on a focused one).

PropertyTypeDescription
idstringThe node's id.
nodeTreeNode | nullThe node's own data, or null.
eventEvent | undefinedThe originating DOM event, when the right-click came from the pointer (absent from the keyboard route). Call `preventDefault()` on it to suppress the browser's own menu; left alone otherwise.

TreeEventPayloads

The payload each tree event name carries.

PropertyTypeDescription
itemClickTreeItemEventA node was clicked.
itemDoubleClickTreeItemEventA node was double-clicked.
expandTreeItemEventA node was opened.
collapseTreeItemEventA node was closed.
selectionChangeTreeSelectionChangeEventThe selection changed.
checkTreeCheckEventA checkbox was explicitly checked or unchecked.
beforeDropTreeBeforeDropEventA drop is about to land; veto it with `preventDefault(reason?)`.
afterDropTreeAfterDropEventA drop committed.
itemContextMenuTreeItemContextMenuEventA node was right-clicked, or its context-menu key pressed while focused.

Tree

The tree instance {@link createTree} returns.

Properties
PropertyTypeDescription
gridunknownThe grid the tree is built on, for anything this API does not cover.
dataTreeDataApiThe Data API.
Methods
MethodSignatureParametersReturnsDescription
expand(id: unknown): voidid: unknownvoidOpen a node.
collapse(id: unknown): voidid: unknownvoidClose a node.
expandAll(): void - voidOpen every node.
collapseAll(): void - voidClose every node.
toggle(id: unknown): voidid: unknownvoidOpen a closed node, or close an open one.
select(id: unknown): voidid: unknownvoidSelect a node (or, under `selection: 'multi'`, add it to the selection).
getSelected(): string[] - string[]The selected ids.
check(id: unknown): voidid: unknownvoidCheck a node, and by the usual tree-checkbox convention every node under it.
uncheck(id: unknown): voidid: unknownvoidUncheck a node and every node under it.
getChecked(): string[] - string[]The ids whose rolled-up state is fully checked - never a mixed branch.
focusItem(id: unknown): voidid: unknownvoidMove keyboard focus to a node.
on<K extends TreeEventName>(name: K, fn: (payload: TreeEventPayloads[K]) => void): () => voidname: K
fn: (payload: TreeEventPayloads[K]) => void
() => voidSubscribe to a tree event; returns an unsubscribe function.
destroy(): void - voidTear the tree down: destroy the inner grid, release every listener. The host still owns the element.
Events

This surface raises events, but the names are not declared: on() accepts any string. They cannot be listed here until the declaration names them.