Skip to content

Data sources

A table’s rows come from a TableSource. It is the seam that keeps tables independent of any storage: the definition declares columns and behaviour, the source turns the current request into rows. The Eloquent source is one implementation; this page covers the contract and how to back a table with anything else.

namespace Lattice\Lattice\Tables\Contracts;
interface TableSource
{
public function query(TableQuery $query): TableResult;
public function resolveMatching(TableQuery $query): Collection;
public function resolveSelection(array $keys): Collection;
}
  • query() fetches one page of rows for the current request. This is the only method a read-only table needs.
  • resolveMatching() and resolveSelection() resolve rows for bulk actions — everything matching the current filters, and an explicit set of selected keys. They can return an empty collection until the table has bulk actions.

TableQuery is the read model of the current request. It is an immutable value with the request’s state as public properties:

PropertyHolds
->filtersThe active column filters.
->sortsThe active sorts.
->tableFiltersThe active dedicated filters.
->pageThe requested page number.
->perPageRows per page.

TableResult wraps the rows and their pagination metadata. Build one with a static factory:

  • TableResult::fromItems($items) — a plain iterable of rows (arrays or models). Use it when you page the data yourself or don’t paginate.
  • TableResult::fromPaginator($paginator) — a LengthAwarePaginator, carrying the total count for a full pager.
  • TableResult::fromSimplePaginator($paginator) — a simple paginator, for previous/next paging without a total.

For a one-off source, CallbackTableSource takes the three methods as closures — you only pass the first unless the table has bulk actions:

use Lattice\Lattice\Tables\CallbackTableSource;
use Lattice\Lattice\Tables\Contracts\TableSource;
use Lattice\Lattice\Tables\TableQuery;
use Lattice\Lattice\Tables\TableResult;
public function source(): TableSource
{
return new CallbackTableSource(
fn (TableQuery $query): TableResult => TableResult::fromItems(
collect($this->rows())->forPage($query->page, $query->perPage),
),
);
}

The closure receives the TableQuery, so it can page, sort, and filter however the backing store allows — slice an in-memory collection, forward the parameters to an HTTP API, or query a search index. When the backend returns its own paginator (or you build one), hand it to TableResult::fromPaginator() so the table renders a full pager:

public function source(): TableSource
{
return new CallbackTableSource(function (TableQuery $query): TableResult {
$response = $this->api->search(
page: $query->page,
perPage: $query->perPage,
);
return TableResult::fromPaginator($response->toPaginator());
});
}

For anything more involved, implement TableSource on a class of its own and return it from source().

TableResult::decorateRows() maps every row through a callback after it is built, so you can attach computed fields a column reads without touching the backing store:

return TableResult::fromItems($rows)->decorateRows(
fn (array $row): array => [...$row, 'full_name' => $row['first'].' '.$row['last']],
);

The callback receives the serialized row array and its index, and returns the row to render — a good place to add a value a display-only column shows, or per-row metadata.