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

feat(utils): replace slick-core extend utils with node-extend #1277

Merged
merged 4 commits into from
Dec 21, 2023
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
125 changes: 0 additions & 125 deletions packages/common/src/core/__tests__/slickCore.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -415,32 +415,6 @@ describe('SlickCore file', () => {
});

describe('Utils', () => {
describe('isPlainObject', () => {
it('should be falsy when object contains prototype methods', () => {
const l = console.log;
const obj = {
method: () => l("method in obj")
};
const obj2: any = { hello: 'world' };
obj2.__proto__ = obj;

expect(Utils.isPlainObject(obj2)).toBeFalsy();
});

it('should be truthy when object does not contains any prototype', () => {
const obj2: any = { hello: 'world' };
obj2.__proto__ = null;

expect(Utils.isPlainObject(obj2)).toBeTruthy();
});

it('should be truthy when object is a regular object without methods', () => {
const obj2 = { hello: 'world' };

expect(Utils.isPlainObject(obj2)).toBeTruthy();
});
});

describe('storage() function', () => {
it('should be able to store an object and retrieve it later', () => {
const div = document.createElement('div');
Expand Down Expand Up @@ -489,105 +463,6 @@ describe('SlickCore file', () => {
});
});

describe('extend() function', () => {
it('should be able to make a perfect deep copy of an object', () => {
const callback = () => console.log('hello');
const obj1 = { hello: { sender: 'me', target: 'world' }, deeper: { children: ['abc', 'cde'], callback } };
const obj2 = Utils.extend(true, {}, obj1, { another: 'prop' });

expect(obj2).toEqual({ hello: { sender: 'me', target: 'world' }, deeper: { children: ['abc', 'cde'], callback }, another: 'prop' });
});

it('should be able to make a perfect deep copy of an object that includes String() and Boolean() constructors', () => {
const callback = () => console.log('hello');
const obj1 = { hello: { sender: 'me', target: String(123), valid: Boolean(null) }, deeper: { children: ['abc', 'cde'], callback } };
const obj2 = Utils.extend(true, {}, obj1, { another: 'prop' });

expect(obj2).toEqual({ hello: { sender: 'me', target: '123', valid: false }, deeper: { children: ['abc', 'cde'], callback }, another: 'prop' });
});

it('should be able to make a deep copy of an object and changing new object prop should not affect input object', () => {
const obj1 = { hello: { sender: 'me', target: 'world' }, deeper: { children: ['abc', 'cde'] } };
const obj2 = Utils.extend(true, {}, obj1, { another: 'prop' });
obj2.hello.target = 'mum';

expect(obj1).toEqual({ hello: { sender: 'me', target: 'world' }, deeper: { children: ['abc', 'cde'] } });
expect(obj2).toEqual({ hello: { sender: 'me', target: 'mum' }, deeper: { children: ['abc', 'cde'] }, another: 'prop' });
});

it('should assume an extended object when passing true boolean but ommitting empty object as target, so changing output object will impact input object as well', () => {
const obj1 = { hello: { sender: 'me', target: 'world' }, deeper: { children: ['abc', 'cde'] } };
const obj2 = Utils.extend(true, obj1, { another: 'prop' });
obj2.hello.target = 'mum';

expect(obj1).toEqual({ hello: { sender: 'me', target: 'mum' }, deeper: { children: ['abc', 'cde'] }, another: 'prop' });
expect(obj2).toEqual({ hello: { sender: 'me', target: 'mum' }, deeper: { children: ['abc', 'cde'] }, another: 'prop' });
});

it('should assume an extended object when ommitting true boolean, so changing output object will impact input object as well', () => {
const obj1 = { hello: { sender: 'me', target: 'world' }, deeper: { children: ['abc', 'cde'] } };
const obj2 = Utils.extend(obj1, { another: { age: 20 } });
obj2.hello.target = 'mum';

expect(obj1).toEqual({ hello: { sender: 'me', target: 'mum' }, deeper: { children: ['abc', 'cde'] }, another: { age: 20 } });
expect(obj2).toEqual({ hello: { sender: 'me', target: 'mum' }, deeper: { children: ['abc', 'cde'] }, another: { age: 20 } });
});

it('should return same object when passing input object twice', () => {
const obj1 = { hello: { sender: 'me', target: 'world' }, deeper: { children: ['abc', 'cde'] } };
const obj2 = Utils.extend(true, {}, obj1, obj1);

expect(obj2).toEqual(obj1);
});

it('should do a deep copy of an array of objects with properties having objects and changing object property should not affect original object', () => {
const obj1 = { firstName: 'John', lastName: 'Doe', address: { zip: 123456 } };
const obj2 = { firstName: 'Jane', lastName: 'Doe', address: { zip: 222222 } };
const arr1 = [obj1, obj2];
const arr2 = Utils.extend(true, [], arr1);
arr2[0].address.zip = 888888;
arr2[1].address.zip = 999999;

expect(arr1[0].address.zip).toBe(123456);
expect(arr1[1].address.zip).toBe(222222);
expect(arr2[0].address.zip).toBe(888888);
expect(arr2[1].address.zip).toBe(999999);
});

it('should return same object when passing only a single object', () => {
expect(Utils.extend({ hello: 'world' })).toEqual({ hello: 'world' });
});

it('should expect Symbol to be converted to Object', () => {
const sym1 = Symbol("foo");
const sym2 = Symbol("bar");

expect(Utils.extend(sym1, sym2, { hello: 'world' })).toEqual({ hello: 'world' });
});

it('should be able to make a copy of an object with prototype', () => {
const l = console.log;
const method = () => l("method in obj");
const obj = {
method
};
const obj2: any = { hello: 'world' };
obj2.__proto__ = obj;

const obj1 = { hello: { sender: 'me', target: 'world' }, deeper: { children: ['abc', 'cde'] } };
const obj3 = Utils.extend(obj1, obj2);

expect(obj3).toEqual({ hello: 'world', deeper: { children: ['abc', 'cde'] }, method });
});
});

describe('noop() function', () => {
it('should return empty function', () => {
expect(typeof Utils.noop).toBe('function');
expect(Utils.noop()).toBeUndefined();
});
});

describe('height() function', () => {
it('should return null when calling without a valid element', () => {
const result = Utils.height(null as any);
Expand Down
84 changes: 0 additions & 84 deletions packages/common/src/core/slickCore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@
* @module Core
* @namespace Slick
*/
import { isDefined } from '@slickgrid-universal/utils';

import { MergeTypes } from '../enums/index';
import type { CSSStyleDeclarationWritable, EditController } from '../interfaces';

Expand Down Expand Up @@ -557,13 +555,6 @@ export class SlickEditorLock {
}

export class Utils {
// jQuery's extend
private static getProto = Object.getPrototypeOf;
private static class2type: any = {};
private static toString = Utils.class2type.toString;
private static hasOwn = Utils.class2type.hasOwnProperty;
private static fnToString = Utils.hasOwn.toString;
private static ObjectFunctionString = Utils.fnToString.call(Object);
public static storage = {
// https://stackoverflow.com/questions/29222027/vanilla-alternative-to-jquery-data-function-any-native-javascript-alternati
_storage: new WeakMap(),
Expand All @@ -589,81 +580,6 @@ export class Utils {
}
};

public static isFunction(obj: any) {
return typeof obj === 'function' && typeof obj.nodeType !== 'number' && typeof obj.item !== 'function';
}

public static isPlainObject(obj: any) {
if (!obj || Utils.toString.call(obj) !== '[object Object]') {
return false;
}

const proto = Utils.getProto(obj);
if (!proto) {
return true;
}
const Ctor = Utils.hasOwn.call(proto, 'constructor') && proto.constructor;
return typeof Ctor === 'function' && Utils.fnToString.call(Ctor) === Utils.ObjectFunctionString;
}

public static extend<T = any>(...args: any[]): T {
// eslint-disable-next-line one-var
let options, name, src, copy, copyIsArray, clone;
let target = args[0];
let i = 1;
let deep = false;
const length = args.length;

if (target === true) {
deep = target;
target = args[i] || {};
i++;
} else {
target = target || {};
}
if (typeof target !== 'object' && !Utils.isFunction(target)) {
target = {}; // Symbol and others will be converted to Object
}
if (length === 1) {
return args[0];
}
/* istanbul ignore if */
if (i === length) {
// @ts-ignore
// eslint-disable-next-line @typescript-eslint/no-this-alias
target = this;
i--;
}
for (; i < length; i++) {
if (isDefined(options = args[i])) {
for (name in options) {
copy = options[name];
/* istanbul ignore if */
if (name === '__proto__' || target === copy) {
continue;
}
if (deep && copy && (Utils.isPlainObject(copy) || (copyIsArray = Array.isArray(copy)))) {
src = target[name];
if (copyIsArray && !Array.isArray(src)) {
clone = [];
} else if (!copyIsArray && !Utils.isPlainObject(src)) {
clone = {};
} else {
clone = src;
}
copyIsArray = false;
target[name] = Utils.extend(deep, clone, copy);
} else if (copy !== undefined) {
target[name] = copy;
}
}
}
}
return target as T;
}

public static noop() { }

public static height(el: HTMLElement, value?: number | string): number | void {
if (!el) {
return;
Expand Down
9 changes: 4 additions & 5 deletions packages/common/src/core/slickDataview.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* eslint-disable no-new-func */
/* eslint-disable no-bitwise */
import { isDefined } from '@slickgrid-universal/utils';
import { extend, isDefined } from '@slickgrid-universal/utils';

import { SlickGroupItemMetadataProvider } from '../extensions/slickGroupItemMetadataProvider';
import type {
Expand All @@ -26,7 +26,6 @@ import {
SlickGroup,
SlickGroupTotals,
SlickNonDataItem,
Utils,
} from './slickCore';
import type { SlickGrid } from './slickGrid';

Expand Down Expand Up @@ -137,7 +136,7 @@ export class SlickDataView<TData extends SlickDataItem = any> implements CustomD
this.onSelectedRowIdsChanged = new SlickEvent<OnSelectedRowIdsChangedEventArgs>('onSelectedRowIdsChanged', externalPubSub);
this.onSetItemsCalled = new SlickEvent<OnSetItemsCalledEventArgs>('onSetItemsCalled', externalPubSub);

this._options = Utils.extend(true, {}, this.defaults, options);
this._options = extend(true, {}, this.defaults, options);
}

/**
Expand Down Expand Up @@ -394,7 +393,7 @@ export class SlickDataView<TData extends SlickDataItem = any> implements CustomD
this.groupingInfos = ((groupingInfo instanceof Array) ? groupingInfo : [groupingInfo]) as any;

for (let i = 0; i < this.groupingInfos.length; i++) {
const gi = this.groupingInfos[i] = Utils.extend(true, {}, this.groupingInfoDefaults, this.groupingInfos[i]);
const gi = this.groupingInfos[i] = extend(true, {}, this.groupingInfoDefaults, this.groupingInfos[i]);
gi.getterIsAFn = typeof gi.getter === 'function';

// pre-compile accumulator loops
Expand Down Expand Up @@ -1319,7 +1318,7 @@ export class SlickDataView<TData extends SlickDataItem = any> implements CustomD
return;
}

const previousPagingInfo = Utils.extend(true, {}, this.getPagingInfo());
const previousPagingInfo = extend(true, {}, this.getPagingInfo());

const countBefore = this.rows.length;
const totalRowsBefore = this.totalRows;
Expand Down
12 changes: 6 additions & 6 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, getInnerSize, getOffset, insertAfterElement, isDefined, isPrimitiveOrHTML } from '@slickgrid-universal/utils';
import { createDomElement, emptyElement, extend, getInnerSize, getOffset, insertAfterElement, isDefined, isPrimitiveOrHTML } from '@slickgrid-universal/utils';

import {
type BasePubSub,
Expand Down Expand Up @@ -588,7 +588,7 @@ export class SlickGrid<TData = any, C extends Column<TData> = Column<TData>, O e
if (!options) { this._options = {} as O; }
Utils.applyDefaults(this._options, this._defaults);
} else {
this._options = Utils.extend<O>(true, {}, this._defaults, options);
this._options = extend<O>(true, {}, this._defaults, options);
}
this.scrollThrottle = this.actionThrottle(this.render.bind(this), this._options.scrollRenderThrottling as number);
this.maxSupportedCssHeight = this.maxSupportedCssHeight || this.getMaxSupportedCssHeight();
Expand Down Expand Up @@ -3002,7 +3002,7 @@ export class SlickGrid<TData = any, C extends Column<TData> = Column<TData>, O e
if (this._options.mixinDefaults) {
Utils.applyDefaults(m, this._columnDefaults);
} else {
m = this.columns[i] = Utils.extend({}, this._columnDefaults, m);
m = this.columns[i] = extend({}, this._columnDefaults, m);
}

this.columnsById[m.id] = i;
Expand Down Expand Up @@ -3076,8 +3076,8 @@ export class SlickGrid<TData = any, C extends Column<TData> = Column<TData>, O e
this.handleScroll(); // trigger scroll to realign column headers as well
}

const originalOptions = Utils.extend(true, {}, this._options);
this._options = Utils.extend(this._options, newOptions);
const originalOptions = extend(true, {}, this._options);
this._options = extend(this._options, newOptions);
this.trigger(this.onSetOptions, { optionsBefore: originalOptions, optionsAfter: this._options });

this.internal_setOptions(suppressRender, suppressColumnSet, suppressSetOverflow);
Expand Down Expand Up @@ -4339,7 +4339,7 @@ export class SlickGrid<TData = any, C extends Column<TData> = Column<TData>, O e
// add new rows & missing cells in existing rows
if (this.lastRenderedScrollLeft !== this.scrollLeft) {
if (this.hasFrozenRows) {
const renderedFrozenRows = Utils.extend(true, {}, rendered);
const renderedFrozenRows = extend(true, {}, rendered);

if (this._options.frozenBottom) {
renderedFrozenRows.top = this.actualFrozenRow;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { createDomElement } from '@slickgrid-universal/utils';
import { createDomElement, extend } from '@slickgrid-universal/utils';

import { SlickEventHandler, Utils as SlickUtils, type SlickDataView, SlickGroup, type SlickGrid } from '../core/index';
import { SlickEventHandler, type SlickDataView, SlickGroup, type SlickGrid } from '../core/index';
import type {
Column,
DOMEvent,
Expand Down Expand Up @@ -42,7 +42,7 @@ export class SlickGroupItemMetadataProvider {

constructor(inputOptions?: GroupItemMetadataProviderOption) {
this._eventHandler = new SlickEventHandler();
this._options = SlickUtils.extend<GroupItemMetadataProviderOption>(true, {}, this._defaults, inputOptions);
this._options = extend<GroupItemMetadataProviderOption>(true, {}, this._defaults, inputOptions);
}

/** Getter of the SlickGrid Event Handler */
Expand Down
5 changes: 2 additions & 3 deletions packages/common/src/services/filter.service.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { BasePubSubService } from '@slickgrid-universal/event-pub-sub';
import { deepCopy, stripTags } from '@slickgrid-universal/utils';
import { deepCopy, extend, stripTags } from '@slickgrid-universal/utils';
import { dequal } from 'dequal/lite';
import { Utils as SlickUtils } from '../core/index';

import { Constants } from '../constants';
import { FilterConditions, getParsedSearchTermsByFieldType } from './../filter-conditions/index';
Expand Down Expand Up @@ -1168,7 +1167,7 @@ export class FilterService {

// event might have been created as a CustomEvent (e.g. CompoundDateFilter), without being a valid SlickEventData,
// if so we will create a new SlickEventData and merge it with that CustomEvent to avoid having SlickGrid errors
const eventData = ((event && typeof (event as any).isPropagationStopped !== 'function') ? SlickUtils.extend({}, new SlickEventData(), event) : event);
const eventData = ((event && typeof (event as any).isPropagationStopped !== 'function') ? extend({}, new SlickEventData(), event) : event);

// trigger an event only if Filters changed or if ENTER key was pressed
const eventKey = (event as KeyboardEvent)?.key;
Expand Down