Inline Editing in Apex Grid
The Apex grid supports inline editing in cell or row mode. Editing is opt-in at the grid level and at the column level: only columns marked editable: true participate.
Enabling editing
grid.editing = {
enabled: true,
mode: 'cell', // 'cell' | 'row'
trigger: 'doubleClick', // 'click' | 'doubleClick'
};
Mark editable columns:
{
key: 'name',
editable: true,
}
Configuration
type EditingConfiguration = {
enabled: boolean;
mode?: 'cell' | 'row';
trigger?: 'click' | 'doubleClick';
history?: { enabled: boolean; stackSize?: number }; // undo/redo, see below
};
In cell mode, exactly one cell is in edit at a time. In row mode, all editable cells in the active row enter edit together; commitEdit() flushes all pending values for the row in one event cycle.
Programmatic API
await grid.editCell(rowIndex, columnKey);
await grid.commitEdit();
await grid.cancelEdit();
const cell = grid.editingCell; // currently-edited cell, if any
const row = grid.editingRow; // currently-edited row in row mode
Batch edits with applyEdits
editCell plus commitEdit drives the editor, which is right for one cell and wrong for a thousand: a bulk update that way is a thousand undo entries and a thousand re-renders. grid.applyEdits(edits) writes many cells as a single operation. Added in 3.5.0.
const result = grid.applyEdits([
{ rowIndex: 0, column: 'discount', value: 0 },
{ rowIndex: 1, column: 'discount', value: 0 },
]);
Every write still goes through the same choke point interactive editing uses, so each one emits the cancellable cellValueChanging and then cellValueChanged, and runs the column's validators. What changes is that the whole set lands as one undo step and triggers one pipeline run.
// Bulk-clear a column for the rows the user selected
grid.applyEdits(
grid.selectedRows.map((row) => ({
rowIndex: grid.pageItems.indexOf(row),
column: 'discount',
value: 0,
}))
);
rowIndex is view-relative and matches grid.pageItems, the same as editCell. Order is preserved, so a later edit to the same cell wins.
The return value is a tally, not a count
{ applied: 3, unchanged: 1, invalid: 0, cancelled: 2, skipped: 0 }
"Nothing changed" and "everything was rejected" are different answers and a caller usually needs to tell them apart, so the outcomes are reported separately:
| Field | Meaning |
|---|---|
applied | The value actually changed |
unchanged | The cell already held that value |
invalid | A column validator rejected it |
cancelled | A cellValueChanging listener cancelled it |
skipped | Unknown column, row index out of range, or a column the user could not edit either (editing off, not editable, or hidden) |
Skipped edits are skipped rather than thrown on, so a partially stale batch still applies what it can.
Keyboard editing
Besides the pointer trigger, the active cell's editor opens from the keyboard with Enter or F2 (new in 3.4.0; editing was previously pointer-only). While editing, Enter or Tab commits (Tab commits and moves), and Escape cancels. Clearing a number or currency editor commits null rather than NaN, so an emptied numeric cell reads as empty instead of an invalid number. See Accessibility for the full key map.
Events
- cellValueChanging: cancellable; fires before a cell's value is committed. Read
event.detail.oldValue/event.detail.newValueto validate. - cellValueChanged: fires after a successful commit.
- cellValidationFailed: fires when a column validator rejects an edit. Detail:
{ key, rowIndex, data, value, errors }. - rowEditStarted: row mode only; fires when the active row enters edit.
- rowEditEnded: row mode only; fires when the active row commits or cancels.
- historyChanged: fires when the undo/redo stack changes (requires
editing.history). Detail:{ canUndo, canRedo }.
Built-in editors
Editor templates come from the column type. Built-in editors for string, number, boolean, select, rating, and date types are provided. Custom column types register their own editor templates against the column-type registry.
Validation
Since v3.3, columns take a declarative validators array. Validators run inside the commit path (before the cancellable cellValueChanging gate); a failure keeps the editor open, marks the cell (aria-invalid plus an inline error-message node), and blocks the commit.
import { required, min, max, pattern, custom } from 'apex-grid';
const columns = [
{ key: 'name', editable: true, validators: [required('Name is required')] },
{ key: 'age', editable: true, type: 'number', validators: [min(18), max(99)] },
{ key: 'email', editable: true, validators: [pattern(/^[^@\s]+@[^@\s]+$/, 'Invalid email')] },
];
Built-ins are required, min, max, pattern, and custom; any (value, ctx) => string | null function works as a validator (return an error string to reject, null to accept). In row mode all pending cells are validated atomically, so there is no partial write. When a validator rejects, the grid fires a cellValidationFailed event:
grid.addEventListener('cellValidationFailed', (event) => {
const { key, rowIndex, value, errors } = event.detail;
console.warn(`${key} @ row ${rowIndex} rejected:`, errors);
});
For a simpler ad-hoc check, you can still listen to cellValueChanging and call event.preventDefault() to reject the new value; the grid keeps the cell in edit and the editor focused so the user can correct the entry.
Undo / redo
Opt in to an edit-history stack with editing.history. Every committed cell edit is recorded (single edits, row-mode commits, and enterprise paste / fill each collapse to one step):
grid.editing = { enabled: true, mode: 'cell', history: { enabled: true } };
grid.undo(); // or Ctrl/Cmd+Z
grid.redo(); // or Ctrl/Cmd+Shift+Z / Ctrl+Y
grid.clearHistory();
grid.canUndo; // boolean getter
grid.canRedo; // boolean getter
Keyboard shortcuts fire while the grid body has focus, so an open editor's native text undo is untouched. The stack holds 100 commands by default; raise it with history: { enabled: true, stackSize: 200 }. A historyChanged event ({ canUndo, canRedo }) fires whenever the stack changes, so you can enable / disable your own undo / redo buttons.