Lattice Grid Buy a licence

developer guide

REST adapter

The endpoint you already have: rename its parameters, declare only the operators it truly applies, and the grid pushes what it can and finishes the rest.

For the API you already wrote

Most data does not sit behind a warehouse. It sits behind a service somebody on the team wrote, in front of a database, and until now the honest answer to "how do I connect the grid to it" was "write a fetch function". The REST adapter is that fetch function, with the query already translated into query parameters and their names yours to choose, so pointing the grid at an existing endpoint is a few lines of configuration rather than a hand-written source.

import { createGrid, createPushdownSource, restAdapter } from '@toclocoinc/lattice-grid';
import * as compute from '@toclocoinc/lattice-grid';

createGrid(el, {
  columns,
  rowKey: 'id',
  source: createPushdownSource({
    adapter: restAdapter({
      url: '/api/orders',
      // Rename the parameters to whatever your endpoint already calls them.
      params: { offset: 'skip', limit: 'take', sort: 'orderBy', order: 'dir' },
      // Declare only the comparisons the endpoint genuinely applies.
      operators: ['eq', 'gt', 'lt', 'contains'],
      rows:  (body) => body.results,          // pull the rows out of your shape
      total: (body) => body.meta.totalCount,  // and the match count
    }),
    compute,
    pageSize: 100,
  }),
});

Declare only what the endpoint truly does

The adapter declares almost nothing by default, and that is deliberate. Paging and sorting are assumed, because an endpoint that cannot page is not one you would put a grid in front of. Filtering is assumed absent until you say otherwise, because guessing wrong here returns the wrong rows silently rather than merely being slow. Claiming an operator the endpoint ignores would mean the grid pushes a filter that is dropped and then trusts the unfiltered result; claiming too little only costs speed. So the safe direction is to declare exactly what your endpoint genuinely applies.

restAdapter({
  url: '/api/readings',
  params: { offset: 'from', limit: 'size', sort: 'orderBy', order: 'dir' },
  // An endpoint that pages and sorts, but does not filter:
  capabilities: { sort: 'single', range: true, total: true, filter: false },
})

Whatever you do not declare, the grid does itself. Leave filtering off and the grid fetches the matching set and filters it in the browser, correct and slower, until you widen the endpoint and the same filter moves to the server. That trade is visible after every query through source.lastPlan(), and the source warns once when it has to finish work the endpoint could not, naming the part.

Reading your response shape

Your endpoint returns whatever shape it already returns. Point rows at the array inside the body and total at the match count, or pass a bare array and the adapter reads it directly. Rename the offset, limit, sort, order, filter and search parameters with params, and encode the filter tree however your endpoint expects with encodeFilter. Authenticate with a header or a custom fetch.

The one thing to get right is total: it is the count of everything matching the filter, not the length of the page returned. The grid sizes its scrollbar from it, so returning the page length makes a large result look like a single page. If your endpoint can report the real match count, read it into total; if it cannot, leave it and the grid manages without an exact scrollbar.

The REST adapter produces an ordinary remote source, so blocks, caching and abort-on-supersede all apply. The pushdown guide covers the capability model and the split rules in full, and connect your data lists the other options.