Skip to content

Commit

Permalink
fix(core): allow extra spaces in headerCssClass & other cssClass (#…
Browse files Browse the repository at this point in the history
…1303)

* fix(core): allow extra spaces in `headerCssClass` & other `cssClass`
  • Loading branch information
ghiscoding committed Jan 2, 2024
1 parent b0102b5 commit 59ebaa6
Show file tree
Hide file tree
Showing 10 changed files with 56 additions and 33 deletions.
6 changes: 3 additions & 3 deletions packages/common/src/core/__tests__/slickGrid.spec.ts
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
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
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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
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
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

0 comments on commit 59ebaa6

Please sign in to comment.