Skip to content

Commit

Permalink
feat(controls): move external Grid Menu into Slickgrid-Universal
Browse files Browse the repository at this point in the history
- remove any reliance of the Grid Menu from SlickGrid fork, move everything internally into Slickgrid-Universal
- the Grid Menu will no longer use SlickGrid Events, instead we will use the regular internal pubsub Service to do this
- add new `alignDropSide` property to the Grid Menu options to choose which side to align the dropdown menu
  • Loading branch information
ghiscoding committed Aug 21, 2021
1 parent 29fde2f commit 40adff4
Show file tree
Hide file tree
Showing 47 changed files with 3,201 additions and 1,692 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ <h5 class="title is-5">Grid 2</h5>
<span class="icon mdi mdi-swap-vertical"></span>
<span>Toggle Pagination</span>
</button>
<button class="button is-small " data-test="external-gridmenu2-btn"
onclick.delegate="toggleGridMenu(event)">
<span class="icon mdi mdi-menu"></span>
<span>Grid Menu</span>
</button>
</p>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.red {
color: #ff0000;
}
37 changes: 35 additions & 2 deletions examples/webpack-demo-vanilla-bundle/src/examples/example01.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { Column, Formatters, GridOption } from '@slickgrid-universal/common';
import { Column, ExtensionName, Formatters, GridOption } from '@slickgrid-universal/common';
import { Slicker, SlickVanillaGridBundle } from '@slickgrid-universal/vanilla-bundle';
import { ExampleGridOptions } from './example-grid-options';

// use any of the Styling Theme
// import '../material-styles.scss';
import '../salesforce-styles.scss';
import './example01.scss';

const NB_ITEMS = 995;

Expand Down Expand Up @@ -63,8 +64,31 @@ export class Example1 {
...this.gridOptions1,
...{
gridHeight: 255,
enablePagination: true,
columnPicker: {
onColumnsChanged: (e, args) => console.log('columnPicker:onColumnsChanged - visible columns count', args.visibleColumns.length),
},
gridMenu: {
// customItems: [
// { command: 'help', title: 'Help', positionOrder: 70, action: (e, args) => console.log(args) },
// { command: '', divider: true, positionOrder: 72 },
// { command: 'hello', title: 'Hello', positionOrder: 69, action: (e, args) => alert('Hello World'), cssClass: 'red', tooltip: 'Hello World', iconCssClass: 'mdi mdi-close' },
// ],
alignDropSide: 'right',
// menuUsabilityOverride: () => false,
onBeforeMenuShow: () => {
console.log('gridMenu:onBeforeMenuShow');
// return false; // returning false would prevent the grid menu from opening
},
onAfterMenuShow: () => console.log('gridMenu:onAfterMenuShow'),
onColumnsChanged: (_e, args) => console.log('gridMenu:onColumnsChanged', args),
onCommand: (e, args) => {
// e.preventDefault(); // preventing default event would keep the menu open after the execution
console.log('gridMenu:onCommand', args.command);
},
onMenuClose: (e, args) => console.log('gridMenu:onMenuClose - visible columns count', args.visibleColumns.length),
},
enableFiltering: true,
enablePagination: true,
pagination: {
pageSizes: [5, 10, 15, 20, 25, 50, 75, 100],
pageSize: 5
Expand Down Expand Up @@ -114,4 +138,13 @@ export class Example1 {
this.isGrid2WithPagination = !this.isGrid2WithPagination;
this.sgb2.paginationService!.togglePaginationVisibility(this.isGrid2WithPagination);
}

toggleGridMenu(e: Event) {
if (this.sgb2?.extensionService) {
const gridMenuInstance = this.sgb2.extensionService.getSlickgridAddonInstance(ExtensionName.gridMenu);
// open the external button Grid Menu, you can also optionally pass Grid Menu options as 2nd argument
// for example we want to align our external button on the left without affecting the menu within which will stay aligned on the right
gridMenuInstance.showGridMenu(e, { alignDropSide: 'left' });
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@ <h3 class="title is-3">
<span class="icon mdi mdi-close"></span>
<span>Clear all Filter & Sorts</span>
</button>
<button class="button is-small" data-test="set-dynamic-filter" onclick.delegate="setFiltersDynamically()">
<button class="button is-small" data-test="set-dynamic-filter-btn" onclick.delegate="setFiltersDynamically()">
Set Filters Dynamically
</button>
<button class="button is-small" data-test="set-dynamic-sorting" onclick.delegate="setSortingDynamically()">
<button class="button is-small" data-test="set-dynamic-sorting-btn" onclick.delegate="setSortingDynamically()">
Set Sorting Dynamically
</button>
<button class="button is-small" style="margin-left: 10px" data-test="add-gender-button"
<button class="button is-small" style="margin-left: 10px" data-test="add-gender-btn"
onclick.delegate="addOtherGender()" disabled.bind="isOtherGenderAdded">
Add Other Gender via RxJS
</button>
Expand Down
66 changes: 39 additions & 27 deletions packages/common/src/controls/__tests__/columnPickerControl.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,18 @@ import { ColumnPickerControl } from '../columnPicker.control';
import { ExtensionUtility } from '../../extensions/extensionUtility';
import { SharedService } from '../../services/shared.service';
import { TranslateServiceStub } from '../../../../../test/translateServiceStub';
import { BackendUtilityService } from '../../services/backendUtility.service';
import { PubSubService } from '../../services';

declare const Slick: SlickNamespace;
const gridUid = 'slickgrid_124343';

const gridStub = {
getColumnIndex: jest.fn(),
getColumns: jest.fn(),
getOptions: jest.fn(),
getSelectedRows: jest.fn(),
getUID: jest.fn(),
getUID: () => gridUid,
registerPlugin: jest.fn(),
setColumns: jest.fn(),
setOptions: jest.fn(),
Expand All @@ -20,6 +23,13 @@ const gridStub = {
onHeaderContextMenu: new Slick.Event(),
} as unknown as SlickGrid;

const pubSubServiceStub = {
publish: jest.fn(),
subscribe: jest.fn(),
unsubscribe: jest.fn(),
unsubscribeAll: jest.fn(),
} as PubSubService;

describe('ColumnPickerControl', () => {
const eventData = { ...new Slick.EventData(), preventDefault: jest.fn() };
const columnsMock: Column[] = [
Expand All @@ -29,6 +39,7 @@ describe('ColumnPickerControl', () => {
];

let control: ColumnPickerControl;
let backendUtilityService: BackendUtilityService;
let sharedService: SharedService;
let translateService: TranslateServiceStub;
let extensionUtility: ExtensionUtility;
Expand All @@ -45,8 +56,9 @@ describe('ColumnPickerControl', () => {

beforeEach(() => {
sharedService = new SharedService();
backendUtilityService = new BackendUtilityService();
translateService = new TranslateServiceStub();
extensionUtility = new ExtensionUtility(sharedService, translateService);
extensionUtility = new ExtensionUtility(sharedService, backendUtilityService, translateService);

jest.spyOn(SharedService.prototype, 'slickGrid', 'get').mockReturnValue(gridStub);
jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(gridOptionsMock);
Expand All @@ -56,7 +68,7 @@ describe('ColumnPickerControl', () => {
jest.spyOn(gridStub, 'getColumns').mockReturnValue(columnsMock);
jest.spyOn(gridStub, 'getOptions').mockReturnValue(gridOptionsMock);

control = new ColumnPickerControl(extensionUtility, sharedService);
control = new ColumnPickerControl(extensionUtility, pubSubServiceStub, sharedService);
translateService.use('fr');
});

Expand All @@ -66,7 +78,7 @@ describe('ColumnPickerControl', () => {
jest.clearAllMocks();
});

describe('registered plugin', () => {
describe('registered control', () => {
afterEach(() => {
gridOptionsMock.columnPicker.headerColumnValueExtractor = null;
gridOptionsMock.columnPicker.onColumnsChanged = null;
Expand All @@ -91,7 +103,7 @@ describe('ColumnPickerControl', () => {
const inputElm = control.menuElement.querySelector('input[type="checkbox"]');
inputElm.dispatchEvent(new Event('click', { bubbles: true, cancelable: true, composed: false }));

expect(control.menuElement.style.display).toBe('block');
expect(control.menuElement.style.visibility).toBe('visible');
expect(setSelectionSpy).toHaveBeenCalledWith(mockRowSelection);
expect(control.getAllColumns()).toEqual(columnsMock);
expect(control.getVisibleColumns()).toEqual(columnsMock);
Expand All @@ -109,12 +121,12 @@ describe('ColumnPickerControl', () => {
const eventData = { ...new Slick.EventData(), preventDefault: jest.fn() };
gridStub.onHeaderContextMenu.notify({ column: columnsMock[1], grid: gridStub }, eventData, gridStub);

expect(control.menuElement.style.display).toBe('block');
expect(control.menuElement.style.visibility).toBe('visible');

const bodyElm = document.body;
bodyElm.dispatchEvent(new Event('mousedown', { bubbles: true }));

expect(control.menuElement.style.display).toBe('none');
expect(control.menuElement.style.visibility).toBe('hidden');
});

it('should query an input checkbox change event and expect "readjustFrozenColumnIndexWhenNeeded" method to be called when the grid is detected to be a frozen grid', () => {
Expand All @@ -140,10 +152,7 @@ describe('ColumnPickerControl', () => {
jest.spyOn(gridStub, 'getColumnIndex').mockReturnValue(undefined).mockReturnValue(1);
const readjustSpy = jest.spyOn(extensionUtility, 'readjustFrozenColumnIndexWhenNeeded');

gridOptionsMock.columnPicker.headerColumnValueExtractor = (column: Column) => {
const headerGroup = column?.columnGroup || '';
return `${headerGroup} - ${column.name}`;
};
gridOptionsMock.columnPicker.headerColumnValueExtractor = (column: Column) => `${column?.columnGroup || ''} - ${column.name}`;
control.columns = columnsMock;
control.init();

Expand All @@ -169,8 +178,8 @@ describe('ColumnPickerControl', () => {

gridStub.onHeaderContextMenu.notify({ column: columnsMock[1], grid: gridStub }, eventData, gridStub);
control.menuElement.querySelector<HTMLInputElement>('input[type="checkbox"]').dispatchEvent(new Event('click', { bubbles: true }));
const inputForcefitElm = control.menuElement.querySelector<HTMLInputElement>('#colpicker-forcefit');
const labelSyncElm = control.menuElement.querySelector<HTMLLabelElement>('label[for=colpicker-forcefit]');
const inputForcefitElm = control.menuElement.querySelector<HTMLInputElement>('#slickgrid_124343-colpicker-forcefit');
const labelSyncElm = control.menuElement.querySelector<HTMLLabelElement>('label[for=slickgrid_124343-colpicker-forcefit]');

expect(handlerSpy).toHaveBeenCalledTimes(2);
expect(control.getAllColumns()).toEqual(columnsMock);
Expand All @@ -191,8 +200,8 @@ describe('ColumnPickerControl', () => {

gridStub.onHeaderContextMenu.notify({ column: columnsMock[1], grid: gridStub }, eventData, gridStub);
control.menuElement.querySelector<HTMLInputElement>('input[type="checkbox"]').dispatchEvent(new Event('click', { bubbles: true }));
const inputSyncElm = control.menuElement.querySelector<HTMLInputElement>('#colpicker-syncresize');
const labelSyncElm = control.menuElement.querySelector<HTMLLabelElement>('label[for=colpicker-syncresize]');
const inputSyncElm = control.menuElement.querySelector<HTMLInputElement>('#slickgrid_124343-colpicker-syncresize');
const labelSyncElm = control.menuElement.querySelector<HTMLLabelElement>('label[for=slickgrid_124343-colpicker-syncresize]');

expect(handlerSpy).toHaveBeenCalledTimes(2);
expect(control.getAllColumns()).toEqual(columnsMock);
Expand All @@ -204,6 +213,7 @@ describe('ColumnPickerControl', () => {

it('should open the column picker via "onHeaderContextMenu" and expect "onColumnsChanged" to be called when defined', () => {
const handlerSpy = jest.spyOn(control.eventHandler, 'subscribe');
const pubSubSpy = jest.spyOn(pubSubServiceStub, 'publish');
const onColChangedMock = jest.fn();
jest.spyOn(gridStub, 'getColumnIndex').mockReturnValue(undefined).mockReturnValue(1);

Expand All @@ -214,16 +224,19 @@ describe('ColumnPickerControl', () => {
gridStub.onHeaderContextMenu.notify({ column: columnsMock[1], grid: gridStub }, eventData, gridStub);
control.menuElement.querySelector<HTMLInputElement>('input[type="checkbox"]').dispatchEvent(new Event('click', { bubbles: true }));

expect(handlerSpy).toHaveBeenCalledTimes(2);
expect(control.getAllColumns()).toEqual(columnsMock);
expect(control.getVisibleColumns()).toEqual(columnsMock);
expect(onColChangedMock).toBeCalledWith(expect.anything(), {
const expectedCallbackArgs = {
columnId: 'field1',
showing: true,
allColumns: columnsMock,
columns: columnsMock,
visibleColumns: columnsMock,
grid: gridStub,
});
};
expect(handlerSpy).toHaveBeenCalledTimes(2);
expect(control.getAllColumns()).toEqual(columnsMock);
expect(control.getVisibleColumns()).toEqual(columnsMock);
expect(onColChangedMock).toBeCalledWith(expect.anything(), expectedCallbackArgs);
expect(pubSubSpy).toHaveBeenCalledWith('columnPicker:onColumnsChanged', expectedCallbackArgs);
});

it('should open the column picker via "onHeaderContextMenu", click on "Force Fit Columns" checkbox and expect "setOptions" and "setColumns" to be called with previous visible columns', () => {
Expand All @@ -239,8 +252,8 @@ describe('ColumnPickerControl', () => {
control.init();

gridStub.onHeaderContextMenu.notify({ column: columnsMock[1], grid: gridStub }, eventData, gridStub);
const inputForcefitElm = control.menuElement.querySelector<HTMLInputElement>('#colpicker-forcefit');
const labelSyncElm = control.menuElement.querySelector<HTMLLabelElement>('label[for=colpicker-forcefit]');
const inputForcefitElm = control.menuElement.querySelector<HTMLInputElement>('#slickgrid_124343-colpicker-forcefit');
const labelSyncElm = control.menuElement.querySelector<HTMLLabelElement>('label[for=slickgrid_124343-colpicker-forcefit]');
inputForcefitElm.dispatchEvent(new Event('click', { bubbles: true }));

expect(handlerSpy).toHaveBeenCalledTimes(2);
Expand All @@ -265,8 +278,8 @@ describe('ColumnPickerControl', () => {
control.init();

gridStub.onHeaderContextMenu.notify({ column: columnsMock[1], grid: gridStub }, eventData, gridStub);
const inputSyncElm = control.menuElement.querySelector<HTMLInputElement>('#colpicker-syncresize');
const labelSyncElm = control.menuElement.querySelector<HTMLLabelElement>('label[for=colpicker-syncresize]');
const inputSyncElm = control.menuElement.querySelector<HTMLInputElement>('#slickgrid_124343-colpicker-syncresize');
const labelSyncElm = control.menuElement.querySelector<HTMLLabelElement>('label[for=slickgrid_124343-colpicker-syncresize]');
inputSyncElm.dispatchEvent(new Event('click', { bubbles: true }));

expect(handlerSpy).toHaveBeenCalledTimes(2);
Expand All @@ -289,7 +302,6 @@ describe('ColumnPickerControl', () => {
{ id: 'field2', field: 'field2', name: 'Field 2', width: 75 },
{ id: 'field3', field: 'field3', name: 'Field 3', width: 75, columnGroup: 'Billing' },
];
jest.spyOn(control, 'getAllColumns').mockReturnValue(columnsMock);
jest.spyOn(gridStub, 'getColumnIndex').mockReturnValue(undefined).mockReturnValueOnce(0).mockReturnValueOnce(1);
const handlerSpy = jest.spyOn(control.eventHandler, 'subscribe');

Expand Down Expand Up @@ -324,8 +336,8 @@ describe('ColumnPickerControl', () => {

gridStub.onHeaderContextMenu.notify({ column: columnsMock[1], grid: gridStub }, eventData, gridStub);
control.menuElement.querySelector<HTMLInputElement>('input[type="checkbox"]').dispatchEvent(new Event('click', { bubbles: true }));
const labelForcefitElm = control.menuElement.querySelector<HTMLLabelElement>('label[for=colpicker-forcefit]');
const labelSyncElm = control.menuElement.querySelector<HTMLLabelElement>('label[for=colpicker-syncresize]');
const labelForcefitElm = control.menuElement.querySelector<HTMLLabelElement>('label[for=slickgrid_124343-colpicker-forcefit]');
const labelSyncElm = control.menuElement.querySelector<HTMLLabelElement>('label[for=slickgrid_124343-colpicker-syncresize]');

expect(handlerSpy).toHaveBeenCalledTimes(2);
expect(labelForcefitElm.textContent).toBe('Ajustement forcé des colonnes');
Expand Down
Loading

0 comments on commit 40adff4

Please sign in to comment.