@schukai/monster 4.142.4 → 4.143.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 +1 -1
- package/source/components/datatable/datatable.mjs +24 -9
- package/source/components/datatable/filter/util.mjs +7 -3
- package/source/components/datatable/filter.mjs +21 -5
- package/source/components/host/config-manager.mjs +222 -32
- package/source/components/host/host.mjs +26 -0
- package/source/components/host/util.mjs +52 -2
- package/test/cases/components/datatable/config-keys.mjs +47 -0
- package/test/cases/components/host/config-manager.mjs +86 -0
- package/test/cases/components/host/util.mjs +98 -72
- package/test/web/import.js +2 -0
package/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"author":"Volker Schukai","dependencies":{"@floating-ui/dom":"^1.7.6"},"description":"Monster is a simple library for creating fast, robust and lightweight websites.","homepage":"https://monsterjs.org/","keywords":["framework","web","dom","css","sass","mobile-first","app","front-end","templates","schukai","core","shopcloud","alvine","monster","buildmap","stack","observer","observable","uuid","node","nodelist","css-in-js","logger","log","theme"],"license":"AGPL 3.0","main":"source/monster.mjs","module":"source/monster.mjs","name":"@schukai/monster","repository":{"type":"git","url":"https://gitlab.schukai.com/oss/libraries/javascript/monster.git"},"type":"module","version":"4.
|
|
1
|
+
{"author":"Volker Schukai","dependencies":{"@floating-ui/dom":"^1.7.6"},"description":"Monster is a simple library for creating fast, robust and lightweight websites.","homepage":"https://monsterjs.org/","keywords":["framework","web","dom","css","sass","mobile-first","app","front-end","templates","schukai","core","shopcloud","alvine","monster","buildmap","stack","observer","observable","uuid","node","nodelist","css-in-js","logger","log","theme"],"license":"AGPL 3.0","main":"source/monster.mjs","module":"source/monster.mjs","name":"@schukai/monster","repository":{"type":"git","url":"https://gitlab.schukai.com/oss/libraries/javascript/monster.git"},"type":"module","version":"4.143.0"}
|
|
@@ -76,7 +76,7 @@ import {
|
|
|
76
76
|
import { getDocumentTranslations } from "../../i18n/translations.mjs";
|
|
77
77
|
import "../state/state.mjs";
|
|
78
78
|
import "../host/collapse.mjs";
|
|
79
|
-
import {
|
|
79
|
+
import { generateComponentConfigKey } from "../host/util.mjs";
|
|
80
80
|
|
|
81
81
|
import "./datasource/dom.mjs";
|
|
82
82
|
import "./datasource/rest.mjs";
|
|
@@ -279,6 +279,10 @@ class DataTable extends CustomElement {
|
|
|
279
279
|
"row-key": null,
|
|
280
280
|
"filter-id": null,
|
|
281
281
|
},
|
|
282
|
+
|
|
283
|
+
config: {
|
|
284
|
+
instanceKey: null,
|
|
285
|
+
},
|
|
282
286
|
},
|
|
283
287
|
initOptionsFromArguments.call(this),
|
|
284
288
|
);
|
|
@@ -659,15 +663,21 @@ class DataTable extends CustomElement {
|
|
|
659
663
|
* @return {string}
|
|
660
664
|
*/
|
|
661
665
|
function getColumnVisibilityConfigKey() {
|
|
662
|
-
return
|
|
666
|
+
return generateComponentConfigKey("datatable", this?.id, "columns-visibility", {
|
|
667
|
+
instanceKey: this.getOption("config.instanceKey"),
|
|
668
|
+
});
|
|
663
669
|
}
|
|
664
670
|
|
|
665
671
|
/**
|
|
666
672
|
* @private
|
|
667
673
|
* @return {string}
|
|
668
674
|
*/
|
|
669
|
-
function
|
|
670
|
-
return
|
|
675
|
+
function hasConfigIdentity() {
|
|
676
|
+
return (
|
|
677
|
+
(isString(this.id) && this.id !== "") ||
|
|
678
|
+
(isString(this.getOption("config.instanceKey")) &&
|
|
679
|
+
this.getOption("config.instanceKey").trim() !== "")
|
|
680
|
+
);
|
|
671
681
|
}
|
|
672
682
|
|
|
673
683
|
/**
|
|
@@ -682,8 +692,11 @@ function getHostConfig(callback) {
|
|
|
682
692
|
return Promise.resolve({});
|
|
683
693
|
}
|
|
684
694
|
|
|
685
|
-
if (!this
|
|
686
|
-
addErrorAttribute(
|
|
695
|
+
if (!hasConfigIdentity.call(this)) {
|
|
696
|
+
addErrorAttribute(
|
|
697
|
+
this,
|
|
698
|
+
"no id or config.instanceKey found; one is required for config",
|
|
699
|
+
);
|
|
687
700
|
return Promise.resolve({});
|
|
688
701
|
}
|
|
689
702
|
|
|
@@ -782,7 +795,7 @@ function updateConfigColumnBar() {
|
|
|
782
795
|
}
|
|
783
796
|
|
|
784
797
|
const host = findElementWithSelectorUpwards(this, "monster-host");
|
|
785
|
-
if (!(host && this
|
|
798
|
+
if (!(host && hasConfigIdentity.call(this))) {
|
|
786
799
|
return;
|
|
787
800
|
}
|
|
788
801
|
const configKey = getColumnVisibilityConfigKey.call(this);
|
|
@@ -1616,7 +1629,9 @@ function getTranslations() {
|
|
|
1616
1629
|
* @return {string}
|
|
1617
1630
|
*/
|
|
1618
1631
|
export function getStoredOrderConfigKey() {
|
|
1619
|
-
return
|
|
1632
|
+
return generateComponentConfigKey("datatable", this?.id, "stored-order", {
|
|
1633
|
+
instanceKey: this.getOption("config.instanceKey"),
|
|
1634
|
+
});
|
|
1620
1635
|
}
|
|
1621
1636
|
|
|
1622
1637
|
/**
|
|
@@ -1629,7 +1644,7 @@ function storeOrderStatement(doFetch) {
|
|
|
1629
1644
|
setDataSource.call(this, { order: statement }, doFetch);
|
|
1630
1645
|
|
|
1631
1646
|
const host = findElementWithSelectorUpwards(this, "monster-host");
|
|
1632
|
-
if (!(host && this
|
|
1647
|
+
if (!(host && hasConfigIdentity.call(this))) {
|
|
1633
1648
|
return;
|
|
1634
1649
|
}
|
|
1635
1650
|
|
|
@@ -12,14 +12,16 @@
|
|
|
12
12
|
* SPDX-License-Identifier: AGPL-3.0
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
-
import {
|
|
15
|
+
import { generateComponentConfigKey } from "../../host/util.mjs";
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
18
|
* @private
|
|
19
19
|
* @return {string}
|
|
20
20
|
*/
|
|
21
21
|
export function getFilterConfigKey() {
|
|
22
|
-
return
|
|
22
|
+
return generateComponentConfigKey("datatable", this?.id, "filter", {
|
|
23
|
+
instanceKey: this.getOption("config.instanceKey"),
|
|
24
|
+
});
|
|
23
25
|
}
|
|
24
26
|
|
|
25
27
|
/**
|
|
@@ -27,7 +29,9 @@ export function getFilterConfigKey() {
|
|
|
27
29
|
* @return {string}
|
|
28
30
|
*/
|
|
29
31
|
export function getStoredFilterConfigKey() {
|
|
30
|
-
return
|
|
32
|
+
return generateComponentConfigKey("datatable", this?.id, "stored-filter", {
|
|
33
|
+
instanceKey: this.getOption("config.instanceKey"),
|
|
34
|
+
});
|
|
31
35
|
}
|
|
32
36
|
|
|
33
37
|
/**
|
|
@@ -293,6 +293,10 @@ class Filter extends CustomElement {
|
|
|
293
293
|
selector: "",
|
|
294
294
|
},
|
|
295
295
|
|
|
296
|
+
config: {
|
|
297
|
+
instanceKey: null,
|
|
298
|
+
},
|
|
299
|
+
|
|
296
300
|
timeouts: {
|
|
297
301
|
message: 4000,
|
|
298
302
|
},
|
|
@@ -915,7 +919,7 @@ function initEventHandler() {
|
|
|
915
919
|
.then(() => {
|
|
916
920
|
const configKey = getStoredFilterConfigKey.call(self);
|
|
917
921
|
const host = getDocument().querySelector("monster-host");
|
|
918
|
-
if (!host) {
|
|
922
|
+
if (!(host && hasConfigIdentity.call(self))) {
|
|
919
923
|
return;
|
|
920
924
|
}
|
|
921
925
|
|
|
@@ -1070,7 +1074,7 @@ function initTabEvents() {
|
|
|
1070
1074
|
}
|
|
1071
1075
|
|
|
1072
1076
|
const host = findElementWithSelectorUpwards(this, "monster-host");
|
|
1073
|
-
if (!(host && this
|
|
1077
|
+
if (!(host && hasConfigIdentity.call(this))) {
|
|
1074
1078
|
return;
|
|
1075
1079
|
}
|
|
1076
1080
|
|
|
@@ -1111,7 +1115,7 @@ function updateFilterTabs() {
|
|
|
1111
1115
|
}
|
|
1112
1116
|
|
|
1113
1117
|
const host = findElementWithSelectorUpwards(this, "monster-host");
|
|
1114
|
-
if (!(host && this
|
|
1118
|
+
if (!(host && hasConfigIdentity.call(this))) {
|
|
1115
1119
|
return;
|
|
1116
1120
|
}
|
|
1117
1121
|
|
|
@@ -1580,7 +1584,7 @@ function getControlValuesFromLabel(label) {
|
|
|
1580
1584
|
function initFromConfig() {
|
|
1581
1585
|
const host = findElementWithSelectorUpwards(this, "monster-host");
|
|
1582
1586
|
|
|
1583
|
-
if (!(isInstance(host, Host) && this
|
|
1587
|
+
if (!(isInstance(host, Host) && hasConfigIdentity.call(this))) {
|
|
1584
1588
|
return Promise.resolve();
|
|
1585
1589
|
}
|
|
1586
1590
|
|
|
@@ -1622,7 +1626,7 @@ function initFromConfig() {
|
|
|
1622
1626
|
*/
|
|
1623
1627
|
function updateConfig() {
|
|
1624
1628
|
const host = findElementWithSelectorUpwards(this, "monster-host");
|
|
1625
|
-
if (!(host && this
|
|
1629
|
+
if (!(host && hasConfigIdentity.call(this))) {
|
|
1626
1630
|
return;
|
|
1627
1631
|
}
|
|
1628
1632
|
const configKey = getFilterConfigKey.call(this);
|
|
@@ -1634,6 +1638,18 @@ function updateConfig() {
|
|
|
1634
1638
|
}
|
|
1635
1639
|
}
|
|
1636
1640
|
|
|
1641
|
+
/**
|
|
1642
|
+
* @private
|
|
1643
|
+
* @return {boolean}
|
|
1644
|
+
*/
|
|
1645
|
+
function hasConfigIdentity() {
|
|
1646
|
+
return (
|
|
1647
|
+
(isString(this.id) && this.id !== "") ||
|
|
1648
|
+
(isString(this.getOption("config.instanceKey")) &&
|
|
1649
|
+
this.getOption("config.instanceKey").trim() !== "")
|
|
1650
|
+
);
|
|
1651
|
+
}
|
|
1652
|
+
|
|
1637
1653
|
/**
|
|
1638
1654
|
* @private
|
|
1639
1655
|
* @return {string}
|
|
@@ -21,6 +21,7 @@ import { ConfigManagerStyleSheet } from "./stylesheet/config-manager.mjs";
|
|
|
21
21
|
import { getWindow } from "../../dom/util.mjs";
|
|
22
22
|
import { instanceSymbol } from "../../constants.mjs";
|
|
23
23
|
import { diff } from "../../data/diff.mjs";
|
|
24
|
+
import { isFunction, isObject } from "../../types/is.mjs";
|
|
24
25
|
|
|
25
26
|
export { ConfigManager };
|
|
26
27
|
|
|
@@ -108,17 +109,42 @@ class ConfigManager extends CustomElement {
|
|
|
108
109
|
keyPath: "key",
|
|
109
110
|
},
|
|
110
111
|
},
|
|
112
|
+
storage: {
|
|
113
|
+
adapter: null,
|
|
114
|
+
serverAuthoritative: false,
|
|
115
|
+
},
|
|
111
116
|
});
|
|
112
117
|
}
|
|
113
118
|
|
|
119
|
+
/**
|
|
120
|
+
* Register an external host configuration storage adapter.
|
|
121
|
+
*
|
|
122
|
+
* Supported adapter methods are getConfig/hasConfig/setConfig/deleteConfig
|
|
123
|
+
* or their short aliases get/has/set/delete. If managesConfigKey, managesKey
|
|
124
|
+
* or manages is present, it is used to decide whether a key is handled by
|
|
125
|
+
* the adapter. Without such a method, an adapter handles all keys.
|
|
126
|
+
*
|
|
127
|
+
* @param {Object|null} adapter
|
|
128
|
+
* @return {ConfigManager}
|
|
129
|
+
*/
|
|
130
|
+
setStorageAdapter(adapter) {
|
|
131
|
+
this.setOption("storage.adapter", adapter);
|
|
132
|
+
return this;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* @return {Object|null}
|
|
137
|
+
*/
|
|
138
|
+
getStorageAdapter() {
|
|
139
|
+
return getStorageAdapter.call(this);
|
|
140
|
+
}
|
|
141
|
+
|
|
114
142
|
/**
|
|
115
143
|
* @param {string} key
|
|
116
144
|
* @return {Promise<unknown>}
|
|
117
145
|
*/
|
|
118
146
|
getConfig(key) {
|
|
119
|
-
return this.ready().then(() =>
|
|
120
|
-
return getBlob.call(this, key);
|
|
121
|
-
});
|
|
147
|
+
return this.ready().then(() => readConfig.call(this, key));
|
|
122
148
|
}
|
|
123
149
|
|
|
124
150
|
/**
|
|
@@ -126,16 +152,7 @@ class ConfigManager extends CustomElement {
|
|
|
126
152
|
* @return {Promise<boolean>}
|
|
127
153
|
*/
|
|
128
154
|
hasConfig(key) {
|
|
129
|
-
return this.ready()
|
|
130
|
-
.then(() => {
|
|
131
|
-
return getBlob.call(this, key);
|
|
132
|
-
})
|
|
133
|
-
.then(() => {
|
|
134
|
-
return true;
|
|
135
|
-
})
|
|
136
|
-
.catch(() => {
|
|
137
|
-
return false;
|
|
138
|
-
});
|
|
155
|
+
return this.ready().then(() => hasConfig.call(this, key));
|
|
139
156
|
}
|
|
140
157
|
|
|
141
158
|
/**
|
|
@@ -144,28 +161,11 @@ class ConfigManager extends CustomElement {
|
|
|
144
161
|
* @return {Promise<unknown>}
|
|
145
162
|
*/
|
|
146
163
|
setConfig(key, value) {
|
|
147
|
-
return this.ready().then(() =>
|
|
148
|
-
return getBlob
|
|
149
|
-
.call(this, key)
|
|
150
|
-
.then((storedValue) => {
|
|
151
|
-
if (diff(storedValue, value).length === 0) {
|
|
152
|
-
return;
|
|
153
|
-
}
|
|
154
|
-
return setBlob.call(this, key, value);
|
|
155
|
-
})
|
|
156
|
-
.catch((error) => {
|
|
157
|
-
if (error?.message?.match(/is not defined/)) {
|
|
158
|
-
return setBlob.call(this, key, value);
|
|
159
|
-
}
|
|
160
|
-
throw error;
|
|
161
|
-
});
|
|
162
|
-
});
|
|
164
|
+
return this.ready().then(() => writeConfig.call(this, key, value));
|
|
163
165
|
}
|
|
164
166
|
|
|
165
167
|
deleteConfig(key) {
|
|
166
|
-
return this.ready().then(() =>
|
|
167
|
-
return deleteBlob.call(this, key);
|
|
168
|
-
});
|
|
168
|
+
return this.ready().then(() => removeConfig.call(this, key));
|
|
169
169
|
}
|
|
170
170
|
|
|
171
171
|
/**
|
|
@@ -190,6 +190,192 @@ class ConfigManager extends CustomElement {
|
|
|
190
190
|
}
|
|
191
191
|
}
|
|
192
192
|
|
|
193
|
+
/**
|
|
194
|
+
* @private
|
|
195
|
+
* @return {Object|null}
|
|
196
|
+
*/
|
|
197
|
+
function getStorageAdapter() {
|
|
198
|
+
const adapter = this.getOption("storage.adapter");
|
|
199
|
+
return isObject(adapter) ? adapter : null;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* @private
|
|
204
|
+
* @param {Object} adapter
|
|
205
|
+
* @param {string} key
|
|
206
|
+
* @return {boolean}
|
|
207
|
+
*/
|
|
208
|
+
function adapterManagesKey(adapter, key) {
|
|
209
|
+
for (const methodName of ["managesConfigKey", "managesKey", "manages"]) {
|
|
210
|
+
if (isFunction(adapter?.[methodName])) {
|
|
211
|
+
return adapter[methodName](key) === true;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return true;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* @private
|
|
220
|
+
* @param {Object} adapter
|
|
221
|
+
* @param {string[]} methodNames
|
|
222
|
+
* @param {Array} args
|
|
223
|
+
* @return {Promise<unknown>}
|
|
224
|
+
*/
|
|
225
|
+
function callAdapter(adapter, methodNames, args) {
|
|
226
|
+
for (const methodName of methodNames) {
|
|
227
|
+
if (isFunction(adapter?.[methodName])) {
|
|
228
|
+
return Promise.resolve(adapter[methodName](...args));
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
return Promise.reject(new Error("The storage adapter method is not defined."));
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* @private
|
|
237
|
+
* @return {boolean}
|
|
238
|
+
*/
|
|
239
|
+
function isServerAuthoritative() {
|
|
240
|
+
return this.getOption("storage.serverAuthoritative") === true;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* @private
|
|
245
|
+
* @param {string} key
|
|
246
|
+
* @return {Promise<unknown>}
|
|
247
|
+
*/
|
|
248
|
+
function readConfig(key) {
|
|
249
|
+
const adapter = getStorageAdapter.call(this);
|
|
250
|
+
const managed = adapter && adapterManagesKey(adapter, key);
|
|
251
|
+
|
|
252
|
+
if (managed) {
|
|
253
|
+
return callAdapter(adapter, ["getConfig", "get"], [key]).catch((error) => {
|
|
254
|
+
if (isServerAuthoritative.call(this)) {
|
|
255
|
+
throw error;
|
|
256
|
+
}
|
|
257
|
+
return getBlob.call(this, key);
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
return getBlob.call(this, key);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* @private
|
|
266
|
+
* @param {string} key
|
|
267
|
+
* @return {Promise<boolean>}
|
|
268
|
+
*/
|
|
269
|
+
function hasConfig(key) {
|
|
270
|
+
const adapter = getStorageAdapter.call(this);
|
|
271
|
+
const managed = adapter && adapterManagesKey(adapter, key);
|
|
272
|
+
|
|
273
|
+
if (managed) {
|
|
274
|
+
return callAdapter(adapter, ["hasConfig", "has"], [key])
|
|
275
|
+
.catch(() => {
|
|
276
|
+
return callAdapter(adapter, ["getConfig", "get"], [key]).then(
|
|
277
|
+
() => true,
|
|
278
|
+
);
|
|
279
|
+
})
|
|
280
|
+
.then((result) => {
|
|
281
|
+
if (result === true || isServerAuthoritative.call(this)) {
|
|
282
|
+
return result === true;
|
|
283
|
+
}
|
|
284
|
+
return hasLocalConfig.call(this, key);
|
|
285
|
+
})
|
|
286
|
+
.catch(() => {
|
|
287
|
+
if (isServerAuthoritative.call(this)) {
|
|
288
|
+
return false;
|
|
289
|
+
}
|
|
290
|
+
return hasLocalConfig.call(this, key);
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
return hasLocalConfig.call(this, key);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* @private
|
|
299
|
+
* @param {string} key
|
|
300
|
+
* @return {Promise<boolean>}
|
|
301
|
+
*/
|
|
302
|
+
function hasLocalConfig(key) {
|
|
303
|
+
return getBlob
|
|
304
|
+
.call(this, key)
|
|
305
|
+
.then(() => true)
|
|
306
|
+
.catch(() => false);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* @private
|
|
311
|
+
* @param {string} key
|
|
312
|
+
* @param {*} value
|
|
313
|
+
* @return {Promise<unknown>}
|
|
314
|
+
*/
|
|
315
|
+
function writeConfig(key, value) {
|
|
316
|
+
const adapter = getStorageAdapter.call(this);
|
|
317
|
+
const managed = adapter && adapterManagesKey(adapter, key);
|
|
318
|
+
|
|
319
|
+
if (managed) {
|
|
320
|
+
return callAdapter(adapter, ["setConfig", "set"], [key, value]).catch(
|
|
321
|
+
(error) => {
|
|
322
|
+
if (isServerAuthoritative.call(this)) {
|
|
323
|
+
throw error;
|
|
324
|
+
}
|
|
325
|
+
return writeLocalConfig.call(this, key, value);
|
|
326
|
+
},
|
|
327
|
+
);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
return writeLocalConfig.call(this, key, value);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* @private
|
|
335
|
+
* @param {string} key
|
|
336
|
+
* @param {*} value
|
|
337
|
+
* @return {Promise<unknown>}
|
|
338
|
+
*/
|
|
339
|
+
function writeLocalConfig(key, value) {
|
|
340
|
+
return getBlob
|
|
341
|
+
.call(this, key)
|
|
342
|
+
.then((storedValue) => {
|
|
343
|
+
if (diff(storedValue, value).length === 0) {
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
return setBlob.call(this, key, value);
|
|
347
|
+
})
|
|
348
|
+
.catch((error) => {
|
|
349
|
+
if (error?.message?.match(/is not defined/)) {
|
|
350
|
+
return setBlob.call(this, key, value);
|
|
351
|
+
}
|
|
352
|
+
throw error;
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* @private
|
|
358
|
+
* @param {string} key
|
|
359
|
+
* @return {Promise<unknown>}
|
|
360
|
+
*/
|
|
361
|
+
function removeConfig(key) {
|
|
362
|
+
const adapter = getStorageAdapter.call(this);
|
|
363
|
+
const managed = adapter && adapterManagesKey(adapter, key);
|
|
364
|
+
|
|
365
|
+
if (managed) {
|
|
366
|
+
return callAdapter(adapter, ["deleteConfig", "delete", "remove"], [
|
|
367
|
+
key,
|
|
368
|
+
]).catch((error) => {
|
|
369
|
+
if (isServerAuthoritative.call(this)) {
|
|
370
|
+
throw error;
|
|
371
|
+
}
|
|
372
|
+
return deleteBlob.call(this, key);
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
return deleteBlob.call(this, key);
|
|
377
|
+
}
|
|
378
|
+
|
|
193
379
|
/**
|
|
194
380
|
* @private
|
|
195
381
|
* @returns {Promise<unknown>}
|
|
@@ -206,6 +392,10 @@ function openDatabase() {
|
|
|
206
392
|
throw new Error("The database name and version must be set.");
|
|
207
393
|
}
|
|
208
394
|
|
|
395
|
+
if (!window.indexedDB) {
|
|
396
|
+
return Promise.resolve(null);
|
|
397
|
+
}
|
|
398
|
+
|
|
209
399
|
const request = window.indexedDB.open(name, version);
|
|
210
400
|
|
|
211
401
|
return new Promise((resolve, reject) => {
|
|
@@ -194,6 +194,32 @@ class Host extends CustomElement {
|
|
|
194
194
|
return this[configManagerElementSymbol].setConfig(key, value);
|
|
195
195
|
}
|
|
196
196
|
|
|
197
|
+
/**
|
|
198
|
+
* Register an external storage adapter for host configuration.
|
|
199
|
+
*
|
|
200
|
+
* @param {Object|null} adapter
|
|
201
|
+
* @return {Host}
|
|
202
|
+
*/
|
|
203
|
+
setConfigStorageAdapter(adapter) {
|
|
204
|
+
if (this[configManagerElementSymbol] instanceof HTMLElement === false) {
|
|
205
|
+
throw new Error("There is no config manager element");
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
this[configManagerElementSymbol].setStorageAdapter(adapter);
|
|
209
|
+
return this;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* @return {Object|null}
|
|
214
|
+
*/
|
|
215
|
+
getConfigStorageAdapter() {
|
|
216
|
+
if (this[configManagerElementSymbol] instanceof HTMLElement === false) {
|
|
217
|
+
throw new Error("There is no config manager element");
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return this[configManagerElementSymbol].getStorageAdapter();
|
|
221
|
+
}
|
|
222
|
+
|
|
197
223
|
/**
|
|
198
224
|
* @private
|
|
199
225
|
* @fires Host#monster-host-connected
|
|
@@ -13,8 +13,14 @@
|
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
15
|
import { getWindow } from "../../dom/util.mjs";
|
|
16
|
+
import { isString } from "../../types/is.mjs";
|
|
16
17
|
|
|
17
|
-
export {
|
|
18
|
+
export {
|
|
19
|
+
generateConfigKey,
|
|
20
|
+
generateComponentConfigKey,
|
|
21
|
+
generateUniqueConfigKey,
|
|
22
|
+
sanitizeConfigKey,
|
|
23
|
+
};
|
|
18
24
|
|
|
19
25
|
/**
|
|
20
26
|
* Generate a unique configuration key based on the current browser location,
|
|
@@ -38,5 +44,49 @@ function generateUniqueConfigKey(componentName, id, prefix) {
|
|
|
38
44
|
const uniqueKey = `${prefix}_${urlWithoutParamsAndHash}_${componentName}_${id}`;
|
|
39
45
|
|
|
40
46
|
// Replace any special characters and spaces with underscores
|
|
41
|
-
return uniqueKey
|
|
47
|
+
return sanitizeConfigKey(uniqueKey);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Generate a stable host configuration key from an explicit technical instance key.
|
|
52
|
+
*
|
|
53
|
+
* @param {string} componentName - The name of the component.
|
|
54
|
+
* @param {string} instanceKey - Technical, host-defined instance key.
|
|
55
|
+
* @param {string} scope - Configuration scope, for example "filter".
|
|
56
|
+
* @return {string}
|
|
57
|
+
*/
|
|
58
|
+
function generateConfigKey(componentName, instanceKey, scope) {
|
|
59
|
+
return sanitizeConfigKey(`${scope}_${componentName}_${instanceKey}`);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Generate a component host configuration key.
|
|
64
|
+
*
|
|
65
|
+
* If an explicit instance key is provided, the key is independent from the
|
|
66
|
+
* current URL and DOM id. Without an instance key, this keeps the historical
|
|
67
|
+
* URL-derived key for backwards compatibility.
|
|
68
|
+
*
|
|
69
|
+
* @param {string} componentName
|
|
70
|
+
* @param {string} id
|
|
71
|
+
* @param {string} scope
|
|
72
|
+
* @param {object} [options]
|
|
73
|
+
* @param {string} [options.instanceKey]
|
|
74
|
+
* @return {string}
|
|
75
|
+
*/
|
|
76
|
+
function generateComponentConfigKey(componentName, id, scope, options = {}) {
|
|
77
|
+
if (isString(options.instanceKey) && options.instanceKey.trim() !== "") {
|
|
78
|
+
return generateConfigKey(componentName, options.instanceKey.trim(), scope);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return generateUniqueConfigKey(componentName, id, scope);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* @param {string} key
|
|
86
|
+
* @return {string}
|
|
87
|
+
*/
|
|
88
|
+
function sanitizeConfigKey(key) {
|
|
89
|
+
return String(key)
|
|
90
|
+
.replace(/[^\w\s]/gi, "_")
|
|
91
|
+
.replace(/\s+/g, "_");
|
|
42
92
|
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { expect } from "chai";
|
|
2
|
+
import { initJSDOM } from "../../../util/jsdom.mjs";
|
|
3
|
+
|
|
4
|
+
let getStoredOrderConfigKey;
|
|
5
|
+
let getFilterConfigKey;
|
|
6
|
+
let getStoredFilterConfigKey;
|
|
7
|
+
|
|
8
|
+
describe("Datatable config keys", function () {
|
|
9
|
+
before(function (done) {
|
|
10
|
+
initJSDOM().then(() => {
|
|
11
|
+
import("../../../../source/components/datatable/datatable.mjs")
|
|
12
|
+
.then((m) => {
|
|
13
|
+
getStoredOrderConfigKey = m.getStoredOrderConfigKey;
|
|
14
|
+
return import(
|
|
15
|
+
"../../../../source/components/datatable/filter/util.mjs"
|
|
16
|
+
);
|
|
17
|
+
})
|
|
18
|
+
.then((m) => {
|
|
19
|
+
getFilterConfigKey = m.getFilterConfigKey;
|
|
20
|
+
getStoredFilterConfigKey = m.getStoredFilterConfigKey;
|
|
21
|
+
done();
|
|
22
|
+
})
|
|
23
|
+
.catch(done);
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("uses the same explicit instance-key contract for filter, stored filters and sorting", function () {
|
|
28
|
+
const context = {
|
|
29
|
+
id: "dom-id",
|
|
30
|
+
getOption(path) {
|
|
31
|
+
if (path === "config.instanceKey") {
|
|
32
|
+
return "orders.default";
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
expect(getFilterConfigKey.call(context)).to.equal(
|
|
38
|
+
"filter_datatable_orders_default",
|
|
39
|
+
);
|
|
40
|
+
expect(getStoredFilterConfigKey.call(context)).to.equal(
|
|
41
|
+
"stored_filter_datatable_orders_default",
|
|
42
|
+
);
|
|
43
|
+
expect(getStoredOrderConfigKey.call(context)).to.equal(
|
|
44
|
+
"stored_order_datatable_orders_default",
|
|
45
|
+
);
|
|
46
|
+
});
|
|
47
|
+
});
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import * as chai from "chai";
|
|
2
|
+
import { initJSDOM } from "../../../util/jsdom.mjs";
|
|
3
|
+
|
|
4
|
+
const expect = chai.expect;
|
|
5
|
+
|
|
6
|
+
describe("ConfigManager storage adapter", function () {
|
|
7
|
+
before(function (done) {
|
|
8
|
+
initJSDOM().then(() => {
|
|
9
|
+
import("../../../../source/components/host/config-manager.mjs")
|
|
10
|
+
.then(() => {
|
|
11
|
+
done();
|
|
12
|
+
})
|
|
13
|
+
.catch((e) => done(e));
|
|
14
|
+
});
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
afterEach(() => {
|
|
18
|
+
let mocks = document.getElementById("mocks");
|
|
19
|
+
mocks.innerHTML = "";
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("uses an external storage adapter for managed keys", function (done) {
|
|
23
|
+
const config = document.createElement("monster-config-manager");
|
|
24
|
+
const values = new Map();
|
|
25
|
+
const calls = [];
|
|
26
|
+
|
|
27
|
+
config.setStorageAdapter({
|
|
28
|
+
managesConfigKey: (key) => key.startsWith("server_"),
|
|
29
|
+
hasConfig: (key) => values.has(key),
|
|
30
|
+
getConfig: (key) => values.get(key),
|
|
31
|
+
setConfig: (key, value) => {
|
|
32
|
+
calls.push(["set", key, value]);
|
|
33
|
+
values.set(key, value);
|
|
34
|
+
},
|
|
35
|
+
deleteConfig: (key) => {
|
|
36
|
+
calls.push(["delete", key]);
|
|
37
|
+
values.delete(key);
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
config.setOption("storage.serverAuthoritative", true);
|
|
42
|
+
|
|
43
|
+
config
|
|
44
|
+
.setConfig("server_table_filter", { visible: true })
|
|
45
|
+
.then(() => config.hasConfig("server_table_filter"))
|
|
46
|
+
.then((hasConfig) => {
|
|
47
|
+
expect(hasConfig).to.equal(true);
|
|
48
|
+
return config.getConfig("server_table_filter");
|
|
49
|
+
})
|
|
50
|
+
.then((value) => {
|
|
51
|
+
expect(value).to.deep.equal({ visible: true });
|
|
52
|
+
expect(calls).to.deep.equal([
|
|
53
|
+
["set", "server_table_filter", { visible: true }],
|
|
54
|
+
]);
|
|
55
|
+
return config.deleteConfig("server_table_filter");
|
|
56
|
+
})
|
|
57
|
+
.then(() => {
|
|
58
|
+
expect(values.has("server_table_filter")).to.equal(false);
|
|
59
|
+
done();
|
|
60
|
+
})
|
|
61
|
+
.catch(done);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("does not fall back to IndexedDB for server-authoritative adapter keys", function (done) {
|
|
65
|
+
const config = document.createElement("monster-config-manager");
|
|
66
|
+
|
|
67
|
+
config.setStorageAdapter({
|
|
68
|
+
managesConfigKey: (key) => key === "server_key",
|
|
69
|
+
setConfig: () => Promise.reject(new Error("server unavailable")),
|
|
70
|
+
});
|
|
71
|
+
config.setOption("storage.serverAuthoritative", true);
|
|
72
|
+
config.setOption("indexDB.objectStore.name", "missing-store");
|
|
73
|
+
|
|
74
|
+
config
|
|
75
|
+
.setConfig("server_key", { value: 1 })
|
|
76
|
+
.then(() => done(new Error("setConfig should reject")))
|
|
77
|
+
.catch((error) => {
|
|
78
|
+
try {
|
|
79
|
+
expect(error.message).to.equal("server unavailable");
|
|
80
|
+
done();
|
|
81
|
+
} catch (e) {
|
|
82
|
+
done(e);
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
});
|
|
@@ -1,79 +1,105 @@
|
|
|
1
1
|
// Import the required libraries
|
|
2
|
-
import { expect } from
|
|
2
|
+
import { expect } from "chai";
|
|
3
3
|
//import { JSDOM } from 'jsdom';
|
|
4
|
-
import {
|
|
5
|
-
|
|
4
|
+
import {
|
|
5
|
+
generateComponentConfigKey,
|
|
6
|
+
generateConfigKey,
|
|
7
|
+
generateUniqueConfigKey,
|
|
8
|
+
} from "../../../../source/components/host/util.mjs";
|
|
9
|
+
import { initJSDOM } from "../../../util/jsdom.mjs";
|
|
6
10
|
|
|
7
11
|
// Create a JSDOM instance to simulate the browser environment
|
|
8
12
|
//const dom = new JSDOM();
|
|
9
13
|
|
|
10
|
-
|
|
11
14
|
// Test suite for the generateUniqueConfigKey function
|
|
12
|
-
describe(
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
15
|
+
describe("generateUniqueConfigKey", () => {
|
|
16
|
+
//let originalWindow;
|
|
17
|
+
|
|
18
|
+
// before(() => {
|
|
19
|
+
// // Store the original window object
|
|
20
|
+
// originalWindow = globalThis.window;
|
|
21
|
+
//
|
|
22
|
+
// // Create a JSDOM instance to simulate the browser environment
|
|
23
|
+
// const dom = new JSDOM();
|
|
24
|
+
// globalThis.window = dom.window;
|
|
25
|
+
// });
|
|
26
|
+
|
|
27
|
+
before(function (done) {
|
|
28
|
+
initJSDOM().then(() => {
|
|
29
|
+
done();
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
// ... (same test cases as before)
|
|
34
|
+
|
|
35
|
+
after(() => {
|
|
36
|
+
// Restore the original window object
|
|
37
|
+
// globalThis.window = originalWindow;
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("should generate a unique key with the given parameters", () => {
|
|
41
|
+
const componentName = "MyComponent";
|
|
42
|
+
const id = "123";
|
|
43
|
+
const prefix = "myPrefix";
|
|
44
|
+
|
|
45
|
+
const uniqueKey = generateUniqueConfigKey(componentName, id, prefix);
|
|
46
|
+
|
|
47
|
+
// Ensure the unique key contains the given parameters and follows the expected format
|
|
48
|
+
expect(uniqueKey).to.include(prefix);
|
|
49
|
+
expect(uniqueKey).to.include(componentName);
|
|
50
|
+
expect(uniqueKey).to.include(id);
|
|
51
|
+
expect(uniqueKey).to.match(/^[a-zA-Z0-9_]+$/);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("should replace special characters and spaces with underscores", () => {
|
|
55
|
+
const componentName = "My$Component";
|
|
56
|
+
const id = "12#3";
|
|
57
|
+
const prefix = "my Prefix";
|
|
58
|
+
|
|
59
|
+
const uniqueKey = generateUniqueConfigKey(componentName, id, prefix);
|
|
60
|
+
|
|
61
|
+
// Ensure the unique key does not contain any special characters or spaces
|
|
62
|
+
expect(uniqueKey).to.match(/^[a-zA-Z0-9_]+$/);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it("should include the browser location without parameters", () => {
|
|
66
|
+
const componentName = "MyComponent";
|
|
67
|
+
const id = "123";
|
|
68
|
+
const prefix = "myPrefix";
|
|
69
|
+
|
|
70
|
+
const uniqueKey = generateUniqueConfigKey(componentName, id, prefix);
|
|
71
|
+
|
|
72
|
+
// Ensure the unique key contains the browser location without parameters
|
|
73
|
+
const urlWithoutParams = window.location.href.split("?")[0];
|
|
74
|
+
const sanitizedUrl = urlWithoutParams
|
|
75
|
+
.replace(/[^\w\s]/gi, "_")
|
|
76
|
+
.replace(/\s+/g, "_");
|
|
77
|
+
expect(uniqueKey).to.include(sanitizedUrl);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it("should generate explicit instance keys without the browser location", () => {
|
|
81
|
+
const uniqueKey = generateConfigKey(
|
|
82
|
+
"datatable",
|
|
83
|
+
"orders.default",
|
|
84
|
+
"filter",
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
expect(uniqueKey).to.equal("filter_datatable_orders_default");
|
|
88
|
+
expect(uniqueKey).to.not.include("example_test");
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it("should prefer explicit instance keys over legacy URL keys", () => {
|
|
92
|
+
const uniqueKey = generateComponentConfigKey(
|
|
93
|
+
"datatable",
|
|
94
|
+
"dom-id",
|
|
95
|
+
"stored-order",
|
|
96
|
+
{
|
|
97
|
+
instanceKey: "orders.default",
|
|
98
|
+
},
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
expect(uniqueKey).to.equal("stored_order_datatable_orders_default");
|
|
102
|
+
expect(uniqueKey).to.not.include("dom-id");
|
|
103
|
+
expect(uniqueKey).to.not.include("example_test");
|
|
104
|
+
});
|
|
105
|
+
});
|
package/test/web/import.js
CHANGED
|
@@ -25,8 +25,10 @@ import "../cases/components/notify/message.mjs";
|
|
|
25
25
|
import "../cases/components/notify/notify.mjs";
|
|
26
26
|
import "../cases/components/host/host.mjs";
|
|
27
27
|
import "../cases/components/host/overlay.mjs";
|
|
28
|
+
import "../cases/components/host/config-manager.mjs";
|
|
28
29
|
import "../cases/components/host/util.mjs";
|
|
29
30
|
import "../cases/components/host/details.mjs";
|
|
31
|
+
import "../cases/components/datatable/config-keys.mjs";
|
|
30
32
|
import "../cases/components/datatable/writeback-sanitizer.mjs";
|
|
31
33
|
import "../cases/components/datatable/pagination.mjs";
|
|
32
34
|
import "../cases/components/navigation/site-navigation.mjs";
|