diff --git a/cypress/e2e/example-draggable-grouping.cy.ts b/cypress/e2e/example-draggable-grouping.cy.ts
index 1ed2f6a4..fdca9185 100644
--- a/cypress/e2e/example-draggable-grouping.cy.ts
+++ b/cypress/e2e/example-draggable-grouping.cy.ts
@@ -12,7 +12,7 @@ describe('Example - Draggable Grouping', { retries: 1 }, () => {
cy.get('h2 + ul > li').first().contains('Draggable Grouping feature');
});
- it('should have exact column titles on both grids', () => {
+ it('should have exact column titles in grid', () => {
cy.get('#myGrid')
.find('.slick-header-columns')
.children()
diff --git a/cypress/e2e/example-draggable-header-grouping.cy.ts b/cypress/e2e/example-draggable-header-grouping.cy.ts
new file mode 100644
index 00000000..5ec925a0
--- /dev/null
+++ b/cypress/e2e/example-draggable-header-grouping.cy.ts
@@ -0,0 +1,218 @@
+describe('Example - Draggable Grouping', { retries: 1 }, () => {
+ // NOTE: everywhere there's a * 2 is because we have a top+bottom (frozen rows) containers even after Unfreeze Columns/Rows
+ const GRID_ROW_HEIGHT = 25;
+ const preHeaders = ['', 'Common Factor', 'Period', 'Analysis', ''];
+ const fullTitles = ['#', 'Title', 'Duration', 'Start', 'Finish', 'Cost', 'Effort-Driven'];
+ for (let i = 0; i < 30; i++) {
+ fullTitles.push(`Mock${i}`);
+ }
+
+ it('should display Example title', () => {
+ cy.visit(`${Cypress.config('baseUrl')}/examples/example-draggable-header-grouping.html`);
+ cy.get('h2').contains('Demonstrates');
+ cy.get('h2 + ul > li').first().contains('Draggable Grouping feature');
+ });
+
+ it('should have exact column (pre-header) grouping titles in grid', () => {
+ cy.get('#myGrid')
+ .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('#myGrid')
+ .find('.slick-header:not(.slick-preheader-panel) .slick-header-columns')
+ .children()
+ .each(($child, index) => expect($child.text()).to.eq(fullTitles[index]));
+ });
+
+ it('should have 6x draggable icons', () => {
+ cy.get('.slick-column-groupable')
+ .should('have.length', 6);
+
+ cy.get('.slick-column-groupable.sgi-drag-vertical')
+ .should('have.length', 6);
+ });
+
+ 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();
+ cy.get('[data-test="group-duration-sort-value-btn"]').click();
+ cy.get('[data-test="collapse-all-btn"]').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', 'Duration: 0');
+
+ cy.get(`[style="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(0) .slick-group-title`).should('contain', 'Duration: 1');
+ cy.get(`[style="top: ${GRID_ROW_HEIGHT * 2}px;"] > .slick-cell:nth(0) .slick-group-title`).should('contain', 'Duration: 2');
+ cy.get(`[style="top: ${GRID_ROW_HEIGHT * 3}px;"] > .slick-cell:nth(0) .slick-group-title`).should('contain', 'Duration: 3');
+ cy.get(`[style="top: ${GRID_ROW_HEIGHT * 4}px;"] > .slick-cell:nth(0) .slick-group-title`).should('contain', 'Duration: 4');
+ });
+
+ it('should click on Expand All columns and expect 1st row as grouping title and 2nd row as a regular row', () => {
+ cy.get('[data-test="add-50k-rows-btn"]').click();
+ cy.get('[data-test="group-duration-sort-value-btn"]').click();
+ cy.get('[data-test="expand-all-btn"]').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', 'Duration: 0');
+
+ cy.get(`[style="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(1)`).should('contain', 'Task');
+ cy.get(`[style="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(2)`).should('contain', '0');
+ });
+
+ it('should show 1 column title (Duration) shown in the pre-header section', () => {
+ cy.get('.slick-dropped-grouping:nth(0) div').contains('Duration');
+ });
+
+ it('should "Group by Duration then Effort-Driven" and expect 1st row to be expanded, 2nd row to be expanded and 3rd row to be a regular row', () => {
+ cy.get('[data-test="group-duration-effort-btn"]').click();
+
+ cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"].slick-group-level-0 > .slick-cell:nth(0) .slick-group-toggle.expanded`).should('have.length', 1);
+ cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"].slick-group-level-0 > .slick-cell:nth(0) .slick-group-title`).should('contain', 'Duration: 0');
+
+ cy.get(`[style="top: ${GRID_ROW_HEIGHT * 1}px;"].slick-group-level-1 .slick-group-toggle.expanded`).should('have.length', 1);
+ cy.get(`[style="top: ${GRID_ROW_HEIGHT * 1}px;"].slick-group-level-1 .slick-group-title`).should('contain', 'Effort-Driven: False');
+
+ cy.get(`[style="top: ${GRID_ROW_HEIGHT * 2}px;"] > .slick-cell:nth(1)`).should('contain', 'Task');
+ cy.get(`[style="top: ${GRID_ROW_HEIGHT * 2}px;"] > .slick-cell:nth(2)`).should('contain', '0');
+ });
+
+ it('should show 2 column titles (Duration, Effort-Driven) shown in the pre-header section', () => {
+ cy.get('.slick-dropped-grouping:nth(0) div').contains('Duration');
+ cy.get('.slick-dropped-grouping:nth(1) div').contains('Effort-Driven');
+ });
+
+ it('should be able to drag and swap pre-header grouped column titles (Effort-Driven, Duration)', () => {
+ cy.get('.slick-dropped-grouping:nth(0) div')
+ .contains('Duration')
+ .drag('.slick-dropped-grouping:nth(1) div');
+
+ cy.get('.slick-dropped-grouping:nth(0) div').contains('Effort-Driven');
+ cy.get('.slick-dropped-grouping:nth(1) div').contains('Duration');
+ });
+
+ it('should expect the grouping to be swapped as well in the grid', () => {
+ cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"].slick-group-level-0 > .slick-cell:nth(0) .slick-group-toggle.expanded`).should('have.length', 1);
+ cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"].slick-group-level-0 > .slick-cell:nth(0) .slick-group-title`).should('contain', 'Effort-Driven: False');
+
+ cy.get(`[style="top: ${GRID_ROW_HEIGHT * 1}px;"].slick-group-level-1 .slick-group-toggle.expanded`).should('have.length', 1);
+ cy.get(`[style="top: ${GRID_ROW_HEIGHT * 1}px;"].slick-group-level-1 .slick-group-title`).should('contain', 'Duration: 0');
+
+ cy.get(`[style="top: ${GRID_ROW_HEIGHT * 2}px;"] > .slick-cell:nth(1)`).should('contain', 'Task');
+ cy.get(`[style="top: ${GRID_ROW_HEIGHT * 2}px;"] > .slick-cell:nth(2)`).should('contain', '0');
+ });
+
+ it('should use the topheader Toggle All button 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');
+ cy.get(`[style="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(0) .slick-group-title`).should('contain', 'Effort-Driven: True');
+ });
+
+ it('should expand all rows with "Expand All" and expect all the Groups to be expanded and the Toogle All icon to be collapsed', () => {
+ cy.get('#myGrid')
+ .find('.slick-row .slick-cell:nth(1)')
+ .rightclick({ force: true });
+
+ cy.get('[data-test="expand-all-btn"]')
+ .click();
+
+ cy.get('#myGrid')
+ .find('.slick-group-toggle.collapsed')
+ .should('have.length', 0);
+
+ cy.get('#myGrid')
+ .find('.slick-group-toggle.expanded')
+ .should(($rows) => expect($rows).to.have.length.greaterThan(0));
+
+ cy.get('.slick-group-toggle-all.expanded')
+ .should('exist');
+ });
+
+ it('should collapse all rows with "Collapse All" and expect all the Groups to be collapsed and the Toogle All icon to be collapsed', () => {
+ cy.get('#myGrid')
+ .find('.slick-row .slick-cell:nth(1)')
+ .rightclick({ force: true });
+
+ cy.get('[data-test="collapse-all-btn"]')
+ .click();
+
+ cy.get('#myGrid')
+ .find('.slick-group-toggle.expanded')
+ .should('have.length', 0);
+
+ cy.get('#myGrid')
+ .find('.slick-group-toggle.collapsed')
+ .should(($rows) => expect($rows).to.have.length.greaterThan(0));
+
+ cy.get('.slick-group-toggle-all.collapsed')
+ .should('exist');
+ });
+
+ 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');
+ cy.get(`[style="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(0) .slick-group-title`).should('contain', 'Duration: 0');
+ cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(0) .slick-group-toggle.expanded`)
+ .should('have.css', 'marginLeft').and('eq', `0px`);
+ cy.get(`[style="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(0) .slick-group-toggle.expanded`)
+ .should('have.css', 'marginLeft').and('eq', `15px`);
+ });
+
+ 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');
+ cy.get(`[style="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(0) .slick-group-title`).should('contain', 'Effort-Driven: True');
+ });
+
+ it('should clear all groups with "Clear all Grouping" and expect all the Groups to be collapsed and the Toogle All icon to be collapsed', () => {
+ cy.get('#myGrid')
+ .find('.slick-row .slick-cell:nth(1)')
+ .rightclick({ force: true });
+
+ cy.get('[data-test="clear-grouping-btn"]')
+ .click();
+
+ cy.get('#myGrid')
+ .find('.slick-group-toggle-all')
+ .should('be.hidden');
+
+ cy.get('#myGrid')
+ .find('.slick-placeholder')
+ .should('be.visible')
+ .should('have.text', 'Drop a column header here to group by the column :)');
+ });
+
+ it('should add 500 items and expect last row to be Task 500', () => {
+ cy.get('[data-test="add-500-rows-btn"]')
+ .click();
+
+ cy.get('#myGrid')
+ .find('.slick-viewport-top.slick-viewport-left')
+ .scrollTo('bottom')
+ .wait(10);
+
+ cy.get(`#myGrid [style="top: ${GRID_ROW_HEIGHT * 499}px;"] > .slick-cell:nth(1)`).should('have.text', 'Task 499');
+ });
+
+ it('should add 50K items and expect last row to be Task 50,000', () => {
+ cy.get('[data-test="add-50k-rows-btn"]')
+ .click();
+
+ cy.get('#myGrid')
+ .find('.slick-viewport-top.slick-viewport-left')
+ .scrollTo('bottom')
+ .wait(10);
+
+ // cy.get(`#myGrid [style="top: ${GRID_ROW_HEIGHT * 49999}px;"] > .slick-cell:nth(1)`).should('have.text', 'Task 49999');
+ cy.get(`#myGrid [style="top: 1.24998e+06px;"] > .slick-cell:nth(1)`).should('have.text', 'Task 49999');
+ });
+ });
+});
diff --git a/examples/example-draggable-header-grouping.html b/examples/example-draggable-header-grouping.html
new file mode 100644
index 00000000..a11879d0
--- /dev/null
+++ b/examples/example-draggable-header-grouping.html
@@ -0,0 +1,544 @@
+
+
+
+
+
+
+ SlickGrid example: Droppable Grouping
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ⌂
+ Demonstrates:
+
+
+ Draggable Grouping feature
+ Similar to regular grouping but the "grouping" must be defined in the column we want to group by
+
+
Options:
+
+
+
Show tasks with % at least:
+
+
+
+
+
+
500 rows
+
50k rows
+
500k rows
+
+
Toggle Draggable Grouping
+
Group by Title
+
+
+ Group by duration & sort groups by value
+
+
+ Group by duration & sort groups by count
+
+
+ Group by Duration then Effort-Driven
+
+
+
Clear grouping
+
Collapse all groups
+
Expand all groups
+
+
Load CSS Theme:
+
Alpine Theme
+
Classic Theme
+
+
+
+
Demonstrates:
+
+
+ Fully dynamic and interactive multi-level grouping with filtering and aggregates over 50'000 items
+ Each grouping level can have its own aggregates (over child rows, child groups, or all descendant rows).
+ Personally, this is just the coolest slickest thing I've ever seen done with DHTML grids!
+
+
+
View Source:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/index.html b/examples/index.html
index 31958b4f..19a3d571 100644
--- a/examples/index.html
+++ b/examples/index.html
@@ -112,6 +112,7 @@ Grouping
Adding tree functionality (expand/collapse) to the grid
Adding tree functionality with tree sorting
Adding grouping using column dragging and dropping
+ Adding Header Grouping and Draggable Column Grouping & Dropping
Checkbox row selection with grouping and checkbox to select rows in group
Row Group with dedicated Grouping Column
diff --git a/src/models/gridOption.interface.ts b/src/models/gridOption.interface.ts
index 4c174d2c..ee3b1962 100644
--- a/src/models/gridOption.interface.ts
+++ b/src/models/gridOption.interface.ts
@@ -87,12 +87,15 @@ export interface GridOption {
/** Context menu options (mouse right+click) */
contextMenu?: ContextMenuOption;
- /** Defaults to false, which leads to create the footer row of the grid */
+ /** Defaults to false, which leads to creating the footer row of the grid */
createFooterRow?: boolean;
- /** Default to false, which leads to create an extra pre-header panel (on top of column header) for column grouping purposes */
+ /** Default to false, which leads to creating an extra pre-header panel (on top of column header) for column grouping purposes */
createPreHeaderPanel?: boolean;
+ /** Default to false, which leads to creating an extra top-header panel (on top of column header & pre-header) for column grouping purposes */
+ createTopHeaderPanel?: boolean;
+
/**
* Custom Tooltip Options, the tooltip could be defined in any of the Column Definition or in the Grid Options,
* it will first try to find it in the Column that the user is hovering over or else (when not found) go and try to find it in the Grid Options
@@ -254,6 +257,12 @@ export interface GridOption {
/** Defaults to "auto", extra pre-header panel (on top of column header) width, it could be a number (pixels) or a string ("100%" or "auto") */
preHeaderPanelWidth?: number | string;
+ /** Extra top-header panel height (on top of column header & pre-header) */
+ topHeaderPanelHeight?: number;
+
+ /** Defaults to "auto", extra top-header panel (on top of column header & pre-header) width, it could be a number (pixels) or a string ("100%" or "auto") */
+ topHeaderPanelWidth?: number | string;
+
/** Do we want to preserve copied selection on paste? */
preserveCopiedSelectionOnPaste?: boolean;
@@ -297,6 +306,9 @@ export interface GridOption {
/** Do we want to show the extra pre-header panel (on top of column header) for column grouping purposes */
showPreHeaderPanel?: boolean;
+ /** Do we want to show the extra top-header panel (on top of column header & pre-header) for column grouping purposes */
+ showTopHeaderPanel?: boolean;
+
/** Do we want to show top panel row? */
showTopPanel?: boolean;
diff --git a/src/plugins/slick.draggablegrouping.ts b/src/plugins/slick.draggablegrouping.ts
index 071a4316..9fc2a3e3 100644
--- a/src/plugins/slick.draggablegrouping.ts
+++ b/src/plugins/slick.draggablegrouping.ts
@@ -19,7 +19,7 @@ const Utils = IIFE_ONLY ? Slick.Utils : Utils_;
* github.com/muthukumarse/Slickgrid
*
* NOTES:
- * This plugin provides the Draggable Grouping feature
+ * This plugin provides the Draggable Grouping feature which could be located in either the Top-Header or the Pre-Header
* A plugin to add Draggable Grouping feature.
*
* USAGE:
@@ -90,7 +90,7 @@ export class SlickDraggableGrouping {
this._gridUid = this._grid.getUID();
this._gridColumns = this._grid.getColumns();
this._dataView = this._grid.getData();
- this._dropzoneElm = this._grid.getPreHeaderPanel();
+ this._dropzoneElm = this._grid.getTopHeaderPanel() || this._grid.getPreHeaderPanel();
this._dropzoneElm.classList.add('slick-dropzone');
const dropPlaceHolderText = this._options.dropPlaceHolderText || 'Drop a column header here to group by the column';
@@ -151,7 +151,7 @@ export class SlickDraggableGrouping {
*/
getSetupColumnReorder(grid: SlickGrid, headers: any, _headerColumnWidthDiff: any, setColumns: (columns: Column[]) => void, setupColumnResize: () => void, _columns: Column[], getColumnIndex: (columnId: string) => number, _uid: string, trigger: (slickEvent: SlickEvent_, data?: any) => void) {
this.destroySortableInstances();
- const dropzoneElm = grid.getPreHeaderPanel();
+ const dropzoneElm = grid.getTopHeaderPanel() || grid.getPreHeaderPanel();
const groupTogglerElm = dropzoneElm.querySelector('.slick-group-toggle-all');
const sortableOptions = {
@@ -260,7 +260,7 @@ export class SlickDraggableGrouping {
this.onGroupChanged.unsubscribe();
this._handler.unsubscribeAll();
this._bindingEventService.unbindAll();
- Utils.emptyElement(document.querySelector(`.${this._gridUid} .slick-preheader-panel`));
+ Utils.emptyElement(document.querySelector(`.${this._gridUid} .slick-preheader-panel,.${this._gridUid} .slick-topheader-panel`));
}
protected destroySortableInstances() {
diff --git a/src/slick.grid.ts b/src/slick.grid.ts
index 0c18cfc4..8636a8a5 100644
--- a/src/slick.grid.ts
+++ b/src/slick.grid.ts
@@ -241,11 +241,15 @@ export class SlickGrid = Column, O e
showFooterRow: false,
footerRowHeight: 25,
createPreHeaderPanel: false,
+ createTopHeaderPanel: false,
showPreHeaderPanel: false,
+ showTopHeaderPanel: false,
preHeaderPanelHeight: 25,
showTopPanel: false,
topPanelHeight: 25,
preHeaderPanelWidth: 'auto', // mostly useful for Draggable Grouping dropzone to take full width
+ topHeaderPanelHeight: 25,
+ topHeaderPanelWidth: 'auto', // mostly useful for Draggable Grouping dropzone to take full width
formatterFactory: null,
editorFactory: null,
cellFlashingCssClass: 'flashing',
@@ -364,6 +368,9 @@ export class SlickGrid = Column, O e
protected _preHeaderPanelR!: HTMLDivElement;
protected _preHeaderPanelScrollerR!: HTMLDivElement;
protected _preHeaderPanelSpacerR!: HTMLDivElement;
+ protected _topHeaderPanel!: HTMLDivElement;
+ protected _topHeaderPanelScroller!: HTMLDivElement;
+ protected _topHeaderPanelSpacer!: HTMLDivElement;
protected _topPanelScrollers!: HTMLDivElement[];
protected _topPanels!: HTMLDivElement[];
protected _viewport!: HTMLDivElement[];
@@ -681,6 +688,17 @@ export class SlickGrid = Column, O e
this._focusSink = Utils.createDomElement('div', { tabIndex: 0, style: { position: 'fixed', width: '0px', height: '0px', top: '0px', left: '0px', outline: '0px' } }, this._container);
+ if (this._options.createTopHeaderPanel) {
+ this._topHeaderPanelScroller = Utils.createDomElement('div', { className: 'slick-topheader-panel slick-state-default', style: { overflow: 'hidden', position: 'relative' } }, this._container);
+ this._topHeaderPanelScroller.appendChild(document.createElement('div'));
+ this._topHeaderPanel = Utils.createDomElement('div', null, this._topHeaderPanelScroller);
+ this._topHeaderPanelSpacer = Utils.createDomElement('div', { style: { display: 'block', height: '1px', position: 'absolute', top: '0px', left: '0px' } }, this._topHeaderPanelScroller);
+
+ if (!this._options.showTopHeaderPanel) {
+ Utils.hide(this._topHeaderPanelScroller);
+ }
+ }
+
// Containers used for scrolling frozen columns and rows
this._paneHeaderL = Utils.createDomElement('div', { className: 'slick-pane slick-pane-header slick-pane-left', tabIndex: 0 }, this._container);
this._paneHeaderR = Utils.createDomElement('div', { className: 'slick-pane slick-pane-header slick-pane-right', tabIndex: 0 }, this._container);
@@ -794,6 +812,11 @@ export class SlickGrid = Column, O e
// Default the active canvas to the top left
this._activeCanvasNode = this._canvasTopL;
+ // top-header
+ if (this._topHeaderPanelSpacer) {
+ Utils.width(this._topHeaderPanelSpacer, this.getCanvasWidth() + this.scrollbarDimensions.width);
+ }
+
// pre-header
if (this._preHeaderPanelSpacer) {
Utils.width(this._preHeaderPanelSpacer, this.getCanvasWidth() + this.scrollbarDimensions.width);
@@ -915,6 +938,10 @@ export class SlickGrid = Column, O e
});
}
+ if (this._options.createTopHeaderPanel) {
+ this._bindingEventService.bind(this._topHeaderPanelScroller, 'scroll', this.handleTopHeaderPanelScroll.bind(this) as EventListener);
+ }
+
if (this._options.createPreHeaderPanel) {
this._bindingEventService.bind(this._preHeaderPanelScroller, 'scroll', this.handlePreHeaderPanelScroll.bind(this) as EventListener);
}
@@ -1191,6 +1218,10 @@ export class SlickGrid = Column, O e
const oldCanvasWidthR = this.canvasWidthR;
this.canvasWidth = this.getCanvasWidth();
+ if (this._options.createTopHeaderPanel) {
+ Utils.width(this._topHeaderPanel, this._options.topHeaderPanelWidth ?? this.canvasWidth);
+ }
+
const widthChanged = this.canvasWidth !== oldCanvasWidth || this.canvasWidthL !== oldCanvasWidthL || this.canvasWidthR !== oldCanvasWidthR;
if (widthChanged || this.hasFrozenColumns() || this.hasFrozenRows) {
@@ -1458,6 +1489,11 @@ export class SlickGrid = Column, O e
return this._preHeaderPanelR;
}
+ /** Get the Top-Header Panel DOM node element */
+ getTopHeaderPanel() {
+ return this._topHeaderPanel;
+ }
+
/**
* Get Header Row Column DOM element by its column Id or index
* @param {Number|String} columnIdOrIdx - column Id or index
@@ -2378,6 +2414,7 @@ export class SlickGrid = Column, O e
`.${this.uid} .slick-header-column { left: 1000px; }`,
`.${this.uid} .slick-top-panel { height: ${this._options.topPanelHeight}px; }`,
`.${this.uid} .slick-preheader-panel { height: ${this._options.preHeaderPanelHeight}px; }`,
+ `.${this.uid} .slick-topheader-panel { height: ${this._options.topHeaderPanelHeight}px; }`,
`.${this.uid} .slick-headerrow-columns { height: ${this._options.headerRowHeight}px; }`,
`.${this.uid} .slick-footerrow-columns { height: ${this._options.footerRowHeight}px; }`,
`.${this.uid} .slick-cell { height: ${rowHeight}px; }`,
@@ -2546,6 +2583,10 @@ export class SlickGrid = Column, O e
this._bindingEventService.unbindByEventName(this._preHeaderPanelScroller, 'scroll');
}
+ if (this._topHeaderPanelScroller) {
+ this._bindingEventService.unbindByEventName(this._topHeaderPanelScroller, 'scroll');
+ }
+
this._bindingEventService.unbindByEventName(this._focusSink, 'keydown');
this._bindingEventService.unbindByEventName(this._focusSink2, 'keydown');
@@ -3701,7 +3742,7 @@ export class SlickGrid = Column, O e
return !Array.isArray(this.data);
}
- protected togglePanelVisibility(option: 'showTopPanel' | 'showHeaderRow' | 'showColumnHeader' | 'showFooterRow' | 'showPreHeaderPanel', container: HTMLElement | HTMLElement[], visible?: boolean, animate?: boolean) {
+ protected togglePanelVisibility(option: 'showTopPanel' | 'showHeaderRow' | 'showColumnHeader' | 'showFooterRow' | 'showPreHeaderPanel' | 'showTopHeaderPanel', container: HTMLElement | HTMLElement[], visible?: boolean, animate?: boolean) {
const animated = (animate === false) ? false : true;
if (this._options[option] !== visible) {
@@ -3769,6 +3810,14 @@ export class SlickGrid = Column, O e
this.togglePanelVisibility('showPreHeaderPanel', [this._preHeaderPanelScroller, this._preHeaderPanelScrollerR], visible, animate);
}
+ /**
+ * Set the Top-Header Visibility
+ * @param {Boolean} [visible] - optionally set if top-header panel is visible or not
+ */
+ setTopHeaderPanelVisibility(visible?: boolean) {
+ this.togglePanelVisibility('showTopHeaderPanel', this._topHeaderPanelScroller, visible);
+ }
+
/** Get Grid Canvas Node DOM Element */
getContainerNode() {
return this._container;
@@ -4266,6 +4315,7 @@ export class SlickGrid = Column, O e
} else {
const columnNamesH = (this._options.showColumnHeader) ? Utils.toFloat(Utils.height(this._headerScroller[0]) as number) + this.getVBoxDelta(this._headerScroller[0]) : 0;
const preHeaderH = (this._options.createPreHeaderPanel && this._options.showPreHeaderPanel) ? this._options.preHeaderPanelHeight! + this.getVBoxDelta(this._preHeaderPanelScroller) : 0;
+ const topHeaderH = (this._options.createTopHeaderPanel && this._options.showTopHeaderPanel) ? this._options.topHeaderPanelHeight! + this.getVBoxDelta(this._topHeaderPanelScroller) : 0;
const style = getComputedStyle(this._container);
this.viewportH = Utils.toFloat(style.height)
@@ -4275,7 +4325,8 @@ export class SlickGrid = Column, O e
- this.topPanelH
- this.headerRowH
- this.footerRowH
- - preHeaderH;
+ - preHeaderH
+ - topHeaderH;
}
this.numVisibleRows = Math.ceil(this.viewportH / this._options.rowHeight!);
@@ -4330,7 +4381,13 @@ export class SlickGrid = Column, O e
this._paneTopL.style.position = 'relative';
}
- Utils.setStyleSize(this._paneTopL, 'top', Utils.height(this._paneHeaderL) || (this._options.showHeaderRow ? this._options.headerRowHeight! : 0) + (this._options.showPreHeaderPanel ? this._options.preHeaderPanelHeight! : 0));
+ let topHeightOffset = Utils.height(this._paneHeaderL);
+ if (topHeightOffset) {
+ topHeightOffset += (this._options.showTopHeaderPanel ? this._options.topHeaderPanelHeight! : 0);
+ } else {
+ topHeightOffset = (this._options.showHeaderRow ? this._options.headerRowHeight! : 0) + (this._options.showPreHeaderPanel ? this._options.preHeaderPanelHeight! : 0);
+ }
+ Utils.setStyleSize(this._paneTopL, 'top', topHeightOffset || topHeightOffset);
Utils.height(this._paneTopL, this.paneTopH);
const paneBottomTop = this._paneTopL.offsetTop + this.paneTopH;
@@ -4340,7 +4397,11 @@ export class SlickGrid = Column, O e
}
if (this.hasFrozenColumns()) {
- Utils.setStyleSize(this._paneTopR, 'top', Utils.height(this._paneHeaderL) as number);
+ let topHeightOffset = Utils.height(this._paneHeaderL);
+ if (topHeightOffset) {
+ topHeightOffset += (this._options.showTopHeaderPanel ? this._options.topHeaderPanelHeight! : 0);
+ }
+ Utils.setStyleSize(this._paneTopR, 'top', topHeightOffset as number);
Utils.height(this._paneTopR, this.paneTopH);
Utils.height(this._viewportTopR, this.viewportTopH);
@@ -4912,6 +4973,10 @@ export class SlickGrid = Column, O e
this.handleElementScroll(this._preHeaderPanelScroller);
}
+ protected handleTopHeaderPanelScroll() {
+ this.handleElementScroll(this._topHeaderPanelScroller);
+ }
+
protected handleElementScroll(element: HTMLElement) {
const scrollLeft = element.scrollLeft;
if (scrollLeft !== this._viewportScrollContainerX.scrollLeft) {
@@ -4962,6 +5027,9 @@ export class SlickGrid = Column, O e
this._preHeaderPanelScroller.scrollLeft = this.scrollLeft;
}
}
+ if (this._options.createTopHeaderPanel) {
+ this._topHeaderPanelScroller.scrollLeft = this.scrollLeft;
+ }
if (this.hasFrozenColumns()) {
if (this.hasFrozenRows) {
diff --git a/src/styles/slick-alpine-theme.scss b/src/styles/slick-alpine-theme.scss
index 27cb847a..0c87751b 100644
--- a/src/styles/slick-alpine-theme.scss
+++ b/src/styles/slick-alpine-theme.scss
@@ -259,7 +259,8 @@
filter: alpha(opacity = 70);
}
-.slick-preheader-panel {
+.slick-preheader-panel,
+.slick-topheader-panel {
.slick-header-column {
border-style: solid;
border-color: var(--alpine-preheader-border-color, $alpine-preheader-border-color);
diff --git a/src/styles/slick-default-theme.scss b/src/styles/slick-default-theme.scss
index 27fecba5..1a0763f4 100644
--- a/src/styles/slick-default-theme.scss
+++ b/src/styles/slick-default-theme.scss
@@ -29,7 +29,8 @@ classes should alter those!
height: 100%;
}
-.slick-preheader-panel {
+.slick-preheader-panel,
+.slick-topheader-panel {
border: 1px solid #d3d3d3;
}