Lattice Grid Buy a licence

demo D74

Tree data in htmx

A hierarchy from a parent reference

tree: { parentKey: 'parentId' }

This grid builds a hierarchy from a parent reference already on each row, an org chart or a bill of materials, without a separate nesting step. It shows nested data in the shape your source already implies, collapsible level by level.

Building…
Loading a live grid…

This is the htmx version. A server-rendered table is upgraded in place, so the page is readable before any script runs and gains the full grid when the module loads, which suits a progressively enhanced app.

The configuration

<link rel="stylesheet" href="https://cdn.latticegrid.dev/lattice-grid@1.107.0/lattice-grid.min.css">

<!-- A real, server-rendered table: readable with JavaScript off. data-field on
     each header names the column that cell becomes, and data-id on each row is
     the key the grid carries it under. -->
<table id="tree-data-table" data-lattice-grid>
  <thead>
    <tr>
      <th data-field="name">Estate</th>
      <th data-field="parentId">Parent</th>
      <th data-field="kind">Level</th>
      <th data-field="status">Status</th>
      <th data-field="devices">Devices</th>
      <th data-field="charge">Monthly charge</th>
    </tr>
  </thead>
  <tbody>
    <tr data-id="S1"><td>London core</td><td></td><td>site</td><td>live</td><td>48</td><td>18400</td></tr>
    <tr data-id="C1"><td>Cabinet A</td><td>S1</td><td>cabinet</td><td>live</td><td>24</td><td>9100</td></tr>
    <tr data-id="C2"><td>Cabinet B</td><td>S1</td><td>cabinet</td><td>provisioning</td><td>24</td><td>9300</td></tr>
    <!-- the rest of the rows, rendered by your server -->
  </tbody>
</table>

<script type="module">
  import { hydrateTable } from 'https://cdn.latticegrid.dev/lattice-grid@1.107.0/modules/htmx.esm.min.js';

  // The markup carries the rows; a server-rendered table's column types are
  // declared in the config hydrateTable takes. The grid replaces the table
  // where it stood.
  const grid = hydrateTable(document.getElementById('tree-data-table'), {
    selection: 'multiple',
    grandTotalRow: 'bottom',
    toolPanel: {
      side: 'left',
      panels: ['columns', 'filters', 'views', 'quick'],
      actions: ['undo', 'redo', 'export', 'restore', 'maximise'],
      exportName: 'lattice-demo',
    },
    // Every node is a real row naming its parent. The grid draws the tree column
    // itself: the expander, the indent and the name all come from label, so name
    // is deliberately not declared as a column of its own below.
    tree: { parentKey: 'parentId', label: 'name', title: 'Estate' },
    columns: [
      { field: 'kind', title: 'Level', filter: { type: 'set' }, layout: { width: 120 } },
      {
        field: 'status', title: 'Status', filter: { type: 'set' }, layout: { width: 140 },
        cell: { decoration: 'pill', variant: { map: { live: 'success', provisioning: 'info', ceased: 'neutral' } } },
      },
      { field: 'devices', title: 'Devices', type: 'number', total: 'sum', layout: { width: 120 } },
      { field: 'charge', title: 'Monthly charge', type: 'number', format: { style: 'currency', currency: 'GBP', decimals: 0 }, total: 'sum', layout: { width: 180 } },
    ],
  });
</script>

Building a row hierarchy from a parent reference

Tree data turns a flat list of rows into nested groups by following a parent-child relationship already present in the source: an org chart where each person carries a manager id, a bill of materials where each part points at its assembly, a category tree where each node points at its parent. A developer reaches for this when the hierarchy is implicit in the data rather than expressed as a path string or pre-nested objects, and reconstructing it by hand would mean a separate grouping step to write and maintain. Lattice Grid builds the tree from tree: { parentKey: 'parentId' }, reading the named field off each row to place it, then expanding branches lazily as they are opened rather than materialising the whole tree up front. Because this is a JavaScript data grid built around incremental updates, adding, removing or re-parenting a row only touches the affected branch: the rest of the tree keeps its expand state and scroll position instead of rebuilding from scratch. Expand and collapse controls are reachable by keyboard, each toggle exposing its state through aria-expanded so a screen reader announces a branch opening the same way a sighted user sees it. Rows with no valid parent, or ids that never resolve, still render rather than silently vanishing from the grid.

How do I build a tree from a flat array with a parent id in a JavaScript data grid?

Set tree: { parentKey: 'parentId' } on the grid configuration, naming the field on each row that holds its parent’s id. Lattice Grid uses that reference to nest rows under their parent, builds branches as they are expanded rather than all at once, and keeps rows with a missing or unresolved parent visible instead of dropping them from the grid. See the step-by-step how to show parent and child rows as a tree guide for a runnable example and the source on GitHub.