Lattice Grid Buy a licence

demo D81

Lazy children

Fetching a branch on expand, with an abort when it closes first

tree.loadChildren

Building…
Loading a live grid…

The configuration

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@toclocoinc/lattice-grid@1.27.0/lattice-grid.min.css">
<style>
  #grid > div { height: 540px; }
</style>
<div id="grid"></div>

<script type="module">
  import * as Vue from 'https://esm.sh/vue@3';
  import { createGrid } from 'https://cdn.jsdelivr.net/npm/@toclocoinc/lattice-grid@1.27.0/lattice-grid.esm.min.js';
  import createLatticeGrid from 'https://cdn.jsdelivr.net/npm/@toclocoinc/lattice-grid@1.27.0/modules/vue.esm.min.js';

  const LatticeGrid = createLatticeGrid({ vue: Vue, createGrid });

  Vue.createApp({
    components: { LatticeGrid },
    data() {
      return {
        config: {
          rowKey: 'id',
          selection: 'multiple',
          toolPanel: {
            side: 'left',
            panels: ['columns', 'filters', 'views', 'quick'],
            actions: ['undo', 'redo', 'export', 'restore', 'maximise'],
            exportName: 'lattice-demo',
          },
          tree: {
            parentKey: 'parentId',
            label: 'name',
            title: 'Region',
            // A node that declares children reads as closed until they arrive.
            hasChildren: (data) => data.childCount > 0,
            // Open a branch and its children are fetched once. The signal is
            // honoured: close it again before they land and the request is aborted,
            // leaving the branch openable rather than permanently empty.
            loadChildren: (row, signal) => new Promise((resolve, reject) => {
              if (signal.aborted) return reject(new DOMException('Aborted', 'AbortError'));
              const node = row.data;
              const leaf = node.kind === 'Site';
              const timer = setTimeout(() => {
                signal.removeEventListener('abort', onAbort);
                resolve(Array.from({ length: node.childCount }, (_, i) => ({
                  id: node.id + '-' + i,
                  parentId: node.id,
                  name: leaf ? 'ETH-' + i : 'Site ' + (100 + i),
                  kind: leaf ? 'Circuit' : 'Site',
                  childCount: leaf ? 0 : 3,
                  devices: leaf ? 2 : undefined,
                  charge: leaf ? 800 : undefined,
                })));
              }, 700);
              function onAbort() { clearTimeout(timer); reject(new DOMException('Aborted', 'AbortError')); }
              signal.addEventListener('abort', onAbort, { once: true });
            }),
          },
          columns: [
            { field: 'kind', title: 'Level', filter: { type: 'set' }, layout: { width: 120 } },
            { field: 'childCount', title: 'Children', type: 'number', layout: { width: 120 } },
            { field: 'devices', title: 'Devices', type: 'number', total: 'sum', layout: { width: 120 } },
            { field: 'charge', title: 'Monthly charge', type: 'number',
              format: { style: 'currency', currency: 'USD', decimals: 2 }, total: 'sum', layout: { width: 180 } },
          ],
          rows,  // the roots; nothing below them until a branch is opened
        },
      };
    },
    template: '<lattice-grid v-bind="config" />',
  }).mount('#grid');
</script>

Fetching tree branches on demand

Lazy children load a branch of a tree only when a row is expanded, rather than pulling the whole hierarchy up front. A developer reaches for this when the source is a remote API rather than an in-memory array: an org chart where each manager’s direct reports live behind a separate endpoint, a file browser where a folder’s contents are unknown until it is opened, a category tree too large to fetch in one request. Lattice Grid calls tree.loadChildren for the row being expanded, passing its id, and expects a promise resolving to that row’s children; the branch renders once the promise settles, and a distinct loading state occupies the row in between so the grid never presents an empty branch as an empty result. Closing a branch before its fetch resolves aborts the in-flight request through the same signal loadChildren receives, so a fast double-click through several folders does not leave stale fetches racing to populate rows the user has already collapsed. Because this is a JavaScript data grid built around incremental rendering, only the expanded branch pays the network cost; sibling rows and the rest of the tree keep their existing state untouched. The loading row exposes aria-busy while a fetch is pending, so a screen reader announces that a branch is populating rather than reporting silence.

How do I load tree children from an API only when a row expands?

Implement tree.loadChildren, returning a promise that resolves to the array of child rows for the expanded row’s id. Lattice Grid calls it on expand, shows a loading row until the promise resolves, and aborts the request if the branch is collapsed again before the fetch completes, so no children are rendered for a branch the user has already closed.