From f4360b9badd2743e78658ea0be4e6acaa2a5b303 Mon Sep 17 00:00:00 2001 From: ghiscoding Date: Mon, 15 Jul 2024 17:38:28 -0400 Subject: [PATCH] feat: add `columnPickerLabel` for custom label, also fix #1605 - fixes #1605 by reverting #1476 - this PR fixes #1605 without regressing on older issue #1475 - also add a `columnPickerLabel` option in the Column interface, because in some cases the default header column extractor (defined in the global grid options) will return all text it finds (via `.textContent`) and that might not be ideal given that an HTML element with extra button will also extract and append the button text in both ColumnPicker/GridMenu --- .../src/examples/example02.ts | 20 +++++++++++++++++-- packages/common/src/core/slickGrid.ts | 9 ++++----- .../src/extensions/slickColumnPicker.ts | 2 +- .../common/src/extensions/slickGridMenu.ts | 2 +- packages/common/src/global-grid-options.ts | 8 ++++++-- .../common/src/interfaces/column.interface.ts | 6 ++++++ test/cypress/e2e/example02.cy.ts | 13 +++++++++++- 7 files changed, 48 insertions(+), 12 deletions(-) diff --git a/examples/vite-demo-vanilla-bundle/src/examples/example02.ts b/examples/vite-demo-vanilla-bundle/src/examples/example02.ts index 8d0360ccb..26dea3c90 100644 --- a/examples/vite-demo-vanilla-bundle/src/examples/example02.ts +++ b/examples/vite-demo-vanilla-bundle/src/examples/example02.ts @@ -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, diff --git a/packages/common/src/core/slickGrid.ts b/packages/common/src/core/slickGrid.ts index 0009f3a85..7b3d5faba 100644 --- a/packages/common/src/core/slickGrid.ts +++ b/packages/common/src/core/slickGrid.ts @@ -585,7 +585,7 @@ export class SlickGrid = Column, 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 @@ -593,8 +593,7 @@ export class SlickGrid = Column, O e 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; @@ -1656,7 +1655,7 @@ export class SlickGrid = Column, 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); @@ -2793,7 +2792,7 @@ export class SlickGrid = Column, 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) { diff --git a/packages/common/src/extensions/slickColumnPicker.ts b/packages/common/src/extensions/slickColumnPicker.ts index 23280e60c..67c9ba1b8 100644 --- a/packages/common/src/extensions/slickColumnPicker.ts +++ b/packages/common/src/extensions/slickColumnPicker.ts @@ -46,7 +46,7 @@ export class SlickColumnPicker { forceFitTitle: 'Force fit columns', minHeight: 200, syncResizeTitle: 'Synchronous resize', - headerColumnValueExtractor: (columnDef: Column) => getHtmlStringOutput(columnDef.name || '', 'innerHTML') + headerColumnValueExtractor: (columnDef: Column) => getHtmlStringOutput(columnDef.columnPickerLabel || columnDef.name || '', 'innerHTML') } as ColumnPickerOption; /** Constructor of the SlickGrid 3rd party plugin, it can optionally receive options */ diff --git a/packages/common/src/extensions/slickGridMenu.ts b/packages/common/src/extensions/slickGridMenu.ts index 8152b8b13..d6abd57ed 100644 --- a/packages/common/src/extensions/slickGridMenu.ts +++ b/packages/common/src/extensions/slickGridMenu.ts @@ -70,7 +70,7 @@ export class SlickGridMenu extends MenuBaseClass { 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 */ diff --git a/packages/common/src/global-grid-options.ts b/packages/common/src/global-grid-options.ts index 3e6695981..201d620b8 100644 --- a/packages/common/src/global-grid-options.ts +++ b/packages/common/src/global-grid-options.ts @@ -274,10 +274,14 @@ export const GlobalGridOptions: Partial = { * 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; } diff --git a/packages/common/src/interfaces/column.interface.ts b/packages/common/src/interfaces/column.interface.ts index 02acd1e02..888308b73 100644 --- a/packages/common/src/interfaces/column.interface.ts +++ b/packages/common/src/interfaces/column.interface.ts @@ -65,6 +65,12 @@ export interface Column { /** 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 | '*'; diff --git a/test/cypress/e2e/example02.cy.ts b/test/cypress/e2e/example02.cy.ts index 5f104e439..91b4456a2 100644 --- a/test/cypress/e2e/example02.cy.ts +++ b/test/cypress/e2e/example02.cy.ts @@ -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 = ''; @@ -300,4 +300,15 @@ 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')); + }); + }); });