diff --git a/examples/webpack-demo-vanilla-bundle/src/examples/example05.ts b/examples/webpack-demo-vanilla-bundle/src/examples/example05.ts index 0c1838289..8a7d5f3f2 100644 --- a/examples/webpack-demo-vanilla-bundle/src/examples/example05.ts +++ b/examples/webpack-demo-vanilla-bundle/src/examples/example05.ts @@ -92,7 +92,7 @@ export class Example5 { enableTreeData: true, // you must enable this flag for the filtering & sorting to work as expected treeDataOptions: { columnId: 'title', - // levelPropName: 'treeLevel', + levelPropName: 'indent', // this is optional, except that in our case we just need to define it because we are adding new item in the demo parentPropName: 'parentId', // you can optionally sort by a different column and/or sort direction @@ -113,13 +113,13 @@ export class Example5 { * After adding the item, it will sort by parent/child recursively */ addNewRow() { - const newId = this.dataset.length; + const newId = this.sgb.dataset.length; const parentPropName = 'parentId'; const treeLevelPropName = 'indent'; const newTreeLevel = 1; // find first parent object and add the new item as a child - const childItemFound = this.dataset.find((item) => item[treeLevelPropName] === newTreeLevel); + const childItemFound = this.sgb.dataset.find((item) => item[treeLevelPropName] === newTreeLevel); const parentItemFound = this.sgb.dataView.getItemByIdx(childItemFound[parentPropName]); const newItem = { @@ -128,25 +128,22 @@ export class Example5 { parentId: parentItemFound.id, title: `Task ${newId}`, duration: '1 day', - percentComplete: 0, + percentComplete: 99, start: new Date(), finish: new Date(), effortDriven: false }; this.sgb.dataView.addItem(newItem); + + // force a refresh of the data by getting the updated list from the DataView & override our local copy as well this.dataset = this.sgb.dataView.getItems(); this.sgb.dataset = this.dataset; - // force a resort - const titleColumn = this.columnDefinitions.find((col) => col.id === 'title'); - this.sgb.sortService.onLocalSortChanged(this.sgb.slickGrid, [{ columnId: 'title', sortCol: titleColumn, sortAsc: true }]); - - // update dataset and re-render (invalidate) the grid - this.sgb.slickGrid.invalidate(); - // scroll to the new row - const rowIndex = this.sgb.dataView.getIdxById(newItem.id); - this.sgb.slickGrid.scrollRowIntoView(rowIndex, false); + setTimeout(() => { + const rowIndex = this.sgb.dataView.getIdxById(newItem.id); + this.sgb.slickGrid.scrollRowIntoView(rowIndex, false); + }, 10); } collapseAll() { diff --git a/packages/common/src/services/__tests__/treeData.service.spec.ts b/packages/common/src/services/__tests__/treeData.service.spec.ts index a86a9dc00..7a936857e 100644 --- a/packages/common/src/services/__tests__/treeData.service.spec.ts +++ b/packages/common/src/services/__tests__/treeData.service.spec.ts @@ -1,4 +1,3 @@ - import { Column, SlickDataView, GridOption, SlickEventHandler, SlickGrid, SlickNamespace } from '../../interfaces/index'; import { SharedService } from '../shared.service'; import { SortService } from '../sort.service'; @@ -49,13 +48,13 @@ describe('TreeData Service', () => { const sharedService = new SharedService(); beforeEach(() => { + gridOptionsMock.multiColumnSort = false; service = new TreeDataService(sharedService, sortServiceStub); slickgridEventHandler = service.eventHandler; jest.spyOn(gridStub, 'getData').mockReturnValue(dataViewStub); }); afterEach(() => { - gridOptionsMock.multiColumnSort = false; jest.clearAllMocks(); service.dispose(); }); diff --git a/packages/vanilla-bundle/src/components/__tests__/slick-vanilla-grid.spec.ts b/packages/vanilla-bundle/src/components/__tests__/slick-vanilla-grid.spec.ts index 6b93400a7..6fabcd203 100644 --- a/packages/vanilla-bundle/src/components/__tests__/slick-vanilla-grid.spec.ts +++ b/packages/vanilla-bundle/src/components/__tests__/slick-vanilla-grid.spec.ts @@ -121,6 +121,7 @@ const filterServiceStub = { bindLocalOnSort: jest.fn(), bindBackendOnSort: jest.fn(), populateColumnFilterSearchTermPresets: jest.fn(), + refreshTreeDataFilters: jest.fn(), getColumnFilters: jest.fn(), } as unknown as FilterService; @@ -2050,12 +2051,14 @@ describe('Slick-Vanilla-Grid-Bundle Component instantiated via Constructor', () const mockHierarchical = [{ id: 0, file: 'documents', files: [{ id: 1, file: 'vacation.txt' }] }]; const hierarchicalSpy = jest.spyOn(SharedService.prototype, 'hierarchicalDataset', 'set'); jest.spyOn(treeDataServiceStub, 'initializeHierarchicalDataset').mockReturnValue({ hierarchical: mockHierarchical, flat: mockFlatDataset }); + const refreshTreeSpy = jest.spyOn(filterServiceStub, 'refreshTreeDataFilters'); component.gridOptions = { enableTreeData: true, treeDataOptions: { columnId: 'file', parentPropName: 'parentId', childrenPropName: 'files' } } as unknown as GridOption; component.initialization(divContainer, slickEventHandler); component.dataset = mockFlatDataset; expect(hierarchicalSpy).toHaveBeenCalledWith(mockHierarchical); + expect(refreshTreeSpy).not.toHaveBeenCalled(); }); it('should change hierarchical dataset and expect processTreeDataInitialSort being called with other methods', () => { @@ -2074,6 +2077,22 @@ describe('Slick-Vanilla-Grid-Bundle Component instantiated via Constructor', () expect(processSpy).toHaveBeenCalled(); expect(setItemsSpy).toHaveBeenCalledWith([], 'id'); }); + + it('should expect "refreshTreeDataFilters" method to be called when our flat dataset was already set and it just got changed a 2nd time', () => { + const mockFlatDataset = [{ id: 0, file: 'documents' }, { id: 1, file: 'vacation.txt', parentId: 0 }]; + const mockHierarchical = [{ id: 0, file: 'documents', files: [{ id: 1, file: 'vacation.txt' }] }]; + const hierarchicalSpy = jest.spyOn(SharedService.prototype, 'hierarchicalDataset', 'set'); + jest.spyOn(treeDataServiceStub, 'initializeHierarchicalDataset').mockReturnValue({ hierarchical: mockHierarchical, flat: mockFlatDataset }); + const refreshTreeSpy = jest.spyOn(filterServiceStub, 'refreshTreeDataFilters'); + + component.dataset = [{ id: 0, file: 'documents' }]; + component.gridOptions = { enableTreeData: true, treeDataOptions: { columnId: 'file', parentPropName: 'parentId', childrenPropName: 'files' } } as unknown as GridOption; + component.initialization(divContainer, slickEventHandler); + component.dataset = mockFlatDataset; + + expect(hierarchicalSpy).toHaveBeenCalledWith(mockHierarchical); + expect(refreshTreeSpy).toHaveBeenCalled(); + }); }); }); }); diff --git a/packages/vanilla-bundle/src/components/slick-vanilla-grid-bundle.ts b/packages/vanilla-bundle/src/components/slick-vanilla-grid-bundle.ts index 41b889131..dc3e6406c 100644 --- a/packages/vanilla-bundle/src/components/slick-vanilla-grid-bundle.ts +++ b/packages/vanilla-bundle/src/components/slick-vanilla-grid-bundle.ts @@ -87,6 +87,7 @@ import { autoAddEditorFormatterToColumnsWithEditor } from './slick-vanilla-utili declare const Slick: SlickNamespace; export class SlickVanillaGridBundle { + private _currentDatasetLength = 0; private _eventPubSubService!: EventPubSubService; private _columnDefinitions!: Column[]; private _gridOptions!: GridOption; @@ -167,15 +168,20 @@ export class SlickVanillaGridBundle { return this.dataView?.getItems() ?? []; } set dataset(newDataset: any[]) { - const prevDatasetLn = this.dataView.getLength(); + const prevDatasetLn = this._currentDatasetLength; const isDeepCopyDataOnPageLoadEnabled = !!(this._gridOptions?.enableDeepCopyDatasetOnPageLoad); let data = isDeepCopyDataOnPageLoadEnabled ? $.extend(true, [], newDataset) : newDataset; // when Tree Data is enabled and we don't yet have the hierarchical dataset filled, we can force a convert & sort of the array - if (this._gridOptions.enableTreeData && Array.isArray(newDataset) && newDataset.length > 0) { + if (this._gridOptions.enableTreeData && Array.isArray(newDataset) && (newDataset.length > 0 || newDataset.length !== prevDatasetLn)) { const sortedDatasetResult = this.treeDataService.initializeHierarchicalDataset(data, this._columnDefinitions); this.sharedService.hierarchicalDataset = sortedDatasetResult.hierarchical; data = sortedDatasetResult.flat; + + // if we add/remove item(s) from the dataset, we need to also refresh our tree data filters + if (newDataset.length > 0 && prevDatasetLn > 0 && newDataset.length !== prevDatasetLn) { + this.filterService.refreshTreeDataFilters(); + } } this.refreshGridData(data || []); @@ -185,6 +191,7 @@ export class SlickVanillaGridBundle { if (this.gridOptions.autoFitColumnsOnFirstLoad && prevDatasetLn === 0) { this.slickGrid.autosizeColumns(); } + this._currentDatasetLength = newDataset.length; } get datasetHierarchical(): any[] | undefined { @@ -408,6 +415,7 @@ export class SlickVanillaGridBundle { this.initialization(this._gridContainerElm, eventHandler); if (!hierarchicalDataset && !this.gridOptions.backendServiceApi) { this.dataset = dataset || []; + this._currentDatasetLength = this.dataset.length; } } @@ -1131,13 +1139,13 @@ export class SlickVanillaGridBundle { /** When data changes in the DataView, we'll refresh the metrics and/or display a warning if the dataset is empty */ private handleOnItemCountChanged(currentPageRowItemCount: number, totalItemCount: number) { + this._currentDatasetLength = totalItemCount; this.metrics = { startTime: new Date(), endTime: new Date(), itemCount: currentPageRowItemCount, totalItemCount }; - // if custom footer is enabled, then we'll update its metrics if (this.slickFooter) { this.slickFooter.metrics = this.metrics;