Editing (inline / modal / slideover / in-cell)
The grid supports full CRUD with four presentation modes you can switch between:
- inline — edit the whole row's cells in place, with Save/Cancel.
- modal — a centered dialog form.
- slideover — a right-side flyout form.
- incell — edit a single cell in place (spreadsheet-style); optionally batch multiple edits and save them together.
Editing is server-validated and only writes columns you mark editable.
Double-click to edit. Double-clicking anywhere on a row enters edit mode. In
incell mode it opens the cell you double-clicked when that cell is editable,
otherwise the first editable cell of the row. In inline, modal and
slideover modes it opens the row editor.
Server side
Mark columns editable with validation rules (the write allow-list):
Grid::table('sales')->key('id')
->column('status', editable: true, editor: 'select',
rules: 'required|in:pending,paid,shipped,delivered,returned')
->column('qty', type: 'number', editable: true, rules: 'required|integer|min:0')
->column('revenue', type: 'number', editable: true, rules: 'required|numeric');
Expose a mutation endpoint (Vue/Svelte) or let Livewire call it directly:
use Elyra\DataGrid\Protocol\GridMutation;
Route::post('/api/sales/mutate', fn (Request $r) =>
salesGrid()->mutate(GridMutation::fromArray($r->all())));
mutate() returns a GridMutationResult:
{ "ok": true, "id": 42, "row": { "id": 42, "status": "paid", ... } }
{ "ok": false, "errors": { "status": ["The selected status is invalid."] } }
Validation. On create, every editable column's rules are applied — so
required fires even for fields the client omitted. On update, only the
submitted fields are validated (partial updates). Only columns you declared
editable are ever written; anything else in the payload is dropped.
Authorization (secure-by-default). mutate(), mutateBatch() and export()
refuse to run until you configure authorization — either declare a policy with
authorize(), or explicitly opt out with withoutAuthorization() (use the latter
only when the surrounding route/controller/policy already guards the operation).
Without one of those, the call throws a GridException before touching the DB.
The authorize() callback receives the ability
(create | update | delete | export) and the request/mutation, and must
return true; returning false throws an unauthorized GridException. Reads
(respond()) are not gated here, so protect the route/policy for those.
Grid::table('sales')->key('id')
->authorize(fn (string $ability, $ctx) => match ($ability) {
'delete' => auth()->user()?->can('delete', Sale::class) ?? false,
'export' => auth()->user()?->can('export', Sale::class) ?? false,
default => auth()->check(),
});
Livewire
class SalesGrid extends DataGrid
{
public bool $editable = true;
public string $editMode = 'slideover'; // 'inline' | 'modal' | 'slideover'
// ... definition() + columns() (with editor/options) + optional aggregates()
}
The grid adds a command column (edit / delete), a New button, and renders
the chosen editor for each editable column. Validation errors appear inline in
the form. Persistence goes straight through GridDefinition::mutate().
Vue / Svelte
Provide an editing option with a mutate function and the mode:
const options = {
/* columns with editable/editor/options ... */
editing: {
mode: 'slideover', // 'inline' | 'modal' | 'slideover'
mutate: async (m) => (await fetch('/api/sales/mutate', {
method: 'POST',
headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
body: JSON.stringify(m),
})).json(),
},
};
Programmatic (client core):
grid.beginEdit(rowId); // edit an existing row
grid.beginCreate(); // new row
grid.setDraft('status', 'paid');
await grid.commitEdit(); // validates + persists via mutate()
grid.cancelEdit();
await grid.removeRow(rowId);
grid.isEditing(rowId);
On success the edited row updates in place; create/delete invalidate the cached total and refetch.
Inline editing
In inline mode, clicking Edit turns the row's editable cells into inputs
(using each column's editor), with Save / Cancel in the command
column. Press Enter to save.
In-cell & batch editing
incell mode is spreadsheet-style: double-click an editable cell to edit it
in place. Press Enter or blur to commit, Escape to cancel.
- Immediate (default): each committed cell persists straight away (one
updatemutation for that field). - Batch (
batch: true): committed cells accumulate as unsaved changes, highlighted in the grid. A toolbar Save (n) / Cancel bar appears; Save persists every dirty row (oneupdatemutation per row) and Cancel discards them.
// Vue / Svelte
editing: { mode: 'incell', batch: true, mutate }
Bulk mutations (one request)
By default a batch Save and a clipboard paste send one update mutation
per affected row. For a client-driven grid (Vue/Svelte) that means N HTTP calls.
Provide an optional mutateBatch to collapse them into a single request:
editing: {
mode: 'incell', batch: true,
mutate: (m) => post('/api/sales/mutate', m),
mutateBatch: async (mutations) =>
(await post('/api/sales/mutate-batch', { mutations })), // GridMutationResult[]
}
use Elyra\DataGrid\Protocol\GridBatchMutation;
Route::post('/api/sales/mutate-batch', fn (Request $r) =>
salesGrid()->mutateBatch(GridBatchMutation::fromArray($r->all())));
mutateBatch() runs the whole batch in one transaction (a thrown or unauthorized
mutation rolls it back; per-row validation failures come back as ok: false
without rolling back) and returns one GridMutationResult per input, in order.
When mutateBatch is omitted the grid falls back to sequential mutate calls,
so it's fully backward-compatible. (Livewire already batches server-side in a
single request.)
// Livewire
public string $editMode = 'incell';
public bool $batchEdit = true;
Unsaved cells are overlaid on the displayed data and flagged with a dg-cell-dirty
style, so users see pending edits before saving. Client-core exposes the
machinery for custom UIs: commitCell, isDirty, dirtyValueOf, cellValue,
dirtyRowCount, saveBatch, cancelBatch.
Editors
text, number, date, select (uses options), and checkbox. See
Columns.
Clipboard paste
With editing enabled, paste tab-separated data (e.g. from a spreadsheet) starting at the active cell — it maps onto editable columns and persists each affected row. See Selection & keyboard.