Editing
In-cell editing is exposed through three builder methods that map directly onto AG Grid's editor lifecycle:
| Method | Level | Purpose |
|---|---|---|
column.editable(value) | column | Enable editing — boolean or (row) => boolean predicate |
column.cellEditorConfig(component, config) | column | Plug in a custom Vue editor component (mirrors cellRendererConfig) |
gridBuilder.onCellValueChanged(handler) | grid | React to a committed cell edit |
editable() and cellEditorConfig() are orthogonal — set both, otherwise the editor never opens. If you only set editable(true), the cell uses AG Grid's built-in text editor.
Default Editor
.editable(true) enables the default text editor. Editing starts on double-click (or pressing Enter / F2 with a cell focused). Enter commits, Escape cancels.
A row predicate gates editing per-row — locked items in the demo below skip the editor entirely:
(col) => col.field('name').editable(row => !row.locked)onCellValueChanged fires once per committed edit and surfaces both the previous and new value. The demo updates a status line below the grid:
<template>
<div>
<div style="height: 320px;">
<CoarDataGrid :builder="builder" />
</div>
<div style="margin-top: 12px; font-size: 13px; color: var(--coar-text-neutral-secondary);">
Double-click any cell to edit. Enter commits, Escape cancels. Locked rows can't be edited.
</div>
<div
v-if="lastChange"
style="margin-top: 8px; padding: 8px 12px; border-radius: 6px; background: var(--coar-surface-neutral-subtle); font-size: 13px;"
>
Last change: <strong>{{ lastChange.name }}</strong>'s
<strong>{{ lastChange.field }}</strong> —
<code>{{ lastChange.oldValue }}</code> → <code>{{ lastChange.newValue }}</code>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, shallowRef } from 'vue';
import { CoarDataGrid, CoarGridBuilder } from '@cocoar/vue-data-grid';
interface Person {
id: number;
name: string;
email: string;
amount: number;
locked: boolean;
}
const data = ref<Person[]>([
{ id: 1, name: 'Alice Johnson', email: 'alice@example.com', amount: 1200, locked: false },
{ id: 2, name: 'Bob Smith', email: 'bob@example.com', amount: 800, locked: true },
{ id: 3, name: 'Carol Williams', email: 'carol@example.com', amount: 1750, locked: false },
{ id: 4, name: 'David Brown', email: 'david@example.com', amount: 450, locked: false },
]);
interface Change {
name: string;
field: string;
oldValue: unknown;
newValue: unknown;
}
const lastChange = shallowRef<Change | null>(null);
const builder = CoarGridBuilder.create<Person>()
.columns([
(col) => col.field('name').header('Name').flex(1).editable((row) => !row.locked),
(col) => col.field('email').header('Email').flex(1).editable((row) => !row.locked),
(col) => col.number('amount').header('Amount').width(120).editable((row) => !row.locked),
(col) => col.field('locked').header('Locked').width(100),
])
.rowDataRef(data)
.onCellValueChanged((event) => {
if (!event.data || !event.colDef.field) return;
lastChange.value = {
name: event.data.name,
field: event.colDef.field,
oldValue: event.oldValue,
newValue: event.newValue,
};
});
</script>
CoarGridBuilder.create<Person>()
.columns([
(col) => col.field('name').editable(row => !row.locked),
(col) => col.number('amount').editable(row => !row.locked),
])
.rowDataRef(data)
.onCellValueChanged((event) => {
saveField(event.data, event.colDef.field, event.newValue);
});Custom Cell Editor
cellEditorConfig(component, config) accepts any Vue component and wraps your config object under params.config — the exact same convention as cellRendererConfig.
The component must follow AG Grid's editor contract: receive a single params prop and expose a getValue() method via defineExpose. Cocoar does not ship editor wrappers — building them is consumer territory, since the appropriate input control (text, select, autocomplete, date picker, custom widget) is application-specific.
<template>
<div>
<div style="height: 280px;">
<CoarDataGrid :builder="builder" />
</div>
<div style="margin-top: 12px; font-size: 13px; color: var(--coar-text-neutral-secondary);">
Double-click the <strong>Role</strong> cell to open a select-based editor. Other columns use the default text editor.
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { CoarDataGrid, CoarGridBuilder } from '@cocoar/vue-data-grid';
import SelectCellEditor from './SelectCellEditor.vue';
interface User {
id: number;
name: string;
role: 'Engineer' | 'Designer' | 'Manager';
}
const data = ref<User[]>([
{ id: 1, name: 'Alice Johnson', role: 'Engineer' },
{ id: 2, name: 'Bob Smith', role: 'Designer' },
{ id: 3, name: 'Carol Williams', role: 'Manager' },
{ id: 4, name: 'David Brown', role: 'Engineer' },
]);
const builder = CoarGridBuilder.create<User>()
.columns([
(col) => col.field('name').header('Name').flex(1).editable(true),
(col) =>
col
.field('role')
.header('Role')
.width(160)
.editable(true)
.cellEditorConfig(SelectCellEditor, {
options: ['Engineer', 'Designer', 'Manager'],
}),
])
.rowDataRef(data);
</script>
The minimal SelectCellEditor.vue used above:
<template>
<select ref="selectRef" v-model="value" style="width:100%;height:100%;">
<option v-for="opt in options" :key="opt" :value="opt">{{ opt }}</option>
</select>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import type { ICellEditorParams } from 'ag-grid-community';
const props = defineProps<{
params: ICellEditorParams<unknown, string> & { config: { options: string[] } };
}>();
const selectRef = ref<HTMLSelectElement | null>(null);
const value = ref(props.params.value ?? '');
const options = props.params.config.options;
onMounted(() => selectRef.value?.focus());
defineExpose({ getValue: () => value.value });
</script>Wire it into a column:
(col) =>
col.field('role')
.editable(true)
.cellEditorConfig(SelectCellEditor, {
options: ['Engineer', 'Designer', 'Manager'],
})Tips
Single-click edit. AG Grid's default is double-click. To enter edit on a single click, pass through the native option:
gridBuilder.option('singleClickEdit', true);Stop editing on focus loss. Useful for forms where clicking outside should commit:
gridBuilder.stopEditingWhenCellsLoseFocus();Full-row editing. Edit every cell in a row at once:
gridBuilder.fullRowEdit();API
Column-level
| Method | Parameters | Description |
|---|---|---|
.editable(value) | boolean | (row: T) => boolean | Enable editing statically or via row predicate. The predicate receives row data; rows without data (group rows etc.) return false. |
.cellEditorConfig(component, config) | Component, object | Set custom cell editor. config is wrapped under cellEditorParams.config. |
Grid-level
| Method | Parameters | Description |
|---|---|---|
.onCellValueChanged(handler) | (event: CellValueChangedEvent<T>) => void | Fires once per committed cell edit. event.oldValue, event.newValue, event.data, event.colDef.field. |
.fullRowEdit(value?) | boolean | Enable full-row editing mode. |
.stopEditingWhenCellsLoseFocus(value?) | boolean | Commit the edit when focus leaves the cell. |