Skip to content

Editing

In-cell editing is exposed through three builder methods that map directly onto AG Grid's editor lifecycle:

MethodLevelPurpose
column.editable(value)columnEnable editing — boolean or (row) => boolean predicate
column.cellEditorConfig(component, config)columnPlug in a custom Vue editor component (mirrors cellRendererConfig)
gridBuilder.onCellValueChanged(handler)gridReact 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:

ts
(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:

Double-click any cell to edit. Enter commits, Escape cancels. Locked rows can't be edited.
vue
<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>
ts
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.

Double-click the Role cell to open a select-based editor. Other columns use the default text editor.
vue
<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:

vue
<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:

ts
(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:

ts
gridBuilder.option('singleClickEdit', true);

Stop editing on focus loss. Useful for forms where clicking outside should commit:

ts
gridBuilder.stopEditingWhenCellsLoseFocus();

Full-row editing. Edit every cell in a row at once:

ts
gridBuilder.fullRowEdit();

API

Column-level

MethodParametersDescription
.editable(value)boolean | (row: T) => booleanEnable editing statically or via row predicate. The predicate receives row data; rows without data (group rows etc.) return false.
.cellEditorConfig(component, config)Component, objectSet custom cell editor. config is wrapped under cellEditorParams.config.

Grid-level

MethodParametersDescription
.onCellValueChanged(handler)(event: CellValueChangedEvent<T>) => voidFires once per committed cell edit. event.oldValue, event.newValue, event.data, event.colDef.field.
.fullRowEdit(value?)booleanEnable full-row editing mode.
.stopEditingWhenCellsLoseFocus(value?)booleanCommit the edit when focus leaves the cell.

Released under the Apache-2.0 License.