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

fix(tree): using initiallyCollapsed change internal toggled state #380

Merged
merged 3 commits into from
Jun 14, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
43 changes: 42 additions & 1 deletion packages/common/src/services/__tests__/treeData.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -399,20 +399,61 @@ describe('TreeData Service', () => {
});

describe('applyToggledItemStateChanges method', () => {
it('should execute the method', () => {
it('should execute the method and expect a full toggle or items', () => {
const dataGetItemsSpy = jest.spyOn(dataViewStub, 'getItems').mockReturnValue(mockFlatDataset);
jest.spyOn(dataViewStub, 'getItemById').mockReturnValue(mockFlatDataset[3]);
jest.spyOn(SharedService.prototype, 'hierarchicalDataset', 'get').mockReturnValue(mockHierarchical);
const beginUpdateSpy = jest.spyOn(dataViewStub, 'beginUpdate');
const endUpdateSpy = jest.spyOn(dataViewStub, 'endUpdate');
const updateItemSpy = jest.spyOn(dataViewStub, 'updateItem');
const pubSubSpy = jest.spyOn(pubSubServiceStub, 'publish');

service.init(gridStub);
service.applyToggledItemStateChanges([{ itemId: 4, isCollapsed: true }]);

expect(dataGetItemsSpy).toHaveBeenCalled();
expect(beginUpdateSpy).toHaveBeenCalled();
expect(updateItemSpy).toHaveBeenNthCalledWith(1, 4, { __collapsed: true, __hasChildren: true, id: 4, file: 'MP3', size: 3.4 });
expect(pubSubSpy).not.toHaveBeenCalledWith(`onTreeItemToggled`);
expect(endUpdateSpy).toHaveBeenCalled();
});

it('should execute the method but without calling "getItems" to skip doing a full toggle of items', () => {
const dataGetItemsSpy = jest.spyOn(dataViewStub, 'getItems').mockReturnValue(mockFlatDataset);
jest.spyOn(dataViewStub, 'getItemById').mockReturnValue(mockFlatDataset[3]);
jest.spyOn(SharedService.prototype, 'hierarchicalDataset', 'get').mockReturnValue(mockHierarchical);
const beginUpdateSpy = jest.spyOn(dataViewStub, 'beginUpdate');
const endUpdateSpy = jest.spyOn(dataViewStub, 'endUpdate');
const updateItemSpy = jest.spyOn(dataViewStub, 'updateItem');
const pubSubSpy = jest.spyOn(pubSubServiceStub, 'publish');

service.init(gridStub);
service.applyToggledItemStateChanges([{ itemId: 4, isCollapsed: true }], 'full-collapse', false, true);

expect(dataGetItemsSpy).not.toHaveBeenCalled();
expect(beginUpdateSpy).toHaveBeenCalled();
expect(updateItemSpy).toHaveBeenNthCalledWith(1, 4, { __collapsed: true, __hasChildren: true, id: 4, file: 'MP3', size: 3.4 });
expect(pubSubSpy).toHaveBeenCalledWith(`onTreeItemToggled`, { fromItemId: 4, previousFullToggleType: 'full-collapse', toggledItems: [{ itemId: 4, isCollapsed: true }], type: 'toggle-collapse' });
expect(endUpdateSpy).toHaveBeenCalled();
});

it('should execute the method and also trigger an event when specified', () => {
const dataGetItemsSpy = jest.spyOn(dataViewStub, 'getItems').mockReturnValue(mockFlatDataset);
jest.spyOn(dataViewStub, 'getItemById').mockReturnValue(mockFlatDataset[3]);
jest.spyOn(SharedService.prototype, 'hierarchicalDataset', 'get').mockReturnValue(mockHierarchical);
const beginUpdateSpy = jest.spyOn(dataViewStub, 'beginUpdate');
const endUpdateSpy = jest.spyOn(dataViewStub, 'endUpdate');
const updateItemSpy = jest.spyOn(dataViewStub, 'updateItem');
const pubSubSpy = jest.spyOn(pubSubServiceStub, 'publish');

service.init(gridStub);
service.applyToggledItemStateChanges([{ itemId: 4, isCollapsed: true }], 'full-collapse', true, true);
service.applyToggledItemStateChanges([{ itemId: 4, isCollapsed: true }], 'full-collapse', true, true); // calling twice shouldn't change toggledItems array

expect(dataGetItemsSpy).toHaveBeenCalled();
expect(beginUpdateSpy).toHaveBeenCalled();
expect(updateItemSpy).toHaveBeenNthCalledWith(1, 4, { __collapsed: true, __hasChildren: true, id: 4, file: 'MP3', size: 3.4 });
expect(pubSubSpy).toHaveBeenCalledWith(`onTreeItemToggled`, { fromItemId: 4, previousFullToggleType: 'full-collapse', toggledItems: [{ itemId: 4, isCollapsed: true }], type: 'toggle-collapse' });
expect(endUpdateSpy).toHaveBeenCalled();
});
});
Expand Down
53 changes: 41 additions & 12 deletions packages/common/src/services/treeData.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,7 @@ declare const Slick: SlickNamespace;

export class TreeDataService {
private _isLastFullToggleCollapsed = false;
private _lastToggleStateChange: Omit<TreeToggleStateChange, 'fromItemId'> = {
type: this.gridOptions?.treeDataOptions?.initiallyCollapsed ? 'full-collapse' : 'full-expand',
previousFullToggleType: this.gridOptions?.treeDataOptions?.initiallyCollapsed ? 'full-collapse' : 'full-expand',
toggledItems: null
};
private _lastToggleStateChange!: Omit<TreeToggleStateChange, 'fromItemId'>;
private _currentToggledItems: TreeToggledItem[] = [];
private _grid!: SlickGrid;
private _eventHandler: SlickEventHandler;
Expand Down Expand Up @@ -78,6 +74,11 @@ export class TreeDataService {
this._grid = grid;
this._isLastFullToggleCollapsed = this.gridOptions?.treeDataOptions?.initiallyCollapsed ?? false;
this._currentToggledItems = this.gridOptions.presets?.treeData?.toggledItems ?? [];
this._lastToggleStateChange = {
type: this._isLastFullToggleCollapsed ? 'full-collapse' : 'full-expand',
previousFullToggleType: this._isLastFullToggleCollapsed ? 'full-collapse' : 'full-expand',
toggledItems: this._currentToggledItems
};

// there's a few limitations with Tree Data, we'll just throw error when that happens
if (this.gridOptions?.enableTreeData) {
Expand Down Expand Up @@ -109,8 +110,11 @@ export class TreeDataService {
* Apply different tree toggle state changes by providing an array of parentIds that are designated as collapsed (or not).
* User will have to provide an array of `parentId` and `isCollapsed` boolean and the code will only apply the ones that are tagged as collapsed, everything else will be expanded
* @param {Array<TreeToggledItem>} treeToggledItems - array of parentId which are tagged as changed
* @param {ToggleStateChangeType} previousFullToggleType - optionally provide the previous full toggle type ('full-expand' or 'full-collapse')
* @param {Boolean} shouldPreProcessFullToggle - should we pre-process a full toggle on all items? defaults to True
* @param {Boolean} shouldTriggerEvent - should we trigger a toggled item event? defaults to False
*/
applyToggledItemStateChanges(treeToggledItems: TreeToggledItem[], previousFullToggleType?: Exclude<ToggleStateChangeType, 'toggle'> | Exclude<ToggleStateChangeTypeString, 'toggle'>) {
applyToggledItemStateChanges(treeToggledItems: TreeToggledItem[], previousFullToggleType?: Exclude<ToggleStateChangeType, 'toggle'> | Exclude<ToggleStateChangeTypeString, 'toggle'>, shouldPreProcessFullToggle = true, shouldTriggerEvent = false) {
if (Array.isArray(treeToggledItems)) {
const collapsedPropName = this.getTreeDataOptionPropName('collapsedPropName');
const hasChildrenPropName = this.getTreeDataOptionPropName('hasChildrenPropName');
Expand All @@ -122,17 +126,42 @@ export class TreeDataService {
// we first need to put back the previous full toggle state (whether it was a full collapse or expand) by collapsing/expanding everything depending on the last toggled that was called `isLastFullToggleCollapsed`
const previousFullToggle = previousFullToggleType ?? this._lastToggleStateChange.previousFullToggleType;
const shouldCollapseAll = previousFullToggle === 'full-collapse';
(this.dataView.getItems() || []).forEach((item: any) => {
// collapse/expand the item but only when it's a parent item with children
if (item[hasChildrenPropName]) {
item[collapsedPropName] = shouldCollapseAll;
}
});

// when full toggle type is provided, we also need to update our internal reference of our current toggle state
if (previousFullToggleType) {
this._lastToggleStateChange.previousFullToggleType = previousFullToggleType;
}

// typically (optionally and defaults to true) if we want to reapply some toggled items we probably want to be in the full toggled state as it was at the start
// collapse/expand from the last full toggle state, all the items which are parent items with children
if (shouldPreProcessFullToggle) {
(this.dataView.getItems() || []).forEach((item: any) => {
if (item[hasChildrenPropName]) {
item[collapsedPropName] = shouldCollapseAll;
}
});
}

// then we reapply only the ones that changed (provided as argument to the function)
for (const collapsedItem of treeToggledItems) {
const item = this.dataView.getItemById(collapsedItem.itemId);
this.updateToggledItem(item, collapsedItem.isCollapsed);

if (shouldTriggerEvent) {
const parentFoundIdx = this._currentToggledItems.findIndex(treeChange => treeChange.itemId === collapsedItem.itemId);
if (parentFoundIdx >= 0) {
this._currentToggledItems[parentFoundIdx].isCollapsed = collapsedItem.isCollapsed;
} else {
this._currentToggledItems.push({ itemId: collapsedItem.itemId, isCollapsed: collapsedItem.isCollapsed });
}

this.pubSubService.publish('onTreeItemToggled', {
...this._lastToggleStateChange,
fromItemId: collapsedItem.itemId,
toggledItems: this._currentToggledItems,
type: collapsedItem.isCollapsed ? ToggleStateChangeType.toggleCollapse : ToggleStateChangeType.toggleExpand
} as TreeToggleStateChange);
}
}

// close the update transaction & call a refresh which will trigger a re-render with filters applied (including expand/collapse)
Expand Down
Binary file not shown.