Elyra
Elyra The coding agent e The native code editor Elyra Grove Native local development environment Askr The real server for Laravel & PHP Elyra Framework Rust + Svelte 5 framework for desktop apps Elyra Conductor Local project conductor Elyra SQL Server MySQL-compatible SQL server in Rust Elyra Félagi Agents as teammates on one board Elyra SQL Client Native desktop SQL workbench Elyra SQL Anywhere Replication-ready SQL engine Elyra Sjá SEO & GEO workspace for macOS Elyra DataGrid Server-driven data grid for Laravel
Elyra
Master-detail & tree grid

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
    childLimit: 1000,                      // max children fetched per node (default 1000)
  },
};
  • 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 initialData for an instant first paint.

Scaling. Tree mode issues one query per expanded node (plus one for the roots), and childLimit bounds a single level — it does not paginate within a level. It is built for modest fan-out (org charts, category trees), not for expanding thousands of nodes with thousands of children each. For very large hierarchies, prefer grouping or a filtered flat view. Raise childLimit only up to the protocol cap (1000); beyond that, redesign the view.

await grid.toggleTreeNode(nodeId);
grid.isTreeExpanded(nodeId);

Livewire supports the tree grid too — set the $tree property (the server component flattens the visible tree, one query per expanded node):

class OrgGrid extends DataGrid
{
    public bool|array $tree = [
        'parentField' => 'parent_id',
        'rootValue' => 0,
        'hasChildrenField' => 'has_children',
        'childLimit' => 1000, // max children fetched per node (default 1000)
    ];
    // definition() declaring parent_id (+ has_children) as columns
}

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 (and has_children) as columns so the client can filter on them, even if you do not display them.