@schukai/monster 3.69.2 → 3.70.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.
Files changed (41) hide show
  1. package/CHANGELOG.md +20 -4
  2. package/package.json +1 -1
  3. package/source/components/datatable/dataset.mjs +278 -202
  4. package/source/components/datatable/datasource/dom.mjs +1 -1
  5. package/source/components/datatable/datasource/rest.mjs +2 -2
  6. package/source/components/datatable/filter.mjs +1 -0
  7. package/source/components/datatable/save-button.mjs +1 -1
  8. package/source/components/datatable/style/datatable.pcss +1 -1
  9. package/source/components/datatable/style/filter-controls-defaults.pcss +1 -1
  10. package/source/components/datatable/stylesheet/filter-controls-defaults.mjs +8 -17
  11. package/source/components/form/context-error.mjs +2 -0
  12. package/source/components/form/context-help.mjs +1 -0
  13. package/source/components/form/field-set.mjs +222 -225
  14. package/source/components/form/form.mjs +192 -545
  15. package/source/components/form/select.mjs +24 -9
  16. package/source/components/form/style/field-set.pcss +84 -7
  17. package/source/components/form/style/form.pcss +5 -3
  18. package/source/components/form/style/select.pcss +5 -4
  19. package/source/components/form/stylesheet/field-set.mjs +7 -14
  20. package/source/components/form/stylesheet/form.mjs +8 -17
  21. package/source/components/form/stylesheet/select.mjs +7 -14
  22. package/source/components/layout/style/collapse.pcss +0 -2
  23. package/source/components/layout/stylesheet/collapse.mjs +7 -14
  24. package/source/components/style/form.pcss +66 -3
  25. package/source/components/style/mixin/property.pcss +8 -1
  26. package/source/components/style/typography.pcss +4 -12
  27. package/source/components/stylesheet/form.mjs +8 -17
  28. package/source/components/stylesheet/mixin/form.mjs +7 -16
  29. package/source/components/stylesheet/mixin/property.mjs +6 -13
  30. package/source/components/stylesheet/typography.mjs +7 -16
  31. package/source/data/datasource/server/restapi.mjs +182 -180
  32. package/source/dom/customelement.mjs +4 -0
  33. package/source/dom/updater.mjs +1 -1
  34. package/source/types/tokenlist.mjs +2 -2
  35. package/test/cases/components/form/form.mjs +1 -182
  36. package/test/cases/components/host/details.mjs +1 -1
  37. package/test/cases/components/host/host.mjs +1 -1
  38. package/test/cases/components/host/overlay.mjs +1 -1
  39. package/test/cases/dom/customcontrol.mjs +1 -1
  40. package/test/cases/dom/customelement.mjs +2 -2
  41. package/source/components/style/mixin/form.pcss +0 -242
@@ -12,577 +12,224 @@
12
12
  * SPDX-License-Identifier: AGPL-3.0
13
13
  */
14
14
 
15
- import { instanceSymbol } from "../../constants.mjs";
16
- import { internalSymbol } from "../../constants.mjs";
17
- import { Datasource } from "../../data/datasource.mjs";
18
- import { RestAPI } from "../../data/datasource/server/restapi.mjs";
19
- import { WebConnect } from "../../data/datasource/server/webconnect.mjs";
20
- import { WriteError } from "../../data/datasource/server/restapi/writeerror.mjs";
21
- import { LocalStorage } from "../../data/datasource/storage/localstorage.mjs";
22
- import { SessionStorage } from "../../data/datasource/storage/sessionstorage.mjs";
15
+ import {instanceSymbol} from "../../constants.mjs";
16
+ import {internalSymbol} from "../../constants.mjs";
17
+ import {TokenList} from "../../types/tokenlist.mjs";
18
+ import {DeadMansSwitch} from "../../util/deadmansswitch.mjs";
19
+ import {DataSet} from "../datatable/dataset.mjs";
20
+ //import { Datasource } from "../../data/datasource.mjs";
21
+ import {RestAPI} from "../../data/datasource/server/restapi.mjs";
22
+ import {WebConnect} from "../../data/datasource/server/webconnect.mjs";
23
+ import {WriteError} from "../../data/datasource/server/restapi/writeerror.mjs";
24
+ import {LocalStorage} from "../../data/datasource/storage/localstorage.mjs";
25
+ import {SessionStorage} from "../../data/datasource/storage/sessionstorage.mjs";
23
26
  import {
24
- ATTRIBUTE_DISABLED,
25
- ATTRIBUTE_ERRORMESSAGE,
26
- ATTRIBUTE_PREFIX,
27
- ATTRIBUTE_UPDATER_ATTRIBUTES,
28
- ATTRIBUTE_UPDATER_INSERT,
29
- ATTRIBUTE_UPDATER_REMOVE,
30
- ATTRIBUTE_UPDATER_REPLACE,
27
+ ATTRIBUTE_DISABLED,
28
+ ATTRIBUTE_ERRORMESSAGE,
29
+ ATTRIBUTE_PREFIX,
30
+ ATTRIBUTE_UPDATER_ATTRIBUTES,
31
+ ATTRIBUTE_UPDATER_INSERT,
32
+ ATTRIBUTE_UPDATER_REMOVE,
33
+ ATTRIBUTE_UPDATER_REPLACE,
31
34
  } from "../../dom/constants.mjs";
32
35
  import {
33
- assembleMethodSymbol,
34
- CustomElement,
35
- registerCustomElement,
36
- getSlottedElements,
36
+ assembleMethodSymbol,
37
+ CustomElement,
38
+ registerCustomElement,
39
+ getSlottedElements,
37
40
  } from "../../dom/customelement.mjs";
38
- import { addObjectWithUpdaterToElement } from "../../dom/updater.mjs";
39
- import { isFunction, isString } from "../../types/is.mjs";
40
- import { Observer } from "../../types/observer.mjs";
41
- import { ProxyObserver } from "../../types/proxyobserver.mjs";
42
- import { Processing } from "../../util/processing.mjs";
43
- import { MessageStateButton } from "./message-state-button.mjs";
41
+ import {addObjectWithUpdaterToElement} from "../../dom/updater.mjs";
42
+ import {findElementWithSelectorUpwards} from "../../dom/util.mjs";
43
+ import {isFunction, isString} from "../../types/is.mjs";
44
+ import {Observer} from "../../types/observer.mjs";
45
+ import {ProxyObserver} from "../../types/proxyobserver.mjs";
46
+ import {Processing} from "../../util/processing.mjs";
47
+ import {datasourceLinkedElementSymbol, handleDataSourceChanges} from "../datatable/util.mjs";
48
+ import {MessageStateButton} from "./message-state-button.mjs";
44
49
  import {
45
- ATTRIBUTE_FORM_DATASOURCE,
46
- ATTRIBUTE_FORM_DATASOURCE_ARGUMENTS,
50
+ ATTRIBUTE_FORM_DATASOURCE,
51
+ ATTRIBUTE_FORM_DATASOURCE_ARGUMENTS,
47
52
  } from "./constants.mjs";
48
- import { StateButton } from "./state-button.mjs";
49
- import { FormStyleSheet } from "./stylesheet/form.mjs";
53
+ import {StateButton} from "./state-button.mjs";
54
+ import {FormStyleSheet} from "./stylesheet/form.mjs";
50
55
 
51
- export { Form };
56
+ export {Form};
52
57
 
53
- /**
54
- * @private
55
- * @since 3.1.0
56
- * @type {string}
57
- */
58
- const ATTRIBUTE_FORM_DATASOURCE_ACTION = `${ATTRIBUTE_PREFIX}datasource-action`;
59
-
60
- /**
61
- * Form data is the internal representation of the form data
62
- *
63
- * @private
64
- * @type {symbol}
65
- * @since 1.7.0
66
- */
67
- const formDataSymbol = Symbol.for(
68
- "@schukai/monster/components/form/form@@formdata",
69
- );
70
-
71
- /**
72
- * @private
73
- * @type {symbol}
74
- * @since 2.8.0
75
- */
76
- const formDataUpdaterSymbol = Symbol.for(
77
- "@schukai/component-form/form@@formdata-updater-link",
78
- );
79
58
 
80
59
  /**
81
60
  * @private
82
61
  * @type {symbol}
83
- * @since 1.7.0
84
- */
85
- const formElementSymbol = Symbol.for(
86
- "@schukai/component-form/form@@form-element",
87
- );
88
-
89
- /**
90
- * @private
91
- * @type {symbol}
92
- * @since 2.5.0
93
- */
94
- const registeredDatasourcesSymbol = Symbol.for(
95
- "@schukai/component-form/form@@registered-datasources",
96
- );
97
-
98
- /**
99
- * @private
100
- * @since 1.7.0
101
- * @type {string}
102
- */
103
- const PROPERTY_VALIDATION_KEY = "__validation";
104
-
105
- /**
106
- * This CustomControl creates a form element with a variety of options.
107
- *
108
- * <img src="./images/form.png">
109
- *
110
- * Dependencies: the system uses functions of the [monsterjs](https://monsterjs.org/) library.
111
- *
112
- * You can create this control either by specifying the HTML tag `<monster-form />` directly in the HTML or using
113
- * Javascript via the `document.createElement('monster-form');` method.
114
- *
115
- * ```html
116
- * <monster-form></monster-form>
117
- * ```
118
- *
119
- * Or you can create this CustomControl directly in Javascript:
120
- *
121
- * ```js
122
- * import {Form} from '@schukai/component-form/source/form.js';
123
- * document.createElement('monster-form');
124
- * ```
125
- *
126
- * @startuml form.png
127
- * skinparam monochrome true
128
- * skinparam shadowing false
129
- * HTMLElement <|-- CustomElement
130
- * CustomElement <|-- Form
131
- * @enduml
132
- *
133
- * @since 1.6.0
134
- * @copyright schukai GmbH
135
- * @memberOf Monster.Components.Form
136
- * @summary A configurable form control
137
62
  */
138
- class Form extends CustomElement {
139
- /**
140
- * @throws {Error} the options attribute does not contain a valid json definition.
141
- * @since 1.7.0
142
- */
143
- constructor() {
144
- super();
145
- this[formDataSymbol] = new ProxyObserver({});
146
- }
147
-
148
- /**
149
- * This method is called by the `instanceof` operator.
150
- * @returns {symbol}
151
- * @since 2.1.0
152
- */
153
- static get [instanceSymbol]() {
154
- return Symbol.for("@schukai/monster/components/form/form");
155
- }
156
-
157
- /**
158
- * To set the options via the html tag the attribute `data-monster-options` must be used.
159
- * @see {@link https://monsterjs.org/en/doc/#configurate-a-monster-control}
160
- *
161
- * The individual configuration values can be found in the table.
162
- *
163
- * @property {Object} templates Template definitions
164
- * @property {string} templates.main Main template
165
- * @property {Datasource} datasource data source
166
- * @property {Object} reportValidity
167
- * @property {string} reportValidity.selector which element should be used to report the validity
168
- * @property {function} reportValidity.errorHandler function to handle the error
169
- * @property {Object} classes
170
- * @property {string} classes.button class for the form
171
- */
172
- get defaults() {
173
- return Object.assign(
174
- {},
175
- super.defaults,
176
- {
177
- templates: {
178
- main: getTemplate(),
179
- },
180
- datasource: undefined,
181
- reportValidity: {
182
- selector: "input,select,textarea",
183
- errorHandler: undefined,
184
- },
185
- classes: {
186
- form: "monster-form",
187
- },
188
- },
189
- initOptionsFromArguments.call(this),
190
- );
191
- }
192
-
193
- /**
194
- * Called every time the element is inserted into the DOM. Useful for running setup code, such as
195
- * fetching resources or rendering. Generally, you should try to delay work until this time.
196
- *
197
- * @return {void}
198
- */
199
- connectedCallback() {
200
- super["connectedCallback"]();
201
- }
202
-
203
- /**
204
- * The refresh method is called to update the control after a change with fresh data.
205
- *
206
- * Therefore, the data source is called again and the data is updated.
207
- *
208
- * If you have updated the data source with `setOption('datasource',datasource), you must call this method.
209
- *
210
- * @return {Form}
211
- * @throws {Error} undefined datasource
212
- */
213
- refresh() {
214
- try {
215
- this.setAttribute(ATTRIBUTE_DISABLED, "");
216
- const datasource = this.getOption("datasource");
217
-
218
- if (!(datasource instanceof Datasource)) {
219
- throw new Error("undefined datasource");
220
- }
221
-
222
- return datasource
223
- .read()
224
- .then(() => {
225
- this[formDataSymbol].setSubject(datasource.get());
226
- })
227
- .then(() => {
228
- new Processing(() => {
229
- this.removeAttribute(ATTRIBUTE_DISABLED);
230
- }).run();
231
- })
232
- .catch((e) => {
233
- this.removeAttribute(ATTRIBUTE_DISABLED);
234
- this.setAttribute(ATTRIBUTE_ERRORMESSAGE, e.toString());
235
- });
236
- } catch (e) {
237
- this.setAttribute(ATTRIBUTE_ERRORMESSAGE, e.toString());
238
- this.removeAttribute(ATTRIBUTE_DISABLED);
239
- throw e;
240
- }
241
- }
242
-
243
- /**
244
- *
245
- * @return {Monster.Components.Form.Form}
246
- */
247
- [assembleMethodSymbol]() {
248
- super[assembleMethodSymbol]();
249
-
250
- initControlReferences.call(this);
251
- initDatasource.call(this);
252
- initUpdater.call(this);
253
- initObserver.call(this);
254
-
255
- return this;
256
- }
257
-
258
- /**
259
- *
260
- * @return {*}
261
- */
262
- getValues() {
263
- return this[formDataSymbol].getSubject();
264
- }
265
-
266
- /**
267
- *
268
- * @return {string}
269
- */
270
- static getTag() {
271
- return "monster-form";
272
- }
273
-
274
- /**
275
- *
276
- * @return {CSSStyleSheet}
277
- */
278
- static getCSSStyleSheet() {
279
- return [FormStyleSheet];
280
- }
281
-
282
- static [registeredDatasourcesSymbol] = new Map([
283
- ["restapi", RestAPI],
284
- ["localstorage", LocalStorage],
285
- ["sessionstorage", SessionStorage],
286
- ["webconnect", WebConnect],
287
- ]);
288
-
289
- /**
290
- * Register a new datasource
291
- *
292
- * @param {string} name
293
- * @param {Monster.Data.Datasource} datasource
294
- */
295
- static registerDatasource(name, datasource) {
296
- Form[registeredDatasourcesSymbol].set(name, datasource);
297
- }
298
-
299
- /**
300
- * Unregister a registered datasource
301
- *
302
- * @param {string} name
303
- */
304
- static unregisterDatasource(name) {
305
- Form[registeredDatasourcesSymbol].delete(name);
306
- }
307
-
308
- /**
309
- * Get registered data sources
310
- *
311
- * @return {Map}
312
- */
313
- static getDatasources() {
314
- return Form[registeredDatasourcesSymbol];
315
- }
316
-
317
- /**
318
- * Run reportValidation on all child html form controls.
319
- *
320
- * @since 2.10.0
321
- * @returns {boolean}
322
- */
323
- reportValidity() {
324
- let valid = true;
325
-
326
- const selector = this.getOption("reportValidity.selector");
327
- const nodes = getSlottedElements.call(this, selector);
328
- nodes.forEach((node) => {
329
- if (typeof node.reportValidity === "function") {
330
- if (node.reportValidity() === false) {
331
- valid = false;
332
- }
333
- }
334
- });
335
-
336
- return valid;
337
- }
338
- }
63
+ const debounceCallbackSymbol = Symbol("timerCallback");
64
+
65
+ class Form extends DataSet {
66
+
67
+ /**
68
+ *
69
+ * @returns {{shadowMode: string, templates: {main: *}, display: string, disabled: boolean, delegatesFocus: boolean, templateMapping: {}} & {templates: {main: string}, classes: {form: string}}}
70
+ */
71
+ get defaults() {
72
+ const obj = Object.assign(
73
+ {},
74
+ super.defaults,
75
+ {
76
+ templates: {
77
+ main: getTemplate(),
78
+ },
79
+
80
+ classes: {
81
+ form: "",
82
+ },
83
+
84
+ writeBack: {
85
+ events: ["change", "input", "keyup"]
86
+ },
87
+
88
+ reportValidity: {
89
+ selector: "input,select,textarea",
90
+ }
91
+
92
+ }
93
+ );
94
+
95
+ obj['features']['mutationObserver'] = false;
96
+ obj['features']['writeBack'] = true;
97
+
98
+ return obj;
99
+
100
+ }
101
+
102
+
103
+ /**
104
+ *
105
+ * @return {string}
106
+ */
107
+ static getTag() {
108
+ return "monster-form";
109
+ }
110
+
111
+ /**
112
+ * @return {CSSStyleSheet[]}
113
+ */
114
+ static getCSSStyleSheet() {
115
+ return [FormStyleSheet];
116
+ }
117
+
118
+ /**
119
+ *
120
+ */
121
+ [assembleMethodSymbol]() {
122
+ super[assembleMethodSymbol]();
123
+
124
+ initControlReferences.call(this);
125
+ initEventHandler.call(this);
126
+ initDataSourceHandler.call(this);
127
+
128
+ }
129
+
130
+ /**
131
+ * This method is called when the component is created.
132
+ * @since 3.70.0
133
+ * @returns {DataSet}
134
+ */
135
+ refresh() {
136
+ this.write();
137
+ super.refresh();
138
+ return this;
139
+ }
140
+
141
+ /**
142
+ * Run reportValidation on all child html form controls.
143
+ *
144
+ * @since 2.10.0
145
+ * @returns {boolean}
146
+ */
147
+ reportValidity() {
148
+ let valid = true;
149
+
150
+ const selector = this.getOption("reportValidity.selector");
151
+ const nodes = getSlottedElements.call(this, selector);
152
+
153
+ nodes.forEach((node) => {
154
+ if (typeof node.reportValidity === "function") {
155
+ if (node.reportValidity() === false) {
156
+ valid = false;
157
+ }
158
+ }
159
+ });
160
+
161
+ return valid;
162
+ }
339
163
 
340
- /**
341
- * @private
342
- */
343
- function initUpdater() {
344
- if (!this.shadowRoot) {
345
- throw new Error("no shadow-root is defined");
346
- }
347
-
348
- const slots = this.shadowRoot.querySelectorAll("slot");
349
- for (const [, slot] of Object.entries(slots)) {
350
- for (const [, node] of Object.entries(slot.assignedNodes())) {
351
- if (!(node instanceof HTMLElement)) {
352
- continue;
353
- }
354
-
355
- const query = `[${ATTRIBUTE_UPDATER_ATTRIBUTES}],[${ATTRIBUTE_UPDATER_REPLACE}],[${ATTRIBUTE_UPDATER_REMOVE}],[${ATTRIBUTE_UPDATER_INSERT}]`;
356
- const controls = node.querySelectorAll(query);
357
-
358
- const list = new Set([...controls]);
359
-
360
- if (node.matches(query)) {
361
- list.add(node);
362
- }
363
-
364
- if (list.size === 0) {
365
- continue;
366
- }
367
-
368
- addObjectWithUpdaterToElement.call(
369
- node,
370
- list,
371
- formDataUpdaterSymbol,
372
- this[formDataSymbol],
373
- );
374
- }
375
- }
376
164
  }
377
165
 
378
- /**
379
- * @private
380
- */
381
- function initDatasource() {
382
- if (!this.shadowRoot) {
383
- throw new Error("no shadow-root is defined");
384
- }
385
-
386
- const slots = this.shadowRoot.querySelectorAll("slot");
387
- for (const [, slot] of Object.entries(slots)) {
388
- for (const [, node] of Object.entries(slot.assignedNodes())) {
389
- if (!(node instanceof HTMLElement)) {
390
- continue;
391
- }
392
-
393
- const query = `[${ATTRIBUTE_FORM_DATASOURCE_ACTION}=write]`;
394
- const controls = node.querySelectorAll(query);
395
-
396
- const list = new Set([...controls]);
397
-
398
- if (node.matches(query)) {
399
- list.add(node);
400
- }
401
-
402
- if (list.size === 0) {
403
- continue;
404
- }
405
-
406
- initWriteActions.call(this, list);
407
- }
408
- }
409
- }
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
+ })
410
174
 
411
- /**
412
- * @private
413
- * @param elements
414
- */
415
- function initWriteActions(elements) {
416
- elements.forEach((element) => {
417
- if (element instanceof HTMLElement) {
418
- element.addEventListener("click", () => {
419
- runWriteCallback.call(this, element);
420
- });
421
-
422
- const g = element?.getOption;
423
- if (!isFunction(g)) {
424
- return;
425
- }
426
-
427
- const s = element?.setOption;
428
- if (!isFunction(s)) {
429
- return;
430
- }
431
-
432
- const fn = element.getOption("actions.click");
433
-
434
- if (!isFunction(fn)) {
435
- return;
436
- }
437
-
438
- // disable console.log of standard click event
439
- element.setOption("actions.click", function () {
440
- // do nothing!
441
- });
442
- }
443
- });
444
- }
445
-
446
- function runWriteCallback(button) {
447
- if (typeof this.reportValidity === "function") {
448
- if (this.reportValidity() === false) {
449
- if (
450
- button instanceof StateButton ||
451
- button instanceof MessageStateButton
452
- ) {
453
- button.setState("failed");
454
- }
455
- return;
456
- }
457
- }
458
-
459
- const datasource = this.getOption("datasource");
460
- if (!(datasource instanceof Datasource)) {
461
- return;
462
- }
463
-
464
- if (button instanceof StateButton || button instanceof MessageStateButton) {
465
- button.setState("activity");
466
- }
467
-
468
- //const data = form?.[formDataSymbol]?.getRealSubject();
469
- const writePromise = datasource
470
- .set(this[formDataSymbol].getRealSubject())
471
- .write();
472
- if (!(writePromise instanceof Promise)) {
473
- throw new Error("datasource.write() must return a promise");
474
- }
475
-
476
- writePromise
477
- .then((r) => {
478
- if (
479
- button instanceof StateButton ||
480
- button instanceof MessageStateButton
481
- ) {
482
- button.setState("successful");
483
- }
484
- this[formDataSymbol].getSubject()[PROPERTY_VALIDATION_KEY] = {};
485
- })
486
- .catch((e) => {
487
- if (e instanceof WriteError) {
488
- this[formDataSymbol].getSubject()[PROPERTY_VALIDATION_KEY] =
489
- e.getValidation();
490
- }
491
-
492
- if (
493
- button instanceof StateButton ||
494
- button instanceof MessageStateButton
495
- ) {
496
- button.setState("failed");
497
- }
498
-
499
- if (button instanceof MessageStateButton) {
500
- button.setMessage(e.message);
501
- button.showMessage();
502
- }
503
- });
504
- }
505
-
506
- /**
507
- * This attribute can be used to pass a URL to this select.
508
- *
509
- * ```
510
- * <monster-form data-monster-datasource="restapi:....."></monster-form>
511
- * ```
512
- *
513
- * @private
514
- * @return {object}
515
- */
516
- function initOptionsFromArguments() {
517
- const options = {};
518
-
519
- const datasource = this.getAttribute(ATTRIBUTE_FORM_DATASOURCE);
520
- if (isString(datasource)) {
521
- for (const [key, classObject] of Form.getDatasources()) {
522
- if (datasource === key) {
523
- let args = this.getAttribute(ATTRIBUTE_FORM_DATASOURCE_ARGUMENTS);
524
-
525
- try {
526
- args = JSON.parse(args);
527
- } catch (e) {
528
- this.setAttribute(ATTRIBUTE_ERRORMESSAGE, e.toString());
529
- continue;
530
- }
531
-
532
- try {
533
- options["datasource"] = new classObject(args);
534
- } catch (e) {
535
- this.setAttribute(ATTRIBUTE_ERRORMESSAGE, e.toString());
536
- continue;
537
- }
538
-
539
- break;
540
- }
541
-
542
- if (options["datasource"] instanceof Datasource) {
543
- break;
544
- }
545
- }
546
- }
547
-
548
- return options;
549
175
  }
550
176
 
551
177
  /**
552
178
  * @private
553
- * @this Form
179
+ * @returns {initEventHandler}
554
180
  */
555
- function initObserver() {
556
- const self = this;
557
-
558
- let lastDatasource = null;
559
- self[internalSymbol].attachObserver(
560
- new Observer(function () {
561
- const datasource = self.getOption("datasource");
562
- if (datasource !== lastDatasource) {
563
- new Processing(100, function () {
564
- self.refresh();
565
- }).run();
566
- }
567
-
568
- lastDatasource = datasource;
569
- }),
570
- );
181
+ function initEventHandler() {
182
+
183
+ if (this.getOption("features.writeBack") === true) {
184
+ const events = this.getOption("writeBack.events");
185
+ for (const event of events) {
186
+
187
+ this.addEventListener(event, (e) => {
188
+
189
+ if (!this.reportValidity()) {
190
+
191
+ this.classList.add("invalid");
192
+ setTimeout(() => {
193
+ this.classList.remove("invalid");
194
+ }, 1000)
195
+
196
+ return;
197
+ }
198
+
199
+ if (this[debounceCallbackSymbol] instanceof DeadMansSwitch) {
200
+ try {
201
+ this[debounceCallbackSymbol].touch();
202
+ return;
203
+ } catch (e) {
204
+ if (e.message !== "has already run") {
205
+ throw e;
206
+ }
207
+ delete this[debounceCallbackSymbol];
208
+ }
209
+ }
210
+
211
+ this[debounceCallbackSymbol] = new DeadMansSwitch(200, () => {
212
+ setTimeout(() => {
213
+ this.write();
214
+ }, 0);
215
+ });
216
+
217
+ });
218
+ }
219
+ }
220
+
221
+ return this;
571
222
  }
572
223
 
573
224
  /**
574
225
  * @private
575
- * @return {Monster.Components.Form.Form}
226
+ * @return {FilterButton}
576
227
  */
577
228
  function initControlReferences() {
578
- if (!this.shadowRoot) {
579
- throw new Error("no shadow-root is defined");
580
- }
581
-
582
- this[formElementSymbol] = this.shadowRoot.querySelector(
583
- "[data-monster-role=form]",
584
- );
585
- return this;
229
+ if (!this.shadowRoot) {
230
+ throw new Error("no shadow-root is defined");
231
+ }
232
+ return this;
586
233
  }
587
234
 
588
235
  /**
@@ -590,8 +237,8 @@ function initControlReferences() {
590
237
  * @return {string}
591
238
  */
592
239
  function getTemplate() {
593
- // language=HTML
594
- return `
240
+ // language=HTML
241
+ return `
595
242
  <div data-monster-role="control" part="control">
596
243
  <form data-monster-attributes="disabled path:disabled | if:true, class path:classes.form"
597
244
  data-monster-role="form"