@schukai/monster 3.95.0 → 3.95.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -12,27 +12,27 @@
12
12
  * SPDX-License-Identifier: AGPL-3.0
13
13
  */
14
14
 
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";
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";
22
22
  import {
23
- assembleMethodSymbol,
24
- registerCustomElement,
23
+ assembleMethodSymbol,
24
+ registerCustomElement,
25
25
  } from "../../../dom/customelement.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";
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";
34
34
 
35
- export { Rest };
35
+ export {Rest};
36
36
 
37
37
  /**
38
38
  * @private
@@ -46,7 +46,7 @@ const intersectionObserverHandlerSymbol = Symbol("intersectionObserverHandler");
46
46
  * @type {symbol}
47
47
  */
48
48
  const rawDataSymbol = Symbol.for(
49
- "@schukai/monster/data/datasource/server/restapi/rawdata",
49
+ "@schukai/monster/data/datasource/server/restapi/rawdata",
50
50
  );
51
51
 
52
52
  /**
@@ -54,7 +54,7 @@ const rawDataSymbol = Symbol.for(
54
54
  * @type {symbol}
55
55
  */
56
56
  const intersectionObserverObserverSymbol = Symbol(
57
- "intersectionObserverObserver",
57
+ "intersectionObserverObserver",
58
58
  );
59
59
 
60
60
  /**
@@ -64,396 +64,427 @@ const intersectionObserverObserverSymbol = Symbol(
64
64
  const filterObserverSymbol = Symbol("filterObserver");
65
65
 
66
66
  /**
67
- * The Datasource component is a basic class for the datatable component.
67
+ * A rest api datasource
68
68
  *
69
- * <img src="./images/rest.png">
69
+ * @fragments /fragments/components/datatable/datasource/rest
70
70
  *
71
- * Dependencies: the system uses functions of the [monsterjs](https://monsterjs.org/) library
71
+ * @example /examples/components/datatable/datasource-rest-simple
72
+ * @example /examples/components/datatable/datasource-rest-auto-init
73
+ * @example /examples/components/datatable/datasource-rest-do-fetch
72
74
  *
73
- * @startuml rest.png
74
- * skinparam monochrome true
75
- * skinparam shadowing false
76
- * HTMLElement <|-- CustomElement
77
- * CustomElement <|-- Datasource
78
- * Datasource <|-- Rest
79
- * @enduml
75
+ * @issue https://localhost.alvine.dev:8443/development/issues/closed/272.html
80
76
  *
81
77
  * @copyright schukai GmbH
82
- * @summary A rest api datasource
78
+ * @summary A rest api datasource for the datatable or other components
83
79
  */
84
-
85
-
86
-
87
80
  class Rest extends Datasource {
88
- /**
89
- * the constructor of the class
90
- */
91
- constructor() {
92
- super();
93
- this[dataSourceSymbol] = new RestAPI();
94
- }
95
-
96
- /**
97
- * This method is called by the `instanceof` operator.
98
- * @return {symbol}
99
- */
100
- static get [instanceSymbol]() {
101
- return Symbol.for("@schukai/monster/components/datasource/rest@@instance");
102
- }
103
-
104
- /**
105
- * To set the options via the HTML tag, the attribute `data-monster-options` must be used.
106
- * @see {@link https://monsterjs.org/en/doc/#configurate-a-monster-control}
107
- *
108
- * The individual configuration values can be found in the table.
109
- *
110
- * @property {Object} templates Template definitions
111
- * @property {string} templates.main Main template
112
- * @property {Object} features Feature definitions
113
- * @property {boolean} features.autoInit If true, the component is initialized automatically
114
- * @property {boolean} features.filter If true, the component is initialized automatically
115
- * @property {Object} autoInit Auto init definitions
116
- * @property {boolean} autoInit.intersectionObserver If true, the intersection observer is initialized automatically
117
- * @property {boolean} autoInit.oneTime If true, the intersection observer is initialized only once
118
- * @property {Object} filter Filter definitions
119
- * @property {string} filter.id The id of the filter control
120
- * @property {Object} datatable Datatable definitions
121
- * @property {string} datatable.id The id of the datatable control
122
- * @property {Object} response Response definitions
123
- * @property {Object} response.path Path definitions (changed in 3.56.0)
124
- * @property {string} response.path.message Path to the message (changed in 3.56.0)
125
- * @property {Object} read Read configuration
126
- * @property {string} read.url The url of the rest api
127
- * @property {string} read.method The method of the rest api
128
- * @property {Object} read.parameters The parameters of the rest api
129
- * @property {Object} read.parameters.filter The filter of the rest api
130
- * @property {Object} read.parameters.orderBy The order by of the rest api
131
- * @property {Object} read.parameters.page The page of the rest api
132
- * @property {Object} write Write configuration
133
- */
134
- get defaults() {
135
- const restOptions = new RestAPI().defaults;
136
-
137
- restOptions.read.parameters = {
138
- filter: undefined,
139
- oderBy: undefined,
140
- page: "1",
141
- };
142
-
143
- return Object.assign({}, super.defaults, restOptions, {
144
- templates: {
145
- main: getTemplate(),
146
- },
147
-
148
- features: {
149
- autoInit: false,
150
- filter: false,
151
- },
152
-
153
- autoInit: {
154
- intersectionObserver: false,
155
- oneTime: true,
156
- },
157
-
158
- filter: {
159
- id: undefined,
160
- },
161
-
162
- datatable: {
163
- id: undefined,
164
- },
165
-
166
- response: {
167
- path: {
168
- message: "sys.message",
169
- code: "sys.code",
170
- },
171
- },
172
- });
173
- }
174
-
175
- /**
176
- *
177
- * @param {string} page
178
- * @param {string} query
179
- * @param {string} orderBy
180
- * @return {Rest}
181
- */
182
- setParameters({ page, query, orderBy }) {
183
- const parameters = this.getOption("read.parameters");
184
- if (query !== undefined) {
185
- parameters.query = `${query}`;
186
- parameters.page = "1";
187
- }
188
-
189
- // after a query the page is set to 1, so if the page is not set, it is set to 1
190
- if (page !== undefined) parameters.page = `${page}`;
191
- if (orderBy !== undefined) parameters.order = `${orderBy}`;
192
- this.setOption("read.parameters", parameters);
193
- return this;
194
- }
195
-
196
- /**
197
- * @return {void}
198
- */
199
- [assembleMethodSymbol]() {
200
- super[assembleMethodSymbol]();
201
-
202
- initEventHandler.call(this);
203
- initAutoInit.call(this);
204
- }
205
-
206
- /**
207
- * @deprecated 2023-06-25
208
- * @return {Promise<never>|*}
209
- */
210
- reload() {
211
- return this.fetch();
212
- }
213
-
214
- /**
215
- * Fetches the data from the rest api
216
- * @return {Promise<never>|*}
217
- */
218
- fetch() {
219
- const opt = clone(this.getOption("read"));
220
- this[dataSourceSymbol].setOption("read", opt);
221
-
222
- let url = this.getOption("read.url");
223
- const formatter = new Formatter(this.getOption("read.parameters"));
224
-
225
- if (!url) {
226
- return Promise.reject(new Error("No url defined"));
227
- }
228
-
229
- url = formatter.format(url);
230
-
231
- this[dataSourceSymbol].setOption("read.url", url);
232
-
233
- return new Promise((resolve, reject) => {
234
- fireCustomEvent(this, "monster-datasource-fetch", {
235
- datasource: this,
236
- });
237
-
238
- queueMicrotask(() => {
239
- this[dataSourceSymbol]
240
- .read()
241
- .then((response) => {
242
- fireCustomEvent(this, "monster-datasource-fetched", {
243
- datasource: this,
244
- });
245
-
246
- resolve(response);
247
- })
248
- .catch((error) => {
249
- fireCustomEvent(this, "monster-datasource-error", {
250
- error: error,
251
- });
252
-
253
- addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, error.toString());
254
- reject(error);
255
- });
256
- });
257
- });
258
- }
259
-
260
- /**
261
- *
262
- * @return {CSSStyleSheet[]}
263
- */
264
- static getCSSStyleSheet() {
265
- return [DatasourceStyleSheet];
266
- }
267
-
268
- /**
269
- * @private
270
- * @return {string}
271
- */
272
- static getTag() {
273
- return "monster-datasource-rest";
274
- }
275
-
276
- /**
277
- * This method activates the intersection observer manually.
278
- * For this purpose, the option `autoInit.intersectionObserver` must be set to `false`.
279
- *
280
- * @return {Monster.Components.Datatable.Datasource.Rest}
281
- */
282
- initIntersectionObserver() {
283
- initIntersectionObserver.call(this);
284
- return this;
285
- }
286
-
287
- /**
288
- * @private
289
- */
290
- connectedCallback() {
291
- super.connectedCallback();
292
-
293
- queueMicrotask(() => {
294
- if (this.getOption("features.filter", false) === true) {
295
- initFilter.call(this);
296
- }
297
- });
298
- }
299
-
300
- /**
301
- * @private
302
- */
303
- disconnectedCallback() {
304
- super.disconnectedCallback();
305
- removeFilter.call(this);
306
- }
307
-
308
- /**
309
- * @return {Promise<never>|*}
310
- */
311
- read() {
312
- return this.fetch();
313
- }
314
-
315
- /**
316
- * Fetches the data from the rest api
317
- * @return {Promise<never>|*}
318
- */
319
- write() {
320
- const opt = clone(this.getOption("write"));
321
- this[dataSourceSymbol].setOption("write", opt);
322
-
323
- let url = this.getOption("write.url");
324
- const formatter = new Formatter(this.getOption("write.parameters"));
325
-
326
- if (!url) {
327
- return Promise.reject(new Error("No url defined"));
328
- }
329
-
330
- url = formatter.format(url);
331
-
332
- this[dataSourceSymbol].setOption("write.url", url);
333
-
334
- return new Promise((resolve, reject) => {
335
- fireCustomEvent(this, "monster-datasource-fetch", {
336
- datasource: this,
337
- });
338
-
339
- queueMicrotask(() => {
340
- this[dataSourceSymbol]
341
- .write()
342
- .then((response) => {
343
- fireCustomEvent(this, "monster-datasource-fetched", {
344
- datasource: this,
345
- });
346
-
347
- resolve(response);
348
- })
349
- .catch((error) => {
350
- fireCustomEvent(this, "monster-datasource-error", {
351
- error: error,
352
- });
353
-
354
- addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, error.toString());
355
- reject(error);
356
- });
357
- });
358
- });
359
- }
81
+ /**
82
+ * the constructor of the class
83
+ */
84
+ constructor() {
85
+ super();
86
+ this[dataSourceSymbol] = new RestAPI();
87
+ }
88
+
89
+ /**
90
+ * This method is called by the `instanceof` operator.
91
+ * @return {symbol}
92
+ */
93
+ static get [instanceSymbol]() {
94
+ return Symbol.for("@schukai/monster/components/datasource/rest@@instance");
95
+ }
96
+
97
+ /**
98
+ * To set the options via the HTML tag, the attribute `data-monster-options` must be used.
99
+ * @see {@link https://monsterjs.org/en/doc/#configurate-a-monster-control}
100
+ *
101
+ * The individual configuration values can be found in the table.
102
+ *
103
+ * @property {Object} templates Template definitions
104
+ * @property {string} templates.main Main template
105
+ * @property {Object} features Feature definitions
106
+ * @property {boolean} features.autoInit If true, the component is initialized automatically
107
+ * @property {boolean} features.filter If true, the component is initialized automatically
108
+ * @property {Object} autoInit Auto init definitions
109
+ * @property {boolean} autoInit.intersectionObserver If true, the intersection observer is initialized automatically
110
+ * @property {boolean} autoInit.oneTime If true, the intersection observer is initialized only once
111
+ * @property {Object} filter Filter definitions
112
+ * @property {string} filter.id The id of the filter control
113
+ * @property {Object} response Response definitions
114
+ * @property {Object} response.path Path definitions (changed in 3.56.0)
115
+ * @property {string} response.path.message Path to the message (changed in 3.56.0)
116
+ * @property {Object} read Read configuration
117
+ * @property {string} read.url The url of the rest api
118
+ * @property {string} read.method The method of the rest api
119
+ * @property {Object} read.parameters The parameters of the rest api
120
+ * @property {Object} read.parameters.filter The filter of the rest api
121
+ * @property {Object} read.parameters.orderBy The order by of the rest api
122
+ * @property {Object} read.parameters.page The page of the rest api
123
+ * @property {string} read.mapping.currentPage The current page
124
+ * @property {Object} write Write configuration
125
+ * @property {string} write.url The url of the rest api
126
+ * @property {string} write.method The method of the rest api
127
+ * @property {Object} write Write configuration
128
+ */
129
+ get defaults() {
130
+ const restOptions = new RestAPI().defaults;
131
+
132
+ restOptions.read.parameters = {
133
+ filter: null,
134
+ oderBy: null,
135
+ page: "1",
136
+ };
137
+
138
+ restOptions.read.mapping.currentPage = "sys.pagination.currentPage";
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: null,
157
+ },
158
+
159
+ /*datatable: {
160
+ id: undefined, // not used?
161
+ }, */
162
+
163
+ response: {
164
+ path: {
165
+ message: "sys.message",
166
+ code: "sys.code",
167
+ },
168
+ },
169
+ });
170
+ }
171
+
172
+ /**
173
+ * With this method, you can set the parameters for the rest api. The parameters are
174
+ * used for building the url.
175
+ *
176
+ * @param {string} page
177
+ * @param {string} query
178
+ * @param {string} orderBy
179
+ * @return {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
+ * @private
197
+ * @return {void}
198
+ */
199
+ [assembleMethodSymbol]() {
200
+ super[assembleMethodSymbol]();
201
+ initEventHandler.call(this);
202
+ initAutoInit.call(this);
203
+ }
204
+
205
+ /**
206
+ * This method reloads the data from the rest api, this method is deprecated.
207
+ * You should use the method `read` instead.
208
+ *
209
+ * @deprecated 2023-06-25
210
+ * @return {Promise<never>|*}
211
+ */
212
+ reload() {
213
+ return this.read();
214
+ }
215
+
216
+ /**
217
+ * Fetches the data from the rest api, this method is deprecated.
218
+ * You should use the method `read` instead.
219
+ *
220
+ * @deprecated 2024-12-24
221
+ * @return {Promise<never>|*}
222
+ */
223
+ fetch() {
224
+ return this.read();
225
+ }
226
+
227
+ /**
228
+ *
229
+ * @return {CSSStyleSheet[]}
230
+ */
231
+ static getCSSStyleSheet() {
232
+ return [DatasourceStyleSheet];
233
+ }
234
+
235
+ /**
236
+ * @private
237
+ * @return {string}
238
+ */
239
+ static getTag() {
240
+ return "monster-datasource-rest";
241
+ }
242
+
243
+ /**
244
+ * This method activates the intersection observer manually.
245
+ * For this purpose, the option `autoInit.intersectionObserver` must be set to `false`.
246
+ *
247
+ * @return {Rest}
248
+ */
249
+ initIntersectionObserver() {
250
+ initIntersectionObserver.call(this);
251
+ return this;
252
+ }
253
+
254
+ /**
255
+ * @private
256
+ */
257
+ connectedCallback() {
258
+ super.connectedCallback();
259
+
260
+ queueMicrotask(() => {
261
+ if (this.getOption("features.filter", false) === true) {
262
+ initFilter.call(this);
263
+ }
264
+ });
265
+ }
266
+
267
+ /**
268
+ * @private
269
+ */
270
+ disconnectedCallback() {
271
+ super.disconnectedCallback();
272
+ removeFilter.call(this);
273
+ }
274
+
275
+ /**
276
+ * This method reads the data from the rest api.
277
+ * The data is stored in the internal dataset object.
278
+ *
279
+ * @return {Promise}
280
+ * @fires monster-datasource-fetch
281
+ * @fires monster-datasource-fetched
282
+ * @fires monster-datasource-error
283
+ */
284
+ read() {
285
+ const opt = clone(this.getOption("read"));
286
+ this[dataSourceSymbol].setOption("read", opt);
287
+
288
+ let url = this.getOption("read.url");
289
+ const formatter = new Formatter(this.getOption("read.parameters"));
290
+
291
+ if (!url) {
292
+ return Promise.reject(new Error("No url defined"));
293
+ }
294
+
295
+ url = formatter.format(url);
296
+
297
+ this[dataSourceSymbol].setOption("read.url", url);
298
+
299
+ return new Promise((resolve, reject) => {
300
+ fireCustomEvent(this, "monster-datasource-fetch", {
301
+ datasource: this,
302
+ });
303
+
304
+ queueMicrotask(() => {
305
+ this[dataSourceSymbol]
306
+ .read()
307
+ .then((response) => {
308
+ fireCustomEvent(this, "monster-datasource-fetched", {
309
+ datasource: this,
310
+ });
311
+
312
+ resolve(response);
313
+ })
314
+ .catch((error) => {
315
+ fireCustomEvent(this, "monster-datasource-error", {
316
+ error: error,
317
+ });
318
+
319
+ addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, error.toString());
320
+ reject(error);
321
+ });
322
+ });
323
+ });
324
+ }
325
+
326
+ /**
327
+ * Fetches the data from the rest api.
328
+ * @return {Promise}
329
+ */
330
+ write() {
331
+ const opt = clone(this.getOption("write"));
332
+ this[dataSourceSymbol].setOption("write", opt);
333
+
334
+ let url = this.getOption("write.url");
335
+ const formatter = new Formatter(this.getOption("write.parameters"));
336
+
337
+ if (!url) {
338
+ return Promise.reject(new Error("No url defined"));
339
+ }
340
+
341
+ url = formatter.format(url);
342
+
343
+ this[dataSourceSymbol].setOption("write.url", url);
344
+
345
+ return new Promise((resolve, reject) => {
346
+ fireCustomEvent(this, "monster-datasource-fetch", {
347
+ datasource: this,
348
+ });
349
+
350
+ queueMicrotask(() => {
351
+ this[dataSourceSymbol]
352
+ .write()
353
+ .then((response) => {
354
+ fireCustomEvent(this, "monster-datasource-fetched", {
355
+ datasource: this,
356
+ });
357
+
358
+ resolve(response);
359
+ })
360
+ .catch((error) => {
361
+ fireCustomEvent(this, "monster-datasource-error", {
362
+ error: error,
363
+ });
364
+
365
+ addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, error.toString());
366
+ reject(error);
367
+ });
368
+ });
369
+ });
370
+ }
371
+
372
+ // /**
373
+ // * @return {int}
374
+ // */
375
+ // currentPage() {
376
+ //
377
+ // const key = this.getOption("read.mapping.currentPage")
378
+ // if (key === undefined) {
379
+ // return 1;
380
+ // }
381
+ //
382
+ // const pf = new Pathfinder(this.data);
383
+ // if (pf.exists(key)) {
384
+ // return parseInt(pf.getVia(key), 10);
385
+ // }
386
+ //
387
+ // return 1;
388
+ //
389
+ // }
390
+
360
391
  }
361
392
 
362
393
  /**
363
394
  * @private
364
395
  */
365
396
  function removeFilter() {
366
- const filterID = this.getOption("filter.id", undefined);
367
- if (!filterID) return;
397
+ const filterID = this.getOption("filter.id", undefined);
398
+ if (!filterID) return;
368
399
 
369
- const filterControl = findElementWithIdUpwards(this, filterID);
400
+ const filterControl = findElementWithIdUpwards(this, filterID);
370
401
 
371
- if (filterControl && this[filterObserverSymbol]) {
372
- filterControl?.detachObserver(this[filterObserverSymbol]);
373
- }
402
+ if (filterControl && this[filterObserverSymbol]) {
403
+ filterControl?.detachObserver(this[filterObserverSymbol]);
404
+ }
374
405
  }
375
406
 
376
407
  /**
377
408
  * @private
378
409
  */
379
410
  function initFilter() {
380
- const filterID = this.getOption("filter.id", undefined);
381
-
382
- if (!filterID)
383
- throw new Error("filter feature is enabled but no filter id is defined");
384
-
385
- const filterControl = findElementWithIdUpwards(this, filterID);
386
- if (!filterControl)
387
- throw new Error(
388
- "filter feature is enabled but no filter control with id " +
389
- filterID +
390
- " is found",
391
- );
392
-
393
- this[filterObserverSymbol] = new Observer(() => {
394
- const query = filterControl.getOption("query");
395
- if (query === undefined) {
396
- return;
397
- }
398
- this.setParameters({ query: query });
399
- this.fetch()
400
- .then((response) => {
401
- if (!(response instanceof Response)) {
402
- throw new Error("Response is not an instance of Response");
403
- }
404
-
405
- if (response?.ok === true) {
406
- this.dispatchEvent(new CustomEvent("reload", { bubbles: true }));
407
- filterControl?.showSuccess();
408
- }
409
-
410
- if (response.bodyUsed === true) {
411
- return handleIntersectionObserver.call(
412
- this,
413
- response[rawDataSymbol],
414
- response,
415
- filterControl,
416
- );
417
- }
418
-
419
- response
420
- .text()
421
- .then((jsonAsText) => {
422
- let json;
423
- try {
424
- json = JSON.parse(jsonAsText);
425
- } catch (e) {
426
- const message = e instanceof Error ? e.message : `${e}`;
427
- filterControl?.showFailureMessage(message);
428
- return Promise.reject(e);
429
- }
430
-
431
- return handleIntersectionObserver.call(
432
- this,
433
- json,
434
- response,
435
- filterControl,
436
- );
437
- })
438
- .catch((e) => {
439
- filterControl?.showFailureMessage(e.message);
440
- });
441
- })
442
- .catch((e) => {
443
- this.dispatchEvent(
444
- new CustomEvent("error", { bubbles: true, detail: e }),
445
- );
446
-
447
- if (!(e instanceof Error)) {
448
- e = new Error(e);
449
- }
450
-
451
- filterControl?.showFailureMessage(e.message);
452
- return Promise.reject(e);
453
- });
454
- });
455
-
456
- filterControl.attachObserver(this[filterObserverSymbol]);
411
+ const filterID = this.getOption("filter.id", undefined);
412
+
413
+ if (!filterID)
414
+ throw new Error("filter feature is enabled but no filter id is defined");
415
+
416
+ const filterControl = findElementWithIdUpwards(this, filterID);
417
+ if (!filterControl)
418
+ throw new Error(
419
+ "filter feature is enabled but no filter control with id " +
420
+ filterID +
421
+ " is found",
422
+ );
423
+
424
+ this[filterObserverSymbol] = new Observer(() => {
425
+ const query = filterControl.getOption("query");
426
+ if (query === undefined) {
427
+ return;
428
+ }
429
+ this.setParameters({query: query});
430
+ this.fetch()
431
+ .then((response) => {
432
+ if (!(response instanceof Response)) {
433
+ throw new Error("Response is not an instance of Response");
434
+ }
435
+
436
+ if (response?.ok === true) {
437
+ this.dispatchEvent(new CustomEvent("reload", {bubbles: true}));
438
+ filterControl?.showSuccess();
439
+ }
440
+
441
+ if (response.bodyUsed === true) {
442
+ return handleIntersectionObserver.call(
443
+ this,
444
+ response[rawDataSymbol],
445
+ response,
446
+ filterControl,
447
+ );
448
+ }
449
+
450
+ response
451
+ .text()
452
+ .then((jsonAsText) => {
453
+ let json;
454
+ try {
455
+ json = JSON.parse(jsonAsText);
456
+ } catch (e) {
457
+ const message = e instanceof Error ? e.message : `${e}`;
458
+ filterControl?.showFailureMessage(message);
459
+ return Promise.reject(e);
460
+ }
461
+
462
+ return handleIntersectionObserver.call(
463
+ this,
464
+ json,
465
+ response,
466
+ filterControl,
467
+ );
468
+ })
469
+ .catch((e) => {
470
+ filterControl?.showFailureMessage(e.message);
471
+ });
472
+ })
473
+ .catch((e) => {
474
+ this.dispatchEvent(
475
+ new CustomEvent("error", {bubbles: true, detail: e}),
476
+ );
477
+
478
+ if (!(e instanceof Error)) {
479
+ e = new Error(e);
480
+ }
481
+
482
+ filterControl?.showFailureMessage(e.message);
483
+ return Promise.reject(e);
484
+ });
485
+ });
486
+
487
+ filterControl.attachObserver(this[filterObserverSymbol]);
457
488
  }
458
489
 
459
490
  /**
@@ -464,88 +495,89 @@ function initFilter() {
464
495
  * @returns {Promise<never>|Promise<Awaited<unknown>>}
465
496
  */
466
497
  function handleIntersectionObserver(json, response, filterControl) {
467
- const path = new Pathfinder(json);
468
-
469
- const codePath = this.getOption("response.path.code");
470
-
471
- if (path.exists(codePath)) {
472
- const code = `${path.getVia(codePath)}`;
473
- if (code && code === "200") {
474
- filterControl?.showSuccess();
475
- return Promise.resolve(response);
476
- }
477
-
478
- const messagePath = this.getOption("response.path.message");
479
- if (path.exists(messagePath)) {
480
- const message = path.getVia(messagePath);
481
- filterControl?.showFailureMessage(message);
482
- return Promise.reject(new Error(message));
483
- }
484
-
485
- return Promise.reject(new Error("Response code is not 200"));
486
- }
498
+ const path = new Pathfinder(json);
499
+
500
+ const codePath = this.getOption("response.path.code");
501
+
502
+ if (path.exists(codePath)) {
503
+ const code = `${path.getVia(codePath)}`;
504
+ if (code && code === "200") {
505
+ filterControl?.showSuccess();
506
+ return Promise.resolve(response);
507
+ }
508
+
509
+ const messagePath = this.getOption("response.path.message");
510
+ if (path.exists(messagePath)) {
511
+ const message = path.getVia(messagePath);
512
+ filterControl?.showFailureMessage(message);
513
+ return Promise.reject(new Error(message));
514
+ }
515
+
516
+ return Promise.reject(new Error("Response code is not 200"));
517
+ }
487
518
  }
488
519
 
489
520
  /**
490
521
  * @private
491
522
  */
492
523
  function initAutoInit() {
493
- const autoInit = this.getOption("features.autoInit");
494
- validateBoolean(autoInit);
524
+ const autoInit = this.getOption("features.autoInit");
525
+ validateBoolean(autoInit);
495
526
 
496
- if (autoInit !== true) return;
527
+ if (autoInit !== true) return;
497
528
 
498
- if (this.getOption("autoInit.intersectionObserver") === true) {
499
- initIntersectionObserver.call(this);
500
- return;
501
- }
529
+ if (this.getOption("autoInit.intersectionObserver") === true) {
530
+ initIntersectionObserver.call(this);
531
+ return;
532
+ }
502
533
 
503
- queueMicrotask(() => {
504
- this.fetch().catch(() => {});
505
- });
534
+ queueMicrotask(() => {
535
+ this.fetch().catch(() => {
536
+ });
537
+ });
506
538
  }
507
539
 
508
540
  /**
509
541
  * @private
510
542
  */
511
543
  function initEventHandler() {
512
- this[intersectionObserverHandlerSymbol] = (entries) => {
513
- entries.forEach((entry) => {
514
- if (entry.isIntersecting) {
515
- if (entry.intersectionRatio > 0) {
516
- this.fetch();
517
- }
518
-
519
- // only load once
520
- if (
521
- this.getOption("autoInit.oneTime") === true &&
522
- this[intersectionObserverObserverSymbol] !== undefined
523
- ) {
524
- this[intersectionObserverObserverSymbol].unobserve(this);
525
- }
526
- }
527
- });
528
- };
544
+ this[intersectionObserverHandlerSymbol] = (entries) => {
545
+ entries.forEach((entry) => {
546
+ if (entry.isIntersecting) {
547
+ if (entry.intersectionRatio > 0) {
548
+ this.fetch();
549
+ }
550
+
551
+ // only load once
552
+ if (
553
+ this.getOption("autoInit.oneTime") === true &&
554
+ this[intersectionObserverObserverSymbol] !== undefined
555
+ ) {
556
+ this[intersectionObserverObserverSymbol].unobserve(this);
557
+ }
558
+ }
559
+ });
560
+ };
529
561
  }
530
562
 
531
563
  /**
532
564
  * @private
533
565
  */
534
566
  function initIntersectionObserver() {
535
- this.classList.add("intersection-observer");
567
+ this.classList.add("intersection-observer");
536
568
 
537
- const options = {
538
- root: null,
539
- rootMargin: "0px",
540
- threshold: 0.1,
541
- };
569
+ const options = {
570
+ root: null,
571
+ rootMargin: "0px",
572
+ threshold: 0.1,
573
+ };
542
574
 
543
- this[intersectionObserverObserverSymbol] = new IntersectionObserver(
544
- this[intersectionObserverHandlerSymbol],
545
- options,
546
- );
575
+ this[intersectionObserverObserverSymbol] = new IntersectionObserver(
576
+ this[intersectionObserverHandlerSymbol],
577
+ options,
578
+ );
547
579
 
548
- this[intersectionObserverObserverSymbol].observe(this);
580
+ this[intersectionObserverObserverSymbol].observe(this);
549
581
  }
550
582
 
551
583
  /**
@@ -553,8 +585,8 @@ function initIntersectionObserver() {
553
585
  * @return {string}
554
586
  */
555
587
  function getTemplate() {
556
- // language=HTML
557
- return `
588
+ // language=HTML
589
+ return `
558
590
  <slot></slot>`;
559
591
  }
560
592