@schukai/monster 3.37.0 → 3.38.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright schukai GmbH and contributors 2023. All Rights Reserved.
|
|
3
|
+
* Node module: @schukai/monster
|
|
4
|
+
* This file is licensed under the AGPLv3 License.
|
|
5
|
+
* License text available at https://www.gnu.org/licenses/agpl-3.0.en.html
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import {Pathfinder} from '../../data/pathfinder.mjs';
|
|
9
|
+
import {isFunction} from '../../types/is.mjs';
|
|
10
|
+
|
|
11
|
+
export {initOptionsFromAttributes };
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Initializes the given options object based on the attributes of the current DOM element.
|
|
15
|
+
* The function looks for attributes with the prefix 'data-monster-option-', and maps them to
|
|
16
|
+
* properties in the options object. It replaces the dashes with dots to form the property path.
|
|
17
|
+
* For example, the attribute 'data-monster-option-url' maps to the 'url' property in the options object.
|
|
18
|
+
*
|
|
19
|
+
* With the mapping parameter, the attribute value can be mapped to a different value.
|
|
20
|
+
* For example, the attribute 'data-monster-option-foo' maps to the 'bar' property in the options object.
|
|
21
|
+
*
|
|
22
|
+
* The mapping object would look like this:
|
|
23
|
+
* {
|
|
24
|
+
* 'foo': (value) => value + 'bar'
|
|
25
|
+
* // the value of the attribute 'data-monster-option-foo' is appended with 'bar'
|
|
26
|
+
* // and assigned to the 'bar' property in the options object.
|
|
27
|
+
* // e.g. <div data-monster-option-foo="foo"></div>
|
|
28
|
+
* 'bar.baz': (value) => value + 'bar'
|
|
29
|
+
* // the value of the attribute 'data-monster-option-bar-baz' is appended with 'bar'
|
|
30
|
+
* // and assigned to the 'bar.baz' property in the options object.
|
|
31
|
+
* // e.g. <div data-monster-option-bar-baz="foo"></div>
|
|
32
|
+
* }
|
|
33
|
+
*
|
|
34
|
+
* @param {HTMLElement} element - The DOM element to be used as the source of the attributes.
|
|
35
|
+
* @param {Object} options - The options object to be initialized.
|
|
36
|
+
* @param {Object} mapping - A mapping between the attribute value and the property value.
|
|
37
|
+
* @param {string} prefix - The prefix of the attributes to be considered.
|
|
38
|
+
* @returns {Object} - The initialized options object.
|
|
39
|
+
* @this HTMLElement - The context of the DOM element.
|
|
40
|
+
*/
|
|
41
|
+
function initOptionsFromAttributes(element, options, mapping={},prefix = 'data-monster-option-') {
|
|
42
|
+
if (!(element instanceof HTMLElement)) return options;
|
|
43
|
+
if (!element.hasAttributes()) return options;
|
|
44
|
+
|
|
45
|
+
const finder = new Pathfinder(options);
|
|
46
|
+
|
|
47
|
+
element.getAttributeNames().forEach((name) => {
|
|
48
|
+
if (!name.startsWith(prefix)) return;
|
|
49
|
+
|
|
50
|
+
// check if the attribute name is a valid option.
|
|
51
|
+
// the mapping between the attribute is simple. The dash is replaced by a dot.
|
|
52
|
+
// e.g. data-monster-url => url
|
|
53
|
+
const optionName = name.replace(prefix, '').replace(/-/g, '.');
|
|
54
|
+
if (!finder.exists(optionName)) return;
|
|
55
|
+
|
|
56
|
+
if (element.hasAttribute(name)) {
|
|
57
|
+
let value = element.getAttribute(name);
|
|
58
|
+
if (mapping.hasOwnProperty(optionName)&&isFunction(mapping[optionName])) {
|
|
59
|
+
value = mapping[optionName](value);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const typeOfOptionValue = typeof finder.getVia(optionName);
|
|
63
|
+
if (typeOfOptionValue === 'boolean') {
|
|
64
|
+
value = value === 'true';
|
|
65
|
+
} else if (typeOfOptionValue === 'number') {
|
|
66
|
+
value = Number(value);
|
|
67
|
+
} else if (typeOfOptionValue === 'string') {
|
|
68
|
+
value = String(value);
|
|
69
|
+
} else if (typeOfOptionValue === 'object') {
|
|
70
|
+
value = JSON.parse(value);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
finder.setVia(optionName, value);
|
|
74
|
+
}
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
return options;
|
|
78
|
+
}
|
package/source/types/version.mjs
CHANGED
|
@@ -8,7 +8,7 @@ import {initJSDOM} from "../../util/jsdom.mjs";
|
|
|
8
8
|
describe('Events', function () {
|
|
9
9
|
|
|
10
10
|
before(async function () {
|
|
11
|
-
initJSDOM();
|
|
11
|
+
await initJSDOM();
|
|
12
12
|
})
|
|
13
13
|
|
|
14
14
|
describe('findTargetElementFromEvent()', function () {
|
|
@@ -23,10 +23,10 @@ describe('Events', function () {
|
|
|
23
23
|
expect(e.getAttribute('data-monster')).to.be.equal('hello')
|
|
24
24
|
done();
|
|
25
25
|
})
|
|
26
|
-
setTimeout(()=>{
|
|
26
|
+
setTimeout(() => {
|
|
27
27
|
fireEvent(div, 'click');
|
|
28
|
-
},0)
|
|
29
|
-
|
|
28
|
+
}, 0)
|
|
29
|
+
|
|
30
30
|
});
|
|
31
31
|
|
|
32
32
|
});
|
|
@@ -76,10 +76,10 @@ describe('Events', function () {
|
|
|
76
76
|
|
|
77
77
|
it('should throw error', function () {
|
|
78
78
|
expect(() => fireEvent({}, 'touch')).to.throw(Error);
|
|
79
|
-
|
|
79
|
+
|
|
80
80
|
});
|
|
81
|
-
});
|
|
82
|
-
|
|
81
|
+
});
|
|
82
|
+
|
|
83
83
|
describe('fireCustomEvent()', function () {
|
|
84
84
|
it('should fire a click event', function (done) {
|
|
85
85
|
let div = document.createElement('div');
|
|
@@ -100,8 +100,8 @@ describe('Events', function () {
|
|
|
100
100
|
it('should fire a touch event on collection1', function (done) {
|
|
101
101
|
let div = document.createElement('div');
|
|
102
102
|
div.addEventListener('touch', (e) => {
|
|
103
|
-
if(e.detail.detail!=='hello world') {
|
|
104
|
-
done('error');
|
|
103
|
+
if (e.detail.detail !== 'hello world') {
|
|
104
|
+
done('error');
|
|
105
105
|
}
|
|
106
106
|
done();
|
|
107
107
|
})
|
|
@@ -111,12 +111,12 @@ describe('Events', function () {
|
|
|
111
111
|
|
|
112
112
|
fireCustomEvent(collection, 'touch', "hello world");
|
|
113
113
|
});
|
|
114
|
-
|
|
114
|
+
|
|
115
115
|
it('should fire a touch event on collection2', function (done) {
|
|
116
116
|
let div = document.createElement('div');
|
|
117
117
|
div.addEventListener('touch', (e) => {
|
|
118
|
-
if(e.detail.a!=='hello world') {
|
|
119
|
-
done('error');
|
|
118
|
+
if (e.detail.a !== 'hello world') {
|
|
119
|
+
done('error');
|
|
120
120
|
}
|
|
121
121
|
done();
|
|
122
122
|
})
|
|
@@ -124,7 +124,7 @@ describe('Events', function () {
|
|
|
124
124
|
div.appendChild(document.createElement('div'));
|
|
125
125
|
let collection = div.querySelectorAll('div');
|
|
126
126
|
|
|
127
|
-
fireCustomEvent(collection, 'touch', {a:"hello world"});
|
|
127
|
+
fireCustomEvent(collection, 'touch', {a: "hello world"});
|
|
128
128
|
});
|
|
129
129
|
|
|
130
130
|
it('should fire a touch event', function (done) {
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import {expect} from "chai"
|
|
2
|
+
|
|
3
|
+
import {initOptionsFromAttributes} from "../../../../..//application/source/dom/util/init-options-from-attributes.mjs";
|
|
4
|
+
import {initJSDOM} from "../../../util/jsdom.mjs";
|
|
5
|
+
|
|
6
|
+
describe('initOptionsFromAttributes', () => {
|
|
7
|
+
let element;
|
|
8
|
+
let options;
|
|
9
|
+
|
|
10
|
+
before(async function () {
|
|
11
|
+
await initJSDOM();
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
options = { url: "", key: { subkey: "" } };
|
|
16
|
+
element = document.createElement('div');
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('should initialize options with matching attributes', () => {
|
|
20
|
+
element.setAttribute('data-monster-option-url', 'https://example.com');
|
|
21
|
+
element.setAttribute('data-monster-option-key.subkey', 'test');
|
|
22
|
+
|
|
23
|
+
const result = initOptionsFromAttributes(element, options);
|
|
24
|
+
|
|
25
|
+
expect(result.url).to.equal('https://example.com');
|
|
26
|
+
expect(result.key.subkey).to.equal('test');
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('should not modify options without matching attributes', () => {
|
|
30
|
+
const result = initOptionsFromAttributes(element, options);
|
|
31
|
+
|
|
32
|
+
expect(result.url).to.equal('');
|
|
33
|
+
expect(result.key.subkey).to.equal('');
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('should ignore attributes without the correct prefix', () => {
|
|
37
|
+
element.setAttribute('data-some-option-url', 'https://example.com');
|
|
38
|
+
|
|
39
|
+
const result = initOptionsFromAttributes(element, options);
|
|
40
|
+
|
|
41
|
+
expect(result.url).to.equal('');
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('should ignore attributes with invalid option paths', () => {
|
|
45
|
+
element.setAttribute('data-monster-option-nonexistent', 'value');
|
|
46
|
+
|
|
47
|
+
const result = initOptionsFromAttributes(element, options);
|
|
48
|
+
|
|
49
|
+
expect(result).to.deep.equal(options);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('should apply mapping for a single attribute', () => {
|
|
53
|
+
element.setAttribute('data-monster-option-url', 'example');
|
|
54
|
+
const mapping = {
|
|
55
|
+
'url': (value) => 'https://' + value + '.com'
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const result = initOptionsFromAttributes(element, options, mapping);
|
|
59
|
+
|
|
60
|
+
expect(result.url).to.equal('https://example.com');
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('should apply mapping for a nested attribute', () => {
|
|
64
|
+
element.setAttribute('data-monster-option-key-subkey', '123');
|
|
65
|
+
const mapping = {
|
|
66
|
+
'key.subkey': (value) => parseInt(value, 10) * 2
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const result = initOptionsFromAttributes(element, options, mapping);
|
|
70
|
+
|
|
71
|
+
expect(result.key.subkey).to.equal("246");
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('should apply multiple mappings', () => {
|
|
75
|
+
element.setAttribute('data-monster-option-url', 'example');
|
|
76
|
+
element.setAttribute('data-monster-option-key.subkey', '123');
|
|
77
|
+
const mapping = {
|
|
78
|
+
'url': (value) => 'https://' + value + '.com',
|
|
79
|
+
'key.subkey': (value) => parseInt(value, 10) * 2
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const result = initOptionsFromAttributes(element, options, mapping);
|
|
83
|
+
|
|
84
|
+
expect(result.url).to.equal('https://example.com');
|
|
85
|
+
expect(result.key.subkey).to.equal("246");
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('should ignore mappings for non-existing attributes', () => {
|
|
89
|
+
const mapping = {
|
|
90
|
+
'url': (value) => 'https://' + value + '.com'
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const result = initOptionsFromAttributes(element, options, mapping);
|
|
94
|
+
|
|
95
|
+
expect(result.url).to.equal('');
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('should ignore mappings for invalid option paths', () => {
|
|
99
|
+
element.setAttribute('data-monster-option-nonexistent', 'value');
|
|
100
|
+
const mapping = {
|
|
101
|
+
'nonexistent': (value) => value + 'bar'
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const result = initOptionsFromAttributes(element, options, mapping);
|
|
105
|
+
|
|
106
|
+
expect(result).to.deep.equal(options);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('should apply mapping only to specified attributes', () => {
|
|
110
|
+
element.setAttribute('data-monster-option-url', 'example');
|
|
111
|
+
element.setAttribute('data-monster-option-key.subkey', '123');
|
|
112
|
+
const mapping = {
|
|
113
|
+
'url': (value) => 'https://' + value + '.com'
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
const result = initOptionsFromAttributes(element, options, mapping);
|
|
117
|
+
|
|
118
|
+
expect(result.url).to.equal('https://example.com');
|
|
119
|
+
expect(result.key.subkey).to.equal('123');
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it('should not apply mapping if not a function', () => {
|
|
123
|
+
element.setAttribute('data-monster-option-url', 'example');
|
|
124
|
+
const mapping = {
|
|
125
|
+
'url': 'https://example.com'
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
const result = initOptionsFromAttributes(element, options, mapping);
|
|
129
|
+
|
|
130
|
+
expect(result.url).to.equal('example');
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it('should apply mapping with custom prefix', () => {
|
|
134
|
+
element.setAttribute('data-custom-option-url', 'example');
|
|
135
|
+
const mapping = {
|
|
136
|
+
'url': (value) => 'https://' + value + '.com'
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
const result = initOptionsFromAttributes(element, options, mapping, 'data-custom-option-');
|
|
140
|
+
|
|
141
|
+
expect(result.url).to.equal('https://example.com');
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it('should not apply mapping with incorrect custom prefix', () => {
|
|
145
|
+
element.setAttribute('data-custom-option-url', 'example');
|
|
146
|
+
const mapping = {
|
|
147
|
+
'url': (value) => 'https://' + value + '.com'
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
const result = initOptionsFromAttributes(element, options, mapping);
|
|
151
|
+
|
|
152
|
+
expect(result.url).to.equal('');
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
});
|
package/test/cases/monster.mjs
CHANGED