Lattice Grid Buy a licence

demo D96

Paged

A page at a time from a server that paginates

source: { mode: 'paged' }

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">

<div id="grid" style="height: 540px"></div>

<script type="module">
  import { createGrid } from 'https://cdn.jsdelivr.net/npm/@toclocoinc/lattice-grid@1.27.0/modules/htmx.esm.js';

  // The server holds the rows and the grid asks for one page at a time as the
  // reader scrolls, so a table of any size loads only the pages on screen. A
  // fixed number of pages is kept: scroll far enough and the ones behind you are
  // dropped and fetched again when you return, which is the trade a paged source
  // makes for never holding the whole dataset.
  const grid = createGrid(document.getElementById('grid'), {
    rowKey: 'id',
    columnDefaults: { allowGroup: false, allowPivot: false },
    selection: 'multiple',
    toolPanel: {
      side: 'left',
      panels: ['columns', 'filters', 'views', 'quick'],
      actions: ['undo', 'redo', 'export', 'restore', 'maximise'],
      exportName: 'lattice-demo',
    },
    statusBar: { panels: ['rowCount', 'progress', 'updates', 'selectedCount'] },
    source: {
      mode: 'paged',
      pageSize: 100,
      maxCachedPages: 12,
      // Return the rows for the page and the full count, so the scrollbar knows
      // how far it reaches.
      fetch: async (req) => {
        const res = await fetch('/api/instruments', {
          method: 'POST',
          headers: { 'content-type': 'application/json' },
          body: JSON.stringify(req),
          signal: req.signal,
        });
        return res.json();  // { rows, total }
      },
    },
    columns: [
      { field: 'symbol', title: 'Symbol', layout: { width: 130, pin: 'start' } },
      { field: 'name', title: 'Instrument', layout: { flex: 1, min: 170, max: 280 } },
      { field: 'desk', title: 'Desk', filter: { type: 'set' } },
      { field: 'region', title: 'Region', filter: { type: 'set' } },
      { field: 'price', title: 'Price', type: 'number', layout: { width: 120 },
        format: { decimals: 4 }, filter: { type: 'number' } },
      { field: 'change', title: 'Change', type: 'number', layout: { width: 110 },
        format: { style: 'percent', decimals: 2 } },
      { field: 'volume', title: 'Volume', type: 'number', layout: { width: 120 },
        format: { notation: 'compact' }, filter: { type: 'number' } },
      { field: 'status', title: 'Status', filter: { type: 'set' }, layout: { width: 110 },
        cell: { decoration: 'pill', variant: { map: { open: 'success', halted: 'danger', settled: 'neutral' } } } },
    ],
    // No rows: the server holds them and returns a page per request.
  });
</script>

Fetching rows page by page from a server that paginates

A paged source asks the server for one page of rows at a time rather than the whole dataset or an open-ended stream, fitting any backend already built around LIMIT/OFFSET or a cursor parameter, with no response returning more than a fixed page size. A developer reaches for this when the row count is too large to hand to a JavaScript data grid in one request, but the server lacks the finer-grained querying a full server-side row model expects, such as pushing sort or filter state down per call. Lattice Grid switches the source into this pattern with source: { mode: 'paged' }, which requests bounded pages on demand as scrolling nears the edge of what it already holds, rather than fetching everything up front or streaming rows unprompted. Fetched pages are cached by index, so scrolling back to a page already retrieved renders from cache instead of issuing the request again. Rows still virtualise in the DOM as an in-memory dataset does: only the visible band plus a small overscan gets real row elements at any one time, so page size and viewport height govern memory use rather than total row count. A pending page renders its row band as a placeholder rather than blank space, keeping the row that holds focus in the layout while its data resolves.

How do I load a large dataset into a data grid without fetching it all at once?

Set source: { mode: 'paged' } and supply a function that returns one page of rows for a given page index. Lattice Grid requests pages as the visible scroll position approaches their range, caches each page once fetched, and virtualises rendering so only the current viewport’s rows exist in the DOM at any time.