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

fix(core): allow extra spaces in headerCssClass #1303

Merged
merged 2 commits into from
Jan 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
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
6 changes: 3 additions & 3 deletions packages/common/src/core/__tests__/slickGrid.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ describe('SlickGrid core file', () => {
});

it('should be able to instantiate SlickGrid and set headerCssClass and expect it in column header', () => {
const columns = [{ id: 'firstName', field: 'firstName', name: 'First Name', headerCssClass: 'header-class' }] as Column[];
const columns = [{ id: 'firstName', field: 'firstName', name: 'First Name', headerCssClass: 'header-class other-class' }] as Column[];
const options = { enableCellNavigation: true, devMode: { ownerNodeIndex: 0 } } as GridOption;
grid = new SlickGrid<any, Column>('#myGrid', [], columns, options);
grid.init();
Expand Down Expand Up @@ -239,7 +239,7 @@ describe('SlickGrid core file', () => {

it('should be able to add CSS classes to all Viewports', () => {
const columns = [{ id: 'firstName', field: 'firstName', name: 'First Name' }] as Column[];
const options = { enableCellNavigation: true, viewportClass: 'vp-class1 vp-class2', devMode: { ownerNodeIndex: 0 } } as GridOption;
const options = { enableCellNavigation: true, viewportClass: 'vp-class1 vp-class2', devMode: { ownerNodeIndex: 0 } } as GridOption;
grid = new SlickGrid<any, Column>(container, [], columns, options);
grid.init();
const vpElms = container.querySelectorAll('.slick-viewport');
Expand Down Expand Up @@ -1289,7 +1289,7 @@ describe('SlickGrid core file', () => {
expect(viewportElm.scrollLeft).toBe(0);
});
});

describe('Navigation', () => {
const columns = [
{ id: 'firstName', field: 'firstName', name: 'First Name', sortable: true },
Expand Down
19 changes: 10 additions & 9 deletions packages/common/src/core/slickGrid.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import Sortable, { SortableEvent } from 'sortablejs';
import DOMPurify from 'dompurify';
import { BindingEventService } from '@slickgrid-universal/binding';
import { createDomElement, emptyElement, extend, getInnerSize, getOffset, insertAfterElement, isDefined, isPrimitiveOrHTML } from '@slickgrid-universal/utils';
import { createDomElement, emptyElement, extend, getInnerSize, getOffset, insertAfterElement, isDefined, isPrimitiveOrHTML, classNameToList } from '@slickgrid-universal/utils';

import {
type BasePubSub,
Expand Down Expand Up @@ -719,7 +719,7 @@ export class SlickGrid<TData = any, C extends Column<TData> = Column<TData>, O e
this._viewport = [this._viewportTopL, this._viewportTopR, this._viewportBottomL, this._viewportBottomR];
if (this._options.viewportClass) {
this._viewport.forEach((view) => {
view.classList.add(...(this._options.viewportClass || '').split(' '));
view.classList.add(...classNameToList(this._options.viewportClass));
});
}

Expand Down Expand Up @@ -1570,7 +1570,7 @@ export class SlickGrid<TData = any, C extends Column<TData> = Column<TData>, O e

let classname = m.headerCssClass || null;
if (classname) {
header.classList.add(...classname.split(' '));
header.classList.add(...classNameToList(classname));
}
classname = this.hasFrozenColumns() && i <= this._options.frozenColumn! ? 'frozen' : null;
if (classname) {
Expand Down Expand Up @@ -2253,10 +2253,11 @@ export class SlickGrid<TData = any, C extends Column<TData> = Column<TData>, O e
this._viewportBottomR.style.overflowY = this._options.alwaysShowVerticalScroll ? 'scroll' : ((this.hasFrozenColumns()) ? (this.hasFrozenRows ? 'auto' : 'auto') : (this.hasFrozenRows ? 'auto' : 'auto'));

if (this._options.viewportClass) {
this._viewportTopL.classList.add(...this._options.viewportClass.split(' '));
this._viewportTopR.classList.add(...this._options.viewportClass.split(' '));
this._viewportBottomL.classList.add(...this._options.viewportClass.split(' '));
this._viewportBottomR.classList.add(...this._options.viewportClass.split(' '));
const viewportClasses = classNameToList(this._options.viewportClass);
this._viewportTopL.classList.add(...viewportClasses);
this._viewportTopR.classList.add(...viewportClasses);
this._viewportBottomL.classList.add(...viewportClasses);
this._viewportBottomR.classList.add(...viewportClasses);
}
}

Expand Down Expand Up @@ -3617,11 +3618,11 @@ export class SlickGrid<TData = any, C extends Column<TData> = Column<TData>, O e
this.applyHtmlCode(cellNode, formatterVal);

if ((formatterResult as FormatterResultObject).removeClasses && !suppressRemove) {
const classes = (formatterResult as FormatterResultObject).removeClasses!.split(' ');
const classes = classNameToList((formatterResult as FormatterResultObject).removeClasses);
classes.forEach((c) => cellNode.classList.remove(c));
}
if ((formatterResult as FormatterResultObject).addClasses) {
const classes = (formatterResult as FormatterResultObject).addClasses!.split(' ');
const classes = classNameToList((formatterResult as FormatterResultObject).addClasses);
classes.forEach((c) => cellNode.classList.add(c));
}
if ((formatterResult as FormatterResultObject).toolTip) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -832,7 +832,7 @@ describe('GridMenuControl', () => {
});

it('should add a custom Grid Menu item with "iconCssClass" and expect an icon to be included on the item DOM element', () => {
gridOptionsMock.gridMenu!.commandItems = [{ command: 'help', title: 'Help', iconCssClass: 'mdi mdi-close' }];
gridOptionsMock.gridMenu!.commandItems = [{ command: 'help', title: 'Help', iconCssClass: 'mdi mdi-close' }];
control.columns = columnsMock;
control.init();
const buttonElm = document.querySelector('.slick-grid-menu-button') as HTMLDivElement;
Expand Down
12 changes: 6 additions & 6 deletions packages/common/src/extensions/menuBaseClass.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { BindingEventService } from '@slickgrid-universal/binding';
import type { BasePubSubService } from '@slickgrid-universal/event-pub-sub';
import { createDomElement, emptyElement, hasData } from '@slickgrid-universal/utils';
import { createDomElement, emptyElement, hasData, classNameToList } from '@slickgrid-universal/utils';

import type {
CellMenu,
Expand Down Expand Up @@ -135,7 +135,7 @@ export class MenuBaseClass<M extends CellMenu | ContextMenu | GridMenu | HeaderM
subMenuTitleElm.textContent = (item as MenuCommandItem | MenuOptionItem | GridMenuItem).subMenuTitle as string;
const subMenuTitleClass = (item as MenuCommandItem | MenuOptionItem | GridMenuItem).subMenuTitleCssClass as string;
if (subMenuTitleClass) {
subMenuTitleElm.classList.add(...subMenuTitleClass.split(' '));
subMenuTitleElm.classList.add(...classNameToList(subMenuTitleClass));
}
commandOrOptionMenu.appendChild(subMenuTitleElm);
}
Expand Down Expand Up @@ -241,7 +241,7 @@ export class MenuBaseClass<M extends CellMenu | ContextMenu | GridMenu | HeaderM
}

if (item.cssClass) {
commandLiElm.classList.add(...item.cssClass.split(' '));
commandLiElm.classList.add(...classNameToList(item.cssClass));
}

if (item.tooltip) {
Expand All @@ -254,7 +254,7 @@ export class MenuBaseClass<M extends CellMenu | ContextMenu | GridMenu | HeaderM
commandLiElm.appendChild(iconElm);

if ((item as MenuCommandItem | MenuOptionItem).iconCssClass) {
iconElm.classList.add(...(item as MenuCommandItem | MenuOptionItem).iconCssClass!.split(' '));
iconElm.classList.add(...classNameToList((item as MenuCommandItem | MenuOptionItem).iconCssClass));
} else if (!(item as MenuCommandItem).commandItems && !(item as MenuOptionItem).optionItems) {
iconElm.textContent = '◦';
}
Expand All @@ -269,7 +269,7 @@ export class MenuBaseClass<M extends CellMenu | ContextMenu | GridMenu | HeaderM
);

if ((item as MenuCommandItem | MenuOptionItem).textCssClass) {
textElm.classList.add(...(item as MenuCommandItem | MenuOptionItem).textCssClass!.split(' '));
textElm.classList.add(...classNameToList((item as MenuCommandItem | MenuOptionItem).textCssClass));
}
}

Expand Down Expand Up @@ -299,7 +299,7 @@ export class MenuBaseClass<M extends CellMenu | ContextMenu | GridMenu | HeaderM
const chevronElm = document.createElement('span');
chevronElm.className = 'sub-item-chevron';
if ((this._addonOptions as any).subItemChevronClass) {
chevronElm.classList.add(...(this._addonOptions as any).subItemChevronClass.split(' '));
chevronElm.classList.add(...classNameToList((this._addonOptions as any).subItemChevronClass));
} else {
chevronElm.textContent = '⮞'; // ⮞ or ▸
}
Expand Down
16 changes: 8 additions & 8 deletions packages/common/src/extensions/slickDraggableGrouping.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { BindingEventService } from '@slickgrid-universal/binding';
import type { BasePubSubService, EventSubscription } from '@slickgrid-universal/event-pub-sub';
import { createDomElement, emptyElement, isEmptyObject } from '@slickgrid-universal/utils';
import { createDomElement, emptyElement, isEmptyObject, classNameToList } from '@slickgrid-universal/utils';
import Sortable, { type Options as SortableOptions, type SortableEvent } from 'sortablejs';

import type { ExtensionUtility } from '../extensions/extensionUtility';
Expand All @@ -16,7 +16,7 @@ import type {
} from '../interfaces/index';
import type { SharedService } from '../services/shared.service';
import { sortByFieldType } from '../sortComparers';
import { type SlickDataView, SlickEvent, SlickEventData, SlickEventHandler, type SlickGrid } from '../core/index';
import { type SlickDataView, SlickEvent, SlickEventData, SlickEventHandler, type SlickGrid, } from '../core/index';

/**
*
Expand Down Expand Up @@ -186,7 +186,7 @@ export class SlickDraggableGrouping {
if (this._addonOptions.groupIconCssClass) {
const groupableIconElm = createDomElement('span', { className: 'slick-column-groupable' }, node);
if (this._addonOptions.groupIconCssClass) {
groupableIconElm.classList.add(...this._addonOptions.groupIconCssClass.split(' '));
groupableIconElm.classList.add(...classNameToList(this._addonOptions.groupIconCssClass));
}
}
}
Expand Down Expand Up @@ -401,17 +401,17 @@ export class SlickDraggableGrouping {
if (sortAsc) {
// ascending icon
if (this._addonOptions.sortAscIconCssClass) {
groupSortContainerElm.classList.remove(...this._addonOptions.sortDescIconCssClass?.split(' ') ?? '');
groupSortContainerElm.classList.add(...this._addonOptions.sortAscIconCssClass.split(' '));
groupSortContainerElm.classList.remove(...classNameToList(this._addonOptions.sortDescIconCssClass));
groupSortContainerElm.classList.add(...classNameToList(this._addonOptions.sortAscIconCssClass));
} else {
groupSortContainerElm.classList.add('slick-groupby-sort-asc-icon');
groupSortContainerElm.classList.remove('slick-groupby-sort-desc-icon');
}
} else {
// descending icon
if (this._addonOptions.sortDescIconCssClass) {
groupSortContainerElm.classList.remove(...this._addonOptions.sortAscIconCssClass?.split(' ') ?? '');
groupSortContainerElm.classList.add(...this._addonOptions.sortDescIconCssClass.split(' '));
groupSortContainerElm.classList.remove(...classNameToList(this._addonOptions.sortAscIconCssClass));
groupSortContainerElm.classList.add(...classNameToList(this._addonOptions.sortDescIconCssClass));
} else {
if (!this._addonOptions.sortDescIconCssClass) {
groupSortContainerElm.classList.add('slick-groupby-sort-desc-icon');
Expand Down Expand Up @@ -448,7 +448,7 @@ export class SlickDraggableGrouping {
// delete icon
const groupRemoveIconElm = createDomElement('div', { className: 'slick-groupby-remove' });
if (this._addonOptions.deleteIconCssClass) {
groupRemoveIconElm.classList.add(...this._addonOptions.deleteIconCssClass.split(' '));
groupRemoveIconElm.classList.add(...classNameToList(this._addonOptions.deleteIconCssClass));
}
if (!this._addonOptions.deleteIconCssClass) {
groupRemoveIconElm.classList.add('slick-groupby-remove-icon');
Expand Down
4 changes: 2 additions & 2 deletions packages/common/src/extensions/slickGridMenu.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { BasePubSubService } from '@slickgrid-universal/event-pub-sub';
import { calculateAvailableSpace, createDomElement, emptyElement, findWidthOrDefault, getOffset, } from '@slickgrid-universal/utils';
import { calculateAvailableSpace, createDomElement, emptyElement, findWidthOrDefault, getOffset, classNameToList, } from '@slickgrid-universal/utils';

import type {
Column,
Expand Down Expand Up @@ -207,7 +207,7 @@ export class SlickGridMenu extends MenuBaseClass<GridMenu> {
if (showButton) {
this._gridMenuButtonElm = createDomElement('button', { className: 'slick-grid-menu-button', ariaLabel: 'Grid Menu' });
if (this._addonOptions?.iconCssClass) {
this._gridMenuButtonElm.classList.add(...this._addonOptions.iconCssClass.split(' '));
this._gridMenuButtonElm.classList.add(...classNameToList(this._addonOptions.iconCssClass));
}
this._headerElm.parentElement!.insertBefore(this._gridMenuButtonElm, this._headerElm.parentElement!.firstChild);

Expand Down
4 changes: 2 additions & 2 deletions packages/common/src/extensions/slickHeaderMenu.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { BasePubSubService } from '@slickgrid-universal/event-pub-sub';
import { arrayRemoveItemByIndex, calculateAvailableSpace, createDomElement, getOffsetRelativeToParent, getOffset, } from '@slickgrid-universal/utils';
import { arrayRemoveItemByIndex, calculateAvailableSpace, createDomElement, getOffsetRelativeToParent, getOffset, classNameToList } from '@slickgrid-universal/utils';

import { EmitterType } from '../enums/index';
import type {
Expand Down Expand Up @@ -229,7 +229,7 @@ export class SlickHeaderMenu extends MenuBaseClass<HeaderMenu> {
const headerButtonDivElm = createDomElement('div', { className: 'slick-header-menu-button', ariaLabel: 'Header Menu' }, args.node);

if (this.addonOptions.buttonCssClass) {
headerButtonDivElm.classList.add(...this.addonOptions.buttonCssClass.split(' '));
headerButtonDivElm.classList.add(...classNameToList(this.addonOptions.buttonCssClass));
}

if (this.addonOptions.tooltip) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { BindingEventService } from '@slickgrid-universal/binding';
import { deepCopy, deepMerge, emptyObject, setDeepValue } from '@slickgrid-universal/utils';
import { deepCopy, deepMerge, emptyObject, setDeepValue, classNameToList } from '@slickgrid-universal/utils';
import type {
Column,
CompositeEditorLabel,
Expand Down Expand Up @@ -634,7 +634,7 @@ export class SlickCompositeEditorComponent implements ExternalResource {
});

if (this._options?.resetEditorButtonCssClass) {
const resetBtnClasses = this._options?.resetEditorButtonCssClass.split(' ');
const resetBtnClasses = classNameToList(this._options?.resetEditorButtonCssClass);
for (const cssClass of resetBtnClasses) {
resetButtonElm.classList.add(cssClass);
}
Expand Down
17 changes: 17 additions & 0 deletions packages/utils/src/__tests__/domUtils.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import 'jest-extended';

import {
calculateAvailableSpace,
classNameToList,
createDomElement,
destroyAllElementProps,
emptyElement,
Expand Down Expand Up @@ -51,6 +52,22 @@ describe('Service/domUtilies', () => {
});
});

describe('classNameToList() method', () => {
it('should return empty array when input string is undefined', () => {
expect(classNameToList(undefined)).toEqual([]);
});

it('should return an array with 2 words when multiple whitespaces are part of the string', () => {
const input = ' hello world ';
expect(classNameToList(input)).toEqual(['hello', 'world']);
});

it('should return an array with 4 words when input includes hypens and multiple whitespaces', () => {
const input = ' my-class another--class hello world! ';
expect(classNameToList(input)).toEqual(['my-class', 'another--class', 'hello', 'world!']);
});
});

describe('createDomElement() method', () => {
it('should create a DOM element via the method to equal a regular DOM element', () => {
const div = document.createElement('div');
Expand Down
5 changes: 5 additions & 0 deletions packages/utils/src/domUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,11 @@ export function createDomElement<T extends keyof HTMLElementTagNameMap, K extend
return elm;
}

/** Takes an input string and splits it into an array of words (extra whitespaces are ignored). */
export function classNameToList(s = ''): string[] {
return s.split(' ').filter(cls => cls); // filter will remove whitespace entries
}

/**
* Loop through all properties of an object and nullify any properties that are instanceof HTMLElement,
* if we detect an array then use recursion to go inside it and apply same logic
Expand Down