Skip to content

Commit

Permalink
fix(gridMenu): column picker list should include grouped header titles (
Browse files Browse the repository at this point in the history
#460)

* fix(gridMenu): column picker list should include grouped header titles
- add "headerColumnValueExtractor" callback in the global grid options to deal with grouped header titles
- also add 2 other new options "contentMinWidth" and "useClickToRepositionMenu"
- this requires a new SlickGrid core version with these new options
  • Loading branch information
ghiscoding-SE authored May 19, 2020
1 parent 0367625 commit e4a34a0
Show file tree
Hide file tree
Showing 6 changed files with 284 additions and 10 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@
"lodash.isequal": "^4.5.0",
"moment-mini": "^2.22.1",
"rxjs": "^6.3.3",
"slickgrid": "^2.4.22",
"slickgrid": "^2.4.23",
"text-encoding-utf-8": "^1.0.2"
},
"devDependencies": {
Expand Down
13 changes: 9 additions & 4 deletions src/app/examples/grid-menu.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,10 +79,15 @@ export class GridMenuComponent implements OnInit {
enableFiltering: true,
enableCellNavigation: true,
gridMenu: {
menuUsabilityOverride: (args) => {
// we could disable the menu entirely by returning false
return true;
},
// we could disable the menu entirely by returning false depending on some code logic
menuUsabilityOverride: (args) => true,

// use the click event position to reposition the grid menu (defaults to false)
// basically which offset do we want to use for reposition the grid menu,
// option1 is where we clicked (true) or option2 is where the icon button is located (false and is the defaults)
// you probably want to set this to True if you use an external grid menu button BUT set to False when using default grid menu
useClickToRepositionMenu: true,

// all titles optionally support translation keys, if you wish to use that feature then use the title properties with the 'Key' suffix (e.g: titleKey)
// example "customTitle" for a plain string OR "customTitleKey" to use a translation key
customTitleKey: 'CUSTOM_COMMANDS',
Expand Down
13 changes: 11 additions & 2 deletions src/app/modules/angular-slickgrid/global-grid-options.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { DelimiterType, FileType, GridOption, OperatorType } from './models/index';
import { Column, DelimiterType, FileType, GridOption, OperatorType } from './models/index';
import { Filters } from './filters/index';

/** Global Grid Options Defaults */
Expand Down Expand Up @@ -133,7 +133,16 @@ export const GlobalGridOptions: Partial<GridOption> = {
iconToggleFilterCommand: 'fa fa-random',
iconTogglePreHeaderCommand: 'fa fa-random',
menuWidth: 16,
resizeOnShowHeaderRow: true
resizeOnShowHeaderRow: true,
useClickToRepositionMenu: false, // use icon location to reposition instead
headerColumnValueExtractor: (column: Column) => {
const headerGroup = column && column.columnGroup || '';
if (headerGroup) {
// when using Column Header Grouping, we'll prefix the column group title
return headerGroup + ' - ' + column.name;
}
return column && column.name || '';
}
},
headerMenu: {
autoAlign: true,
Expand Down
11 changes: 10 additions & 1 deletion src/app/modules/angular-slickgrid/models/gridMenu.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import { MenuCallbackArgs } from './menuCallbackArgs.interface';
import { MenuCommandItemCallbackArgs } from './menuCommandItemCallbackArgs.interface';

export interface GridMenu {
/** Defaults to 0 (auto), minimum width of grid menu content (command, column list) */
contentMinWidth?: number;

/** Array of Custom Items (title, command, disabled, ...) */
customItems?: Array<GridMenuItem | 'divider'>;

Expand Down Expand Up @@ -88,7 +91,7 @@ export interface GridMenu {
/** Defaults to False, which leads to leaving the menu open after a click */
leaveOpen?: boolean;

/** Defaults to 16 pixels (only the number), which is the width in pixels of the Grid Menu icon */
/** Defaults to 16 pixels (only the number), which is the width in pixels of the Grid Menu icon container */
menuWidth?: number;

/** Defaults to False, which will resize the Header Row and remove the width of the Grid Menu icon from it's total width. */
Expand All @@ -100,9 +103,15 @@ export interface GridMenu {
/** Same as "syncResizeTitle", except that it's a translation key which can be used on page load and/or when switching locale */
syncResizeTitleKey?: string;

/** Defaults to true, Use the Click offset to reposition the Grid Menu, when set to False it will use the icon offset to reposition the grid menu */
useClickToRepositionMenu?: boolean;

// --
// action/override callbacks

/** Callback method to override the column name output used by the ColumnPicker/GridMenu. */
headerColumnValueExtractor?: (column: Column) => string;

/** Callback method that user can override the default behavior of enabling/disabling an item from the list. */
menuUsabilityOverride?: (args: MenuCallbackArgs) => boolean;

Expand Down
254 changes: 252 additions & 2 deletions test/cypress/integration/example01.spec.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/// <reference types="cypress" />

describe('Example 1 - Basic Grids', () => {
const titles = ['Title', 'Duration (days)', '% Complete', 'Start', 'Finish', 'Effort Driven'];
const fullTitles = ['Title', 'Duration (days)', '% Complete', 'Start', 'Finish', 'Effort Driven'];

it('should display Example title', () => {
cy.visit(`${Cypress.config('baseUrl')}`);
Expand All @@ -26,7 +26,7 @@ describe('Example 1 - Basic Grids', () => {
cy.get('#slickGridContainer-grid1')
.find('.slick-header-columns')
.children()
.each(($child, index) => expect($child.text()).to.eq(titles[index]));
.each(($child, index) => expect($child.text()).to.eq(fullTitles[index]));
});

it('should hover over the Title column and click on "Sort Descending" command', () => {
Expand Down Expand Up @@ -198,4 +198,254 @@ describe('Example 1 - Basic Grids', () => {
cy.get('[data-test=total-items]')
.contains('995');
});

it('should open the Grid Menu on 1st Grid and expect all Columns to be checked', () => {
let gridUid = '';
cy.get('#grid1')
.find('button.slick-gridmenu-button')
.click({ force: true });

cy.get('#grid1')
.should(($grid) => {
const classes = $grid.prop('className').split(' ');
gridUid = classes.find(className => /slickgrid_.*/.test(className));
expect(gridUid).to.not.be.null;
})
.then(() => {
cy.get(`.slick-gridmenu.${gridUid}`)
.find('.slick-gridmenu-list')
.children('li')
.each(($child, index) => {
if (index <= 5) {
const $input = $child.children('input');
const $label = $child.children('label');
expect($input.attr('checked')).to.eq('checked');
expect($label.text()).to.eq(fullTitles[index]);
}
});
});
});

it('should then hide "Title" column from same 1st Grid and expect the column to be removed from 1st Grid', () => {
const newColumnList = ['Duration (days)', '% Complete', 'Start', 'Finish', 'Effort Driven'];
cy.get('#grid1')
.get('.slick-gridmenu:visible')
.find('.slick-gridmenu-list')
.children('li:visible:nth(0)')
.children('label')
.should('contain', 'Title')
.click({ force: true });

cy.get('#grid1')
.get('.slick-gridmenu:visible')
.find('span.close')
.click({ force: true });

cy.get('#grid1')
.find('.slick-header-columns')
.children()
.each(($child, index) => expect($child.text()).to.eq(newColumnList[index]));
});

it('should open the Grid Menu off 2nd Grid and expect all Columns to still be all checked', () => {
let gridUid = '';
cy.get('#grid2')
.find('button.slick-gridmenu-button')
.click({ force: true });

cy.get('#grid2')
.should(($grid) => {
const classes = $grid.prop('className').split(' ');
gridUid = classes.find(className => /slickgrid_.*/.test(className));
expect(gridUid).to.not.be.null;
})
.then(() => {
cy.get(`.slick-gridmenu.${gridUid}`)
.find('.slick-gridmenu-list')
.children('li')
.each(($child, index) => {
if (index <= 5) {
const $input = $child.children('input');
const $label = $child.children('label');
expect($input.attr('checked')).to.eq('checked');
expect($label.text()).to.eq(fullTitles[index]);
}
});
});
});

it('should then hide "% Complete" column from this same 2nd Grid and expect the column to be removed from 2nd Grid', () => {
const newColumnList = ['Title', 'Duration (days)', 'Start', 'Finish', 'Effort Driven'];
cy.get('#grid2')
.get('.slick-gridmenu:visible')
.find('.slick-gridmenu-list')
.children('li:visible:nth(2)')
.children('label')
.should('contain', '% Complete')
.click({ force: true });

cy.get('#grid2')
.get('.slick-gridmenu:visible')
.find('span.close')
.click({ force: true });

cy.get('#grid2')
.find('.slick-header-columns')
.children()
.each(($child, index) => expect($child.text()).to.eq(newColumnList[index]));
});

it('should go back to 1st Grid and open its Grid Menu and we expect this grid to stil have the "Title" column be hidden (unchecked)', () => {
cy.get('#grid1')
.find('button.slick-gridmenu-button')
.click({ force: true });

cy.get('.slick-gridmenu-list')
.children('li')
.each(($child, index) => {
if (index <= 5) {
const $input = $child.children('input');
const $label = $child.children('label');
if ($label.text() === 'Title') {
expect($input.attr('checked')).to.eq(undefined);
} else {
expect($input.attr('checked')).to.eq('checked');
}
expect($label.text()).to.eq(fullTitles[index]);
}
});
});

it('should hide "Start" column from 1st Grid and expect to have 2 hidden columns (Title, Start)', () => {
const newColumnList = ['Duration (days)', '% Complete', 'Finish', 'Effort Driven'];
cy.get('#grid1')
.get('.slick-gridmenu:visible')
.find('.slick-gridmenu-list')
.children('li:visible:nth(3)')
.children('label')
.should('contain', 'Start')
.click({ force: true });

cy.get('#grid1')
.get('.slick-gridmenu:visible')
.find('span.close')
.click({ force: true });

cy.get('#grid1')
.find('.slick-header-columns')
.children()
.each(($child, index) => expect($child.text()).to.eq(newColumnList[index]));
});

it('should open Column Picker of 2nd Grid and show the "% Complete" column back to visible', () => {
cy.get('#grid2')
.find('.slick-header-column')
.first()
.trigger('mouseover')
.trigger('contextmenu')
.invoke('show');

cy.get('.slick-columnpicker')
.find('.slick-columnpicker-list')
.children()
.each(($child, index) => {
if (index <= 5) {
expect($child.text()).to.eq(fullTitles[index]);
}
});

cy.get('.slick-columnpicker')
.find('.slick-columnpicker-list')
.children('li:nth-child(3)')
.children('label')
.should('contain', '% Complete')
.click();

cy.get('#grid2')
.find('.slick-header-columns')
.children()
.each(($child, index) => {
if (index <= 5) {
expect($child.text()).to.eq(fullTitles[index]);
}
});

cy.get('#grid2')
.get('.slick-columnpicker:visible')
.find('span.close')
.trigger('click')
.click();
});

it('should open the Grid Menu on 2nd Grid and expect all Columns to be checked', () => {
let gridUid = '';
cy.get('#grid2')
.find('button.slick-gridmenu-button')
.click({ force: true });

cy.get('#grid2')
.should(($grid) => {
const classes = $grid.prop('className').split(' ');
gridUid = classes.find(className => /slickgrid_.*/.test(className));
expect(gridUid).to.not.be.null;
})
.then(() => {
cy.get(`.slick-gridmenu.${gridUid}`)
.find('.slick-gridmenu-list')
.children('li')
.each(($child, index) => {
if (index <= 5) {
const $input = $child.children('input');
const $label = $child.children('label');
expect($input.attr('checked')).to.eq('checked');
expect($label.text()).to.eq(fullTitles[index]);
}
});
});
});

it('should still expect 1st Grid to be unchanged from previous state and still have only 4 columns shown', () => {
const newColumnList = ['Duration (days)', '% Complete', 'Finish', 'Effort Driven'];

cy.get('#grid1')
.find('.slick-header-columns')
.children()
.each(($child, index) => expect($child.text()).to.eq(newColumnList[index]));
});

it('should open the Grid Menu on 1st Grid and also expect to only have 4 columns checked (visible)', () => {
let gridUid = '';
cy.get('#grid1')
.find('button.slick-gridmenu-button')
.click({ force: true });

cy.get('#grid1')
.should(($grid) => {
const classes = $grid.prop('className').split(' ');
gridUid = classes.find(className => /slickgrid_.*/.test(className));
expect(gridUid).to.not.be.null;
})
.then(() => {
cy.get(`.slick-gridmenu.${gridUid}`)
.find('.slick-gridmenu-list')
.children('li')
.each(($child, index) => {
if (index <= 5) {
const $input = $child.children('input');
const $label = $child.children('label');
if ($label.text() === 'Title' || $label.text() === 'Start') {
expect($input.attr('checked')).to.eq(undefined);
} else {
expect($input.attr('checked')).to.eq('checked');
}
expect($label.text()).to.eq(fullTitles[index]);
}
});
});

cy.get('#grid1')
.get('.slick-gridmenu:visible')
.find('span.close')
.click({ force: true });
});
});
1 change: 1 addition & 0 deletions test/jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ module.exports = {
coverageDirectory: './test/jest-coverage/',
coveragePathIgnorePatterns: [
'example-data.js',
'global-grid-options.ts',
'jest-global-mocks.ts',
'jest-pretest.ts',
'polyfills.ts',
Expand Down

0 comments on commit e4a34a0

Please sign in to comment.