@schukai/monster 3.86.4 → 3.87.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.
@@ -12,75 +12,75 @@
12
12
  * SPDX-License-Identifier: AGPL-3.0
13
13
  */
14
14
 
15
- import { Datasource } from "./datasource.mjs";
15
+ import {Datasource} from "./datasource.mjs";
16
16
  import {
17
- assembleMethodSymbol,
18
- CustomElement,
19
- registerCustomElement,
20
- getSlottedElements,
17
+ assembleMethodSymbol,
18
+ CustomElement,
19
+ registerCustomElement,
20
+ getSlottedElements,
21
21
  } from "../../dom/customelement.mjs";
22
22
  import {
23
- findTargetElementFromEvent,
24
- fireCustomEvent,
23
+ findTargetElementFromEvent,
24
+ fireCustomEvent,
25
25
  } from "../../dom/events.mjs";
26
- import { clone } from "../../util/clone.mjs";
26
+ import {clone} from "../../util/clone.mjs";
27
27
  import {
28
- isString,
29
- isFunction,
30
- isInstance,
31
- isObject,
32
- isArray,
28
+ isString,
29
+ isFunction,
30
+ isInstance,
31
+ isObject,
32
+ isArray,
33
33
  } from "../../types/is.mjs";
34
34
  import {
35
- validateArray,
36
- validateInteger,
37
- validateObject,
35
+ validateArray,
36
+ validateInteger,
37
+ validateObject,
38
38
  } from "../../types/validate.mjs";
39
- import { Observer } from "../../types/observer.mjs";
39
+ import {Observer} from "../../types/observer.mjs";
40
40
  import {
41
- ATTRIBUTE_DATATABLE_HEAD,
42
- ATTRIBUTE_DATATABLE_GRID_TEMPLATE,
43
- ATTRIBUTE_DATASOURCE_SELECTOR,
44
- ATTRIBUTE_DATATABLE_ALIGN,
45
- ATTRIBUTE_DATATABLE_SORTABLE,
46
- ATTRIBUTE_DATATABLE_MODE,
47
- ATTRIBUTE_DATATABLE_INDEX,
48
- ATTRIBUTE_DATATABLE_MODE_HIDDEN,
49
- ATTRIBUTE_DATATABLE_MODE_VISIBLE,
50
- ATTRIBUTE_DATATABLE_RESPONSIVE_BREAKPOINT,
51
- ATTRIBUTE_DATATABLE_MODE_FIXED,
41
+ ATTRIBUTE_DATATABLE_HEAD,
42
+ ATTRIBUTE_DATATABLE_GRID_TEMPLATE,
43
+ ATTRIBUTE_DATASOURCE_SELECTOR,
44
+ ATTRIBUTE_DATATABLE_ALIGN,
45
+ ATTRIBUTE_DATATABLE_SORTABLE,
46
+ ATTRIBUTE_DATATABLE_MODE,
47
+ ATTRIBUTE_DATATABLE_INDEX,
48
+ ATTRIBUTE_DATATABLE_MODE_HIDDEN,
49
+ ATTRIBUTE_DATATABLE_MODE_VISIBLE,
50
+ ATTRIBUTE_DATATABLE_RESPONSIVE_BREAKPOINT,
51
+ ATTRIBUTE_DATATABLE_MODE_FIXED,
52
52
  } from "./constants.mjs";
53
- import { instanceSymbol } from "../../constants.mjs";
53
+ import {instanceSymbol} from "../../constants.mjs";
54
54
  import {
55
- Header,
56
- createOrderStatement,
57
- DIRECTION_ASC,
58
- DIRECTION_DESC,
59
- DIRECTION_NONE,
55
+ Header,
56
+ createOrderStatement,
57
+ DIRECTION_ASC,
58
+ DIRECTION_DESC,
59
+ DIRECTION_NONE,
60
60
  } from "./datatable/header.mjs";
61
- import { DatatableStyleSheet } from "./stylesheet/datatable.mjs";
61
+ import {DatatableStyleSheet} from "./stylesheet/datatable.mjs";
62
62
  import {
63
- handleDataSourceChanges,
64
- datasourceLinkedElementSymbol,
63
+ handleDataSourceChanges,
64
+ datasourceLinkedElementSymbol,
65
65
  } from "./util.mjs";
66
66
  import "./columnbar.mjs";
67
67
  import "./filter-button.mjs";
68
68
  import {
69
- findElementWithSelectorUpwards,
70
- getDocument,
71
- getWindow,
69
+ findElementWithSelectorUpwards,
70
+ getDocument,
71
+ getWindow,
72
72
  } from "../../dom/util.mjs";
73
- import { addAttributeToken } from "../../dom/attributes.mjs";
74
- import { ATTRIBUTE_ERRORMESSAGE } from "../../dom/constants.mjs";
75
- import { getDocumentTranslations } from "../../i18n/translations.mjs";
73
+ import {addAttributeToken} from "../../dom/attributes.mjs";
74
+ import {ATTRIBUTE_ERRORMESSAGE} from "../../dom/constants.mjs";
75
+ import {getDocumentTranslations} from "../../i18n/translations.mjs";
76
76
  import "../state/state.mjs";
77
77
  import "../host/collapse.mjs";
78
- import { generateUniqueConfigKey } from "../host/util.mjs";
78
+ import {generateUniqueConfigKey} from "../host/util.mjs";
79
79
 
80
80
  import "./datasource/dom.mjs";
81
81
  import "./datasource/rest.mjs";
82
82
 
83
- export { DataTable };
83
+ export {DataTable};
84
84
 
85
85
  /**
86
86
  * @private
@@ -106,6 +106,12 @@ const gridHeadersElementSymbol = Symbol("gridHeadersElement");
106
106
  */
107
107
  const columnBarElementSymbol = Symbol("columnBarElement");
108
108
 
109
+ /**
110
+ * @private
111
+ * @type {symbol}
112
+ */
113
+ const copyAllElementSymbol = Symbol("copyAllElement");
114
+
109
115
  /**
110
116
  * @private
111
117
  * @type {symbol}
@@ -142,387 +148,398 @@ const resizeObserverSymbol = Symbol("resizeObserver");
142
148
  * @fires monster-datatable-row-added
143
149
  **/
144
150
  class DataTable extends CustomElement {
145
- /**
146
- * This method is called by the `instanceof` operator.
147
- * @return {symbol}
148
- */
149
- static get [instanceSymbol]() {
150
- return Symbol.for("@schukai/monster/components/datatable@@instance");
151
- }
152
-
153
- /**
154
- * To set the options via the HTML tag, the attribute `data-monster-options` must be used.
155
- * @see {@link https://monsterjs.org/en/doc/#configurate-a-monster-control}
156
- *
157
- * The individual configuration values can be found in the table.
158
- *
159
- * @property {Object} templates Template definitions
160
- * @property {string} templates.main Main template
161
- * @property {Object} datasource Datasource configuration
162
- * @property {string} datasource.selector Selector for the datasource
163
- * @property {Object} mapping Mapping configuration
164
- * @property {string} mapping.data Data mapping
165
- * @property {Array} data Data
166
- * @property {Array} headers Headers
167
- * @property {Object} responsive Responsive configuration
168
- * @property {number} responsive.breakpoint Breakpoint for responsive mode
169
- * @property {Object} labels Labels
170
- * @property {string} labels.theListContainsNoEntries Label for empty state
171
- * @property {Object} classes Classes
172
- * @property {string} classes.container Container class
173
- * @property {Object} features Features
174
- * @property {boolean} features.settings Settings feature
175
- * @property {boolean} features.footer Footer feature
176
- * @property {boolean} features.autoInit Auto init feature (init datasource automatically)
177
- * @property {Object} templateMapping Template mapping
178
- * @property {string} templateMapping.row-key Row key
179
- * @property {string} templateMapping.filter-id Filter id
180
- **/
181
- get defaults() {
182
- return Object.assign(
183
- {},
184
- super.defaults,
185
- {
186
- templates: {
187
- main: getTemplate(),
188
- emptyState: getEmptyTemplate(),
189
- },
190
-
191
- datasource: {
192
- selector: null,
193
- },
194
-
195
- mapping: {
196
- data: "dataset",
197
- },
198
-
199
- data: [],
200
- headers: [],
201
-
202
- responsive: {
203
- breakpoint: 900,
204
- },
205
-
206
- labels: {
207
- theListContainsNoEntries: "The list contains no entries",
208
- },
209
-
210
- classes: {
211
- control: "monster-theme-control-container-1",
212
- container: "",
213
- row: "monster-theme-control-row-1",
214
- },
215
-
216
- features: {
217
- settings: true,
218
- footer: true,
219
- autoInit: true,
220
- },
221
-
222
- templateMapping: {
223
- "row-key": null,
224
- "filter-id": null,
225
- },
226
- },
227
- initOptionsFromArguments.call(this),
228
- );
229
- }
230
-
231
- /**
232
- *
233
- * @param {string} selector
234
- * @return {NodeListOf<*>}
235
- */
236
- getGridElements(selector) {
237
- return this[gridElementSymbol].querySelectorAll(selector);
238
- }
239
-
240
- /**
241
- *
242
- * @return {string}
243
- */
244
- static getTag() {
245
- return "monster-datatable";
246
- }
247
-
248
- /**
249
- * @return {void}
250
- */
251
- disconnectedCallback() {
252
- super.disconnectedCallback();
253
- if (this?.[resizeObserverSymbol] instanceof ResizeObserver) {
254
- this[resizeObserverSymbol].disconnect();
255
- }
256
- }
257
-
258
- /**
259
- * @return {void}
260
- */
261
- connectedCallback() {
262
- const self = this;
263
- super.connectedCallback();
264
-
265
- this[resizeObserverSymbol] = new ResizeObserver((entries) => {
266
- updateGrid.call(self);
267
- });
268
-
269
- this[resizeObserverSymbol].observe(this.parentNode);
270
- }
271
-
272
- /**
273
- * @return void
274
- */
275
- [assembleMethodSymbol]() {
276
- const rawKey = this.getOption("templateMapping.row-key");
277
-
278
- if (rawKey === null) {
279
- if (this.id !== null && this.id !== "") {
280
- const rawKey = this.getOption("templateMapping.row-key");
281
- if (rawKey === null) {
282
- this.setOption("templateMapping.row-key", this.id + "-row");
283
- }
284
- } else {
285
- this.setOption("templateMapping.row-key", "row");
286
- }
287
- }
288
-
289
- if (this.id !== null && this.id !== "") {
290
- this.setOption("templateMapping.filter-id", "" + this.id + "-filter");
291
- } else {
292
- this.setOption("templateMapping.filter-id", "filter");
293
- }
294
-
295
- super[assembleMethodSymbol]();
296
-
297
- initControlReferences.call(this);
298
- initEventHandler.call(this);
299
-
300
- const selector = this.getOption("datasource.selector");
301
-
302
- if (isString(selector)) {
303
- const element = findElementWithSelectorUpwards(this, selector);
304
- if (element === null) {
305
- throw new Error("the selector must match exactly one element");
306
- }
307
-
308
- if (!isInstance(element, Datasource)) {
309
- throw new TypeError("the element must be a datasource");
310
- }
311
-
312
- this[datasourceLinkedElementSymbol] = element;
313
-
314
- queueMicrotask(() => {
315
- handleDataSourceChanges.call(this);
316
- element.datasource.attachObserver(
317
- new Observer(handleDataSourceChanges.bind(this)),
318
- );
319
- });
320
- }
321
-
322
- getHostConfig
323
- .call(this, getColumnVisibilityConfigKey)
324
- .then((config) => {
325
- const headerOrderMap = new Map();
326
-
327
- getHostConfig
328
- .call(this, getStoredOrderConfigKey)
329
- .then((orderConfig) => {
330
- if (isArray(orderConfig) || orderConfig.length > 0) {
331
- for (let i = 0; i < orderConfig.length; i++) {
332
- const item = orderConfig[i];
333
- const parts = item.split(" ");
334
- const field = parts[0];
335
- const direction = parts[1] || DIRECTION_ASC;
336
- headerOrderMap.set(field, direction);
337
- }
338
- }
339
- })
340
- .then(() => {
341
- try {
342
- initGridAndStructs.call(this, config, headerOrderMap);
343
- } catch (error) {
344
- addAttributeToken(
345
- this,
346
- ATTRIBUTE_ERRORMESSAGE,
347
- error?.message || error.toString(),
348
- );
349
- }
350
-
351
- updateColumnBar.call(this);
352
- })
353
- .catch((error) => {
354
- addAttributeToken(
355
- this,
356
- ATTRIBUTE_ERRORMESSAGE,
357
- error?.message || error.toString(),
358
- );
359
- });
360
- })
361
- .catch((error) => {
362
- addAttributeToken(
363
- this,
364
- ATTRIBUTE_ERRORMESSAGE,
365
- error?.message || error.toString(),
366
- );
367
- });
368
- }
369
-
370
- /**
371
- * @return {CSSStyleSheet[]}
372
- */
373
- static getCSSStyleSheet() {
374
- return [DatatableStyleSheet];
375
- }
376
-
377
- /**
378
- * Copy a row from the datatable
379
- *
380
- * @param {number|string} fromIndex
381
- * @param {number|string} toIndex
382
- * @return {DataTable}
383
- * @fires monster-datatable-row-copied
384
- */
385
- copyRow(fromIndex, toIndex) {
386
- const datasource = this[datasourceLinkedElementSymbol];
387
- if (!datasource) {
388
- return this;
389
- }
390
- let d = datasource.data;
391
- let c = clone(d);
392
-
393
- let rows = c;
394
- const mapping = this.getOption("mapping.data");
395
-
396
- if (mapping) {
397
- rows = c?.[mapping];
398
- }
399
-
400
- if (rows === undefined || rows === null) {
401
- rows = [];
402
- }
403
-
404
- if (toIndex === undefined) {
405
- toIndex = rows.length;
406
- }
407
-
408
- if (isString(fromIndex)) {
409
- fromIndex = parseInt(fromIndex);
410
- }
411
- if (isString(toIndex)) {
412
- toIndex = parseInt(toIndex);
413
- }
414
-
415
- if (toIndex < 0 || toIndex > rows.length) {
416
- throw new RangeError("index out of bounds");
417
- }
418
-
419
- validateArray(rows);
420
- validateInteger(fromIndex);
421
- validateInteger(toIndex);
422
-
423
- if (fromIndex < 0 || fromIndex >= rows.length) {
424
- throw new RangeError("index out of bounds");
425
- }
426
-
427
- rows.splice(toIndex, 0, clone(rows[fromIndex]));
428
- datasource.data = c;
429
-
430
- fireCustomEvent(this, "monster-datatable-row-copied", {
431
- index: toIndex,
432
- });
433
-
434
- return this;
435
- }
436
-
437
- /**
438
- * Remove a row from the datatable
439
- *
440
- * @param {number|string} index
441
- * @return {DataTable}
442
- * @fires monster-datatable-row-removed
443
- */
444
- removeRow(index) {
445
- const datasource = this[datasourceLinkedElementSymbol];
446
- if (!datasource) {
447
- return this;
448
- }
449
- let d = datasource.data;
450
- let c = clone(d);
451
-
452
- let rows = c;
453
- const mapping = this.getOption("mapping.data");
454
-
455
- if (mapping) {
456
- rows = c?.[mapping];
457
- }
458
-
459
- if (rows === undefined || rows === null) {
460
- rows = [];
461
- }
462
-
463
- if (isString(index)) {
464
- index = parseInt(index);
465
- }
466
-
467
- validateArray(rows);
468
- validateInteger(index);
469
-
470
- if (index < 0 || index >= rows.length) {
471
- throw new RangeError("index out of bounds");
472
- }
473
- if (mapping) {
474
- rows = c?.[mapping];
475
- }
476
-
477
- rows.splice(index, 1);
478
- datasource.data = c;
479
-
480
- fireCustomEvent(this, "monster-datatable-row-removed", {
481
- index: index,
482
- });
483
-
484
- return this;
485
- }
486
-
487
- /**
488
- * Add a row to the datatable
489
- *
490
- * @param {Object} data
491
- * @return {DataTable}
492
- *
493
- * @fires monster-datatable-row-added
494
- **/
495
- addRow(data) {
496
- const datasource = this[datasourceLinkedElementSymbol];
497
- if (!datasource) {
498
- return this;
499
- }
500
- let d = datasource.data;
501
- let c = clone(d);
502
-
503
- let rows = c;
504
-
505
- const mapping = this.getOption("mapping.data");
506
- if (mapping) {
507
- rows = c?.[mapping];
508
- }
509
-
510
- if (rows === undefined || rows === null) {
511
- rows = [];
512
- }
513
-
514
- validateArray(rows);
515
- validateObject(data);
516
-
517
- rows.push(data);
518
- datasource.data = c;
519
-
520
- fireCustomEvent(this, "monster-datatable-row-added", {
521
- index: rows.length - 1,
522
- });
523
-
524
- return this;
525
- }
151
+ /**
152
+ * This method is called by the `instanceof` operator.
153
+ * @return {symbol}
154
+ */
155
+ static get [instanceSymbol]() {
156
+ return Symbol.for("@schukai/monster/components/datatable@@instance");
157
+ }
158
+
159
+ /**
160
+ * To set the options via the HTML tag, the attribute `data-monster-options` must be used.
161
+ * @see {@link https://monsterjs.org/en/doc/#configurate-a-monster-control}
162
+ *
163
+ * The individual configuration values can be found in the table.
164
+ *
165
+ * @property {Object} templates Template definitions
166
+ * @property {string} templates.main Main template
167
+ * @property {Object} datasource Datasource configuration
168
+ * @property {string} datasource.selector Selector for the datasource
169
+ * @property {Object} mapping Mapping configuration
170
+ * @property {string} mapping.data Data mapping
171
+ * @property {Array} data Data
172
+ * @property {Array} headers Headers
173
+ * @property {Object} responsive Responsive configuration
174
+ * @property {number} responsive.breakpoint Breakpoint for responsive mode
175
+ * @property {Object} labels Labels
176
+ * @property {string} labels.theListContainsNoEntries Label for empty state
177
+ * @property {Object} classes Classes
178
+ * @property {string} classes.container Container class
179
+ * @property {Object} features Features
180
+ * @property {boolean} features.settings Settings feature
181
+ * @property {boolean} features.footer Footer feature
182
+ * @property {boolean} features.autoInit Auto init feature (init datasource automatically)
183
+ * @property {boolean} features.doubleClickCopyToClipboard Double click copy to clipboard feature
184
+ * @property {Object} templateMapping Template mapping
185
+ * @property {string} templateMapping.row-key Row key
186
+ * @property {string} templateMapping.filter-id Filter id
187
+ **/
188
+ get defaults() {
189
+ return Object.assign(
190
+ {},
191
+ super.defaults,
192
+ {
193
+ templates: {
194
+ main: getTemplate(),
195
+ emptyState: getEmptyTemplate(),
196
+ },
197
+
198
+ datasource: {
199
+ selector: null,
200
+ },
201
+
202
+ mapping: {
203
+ data: "dataset",
204
+ },
205
+
206
+ data: [],
207
+ headers: [],
208
+
209
+ responsive: {
210
+ breakpoint: 900,
211
+ },
212
+
213
+ labels: {
214
+ theListContainsNoEntries: "The list contains no entries",
215
+ copyAll: "Copy all",
216
+ },
217
+
218
+ classes: {
219
+ control: "monster-theme-control-container-1",
220
+ container: "",
221
+ row: "monster-theme-control-row-1",
222
+ },
223
+
224
+ features: {
225
+ settings: true,
226
+ footer: true,
227
+ autoInit: true,
228
+ doubleClickCopyToClipboard: true,
229
+ copyAll: true,
230
+ },
231
+
232
+ copy: {
233
+ delimiter: ";",
234
+ quoteOpen: '"',
235
+ quoteClose: '"',
236
+ rowBreak: "\n",
237
+ },
238
+
239
+ templateMapping: {
240
+ "row-key": null,
241
+ "filter-id": null,
242
+ },
243
+ },
244
+ initOptionsFromArguments.call(this),
245
+ );
246
+ }
247
+
248
+ /**
249
+ *
250
+ * @param {string} selector
251
+ * @return {NodeListOf<*>}
252
+ */
253
+ getGridElements(selector) {
254
+ return this[gridElementSymbol].querySelectorAll(selector);
255
+ }
256
+
257
+ /**
258
+ *
259
+ * @return {string}
260
+ */
261
+ static getTag() {
262
+ return "monster-datatable";
263
+ }
264
+
265
+ /**
266
+ * @return {void}
267
+ */
268
+ disconnectedCallback() {
269
+ super.disconnectedCallback();
270
+ if (this?.[resizeObserverSymbol] instanceof ResizeObserver) {
271
+ this[resizeObserverSymbol].disconnect();
272
+ }
273
+ }
274
+
275
+ /**
276
+ * @return {void}
277
+ */
278
+ connectedCallback() {
279
+ const self = this;
280
+ super.connectedCallback();
281
+
282
+ this[resizeObserverSymbol] = new ResizeObserver((entries) => {
283
+ updateGrid.call(self);
284
+ });
285
+
286
+ this[resizeObserverSymbol].observe(this.parentNode);
287
+ }
288
+
289
+ /**
290
+ * @return void
291
+ */
292
+ [assembleMethodSymbol]() {
293
+ const rawKey = this.getOption("templateMapping.row-key");
294
+
295
+ if (rawKey === null) {
296
+ if (this.id !== null && this.id !== "") {
297
+ const rawKey = this.getOption("templateMapping.row-key");
298
+ if (rawKey === null) {
299
+ this.setOption("templateMapping.row-key", this.id + "-row");
300
+ }
301
+ } else {
302
+ this.setOption("templateMapping.row-key", "row");
303
+ }
304
+ }
305
+
306
+ if (this.id !== null && this.id !== "") {
307
+ this.setOption("templateMapping.filter-id", "" + this.id + "-filter");
308
+ } else {
309
+ this.setOption("templateMapping.filter-id", "filter");
310
+ }
311
+
312
+ super[assembleMethodSymbol]();
313
+
314
+ initControlReferences.call(this);
315
+ initEventHandler.call(this);
316
+
317
+ const selector = this.getOption("datasource.selector");
318
+
319
+ if (isString(selector)) {
320
+ const element = findElementWithSelectorUpwards(this, selector);
321
+ if (element === null) {
322
+ throw new Error("the selector must match exactly one element");
323
+ }
324
+
325
+ if (!isInstance(element, Datasource)) {
326
+ throw new TypeError("the element must be a datasource");
327
+ }
328
+
329
+ this[datasourceLinkedElementSymbol] = element;
330
+
331
+ queueMicrotask(() => {
332
+ handleDataSourceChanges.call(this);
333
+ element.datasource.attachObserver(
334
+ new Observer(handleDataSourceChanges.bind(this)),
335
+ );
336
+ });
337
+ }
338
+
339
+ getHostConfig
340
+ .call(this, getColumnVisibilityConfigKey)
341
+ .then((config) => {
342
+ const headerOrderMap = new Map();
343
+
344
+ getHostConfig
345
+ .call(this, getStoredOrderConfigKey)
346
+ .then((orderConfig) => {
347
+ if (isArray(orderConfig) || orderConfig.length > 0) {
348
+ for (let i = 0; i < orderConfig.length; i++) {
349
+ const item = orderConfig[i];
350
+ const parts = item.split(" ");
351
+ const field = parts[0];
352
+ const direction = parts[1] || DIRECTION_ASC;
353
+ headerOrderMap.set(field, direction);
354
+ }
355
+ }
356
+ })
357
+ .then(() => {
358
+ try {
359
+ initGridAndStructs.call(this, config, headerOrderMap);
360
+ } catch (error) {
361
+ addAttributeToken(
362
+ this,
363
+ ATTRIBUTE_ERRORMESSAGE,
364
+ error?.message || error.toString(),
365
+ );
366
+ }
367
+
368
+ updateColumnBar.call(this);
369
+ })
370
+ .catch((error) => {
371
+ addAttributeToken(
372
+ this,
373
+ ATTRIBUTE_ERRORMESSAGE,
374
+ error?.message || error.toString(),
375
+ );
376
+ });
377
+ })
378
+ .catch((error) => {
379
+ addAttributeToken(
380
+ this,
381
+ ATTRIBUTE_ERRORMESSAGE,
382
+ error?.message || error.toString(),
383
+ );
384
+ });
385
+ }
386
+
387
+ /**
388
+ * @return {CSSStyleSheet[]}
389
+ */
390
+ static getCSSStyleSheet() {
391
+ return [DatatableStyleSheet];
392
+ }
393
+
394
+ /**
395
+ * Copy a row from the datatable
396
+ *
397
+ * @param {number|string} fromIndex
398
+ * @param {number|string} toIndex
399
+ * @return {DataTable}
400
+ * @fires monster-datatable-row-copied
401
+ */
402
+ copyRow(fromIndex, toIndex) {
403
+ const datasource = this[datasourceLinkedElementSymbol];
404
+ if (!datasource) {
405
+ return this;
406
+ }
407
+ let d = datasource.data;
408
+ let c = clone(d);
409
+
410
+ let rows = c;
411
+ const mapping = this.getOption("mapping.data");
412
+
413
+ if (mapping) {
414
+ rows = c?.[mapping];
415
+ }
416
+
417
+ if (rows === undefined || rows === null) {
418
+ rows = [];
419
+ }
420
+
421
+ if (toIndex === undefined) {
422
+ toIndex = rows.length;
423
+ }
424
+
425
+ if (isString(fromIndex)) {
426
+ fromIndex = parseInt(fromIndex);
427
+ }
428
+ if (isString(toIndex)) {
429
+ toIndex = parseInt(toIndex);
430
+ }
431
+
432
+ if (toIndex < 0 || toIndex > rows.length) {
433
+ throw new RangeError("index out of bounds");
434
+ }
435
+
436
+ validateArray(rows);
437
+ validateInteger(fromIndex);
438
+ validateInteger(toIndex);
439
+
440
+ if (fromIndex < 0 || fromIndex >= rows.length) {
441
+ throw new RangeError("index out of bounds");
442
+ }
443
+
444
+ rows.splice(toIndex, 0, clone(rows[fromIndex]));
445
+ datasource.data = c;
446
+
447
+ fireCustomEvent(this, "monster-datatable-row-copied", {
448
+ index: toIndex,
449
+ });
450
+
451
+ return this;
452
+ }
453
+
454
+ /**
455
+ * Remove a row from the datatable
456
+ *
457
+ * @param {number|string} index
458
+ * @return {DataTable}
459
+ * @fires monster-datatable-row-removed
460
+ */
461
+ removeRow(index) {
462
+ const datasource = this[datasourceLinkedElementSymbol];
463
+ if (!datasource) {
464
+ return this;
465
+ }
466
+ let d = datasource.data;
467
+ let c = clone(d);
468
+
469
+ let rows = c;
470
+ const mapping = this.getOption("mapping.data");
471
+
472
+ if (mapping) {
473
+ rows = c?.[mapping];
474
+ }
475
+
476
+ if (rows === undefined || rows === null) {
477
+ rows = [];
478
+ }
479
+
480
+ if (isString(index)) {
481
+ index = parseInt(index);
482
+ }
483
+
484
+ validateArray(rows);
485
+ validateInteger(index);
486
+
487
+ if (index < 0 || index >= rows.length) {
488
+ throw new RangeError("index out of bounds");
489
+ }
490
+ if (mapping) {
491
+ rows = c?.[mapping];
492
+ }
493
+
494
+ rows.splice(index, 1);
495
+ datasource.data = c;
496
+
497
+ fireCustomEvent(this, "monster-datatable-row-removed", {
498
+ index: index,
499
+ });
500
+
501
+ return this;
502
+ }
503
+
504
+ /**
505
+ * Add a row to the datatable
506
+ *
507
+ * @param {Object} data
508
+ * @return {DataTable}
509
+ *
510
+ * @fires monster-datatable-row-added
511
+ **/
512
+ addRow(data) {
513
+ const datasource = this[datasourceLinkedElementSymbol];
514
+ if (!datasource) {
515
+ return this;
516
+ }
517
+ let d = datasource.data;
518
+ let c = clone(d);
519
+
520
+ let rows = c;
521
+
522
+ const mapping = this.getOption("mapping.data");
523
+ if (mapping) {
524
+ rows = c?.[mapping];
525
+ }
526
+
527
+ if (rows === undefined || rows === null) {
528
+ rows = [];
529
+ }
530
+
531
+ validateArray(rows);
532
+ validateObject(data);
533
+
534
+ rows.push(data);
535
+ datasource.data = c;
536
+
537
+ fireCustomEvent(this, "monster-datatable-row-added", {
538
+ index: rows.length - 1,
539
+ });
540
+
541
+ return this;
542
+ }
526
543
  }
527
544
 
528
545
  /**
@@ -530,7 +547,7 @@ class DataTable extends CustomElement {
530
547
  * @return {string}
531
548
  */
532
549
  function getColumnVisibilityConfigKey() {
533
- return generateUniqueConfigKey("datatable", this?.id, "columns-visibility");
550
+ return generateUniqueConfigKey("datatable", this?.id, "columns-visibility");
534
551
  }
535
552
 
536
553
  /**
@@ -538,7 +555,7 @@ function getColumnVisibilityConfigKey() {
538
555
  * @return {string}
539
556
  */
540
557
  function getFilterConfigKey() {
541
- return generateUniqueConfigKey("datatable", this?.id, "filter");
558
+ return generateUniqueConfigKey("datatable", this?.id, "filter");
542
559
  }
543
560
 
544
561
  /**
@@ -546,287 +563,399 @@ function getFilterConfigKey() {
546
563
  * @return {Promise}
547
564
  */
548
565
  function getHostConfig(callback) {
549
- const host = findElementWithSelectorUpwards(this, "monster-host");
550
-
551
- if (!(host && this.id)) {
552
- return Promise.resolve({});
553
- }
554
-
555
- if (!host || !isFunction(host?.getConfig)) {
556
- throw new TypeError("the host must be a monster-host");
557
- }
558
-
559
- const configKey = callback.call(this);
560
- return host.hasConfig(configKey).then((hasConfig) => {
561
- if (hasConfig) {
562
- return host.getConfig(configKey);
563
- } else {
564
- return {};
565
- }
566
- });
566
+ const host = findElementWithSelectorUpwards(this, "monster-host");
567
+
568
+ if (!(host && this.id)) {
569
+ return Promise.resolve({});
570
+ }
571
+
572
+ if (!host || !isFunction(host?.getConfig)) {
573
+ throw new TypeError("the host must be a monster-host");
574
+ }
575
+
576
+ const configKey = callback.call(this);
577
+ return host.hasConfig(configKey).then((hasConfig) => {
578
+ if (hasConfig) {
579
+ return host.getConfig(configKey);
580
+ } else {
581
+ return {};
582
+ }
583
+ });
567
584
  }
568
585
 
569
586
  /**
570
587
  * @private
571
588
  */
572
589
  function updateColumnBar() {
573
- if (!this[columnBarElementSymbol]) {
574
- return;
575
- }
576
-
577
- const columns = [];
578
- for (const header of this.getOption("headers")) {
579
- const mode = header.getInternal("mode");
580
-
581
- if (mode === ATTRIBUTE_DATATABLE_MODE_FIXED) {
582
- continue;
583
- }
584
-
585
- columns.push({
586
- visible: mode !== ATTRIBUTE_DATATABLE_MODE_HIDDEN,
587
- name: header.label,
588
- index: header.index,
589
- });
590
- }
591
-
592
- this[columnBarElementSymbol].setOption("columns", columns);
590
+ if (!this[columnBarElementSymbol]) {
591
+ return;
592
+ }
593
+
594
+ const columns = [];
595
+ for (const header of this.getOption("headers")) {
596
+ const mode = header.getInternal("mode");
597
+
598
+ if (mode === ATTRIBUTE_DATATABLE_MODE_FIXED) {
599
+ continue;
600
+ }
601
+
602
+ columns.push({
603
+ visible: mode !== ATTRIBUTE_DATATABLE_MODE_HIDDEN,
604
+ name: header.label,
605
+ index: header.index,
606
+ });
607
+ }
608
+
609
+ this[columnBarElementSymbol].setOption("columns", columns);
593
610
  }
594
611
 
595
612
  /**
596
613
  * @private
597
614
  */
598
615
  function updateHeaderFromColumnBar() {
599
- if (!this[columnBarElementSymbol]) {
600
- return;
601
- }
616
+ if (!this[columnBarElementSymbol]) {
617
+ return;
618
+ }
602
619
 
603
- const options = this[columnBarElementSymbol].getOption("columns");
604
- if (!isArray(options)) return;
620
+ const options = this[columnBarElementSymbol].getOption("columns");
621
+ if (!isArray(options)) return;
605
622
 
606
- const invisibleMap = {};
623
+ const invisibleMap = {};
607
624
 
608
- for (let i = 0; i < options.length; i++) {
609
- const option = options[i];
610
- invisibleMap[option.index] = option.visible;
611
- }
625
+ for (let i = 0; i < options.length; i++) {
626
+ const option = options[i];
627
+ invisibleMap[option.index] = option.visible;
628
+ }
612
629
 
613
- for (const header of this.getOption("headers")) {
614
- const mode = header.getInternal("mode");
630
+ for (const header of this.getOption("headers")) {
631
+ const mode = header.getInternal("mode");
615
632
 
616
- if (mode === ATTRIBUTE_DATATABLE_MODE_FIXED) {
617
- continue;
618
- }
633
+ if (mode === ATTRIBUTE_DATATABLE_MODE_FIXED) {
634
+ continue;
635
+ }
619
636
 
620
- if (invisibleMap[header.index] === false) {
621
- header.setInternal("mode", ATTRIBUTE_DATATABLE_MODE_HIDDEN);
622
- } else {
623
- header.setInternal("mode", ATTRIBUTE_DATATABLE_MODE_VISIBLE);
624
- }
625
- }
637
+ if (invisibleMap[header.index] === false) {
638
+ header.setInternal("mode", ATTRIBUTE_DATATABLE_MODE_HIDDEN);
639
+ } else {
640
+ header.setInternal("mode", ATTRIBUTE_DATATABLE_MODE_VISIBLE);
641
+ }
642
+ }
626
643
  }
627
644
 
628
645
  /**
629
646
  * @private
630
647
  */
631
648
  function updateConfigColumnBar() {
632
- if (!this[columnBarElementSymbol]) {
633
- return;
634
- }
635
-
636
- const options = this[columnBarElementSymbol].getOption("columns");
637
- if (!isArray(options)) return;
638
-
639
- const map = {};
640
- for (let i = 0; i < options.length; i++) {
641
- const option = options[i];
642
- map[option.name] = option.visible;
643
- }
644
-
645
- const host = findElementWithSelectorUpwards(this, "monster-host");
646
- if (!(host && this.id)) {
647
- return;
648
- }
649
- const configKey = getColumnVisibilityConfigKey.call(this);
650
-
651
- try {
652
- host.setConfig(configKey, map);
653
- } catch (error) {
654
- addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, String(error));
655
- }
649
+ if (!this[columnBarElementSymbol]) {
650
+ return;
651
+ }
652
+
653
+ const options = this[columnBarElementSymbol].getOption("columns");
654
+ if (!isArray(options)) return;
655
+
656
+ const map = {};
657
+ for (let i = 0; i < options.length; i++) {
658
+ const option = options[i];
659
+ map[option.name] = option.visible;
660
+ }
661
+
662
+ const host = findElementWithSelectorUpwards(this, "monster-host");
663
+ if (!(host && this.id)) {
664
+ return;
665
+ }
666
+ const configKey = getColumnVisibilityConfigKey.call(this);
667
+
668
+ try {
669
+ host.setConfig(configKey, map);
670
+ } catch (error) {
671
+ addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, String(error));
672
+ }
656
673
  }
657
674
 
658
675
  /**
659
676
  * @private
660
677
  */
661
678
  function initEventHandler() {
662
- const self = this;
663
-
664
- self[columnBarElementSymbol].attachObserver(
665
- new Observer((e) => {
666
- updateHeaderFromColumnBar.call(self);
667
- updateGrid.call(self);
668
- updateConfigColumnBar.call(self);
669
- }),
670
- );
671
-
672
- self[gridHeadersElementSymbol].addEventListener("click", function (event) {
673
- let element = null;
674
- const datasource = self[datasourceLinkedElementSymbol];
675
- if (!datasource) {
676
- return;
677
- }
678
-
679
- element = findTargetElementFromEvent(event, ATTRIBUTE_DATATABLE_SORTABLE);
680
- if (element) {
681
- const index = element.parentNode.getAttribute(ATTRIBUTE_DATATABLE_INDEX);
682
- const headers = self.getOption("headers");
683
-
684
- event.preventDefault();
685
-
686
- headers[index].changeDirection();
687
-
688
- queueMicrotask(function () {
689
- /** hotfix, normally this should be done via the updater, no idea why this is not possible. */
690
- element.setAttribute(
691
- ATTRIBUTE_DATATABLE_SORTABLE,
692
- `${headers[index].field} ${headers[index].direction}`,
693
- );
694
-
695
- storeOrderStatement.call(self, true);
696
- });
697
- }
698
- });
679
+ const self = this;
680
+
681
+ const quoteOpenChar = this.getOption("copy.quoteOpen");
682
+ const quoteCloseChar = this.getOption("copy.quoteClose");
683
+ const delimiterChar = this.getOption("copy.delimiter");
684
+ const rowBreak = this.getOption("copy.rowBreak");
685
+
686
+ self[columnBarElementSymbol].attachObserver(
687
+ new Observer((e) => {
688
+ updateHeaderFromColumnBar.call(self);
689
+ updateGrid.call(self);
690
+ updateConfigColumnBar.call(self);
691
+ }),
692
+ );
693
+
694
+ self[gridHeadersElementSymbol].addEventListener("click", function (event) {
695
+ let element = null;
696
+ const datasource = self[datasourceLinkedElementSymbol];
697
+ if (!datasource) {
698
+ return;
699
+ }
700
+
701
+ element = findTargetElementFromEvent(event, ATTRIBUTE_DATATABLE_SORTABLE);
702
+ if (element) {
703
+ const index = element.parentNode.getAttribute(ATTRIBUTE_DATATABLE_INDEX);
704
+ const headers = self.getOption("headers");
705
+
706
+ event.preventDefault();
707
+
708
+ headers[index].changeDirection();
709
+
710
+ queueMicrotask(function () {
711
+ /** hotfix, normally this should be done via the updater, no idea why this is not possible. */
712
+ element.setAttribute(
713
+ ATTRIBUTE_DATATABLE_SORTABLE,
714
+ `${headers[index].field} ${headers[index].direction}`,
715
+ );
716
+
717
+ storeOrderStatement.call(self, true);
718
+ });
719
+ }
720
+ });
721
+
722
+
723
+
724
+ const eventHandlerDoubleClickCopyToClipboard = (event) => {
725
+ const element = findTargetElementFromEvent(event, "data-monster-head");
726
+ if (element) {
727
+
728
+
729
+ let text = "";
730
+
731
+ if (event.shiftKey) {
732
+ const index = element.getAttribute("data-monster-insert-reference");
733
+ if (index) {
734
+ const cols = self.getGridElements(`[data-monster-insert-reference="${index}"]`);
735
+
736
+ const colTexts = []
737
+ for (let i = 0; i < cols.length; i++) {
738
+ const col = cols[i];
739
+
740
+ if (col.querySelector("monster-button-bar") || col.querySelector("monster-button")) {
741
+ continue;
742
+ }
743
+
744
+ if (col.textContent) {
745
+ colTexts.push(quoteOpenChar+col.textContent.trim()+quoteCloseChar);
746
+ }
747
+ }
748
+
749
+ text = colTexts.join(delimiterChar);
750
+
751
+ }
752
+
753
+
754
+ } else {
755
+ if (element.querySelector("monster-button-bar") || element.querySelector("monster-button")) {
756
+ return;
757
+ }
758
+
759
+ text = element.textContent.trim();
760
+
761
+ }
762
+
763
+ if (getWindow().navigator.clipboard && text) {
764
+ getWindow().navigator.clipboard.writeText(text).then(
765
+ () => {
766
+ },
767
+ (err) => {
768
+ }
769
+ );
770
+ }
771
+ }
772
+ }
773
+
774
+ if (self.getOption("features.doubleClickCopyToClipboard")) {
775
+ self[gridElementSymbol].addEventListener("dblclick", eventHandlerDoubleClickCopyToClipboard);
776
+ }
777
+
778
+ if (self.getOption("features.copyAll") && this[copyAllElementSymbol]) {
779
+ this[copyAllElementSymbol].addEventListener("click", (event) => {
780
+ event.preventDefault();
781
+
782
+ const table = [];
783
+ let currentRow = [];
784
+ let currentIndex= null;
785
+
786
+ const cols = self.getGridElements(`[data-monster-insert-reference]`);
787
+ for (let i = 0; i < cols.length; i++) {
788
+ const col = cols[i];
789
+
790
+ const index = col.getAttribute("data-monster-insert-reference");
791
+ if (currentIndex !== index) {
792
+ if (currentRow.length > 0) {
793
+ table.push(currentRow);
794
+ }
795
+ currentRow = [];
796
+ currentIndex = index;
797
+ }
798
+
799
+ if (col.querySelector("monster-button-bar") || col.querySelector("monster-button")) {
800
+ continue;
801
+ }
802
+
803
+ if (col.textContent) {
804
+ currentRow.push(quoteOpenChar+col.textContent.trim()+quoteCloseChar);
805
+ }
806
+
807
+ }
808
+
809
+ if(table.length > 0) {
810
+ const text = table.map(row => row.join(delimiterChar)).join(rowBreak);
811
+ if (getWindow().navigator.clipboard && text) {
812
+ getWindow().navigator.clipboard.writeText(text).then(
813
+ () => {
814
+ },
815
+ (err) => {
816
+ }
817
+ );
818
+ }
819
+ }
820
+
821
+ });
822
+
823
+
824
+ }
825
+
826
+
827
+
699
828
  }
700
829
 
701
830
  /**
702
831
  * @private
703
832
  */
704
833
  function initGridAndStructs(hostConfig, headerOrderMap) {
705
- const rowID = this.getOption("templateMapping.row-key");
706
-
707
- if (!this[gridElementSymbol]) {
708
- throw new Error("no grid element is defined");
709
- }
710
-
711
- let template;
712
- getSlottedElements.call(this).forEach((e) => {
713
- if (e instanceof HTMLTemplateElement && e.id === rowID) {
714
- template = e;
715
- }
716
- });
717
-
718
- if (!template) {
719
- throw new Error("no template is defined");
720
- }
721
-
722
- const rowCount = template.content.children.length;
723
-
724
- const headers = [];
725
-
726
- for (let i = 0; i < rowCount; i++) {
727
- let hClass = "";
728
- const row = template.content.children[i];
729
-
730
- let mode = "";
731
- if (row.hasAttribute(ATTRIBUTE_DATATABLE_MODE)) {
732
- mode = row.getAttribute(ATTRIBUTE_DATATABLE_MODE);
733
- }
734
-
735
- let grid = row.getAttribute(ATTRIBUTE_DATATABLE_GRID_TEMPLATE);
736
- if (!grid || grid === "" || grid === "auto") {
737
- grid = "minmax(0, 1fr)";
738
- }
739
-
740
- let label = "";
741
- let labelKey = "";
742
-
743
- if (row.hasAttribute(ATTRIBUTE_DATATABLE_HEAD)) {
744
- label = row.getAttribute(ATTRIBUTE_DATATABLE_HEAD);
745
- labelKey = label;
746
-
747
- try {
748
- if (label.startsWith("i18n:")) {
749
- label = label.substring(5, label.length);
750
- label = getDocumentTranslations().getText(label, label);
751
- }
752
- } catch (e) {
753
- label = "i18n error " + label;
754
- }
755
- }
756
-
757
- if (!label) {
758
- label = i + 1 + "";
759
- mode = ATTRIBUTE_DATATABLE_MODE_FIXED;
760
- labelKey = label;
761
- }
762
-
763
- if (isObject(hostConfig) && hostConfig.hasOwnProperty(label)) {
764
- if (hostConfig[label] === false) {
765
- mode = ATTRIBUTE_DATATABLE_MODE_HIDDEN;
766
- } else {
767
- mode = ATTRIBUTE_DATATABLE_MODE_VISIBLE;
768
- }
769
- }
770
-
771
- let align = "";
772
- if (row.hasAttribute(ATTRIBUTE_DATATABLE_ALIGN)) {
773
- align = row.getAttribute(ATTRIBUTE_DATATABLE_ALIGN);
774
- }
775
-
776
- switch (align) {
777
- case "center":
778
- hClass = "flex-center";
779
- break;
780
- case "end":
781
- hClass = "flex-end";
782
- break;
783
- case "start":
784
- hClass = "flex-start";
785
- break;
786
- default:
787
- hClass = "flex-start";
788
- }
789
-
790
- let field = "";
791
- let direction = DIRECTION_NONE;
792
- if (row.hasAttribute(ATTRIBUTE_DATATABLE_SORTABLE)) {
793
- field = row.getAttribute(ATTRIBUTE_DATATABLE_SORTABLE).trim();
794
- const parts = field.split(" ").map((item) => item.trim());
795
- field = parts[0];
796
-
797
- if (headerOrderMap.has(field)) {
798
- direction = headerOrderMap.get(field);
799
- } else if (
800
- parts.length === 2 &&
801
- [DIRECTION_ASC, DIRECTION_DESC].indexOf(parts[1]) !== -1
802
- ) {
803
- direction = parts[1];
804
- }
805
- }
806
-
807
- if (mode === ATTRIBUTE_DATATABLE_MODE_HIDDEN) {
808
- hClass += " hidden";
809
- }
810
-
811
- const header = new Header();
812
- header.setInternals({
813
- field: field,
814
- label: label,
815
- classes: hClass,
816
- index: i,
817
- mode: mode,
818
- grid: grid,
819
- labelKey: labelKey,
820
- direction: direction,
821
- });
822
-
823
- headers.push(header);
824
- }
825
-
826
- this.setOption("headers", headers);
827
- queueMicrotask(() => {
828
- storeOrderStatement.call(this, this.getOption("features.autoInit"));
829
- });
834
+ const rowID = this.getOption("templateMapping.row-key");
835
+
836
+ if (!this[gridElementSymbol]) {
837
+ throw new Error("no grid element is defined");
838
+ }
839
+
840
+ let template;
841
+ getSlottedElements.call(this).forEach((e) => {
842
+ if (e instanceof HTMLTemplateElement && e.id === rowID) {
843
+ template = e;
844
+ }
845
+ });
846
+
847
+ if (!template) {
848
+ throw new Error("no template is defined");
849
+ }
850
+
851
+ const rowCount = template.content.children.length;
852
+
853
+ const headers = [];
854
+
855
+ for (let i = 0; i < rowCount; i++) {
856
+ let hClass = "";
857
+ const row = template.content.children[i];
858
+
859
+ let mode = "";
860
+ if (row.hasAttribute(ATTRIBUTE_DATATABLE_MODE)) {
861
+ mode = row.getAttribute(ATTRIBUTE_DATATABLE_MODE);
862
+ }
863
+
864
+ let grid = row.getAttribute(ATTRIBUTE_DATATABLE_GRID_TEMPLATE);
865
+ if (!grid || grid === "" || grid === "auto") {
866
+ grid = "minmax(0, 1fr)";
867
+ }
868
+
869
+ let label = "";
870
+ let labelKey = "";
871
+
872
+ if (row.hasAttribute(ATTRIBUTE_DATATABLE_HEAD)) {
873
+ label = row.getAttribute(ATTRIBUTE_DATATABLE_HEAD);
874
+ labelKey = label;
875
+
876
+ try {
877
+ if (label.startsWith("i18n:")) {
878
+ label = label.substring(5, label.length);
879
+ label = getDocumentTranslations().getText(label, label);
880
+ }
881
+ } catch (e) {
882
+ label = "i18n error " + label;
883
+ }
884
+ }
885
+
886
+ if (!label) {
887
+ label = i + 1 + "";
888
+ mode = ATTRIBUTE_DATATABLE_MODE_FIXED;
889
+ labelKey = label;
890
+ }
891
+
892
+ if (isObject(hostConfig) && hostConfig.hasOwnProperty(label)) {
893
+ if (hostConfig[label] === false) {
894
+ mode = ATTRIBUTE_DATATABLE_MODE_HIDDEN;
895
+ } else {
896
+ mode = ATTRIBUTE_DATATABLE_MODE_VISIBLE;
897
+ }
898
+ }
899
+
900
+ let align = "";
901
+ if (row.hasAttribute(ATTRIBUTE_DATATABLE_ALIGN)) {
902
+ align = row.getAttribute(ATTRIBUTE_DATATABLE_ALIGN);
903
+ }
904
+
905
+ switch (align) {
906
+ case "center":
907
+ hClass = "flex-center";
908
+ break;
909
+ case "end":
910
+ hClass = "flex-end";
911
+ break;
912
+ case "start":
913
+ hClass = "flex-start";
914
+ break;
915
+ default:
916
+ hClass = "flex-start";
917
+ }
918
+
919
+ let field = "";
920
+ let direction = DIRECTION_NONE;
921
+ if (row.hasAttribute(ATTRIBUTE_DATATABLE_SORTABLE)) {
922
+ field = row.getAttribute(ATTRIBUTE_DATATABLE_SORTABLE).trim();
923
+ const parts = field.split(" ").map((item) => item.trim());
924
+ field = parts[0];
925
+
926
+ if (headerOrderMap.has(field)) {
927
+ direction = headerOrderMap.get(field);
928
+ } else if (
929
+ parts.length === 2 &&
930
+ [DIRECTION_ASC, DIRECTION_DESC].indexOf(parts[1]) !== -1
931
+ ) {
932
+ direction = parts[1];
933
+ }
934
+ }
935
+
936
+ if (mode === ATTRIBUTE_DATATABLE_MODE_HIDDEN) {
937
+ hClass += " hidden";
938
+ }
939
+
940
+ const header = new Header();
941
+ header.setInternals({
942
+ field: field,
943
+ label: label,
944
+ classes: hClass,
945
+ index: i,
946
+ mode: mode,
947
+ grid: grid,
948
+ labelKey: labelKey,
949
+ direction: direction,
950
+ });
951
+
952
+ headers.push(header);
953
+ }
954
+
955
+ this.setOption("headers", headers);
956
+ queueMicrotask(() => {
957
+ storeOrderStatement.call(this, this.getOption("features.autoInit"));
958
+ });
830
959
  }
831
960
 
832
961
  /**
@@ -834,79 +963,79 @@ function initGridAndStructs(hostConfig, headerOrderMap) {
834
963
  * @return {string}
835
964
  */
836
965
  export function getStoredOrderConfigKey() {
837
- return generateUniqueConfigKey("datatable", this?.id, "stored-order");
966
+ return generateUniqueConfigKey("datatable", this?.id, "stored-order");
838
967
  }
839
968
 
840
969
  /**
841
970
  * @private
842
971
  */
843
972
  function storeOrderStatement(doFetch) {
844
- const headers = this.getOption("headers");
845
- const statement = createOrderStatement(headers);
846
- setDataSource.call(this, { orderBy: statement }, doFetch);
973
+ const headers = this.getOption("headers");
974
+ const statement = createOrderStatement(headers);
975
+ setDataSource.call(this, {orderBy: statement}, doFetch);
847
976
 
848
- const host = findElementWithSelectorUpwards(this, "monster-host");
849
- if (!(host && this.id)) {
850
- return;
851
- }
977
+ const host = findElementWithSelectorUpwards(this, "monster-host");
978
+ if (!(host && this.id)) {
979
+ return;
980
+ }
852
981
 
853
- const configKey = getStoredOrderConfigKey.call(this);
982
+ const configKey = getStoredOrderConfigKey.call(this);
854
983
 
855
- // statement explode with , and remove all empty
856
- const list = statement.split(",").filter((item) => item.trim() !== "");
857
- if (list.length === 0) {
858
- return;
859
- }
984
+ // statement explode with , and remove all empty
985
+ const list = statement.split(",").filter((item) => item.trim() !== "");
986
+ if (list.length === 0) {
987
+ return;
988
+ }
860
989
 
861
- host.setConfig(configKey, list);
990
+ host.setConfig(configKey, list);
862
991
  }
863
992
 
864
993
  /**
865
994
  * @private
866
995
  */
867
996
  function updateGrid() {
868
- if (!this[gridElementSymbol]) {
869
- throw new Error("no grid element is defined");
870
- }
871
-
872
- let gridTemplateColumns = "";
873
-
874
- const headers = this.getOption("headers");
875
-
876
- let styles = "";
877
-
878
- for (let i = 0; i < headers.length; i++) {
879
- const header = headers[i];
880
-
881
- if (header.mode === ATTRIBUTE_DATATABLE_MODE_HIDDEN) {
882
- styles += `[data-monster-role=datatable]>[data-monster-head="${header.labelKey}"] { display: none; }\n`;
883
- styles += `[data-monster-role=datatable-headers]>[data-monster-index="${header.index}"] { display: none; }\n`;
884
- } else {
885
- gridTemplateColumns += `${header.grid} `;
886
- }
887
- }
888
-
889
- const sheet = new CSSStyleSheet();
890
- if (styles !== "") sheet.replaceSync(styles);
891
- this.shadowRoot.adoptedStyleSheets = [...DataTable.getCSSStyleSheet(), sheet];
892
-
893
- const bodyWidth = this.parentNode.clientWidth;
894
-
895
- const breakpoint = this.getOption("responsive.breakpoint");
896
- this[dataControlElementSymbol].classList.toggle(
897
- "small",
898
- bodyWidth <= breakpoint,
899
- );
900
-
901
- if (bodyWidth > breakpoint) {
902
- this[gridElementSymbol].style.gridTemplateColumns =
903
- `${gridTemplateColumns}`;
904
- this[gridHeadersElementSymbol].style.gridTemplateColumns =
905
- `${gridTemplateColumns}`;
906
- } else {
907
- this[gridElementSymbol].style.gridTemplateColumns = "auto";
908
- this[gridHeadersElementSymbol].style.gridTemplateColumns = "auto";
909
- }
997
+ if (!this[gridElementSymbol]) {
998
+ throw new Error("no grid element is defined");
999
+ }
1000
+
1001
+ let gridTemplateColumns = "";
1002
+
1003
+ const headers = this.getOption("headers");
1004
+
1005
+ let styles = "";
1006
+
1007
+ for (let i = 0; i < headers.length; i++) {
1008
+ const header = headers[i];
1009
+
1010
+ if (header.mode === ATTRIBUTE_DATATABLE_MODE_HIDDEN) {
1011
+ styles += `[data-monster-role=datatable]>[data-monster-head="${header.labelKey}"] { display: none; }\n`;
1012
+ styles += `[data-monster-role=datatable-headers]>[data-monster-index="${header.index}"] { display: none; }\n`;
1013
+ } else {
1014
+ gridTemplateColumns += `${header.grid} `;
1015
+ }
1016
+ }
1017
+
1018
+ const sheet = new CSSStyleSheet();
1019
+ if (styles !== "") sheet.replaceSync(styles);
1020
+ this.shadowRoot.adoptedStyleSheets = [...DataTable.getCSSStyleSheet(), sheet];
1021
+
1022
+ const bodyWidth = this.parentNode.clientWidth;
1023
+
1024
+ const breakpoint = this.getOption("responsive.breakpoint");
1025
+ this[dataControlElementSymbol].classList.toggle(
1026
+ "small",
1027
+ bodyWidth <= breakpoint,
1028
+ );
1029
+
1030
+ if (bodyWidth > breakpoint) {
1031
+ this[gridElementSymbol].style.gridTemplateColumns =
1032
+ `${gridTemplateColumns}`;
1033
+ this[gridHeadersElementSymbol].style.gridTemplateColumns =
1034
+ `${gridTemplateColumns}`;
1035
+ } else {
1036
+ this[gridElementSymbol].style.gridTemplateColumns = "auto";
1037
+ this[gridHeadersElementSymbol].style.gridTemplateColumns = "auto";
1038
+ }
910
1039
  }
911
1040
 
912
1041
  /**
@@ -914,20 +1043,20 @@ function updateGrid() {
914
1043
  * @param {Header[]} headers
915
1044
  * @param {bool} doFetch
916
1045
  */
917
- function setDataSource({ orderBy }, doFetch) {
918
- const datasource = this[datasourceLinkedElementSymbol];
1046
+ function setDataSource({orderBy}, doFetch) {
1047
+ const datasource = this[datasourceLinkedElementSymbol];
919
1048
 
920
- if (!datasource) {
921
- return;
922
- }
1049
+ if (!datasource) {
1050
+ return;
1051
+ }
923
1052
 
924
- if (isFunction(datasource?.setParameters)) {
925
- datasource.setParameters({ orderBy });
926
- }
1053
+ if (isFunction(datasource?.setParameters)) {
1054
+ datasource.setParameters({orderBy});
1055
+ }
927
1056
 
928
- if (doFetch !== false && isFunction(datasource?.fetch)) {
929
- datasource.fetch();
930
- }
1057
+ if (doFetch !== false && isFunction(datasource?.fetch)) {
1058
+ datasource.fetch();
1059
+ }
931
1060
  }
932
1061
 
933
1062
  /**
@@ -935,24 +1064,28 @@ function setDataSource({ orderBy }, doFetch) {
935
1064
  * @return {DataTable}
936
1065
  */
937
1066
  function initControlReferences() {
938
- if (!this.shadowRoot) {
939
- throw new Error("no shadow-root is defined");
940
- }
941
-
942
- this[dataControlElementSymbol] = this.shadowRoot.querySelector(
943
- "[data-monster-role=control]",
944
- );
945
-
946
- this[gridElementSymbol] = this.shadowRoot.querySelector(
947
- "[data-monster-role=datatable]",
948
- );
949
- this[gridHeadersElementSymbol] = this.shadowRoot.querySelector(
950
- "[data-monster-role=datatable-headers]",
951
- );
952
- this[columnBarElementSymbol] =
953
- this.shadowRoot.querySelector("monster-column-bar");
954
-
955
- return this;
1067
+ if (!this.shadowRoot) {
1068
+ throw new Error("no shadow-root is defined");
1069
+ }
1070
+
1071
+ this[dataControlElementSymbol] = this.shadowRoot.querySelector(
1072
+ "[data-monster-role=control]",
1073
+ );
1074
+
1075
+ this[gridElementSymbol] = this.shadowRoot.querySelector(
1076
+ "[data-monster-role=datatable]",
1077
+ );
1078
+ this[gridHeadersElementSymbol] = this.shadowRoot.querySelector(
1079
+ "[data-monster-role=datatable-headers]",
1080
+ );
1081
+ this[columnBarElementSymbol] =
1082
+ this.shadowRoot.querySelector("monster-column-bar");
1083
+
1084
+ this[copyAllElementSymbol] = this.shadowRoot.querySelector(
1085
+ "[data-monster-role=copy-all]",
1086
+ );
1087
+
1088
+ return this;
956
1089
  }
957
1090
 
958
1091
  /**
@@ -962,22 +1095,22 @@ function initControlReferences() {
962
1095
  * @throws {Error} the datasource could not be initialized
963
1096
  */
964
1097
  function initOptionsFromArguments() {
965
- const options = {};
966
- const selector = this.getAttribute(ATTRIBUTE_DATASOURCE_SELECTOR);
967
-
968
- if (selector) {
969
- options.datasource = { selector: selector };
970
- }
971
-
972
- const breakpoint = this.getAttribute(
973
- ATTRIBUTE_DATATABLE_RESPONSIVE_BREAKPOINT,
974
- );
975
- if (breakpoint) {
976
- options.responsive = {};
977
- options.responsive.breakpoint = parseInt(breakpoint);
978
- }
979
-
980
- return options;
1098
+ const options = {};
1099
+ const selector = this.getAttribute(ATTRIBUTE_DATASOURCE_SELECTOR);
1100
+
1101
+ if (selector) {
1102
+ options.datasource = {selector: selector};
1103
+ }
1104
+
1105
+ const breakpoint = this.getAttribute(
1106
+ ATTRIBUTE_DATATABLE_RESPONSIVE_BREAKPOINT,
1107
+ );
1108
+ if (breakpoint) {
1109
+ options.responsive = {};
1110
+ options.responsive.breakpoint = parseInt(breakpoint);
1111
+ }
1112
+
1113
+ return options;
981
1114
  }
982
1115
 
983
1116
  /**
@@ -985,7 +1118,7 @@ function initOptionsFromArguments() {
985
1118
  * @return {string}
986
1119
  */
987
1120
  function getEmptyTemplate() {
988
- return `<monster-state data-monster-role="empty-without-action">
1121
+ return `<monster-state data-monster-role="empty-without-action">
989
1122
  <div part="visual">
990
1123
  <svg width="4rem" height="4rem" fill="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
991
1124
  <path d="m21.5 22h-19c-1.378 0-2.5-1.121-2.5-2.5v-7c0-.07.015-.141.044-.205l3.969-8.82c.404-.896 1.299-1.475 2.28-1.475h11.414c.981 0 1.876.579 2.28 1.475l3.969 8.82c.029.064.044.135.044.205v7c0 1.379-1.122 2.5-2.5 2.5zm-20.5-9.393v6.893c0 .827.673 1.5 1.5 1.5h19c.827 0 1.5-.673 1.5-1.5v-6.893l-3.925-8.723c-.242-.536-.779-.884-1.368-.884h-11.414c-.589 0-1.126.348-1.368.885z"/>
@@ -1003,8 +1136,8 @@ function getEmptyTemplate() {
1003
1136
  * @return {string}
1004
1137
  */
1005
1138
  function getTemplate() {
1006
- // language=HTML
1007
- return `
1139
+ // language=HTML
1140
+ return `
1008
1141
  <div data-monster-role="control" part="control" data-monster-attributes="class path:classes.control">
1009
1142
  <template id="headers-row">
1010
1143
  <div data-monster-attributes="class path:headers-row.classes,
@@ -1018,6 +1151,7 @@ function getTemplate() {
1018
1151
  <slot name="filter"></slot>
1019
1152
  </div>
1020
1153
  <div class="bar">
1154
+ <a href="#" data-monster-attributes="class path:features.copyAll | ?::hidden" data-monster-role="copy-all" data-monster-replace="path:locale.copyAll">Copy all</a>
1021
1155
  <monster-column-bar
1022
1156
  data-monster-attributes="class path:features.settings | ?::hidden"></monster-column-bar>
1023
1157
  <slot name="bar"></slot>