Skip to content

Commit

Permalink
Feat(web): Introduce Config class for loading configuration
Browse files Browse the repository at this point in the history
refs #DS-1012
  • Loading branch information
literat committed Oct 31, 2023
1 parent 1d3bf3d commit 87b1dd8
Show file tree
Hide file tree
Showing 4 changed files with 147 additions and 5 deletions.
17 changes: 14 additions & 3 deletions packages/web/src/js/BaseComponent.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
import { getElement } from './utils/index';
import InstanceMap from './dom/InstanceMap';
import Config from './utils/Config';
import { getElement } from './utils/index';

interface IBaseComponent extends FunctionConstructor {
INSTANCE_KEY: string;
}

class BaseComponent {
class BaseComponent extends Config {
element: SpiritElement;
config: unknown;
NAME: string | null;

constructor(element: SpiritElement | string) {
constructor(element: SpiritElement | string, config) {
this.element = getElement(element);
this.NAME = '';
this.config = this.getConfig(config);

InstanceMap.set(this.element, (this.constructor as IBaseComponent).INSTANCE_KEY, this);
}
Expand All @@ -27,6 +30,14 @@ class BaseComponent {
}
}

getConfig(config) {
config = this.mergeConfigObj(config, this.element);
config = this.configAfterMerge(config);
this.typeCheckConfig(config);

return config;
}

static get NAME() {
return '';
}
Expand Down
66 changes: 66 additions & 0 deletions packages/web/src/js/dom/Manipulator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
function normalizeData(value) {
if (value === 'true') {
return true;
}

if (value === 'false') {
return false;
}

if (value === Number(value).toString()) {
return Number(value);
}

if (value === '' || value === 'null') {
return null;
}

if (typeof value !== 'string') {
return value;
}

try {
return JSON.parse(decodeURIComponent(value));
} catch {
return value;
}
}

function normalizeDataKey(key) {
return key.replace(/[A-Z]/g, (chr) => `-${chr.toLowerCase()}`);
}

const Manipulator = {
setDataAttribute(element, key, value) {
element.setAttribute(`data-spirit-${normalizeDataKey(key)}`, value);
},

removeDataAttribute(element, key) {
element.removeAttribute(`data-spirit-${normalizeDataKey(key)}`);
},

getDataAttributes(element) {
if (!element) {
return {};
}

const attributes = {};
const bsKeys = Object.keys(element.dataset).filter(
(key) => key.startsWith('spirit') && !key.startsWith('spiritConfig'),
);

for (const key of bsKeys) {
let pureKey = key.replace(/^spirit/, '');
pureKey = pureKey.charAt(0).toLowerCase() + pureKey.slice(1, pureKey.length);
attributes[pureKey] = normalizeData(element.dataset[key]);
}

return attributes;
},

getDataAttribute(element, key) {
return normalizeData(element.getAttribute(`data-spirit-${normalizeDataKey(key)}`));
},
};

export default Manipulator;
65 changes: 65 additions & 0 deletions packages/web/src/js/utils/Config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import Manipulator from '../dom/Manipulator';
import { isElement } from './index';

// Shout-out Angus Croll (https://goo.gl/pxwQGp)
const toType = (object) => {
if (object === null || object === undefined) {
return `${object}`;
}

return Object.prototype.toString
.call(object)
.match(/\s([a-z]+)/i)[1]
.toLowerCase();
};

class Config {
static get Default() {
return {};
}

static get DefaultType() {
return {};
}

static get NAME() {
throw new Error('You have to implement the static method "NAME", for each component!');
}

getConfig(config) {
config = this._mergeConfigObj(config);
config = this._configAfterMerge(config);
this._typeCheckConfig(config);
return config;
}

configAfterMerge(config) {
return config;
}

mergeConfigObj(config, element) {
const jsonConfig = isElement(element) ? Manipulator.getDataAttribute(element, 'config') : {}; // try to parse

return {
...this.constructor.Default,
...(typeof jsonConfig === 'object' ? jsonConfig : {}),
...(isElement(element) ? Manipulator.getDataAttributes(element) : {}),
...(typeof config === 'object' ? config : {}),
};
}

typeCheckConfig(config, configTypes = this.constructor.DefaultType) {
for (const [property, expectedTypes] of Object.entries(configTypes)) {
const value = config[property];
const valueType = isElement(value) ? 'element' : toType(value);

if (!new RegExp(expectedTypes).test(valueType)) {
throw new TypeError(
`${this.constructor.NAME.toUpperCase()}: Option "${property}" provided type "${valueType}" but expected type "${expectedTypes}".`,
);
}
}
}
}

export default Config;
4 changes: 2 additions & 2 deletions packages/web/src/js/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ const reflow = (element: HTMLElement): void => {
element.offsetHeight;
};

export { reflow };
export * from './ComponentFunctions';
export * from './Debounce';
export * from './Deprecations';
export * from './Elements';
export * from './Image2Base64Preview';
export { default as ScrollControl } from './ScrollControl';
export * from './Transitions';
export * from './Image2Base64Preview';
export { reflow };

0 comments on commit 87b1dd8

Please sign in to comment.