@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 +9 -0
- package/package.json +1 -1
- package/source/components/datatable/datasource/rest.mjs +410 -404
- package/source/components/datatable/save-button.mjs +0 -8
- package/source/components/form/form.mjs +0 -7
- package/source/data/datasource/server/restapi.mjs +12 -3
- package/source/data/datasource/server.mjs +118 -74
- package/source/data/diff.mjs +1 -1
- package/test/cases/data/diff.mjs +37 -0
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.
|
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 {
|
16
|
-
import {
|
17
|
-
import {
|
18
|
-
import {
|
19
|
-
import {
|
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
|
-
|
22
|
-
|
23
|
+
assembleMethodSymbol,
|
24
|
+
registerCustomElement,
|
23
25
|
} from "../../../dom/customelement.mjs";
|
24
|
-
import {
|
25
|
-
import {
|
26
|
-
import {
|
27
|
-
import {
|
28
|
-
import {
|
29
|
-
import {
|
30
|
-
import {
|
31
|
-
import {
|
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 {
|
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
|
-
|
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
|
-
|
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
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
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
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
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
|
-
|
361
|
-
|
365
|
+
const filterID = this.getOption("filter.id", undefined);
|
366
|
+
if (!filterID) return;
|
362
367
|
|
363
|
-
|
368
|
+
const filterControl = findElementWithIdUpwards(this, filterID);
|
364
369
|
|
365
|
-
|
366
|
-
|
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
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
|
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
|
-
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
|
462
|
-
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
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
|
-
|
486
|
+
const autoInit = this.getOption("features.autoInit");
|
487
|
+
validateBoolean(autoInit);
|
488
|
+
|
489
|
+
if (autoInit !== true) return;
|
485
490
|
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
491
|
+
if (this.getOption("autoInit.intersectionObserver") === true) {
|
492
|
+
initIntersectionObserver.call(this);
|
493
|
+
return;
|
494
|
+
}
|
490
495
|
|
491
|
-
|
492
|
-
|
493
|
-
|
496
|
+
setTimeout(() => {
|
497
|
+
this.fetch().catch(() => {
|
498
|
+
});
|
499
|
+
}, 0);
|
494
500
|
}
|
495
501
|
|
496
502
|
function initEventHandler() {
|
497
|
-
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
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
|
-
|
518
|
-
|
519
|
-
|
520
|
-
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
|
526
|
-
|
527
|
-
|
528
|
-
|
529
|
-
|
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
|
-
|
538
|
-
|
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 =
|
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 {
|
16
|
-
import { isObject
|
17
|
-
import {
|
18
|
-
import {
|
19
|
-
import {
|
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 {
|
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
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
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
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
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
|
}
|
package/source/data/diff.mjs
CHANGED
package/test/cases/data/diff.mjs
CHANGED
@@ -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;
|