@schukai/monster 3.70.0 → 3.71.0

Sign up to get free protection for your applications and to get access to all the features.
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;