@schukai/monster 3.86.5 → 3.87.1

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,403 @@ 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
+ const eventHandlerDoubleClickCopyToClipboard = (event) => {
724
+ const element = findTargetElementFromEvent(event, "data-monster-head");
725
+ if (element) {
726
+
727
+
728
+ let text = "";
729
+
730
+ if (event.shiftKey) {
731
+ const index = element.getAttribute("data-monster-insert-reference");
732
+ if (index) {
733
+ const cols = self.getGridElements(`[data-monster-insert-reference="${index}"]`);
734
+
735
+ const colTexts = []
736
+ for (let i = 0; i < cols.length; i++) {
737
+ const col = cols[i];
738
+
739
+ if (col.querySelector("monster-button-bar") || col.querySelector("monster-button")) {
740
+ continue;
741
+ }
742
+
743
+ if (col.textContent) {
744
+ colTexts.push(quoteOpenChar+col.textContent.trim()+quoteCloseChar);
745
+ }
746
+ }
747
+
748
+ text = colTexts.join(delimiterChar);
749
+
750
+ }
751
+
752
+
753
+ } else {
754
+ if (element.querySelector("monster-button-bar") || element.querySelector("monster-button")) {
755
+ return;
756
+ }
757
+
758
+ text = element.textContent.trim();
759
+
760
+ }
761
+
762
+ if (getWindow().navigator.clipboard && text) {
763
+ getWindow().navigator.clipboard.writeText(text).then(
764
+ () => {
765
+ },
766
+ (err) => {
767
+ }
768
+ );
769
+ }
770
+ }
771
+ }
772
+
773
+ if (self.getOption("features.doubleClickCopyToClipboard")) {
774
+ self[gridElementSymbol].addEventListener("dblclick", eventHandlerDoubleClickCopyToClipboard);
775
+ }
776
+
777
+ if (self.getOption("features.copyAll") && this[copyAllElementSymbol]) {
778
+ this[copyAllElementSymbol].addEventListener("click", (event) => {
779
+ event.preventDefault();
780
+
781
+ const table = [];
782
+ let currentRow = [];
783
+ let currentIndex= null;
784
+
785
+ const cols = self.getGridElements(`[data-monster-insert-reference]`);
786
+ const rowIndexes = new Map();
787
+ cols.forEach((col) => {
788
+ const index = col.getAttribute("data-monster-insert-reference");
789
+ rowIndexes.set(index, true);
790
+ });
791
+
792
+ rowIndexes.forEach((value, key) => {
793
+ const cols = self.getGridElements(`[data-monster-insert-reference="${key}"]`);
794
+
795
+ for (let i = 0; i < cols.length; i++) {
796
+ const col = cols[i];
797
+
798
+ if (col.querySelector("monster-button-bar") || col.querySelector("monster-button")) {
799
+ continue;
800
+ }
801
+
802
+ if (col.textContent) {
803
+ currentRow.push(quoteOpenChar+col.textContent.trim()+quoteCloseChar);
804
+ }
805
+ }
806
+
807
+ if(currentRow.length > 0) {
808
+ table.push(currentRow);
809
+ }
810
+ currentRow = [];
811
+ });
812
+
813
+ if(table.length > 0) {
814
+ const text = table.map(row => row.join(delimiterChar)).join(rowBreak);
815
+ if (getWindow().navigator.clipboard && text) {
816
+ getWindow().navigator.clipboard.writeText(text).then(
817
+ () => {
818
+ },
819
+ (err) => {
820
+ }
821
+ );
822
+ }
823
+ }
824
+
825
+ });
826
+
827
+
828
+ }
829
+
830
+
831
+
699
832
  }
700
833
 
701
834
  /**
702
835
  * @private
703
836
  */
704
837
  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
- });
838
+ const rowID = this.getOption("templateMapping.row-key");
839
+
840
+ if (!this[gridElementSymbol]) {
841
+ throw new Error("no grid element is defined");
842
+ }
843
+
844
+ let template;
845
+ getSlottedElements.call(this).forEach((e) => {
846
+ if (e instanceof HTMLTemplateElement && e.id === rowID) {
847
+ template = e;
848
+ }
849
+ });
850
+
851
+ if (!template) {
852
+ throw new Error("no template is defined");
853
+ }
854
+
855
+ const rowCount = template.content.children.length;
856
+
857
+ const headers = [];
858
+
859
+ for (let i = 0; i < rowCount; i++) {
860
+ let hClass = "";
861
+ const row = template.content.children[i];
862
+
863
+ let mode = "";
864
+ if (row.hasAttribute(ATTRIBUTE_DATATABLE_MODE)) {
865
+ mode = row.getAttribute(ATTRIBUTE_DATATABLE_MODE);
866
+ }
867
+
868
+ let grid = row.getAttribute(ATTRIBUTE_DATATABLE_GRID_TEMPLATE);
869
+ if (!grid || grid === "" || grid === "auto") {
870
+ grid = "minmax(0, 1fr)";
871
+ }
872
+
873
+ let label = "";
874
+ let labelKey = "";
875
+
876
+ if (row.hasAttribute(ATTRIBUTE_DATATABLE_HEAD)) {
877
+ label = row.getAttribute(ATTRIBUTE_DATATABLE_HEAD);
878
+ labelKey = label;
879
+
880
+ try {
881
+ if (label.startsWith("i18n:")) {
882
+ label = label.substring(5, label.length);
883
+ label = getDocumentTranslations().getText(label, label);
884
+ }
885
+ } catch (e) {
886
+ label = "i18n error " + label;
887
+ }
888
+ }
889
+
890
+ if (!label) {
891
+ label = i + 1 + "";
892
+ mode = ATTRIBUTE_DATATABLE_MODE_FIXED;
893
+ labelKey = label;
894
+ }
895
+
896
+ if (isObject(hostConfig) && hostConfig.hasOwnProperty(label)) {
897
+ if (hostConfig[label] === false) {
898
+ mode = ATTRIBUTE_DATATABLE_MODE_HIDDEN;
899
+ } else {
900
+ mode = ATTRIBUTE_DATATABLE_MODE_VISIBLE;
901
+ }
902
+ }
903
+
904
+ let align = "";
905
+ if (row.hasAttribute(ATTRIBUTE_DATATABLE_ALIGN)) {
906
+ align = row.getAttribute(ATTRIBUTE_DATATABLE_ALIGN);
907
+ }
908
+
909
+ switch (align) {
910
+ case "center":
911
+ hClass = "flex-center";
912
+ break;
913
+ case "end":
914
+ hClass = "flex-end";
915
+ break;
916
+ case "start":
917
+ hClass = "flex-start";
918
+ break;
919
+ default:
920
+ hClass = "flex-start";
921
+ }
922
+
923
+ let field = "";
924
+ let direction = DIRECTION_NONE;
925
+ if (row.hasAttribute(ATTRIBUTE_DATATABLE_SORTABLE)) {
926
+ field = row.getAttribute(ATTRIBUTE_DATATABLE_SORTABLE).trim();
927
+ const parts = field.split(" ").map((item) => item.trim());
928
+ field = parts[0];
929
+
930
+ if (headerOrderMap.has(field)) {
931
+ direction = headerOrderMap.get(field);
932
+ } else if (
933
+ parts.length === 2 &&
934
+ [DIRECTION_ASC, DIRECTION_DESC].indexOf(parts[1]) !== -1
935
+ ) {
936
+ direction = parts[1];
937
+ }
938
+ }
939
+
940
+ if (mode === ATTRIBUTE_DATATABLE_MODE_HIDDEN) {
941
+ hClass += " hidden";
942
+ }
943
+
944
+ const header = new Header();
945
+ header.setInternals({
946
+ field: field,
947
+ label: label,
948
+ classes: hClass,
949
+ index: i,
950
+ mode: mode,
951
+ grid: grid,
952
+ labelKey: labelKey,
953
+ direction: direction,
954
+ });
955
+
956
+ headers.push(header);
957
+ }
958
+
959
+ this.setOption("headers", headers);
960
+ queueMicrotask(() => {
961
+ storeOrderStatement.call(this, this.getOption("features.autoInit"));
962
+ });
830
963
  }
831
964
 
832
965
  /**
@@ -834,79 +967,79 @@ function initGridAndStructs(hostConfig, headerOrderMap) {
834
967
  * @return {string}
835
968
  */
836
969
  export function getStoredOrderConfigKey() {
837
- return generateUniqueConfigKey("datatable", this?.id, "stored-order");
970
+ return generateUniqueConfigKey("datatable", this?.id, "stored-order");
838
971
  }
839
972
 
840
973
  /**
841
974
  * @private
842
975
  */
843
976
  function storeOrderStatement(doFetch) {
844
- const headers = this.getOption("headers");
845
- const statement = createOrderStatement(headers);
846
- setDataSource.call(this, { orderBy: statement }, doFetch);
977
+ const headers = this.getOption("headers");
978
+ const statement = createOrderStatement(headers);
979
+ setDataSource.call(this, {orderBy: statement}, doFetch);
847
980
 
848
- const host = findElementWithSelectorUpwards(this, "monster-host");
849
- if (!(host && this.id)) {
850
- return;
851
- }
981
+ const host = findElementWithSelectorUpwards(this, "monster-host");
982
+ if (!(host && this.id)) {
983
+ return;
984
+ }
852
985
 
853
- const configKey = getStoredOrderConfigKey.call(this);
986
+ const configKey = getStoredOrderConfigKey.call(this);
854
987
 
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
- }
988
+ // statement explode with , and remove all empty
989
+ const list = statement.split(",").filter((item) => item.trim() !== "");
990
+ if (list.length === 0) {
991
+ return;
992
+ }
860
993
 
861
- host.setConfig(configKey, list);
994
+ host.setConfig(configKey, list);
862
995
  }
863
996
 
864
997
  /**
865
998
  * @private
866
999
  */
867
1000
  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
- }
1001
+ if (!this[gridElementSymbol]) {
1002
+ throw new Error("no grid element is defined");
1003
+ }
1004
+
1005
+ let gridTemplateColumns = "";
1006
+
1007
+ const headers = this.getOption("headers");
1008
+
1009
+ let styles = "";
1010
+
1011
+ for (let i = 0; i < headers.length; i++) {
1012
+ const header = headers[i];
1013
+
1014
+ if (header.mode === ATTRIBUTE_DATATABLE_MODE_HIDDEN) {
1015
+ styles += `[data-monster-role=datatable]>[data-monster-head="${header.labelKey}"] { display: none; }\n`;
1016
+ styles += `[data-monster-role=datatable-headers]>[data-monster-index="${header.index}"] { display: none; }\n`;
1017
+ } else {
1018
+ gridTemplateColumns += `${header.grid} `;
1019
+ }
1020
+ }
1021
+
1022
+ const sheet = new CSSStyleSheet();
1023
+ if (styles !== "") sheet.replaceSync(styles);
1024
+ this.shadowRoot.adoptedStyleSheets = [...DataTable.getCSSStyleSheet(), sheet];
1025
+
1026
+ const bodyWidth = this.parentNode.clientWidth;
1027
+
1028
+ const breakpoint = this.getOption("responsive.breakpoint");
1029
+ this[dataControlElementSymbol].classList.toggle(
1030
+ "small",
1031
+ bodyWidth <= breakpoint,
1032
+ );
1033
+
1034
+ if (bodyWidth > breakpoint) {
1035
+ this[gridElementSymbol].style.gridTemplateColumns =
1036
+ `${gridTemplateColumns}`;
1037
+ this[gridHeadersElementSymbol].style.gridTemplateColumns =
1038
+ `${gridTemplateColumns}`;
1039
+ } else {
1040
+ this[gridElementSymbol].style.gridTemplateColumns = "auto";
1041
+ this[gridHeadersElementSymbol].style.gridTemplateColumns = "auto";
1042
+ }
910
1043
  }
911
1044
 
912
1045
  /**
@@ -914,20 +1047,20 @@ function updateGrid() {
914
1047
  * @param {Header[]} headers
915
1048
  * @param {bool} doFetch
916
1049
  */
917
- function setDataSource({ orderBy }, doFetch) {
918
- const datasource = this[datasourceLinkedElementSymbol];
1050
+ function setDataSource({orderBy}, doFetch) {
1051
+ const datasource = this[datasourceLinkedElementSymbol];
919
1052
 
920
- if (!datasource) {
921
- return;
922
- }
1053
+ if (!datasource) {
1054
+ return;
1055
+ }
923
1056
 
924
- if (isFunction(datasource?.setParameters)) {
925
- datasource.setParameters({ orderBy });
926
- }
1057
+ if (isFunction(datasource?.setParameters)) {
1058
+ datasource.setParameters({orderBy});
1059
+ }
927
1060
 
928
- if (doFetch !== false && isFunction(datasource?.fetch)) {
929
- datasource.fetch();
930
- }
1061
+ if (doFetch !== false && isFunction(datasource?.fetch)) {
1062
+ datasource.fetch();
1063
+ }
931
1064
  }
932
1065
 
933
1066
  /**
@@ -935,24 +1068,28 @@ function setDataSource({ orderBy }, doFetch) {
935
1068
  * @return {DataTable}
936
1069
  */
937
1070
  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;
1071
+ if (!this.shadowRoot) {
1072
+ throw new Error("no shadow-root is defined");
1073
+ }
1074
+
1075
+ this[dataControlElementSymbol] = this.shadowRoot.querySelector(
1076
+ "[data-monster-role=control]",
1077
+ );
1078
+
1079
+ this[gridElementSymbol] = this.shadowRoot.querySelector(
1080
+ "[data-monster-role=datatable]",
1081
+ );
1082
+ this[gridHeadersElementSymbol] = this.shadowRoot.querySelector(
1083
+ "[data-monster-role=datatable-headers]",
1084
+ );
1085
+ this[columnBarElementSymbol] =
1086
+ this.shadowRoot.querySelector("monster-column-bar");
1087
+
1088
+ this[copyAllElementSymbol] = this.shadowRoot.querySelector(
1089
+ "[data-monster-role=copy-all]",
1090
+ );
1091
+
1092
+ return this;
956
1093
  }
957
1094
 
958
1095
  /**
@@ -962,22 +1099,22 @@ function initControlReferences() {
962
1099
  * @throws {Error} the datasource could not be initialized
963
1100
  */
964
1101
  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;
1102
+ const options = {};
1103
+ const selector = this.getAttribute(ATTRIBUTE_DATASOURCE_SELECTOR);
1104
+
1105
+ if (selector) {
1106
+ options.datasource = {selector: selector};
1107
+ }
1108
+
1109
+ const breakpoint = this.getAttribute(
1110
+ ATTRIBUTE_DATATABLE_RESPONSIVE_BREAKPOINT,
1111
+ );
1112
+ if (breakpoint) {
1113
+ options.responsive = {};
1114
+ options.responsive.breakpoint = parseInt(breakpoint);
1115
+ }
1116
+
1117
+ return options;
981
1118
  }
982
1119
 
983
1120
  /**
@@ -985,7 +1122,7 @@ function initOptionsFromArguments() {
985
1122
  * @return {string}
986
1123
  */
987
1124
  function getEmptyTemplate() {
988
- return `<monster-state data-monster-role="empty-without-action">
1125
+ return `<monster-state data-monster-role="empty-without-action">
989
1126
  <div part="visual">
990
1127
  <svg width="4rem" height="4rem" fill="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
991
1128
  <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 +1140,8 @@ function getEmptyTemplate() {
1003
1140
  * @return {string}
1004
1141
  */
1005
1142
  function getTemplate() {
1006
- // language=HTML
1007
- return `
1143
+ // language=HTML
1144
+ return `
1008
1145
  <div data-monster-role="control" part="control" data-monster-attributes="class path:classes.control">
1009
1146
  <template id="headers-row">
1010
1147
  <div data-monster-attributes="class path:headers-row.classes,
@@ -1018,6 +1155,7 @@ function getTemplate() {
1018
1155
  <slot name="filter"></slot>
1019
1156
  </div>
1020
1157
  <div class="bar">
1158
+ <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
1159
  <monster-column-bar
1022
1160
  data-monster-attributes="class path:features.settings | ?::hidden"></monster-column-bar>
1023
1161
  <slot name="bar"></slot>