Events
Live demoEvents
Every event the grid sends, logged as you click, edit, sort and filter.
Code for this demo
import { createGrid, type GridEvents } from '@freegridjs/core';
import { HEADERS, makeRows } from '../data';
import { button, separator, text, toggle, tr, type DemoContext } from '../ui';
export default function mount(ctx: DemoContext) {
const { lang } = ctx;
const h = (en: string, ko: string) => tr(lang, { en, ko });
const t = HEADERS[lang];
const grid = createGrid(ctx.host, {
columns: [
{ field: 'name', header: t.name, width: 150, editable: true },
{ field: 'country', header: t.country, width: 130, filter: 'set' },
{ field: 'q1', header: t.q1, type: 'number', format: '#,##0', width: 110, editable: true },
{ field: 'growth', header: t.growth, type: 'number', format: '0.0%', width: 100 },
{ field: 'status', header: t.status, width: 100, editable: true, editor: { type: 'select', options: ['Active', 'Pending', 'Closed'] } },
],
data: makeRows(80), locale: lang, getRowId: r => String(r.id), rowSelection: 'multiple', pagination: { pageSize: 20 },
});
// each event and a short line for the log; on() returns a function that stops listening
const show: { [K in keyof GridEvents]?: (e: GridEvents[K]) => string } = {
cellClick: e => `row ${e.rowIndex}, ${e.colId} = ${String(e.value)}`,
cellDoubleClick: e => `row ${e.rowIndex}, ${e.colId}`,
editStart: e => `row ${e.rowIndex}, ${e.colId}`,
cellValueChanged: e => `${e.colId}: ${String(e.oldValue)} → ${String(e.newValue)}`,
editStop: e => `committed: ${e.committed}`,
selectionChange: e => `${e.selected.length} ${h('selected', '개 선택')}`,
sortChange: e => JSON.stringify(e.sort),
filterChange: e => Object.keys(e.filters).join(', ') || h('(none)', '(없음)'),
columnResize: e => `${e.colId} → ${e.width}px`,
columnMove: e => `${e.colId} → ${e.toIndex}`,
pageChange: e => `${e.page + 1} / ${e.pageCount}`,
rangeChange: e => (e.range ? `${e.range.start.colId}${e.range.start.rowIndex}:${e.range.end.colId}${e.range.end.rowIndex}` : '-'),
};
const offs = new Map<string, () => void>();
const listen = (name: keyof GridEvents) => {
const fmt = show[name] as (e: unknown) => string;
offs.set(name, grid.on(name, ((e: unknown) => {
try {
ctx.log(`${name} → ${fmt(e)}`);
} catch {
ctx.log(name);
}
}) as never));
};
for (const name of Object.keys(show) as (keyof GridEvents)[]) listen(name);
text(ctx, h('Click, double-click to edit, sort, filter, resize, drag a column, select rows and change pages: every event appears in the log below.',
'클릭, 더블클릭 편집, 정렬, 필터, 너비 조절, 컬럼 끌기, 행 선택, 페이지 이동을 해 보세요. 모든 이벤트가 아래 기록에 나타납니다.'));
separator(ctx);
toggle(ctx, h('Log clicks', '클릭 기록'), true, on => {
if (on) listen('cellClick');
else offs.get('cellClick')?.();
});
button(ctx, h('Sort from code', '코드로 정렬'), () => grid.setSort([{ colId: 'q1', dir: 'desc' }]));
return grid;
}grid.on(name, fn) listens and returns a function that stops listening. The React, Vue, Svelte and Angular wrappers use the same events as onCellClick, @cell-click and (cellClick). Click, edit, sort and filter in the demo above to see which events arrive and in what order.
Common events
// row click → details
grid.on('cellClick', ({ row, colId, value }) => openDetail(row.id));
grid.on('cellDoubleClick', ({ row }) => openEditor(row));
// enable a button while rows are selected
grid.on('selectionChange', ({ selected }) => (deleteBtn.disabled = selected.length === 0));
// save the view when sorting, filters or columns change
const save = () => localStorage.setItem('orders-view', JSON.stringify(grid.getState()));
grid.on('sortChange', save);
grid.on('filterChange', save);
grid.on('columnResize', save);
grid.on('columnMove', save);
// every value change (edit, paste, fill, undo)
grid.on('cellValueChanged', ({ row, colId, oldValue, newValue }) => console.log(row.id, colId, oldValue, '→', newValue));
// stop listening
const off = grid.on('rangeChange', ({ range }) => updateStatus(range));
off();Events you can cancel (before…)
beforeSort, beforeFilter, beforePageChange, beforeEdit and beforeCellChange come before the change; calling cancel() stops it. Use them for permission checks or to sort on the server instead.
// approved orders cannot be edited
grid.on('beforeEdit', e => { if (e.row.status === 'Approved') e.cancel(); });
// no negative quantities
grid.on('beforeCellChange', e => { if (e.colId === 'qty' && Number(e.newValue) < 0) e.cancel(); });
// sort on the server: stop the grid's sort and reload
grid.on('beforeSort', e => { e.cancel(); reload({ sort: e.sort }); });Saving edits to the server
// save each edited cell
grid.on('cellValueChanged', async ({ row, colId, newValue }) => {
await fetch(`/api/orders/${row.id}`, { method: 'PATCH', body: JSON.stringify({ [colId]: newValue }) });
});
// row editing (editType: 'row'): save once per row
grid.on('rowEditStop', ({ row, committed, changes }) => { if (committed && changes.length) saveRow(row); });
// tell people why a value was refused
grid.on('validationFailed', ({ message }) => toast(message));In frameworks
// React
<FreeGrid columns={columns} data={rows} onCellClick={e => open(e.row)} onSelectionChange={e => setSelected(e.selected)} />
// Vue
<FreeGrid :columns="columns" :data="rows" @cell-click="e => open(e.row)" @selection-change="e => selected = e.selected" />All events
| Event | Payload | When |
|---|---|---|
cellClick / cellDoubleClick | { row, rowIndex, colId, value, event } | A data cell was clicked. |
selectionChange | { selected: Row[] } | Selected rows changed. |
sortChange | { sort } | Sort changed. |
filterChange | { filters } | A column filter changed. |
columnResize | { colId, width } | Resize or autosize finished. |
columnMove | { colId, toIndex } | A column was dragged to a new position. |
columnVisible | { colId, visible } | A column was shown or hidden. |
rangeChange | { range } | The cell range changed. |
cellValueChanged | { row, rowIndex, colId, oldValue, newValue } | A paste changed a cell. |
paste | { changed, skipped } | A paste finished. |
editStart / editStop | { rowIndex, colId, committed? } | An editor opened or closed. |
validationFailed | { rowIndex, colId, value, message } | An edited value was rejected. |
rowsAdded / rowsRemoved | { rows } | Rows were inserted or removed. |
groupChange / groupToggle | { groupBy } / { key, expanded } | Grouping changed; a group or tree row opened or closed. |
detailToggle | { row, expanded } | A detail panel opened or closed. |
rowDragEnd | { row, fromIndex, toIndex } | A row was dragged to a new position. |
pageChange | { page, pageSize, pageCount } | The page or page size changed. |
rowsLoaded / rowsLoadFailed | { start, end, total? } / { start, end, error } | A server block arrived or failed. |
pivotChange | { pivot } | Pivot mode was turned on, changed or turned off. |
focusChange | { rowIndex, colId } | The focused cell moved. |
beforeSort / beforeFilter / beforePageChange | { sort | colId, model | page, pageSize, cancel() } | Fired before the change; call cancel() to stop it (for example to sort or filter on your server instead). |
beforeEdit / beforeCellChange | { row, rowIndex, colId, oldValue?, newValue?, cancel() } | Fired before an editor opens or a value is written (edit, paste, fill, delete); cancel() stops it. |
rowDrop | { row, target, position, fromOtherGrid, fromGrid } | A row was dropped on this grid, also from another grid of the same drag group. |
scroll | { top, left } | The rows area scrolled. |
childrenLoaded / childrenLoadFailed | { row, rows } / { row, error } | treeData.loadChildren finished or failed for a row. |
transactionApplied | { added, updated, removed, notFound } | applyTransaction (or a batch of applyTransactionAsync) finished. |
conditionalFormatsChange | { formats } | The conditional formats changed (API or the dialog). |
assistantAnswer | { prompt, message, applied, skipped } | The assistant panel got an answer and applied its actions. |
rowEditStart / rowEditStop | { row, rowIndex } / { row, rowIndex, committed, changes } | editType 'row': a row started or finished editing; changes lists the changed cells. |
advancedFilterChange | { advancedFilter } | The advanced filter changed. |
columnGroupOpened | { groupId, open } | A collapsible column group was opened or closed. |
Questions about this page
How do I listen to grid events?
Call grid.on("cellClick", handler); it returns a function that stops listening. The wrappers use the same events as onCellClick (React), @cell-click (Vue) and (cellClick) (Angular).
Can I stop a sort or an edit before it happens?
Yes. beforeSort, beforeFilter, beforePageChange, beforeEdit and beforeCellChange arrive first; call cancel() to stop the change.