Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(plugin): new Row Based Editor #1323

Merged
merged 41 commits into from
Jan 18, 2024
Merged
Show file tree
Hide file tree
Changes from 37 commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
d4372aa
feat: initial work
zewa666 Dec 29, 2023
923fd9c
Merge remote-tracking branch 'upstream/master' into plugin-row-based-…
zewa666 Dec 30, 2023
8adc3dd
feat: highlighting editmode rows; proper re-render handling
zewa666 Dec 30, 2023
c4131ab
refactor: switch to Set
zewa666 Dec 30, 2023
ca9ae9a
Merge branch 'master' into plugin-row-based-edit
zewa666 Jan 4, 2024
39e6a58
refactor: create and getColDef to align with other plugins
zewa666 Jan 4, 2024
808d6ea
feat: add sample 22
zewa666 Jan 4, 2024
c66835f
feat: demonstrate overriding css vars
zewa666 Jan 4, 2024
8535608
test: fix tests and cleanup
zewa666 Jan 4, 2024
b7e7da5
feat: use itemMetaData to set row css class
zewa666 Jan 5, 2024
a13b973
feat: undo on cancel and modified cell highlighting
zewa666 Jan 5, 2024
c826edf
docs: add demo on how to handle saving values
zewa666 Jan 5, 2024
3679da3
feat: options requirements; autoEdit force with warning; cleanup css …
zewa666 Jan 5, 2024
35e7007
feat: more css vars; editmode active override
zewa666 Jan 5, 2024
b4d451f
feat: csp compliant action buttons
zewa666 Jan 5, 2024
168c447
feat: configurable buttons/icons; custom error prefix
zewa666 Jan 5, 2024
60d2123
feat: add optional prompts for buttons
zewa666 Jan 6, 2024
4c49a17
Merge remote-tracking branch 'upstream/master' into plugin-row-based-…
zewa666 Jan 6, 2024
9b58f13
feat: onBeforeRowUpdated requiring Promise<boolean>
zewa666 Jan 6, 2024
cc9c8fd
feat: support coop with ExcelCopyBuffer plugin
zewa666 Jan 6, 2024
a0615ae
chore: fix css stylings so that rbe row class beats even selected
zewa666 Jan 7, 2024
e1ec16d
feat: re-render unsaved cells after filter/sort ...
zewa666 Jan 7, 2024
a901d4c
Merge remote-tracking branch 'upstream/master' into plugin-row-based-…
zewa666 Jan 8, 2024
f953de3
test: init method tested
zewa666 Jan 8, 2024
5fd11b2
feat: add onExtensionRegistered
zewa666 Jan 8, 2024
df31f83
test: additional test coverage
zewa666 Jan 9, 2024
49a9f00
test: finish full coverage
zewa666 Jan 10, 2024
3bc561c
feat: properly bind scope to orignal getItemMetadata
zewa666 Jan 11, 2024
2f2eb86
test: add cypress tests
zewa666 Jan 11, 2024
1f58d2c
test: additional unit test coverage
zewa666 Jan 11, 2024
6b2e522
refactor: apply review feedback
zewa666 Jan 13, 2024
23a888c
Merge remote-tracking branch 'upstream/master' into plugin-row-based-…
zewa666 Jan 13, 2024
cbe7b6a
chore: adjust types for new SlickEventData
zewa666 Jan 13, 2024
cb50609
chore: ignore last two open coverage paths; re-format to 120 line width
zewa666 Jan 13, 2024
454c318
chore: fix css
zewa666 Jan 13, 2024
4935b4c
feat: add i18n support
zewa666 Jan 15, 2024
b8b9fa8
feat: updated example22 with translations and cy test
zewa666 Jan 15, 2024
e088264
refactor: address review feedback
zewa666 Jan 17, 2024
341d887
docs: plugin documentation
zewa666 Jan 17, 2024
7abc30f
docs: mention plugin in editors.md and fix ref
zewa666 Jan 17, 2024
f5f429c
docs: address review feedback
zewa666 Jan 18, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion examples/vite-demo-vanilla-bundle/public/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -99,5 +99,7 @@
"TASK_X": "Task {{x}}",
"TITLE": "Title",
"TRUE": "True",
"X_DAY_PLURAL": "{{x}} day{{plural}}"
"X_DAY_PLURAL": "{{x}} day{{plural}}",
"RBE_BTN_UPDATE": "Update the current row",
"RBE_BTN_CANCEL": "Cancel changes of the current row"
}
4 changes: 3 additions & 1 deletion examples/vite-demo-vanilla-bundle/public/i18n/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -100,5 +100,7 @@
"TITLE": "Titre",
"TITLE.NAME": "Nom du Titre",
"TRUE": "Vrai",
"X_DAY_PLURAL": "{{x}} journée{{plural}}"
"X_DAY_PLURAL": "{{x}} journée{{plural}}",
"RBE_BTN_UPDATE": "Mettre à jour la ligne actuelle",
"RBE_BTN_CANCEL": "Annuler la ligne actuelle"
ghiscoding marked this conversation as resolved.
Show resolved Hide resolved
}
2 changes: 2 additions & 0 deletions examples/vite-demo-vanilla-bundle/src/app-routing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import Example18 from './examples/example18';
import Example19 from './examples/example19';
import Example20 from './examples/example20';
import Example21 from './examples/example21';
import Example22 from './examples/example22';

export class AppRouting {
constructor(private config: RouterConfig) {
Expand Down Expand Up @@ -49,6 +50,7 @@ export class AppRouting {
{ route: 'example19', name: 'example19', view: './examples/example19.html', viewModel: Example19, title: 'Example19', },
{ route: 'example20', name: 'example20', view: './examples/example20.html', viewModel: Example20, title: 'Example20', },
{ route: 'example21', name: 'example21', view: './examples/example21.html', viewModel: Example21, title: 'Example21', },
{ route: 'example22', name: 'example22', view: './examples/example22.html', viewModel: Example22, title: 'Example22', },
{ route: '', redirect: 'example01' },
{ route: '**', redirect: 'example01' }
];
Expand Down
3 changes: 3 additions & 0 deletions examples/vite-demo-vanilla-bundle/src/app.html
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,9 @@ <h4 class="title is-4 has-text-white">Slickgrid-Universal</h4>
<a class="navbar-item" onclick.delegate="loadRoute('example21')">
Example21 - Row Detail View
</a>
<a class="navbar-item" onclick.delegate="loadRoute('example22')">
Example22 - Row Based Editing
</a>
</div>
</div>
</div>
Expand Down
72 changes: 72 additions & 0 deletions examples/vite-demo-vanilla-bundle/src/examples/example22.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<h3 class="title is-3">
Example 22 - Row Based Editing
<span class="subtitle">(with Salesforce Theme)</span>
<div class="subtitle code-link">
<span class="is-size-6">see</span>
<a
class="is-size-5"
target="_blank"
href="https://github.com/ghiscoding/slickgrid-universal/blob/master/examples/vite-demo-vanilla-bundle/src/examples/example22.ts"
>
<span class="mdi mdi-link-variant mdi-v-align-sub"></span> code
</a>
</div>
<h6 class="title is-6 italic content">
<ul>
<li>
The Row Based Edit plugin allows you to edit either a single or multiple
specific rows at a time, while disabling the rest of the grid rows.
</li>
<li>
Editedable rows, as well as modified cells are highlighted with a
different color, which you can customize using css variables (see
<a
target="_blank"
href="https://github.com/ghiscoding/slickgrid-universal/blob/master/examples/vite-demo-vanilla-bundle/src/examples/example22.scss"
>
example22.scss </a
>)
</li>
<li>
Modifications are kept track of and if the cancel button is pressed, all
modifications are rolled back.
</li>
<li>
If the save button is pressed, a custom "onBeforeRowUpdated" callback is called, which you can use to save the data with your backend.<br />
The callback needs to return a Promise&lt;boolean&gt; and if the promise resolves to true, then the row will be updated, otherwise it will be cancelled and stays in edit mode.
You can try out the later by defining a Duration value <b>larger than 40</b>.
<br />
<small><span class="has-text-danger">NOTE:</span> You can also combine this with e.g. Batch Editing like shown <a href="#/example11">in Example 11</a></small>
</li>
<li>
This example additionally uses the ExcelCopyBuffer Plugin, which you can see also in <a href="#/example19">example 19</a>.
The example defines a rule that pastes in the first column are prohibited. In combination with the Row Based Editing Plugin though, this rule gets enhanced with the fact
that only the edited rows are allowed to be pasted into, while still respecting the original rule.
</li>
</ul>
</h6>
</h3>

<section>
<div class="row mb-1">
<button
class="button is-small"
data-test="single-multi-toggle"
onclick.delegate="toggleSingleMultiRowEdit()"
>
Toggle Single/Multi Row Edit
</button>
<button
class="button is-small"
data-test="toggle-language"
onclick.delegate="switchLanguage()"
>
Switch Language for Action column buttons
</button>
<b>Locale:</b>
<span class="text-italic" data-test="selected-locale" textcontent.bind="selectedLanguageFile">
</span>
</div>
</section>

<div class="grid1"></div>
9 changes: 9 additions & 0 deletions examples/vite-demo-vanilla-bundle/src/examples/example22.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
:root {
// turn on/off the following variables to see the difference in styling

// --slick-row-based-edit-editmode-bgcolor: rgb(82, 235, 158);
// --slick-row-based-edit-editmode-hover-bgcolor: cyan;
// --slick-row-based-edit-unsaved-cell-bgcolor: rgb(190, 114, 127);
// --slick-row-based-edit-editmode-active-bgcolor: rgb(82, 235, 158);
// --slick-row-based-edit-editmode-active-hover-bgcolor: cyan;
}
250 changes: 250 additions & 0 deletions examples/vite-demo-vanilla-bundle/src/examples/example22.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,250 @@
import {
type Column,
FieldType,
Formatters,
type GridOption,
Editors,
} from '@slickgrid-universal/common';
import {
Slicker,
SlickVanillaGridBundle,
} from '@slickgrid-universal/vanilla-bundle';
import { ExampleGridOptions } from './example-grid-options';

import './example22.scss';
import { TranslateService } from '../translate.service';

const NB_ITEMS = 20;

export default class Example22 {
gridOptions!: GridOption;
columnDefinitions!: Column[];
dataset!: any[];
sgb!: SlickVanillaGridBundle;
translateService: TranslateService;
selectedLanguage: string;
selectedLanguageFile: string;

constructor() {
this.translateService = (<any>window).TranslateService;
this.selectedLanguage = this.translateService.getCurrentLanguage();
this.selectedLanguageFile = `${this.selectedLanguage}.json`;
}

attached() {
this.defineGrids();

// mock some data (different in each dataset)
this.dataset = this.mockData(NB_ITEMS);

this.sgb = new Slicker.GridBundle(
document.querySelector(`.grid1`) as HTMLDivElement,
this.columnDefinitions,
{ ...ExampleGridOptions, ...this.gridOptions },
this.dataset
);
}

dispose() {
this.sgb?.dispose();
}

/* Define grid Options and Columns */
defineGrids() {
this.columnDefinitions = [
{
id: 'title',
name: 'Title',
field: 'title',
sortable: true,
minWidth: 100,
filterable: true,
editor: { model: Editors.text },
},
{
id: 'duration',
name: 'Duration (days)',
field: 'duration',
sortable: true,
minWidth: 100,
filterable: true,
type: FieldType.number,
editor: { model: Editors.text },
},
{
id: '%',
name: '% Complete',
field: 'percentComplete',
sortable: true,
minWidth: 100,
filterable: true,
type: FieldType.number,
editor: { model: Editors.text },
},
{
id: 'start',
name: 'Start',
field: 'start',
formatter: Formatters.dateIso,
exportWithFormatter: true,
filterable: true,
editor: { model: Editors.text },
},
{
id: 'finish',
name: 'Finish',
field: 'finish',
formatter: Formatters.dateIso,
exportWithFormatter: true,
filterable: true,
editor: { model: Editors.text },
},
{
id: 'effort-driven',
name: 'Effort Driven',
field: 'effortDriven',
sortable: true,
minWidth: 100,
filterable: true,
editor: { model: Editors.text },
},
];

this.gridOptions = {
zewa666 marked this conversation as resolved.
Show resolved Hide resolved
enableAutoResize: false,
gridHeight: 225,
gridWidth: 800,
rowHeight: 33,
enableExcelCopyBuffer: true,
excelCopyBufferOptions: {
onBeforePasteCell: (_e, args) => {
// for the sake of the demo, do not allow to paste into the first column title
// this will be overriden by the row based edit plugin to additionally only work if the row is in editmode
return args.cell > 0;
},
},
// NOTE: this will be automatically turned to true by the Row Based Edit Plugin.
// A console warning will be shown if you omit this flag
autoEdit: false,
editable: true,
enableCellNavigation: true,
enableRowBasedEdit: true,
rowBasedEditOptions: {
allowMultipleRows: false,
onBeforeRowUpdated(args) {
const { effortDriven, percentComplete, finish, start, duration, title } = args.dataContext;

if (duration > 40) {
alert('Sorry, 40 is the maximum allowed duration.');
return Promise.resolve(false);
}

return fakeFetch('your-backend-api/endpoint', {
method: 'POST',
body: JSON.stringify({ effortDriven, percentComplete, finish, start, duration, title }),
headers: {
'Content-type': 'application/json; charset=UTF-8'
}
}).catch(err => {
console.error(err);
return false;
})
.then(response => {
if (response === false) {
return false;
}
if (typeof response === 'object') {
return response!.json();
}
})
.then(json => {
alert(json.message);
return true;
});
},
actionColumnConfig: { // override the defaults of the action column
width: 100,
minWidth: 100,
maxWidth: 100,
},
actionButtons: {
editButtonClassName: 'button-style padding-1px mr-2',
iconEditButtonClassName: 'mdi mdi-pencil',
editButtonTitle: 'Edit row',

cancelButtonClassName: 'button-style padding-1px',
cancelButtonTitle: 'Cancel row',
cancelButtonTitleKey: 'RBE_BTN_CANCEL',
iconCancelButtonClassName: 'mdi mdi-undo color-danger',
cancelButtonPrompt: 'Are you sure you want to cancel your changes?',

updateButtonClassName: 'button-style padding-1px mr-2',
updateButtonTitle: 'Update row',
updateButtonTitleKey: 'RBE_BTN_UPDATE',
iconUpdateButtonClassName: 'mdi mdi-check color-success',
updateButtonPrompt: 'Save changes?',

deleteButtonClassName: 'button-style padding-1px',
deleteButtonTitle: 'Delete row',
iconDeleteButtonClassName: 'mdi mdi-trash-can color-danger',
deleteButtonPrompt: 'Are you sure you want to delete this row?',
},
},
enableTranslate: true,
translater: this.translateService
};
}

mockData(count: number) {
// mock a dataset
const mockDataset: any[] = [];
for (let i = 0; i < count; i++) {
const randomYear = 2000 + Math.floor(Math.random() * 10);
const randomMonth = Math.floor(Math.random() * 11);
const randomDay = Math.floor(Math.random() * 29);
const randomPercent = Math.round(Math.random() * 100);

mockDataset[i] = {
id: i,
title: 'Task ' + i,
duration: Math.round(Math.random() * 40) + '',
percentComplete: randomPercent,
start: new Date(randomYear, randomMonth + 1, randomDay),
finish: new Date(randomYear + 1, randomMonth + 1, randomDay),
effortDriven: i % 5 === 0,
};
}

return mockDataset;
}

toggleSingleMultiRowEdit() {
this.sgb.gridOptions = {
...this.sgb.gridOptions,
...{
rowBasedEditOptions: {
...this.sgb.gridOptions.rowBasedEditOptions,
...{ allowMultipleRows: !this.sgb.gridOptions.rowBasedEditOptions?.allowMultipleRows },
},
},
};

this.gridOptions = this.sgb.gridOptions;
}

async switchLanguage() {
const nextLanguage = (this.selectedLanguage === 'en') ? 'fr' : 'en';
await this.translateService.use(nextLanguage);
this.selectedLanguage = nextLanguage;
this.selectedLanguageFile = `${this.selectedLanguage}.json`;
}
}

function fakeFetch(_input: string | URL | Request, _init?: RequestInit | undefined): Promise<Response> {
return new Promise((resolve) => {
setTimeout(() => {
resolve(new Response(JSON.stringify({ status: 200, message: 'success' })));
// reduces the delay for automated Cypress tests
}, (window as any).Cypress ? 10 : 500);
});
}
4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -95,5 +95,9 @@
"funding": {
"type": "ko_fi",
"url": "https://ko-fi.com/ghiscoding"
},
"prettier": {
"singleQuote": true,
"printWidth": 120
}
}
1 change: 1 addition & 0 deletions packages/common/src/enums/extensionName.enum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export enum ExtensionName {
gridMenu = 'gridMenu',
headerButton = 'headerButton',
headerMenu = 'headerMenu',
rowBasedEdit = 'rowBasedEdit',
rowDetailView = 'rowDetailView',
rowMoveManager = 'rowMoveManager',
rowSelection = 'rowSelection',
Expand Down
Loading