Skip to content

Commit

Permalink
Merge pull request #1607 from ghiscoding/feat/col-picker-label
Browse files Browse the repository at this point in the history
feat: add `columnPickerLabel` for custom label, also fix #1605
  • Loading branch information
ghiscoding committed Jul 16, 2024
2 parents b1d42f5 + ac439dd commit e4230e3
Show file tree
Hide file tree
Showing 8 changed files with 100 additions and 16 deletions.
22 changes: 19 additions & 3 deletions examples/vite-demo-vanilla-bundle/src/examples/example02.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,27 @@ export default class Example02 {
}

initializeGrid() {
// add a simple button with event listener on 1st column for testing purposes
// a simple button with click event
const nameElementColumn1 = document.createElement('div');
const btn = document.createElement('button');
const btnLabel = document.createElement('span');
btnLabel.className = 'mdi mdi-help-circle no-padding';
btn.dataset.test = 'col1-hello-btn';
btn.className = 'button is-small ml-5';
btn.textContent = 'Click me';
btn.title = 'simple column header test with a button click listener';
btn.addEventListener('click', () => alert('Hello World'));
btn.appendChild(btnLabel);
nameElementColumn1.appendChild(document.createTextNode('Id '));
nameElementColumn1.appendChild(btn);

this.columnDefinitions = [
{
id: 'sel', name: '#', field: 'num', width: 40, type: FieldType.number,
id: 'sel', name: nameElementColumn1, field: 'num', type: FieldType.number,
columnPickerLabel: 'Custom Label', // add a custom label for the ColumnPicker/GridMenu when default header value extractor doesn't work for you ()
width: 160, maxWidth: 200,
excludeFromExport: true,
maxWidth: 70,
resizable: true,
filterable: true,
selectable: false,
Expand Down Expand Up @@ -183,7 +199,7 @@ export default class Example02 {
this.gridOptions = {
autoResize: {
bottomPadding: 30,
rightPadding: 10
rightPadding: 30
},
enableTextExport: true,
enableFiltering: true,
Expand Down
9 changes: 4 additions & 5 deletions packages/common/src/core/slickGrid.ts
Original file line number Diff line number Diff line change
Expand Up @@ -585,16 +585,15 @@ export class SlickGrid<TData = any, C extends Column<TData> = Column<TData>, O e
* `sanitizerOptions` is to provide extra options when using `innerHTML` and the sanitizer.
* `skipEmptyReassignment`, defaults to true, when enabled it will not try to reapply an empty value when the target is already empty
*/
applyHtmlCode(target: HTMLElement, val: string | boolean | number | HTMLElement | DocumentFragment = '', options?: { emptyTarget?: boolean; sanitizerOptions?: unknown; skipEmptyReassignment?: boolean; cloneNode?: boolean; }): void {
applyHtmlCode(target: HTMLElement, val: string | boolean | number | HTMLElement | DocumentFragment = '', options?: { emptyTarget?: boolean; sanitizerOptions?: unknown; skipEmptyReassignment?: boolean; }): void {
if (target) {
if (val instanceof HTMLElement || val instanceof DocumentFragment) {
// first empty target and then append new HTML element
const emptyTarget = options?.emptyTarget !== false;
if (emptyTarget) {
emptyElement(target);
}
const node = options?.cloneNode ? val.cloneNode(true) : val;
target.appendChild(node);
target.appendChild(val);
} else {
// when it's already empty and we try to reassign empty, it's probably ok to skip the assignment
const skipEmptyReassignment = options?.skipEmptyReassignment !== false;
Expand Down Expand Up @@ -1656,7 +1655,7 @@ export class SlickGrid<TData = any, C extends Column<TData> = Column<TData>, O e
header.classList.add(this._options.unorderableColumnCssClass!);
}
const colNameElm = createDomElement('span', { className: 'slick-column-name' }, header);
this.applyHtmlCode(colNameElm, m.name, { cloneNode: true });
this.applyHtmlCode(colNameElm, m.name);

Utils.width(header, m.width! - this.headerColumnWidthDiff);

Expand Down Expand Up @@ -2793,7 +2792,7 @@ export class SlickGrid<TData = any, C extends Column<TData> = Column<TData>, O e
w = this.columns[i].width || 0;

rule = this.getColumnCssRules(i);
if (rule.left) {
if (rule.left) {
rule.left.style.left = `${x}px`;
}
if (rule.right) {
Expand Down
25 changes: 22 additions & 3 deletions packages/common/src/extensions/__tests__/slickColumnPicker.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ describe('ColumnPickerControl', () => {
const eventData = { ...new SlickEventData(), preventDefault: jest.fn() };
const columnsMock: Column[] = [
{ id: 'field1', field: 'field1', name: 'Field 1', width: 100, nameKey: 'TITLE' },
{ id: 'field2', field: 'field2', name: 'Field 2', width: 75 },
{ id: 'field2', field: 'field2', name: 'Field 2', width: 75, columnPickerLabel: 'Custom Label' },
{ id: 'field3', field: 'field3', name: 'Field 3', width: 75, columnGroup: 'Billing' },
{ id: 'field4', field: 'field4', name: 'Field 4', width: 75, excludeFromColumnPicker: true },
];
Expand Down Expand Up @@ -182,6 +182,25 @@ describe('ColumnPickerControl', () => {
expect(liElmList[2].textContent).toBe('Billing - Field 3');
});

it('should return custom label when columnPickerLabel is defined', () => {
const handlerSpy = jest.spyOn(control.eventHandler, 'subscribe');
jest.spyOn(gridStub, 'getColumnIndex').mockReturnValue(undefined as any).mockReturnValue(0);
const readjustSpy = jest.spyOn(extensionUtility, 'readjustFrozenColumnIndexWhenNeeded');

control.columns = columnsMock;
control.init();

gridStub.onHeaderContextMenu.notify({ column: columnsMock[1], grid: gridStub }, eventData as any, gridStub);
control.menuElement!.querySelector<HTMLInputElement>('input[type="checkbox"]')!.dispatchEvent(new Event('click', { bubbles: true }));
const liElmList = control.menuElement!.querySelectorAll<HTMLLIElement>('li');

expect(handlerSpy).toHaveBeenCalledTimes(4);
expect(readjustSpy).toHaveBeenCalledWith(0, columnsMock, columnsMock);
expect(control.getAllColumns()).toEqual(columnsMock);
expect(control.getVisibleColumns()).toEqual(columnsMock);
expect(liElmList[1].textContent).toBe('Custom Label');
});

it('should open the column picker via "onPreHeaderContextMenu" and expect "Forcefit" to be checked when "hideForceFitButton" is false', () => {
const handlerSpy = jest.spyOn(control.eventHandler, 'subscribe');
jest.spyOn(gridStub, 'getColumnIndex').mockReturnValue(undefined as any).mockReturnValue(1);
Expand Down Expand Up @@ -352,7 +371,7 @@ describe('ColumnPickerControl', () => {
];
const columnsMock: Column[] = [
{ id: 'field1', field: 'field1', name: 'Field 1', width: 100, nameKey: 'TITLE' },
{ id: 'field2', field: 'field2', name: 'Field 2', width: 75 },
{ id: 'field2', field: 'field2', name: 'Field 2', width: 75, columnPickerLabel: 'Custom Label' },
{ id: 'field3', field: 'field3', name: 'Field 3', width: 75, columnGroup: 'Billing' },
{ id: 'field4', field: 'field4', name: 'Field 4', width: 75, excludeFromColumnPicker: true, }
];
Expand Down Expand Up @@ -405,7 +424,7 @@ describe('ColumnPickerControl', () => {
expect((SharedService.prototype.gridOptions.columnPicker as ColumnPicker).syncResizeTitle).toBe('Redimension synchrone');
expect(columnsMock).toEqual([
{ id: 'field1', field: 'field1', name: 'Titre', width: 100, nameKey: 'TITLE' },
{ id: 'field2', field: 'field2', name: 'Field 2', width: 75 },
{ id: 'field2', field: 'field2', name: 'Field 2', width: 75, columnPickerLabel: 'Custom Label' },
{ id: 'field3', field: 'field3', name: 'Field 3', columnGroup: 'Billing', width: 75 },
{ id: 'field4', field: 'field4', name: 'Field 4', width: 75, excludeFromColumnPicker: true, }
]);
Expand Down
4 changes: 3 additions & 1 deletion packages/common/src/extensions/slickColumnPicker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,9 @@ export class SlickColumnPicker {
forceFitTitle: 'Force fit columns',
minHeight: 200,
syncResizeTitle: 'Synchronous resize',
headerColumnValueExtractor: (columnDef: Column) => getHtmlStringOutput(columnDef.name || '', 'innerHTML')
headerColumnValueExtractor: (columnDef: Column) => {
return getHtmlStringOutput(columnDef.columnPickerLabel || columnDef.name || '', 'innerHTML');
}
} as ColumnPickerOption;

/** Constructor of the SlickGrid 3rd party plugin, it can optionally receive options */
Expand Down
2 changes: 1 addition & 1 deletion packages/common/src/extensions/slickGridMenu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ export class SlickGridMenu extends MenuBaseClass<GridMenu> {
resizeOnShowHeaderRow: false,
syncResizeTitle: 'Synchronous resize',
subMenuOpenByEvent: 'mouseover',
headerColumnValueExtractor: (columnDef: Column) => getHtmlStringOutput(columnDef.name || '', 'innerHTML')
headerColumnValueExtractor: (columnDef: Column) => getHtmlStringOutput(columnDef.columnPickerLabel || columnDef.name || '', 'innerHTML')
} as GridMenuOption;

/** Constructor of the SlickGrid 3rd party plugin, it can optionally receive options */
Expand Down
8 changes: 6 additions & 2 deletions packages/common/src/global-grid-options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -274,10 +274,14 @@ export const GlobalGridOptions: Partial<GridOption> = {
* else we'll simply return the column name title
*/
function pickerHeaderColumnValueExtractor(column: Column, gridOptions?: GridOption) {
let colName = column?.columnPickerLabel ?? column?.name ?? '';
if (colName instanceof HTMLElement || colName instanceof DocumentFragment) {
colName = colName.textContent || '';
}
const headerGroup = column?.columnGroup || '';
const columnGroupSeparator = gridOptions?.columnGroupSeparator ?? ' - ';
if (headerGroup) {
return headerGroup + columnGroupSeparator + column.name;
return headerGroup + columnGroupSeparator + colName;
}
return column?.name ?? '';
return colName;
}
6 changes: 6 additions & 0 deletions packages/common/src/interfaces/column.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,12 @@ export interface Column<T = any> {
/** Column group name translation key that can be used by the Translate Service (i18n) for grouping of column headers spanning accross multiple columns */
columnGroupKey?: string;

/**
* Column Picker Label to use by ColumnPicker/GridMenu instead of the default column name (fallback to the column name when no label provided).
* Note: this will be used by the `columnPicker.headerColumnValueExtractor`
*/
columnPickerLabel?: string | HTMLElement | DocumentFragment;

/** Column span in cell count or use `*` to span across the entire row */
colspan?: number | string | '*';

Expand Down
40 changes: 39 additions & 1 deletion test/cypress/e2e/example02.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { format } from '@formkit/tempo';
import { removeExtraSpaces } from '../plugins/utilities';

describe('Example 02 - Grouping & Aggregators', () => {
const fullTitles = ['#', 'Title', 'Duration', '% Complete', 'Start', 'Finish', 'Cost', 'Effort Driven'];
const fullTitles = ['Id Click me', 'Title', 'Duration', '% Complete', 'Start', 'Finish', 'Cost', 'Effort Driven'];
const GRID_ROW_HEIGHT = 45;
let currentTimestamp = '';

Expand Down Expand Up @@ -300,4 +300,42 @@ describe('Example 02 - Grouping & Aggregators', () => {
cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(1)`).should('contain', 'Task 311');
});
});

describe('Column Header with HTML Elements', () => {
it('should trigger an alert when clicking on the 1st column button inside its header', () => {
const stub = cy.stub();
cy.on('window:alert', stub);

cy.get('button[data-test=col1-hello-btn]')
.click({ force: true })
.then(() => expect(stub.getCall(0)).to.be.calledWith('Hello World'));
});

it('should open Column Picker and have a "Custom Label" as the 1st column label', () => {
cy.get('.grid2')
.find('.slick-header-column')
.first()
.trigger('mouseover')
.trigger('contextmenu')
.invoke('show');

cy.get('.slick-column-picker')
.find('.slick-column-picker-list li:nth-child(1) .checkbox-label')
.should('have.text', 'Custom Label');
});

it('should open Grid Menu and have a "Custom Label" as the 1st column label', () => {
cy.get('.grid2')
.find('button.slick-grid-menu-button')
.trigger('click')
.click({ force: true });

cy.get(`.slick-grid-menu:visible`)
.find('.slick-column-picker-list li:nth-child(1) .checkbox-label')
.should('have.text', 'Custom Label');

cy.get('[data-dismiss="slick-grid-menu"]')
.click();
});
});
});

0 comments on commit e4230e3

Please sign in to comment.