From e4d1706271807f7aca7299cf8415b681ddab9cdb Mon Sep 17 00:00:00 2001 From: Ghislain B Date: Fri, 7 Jun 2024 22:42:17 -0400 Subject: [PATCH] feat: add optional Top-Header for Draggable Grouping & Header Grouping (#358) --- .../grouping-aggregators.md | 38 +++++++++++ src/examples/slickgrid/Example18.tsx | 68 +++++++++++-------- .../components/slickgrid-react.tsx | 4 +- test/cypress/e2e/example18.cy.ts | 28 ++++++-- 4 files changed, 101 insertions(+), 37 deletions(-) diff --git a/docs/grid-functionalities/grouping-aggregators.md b/docs/grid-functionalities/grouping-aggregators.md index ed9d20ad..2e5ba7a6 100644 --- a/docs/grid-functionalities/grouping-aggregators.md +++ b/docs/grid-functionalities/grouping-aggregators.md @@ -2,6 +2,7 @@ - [Demo](#demo) - [Description](#description) - [Setup](#setup) +- [Draggable Dropzone Location](#draggable-dropzone-location) - [Aggregators](#aggregators) - [SortComparers](https://github.com/ghiscoding/slickgrid-universal/blob/master/packages/common/src/sortComparers/sortComparers.index.ts) - [GroupTotalsFormatter](#group-totals-formatter) @@ -63,6 +64,43 @@ export class Example extends React.Component { } ``` +### Draggable Dropzone Location + +The Draggable Grouping can be located in either the Top-Header or the Pre-Header as described below. + +#### Pre-Heaader +Draggable Grouping can be located in either the Pre-Header of the Top-Header, however when it is located in the Pre-Header then the Header Grouping will not be available (because both of them would conflict with each other). Note that prior to the version 5.1 of Slickgrid-React, the Pre-Header was the default and only available option. + +```ts +this.gridOptions = { + createPreHeaderPanel: true, + showPreHeaderPanel: true, + preHeaderPanelHeight: 26, + draggableGrouping: { + // ... any draggable plugin option + }, +} +``` + +#### Top-Heaader +##### requires v5.1 and higher +This is the preferred section since the Top-Header is on top of all headers (including pre-header) and it will always be the full grid width. Using the Top-Header also frees up the Pre-Header section for the potential use of Header Grouping. + +When using Draggable Grouping and Header Grouping together, you need to enable both top-header and pre-header. +```ts +this.gridOptions = { + // we'll use top-header for the Draggable Grouping + createTopHeaderPanel: true, + showTopHeaderPanel: true, + topHeaderPanelHeight: 35, + + // pre-header will include our Header Grouping (i.e. "Common Factor") + createPreHeaderPanel: true, + showPreHeaderPanel: true, + preHeaderPanelHeight: 26, +} +``` + ### Aggregators The `Aggregators` is basically the accumulator, the logic that will do the sum (or any other aggregate we defined). We simply need to instantiate the `Aggregator` by passing the column definition `field` that will be used to accumulate. For example, if we have a column definition of Cost and we want to calculate it's sum, we can call the `Aggregator` as follow ```ts diff --git a/src/examples/slickgrid/Example18.tsx b/src/examples/slickgrid/Example18.tsx index 8fb02ef8..02ddf1d1 100644 --- a/src/examples/slickgrid/Example18.tsx +++ b/src/examples/slickgrid/Example18.tsx @@ -85,7 +85,7 @@ export default class Example18 extends React.Component { defineGrid() { const columnDefinitions: Column[] = [ { - id: 'title', name: 'Title', field: 'title', + id: 'title', name: 'Title', field: 'title', columnGroup: 'Common Factor', width: 70, minWidth: 50, cssClass: 'cell-title', filterable: true, @@ -101,7 +101,7 @@ export default class Example18 extends React.Component { } }, { - id: 'duration', name: 'Duration', field: 'duration', + id: 'duration', name: 'Duration', field: 'duration', columnGroup: 'Common Factor', width: 70, sortable: true, filterable: true, @@ -122,27 +122,8 @@ export default class Example18 extends React.Component { } }, { - id: 'percentComplete', name: '% Complete', field: 'percentComplete', - minWidth: 70, width: 90, - formatter: Formatters.percentCompleteBar, - type: FieldType.number, - filterable: true, - filter: { model: Filters.compoundSlider }, - sortable: true, - groupTotalsFormatter: GroupTotalFormatters.avgTotalsPercentage, - grouping: { - getter: 'percentComplete', - formatter: (g) => `% Complete: ${g.value} (${g.count} items)`, - aggregators: [ - new Aggregators.Sum('cost') - ], - aggregateCollapsed: false, - collapsed: false - }, - params: { groupFormatterPrefix: 'Avg: ' } - }, - { - id: 'start', name: 'Start', field: 'start', minWidth: 60, + id: 'start', name: 'Start', field: 'start', columnGroup: 'Period', + minWidth: 60, sortable: true, filterable: true, filter: { model: Filters.compoundDate }, @@ -161,7 +142,7 @@ export default class Example18 extends React.Component { } }, { - id: 'finish', name: 'Finish', field: 'finish', + id: 'finish', name: 'Finish', field: 'finish', columnGroup: 'Period', minWidth: 60, sortable: true, filterable: true, @@ -181,7 +162,7 @@ export default class Example18 extends React.Component { } }, { - id: 'cost', name: 'Cost', field: 'cost', + id: 'cost', name: 'Cost', field: 'cost', columnGroup: 'Analysis', width: 90, sortable: true, filterable: true, @@ -201,7 +182,27 @@ export default class Example18 extends React.Component { } }, { - id: 'effortDriven', name: 'Effort-Driven', field: 'effortDriven', + id: 'percentComplete', name: '% Complete', field: 'percentComplete', columnGroup: 'Analysis', + minWidth: 70, width: 90, + formatter: Formatters.percentCompleteBar, + type: FieldType.number, + filterable: true, + filter: { model: Filters.compoundSlider }, + sortable: true, + groupTotalsFormatter: GroupTotalFormatters.avgTotalsPercentage, + grouping: { + getter: 'percentComplete', + formatter: (g) => `% Complete: ${g.value} (${g.count} items)`, + aggregators: [ + new Aggregators.Sum('cost') + ], + aggregateCollapsed: false, + collapsed: false + }, + params: { groupFormatterPrefix: 'Avg: ' } + }, + { + id: 'effortDriven', name: 'Effort-Driven', field: 'effortDriven', columnGroup: 'Analysis', width: 80, minWidth: 20, maxWidth: 100, cssClass: 'cell-effort-driven', sortable: true, @@ -228,9 +229,18 @@ export default class Example18 extends React.Component { rightPadding: 10 }, enableDraggableGrouping: true, + + // pre-header will include our Header Grouping (i.e. "Common Factor") + // Draggable Grouping could be located in either the Pre-Header OR the new Top-Header createPreHeaderPanel: true, showPreHeaderPanel: true, - preHeaderPanelHeight: 40, + preHeaderPanelHeight: 30, + + // when Top-Header is created, it will be used by the Draggable Grouping (otherwise the Pre-Header will be used) + createTopHeaderPanel: true, + showTopHeaderPanel: true, + topHeaderPanelHeight: 35, + showCustomFooter: true, enableFiltering: true, // you could debounce/throttle the input text filter if you have lots of data @@ -248,7 +258,9 @@ export default class Example18 extends React.Component { draggableGrouping: { dropPlaceHolderText: 'Drop a column header here to group by the column', // groupIconCssClass: 'mdi mdi-drag-vertical', - deleteIconCssClass: 'mdi mdi-close', + deleteIconCssClass: 'mdi mdi-close text-color-danger', + sortAscIconCssClass: 'mdi mdi-arrow-up', + sortDescIconCssClass: 'mdi mdi-arrow-down', onGroupChanged: (_e, args) => this.onGroupChanged(args), onExtensionRegistered: (extension) => this.draggableGroupingPlugin = extension, }, diff --git a/src/slickgrid-react/components/slickgrid-react.tsx b/src/slickgrid-react/components/slickgrid-react.tsx index 33f78ae9..1af33168 100644 --- a/src/slickgrid-react/components/slickgrid-react.tsx +++ b/src/slickgrid-react/components/slickgrid-react.tsx @@ -749,7 +749,7 @@ export class SlickgridReact extends React.Component extends React.Component { - const fullTitles = ['Title', 'Duration', '% Complete', 'Start', 'Finish', 'Cost', 'Effort-Driven']; + const preHeaders = ['Common Factor', 'Period', 'Analysis', '']; + const fullTitles = ['Title', 'Duration', 'Start', 'Finish', 'Cost', '% Complete', 'Effort-Driven']; const GRID_ROW_HEIGHT = 35; it('should display Example title', () => { @@ -7,13 +8,26 @@ describe('Example 18 - Draggable Grouping & Aggregators', () => { cy.get('h2').should('contain', 'Example 18: Draggable Grouping & Aggregators'); }); - it('should have exact column titles on 1st grid', () => { + it('should have exact column (pre-header) grouping titles in grid', () => { cy.get('#grid18') - .find('.slick-header-columns') + .find('.slick-preheader-panel .slick-header-columns') + .children() + .each(($child, index) => expect($child.text()).to.eq(preHeaders[index])); + }); + + it('should have exact column titles in grid', () => { + cy.get('#grid18') + .find('.slick-header:not(.slick-preheader-panel) .slick-header-columns') .children() .each(($child, index) => expect($child.text()).to.eq(fullTitles[index])); }); + it('should have a draggable dropzone on top of the grid in the top-header section', () => { + cy.get('#grid18') + .find('.slick-topheader-panel .slick-dropzone:visible') + .contains('Drop a column header here to group by the column'); + }); + describe('Grouping Tests', () => { it('should "Group by Duration & sort groups by value" then Collapse All and expect only group titles', () => { cy.get('[data-test="add-50k-rows-btn"]').click(); @@ -141,8 +155,8 @@ describe('Example 18 - Draggable Grouping & Aggregators', () => { .should('exist'); }); - it('should use the preheader Toggle All button and expect all groups to now be expanded', () => { - cy.get('.slick-preheader-panel .slick-group-toggle-all').click(); + it('should use the topheader Toggle All button and expect all groups to now be expanded', () => { + cy.get('.slick-topheader-panel .slick-group-toggle-all').click(); cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(0) .slick-group-toggle.expanded`).should('have.length', 1); cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(0) .slick-group-title`).should('contain', 'Effort-Driven: False'); @@ -153,8 +167,8 @@ describe('Example 18 - Draggable Grouping & Aggregators', () => { .should('have.css', 'marginLeft').and('eq', `15px`); }); - it('should use the preheader Toggle All button again and expect all groups to now be collapsed', () => { - cy.get('.slick-preheader-panel .slick-group-toggle-all').click(); + it('should use the topheader Toggle All button again and expect all groups to now be collapsed', () => { + cy.get('.slick-topheader-panel .slick-group-toggle-all').click(); cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(0) .slick-group-toggle.collapsed`).should('have.length', 1); cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(0) .slick-group-title`).should('contain', 'Effort-Driven: False');