UndoRedoPlugin
UndoRedoPlugin implements command pattern with CommandStack for undo/redo operations.
The UndoRedoPlugin implements the command pattern using CommandStack from @zengrid/shared, providing undo/redo functionality for grid operations.
Overview
UndoRedoPlugin runs at phase 50 and provides:
- Command pattern implementation
- Undo/redo for cell edits, filters, sorting
- Custom command support
- Command history management
- Reactive state integration
UndoRedoPlugin uses CommandStack from @zengrid/shared for efficient command history management.
Creation
Create the UndoRedoPlugin using the factory function:
import { createUndoRedoPlugin } from '@zengrid/core';
const undoRedoPlugin = createUndoRedoPlugin();grid.usePlugin(undoRedoPlugin);Command Pattern
UndoRedoPlugin implements the command pattern:
interface ICommand { description: string; execute(): void; undo(): void; redo?(): void;}Each operation (cell edit, filter change, sort change) is wrapped in a command that knows how to execute, undo, and redo itself.
Built-in Commands
Cell Edit Command
Record cell edits automatically:
// EditingPlugin automatically records editsgrid.api.editing.startEditing(5, 2);grid.api.editing.commitEdit(); // Automatically recorded
// Or record manuallygrid.api.undoRedo.recordCellEdit( 5, // row 2, // col 'old value', // old value 'new value', // new value (row, col, value) => { grid.api.data.setCellValue(row, col, value); });Filter Change Command
Record filter changes:
const oldFilters = grid.api.filter.getFilters();
// Apply new filtersgrid.api.filter.setFilters([ { field: 'age', operator: 'gt', value: 18 }]);
// Record the changegrid.api.undoRedo.recordFilterChange( oldFilters, grid.api.filter.getFilters(), (filters) => { grid.api.filter.setFilters(filters); });Sort Change Command
Record sort changes:
const oldSortState = grid.api.sort.getSortState();
// Apply new sortgrid.api.sort.setSortState([ { field: 'name', direction: 'asc' }]);
// Record the changegrid.api.undoRedo.recordSortChange( oldSortState, grid.api.sort.getSortState(), (sortState) => { grid.api.sort.setSortState(sortState); });Built-in plugins automatically record their changes to the undo/redo stack.
Custom Commands
Create custom commands for any operation:
grid.api.undoRedo.recordCustomCommand( 'Bulk Update', // description () => { // execute // Perform operation for (let i = 0; i < 10; i++) { grid.api.data.setCellValue(i, 0, 'Updated'); } }, () => { // undo // Reverse operation for (let i = 0; i < 10; i++) { grid.api.data.setCellValue(i, 0, 'Original'); } }, () => { // redo (optional) // Re-execute operation for (let i = 0; i < 10; i++) { grid.api.data.setCellValue(i, 0, 'Updated'); } });Reactive State
UndoRedoPlugin creates these reactive signals:
// Undo/redo availabilitystore.get('undoRedo.canUndo') // Boolean: undo availablestore.get('undoRedo.canRedo') // Boolean: redo available
// Command countsstore.get('undoRedo.undoCount') // Number of undo commandsstore.get('undoRedo.redoCount') // Number of redo commands
// Current commandstore.get('undoRedo.currentCommand') // Current command descriptionAPI Methods
UndoRedoPlugin registers these methods on grid.api.undoRedo:
// Undo/Redo operationsgrid.api.undoRedo.undo();grid.api.undoRedo.redo();
// Check availabilityconst canUndo = grid.api.undoRedo.canUndo();const canRedo = grid.api.undoRedo.canRedo();
// Command recordinggrid.api.undoRedo.recordCellEdit(row, col, oldValue, newValue, setValue);grid.api.undoRedo.recordFilterChange(oldFilters, newFilters, setFilters);grid.api.undoRedo.recordSortChange(oldSort, newSort, setSort);grid.api.undoRedo.recordCustomCommand(description, execute, undo, redo);
// Execute command directlygrid.api.undoRedo.executeCommand(command);
// History managementgrid.api.undoRedo.clear();grid.api.undoRedo.getUndoHistory();grid.api.undoRedo.getRedoHistory();Command Execution
Execute commands directly through the API:
const command: ICommand = { description: 'Clear Grid', execute() { grid.api.data.setData([]); }, undo() { grid.api.data.setData(originalData); }};
// Execute and add to historygrid.api.undoRedo.executeCommand(command);History Management
Manage command history:
// Get undo historyconst undoHistory = grid.api.undoRedo.getUndoHistory();console.log('Undo commands:', undoHistory.map(c => c.description));
// Get redo historyconst redoHistory = grid.api.undoRedo.getRedoHistory();console.log('Redo commands:', redoHistory.map(c => c.description));
// Clear all historygrid.api.undoRedo.clear();Events
UndoRedoPlugin emits events for undo/redo operations:
grid.on('undo-redo:change', (event) => { console.log('Undo/Redo state changed'); console.log('Can undo:', event.canUndo); console.log('Can redo:', event.canRedo);});
grid.on('undo-redo:undo', (command) => { console.log('Undid:', command.description);});
grid.on('undo-redo:redo', (command) => { console.log('Redid:', command.description);});
grid.on('undo-redo:execute', (command) => { console.log('Executed:', command.description);});Keyboard Shortcuts
Integrate with keyboard shortcuts:
const grid = createGrid(container, { undoRedo: { shortcuts: { undo: 'Ctrl+Z', // Windows/Linux redo: 'Ctrl+Y', // Windows/Linux undoMac: 'Cmd+Z', // macOS redoMac: 'Cmd+Shift+Z' // macOS } }});
grid.usePlugin(createUndoRedoPlugin());
// Keyboard shortcuts automatically trigger undo/redoUndoRedoPlugin automatically detects platform and uses appropriate keyboard shortcuts.
CommandStack Integration
UndoRedoPlugin uses CommandStack from @zengrid/shared:
// Stack size limitconst grid = createGrid(container, { undoRedo: { maxStackSize: 100 // Limit command history }});
// Automatic cleanup when limit reached// Oldest commands removed first (FIFO)Batch Commands
Group multiple operations into a single undo/redo action:
grid.api.undoRedo.beginBatch('Bulk Update');
// Multiple operationsfor (let i = 0; i < 100; i++) { grid.api.data.setCellValue(i, 0, 'Updated');}
grid.api.undoRedo.endBatch();
// Undo reverses all 100 operations as onegrid.api.undoRedo.undo();Batch commands improve performance and user experience for bulk operations.
Integration with Store
UndoRedoPlugin integrates seamlessly with the reactive store:
// Watch undo/redo changesstore.effect('updateUndoRedoUI', () => { const canUndo = store.get('undoRedo.canUndo'); const canRedo = store.get('undoRedo.canRedo');
// Update UI buttons undoButton.disabled = !canUndo; redoButton.disabled = !canRedo;}, 'MyComponent', 100);
// Computed from undo/redo statestore.computed('undoRedo.status', () => { const undoCount = store.get('undoRedo.undoCount'); const redoCount = store.get('undoRedo.redoCount'); return `Undo: ${undoCount}, Redo: ${redoCount}`;}, 'MyComponent', 100);UndoRedoManager Methods
The UndoRedoManager provides these methods:
interface UndoRedoManager { // Cell operations recordCellEdit(row: number, col: number, oldValue: any, newValue: any, setValue: Function): void;
// Filter operations recordFilterChange(oldFilters: FilterState, newFilters: FilterState, setFilters: Function): void;
// Sort operations recordSortChange(oldSortState: SortState, newSortState: SortState, setSortState: Function): void;
// Custom commands recordCustomCommand(description: string, execute: Function, undo: Function, redo?: Function): void; executeCommand(command: ICommand): void;
// Undo/Redo undo(): void; redo(): void; canUndo(): boolean; canRedo(): boolean;
// History clear(): void; getUndoHistory(): ICommand[]; getRedoHistory(): ICommand[];}Performance
UndoRedoPlugin optimizes command history:
- Efficient stack: O(1) push/pop operations
- Memory limits: Configurable max stack size
- Batch operations: Group multiple commands
- Lazy execution: Commands only execute when needed