@schukai/monster 3.95.0 → 3.95.2

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.
@@ -12,290 +12,312 @@
12
12
  * SPDX-License-Identifier: AGPL-3.0
13
13
  */
14
14
 
15
- import { instanceSymbol, internalSymbol } from "../../constants.mjs";
16
- import { Pathfinder } from "../../data/pathfinder.mjs";
17
- import { getLinkedObjects, hasObjectLink } from "../../dom/attributes.mjs";
18
- import { customElementUpdaterLinkSymbol } from "../../dom/constants.mjs";
15
+ import {instanceSymbol} from "../../constants.mjs";
16
+ import {Pathfinder} from "../../data/pathfinder.mjs";
19
17
  import {
20
- assembleMethodSymbol,
21
- CustomElement,
22
- attributeObserverSymbol,
23
- registerCustomElement,
18
+ assembleMethodSymbol,
19
+ CustomElement,
20
+ attributeObserverSymbol,
21
+ registerCustomElement,
24
22
  } from "../../dom/customelement.mjs";
25
- import { findElementWithSelectorUpwards } from "../../dom/util.mjs";
26
- import { isString } from "../../types/is.mjs";
27
- import { Observer } from "../../types/observer.mjs";
28
- import { clone } from "../../util/clone.mjs";
23
+ import {findElementWithSelectorUpwards} from "../../dom/util.mjs";
24
+ import {isString} from "../../types/is.mjs";
25
+ import {Observer} from "../../types/observer.mjs";
29
26
  import {
30
- ATTRIBUTE_DATASOURCE_SELECTOR,
31
- ATTRIBUTE_DATATABLE_INDEX,
27
+ ATTRIBUTE_DATASOURCE_SELECTOR,
28
+ ATTRIBUTE_DATATABLE_INDEX,
32
29
  } from "./constants.mjs";
33
- import { Datasource } from "./datasource.mjs";
34
- import { DatasetStyleSheet } from "./stylesheet/dataset.mjs";
30
+ import {Datasource} from "./datasource.mjs";
31
+ import {DatasetStyleSheet} from "./stylesheet/dataset.mjs";
35
32
  import {
36
- handleDataSourceChanges,
37
- datasourceLinkedElementSymbol,
33
+ handleDataSourceChanges,
34
+ datasourceLinkedElementSymbol,
38
35
  } from "./util.mjs";
39
- import { FormStyleSheet } from "../stylesheet/form.mjs";
36
+ import {FormStyleSheet} from "../stylesheet/form.mjs";
40
37
 
41
- export { DataSet };
38
+ export {DataSet};
42
39
 
43
40
  /**
44
- * The data set component is used to show the data of a data source.
41
+ * A data set component
45
42
  *
46
- * <img src="./images/dataset.png">
43
+ * @fragments /fragments/components/datatable/dataset
47
44
  *
48
- * You can create this control either by specifying the HTML tag <monster-dataset />` directly in the HTML or using
49
- * Javascript via the `document.createElement('monster-dataset');` method.
45
+ * @example /examples/components/datatable/dataset-simple
46
+ * @example /examples/components/datatable/dataset-rest
50
47
  *
51
- * ```html
52
- * <monster-dataset></monster-dataset>
53
- * ```
54
- *
55
- * Or you can create this CustomControl directly in Javascript:
56
- *
57
- * ```js
58
- * import '@schukai/component-datatable/source/dataset.mjs';
59
- * document.createElement('monster-dataset');
60
- * ```
61
- *
62
- * The Body should have a class "hidden" to ensure that the styles are applied correctly.
63
- *
64
- * ```css
65
- * body.hidden {
66
- * visibility: hidden;
67
- * }
68
- * ```
69
- *
70
- * @startuml dataset.png
71
- * skinparam monochrome true
72
- * skinparam shadowing false
73
- * HTMLElement <|-- CustomElement
74
- * CustomElement <|-- DataSet
75
- * @enduml
48
+ * @issue https://localhost.alvine.dev:8443/development/issues/closed/272.html
76
49
  *
77
50
  * @copyright schukai GmbH
78
- * @summary A data set
51
+ * @summary A dataset component that can be used to show the data of a data source
79
52
  */
80
53
  class DataSet extends CustomElement {
81
- /**
82
- * This method is called by the `instanceof` operator.
83
- * @return {symbol}
84
- */
85
- static get [instanceSymbol]() {
86
- return Symbol.for("@schukai/monster/components/dataset@@instance");
87
- }
88
-
89
- /**
90
- * This method determines which attributes are to be monitored by `attributeChangedCallback()`.
91
- *
92
- * @return {string[]}
93
- * @since 1.15.0
94
- */
95
- static get observedAttributes() {
96
- const attributes = super.observedAttributes;
97
- attributes.push(ATTRIBUTE_DATATABLE_INDEX);
98
- return attributes;
99
- }
100
-
101
- /**
102
- * To set the options via the HTML tag, the attribute `data-monster-options` must be used.
103
- * @see {@link https://monsterjs.org/en/doc/#configurate-a-monster-control}
104
- *
105
- * The individual configuration values can be found in the table.
106
- *
107
- * @property {Object} templates Template definitions
108
- * @property {string} templates.main Main template
109
- * @property {object} datasource The datasource
110
- * @property {string} datasource.selector The selector of the datasource
111
- * @property {object} mapping The mapping
112
- * @property {string} mapping.data The data
113
- * @property {number} mapping.index The index
114
- * @property {Array} data The data
115
- */
116
- get defaults() {
117
- const obj = Object.assign({}, super.defaults, {
118
- templates: {
119
- main: getTemplate(),
120
- },
121
-
122
- datasource: {
123
- selector: null,
124
- },
125
-
126
- mapping: {
127
- data: "dataset",
128
- index: 0,
129
- },
130
-
131
- features: {
132
- /**
133
- * @since 3.70.0
134
- * @type {boolean}
135
- */
136
- refreshOnMutation: true,
137
- },
138
-
139
- /**
140
- * @since 3.70.0
141
- * @type {boolean}
142
- */
143
- refreshOnMutation: {
144
- selector: "input, select, textarea",
145
- },
146
-
147
- data: {},
148
- });
149
-
150
- updateOptionsFromArguments.call(this, obj);
151
- return obj;
152
- }
153
-
154
- /**
155
- *
156
- * @return {string}
157
- */
158
- static getTag() {
159
- return "monster-dataset";
160
- }
161
-
162
- /**
163
- * This method is called when the component is created.
164
- * @since 3.70.0
165
- * @return {DataSet}
166
- */
167
- refresh() {
168
- // makes sure that handleDataSourceChanges is called
169
- this.setOption("data", {});
170
- return this;
171
- }
172
-
173
- /**
174
- *
175
- * @return {Promise<unknown>}
176
- */
177
- write() {
178
- return new Promise((resolve, reject) => {
179
- if (!this[datasourceLinkedElementSymbol]) {
180
- reject(new Error("No datasource"));
181
- return;
182
- }
183
-
184
- const internalUpdateCloneData = this.getInternalUpdateCloneData();
185
- if (!internalUpdateCloneData) {
186
- reject(new Error("No update data"));
187
- return;
188
- }
189
-
190
- const internalData = internalUpdateCloneData?.["data"];
191
- if (
192
- internalData === undefined ||
193
- internalData === null ||
194
- internalData === ""
195
- ) {
196
- reject(new Error("No data"));
197
- return;
198
- }
199
-
200
- queueMicrotask(() => {
201
- const path = this.getOption("mapping.data");
202
- const index = this.getOption("mapping.index");
203
-
204
- let pathWithIndex;
205
-
206
- if (isString(path) && path !== "") {
207
- pathWithIndex = path + "." + index;
208
- } else {
209
- pathWithIndex = String(index);
210
- }
211
-
212
- const data = this[datasourceLinkedElementSymbol]?.data;
213
- if (!data) {
214
- reject(new Error("No data"));
215
- return;
216
- }
217
-
218
- const unref = JSON.stringify(data);
219
- const ref = JSON.parse(unref);
220
-
221
- new Pathfinder(ref).setVia(pathWithIndex, internalData);
222
-
223
- this[datasourceLinkedElementSymbol].data = ref;
224
-
225
- resolve();
226
- });
227
- });
228
- }
229
-
230
- /**
231
- * This method is responsible for assembling the component.
232
- *
233
- * It calls the parent's assemble method first, then initializes control references and event handlers.
234
- * If the `datasource.selector` option is provided and is a string, it searches for the corresponding
235
- * element in the DOM using that selector.
236
- *
237
- * If the selector matches exactly one element, it checks if the element is an instance of the `Datasource` class.
238
- *
239
- * If it is, the component's `datasourceLinkedElementSymbol` property is set to the element, and the component
240
- * attaches an observer to the datasource's changes.
241
- *
242
- * The observer is a function that calls the `handleDataSourceChanges` method in the context of the component.
243
- * Additionally, the component attaches an observer to itself, which also calls the `handleDataSourceChanges`
244
- * method in the component's context.
245
- */
246
- [assembleMethodSymbol]() {
247
- super[assembleMethodSymbol]();
248
-
249
- initEventHandler.call(this);
250
-
251
- if (!this[datasourceLinkedElementSymbol]) {
252
- const selector = this.getOption("datasource.selector");
253
-
254
- if (isString(selector)) {
255
- const element = findElementWithSelectorUpwards(this, selector);
256
- if (element === null) {
257
- throw new Error("the selector must match exactly one element");
258
- }
259
-
260
- if (!(element instanceof Datasource)) {
261
- throw new TypeError("the element must be a datasource");
262
- }
263
-
264
- this[datasourceLinkedElementSymbol] = element;
265
- element.datasource.attachObserver(
266
- new Observer(handleDataSourceChanges.bind(this)),
267
- );
268
- } else {
269
- throw new Error("the selector must be a string");
270
- }
271
- }
272
-
273
- if (
274
- this.getOption("features.refreshOnMutation") &&
275
- this.getOption("refreshOnMutation.selector")
276
- ) {
277
- initMutationObserver.call(this);
278
- }
279
- }
280
-
281
- /**
282
- * @return [CSSStyleSheet]
283
- */
284
- static getCSSStyleSheet() {
285
- return [FormStyleSheet, DatasetStyleSheet];
286
- }
54
+ /**
55
+ * This method is called by the `instanceof` operator.
56
+ * @return {symbol}
57
+ */
58
+ static get [instanceSymbol]() {
59
+ return Symbol.for("@schukai/monster/components/dataset@@instance");
60
+ }
61
+
62
+ /**
63
+ * This method determines which attributes are to be monitored by `attributeChangedCallback()`.
64
+ *
65
+ * @return {string[]}
66
+ * @since 1.15.0
67
+ */
68
+ static get observedAttributes() {
69
+ const attributes = super.observedAttributes;
70
+ attributes.push(ATTRIBUTE_DATATABLE_INDEX);
71
+ attributes.push("data-monster-option-mapping-index");
72
+ return attributes;
73
+ }
74
+
75
+ /**
76
+ * To set the options via the HTML tag, the attribute `data-monster-options` must be used.
77
+ * @see {@link https://monsterjs.org/en/doc/#configurate-a-monster-control}
78
+ *
79
+ * The individual configuration values can be found in the table.
80
+ *
81
+ * @property {Object} templates Template definitions
82
+ * @property {string} templates.main Main template
83
+ * @property {object} datasource The datasource
84
+ * @property {string} datasource.selector The selector of the datasource
85
+ * @property {object} mapping The mapping
86
+ * @property {string} mapping.data The data
87
+ * @property {number} mapping.index The index
88
+ * @property {object} features The features
89
+ * @property {boolean} features.refreshOnMutation Refresh on mutation
90
+ * @property {object} refreshOnMutation The refresh on mutation
91
+ * @property {string} refreshOnMutation.selector The selector
92
+ */
93
+ get defaults() {
94
+ const obj = Object.assign({}, super.defaults, {
95
+ templates: {
96
+ main: getTemplate(),
97
+ },
98
+
99
+ datasource: {
100
+ selector: null,
101
+ },
102
+
103
+ mapping: {
104
+ data: "dataset",
105
+ index: 0,
106
+ },
107
+
108
+ features: {
109
+ /**
110
+ * @since 3.70.0
111
+ * @type {boolean}
112
+ */
113
+ refreshOnMutation: true,
114
+ },
115
+
116
+ /**
117
+ * @since 3.70.0
118
+ * @type {boolean}
119
+ */
120
+ refreshOnMutation: {
121
+ selector: "input, select, textarea",
122
+ },
123
+
124
+ data: {},
125
+ });
126
+
127
+ updateOptionsFromArguments.call(this, obj);
128
+ return obj;
129
+ }
130
+
131
+ /**
132
+ *
133
+ * @return {string}
134
+ */
135
+ static getTag() {
136
+ return "monster-dataset";
137
+ }
138
+
139
+ /**
140
+ * This method is called when the component is created.
141
+ * @since 3.70.0
142
+ * @return {DataSet}
143
+ */
144
+ refresh() {
145
+ // makes sure that handleDataSourceChanges is called
146
+ this.setOption("data", {});
147
+ return this;
148
+ }
149
+
150
+ /**
151
+ *
152
+ * @return {Promise<unknown>}
153
+ */
154
+ write() {
155
+ return new Promise((resolve, reject) => {
156
+ if (!this[datasourceLinkedElementSymbol]) {
157
+ reject(new Error("No datasource"));
158
+ return;
159
+ }
160
+
161
+ const internalUpdateCloneData = this.getInternalUpdateCloneData();
162
+ if (!internalUpdateCloneData) {
163
+ reject(new Error("No update data"));
164
+ return;
165
+ }
166
+
167
+ const internalData = internalUpdateCloneData?.["data"];
168
+ if (
169
+ internalData === undefined ||
170
+ internalData === null ||
171
+ internalData === ""
172
+ ) {
173
+ reject(new Error("No data"));
174
+ return;
175
+ }
176
+
177
+ queueMicrotask(() => {
178
+ const path = this.getOption("mapping.data");
179
+ const index = this.getOption("mapping.index");
180
+
181
+ let pathWithIndex;
182
+
183
+ if (isString(path) && path !== "") {
184
+ pathWithIndex = path + "." + index;
185
+ } else {
186
+ pathWithIndex = String(index);
187
+ }
188
+
189
+ const data = this[datasourceLinkedElementSymbol]?.data;
190
+ if (!data) {
191
+ reject(new Error("No data"));
192
+ return;
193
+ }
194
+
195
+ const unref = JSON.stringify(data);
196
+ const ref = JSON.parse(unref);
197
+
198
+ new Pathfinder(ref).setVia(pathWithIndex, internalData);
199
+
200
+ this[datasourceLinkedElementSymbol].data = ref;
201
+
202
+ resolve();
203
+ });
204
+ });
205
+ }
206
+
207
+ /**
208
+ * This method is responsible for assembling the component.
209
+ *
210
+ * It calls the parent's assemble method first, then initializes control references and event handlers.
211
+ * If the `datasource.selector` option is provided and is a string, it searches for the corresponding
212
+ * element in the DOM using that selector.
213
+ *
214
+ * If the selector matches exactly one element, it checks if the element is an instance of the `Datasource` class.
215
+ *
216
+ * If it is, the component's `datasourceLinkedElementSymbol` property is set to the element, and the component
217
+ * attaches an observer to the datasource's changes.
218
+ *
219
+ * The observer is a function that calls the `handleDataSourceChanges` method in the context of the component.
220
+ * Additionally, the component attaches an observer to itself, which also calls the `handleDataSourceChanges`
221
+ * method in the component's context.
222
+ */
223
+ [assembleMethodSymbol]() {
224
+ super[assembleMethodSymbol]();
225
+
226
+ requestAnimationFrame(() => {
227
+
228
+ if (!this[datasourceLinkedElementSymbol]) {
229
+ const selector = this.getOption("datasource.selector");
230
+
231
+ if (isString(selector)) {
232
+ const element = findElementWithSelectorUpwards(this, selector);
233
+ if (element === null) {
234
+ throw new Error("the selector must match exactly one element");
235
+ }
236
+
237
+ if (!(element instanceof Datasource)) {
238
+ throw new TypeError("the element must be a datasource");
239
+ }
240
+
241
+ this[datasourceLinkedElementSymbol] = element;
242
+ element.datasource.attachObserver(
243
+ new Observer(handleDataSourceChanges.bind(this)),
244
+ );
245
+
246
+ handleDataSourceChanges.call(this);
247
+ } else {
248
+ throw new Error("the selector must be a string");
249
+ }
250
+ }
251
+
252
+ if (
253
+ this.getOption("features.refreshOnMutation") &&
254
+ this.getOption("refreshOnMutation.selector")
255
+ ) {
256
+ initMutationObserver.call(this);
257
+ }
258
+
259
+ initEventHandler.call(this);
260
+
261
+ });
262
+
263
+ }
264
+
265
+ /**
266
+ * @return [CSSStyleSheet]
267
+ */
268
+ static getCSSStyleSheet() {
269
+ return [FormStyleSheet, DatasetStyleSheet];
270
+ }
287
271
  }
288
272
 
289
273
  /**
290
274
  * @private
291
275
  */
292
276
  function initEventHandler() {
293
- this[attributeObserverSymbol][ATTRIBUTE_DATATABLE_INDEX] = () => {
294
- const index = this.getAttribute(ATTRIBUTE_DATATABLE_INDEX);
295
- if (index) {
296
- this.setOption("mapping.index", parseInt(index, 10));
297
- }
298
- };
277
+
278
+ this[attributeObserverSymbol][ATTRIBUTE_DATATABLE_INDEX] = () => { // @deprecated use data-monster-option-mapping-index
279
+ const index = this.getAttribute(ATTRIBUTE_DATATABLE_INDEX);
280
+ if (index) {
281
+ this.setOption("mapping.index", parseInt(index, 10));
282
+ handleDataSourceChanges.call(this);
283
+ }
284
+ };
285
+
286
+ this[attributeObserverSymbol]["data-monster-option-mapping-index"] = () => {
287
+ const index = this.getAttribute("data-monster-option-mapping-index");
288
+ if (index !== null && index !== undefined && index !== "") {
289
+ this.setOption("mapping.index", parseInt(index, 10));
290
+ handleDataSourceChanges.call(this);
291
+ }
292
+ };
293
+
294
+ if (this[datasourceLinkedElementSymbol]) {
295
+
296
+ this[datasourceLinkedElementSymbol].datasource.attachObserver(
297
+ new Observer(() => {
298
+ const page = this[datasourceLinkedElementSymbol]?.currentPage();
299
+ if (page !== null && page !== undefined && page !== "") {
300
+ const index = parseInt(page, 10) - 1;
301
+ this.setOption("mapping.index", index);
302
+ handleDataSourceChanges.call(this);
303
+ }
304
+ }),
305
+ );
306
+
307
+ this[datasourceLinkedElementSymbol].attachObserver(
308
+ new Observer(() => {
309
+ const page = this[datasourceLinkedElementSymbol]?.currentPage();
310
+ if (page !== null && page !== undefined && page !== "") {
311
+ const index = parseInt(page, 10) - 1;
312
+ this.setOption("mapping.index", index);
313
+ handleDataSourceChanges.call(this);
314
+ }
315
+ }),
316
+ );
317
+
318
+ handleDataSourceChanges.call(this);
319
+ }
320
+
299
321
  }
300
322
 
301
323
  /**
@@ -303,56 +325,56 @@ function initEventHandler() {
303
325
  * @param {Object} options
304
326
  */
305
327
  function updateOptionsFromArguments(options) {
306
- const index = this.getAttribute(ATTRIBUTE_DATATABLE_INDEX);
328
+ const index = this.getAttribute(ATTRIBUTE_DATATABLE_INDEX); // @deprecated use data-monster-option-mapping-index
307
329
 
308
- if (index !== null && index !== undefined) {
309
- options.mapping.index = parseInt(index, 10);
310
- }
330
+ if (index !== null && index !== undefined) {
331
+ options.mapping.index = parseInt(index, 10);
332
+ }
311
333
 
312
- const selector = this.getAttribute(ATTRIBUTE_DATASOURCE_SELECTOR);
334
+ const selector = this.getAttribute(ATTRIBUTE_DATASOURCE_SELECTOR);
313
335
 
314
- if (selector) {
315
- options.datasource.selector = selector;
316
- }
336
+ if (selector) {
337
+ options.datasource.selector = selector;
338
+ }
317
339
  }
318
340
 
319
341
  /**
320
342
  * @private
321
343
  */
322
344
  function initMutationObserver() {
323
- const config = { attributes: false, childList: true, subtree: true };
324
-
325
- const callback = (mutationList, observer) => {
326
- if (mutationList.length === 0) {
327
- return;
328
- }
329
-
330
- let doneFlag = false;
331
- for (const mutation of mutationList) {
332
- if (mutation.type === "childList") {
333
- for (const node of mutation.addedNodes) {
334
- if (
335
- node instanceof HTMLElement &&
336
- node.matches(this.getOption("refreshOnMutation.selector"))
337
- ) {
338
- doneFlag = true;
339
- break;
340
- }
341
- }
342
-
343
- if (doneFlag) {
344
- break;
345
- }
346
- }
347
- }
348
-
349
- if (doneFlag) {
350
- this.refresh();
351
- }
352
- };
353
-
354
- const observer = new MutationObserver(callback);
355
- observer.observe(this, config);
345
+ const config = {attributes: false, childList: true, subtree: true};
346
+
347
+ const callback = (mutationList, observer) => {
348
+ if (mutationList.length === 0) {
349
+ return;
350
+ }
351
+
352
+ let doneFlag = false;
353
+ for (const mutation of mutationList) {
354
+ if (mutation.type === "childList") {
355
+ for (const node of mutation.addedNodes) {
356
+ if (
357
+ node instanceof HTMLElement &&
358
+ node.matches(this.getOption("refreshOnMutation.selector"))
359
+ ) {
360
+ doneFlag = true;
361
+ break;
362
+ }
363
+ }
364
+
365
+ if (doneFlag) {
366
+ break;
367
+ }
368
+ }
369
+ }
370
+
371
+ if (doneFlag) {
372
+ this.refresh();
373
+ }
374
+ };
375
+
376
+ const observer = new MutationObserver(callback);
377
+ observer.observe(this, config);
356
378
  }
357
379
 
358
380
  /**
@@ -360,8 +382,8 @@ function initMutationObserver() {
360
382
  * @return {string}
361
383
  */
362
384
  function getTemplate() {
363
- // language=HTML
364
- return `
385
+ // language=HTML
386
+ return `
365
387
  <div data-monster-role="control" part="control">
366
388
  <slot></slot>
367
389
  </div>