Lattice Grid Buy a licence

demo D287

A feed that survives a reload in ESM

The routed rows written to the browser’s durable store and read straight back on the next visit, beside a fast feed held to a steady repaint rate so a firehose lands smoothly

router.persist() · router.restore()

This writes the routed rows to the browser's durable store and reads them straight back on the next visit, so a screen built on a live feed is not empty after a reload. Alongside it, a fast feed is held to a steady repaint rate so a firehose lands smoothly.

Building…
Loading a live grid…

This is the ES module version. The grid is imported straight from a CDN as a module, the same configuration as the vanilla tab without a global script tag, so it drops into any bundler or a bare module script.

The configuration

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@toclocoinc/lattice-grid@1.71.0/lattice-grid.min.css">

<div id="orders" style="height: 340px"></div>
<div id="sensors" style="height: 340px"></div>
<p id="metrics" style="font: 12.5px ui-monospace, monospace"></p>

<script type="module">
  import { createDataRouter } from '@toclocoinc/lattice-grid/modules/data-router';

  const money = { style: 'currency', currency: 'USD', decimals: 0 };
  const orders = createGrid(document.getElementById('orders'), {
    rowKey: 'id',
    columns: [
      { field: 'id', title: 'Ref', layout: { width: 110, pin: 'start' } },
      { field: 'region', title: 'Region', filter: { type: 'set' } },
      { field: 'value', title: 'Value', type: 'number', format: money, total: 'sum' },
    ],
  });
  const sensors = createGrid(document.getElementById('sensors'), {
    rowKey: 'id',
    columns: [
      { field: 'id', title: 'Sensor', layout: { width: 120, pin: 'start' } },
      { field: 'reading', title: 'Reading', type: 'number' },
    ],
  });

  const router = createDataRouter({ key: 'type', rowKey: 'id' });
  router.attach(orders, 'order', { label: 'orders' });
  // Hold the fast route to four repaints a second; the updates that arrive in
  // between are folded into the next one, so a firehose lands as a smooth grid.
  router.attach(sensors, 'sensor', { label: 'sensors', backpressure: { maxHz: 4 } });

  // Write the routed rows to the browser's own durable store, then ask for the
  // last session back before any new event arrives. On a first visit restore()
  // returns false, so seed; on a reload the orders return from storage.
  router.persist({ key: 'my-desk' });
  const restored = await router.restore();
  if (!restored) router.load(seed);  // rows tagged type: 'order' | 'sensor'

  // A steady trickle of new orders, each written through to storage.
  setInterval(() => router.apply([{ op: 'upsert', row: nextOrder() }]), 2000);
  // A firehose of sensor updates on the same keys, for the held route to coalesce.
  setInterval(() => router.apply(sensorBatch()), 120);  // sensorBatch(): many upserts

  // A point-in-time snapshot: per-route counts and throughput, and how many fast
  // updates the backpressure route coalesced away to keep the grid smooth.
  setInterval(() => {
    const route = router.metrics().routes.find((r) => r.label === 'sensors');
    document.getElementById('metrics').textContent =
      'orders in storage: ' + orders.rows.count() +
      '   ·   sensor updates coalesced: ' + (route?.backpressure?.coalesced ?? 0);
  }, 500);
</script>

A live desk that comes back exactly where you left it

A screen built on a live feed usually starts empty and waits for the feed to refill it. Reload the page and the desk you were reading is gone until the next batch arrives. This keeps it. As routed rows arrive they are written to the browser’s own durable store, so when the page loads again the desk comes back exactly where you left it, before a single new event has landed. Reload the tab and the orders are already there.

The same router that fans one feed out to several views is the one doing the saving, so nothing extra sits between the feed and the screen. Name a key to store under and the routed rows are kept up to date as they change; ask for them back on load and they return. Where the browser will not keep durable storage, the router simply runs in memory and fills from the feed as before, so the page still works, it just does not resume.

A fast feed gets its own treatment. Point a route at a firehose and hold its view to a fixed number of repaints a second, and the updates that arrive in between are folded into the next one. The grid stays smooth under a feed that would otherwise make it stutter, and the count of updates merged away tells you how much work the view was spared. One feed, then, that both survives a reload and takes a firehose without flinching.

How do I make a live feed survive a page reload?

Turn on persistence on the data router: call router.persist with a key to store under, and the routed rows are written to the browser’s durable store as they arrive. On the next load, call router.restore before you start the feed, and the views come back filled from storage. For a fast route, pass a backpressure option when you attach its view, capping how often it repaints, and the router coalesces the updates in between into one. Read the coalesced count from the router’s own metrics to see how many updates were merged away.