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(tree): improve Tree Data speed considerably, fixes #307 #336

Merged
merged 23 commits into from
May 12, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
5487798
feat(tree): improve Tree Data speed considerably
ghiscoding May 8, 2021
8adcaa8
refactor: fix failing unit tests and test with dataview sort
ghiscoding May 9, 2021
b69d11e
tests: fix failing unit tests and code
ghiscoding May 9, 2021
1b19028
fix: add item to flat and/or tree should both work
ghiscoding May 9, 2021
697072a
Merge branch 'master' into feat/tree-data-optimization
ghiscoding May 10, 2021
982c01f
refactor: change row count & fix title formatter when adding new row
ghiscoding May 10, 2021
10d2f9c
tests: add missing unit tests
ghiscoding May 10, 2021
c9ab87f
chore(styling): add vertical alignment to collapsing icons
ghiscoding May 10, 2021
6902f94
chore: add extra salesforce icon, which has a different viewport size
ghiscoding May 10, 2021
a76a024
refactor: remove the multi-column sort error since it does work with it
ghiscoding May 10, 2021
9da9662
feat: add optional child value prefix to Tree Formatter
ghiscoding May 10, 2021
5ad40b9
refactor: rename a few methods from hierarchical to shorter "tree" word
ghiscoding May 10, 2021
0885d79
refactor: remove all console time benchmarking
ghiscoding May 10, 2021
bbcd4fd
refactor: tweak css styling with tree level value prefix
ghiscoding May 10, 2021
360c62c
feat: add few pubsub events to help with big dataset
ghiscoding May 11, 2021
bb36d1a
fix: return all onBeforeX events in delayed promise to fix spinner
ghiscoding May 11, 2021
bb3d488
chore(deps): update few npm packages
ghiscoding May 11, 2021
8bf32ca
feat: add `titleFormatter` to Tree Data
ghiscoding May 11, 2021
7252e76
refactor: drop export-utilities and move them into formatterUtilities
ghiscoding May 11, 2021
d816171
refactor: change export utilities to have similar signature as Formatter
ghiscoding May 11, 2021
1079cc2
refactor: put back error message to not support multi-column sorting
ghiscoding May 11, 2021
1abdb47
tests: add Cypress E2E and fix issue found while testing example 6
ghiscoding May 11, 2021
a67f2f3
chore: rebuild slick vanilla bundle zip file
ghiscoding May 11, 2021
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: 2 additions & 2 deletions examples/webpack-demo-vanilla-bundle/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,12 @@
"mini-css-extract-plugin": "^1.6.0",
"rxjs": "^7.0.0",
"sass": "^1.32.12",
"sass-loader": "^11.0.1",
"sass-loader": "^11.1.0",
"style-loader": "^2.0.0",
"ts-loader": "^9.1.2",
"ts-node": "^9.1.1",
"url-loader": "^4.1.1",
"webpack": "^5.36.2",
"webpack": "^5.37.0",
"webpack-cli": "^4.7.0",
"webpack-dev-server": "^3.11.2"
}
Expand Down
57 changes: 35 additions & 22 deletions examples/webpack-demo-vanilla-bundle/src/examples/example05.html
Original file line number Diff line number Diff line change
Expand Up @@ -15,28 +15,41 @@ <h6 class="title is-6 italic">
</h6>
<div class="columns">
<div class="column is-narrow">
<button onclick.delegate="addNewRow()" class="button is-small is-info">
<span class="icon mdi mdi-plus"></span>
<span>Add New Item (in 1st group)</span>
</button>
<button onclick.delegate="collapseAll()" data-test="collapse-all" class="button is-small">
<span class="icon mdi mdi-arrow-collapse"></span>
<span>Collapse All</span>
</button>
<button onclick.delegate="expandAll()" data-test="expand-all" class="button is-small">
<span class="icon mdi mdi-arrow-expand"></span>
<span>Expand All</span>
</button>
<button onclick.delegate="logFlatStructure()" class="button is-small">
<span>Log Flat Structure</span>
</button>
<button onclick.delegate="logHierarchicalStructure()" class="button is-small">
<span>Log Hierarchical Structure</span>
</button>
<button onclick.delegate="dynamicallyChangeFilter()" class="button is-small">
<span class="icon mdi mdi-filter-outline"></span>
<span>Dynamically Change Filter (% complete &lt; 40)</span>
</button>
<div class="row" style="margin-bottom: 4px;">
<button class="button is-small" data-test="add-500-rows-btn" onclick.delegate="loadData(500)">
500 rows
</button>
<button class="button is-small" data-test="add-50k-rows-btn" onclick.delegate="loadData(25000)">
25k rows
</button>
<button onclick.delegate="dynamicallyChangeFilter()" class="button is-small"
data-test="change-filter-dynamically">
<span class="icon mdi mdi-filter-outline"></span>
<span>Dynamically Change Filter (% complete &lt; 40)</span>
</button>
</div>

<div class="row" style="margin-bottom: 4px;">
<button onclick.delegate="addNewRow()" class="button is-small is-info" data-test="add-item-btn">
<span class="icon mdi mdi-plus"></span>
<span>Add New Item (in 1st group)</span>
</button>
<button onclick.delegate="collapseAll()" data-test="collapse-all-btn" class="button is-small">
<span class="icon mdi mdi-arrow-collapse"></span>
<span>Collapse All</span>
</button>
<button onclick.delegate="expandAll()" data-test="expand-all-btn" class="button is-small">
<span class="icon mdi mdi-arrow-expand"></span>
<span>Expand All</span>
</button>
<button onclick.delegate="logFlatStructure()" class="button is-small">
<span>Log Flat Structure</span>
</button>
<button onclick.delegate="logHierarchicalStructure()" class="button is-small">
<span>Log Hierarchical Structure</span>
</button>
<span class.bind="loadingClass"></span>
</div>
</div>
</div>

Expand Down
84 changes: 64 additions & 20 deletions examples/webpack-demo-vanilla-bundle/src/examples/example05.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {
BindingEventService,
Column,
FieldType,
Filters,
Expand All @@ -11,29 +12,56 @@ import { Slicker, SlickVanillaGridBundle } from '@slickgrid-universal/vanilla-bu
import { ExampleGridOptions } from './example-grid-options';
import './example05.scss';

const NB_ITEMS = 200;
const NB_ITEMS = 500;

export class Example5 {
private _bindingEventService: BindingEventService;
columnDefinitions: Column[];
gridOptions: GridOption;
dataset: any[];
sgb: SlickVanillaGridBundle;
durationOrderByCount = false;
loadingClass = '';
isLargeDataset = false;

constructor() {
this._bindingEventService = new BindingEventService();
}

attached() {
this.initializeGrid();
this.dataset = [];
const gridContainerElm = document.querySelector<HTMLDivElement>('.grid5');

this.sgb = new Slicker.GridBundle(gridContainerElm, this.columnDefinitions, { ...ExampleGridOptions, ...this.gridOptions });
this.dataset = this.mockDataset();
this.sgb.dataset = this.dataset;
this.dataset = this.loadData(NB_ITEMS);
// this.sgb.dataset = this.dataset;

// with large dataset you maybe want to show spinner before/after these events: sorting/filtering/collapsing/expanding
this._bindingEventService.bind(gridContainerElm, 'onbeforefilterchange', this.showSpinner.bind(this));
this._bindingEventService.bind(gridContainerElm, 'onfilterchanged', this.hideSpinner.bind(this));
this._bindingEventService.bind(gridContainerElm, 'onbeforefilterclear', this.showSpinner.bind(this));
this._bindingEventService.bind(gridContainerElm, 'onfiltercleared', this.hideSpinner.bind(this));
this._bindingEventService.bind(gridContainerElm, 'onbeforesortchange', this.showSpinner.bind(this));
this._bindingEventService.bind(gridContainerElm, 'onsortchanged', this.hideSpinner.bind(this));
this._bindingEventService.bind(gridContainerElm, 'onbeforetoggletreecollapse', this.showSpinner.bind(this));
this._bindingEventService.bind(gridContainerElm, 'ontoggletreecollapsed', this.hideSpinner.bind(this));
}

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

hideSpinner() {
setTimeout(() => this.loadingClass = '', 200); // delay the hide spinner a bit to avoid show/hide too quickly
}

showSpinner() {
if (this.isLargeDataset) {
this.loadingClass = 'mdi mdi-load mdi-spin-1s mdi-24px color-alt-success';
}
}

initializeGrid() {
this.columnDefinitions = [
{
Expand All @@ -48,7 +76,7 @@ export class Example5 {
id: 'percentComplete', name: '% Complete', field: 'percentComplete',
minWidth: 120, maxWidth: 200, exportWithFormatter: false,
sortable: true, filterable: true, filter: { model: Filters.compoundSlider, operator: '>=' },
formatter: Formatters.percentCompleteBar, type: FieldType.number,
formatter: Formatters.percentCompleteBarWithText, type: FieldType.number,
},
{
id: 'start', name: 'Start', field: 'start', minWidth: 60,
Expand Down Expand Up @@ -93,19 +121,32 @@ export class Example5 {
enableTreeData: true, // you must enable this flag for the filtering & sorting to work as expected
treeDataOptions: {
columnId: 'title',
// levelPropName: 'indent', // this is optional, you can define the tree level property name that will be used for the sorting/indentation, internally it will use "__treeLevel"
parentPropName: 'parentId',
// this is optional, you can define the tree level property name that will be used for the sorting/indentation, internally it will use "__treeLevel"
levelPropName: 'treeLevel',
indentMarginLeft: 15,

// you can optionally sort by a different column and/or sort direction
// this is the recommend approach, unless you are 100% that your original array is already sorted (in most cases it's not)
initialSort: {
columnId: 'title',
direction: 'ASC'
}
},
// we can also add a custom Formatter just for the title text portion
titleFormatter: (_row, _cell, value, _def, dataContext) => {
let prefix = '';
if (dataContext.treeLevel > 0) {
prefix = `<span class="mdi mdi-subdirectory-arrow-right mdi-v-align-sub color-se-secondary"></span>`;
}
return `${prefix}<span class="bold">${value}</span><span style="font-size:11px; margin-left: 15px;">(parentId: ${dataContext.parentId})</span>`;
},
},
multiColumnSort: false, // multi-column sorting is not supported with Tree Data, so you need to disable it
presets: {
filters: [{ columnId: 'percentComplete', searchTerms: [25], operator: '>=' }]
}
},
// if you're dealing with lots of data, it is recommended to use the filter debounce
filterTypingDebounce: 250,
};
}

Expand All @@ -116,7 +157,7 @@ export class Example5 {
addNewRow() {
const newId = this.sgb.dataset.length;
const parentPropName = 'parentId';
const treeLevelPropName = '__treeLevel'; // if undefined in your options, the default prop name is "__treeLevel"
const treeLevelPropName = 'treeLevel'; // if undefined in your options, the default prop name is "__treeLevel"
const newTreeLevel = 1;
// find first parent object and add the new item as a child
const childItemFound = this.sgb.dataset.find((item) => item[treeLevelPropName] === newTreeLevel);
Expand Down Expand Up @@ -160,17 +201,18 @@ export class Example5 {
console.log('flat array', this.sgb.treeDataService.dataset);
}

mockDataset() {
loadData(rowCount: number) {
this.isLargeDataset = rowCount > 5000; // we'll show a spinner when it's large, else don't show show since it should be fast enough
let indent = 0;
const parents = [];
const data = [];

// prepare the data
for (let i = 0; i < NB_ITEMS; i++) {
for (let i = 0; i < rowCount; i++) {
const randomYear = 2000 + Math.floor(Math.random() * 10);
const randomMonth = Math.floor(Math.random() * 11);
const randomDay = Math.floor((Math.random() * 29));
const d = (data[i] = {});
const item = (data[i] = {});
let parentId;

// for implementing filtering/sorting, don't go over indent of 2
Expand All @@ -188,15 +230,17 @@ export class Example5 {
parentId = null;
}

d['id'] = i;
d['parentId'] = parentId;
// d['title'] = `Task ${i} - [P]: ${parentId}`;
d['title'] = `Task ${i}`;
d['duration'] = '5 days';
d['percentComplete'] = Math.round(Math.random() * 100);
d['start'] = new Date(randomYear, randomMonth, randomDay);
d['finish'] = new Date(randomYear, (randomMonth + 1), randomDay);
d['effortDriven'] = (i % 5 === 0);
item['id'] = i;
item['parentId'] = parentId;
item['title'] = `Task ${i}`;
item['duration'] = '5 days';
item['percentComplete'] = Math.round(Math.random() * 100);
item['start'] = new Date(randomYear, randomMonth, randomDay);
item['finish'] = new Date(randomYear, (randomMonth + 1), randomDay);
item['effortDriven'] = (i % 5 === 0);
}
if (this.sgb) {
this.sgb.dataset = data;
}
return data;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,15 @@ <h6 class="title is-6 italic">
</h6>
<div class="columns">
<div class="column is-narrow">
<button onclick.delegate="addNewFile()" class="button is-small is-info">
<button onclick.delegate="addNewFile()" class="button is-small is-info" data-test="add-item-btn">
<span class="icon mdi mdi-plus"></span>
<span>Add New Pop Song</span>
</button>
<button onclick.delegate="collapseAll()" class="button is-small">
<button onclick.delegate="collapseAll()" class="button is-small" data-test="collapse-all-btn">
<span class="icon mdi mdi-arrow-collapse"></span>
<span>Collapse All</span>
</button>
<button onclick.delegate="expandAll()" class="button is-small">
<button onclick.delegate="expandAll()" class="button is-small" data-test="expand-all-btn">
<span class="icon mdi mdi-arrow-expand"></span>
<span>Expand All</span>
</button>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {
GridOption,
FieldType,
Filters,
findItemInHierarchicalStructure,
findItemInTreeStructure,
Formatter,
Formatters,
SlickDataView,
Expand Down Expand Up @@ -146,12 +146,12 @@ export class Example6 {
const newId = this.sgb.dataView.getItemCount() + 100;

// find first parent object and add the new item as a child
const popItem = findItemInHierarchicalStructure(this.datasetHierarchical, x => x.file === 'pop', 'files');
const popItem = findItemInTreeStructure(this.datasetHierarchical, x => x.file === 'pop', 'files');

if (popItem && Array.isArray(popItem.files)) {
popItem.files.push({
id: newId,
file: `pop${Math.round(Math.random() * 1000)}.mp3`,
file: `pop-${newId}.mp3`,
dateModified: new Date(),
size: Math.round(Math.random() * 100),
});
Expand Down
2 changes: 2 additions & 0 deletions examples/webpack-demo-vanilla-bundle/src/examples/icons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ export class Icons {
'.mdi.mdi-cash-remove',
'.mdi.mdi-certificate',
'.mdi.mdi-certificate-outline',
'.mdi.mdi-change-record-type',
'.mdi.mdi-check',
'.mdi.mdi-check-all',
'.mdi.mdi-check-bold',
Expand Down Expand Up @@ -180,6 +181,7 @@ export class Icons {
'.mdi.mdi-sort-descending',
'.mdi.mdi-sort-variant-remove',
'.mdi.mdi-square-edit-outline',
'.mdi.mdi-subdirectory-arrow-right',
'.mdi.mdi-swap-horizontal',
'.mdi.mdi-swap-vertical',
'.mdi.mdi-sync',
Expand Down
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,9 @@
"devDependencies": {
"@types/jest": "^26.0.23",
"@types/node": "^15.0.2",
"@typescript-eslint/eslint-plugin": "^4.22.1",
"@typescript-eslint/parser": "^4.22.1",
"cypress": "^7.2.0",
"@typescript-eslint/eslint-plugin": "^4.23.0",
"@typescript-eslint/parser": "^4.23.0",
"cypress": "^7.3.0",
"eslint": "^7.26.0",
"eslint-plugin-import": "^2.22.1",
"eslint-plugin-prefer-arrow": "^1.2.3",
Expand Down
5 changes: 3 additions & 2 deletions packages/common/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,8 @@
"jquery-ui-dist": "^1.12.1",
"moment-mini": "^2.24.0",
"multiple-select-modified": "^1.3.12",
"slickgrid": "^2.4.35"
"slickgrid": "^2.4.35",
"un-flatten-tree": "^2.0.12"
},
"devDependencies": {
"@types/dompurify": "^2.2.2",
Expand All @@ -82,7 +83,7 @@
"mini-css-extract-plugin": "^1.6.0",
"nodemon": "^2.0.7",
"npm-run-all": "^4.1.5",
"postcss": "^8.2.14",
"postcss": "^8.2.15",
"postcss-cli": "^8.3.1",
"rimraf": "^3.0.2",
"sass": "^1.32.12"
Expand Down
4 changes: 2 additions & 2 deletions packages/common/src/extensions/contextMenuExtension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
} from '../interfaces/index';
import { DelimiterType, FileType, } from '../enums/index';
import { ExtensionUtility } from './extensionUtility';
import { exportWithFormatterWhenDefined } from '../services/export-utilities';
import { exportWithFormatterWhenDefined } from '../formatters/formatterUtilities';
import { SharedService } from '../services/shared.service';
import { getDescendantProperty, getTranslationPrefix } from '../services/utilities';
import { ExcelExportService, TextExportService, TranslaterService, TreeDataService } from '../services/index';
Expand Down Expand Up @@ -413,7 +413,7 @@ export class ContextMenuExtension implements Extension {
const dataContext = args && args.dataContext;
const grid = this.sharedService && this.sharedService.slickGrid;
const exportOptions = gridOptions && (gridOptions.excelExportOptions || { ...gridOptions.exportOptions, ...gridOptions.textExportOptions });
let textToCopy = exportWithFormatterWhenDefined(row, cell, dataContext, columnDef, grid, exportOptions);
let textToCopy = exportWithFormatterWhenDefined(row, cell, columnDef, dataContext, grid, exportOptions);

if (typeof columnDef.queryFieldNameGetterFn === 'function') {
textToCopy = this.getCellValueFromQueryFieldGetter(columnDef, dataContext);
Expand Down
Loading