Lattice Grid Buy a licence

api reference

Editing, selection and history

grid.edit and validation, grid.selection and ranges, grid.history, column permissions, and your own menu items.

API reference › Editing, selection and history

grid.edit

MethodReturnsDescription
start(key, colId)voidOpen an edit session. The row must be rendered.
stop(cancel?, opts?)objectCommit or discard. Pass { value, key, colId } to write a value.
undo() / redo()voidDepth from edit.undoDepth.
setCells(writes, type?)numberWrite many cells as one undoable step. Returns how many landed.
pasteInto(anchor, text, extent?)numberPaste tab-separated text, using Excel's tiling rules.
settle(id, ok, reason?)booleanReport the outcome of an optimistic write. Only needed with edit.confirm: 'manual'; the id arrives on cell:pending.
pending()OpenWrite[]Writes still awaiting an outcome. Empty unless edit.commit is set.
status(key, colId)'pending' | nullWhether a cell has a write in flight.

grid.selection

MethodReturnsDescription
keys()string[]Selected row keys.
rows()Row[]
all()Row[]Including rows selected but currently filtered out.
set(keys)voidReplace the selection.
clear()void
ranges()Range[]Cell ranges, for spreadsheet-style selection.
setRange(range)voidReplace every range with one.
addRange(range)voidAdd a range without discarding the others, the API form of ctrl-click. Becomes the anchor extendRange grows.
startRange(rowIndex, colId, opts)voidBegin a range at a cell. opts.additive keeps the existing ranges.
extendRange(rowIndex, colId)voidExtend the newest range, keeping its anchor.
corner(){ row, colId } | nullBottom-right cell of the newest range, where the fill handle sits.
inRange(rowIndex, colId)booleanIs a cell inside any selected range?
cells(){ key, colId }[]Every cell in the selected ranges.
statistics()object | nullEverything summary() reports plus median, quartiles, deviation, distinct and outliers: over the selected cells, so a rectangle spanning three columns is one set of numbers. Null with nothing selected.
summary()objectcount, sum, min, max, avg over the range.

grid.history

Undo across the whole grid, not only edits. Sorts, filters, column moves, grouping, an applied view and a restore all record an entry, and each carries a label written for a button: "sort by Region", not "sort".

MethodReturnsDescription
undo()object | nullThe entry that was undone.
redo()object | null
canUndo() / canRedo()boolean
peek(direction?)object | nullWhat the next undo or redo would do, so a control can name it before it is pressed.
list()object[]The timeline, newest first.
transaction(label, fn)object | nullGroup several changes into one entry. Nested transactions join the outer one.
clear()void

A multi-cell paste is one entry, not one per cell. An AI plan is one entry however many actions it contains, labelled with what it did.

grid.permissions

Four levels per column, resolved from configuration or a callback. They are the four corners of read × write rather than a ladder:

LevelVisibleReadableEditableFor
hidden, , , Absent from the grid, the tool panel, exports, the clipboard, saved state, the filter model and formula references.
readyesyes, No editor opens; paste, fill and range-clear skip it.
writeOnlyyes, yesA secret: an API key a user may rotate but never read. The cell shows a mask and the editor opens empty, and a formula in another cell cannot reference it.
writeyesyesyesThe default, so the feature is opt-in.
permissions: 'read'                                  // blanket
permissions: { salary: 'read', ssn: 'hidden' }       // map; '*' sets the default
permissions: (column, ctx) => ctx.context.role === 'admin' ? 'write' : 'read'
permissions: { default: 'read', columns: { name: 'write' }, resolve }

grid.permissions.setContext({ role: 'clerk' });      // re-resolves everything

For three of the four this is a usability control, not a security boundary. Anything the grid can render it has already loaded, and devtools reaches it. writeOnly is the exception, and the reason it is worth having: nothing in the grid needs the value, so your server can send null for that field and the column still works: at which point the secret is genuinely not on the page. Enforce everything else on the server; permittedColumns and permittedExport are pure and dependency-free so the same policy can run there.

Your own menu items and buttons

The cell menu's function form is handed the cell that was clicked and the built-in items, so adding one entry does not mean reproducing the other thirteen.

createGrid(el, {
  contextMenu: (params, defaults) => [
    ...defaults,
    { separator: true },
    {
      name: `Open ${params.value} in CRM`,
      action: (ctx) => open(`/crm/${ctx.data.accountId}`),
    },
  ],
});

params and the action's argument carry the same cell: { key, colId, value, row, data, column, index, grid }, where data is your original row object. Return the array you want shown: add, remove, reorder or replace. Returning an empty array suppresses the menu; returning nothing at all leaves the defaults alone, so a missing return cannot silently delete the menu.

columnMenu takes the same form for the header's menu: both the 3-dot button and a right-click on a heading. Its params is { colId, column, grid }. Anything of your own that you put on a column definition is on column.def, so an item can appear on some columns and not others.

createGrid(el, {
  columns: [{ field: 'jan', title: 'Jan', context: { month: 1 } }],
  columnMenu: (params, defaults) => {
    // Your own keys live on the definition you wrote.
    const month = params.column.def.context?.month;
    if (!month) return defaults;
    return [...defaults, { separator: true },
      { name: 'Select quarter', action: () => selectQuarter(month) }];
  },
});

The rail takes host buttons the same way. A string names a built-in and an object is yours, placed where it appears in the list rather than appended after the built-ins.

createGrid(el, {
  toolPanel: {
    side: 'left',
    actions: ['undo', 'redo', {
      name: 'sync',
      title: 'Sync to the server',        // or a function, re-read on every repaint
      icon: 'restore',
      run: ({ grid, keys, cells }) => api.sync(keys),
      enabled: () => grid.history.canUndo(),
    }],
  },
});

grid.form

The row edit form, a drawer or dialog holding one control per field. Present whether or not rowForm is configured; without it every method declines rather than throwing, so a caller need not guard. See Editing a row on a form.

MethodReturnsDescription
open(key)booleanOpen a row by key. False if there is no such row, or no form is configured.
close()voidClose without saving. Focus returns to where it was.
save()booleanCommit the fields and close. False if a validator refused, or there is nothing to save.
isOpen()booleanWhether the panel is showing.