Skip to content

Commit

Permalink
feat: add optional Top-Header for Draggable Grouping & Header Grouping (
Browse files Browse the repository at this point in the history
  • Loading branch information
ghiscoding authored Jun 8, 2024
1 parent 643f8a8 commit e4d1706
Show file tree
Hide file tree
Showing 4 changed files with 101 additions and 37 deletions.
38 changes: 38 additions & 0 deletions docs/grid-functionalities/grouping-aggregators.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -63,6 +64,43 @@ export class Example extends React.Component<Props, State> {
}
```
### 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
Expand Down
68 changes: 40 additions & 28 deletions src/examples/slickgrid/Example18.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ export default class Example18 extends React.Component<Props, State> {
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,
Expand All @@ -101,7 +101,7 @@ export default class Example18 extends React.Component<Props, State> {
}
},
{
id: 'duration', name: 'Duration', field: 'duration',
id: 'duration', name: 'Duration', field: 'duration', columnGroup: 'Common Factor',
width: 70,
sortable: true,
filterable: true,
Expand All @@ -122,27 +122,8 @@ export default class Example18 extends React.Component<Props, State> {
}
},
{
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} <span class="text-primary">(${g.count} items)</span>`,
aggregators: [
new Aggregators.Sum('cost')
],
aggregateCollapsed: false,
collapsed: false
},
params: { groupFormatterPrefix: '<i>Avg</i>: ' }
},
{
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 },
Expand All @@ -161,7 +142,7 @@ export default class Example18 extends React.Component<Props, State> {
}
},
{
id: 'finish', name: 'Finish', field: 'finish',
id: 'finish', name: 'Finish', field: 'finish', columnGroup: 'Period',
minWidth: 60,
sortable: true,
filterable: true,
Expand All @@ -181,7 +162,7 @@ export default class Example18 extends React.Component<Props, State> {
}
},
{
id: 'cost', name: 'Cost', field: 'cost',
id: 'cost', name: 'Cost', field: 'cost', columnGroup: 'Analysis',
width: 90,
sortable: true,
filterable: true,
Expand All @@ -201,7 +182,27 @@ export default class Example18 extends React.Component<Props, State> {
}
},
{
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} <span class="text-primary">(${g.count} items)</span>`,
aggregators: [
new Aggregators.Sum('cost')
],
aggregateCollapsed: false,
collapsed: false
},
params: { groupFormatterPrefix: '<i>Avg</i>: ' }
},
{
id: 'effortDriven', name: 'Effort-Driven', field: 'effortDriven', columnGroup: 'Analysis',
width: 80, minWidth: 20, maxWidth: 100,
cssClass: 'cell-effort-driven',
sortable: true,
Expand All @@ -228,9 +229,18 @@ export default class Example18 extends React.Component<Props, State> {
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
Expand All @@ -248,7 +258,9 @@ export default class Example18 extends React.Component<Props, State> {
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,
},
Expand Down
4 changes: 2 additions & 2 deletions src/slickgrid-react/components/slickgrid-react.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -749,7 +749,7 @@ export class SlickgridReact<TData = any> extends React.Component<SlickgridReactP

if (gridOptions.enableTranslate) {
this.extensionService.translateAllExtensions(lang);
if (gridOptions.createPreHeaderPanel && !gridOptions.enableDraggableGrouping) {
if ((gridOptions.createPreHeaderPanel && gridOptions.createTopHeaderPanel) || (gridOptions.createPreHeaderPanel && !gridOptions.enableDraggableGrouping)) {
this.groupingService.translateGroupingAndColSpan();
}
}
Expand Down Expand Up @@ -1439,7 +1439,7 @@ export class SlickgridReact<TData = any> extends React.Component<SlickgridReactP
this._registeredResources.push(this.gridService, this.gridStateService);

// when using Grouping/DraggableGrouping/Colspan register its Service
if (this.gridOptions.createPreHeaderPanel && !this.gridOptions.enableDraggableGrouping) {
if ((this.gridOptions.createPreHeaderPanel && this.gridOptions.createTopHeaderPanel) || (this.gridOptions.createPreHeaderPanel && !this.gridOptions.enableDraggableGrouping)) {
this._registeredResources.push(this.groupingService);
}

Expand Down
28 changes: 21 additions & 7 deletions test/cypress/e2e/example18.cy.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,33 @@
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', () => {
cy.visit(`${Cypress.config('baseUrl')}/example18`);
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();
Expand Down Expand Up @@ -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');
Expand All @@ -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');
Expand Down

0 comments on commit e4d1706

Please sign in to comment.