@schukai/monster 3.70.0 → 3.71.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/CHANGELOG.md CHANGED
@@ -2,6 +2,12 @@
2
2
 
3
3
 
4
4
 
5
+ ## [3.71.0] - 2024-06-25
6
+
7
+ ### Add Features
8
+
9
+ - new feature for transfer the part that has been changed [#217](https://gitlab.schukai.com/oss/libraries/javascript/monster/issues/217)
10
+
5
11
  ## [3.70.0] - 2024-06-25
6
12
 
7
13
  ### Add Features
@@ -9,15 +15,18 @@
9
15
  - complete change of form control to a derivation of dataset [#216](https://gitlab.schukai.com/oss/libraries/javascript/monster/issues/216)
10
16
  - new dataset feature refreshOnMutation [#215](https://gitlab.schukai.com/oss/libraries/javascript/monster/issues/215)
11
17
  - new comprehensive options display [#213](https://gitlab.schukai.com/oss/libraries/javascript/monster/issues/213)
18
+
12
19
  ### Bug Fixes
13
20
 
14
21
  - initialize of loaded html fields [#210](https://gitlab.schukai.com/oss/libraries/javascript/monster/issues/210)
15
22
  - values from the value attribute are now displayed correctly after loading the options. [#212](https://gitlab.schukai.com/oss/libraries/javascript/monster/issues/212)
16
23
  - If a value is specified in the select, it is now also displayed with a label from the options. [#212](https://gitlab.schukai.com/oss/libraries/javascript/monster/issues/212)
24
+
17
25
  ### Changes
18
26
 
19
27
  - doc, little bugs and tidy
20
28
  - test adjustments and minor layout adjustments
29
+
21
30
  ### Code Refactoring
22
31
 
23
32
  - adjustments to the form stylesheets [#214](https://gitlab.schukai.com/oss/libraries/javascript/monster/issues/214)
package/package.json CHANGED
@@ -1 +1 @@
1
- {"author":"schukai GmbH","dependencies":{"@floating-ui/dom":"^1.6.5","@popperjs/core":"^2.11.8"},"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":"3.70.0"}
1
+ {"author":"schukai GmbH","dependencies":{"@floating-ui/dom":"^1.6.5","@popperjs/core":"^2.11.8"},"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":"3.71.0"}
@@ -12,25 +12,27 @@
12
12
  * SPDX-License-Identifier: AGPL-3.0
13
13
  */
14
14
 
15
- import { addAttributeToken } from "../../../dom/attributes.mjs";
16
- import { ATTRIBUTE_ERRORMESSAGE } from "../../../dom/constants.mjs";
17
- import { Datasource, dataSourceSymbol } from "../datasource.mjs";
18
- import { DatasourceStyleSheet } from "../stylesheet/datasource.mjs";
19
- import { instanceSymbol } from "../../../constants.mjs";
15
+ import {diff} from "../../../data/diff.mjs";
16
+ import {addAttributeToken} from "../../../dom/attributes.mjs";
17
+ import {ATTRIBUTE_ERRORMESSAGE} from "../../../dom/constants.mjs";
18
+ import {isArray} from "../../../types/is.mjs";
19
+ import {Datasource, dataSourceSymbol} from "../datasource.mjs";
20
+ import {DatasourceStyleSheet} from "../stylesheet/datasource.mjs";
21
+ import {instanceSymbol} from "../../../constants.mjs";
20
22
  import {
21
- assembleMethodSymbol,
22
- registerCustomElement,
23
+ assembleMethodSymbol,
24
+ registerCustomElement,
23
25
  } from "../../../dom/customelement.mjs";
24
- import { RestAPI } from "../../../data/datasource/server/restapi.mjs";
25
- import { Formatter } from "../../../text/formatter.mjs";
26
- import { clone } from "../../../util/clone.mjs";
27
- import { validateBoolean } from "../../../types/validate.mjs";
28
- import { findElementWithIdUpwards } from "../../../dom/util.mjs";
29
- import { Observer } from "../../../types/observer.mjs";
30
- import { Pathfinder } from "../../../data/pathfinder.mjs";
31
- import { fireCustomEvent } from "../../../dom/events.mjs";
26
+ import {RestAPI} from "../../../data/datasource/server/restapi.mjs";
27
+ import {Formatter} from "../../../text/formatter.mjs";
28
+ import {clone} from "../../../util/clone.mjs";
29
+ import {validateBoolean} from "../../../types/validate.mjs";
30
+ import {findElementWithIdUpwards} from "../../../dom/util.mjs";
31
+ import {Observer} from "../../../types/observer.mjs";
32
+ import {Pathfinder} from "../../../data/pathfinder.mjs";
33
+ import {fireCustomEvent} from "../../../dom/events.mjs";
32
34
 
33
- export { Rest };
35
+ export {Rest};
34
36
 
35
37
  /**
36
38
  * @private
@@ -44,7 +46,7 @@ const intersectionObserverHandlerSymbol = Symbol("intersectionObserverHandler");
44
46
  * @type {symbol}
45
47
  */
46
48
  const rawDataSymbol = Symbol.for(
47
- "@schukai/monster/data/datasource/server/restapi/rawdata",
49
+ "@schukai/monster/data/datasource/server/restapi/rawdata",
48
50
  );
49
51
 
50
52
  /**
@@ -52,7 +54,7 @@ const rawDataSymbol = Symbol.for(
52
54
  * @type {symbol}
53
55
  */
54
56
  const intersectionObserverObserverSymbol = Symbol(
55
- "intersectionObserverObserver",
57
+ "intersectionObserverObserver",
56
58
  );
57
59
 
58
60
  /**
@@ -81,23 +83,23 @@ const filterObserverSymbol = Symbol("filterObserver");
81
83
  * @summary A rest api datasource
82
84
  */
83
85
  class Rest extends Datasource {
84
- /**
85
- * the constructor of the class
86
- */
87
- constructor() {
88
- super();
89
- this[dataSourceSymbol] = new RestAPI();
90
- }
91
-
92
- /**
93
- * This method is called by the `instanceof` operator.
94
- * @returns {symbol}
95
- */
96
- static get [instanceSymbol]() {
97
- return Symbol.for("@schukai/monster/components/datasource/rest@@instance");
98
- }
99
-
100
- /**
86
+ /**
87
+ * the constructor of the class
88
+ */
89
+ constructor() {
90
+ super();
91
+ this[dataSourceSymbol] = new RestAPI();
92
+ }
93
+
94
+ /**
95
+ * This method is called by the `instanceof` operator.
96
+ * @returns {symbol}
97
+ */
98
+ static get [instanceSymbol]() {
99
+ return Symbol.for("@schukai/monster/components/datasource/rest@@instance");
100
+ }
101
+
102
+ /**
101
103
  * To set the options via the html tag the attribute `data-monster-options` must be used.
102
104
  * @see {@link https://monsterjs.org/en/doc/#configurate-a-monster-control}
103
105
  *
@@ -128,405 +130,409 @@ class Rest extends Datasource {
128
130
  * @property {Object} write Write configuration
129
131
 
130
132
  */
131
- get defaults() {
132
- const restOptions = new RestAPI().defaults;
133
-
134
- restOptions.read.parameters = {
135
- filter: undefined,
136
- oderBy: undefined,
137
- page: "1",
138
- };
139
-
140
- return Object.assign({}, super.defaults, restOptions, {
141
- templates: {
142
- main: getTemplate(),
143
- },
144
-
145
- features: {
146
- autoInit: false,
147
- filter: false,
148
- },
149
-
150
- autoInit: {
151
- intersectionObserver: false,
152
- oneTime: true,
153
- },
154
-
155
- filter: {
156
- id: undefined,
157
- },
158
-
159
- datatable: {
160
- id: undefined,
161
- },
162
-
163
- response: {
164
- path: {
165
- message: "sys.message",
166
- code: "sys.code",
167
- },
168
- },
169
- });
170
- }
171
-
172
- /**
173
- *
174
- * @param {string} page
175
- * @param {string} query
176
- * @param {string} orderBy
177
- * @returns {Monster.Components.Datatable.Datasource.Rest}
178
- */
179
- setParameters({ page, query, orderBy }) {
180
- const parameters = this.getOption("read.parameters");
181
- if (query !== undefined) {
182
- parameters.query = `${query}`;
183
- parameters.page = "1";
184
- }
185
-
186
- // after a query the page is set to 1, so if the page is not set, it is set to 1
187
- if (page !== undefined) parameters.page = `${page}`;
188
- if (orderBy !== undefined) parameters.order = `${orderBy}`;
189
- this.setOption("read.parameters", parameters);
190
- return this;
191
- }
192
-
193
- /**
194
- * @return {void}
195
- */
196
- [assembleMethodSymbol]() {
197
- super[assembleMethodSymbol]();
198
-
199
- initEventHandler.call(this);
200
- initAutoInit.call(this);
201
- }
202
-
203
- /**
204
- * @deprecated 2023-06-25
205
- * @returns {Promise<never>|*}
206
- */
207
- reload() {
208
- return this.fetch();
209
- }
210
-
211
- /**
212
- * Fetches the data from the rest api
213
- * @returns {Promise<never>|*}
214
- */
215
- fetch() {
216
- const opt = clone(this.getOption("read"));
217
- this[dataSourceSymbol].setOption("read", opt);
218
-
219
- let url = this.getOption("read.url");
220
- const formatter = new Formatter(this.getOption("read.parameters"));
221
-
222
- if (!url) {
223
- return Promise.reject(new Error("No url defined"));
224
- }
225
-
226
- url = formatter.format(url);
227
-
228
- this[dataSourceSymbol].setOption("read.url", url);
229
-
230
- return new Promise((resolve, reject) => {
231
- fireCustomEvent(this, "monster-datasource-fetch", {
232
- datasource: this,
233
- });
234
-
235
- setTimeout(() => {
236
- this[dataSourceSymbol]
237
- .read()
238
- .then((response) => {
239
- fireCustomEvent(this, "monster-datasource-fetched", {
240
- datasource: this,
241
- });
242
-
243
- resolve(response);
244
- })
245
- .catch((error) => {
246
- fireCustomEvent(this, "monster-datasource-error", {
247
- error: error,
248
- });
249
-
250
- addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, error.toString());
251
- reject(error);
252
- });
253
- }, 0);
254
- });
255
- }
256
-
257
- /**
258
- *
259
- * @return {CSSStyleSheet[]}
260
- */
261
- static getCSSStyleSheet() {
262
- return [DatasourceStyleSheet];
263
- }
264
-
265
- /**
266
- * @private
267
- * @return {string}
268
- */
269
- static getTag() {
270
- return "monster-datasource-rest";
271
- }
272
-
273
- /**
274
- * This method activates the intersection observer manually.
275
- * For this purpose, the option `autoInit.intersectionObserver` must be set to `false`.
276
- *
277
- * @returns {Monster.Components.Datatable.Datasource.Rest}
278
- */
279
- initIntersectionObserver() {
280
- initIntersectionObserver.call(this);
281
- return this;
282
- }
283
-
284
- /**
285
- * @private
286
- */
287
- connectedCallback() {
288
- super.connectedCallback();
289
-
290
- setTimeout(() => {
291
- if (this.getOption("features.filter", false) === true) {
292
- initFilter.call(this);
293
- }
294
- }, 0);
295
- }
296
-
297
- /**
298
- * @private
299
- */
300
- disconnectedCallback() {
301
- super.disconnectedCallback();
302
- removeFilter.call(this);
303
- }
304
-
305
- read() {
306
- return this.fetch();
307
- }
308
-
309
- /**
310
- * Fetches the data from the rest api
311
- * @returns {Promise<never>|*}
312
- */
313
- write() {
314
- const opt = clone(this.getOption("write"));
315
- this[dataSourceSymbol].setOption("write", opt);
316
-
317
- let url = this.getOption("write.url");
318
- const formatter = new Formatter(this.getOption("write.parameters"));
319
-
320
- if (!url) {
321
- return Promise.reject(new Error("No url defined"));
322
- }
323
-
324
- url = formatter.format(url);
325
-
326
- this[dataSourceSymbol].setOption("write.url", url);
327
-
328
- return new Promise((resolve, reject) => {
329
- fireCustomEvent(this, "monster-datasource-fetch", {
330
- datasource: this,
331
- });
332
-
333
- setTimeout(() => {
334
- this[dataSourceSymbol]
335
- .write()
336
- .then((response) => {
337
- fireCustomEvent(this, "monster-datasource-fetched", {
338
- datasource: this,
339
- });
340
-
341
- resolve(response);
342
- })
343
- .catch((error) => {
344
- fireCustomEvent(this, "monster-datasource-error", {
345
- error: error,
346
- });
347
-
348
- addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, error.toString());
349
- reject(error);
350
- });
351
- }, 0);
352
- });
353
- }
133
+ get defaults() {
134
+ const restOptions = new RestAPI().defaults;
135
+
136
+ restOptions.read.parameters = {
137
+ filter: undefined,
138
+ oderBy: undefined,
139
+ page: "1",
140
+ };
141
+
142
+ return Object.assign({}, super.defaults, restOptions, {
143
+ templates: {
144
+ main: getTemplate(),
145
+ },
146
+
147
+ features: {
148
+ autoInit: false,
149
+ filter: false,
150
+ },
151
+
152
+ autoInit: {
153
+ intersectionObserver: false,
154
+ oneTime: true,
155
+ },
156
+
157
+ filter: {
158
+ id: undefined,
159
+ },
160
+
161
+ datatable: {
162
+ id: undefined,
163
+ },
164
+
165
+ response: {
166
+ path: {
167
+ message: "sys.message",
168
+ code: "sys.code",
169
+ },
170
+ },
171
+ });
172
+ }
173
+
174
+ /**
175
+ *
176
+ * @param {string} page
177
+ * @param {string} query
178
+ * @param {string} orderBy
179
+ * @returns {Monster.Components.Datatable.Datasource.Rest}
180
+ */
181
+ setParameters({page, query, orderBy}) {
182
+ const parameters = this.getOption("read.parameters");
183
+ if (query !== undefined) {
184
+ parameters.query = `${query}`;
185
+ parameters.page = "1";
186
+ }
187
+
188
+ // after a query the page is set to 1, so if the page is not set, it is set to 1
189
+ if (page !== undefined) parameters.page = `${page}`;
190
+ if (orderBy !== undefined) parameters.order = `${orderBy}`;
191
+ this.setOption("read.parameters", parameters);
192
+ return this;
193
+ }
194
+
195
+ /**
196
+ * @return {void}
197
+ */
198
+ [assembleMethodSymbol]() {
199
+ super[assembleMethodSymbol]();
200
+
201
+ initEventHandler.call(this);
202
+ initAutoInit.call(this);
203
+ }
204
+
205
+ /**
206
+ * @deprecated 2023-06-25
207
+ * @returns {Promise<never>|*}
208
+ */
209
+ reload() {
210
+ return this.fetch();
211
+ }
212
+
213
+ /**
214
+ * Fetches the data from the rest api
215
+ * @returns {Promise<never>|*}
216
+ */
217
+ fetch() {
218
+ const opt = clone(this.getOption("read"));
219
+ this[dataSourceSymbol].setOption("read", opt);
220
+
221
+ let url = this.getOption("read.url");
222
+ const formatter = new Formatter(this.getOption("read.parameters"));
223
+
224
+ if (!url) {
225
+ return Promise.reject(new Error("No url defined"));
226
+ }
227
+
228
+ url = formatter.format(url);
229
+
230
+ this[dataSourceSymbol].setOption("read.url", url);
231
+
232
+ return new Promise((resolve, reject) => {
233
+ fireCustomEvent(this, "monster-datasource-fetch", {
234
+ datasource: this,
235
+ });
236
+
237
+ setTimeout(() => {
238
+ this[dataSourceSymbol]
239
+ .read()
240
+ .then((response) => {
241
+ fireCustomEvent(this, "monster-datasource-fetched", {
242
+ datasource: this,
243
+ });
244
+
245
+ resolve(response);
246
+ })
247
+ .catch((error) => {
248
+ fireCustomEvent(this, "monster-datasource-error", {
249
+ error: error,
250
+ });
251
+
252
+ addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, error.toString());
253
+ reject(error);
254
+ });
255
+ }, 0);
256
+ });
257
+ }
258
+
259
+ /**
260
+ *
261
+ * @return {CSSStyleSheet[]}
262
+ */
263
+ static getCSSStyleSheet() {
264
+ return [DatasourceStyleSheet];
265
+ }
266
+
267
+ /**
268
+ * @private
269
+ * @return {string}
270
+ */
271
+ static getTag() {
272
+ return "monster-datasource-rest";
273
+ }
274
+
275
+ /**
276
+ * This method activates the intersection observer manually.
277
+ * For this purpose, the option `autoInit.intersectionObserver` must be set to `false`.
278
+ *
279
+ * @returns {Monster.Components.Datatable.Datasource.Rest}
280
+ */
281
+ initIntersectionObserver() {
282
+ initIntersectionObserver.call(this);
283
+ return this;
284
+ }
285
+
286
+ /**
287
+ * @private
288
+ */
289
+ connectedCallback() {
290
+ super.connectedCallback();
291
+
292
+ setTimeout(() => {
293
+ if (this.getOption("features.filter", false) === true) {
294
+ initFilter.call(this);
295
+ }
296
+ }, 0);
297
+ }
298
+
299
+ /**
300
+ * @private
301
+ */
302
+ disconnectedCallback() {
303
+ super.disconnectedCallback();
304
+ removeFilter.call(this);
305
+ }
306
+
307
+ /**
308
+ * @returns {Promise<never>|*}
309
+ */
310
+ read() {
311
+ return this.fetch();
312
+ }
313
+
314
+ /**
315
+ * Fetches the data from the rest api
316
+ * @returns {Promise<never>|*}
317
+ */
318
+ write() {
319
+ const opt = clone(this.getOption("write"));
320
+ this[dataSourceSymbol].setOption("write", opt);
321
+
322
+ let url = this.getOption("write.url");
323
+ const formatter = new Formatter(this.getOption("write.parameters"));
324
+
325
+ if (!url) {
326
+ return Promise.reject(new Error("No url defined"));
327
+ }
328
+
329
+ url = formatter.format(url);
330
+
331
+ this[dataSourceSymbol].setOption("write.url", url);
332
+
333
+ return new Promise((resolve, reject) => {
334
+ fireCustomEvent(this, "monster-datasource-fetch", {
335
+ datasource: this,
336
+ });
337
+
338
+ setTimeout(() => {
339
+ this[dataSourceSymbol]
340
+ .write()
341
+ .then((response) => {
342
+ fireCustomEvent(this, "monster-datasource-fetched", {
343
+ datasource: this,
344
+ });
345
+
346
+ resolve(response);
347
+ })
348
+ .catch((error) => {
349
+ fireCustomEvent(this, "monster-datasource-error", {
350
+ error: error,
351
+ });
352
+
353
+ addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, error.toString());
354
+ reject(error);
355
+ });
356
+ }, 0);
357
+ });
358
+ }
354
359
  }
355
360
 
356
361
  /**
357
362
  * @private
358
363
  */
359
364
  function removeFilter() {
360
- const filterID = this.getOption("filter.id", undefined);
361
- if (!filterID) return;
365
+ const filterID = this.getOption("filter.id", undefined);
366
+ if (!filterID) return;
362
367
 
363
- const filterControl = findElementWithIdUpwards(this, filterID);
368
+ const filterControl = findElementWithIdUpwards(this, filterID);
364
369
 
365
- if (filterControl && this[filterObserverSymbol]) {
366
- filterControl?.detachObserver(this[filterObserverSymbol]);
367
- }
370
+ if (filterControl && this[filterObserverSymbol]) {
371
+ filterControl?.detachObserver(this[filterObserverSymbol]);
372
+ }
368
373
  }
369
374
 
370
375
  /**
371
376
  * @private
372
377
  */
373
378
  function initFilter() {
374
- const filterID = this.getOption("filter.id", undefined);
375
-
376
- if (!filterID)
377
- throw new Error("filter feature is enabled but no filter id is defined");
378
-
379
- const filterControl = findElementWithIdUpwards(this, filterID);
380
- if (!filterControl)
381
- throw new Error(
382
- "filter feature is enabled but no filter control with id " +
383
- filterID +
384
- " is found",
385
- );
386
-
387
- this[filterObserverSymbol] = new Observer(() => {
388
- const query = filterControl.getOption("query");
389
- if ( query===undefined) {
390
- return;
391
- }
392
- this.setParameters({ query: query });
393
- this.fetch()
394
- .then((response) => {
395
- if (!(response instanceof Response)) {
396
- throw new Error("Response is not an instance of Response");
397
- }
398
-
399
- if (response?.ok === true) {
400
- this.dispatchEvent(new CustomEvent("reload", { bubbles: true }));
401
- filterControl?.showSuccess();
402
- }
403
-
404
- if (response.bodyUsed === true) {
405
- return handleIntersectionObserver.call(
406
- this,
407
- response[rawDataSymbol],
408
- response,
409
- filterControl,
410
- );
411
- }
412
-
413
- response
414
- .text()
415
- .then((jsonAsText) => {
416
- let json;
417
- try {
418
- json = JSON.parse(jsonAsText);
419
- } catch (e) {
420
- const message = e instanceof Error ? e.message : `${e}`;
421
- filterControl?.showFailureMessage(message);
422
- return Promise.reject(e);
423
- }
424
-
425
- return handleIntersectionObserver.call(
426
- this,
427
- json,
428
- response,
429
- filterControl,
430
- );
431
- })
432
- .catch((e) => {
433
- filterControl?.showFailureMessage(e.message);
434
- });
435
- })
436
- .catch((e) => {
437
- this.dispatchEvent(
438
- new CustomEvent("error", { bubbles: true, detail: e }),
439
- );
440
-
441
- if (!(e instanceof Error)) {
442
- e = new Error(e);
443
- }
444
-
445
- filterControl?.showFailureMessage(e.message);
446
- return Promise.reject(e);
447
- });
448
- });
449
-
450
- filterControl.attachObserver(this[filterObserverSymbol]);
379
+ const filterID = this.getOption("filter.id", undefined);
380
+
381
+ if (!filterID)
382
+ throw new Error("filter feature is enabled but no filter id is defined");
383
+
384
+ const filterControl = findElementWithIdUpwards(this, filterID);
385
+ if (!filterControl)
386
+ throw new Error(
387
+ "filter feature is enabled but no filter control with id " +
388
+ filterID +
389
+ " is found",
390
+ );
391
+
392
+ this[filterObserverSymbol] = new Observer(() => {
393
+ const query = filterControl.getOption("query");
394
+ if (query === undefined) {
395
+ return;
396
+ }
397
+ this.setParameters({query: query});
398
+ this.fetch()
399
+ .then((response) => {
400
+ if (!(response instanceof Response)) {
401
+ throw new Error("Response is not an instance of Response");
402
+ }
403
+
404
+ if (response?.ok === true) {
405
+ this.dispatchEvent(new CustomEvent("reload", {bubbles: true}));
406
+ filterControl?.showSuccess();
407
+ }
408
+
409
+ if (response.bodyUsed === true) {
410
+ return handleIntersectionObserver.call(
411
+ this,
412
+ response[rawDataSymbol],
413
+ response,
414
+ filterControl,
415
+ );
416
+ }
417
+
418
+ response
419
+ .text()
420
+ .then((jsonAsText) => {
421
+ let json;
422
+ try {
423
+ json = JSON.parse(jsonAsText);
424
+ } catch (e) {
425
+ const message = e instanceof Error ? e.message : `${e}`;
426
+ filterControl?.showFailureMessage(message);
427
+ return Promise.reject(e);
428
+ }
429
+
430
+ return handleIntersectionObserver.call(
431
+ this,
432
+ json,
433
+ response,
434
+ filterControl,
435
+ );
436
+ })
437
+ .catch((e) => {
438
+ filterControl?.showFailureMessage(e.message);
439
+ });
440
+ })
441
+ .catch((e) => {
442
+ this.dispatchEvent(
443
+ new CustomEvent("error", {bubbles: true, detail: e}),
444
+ );
445
+
446
+ if (!(e instanceof Error)) {
447
+ e = new Error(e);
448
+ }
449
+
450
+ filterControl?.showFailureMessage(e.message);
451
+ return Promise.reject(e);
452
+ });
453
+ });
454
+
455
+ filterControl.attachObserver(this[filterObserverSymbol]);
451
456
  }
452
457
 
453
458
  function handleIntersectionObserver(json, response, filterControl) {
454
- const path = new Pathfinder(json);
455
-
456
- const codePath = this.getOption("response.path.code");
457
-
458
- if (path.exists(codePath)) {
459
- const code = `${path.getVia(codePath)}`;
460
- if (code && code === "200") {
461
- filterControl?.showSuccess();
462
- return Promise.resolve(response);
463
- }
464
-
465
- const messagePath = this.getOption("response.path.message");
466
- if (path.exists(messagePath)) {
467
- const message = path.getVia(messagePath);
468
- filterControl?.showFailureMessage(message);
469
- return Promise.reject(new Error(message));
470
- }
471
-
472
- return Promise.reject(new Error("Response code is not 200"));
473
- }
459
+ const path = new Pathfinder(json);
460
+
461
+ const codePath = this.getOption("response.path.code");
462
+
463
+ if (path.exists(codePath)) {
464
+ const code = `${path.getVia(codePath)}`;
465
+ if (code && code === "200") {
466
+ filterControl?.showSuccess();
467
+ return Promise.resolve(response);
468
+ }
469
+
470
+ const messagePath = this.getOption("response.path.message");
471
+ if (path.exists(messagePath)) {
472
+ const message = path.getVia(messagePath);
473
+ filterControl?.showFailureMessage(message);
474
+ return Promise.reject(new Error(message));
475
+ }
476
+
477
+ return Promise.reject(new Error("Response code is not 200"));
478
+ }
474
479
  }
475
480
 
476
481
  /**
477
482
  * @private
478
483
  */
479
484
  function initAutoInit() {
480
-
481
- const autoInit = this.getOption("features.autoInit");
482
- validateBoolean(autoInit);
483
485
 
484
- if (autoInit !== true) return;
486
+ const autoInit = this.getOption("features.autoInit");
487
+ validateBoolean(autoInit);
488
+
489
+ if (autoInit !== true) return;
485
490
 
486
- if (this.getOption("autoInit.intersectionObserver") === true) {
487
- initIntersectionObserver.call(this);
488
- return;
489
- }
491
+ if (this.getOption("autoInit.intersectionObserver") === true) {
492
+ initIntersectionObserver.call(this);
493
+ return;
494
+ }
490
495
 
491
- setTimeout(() => {
492
- this.fetch().catch(() => {});
493
- }, 0);
496
+ setTimeout(() => {
497
+ this.fetch().catch(() => {
498
+ });
499
+ }, 0);
494
500
  }
495
501
 
496
502
  function initEventHandler() {
497
- this[intersectionObserverHandlerSymbol] = (entries) => {
498
- entries.forEach((entry) => {
499
- if (entry.isIntersecting) {
500
- if (entry.intersectionRatio > 0) {
501
- this.fetch();
502
- }
503
-
504
- // only load once
505
- if (
506
- this.getOption("autoInit.oneTime") === true &&
507
- this[intersectionObserverObserverSymbol] !== undefined
508
- ) {
509
- this[intersectionObserverObserverSymbol].unobserve(this);
510
- }
511
- }
512
- });
513
- };
503
+ this[intersectionObserverHandlerSymbol] = (entries) => {
504
+ entries.forEach((entry) => {
505
+ if (entry.isIntersecting) {
506
+ if (entry.intersectionRatio > 0) {
507
+ this.fetch();
508
+ }
509
+
510
+ // only load once
511
+ if (
512
+ this.getOption("autoInit.oneTime") === true &&
513
+ this[intersectionObserverObserverSymbol] !== undefined
514
+ ) {
515
+ this[intersectionObserverObserverSymbol].unobserve(this);
516
+ }
517
+ }
518
+ });
519
+ };
514
520
  }
515
521
 
516
522
  function initIntersectionObserver() {
517
- this.classList.add("intersection-observer");
518
-
519
- const options = {
520
- root: null,
521
- rootMargin: "0px",
522
- threshold: 0.1,
523
- };
524
-
525
- this[intersectionObserverObserverSymbol] = new IntersectionObserver(
526
- this[intersectionObserverHandlerSymbol],
527
- options,
528
- );
529
- this[intersectionObserverObserverSymbol].observe(this);
523
+ this.classList.add("intersection-observer");
524
+
525
+ const options = {
526
+ root: null,
527
+ rootMargin: "0px",
528
+ threshold: 0.1,
529
+ };
530
+
531
+ this[intersectionObserverObserverSymbol] = new IntersectionObserver(
532
+ this[intersectionObserverHandlerSymbol],
533
+ options,
534
+ );
535
+ this[intersectionObserverObserverSymbol].observe(this);
530
536
  }
531
537
 
532
538
  /**
@@ -534,8 +540,8 @@ function initIntersectionObserver() {
534
540
  * @return {string}
535
541
  */
536
542
  function getTemplate() {
537
- // language=HTML
538
- return `
543
+ // language=HTML
544
+ return `
539
545
  <slot></slot>`;
540
546
  }
541
547
 
@@ -77,9 +77,6 @@ class SaveButton extends CustomElement {
77
77
  * @property {Object} classes The classes
78
78
  * @property {string} classes.bar The bar class
79
79
  * @property {string} classes.badge The badge class
80
- * @property {object} mapping The mapping
81
- * @property {string} mapping.data The data
82
- * @property {number} mapping.index The index
83
80
  * @property {Array} ignoreChanges The ignore changes (regex)
84
81
  * @property {Array} data The data
85
82
  * @return {Object}
@@ -105,11 +102,6 @@ class SaveButton extends CustomElement {
105
102
 
106
103
  changes: "0",
107
104
 
108
- mapping: {
109
- data: "dataset",
110
- index: 0,
111
- },
112
-
113
105
  ignoreChanges: [],
114
106
 
115
107
  data: {},
@@ -164,13 +164,6 @@ class Form extends DataSet {
164
164
  }
165
165
 
166
166
  function initDataSourceHandler() {
167
- if (!this[datasourceLinkedElementSymbol]) {
168
- return;
169
- }
170
- console.log(this[datasourceLinkedElementSymbol]);
171
- this[datasourceLinkedElementSymbol].setOption("write.responseCallback", (response) => {
172
- console.log("response!!!", response);
173
- })
174
167
 
175
168
  }
176
169
 
@@ -13,7 +13,8 @@
13
13
  */
14
14
 
15
15
  import {internalSymbol, instanceSymbol} from "../../../constants.mjs";
16
- import {isObject, isFunction} from "../../../types/is.mjs";
16
+ import {isObject, isFunction, isArray} from "../../../types/is.mjs";
17
+ import {diff} from "../../diff.mjs";
17
18
  import {Server} from "../server.mjs";
18
19
  import {WriteError} from "./restapi/writeerror.mjs";
19
20
  import {DataFetchError} from "./restapi/data-fetch-error.mjs";
@@ -30,6 +31,7 @@ const rawDataSymbol = Symbol.for(
30
31
  "@schukai/monster/data/datasource/server/restapi/rawdata",
31
32
  );
32
33
 
34
+
33
35
  /**
34
36
  * The RestAPI is a class that enables a REST API server.
35
37
  *
@@ -75,6 +77,8 @@ class RestAPI extends Server {
75
77
  * @property {Monster.Data.Datasource~exampleCallback[]} write.mapping.callback with the help of the callback, the structures can be adjusted before writing.
76
78
  * @property {Object} write.report
77
79
  * @property {String} write.report.path Path to validations
80
+ * @property {Object} write.partial
81
+ * @property {Function} write.partial.callback Callback function to be executed after the request has been completed. (obj, diffResult) => obj
78
82
  * @property {Object} write.sheathing
79
83
  * @property {Object} write.sheathing.object Object to be wrapped
80
84
  * @property {string} write.sheathing.path Path to the data
@@ -107,6 +111,10 @@ class RestAPI extends Server {
107
111
  report: {
108
112
  path: undefined,
109
113
  },
114
+
115
+ partial: {
116
+ callback: null,
117
+ }
110
118
  },
111
119
  read: {
112
120
  init: {
@@ -135,10 +143,11 @@ class RestAPI extends Server {
135
143
  if (!init["method"]) init["method"] = "GET";
136
144
 
137
145
  let callback = this.getOption("read.responseCallback");
138
- if (!callback)
146
+ if (!callback) {
139
147
  callback = (obj) => {
140
148
  this.set(this.transformServerPayload.call(this, obj));
141
149
  };
150
+ }
142
151
 
143
152
  return fetchData.call(this, init, "read", callback);
144
153
  }
@@ -189,7 +198,7 @@ function fetchData(init, key, callback) {
189
198
  .then((resp) => {
190
199
  response = resp;
191
200
 
192
- const acceptedStatus = this.getOption(`${key}.acceptedStatus`, [200]).map(Number);
201
+ const acceptedStatus = this.getOption(`${key}.acceptedStatus`, [200]).map(Number);
193
202
 
194
203
  if (acceptedStatus.indexOf(resp.status) === -1) {
195
204
  throw new DataFetchError(
@@ -12,13 +12,21 @@
12
12
  * SPDX-License-Identifier: AGPL-3.0
13
13
  */
14
14
 
15
- import { internalSymbol, instanceSymbol } from "../../constants.mjs";
16
- import { isObject } from "../../types/is.mjs";
17
- import { Datasource } from "../datasource.mjs";
18
- import { Pathfinder } from "../pathfinder.mjs";
19
- import { Pipe } from "../pipe.mjs";
15
+ import {instanceSymbol} from "../../constants.mjs";
16
+ import {isArray, isFunction, isObject} from "../../types/is.mjs";
17
+ import {Datasource} from "../datasource.mjs";
18
+ import {diff} from "../diff.mjs";
19
+ import {Pathfinder} from "../pathfinder.mjs";
20
+ import {Pipe} from "../pipe.mjs";
20
21
 
21
- export { Server };
22
+ export {Server};
23
+
24
+
25
+ /**
26
+ * @private
27
+ * @type {symbol}
28
+ */
29
+ const serverVersionSymbol = Symbol("serverVersion");
22
30
 
23
31
  /**
24
32
  * Base class for all server data sources
@@ -30,54 +38,82 @@ export { Server };
30
38
  * @summary The Server class encapsulates the access to a server datasource
31
39
  */
32
40
  class Server extends Datasource {
33
- /**
34
- * This method is called by the `instanceof` operator.
35
- * @returns {symbol}
36
- */
37
- static get [instanceSymbol]() {
38
- return Symbol.for("@schukai/monster/data/datasource/server");
39
- }
40
-
41
- /**
42
- * This prepares the data that comes from the server.
43
- * Should not be called directly.
44
- *
45
- * @private
46
- * @param {Object} payload
47
- * @returns {Object}
48
- */
49
- transformServerPayload(payload) {
50
- payload = doTransform.call(this, "read", payload);
51
-
52
- const dataPath = this.getOption("read.path");
53
- if (dataPath) {
54
- payload = new Pathfinder(payload).getVia(dataPath);
55
- }
56
-
57
- return payload;
58
- }
59
-
60
- /**
61
- * This prepares the data for writing and should not be called directly.
62
- *
63
- * @private
64
- * @param {Object} payload
65
- * @returns {Object}
66
- */
67
- prepareServerPayload(payload) {
68
- payload = doTransform.call(this, "write", payload);
69
-
70
- const sheathingObject = this.getOption("write.sheathing.object");
71
- const sheathingPath = this.getOption("write.sheathing.path");
72
-
73
- if (sheathingObject && sheathingPath) {
74
- const sub = payload;
75
- payload = sheathingObject;
76
- new Pathfinder(payload).setVia(sheathingPath, sub);
77
- }
78
-
79
- return payload;
80
- }
41
+ /**
42
+ * This method is called by the `instanceof` operator.
43
+ * @returns {symbol}
44
+ */
45
+ static get [instanceSymbol]() {
46
+ return Symbol.for("@schukai/monster/data/datasource/server");
47
+ }
48
+
49
+ /**
50
+ * This prepares the data that comes from the server.
51
+ * Should not be called directly.
52
+ *
53
+ * @private
54
+ * @param {Object} payload
55
+ * @returns {Object}
56
+ */
57
+ transformServerPayload(payload) {
58
+ payload = doTransform.call(this, "read", payload);
59
+ this[serverVersionSymbol] = payload;
60
+
61
+ const dataPath = this.getOption("read.path");
62
+ if (dataPath) {
63
+ payload = new Pathfinder(payload).getVia(dataPath);
64
+ }
65
+
66
+ return payload;
67
+ }
68
+
69
+ /**
70
+ * This prepares the data for writing and should not be called directly.
71
+ *
72
+ * @private
73
+ * @param {Object} payload
74
+ * @returns {Object}
75
+ */
76
+ prepareServerPayload(payload) {
77
+ payload = doTransform.call(this, "write", payload);
78
+ payload = doDiff.call(this, payload);
79
+
80
+ const sheathingObject = this.getOption("write.sheathing.object");
81
+ const sheathingPath = this.getOption("write.sheathing.path");
82
+
83
+ if (sheathingObject && sheathingPath) {
84
+ const sub = payload;
85
+ payload = sheathingObject;
86
+ new Pathfinder(payload).setVia(sheathingPath, sub);
87
+ }
88
+
89
+ return payload;
90
+ }
91
+ }
92
+
93
+ /**
94
+ *
95
+ * @param obj
96
+ * @returns {*}
97
+ */
98
+ function doDiff(obj) {
99
+ if (this[serverVersionSymbol] === null || this[serverVersionSymbol] === undefined) {
100
+ return obj;
101
+ }
102
+
103
+ const callback = this.getOption("write.partial.callback");
104
+ if (!isFunction(callback)) {
105
+ return obj;
106
+ }
107
+
108
+ const results = diff(this[serverVersionSymbol], obj);
109
+ if (!results) {
110
+ return obj;
111
+ }
112
+
113
+ obj = callback(obj, results);
114
+ this[serverVersionSymbol] = obj;
115
+
116
+ return obj;
81
117
  }
82
118
 
83
119
  /**
@@ -87,24 +123,32 @@ class Server extends Datasource {
87
123
  * @returns {Object}
88
124
  */
89
125
  function doTransform(type, obj) {
90
- const transformation = this.getOption(`${type}.mapping.transformer`);
91
- if (transformation !== undefined && transformation !== null) {
92
- const pipe = new Pipe(transformation);
93
- const callbacks = this.getOption(`${type}.mapping.callbacks`);
94
-
95
- if (isObject(callbacks)) {
96
- for (const key in callbacks) {
97
- if (
98
- callbacks.hasOwnProperty(key) &&
99
- typeof callbacks[key] === "function"
100
- ) {
101
- pipe.setCallback(key, callbacks[key]);
102
- }
103
- }
104
- }
105
-
106
- obj = pipe.run(obj);
107
- }
108
-
109
- return obj;
126
+ const transformation = this.getOption(`${type}.mapping.transformer`);
127
+ if (transformation !== undefined && transformation !== null) {
128
+ const pipe = new Pipe(transformation);
129
+ const callbacks = this.getOption(`${type}.mapping.callbacks`);
130
+
131
+ if (isArray(callbacks)) {
132
+ for (const callback of callbacks) {
133
+ if (typeof callback === "function") {
134
+ pipe.setCallback(callback);
135
+ }
136
+ }
137
+ }
138
+
139
+ if (isObject(callbacks)) {
140
+ for (const key in callbacks) {
141
+ if (
142
+ callbacks.hasOwnProperty(key) &&
143
+ typeof callbacks[key] === "function"
144
+ ) {
145
+ pipe.setCallback(key, callbacks[key]);
146
+ }
147
+ }
148
+ }
149
+
150
+ obj = pipe.run(obj);
151
+ }
152
+
153
+ return obj;
110
154
  }
@@ -183,4 +183,4 @@ function getOperator(a, b) {
183
183
  }
184
184
 
185
185
  return operator;
186
- }
186
+ }
@@ -6,6 +6,43 @@ import {Queue} from "../../../source/types/queue.mjs";
6
6
 
7
7
  describe('Diff', function () {
8
8
 
9
+ describe('test to datasets', function () {
10
+
11
+ var obj1, obj2;
12
+
13
+ beforeEach(() => {
14
+ obj1 = [
15
+ {
16
+ "id": 1,
17
+ "name": "test"
18
+ },
19
+ {
20
+ "id": 2,
21
+ "name": "test2"
22
+ }
23
+ ]
24
+
25
+ obj2 = [
26
+ {
27
+ "id": 1,
28
+ "name": "test"
29
+ },
30
+ {
31
+ "id": "3",
32
+ "name": "test2"
33
+ }
34
+ ]
35
+
36
+ });
37
+
38
+ it('should return the difference between two datasets', function () {
39
+ let d = diff(obj1, obj2);
40
+ expect(JSON.stringify(d)).is.equal('[{"operator":"update","path":["1","id"],"first":{"value":2,"type":"number"},"second":{"value":"3","type":"string"}}]');
41
+ });
42
+
43
+
44
+ })
45
+
9
46
  describe('Diff special cases', function () {
10
47
 
11
48
  var obj1, obj2;