Lattice Grid Buy a licence

demo D96

Paged in htmx

A page at a time from a server that paginates

source: { mode: 'paged' }

This grid asks the server for one page of rows at a time, fitting a backend already built around limit and offset or a cursor. It suits an API that paginates, loading a page as the reader reaches it rather than the whole dataset at once.

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">
<script src="https://cdn.jsdelivr.net/npm/htmx.org@2.0.4/dist/htmx.min.js"></script>

<!-- htmx asks your endpoint for the first block on load and for the next one
     as the reader nears the end of what has arrived. The reply is ordinary
     table markup, the same rows a server template already renders. -->
<div id="grid" hx-get="/api/instruments" hx-trigger="load, lattice:scroll-near-end"
     style="height: 540px"></div>

<script type="module">
  import { createGrid, driveInfiniteScroll } from 'https://cdn.latticegrid.dev/lattice-grid@1.107.0/modules/htmx.esm.min.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 el = document.getElementById('grid');
  const grid = createGrid(el, {
    // Each row's data-id arrives as __row, so that is the key here.
    rowKey: '__row',
    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'] },
    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.
  });

  // Each request carries the sort, the filters and how many rows are already
  // held, and the reply is added to what is there rather than swapped into the
  // page, so only what the reader has reached is ever asked for.
  driveInfiniteScroll(grid, el);
</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.