Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add sub-menu(s) to CellMenu & ContextMenu plugins #867

Merged
merged 13 commits into from
Oct 20, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
182 changes: 177 additions & 5 deletions cypress/e2e/example-plugin-contextmenu.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ describe('Example - Context Menu & Cell Menu', () => {
.contains('Action');

cy.get('.slick-cell-menu')
.should('not.exist')
.should('not.exist');
});

it('should open the Context Menu and expect onBeforeMenuShow then onAfterMenuShow to show in the console log', () => {
Expand Down Expand Up @@ -147,11 +147,11 @@ describe('Example - Context Menu & Cell Menu', () => {
.should('exist');
});

it('should change the Effort Driven to "False" in that same Action and then expect the "Command 2" to enabled and clickable', () => {
it('should change the Effort Driven to "False" in that same Action and then expect the "Command 2" to be enabled and clickable', () => {
const stub = cy.stub();
cy.on('window:alert', stub);

cy.get('.slick-cell-menu .slick-cell-menu-option-list')
cy.get('.slick-cell-menu.level-0 .slick-cell-menu-option-list')
.find('.slick-cell-menu-item')
.contains('False')
.click();
Expand All @@ -167,6 +167,72 @@ describe('Example - Context Menu & Cell Menu', () => {
.then(() => expect(stub.getCall(0)).to.be.calledWith('Command 2'));
});

it('should change the Effort Driven to "True" by using sub-options in that same Action and then expect the "Command 2" to be disabled and not clickable and "Delete Row" to not be shown', () => {
const stub = cy.stub();
cy.on('window:alert', stub);

cy.get('#myGrid')
.find('.slick-row .slick-cell:nth(7)')
.contains('Action')
.click({ force: true });

cy.get('.slick-cell-menu.level-0 .slick-cell-menu-option-list')
.find('.slick-cell-menu-item')
.contains('Sub-Options')
.click();

cy.get('.slick-cell-menu.level-1 .slick-cell-menu-option-list')
.find('.slick-cell-menu-item')
.contains('True')
.click();

cy.get('#myGrid')
.find('.slick-row .slick-cell:nth(7)')
.contains('Action')
.click({ force: true });

cy.get('.slick-cell-menu .slick-cell-menu-item.slick-cell-menu-item-disabled')
.contains('Command 2');

cy.get('.slick-cell-menu .slick-cell-menu-item')
.contains('Delete Row')
.should('not.exist');
});

it('should change the Effort Driven back to "False" by using sub-options in that same Action and then expect the "Command 2" to enabled and clickable and also show "Delete Row" command', () => {
const stub = cy.stub();
cy.on('window:alert', stub);

cy.get('#myGrid')
.find('.slick-row .slick-cell:nth(7)')
.contains('Action')
.click({ force: true });

cy.get('.slick-cell-menu.level-0 .slick-cell-menu-option-list')
.find('.slick-cell-menu-item')
.contains('Sub-Options')
.click();

cy.get('.slick-cell-menu.level-1 .slick-cell-menu-option-list')
.find('.slick-cell-menu-item')
.contains('False')
.click();

cy.get('#myGrid')
.find('.slick-row .slick-cell:nth(7)')
.contains('Action')
.click({ force: true });

cy.get('.slick-cell-menu .slick-cell-menu-item')
.contains('Delete Row')
.should('exist');

cy.get('.slick-cell-menu .slick-cell-menu-item')
.contains('Command 2')
.click()
.then(() => expect(stub.getCall(0)).to.be.calledWith('Command 2'));
});

it('should expect the Context Menu now have the "Help" menu when Effort Driven is set to False', () => {
const commands = ['Copy Cell Value', 'Delete Row', '', 'Help', 'Command (always disabled)'];

Expand Down Expand Up @@ -213,6 +279,112 @@ describe('Example - Context Menu & Cell Menu', () => {
.should('not.exist');
});

it('should be able to open Cell Menu and click on Export->PDF sub-commands to see 1 cell menu + 1 sub-menu then clicking on PDF should call alert action', () => {
const subCommands = ['PDF', 'Excel'];
const stub = cy.stub();
cy.on('window:alert', stub);

cy.get('#myGrid')
.find('.slick-row .slick-cell:nth(7)')
.contains('Action')
.click({ force: true });

cy.get('.slick-cell-menu.level-0 .slick-cell-menu-command-list')
.find('.slick-cell-menu-item')
.contains('Export')
.click();

cy.get('.slick-cell-menu.level-1 .slick-cell-menu-command-list')
.should('exist')
.find('.slick-cell-menu-item')
.each(($command, index) => expect($command.text()).to.eq(subCommands[index]));

cy.get('.slick-cell-menu.level-1 .slick-cell-menu-command-list')
.find('.slick-cell-menu-item')
.contains('PDF')
.click()
.then(() => expect(stub.getCall(0)).to.be.calledWith('Exporting as PDF'));
});

it('should be able to open Cell Menu and click on Export->Excel-> sub-commands to see 1 cell menu + 1 sub-menu then clicking on PDF should call alert action', () => {
const subCommands1 = ['PDF', 'Excel'];
const subCommands2 = ['Excel (csv)', 'Excel (xls)'];
const stub = cy.stub();
cy.on('window:alert', stub);

cy.get('#myGrid')
.find('.slick-row .slick-cell:nth(7)')
.contains('Action')
.click({ force: true });

cy.get('.slick-cell-menu.level-0 .slick-cell-menu-command-list')
.find('.slick-cell-menu-item')
.contains('Export')
.click();

cy.get('.slick-cell-menu.level-1 .slick-cell-menu-command-list')
.should('exist')
.find('.slick-cell-menu-item')
.each(($command, index) => expect($command.text()).to.eq(subCommands1[index]));

cy.get('.slick-cell-menu.level-1 .slick-cell-menu-command-list')
.find('.slick-cell-menu-item')
.contains('Excel')
.click();

cy.get('.slick-cell-menu.level-2 .slick-cell-menu-command-list')
.should('exist')
.find('.slick-cell-menu-item')
.each(($command, index) => expect($command.text()).to.eq(subCommands2[index]));

cy.get('.slick-cell-menu.level-2 .slick-cell-menu-command-list')
.find('.slick-cell-menu-item')
.contains('Excel (xls)')
.click()
.then(() => expect(stub.getCall(0)).to.be.calledWith('Exporting as Excel (xls)'));
});

it('should open Export->Excel sub-menu & open again Sub-Options on top and expect sub-menu to be recreated with that Sub-Options list instead of the Export->Excel list', () => {
const subCommands1 = ['PDF', 'Excel'];
const subCommands2 = ['Excel (csv)', 'Excel (xls)'];
const subOptions = ['True', 'False'];

cy.get('#myGrid')
.find('.slick-row .slick-cell:nth(7)')
.contains('Action')
.click({ force: true });

cy.get('.slick-cell-menu.level-0 .slick-cell-menu-command-list')
.find('.slick-cell-menu-item')
.contains('Export')
.click();

cy.get('.slick-cell-menu.level-1 .slick-cell-menu-command-list')
.should('exist')
.find('.slick-cell-menu-item')
.each(($command, index) => expect($command.text()).to.eq(subCommands1[index]));

cy.get('.slick-cell-menu.level-1 .slick-cell-menu-command-list')
.find('.slick-cell-menu-item')
.contains('Excel')
.click();

cy.get('.slick-cell-menu.level-2 .slick-cell-menu-command-list')
.should('exist')
.find('.slick-cell-menu-item')
.each(($command, index) => expect($command.text()).to.eq(subCommands2[index]));

cy.get('.slick-cell-menu.level-0 .slick-cell-menu-option-list')
.find('.slick-cell-menu-item')
.contains('Sub-Options')
.click();

cy.get('.slick-cell-menu.level-1 .slick-cell-menu-option-list')
.should('exist')
.find('.slick-cell-menu-item')
.each(($option, index) => expect($option.text()).to.eq(subOptions[index]));
});

it('should click on the "Show Commands & Priority Options" button and see both list when opening Context Menu', () => {
cy.get('button')
.contains('Show Commands & Priority Options')
Expand Down Expand Up @@ -320,8 +492,8 @@ describe('Example - Context Menu & Cell Menu', () => {
.click();
});

it('should click on the "Show Action Commands Only" button and see both list when opening Context Menu', () => {
const commands = ['Command 1', 'Command 2', 'Delete Row', '', 'Help', 'Disabled Command'];
it('should click on the "Show Action Commands Only" button and see both list when opening Cell Menu', () => {
const commands = ['Command 1', 'Command 2', 'Delete Row', '', 'Help', 'Disabled Command', '', 'Export'];

cy.get('button')
.contains('Show Action Commands Only')
Expand Down
40 changes: 33 additions & 7 deletions examples/example-plugin-contextmenu.html
Original file line number Diff line number Diff line change
Expand Up @@ -84,10 +84,10 @@
<body>
<table width="100%">
<tr>
<td valign="top" width="50%">
<td valign="top" width="50%" style="padding-left: 0px">
<div id="myGrid" class="slick-container" style="width:650px;height:700px;"></div>
</td>
<td valign="top">
<td valign="top" style="display: block">
<h2>
<h2>
<a href="/examples/index.html" style="text-decoration: none; font-size: 22px">&#x2302;</a>
Expand Down Expand Up @@ -285,10 +285,13 @@ <h2>View Source:</h2>

switch (command) {
case "command1":
alert('Command 1');
break;
case "command2":
alert('Command 2');
alert(args.item.title);
break;
case "export-csv":
case "export-pdf":
case "export-xls":
alert("Exporting as " + args.item.title);
break;
case "copy-text":
copyCellValue(args.value);
Expand Down Expand Up @@ -352,7 +355,22 @@ <h2>View Source:</h2>
"divider",
// { divider: true },
{ command: "help", title: "Help", iconCssClass: "sgi sgi-help-circle-outline" },
{ command: "something", title: "Disabled Command", disabled: true }
{ command: "something", title: "Disabled Command", disabled: true },
"divider",
{
// we can also have multiple sub-items
command: 'export', title: 'Export',
commandItems: [
{ command: "export-pdf", title: "PDF" },
{
command: 'sub-menu', title: 'Excel', cssClass: "green",
commandItems: [
{ command: "export-csv", title: "Excel (csv)" },
{ command: "export-xls", title: "Excel (xls)" },
]
}
]
}
],
optionTitle: "Change Effort Driven",
optionItems: [
Expand All @@ -373,6 +391,13 @@ <h2>View Source:</h2>
return (!args.dataContext.effortDriven);
}
},
{
// we can also have multiple sub-items
option: null, title: "Sub-Options (demo)", optionItems: [
{ option: true, title: "True", iconCssClass: 'sgi sgi-checkbox-marked-outline green' },
{ option: false, title: "False", iconCssClass: 'sgi sgi-checkbox-blank-outline pink' },
]
}
]
}
}
Expand Down Expand Up @@ -450,7 +475,7 @@ <h2>View Source:</h2>
document.addEventListener("DOMContentLoaded", function() {
dataView = new Slick.Data.DataView();
grid = new Slick.Grid("#myGrid", dataView, columns, gridOptions);
cellMenuPlugin = new Slick.Plugins.CellMenu({ hideMenuOnScroll: true });
cellMenuPlugin = new Slick.Plugins.CellMenu({ hideMenuOnScroll: true, subItemChevronClass: 'sgi sgi-chevron-right' });
contextMenuPlugin = new Slick.Plugins.ContextMenu(contextMenuOptions);
var columnpicker = new Slick.Controls.ColumnPicker(columns, grid, gridOptions);

Expand Down Expand Up @@ -542,6 +567,7 @@ <h2>View Source:</h2>

// subscribe to Cell Menu onOptionSelected event (or use the action callback on each option)
cellMenuPlugin.onOptionSelected.subscribe(function (e, args) {
console.log('onOptionSelected', args)
ghiscoding marked this conversation as resolved.
Show resolved Hide resolved
// e.preventDefault(); // you could do if you wish to keep the menu open
var dataContext = args && args.dataContext;

Expand Down
3 changes: 3 additions & 0 deletions src/models/cellMenuOption.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@ export interface CellMenuOption {
/** Optional Title of the Option section, it will be hidden when nothing is provided */
optionTitle?: string;

/** CSS class that can be added on the right side of a sub-item parent (typically a chevron-right icon) */
subItemChevronClass?: string;

// --
// action/override callbacks

Expand Down
3 changes: 3 additions & 0 deletions src/models/menuCommandItem.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ export interface MenuCommandItem<A = MenuCommandItemCallbackArgs, R = MenuCallba
/** A command identifier to be passed to the onCommand event callback handler (when using "commandItems"). */
command: string;

/** Array of Command Items (title, command, disabled, ...) */
commandItems?: Array<MenuCommandItem | 'divider'>;

// --
// action/override callbacks

Expand Down
3 changes: 3 additions & 0 deletions src/models/menuOptionItem.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ export interface MenuOptionItem extends MenuItem {
/** An option returned by the onOptionSelected (or action) event callback handler. */
option: any;

/** Array of Option Items (title, command, disabled, ...) */
optionItems?: Array<MenuOptionItem | 'divider'>;

// --
// action/override callbacks

Expand Down
Loading