From 09c190e87dc629d38990152f1332657ea9ed6926 Mon Sep 17 00:00:00 2001 From: ghiscoding Date: Tue, 4 Jun 2024 22:31:22 -0400 Subject: [PATCH] feat: add optional Top-Header for Draggable Grouping & Header Grouping --- .../grouping-aggregators.md | 38 +++++++++++ .../src/custom-elements/aurelia-slickgrid.ts | 7 +- .../demo/src/examples/slickgrid/example18.ts | 68 +++++++++++-------- test/cypress/e2e/example18.cy.ts | 28 ++++++-- 4 files changed, 104 insertions(+), 37 deletions(-) diff --git a/docs/grid-functionalities/grouping-aggregators.md b/docs/grid-functionalities/grouping-aggregators.md index ef73f6266..697317af5 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) @@ -62,6 +63,43 @@ export class Example { } ``` +### 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 8.1 of Aurelia-Slickgrid, 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 v8.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/packages/aurelia-slickgrid/src/custom-elements/aurelia-slickgrid.ts b/packages/aurelia-slickgrid/src/custom-elements/aurelia-slickgrid.ts index 1a717ec03..bc1a29869 100644 --- a/packages/aurelia-slickgrid/src/custom-elements/aurelia-slickgrid.ts +++ b/packages/aurelia-slickgrid/src/custom-elements/aurelia-slickgrid.ts @@ -673,7 +673,7 @@ export class AureliaSlickgridCustomElement { if (gridOptions.enableTranslate) { this.extensionService.translateAllExtensions(args.newLocale); - if (gridOptions.createPreHeaderPanel && !gridOptions.enableDraggableGrouping) { + if ((gridOptions.createPreHeaderPanel && gridOptions.createTopHeaderPanel) || (gridOptions.createPreHeaderPanel && !gridOptions.enableDraggableGrouping)) { this.groupingService.translateGroupingAndColSpan(); } } @@ -1358,7 +1358,10 @@ export class AureliaSlickgridCustomElement { } // when using Grouping/DraggableGrouping/Colspan register its Service - if (this.gridOptions.createPreHeaderPanel && !this.gridOptions.enableDraggableGrouping && !this._registeredResources.some(r => r instanceof GroupingAndColspanService)) { + if ( + ((this.gridOptions.createPreHeaderPanel && this.gridOptions.createTopHeaderPanel) || (this.gridOptions.createPreHeaderPanel && !this.gridOptions.enableDraggableGrouping)) + && !this._registeredResources.some(r => r instanceof GroupingAndColspanService) + ) { this._registeredResources.push(this.groupingService); } diff --git a/packages/demo/src/examples/slickgrid/example18.ts b/packages/demo/src/examples/slickgrid/example18.ts index a814dcaa2..0443b6930 100644 --- a/packages/demo/src/examples/slickgrid/example18.ts +++ b/packages/demo/src/examples/slickgrid/example18.ts @@ -71,7 +71,7 @@ export class Example18 { defineGrid() { this.columnDefinitions = [ { - id: 'title', name: 'Title', field: 'title', + id: 'title', name: 'Title', field: 'title', columnGroup: 'Common Factor', width: 70, minWidth: 50, cssClass: 'cell-title', filterable: true, @@ -87,7 +87,7 @@ export class Example18 { } }, { - id: 'duration', name: 'Duration', field: 'duration', + id: 'duration', name: 'Duration', field: 'duration', columnGroup: 'Common Factor', width: 70, sortable: true, filterable: true, @@ -108,27 +108,8 @@ export class Example18 { } }, { - 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 }, @@ -147,7 +128,7 @@ export class Example18 { } }, { - id: 'finish', name: 'Finish', field: 'finish', + id: 'finish', name: 'Finish', field: 'finish', columnGroup: 'Period', minWidth: 60, sortable: true, filterable: true, @@ -167,7 +148,7 @@ export class Example18 { } }, { - id: 'cost', name: 'Cost', field: 'cost', + id: 'cost', name: 'Cost', field: 'cost', columnGroup: 'Analysis', width: 90, sortable: true, filterable: true, @@ -187,7 +168,27 @@ export class Example18 { } }, { - 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, @@ -214,9 +215,18 @@ export class Example18 { 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 @@ -234,7 +244,9 @@ export class Example18 { 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/test/cypress/e2e/example18.cy.ts b/test/cypress/e2e/example18.cy.ts index 9005fb026..7e04e586d 100644 --- a/test/cypress/e2e/example18.cy.ts +++ b/test/cypress/e2e/example18.cy.ts @@ -1,5 +1,6 @@ describe('Example 18 - Draggable Grouping & Aggregators', () => { - 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');