@schukai/monster 3.38.0 → 3.39.0
Sign up to get free protection for your applications and to get access to all the features.
package/package.json
CHANGED
@@ -30,6 +30,7 @@ import { addObjectWithUpdaterToElement } from "./updater.mjs";
|
|
30
30
|
import { instanceSymbol } from "../constants.mjs";
|
31
31
|
import { getDocumentTranslations, Translations } from "../i18n/translations.mjs";
|
32
32
|
import { getSlottedElements } from "./slotted.mjs";
|
33
|
+
import {initOptionsFromAttributes} from "./util/init-options-from-attributes.mjs";
|
33
34
|
|
34
35
|
export {
|
35
36
|
CustomElement,
|
@@ -197,8 +198,9 @@ class CustomElement extends HTMLElement {
|
|
197
198
|
*/
|
198
199
|
constructor() {
|
199
200
|
super();
|
201
|
+
|
200
202
|
this[internalSymbol] = new ProxyObserver({
|
201
|
-
options: extend({}, this.defaults),
|
203
|
+
options: initOptionsFromAttributes(this, extend({}, this.defaults)),
|
202
204
|
});
|
203
205
|
this[attributeObserverSymbol] = {};
|
204
206
|
initOptionObserver.call(this);
|
@@ -7,18 +7,19 @@
|
|
7
7
|
|
8
8
|
import {Pathfinder} from '../../data/pathfinder.mjs';
|
9
9
|
import {isFunction} from '../../types/is.mjs';
|
10
|
+
import {attributeObserverSymbol} from "../customelement.mjs";
|
10
11
|
|
11
|
-
export {initOptionsFromAttributes
|
12
|
+
export {initOptionsFromAttributes};
|
12
13
|
|
13
14
|
/**
|
14
15
|
* Initializes the given options object based on the attributes of the current DOM element.
|
15
16
|
* The function looks for attributes with the prefix 'data-monster-option-', and maps them to
|
16
17
|
* properties in the options object. It replaces the dashes with dots to form the property path.
|
17
18
|
* For example, the attribute 'data-monster-option-url' maps to the 'url' property in the options object.
|
18
|
-
*
|
19
|
+
*
|
19
20
|
* With the mapping parameter, the attribute value can be mapped to a different value.
|
20
21
|
* For example, the attribute 'data-monster-option-foo' maps to the 'bar' property in the options object.
|
21
|
-
*
|
22
|
+
*
|
22
23
|
* The mapping object would look like this:
|
23
24
|
* {
|
24
25
|
* 'foo': (value) => value + 'bar'
|
@@ -31,6 +32,7 @@ export {initOptionsFromAttributes };
|
|
31
32
|
* // e.g. <div data-monster-option-bar-baz="foo"></div>
|
32
33
|
* }
|
33
34
|
*
|
35
|
+
* @since 3.38.0
|
34
36
|
* @param {HTMLElement} element - The DOM element to be used as the source of the attributes.
|
35
37
|
* @param {Object} options - The options object to be initialized.
|
36
38
|
* @param {Object} mapping - A mapping between the attribute value and the property value.
|
@@ -38,10 +40,12 @@ export {initOptionsFromAttributes };
|
|
38
40
|
* @returns {Object} - The initialized options object.
|
39
41
|
* @this HTMLElement - The context of the DOM element.
|
40
42
|
*/
|
41
|
-
function initOptionsFromAttributes(element, options, mapping={},prefix = 'data-monster-option-') {
|
43
|
+
function initOptionsFromAttributes(element, options, mapping = {}, prefix = 'data-monster-option-') {
|
42
44
|
if (!(element instanceof HTMLElement)) return options;
|
43
45
|
if (!element.hasAttributes()) return options;
|
44
46
|
|
47
|
+
const keyMap = extractKeys(options);
|
48
|
+
|
45
49
|
const finder = new Pathfinder(options);
|
46
50
|
|
47
51
|
element.getAttributeNames().forEach((name) => {
|
@@ -50,15 +54,15 @@ function initOptionsFromAttributes(element, options, mapping={},prefix = 'data-m
|
|
50
54
|
// check if the attribute name is a valid option.
|
51
55
|
// the mapping between the attribute is simple. The dash is replaced by a dot.
|
52
56
|
// e.g. data-monster-url => url
|
53
|
-
const optionName = name.
|
57
|
+
const optionName = keyMap.get(name.substring(prefix.length).toLowerCase());
|
54
58
|
if (!finder.exists(optionName)) return;
|
55
59
|
|
56
60
|
if (element.hasAttribute(name)) {
|
57
61
|
let value = element.getAttribute(name);
|
58
|
-
if (mapping.hasOwnProperty(optionName)&&isFunction(mapping[optionName])) {
|
62
|
+
if (mapping.hasOwnProperty(optionName) && isFunction(mapping[optionName])) {
|
59
63
|
value = mapping[optionName](value);
|
60
64
|
}
|
61
|
-
|
65
|
+
|
62
66
|
const typeOfOptionValue = typeof finder.getVia(optionName);
|
63
67
|
if (typeOfOptionValue === 'boolean') {
|
64
68
|
value = value === 'true';
|
@@ -69,10 +73,65 @@ function initOptionsFromAttributes(element, options, mapping={},prefix = 'data-m
|
|
69
73
|
} else if (typeOfOptionValue === 'object') {
|
70
74
|
value = JSON.parse(value);
|
71
75
|
}
|
72
|
-
|
76
|
+
|
73
77
|
finder.setVia(optionName, value);
|
78
|
+
|
79
|
+
// if element has an attribute observer, then register the attribute observer
|
80
|
+
if (element?.[attributeObserverSymbol]) {
|
81
|
+
element[attributeObserverSymbol][name] = (newValue, oldValue) => {
|
82
|
+
|
83
|
+
if (newValue === oldValue) return;
|
84
|
+
|
85
|
+
let changedValue = newValue;
|
86
|
+
|
87
|
+
if (typeOfOptionValue === 'boolean') {
|
88
|
+
changedValue = changedValue === 'true';
|
89
|
+
} else if (typeOfOptionValue === 'number') {
|
90
|
+
changedValue = Number(changedValue);
|
91
|
+
} else if (typeOfOptionValue === 'string') {
|
92
|
+
changedValue = String(changedValue);
|
93
|
+
} else if (typeOfOptionValue === 'object') {
|
94
|
+
changedValue = JSON.parse(changedValue);
|
95
|
+
}
|
96
|
+
|
97
|
+
finder.setVia(optionName, changedValue);
|
98
|
+
}
|
99
|
+
}
|
100
|
+
|
101
|
+
|
74
102
|
}
|
75
103
|
})
|
76
104
|
|
77
105
|
return options;
|
78
|
-
}
|
106
|
+
}
|
107
|
+
|
108
|
+
/**
|
109
|
+
* Extracts the keys from the given object and returns a map with the keys and values.
|
110
|
+
*
|
111
|
+
* @private
|
112
|
+
* @param {object} obj
|
113
|
+
* @param {string} keyPrefix
|
114
|
+
* @param {string} keySeparator
|
115
|
+
* @param {string} valueSeparator
|
116
|
+
* @returns {Map<any, any>}
|
117
|
+
*/
|
118
|
+
function extractKeys(obj, keyPrefix = '', keySeparator = '-', valueSeparator = '.') {
|
119
|
+
const resultMap = new Map();
|
120
|
+
|
121
|
+
function helper(currentObj, currentKeyPrefix, currentValuePrefix) {
|
122
|
+
for (const key in currentObj) {
|
123
|
+
if (typeof currentObj[key] === 'object' && !Array.isArray(currentObj[key])) {
|
124
|
+
const newKeyPrefix = currentKeyPrefix ? currentKeyPrefix + keySeparator + key.toLowerCase() : key.toLowerCase();
|
125
|
+
const newValuePrefix = currentValuePrefix ? currentValuePrefix + valueSeparator + key : key;
|
126
|
+
helper(currentObj[key], newKeyPrefix, newValuePrefix);
|
127
|
+
} else {
|
128
|
+
const finalKey = currentKeyPrefix ? currentKeyPrefix + keySeparator + key.toLowerCase() : key.toLowerCase();
|
129
|
+
const finalValue = currentValuePrefix ? currentValuePrefix + valueSeparator + key : key;
|
130
|
+
resultMap.set(finalKey, finalValue);
|
131
|
+
}
|
132
|
+
}
|
133
|
+
}
|
134
|
+
|
135
|
+
helper(obj, keyPrefix, keyPrefix);
|
136
|
+
return resultMap;
|
137
|
+
}
|
package/source/types/version.mjs
CHANGED
@@ -1,24 +1,29 @@
|
|
1
1
|
import {expect} from "chai"
|
2
2
|
|
3
|
-
import {initOptionsFromAttributes} from "../../../../..//application/source/dom/util/init-options-from-attributes.mjs";
|
4
3
|
import {initJSDOM} from "../../../util/jsdom.mjs";
|
5
4
|
|
6
5
|
describe('initOptionsFromAttributes', () => {
|
7
6
|
let element;
|
8
7
|
let options;
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
8
|
+
let initOptionsFromAttributes;
|
9
|
+
|
10
|
+
before( function (done) {
|
11
|
+
initJSDOM().then(() => {
|
12
|
+
import("../../../../..//application/source/dom/util/init-options-from-attributes.mjs").then((m) => {
|
13
|
+
initOptionsFromAttributes = m['initOptionsFromAttributes'];
|
14
|
+
done();
|
15
|
+
})
|
16
|
+
})
|
17
|
+
});
|
13
18
|
|
14
19
|
beforeEach(() => {
|
15
|
-
options = {
|
20
|
+
options = {url: "", key: {subkey: "", caseSensitive: true}};
|
16
21
|
element = document.createElement('div');
|
17
22
|
});
|
18
23
|
|
19
24
|
it('should initialize options with matching attributes', () => {
|
20
25
|
element.setAttribute('data-monster-option-url', 'https://example.com');
|
21
|
-
element.setAttribute('data-monster-option-key
|
26
|
+
element.setAttribute('data-monster-option-key-subkey', 'test');
|
22
27
|
|
23
28
|
const result = initOptionsFromAttributes(element, options);
|
24
29
|
|
@@ -73,7 +78,7 @@ describe('initOptionsFromAttributes', () => {
|
|
73
78
|
|
74
79
|
it('should apply multiple mappings', () => {
|
75
80
|
element.setAttribute('data-monster-option-url', 'example');
|
76
|
-
element.setAttribute('data-monster-option-key
|
81
|
+
element.setAttribute('data-monster-option-key-subkey', '123');
|
77
82
|
const mapping = {
|
78
83
|
'url': (value) => 'https://' + value + '.com',
|
79
84
|
'key.subkey': (value) => parseInt(value, 10) * 2
|
@@ -108,7 +113,7 @@ describe('initOptionsFromAttributes', () => {
|
|
108
113
|
|
109
114
|
it('should apply mapping only to specified attributes', () => {
|
110
115
|
element.setAttribute('data-monster-option-url', 'example');
|
111
|
-
element.setAttribute('data-monster-option-key
|
116
|
+
element.setAttribute('data-monster-option-key-subkey', '123');
|
112
117
|
const mapping = {
|
113
118
|
'url': (value) => 'https://' + value + '.com'
|
114
119
|
};
|
@@ -150,6 +155,13 @@ describe('initOptionsFromAttributes', () => {
|
|
150
155
|
const result = initOptionsFromAttributes(element, options, mapping);
|
151
156
|
|
152
157
|
expect(result.url).to.equal('');
|
153
|
-
});
|
154
|
-
|
158
|
+
});
|
159
|
+
|
160
|
+
it('should apply case sensitive mapping', () => {
|
161
|
+
element.setAttribute('data-monster-option-key-caseSensitive', 'false');
|
162
|
+
const result = initOptionsFromAttributes(element, options);
|
163
|
+
|
164
|
+
expect(result.key.caseSensitive).to.equal(false);
|
165
|
+
});
|
166
|
+
|
155
167
|
});
|
package/test/cases/monster.mjs
CHANGED