diff --git a/docs/grid-functionalities/row-based-edit.md b/docs/grid-functionalities/row-based-edit.md index 3369848f6..7efe2db79 100644 --- a/docs/grid-functionalities/row-based-edit.md +++ b/docs/grid-functionalities/row-based-edit.md @@ -33,6 +33,8 @@ If the [Excel Copy Buffer Plugin](excel-copy-buffer.md) is configured, the Row b The idea of the plugin is to focus the users editing experience on specific individual rows and and save them individually. This is achieved by letting the user toggle one or more rows into edit mode. When a that happens a potentially registered `onBeforeEditMode` callback is executed to handle various preparation or cleanup tasks. Now changes can be made to those rows and will be highlighted and tracked. The user may cancel the edit mode at any time and revert all cells changes. If the save button is pressed on the other hand an `onBeforeRowUpdated` hook, which you define via plugin options, is called and expects a `Promise`. In that method you'd typically write the changes to your backend and return either true or false based on the operations outcome. If a negative boolean is returned the edit mode is kept, otherwise the row applies the changes and toggles back into readonly mode. That means, no modifications can be done on the grid. Additionally, when a row is deleted via the delete button, an `onAfterDelete` callback is executed, which you can use to e.g. remove the row from your backend. +Additionally, you can also register the optional `onBeforeCellEdit` hook which allows to control on a per cell level whether editing should be allowed or not, by returning a boolean. + Here's the respective code shown in Example22: #### ViewModel diff --git a/frameworks/angular-slickgrid/docs/grid-functionalities/row-based-edit.md b/frameworks/angular-slickgrid/docs/grid-functionalities/row-based-edit.md index 53f526153..c792fca25 100644 --- a/frameworks/angular-slickgrid/docs/grid-functionalities/row-based-edit.md +++ b/frameworks/angular-slickgrid/docs/grid-functionalities/row-based-edit.md @@ -33,6 +33,8 @@ If the [Excel Copy Buffer Plugin](excel-copy-buffer.md) is configured, the Row b The idea of the plugin is to focus the users editing experience on specific individual rows and and save them individually. This is achieved by letting the user toggle one or more rows into edit mode. When a that happens a potentially registered `onBeforeEditMode` callback is executed to handle various preparation or cleanup tasks. Now changes can be made to those rows and will be highlighted and tracked. The user may cancel the edit mode at any time and revert all cells changes. If the save button is pressed on the other hand an `onBeforeRowUpdated` hook, which you define via plugin options, is called and expects a `Promise`. In that method you'd typically write the changes to your backend and return either true or false based on the operations outcome. If a negative boolean is returned the edit mode is kept, otherwise the row applies the changes and toggles back into readonly mode. That means, no modifications can be done on the grid. Additionally, when a row is deleted via the delete button, an `onAfterDelete` callback is executed, which you can use to e.g. remove the row from your backend. +Additionally, you can also register the optional `onBeforeCellEdit` hook which allows to control on a per cell level whether editing should be allowed or not, by returning a boolean. + Here's the respective code shown in Example22: #### ViewModel diff --git a/frameworks/aurelia-slickgrid/docs/grid-functionalities/row-based-edit.md b/frameworks/aurelia-slickgrid/docs/grid-functionalities/row-based-edit.md index 44b29556a..4c748a750 100644 --- a/frameworks/aurelia-slickgrid/docs/grid-functionalities/row-based-edit.md +++ b/frameworks/aurelia-slickgrid/docs/grid-functionalities/row-based-edit.md @@ -33,6 +33,8 @@ If the [Excel Copy Buffer Plugin](excel-copy-buffer.md) is configured, the Row b The idea of the plugin is to focus the users editing experience on specific individual rows and and save them individually. This is achieved by letting the user toggle one or more rows into edit mode. When a that happens a potentially registered `onBeforeEditMode` callback is executed to handle various preparation or cleanup tasks. Now changes can be made to those rows and will be highlighted and tracked. The user may cancel the edit mode at any time and revert all cells changes. If the save button is pressed on the other hand an `onBeforeRowUpdated` hook, which you define via plugin options, is called and expects a `Promise`. In that method you'd typically write the changes to your backend and return either true or false based on the operations outcome. If a negative boolean is returned the edit mode is kept, otherwise the row applies the changes and toggles back into readonly mode. That means, no modifications can be done on the grid. Additionally, when a row is deleted via the delete button, an `onAfterDelete` callback is executed, which you can use to e.g. remove the row from your backend. +Additionally, you can also register the optional `onBeforeCellEdit` hook which allows to control on a per cell level whether editing should be allowed or not, by returning a boolean. + Here's the respective code shown in Example22: #### ViewModel diff --git a/frameworks/slickgrid-react/docs/grid-functionalities/row-based-edit.md b/frameworks/slickgrid-react/docs/grid-functionalities/row-based-edit.md index 6d14dcc02..a142cdaa9 100644 --- a/frameworks/slickgrid-react/docs/grid-functionalities/row-based-edit.md +++ b/frameworks/slickgrid-react/docs/grid-functionalities/row-based-edit.md @@ -33,6 +33,8 @@ If the [Excel Copy Buffer Plugin](excel-copy-buffer.md) is configured, the Row b The idea of the plugin is to focus the users editing experience on specific individual rows and and save them individually. This is achieved by letting the user toggle one or more rows into edit mode. When a that happens a potentially registered `onBeforeEditMode` callback is executed to handle various preparation or cleanup tasks. Now changes can be made to those rows and will be highlighted and tracked. The user may cancel the edit mode at any time and revert all cells changes. If the save button is pressed on the other hand an `onBeforeRowUpdated` hook, which you define via plugin options, is called and expects a `Promise`. In that method you'd typically write the changes to your backend and return either true or false based on the operations outcome. If a negative boolean is returned the edit mode is kept, otherwise the row applies the changes and toggles back into readonly mode. That means, no modifications can be done on the grid. Additionally, when a row is deleted via the delete button, an `onAfterDelete` callback is executed, which you can use to e.g. remove the row from your backend. +Additionally, you can also register the optional `onBeforeCellEdit` hook which allows to control on a per cell level whether editing should be allowed or not, by returning a boolean. + Here's the respective code shown in Example22: #### ViewModel diff --git a/frameworks/slickgrid-vue/docs/grid-functionalities/row-based-edit.md b/frameworks/slickgrid-vue/docs/grid-functionalities/row-based-edit.md index 21c09223f..b99f88d81 100644 --- a/frameworks/slickgrid-vue/docs/grid-functionalities/row-based-edit.md +++ b/frameworks/slickgrid-vue/docs/grid-functionalities/row-based-edit.md @@ -33,6 +33,8 @@ If the [Excel Copy Buffer Plugin](excel-copy-buffer.md) is configured, the Row b The idea of the plugin is to focus the users editing experience on specific individual rows and and save them individually. This is achieved by letting the user toggle one or more rows into edit mode. When a that happens a potentially registered `onBeforeEditMode` callback is executed to handle various preparation or cleanup tasks. Now changes can be made to those rows and will be highlighted and tracked. The user may cancel the edit mode at any time and revert all cells changes. If the save button is pressed on the other hand an `onBeforeRowUpdated` hook, which you define via plugin options, is called and expects a `Promise`. In that method you'd typically write the changes to your backend and return either true or false based on the operations outcome. If a negative boolean is returned the edit mode is kept, otherwise the row applies the changes and toggles back into readonly mode. That means, no modifications can be done on the grid. Additionally, when a row is deleted via the delete button, an `onAfterDelete` callback is executed, which you can use to e.g. remove the row from your backend. +Additionally, you can also register the optional `onBeforeCellEdit` hook which allows to control on a per cell level whether editing should be allowed or not, by returning a boolean. + Here's the respective code shown in Example22: #### ViewModel diff --git a/packages/common/src/extensions/__tests__/slickRowBasedEdit.spec.ts b/packages/common/src/extensions/__tests__/slickRowBasedEdit.spec.ts index 4f0c83b1b..fc12e8211 100644 --- a/packages/common/src/extensions/__tests__/slickRowBasedEdit.spec.ts +++ b/packages/common/src/extensions/__tests__/slickRowBasedEdit.spec.ts @@ -3,7 +3,15 @@ import { afterEach, beforeEach, describe, expect, it, vi, type Mock } from 'vite import { TranslateServiceStub } from '../../../../../test/translateServiceStub.js'; import { SlickEvent, type SlickGrid } from '../../core/index.js'; import { Editors } from '../../editors/editors.index.js'; -import type { Column, EditCommand, GridOption, OnBeforeEditCellEventArgs, OnSetOptionsEventArgs, RowBasedEditOptions } from '../../interfaces/index.js'; +import type { + Column, + EditCommand, + GridOption, + OnBeforeEditCellEventArgs, + OnEventArgs, + OnSetOptionsEventArgs, + RowBasedEditOptions, +} from '../../interfaces/index.js'; import { GridService } from '../../services/grid.service.js'; import type { ExtensionUtility } from '../extensionUtility.js'; import { @@ -148,6 +156,36 @@ describe('Row Based Edit Plugin', () => { expect(onBeforeEditCellHandler({} as Event, { item: fakeItem } as OnBeforeEditCellEventArgs)).toBe(true); }); + it('should respect user provided custom onBeforeCellEdit hooks', () => { + const fakeItem = { id: 'test' }; + + gridStub.getData.mockReturnValue({ getItem: () => fakeItem }); + gridStub.getOptions.mockReturnValue({ ...optionsMock }); + let userCanEdit = false; + plugin = new SlickRowBasedEdit(extensionUtilityStub, pubSubServiceStub, { + ...addonOptions, + onBeforeCellEdit: (_args: OnEventArgs) => (() => userCanEdit)(), + }); + (plugin as any)._eventHandler = { + subscribe: vi.fn(), + unsubscribeAll: vi.fn(), + }; + plugin.init(gridStub, gridService); + + const onBeforeEditCellHandler = (plugin.eventHandler.subscribe as Mock).mock.calls[0][1] as (e: Event, args: OnBeforeEditCellEventArgs) => boolean; + expect(onBeforeEditCellHandler({} as Event, { item: fakeItem } as OnBeforeEditCellEventArgs)).toBe(false); + + // set row to edit mode + plugin.rowBasedEditCommandHandler(fakeItem, {} as Column, {} as EditCommand); + + // should still fail + expect(onBeforeEditCellHandler({} as Event, { item: fakeItem } as OnBeforeEditCellEventArgs)).toBe(false); + + // change to allow in userland + userCanEdit = true; + expect(onBeforeEditCellHandler({} as Event, { item: fakeItem } as OnBeforeEditCellEventArgs)).toBe(true); + }); + it('should throw an error when "enableRowEdit" is set without "enableCellNavigation"', () => { gridStub.getOptions.mockReturnValue({}); diff --git a/packages/common/src/extensions/slickRowBasedEdit.ts b/packages/common/src/extensions/slickRowBasedEdit.ts index a4af34d21..a8a5abcf4 100644 --- a/packages/common/src/extensions/slickRowBasedEdit.ts +++ b/packages/common/src/extensions/slickRowBasedEdit.ts @@ -472,8 +472,15 @@ export class SlickRowBasedEdit { return actionFragment; } - protected onBeforeEditCellHandler = (_e: SlickEventData, args: OnBeforeEditCellEventArgs) => { - return this._editedRows.has(args.item?.[this.gridOptions.datasetIdPropertyName ?? 'id']) as boolean; + protected onBeforeEditCellHandler = (_e: SlickEventData, args: OnBeforeEditCellEventArgs): boolean => { + const hasOnBeforeCellEditFn = typeof this._addonOptions?.onBeforeCellEdit === 'function'; + const canEdit = this._editedRows.has(args.item?.[this.gridOptions.datasetIdPropertyName ?? 'id']) as boolean; + let userCanEdit = true; + if (hasOnBeforeCellEditFn) { + userCanEdit = this._addonOptions?.onBeforeCellEdit!(args as unknown as OnEventArgs) ?? true; + } + + return canEdit && userCanEdit; }; protected toggleEditmode(dataContext: any, editMode: boolean): void { diff --git a/packages/common/src/interfaces/rowBasedEditOption.interface.ts b/packages/common/src/interfaces/rowBasedEditOption.interface.ts index 95841471b..aaac8dc36 100644 --- a/packages/common/src/interfaces/rowBasedEditOption.interface.ts +++ b/packages/common/src/interfaces/rowBasedEditOption.interface.ts @@ -76,6 +76,9 @@ export interface RowBasedEditOptions { /** method called before a row enters edit mode. */ onBeforeEditMode?: (args: OnEventArgs) => void; + /** method deciding whether an individual cell can be toggled to edit mode */ + onBeforeCellEdit?: (args: OnEventArgs) => boolean; + /** method called after a row is deleted. */ onAfterDelete?: (args: OnEventArgs) => void; }