From 72ef747bed152fb8c2fa2f08c2739817832219ac Mon Sep 17 00:00:00 2001 From: Ghislain B <gbeaulac@gmail.com> Date: Wed, 28 Feb 2018 00:35:57 -0500 Subject: [PATCH] feat(grid): add inline editor undo command --- .../models/editCommand.interface.ts | 15 ++++ .../models/gridOption.interface.ts | 27 ++++--- .../src/aurelia-slickgrid/models/index.ts | 2 + .../src/examples/slickgrid/example3.html | 9 +++ .../src/examples/slickgrid/example3.ts | 37 +++++++--- .../src/examples/slickgrid/example3.html | 11 +++ client-cli/src/examples/slickgrid/example3.js | 39 +++++++---- .../src/examples/slickgrid/example3.html | 11 +++ .../src/examples/slickgrid/example3.ts | 70 +++++++++++++++---- 9 files changed, 175 insertions(+), 46 deletions(-) create mode 100644 aurelia-slickgrid/src/aurelia-slickgrid/models/editCommand.interface.ts diff --git a/aurelia-slickgrid/src/aurelia-slickgrid/models/editCommand.interface.ts b/aurelia-slickgrid/src/aurelia-slickgrid/models/editCommand.interface.ts new file mode 100644 index 000000000..e656f44b9 --- /dev/null +++ b/aurelia-slickgrid/src/aurelia-slickgrid/models/editCommand.interface.ts @@ -0,0 +1,15 @@ +import { Editor } from './editor.interface'; + +export interface EditCommand { + row: number; + cell: number; + editor: Editor | any; + serializedValue: any; + prevSerializedValue: any; + + /** Call to commit changes */ + execute: () => void; + + /** Call to rollback changes */ + undo: () => void; +} diff --git a/aurelia-slickgrid/src/aurelia-slickgrid/models/gridOption.interface.ts b/aurelia-slickgrid/src/aurelia-slickgrid/models/gridOption.interface.ts index 9cc331e18..54364690c 100644 --- a/aurelia-slickgrid/src/aurelia-slickgrid/models/gridOption.interface.ts +++ b/aurelia-slickgrid/src/aurelia-slickgrid/models/gridOption.interface.ts @@ -1,13 +1,17 @@ -import { AutoResizeOption } from './autoResizeOption.interface'; -import { BackendEventChanged } from './backendEventChanged.interface'; -import { BackendServiceApi } from './backendServiceApi.interface'; -import { ColumnPicker } from './columnPicker.interface'; -import { CheckboxSelector } from './checkboxSelector.interface'; -import { ExportOption } from './exportOption.interface'; -import { GridMenu } from './gridMenu.interface'; -import { HeaderButton } from './headerButton.interface'; -import { HeaderMenu } from './headerMenu.interface'; -import { Pagination } from './pagination.interface'; +import { + AutoResizeOption, + BackendEventChanged, + BackendServiceApi, + Column, + ColumnPicker, + CheckboxSelector, + EditCommand, + ExportOption, + GridMenu, + HeaderButton, + HeaderMenu, + Pagination +} from './../models/index'; export interface GridOption { /** Defaults to false, which leads to load editor asynchronously (delayed) */ @@ -52,6 +56,9 @@ export interface GridOption { /** Defaults to false, when enabled will give the possibility to edit cell values with inline editors. */ editable?: boolean; + /** option to intercept edit commands and implement undo support. */ + editCommandHandler?: (item: any, column: Column, command: EditCommand) => void; + /** Do we want to enable asynchronous (delayed) post rendering */ enableAsyncPostRender?: boolean; diff --git a/aurelia-slickgrid/src/aurelia-slickgrid/models/index.ts b/aurelia-slickgrid/src/aurelia-slickgrid/models/index.ts index d88025a69..37a2c6802 100644 --- a/aurelia-slickgrid/src/aurelia-slickgrid/models/index.ts +++ b/aurelia-slickgrid/src/aurelia-slickgrid/models/index.ts @@ -9,9 +9,11 @@ export * from './checkboxSelector.interface'; export * from './column.interface'; export * from './columnFilter.interface'; export * from './columnFilters.interface'; +export * from './columnPicker.interface'; export * from './customGridMenu.interface'; export * from './delimiterType.enum'; export * from './editor.interface'; +export * from './editCommand.interface'; export * from './exportOption.interface'; export * from './fieldType.enum'; export * from './fileType.enum'; diff --git a/aurelia-slickgrid/src/examples/slickgrid/example3.html b/aurelia-slickgrid/src/examples/slickgrid/example3.html index fa08af263..5a36318dd 100644 --- a/aurelia-slickgrid/src/examples/slickgrid/example3.html +++ b/aurelia-slickgrid/src/examples/slickgrid/example3.html @@ -12,6 +12,12 @@ <h2>${title}</h2> <label class="radio-inline control-label" for="radioFalse"> <input type="radio" name="inlineRadioOptions" id="radioFalse" value.bind="isAutoEdit" click.delegate="setAutoEdit(false)"> OFF (double-click) </label> + <span> + <button class="btn btn-default btn-sm" click.delegate="undo()"> + <i class="fa fa-undo"></i> + Undo last edit + </button> + </span> <button class="btn btn-default btn-sm" click.delegate="switchLanguage()">Switch Language</button> <label>Locale</label>: ${selectedLanguage + '.json'} </span> @@ -21,6 +27,9 @@ <h2>${title}</h2> <div class="alert alert-info" show.bind="updatedObject"> <strong>Update Object:</strong> ${updatedObject | stringify} </div> + <div class="alert alert-warning" show.bind="alertWarning"> + <strong></strong> ${alertWarning} + </div> </div> <div id="grid-container" class="col-sm-12"> diff --git a/aurelia-slickgrid/src/examples/slickgrid/example3.ts b/aurelia-slickgrid/src/examples/slickgrid/example3.ts index ef97265ba..e0ce9f399 100644 --- a/aurelia-slickgrid/src/examples/slickgrid/example3.ts +++ b/aurelia-slickgrid/src/examples/slickgrid/example3.ts @@ -2,24 +2,29 @@ import { I18N } from 'aurelia-i18n'; import { autoinject, bindable } from 'aurelia-framework'; import { Column, Editors, FieldType, Formatters, GridExtraService, GridExtraUtils, GridOption, OnEventArgs, ResizerService } from '../../aurelia-slickgrid'; +// using external non-typed js libraries +declare var Slick: any; + @autoinject() export class Example3 { @bindable() gridObj: any; @bindable() dataview: any; title = 'Example 3: Editors'; subTitle = ` - Grid with Inline Editors and onCellClick actions (<a href="https://github.com/ghiscoding/aurelia-slickgrid/wiki/Editors" target="_blank">Wiki link</a>). - <ul> - <li>When using "enableCellNavigation: true", clicking on a cell will automatically make it active & selected.</li> - <ul><li>If you don't want this behavior, then you should disable "enableCellNavigation"</li></ul> - <li>Inline Editors requires "enableCellNavigation: true" (not sure why though)</li> - </ul> + Grid with Inline Editors and onCellClick actions (<a href="https://github.com/ghiscoding/aurelia-slickgrid/wiki/Editors" target="_blank">Wiki link</a>). + <ul> + <li>When using "enableCellNavigation: true", clicking on a cell will automatically make it active & selected.</li> + <ul><li>If you don't want this behavior, then you should disable "enableCellNavigation"</li></ul> + <li>Inline Editors requires "enableCellNavigation: true" (not sure why though)</li> + </ul> `; + private _commandQueue = []; gridOptions: GridOption; columnDefinitions: Column[]; dataset: any[]; updatedObject: any; isAutoEdit: boolean = true; + alertWarning: any; selectedLanguage: string; constructor(private gridExtraService: GridExtraService, private i18n: I18N, private resizer: ResizerService) { @@ -51,7 +56,7 @@ export class Example3 { // use onCellClick OR grid.onClick.subscribe which you can see down below onCellClick: (args: OnEventArgs) => { console.log(args); - alert(`Editing: ${args.dataContext.title}`); + this.alertWarning = `Editing: ${args.dataContext.title}`; this.gridExtraService.highlightRow(args.row, 1500); this.gridExtraService.setSelectedRow(args.row); } @@ -65,7 +70,7 @@ export class Example3 { /* onCellClick: (args: OnEventArgs) => { console.log(args); - alert(`Deleting: ${args.dataContext.title}`); + this.alertWarning = `Deleting: ${args.dataContext.title}`; } */ }, @@ -85,7 +90,11 @@ export class Example3 { sidePadding: 15 }, editable: true, - enableCellNavigation: true + enableCellNavigation: true, + editCommandHandler: (item, column, editCommand) => { + this._commandQueue.push(editCommand); + editCommand.execute(); + } }; } @@ -138,7 +147,7 @@ export class Example3 { const column = GridExtraUtils.getColumnDefinitionAndData(args); console.log('onClick', args, column); if (column.columnDef.id === 'edit') { - alert(`Call a modal window to edit: ${column.dataContext.title}`); + this.alertWarning = `open a modal window to edit: ${column.dataContext.title}`; // highlight the row, to customize the color, you can change the SASS variable $row-highlight-background-color this.gridExtraService.highlightRow(args.row, 1500); @@ -165,4 +174,12 @@ export class Example3 { this.selectedLanguage = (this.selectedLanguage === 'en') ? 'fr' : 'en'; this.i18n.setLocale(this.selectedLanguage); } + + undo() { + const command = this._commandQueue.pop(); + if (command && Slick.GlobalEditorLock.cancelCurrentEdit()) { + command.undo(); + this.gridObj.gotoCell(command.row, command.cell, false); + } + } } diff --git a/client-cli/src/examples/slickgrid/example3.html b/client-cli/src/examples/slickgrid/example3.html index d208f70f9..5a36318dd 100644 --- a/client-cli/src/examples/slickgrid/example3.html +++ b/client-cli/src/examples/slickgrid/example3.html @@ -12,6 +12,14 @@ <h2>${title}</h2> <label class="radio-inline control-label" for="radioFalse"> <input type="radio" name="inlineRadioOptions" id="radioFalse" value.bind="isAutoEdit" click.delegate="setAutoEdit(false)"> OFF (double-click) </label> + <span> + <button class="btn btn-default btn-sm" click.delegate="undo()"> + <i class="fa fa-undo"></i> + Undo last edit + </button> + </span> + <button class="btn btn-default btn-sm" click.delegate="switchLanguage()">Switch Language</button> + <label>Locale</label>: ${selectedLanguage + '.json'} </span> </div> @@ -19,6 +27,9 @@ <h2>${title}</h2> <div class="alert alert-info" show.bind="updatedObject"> <strong>Update Object:</strong> ${updatedObject | stringify} </div> + <div class="alert alert-warning" show.bind="alertWarning"> + <strong></strong> ${alertWarning} + </div> </div> <div id="grid-container" class="col-sm-12"> diff --git a/client-cli/src/examples/slickgrid/example3.js b/client-cli/src/examples/slickgrid/example3.js index 6d83c7661..4909ad09e 100644 --- a/client-cli/src/examples/slickgrid/example3.js +++ b/client-cli/src/examples/slickgrid/example3.js @@ -7,18 +7,20 @@ export class Example3 { @bindable() dataview; title = 'Example 3: Editors'; subTitle = ` - Grid with Inline Editors and onCellClick actions (<a href="https://github.com/ghiscoding/aurelia-slickgrid/wiki/Editors" target="_blank">Wiki link</a>). - <ul> - <li>When using "enableCellNavigation: true", clicking on a cell will automatically make it active & selected. - <ul><li>If you don't want this behavior, then you should disable "enableCellNavigation"</li></ul> - <li>Inline Editors requires "enableCellNavigation: true" (not sure why though)</li> - </ul> + Grid with Inline Editors and onCellClick actions (<a href="https://github.com/ghiscoding/aurelia-slickgrid/wiki/Editors" target="_blank">Wiki link</a>). + <ul> + <li>When using "enableCellNavigation: true", clicking on a cell will automatically make it active & selected. + <ul><li>If you don't want this behavior, then you should disable "enableCellNavigation"</li></ul> + <li>Inline Editors requires "enableCellNavigation: true" (not sure why though)</li> + </ul> `; gridOptions; columnDefinitions; + commandQueue = []; dataset = []; updatedObject; isAutoEdit = true; + alertWarning; gridExtraService; resizer; @@ -34,6 +36,13 @@ export class Example3 { this.getData(); } + detached() { + // unsubscrible any Slick.Event you might have used + // a reminder again, these are SlickGrid Event, not Event Aggregator events + this.gridObj.onCellChange.unsubscribe(); + this.gridObj.onClick.unsubscribe(); + } + /* Define grid Options and Columns */ defineGrid() { this.columnDefinitions = [ @@ -45,7 +54,7 @@ export class Example3 { // use onCellClick OR grid.onClick.subscribe which you can see down below onCellClick: (args) => { console.log(args); - alert(`Editing: ${args.dataContext.title}`); + this.alertWarning = `Editing: ${args.dataContext.title}`; this.gridExtraService.highlightRow(args.row, 1500); this.gridExtraService.setSelectedRow(args.row); } @@ -54,12 +63,14 @@ export class Example3 { id: 'delete', field: 'id', formatter: Formatters.deleteIcon, minWidth: 30, - maxWidth: 30, + maxWidth: 30 // use onCellClick OR grid.onClick.subscribe which you can see down below - onCellClick: (args) => { + /* + onCellClick: (args: OnEventArgs) => { console.log(args); - alert(`Deleting: ${args.dataContext.title}`); + this.alertWarning = `Deleting: ${args.dataContext.title}`; } + */ }, { id: 'title', name: 'Title', field: 'title', sortable: true, type: FieldType.string, editor: Editors.longText, minWidth: 100 }, { id: 'duration', name: 'Duration (days)', field: 'duration', sortable: true, type: FieldType.number, editor: Editors.text, minWidth: 100 }, @@ -77,7 +88,11 @@ export class Example3 { sidePadding: 15 }, editable: true, - enableCellNavigation: true + enableCellNavigation: true, + editCommandHandler: (item, column, editCommand) => { + this._commandQueue.push(editCommand); + editCommand.execute(); + } }; } @@ -121,7 +136,7 @@ export class Example3 { const column = GridExtraUtils.getColumnDefinitionAndData(args); console.log('onClick', args, column); if (column.columnDef.id === 'edit') { - alert(`Call a modal window to edit: ${column.dataContext.title}`); + this.alertWarning = `open a modal window to edit: ${column.dataContext.title}`; // highlight the row, to customize the color, you can change the SASS variable $row-highlight-background-color this.gridExtraService.highlightRow(args.row, 1500); diff --git a/doc/github-demo/src/examples/slickgrid/example3.html b/doc/github-demo/src/examples/slickgrid/example3.html index d208f70f9..5a36318dd 100644 --- a/doc/github-demo/src/examples/slickgrid/example3.html +++ b/doc/github-demo/src/examples/slickgrid/example3.html @@ -12,6 +12,14 @@ <h2>${title}</h2> <label class="radio-inline control-label" for="radioFalse"> <input type="radio" name="inlineRadioOptions" id="radioFalse" value.bind="isAutoEdit" click.delegate="setAutoEdit(false)"> OFF (double-click) </label> + <span> + <button class="btn btn-default btn-sm" click.delegate="undo()"> + <i class="fa fa-undo"></i> + Undo last edit + </button> + </span> + <button class="btn btn-default btn-sm" click.delegate="switchLanguage()">Switch Language</button> + <label>Locale</label>: ${selectedLanguage + '.json'} </span> </div> @@ -19,6 +27,9 @@ <h2>${title}</h2> <div class="alert alert-info" show.bind="updatedObject"> <strong>Update Object:</strong> ${updatedObject | stringify} </div> + <div class="alert alert-warning" show.bind="alertWarning"> + <strong></strong> ${alertWarning} + </div> </div> <div id="grid-container" class="col-sm-12"> diff --git a/doc/github-demo/src/examples/slickgrid/example3.ts b/doc/github-demo/src/examples/slickgrid/example3.ts index 0037e62a9..fb9589469 100644 --- a/doc/github-demo/src/examples/slickgrid/example3.ts +++ b/doc/github-demo/src/examples/slickgrid/example3.ts @@ -1,29 +1,36 @@ +import { I18N } from 'aurelia-i18n'; import { autoinject, bindable } from 'aurelia-framework'; import { Column, Editors, FieldType, Formatters, GridExtraService, GridExtraUtils, GridOption, OnEventArgs, ResizerService } from 'aurelia-slickgrid'; +// using external non-typed js libraries +declare var Slick: any; + @autoinject() export class Example3 { @bindable() gridObj: any; @bindable() dataview: any; title = 'Example 3: Editors'; subTitle = ` - Grid with Inline Editors and onCellClick actions (<a href="https://github.com/ghiscoding/aurelia-slickgrid/wiki/Editors" target="_blank">Wiki link</a>). - <ul> - <li>When using "enableCellNavigation: true", clicking on a cell will automatically make it active & selected. - <ul><li>If you don't want this behavior, then you should disable "enableCellNavigation"</li></ul> - <li>Inline Editors requires "enableCellNavigation: true" (not sure why though)</li> - </ul> + Grid with Inline Editors and onCellClick actions (<a href="https://github.com/ghiscoding/aurelia-slickgrid/wiki/Editors" target="_blank">Wiki link</a>). + <ul> + <li>When using "enableCellNavigation: true", clicking on a cell will automatically make it active & selected.</li> + <ul><li>If you don't want this behavior, then you should disable "enableCellNavigation"</li></ul> + <li>Inline Editors requires "enableCellNavigation: true" (not sure why though)</li> + </ul> `; - + private _commandQueue = []; gridOptions: GridOption; columnDefinitions: Column[]; dataset: any[]; updatedObject: any; isAutoEdit: boolean = true; + alertWarning: any; + selectedLanguage: string; - constructor(private gridExtraService: GridExtraService, private resizer: ResizerService) { + constructor(private gridExtraService: GridExtraService, private i18n: I18N, private resizer: ResizerService) { // define the grid options & columns and then create the grid itself this.defineGrid(); + this.selectedLanguage = this.i18n.getLocale(); } attached() { @@ -31,6 +38,13 @@ export class Example3 { this.getData(); } + detached() { + // unsubscrible any Slick.Event you might have used + // a reminder again, these are SlickGrid Event, not Event Aggregator events + this.gridObj.onCellChange.unsubscribe(); + this.gridObj.onClick.unsubscribe(); + } + /* Define grid Options and Columns */ defineGrid() { this.columnDefinitions = [ @@ -42,7 +56,7 @@ export class Example3 { // use onCellClick OR grid.onClick.subscribe which you can see down below onCellClick: (args: OnEventArgs) => { console.log(args); - alert(`Editing: ${args.dataContext.title}`); + this.alertWarning = `Editing: ${args.dataContext.title}`; this.gridExtraService.highlightRow(args.row, 1500); this.gridExtraService.setSelectedRow(args.row); } @@ -53,15 +67,17 @@ export class Example3 { minWidth: 30, maxWidth: 30, // use onCellClick OR grid.onClick.subscribe which you can see down below + /* onCellClick: (args: OnEventArgs) => { console.log(args); - alert(`Deleting: ${args.dataContext.title}`); + this.alertWarning = `Deleting: ${args.dataContext.title}`; } + */ }, { id: 'title', name: 'Title', field: 'title', sortable: true, type: FieldType.string, editor: Editors.longText, minWidth: 100 }, { id: 'duration', name: 'Duration (days)', field: 'duration', sortable: true, type: FieldType.number, editor: Editors.text, minWidth: 100 }, { id: 'complete', name: '% Complete', field: 'percentComplete', formatter: Formatters.percentCompleteBar, type: FieldType.number, editor: Editors.integer, minWidth: 100 }, - { id: 'start', name: 'Start', field: 'start', formatter: Formatters.dateIso, sortable: true, minWidth: 100, type: FieldType.date, editor: Editors.date }, + { id: 'start', name: 'Start', field: 'start', formatter: Formatters.dateIso, sortable: true, minWidth: 100, type: FieldType.date, editor: Editors.date, params: { i18n: this.i18n } }, { id: 'finish', name: 'Finish', field: 'finish', formatter: Formatters.dateIso, sortable: true, minWidth: 100, type: FieldType.date, editor: Editors.date }, { id: 'effort-driven', name: 'Effort Driven', field: 'effortDriven', formatter: Formatters.checkmark, type: FieldType.number, editor: Editors.checkbox, minWidth: 100 } ]; @@ -74,7 +90,11 @@ export class Example3 { sidePadding: 15 }, editable: true, - enableCellNavigation: true + enableCellNavigation: true, + editCommandHandler: (item, column, editCommand) => { + this._commandQueue.push(editCommand); + editCommand.execute(); + } }; } @@ -84,7 +104,7 @@ export class Example3 { getData() { // mock a dataset - let mockedDataset = []; + const mockedDataset = []; for (let i = 0; i < 1000; i++) { const randomYear = 2000 + Math.floor(Math.random() * 10); const randomMonth = Math.floor(Math.random() * 11); @@ -106,6 +126,15 @@ export class Example3 { } gridObjChanged(grid) { + grid.onBeforeEditCell.subscribe((e, args) => { + console.log('before edit', e); + e.stopImmediatePropagation(); + }); + grid.onBeforeCellEditorDestroy.subscribe((e, args) => { + console.log('before destroy'); + e.stopPropagation(); + }); + grid.onCellChange.subscribe((e, args) => { console.log('onCellChange', args); this.updatedObject = args.item; @@ -118,7 +147,7 @@ export class Example3 { const column = GridExtraUtils.getColumnDefinitionAndData(args); console.log('onClick', args, column); if (column.columnDef.id === 'edit') { - alert(`Call a modal window to edit: ${column.dataContext.title}`); + this.alertWarning = `open a modal window to edit: ${column.dataContext.title}`; // highlight the row, to customize the color, you can change the SASS variable $row-highlight-background-color this.gridExtraService.highlightRow(args.row, 1500); @@ -140,4 +169,17 @@ export class Example3 { this.gridObj.setOptions({ autoEdit: isAutoEdit }); return true; } + + switchLanguage() { + this.selectedLanguage = (this.selectedLanguage === 'en') ? 'fr' : 'en'; + this.i18n.setLocale(this.selectedLanguage); + } + + undo() { + const command = this._commandQueue.pop(); + if (command && Slick.GlobalEditorLock.cancelCurrentEdit()) { + command.undo(); + this.gridObj.gotoCell(command.row, command.cell, false); + } + } }