diff --git a/src/app/examples/grid-tree-data-parent-child.component.html b/src/app/examples/grid-tree-data-parent-child.component.html index d0e40b120..9c334a3a8 100644 --- a/src/app/examples/grid-tree-data-parent-child.component.html +++ b/src/app/examples/grid-tree-data-parent-child.component.html @@ -35,6 +35,21 @@

Reapply Previous Toggled Items + + +
@@ -75,6 +90,17 @@

[gridOptions]="gridOptions" [dataset]="dataset" (onGridStateChanged)="handleOnGridStateChanged($event.detail)" - (onAngularGridCreated)="angularGridReady($event.detail)"> + (onAngularGridCreated)="angularGridReady($event.detail)" + (onBeforeFilterChange)="showSpinner()" + (onFilterChanged)="hideSpinner()" + (onBeforeFilterClear)="showSpinner()" + (onFilterCleared)="hideSpinner()" + (onBeforeSortChange)="showSpinner()" + (onSortChanged)="hideSpinner()" + (onBeforeToggleTreeCollapse)="showSpinner()" + (onToggle-tree-collapsed)="hideSpinner()" + (onTree-full-toggle-start)="showSpinner()" + (onTree-full-toggle-end)="handleOnTreeFullToggleEnd($event.detail)" + (onTree-item-toggled)="handleOnTreeItemToggled($event.detail)"> \ No newline at end of file diff --git a/src/app/examples/grid-tree-data-parent-child.component.ts b/src/app/examples/grid-tree-data-parent-child.component.ts index e1baeafdf..779988177 100644 --- a/src/app/examples/grid-tree-data-parent-child.component.ts +++ b/src/app/examples/grid-tree-data-parent-child.component.ts @@ -11,6 +11,7 @@ import { GridStateChange, GridStateType, TreeToggledItem, + TreeToggleStateChange, } from './../modules/angular-slickgrid'; const NB_ITEMS = 500; @@ -72,7 +73,7 @@ export class GridTreeDataParentChildComponent implements OnInit { 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, @@ -137,7 +138,7 @@ export class GridTreeDataParentChildComponent implements OnInit { multiColumnSort: false, // multi-column sorting is not supported with Tree Data, so you need to disable it presets: { filters: [{ columnId: 'percentComplete', searchTerms: [25], operator: '>=' }], - treeData: { toggledItems: [{ itemId: 1, isCollapsed: false }] }, + // treeData: { toggledItems: [{ itemId: 1, isCollapsed: false }] }, }, // change header/cell row height for material design theme @@ -255,7 +256,6 @@ export class GridTreeDataParentChildComponent implements OnInit { let indent = 0; const parents = []; const data = []; - // prepare the data for (let i = 0; i < rowCount; i++) { const randomYear = 2000 + Math.floor(Math.random() * 10); @@ -286,7 +286,6 @@ export class GridTreeDataParentChildComponent implements OnInit { indent--; parents.pop(); } - if (parents.length > 0) { parentId = parents[parents.length - 1]; } else { @@ -306,13 +305,24 @@ export class GridTreeDataParentChildComponent implements OnInit { return data; } - /** Dispatched event of a Grid State Changed event */ + handleOnTreeFullToggleEnd(treeToggleExecution: TreeToggleStateChange) { + 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(treeToggleExecution: TreeToggleStateChange) { + this.hasNoExpandCollapseChanged = false; + this.treeToggleItems = treeToggleExecution.toggledItems as TreeToggledItem[]; + console.log('Tree Data changes', treeToggleExecution); + } + handleOnGridStateChanged(gridStateChange: GridStateChange) { this.hasNoExpandCollapseChanged = false; - if (gridStateChange.change!.type === GridStateType.treeData) { - console.log('Tree Data gridStateChange', gridStateChange.gridState!.treeData); - this.treeToggleItems = gridStateChange.gridState!.treeData!.toggledItems as TreeToggledItem[]; + if (gridStateChange?.change?.type === GridStateType.treeData) { + console.log('Tree Data gridStateChange', gridStateChange?.gridState?.treeData); + this.treeToggleItems = gridStateChange?.gridState?.treeData?.toggledItems as TreeToggledItem[]; } } @@ -320,6 +330,20 @@ export class GridTreeDataParentChildComponent implements OnInit { console.log(this.angularGrid.treeDataService.getToggledItems()); } + dynamicallyToggledFirstParent() { + const parentPropName = 'parentId'; + const treeLevelPropName = 'treeLevel'; // if undefined in your options, the default prop name is "__treeLevel" + const newTreeLevel = 1; + + // find first parent object and toggle it + const childItemFound = this.dataset.find((item) => item[treeLevelPropName] === newTreeLevel); + const parentItemFound = this.angularGrid.dataView.getItemByIdx(childItemFound[parentPropName]); + + if (childItemFound && parentItemFound) { + this.angularGrid.treeDataService.dynamicallyToggleItemState([{ itemId: parentItemFound.id, isCollapsed: !parentItemFound.__collapsed }]); + } + } + reapplyToggledItems() { this.angularGrid.treeDataService.applyToggledItemStateChanges(this.treeToggleItems); } diff --git a/test/cypress/integration/example28.spec.js b/test/cypress/integration/example28.spec.js index f988f80ce..9cfd1727a 100644 --- a/test/cypress/integration/example28.spec.js +++ b/test/cypress/integration/example28.spec.js @@ -4,7 +4,7 @@ function removeExtraSpaces(text) { return `${text}`.replace(/\s+/g, ' ').trim(); } -describe('Example 28 - Tree Data (from a Hierarchical Dataset)', { retries: 1 }, () => { +describe('Example 28 - Tree Data (from a flat dataset with parentId references)', { retries: 1 }, () => { const GRID_ROW_HEIGHT = 40; const titles = ['Title', 'Duration', '% Complete', 'Start', 'Finish', 'Effort Driven']; @@ -20,6 +20,16 @@ describe('Example 28 - Tree Data (from a Hierarchical Dataset)', { retries: 1 }, .each(($child, index) => expect($child.text()).to.eq(titles[index])); }); + it('should expect all rows to be collapsed on first page load', () => { + cy.get('#grid28') + .find('.slick-group-toggle.expanded') + .should('have.length', 0); + + cy.get('#grid28') + .find('.slick-group-toggle.collapsed') + .should(($rows) => expect($rows).to.have.length.greaterThan(0)); + }); + it('should have a Grid Preset Filter on 3rd column "% Complete" and expect all rows to be filtered as well', () => { cy.get('.input-group-text.rangeOutput_percentComplete') .contains('25'); @@ -29,90 +39,18 @@ describe('Example 28 - Tree Data (from a Hierarchical Dataset)', { retries: 1 }, .contains('>='); }); - it('should have data filtered, with "% Complete" >=25, and not show the full item count in the footer', () => { - cy.get('.search-filter.filter-percentComplete .operator .form-control') - .should('have.value', '>='); - - cy.get('.rangeInput_percentComplete') - .invoke('val') - .then(text => expect(text).to.eq('25')); - - cy.get('.search-filter .input-group-text') - .should($span => expect($span.text()).to.eq('25')); - - cy.get('.right-footer') - .should($span => { - const text = removeExtraSpaces($span.text()); // remove all white spaces - expect(text).not.to.eq('500 of 500 items'); - }); - }); - - it('should open the Grid Menu "Clear all Filters" command', () => { - cy.get('#grid28') - .find('button.slick-gridmenu-button') - .trigger('click') - .click(); - - cy.get(`.slick-gridmenu:visible`) - .find('.slick-gridmenu-item') - .first() - .find('span') - .contains('Clear all Filters') - .click(); - }); - - it('should expect the "Task 1" to be expanded on page load by the grid preset toggled items array', () => { - cy.get(`.slick-group-toggle.expanded`).should('have.length', 1); - - cy.get(`#grid28 [style="top:${GRID_ROW_HEIGHT * 0}px"] > .slick-cell:nth(0).slick-tree-level-0`).should('contain', 'Task 0'); - cy.get(`#grid28 [style="top:${GRID_ROW_HEIGHT * 1}px"] > .slick-cell:nth(0).slick-tree-level-0`).should('contain', 'Task 1'); - cy.get(`#grid28 [style="top:${GRID_ROW_HEIGHT * 2}px"] > .slick-cell:nth(0).slick-tree-level-1`).should('contain', 'Task 2'); - cy.get(`#grid28 [style="top:${GRID_ROW_HEIGHT * 3}px"] > .slick-cell:nth(0).slick-tree-level-1`).should('contain', 'Task 3'); - cy.get(`#grid28 [style="top:${GRID_ROW_HEIGHT * 3}px"] > .slick-cell:nth(0)`).should('not.contain', 'Task 4'); - }); - - it('should be able to expand the "Task 3"', () => { - /* - now we should find this structure - Task 0 - Task 1 - Task 2 - Task 3 - Task 4 - ... - */ - cy.get(`#grid28 [style="top:${GRID_ROW_HEIGHT * 3}px"] > .slick-cell:nth(0) .slick-group-toggle.collapsed`) - .click(); - - cy.get(`#grid28 .slick-group-toggle.expanded`).should('have.length', 2); - cy.get(`#grid28 [style="top:${GRID_ROW_HEIGHT * 0}px"] > .slick-cell:nth(0).slick-tree-level-0`).should('contain', 'Task 0'); - cy.get(`#grid28 [style="top:${GRID_ROW_HEIGHT * 1}px"] > .slick-cell:nth(0).slick-tree-level-0`).should('contain', 'Task 1'); - cy.get(`#grid28 [style="top:${GRID_ROW_HEIGHT * 1}px"] > .slick-cell:nth(0) .slick-group-toggle.expanded`); - cy.get(`#grid28 [style="top:${GRID_ROW_HEIGHT * 2}px"] > .slick-cell:nth(0).slick-tree-level-1`).should('contain', 'Task 2'); - cy.get(`#grid28 [style="top:${GRID_ROW_HEIGHT * 3}px"] > .slick-cell:nth(0).slick-tree-level-1`).should('contain', 'Task 3'); - cy.get(`#grid28 [style="top:${GRID_ROW_HEIGHT * 3}px"] > .slick-cell:nth(0) .slick-group-toggle.expanded`); - cy.get(`#grid28 [style="top:${GRID_ROW_HEIGHT * 4}px"] > .slick-cell:nth(0).slick-tree-level-2`).should('contain', 'Task 4'); - }); - - it('should be able to click on the "Collapse All (wihout event)" button', () => { - cy.get('[data-test=collapse-all-noevent-btn]') - .contains('Collapse All (without triggering event)') - .click(); - }); - - it('should be able to click on the "Reapply Previous Toggled Items" button and expect "Task 1" and "Task 3" parents to become open (via Grid State change) while every other parents remains collapsed', () => { - cy.get('[data-test=reapply-toggled-items-btn]') - .contains('Reapply Previous Toggled Items') + it('should expand all rows from "Expand All" button', () => { + cy.get('[data-test=expand-all-btn]') + .contains('Expand All') .click(); - cy.get(`#grid28 [style="top:${GRID_ROW_HEIGHT * 1}px"] > .slick-cell:nth(0)`).should('contain', 'Task 1'); - cy.get(`#grid28 [style="top:${GRID_ROW_HEIGHT * 1}px"] > .slick-cell:nth(0) .slick-group-toggle.expanded`).should('have.length', 1); - - cy.get(`#grid28 [style="top:${GRID_ROW_HEIGHT * 2}px"] > .slick-cell:nth(0)`).should('contain', 'Task 2'); - cy.get(`#grid28 [style="top:${GRID_ROW_HEIGHT * 3}px"] > .slick-cell:nth(0)`).should('contain', 'Task 3'); - cy.get(`#grid28 [style="top:${GRID_ROW_HEIGHT * 3}px"] > .slick-cell:nth(0) .slick-group-toggle.expanded`).should('have.length', 1); + cy.get('#grid28') + .find('.slick-group-toggle.collapsed') + .should('have.length', 0); - cy.get(`#grid28 .slick-group-toggle.expanded`).should('have.length', 2); + cy.get('#grid28') + .find('.slick-group-toggle.expanded') + .should(($rows) => expect($rows).to.have.length.greaterThan(0)); }); it('should collapsed all rows from "Collapse All" button', () => { @@ -129,20 +67,6 @@ describe('Example 28 - Tree Data (from a Hierarchical Dataset)', { retries: 1 }, .should(($rows) => expect($rows).to.have.length.greaterThan(0)); }); - it('should expand all rows from "Expand All" button', () => { - cy.get('[data-test=expand-all-btn]') - .contains('Expand All') - .click(); - - cy.get('#grid28') - .find('.slick-group-toggle.collapsed') - .should('have.length', 0); - - cy.get('#grid28') - .find('.slick-group-toggle.expanded') - .should(($rows) => expect($rows).to.have.length.greaterThan(0)); - }); - it('should collapsed all rows from "Collapse All" context menu', () => { cy.get('#grid28') .contains('5 days'); @@ -166,7 +90,7 @@ describe('Example 28 - Tree Data (from a Hierarchical Dataset)', { retries: 1 }, .should(($rows) => expect($rows).to.have.length.greaterThan(0)); }); - it('should expand all rows from "Expand All" context menu', () => { + it('should collapsed all rows from "Expand All" context menu', () => { cy.get('#grid28') .contains('5 days'); @@ -189,6 +113,38 @@ describe('Example 28 - Tree Data (from a Hierarchical Dataset)', { retries: 1 }, .should(($rows) => expect($rows).to.have.length.greaterThan(0)); }); + it('should have data filtered, with "% Complete" >=25, and not show the full item count in the footer', () => { + cy.get('.search-filter.filter-percentComplete .operator .form-control') + .should('have.value', '>='); + + cy.get('.rangeInput_percentComplete') + .invoke('val') + .then(text => expect(text).to.eq('25')); + + cy.get('.search-filter .input-group-text') + .should($span => expect($span.text()).to.eq('25')); + + cy.get('.right-footer') + .should($span => { + const text = removeExtraSpaces($span.text()); // remove all white spaces + expect(text).not.to.eq('500 of 500 items'); + }); + }); + + it('should open the Grid Menu "Clear all Filters" command', () => { + cy.get('#grid28') + .find('button.slick-gridmenu-button') + .trigger('click') + .click(); + + cy.get(`.slick-gridmenu:visible`) + .find('.slick-gridmenu-item') + .first() + .find('span') + .contains('Clear all Filters') + .click(); + }); + it('should no longer have filters and it should show the full item count in the footer', () => { cy.get('.search-filter.filter-percentComplete .operator .form-control') .should('have.value', ''); @@ -263,4 +219,70 @@ describe('Example 28 - Tree Data (from a Hierarchical Dataset)', { retries: 1 }, .get('.slick-cell') .contains(/^((?!Task 500).)*$/); }); -}); + + it('should open the Grid Menu "Clear all Filters" command', () => { + cy.get('#grid28') + .find('button.slick-gridmenu-button') + .trigger('click') + .click(); + + cy.get(`.slick-gridmenu:visible`) + .find('.slick-gridmenu-item') + .first() + .find('span') + .contains('Clear all Filters') + .click(); + + cy.get('.slick-viewport-top.slick-viewport-left') + .scrollTo('top'); + }); + + it('should be able to open "Task 1" and "Task 3" parents', () => { + /* + we should find this structure + Task 0 + Task 1 + Task 2 + Task 3 + Task 4 + ... + */ + cy.get(`[style="top:${GRID_ROW_HEIGHT * 1}px"] > .slick-cell:nth(0)`).should('contain', 'Task 1'); + cy.get(`[style="top:${GRID_ROW_HEIGHT * 1}px"] > .slick-cell:nth(0) .slick-group-toggle.collapsed`).should('have.length', 1); + cy.get(`[style="top:${GRID_ROW_HEIGHT * 1}px"] > .slick-cell:nth(0) .slick-group-toggle.collapsed`).click({ force: true }); + + cy.get(`[style="top:${GRID_ROW_HEIGHT * 2}px"] > .slick-cell:nth(0)`).should('contain', 'Task 2'); + cy.get(`[style="top:${GRID_ROW_HEIGHT * 3}px"] > .slick-cell:nth(0)`).should('contain', 'Task 3'); + cy.get(`[style="top:${GRID_ROW_HEIGHT * 3}px"] > .slick-cell:nth(0) .slick-group-toggle.collapsed`).should('have.length', 1); + cy.get(`[style="top:${GRID_ROW_HEIGHT * 3}px"] > .slick-cell:nth(0) .slick-group-toggle.collapsed`).click({ force: true }); + }); + + it('should be able to click on the "Collapse All (wihout event)" button', () => { + cy.get('[data-test=collapse-all-noevent-btn]') + .contains('Collapse All (without triggering event)') + .click(); + }); + + it('should be able to click on the "Reapply Previous Toggled Items" button and expect "Task 1" and "Task 3" parents to become open (via Grid State change) while every other parents remains collapsed', () => { + cy.get('[data-test=reapply-toggled-items-btn]') + .contains('Reapply Previous Toggled Items') + .click({ force: true }); + + cy.get(`[style="top:${GRID_ROW_HEIGHT * 1}px"] > .slick-cell:nth(0)`).should('contain', 'Task 1'); + cy.get(`[style="top:${GRID_ROW_HEIGHT * 1}px"] > .slick-cell:nth(0) .slick-group-toggle.expanded`).should('have.length', 1); + + cy.get(`[style="top:${GRID_ROW_HEIGHT * 2}px"] > .slick-cell:nth(0)`).should('contain', 'Task 2'); + cy.get(`[style="top:${GRID_ROW_HEIGHT * 3}px"] > .slick-cell:nth(0)`).should('contain', 'Task 3'); + cy.get(`[style="top:${GRID_ROW_HEIGHT * 3}px"] > .slick-cell:nth(0) .slick-group-toggle.expanded`).should('have.length', 1); + + cy.get(`#grid28 .slick-group-toggle.expanded`).should('have.length', 2); + }); + + it('should be able to click on "Dynamically Toggle First Parent" expect only the first parent item to get collapsed', () => { + cy.get('[data-test=dynamically-toggle-first-parent-btn]') + .contains('Dynamically Toggle First Parent') + .click(); + + cy.get(`#grid28 .slick-group-toggle.expanded`).should('have.length', 0); + }); +}); \ No newline at end of file