Skip to content

Commit

Permalink
feat(tree): add Tree Collapse Grid State/Preset
Browse files Browse the repository at this point in the history
- add ways to reapply Tree Collapse previous state whenever we want
- add `initiallyCollapsed` flag to Tree Data Option to allow loading the grid as collapsed or not from the start (defaults to false)
- add Grid Preset that can be used with Grid State
- add `__hasChildren` flag to every dataset item that will always be included & also refactor Tree Formatters to use this new flag instead of querying next item
- add `__collapsed` flag on all items which are parents, in other words don't add this flag to child item that have no children themselves
  • Loading branch information
ghiscoding committed Jun 11, 2021
1 parent 3702ed3 commit 998b01a
Show file tree
Hide file tree
Showing 31 changed files with 1,082 additions and 537 deletions.
15 changes: 11 additions & 4 deletions examples/webpack-demo-vanilla-bundle/src/examples/example05.html
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,15 @@ <h6 class="title is-6 italic">
<span class="icon mdi mdi-filter-outline"></span>
<span>Dynamically Change Filter (% complete &lt; 40)</span>
</button>
<button onclick.delegate="collapseAllWithoutEvent()" data-test="collapse-all-noevent-btn" class="button is-small">
<span class="icon mdi mdi-arrow-collapse"></span>
<span>Collapse All (without triggering event)</span>
</button>
<button onclick.delegate="reapplyToggledItems()" data-test="reapply-toggled-items-btn" class="button is-small"
disabled.bind="hasNoExpandCollapseChanged">
<span class="icon mdi mdi-history"></span>
<span>Reapply Previous Toggled Items</span>
</button>
</div>

<div class="row" style="margin-bottom: 4px;">
Expand All @@ -42,10 +51,8 @@ <h6 class="title is-6 italic">
<span class="icon mdi mdi-arrow-expand"></span>
<span>Expand All</span>
</button>
<button onclick.delegate="reapplyCollapsedItems()" data-test="reapply-collapsing-btn" class="button is-small"
disabled.bind="hasNoExpandCollapseChanged">
<span class="icon mdi mdi-history"></span>
<span>Re-Apply Previous Expanded/Collapsed Items</span>
<button onclick.delegate="logTreeDataToggledItems()" class="button is-small">
<span>Log Tree Toggled Items</span>
</button>
<button onclick.delegate="logFlatStructure()" class="button is-small">
<span>Log Flat Structure</span>
Expand Down
97 changes: 66 additions & 31 deletions examples/webpack-demo-vanilla-bundle/src/examples/example05.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ import {
Filters,
Formatters,
GridOption,
GridStateChange,
GridStateType,
TreeToggledItem,
TreeToggleStateChange,
} from '@slickgrid-universal/common';
import { ExcelExportService } from '@slickgrid-universal/excel-export';
import { Slicker, SlickVanillaGridBundle } from '@slickgrid-universal/vanilla-bundle';
Expand All @@ -23,7 +27,7 @@ export class Example5 {
loadingClass = '';
isLargeDataset = false;
hasNoExpandCollapseChanged = true;
collapsedTreeItems: { parentId: number | string; isCollapsed: boolean; }[] = [];
treeToggleItems: TreeToggledItem[] = [];

constructor() {
this._bindingEventService = new BindingEventService();
Expand All @@ -36,7 +40,6 @@ export class Example5 {

this.sgb = new Slicker.GridBundle(gridContainerElm, this.columnDefinitions, { ...ExampleGridOptions, ...this.gridOptions });
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));
Expand All @@ -45,15 +48,13 @@ export class Example5 {
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, 'ontreetoggleallrequested', this.hideSpinner.bind(this));
// @ts-ignore
this._bindingEventService.bind(gridContainerElm, 'onTreeToggleStageChanged', (e: CustomEvent) => {
this.hasNoExpandCollapseChanged = false;
const treeToggleExecution = e.detail;
this.collapsedTreeItems = treeToggleExecution.treeChanges;
console.log('onTreeCollapseChanged', treeToggleExecution);
});

// keep toggled items, note that we could also get these changes via the `onGridStateChanged`
this._bindingEventService.bind(gridContainerElm, 'ontreefulltogglestart', this.showSpinner.bind(this));
this._bindingEventService.bind(gridContainerElm, 'ontreefulltoggleend', this.handleOnTreeFullToggleEnd.bind(this));
this._bindingEventService.bind(gridContainerElm, 'ontreeitemtoggled', this.handleOnTreeItemToggled.bind(this));
// or use the Grid State change event
// this._bindingEventService.bind(gridContainerElm, 'ongridstatechanged', this.handleOnGridStateChanged.bind(this));
}

dispose() {
Expand Down Expand Up @@ -133,6 +134,7 @@ export class Example5 {
// 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,
initiallyCollapsed: true,

// 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)
Expand All @@ -152,8 +154,6 @@ export class Example5 {
multiColumnSort: false, // multi-column sorting is not supported with Tree Data, so you need to disable it
presets: {
filters: [{ columnId: 'percentComplete', searchTerms: [25], operator: '>=' }],
// @ts-ignore
treeCollapsedParents: [{ parentId: 12, isCollapsed: true }],
},
// if you're dealing with lots of data, it is recommended to use the filter debounce
filterTypingDebounce: 250,
Expand Down Expand Up @@ -195,6 +195,10 @@ export class Example5 {
this.sgb.treeDataService.toggleTreeDataCollapse(true);
}

collapseAllWithoutEvent() {
this.sgb.treeDataService.toggleTreeDataCollapse(true, false);
}

expandAll() {
this.sgb.treeDataService.toggleTreeDataCollapse(false);
}
Expand All @@ -203,21 +207,6 @@ export class Example5 {
this.sgb.filterService.updateFilters([{ columnId: 'percentComplete', operator: '<', searchTerms: [40] }]);
}

reapplyCollapsedItems() {
// const collapsedItems = Object.keys(this.collapsedTreeItems);
const collapsedItems = this.collapsedTreeItems.filter(item => item.isCollapsed);
if (Array.isArray(collapsedItems)) {
this.sgb.dataView.beginUpdate(true);
for (const collapsedItem of collapsedItems) {
if (collapsedItem) {
this.sgb.dataView.updateItem(collapsedItem.parentId, { ...this.sgb.dataView.getItemById(collapsedItem.parentId), __collapsed: collapsedItem.isCollapsed });
}
}
this.sgb.dataView.endUpdate();
this.sgb.dataView.refresh();
}
}

logHierarchicalStructure() {
console.log('hierarchical array', this.sgb.treeDataService.datasetHierarchical);
}
Expand All @@ -240,11 +229,25 @@ export class Example5 {
const item = (data[i] = {});
let parentId;

// for implementing filtering/sorting, don't go over indent of 2
if (Math.random() > 0.8 && i > 0 && indent < 3) {
/*
for demo & E2E testing purposes, let's make "Task 0" empty and then "Task 1" a parent that contains at least "Task 2" and "Task 3" which the latter will also contain "Task 4" (as shown below)
also for all other rows don't go over indent tree level depth of 2
Task 0
Task 1
Task 2
Task 3
Task 4
...
*/
if (i === 1 || i === 0) {
indent = 0;
parents.pop();
} if (i === 3) {
indent = 1;
} else if (i === 2 || i === 4 || (Math.random() > 0.8 && i > 0 && indent < 3 && i - 1 !== 0 && i - 1 !== 2)) { // also make sure Task 0, 2 remains empty
indent++;
parents.push(i - 1);
} else if (Math.random() < 0.3 && indent > 0) {
} else if ((Math.random() < 0.3 && indent > 0)) {
indent--;
parents.pop();
}
Expand All @@ -269,4 +272,36 @@ export class Example5 {
}
return data;
}

handleOnTreeFullToggleEnd(e: CustomEvent<TreeToggleStateChange>) {
const treeToggleExecution = e.detail;
console.log('Tree Data changes', treeToggleExecution);
this.hideSpinner();
}

/** Whenever a parent is being toggled, we'll keep a reference of all of these changes so that we can reapply them whenever we want */
handleOnTreeItemToggled(e: CustomEvent<TreeToggleStateChange>) {
this.hasNoExpandCollapseChanged = false;
const treeToggleExecution = e.detail;
this.treeToggleItems = treeToggleExecution.toggledItems;
console.log('Tree Data changes', treeToggleExecution);
}

handleOnGridStateChanged(e: CustomEvent<GridStateChange>) {
this.hasNoExpandCollapseChanged = false;
const gridStateChange = e.detail;

if (gridStateChange.change.type === GridStateType.treeData) {
console.log('Tree Data gridStateChange', gridStateChange.gridState.treeData);
this.treeToggleItems = gridStateChange.gridState.treeData.toggledItems;
}
}

logTreeDataToggledItems() {
console.log(this.sgb.treeDataService.getToggledItems());
}

reapplyToggledItems() {
this.sgb.treeDataService.applyToggledItemStateChanges(this.treeToggleItems);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,11 @@ export class Example6 {
// }
},
showCustomFooter: true,

// we can also preset collapsed items via Grid Presets (parentId: 4 => is the "pdf" folder)
presets: {
treeData: { toggledItems: [{ itemId: 4, isCollapsed: true }] },
},
};
}

Expand Down
7 changes: 7 additions & 0 deletions packages/common/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,13 @@ export class Constants {
TEXT_X_OF_Y_MASS_SELECTED: '{{x}} of {{y}} selected',
};

static readonly treeDataProperties = {
CHILDREN_PROP: 'children',
COLLAPSED_PROP: '__collapsed',
HAS_CHILDREN_PROP: '__hasChildren',
TREE_LEVEL_PROP: '__treeLevel',
PARENT_PROP: '__parentId',
};
static readonly VALIDATION_REQUIRED_FIELD = 'Field is required';
static readonly VALIDATION_EDITOR_VALID_NUMBER = 'Please enter a valid number';
static readonly VALIDATION_EDITOR_VALID_INTEGER = 'Please enter a valid integer number';
Expand Down
1 change: 1 addition & 0 deletions packages/common/src/enums/gridStateType.enum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ export enum GridStateType {
pinning = 'pinning',
rowSelection = 'rowSelection',
sorter = 'sorter',
treeData = 'treeData',
}
1 change: 1 addition & 0 deletions packages/common/src/enums/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,4 @@ export * from './slickPluginList.enum';
export * from './sortDirection.enum';
export * from './sortDirectionString.type';
export * from './sortDirectionNumber.enum';
export * from './toggleStateChangeType';
15 changes: 15 additions & 0 deletions packages/common/src/enums/toggleStateChangeType.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
export type ToggleStateChangeTypeString = 'toggle-collapse' | 'toggle-expand' | 'full-collapse' | 'full-expand';

export enum ToggleStateChangeType {
/** full tree collapse */
toggleCollapse = 'toggle-collapse',

/** full tree expand */
fullExpand = 'full-expand',

/** item toggle collapse */
fullCollapse = 'full-collapse',

/** item toggle expand */
toggleExpand = 'toggle-expand',
}
Loading

0 comments on commit 998b01a

Please sign in to comment.