@schukai/monster 3.53.0 → 3.55.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 (72) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/package.json +1 -1
  3. package/source/components/datatable/datasource/rest.mjs +358 -309
  4. package/source/components/datatable/datatable/header.mjs +8 -0
  5. package/source/components/datatable/datatable.mjs +606 -557
  6. package/source/components/datatable/embedded-pagination.mjs +50 -62
  7. package/source/components/datatable/filter/util.mjs +122 -0
  8. package/source/components/datatable/filter.mjs +893 -708
  9. package/source/components/datatable/pagination.mjs +335 -310
  10. package/source/components/datatable/status.mjs +248 -0
  11. package/source/components/datatable/style/datatable.pcss +1 -0
  12. package/source/components/datatable/style/embedded-pagination.pcss +59 -2
  13. package/source/components/datatable/style/filter.pcss +4 -0
  14. package/source/components/datatable/style/pagination.pcss +28 -4
  15. package/source/components/datatable/style/status.pcss +42 -0
  16. package/source/components/datatable/stylesheet/column-bar.mjs +1 -1
  17. package/source/components/datatable/stylesheet/datatable.mjs +1 -1
  18. package/source/components/datatable/stylesheet/filter-button.mjs +1 -1
  19. package/source/components/datatable/stylesheet/filter.mjs +1 -1
  20. package/source/components/datatable/stylesheet/pagination.mjs +1 -1
  21. package/source/components/datatable/stylesheet/status.mjs +27 -0
  22. package/source/components/form/action-button.mjs +1 -1
  23. package/source/components/form/api-button.mjs +1 -1
  24. package/source/components/form/button-bar.mjs +1 -1
  25. package/source/components/form/button.mjs +1 -1
  26. package/source/components/form/confirm-button.mjs +1 -1
  27. package/source/components/form/context-error.mjs +275 -0
  28. package/source/components/form/context-help.mjs +5 -5
  29. package/source/components/form/form.mjs +2 -2
  30. package/source/components/form/message-state-button.mjs +2 -2
  31. package/source/components/form/popper-button.mjs +7 -4
  32. package/source/components/form/popper.mjs +317 -309
  33. package/source/components/form/reload.mjs +1 -1
  34. package/source/components/form/select.mjs +9 -3
  35. package/source/components/form/shadow-reload.mjs +1 -1
  36. package/source/components/form/state-button.mjs +2 -1
  37. package/source/components/form/style/context-error.pcss +32 -0
  38. package/source/components/form/style/context-help.pcss +22 -5
  39. package/source/components/form/stylesheet/context-error.mjs +27 -0
  40. package/source/components/form/stylesheet/context-help.mjs +1 -1
  41. package/source/components/form/stylesheet/select.mjs +1 -1
  42. package/source/components/form/stylesheet/tabs.mjs +1 -1
  43. package/source/components/form/tabs.mjs +757 -707
  44. package/source/components/form/template.mjs +1 -1
  45. package/source/components/form/tree-select.mjs +1 -1
  46. package/source/components/host/collapse.mjs +22 -5
  47. package/source/components/host/config-manager.mjs +39 -2
  48. package/source/components/host/host.mjs +14 -0
  49. package/source/components/host/stylesheet/call-button.mjs +1 -1
  50. package/source/components/host/stylesheet/overlay.mjs +1 -1
  51. package/source/components/host/stylesheet/toggle-button.mjs +1 -1
  52. package/source/components/host/util.mjs +6 -1
  53. package/source/components/notify/stylesheet/message.mjs +1 -1
  54. package/source/components/stylesheet/icons.mjs +1 -1
  55. package/source/data/transformer.mjs +39 -42
  56. package/source/dom/customelement.mjs +1 -1
  57. package/source/dom/updater.mjs +700 -688
  58. package/source/dom/util.mjs +42 -0
  59. package/source/i18n/providers/embed.mjs +3 -3
  60. package/source/monster.mjs +6 -0
  61. package/source/text/formatter.mjs +2 -2
  62. package/source/types/observer.mjs +1 -1
  63. package/source/types/version.mjs +1 -1
  64. package/source/util/sleep.mjs +18 -0
  65. package/test/cases/components/form/button.mjs +2 -1
  66. package/test/cases/components/form/select.mjs +1 -1
  67. package/test/cases/components/form/tree-select.mjs +1 -1
  68. package/test/cases/data/transformer.mjs +2 -2
  69. package/test/cases/dom/updater.mjs +67 -46
  70. package/test/cases/monster.mjs +1 -1
  71. package/test/web/test.html +2 -2
  72. package/test/web/tests.js +18 -13
@@ -3,35 +3,41 @@
3
3
  * SPDX-License-Identifier: AGPL-3.0
4
4
  */
5
5
 
6
- import { instanceSymbol } from "../../constants.mjs";
7
- import { findElementWithIdUpwards } from "../../dom/util.mjs";
6
+ import {instanceSymbol} from "../../constants.mjs";
7
+ import {findTargetElementFromEvent} from "../../dom/events.mjs";
8
+ import {findElementWithIdUpwards, findElementWithSelectorUpwards} from "../../dom/util.mjs";
8
9
  import {
9
- assembleMethodSymbol,
10
- CustomElement,
11
- getSlottedElements,
12
- registerCustomElement,
10
+ assembleMethodSymbol,
11
+ CustomElement,
12
+ getSlottedElements,
13
+ registerCustomElement,
13
14
  } from "../../dom/customelement.mjs";
14
- import { ID } from "../../types/id.mjs";
15
- import { Settings } from "./filter/settings.mjs";
16
- import { FilterStyleSheet } from "./stylesheet/filter.mjs";
17
- import { getDocument, getWindow } from "../../dom/util.mjs";
18
- import { getGlobal } from "../../types/global.mjs";
19
- import { isInstance, isFunction, isObject, isArray } from "../../types/is.mjs";
20
- import { Host } from "../host/host.mjs";
21
- import { addAttributeToken } from "../../dom/attributes.mjs";
22
- import { ATTRIBUTE_ERRORMESSAGE } from "../../dom/constants.mjs";
15
+ import {ID} from "../../types/id.mjs";
16
+ import {Settings} from "./filter/settings.mjs";
17
+ import {FilterStyleSheet} from "./stylesheet/filter.mjs";
18
+ import {getDocument, getWindow} from "../../dom/util.mjs";
19
+ import {getGlobal} from "../../types/global.mjs";
20
+ import {isInstance, isFunction, isObject, isArray} from "../../types/is.mjs";
21
+ import {Host} from "../host/host.mjs";
22
+ import {addAttributeToken} from "../../dom/attributes.mjs";
23
+ import {ATTRIBUTE_ERRORMESSAGE} from "../../dom/constants.mjs";
23
24
  import "../form/message-state-button.mjs";
24
- import { Formatter } from "../../text/formatter.mjs";
25
- import { generateRangeComparisonExpression } from "../../text/util.mjs";
26
- import { generateUniqueConfigKey } from "../host/util.mjs";
25
+ import {Formatter} from "../../text/formatter.mjs";
26
+ import {generateRangeComparisonExpression} from "../../text/util.mjs";
27
+
27
28
  import {
28
- parseBracketedKeyValueHash,
29
- createBracketedKeyValueHash,
29
+ parseBracketedKeyValueHash,
30
+ createBracketedKeyValueHash,
30
31
  } from "../../text/bracketed-key-value-hash.mjs";
32
+ import {ThemeStyleSheet} from "../stylesheet/theme.mjs";
33
+ import {SpaceStyleSheet} from "../stylesheet/space.mjs";
34
+ import {FormStyleSheet} from "../stylesheet/form.mjs";
35
+
36
+ import {getStoredFilterConfigKey, getFilterConfigKey, parseDateInput} from "./filter/util.mjs";
31
37
 
32
38
  import "./filter/select.mjs";
33
39
 
34
- export { Filter };
40
+ export {Filter};
35
41
 
36
42
  /**
37
43
  * @private
@@ -51,12 +57,30 @@ const searchButtonElementSymbol = Symbol("searchButtonElement");
51
57
  */
52
58
  const resetButtonElementSymbol = Symbol("resetButtonElement");
53
59
 
60
+ /**
61
+ * @private
62
+ * @type {symbol}
63
+ */
64
+ const saveButtonElementSymbol = Symbol("saveButtonElement");
65
+
54
66
  /**
55
67
  * @private
56
68
  * @type {symbol}
57
69
  */
58
70
  const filterControlElementSymbol = Symbol("filterControlElement");
59
71
 
72
+ /**
73
+ * @private
74
+ * @type {symbol}
75
+ */
76
+ const filterSaveActionButtonElementSymbol = Symbol("filterSaveActionButtonElement");
77
+
78
+ /**
79
+ * @private
80
+ * @type {symbol}
81
+ */
82
+ const filterTabElementSymbol = Symbol("filterTabElement");
83
+
60
84
  /**
61
85
  * @private
62
86
  * @type {symbol}
@@ -110,158 +134,180 @@ const settingsSymbol = Symbol("settings");
110
134
  * @summary A data set
111
135
  */
112
136
  class Filter extends CustomElement {
113
- /**
114
- *
115
- */
116
- constructor() {
117
- super();
118
- this[settingsSymbol] = new Settings();
119
- }
120
-
121
- /**
122
- * This method is called by the `instanceof` operator.
123
- * @returns {symbol}
124
- */
125
- static get [instanceSymbol]() {
126
- return Symbol.for("@schukai/monster/components/filter@@instance");
127
- }
128
-
129
- /**
130
- *
131
- * @param {string} message
132
- * @returns {Monster.Components.Datatable.Filter}
133
- */
134
- showFailureMessage(message) {
135
- this[searchButtonElementSymbol].setState("failed", 10000);
136
- this[searchButtonElementSymbol]
137
- .setMessage(String(message))
138
- .showMessage(10000);
139
- return this;
140
- }
141
-
142
- /**
143
- *
144
- * @returns {Monster.Components.Datatable.Filter}
145
- */
146
- resetFailureMessage() {
147
- this[searchButtonElementSymbol].hideMessage();
148
- this[searchButtonElementSymbol].removeState();
149
- return this;
150
- }
151
-
152
- /**
153
- *
154
- * @returns {Monster.Components.Datatable.Filter}
155
- */
156
- showSuccess() {
157
- this[searchButtonElementSymbol].setState("successful", 5000);
158
- return this;
159
- }
160
-
161
- /**
162
- * To set the options via the html tag the attribute `data-monster-options` must be used.
163
- * @see {@link https://monsterjs.org/en/doc/#configurate-a-monster-control}
164
- *
165
- * The individual configuration values can be found in the table.
166
- *
167
- * @property {Object} templates Template definitions
168
- * @property {string} templates.main Main template
169
- * @property {Object} labels Label definitions
170
- * @property {string} labels.search Search button label
171
- * @property {string} labels.reset Reset button label
172
- * @property {Object} queries Query definitions
173
- * @property {function} queries.wrap Wrap query
174
- * @property {function} queries.join Join queries
175
- * @property {string} defaultQuery Default query
176
- */
177
- get defaults() {
178
- const obj = Object.assign({}, super.defaults, {
179
- templates: {
180
- main: getTemplate(),
181
- },
182
- labels: {
183
- search: "Search",
184
- reset: "Reset",
185
- "empty-query-and-no-default": "Please select a filter",
186
- "query-not-changed": "The query has not changed",
187
- },
188
-
189
- queries: {
190
- wrap: (value, definition) => {
191
- return value;
192
- },
193
- join: (queries) => {
194
- if (queries.length === 0) {
195
- return "";
196
- }
197
- return queries.join(" AND ");
198
- },
199
- },
200
-
201
- query: "",
202
- defaultQuery: "",
203
- });
204
-
205
- return obj;
206
- }
207
-
208
- /**
209
- *
210
- * @return {string}
211
- */
212
- static getTag() {
213
- return "monster-datatable-filter";
214
- }
215
-
216
- /**
217
- * @return {FilterButton}
218
- */
219
- [assembleMethodSymbol]() {
220
- super[assembleMethodSymbol]();
221
-
222
- initControlReferences.call(this);
223
- initEventHandler.call(this);
224
-
225
- initFromConfig
226
- .call(this)
227
- .then(() => {
228
- initFilter.call(this);
229
- })
230
- .catch((error) => {
231
- console.error(error);
232
- });
233
- }
234
-
235
- /**
236
- *
237
- */
238
- connectedCallback() {
239
- super.connectedCallback();
240
-
241
- getWindow().addEventListener(
242
- "hashchange",
243
- this[locationChangeHandlerSymbol],
244
- );
245
- }
246
-
247
- /**
248
- *
249
- */
250
- disconnectedCallback() {
251
- super.disconnectedCallback();
252
-
253
- getWindow().removeEventListener(
254
- "hashchange",
255
- this[locationChangeHandlerSymbol],
256
- );
257
- }
258
-
259
- /**
260
- * @return {Array<CSSStyleSheet>}
261
- */
262
- static getCSSStyleSheet() {
263
- return [FilterStyleSheet];
264
- }
137
+ /**
138
+ *
139
+ */
140
+ constructor() {
141
+ super();
142
+ this[settingsSymbol] = new Settings();
143
+ }
144
+
145
+ /**
146
+ * This method is called by the `instanceof` operator.
147
+ * @returns {symbol}
148
+ */
149
+ static get [instanceSymbol]() {
150
+ return Symbol.for("@schukai/monster/components/filter@@instance");
151
+ }
152
+
153
+ /**
154
+ *
155
+ * @param {string} message
156
+ * @returns {Monster.Components.Datatable.Filter}
157
+ */
158
+ showFailureMessage(message) {
159
+ this[searchButtonElementSymbol].setState("failed", this.getOption("timeouts.message", 4000));
160
+ this[searchButtonElementSymbol]
161
+ .setMessage(message.toString())
162
+ .showMessage(this.getOption("timeouts.message", 4000));
163
+ return this;
164
+ }
165
+
166
+ /**
167
+ *
168
+ * @returns {Monster.Components.Datatable.Filter}
169
+ */
170
+ resetFailureMessage() {
171
+ this[searchButtonElementSymbol].hideMessage();
172
+ this[searchButtonElementSymbol].removeState();
173
+ return this;
174
+ }
175
+
176
+ /**
177
+ *
178
+ * @returns {Monster.Components.Datatable.Filter}
179
+ */
180
+ showSuccess() {
181
+ this[searchButtonElementSymbol].setState("successful", this.getOption("timeouts.message", 4000));
182
+ return this;
183
+ }
184
+
185
+ /**
186
+ * To set the options via the html tag the attribute `data-monster-options` must be used.
187
+ * @see {@link https://monsterjs.org/en/doc/#configurate-a-monster-control}
188
+ *
189
+ * The individual configuration values can be found in the table.
190
+ *
191
+ * @property {Object} templates Template definitions
192
+ * @property {string} templates.main Main template
193
+ * @property {Object} labels Label definitions
194
+ * @property {string} labels.search Search button label
195
+ * @property {string} labels.reset Reset button label
196
+ * @property {Object} queries Query definitions
197
+ * @property {function} queries.wrap Wrap query
198
+ * @property {function} queries.join Join queries
199
+ * @property {string} defaultQuery Default query
200
+ */
201
+ get defaults() {
202
+ return Object.assign({}, super.defaults, {
203
+ templates: {
204
+ main: getTemplate(),
205
+ },
206
+
207
+ labels: {
208
+ search: "Search",
209
+ reset: "Reset",
210
+ save: "Save",
211
+ "filter-name": "Filter name",
212
+ "empty-query-and-no-default": "Please select a filter",
213
+ "query-not-changed": "The query has not changed",
214
+ },
215
+
216
+ templateMapping: {
217
+ "filter-save-label": null,
218
+ "filter-name-label": name,
219
+ },
220
+
221
+ storedConfig: {
222
+ enabled: true,
223
+ selector: ""
224
+ },
225
+
226
+ timeouts: {
227
+ message: 4000,
228
+ },
229
+
230
+ queries: {
231
+ wrap: (value, definition) => {
232
+ return value;
233
+ },
234
+ join: (queries) => {
235
+ if (queries.length === 0) {
236
+ return "";
237
+ }
238
+ return queries.join(" AND ");
239
+ },
240
+ },
241
+
242
+ query: "",
243
+ defaultQuery: "",
244
+ });
245
+
246
+ }
247
+
248
+ /**
249
+ *
250
+ * @return {string}
251
+ */
252
+ static getTag() {
253
+ return "monster-datatable-filter";
254
+ }
255
+
256
+ /**
257
+ * @return {FilterButton}
258
+ */
259
+ [assembleMethodSymbol]() {
260
+
261
+ this.setOption("templateMapping.filter-save-label", this.getOption("labels.save"));
262
+ this.setOption("templateMapping.filter-name-label", this.getOption("labels.filter-name"));
263
+
264
+ super[assembleMethodSymbol]();
265
+
266
+ initControlReferences.call(this);
267
+ initEventHandler.call(this);
268
+
269
+ initFromConfig.call(this)
270
+ .then(() => {
271
+ initFilter.call(this);
272
+ updateFilterTabs.call(this);
273
+
274
+ })
275
+ .catch((error) => {
276
+ addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, error?.message);
277
+ });
278
+
279
+ }
280
+
281
+ /**
282
+ *
283
+ */
284
+ connectedCallback() {
285
+ super.connectedCallback();
286
+
287
+ getWindow().addEventListener(
288
+ "hashchange",
289
+ this[locationChangeHandlerSymbol],
290
+ );
291
+ }
292
+
293
+ /**
294
+ *
295
+ */
296
+ disconnectedCallback() {
297
+ super.disconnectedCallback();
298
+
299
+ getWindow().removeEventListener(
300
+ "hashchange",
301
+ this[locationChangeHandlerSymbol],
302
+ );
303
+ }
304
+
305
+ /**
306
+ * @return {Array<CSSStyleSheet>}
307
+ */
308
+ static getCSSStyleSheet() {
309
+ return [FilterStyleSheet, FormStyleSheet, ThemeStyleSheet, SpaceStyleSheet];
310
+ }
265
311
  }
266
312
 
267
313
  /**
@@ -269,23 +315,35 @@ class Filter extends CustomElement {
269
315
  * @return {FilterButton}
270
316
  */
271
317
  function initControlReferences() {
272
- if (!this.shadowRoot) {
273
- throw new Error("no shadow-root is defined");
274
- }
275
-
276
- this[filterControlElementSymbol] = this.shadowRoot.querySelector(
277
- "[data-monster-role=control]",
278
- );
279
- this[filterSelectElementSymbol] = this.shadowRoot.querySelector(
280
- "[data-monster-role=filter-select]",
281
- );
282
- this[searchButtonElementSymbol] = this.shadowRoot.querySelector(
283
- "[data-monster-role=search-button]",
284
- );
285
- this[resetButtonElementSymbol] = this.shadowRoot.querySelector(
286
- "[data-monster-role=reset-button]",
287
- );
288
- return this;
318
+ if (!this.shadowRoot) {
319
+ throw new Error("no shadow-root is defined");
320
+ }
321
+
322
+ this[filterControlElementSymbol] = this.shadowRoot.querySelector(
323
+ "[data-monster-role=control]"
324
+ );
325
+ this[filterSelectElementSymbol] = this.shadowRoot.querySelector(
326
+ "[data-monster-role=filter-select]"
327
+ );
328
+ this[searchButtonElementSymbol] = this.shadowRoot.querySelector(
329
+ "[data-monster-role=search-button]"
330
+ );
331
+ this[resetButtonElementSymbol] = this.shadowRoot.querySelector(
332
+ "[data-monster-role=reset-button]"
333
+ );
334
+
335
+ this[saveButtonElementSymbol] = this.shadowRoot.querySelector(
336
+ "[data-monster-role=save-button]"
337
+ );
338
+
339
+ this[filterSaveActionButtonElementSymbol] = this.shadowRoot.querySelector(
340
+ "[data-monster-role=save-action-button]"
341
+ );
342
+
343
+ this[filterTabElementSymbol] = findElementWithSelectorUpwards(this, this.getOption("storedConfig.selector", ""))
344
+
345
+
346
+ return this;
289
347
  }
290
348
 
291
349
  /**
@@ -293,65 +351,69 @@ function initControlReferences() {
293
351
  * @throws {Error} no filter label is defined
294
352
  */
295
353
  function initFilter() {
296
- const storedConfig = this[settingsSymbol];
297
- this[settingsSymbol] = new Settings();
298
-
299
- const result = parseBracketedKeyValueHash(getGlobal().location.hash);
300
- let valuesFromHash = {};
301
- if (isObject(result) && result?.[this.id]) {
302
- valuesFromHash = result[this.id];
303
- }
304
-
305
- getSlottedElements
306
- .call(this, "label[data-monster-label]")
307
- .forEach((element) => {
308
- const label = element.getAttribute("data-monster-label");
309
- if (!label) {
310
- throw new Error("no filter label is defined");
311
- }
312
-
313
- let value = element.id;
314
- if (!value) {
315
- const prefix = label.replace(/\W/g, "-");
316
- prefix.charAt(0).match(/[\d_]/g)?.length ? `f${prefix}` : prefix;
317
-
318
- value = new ID(prefix + "-").toString();
319
- element.id = value;
320
- }
321
-
322
- let setting = storedConfig.get(value);
323
-
324
- if (setting) {
325
- this[settingsSymbol].set(setting);
326
- }
327
-
328
- if (valuesFromHash?.[element.id]) {
329
- const v = escapeAttributeValue(valuesFromHash[element.id]);
330
- const searchInput = element.firstElementChild;
331
- try {
332
- searchInput.value = valuesFromHash[element.id];
333
- } catch (error) {}
334
- }
335
-
336
- setting = this[settingsSymbol].get(value);
337
- if (setting) {
338
- setSlotAttribute(element, setting.visible);
339
- //style.display = setting.visible ? "block" : "none";
340
- }
341
-
342
- //const visible = window.getComputedStyle(element).display !== "none";
343
- const visible = getVisibilityFromSlotAttribute(element);
344
-
345
- this[settingsSymbol].set({ value, label, visible });
346
- });
347
-
348
- this[filterSelectElementSymbol].setOption(
349
- "options",
350
- this[settingsSymbol].getOptions(),
351
- );
352
- setTimeout(() => {
353
- this[filterSelectElementSymbol].value = this[settingsSymbol].getSelected();
354
- }, 10);
354
+
355
+ const storedConfig = this[settingsSymbol];
356
+ this[settingsSymbol] = new Settings();
357
+
358
+ const result = parseBracketedKeyValueHash(getGlobal().location.hash);
359
+ let valuesFromHash = {};
360
+ if (isObject(result) && result?.[this.id]) {
361
+ valuesFromHash = result[this.id];
362
+ }
363
+
364
+ getSlottedElements
365
+ .call(this, "label[data-monster-label]")
366
+ .forEach((element) => {
367
+ const label = element.getAttribute("data-monster-label");
368
+ if (!label) {
369
+ throw new Error("no filter label is defined");
370
+ }
371
+
372
+ let value = element.id;
373
+ if (!value) {
374
+ const prefix = label.replace(/\W/g, "-");
375
+ prefix.charAt(0).match(/[\d_]/g)?.length ? `f${prefix}` : prefix;
376
+
377
+ value = new ID(prefix + "-").toString();
378
+ element.id = value;
379
+ }
380
+
381
+ let setting = storedConfig.get(value);
382
+
383
+ if (setting) {
384
+ this[settingsSymbol].set(setting);
385
+ }
386
+
387
+ if (valuesFromHash?.[element.id]) {
388
+ const v = escapeAttributeValue(valuesFromHash[element.id]);
389
+ const searchInput = element.firstElementChild;
390
+ try {
391
+ // searchInput.value = valuesFromHash[element.id];
392
+ searchInput.value = v;//valuesFromHash[element.id];
393
+ } catch (error) {
394
+ }
395
+ }
396
+
397
+ setting = this[settingsSymbol].get(value);
398
+ if (setting) {
399
+ setSlotAttribute(element, setting.visible);
400
+ //style.display = setting.visible ? "block" : "none";
401
+ }
402
+
403
+ //const visible = window.getComputedStyle(element).display !== "none";
404
+ const visible = getVisibilityFromSlotAttribute(element);
405
+
406
+ this[settingsSymbol].set({value, label, visible});
407
+ });
408
+
409
+ this[filterSelectElementSymbol].setOption(
410
+ "options",
411
+ this[settingsSymbol].getOptions(),
412
+ );
413
+
414
+ setTimeout(() => {
415
+ this[filterSelectElementSymbol].value = this[settingsSymbol].getSelected();
416
+ }, 10);
355
417
  }
356
418
 
357
419
  /**
@@ -360,12 +422,18 @@ function initFilter() {
360
422
  * @returns {*}
361
423
  */
362
424
  function escapeAttributeValue(input) {
363
- return input
364
- .replace(/&/g, "&amp;")
365
- .replace(/"/g, "&quot;")
366
- .replace(/'/g, "&#x27;")
367
- .replace(/</g, "&lt;")
368
- .replace(/>/g, "&gt;");
425
+
426
+ if (input === undefined || input === null) {
427
+ debugger
428
+ return input;
429
+ }
430
+
431
+ return input
432
+ .replace(/&/g, "&amp;")
433
+ .replace(/"/g, "&quot;")
434
+ .replace(/'/g, "&#x27;")
435
+ .replace(/</g, "&lt;")
436
+ .replace(/>/g, "&gt;");
369
437
  }
370
438
 
371
439
  /**
@@ -374,10 +442,7 @@ function escapeAttributeValue(input) {
374
442
  * @returns {boolean}
375
443
  */
376
444
  function getVisibilityFromSlotAttribute(element) {
377
- return element.hasAttribute("slot") &&
378
- element.getAttribute("slot") === "hidden"
379
- ? false
380
- : true;
445
+ return !(element.hasAttribute("slot") && element.getAttribute("slot") === "hidden");
381
446
  }
382
447
 
383
448
  /**
@@ -386,188 +451,404 @@ function getVisibilityFromSlotAttribute(element) {
386
451
  * @param {boolean} visible
387
452
  */
388
453
  function setSlotAttribute(element, visible) {
389
- if (visible) {
390
- element.removeAttribute("slot");
391
- return;
392
- }
454
+ if (visible) {
455
+ element.removeAttribute("slot");
456
+ return;
457
+ }
393
458
 
394
- element.setAttribute("slot", "hidden");
459
+ element.setAttribute("slot", "hidden");
395
460
  }
396
461
 
397
462
  /**
398
463
  * @private
399
464
  */
400
465
  function initEventHandler() {
401
- const self = this;
402
- /**
403
- * Monster.Components.Form.event:monster-selection-cleared
404
- */
405
- if (self[filterSelectElementSymbol]) {
406
- self[filterSelectElementSymbol].addEventListener(
407
- "monster-selection-cleared",
408
- function (event) {
409
- const settings = self[settingsSymbol].getOptions();
410
-
411
- for (const setting of settings) {
412
- const filterElement = findElementWithIdUpwards(self, setting.value);
413
- if (filterElement) {
414
- setSlotAttribute(filterElement, false);
415
-
416
- self[settingsSymbol].set({ value: setting.value, visible: false });
417
- }
418
- }
419
-
420
- updateConfig.call(self);
421
- },
422
- );
423
-
424
- self[filterSelectElementSymbol].addEventListener(
425
- "monster-changed",
426
- function (event) {
427
- const filterElement = findElementWithIdUpwards(
428
- self,
429
- event.detail.value,
430
- );
431
- if (filterElement) {
432
- //filterElement.style.display = event.detail.checked ? "block" : "none";
433
- setSlotAttribute(filterElement, event.detail.checked);
434
- }
435
-
436
- self[settingsSymbol].set({
437
- value: event.detail.value,
438
- visible: event.detail.checked,
439
- });
440
-
441
- updateConfig.call(self);
442
- },
443
- );
444
- }
445
-
446
- self[searchButtonElementSymbol].setOption("actions.click", () => {
447
- doSearch.call(self);
448
- });
449
-
450
- // the reset button should reset the filter and the search query
451
- // all input elements should be reset to their default values
452
- // which is the empty string. we search for all input elements
453
- // in the filter and reset them to their default value
454
- self[resetButtonElementSymbol].setOption("actions.click", () => {
455
- getSlottedElements
456
- .call(self, "label[data-monster-label]")
457
- .forEach((element) => {
458
- const label = element.getAttribute("data-monster-label");
459
- if (!label) {
460
- return;
461
- }
462
-
463
- const input = element.firstElementChild;
464
-
465
- if (input) {
466
- input.value = "";
467
- }
468
- });
469
-
470
- doSearch.call(self, { showEffect: false });
471
- });
472
-
473
- self[locationChangeHandlerSymbol] = (event) => {
474
- if (event instanceof HashChangeEvent) {
475
- if (event.oldURL === event.newURL) {
476
- return;
477
- }
478
- }
479
- };
480
-
481
- self.addEventListener("keyup", (event) => {
482
- const path = event.composedPath();
483
- if (path.length === 0) {
484
- return;
485
- }
486
-
487
- if (!(path[0] instanceof HTMLInputElement)) {
488
- return;
489
- }
490
-
491
- if (event.keyCode === 13) {
492
- doSearch.call(self, { showEffect: false });
493
- }
494
- });
466
+ const self = this;
467
+ /**
468
+ * Monster.Components.Form.event:monster-selection-cleared
469
+ */
470
+ if (self[filterSelectElementSymbol]) {
471
+ self[filterSelectElementSymbol].addEventListener(
472
+ "monster-selection-cleared",
473
+ function () {
474
+ const settings = self[settingsSymbol].getOptions();
475
+
476
+ for (const setting of settings) {
477
+ const filterElement = findElementWithIdUpwards(self, setting.value);
478
+ if (filterElement) {
479
+ setSlotAttribute(filterElement, false);
480
+
481
+ self[settingsSymbol].set({value: setting.value, visible: false});
482
+ }
483
+ }
484
+
485
+ updateConfig.call(self);
486
+ },
487
+ );
488
+
489
+ self[filterSelectElementSymbol].addEventListener(
490
+ "monster-changed",
491
+ function (event) {
492
+ const filterElement = findElementWithIdUpwards(
493
+ self,
494
+ event.detail.value,
495
+ );
496
+ if (filterElement) {
497
+ //filterElement.style.display = event.detail.checked ? "block" : "none";
498
+ setSlotAttribute(filterElement, event.detail.checked);
499
+ }
500
+
501
+ self[settingsSymbol].set({
502
+ value: event.detail.value,
503
+ visible: event.detail.checked,
504
+ });
505
+
506
+ updateConfig.call(self);
507
+ },
508
+ );
509
+ }
510
+
511
+ if (self[filterSaveActionButtonElementSymbol]) {
512
+ self[filterSaveActionButtonElementSymbol].setOption("actions.click", function (event) {
513
+
514
+ const button = findTargetElementFromEvent(event, "data-monster-role", "save-action-button");
515
+ const form = button.closest("[data-monster-role=form]");
516
+
517
+ if (!form) {
518
+ button.setState("failed", self.getOption("timeouts.message", 4000))
519
+ return;
520
+ }
521
+
522
+ const input = form.querySelector("input[name=filter-name]");
523
+ if (!input) {
524
+ button.setState("failed", self.getOption("timeouts.message", 4000))
525
+ return;
526
+ }
527
+
528
+ const name = input.value;
529
+ if (!name) {
530
+ button.setState("failed", self.getOption("timeouts.message", 4000))
531
+ button.setMessage("Please enter a name").showMessage();
532
+ return;
533
+ }
534
+
535
+ doSearch.call(self, {showEffect: false}).then(() => {
536
+
537
+ const configKey = getStoredFilterConfigKey.call(self);
538
+ const host = getDocument().querySelector("monster-host");
539
+ if (!(host)) {
540
+ return;
541
+ }
542
+
543
+ const query = self.getOption("query");
544
+ if (!query) {
545
+ button.setState("failed", self.getOption("timeouts.message", self.getOption("timeouts.message", 4000)))
546
+ button.setMessage("No query found").showMessage(self.getOption("timeouts.message", 4000));
547
+ return;
548
+ }
549
+
550
+ host.hasConfig(configKey).then((hasConfig) => {
551
+
552
+ return new Promise((resolve, reject) => {
553
+ if (hasConfig) {
554
+ host.getConfig(configKey).then(resolve).catch(reject);
555
+ return;
556
+ }
557
+ return resolve({});
558
+ })
559
+
560
+ }).then((config) => {
561
+
562
+ config[name] = query;
563
+ return host.setConfig(configKey, {
564
+ ...config,
565
+ })
566
+
567
+
568
+ }).then(() => {
569
+ button.setState("successful", self.getOption("timeouts.message", 4000))
570
+ updateFilterTabs.call(self);
571
+
572
+ }).catch((error) => {
573
+ button.setState("failed", self.getOption("timeouts.message", 4000))
574
+ button.setMessage(error.message).showMessage(self.getOption("timeouts.message", 4000));
575
+ })
576
+
577
+ }).catch((error) => {
578
+ button.setState("failed", self.getOption("timeouts.message", 4000))
579
+ const msg = error.message || error;
580
+ button.setMessage(msg).showMessage(self.getOption("timeouts.message", 4000));
581
+ })
582
+
583
+ });
584
+ }
585
+
586
+
587
+ self[searchButtonElementSymbol].setOption("actions.click", () => {
588
+ doSearch.call(self).then(() => {
589
+
590
+ }).catch((error) => {
591
+
592
+ })
593
+ });
594
+
595
+ // the reset button should reset the filter and the search query
596
+ // all input elements should be reset to their default values
597
+ // which is the empty string. we search for all input elements
598
+ // in the filter and reset them to their default value
599
+ self[resetButtonElementSymbol].setOption("actions.click", () => {
600
+ getSlottedElements
601
+ .call(self, "label[data-monster-label]")
602
+ .forEach((element) => {
603
+ const label = element.getAttribute("data-monster-label");
604
+ if (!label) {
605
+ return;
606
+ }
607
+
608
+ const input = element.firstElementChild;
609
+
610
+ if (input) {
611
+ input.value = "";
612
+ }
613
+ });
614
+
615
+ doSearch.call(self, {showEffect: false});
616
+ });
617
+
618
+ self[locationChangeHandlerSymbol] = (event) => {
619
+ if (event instanceof HashChangeEvent) {
620
+ if (event.oldURL === event.newURL) {
621
+ return;
622
+ }
623
+ }
624
+ };
625
+
626
+ self.addEventListener("keyup", (event) => {
627
+ const path = event.composedPath();
628
+ if (path.length === 0) {
629
+ return;
630
+ }
631
+
632
+ if (!(path[0] instanceof HTMLInputElement)) {
633
+ return;
634
+ }
635
+
636
+ if (event.keyCode === 13) {
637
+ doSearch.call(self, {showEffect: false});
638
+ }
639
+ });
640
+
641
+ // tabs
642
+ const element = this[filterTabElementSymbol];
643
+ if (element) {
644
+ initTabEvents.call(this)
645
+ }
646
+
647
+ }
648
+
649
+ function initTabEvents() {
650
+
651
+ this[filterTabElementSymbol].addEventListener("monster-tab-changed", (event) => {
652
+
653
+ const query = event?.detail?.data?.['data-monster-query'];
654
+ this.setOption("query", query);
655
+
656
+
657
+ })
658
+
659
+ this[filterTabElementSymbol].addEventListener("monster-tab-remove", (event) => {
660
+
661
+ const labels = []
662
+ const buttons = this[filterTabElementSymbol].getOption("buttons")
663
+
664
+ const keys=["popper", "standard"]
665
+ for(let i=0; i<keys.length; i++) {
666
+ const key = keys[i]
667
+
668
+ for (const button of buttons[key]) {
669
+ if (button.label !== event.detail.label) {
670
+ labels.push(button.label)
671
+ }
672
+ }
673
+ }
674
+
675
+ const document = getDocument();
676
+ const host = document.querySelector("monster-host");
677
+ if (!(host && this.id)) {
678
+ return;
679
+ }
680
+
681
+ const configKey = getStoredFilterConfigKey.call(this);
682
+ host.hasConfig(configKey).then((hasConfig) => {
683
+ if (!hasConfig) {
684
+ return;
685
+ }
686
+
687
+ return host.getConfig(configKey);
688
+ }).then((config) => {
689
+
690
+ for (const [name, query] of Object.entries(config)) {
691
+ if (labels.includes(name)) {
692
+ continue;
693
+ }
694
+
695
+ delete config[name]
696
+ }
697
+
698
+ return host.setConfig(configKey, {
699
+ ...config
700
+ })
701
+
702
+ })
703
+
704
+ });
705
+
706
+
707
+ }
708
+
709
+ /**
710
+ * @private
711
+ */
712
+ function updateFilterTabs() {
713
+
714
+ const element = this[filterTabElementSymbol];
715
+ if (!element) {
716
+ return;
717
+ }
718
+
719
+ const document = getDocument();
720
+ const host = document.querySelector("monster-host");
721
+ if (!(host && this.id)) {
722
+ return;
723
+ }
724
+
725
+ const configKey = getStoredFilterConfigKey.call(this);
726
+ host.hasConfig(configKey).then((hasConfig) => {
727
+ if (!hasConfig) {
728
+ return;
729
+ }
730
+
731
+ return host.getConfig(configKey);
732
+ }).then((config) => {
733
+
734
+ for (const [name, query] of Object.entries(config)) {
735
+
736
+ const found = element.querySelector(`[data-monster-button-label="${name}"]`);
737
+ if (found) {
738
+ continue;
739
+ }
740
+
741
+ if (query===undefined || query===null) {
742
+ continue;
743
+ }
744
+
745
+ const escapedQuery = escapeAttributeValue(query);
746
+
747
+ element.insertAdjacentHTML("beforeend", `
748
+ <div data-monster-button-label="${name}"
749
+ data-monster-removable="true"
750
+ data-monster-query="${escapedQuery}" data-monster-role="filter-tab" >
751
+ </div>`);
752
+ }
753
+ })
495
754
  }
496
755
 
497
756
  /**
498
757
  * @private
758
+ * @param showEffect
759
+ * @returns {Promise<*>}
499
760
  */
500
- function doSearch({ showEffect } = { showEffect: true }) {
501
- this.resetFailureMessage();
502
-
503
- if (showEffect) {
504
- this[searchButtonElementSymbol].setState("activity", 10000);
505
- }
506
-
507
- collectSearchQueries
508
- .call(this)
509
- .then((query) => {
510
- const buildQuery = buildSearchQuery.call(this, query);
511
- if (buildQuery === "" && !this.getOption("defaultQuery")) {
512
- this[searchButtonElementSymbol].removeState();
513
- this[searchButtonElementSymbol]
514
- .setMessage(this.getOption("labels.empty-query-and-no-default"))
515
- .showMessage(5000);
516
- return;
517
- }
518
-
519
- if (buildQuery === this.getOption("query")) {
520
- this[searchButtonElementSymbol].removeState();
521
- this[searchButtonElementSymbol]
522
- .setMessage(this.getOption("labels.query-not-changed"))
523
- .showMessage(5000);
524
- return;
525
- }
526
-
527
- if (showEffect) {
528
- this[searchButtonElementSymbol].setState("activity", 2000);
529
- }
530
-
531
- this.setOption("query", buildSearchQuery.call(this, query));
532
- })
533
- .catch((error) => {
534
- console.error(error);
535
- if (error instanceof Error) {
536
- addAttributeToken(
537
- this,
538
- ATTRIBUTE_ERRORMESSAGE,
539
- error.message + " " + error.stack,
540
- );
541
- } else {
542
- addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, String(error));
543
- }
544
-
545
- this[searchButtonElementSymbol].setState("failed", 10000);
546
- this[searchButtonElementSymbol].setMessage(String(error)).showMessage();
547
- });
761
+ function doSearch({showEffect} = {showEffect: true}) {
762
+ const self = this;
763
+
764
+ this.resetFailureMessage();
765
+
766
+ if (showEffect) {
767
+ this[searchButtonElementSymbol].setState("activity", self.getOption("timeouts.message", 4000));
768
+ }
769
+
770
+ return collectSearchQueries
771
+ .call(this)
772
+ .then((query) => {
773
+
774
+ const buildQuery = buildSearchQuery.call(this, query);
775
+ if (buildQuery === "" && !this.getOption("defaultQuery")) {
776
+
777
+ const msg = this.getOption("labels.empty-query-and-no-default");
778
+
779
+ if (showEffect) {
780
+ this[searchButtonElementSymbol].removeState();
781
+ this[searchButtonElementSymbol]
782
+ .setMessage(msg)
783
+ .showMessage(self.getOption("timeouts.message", 4000));
784
+ }
785
+
786
+ throw new Error(msg);
787
+
788
+ }
789
+
790
+ if (buildQuery === this.getOption("query")) {
791
+
792
+ const msg = this.getOption("labels.query-not-changed");
793
+
794
+ if (showEffect) {
795
+ this[searchButtonElementSymbol].removeState();
796
+ this[searchButtonElementSymbol]
797
+ .setMessage(msg)
798
+ .showMessage(self.getOption("timeouts.message", 4000));
799
+ }
800
+
801
+ throw new Error(msg);
802
+ }
803
+
804
+ if (showEffect) {
805
+ this[searchButtonElementSymbol].setState("activity", self.getOption("timeouts.message", 4000));
806
+ }
807
+
808
+ this.setOption("query", buildSearchQuery.call(this, query));
809
+ })
810
+ .catch((error) => {
811
+
812
+ if (error instanceof Error) {
813
+ addAttributeToken(
814
+ this,
815
+ ATTRIBUTE_ERRORMESSAGE,
816
+ error.message + " " + error.stack,
817
+ );
818
+ } else {
819
+ addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, String(error));
820
+ }
821
+
822
+ if (showEffect) {
823
+ this[searchButtonElementSymbol].setState("failed", self.getOption("timeouts.message", 4000));
824
+ this[searchButtonElementSymbol].setMessage(error.message).showMessage();
825
+ }
826
+
827
+ return Promise.reject(error);
828
+ });
548
829
  }
549
830
 
550
831
  /**
551
832
  * @private
552
833
  * @param queries
553
- * @returns {string|undefind}
834
+ * @returns {*|string}
554
835
  */
555
836
  function buildSearchQuery(queries) {
556
- if (!isArray(queries) || queries.length === 0) {
557
- return this.getOption("defaultQuery");
558
- }
837
+ if (!isArray(queries) || queries.length === 0) {
838
+ return this.getOption("defaultQuery");
839
+ }
559
840
 
560
- const joinCallback = this.getOption("queries.join");
561
- if (isFunction(joinCallback)) {
562
- return joinCallback(queries);
563
- }
841
+ const joinCallback = this.getOption("queries.join");
842
+ if (isFunction(joinCallback)) {
843
+ return joinCallback(queries);
844
+ }
564
845
 
565
- const q = queries.join(" ").trim();
566
- if (q.length === 0) {
567
- return this.getOption("defaultQuery");
568
- }
846
+ const q = queries.join(" ").trim();
847
+ if (q.length === 0) {
848
+ return this.getOption("defaultQuery");
849
+ }
569
850
 
570
- return q;
851
+ return q;
571
852
  }
572
853
 
573
854
  /**
@@ -575,215 +856,103 @@ function buildSearchQuery(queries) {
575
856
  * @returns {Promise<unknown>}
576
857
  */
577
858
  function collectSearchQueries() {
578
- const currentHash = parseBracketedKeyValueHash(getGlobal().location.hash);
579
-
580
- return new Promise((resolve, reject) => {
581
- const query = [];
582
- const wrapCallback = this.getOption("queries.wrap");
583
-
584
- getSlottedElements
585
- .call(this, "label[data-monster-label]")
586
- .forEach((element) => {
587
- const label = element.getAttribute("data-monster-label");
588
- if (!label) {
589
- throw new Error("no filter label is defined");
590
- }
591
-
592
- const id = element.id;
593
- if (!id) {
594
- reject(new Error("no filter id is defined"));
595
- return;
596
- }
597
-
598
- //const visible = window.getComputedStyle(element).display !== "none";
599
- const visible = getVisibilityFromSlotAttribute(element);
600
- if (!visible) {
601
- return;
602
- }
603
-
604
- let template = element.getAttribute("data-monster-template");
605
- if (!template) {
606
- template = "${id}=${value}";
607
- }
608
-
609
- const controlValue = getControlValuesFromLabel(element);
610
- if (!controlValue) {
611
- if (controlValue === "" && currentHash?.[this.id]?.[id]) {
612
- delete currentHash[this.id][id];
613
- }
614
-
615
- return;
616
- }
617
-
618
- if (!isObject(currentHash[this.id])) {
619
- currentHash[this.id] = {};
620
- }
621
- currentHash[this.id][id] = controlValue;
622
-
623
- const mapping = {
624
- id,
625
- value: controlValue,
626
- label,
627
- };
628
-
629
- const formatter = new Formatter(mapping, {
630
- callbacks: {
631
- range: (value, key) => {
632
- return generateRangeComparisonExpression(value, key, {
633
- urlEncode: true,
634
- andOp: "AND",
635
- orOp: "OR",
636
- eqOp: "=",
637
- gtOp: ">",
638
- ltOp: "<",
639
- });
640
- },
641
- "date-range": (value, key) => {
642
- const query = parseDateInput(value, key);
643
- if (!query || query === "false") {
644
- return "";
645
- }
646
-
647
- // return query as url encoded
648
- return encodeURIComponent(query);
649
- },
650
- },
651
- });
652
-
653
- let queryPart = formatter.format(template);
654
- if (queryPart) {
655
- if (isFunction(wrapCallback)) {
656
- queryPart = wrapCallback(queryPart, mapping);
657
- }
658
- query.push(queryPart);
659
- }
660
- });
661
-
662
- getGlobal().location.hash = createBracketedKeyValueHash(currentHash);
663
- resolve(query);
664
- });
859
+ const currentHash = parseBracketedKeyValueHash(getGlobal().location.hash);
860
+
861
+ return new Promise((resolve, reject) => {
862
+ const query = [];
863
+ const wrapCallback = this.getOption("queries.wrap");
864
+
865
+ let hasNoIdError = false;
866
+
867
+ getSlottedElements
868
+ .call(this, "label[data-monster-label]")
869
+ .forEach((element) => {
870
+ const label = element.getAttribute("data-monster-label");
871
+ if (!label) {
872
+ throw new Error("no filter label is defined");
873
+ }
874
+
875
+ const id = element.id;
876
+ if (!id) {
877
+ hasNoIdError = true;
878
+ return;
879
+ }
880
+
881
+ //const visible = window.getComputedStyle(element).display !== "none";
882
+ const visible = getVisibilityFromSlotAttribute(element);
883
+ if (!visible) {
884
+ return;
885
+ }
886
+
887
+ let template = element.getAttribute("data-monster-template");
888
+ if (!template) {
889
+ template = "${id}=${value}";
890
+ }
891
+
892
+ const controlValue = getControlValuesFromLabel(element);
893
+ if (!controlValue) {
894
+ if (controlValue === "" && currentHash?.[this.id]?.[id]) {
895
+ delete currentHash[this.id][id];
896
+ }
897
+
898
+ return;
899
+ }
900
+
901
+ if (!isObject(currentHash[this.id])) {
902
+ currentHash[this.id] = {};
903
+ }
904
+ currentHash[this.id][id] = controlValue;
905
+
906
+ const mapping = {
907
+ id,
908
+ value: controlValue,
909
+ label,
910
+ };
911
+
912
+ const formatter = new Formatter(mapping, {
913
+ callbacks: {
914
+ range: (value, key) => {
915
+ return generateRangeComparisonExpression(value, key, {
916
+ urlEncode: true,
917
+ andOp: "AND",
918
+ orOp: "OR",
919
+ eqOp: "=",
920
+ gtOp: ">",
921
+ ltOp: "<",
922
+ });
923
+ },
924
+ "date-range": (value, key) => {
925
+ const query = parseDateInput(value, key);
926
+ if (!query || query === "false") {
927
+ return "";
928
+ }
929
+
930
+ // return query as url encoded
931
+ return encodeURIComponent(query);
932
+ },
933
+ },
934
+ });
935
+
936
+ let queryPart = formatter.format(template);
937
+ if (queryPart) {
938
+ if (isFunction(wrapCallback)) {
939
+ queryPart = wrapCallback(queryPart, mapping);
940
+ }
941
+ query.push(queryPart);
942
+ }
943
+ });
944
+
945
+ if (hasNoIdError) {
946
+ reject(new Error("some or all filter elements have no id"));
947
+ return;
948
+ }
949
+
950
+
951
+ getGlobal().location.hash = createBracketedKeyValueHash(currentHash);
952
+ resolve(query);
953
+ });
665
954
  }
666
955
 
667
- /**
668
- * @private
669
- * @param {String} str
670
- * @param {String} field
671
- * @returns {String}
672
- * @throws {Error} if no field is defined
673
- */
674
- function parseDateInput(str, field) {
675
- if (!str) {
676
- return "";
677
- }
678
-
679
- if (!field) {
680
- throw new Error("no field is defined");
681
- }
682
-
683
- // Define the supported formats
684
- //let formats = ['DD-MM-YYYY', 'MM-DD-YYYY', 'YYYY-MM-DD', 'YYYY/MM/DD', 'DD.MM.YYYY'];
685
- const formats = ["YYYY-MM-DD"];
686
- // Determine the current date format of the localization
687
- const currentDateFormat = new Intl.DateTimeFormat()
688
- .format(new Date())
689
- .replace(/\d/g, "D");
690
- // formats.push(currentDateFormat);
691
-
692
- // Run through the supported formats and try to parse the date
693
- for (let i = 0; i < formats.length; i++) {
694
- const format = formats[i];
695
- // Replace the corresponding placeholders in the format string with regular expressions
696
-
697
- try {
698
- const pattern = format
699
- .replace("DD", "\\d{2}")
700
- .replace("MM", "\\d{2}")
701
- .replace("YYYY", "\\d{4}");
702
- const rangePattern =
703
- "(?<from>" + pattern + ")\\s*-\\s*(?<to>" + pattern + ")";
704
-
705
- const rangeRegex = new RegExp("^" + rangePattern + "$", "g");
706
-
707
- if (rangeRegex.test(str)) {
708
- rangeRegex.lastIndex = 0;
709
-
710
- const rangeResult = rangeRegex.exec(str);
711
-
712
- if (!rangeResult) {
713
- continue;
714
- }
715
-
716
- const from = rangeResult?.groups?.from;
717
- const to = rangeResult?.groups?.to;
718
-
719
- if (from && to) {
720
- return (
721
- "(" +
722
- field +
723
- ">='" +
724
- from +
725
- " 00:00:00' AND " +
726
- field +
727
- "<='" +
728
- to +
729
- " 23:59:59')"
730
- );
731
- }
732
-
733
- if (from) {
734
- return "(" + field + ">='" + from + " 00:00:00')";
735
- } else if (to) {
736
- return "(" + field + "<='" + to + "' 23:59:59')";
737
- }
738
-
739
- return "false";
740
- }
741
-
742
- const prefix = str.substring(0, 1) === "-";
743
- const suffix = str.substring(str.length - 1, str.length) === "-";
744
-
745
- if (prefix) {
746
- str = str.substring(1, str.length);
747
- } else if (suffix) {
748
- str = str.substring(0, str.length - 1);
749
- }
750
-
751
- const regex = new RegExp("^(?<date>" + pattern + ")$", "g");
752
- if (regex.test(str)) {
753
- regex.lastIndex = 0;
754
- const result = regex.exec(str);
755
-
756
- if (!result) {
757
- continue;
758
- }
759
-
760
- const date = result?.groups?.date;
761
- if (date) {
762
- if (prefix) {
763
- return "(" + field + "<='" + date + " 23:59:59')";
764
- } else if (suffix) {
765
- return "(" + field + ">='" + date + "' 00:00:00')";
766
- }
767
- return (
768
- "(" +
769
- field +
770
- ">='" +
771
- date +
772
- " 00:00:00' AND " +
773
- field +
774
- "<='" +
775
- date +
776
- " 23:59:59')"
777
- );
778
- } else {
779
- return "false";
780
- }
781
- }
782
- } catch (e) {}
783
- }
784
-
785
- return "false";
786
- }
787
956
 
788
957
  /**
789
958
  * @private
@@ -791,97 +960,96 @@ function parseDateInput(str, field) {
791
960
  * @returns {null|Array|undefined|string}
792
961
  */
793
962
  function getControlValuesFromLabel(label) {
794
- const foundControl = label.firstElementChild;
795
-
796
- if (foundControl) {
797
- if (foundControl.tagName === "INPUT") {
798
- if (foundControl.type === "checkbox") {
799
- const checkedControls = label.querySelectorAll(`${control}:checked`);
800
- const values = [];
801
-
802
- checkedControls.forEach((checkedControl) => {
803
- values.push(checkedControl.value);
804
- });
805
-
806
- return values;
807
- } else if (foundControl.type === "radio") {
808
- const checkedControl = label.querySelector(`${control}:checked`);
809
-
810
- if (checkedControl) {
811
- return checkedControl.value;
812
- } else {
813
- return null;
814
- }
815
- } else {
816
- return foundControl.value;
817
- }
818
- } else {
819
- return foundControl.value;
820
- }
821
- }
822
-
823
- return null;
963
+ const foundControl = label.firstElementChild;
964
+
965
+ if (foundControl) {
966
+ if (foundControl.tagName === "INPUT") {
967
+ if (foundControl.type === "checkbox") {
968
+ const checkedControls = label.querySelectorAll(`${foundControl}:checked`);
969
+ const values = [];
970
+
971
+ checkedControls.forEach((checkedControl) => {
972
+ values.push(checkedControl.value);
973
+ });
974
+
975
+ return values;
976
+ } else if (foundControl.type === "radio") {
977
+ const checkedControl = label.querySelector(`${foundControl}:checked`);
978
+
979
+ if (checkedControl) {
980
+ return checkedControl.value;
981
+ } else {
982
+ return null;
983
+ }
984
+ } else {
985
+ return foundControl.value;
986
+ }
987
+ } else {
988
+ return foundControl.value;
989
+ }
990
+ }
991
+
992
+ return null;
824
993
  }
825
994
 
826
- /**
827
- * @private
828
- * @returns {string}
829
- */
830
- function getFilterConfigKey() {
831
- return generateUniqueConfigKey("datatable", this?.id, "filter");
832
- }
833
995
 
834
996
  /**
835
997
  * @private
836
998
  * @returns {Promise<unknown>}
837
999
  */
838
1000
  function initFromConfig() {
839
- const document = getDocument();
840
- const host = document.querySelector("monster-host");
841
-
842
- if (!(isInstance(host, Host) && this.id)) {
843
- return Promise.resolve();
844
- }
845
-
846
- const configKey = getFilterConfigKey.call(this);
847
-
848
- return new Promise((resolve, reject) => {
849
- host
850
- .getConfig(configKey)
851
- .then((config) => {
852
- if (config) {
853
- this[settingsSymbol].setOptions(config);
854
- }
855
- resolve();
856
- })
857
- .catch((error) => {
858
- if (error === undefined) {
859
- resolve();
860
- return;
861
- }
862
-
863
- addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, String(error));
864
- reject(error);
865
- });
866
- });
1001
+ const document = getDocument();
1002
+ const host = document.querySelector("monster-host");
1003
+
1004
+ if (!(isInstance(host, Host) && this.id)) {
1005
+ return Promise.resolve();
1006
+ }
1007
+
1008
+ const configKey = getFilterConfigKey.call(this);
1009
+
1010
+ return new Promise((resolve, reject) => {
1011
+ host
1012
+ .getConfig(configKey)
1013
+ .then((config) => {
1014
+ if (config && isObject(config)) {
1015
+ this[settingsSymbol].setOptions(config);
1016
+ }
1017
+ resolve();
1018
+ })
1019
+ .catch((error) => {
1020
+ if (error === undefined) {
1021
+ resolve();
1022
+ return;
1023
+ }
1024
+
1025
+ // config not written
1026
+ if (error?.message?.match(/is not defined/)) {
1027
+ resolve();
1028
+ return;
1029
+ }
1030
+
1031
+ addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, error?.message || error);
1032
+ reject(error);
1033
+ });
1034
+ });
867
1035
  }
868
1036
 
869
1037
  /**
870
1038
  * @private
871
1039
  */
872
1040
  function updateConfig() {
873
- const document = getDocument();
874
- const host = document.querySelector("monster-host");
875
- if (!(host && this.id)) {
876
- return;
877
- }
878
- const configKey = getFilterConfigKey.call(this);
879
-
880
- try {
881
- host.setConfig(configKey, this[settingsSymbol].getOptions());
882
- } catch (error) {
883
- addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, String(error));
884
- }
1041
+ const document = getDocument();
1042
+ const host = document.querySelector("monster-host");
1043
+ if (!(host && this.id)) {
1044
+ return;
1045
+ }
1046
+ const configKey = getFilterConfigKey.call(this);
1047
+
1048
+ try {
1049
+ host.setConfig(configKey, this[settingsSymbol].getOptions());
1050
+ } catch (error) {
1051
+ addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, error?.message || error);
1052
+ }
885
1053
  }
886
1054
 
887
1055
  /**
@@ -889,8 +1057,9 @@ function updateConfig() {
889
1057
  * @return {string}
890
1058
  */
891
1059
  function getTemplate() {
892
- // language=HTML
893
- return `
1060
+
1061
+ // language=HTML
1062
+ return `
894
1063
  <div data-monster-role="control" part="control">
895
1064
  <div data-monster-role="layout">
896
1065
  <div data-monster-role="filter">
@@ -898,15 +1067,31 @@ function getTemplate() {
898
1067
  <slot name="hidden"></slot>
899
1068
  </div>
900
1069
  <div data-monster-role="select-and-search">
1070
+ <monster-message-state-button data-monster-role="search-button" class="stretched-control"
1071
+ data-monster-replace="path:labels.search">
1072
+ </monster-message-state-button>
901
1073
  <monster-select class="stretched-control"
902
1074
  data-monster-selected-template="summary"
903
1075
  data-monster-option-type="checkbox"
904
1076
  data-monster-option-filter-mode="options"
905
1077
  data-monster-option-filter-position="popper"
906
1078
  data-monster-role="filter-select"></monster-select>
907
- <monster-message-state-button data-monster-role="search-button" class="stretched-control"
908
- data-monster-replace="path:labels.search">
909
- </monster-message-state-button>
1079
+ <monster-popper-button data-monster-role="save-button" class="stretched-control"
1080
+ data-monster-attributes="data-monster-visible path:storedConfig.enabled">
1081
+ <div slot="button">\${filter-save-label}</div>
1082
+ <div class="monster-form" data-monster-role="form">
1083
+
1084
+ <label for="filter-name">\${filter-name-label}
1085
+ <input name="filter-name"
1086
+ type="search"
1087
+ class="monster-margin-bottom-5"></label>
1088
+ <monster-message-state-button
1089
+ data-monster-role="save-action-button"
1090
+ data-monster-option-labels-button="\${filter-save-label}">
1091
+ </monster-message-state-button>
1092
+
1093
+ </div>
1094
+ </monster-popper-button>
910
1095
  <monster-button data-monster-role="reset-button" class="stretched-control"
911
1096
  data-monster-replace="path:labels.reset">
912
1097
  </monster-button>