Skip to content

Commit

Permalink
feat: add columnPickerLabel for custom label, also fix #1605
Browse files Browse the repository at this point in the history
- 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
  • Loading branch information
ghiscoding-SE committed Jul 15, 2024
1 parent b1d42f5 commit f4360b9
Show file tree
Hide file tree
Showing 7 changed files with 48 additions and 12 deletions.
20 changes: 18 additions & 2 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
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
2 changes: 1 addition & 1 deletion packages/common/src/extensions/slickColumnPicker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
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
13 changes: 12 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,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'));
});
});
});

0 comments on commit f4360b9

Please sign in to comment.