UNPKG

@progress/kendo-angular-grid

Version:

Kendo UI Grid for Angular - high performance data grid with paging, filtering, virtualization, CRUD, and more.

250 lines (249 loc) 11 kB
/**----------------------------------------------------------------------------------------- * Copyright © 2025 Progress Software Corporation. All rights reserved. * Licensed under commercial license. See LICENSE.md in the project root for more information *-------------------------------------------------------------------------------------------*/ import { Directive, EventEmitter, Input, Output } from '@angular/core'; import { UndoRedoStack } from './undo-redo.stack'; import { GridComponent } from '../grid.component'; import { UndoRedoEvent } from './grid-state.models'; import { Subscription } from 'rxjs'; import { EditService } from '../editing/edit.service'; import { filter } from 'rxjs/operators'; import { UndoRedoService } from './undo-redo.service'; import { hasObservers, isPresent } from '@progress/kendo-angular-common'; import { ChangeNotificationService } from '../data/change-notification.service'; import { ContextService } from '../common/provider.service'; import { LocalDataChangesService } from '../editing/local-data-changes.service'; import { recursiveFlatMap } from '../utils'; import * as i0 from "@angular/core"; import * as i1 from "../grid.component"; import * as i2 from "../editing/edit.service"; import * as i3 from "./undo-redo.service"; import * as i4 from "../data/change-notification.service"; import * as i5 from "../common/provider.service"; import * as i6 from "../editing/local-data-changes.service"; /** * Represents the directive that manages undo-redo operations in the Grid. * Use this directive to enable undo and redo functionality for user actions in the Grid. * * @example * ```html * <kendo-grid [data]="data" kendoGridUndoRedo></kendo-grid> * ``` * @remarks * Applied to: {@link GridComponent}. */ export class UndoRedoDirective { host; editService; undoRedoService; changeNotification; ctx; localDataChangesService; /** * Sets the maximum number of actions to keep in the undo-redo stack. * @default 10 */ maxStoredStates = 10; /** * Defines the property name of the data item unique key that will be used to identify the items when performing undo-redo actions. */ itemIdKey; /** * Fires when you perform the undo action. Provides the Grid state to apply. */ onUndo = new EventEmitter(); /** * Fires when you perform the redo action. Provides the Grid state to apply. */ onRedo = new EventEmitter(); /** * Returns all undo-redo actions currently in the stack. */ get undoRedoItems() { return this.stack.toArray(); } stack; subs = new Subscription(); addToState = true; constructor(host, editService, undoRedoService, changeNotification, ctx, localDataChangesService) { this.host = host; this.editService = editService; this.undoRedoService = undoRedoService; this.changeNotification = changeNotification; this.ctx = ctx; this.localDataChangesService = localDataChangesService; this.host.undoRedoService = this.undoRedoService; } ngOnInit() { this.stack = new UndoRedoStack(this.maxStoredStates); this.subs = this.host.gridStateChange.subscribe((state) => { if (this.addToState) { this.stack.add({ originalEvent: { skip: state.skip, take: state.take, sort: state.sort, filter: state.filter, group: state.group }, gridState: state }); } let stackEndPointReached; if (this.stack.canUndo) { stackEndPointReached = this.stack.canRedo ? false : 'end'; } else { stackEndPointReached = 'start'; } this.undoRedoService.stackEndReached.next(stackEndPointReached); }); this.subs.add(this.editService.changes .pipe(filter((event) => event.action === 'save' || event.action === 'remove')) .subscribe(event => { this.stack.add({ originalEvent: { ...event, dataItem: structuredClone(event.dataItem) }, gridState: this.host.currentState }); this.addToState = false; this.host.gridStateChange.emit(this.stack.current.gridState); this.addToState = true; this.updateUndoRedoDisabled(); })); this.subs.add(this.changeNotification.changes.subscribe(() => { if (!this.ctx.dataBindingDirective) { this.stack.current.gridState = this.host.currentState; } })); ['Undo', 'Redo'].forEach((action) => { this.subs.add(this.undoRedoService[`on${action}`].subscribe(() => { if (!this.stack[`can${action}`]) { return; } let eventData; if (action === 'Undo') { const isSaveOrRemove = this.stack.current.originalEvent.action === 'save' || this.stack.current.originalEvent.action === 'remove'; eventData = isSaveOrRemove ? this.stack.current : this.stack.peekPrev(); } else { eventData = this.stack.peekNext(); } const event = new UndoRedoEvent(eventData); if (hasObservers(this[`on${action}`])) { this[`on${action}`].emit(event); if (event.isDefaultPrevented()) { return; } } this.stack[`${action.toLowerCase()}`](); this.updateUndoRedoDisabled(); const originalAction = event.originalEvent.action; const isLocalData = isPresent(this.ctx?.dataBindingDirective); if (!isLocalData) { return; } const isSaveOrRemove = originalAction === 'save' || originalAction === 'remove'; if (isSaveOrRemove) { if (originalAction === 'save') { const stateItem = this.getGridDataItems(this.stack.current.gridState.currentData).find(item => item[this.itemIdKey] === event.originalEvent.dataItem[this.itemIdKey]); this.localDataChangesService?.data.splice(event.originalEvent.rowIndex, 1, stateItem); } else if (action === 'Undo') { this.localDataChangesService?.data.splice(event.originalEvent.rowIndex, 0, event.originalEvent.dataItem); } else { this.localDataChangesService?.data.splice(event.originalEvent.rowIndex, 1); } this.localDataChangesService?.changes.emit(); } else { this.host.loadState({ ...this.stack.current.gridState, currentData: null }); if (this.isDataStateChangeEvent(event.originalEvent)) { const { skip, take, sort, filter, group } = event.gridState; this.host.dataStateChange.emit({ skip, take, sort, filter, group }); } } })); }); } ngAfterViewInit() { this.stack.add({ originalEvent: { skip: this.host.skip, take: this.host.pageSize, sort: this.host.sort, filter: this.host.filter, group: this.host.group }, gridState: this.host.currentState }); } ngOnDestroy() { this.stack.clear(); this.stack = null; this.subs.unsubscribe(); } /** * Re-applies the last action that you reverted with the `undo` method. */ redo() { if (this.stack.canRedo) { this.stack.redo(); this.host.loadState(this.stack.current.gridState); if (!this.stack.canRedo) { this.undoRedoService.stackEndReached.next('end'); } } } /** * Reverts the last user action that you performed. */ undo() { if (this.stack.canUndo) { this.stack.undo(); this.host.loadState(this.stack.current.gridState); if (!this.stack.canUndo) { this.undoRedoService.stackEndReached.next('start'); } } } updateUndoRedoDisabled() { if (!this.stack.canRedo) { this.undoRedoService.stackEndReached.next('end'); return; } if (!this.stack.canUndo) { this.undoRedoService.stackEndReached.next('start'); return; } this.undoRedoService.stackEndReached.next(false); } getGridDataItems(data) { return Array.isArray(data) ? data.flatMap(recursiveFlatMap) : data.data.flatMap(recursiveFlatMap); } isDataStateChangeEvent(event) { return event && ['skip', 'take', 'sort', 'filter', 'group'].some(prop => prop in event); } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: UndoRedoDirective, deps: [{ token: i1.GridComponent }, { token: i2.EditService }, { token: i3.UndoRedoService }, { token: i4.ChangeNotificationService }, { token: i5.ContextService }, { token: i6.LocalDataChangesService }], target: i0.ɵɵFactoryTarget.Directive }); static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "18.2.14", type: UndoRedoDirective, isStandalone: true, selector: "[kendoGridUndoRedo]", inputs: { maxStoredStates: "maxStoredStates", itemIdKey: "itemIdKey" }, outputs: { onUndo: "undo", onRedo: "redo" }, providers: [UndoRedoService], exportAs: ["kendoGridUndoRedo"], ngImport: i0 }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: UndoRedoDirective, decorators: [{ type: Directive, args: [{ selector: '[kendoGridUndoRedo]', standalone: true, exportAs: 'kendoGridUndoRedo', providers: [UndoRedoService] }] }], ctorParameters: () => [{ type: i1.GridComponent }, { type: i2.EditService }, { type: i3.UndoRedoService }, { type: i4.ChangeNotificationService }, { type: i5.ContextService }, { type: i6.LocalDataChangesService }], propDecorators: { maxStoredStates: [{ type: Input }], itemIdKey: [{ type: Input }], onUndo: [{ type: Output, args: ['undo'] }], onRedo: [{ type: Output, args: ['redo'] }] } });