Master-detail & tree grid
Two ways to show hierarchy: master-detail (an expandable panel under a row) and a tree grid (self-referencing parent/child data with lazy children).
Master-detail
Expand a row to reveal a detail panel — a custom view, or a nested grid.
Livewire
class OrdersGrid extends DataGrid
{
public bool $masterDetail = true;
public ?string $detailView = 'orders.detail'; // Blade view, rendered with ['row' => ...]
// ...
}
The grid adds an expand column; expanding renders your detailView (or a
key–value fallback if none is set). Because the detail view is arbitrary Blade,
it can contain another grid.
Vue / Svelte (core)
The core exposes the mechanism; the detail payload is loaded lazily:
const options = {
/* ... */
masterDetail: { load: (row) => fetch(`/api/orders/${row.id}/lines`).then(r => r.json()) },
};
grid.toggleExpand(rowId);
grid.isExpanded(rowId);
grid.detailOf(rowId); // the loaded payload
Tree grid
Bind self-referencing data (a parent_id column). Root rows load first;
expanding a node lazily fetches its children (WHERE parent = id).
const options = {
key: 'id',
columns: [
{ field: 'name', header: 'Name' }, // first column shows the tree
{ field: 'kind', header: 'Kind' },
{ field: 'headcount', header: 'Headcount', type: 'number', align: 'right' },
],
fetch: laravelFetch('/api/org/grid'),
initialData: props.initial, // server-seeded roots
tree: {
parentField: 'parent_id',
rootValue: 0, // value marking roots (default null)
hasChildrenField: 'has_children', // boolean column → show a chevron
},
};
- The first column renders indentation + an expand chevron.
- Children are fetched on demand and inserted after their parent with the next depth.
- Roots can be server-seeded via
initialDatafor an instant first paint.
await grid.toggleTreeNode(nodeId);
grid.isTreeExpanded(nodeId);
Server
No special server support is needed — children are just a filtered request:
function orgGrid()
{
return Grid::table('org')->connection('elyrasql')->key('id')
->columns(['id', 'name', 'kind', 'headcount', 'has_children', 'parent_id'])
->column('budget', type: 'number');
}
// roots (server-seeded)
Route::get('/org', fn () => Inertia::render('Org', [
'initial' => orgGrid()->respond(GridRequest::fromArray([
'columns' => ['id', 'name', 'kind', 'headcount', 'has_children'],
'filter' => ['logic' => 'and', 'filters' => [['field' => 'parent_id', 'op' => 'eq', 'value' => 0]]],
'view' => ['skip' => 0, 'take' => 50],
])),
]));
// children (lazy)
Route::post('/api/org/grid', fn (Request $r) => orgGrid()->respond(GridRequest::fromArray($r->all())));
Declare
parent_id(andhas_children) as columns so the client can filter on them, even if you do not display them.