Results:
Loading...

Vue Data GridUndo / Redo Edits

This section covers how to allow users to undo / redo their cell edits.

When Cell Editing is enabled in the grid, it is usually desirable to allow users to undo / redo any edits.

Users can change the contents of cells through the following grid features:

This Undo / Redo feature is designed to be a recovery mechanism for user editing mistakes. Performing grid operations that change the row / column order, e.g. sorting, filtering and grouping, will clear the undo / redo stacks.

Enabling Undo / Redo

The following undo / redo properties are provided in the grid options interface:

<ag-grid-vue
    :undoRedoCellEditing="undoRedoCellEditing"
    :undoRedoCellEditingLimit="undoRedoCellEditingLimit"
    /* other grid options ... */>
</ag-grid-vue>

this.undoRedoCellEditing = true;
this.undoRedoCellEditingLimit = 20;

As shown in the snippet above, undo / redo is enabled through the undoRedoCellEditing property.

The default number of undo / redo steps is 10. To change this default the undoRedoCellEditingLimit property can be used.

Undo / Redo Shortcuts

The following keyboard shortcuts are available when undo / redo is enabled:

  • Ctrl+Z / Command+Z: will undo the last cell edit(s).
  • Ctrl+Y / Command+Y: will redo the last undo.

Note that the grid needs focus for these shortcuts to have an effect.

Undo / Redo API

It is also possible to programmatically control undo / redo and check the number of currently available undo / redo actions. These API methods are listed below:

undoCellEditing
Function
Reverts the last cell edit.
undoCellEditing = () => void;
redoCellEditing
Function
Re-applies the most recently undone cell edit.
redoCellEditing = () => void;
getCurrentUndoSize
Function
Returns current number of available cell edit undo operations.
getCurrentUndoSize = () => number;
getCurrentRedoSize
Function
Returns current number of available cell edit redo operations.
getCurrentRedoSize = () => number;

Undo / Redo Events

The following events are relevant to undo / redo:

cellValueChanged
CellValueChangedEvent
Value has changed after editing (this event will not fire if editing was cancelled, eg ESC was pressed) or if cell value has changed as a result of cut, paste, cell clear (pressing Delete key), fill handle, copy range down, undo and redo.
onCellValueChanged = (
    event: CellValueChangedEvent<TData>
) => void;

interface CellValueChangedEvent<TData = any, TValue = any> {
  oldValue: any;
  newValue: any;
  source: string | undefined;
  column: Column;
  colDef: ColDef<TData>;
  // The value for the cell 
  value: TValue;
  // The user provided data for the row. 
  data: TData;
  // The row node. 
  node: IRowNode<TData>;
  // The visible row index for the row 
  rowIndex: number | null;
  // Either 'top', 'bottom' or null / undefined (if not set) 
  rowPinned: RowPinnedType;
  // If event was due to browser event (eg click), this is the browser event 
  event?: Event | null;
  // If the browser `event` is present the `eventPath` persists the `event.composedPath()` result for access within AG Grid event handlers.  
  eventPath?: EventTarget[];
  // The grid api. 
  api: GridApi<TData>;
  // The column api. 
  columnApi: ColumnApi;
  // Application context as set on `gridOptions.context`. 
  context: TContext;
  // Event identifier 
  type: string;
}

type RowPinnedType = 
      'top' 
    | 'bottom' 
    | null 
    | undefined
undoStarted
UndoStartedEvent
Undo operation has started.
onUndoStarted = (
    event: UndoStartedEvent<TData>
) => void;

interface UndoStartedEvent<TData = any, TContext = any> {
  // Source of the event. `api` if via API method. `ui` if via keyboard shortcut. 
  source: 'api' | 'ui';
  // The grid api. 
  api: GridApi<TData>;
  // The column api. 
  columnApi: ColumnApi;
  // Application context as set on `gridOptions.context`. 
  context: TContext;
  // Event identifier 
  type: string;
}
undoEnded
UndoEndedEvent
Undo operation has ended.
onUndoEnded = (
    event: UndoEndedEvent<TData>
) => void;

interface UndoEndedEvent<TData = any, TContext = any> {
  // Source of the event. `api` if via API method. `ui` if via keyboard shortcut. 
  source: 'api' | 'ui';
  // `true` if any undo operations were performed. 
  operationPerformed: boolean;
  // The grid api. 
  api: GridApi<TData>;
  // The column api. 
  columnApi: ColumnApi;
  // Application context as set on `gridOptions.context`. 
  context: TContext;
  // Event identifier 
  type: string;
}
redoStarted
RedoStartedEvent
Redo operation has started.
onRedoStarted = (
    event: RedoStartedEvent<TData>
) => void;

interface RedoStartedEvent<TData = any, TContext = any> {
  // Source of the event. `api` if via API method. `ui` if via keyboard shortcut. 
  source: 'api' | 'ui';
  // The grid api. 
  api: GridApi<TData>;
  // The column api. 
  columnApi: ColumnApi;
  // Application context as set on `gridOptions.context`. 
  context: TContext;
  // Event identifier 
  type: string;
}
redoEnded
RedoEndedEvent
Redo operation has ended.
onRedoEnded = (
    event: RedoEndedEvent<TData>
) => void;

interface RedoEndedEvent<TData = any, TContext = any> {
  // Source of the event. `api` if via API method. `ui` if via keyboard shortcut. 
  source: 'api' | 'ui';
  // `true` if any redo operations were performed. 
  operationPerformed: boolean;
  // The grid api. 
  api: GridApi<TData>;
  // The column api. 
  columnApi: ColumnApi;
  // Application context as set on `gridOptions.context`. 
  context: TContext;
  // Event identifier 
  type: string;
}

For an undo / redo, the events will be fired as:

  1. One undoStarted / redoStarted event.
  2. Zero to many cellValueChanged events.
  3. One undoEnded / redoEnded event.

When there are no undo / redo operations to perform, the started and ended events will still fire. However, the ended event will have a value of false for the operationPerformed property (compared to true when an operation was performed).

If the application is doing work each time it receives a cellValueChanged event, you can use the undoStarted / redoStarted and undoEnded / redoEnded events to suspend the application's work and then do the work for all cells impacted by the undo / redo operation afterwards.

If Read Only Edit is enabled, undo / redo will not perform any operations. The started and ended events will still fire, which means that you can implement your own undo / redo by keeping track of the cellEditRequest events.

Example: Undo / Redo

The example below has the following grid options enabled to demonstrate undo / redo:

<ag-grid-vue
    :defaultColDef="defaultColDef"
    :enableRangeSelection="enableRangeSelection"
    :enableFillHandle="enableFillHandle"
    :undoRedoCellEditing="undoRedoCellEditing"
    :undoRedoCellEditingLimit="undoRedoCellEditingLimit"
    :enableCellChangeFlash="enableCellChangeFlash"
    /* other grid options ... */>
</ag-grid-vue>

this.defaultColDef = {
     // makes all cells editable
     editable: true
 };

// allows copy / paste using cell ranges
this.enableRangeSelection = true;

// enables the fill handle
this.enableFillHandle = true;

// enables undo / redo
this.undoRedoCellEditing = true;

// restricts the number of undo / redo steps to 5
this.undoRedoCellEditingLimit = 5;

// enables flashing to help see cell changes
this.enableCellChangeFlash = true;

To see undo / redo in action, try the following:

  • Cell Editing: click and edit some cell values.
  • Fill Handle: drag the fill handle to change a range of cells.
  • Copy / Paste: use Ctrl+C / Ctrl+V to copy and paste a range of cells.
  • Undo Shortcut: use Ctrl+Z to undo the cell edits.
  • Redo Shortcut: use Ctrl+Y to redo the undone cell edits.
  • Undo API: use the 'Undo' button to invoke gridApi.undoCellEditing().
  • Redo API: use the 'Redo' button to invoke gridApi.redoCellEditing().
  • Undo / Redo Limit: only 5 actions are allowed as undoRedoCellEditingLimit=5.

Complex Objects

If your cell values contain complex objects, there are a few steps necessary for undo / redo to work.

For manual editing, a Value Parser is required to convert string values back into complex objects.

<ag-grid-vue
    :columnDefs="columnDefs"
    /* other grid options ... */>
</ag-grid-vue>

this.columnDefs = [
    {
        field: 'a',
        editable: true,
        valueParser: params => {
            // convert `params.newValue` string value into complex object
            return {
                actualValue: params.newValue,
                anotherProperty: params.data.anotherProperty,
            }
        }
    }
];

If a Value Getter is being used to create complex objects, a Value Setter must be used to update the data. colDef.equals is also needed when Comparing Values to determine if the cell value has changed for rendering.

<ag-grid-vue
    :columnDefs="columnDefs"
    /* other grid options ... */>
</ag-grid-vue>

this.columnDefs = [
    {
        field: 'a',
        editable: true,
        valueGetter: params => {
            // create complex object from data
            return {
                actualValue: params.data[params.colDef.field],
                anotherProperty: params.data.anotherProperty,
            }
        },
        valueSetter: params => {
            // update data from complex object
            params.data[params.colDef.field] = params.newValue.actualValue
            return true
        },
        equals: (valueA, valueB) => {
            // compare complex objects
            return valueA.actualValue === valueB.actualValue
        }
    }
];

Complex object cell values must be immutable. If the cell values are mutated, undo / redo will not be able to restore the original values. This means that the Value Parser must return a new complex object.

When using a Fill Handle with a horizontal fill direction and your columns do not all have same complex object type, you will need to implement a Custom User Function. Note that the fill values provided to the function could be complex objects from any column, which you will need to handle.

Clipboard operations (copy/paste) use string values, so complex objects require Processing Pasted Data to convert between complex objects and strings.

The following example demonstrates how to use complex objects with undo / redo.

  • For column A:

    • A Value Getter is used to create complex objects from the data.
    • The complex objects have a toString property used for rendering.
    • A Value Setter is used to update the data from the complex objects (the inverse of the Value Getter).
    • A Value Parser is used to convert the string values produced from cell editing into complex objects (the inverse of the toString method).
    • A Column Definition equals function is provided to compare the complex objects (without this the grid would use reference equality, but this won't work here as the Value Getter returns a new object each time).
  • For column B:

    • The column values are complex objects.
    • A Value Formatter is used to convert the complex objects into strings for rendering.
    • A Value Parser is used to convert the string values produced from cell editing into complex objects (the inverse of the Value Formatter).
    • Dynamic Parameters are provided to the cell editor to display a string value when you edit the cell (column A didn't need this as it has a toString property).
  • For all columns:

    • fillHandleDirection = 'y' which prevents the Fill Handle from being used to drag values between the columns, as they have different complex object formats.
    • processCellForClipboard is implemented, which converts complex object values into strings when copying cell values.
    • processCellFromClipboard is implemented, which converts string values into complex objects when pasting cell values.
  • Try the following actions:

    • Cell Editing: click and edit some cell values.
    • Fill Handle: drag the fill handle to change a range of cells.
    • Copy / Paste: use Ctrl+C / Ctrl+V to copy and paste a range of cells.
    • Undo Shortcut: use Ctrl+Z to undo the cell edits.
    • Redo Shortcut: use Ctrl+Y to redo the undone cell edits.
    • Undo API: use the 'Undo' button to invoke gridApi.undoCellEditing().
    • Redo API: use the 'Redo' button to invoke gridApi.redoCellEditing().
    • Undo / Redo Limit: only 5 actions are allowed as undoRedoCellEditingLimit=5.