@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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@schukai/monster",
3
- "version": "3.38.0",
3
+ "version": "3.39.0",
4
4
  "description": "Monster is a simple library for creating fast, robust and lightweight websites.",
5
5
  "keywords": [
6
6
  "framework",
@@ -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.replace(prefix, '').replace(/-/g, '.');
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
+ }
@@ -142,7 +142,7 @@ function getMonsterVersion() {
142
142
  }
143
143
 
144
144
  /** don't touch, replaced by make with package.json version */
145
- monsterVersion = new Version("3.38.0");
145
+ monsterVersion = new Version("3.39.0");
146
146
 
147
147
  return monsterVersion;
148
148
  }
@@ -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
- before(async function () {
11
- await initJSDOM();
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 = { url: "", key: { subkey: "" } };
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.subkey', 'test');
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.subkey', '123');
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.subkey', '123');
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
  });
@@ -7,7 +7,7 @@ describe('Monster', function () {
7
7
  let monsterVersion
8
8
 
9
9
  /** don´t touch, replaced by make with package.json version */
10
- monsterVersion = new Version("3.38.0")
10
+ monsterVersion = new Version("3.39.0")
11
11
 
12
12
  let m = getMonsterVersion();
13
13