diff --git a/demos/aurelia/src/examples/slickgrid/example18.ts b/demos/aurelia/src/examples/slickgrid/example18.ts index dde14856fc..bc4be3af9c 100644 --- a/demos/aurelia/src/examples/slickgrid/example18.ts +++ b/demos/aurelia/src/examples/slickgrid/example18.ts @@ -64,6 +64,7 @@ export class Example18 { width: 70, minWidth: 50, cssClass: 'cell-title', + hidden: true, // column initially hidden filterable: true, sortable: true, grouping: { diff --git a/demos/aurelia/test/cypress/e2e/example18.cy.ts b/demos/aurelia/test/cypress/e2e/example18.cy.ts index eae744afce..dd795e35c8 100644 --- a/demos/aurelia/test/cypress/e2e/example18.cy.ts +++ b/demos/aurelia/test/cypress/e2e/example18.cy.ts @@ -1,6 +1,16 @@ describe('Example 18 - Draggable Grouping & Aggregators', () => { const preHeaders = ['Common Factor', 'Period', 'Analysis', '']; + const originalTitles = ['Duration', 'Start', 'Finish', 'Cost', '% Complete', 'Effort-Driven']; const fullTitles = ['Title', 'Duration', 'Start', 'Finish', 'Cost', '% Complete', 'Effort-Driven']; + const gridMenuTitles = [ + 'Common Factor - Title', + 'Common Factor - Duration', + 'Period - Start', + 'Period - Finish', + 'Analysis - Cost', + 'Analysis - % Complete', + 'Analysis - Effort-Driven', + ]; it('should display Example title', () => { cy.visit(`${Cypress.config('baseUrl')}/example18`); @@ -18,7 +28,7 @@ describe('Example 18 - Draggable Grouping & Aggregators', () => { cy.get('#grid18') .find('.slick-header:not(.slick-preheader-panel) .slick-header-columns') .children() - .each(($child, index) => expect($child.text()).to.eq(fullTitles[index])); + .each(($child, index) => expect($child.text()).to.eq(originalTitles[index])); }); it('should initially be grouped by "Duration" when loading the grid', () => { @@ -26,6 +36,52 @@ describe('Example 18 - Draggable Grouping & Aggregators', () => { cy.get('[data-row=1] > .slick-cell:nth(2)').should('contain', '0'); }); + it('should open Grid Menu and be able to unhide "Title" column', () => { + cy.get('button.slick-grid-menu-button').click({ force: true }); + cy.get('.slick-grid-menu:visible') + .find('.slick-column-picker-list') + .children('li:visible:nth(0)') + .children('label') + .should('contain', 'Common Factor - Title'); + // .click({ force: true }); + + cy.get('.slick-column-picker-list input[data-columnid="title"]') + .parent('.icon-checkbox-container') + .find('.sgi') + .should('have.class', 'sgi-icon-picker-uncheck'); + + cy.get('.slick-column-picker-list li') + .children() + + .each(($child, index) => { + if (index <= 5) { + expect($child.text()).to.eq(gridMenuTitles[index]); + } + }); + + cy.get('.slick-grid-menu:visible') + .find('.slick-column-picker-list') + .children('li:visible:nth(0)') + .children('label') + .should('contain', 'Common Factor - Title') + .click(); + + cy.get('.slick-column-picker-list input[data-columnid="title"]') + .parent('.icon-checkbox-container') + .find('.sgi') + .should('have.class', 'sgi-icon-picker-check'); + + 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 still be grouped by "Duration"', () => { + cy.get('[data-row=0] > .cell-title .slick-group-title').contains(/Duration: [0-9]/); + cy.get('[data-row=1] > .slick-cell:nth(2)').should('contain', '0'); + }); + it('should clear all groups with "Clear all Grouping" and no longer expect any grouping', () => { cy.get('[data-test="clear-grouping-btn"]').click(); cy.get('#grid18').find('.slick-group-toggle-all').should('be.hidden'); diff --git a/demos/react/src/examples/slickgrid/Example18.tsx b/demos/react/src/examples/slickgrid/Example18.tsx index 2d6b539fe6..9d726d15da 100644 --- a/demos/react/src/examples/slickgrid/Example18.tsx +++ b/demos/react/src/examples/slickgrid/Example18.tsx @@ -56,6 +56,7 @@ const Example18: React.FC = () => { width: 70, minWidth: 50, cssClass: 'cell-title', + hidden: true, // column initially hidden filterable: true, sortable: true, grouping: { diff --git a/demos/react/test/cypress/e2e/example18.cy.ts b/demos/react/test/cypress/e2e/example18.cy.ts index b9807b0bc8..397df8aa69 100644 --- a/demos/react/test/cypress/e2e/example18.cy.ts +++ b/demos/react/test/cypress/e2e/example18.cy.ts @@ -1,6 +1,16 @@ describe('Example 18 - Draggable Grouping & Aggregators', () => { const preHeaders = ['Common Factor', 'Period', 'Analysis', '']; + const originalTitles = ['Duration', 'Start', 'Finish', 'Cost', '% Complete', 'Effort-Driven']; const fullTitles = ['Title', 'Duration', 'Start', 'Finish', 'Cost', '% Complete', 'Effort-Driven']; + const gridMenuTitles = [ + 'Common Factor - Title', + 'Common Factor - Duration', + 'Period - Start', + 'Period - Finish', + 'Analysis - Cost', + 'Analysis - % Complete', + 'Analysis - Effort-Driven', + ]; it('should display Example title', () => { cy.visit(`${Cypress.config('baseUrl')}/example18`); @@ -18,7 +28,7 @@ describe('Example 18 - Draggable Grouping & Aggregators', () => { cy.get('#grid18') .find('.slick-header:not(.slick-preheader-panel) .slick-header-columns') .children() - .each(($child, index) => expect($child.text()).to.eq(fullTitles[index])); + .each(($child, index) => expect($child.text()).to.eq(originalTitles[index])); }); it('should initially be grouped by "Duration" when loading the grid', () => { @@ -26,6 +36,52 @@ describe('Example 18 - Draggable Grouping & Aggregators', () => { cy.get('[data-row=1] > .slick-cell:nth(2)').should('contain', '0'); }); + it('should open Grid Menu and be able to unhide "Title" column', () => { + cy.get('button.slick-grid-menu-button').click({ force: true }); + cy.get('.slick-grid-menu:visible') + .find('.slick-column-picker-list') + .children('li:visible:nth(0)') + .children('label') + .should('contain', 'Common Factor - Title'); + // .click({ force: true }); + + cy.get('.slick-column-picker-list input[data-columnid="title"]') + .parent('.icon-checkbox-container') + .find('.sgi') + .should('have.class', 'sgi-icon-picker-uncheck'); + + cy.get('.slick-column-picker-list li') + .children() + + .each(($child, index) => { + if (index <= 5) { + expect($child.text()).to.eq(gridMenuTitles[index]); + } + }); + + cy.get('.slick-grid-menu:visible') + .find('.slick-column-picker-list') + .children('li:visible:nth(0)') + .children('label') + .should('contain', 'Common Factor - Title') + .click(); + + cy.get('.slick-column-picker-list input[data-columnid="title"]') + .parent('.icon-checkbox-container') + .find('.sgi') + .should('have.class', 'sgi-icon-picker-check'); + + 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 still be grouped by "Duration"', () => { + cy.get('[data-row=0] > .cell-title .slick-group-title').contains(/Duration: [0-9]/); + cy.get('[data-row=1] > .slick-cell:nth(2)').should('contain', '0'); + }); + it('should clear all groups with "Clear all Grouping" and no longer expect any grouping', () => { cy.get('[data-test="clear-grouping-btn"]').click(); cy.get('#grid18').find('.slick-group-toggle-all').should('be.hidden'); diff --git a/demos/vanilla/src/examples/example03.html b/demos/vanilla/src/examples/example03.html index f271da75db..b93ea06b33 100644 --- a/demos/vanilla/src/examples/example03.html +++ b/demos/vanilla/src/examples/example03.html @@ -19,6 +19,10 @@

+
+ Please note that the grid below initially has its first column "Title" hidden by default for E2E testing. +
+
diff --git a/demos/vanilla/src/examples/example03.ts b/demos/vanilla/src/examples/example03.ts index c40f8043cb..5a774f282b 100644 --- a/demos/vanilla/src/examples/example03.ts +++ b/demos/vanilla/src/examples/example03.ts @@ -94,6 +94,7 @@ export default class Example03 { name: 'Title', field: 'title', columnGroup: 'Common Factor', + hidden: true, // column initially hidden sortable: true, editor: { model: Editors.longText, diff --git a/demos/vue/src/components/Example18.vue b/demos/vue/src/components/Example18.vue index a3fe491226..56d07fdf29 100644 --- a/demos/vue/src/components/Example18.vue +++ b/demos/vue/src/components/Example18.vue @@ -54,6 +54,7 @@ function defineGrid() { width: 70, minWidth: 50, cssClass: 'cell-title', + hidden: true, // column initially hidden filterable: true, sortable: true, grouping: { diff --git a/demos/vue/test/cypress/e2e/example18.cy.ts b/demos/vue/test/cypress/e2e/example18.cy.ts index b9807b0bc8..397df8aa69 100644 --- a/demos/vue/test/cypress/e2e/example18.cy.ts +++ b/demos/vue/test/cypress/e2e/example18.cy.ts @@ -1,6 +1,16 @@ describe('Example 18 - Draggable Grouping & Aggregators', () => { const preHeaders = ['Common Factor', 'Period', 'Analysis', '']; + const originalTitles = ['Duration', 'Start', 'Finish', 'Cost', '% Complete', 'Effort-Driven']; const fullTitles = ['Title', 'Duration', 'Start', 'Finish', 'Cost', '% Complete', 'Effort-Driven']; + const gridMenuTitles = [ + 'Common Factor - Title', + 'Common Factor - Duration', + 'Period - Start', + 'Period - Finish', + 'Analysis - Cost', + 'Analysis - % Complete', + 'Analysis - Effort-Driven', + ]; it('should display Example title', () => { cy.visit(`${Cypress.config('baseUrl')}/example18`); @@ -18,7 +28,7 @@ describe('Example 18 - Draggable Grouping & Aggregators', () => { cy.get('#grid18') .find('.slick-header:not(.slick-preheader-panel) .slick-header-columns') .children() - .each(($child, index) => expect($child.text()).to.eq(fullTitles[index])); + .each(($child, index) => expect($child.text()).to.eq(originalTitles[index])); }); it('should initially be grouped by "Duration" when loading the grid', () => { @@ -26,6 +36,52 @@ describe('Example 18 - Draggable Grouping & Aggregators', () => { cy.get('[data-row=1] > .slick-cell:nth(2)').should('contain', '0'); }); + it('should open Grid Menu and be able to unhide "Title" column', () => { + cy.get('button.slick-grid-menu-button').click({ force: true }); + cy.get('.slick-grid-menu:visible') + .find('.slick-column-picker-list') + .children('li:visible:nth(0)') + .children('label') + .should('contain', 'Common Factor - Title'); + // .click({ force: true }); + + cy.get('.slick-column-picker-list input[data-columnid="title"]') + .parent('.icon-checkbox-container') + .find('.sgi') + .should('have.class', 'sgi-icon-picker-uncheck'); + + cy.get('.slick-column-picker-list li') + .children() + + .each(($child, index) => { + if (index <= 5) { + expect($child.text()).to.eq(gridMenuTitles[index]); + } + }); + + cy.get('.slick-grid-menu:visible') + .find('.slick-column-picker-list') + .children('li:visible:nth(0)') + .children('label') + .should('contain', 'Common Factor - Title') + .click(); + + cy.get('.slick-column-picker-list input[data-columnid="title"]') + .parent('.icon-checkbox-container') + .find('.sgi') + .should('have.class', 'sgi-icon-picker-check'); + + 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 still be grouped by "Duration"', () => { + cy.get('[data-row=0] > .cell-title .slick-group-title').contains(/Duration: [0-9]/); + cy.get('[data-row=1] > .slick-cell:nth(2)').should('contain', '0'); + }); + it('should clear all groups with "Clear all Grouping" and no longer expect any grouping', () => { cy.get('[data-test="clear-grouping-btn"]').click(); cy.get('#grid18').find('.slick-group-toggle-all').should('be.hidden'); diff --git a/docs/TOC.md b/docs/TOC.md index 7006be6762..5724264197 100644 --- a/docs/TOC.md +++ b/docs/TOC.md @@ -35,6 +35,7 @@ * [Single Search Filter](column-functionalities/filters/single-search-filter.md) * [Formatters](column-functionalities/formatters.md) * [Sorting](column-functionalities/sorting.md) +* [Visibility](column-functionalities/visibility.md) ## Events diff --git a/docs/column-functionalities/visibility.md b/docs/column-functionalities/visibility.md new file mode 100644 index 0000000000..1402c78681 --- /dev/null +++ b/docs/column-functionalities/visibility.md @@ -0,0 +1,56 @@ +### Demo +[Demo](https://ghiscoding.github.io/slickgrid-universal/#/example03) / [Demo Component](https://github.com/ghiscoding/slickgrid-universal/blob/master/demos/vanilla/src/examples/example03.ts) + +### Description + +For column visibility, you can define the `hidden` property in your column definitions to initially hide some columns. You could also toggle the `hidden` property at any point in time (see below for more code usage). + +### initially hidden columns + +Let's start by demoing how to initially hide some column(s) by using the `hidden` property. + +##### define columns + +```ts +this.columns = [ + { id: 'firstName', field: 'firstName', name: 'First Name' }, + { id: 'lastName', field: 'lastName', name: 'Last Name' }, + { id: 'age', field: 'age', name: 'Age', hidden: true }, // column initially hidden +]; +``` + +### change visibility afterward + +At any point in time, you could toggle the `hidden` property by using `grid.updateColumnById()` and make sure to also call `grid.updateColumns()` so that the UI is also updated. + +##### define columns + +```ts +export class MyExample { + columns: Column[]; + + defineGrid() { + this.columns = [ + { id: 'firstName', field: 'firstName', name: 'First Name' }, + { id: 'lastName', field: 'lastName', name: 'Last Name' }, + { id: 'age', field: 'age', name: 'Age' }, + ]; + } + + // toggle column visibility & then update columns to show changes in the grid + toggleColumnVisibility(columnName: string) { + this.sgb.slickGrid.updateColumnById(columnName, { hidden: true }); + this.sgb.slickGrid.updateColumns(); + } + + // get all columns (including `hidden` columns) + getAllColumns() { + this.sgb.slickGrid.getColumns(); + } + + // get only the visible columns + getOnlyVisibleColumns() { + this.sgb.slickGrid.getVisibleColumns(); + } +} +``` diff --git a/frameworks/angular-slickgrid/docs/TOC.md b/frameworks/angular-slickgrid/docs/TOC.md index d223637497..8b8e7523bc 100644 --- a/frameworks/angular-slickgrid/docs/TOC.md +++ b/frameworks/angular-slickgrid/docs/TOC.md @@ -35,6 +35,7 @@ * [Single Search Filter](column-functionalities/filters/single-search-filter.md) * [Formatters](column-functionalities/formatters.md) * [Sorting](column-functionalities/sorting.md) +* [Visibility](column-functionalities/visibility.md) ## Events diff --git a/frameworks/angular-slickgrid/docs/column-functionalities/visibility.md b/frameworks/angular-slickgrid/docs/column-functionalities/visibility.md new file mode 100644 index 0000000000..f63027d71e --- /dev/null +++ b/frameworks/angular-slickgrid/docs/column-functionalities/visibility.md @@ -0,0 +1,72 @@ +### Demo +[Demo](https://ghiscoding.github.io/slickgrid-universal/#/example03) / [Demo Component](https://github.com/ghiscoding/slickgrid-universal/blob/master/demos/vanilla/src/examples/example03.ts) + +### Description + +For column visibility, you can define the `hidden` property in your column definitions to initially hide some columns. You could also toggle the `hidden` property at any point in time (see below for more code usage). + +### initially hidden columns + +Let's start by demoing how to initially hide some column(s) by using the `hidden` property. + +##### define columns + +```ts +this.columns = [ + { id: 'firstName', field: 'firstName', name: 'First Name' }, + { id: 'lastName', field: 'lastName', name: 'Last Name' }, + { id: 'age', field: 'age', name: 'Age', hidden: true }, // column initially hidden +]; +``` + +### change visibility afterward + +At any point in time, you could toggle the `hidden` property by using `grid.updateColumnById()` and make sure to also call `grid.updateColumns()` so that the UI is also updated. + +##### define columns + +###### View +```ts +export class MyExample { + angularGrid: AngularGridInstance; + columns: Column[]; + + defineGrid() { + this.columns = [ + { id: 'firstName', field: 'firstName', name: 'First Name' }, + { id: 'lastName', field: 'lastName', name: 'Last Name' }, + { id: 'age', field: 'age', name: 'Age' }, + ]; + } + + angularGridReady(angularGrid: AngularGridInstance) { + this.angularGrid = angularGrid; + } + + // toggle column visibility & then update columns to show changes in the grid + toggleColumnVisibility(columnName: string) { + this.angularGrid.slickGrid.updateColumnById(columnName, { hidden: true }); + this.angularGrid.slickGrid.updateColumns(); + } + + // get all columns (including `hidden` columns) + getAllColumns() { + this.angularGrid.slickGrid.getColumns(); + } + + // get only the visible columns + getOnlyVisibleColumns() { + this.angularGrid.slickGrid.getVisibleColumns(); + } +} +``` + +###### ViewModel +```html + + +``` \ No newline at end of file diff --git a/frameworks/angular-slickgrid/src/demos/examples/example18.component.ts b/frameworks/angular-slickgrid/src/demos/examples/example18.component.ts index 87e22c2dec..dae9b619a8 100644 --- a/frameworks/angular-slickgrid/src/demos/examples/example18.component.ts +++ b/frameworks/angular-slickgrid/src/demos/examples/example18.component.ts @@ -75,6 +75,7 @@ export class Example18Component implements OnInit, OnDestroy { name: 'Title', field: 'title', columnGroup: 'Common Factor', + hidden: true, width: 70, minWidth: 50, cssClass: 'cell-title', diff --git a/frameworks/angular-slickgrid/test/cypress/e2e/example18.cy.ts b/frameworks/angular-slickgrid/test/cypress/e2e/example18.cy.ts index 855b1a5f6b..a2f067c377 100644 --- a/frameworks/angular-slickgrid/test/cypress/e2e/example18.cy.ts +++ b/frameworks/angular-slickgrid/test/cypress/e2e/example18.cy.ts @@ -1,6 +1,16 @@ describe('Example 18 - Draggable Grouping & Aggregators', () => { const preHeaders = ['Common Factor', 'Period', 'Analysis', '']; + const originalTitles = ['Duration', 'Start', 'Finish', 'Cost', '% Complete', 'Effort-Driven']; const fullTitles = ['Title', 'Duration', 'Start', 'Finish', 'Cost', '% Complete', 'Effort-Driven']; + const gridMenuTitles = [ + 'Common Factor - Title', + 'Common Factor - Duration', + 'Period - Start', + 'Period - Finish', + 'Analysis - Cost', + 'Analysis - % Complete', + 'Analysis - Effort-Driven', + ]; it('should display Example title', () => { cy.visit(`${Cypress.config('baseUrl')}/example18`); @@ -18,7 +28,7 @@ describe('Example 18 - Draggable Grouping & Aggregators', () => { cy.get('#grid18') .find('.slick-header:not(.slick-preheader-panel) .slick-header-columns') .children() - .each(($child, index) => expect($child.text()).to.eq(fullTitles[index])); + .each(($child, index) => expect($child.text()).to.eq(originalTitles[index])); }); it('should initially be grouped by "Duration" when loading the grid', () => { @@ -26,6 +36,52 @@ describe('Example 18 - Draggable Grouping & Aggregators', () => { cy.get('[data-row=1] > .slick-cell:nth(2)').should('contain', '0'); }); + it('should open Grid Menu and be able to unhide "Title" column', () => { + cy.get('button.slick-grid-menu-button').click({ force: true }); + cy.get('.slick-grid-menu:visible') + .find('.slick-column-picker-list') + .children('li:visible:nth(0)') + .children('label') + .should('contain', 'Common Factor - Title'); + // .click({ force: true }); + + cy.get('.slick-column-picker-list input[data-columnid="title"]') + .parent('.icon-checkbox-container') + .find('.sgi') + .should('have.class', 'sgi-icon-picker-uncheck'); + + cy.get('.slick-column-picker-list li') + .children() + + .each(($child, index) => { + if (index <= 5) { + expect($child.text()).to.eq(gridMenuTitles[index]); + } + }); + + cy.get('.slick-grid-menu:visible') + .find('.slick-column-picker-list') + .children('li:visible:nth(0)') + .children('label') + .should('contain', 'Common Factor - Title') + .click(); + + cy.get('.slick-column-picker-list input[data-columnid="title"]') + .parent('.icon-checkbox-container') + .find('.sgi') + .should('have.class', 'sgi-icon-picker-check'); + + 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 still be grouped by "Duration"', () => { + cy.get('[data-row=0] > .cell-title .slick-group-title').contains(/Duration: [0-9]/); + cy.get('[data-row=1] > .slick-cell:nth(2)').should('contain', '0'); + }); + it('should clear all groups with "Clear all Grouping" and no longer expect any grouping', () => { cy.get('[data-test="clear-grouping-btn"]').click(); cy.get('#grid18').find('.slick-group-toggle-all').should('be.hidden'); diff --git a/frameworks/aurelia-slickgrid/docs/TOC.md b/frameworks/aurelia-slickgrid/docs/TOC.md index 6726319c1d..8c91a4757b 100644 --- a/frameworks/aurelia-slickgrid/docs/TOC.md +++ b/frameworks/aurelia-slickgrid/docs/TOC.md @@ -34,6 +34,7 @@ * [Single Search Filter](column-functionalities/filters/single-search-filter.md) * [Formatters](column-functionalities/formatters.md) * [Sorting](column-functionalities/sorting.md) +* [Visibility](column-functionalities/visibility.md) ## Events diff --git a/frameworks/aurelia-slickgrid/docs/column-functionalities/visibility.md b/frameworks/aurelia-slickgrid/docs/column-functionalities/visibility.md new file mode 100644 index 0000000000..7cd4c58dcd --- /dev/null +++ b/frameworks/aurelia-slickgrid/docs/column-functionalities/visibility.md @@ -0,0 +1,72 @@ +### Demo +[Demo](https://ghiscoding.github.io/slickgrid-universal/#/example03) / [Demo Component](https://github.com/ghiscoding/slickgrid-universal/blob/master/demos/vanilla/src/examples/example03.ts) + +### Description + +For column visibility, you can define the `hidden` property in your column definitions to initially hide some columns. You could also toggle the `hidden` property at any point in time (see below for more code usage). + +### initially hidden columns + +Let's start by demoing how to initially hide some column(s) by using the `hidden` property. + +##### define columns + +```ts +this.columns = [ + { id: 'firstName', field: 'firstName', name: 'First Name' }, + { id: 'lastName', field: 'lastName', name: 'Last Name' }, + { id: 'age', field: 'age', name: 'Age', hidden: true }, // column initially hidden +]; +``` + +### change visibility afterward + +At any point in time, you could toggle the `hidden` property by using `grid.updateColumnById()` and make sure to also call `grid.updateColumns()` so that the UI is also updated. + +##### define columns + +###### View +```ts +export class MyExample { + aureliaGrid: AureliaGridInstance; + columns: Column[]; + + defineGrid() { + this.columns = [ + { id: 'firstName', field: 'firstName', name: 'First Name' }, + { id: 'lastName', field: 'lastName', name: 'Last Name' }, + { id: 'age', field: 'age', name: 'Age' }, + ]; + } + + aureliaGridReady(aureliaGrid: aureliaGridInstance) { + this.aureliaGrid = aureliaGrid; + } + + // toggle column visibility & then update columns to show changes in the grid + toggleColumnVisibility(columnName: string) { + this.aureliaGrid.slickGrid.updateColumnById(columnName, { hidden: true }); + this.aureliaGrid.slickGrid.updateColumns(); + } + + // get all columns (including `hidden` columns) + getAllColumns() { + this.aureliaGrid.slickGrid.getColumns(); + } + + // get only the visible columns + getOnlyVisibleColumns() { + this.aureliaGrid.slickGrid.getVisibleColumns(); + } +} +``` + +###### ViewModel +```html + + +``` diff --git a/frameworks/slickgrid-react/docs/TOC.md b/frameworks/slickgrid-react/docs/TOC.md index 181477029c..8a7f62a075 100644 --- a/frameworks/slickgrid-react/docs/TOC.md +++ b/frameworks/slickgrid-react/docs/TOC.md @@ -34,6 +34,7 @@ * [Single Search Filter](column-functionalities/filters/single-search-filter.md) * [Formatters](column-functionalities/formatters.md) * [Sorting](column-functionalities/sorting.md) +* [Visibility](column-functionalities/visibility.md) ## Events diff --git a/frameworks/slickgrid-react/docs/column-functionalities/visibility.md b/frameworks/slickgrid-react/docs/column-functionalities/visibility.md new file mode 100644 index 0000000000..7cbcd67ee5 --- /dev/null +++ b/frameworks/slickgrid-react/docs/column-functionalities/visibility.md @@ -0,0 +1,76 @@ +### Demo +[Demo](https://ghiscoding.github.io/slickgrid-universal/#/example03) / [Demo Component](https://github.com/ghiscoding/slickgrid-universal/blob/master/demos/vanilla/src/examples/example03.ts) + +### Description + +For column visibility, you can define the `hidden` property in your column definitions to initially hide some columns. You could also toggle the `hidden` property at any point in time (see below for more code usage). + +### initially hidden columns + +Let's start by demoing how to initially hide some column(s) by using the `hidden` property. + +##### define columns + +```ts +this.columns = [ + { id: 'firstName', field: 'firstName', name: 'First Name' }, + { id: 'lastName', field: 'lastName', name: 'Last Name' }, + { id: 'age', field: 'age', name: 'Age', hidden: true }, // column initially hidden +]; +``` + +### change visibility afterward + +At any point in time, you could toggle the `hidden` property by using `grid.updateColumnById()` and make sure to also call `grid.updateColumns()` so that the UI is also updated. + +##### define columns + +```tsx +const Example: React.FC = () => { + const [dataset, setDataset] = useState([]); + const [columns, setColumns] = useState([]); + const [options, setOptions] = useState(undefined); + const reactGridRef = useRef(null); + + useEffect(() => defineGrid()); + + function defineGrid() { + this.columns = [ + { id: 'firstName', field: 'firstName', name: 'First Name' }, + { id: 'lastName', field: 'lastName', name: 'Last Name' }, + { id: 'age', field: 'age', name: 'Age' }, + ]; + } + + function reactGridReady(reactGrid: SlickgridReactInstance) { + reactGridRef.current = reactGrid; + } + + // toggle column visibility & then update columns to show changes in the grid + function toggleColumnVisibility(columnName: string) { + this.aureliaGrid.slickGrid.updateColumnById(columnName, { hidden: true }); + this.aureliaGrid.slickGrid.updateColumns(); + } + + // get all columns (including `hidden` columns) + function getAllColumns() { + this.aureliaGrid.slickGrid.getColumns(); + } + + // get only the visible columns + function getOnlyVisibleColumns() { + this.aureliaGrid.slickGrid.getVisibleColumns(); + } + return !options ? '' : ( +
+ { reactGridReady(e.detail); }} + /> +
+ ); +} +``` diff --git a/frameworks/slickgrid-react/docs/slick-grid-dataview-objects/slickgrid-dataview-objects.md b/frameworks/slickgrid-react/docs/slick-grid-dataview-objects/slickgrid-dataview-objects.md index 876d0fc2bc..9ed99cc93d 100644 --- a/frameworks/slickgrid-react/docs/slick-grid-dataview-objects/slickgrid-dataview-objects.md +++ b/frameworks/slickgrid-react/docs/slick-grid-dataview-objects/slickgrid-dataview-objects.md @@ -29,16 +29,16 @@ const Example: React.FC = () => { /** Change dynamically `autoEdit` grid options */ function setAutoEdit(isAutoEdit) { setIsAutoEdit(isAutoEdit); - reactGridRef.current?.setOptions({ autoEdit: isAutoEdit }); // change the grid option dynamically + reactGridRef.current?.slickGrid.setOptions({ autoEdit: isAutoEdit }); // change the grid option dynamically return true; } function collapseAllGroups() { - reactGridRef.current?.collapseAllGroups(); + reactGridRef.current?.slickGrid.collapseAllGroups(); } function expandAllGroups() { - reactGridRef.current?.expandAllGroups(); + reactGridRef.current?.slickGrid.expandAllGroups(); } return !options ? '' : ( diff --git a/frameworks/slickgrid-vue/docs/TOC.md b/frameworks/slickgrid-vue/docs/TOC.md index 9b6cc4d910..12f4c0acd0 100644 --- a/frameworks/slickgrid-vue/docs/TOC.md +++ b/frameworks/slickgrid-vue/docs/TOC.md @@ -34,6 +34,7 @@ * [Single Search Filter](column-functionalities/filters/single-search-filter.md) * [Formatters](column-functionalities/formatters.md) * [Sorting](column-functionalities/sorting.md) +* [Visibility](column-functionalities/visibility.md) ## Events diff --git a/frameworks/slickgrid-vue/docs/column-functionalities/visibility.md b/frameworks/slickgrid-vue/docs/column-functionalities/visibility.md new file mode 100644 index 0000000000..5da6f15ff4 --- /dev/null +++ b/frameworks/slickgrid-vue/docs/column-functionalities/visibility.md @@ -0,0 +1,82 @@ +### Demo +[Demo](https://ghiscoding.github.io/slickgrid-universal/#/example03) / [Demo Component](https://github.com/ghiscoding/slickgrid-universal/blob/master/demos/vanilla/src/examples/example03.ts) + +### Description + +For column visibility, you can define the `hidden` property in your column definitions to initially hide some columns. You could also toggle the `hidden` property at any point in time (see below for more code usage). + +### initially hidden columns + +Let's start by demoing how to initially hide some column(s) by using the `hidden` property. + +##### define columns + +```ts +this.columns = [ + { id: 'firstName', field: 'firstName', name: 'First Name' }, + { id: 'lastName', field: 'lastName', name: 'Last Name' }, + { id: 'age', field: 'age', name: 'Age', hidden: true }, // column initially hidden +]; +``` + +### change visibility afterward + +At any point in time, you could toggle the `hidden` property by using `grid.updateColumnById()` and make sure to also call `grid.updateColumns()` so that the UI is also updated. + +##### define columns + +```vue + + + +``` diff --git a/packages/common/src/core/__tests__/slickDataView.spec.ts b/packages/common/src/core/__tests__/slickDataView.spec.ts index 770b5a6b90..779a72187d 100644 --- a/packages/common/src/core/__tests__/slickDataView.spec.ts +++ b/packages/common/src/core/__tests__/slickDataView.spec.ts @@ -1114,6 +1114,7 @@ describe('SlickDatView core file', () => { }, }, cssClasses: 'slick-group slick-group-level-0', + isGroup: true, focusable: true, formatter: undefined, selectable: false, @@ -1164,6 +1165,7 @@ describe('SlickDatView core file', () => { cssClasses: 'slick-group-totals slick-group-level-1', editorClass: null, focusable: false, + isGroup: true, formatter: expect.anything(), selectable: false, }); @@ -1260,6 +1262,7 @@ describe('SlickDatView core file', () => { }, }, cssClasses: 'slick-group slick-group-level-0', + isGroup: true, focusable: true, formatter: undefined, selectable: false, diff --git a/packages/common/src/core/__tests__/slickGrid.spec.ts b/packages/common/src/core/__tests__/slickGrid.spec.ts index 34f12382d6..910bc1f74a 100644 --- a/packages/common/src/core/__tests__/slickGrid.spec.ts +++ b/packages/common/src/core/__tests__/slickGrid.spec.ts @@ -2122,6 +2122,36 @@ describe('SlickGrid core file', () => { expect(slickRowElms[0].classList.contains('highlight-animate')).toBeFalsy(); expect(slickRowElms[1].classList.contains('highlight-animate')).toBeFalsy(); }); + + it('should return the correct header column by id when a hidden column precedes it', () => { + const columns = [ + { id: 'a', field: 'a', name: 'A', hidden: true }, + { id: 'b', field: 'b', name: 'B' }, + { id: 'c', field: 'c', name: 'C' }, + ] as Column[]; + const rows = [{ id: 0, a: 'x', b: 'y', c: 'z' }]; + + grid = new SlickGrid(container, rows, columns, defaultOptions); + grid.init(); + + const headerElm = grid.getHeaderColumn('b'); + + expect(headerElm).toBeInstanceOf(HTMLDivElement); + expect(headerElm.dataset.id).toBe('b'); + }); + + it('should return undefined from getHeaderColumn fallback when target column is hidden', () => { + const columns = [ + { id: 'a', field: 'a', name: 'A', hidden: true }, + { id: 'b', field: 'b', name: 'B' }, + ] as Column[]; + const rows = [{ id: 0, a: 'x', b: 'y' }]; + + grid = new SlickGrid(container, rows, columns, defaultOptions); + grid.init(); + + expect(grid.getHeaderColumn(0)).toBeUndefined(); + }); }); describe('flashCell() method', () => { diff --git a/packages/common/src/core/slickGrid.ts b/packages/common/src/core/slickGrid.ts index c21d491b3d..db6fa8518f 100755 --- a/packages/common/src/core/slickGrid.ts +++ b/packages/common/src/core/slickGrid.ts @@ -1640,12 +1640,19 @@ export class SlickGrid = Column, O e */ getHeaderColumn(columnIdOrIdx: number | string): HTMLDivElement { const idx = typeof columnIdOrIdx === 'number' ? columnIdOrIdx : this.getColumnIndex(columnIdOrIdx); - // prettier-ignore - const targetHeader = this.hasFrozenColumns() ? ((idx <= this._options.frozenColumn!) ? this._headerL : this._headerR) : this._headerL; - // prettier-ignore - const targetIndex = this.hasFrozenColumns() ? ((idx <= this._options.frozenColumn!) ? idx : idx - this._options.frozenColumn! - 1) : idx; + const targetHeader = this.hasFrozenColumns() ? (idx <= this._options.frozenColumn! ? this._headerL : this._headerR) : this._headerL; + const targetIndex = this.hasFrozenColumns() ? (idx <= this._options.frozenColumn! ? idx : idx - this._options.frozenColumn! - 1) : idx; + const directMatch = targetHeader.children[targetIndex] as HTMLDivElement | undefined; + const targetColumnId = String(this.columns[idx]?.id ?? columnIdOrIdx); + const directMatchColumn = Utils.storage.get(directMatch, 'column') as C | undefined; + if (directMatch && (directMatch.dataset?.id === targetColumnId || String(directMatchColumn?.id) === targetColumnId)) { + return directMatch; + } - return targetHeader.children[targetIndex] as HTMLDivElement; + return ( + (Array.from(targetHeader.children).find((child) => (child as HTMLDivElement).dataset?.id === targetColumnId) as HTMLDivElement) || + (undefined as any) + ); } /** Get the Header Row DOM element */ @@ -4159,7 +4166,7 @@ export class SlickGrid = Column, O e for (let i = 0, ii = columnCount; i < ii; i++) { isRenderCell = true; m = this.columns[i]; - if (m && !m.hidden) { + if (m && (!m.hidden || metadata?.isGroup)) { colspan = 1; rowspan = 1; columnData = null; @@ -5256,15 +5263,15 @@ export class SlickGrid = Column, O e // Render missing cells. cellsAdded = 0; - let metadata = this.getItemMetadaWhenExists(row); - metadata = metadata?.columns as ItemMetadata; + const metadata = this.getItemMetadaWhenExists(row); + const metadataCol = metadata?.columns; const d = this.getDataItem(row); let isFullColspan = false; // TODO: shorten this loop (index? heuristics? binary search?) for (let i = 0, ii = columnCount; i < ii; i++) { - if (this.columns[i] && !this.columns[i].hidden) { + if (this.columns[i] && (!this.columns[i].hidden || metadata?.isGroup)) { // Cells to the right are outside the range. if (this.columnPosLeft[i] > range.rightPx) { break; @@ -5278,8 +5285,8 @@ export class SlickGrid = Column, O e colspan = 1; columnData = null; - if (metadata) { - columnData = metadata[this.columns[i].id as keyof ItemMetadata] || (metadata as any)[i]; + if (metadataCol) { + columnData = metadataCol[this.columns[i].id as keyof ItemMetadata] || (metadataCol as any)[i]; colspan = columnData?.colspan ?? 1; if (colspan === '*') { colspan = ii - i; diff --git a/packages/common/src/extensions/__tests__/slickGroupItemMetadataProvider.spec.ts b/packages/common/src/extensions/__tests__/slickGroupItemMetadataProvider.spec.ts index 9b9f1feb0e..164ff36cd1 100644 --- a/packages/common/src/extensions/__tests__/slickGroupItemMetadataProvider.spec.ts +++ b/packages/common/src/extensions/__tests__/slickGroupItemMetadataProvider.spec.ts @@ -235,6 +235,7 @@ describe('GroupItemMetadataProvider Service', () => { const output = service.getGroupRowMetadata({ count: 12, level: undefined as any, groupingKey: 'age', value: 33 }, 0); expect(output).toEqual({ selectable: false, + isGroup: true, focusable: mockOptions.groupFocusable, cssClasses: `${mockOptions.groupCssClass} slick-group-level-0`, formatter: service.getOptions().totalsFormatter, @@ -255,6 +256,7 @@ describe('GroupItemMetadataProvider Service', () => { const output = service.getGroupRowMetadata({ count: 12, level: 2, groupingKey: 'age', value: 33 }, 0); expect(output).toEqual({ selectable: false, + isGroup: true, focusable: mockOptions.groupFocusable, cssClasses: `${mockOptions.groupCssClass} slick-group-level-2`, formatter: undefined, @@ -277,6 +279,7 @@ describe('GroupItemMetadataProvider Service', () => { const output = service.getTotalsRowMetadata({ group: { count: 12, level: undefined as any, groupingKey: 'age', value: 33 } }, 0); expect(output).toEqual({ editorClass: null, + isGroup: true, selectable: false, focusable: mockOptions.totalsFocusable, cssClasses: `groupy-totals slick-group-level-0`, @@ -288,6 +291,7 @@ describe('GroupItemMetadataProvider Service', () => { const output = service.getTotalsRowMetadata({ group: { count: 12, level: 3, groupingKey: 'age', value: 33 } }, 0); expect(output).toEqual({ editorClass: null, + isGroup: true, focusable: false, selectable: false, cssClasses: `slick-group-totals slick-group-level-3`, diff --git a/packages/common/src/extensions/slickGroupItemMetadataProvider.ts b/packages/common/src/extensions/slickGroupItemMetadataProvider.ts index f074571d5d..fe69718121 100644 --- a/packages/common/src/extensions/slickGroupItemMetadataProvider.ts +++ b/packages/common/src/extensions/slickGroupItemMetadataProvider.ts @@ -9,7 +9,6 @@ import { } from '../core/index.js'; import type { Column, - Formatter, GridOption, GroupingFormatterItem, GroupItemMetadataProviderOption, @@ -97,6 +96,7 @@ export class SlickGroupItemMetadataProvider implements SlickPlugin { focusable: this._options.groupFocusable, cssClasses: `${this._options.groupCssClass} slick-group-level-${item?.level || 0}`, formatter: (this._options.includeHeaderTotals && this._options.totalsFormatter) || undefined, + isGroup: true, columns: { 0: { colspan: this._options.includeHeaderTotals ? '1' : '*', @@ -108,19 +108,14 @@ export class SlickGroupItemMetadataProvider implements SlickPlugin { } // prettier-ignore - getTotalsRowMetadata(item: { group: GroupingFormatterItem }, _row: number): { - selectable: boolean; - focusable: boolean | undefined; - cssClasses: string; - formatter: Formatter | undefined; - editorClass: null; - } { + getTotalsRowMetadata(item: { group: GroupingFormatterItem }, _row: number): ItemMetadata { return { selectable: false, focusable: this._options.totalsFocusable, cssClasses: `${this._options.totalsCssClass} slick-group-level-${item?.group?.level || 0}`, formatter: this._options.totalsFormatter, editorClass: null, + isGroup: true }; } diff --git a/packages/common/src/interfaces/itemMetadata.interface.ts b/packages/common/src/interfaces/itemMetadata.interface.ts index 1a1d2311cf..6d9da38077 100644 --- a/packages/common/src/interfaces/itemMetadata.interface.ts +++ b/packages/common/src/interfaces/itemMetadata.interface.ts @@ -1,4 +1,4 @@ -import type { Column, Formatter, GroupTotalsFormatter } from './index.js'; +import type { Column, Editor, EditorConstructor, Formatter, GroupTotalsFormatter } from './index.js'; export type ColumnMetadata = Pick< Column, @@ -32,4 +32,10 @@ export interface ItemMetadata { // properties describing metadata related to individual columns [colIdOrIdx in string | number]: ColumnMetadata; }; + + /** Any inline Editor class or Editor constructor, this is mostly used internally by SlickGrid */ + editorClass?: Editor | EditorConstructor | null; + + /** defined when the metadata is defined to be a group */ + isGroup?: boolean; } diff --git a/test/cypress/e2e/example03.cy.ts b/test/cypress/e2e/example03.cy.ts index f4296818fe..a89b87ca08 100644 --- a/test/cypress/e2e/example03.cy.ts +++ b/test/cypress/e2e/example03.cy.ts @@ -1,6 +1,18 @@ describe('Example 03 - Draggable Grouping', () => { const preHeaders = ['', 'Common Factor', 'Period', 'Analysis', '']; + const originalTitles = ['', 'Duration', 'Start', 'Finish', 'Cost', '% Complete', 'Effort-Driven', 'Action']; const fullTitles = ['', 'Title', 'Duration', 'Start', 'Finish', 'Cost', '% Complete', 'Effort-Driven', 'Action']; + const gridMenuTitles = [ + '', + 'Common Factor - Title', + 'Common Factor - Duration', + 'Period - Start', + 'Period - Finish', + 'Analysis - Cost', + 'Analysis - % Complete', + 'Analysis - Effort-Driven', + 'Action', + ]; const GRID_ROW_HEIGHT = 33; it('should display Example title', () => { @@ -20,7 +32,7 @@ describe('Example 03 - Draggable Grouping', () => { cy.get('.grid3') .find('.slick-header:not(.slick-preheader-panel) .slick-header-columns') .children() - .each(($child, index) => expect($child.text()).to.eq(fullTitles[index])); + .each(($child, index) => expect($child.text()).to.eq(originalTitles[index])); }); it('should initially be grouped by "Duration" when loading the grid', () => { @@ -30,6 +42,54 @@ describe('Example 03 - Draggable Grouping', () => { cy.get(`[style="transform: translateY(${GRID_ROW_HEIGHT * 1}px);"] > .slick-cell:nth(2)`).should('contain', '0'); }); + it('should open Grid Menu and be able to unhide "Title" column', () => { + cy.get('button.slick-grid-menu-button').click({ force: true }); + cy.get('.slick-grid-menu:visible') + .find('.slick-column-picker-list') + .children('li:visible:nth(0)') + .children('label') + .should('contain', 'Common Factor - Title'); + // .click({ force: true }); + + cy.get('.slick-column-picker-list input[data-columnid="title"]') + .parent('.icon-checkbox-container') + .find('.sgi') + .should('have.class', 'sgi-icon-picker-uncheck'); + + cy.get('.slick-column-picker-list li') + .children() + + .each(($child, index) => { + if (index <= 5) { + expect($child.text()).to.eq(gridMenuTitles[index]); + } + }); + + cy.get('.slick-grid-menu:visible') + .find('.slick-column-picker-list') + .children('li:visible:nth(0)') + .children('label') + .should('contain', 'Common Factor - Title') + .click(); + + cy.get('.slick-column-picker-list input[data-columnid="title"]') + .parent('.icon-checkbox-container') + .find('.sgi') + .should('have.class', 'sgi-icon-picker-check'); + + cy.get('.grid3') + .find('.slick-header:not(.slick-preheader-panel) .slick-header-columns') + .children() + .each(($child, index) => expect($child.text()).to.eq(fullTitles[index])); + }); + + it('should still be grouped by "Duration"', () => { + cy.get(`[style="transform: translateY(${GRID_ROW_HEIGHT * 0}px);"] > .slick-cell:nth(0) .slick-group-title`).contains( + /Duration: [0-9]/ + ); + cy.get(`[style="transform: translateY(${GRID_ROW_HEIGHT * 1}px);"] > .slick-cell:nth(2)`).should('contain', '0'); + }); + it('should clear all groups with "Clear all Grouping" and no longer expect any grouping', () => { cy.get('[data-test="clear-grouping-btn"]').click(); cy.get('.grid3').find('.slick-group-toggle-all').should('be.hidden');