Lattice Grid Buy a licence

demo D97

Server-Side Row Model: Remote Data Source in htmx

Blocks fetched as you scroll, sort and filter pushed to the API

source: { mode: 'remote' }

This grid hands the work to the server, fetching blocks of rows as you scroll and pushing sort, filter and grouping into the request. It keeps a very large dataset on the server where it belongs, so the browser only ever holds what is on screen.

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 block at a time as the
  // reader scrolls, so a table of any size loads only what is on screen. The
  // sort and the filter travel in the request, so the work stays on the server
  // that already speaks to your data; change either and the grid asks again and
  // drops the blocks still in flight.
  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: 'book', title: 'Book', 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: 'bid', title: 'Bid', type: 'number', layout: { width: 110 }, format: { decimals: 4 } },
      { field: 'ask', title: 'Ask', type: 'number', layout: { width: 110 }, format: { decimals: 4 } },
      { field: 'volume', title: 'Volume', type: 'number', layout: { width: 120 },
        format: { notation: 'compact' }, filter: { type: 'number' } },
      { field: 'notional', title: 'Notional', type: 'number', layout: { width: 150 },
        format: { style: 'currency', currency: 'USD', notation: 'compact' } },
      { 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 block 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 from a server as the grid scrolls

A remote source hands row-fetching to the server: the grid asks for the block of rows a scroll position needs, and the sort, filter and grouping state travel with that request rather than being applied afterwards to a dataset already sitting in the browser. A developer reaches for this when the underlying table is too large to hand over in one response, or when the source of truth already has an index that can sort and filter faster than the client could. Lattice Grid switches on this behaviour with source: { mode: 'remote' }, and from that point the grid’s job is to request blocks, cache what it has already fetched, and re-request only the range that scrolling brings into view. Because this is a JavaScript data grid rather than a table that renders every row into the DOM, the visible viewport stays a fixed number of rows regardless of how many exist on the server, so scroll performance does not depend on total row count the way it would with a server-side row model bolted onto a client-rendered table. A column sort or a filter change triggers one new request for the current viewport rather than a re-fetch of the whole set, and rows already cached from an earlier scroll position are reused rather than requested twice.

How do I connect a data grid to a paginated or remote API?

Set source: { mode: 'remote' } and supply a fetch function that Lattice Grid calls with the requested row range, sort state and active filters. The grid requests only the block a scroll position needs, caches the result, and issues a new request when sorting, filtering or scrolling moves outside the cached range.