@danielgindi/dgtable.js 2.0.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.
package/src/index.js ADDED
@@ -0,0 +1,4003 @@
1
+ /* eslint-env browser */
2
+
3
+ 'use strict';
4
+
5
+ import { find, htmlEncode } from './util.js';
6
+ import RowCollection from './row_collection.js';
7
+ import ColumnCollection from './column_collection.js';
8
+ import SelectionHelper from './SelectionHelper.js';
9
+ import {
10
+ getScrollHorz,
11
+ setScrollHorz,
12
+ } from '@danielgindi/dom-utils/lib/ScrollHelper.js';
13
+ import {
14
+ getElementWidth,
15
+ getElementHeight,
16
+ setElementWidth,
17
+ getElementOffset,
18
+ setCssProps,
19
+ } from '@danielgindi/dom-utils/lib/Css.js';
20
+ import {
21
+ scopedSelector, scopedSelectorAll,
22
+ } from '@danielgindi/dom-utils/lib/DomCompat.js';
23
+ import VirtualListHelper from '@danielgindi/virtual-list-helper';
24
+ import ByColumnFilter from './by_column_filter.js';
25
+ import DomEventsSink from '@danielgindi/dom-utils/lib/DomEventsSink.js';
26
+ import mitt from 'mitt';
27
+
28
+ const nativeIndexOf = Array.prototype.indexOf;
29
+
30
+ let createElement = document.createElement.bind(document);
31
+ const hasOwnProperty = Object.prototype.hasOwnProperty;
32
+
33
+ const IsSafeSymbol = Symbol('safe');
34
+ const HoverInEventSymbol = Symbol('hover_in');
35
+ const HoverOutEventSymbol = Symbol('hover_out');
36
+ const RowClickEventSymbol = Symbol('row_click');
37
+ const PreviewCellSymbol = Symbol('preview_cell');
38
+ const OriginalCellSymbol = Symbol('cell');
39
+
40
+ function webkitRenderBugfix(el) {
41
+ // BUGFIX: WebKit has a bug where it does not relayout, and this affects us because scrollbars
42
+ // are still calculated even though they are not there yet. This is the last resort.
43
+ let oldDisplay = el.style.display;
44
+ el.style.display = 'none';
45
+ //noinspection BadExpressionStatementJS
46
+ el.offsetHeight; // No need to store this anywhere, the reference is enough
47
+ el.style.display = oldDisplay;
48
+ return el;
49
+ }
50
+
51
+ function relativizeElement(el) {
52
+ if (!['relative', 'absolute', 'fixed'].includes(getComputedStyle(el).position)) {
53
+ el.style.position = 'relative';
54
+ }
55
+ }
56
+
57
+ const isInputElementEvent = event => /^(?:INPUT|TEXTAREA|BUTTON|SELECT)$/.test(event.target.tagName);
58
+
59
+ // noinspection JSUnusedGlobalSymbols
60
+ class DGTable {
61
+ /**
62
+ * @param {DGTable.Options?} options - initialization options
63
+ */
64
+ constructor(options) {
65
+ this._init(options);
66
+
67
+ /**
68
+ * @public
69
+ * @expose
70
+ * @type {string}
71
+ */
72
+ this.VERSION = DGTable.VERSION;
73
+ }
74
+
75
+ /**
76
+ * @param {DGTable.Options?} options - initialization options
77
+ */
78
+ _init(options) {
79
+ options = options || {};
80
+
81
+ /**
82
+ * @private
83
+ * @type {DGTable.Options}
84
+ * */
85
+ let o = this._o = {};
86
+
87
+ /**
88
+ * @private
89
+ * This is for encapsulating private data */
90
+ let p = this._p = {
91
+ eventsSink: new DomEventsSink(),
92
+ mitt: mitt(),
93
+ /** @type {boolean} */
94
+ tableSkeletonNeedsRendering: true,
95
+ };
96
+
97
+ /**
98
+ * @public
99
+ * @expose
100
+ * */
101
+ this.el = (options.el && options.el instanceof Element) ? options.el : document.createElement('div');
102
+
103
+ if (this.el !== options.el) {
104
+ this.el.classList.add(options.className || 'dgtable-wrapper');
105
+ }
106
+
107
+ p.eventsSink.add(this.el, 'dragend.colresize', this._onEndDragColumnHeader.bind(this));
108
+
109
+ /**
110
+ * @private
111
+ * @field {boolean} virtualTable */
112
+ o.virtualTable = options.virtualTable === undefined ? true : !!options.virtualTable;
113
+
114
+ /**
115
+ * @private
116
+ * @field {number} estimatedRowHeight */
117
+ o.estimatedRowHeight = options.estimatedRowHeight || undefined;
118
+
119
+ /**
120
+ * @private
121
+ * @field {number} rowsBufferSize */
122
+ o.rowsBufferSize = options.rowsBufferSize || 3;
123
+
124
+ /**
125
+ * @private
126
+ * @field {number} minColumnWidth */
127
+ o.minColumnWidth = Math.max(options.minColumnWidth || 35, 0);
128
+
129
+ /**
130
+ * @private
131
+ * @field {number} resizeAreaWidth */
132
+ o.resizeAreaWidth = options.resizeAreaWidth || 8;
133
+
134
+ /**
135
+ * @private
136
+ * @field {boolean} resizableColumns */
137
+ o.resizableColumns = options.resizableColumns === undefined ? true : !!options.resizableColumns;
138
+
139
+ /**
140
+ * @private
141
+ * @field {boolean} movableColumns */
142
+ o.movableColumns = options.movableColumns === undefined ? true : !!options.movableColumns;
143
+
144
+ /**
145
+ * @private
146
+ * @field {number} sortableColumns */
147
+ o.sortableColumns = options.sortableColumns === undefined ? 1 : (parseInt(options.sortableColumns, 10) || 1);
148
+
149
+ /**
150
+ * @private
151
+ * @field {boolean} adjustColumnWidthForSortArrow */
152
+ o.adjustColumnWidthForSortArrow = options.adjustColumnWidthForSortArrow === undefined ? true : !!options.adjustColumnWidthForSortArrow;
153
+
154
+ /**
155
+ * @private
156
+ * @field {boolean} convertColumnWidthsToRelative */
157
+ o.convertColumnWidthsToRelative = options.convertColumnWidthsToRelative === undefined ? false : !!options.convertColumnWidthsToRelative;
158
+
159
+ /**
160
+ * @private
161
+ * @field {boolean} autoFillTableWidth */
162
+ o.autoFillTableWidth = options.autoFillTableWidth === undefined ? false : !!options.autoFillTableWidth;
163
+
164
+ /**
165
+ * @private
166
+ * @field {boolean} allowCancelSort */
167
+ o.allowCancelSort = options.allowCancelSort === undefined ? true : !!options.allowCancelSort;
168
+
169
+ /**
170
+ * @private
171
+ * @field {string} cellClasses */
172
+ o.cellClasses = options.cellClasses === undefined ? '' : options.cellClasses;
173
+
174
+ /**
175
+ * @private
176
+ * @field {string} resizerClassName */
177
+ o.resizerClassName = options.resizerClassName === undefined ? 'dgtable-resize' : options.resizerClassName;
178
+
179
+ /**
180
+ * @private
181
+ * @field {string} tableClassName */
182
+ o.tableClassName = options.tableClassName === undefined ? 'dgtable' : options.tableClassName;
183
+
184
+ /**
185
+ * @private
186
+ * @field {boolean} allowCellPreview */
187
+ o.allowCellPreview = options.allowCellPreview === undefined ? true : options.allowCellPreview;
188
+
189
+ /**
190
+ * @private
191
+ * @field {boolean} allowHeaderCellPreview */
192
+ o.allowHeaderCellPreview = options.allowHeaderCellPreview === undefined ? true : options.allowHeaderCellPreview;
193
+
194
+ /**
195
+ * @private
196
+ * @field {string} cellPreviewClassName */
197
+ o.cellPreviewClassName = options.cellPreviewClassName === undefined ? 'dgtable-cell-preview' : options.cellPreviewClassName;
198
+
199
+ /**
200
+ * @private
201
+ * @field {boolean} cellPreviewAutoBackground */
202
+ o.cellPreviewAutoBackground = options.cellPreviewAutoBackground === undefined ? true : options.cellPreviewAutoBackground;
203
+
204
+ /**
205
+ * @private
206
+ * @field {function(columnName: string, descending: boolean, defaultComparator: function(a,b):number):(function(a,b):number)} onComparatorRequired */
207
+ o.onComparatorRequired = options.onComparatorRequired === undefined ? null : options.onComparatorRequired;
208
+ if (!o.onComparatorRequired && typeof options['comparatorCallback'] === 'function') {
209
+ o.onComparatorRequired = options['comparatorCallback'];
210
+ }
211
+
212
+ o.customSortingProvider = options.customSortingProvider === undefined ? null : options.customSortingProvider;
213
+
214
+ /**
215
+ * @private
216
+ * @field {boolean} width */
217
+ o.width = options.width === undefined ? DGTable.Width.NONE : options.width;
218
+
219
+ /**
220
+ * @private
221
+ * @field {boolean} relativeWidthGrowsToFillWidth */
222
+ o.relativeWidthGrowsToFillWidth = options.relativeWidthGrowsToFillWidth === undefined ? true : !!options.relativeWidthGrowsToFillWidth;
223
+
224
+ /**
225
+ * @private
226
+ * @field {boolean} relativeWidthShrinksToFillWidth */
227
+ o.relativeWidthShrinksToFillWidth = options.relativeWidthShrinksToFillWidth === undefined ? false : !!options.relativeWidthShrinksToFillWidth;
228
+
229
+ this.setCellFormatter(options.cellFormatter);
230
+ this.setHeaderCellFormatter(options.headerCellFormatter);
231
+ this.setFilter(options.filter);
232
+
233
+ /** @private
234
+ * @field {number} height */
235
+ o.height = options.height;
236
+
237
+ // Prepare columns
238
+ this.setColumns(options.columns || [], false);
239
+
240
+ // Set sorting columns
241
+ let sortColumns = [];
242
+
243
+ if (options.sortColumn) {
244
+
245
+ let tmpSortColumns = options.sortColumn;
246
+
247
+ if (tmpSortColumns && !Array.isArray(tmpSortColumns)) {
248
+ tmpSortColumns = [tmpSortColumns];
249
+ }
250
+
251
+ if (tmpSortColumns) {
252
+ for (let i = 0, len = tmpSortColumns.length; i < len; i++) {
253
+ let sortColumn = tmpSortColumns[i];
254
+ if (typeof sortColumn === 'string') {
255
+ sortColumn = { column: sortColumn, descending: false };
256
+ }
257
+ let col = p.columns.get(sortColumn.column);
258
+ if (!col) continue;
259
+
260
+ sortColumns.push({
261
+ column: sortColumn.column,
262
+ comparePath: col.comparePath || col.dataPath,
263
+ descending: sortColumn.descending,
264
+ });
265
+ }
266
+ }
267
+ }
268
+
269
+ /** @field {RowCollection} _rows */
270
+ p.rows = new RowCollection({ sortColumn: sortColumns });
271
+ p.rows.onComparatorRequired = (column, descending, defaultComparator) => {
272
+ if (o.onComparatorRequired) {
273
+ return o.onComparatorRequired(column, descending, defaultComparator);
274
+ }
275
+ };
276
+ p.rows.customSortingProvider = (data, sort) => {
277
+ if (o.customSortingProvider) {
278
+ return o.customSortingProvider(data, sort);
279
+ } else {
280
+ return sort(data);
281
+ }
282
+ };
283
+
284
+ /** @private
285
+ * @field {RowCollection} _filteredRows */
286
+ p.filteredRows = null;
287
+
288
+ p.scrollbarWidth = 0;
289
+ p.lastVirtualScrollHeight = 0;
290
+
291
+ this._setupHovers();
292
+ }
293
+
294
+ _setupHovers() {
295
+ const p = this._p;
296
+
297
+ /*
298
+ Setup hover mechanism.
299
+ We need this to be high performance, as there may be MANY cells to call this on, on creation and destruction.
300
+ */
301
+
302
+ /**
303
+ * @param {MouseEvent} event
304
+ * @this {HTMLElement}
305
+ * */
306
+ let hoverMouseOverHandler = (event) => {
307
+ let cell = event.currentTarget;
308
+ let target = event.relatedTarget;
309
+ if (target === cell || cell.contains(target))
310
+ return;
311
+ if (cell[PreviewCellSymbol] &&
312
+ (target === cell[PreviewCellSymbol] || cell[PreviewCellSymbol].contains(target)))
313
+ return;
314
+ this._cellMouseOverEvent(cell);
315
+ };
316
+
317
+ /**
318
+ * @param {MouseEvent} event
319
+ * @this {HTMLElement}
320
+ * */
321
+ let hoverMouseOutHandler = (event) => {
322
+ let cell = event.currentTarget[OriginalCellSymbol] || event.currentTarget;
323
+ let target = event.relatedTarget;
324
+ if (target === this || cell.contains(target))
325
+ return;
326
+ if (cell[PreviewCellSymbol] &&
327
+ (target === cell[PreviewCellSymbol] || cell[PreviewCellSymbol].contains(target)))
328
+ return;
329
+ this._cellMouseOutEvent(cell);
330
+ };
331
+
332
+ /**
333
+ * @param {HTMLElement} el cell or header-cell
334
+ * */
335
+ p._bindCellHoverIn = el => {
336
+ if (!el[HoverInEventSymbol]) {
337
+ el.addEventListener('mouseover', el[HoverInEventSymbol] = hoverMouseOverHandler);
338
+ }
339
+ };
340
+
341
+ /**
342
+ * @param {HTMLElement} el cell or header-cell
343
+ * */
344
+ p._unbindCellHoverIn = el => {
345
+ if (el[HoverInEventSymbol]) {
346
+ el.removeEventListener('mouseover', el[HoverInEventSymbol]);
347
+ el[HoverInEventSymbol] = null;
348
+ }
349
+ };
350
+
351
+ /**
352
+ * @param {HTMLElement} el cell or header-cell
353
+ * @returns {DGTable} self
354
+ * */
355
+ p._bindCellHoverOut = (el) => {
356
+ if (!el[HoverOutEventSymbol]) {
357
+ el.addEventListener('mouseout', el[HoverOutEventSymbol] = hoverMouseOutHandler);
358
+ }
359
+ };
360
+
361
+ /**
362
+ * @param {HTMLElement} el cell or header-cell
363
+ * @returns {DGTable} self
364
+ * */
365
+ p._unbindCellHoverOut = el => {
366
+ if (el[HoverOutEventSymbol]) {
367
+ el.removeEventListener('mouseout', el[HoverOutEventSymbol]);
368
+ el[HoverOutEventSymbol] = null;
369
+ }
370
+ };
371
+ }
372
+
373
+ _setupVirtualTable() {
374
+ const p = this._p, o = this._o;
375
+
376
+ const tableClassName = o.tableClassName,
377
+ rowClassName = tableClassName + '-row',
378
+ altRowClassName = tableClassName + '-row-alt',
379
+ cellClassName = tableClassName + '-cell';
380
+
381
+ let visibleColumns = p.visibleColumns,
382
+ colCount = visibleColumns.length;
383
+
384
+ p.notifyRendererOfColumnsConfig = () => {
385
+ visibleColumns = p.visibleColumns;
386
+ colCount = visibleColumns.length;
387
+
388
+ for (let colIndex = 0, column; colIndex < colCount; colIndex++) {
389
+ column = visibleColumns[colIndex];
390
+ column._finalWidth = (column.actualWidthConsideringScrollbarWidth || column.actualWidth);
391
+ }
392
+ };
393
+
394
+ p.virtualListHelper = new VirtualListHelper({
395
+ list: p.table,
396
+ itemsParent: p.tbody,
397
+ autoVirtualWrapperWidth: false,
398
+ virtual: o.virtualTable,
399
+ buffer: o.rowsBufferSize,
400
+ estimatedItemHeight: o.estimatedRowHeight ? o.estimatedRowHeight : (p.virtualRowHeight || 40),
401
+ itemElementCreatorFn: () => {
402
+ return createElement('div');
403
+ },
404
+ onItemRender: (row, virtualIndex) => {
405
+ const rows = p.filteredRows || p.rows,
406
+ isDataFiltered = !!p.filteredRows,
407
+ allowCellPreview = o.allowCellPreview;
408
+
409
+ row.className = rowClassName;
410
+ if ((virtualIndex % 2) === 1)
411
+ row.className += ' ' + altRowClassName;
412
+
413
+ let rowData = rows[virtualIndex];
414
+ let rowIndex = isDataFiltered ? rowData['__i'] : virtualIndex;
415
+
416
+ row['vIndex'] = virtualIndex;
417
+ row['index'] = rowIndex;
418
+
419
+ for (let colIndex = 0; colIndex < colCount; colIndex++) {
420
+ let column = visibleColumns[colIndex];
421
+ let cell = createElement('div');
422
+ cell['columnName'] = column.name;
423
+ cell.setAttribute('data-column', column.name);
424
+ cell.className = cellClassName;
425
+ cell.style.width = column._finalWidth + 'px';
426
+ if (column.cellClasses) cell.className += ' ' + column.cellClasses;
427
+ if (allowCellPreview) {
428
+ p._bindCellHoverIn(cell);
429
+ }
430
+
431
+ let cellInner = cell.appendChild(createElement('div'));
432
+ cellInner.innerHTML = this._getHtmlForCell(rowData, column);
433
+
434
+ row.appendChild(cell);
435
+ }
436
+
437
+ row.addEventListener('click', row[RowClickEventSymbol] = event => {
438
+ this.emit('rowclick', {
439
+ event: event,
440
+ filteredRowIndex: virtualIndex,
441
+ rowIndex: rowIndex,
442
+ rowEl: row,
443
+ rowData: rowData,
444
+ });
445
+ });
446
+
447
+ this.emit('rowcreate', {
448
+ filteredRowIndex: virtualIndex,
449
+ rowIndex: rowIndex,
450
+ rowEl: row,
451
+ rowData: rowData,
452
+ });
453
+ },
454
+
455
+ onItemUnrender: (row) => {
456
+ if (row[RowClickEventSymbol]) {
457
+ row.removeEventListener('click', row[RowClickEventSymbol]);
458
+ }
459
+
460
+ this._unbindCellEventsForRow(row);
461
+
462
+ this.emit('rowdestroy', row);
463
+ },
464
+
465
+ onScrollHeightChange: height => {
466
+ // only recalculate scrollbar width if height increased. we reset it in other situations.
467
+ if (height > p._lastVirtualScrollHeight && !p.scrollbarWidth) {
468
+ this._updateLastCellWidthFromScrollbar();
469
+ }
470
+
471
+ p._lastVirtualScrollHeight = height;
472
+ },
473
+ });
474
+
475
+ p.virtualListHelper.setCount((p.filteredRows ?? p.rows).length);
476
+
477
+ p.notifyRendererOfColumnsConfig();
478
+ }
479
+
480
+ trigger(eventName) {
481
+ const p = this._p;
482
+ if (!p) return;
483
+
484
+ let events = p.events;
485
+
486
+ if (hasOwnProperty.call(events, eventName)) {
487
+ let callbacks = events[eventName];
488
+ for (let i = 0; i < callbacks.length; i++) {
489
+ let item = callbacks[i];
490
+ if (item.once) {
491
+ callbacks.splice(i--, 1);
492
+ }
493
+ item.cb.apply(this, Array.prototype.slice.call(arguments, 1));
494
+ }
495
+ }
496
+
497
+ return this;
498
+ }
499
+
500
+ /**
501
+ * Register an event handler
502
+ * @param {(string|'*')?} event
503
+ * @param {function(any)} handler
504
+ * @returns {DGTable}
505
+ */
506
+ on(/**string|'*'*/event, /**Function?*/handler) {
507
+ this._p.mitt.on(event, handler);
508
+ return this;
509
+ }
510
+
511
+ /**
512
+ * Register a one time event handler
513
+ * @param {(string|'*')?} event
514
+ * @param {function(any)} handler
515
+ * @returns {DGTable}
516
+ */
517
+ once(/**string|'*'*/event, /**Function?*/handler) {
518
+ let wrapped = (value) => {
519
+ this._p.mitt.off(event, wrapped);
520
+ handler(value);
521
+ };
522
+ this._p.mitt.on(event, wrapped);
523
+ return this;
524
+ }
525
+
526
+ /**
527
+ * Remove an `handler` for `event`, all events for `event`, or all events completely.
528
+ * @param {(string|'*')?} event
529
+ * @param {function(any)} handler
530
+ * @returns {DGTable}
531
+ */
532
+ off(/**(string|'*')?*/event, /**Function?*/handler) {
533
+ if (!event && !event) {
534
+ this._p.mitt.all.clear();
535
+ } else {
536
+ this._p.mitt.off(event, handler);
537
+ }
538
+ return this;
539
+ }
540
+
541
+ /**
542
+ * Emit an event
543
+ * @param {string} event
544
+ * @param {any?} value
545
+ * @returns {DGTable}
546
+ */
547
+ emit(/**string|'*'*/event, /**any?*/value) {
548
+ this._p.mitt.emit(event, value);
549
+ return this;
550
+ }
551
+
552
+ /**
553
+ * Detect column width mode
554
+ * @private
555
+ * @param {Number|string} width
556
+ * @param {number} minWidth
557
+ * @returns {Object} parsed width
558
+ */
559
+ _parseColumnWidth(width, minWidth) {
560
+
561
+ let widthSize = Math.max(0, parseFloat(width)),
562
+ widthMode = ColumnWidthMode.AUTO; // Default
563
+
564
+ if (widthSize > 0) {
565
+ // Well, it's sure is not AUTO, as we have a value
566
+
567
+ if (width === widthSize + '%') {
568
+ // It's a percentage!
569
+
570
+ widthMode = ColumnWidthMode.RELATIVE;
571
+ widthSize /= 100;
572
+ } else if (widthSize > 0 && widthSize < 1) {
573
+ // It's a decimal value, as a relative value!
574
+
575
+ widthMode = ColumnWidthMode.RELATIVE;
576
+ } else {
577
+ // It's an absolute size!
578
+
579
+ if (widthSize < minWidth) {
580
+ widthSize = minWidth;
581
+ }
582
+ widthMode = ColumnWidthMode.ABSOLUTE;
583
+ }
584
+ }
585
+
586
+ return { width: widthSize, mode: widthMode };
587
+ }
588
+
589
+ /**
590
+ * @private
591
+ * @param {COLUMN_OPTIONS} columnData
592
+ */
593
+ _initColumnFromData(columnData) {
594
+
595
+ let parsedWidth = this._parseColumnWidth(columnData.width, columnData.ignoreMin ? 0 : this._o.minColumnWidth);
596
+
597
+ let col = {
598
+ name: columnData.name,
599
+ label: columnData.label === undefined ? columnData.name : columnData.label,
600
+ width: parsedWidth.width,
601
+ widthMode: parsedWidth.mode,
602
+ resizable: columnData.resizable === undefined ? true : columnData.resizable,
603
+ sortable: columnData.sortable === undefined ? true : columnData.sortable,
604
+ movable: columnData.movable === undefined ? true : columnData.movable,
605
+ visible: columnData.visible === undefined ? true : columnData.visible,
606
+ cellClasses: columnData.cellClasses === undefined ? this._o.cellClasses : columnData.cellClasses,
607
+ ignoreMin: columnData.ignoreMin === undefined ? false : !!columnData.ignoreMin,
608
+ };
609
+
610
+ col.dataPath = columnData.dataPath === undefined ? col.name : columnData.dataPath;
611
+ col.comparePath = columnData.comparePath === undefined ? col.dataPath : columnData.comparePath;
612
+
613
+ if (typeof col.dataPath === 'string') {
614
+ col.dataPath = col.dataPath.split('.');
615
+ }
616
+ if (typeof col.comparePath === 'string') {
617
+ col.comparePath = col.comparePath.split('.');
618
+ }
619
+
620
+ return col;
621
+ }
622
+
623
+ /**
624
+ * Destroy, releasing all memory, events and DOM elements
625
+ * @public
626
+ * @expose
627
+ */
628
+ destroy() {
629
+ let p = this._p || {},
630
+ el = this.el;
631
+
632
+ if (this.__removed) {
633
+ return this;
634
+ }
635
+
636
+ if (p.resizer) {
637
+ p.resizer.remove();
638
+ p.resizer = null;
639
+ }
640
+
641
+ p.virtualListHelper?.destroy();
642
+ p.virtualListHelper = null;
643
+
644
+ // Using quotes for __super__ because Google Closure Compiler has a bug...
645
+
646
+ this._destroyHeaderCells();
647
+
648
+ p.table?.remove();
649
+ p.tbody?.remove();
650
+
651
+ if (p.workerListeners) {
652
+ for (let j = 0; j < p.workerListeners.length; j++) {
653
+ let worker = p.workerListeners[j];
654
+ worker.worker.removeEventListener('message', worker.listener, false);
655
+ }
656
+ p.workerListeners.length = 0;
657
+ }
658
+
659
+ p.rows.length = p.columns.length = 0;
660
+
661
+ if (p._deferredRender) {
662
+ clearTimeout(p._deferredRender);
663
+ }
664
+
665
+ // Cleanup
666
+ for (let prop in this) {
667
+ if (hasOwnProperty.call(this, prop)) {
668
+ this[prop] = null;
669
+ }
670
+ }
671
+
672
+ this.__removed = true;
673
+
674
+ if (el) {
675
+ el.remove();
676
+ }
677
+
678
+ return this;
679
+ }
680
+
681
+ // Backwards compatibility
682
+ close() {
683
+ this.destroy();
684
+ }
685
+
686
+ // Backwards compatibility
687
+ remove() {
688
+ this.destroy();
689
+ }
690
+
691
+ /**
692
+ * @private
693
+ * @returns {DGTable} self
694
+ */
695
+ _unbindCellEventsForTable() {
696
+ const p = this._p;
697
+
698
+ if (p.headerRow) {
699
+ for (let i = 0, rows = p.headerRow.childNodes, rowCount = rows.length; i < rowCount; i++) {
700
+ let rowToClean = rows[i];
701
+ for (let j = 0, cells = rowToClean.childNodes, cellCount = cells.length; j < cellCount; j++) {
702
+ p._unbindCellHoverIn(cells[j]);
703
+ }
704
+ }
705
+ }
706
+
707
+ return this;
708
+ }
709
+
710
+ /**
711
+ * @private
712
+ * @param {HTMLElement} rowToClean
713
+ * @returns {DGTable} self
714
+ */
715
+ _unbindCellEventsForRow(rowToClean) {
716
+ const p = this._p;
717
+ for (let i = 0, cells = rowToClean.childNodes, cellCount = cells.length; i < cellCount; i++) {
718
+ p._unbindCellHoverIn(cells[i]);
719
+ }
720
+ return this;
721
+ }
722
+
723
+ /**
724
+ * @public
725
+ * @expose
726
+ * @returns {DGTable} self
727
+ */
728
+ render() {
729
+ const o = this._o, p = this._p;
730
+
731
+ if (!this.el.offsetParent) {
732
+ if (!p._deferredRender) {
733
+ p._deferredRender = setTimeout(() => {
734
+ p._deferredRender = null;
735
+ if (!this.__removed && this.el.offsetParent) {
736
+ this.render();
737
+ }
738
+ });
739
+ }
740
+
741
+ return this;
742
+ }
743
+
744
+ if (p.tableSkeletonNeedsRendering === true) {
745
+ p.tableSkeletonNeedsRendering = false;
746
+
747
+ if (o.width === DGTable.Width.AUTO) {
748
+ // We need to do this to return to the specified widths instead. The arrows added to the column widths...
749
+ this._clearSortArrows();
750
+ }
751
+
752
+ let lastScrollTop = p.table && p.table.parentNode ? p.table.scrollTop : NaN,
753
+ lastScrollHorz = p.table && p.table.parentNode ? getScrollHorz(p.table) : NaN;
754
+
755
+ this._renderSkeletonBase()
756
+ ._renderSkeletonBody()
757
+ .tableWidthChanged(true, false) // Take this chance to calculate required column widths
758
+ ._renderSkeletonHeaderCells();
759
+
760
+ p.virtualListHelper.setCount((p.filteredRows ?? p.rows).length);
761
+
762
+ this._updateVirtualHeight();
763
+ this._updateLastCellWidthFromScrollbar(true);
764
+ this._updateTableWidth(true);
765
+
766
+ // Show sort arrows
767
+ for (let i = 0; i < p.rows.sortColumn.length; i++) {
768
+ this._showSortArrow(p.rows.sortColumn[i].column, p.rows.sortColumn[i].descending);
769
+ }
770
+ if (o.adjustColumnWidthForSortArrow && p.rows.sortColumn.length) {
771
+ this.tableWidthChanged(true);
772
+ } else if (!o.virtualTable) {
773
+ this.tableWidthChanged();
774
+ }
775
+
776
+ if (!isNaN(lastScrollTop))
777
+ p.table.scrollTop = lastScrollTop;
778
+
779
+ if (!isNaN(lastScrollHorz)) {
780
+ setScrollHorz(p.table, lastScrollHorz);
781
+ setScrollHorz(p.header, lastScrollHorz);
782
+ }
783
+
784
+ this.emit('renderskeleton');
785
+ }
786
+
787
+ p.virtualListHelper.render();
788
+
789
+ this.emit('render');
790
+ return this;
791
+ }
792
+
793
+ /**
794
+ * Forces a full render of the table
795
+ * @public
796
+ * @expose
797
+ * @param {boolean=true} render - Should render now?
798
+ * @returns {DGTable} self
799
+ */
800
+ clearAndRender(render) {
801
+ let p = this._p;
802
+
803
+ p.tableSkeletonNeedsRendering = true;
804
+ p.notifyRendererOfColumnsConfig?.();
805
+
806
+ if (render === undefined || render) {
807
+ this.render();
808
+ }
809
+
810
+ return this;
811
+ }
812
+
813
+ /**
814
+ * Calculate the size required for the table body width (which is the row's width)
815
+ * @private
816
+ * @returns {number} calculated width
817
+ */
818
+ _calculateTbodyWidth() {
819
+ const p = this._p;
820
+
821
+ let tableClassName = this._o.tableClassName,
822
+ rowClassName = tableClassName + '-row',
823
+ cellClassName = tableClassName + '-cell',
824
+ visibleColumns = p.visibleColumns,
825
+ colCount = visibleColumns.length;
826
+
827
+ const row = createElement('div');
828
+ row.className = rowClassName;
829
+ row.style.float = 'left';
830
+
831
+ let sumActualWidth = 0;
832
+
833
+ for (let colIndex = 0; colIndex < colCount; colIndex++) {
834
+ const column = visibleColumns[colIndex];
835
+ const cell = createElement('div');
836
+ cell.className = cellClassName;
837
+ cell.style.width = column.actualWidth + 'px';
838
+ if (column.cellClasses) cell.className += ' ' + column.cellClasses;
839
+ cell.appendChild(createElement('div'));
840
+ row.appendChild(cell);
841
+ sumActualWidth += column.actualWidth;
842
+ }
843
+
844
+ const thisWrapper = createElement('div');
845
+ thisWrapper.className = this.el.className;
846
+ setCssProps(thisWrapper, {
847
+ 'z-index': -1,
848
+ 'position': 'absolute',
849
+ 'left': '0',
850
+ 'top': '-9999px',
851
+ 'float': 'left',
852
+ 'width': '1px',
853
+ 'overflow': 'hidden',
854
+ });
855
+
856
+ const tableDiv = createElement('div');
857
+ tableDiv.className = tableClassName;
858
+ thisWrapper.appendChild(tableDiv);
859
+ const tableBodyDiv = createElement('div');
860
+ tableBodyDiv.className = tableClassName + '-body';
861
+ tableBodyDiv.style.width = (sumActualWidth + 10000) + 'px';
862
+ tableDiv.appendChild(tableBodyDiv);
863
+ tableBodyDiv.appendChild(row);
864
+
865
+ document.body.appendChild(thisWrapper);
866
+
867
+ const fractionTest = createElement('div');
868
+ setCssProps(fractionTest, {
869
+ border: '1.5px solid #000',
870
+ width: '0',
871
+ height: '0',
872
+ position: 'absolute',
873
+ left: '0',
874
+ top: '-9999px',
875
+ });
876
+ document.body.appendChild(fractionTest);
877
+ let fractionValue = parseFloat(getComputedStyle(fractionTest).borderWidth);
878
+ let hasFractions = Math.round(fractionValue) !== fractionValue;
879
+ fractionTest.remove();
880
+
881
+ let width = getElementWidth(row, true, true, true);
882
+ width -= p.scrollbarWidth || 0;
883
+
884
+ if (hasFractions) {
885
+ width++;
886
+ }
887
+
888
+ thisWrapper.remove();
889
+ return width;
890
+ }
891
+
892
+ /**
893
+ * Sets the columns of the table
894
+ * @public
895
+ * @expose
896
+ * @param {COLUMN_OPTIONS[]} columns - Column definitions array
897
+ * @param {boolean=true} render - Should render now?
898
+ * @returns {DGTable} self
899
+ */
900
+ setColumns(columns, render) {
901
+ const p = this._p;
902
+
903
+ columns = columns || [];
904
+
905
+ let normalizedCols = new ColumnCollection();
906
+ for (let i = 0, order = 0; i < columns.length; i++) {
907
+
908
+ let columnData = columns[i];
909
+ let normalizedColumn = this._initColumnFromData(columnData);
910
+
911
+ if (columnData.order !== undefined) {
912
+ if (columnData.order > order) {
913
+ order = columnData.order + 1;
914
+ }
915
+ normalizedColumn.order = columnData.order;
916
+ } else {
917
+ normalizedColumn.order = order++;
918
+ }
919
+
920
+ normalizedCols.push(normalizedColumn);
921
+ }
922
+ normalizedCols.normalizeOrder();
923
+
924
+ p.columns = normalizedCols;
925
+ p.visibleColumns = normalizedCols.getVisibleColumns();
926
+
927
+ this._ensureVisibleColumns().clearAndRender(render);
928
+
929
+ return this;
930
+ }
931
+
932
+ /**
933
+ * Add a column to the table
934
+ * @public
935
+ * @expose
936
+ * @param {COLUMN_OPTIONS} columnData column properties
937
+ * @param {string|number} [before=-1] column name or order to be inserted before
938
+ * @param {boolean=true} render - Should render now?
939
+ * @returns {DGTable} self
940
+ */
941
+ addColumn(columnData, before, render) {
942
+ const p = this._p;
943
+ let columns = p.columns;
944
+
945
+ if (columnData && !columns.get(columnData.name)) {
946
+ let beforeColumn = null;
947
+ if (before !== undefined) {
948
+ beforeColumn = columns.get(before) || columns.getByOrder(before);
949
+ }
950
+
951
+ let column = this._initColumnFromData(columnData);
952
+ column.order = beforeColumn ? beforeColumn.order : (columns.getMaxOrder() + 1);
953
+
954
+ for (let i = columns.getMaxOrder(), to = column.order; i >= to ; i--) {
955
+ let col = columns.getByOrder(i);
956
+ if (col) {
957
+ col.order++;
958
+ }
959
+ }
960
+
961
+ columns.push(column);
962
+ columns.normalizeOrder();
963
+
964
+ p.visibleColumns = columns.getVisibleColumns();
965
+ this._ensureVisibleColumns().clearAndRender(render);
966
+
967
+ this.emit('addcolumn', column.name);
968
+ }
969
+ return this;
970
+ }
971
+
972
+ /**
973
+ * Remove a column from the table
974
+ * @public
975
+ * @expose
976
+ * @param {string} column column name
977
+ * @param {boolean=true} render - Should render now?
978
+ * @returns {DGTable} self
979
+ */
980
+ removeColumn(column, render) {
981
+ const p = this._p;
982
+ let columns = p.columns;
983
+
984
+ let colIdx = columns.indexOf(column);
985
+ if (colIdx > -1) {
986
+ columns.splice(colIdx, 1);
987
+ columns.normalizeOrder();
988
+
989
+ p.visibleColumns = columns.getVisibleColumns();
990
+ this._ensureVisibleColumns().clearAndRender(render);
991
+
992
+ this.emit('removecolumn', column);
993
+ }
994
+ return this;
995
+ }
996
+
997
+ /**
998
+ * Sets a new cell formatter.
999
+ * @public
1000
+ * @expose
1001
+ * @param {function(value: *, columnName: string, row: Object):string|null} [formatter=null] - The cell formatter. Should return an HTML.
1002
+ * @returns {DGTable} self
1003
+ */
1004
+ setCellFormatter(formatter) {
1005
+ if (!formatter) {
1006
+ formatter = val => (typeof val === 'string') ? htmlEncode(val) : val;
1007
+ formatter[IsSafeSymbol] = true;
1008
+ }
1009
+
1010
+ /**
1011
+ * @private
1012
+ * @field {Function} cellFormatter */
1013
+ this._o.cellFormatter = formatter;
1014
+
1015
+ return this;
1016
+ }
1017
+
1018
+ /**
1019
+ * Sets a new header cell formatter.
1020
+ * @public
1021
+ * @expose
1022
+ * @param {function(label: string, columnName: string):string|null} [formatter=null] - The cell formatter. Should return an HTML.
1023
+ * @returns {DGTable} self
1024
+ */
1025
+ setHeaderCellFormatter(formatter) {
1026
+ /**
1027
+ * @private
1028
+ * @field {Function} headerCellFormatter */
1029
+ this._o.headerCellFormatter = formatter || function (val) {
1030
+ return (typeof val === 'string') ? htmlEncode(val) : val;
1031
+ };
1032
+
1033
+ return this;
1034
+ }
1035
+
1036
+ /**
1037
+ * @public
1038
+ * @expose
1039
+ * @param {function(row:Object,args:Object):boolean|null} [filterFunc=null] - The filter function to work with filters. Default is a by-colum filter.
1040
+ * @returns {DGTable} self
1041
+ */
1042
+ setFilter(filterFunc) {
1043
+ /** @private
1044
+ * @field {Function} filter */
1045
+ this._o.filter = filterFunc;
1046
+ return this;
1047
+ }
1048
+
1049
+ /**
1050
+ * @public
1051
+ * @expose
1052
+ * @param {Object|null} args - Options to pass to the filter function
1053
+ * @returns {DGTable} self
1054
+ */
1055
+ filter(args) {
1056
+ const p = this._p;
1057
+
1058
+ let filterFunc = this._o.filter || ByColumnFilter;
1059
+
1060
+ // Deprecated use of older by-column filter
1061
+ if (typeof arguments[0] === 'string' && typeof arguments[1] === 'string') {
1062
+ args = {
1063
+ column: arguments[0],
1064
+ keyword: arguments[1],
1065
+ caseSensitive: arguments[2],
1066
+ };
1067
+ }
1068
+
1069
+ let hadFilter = !!p.filteredRows;
1070
+ if (p.filteredRows) {
1071
+ p.filteredRows = null; // Allow releasing array memory now
1072
+ }
1073
+
1074
+ // Shallow-clone the args, as the filter function may want to modify it for keeping state
1075
+ p.filterArgs = args == null ? null : ((typeof args === 'object' && !Array.isArray(args)) ? Object.assign({}, args) : args);
1076
+
1077
+ if (p.filterArgs !== null) {
1078
+ p.filteredRows = p.rows.filteredCollection(filterFunc, p.filterArgs);
1079
+
1080
+ if (hadFilter || p.filteredRows) {
1081
+ this.clearAndRender();
1082
+ this.emit('filter', args);
1083
+ }
1084
+ }
1085
+ else {
1086
+ p.filterArgs = null;
1087
+ p.filteredRows = null;
1088
+ this.clearAndRender();
1089
+ this.emit('filterclear', {});
1090
+ }
1091
+
1092
+ return this;
1093
+ }
1094
+
1095
+ /**
1096
+ * @public
1097
+ * @expose
1098
+ * @returns {DGTable} self
1099
+ */
1100
+ clearFilter() {
1101
+ const p = this._p;
1102
+
1103
+ if (p.filteredRows) {
1104
+ p.filterArgs = null;
1105
+ p.filteredRows = null;
1106
+ this.clearAndRender();
1107
+ this.emit('filterclear', {});
1108
+ }
1109
+
1110
+ return this;
1111
+ }
1112
+
1113
+ /**
1114
+ * @private
1115
+ * @returns {DGTable} self
1116
+ */
1117
+ _refilter() {
1118
+ const p = this._p;
1119
+
1120
+ if (p.filteredRows && p.filterArgs) {
1121
+ let filterFunc = this._o.filter || ByColumnFilter;
1122
+ p.filteredRows = p.rows.filteredCollection(filterFunc, p.filterArgs);
1123
+ }
1124
+ return this;
1125
+ }
1126
+
1127
+ /**
1128
+ * Set a new label to a column
1129
+ * @public
1130
+ * @expose
1131
+ * @param {string} column Name of the column
1132
+ * @param {string} label New label for the column
1133
+ * @returns {DGTable} self
1134
+ */
1135
+ setColumnLabel(column, label) {
1136
+ const p = this._p;
1137
+
1138
+ let col = p.columns.get(column);
1139
+ if (col) {
1140
+ col.label = label === undefined ? col.name : label;
1141
+
1142
+ if (col.element) {
1143
+ for (let i = 0; i < col.element.firstChild.childNodes.length; i++) {
1144
+ let node = col.element.firstChild.childNodes[i];
1145
+ if (node.nodeType === 3) {
1146
+ node.textContent = col.label;
1147
+ break;
1148
+ }
1149
+ }
1150
+ }
1151
+ }
1152
+ return this;
1153
+ }
1154
+
1155
+ /**
1156
+ * Move a column to a new position
1157
+ * @public
1158
+ * @expose
1159
+ * @param {string|number} src Name or position of the column to be moved
1160
+ * @param {string|number} dest Name of the column currently in the desired position, or the position itself
1161
+ * @param {boolean} [visibleOnly=true] Should consider only visible columns and visible-relative indexes
1162
+ * @returns {DGTable} self
1163
+ */
1164
+ moveColumn(src, dest, visibleOnly = true) {
1165
+ const o = this._o, p = this._p;
1166
+
1167
+ let columns = p.columns,
1168
+ col, destCol;
1169
+
1170
+ let columnsArray = visibleOnly ? p.visibleColumns : columns.getColumns();
1171
+
1172
+ if (typeof src === 'string') {
1173
+ col = columns.get(src);
1174
+ } else if (typeof src === 'number') {
1175
+ col = columnsArray[src];
1176
+ }
1177
+ if (typeof dest === 'string') {
1178
+ destCol = columns.get(dest);
1179
+ } else if (typeof dest === 'number') {
1180
+ destCol = columnsArray[dest];
1181
+ }
1182
+
1183
+ if (col && destCol && src !== dest) {
1184
+ let srcOrder = col.order, destOrder = destCol.order;
1185
+
1186
+ let visibleColumns = columns.moveColumn(col, destCol).getVisibleColumns();
1187
+
1188
+ if (p.visibleColumns.length !== visibleColumns.length ||
1189
+ p.visibleColumns.some((x, i) => x !== visibleColumns[i])) {
1190
+
1191
+ p.visibleColumns = visibleColumns;
1192
+ this._ensureVisibleColumns();
1193
+
1194
+ if (o.virtualTable) {
1195
+ this.clearAndRender();
1196
+ } else {
1197
+ const headerCells = scopedSelectorAll(p.headerRow, `>div.${o.tableClassName}-header-cell`);
1198
+ let beforePos = srcOrder < destOrder ? destOrder + 1 : destOrder,
1199
+ fromPos = srcOrder;
1200
+ headerCells[0].parentNode.insertBefore(headerCells[fromPos], headerCells[beforePos]);
1201
+
1202
+ let srcWidth = p.visibleColumns[srcOrder];
1203
+ srcWidth = (srcWidth.actualWidthConsideringScrollbarWidth || srcWidth.actualWidth) + 'px';
1204
+ let destWidth = p.visibleColumns[destOrder];
1205
+ destWidth = (destWidth.actualWidthConsideringScrollbarWidth || destWidth.actualWidth) + 'px';
1206
+
1207
+ let tbodyChildren = p.tbody.childNodes;
1208
+ for (let i = 0, count = tbodyChildren.length; i < count; i++) {
1209
+ let row = tbodyChildren[i];
1210
+ if (row.nodeType !== 1) continue;
1211
+ row.insertBefore(row.childNodes[fromPos], row.childNodes[beforePos]);
1212
+ row.childNodes[destOrder].firstChild.style.width = destWidth;
1213
+ row.childNodes[srcOrder].firstChild.style.width = srcWidth;
1214
+ }
1215
+ }
1216
+ }
1217
+
1218
+ this.emit('movecolumn', { name: col.name, src: srcOrder, dest: destOrder });
1219
+ }
1220
+ return this;
1221
+ }
1222
+
1223
+ /**
1224
+ * Sort the table
1225
+ * @public
1226
+ * @expose
1227
+ * @param {string?} column Name of the column to sort on (or null to remove sort arrow)
1228
+ * @param {boolean=} descending Sort in descending order
1229
+ * @param {boolean} [add=false] Should this sort be on top of the existing sort? (For multiple column sort)
1230
+ * @returns {DGTable} self
1231
+ */
1232
+ sort(column, descending, add) {
1233
+ const o = this._o, p = this._p;
1234
+
1235
+ let columns = p.columns,
1236
+ col = columns.get(column);
1237
+
1238
+ let currentSort = p.rows.sortColumn;
1239
+
1240
+ if (col) {
1241
+ if (add) { // Add the sort to current sort stack
1242
+
1243
+ for (let i = 0; i < currentSort.length; i++) {
1244
+ if (currentSort[i].column === col.name) {
1245
+ if (i < currentSort.length - 1) {
1246
+ currentSort.length = 0;
1247
+ } else {
1248
+ descending = currentSort[currentSort.length - 1].descending;
1249
+ currentSort.splice(currentSort.length - 1, 1);
1250
+ }
1251
+ break;
1252
+ }
1253
+ }
1254
+ if ((o.sortableColumns > 0 /* allow manual sort when disabled */ && currentSort.length >= o.sortableColumns) || currentSort.length >= p.visibleColumns.length) {
1255
+ currentSort.length = 0;
1256
+ }
1257
+
1258
+ } else { // Sort only by this column
1259
+ currentSort.length = 0;
1260
+ }
1261
+
1262
+ // Default to ascending
1263
+ descending = descending === undefined ? false : descending;
1264
+
1265
+ // Set the required column in the front of the stack
1266
+ currentSort.push({
1267
+ column: col.name,
1268
+ comparePath: col.comparePath || col.dataPath,
1269
+ descending: !!descending,
1270
+ });
1271
+ } else {
1272
+ currentSort.length = 0;
1273
+ }
1274
+
1275
+ this._clearSortArrows();
1276
+
1277
+ for (let i = 0; i < currentSort.length; i++) {
1278
+ this._showSortArrow(currentSort[i].column, currentSort[i].descending);
1279
+ }
1280
+
1281
+ if (o.adjustColumnWidthForSortArrow && !p.tableSkeletonNeedsRendering) {
1282
+ this.tableWidthChanged(true);
1283
+ }
1284
+
1285
+ p.rows.sortColumn = currentSort;
1286
+
1287
+ let comparator;
1288
+ if (currentSort.length) {
1289
+ comparator = p.rows.sort(!!p.filteredRows);
1290
+ if (p.filteredRows) {
1291
+ p.filteredRows.sort(!!p.filteredRows);
1292
+ }
1293
+ }
1294
+
1295
+ if (p.virtualListHelper)
1296
+ p.virtualListHelper.invalidate().render();
1297
+
1298
+ // Build output for event, with option names that will survive compilers
1299
+ let sorts = [];
1300
+ for (let i = 0; i < currentSort.length; i++) {
1301
+ sorts.push({ 'column': currentSort[i].column, 'descending': currentSort[i].descending });
1302
+ }
1303
+ this.emit('sort', { sorts: sorts, comparator: comparator });
1304
+
1305
+ return this;
1306
+ }
1307
+
1308
+ /**
1309
+ * Re-sort the table using current sort specifiers
1310
+ * @public
1311
+ * @expose
1312
+ * @returns {DGTable} self
1313
+ */
1314
+ resort() {
1315
+ const p = this._p;
1316
+ let columns = p.columns;
1317
+
1318
+ let currentSort = p.rows.sortColumn;
1319
+ if (currentSort.length) {
1320
+
1321
+ for (let i = 0; i < currentSort.length; i++) {
1322
+ if (!columns.get(currentSort[i].column)) {
1323
+ currentSort.splice(i--, 1);
1324
+ }
1325
+ }
1326
+
1327
+ let comparator;
1328
+ p.rows.sortColumn = currentSort;
1329
+ if (currentSort.length) {
1330
+ comparator = p.rows.sort(!!p.filteredRows);
1331
+ if (p.filteredRows) {
1332
+ p.filteredRows.sort(!!p.filteredRows);
1333
+ }
1334
+ }
1335
+
1336
+ // Build output for event, with option names that will survive compilers
1337
+ let sorts = [];
1338
+ for (let i = 0; i < currentSort.length; i++) {
1339
+ sorts.push({ 'column': currentSort[i].column, 'descending': currentSort[i].descending });
1340
+ }
1341
+ this.emit('sort', { sorts: sorts, resort: true, comparator: comparator });
1342
+ }
1343
+
1344
+ return this;
1345
+ }
1346
+
1347
+ /**
1348
+ * Make sure there's at least one column visible
1349
+ * @private
1350
+ * @expose
1351
+ * @returns {DGTable} self
1352
+ */
1353
+ _ensureVisibleColumns() {
1354
+ const p = this._p;
1355
+
1356
+ if (p.visibleColumns.length === 0 && p.columns.length) {
1357
+ p.columns[0].visible = true;
1358
+ p.visibleColumns.push(p.columns[0]);
1359
+ this.emit('showcolumn', p.columns[0].name);
1360
+ }
1361
+
1362
+ return this;
1363
+ }
1364
+
1365
+ /**
1366
+ * Show or hide a column
1367
+ * @public
1368
+ * @expose
1369
+ * @param {string} column Unique column name
1370
+ * @param {boolean} visible New visibility mode for the column
1371
+ * @returns {DGTable} self
1372
+ */
1373
+ setColumnVisible(column, visible) {
1374
+ const p = this._p;
1375
+
1376
+ let col = p.columns.get(column);
1377
+
1378
+ //noinspection PointlessBooleanExpressionJS
1379
+ visible = !!visible;
1380
+
1381
+ if (col && !!col.visible !== visible) {
1382
+ col.visible = visible;
1383
+ p.visibleColumns = p.columns.getVisibleColumns();
1384
+ this.emit(visible ? 'showcolumn' : 'hidecolumn', column);
1385
+ this._ensureVisibleColumns();
1386
+ this.clearAndRender();
1387
+ }
1388
+ return this;
1389
+ }
1390
+
1391
+ /**
1392
+ * Get the visibility mode of a column
1393
+ * @public
1394
+ * @expose
1395
+ * @returns {boolean} true if visible
1396
+ */
1397
+ isColumnVisible(column) {
1398
+ const p = this._p;
1399
+ let col = p.columns.get(column);
1400
+ if (col) {
1401
+ return col.visible;
1402
+ }
1403
+ return false;
1404
+ }
1405
+
1406
+ /**
1407
+ * Globally set the minimum column width
1408
+ * @public
1409
+ * @expose
1410
+ * @param {number} minColumnWidth Minimum column width
1411
+ * @returns {DGTable} self
1412
+ */
1413
+ setMinColumnWidth(minColumnWidth) {
1414
+ let o = this._o;
1415
+ minColumnWidth = Math.max(minColumnWidth, 0);
1416
+ if (o.minColumnWidth !== minColumnWidth) {
1417
+ o.minColumnWidth = minColumnWidth;
1418
+ this.tableWidthChanged(true);
1419
+ }
1420
+ return this;
1421
+ }
1422
+
1423
+ /**
1424
+ * Get the current minimum column width
1425
+ * @public
1426
+ * @expose
1427
+ * @returns {number} Minimum column width
1428
+ */
1429
+ getMinColumnWidth() {
1430
+ return this._o.minColumnWidth;
1431
+ }
1432
+
1433
+ /**
1434
+ * Set the limit on concurrent columns sorted
1435
+ * @public
1436
+ * @expose
1437
+ * @param {number} sortableColumns How many sortable columns to allow?
1438
+ * @returns {DGTable} self
1439
+ */
1440
+ setSortableColumns(sortableColumns) {
1441
+ const p = this._p, o = this._o;
1442
+ if (o.sortableColumns !== sortableColumns) {
1443
+ o.sortableColumns = sortableColumns;
1444
+ if (p.table) {
1445
+ const headerCells = scopedSelectorAll(p.headerRow, `>div.${o.tableClassName}-header-cell`);
1446
+ for (let i = 0, len = headerCells.length; i < len; i++) {
1447
+ const cell = headerCells[i];
1448
+ cell.classList[(o.sortableColumns > 0 && p.visibleColumns[i].sortable) ? 'add' : 'remove']('sortable');
1449
+ }
1450
+ }
1451
+ }
1452
+ return this;
1453
+ }
1454
+
1455
+ /**
1456
+ * Get the limit on concurrent columns sorted
1457
+ * @public
1458
+ * @expose
1459
+ * @returns {number} How many sortable columns are allowed?
1460
+ */
1461
+ getSortableColumns() {
1462
+ return this._o.sortableColumns;
1463
+ }
1464
+
1465
+ /**
1466
+ * @public
1467
+ * @expose
1468
+ * @param {boolean?} movableColumns=true are the columns movable?
1469
+ * @returns {DGTable} self
1470
+ */
1471
+ setMovableColumns(movableColumns) {
1472
+ let o = this._o;
1473
+ //noinspection PointlessBooleanExpressionJS
1474
+ movableColumns = movableColumns === undefined ? true : !!movableColumns;
1475
+ if (o.movableColumns !== movableColumns) {
1476
+ o.movableColumns = movableColumns;
1477
+ }
1478
+ return this;
1479
+ }
1480
+
1481
+ /**
1482
+ * @public
1483
+ * @expose
1484
+ * @returns {boolean} are the columns movable?
1485
+ */
1486
+ getMovableColumns() {
1487
+ return this._o.movableColumns;
1488
+ }
1489
+
1490
+ /**
1491
+ * @public
1492
+ * @expose
1493
+ * @param {boolean} resizableColumns=true are the columns resizable?
1494
+ * @returns {DGTable} self
1495
+ */
1496
+ setResizableColumns(resizableColumns) {
1497
+ let o = this._o;
1498
+ //noinspection PointlessBooleanExpressionJS
1499
+ resizableColumns = resizableColumns === undefined ? true : !!resizableColumns;
1500
+ if (o.resizableColumns !== resizableColumns) {
1501
+ o.resizableColumns = resizableColumns;
1502
+ }
1503
+ return this;
1504
+ }
1505
+
1506
+ /**
1507
+ * @public
1508
+ * @expose
1509
+ * @returns {boolean} are the columns resizable?
1510
+ */
1511
+ getResizableColumns() {
1512
+ return this._o.resizableColumns;
1513
+ }
1514
+
1515
+ /**
1516
+ * Sets a functions that supplies comparators dynamically
1517
+ * @public
1518
+ * @expose
1519
+ * @param {{function(columnName: string, descending: boolean, defaultComparator: {function(a:any,b:any):number}):{function(a:any,b:any):number}}|null|undefined} comparatorCallback a function that returns the comparator for a specific column
1520
+ * @returns {DGTable} self
1521
+ */
1522
+ setOnComparatorRequired(comparatorCallback) {
1523
+ let o = this._o;
1524
+ if (o.onComparatorRequired !== comparatorCallback) {
1525
+ o.onComparatorRequired = comparatorCallback;
1526
+ }
1527
+ return this;
1528
+ }
1529
+
1530
+ // Backwards compatibility
1531
+ setComparatorCallback(comparatorCallback) {
1532
+ return this.setOnComparatorRequired(comparatorCallback);
1533
+ }
1534
+
1535
+ /**
1536
+ * sets custom sorting function for a data set
1537
+ * @public
1538
+ * @expose
1539
+ * @param {{function(data: any[], sort: function(any[]):any[]):any[]}|null|undefined} customSortingProvider provides a custom sorting function (not the comparator, but a sort() alternative) for a data set
1540
+ * @returns {DGTable} self
1541
+ */
1542
+ setCustomSortingProvider(customSortingProvider) {
1543
+ let o = this._o;
1544
+ if (o.customSortingProvider !== customSortingProvider) {
1545
+ o.customSortingProvider = customSortingProvider;
1546
+ }
1547
+ return this;
1548
+ }
1549
+
1550
+ /**
1551
+ * Set a new width to a column
1552
+ * @public
1553
+ * @expose
1554
+ * @param {string} column name of the column to resize
1555
+ * @param {number|string} width new column as pixels, or relative size (0.5, 50%)
1556
+ * @returns {DGTable} self
1557
+ */
1558
+ setColumnWidth(column, width) {
1559
+
1560
+ const p = this._p;
1561
+
1562
+ let col = p.columns.get(column);
1563
+
1564
+ let parsedWidth = this._parseColumnWidth(width, col.ignoreMin ? 0 : this._o.minColumnWidth);
1565
+
1566
+ if (col) {
1567
+ let oldWidth = this._serializeColumnWidth(col);
1568
+
1569
+ col.width = parsedWidth.width;
1570
+ col.widthMode = parsedWidth.mode;
1571
+
1572
+ let newWidth = this._serializeColumnWidth(col);
1573
+
1574
+ if (oldWidth !== newWidth) {
1575
+ this.tableWidthChanged(true); // Calculate actual sizes
1576
+ }
1577
+
1578
+ this.emit('columnwidth', { name: col.name, width: newWidth, oldWidth: oldWidth });
1579
+ }
1580
+ return this;
1581
+ }
1582
+
1583
+ /**
1584
+ * @public
1585
+ * @expose
1586
+ * @param {string} column name of the column
1587
+ * @returns {string|null} the serialized width of the specified column, or null if column not found
1588
+ */
1589
+ getColumnWidth(column) {
1590
+ const p = this._p;
1591
+
1592
+ let col = p.columns.get(column);
1593
+ if (col) {
1594
+ return this._serializeColumnWidth(col);
1595
+ }
1596
+ return null;
1597
+ }
1598
+
1599
+ /**
1600
+ * @public
1601
+ * @expose
1602
+ * @param {string} column name of the column
1603
+ * @returns {SERIALIZED_COLUMN|null} configuration for all columns
1604
+ */
1605
+ getColumnConfig(column) {
1606
+ const p = this._p;
1607
+ let col = p.columns.get(column);
1608
+ if (col) {
1609
+ return {
1610
+ 'order': col.order,
1611
+ 'width': this._serializeColumnWidth(col),
1612
+ 'visible': col.visible,
1613
+ 'label': col.label,
1614
+ };
1615
+ }
1616
+ return null;
1617
+ }
1618
+
1619
+ /**
1620
+ * Returns a config object for the columns, to allow saving configurations for next time...
1621
+ * @public
1622
+ * @expose
1623
+ * @returns {Object} configuration for all columns
1624
+ */
1625
+ getColumnsConfig() {
1626
+ const p = this._p;
1627
+
1628
+ let config = {};
1629
+ for (let i = 0; i < p.columns.length; i++) {
1630
+ config[p.columns[i].name] = this.getColumnConfig(p.columns[i].name);
1631
+ }
1632
+ return config;
1633
+ }
1634
+
1635
+ /**
1636
+ * Returns an array of the currently sorted columns
1637
+ * @public
1638
+ * @expose
1639
+ * @returns {Array.<SERIALIZED_COLUMN_SORT>} configuration for all columns
1640
+ */
1641
+ getSortedColumns() {
1642
+ const p = this._p;
1643
+
1644
+ let sorted = [];
1645
+ for (let i = 0; i < p.rows.sortColumn.length; i++) {
1646
+ let sort = p.rows.sortColumn[i];
1647
+ sorted.push({ column: sort.column, descending: sort.descending });
1648
+ }
1649
+ return sorted;
1650
+ }
1651
+
1652
+ /**
1653
+ * Returns the HTML string for a specific cell. Can be used externally for special cases (i.e. when setting a fresh HTML in the cell preview through the callback).
1654
+ * @public
1655
+ * @expose
1656
+ * @param {number} rowIndex - index of the row
1657
+ * @param {string} columnName - name of the column
1658
+ * @returns {string|null} HTML string for the specified cell
1659
+ */
1660
+ getHtmlForRowCell(rowIndex, columnName) {
1661
+ const p = this._p;
1662
+
1663
+ if (rowIndex < 0 || rowIndex > p.rows.length - 1) return null;
1664
+ let column = p.columns.get(columnName);
1665
+ if (!column) return null;
1666
+ let rowData = p.rows[rowIndex];
1667
+
1668
+ return this._getHtmlForCell(rowData, column);
1669
+ }
1670
+
1671
+ /**
1672
+ * Returns the HTML string for a specific cell. Can be used externally for special cases (i.e. when setting a fresh HTML in the cell preview through the callback).
1673
+ * @public
1674
+ * @expose
1675
+ * @param {Object} rowData - row data
1676
+ * @param {Object} columnName - column data
1677
+ * @returns {string|null} HTML string for the specified cell
1678
+ */
1679
+ getHtmlForRowDataCell(rowData, columnName) {
1680
+ const p = this._p;
1681
+
1682
+ let column = p.columns.get(columnName);
1683
+ if (!column) return null;
1684
+
1685
+ return this._getHtmlForCell(rowData, column);
1686
+ }
1687
+
1688
+ /**
1689
+ * Returns the HTML string for a specific cell. Can be used externally for special cases (i.e. when setting a fresh HTML in the cell preview through the callback).
1690
+ * @private
1691
+ * @expose
1692
+ * @param {Object} rowData - row data
1693
+ * @param {Object} column - column data
1694
+ * @returns {string} HTML string for the specified cell
1695
+ */
1696
+ _getHtmlForCell(rowData, column) {
1697
+ let dataPath = column.dataPath;
1698
+ let colValue = rowData[dataPath[0]];
1699
+ for (let dataPathIndex = 1; dataPathIndex < dataPath.length; dataPathIndex++) {
1700
+ if (colValue == null) break;
1701
+ colValue = colValue && colValue[dataPath[dataPathIndex]];
1702
+ }
1703
+
1704
+ const formatter = this._o.cellFormatter;
1705
+ let content;
1706
+
1707
+ if (formatter[IsSafeSymbol]) {
1708
+ content = formatter(colValue, column.name, rowData);
1709
+ } else {
1710
+ try {
1711
+ content = formatter(colValue, column.name, rowData);
1712
+ } catch (err) {
1713
+ content = '[ERROR]';
1714
+ // eslint-disable-next-line no-console
1715
+ console.error('Failed to generate content for cell ' + column.name, err);
1716
+ }
1717
+ }
1718
+
1719
+ if (content === undefined || content === null) {
1720
+ content = '';
1721
+ }
1722
+
1723
+ return content;
1724
+ }
1725
+
1726
+ /**
1727
+ * Returns the y pos of a row by index
1728
+ * @public
1729
+ * @expose
1730
+ * @param {number} rowIndex - index of the row
1731
+ * @returns {number|null} Y pos
1732
+ */
1733
+ getRowYPos(rowIndex) {
1734
+ const p = this._p;
1735
+
1736
+ return p.virtualListHelper.getItemPosition(rowIndex) || null;
1737
+ }
1738
+
1739
+ /**
1740
+ * Returns the row data for a specific row
1741
+ * @public
1742
+ * @expose
1743
+ * @param {number} row index of the row
1744
+ * @returns {Object} Row data
1745
+ */
1746
+ getDataForRow(row) {
1747
+ const p = this._p;
1748
+
1749
+ if (row < 0 || row > p.rows.length - 1) return null;
1750
+ return p.rows[row];
1751
+ }
1752
+
1753
+ /**
1754
+ * Gets the number of rows
1755
+ * @public
1756
+ * @expose
1757
+ * @returns {number} Row count
1758
+ */
1759
+ getRowCount() {
1760
+ const p = this._p;
1761
+ return p.rows ? p.rows.length : 0;
1762
+ }
1763
+
1764
+ /**
1765
+ * Returns the actual row index for specific row (out of the full data set, not filtered)
1766
+ * @public
1767
+ * @expose
1768
+ * @param {Object} rowData - Row data to find
1769
+ * @returns {number} Row index
1770
+ */
1771
+ getIndexForRow(rowData) {
1772
+ const p = this._p;
1773
+ return p.rows.indexOf(rowData);
1774
+ }
1775
+
1776
+ /**
1777
+ * Gets the number of filtered rows
1778
+ * @public
1779
+ * @expose
1780
+ * @returns {number} Filtered row count
1781
+ */
1782
+ getFilteredRowCount() {
1783
+ const p = this._p;
1784
+ return (p.filteredRows || p.rows).length;
1785
+ }
1786
+
1787
+ /**
1788
+ * Returns the filtered row index for specific row
1789
+ * @public
1790
+ * @expose
1791
+ * @param {Object} rowData - Row data to find
1792
+ * @returns {number} Row index
1793
+ */
1794
+ getIndexForFilteredRow(rowData) {
1795
+ const p = this._p;
1796
+ return (p.filteredRows || p.rows).indexOf(rowData);
1797
+ }
1798
+
1799
+ /**
1800
+ * Returns the row data for a specific row
1801
+ * @public
1802
+ * @expose
1803
+ * @param {number} row index of the filtered row
1804
+ * @returns {Object} Row data
1805
+ */
1806
+ getDataForFilteredRow(row) {
1807
+ const p = this._p;
1808
+ if (row < 0 || row > (p.filteredRows || p.rows).length - 1) return null;
1809
+ return (p.filteredRows || p.rows)[row];
1810
+ }
1811
+
1812
+ /**
1813
+ * Returns DOM element of the header row
1814
+ * @public
1815
+ * @expose
1816
+ * @returns {Element} Row element
1817
+ */
1818
+ getHeaderRowElement() {
1819
+ return this._p.headerRow;
1820
+ }
1821
+
1822
+ /**
1823
+ * @private
1824
+ * @param {Element} el
1825
+ * @returns {number} width
1826
+ */
1827
+ _horizontalPadding(el) {
1828
+ const style = getComputedStyle(el);
1829
+ return ((parseFloat(style.paddingLeft) || 0) +
1830
+ (parseFloat(style.paddingRight) || 0));
1831
+ }
1832
+
1833
+ /**
1834
+ * @private
1835
+ * @param {Element} el
1836
+ * @returns {number} width
1837
+ */
1838
+ _horizontalBorderWidth(el) {
1839
+ const style = getComputedStyle(el);
1840
+ return ((parseFloat(style.borderLeftWidth) || 0) +
1841
+ (parseFloat(style.borderRightWidth) || 0));
1842
+ }
1843
+
1844
+ /**
1845
+ * @private
1846
+ * @returns {number} width
1847
+ */
1848
+ _calculateWidthAvailableForColumns() {
1849
+ const o = this._o, p = this._p;
1850
+
1851
+ // Changing display mode briefly, to prevent taking in account the parent's scrollbar width when we are the cause for it
1852
+ let oldDisplay, lastScrollTop, lastScrollLeft;
1853
+ if (p.table) {
1854
+ lastScrollTop = p.table ? p.table.scrollTop : 0;
1855
+ lastScrollLeft = p.table ? p.table.scrollLeft : 0;
1856
+
1857
+ if (o.virtualTable) {
1858
+ oldDisplay = p.table.style.display;
1859
+ p.table.style.display = 'none';
1860
+ }
1861
+ }
1862
+
1863
+ let detectedWidth = getElementWidth(this.el);
1864
+
1865
+ if (p.table) {
1866
+ if (o.virtualTable) {
1867
+ p.table.style.display = oldDisplay;
1868
+ }
1869
+
1870
+ p.table.scrollTop = lastScrollTop;
1871
+ p.table.scrollLeft = lastScrollLeft;
1872
+ p.header.scrollLeft = lastScrollLeft;
1873
+ }
1874
+
1875
+ let tableClassName = o.tableClassName;
1876
+
1877
+ const thisWrapper = createElement('div');
1878
+ thisWrapper.className = this.el.className;
1879
+ setCssProps(thisWrapper, {
1880
+ 'z-index': -1,
1881
+ 'position': 'absolute',
1882
+ left: '0',
1883
+ top: '-9999px',
1884
+ });
1885
+ let header = createElement('div');
1886
+ header.className = `${tableClassName}-header`;
1887
+ thisWrapper.appendChild(header);
1888
+ let headerRow = createElement('div');
1889
+ headerRow.className = `${tableClassName}-header-row`;
1890
+ header.appendChild(headerRow);
1891
+ for (let i = 0; i < p.visibleColumns.length; i++) {
1892
+ const column = p.visibleColumns[i];
1893
+ const cell = createElement('div');
1894
+ cell.className = `${tableClassName}-header-cell ${column.cellClasses || ''}`;
1895
+ cell['columnName'] = column.name;
1896
+ cell.appendChild(createElement('div'));
1897
+ headerRow.appendChild(cell);
1898
+ }
1899
+ document.body.appendChild(thisWrapper);
1900
+
1901
+ detectedWidth -= this._horizontalBorderWidth(headerRow);
1902
+
1903
+ let cells = scopedSelectorAll(headerRow, `>div.${tableClassName}-header-cell`);
1904
+ for (const cell of cells) {
1905
+ const cellStyle = getComputedStyle(cell);
1906
+ let isBoxing = cellStyle.boxSizing === 'border-box';
1907
+ if (!isBoxing) {
1908
+ detectedWidth -=
1909
+ (parseFloat(cellStyle.borderRightWidth) || 0) +
1910
+ (parseFloat(cellStyle.borderLeftWidth) || 0) +
1911
+ (this._horizontalPadding(cell)); // CELL's padding
1912
+
1913
+ const colName = cell['columnName'];
1914
+ const column = p.columns.get(colName);
1915
+ if (column)
1916
+ detectedWidth -= column.arrowProposedWidth || 0;
1917
+ }
1918
+ }
1919
+
1920
+ thisWrapper.remove();
1921
+
1922
+ return Math.max(0, detectedWidth);
1923
+ }
1924
+
1925
+ _getTextWidth(text) {
1926
+ let tableClassName = this._o.tableClassName;
1927
+
1928
+ const tableWrapper = createElement('div');
1929
+ tableWrapper.className = this.el.className;
1930
+ const header = createElement('div');
1931
+ header.className = tableClassName + '-header';
1932
+ const headerRow = createElement('div');
1933
+ headerRow.className = tableClassName + '-header-row';
1934
+ const cell = createElement('div');
1935
+ cell.className = tableClassName + '-header-cell';
1936
+ const cellContent = createElement('div');
1937
+ cellContent.textContent = text;
1938
+
1939
+ cell.appendChild(cellContent);
1940
+ headerRow.appendChild(cell);
1941
+ header.appendChild(headerRow);
1942
+ tableWrapper.appendChild(header);
1943
+ setCssProps(tableWrapper, {
1944
+ position: 'absolute',
1945
+ top: '-9999px',
1946
+ visibility: 'hidden',
1947
+ });
1948
+
1949
+ document.body.appendChild(tableWrapper);
1950
+
1951
+ let width = getElementWidth(cell);
1952
+
1953
+ tableWrapper.remove();
1954
+
1955
+ return width;
1956
+ }
1957
+
1958
+ /**
1959
+ * Notify the table that its width has changed
1960
+ * @public
1961
+ * @expose
1962
+ * @param {boolean} [forceUpdate=false]
1963
+ * @param {boolean} [renderColumns=true]
1964
+ * @returns {DGTable} self
1965
+ */
1966
+ tableWidthChanged(forceUpdate, renderColumns) {
1967
+
1968
+ let o = this._o,
1969
+ p = this._p,
1970
+ detectedWidth = this._calculateWidthAvailableForColumns(),
1971
+ sizeLeft = detectedWidth,
1972
+ relatives = 0;
1973
+
1974
+ if (!p.table) return this;
1975
+
1976
+ renderColumns = renderColumns === undefined || renderColumns;
1977
+
1978
+ let tableWidthBeforeCalculations = 0;
1979
+
1980
+ if (!p.tbody) {
1981
+ renderColumns = false;
1982
+ }
1983
+
1984
+ if (renderColumns) {
1985
+ tableWidthBeforeCalculations = parseFloat(p.tbody.style.minWidth) || 0;
1986
+ }
1987
+
1988
+ if (sizeLeft !== p.lastDetectedWidth || forceUpdate) {
1989
+ p.lastDetectedWidth = detectedWidth;
1990
+
1991
+ let absWidthTotal = 0, changedColumnIndexes = [], totalRelativePercentage = 0;
1992
+
1993
+ for (let i = 0; i < p.columns.length; i++) {
1994
+ p.columns[i].actualWidthConsideringScrollbarWidth = null;
1995
+ }
1996
+
1997
+ for (let i = 0; i < p.visibleColumns.length; i++) {
1998
+ let col = p.visibleColumns[i];
1999
+ if (col.widthMode === ColumnWidthMode.ABSOLUTE) {
2000
+ let width = col.width;
2001
+ width += col.arrowProposedWidth || 0; // Sort-arrow width
2002
+ if (!col.ignoreMin && width < o.minColumnWidth) {
2003
+ width = o.minColumnWidth;
2004
+ }
2005
+ sizeLeft -= width;
2006
+ absWidthTotal += width;
2007
+
2008
+ // Update actualWidth
2009
+ if (width !== col.actualWidth) {
2010
+ col.actualWidth = width;
2011
+ changedColumnIndexes.push(i);
2012
+ }
2013
+ } else if (col.widthMode === ColumnWidthMode.AUTO) {
2014
+ let width = this._getTextWidth(col.label) + 20;
2015
+ width += col.arrowProposedWidth || 0; // Sort-arrow width
2016
+ if (!col.ignoreMin && width < o.minColumnWidth) {
2017
+ width = o.minColumnWidth;
2018
+ }
2019
+ sizeLeft -= width;
2020
+ absWidthTotal += width;
2021
+
2022
+ // Update actualWidth
2023
+ if (width !== col.actualWidth) {
2024
+ col.actualWidth = width;
2025
+ if (!o.convertColumnWidthsToRelative) {
2026
+ changedColumnIndexes.push(i);
2027
+ }
2028
+ }
2029
+ } else if (col.widthMode === ColumnWidthMode.RELATIVE) {
2030
+ totalRelativePercentage += col.width;
2031
+ relatives++;
2032
+ }
2033
+ }
2034
+
2035
+ // Normalize relative sizes if needed
2036
+ if (o.convertColumnWidthsToRelative) {
2037
+ for (let i = 0; i < p.visibleColumns.length; i++) {
2038
+ let col = p.visibleColumns[i];
2039
+ if (col.widthMode === ColumnWidthMode.AUTO) {
2040
+ col.widthMode = ColumnWidthMode.RELATIVE;
2041
+ sizeLeft += col.actualWidth;
2042
+ col.width = col.actualWidth / absWidthTotal;
2043
+ totalRelativePercentage += col.width;
2044
+ relatives++;
2045
+ }
2046
+ }
2047
+ }
2048
+
2049
+ // Normalize relative sizes if needed
2050
+ if (relatives && ((totalRelativePercentage < 1 && o.relativeWidthGrowsToFillWidth) ||
2051
+ (totalRelativePercentage > 1 && o.relativeWidthShrinksToFillWidth))) {
2052
+ for (let i = 0; i < p.visibleColumns.length; i++) {
2053
+ let col = p.visibleColumns[i];
2054
+ if (col.widthMode === ColumnWidthMode.RELATIVE) {
2055
+ col.width /= totalRelativePercentage;
2056
+ }
2057
+ }
2058
+ }
2059
+
2060
+ let sizeLeftForRelative = Math.max(0, sizeLeft); // Use this as the space to take the relative widths out of
2061
+ if (sizeLeftForRelative === 0) {
2062
+ sizeLeftForRelative = p.table.clientWidth;
2063
+ }
2064
+
2065
+ let minColumnWidthRelative = (o.minColumnWidth / sizeLeftForRelative);
2066
+ if (isNaN(minColumnWidthRelative)) {
2067
+ minColumnWidthRelative = 0;
2068
+ }
2069
+ if (minColumnWidthRelative > 0) {
2070
+ let extraRelative = 0, delta;
2071
+
2072
+ // First pass - make sure they are all constrained to the minimum width
2073
+ for (let i = 0; i < p.visibleColumns.length; i++) {
2074
+ let col = p.visibleColumns[i];
2075
+ if (col.widthMode === ColumnWidthMode.RELATIVE) {
2076
+ if (!col.ignoreMin && col.width < minColumnWidthRelative) {
2077
+ extraRelative += minColumnWidthRelative - col.width;
2078
+ col.width = minColumnWidthRelative;
2079
+ }
2080
+ }
2081
+ }
2082
+
2083
+ // Second pass - try to take the extra width out of the other columns to compensate
2084
+ for (let i = 0; i < p.visibleColumns.length; i++) {
2085
+ let col = p.visibleColumns[i];
2086
+ if (col.widthMode === ColumnWidthMode.RELATIVE) {
2087
+ if (!col.ignoreMin && col.width > minColumnWidthRelative) {
2088
+ if (extraRelative > 0) {
2089
+ delta = Math.min(extraRelative, col.width - minColumnWidthRelative);
2090
+ col.width -= delta;
2091
+ extraRelative -= delta;
2092
+ }
2093
+ }
2094
+ }
2095
+ }
2096
+ }
2097
+
2098
+ // Try to fill width
2099
+ if (o.autoFillTableWidth && sizeLeft > 0) {
2100
+ let nonResizableTotal = 0;
2101
+ let sizeLeftToFill = sizeLeft;
2102
+
2103
+ for (let i = 0; i < p.visibleColumns.length; i++) {
2104
+ let col = p.visibleColumns[i];
2105
+ if (!col.resizable && col.widthMode === ColumnWidthMode.ABSOLUTE)
2106
+ nonResizableTotal += col.width;
2107
+
2108
+ if (col.widthMode === ColumnWidthMode.RELATIVE)
2109
+ sizeLeftToFill -= Math.round(sizeLeftForRelative * col.width);
2110
+ }
2111
+
2112
+ let conv = ((detectedWidth - nonResizableTotal) / (detectedWidth - sizeLeftToFill - nonResizableTotal)) || NaN;
2113
+ for (let i = 0; i < p.visibleColumns.length && sizeLeftToFill > 0; i++) {
2114
+ let col = p.visibleColumns[i];
2115
+ if (!col.resizable && col.widthMode === ColumnWidthMode.ABSOLUTE)
2116
+ continue;
2117
+
2118
+ if (col.widthMode === ColumnWidthMode.RELATIVE) {
2119
+ col.width *= conv;
2120
+ } else {
2121
+ let width = col.actualWidth * conv;
2122
+ if (col.actualWidth !== width) {
2123
+ col.actualWidth = width;
2124
+ if (changedColumnIndexes.indexOf(i) === -1)
2125
+ changedColumnIndexes.push(i);
2126
+ }
2127
+ }
2128
+ }
2129
+ }
2130
+
2131
+ // Materialize relative sizes
2132
+ for (let i = 0; i < p.visibleColumns.length; i++) {
2133
+ let col = p.visibleColumns[i];
2134
+ if (col.widthMode === ColumnWidthMode.RELATIVE) {
2135
+ let width = Math.round(sizeLeftForRelative * col.width);
2136
+ sizeLeft -= width;
2137
+ relatives--;
2138
+
2139
+ // Take care of rounding errors
2140
+ if (relatives === 0 && sizeLeft === 1) { // Take care of rounding errors
2141
+ width++;
2142
+ sizeLeft--;
2143
+ }
2144
+ if (sizeLeft === -1) {
2145
+ width--;
2146
+ sizeLeft++;
2147
+ }
2148
+
2149
+ // Update actualWidth
2150
+ if (width !== col.actualWidth) {
2151
+ col.actualWidth = width;
2152
+ changedColumnIndexes.push(i);
2153
+ }
2154
+ }
2155
+ }
2156
+
2157
+ if (p.visibleColumns.length) {
2158
+ // (There should always be at least 1 column visible, but just in case)
2159
+ p.visibleColumns[p.visibleColumns.length - 1].actualWidthConsideringScrollbarWidth =
2160
+ p.visibleColumns[p.visibleColumns.length - 1].actualWidth - (p.scrollbarWidth || 0);
2161
+ }
2162
+
2163
+ p.notifyRendererOfColumnsConfig?.();
2164
+
2165
+ if (renderColumns) {
2166
+ let tableWidth = this._calculateTbodyWidth();
2167
+
2168
+ if (tableWidthBeforeCalculations < tableWidth) {
2169
+ this._updateTableWidth(false);
2170
+ }
2171
+
2172
+ for (let i = 0; i < changedColumnIndexes.length; i++) {
2173
+ this._resizeColumnElements(changedColumnIndexes[i]);
2174
+ }
2175
+
2176
+ if (tableWidthBeforeCalculations > tableWidth) {
2177
+ this._updateTableWidth(false);
2178
+ }
2179
+ }
2180
+ }
2181
+
2182
+ return this;
2183
+ }
2184
+
2185
+ /**
2186
+ * Notify the table that its height has changed
2187
+ * @public
2188
+ * @expose
2189
+ * @returns {DGTable} self
2190
+ */
2191
+ tableHeightChanged() {
2192
+ let o = this._o,
2193
+ p = this._p;
2194
+
2195
+ if (!p.table) {
2196
+ return this;
2197
+ }
2198
+
2199
+ const tableStyle = getComputedStyle(p.table);
2200
+
2201
+ let height = getElementHeight(this.el, true)
2202
+ - (parseFloat(tableStyle.borderTopWidth) || 0) // Subtract top border of inner element
2203
+ - (parseFloat(tableStyle.borderBottomWidth) || 0); // Subtract bottom border of inner element
2204
+
2205
+ if (height !== o.height) {
2206
+
2207
+ o.height = height;
2208
+
2209
+ if (p.tbody) {
2210
+ // At least 1 pixel - to show scrollbars correctly.
2211
+ p.tbody.style.height = Math.max(o.height - getElementHeight(p.header, true, true, true), 1) + 'px';
2212
+ }
2213
+
2214
+ if (o.virtualTable) {
2215
+ this.clearAndRender();
2216
+ }
2217
+ }
2218
+
2219
+ return this;
2220
+ }
2221
+
2222
+ /**
2223
+ * Add rows to the table
2224
+ * @public
2225
+ * @expose
2226
+ * @param {Object[]} data - array of rows to add to the table
2227
+ * @param {number} [at=-1] - where to add the rows at
2228
+ * @param {boolean} [resort=false] - should resort all rows?
2229
+ * @param {boolean} [render=true]
2230
+ * @returns {DGTable} self
2231
+ */
2232
+ addRows(data, at, resort, render) {
2233
+ let p = this._p;
2234
+
2235
+ if (typeof at === 'boolean') {
2236
+ render = resort;
2237
+ resort = at;
2238
+ at = -1;
2239
+ }
2240
+
2241
+ if (typeof at !== 'number')
2242
+ at = -1;
2243
+
2244
+ if (at < 0 || at > p.rows.length)
2245
+ at = p.rows.length;
2246
+
2247
+ render = (render === undefined) ? true : !!render;
2248
+
2249
+ if (data) {
2250
+ p.rows.add(data, at);
2251
+
2252
+ if (p.filteredRows || (resort && p.rows.sortColumn.length)) {
2253
+
2254
+ if (resort && p.rows.sortColumn.length) {
2255
+ this.resort();
2256
+ } else {
2257
+ this._refilter();
2258
+ }
2259
+
2260
+ p.tableSkeletonNeedsRendering = true;
2261
+
2262
+ if (render) {
2263
+ // Render the skeleton with all rows from scratch
2264
+ this.render();
2265
+ }
2266
+
2267
+ } else if (render) {
2268
+ p.virtualListHelper.addItemsAt(data.length, at);
2269
+
2270
+ if (this._o.virtualTable) {
2271
+ this._updateVirtualHeight()
2272
+ ._updateLastCellWidthFromScrollbar() // Detect vertical scrollbar height
2273
+ .render()
2274
+ ._updateTableWidth(false); // Update table width to suit the required width considering vertical scrollbar
2275
+
2276
+ } else if (p.tbody) {
2277
+ this.render()
2278
+ ._updateLastCellWidthFromScrollbar() // Detect vertical scrollbar height, and update existing last cells
2279
+ ._updateTableWidth(true); // Update table width to suit the required width considering vertical scrollbar
2280
+ }
2281
+ }
2282
+
2283
+ this.emit('addrows', { count: data.length, clear: false });
2284
+ }
2285
+ return this;
2286
+ }
2287
+
2288
+ /**
2289
+ * Removes a row from the table
2290
+ * @public
2291
+ * @expose
2292
+ * @param {number} rowIndex - index
2293
+ * @param {number} count - how many rows to remove
2294
+ * @param {boolean=true} render
2295
+ * @returns {DGTable} self
2296
+ */
2297
+ removeRows(rowIndex, count, render) {
2298
+ let p = this._p;
2299
+
2300
+ if (typeof count !== 'number' || count <= 0) return this;
2301
+
2302
+ if (rowIndex < 0 || rowIndex > p.rows.length - 1) return this;
2303
+
2304
+ p.rows.splice(rowIndex, count);
2305
+ render = (render === undefined) ? true : !!render;
2306
+
2307
+ if (p.filteredRows) {
2308
+ this._refilter();
2309
+
2310
+ p.tableSkeletonNeedsRendering = true;
2311
+
2312
+ if (render) {
2313
+ // Render the skeleton with all rows from scratch
2314
+ this.render();
2315
+ }
2316
+
2317
+ } else if (render) {
2318
+ p.virtualListHelper.removeItemsAt(count, rowIndex);
2319
+
2320
+ if (this._o.virtualTable) {
2321
+ this._updateVirtualHeight()
2322
+ ._updateLastCellWidthFromScrollbar()
2323
+ .render()
2324
+ ._updateTableWidth(false); // Update table width to suit the required width considering vertical scrollbar
2325
+ } else {
2326
+ this.render()
2327
+ ._updateLastCellWidthFromScrollbar()
2328
+ ._updateTableWidth(true); // Update table width to suit the required width considering vertical scrollbar
2329
+ }
2330
+ }
2331
+
2332
+ return this;
2333
+ }
2334
+
2335
+ /**
2336
+ * Removes a row from the table
2337
+ * @public
2338
+ * @expose
2339
+ * @param {number} rowIndex - index
2340
+ * @param {boolean=true} render
2341
+ * @returns {DGTable} self
2342
+ */
2343
+ removeRow(rowIndex, render) {
2344
+ return this.removeRows(rowIndex, 1, render);
2345
+ }
2346
+
2347
+ /**
2348
+ * Refreshes the row specified
2349
+ * @public
2350
+ * @expose
2351
+ * @param {number} rowIndex index
2352
+ * @param {boolean} render should render the changes immediately?
2353
+ * @returns {DGTable} self
2354
+ */
2355
+ refreshRow(rowIndex, render = true) {
2356
+ let p = this._p;
2357
+
2358
+ if (rowIndex < 0 || rowIndex > p.rows.length - 1)
2359
+ return this;
2360
+
2361
+ // Find out if the row is in the rendered dataset
2362
+ let filteredRowIndex = -1;
2363
+ if (p.filteredRows && (filteredRowIndex = p.filteredRows.indexOf(p.rows[rowIndex])) === -1)
2364
+ return this;
2365
+
2366
+ if (filteredRowIndex === -1) {
2367
+ filteredRowIndex = rowIndex;
2368
+ }
2369
+
2370
+ p.virtualListHelper.refreshItemAt(filteredRowIndex);
2371
+
2372
+ if (render)
2373
+ p.virtualListHelper.render();
2374
+
2375
+ return this;
2376
+ }
2377
+
2378
+ /**
2379
+ * Get the DOM element for the specified row, if it exists
2380
+ * @public
2381
+ * @expose
2382
+ * @param {number} rowIndex index
2383
+ * @returns {Element|null} row or null
2384
+ */
2385
+ getRowElement(rowIndex) {
2386
+ let p = this._p;
2387
+
2388
+ if (rowIndex < 0 || rowIndex > p.rows.length - 1)
2389
+ return null;
2390
+
2391
+ // Find out if the row is in the rendered dataset
2392
+ let filteredRowIndex = -1;
2393
+ if (p.filteredRows && (filteredRowIndex = p.filteredRows.indexOf(p.rows[rowIndex])) === -1)
2394
+ return null;
2395
+
2396
+ if (filteredRowIndex === -1) {
2397
+ filteredRowIndex = rowIndex;
2398
+ }
2399
+
2400
+ return p.virtualListHelper.getItemElementAt(filteredRowIndex) || null;
2401
+ }
2402
+
2403
+ /**
2404
+ * Refreshes all virtual rows
2405
+ * @public
2406
+ * @expose
2407
+ * @returns {DGTable} self
2408
+ */
2409
+ refreshAllVirtualRows() {
2410
+ const p = this._p;
2411
+ p.virtualListHelper.invalidate().render();
2412
+ return this;
2413
+ }
2414
+
2415
+ /**
2416
+ * Replace the whole dataset
2417
+ * @public
2418
+ * @expose
2419
+ * @param {Object[]} data array of rows to add to the table
2420
+ * @param {boolean} [resort=false] should resort all rows?
2421
+ * @returns {DGTable} self
2422
+ */
2423
+ setRows(data, resort) {
2424
+ let p = this._p;
2425
+
2426
+ // this.scrollTop = this.$el.find('.table').scrollTop();
2427
+ p.rows.reset(data);
2428
+
2429
+ if (resort && p.rows.sortColumn.length) {
2430
+ this.resort();
2431
+ } else {
2432
+ this._refilter();
2433
+ }
2434
+
2435
+ this.clearAndRender().trigger('addrows', { count: data.length, clear: true });
2436
+
2437
+ return this;
2438
+ }
2439
+
2440
+ /**
2441
+ * Creates a URL representing the data in the specified element.
2442
+ * This uses the Blob or BlobBuilder of the modern browsers.
2443
+ * The url can be used for a Web Worker.
2444
+ * @public
2445
+ * @expose
2446
+ * @param {string} id Id of the element containing your data
2447
+ * @returns {string|null} the url, or null if not supported
2448
+ */
2449
+ getUrlForElementContent(id) {
2450
+ let blob,
2451
+ el = document.getElementById(id);
2452
+ if (el) {
2453
+ let data = el.textContent;
2454
+ if (typeof Blob === 'function') {
2455
+ blob = new Blob([data]);
2456
+ } else {
2457
+ let BlobBuilder = window.BlobBuilder || window.WebKitBlobBuilder || window.MozBlobBuilder || window.MSBlobBuilder;
2458
+ if (!BlobBuilder) {
2459
+ return null;
2460
+ }
2461
+ let builder = new BlobBuilder();
2462
+ builder.append(data);
2463
+ blob = builder.getBlob();
2464
+ }
2465
+ return (window.URL || window.webkitURL).createObjectURL(blob);
2466
+ }
2467
+ return null;
2468
+ }
2469
+
2470
+ /**
2471
+ * @public
2472
+ * @expose
2473
+ * @returns {boolean} A value indicating whether Web Workers are supported
2474
+ */
2475
+ isWorkerSupported() {
2476
+ return window['Worker'] instanceof Function;
2477
+ }
2478
+
2479
+ /**
2480
+ * Creates a Web Worker for updating the table.
2481
+ * @public
2482
+ * @expose
2483
+ * @param {string} url Url to the script for the Web Worker
2484
+ * @param {boolean} [start=true] if true, starts the Worker immediately
2485
+ * @param {boolean} [resort=false]
2486
+ * @returns {Worker|null} the Web Worker, or null if not supported
2487
+ */
2488
+ createWebWorker(url, start, resort) {
2489
+ if (this.isWorkerSupported()) {
2490
+ let p = this._p;
2491
+
2492
+ let worker = new Worker(url);
2493
+ let listener = (evt) => {
2494
+ if (evt.data.append) {
2495
+ this.addRows(evt.data.rows, resort);
2496
+ } else {
2497
+ this.setRows(evt.data.rows, resort);
2498
+ }
2499
+ };
2500
+ worker.addEventListener('message', listener, false);
2501
+ if (!p.workerListeners) {
2502
+ p.workerListeners = [];
2503
+ }
2504
+ p.workerListeners.push({ worker: worker, listener: listener });
2505
+ if (start || start === undefined) {
2506
+ worker.postMessage(null);
2507
+ }
2508
+ return worker;
2509
+ }
2510
+ return null;
2511
+ }
2512
+
2513
+ /**
2514
+ * Unbinds a Web Worker from the table, stopping updates.
2515
+ * @public
2516
+ * @expose
2517
+ * @param {Worker} worker the Web Worker
2518
+ * @returns {DGTable} self
2519
+ */
2520
+ unbindWebWorker(worker) {
2521
+ let p = this._p;
2522
+
2523
+ if (p.workerListeners) {
2524
+ for (let j = 0; j < p.workerListeners.length; j++) {
2525
+ if (p.workerListeners[j].worker === worker) {
2526
+ worker.removeEventListener('message', p.workerListeners[j].listener, false);
2527
+ p.workerListeners.splice(j, 1);
2528
+ j--;
2529
+ }
2530
+ }
2531
+ }
2532
+
2533
+ return this;
2534
+ }
2535
+
2536
+ /**
2537
+ * A synonym for hideCellPreview()
2538
+ * @public
2539
+ * @expose
2540
+ * @returns {DGTable} self
2541
+ */
2542
+ abortCellPreview() {
2543
+ this.hideCellPreview();
2544
+ return this;
2545
+ }
2546
+
2547
+ /**
2548
+ * Cancel a resize in progress
2549
+ * @expose
2550
+ * @private
2551
+ * @returns {DGTable} self
2552
+ */
2553
+ cancelColumnResize() {
2554
+ const p = this._p;
2555
+
2556
+ if (p.resizer) {
2557
+ p.resizer.remove();
2558
+ p.resizer = null;
2559
+ p.eventsSink.remove(document, '.colresize');
2560
+ }
2561
+
2562
+ return this;
2563
+ }
2564
+
2565
+ _onTableScrolledHorizontally() {
2566
+ const p = this._p;
2567
+
2568
+ p.header.scrollLeft = p.table.scrollLeft;
2569
+ }
2570
+
2571
+ /**previousElementSibling
2572
+ * Reverse-calculate the column to resize from mouse position
2573
+ * @private
2574
+ * @param {MouseEvent} event mouse event
2575
+ * @returns {string|null} name of the column which the mouse is over, or null if the mouse is not in resize position
2576
+ */
2577
+ _getColumnByResizePosition(event) {
2578
+ let o = this._o,
2579
+ rtl = this._isTableRtl();
2580
+
2581
+ let headerCell = event.target.closest(`div.${o.tableClassName}-header-cell,div.${o.cellPreviewClassName}`);
2582
+ if (headerCell[OriginalCellSymbol]) {
2583
+ headerCell = headerCell[OriginalCellSymbol];
2584
+ }
2585
+
2586
+ let previousElementSibling = headerCell.previousSibling;
2587
+ while (previousElementSibling && previousElementSibling.nodeType !== 1) {
2588
+ previousElementSibling = previousElementSibling.previousSibling;
2589
+ }
2590
+
2591
+ let firstCol = !previousElementSibling;
2592
+
2593
+ let mouseX = (event.pageX || event.clientX) - getElementOffset(headerCell).left;
2594
+
2595
+ if (rtl) {
2596
+ if (!firstCol && getElementWidth(headerCell, true, true, true) - mouseX <= o.resizeAreaWidth / 2) {
2597
+ return previousElementSibling['columnName'];
2598
+ } else if (mouseX <= o.resizeAreaWidth / 2) {
2599
+ return headerCell['columnName'];
2600
+ }
2601
+ } else {
2602
+ if (!firstCol && mouseX <= o.resizeAreaWidth / 2) {
2603
+ return previousElementSibling['columnName'];
2604
+ } else if (getElementWidth(headerCell, true, true, true) - mouseX <= o.resizeAreaWidth / 2) {
2605
+ return headerCell['columnName'];
2606
+ }
2607
+ }
2608
+
2609
+ return null;
2610
+ }
2611
+
2612
+ /**
2613
+ * @param {TouchEvent} event
2614
+ */
2615
+ _onTouchStartColumnHeader(event) {
2616
+ const p = this._p;
2617
+
2618
+ if (p.currentTouchId) return;
2619
+
2620
+ let startTouch = event.changedTouches[0];
2621
+ p.currentTouchId = startTouch.identifier;
2622
+
2623
+ let cellEl = event.currentTarget;
2624
+
2625
+ let startPos = { x: startTouch.pageX, y: startTouch.pageY },
2626
+ currentPos = startPos,
2627
+ distanceTreshold = 9;
2628
+
2629
+ let tapAndHoldTimeout;
2630
+
2631
+ let unbind = function () {
2632
+ p.currentTouchId = null;
2633
+ p.eventsSink.remove(cellEl, '.colheader');
2634
+ clearTimeout(tapAndHoldTimeout);
2635
+ };
2636
+
2637
+ let fakeMouseEvent = (name, ...args) => {
2638
+ const dict = {};
2639
+ for (const k of event)
2640
+ dict[k] = event[k];
2641
+
2642
+ for (const obj of args) {
2643
+ for (const key of ['target', 'clientX', 'clientY', 'offsetX', 'offsetY', 'screenX', 'screenY', 'pageX', 'pageY', 'which']) {
2644
+ if (obj[key] != null)
2645
+ dict[key] = obj[key];
2646
+ }
2647
+ }
2648
+
2649
+ return new MouseEvent(name, event);
2650
+ };
2651
+
2652
+ cellEl.dispatchEvent(
2653
+ fakeMouseEvent('mousedown', event.changedTouches[0], { button: 0, target: event.target }),
2654
+ );
2655
+
2656
+ tapAndHoldTimeout = setTimeout(() => {
2657
+ unbind();
2658
+
2659
+ p.eventsSink
2660
+ .add(cellEl, 'touchend.colheader', (event) => {
2661
+ // Prevent simulated mouse events after touchend
2662
+ if (!isInputElementEvent(event))
2663
+ event.preventDefault();
2664
+
2665
+ p.eventsSink.remove(cellEl, '.colheader');
2666
+ }, { once: true })
2667
+ .add(cellEl, 'touchcancel.colheader', (_event) => {
2668
+ p.eventsSink.remove(cellEl, '.colheader');
2669
+ }, { once: true });
2670
+
2671
+ let distanceTravelled = Math.sqrt(Math.pow(Math.abs(currentPos.x - startPos.x), 2) + Math.pow(Math.abs(currentPos.y - startPos.y), 2));
2672
+
2673
+ if (distanceTravelled < distanceTreshold) {
2674
+ this.cancelColumnResize();
2675
+
2676
+ cellEl.dispatchEvent(
2677
+ fakeMouseEvent('mouseup', event.changedTouches[0], { button: 2, target: event.target }),
2678
+ );
2679
+ }
2680
+
2681
+ }, 500);
2682
+
2683
+ p.eventsSink
2684
+ .add(cellEl, 'touchend.colheader', (event) => {
2685
+ let touch = find(event.changedTouches, (touch) => touch.identifier === p.currentTouchId);
2686
+ if (!touch) return;
2687
+
2688
+ unbind();
2689
+
2690
+ // Prevent simulated mouse events after touchend
2691
+ if (!isInputElementEvent(event))
2692
+ event.preventDefault();
2693
+
2694
+ currentPos = { x: touch.pageX, y: touch.pageY };
2695
+ let distanceTravelled = Math.sqrt(Math.pow(Math.abs(currentPos.x - startPos.x), 2) + Math.pow(Math.abs(currentPos.y - startPos.y), 2));
2696
+
2697
+ if (distanceTravelled < distanceTreshold || p.resizer) {
2698
+ cellEl.dispatchEvent(
2699
+ fakeMouseEvent('mouseup', touch, { 0: 2, target: event.target }),
2700
+ );
2701
+
2702
+ cellEl.dispatchEvent(
2703
+ fakeMouseEvent('click', touch, { button: 0, target: event.target }),
2704
+ );
2705
+ }
2706
+
2707
+ })
2708
+ .add(cellEl, 'touchcancel.colheader', unbind)
2709
+ .add(cellEl, 'touchmove.colheader', (event) => {
2710
+ let touch = find(event.changedTouches, (touch) => touch.identifier === p.currentTouchId);
2711
+ if (!touch) return;
2712
+
2713
+ // Keep track of current position, so we know if we need to cancel the tap-and-hold
2714
+ currentPos = { x: touch.pageX, y: touch.pageY };
2715
+
2716
+ if (p.resizer) {
2717
+ event.preventDefault();
2718
+
2719
+ cellEl.dispatchEvent(
2720
+ fakeMouseEvent('mousemove', touch, { target: event.target }),
2721
+ );
2722
+ }
2723
+ });
2724
+ }
2725
+
2726
+ /**
2727
+ * @param {MouseEvent} event
2728
+ */
2729
+ _onMouseDownColumnHeader(event) {
2730
+ if (event.button !== 0) return this; // Only treat left-clicks
2731
+
2732
+ let o = this._o,
2733
+ p = this._p,
2734
+ col = this._getColumnByResizePosition(event);
2735
+
2736
+ if (col) {
2737
+ let column = p.columns.get(col);
2738
+ if (!o.resizableColumns || !column || !column.resizable) {
2739
+ return false;
2740
+ }
2741
+
2742
+ let rtl = this._isTableRtl();
2743
+
2744
+ if (p.resizer) {
2745
+ p.resizer.remove();
2746
+ }
2747
+ p.resizer = createElement('div');
2748
+ p.resizer.className = o.resizerClassName;
2749
+ setCssProps(p.resizer, {
2750
+ position: 'absolute',
2751
+ display: 'block',
2752
+ zIndex: -1,
2753
+ visibility: 'hidden',
2754
+ width: '2px',
2755
+ background: '#000',
2756
+ opacity: 0.7,
2757
+ });
2758
+ this.el.appendChild(p.resizer);
2759
+
2760
+ let selectedHeaderCell = column.element,
2761
+ commonAncestor = p.resizer.parentNode;
2762
+
2763
+ const commonAncestorStyle = getComputedStyle(commonAncestor);
2764
+ const selectedHeaderCellStyle = getComputedStyle(selectedHeaderCell);
2765
+
2766
+ let posCol = getElementOffset(selectedHeaderCell),
2767
+ posRelative = getElementOffset(commonAncestor);
2768
+ posRelative.left += parseFloat(commonAncestorStyle.borderLeftWidth) || 0;
2769
+ posRelative.top += parseFloat(commonAncestorStyle.borderTopWidth) || 0;
2770
+ posCol.left -= posRelative.left;
2771
+ posCol.top -= posRelative.top;
2772
+ posCol.top -= parseFloat(selectedHeaderCellStyle.borderTopWidth) || 0;
2773
+ let resizerWidth = getElementWidth(p.resizer, true, true, true);
2774
+ if (rtl) {
2775
+ posCol.left -= Math.ceil((parseFloat(selectedHeaderCellStyle.borderLeftWidth) || 0) / 2);
2776
+ posCol.left -= Math.ceil(resizerWidth / 2);
2777
+ } else {
2778
+ posCol.left += getElementWidth(selectedHeaderCell, true, true, true);
2779
+ posCol.left += Math.ceil((parseFloat(selectedHeaderCellStyle.borderRightWidth) || 0) / 2);
2780
+ posCol.left -= Math.ceil(resizerWidth / 2);
2781
+ }
2782
+
2783
+ setCssProps(p.resizer, {
2784
+ 'z-index': '10',
2785
+ 'visibility': 'visible',
2786
+ 'left': posCol.left + 'px',
2787
+ 'top': posCol.top + 'px',
2788
+ 'height': getElementHeight(this.el, false, false, false) + 'px',
2789
+ });
2790
+ p.resizer['columnName'] = selectedHeaderCell['columnName'];
2791
+
2792
+ try { p.resizer.style.zIndex = ''; }
2793
+ catch (ignored) { /* we're ok with this */ }
2794
+
2795
+ p.eventsSink
2796
+ .add(document, 'mousemove.colresize', this._onMouseMoveResizeArea.bind(this))
2797
+ .add(document, 'mouseup.colresize', this._onEndDragColumnHeader.bind(this));
2798
+
2799
+ event.preventDefault();
2800
+ }
2801
+ }
2802
+
2803
+ /**
2804
+ * @param {MouseEvent} event event
2805
+ */
2806
+ _onMouseMoveColumnHeader(event) {
2807
+ let o = this._o,
2808
+ p = this._p;
2809
+
2810
+ if (o.resizableColumns) {
2811
+ let col = this._getColumnByResizePosition(event);
2812
+ let headerCell = event.target.closest(`div.${o.tableClassName}-header-cell,div.${o.cellPreviewClassName}`);
2813
+ if (!col || !p.columns.get(col).resizable) {
2814
+ headerCell.style.cursor = '';
2815
+ } else {
2816
+ headerCell.style.cursor = 'e-resize';
2817
+ }
2818
+ }
2819
+ }
2820
+
2821
+ /**
2822
+ * @param {MouseEvent} event
2823
+ */
2824
+ _onMouseUpColumnHeader(event) {
2825
+ if (event.button === 2) {
2826
+ let o = this._o;
2827
+ let headerCell = event.target.closest(`div.${o.tableClassName}-header-cell,div.${o.cellPreviewClassName}`);
2828
+ let bounds = getElementOffset(headerCell);
2829
+ bounds['width'] = getElementWidth(headerCell, true, true, true);
2830
+ bounds['height'] = getElementHeight(headerCell, true, true, true);
2831
+ this.emit('headercontextmenu', {
2832
+ name: headerCell['columnName'],
2833
+ pageX: event.pageX,
2834
+ pageY: event.pageY,
2835
+ bounds: bounds,
2836
+ });
2837
+ }
2838
+ return this;
2839
+ }
2840
+
2841
+ /**
2842
+ * @private
2843
+ * @param {MouseEvent} event event
2844
+ */
2845
+ _onMouseLeaveColumnHeader(event) {
2846
+ let o = this._o;
2847
+ let headerCell = event.target.closest(`div.${o.tableClassName}-header-cell,div.${o.cellPreviewClassName}`);
2848
+ headerCell.style.cursor = '';
2849
+ }
2850
+
2851
+ /**
2852
+ * @private
2853
+ * @param {MouseEvent} event event
2854
+ */
2855
+ _onClickColumnHeader(event) {
2856
+ if (isInputElementEvent(event))
2857
+ return;
2858
+
2859
+ if (!this._getColumnByResizePosition(event)) {
2860
+ let o = this._o,
2861
+ p = this._p;
2862
+
2863
+ let headerCell = event.target.closest(`div.${o.tableClassName}-header-cell,div.${o.cellPreviewClassName}`);
2864
+ if (o.sortableColumns) {
2865
+ let column = p.columns.get(headerCell['columnName']);
2866
+ let currentSort = p.rows.sortColumn;
2867
+ if (column && column.sortable) {
2868
+ let shouldAdd = true;
2869
+
2870
+ let lastSort = currentSort.length ? currentSort[currentSort.length - 1] : null;
2871
+
2872
+ if (lastSort && lastSort.column === column.name) {
2873
+ if (!lastSort.descending || !o.allowCancelSort) {
2874
+ lastSort.descending = !lastSort.descending;
2875
+ } else {
2876
+ shouldAdd = false;
2877
+ currentSort.splice(currentSort.length - 1, 1);
2878
+ }
2879
+ }
2880
+
2881
+ if (shouldAdd) {
2882
+ this.sort(column.name, undefined, true).render();
2883
+ } else {
2884
+ this.sort(); // just refresh current situation
2885
+ }
2886
+ }
2887
+ }
2888
+ }
2889
+ }
2890
+
2891
+ /**
2892
+ * @private
2893
+ * @param {DragEvent} event event
2894
+ */
2895
+ _onStartDragColumnHeader(event) {
2896
+ let o = this._o,
2897
+ p = this._p;
2898
+
2899
+ if (o.movableColumns) {
2900
+ let headerCell = event.target.closest(`div.${o.tableClassName}-header-cell,div.${o.cellPreviewClassName}`);
2901
+ let column = p.columns.get(headerCell['columnName']);
2902
+ if (column && column.movable) {
2903
+ headerCell.style.opacity = 0.35;
2904
+ p.dragId = Math.random() * 0x9999999; // Recognize this ID on drop
2905
+ event.dataTransfer.setData('text', JSON.stringify({ dragId: p.dragId, column: column.name }));
2906
+ } else {
2907
+ event.preventDefault();
2908
+ }
2909
+ } else {
2910
+ event.preventDefault();
2911
+ }
2912
+
2913
+ return undefined;
2914
+ }
2915
+
2916
+ /**
2917
+ * @private
2918
+ * @param {MouseEvent} event event
2919
+ */
2920
+ _onMouseMoveResizeArea(event) {
2921
+
2922
+ let p = this._p;
2923
+
2924
+ let column = p.columns.get(p.resizer['columnName']);
2925
+ let rtl = this._isTableRtl();
2926
+
2927
+ let selectedHeaderCell = column.element,
2928
+ commonAncestor = p.resizer.parentNode;
2929
+
2930
+ const commonAncestorStyle = getComputedStyle(commonAncestor);
2931
+ const selectedHeaderCellStyle = getComputedStyle(selectedHeaderCell);
2932
+
2933
+ let posCol = getElementOffset(selectedHeaderCell),
2934
+ posRelative = getElementOffset(commonAncestor);
2935
+ posRelative.left += parseFloat(commonAncestorStyle.borderLeftWidth) || 0;
2936
+ posCol.left -= posRelative.left;
2937
+ let resizerWidth = getElementWidth(p.resizer, true, true, true);
2938
+
2939
+ let isBoxing = selectedHeaderCellStyle.boxSizing === 'border-box';
2940
+
2941
+ let actualX = event.pageX - posRelative.left;
2942
+ let minX = posCol.left;
2943
+
2944
+ minX -= Math.ceil(resizerWidth / 2);
2945
+
2946
+ if (rtl) {
2947
+ minX += getElementWidth(selectedHeaderCell, true, true, true);
2948
+ minX -= column.ignoreMin ? 0 : this._o.minColumnWidth;
2949
+
2950
+ if (!isBoxing) {
2951
+ minX -= Math.ceil((parseFloat(selectedHeaderCellStyle.borderLeftWidth) || 0) / 2);
2952
+ minX -= this._horizontalPadding(selectedHeaderCell);
2953
+ }
2954
+
2955
+ if (actualX > minX) {
2956
+ actualX = minX;
2957
+ }
2958
+ } else {
2959
+ minX += column.ignoreMin ? 0 : this._o.minColumnWidth;
2960
+
2961
+ if (!isBoxing) {
2962
+ minX += Math.ceil((parseFloat(selectedHeaderCellStyle.borderRightWidth) || 0) / 2);
2963
+ minX += this._horizontalPadding(selectedHeaderCell);
2964
+ }
2965
+
2966
+ if (actualX < minX) {
2967
+ actualX = minX;
2968
+ }
2969
+ }
2970
+
2971
+ p.resizer.style.left = actualX + 'px';
2972
+ }
2973
+
2974
+ /**
2975
+ * @private
2976
+ * @param {DragEvent} event event
2977
+ */
2978
+ _onEndDragColumnHeader(event) {
2979
+
2980
+ let o = this._o,
2981
+ p = this._p;
2982
+
2983
+ if (!p.resizer) {
2984
+ event.target.style.opacity = null;
2985
+ } else {
2986
+ p.eventsSink.remove(document, '.colresize');
2987
+
2988
+ let column = p.columns.get(p.resizer['columnName']);
2989
+ let rtl = this._isTableRtl();
2990
+
2991
+ let selectedHeaderCell = column.element,
2992
+ selectedHeaderCellInner = selectedHeaderCell.firstChild,
2993
+ commonAncestor = p.resizer.parentNode;
2994
+
2995
+ const commonAncestorStyle = getComputedStyle(commonAncestor);
2996
+ const selectedHeaderCellStyle = getComputedStyle(selectedHeaderCell);
2997
+
2998
+ let posCol = getElementOffset(selectedHeaderCell),
2999
+ posRelative = getElementOffset(commonAncestor);
3000
+ posRelative.left += parseFloat(commonAncestorStyle.borderLeftWidth) || 0;
3001
+ posCol.left -= posRelative.left;
3002
+ let resizerWidth = getElementWidth(p.resizer, true, true, true);
3003
+
3004
+ let isBoxing = selectedHeaderCellStyle.boxSizing === 'border-box';
3005
+
3006
+ let actualX = event.pageX - posRelative.left;
3007
+ let baseX = posCol.left, minX = posCol.left;
3008
+ let width = 0;
3009
+
3010
+ baseX -= Math.ceil(resizerWidth / 2);
3011
+
3012
+ if (rtl) {
3013
+ if (!isBoxing) {
3014
+ actualX += this._horizontalPadding(selectedHeaderCell);
3015
+ const innerComputedStyle = getComputedStyle(selectedHeaderCellInner || selectedHeaderCell);
3016
+ actualX += parseFloat(innerComputedStyle.borderLeftWidth) || 0;
3017
+ actualX += parseFloat(innerComputedStyle.borderRightWidth) || 0;
3018
+ actualX += column.arrowProposedWidth || 0; // Sort-arrow width
3019
+ }
3020
+
3021
+ baseX += getElementWidth(selectedHeaderCell, true, true, true);
3022
+
3023
+ minX = baseX - (column.ignoreMin ? 0 : this._o.minColumnWidth);
3024
+ if (actualX > minX) {
3025
+ actualX = minX;
3026
+ }
3027
+
3028
+ width = baseX - actualX;
3029
+ } else {
3030
+ if (!isBoxing) {
3031
+ actualX -= this._horizontalPadding(selectedHeaderCell);
3032
+ const innerComputedStyle = getComputedStyle(selectedHeaderCellInner || selectedHeaderCell);
3033
+ actualX -= parseFloat(innerComputedStyle.borderLeftWidth) || 0;
3034
+ actualX -= parseFloat(innerComputedStyle.borderRightWidth) || 0;
3035
+ actualX -= column.arrowProposedWidth || 0; // Sort-arrow width
3036
+ }
3037
+
3038
+ minX = baseX + (column.ignoreMin ? 0 : this._o.minColumnWidth);
3039
+ if (actualX < minX) {
3040
+ actualX = minX;
3041
+ }
3042
+
3043
+ width = actualX - baseX;
3044
+ }
3045
+
3046
+ p.resizer.remove();
3047
+ p.resizer = null;
3048
+
3049
+ let sizeToSet = width;
3050
+
3051
+ if (column.widthMode === ColumnWidthMode.RELATIVE) {
3052
+ let sizeLeft = this._calculateWidthAvailableForColumns();
3053
+ //sizeLeft -= p.table.offsetWidth - p.table.clientWidth;
3054
+
3055
+ let totalRelativePercentage = 0;
3056
+ let relatives = 0;
3057
+
3058
+ for (let i = 0; i < p.visibleColumns.length; i++) {
3059
+ let col = p.visibleColumns[i];
3060
+ if (col.name === column.name) continue;
3061
+
3062
+ if (col.widthMode === ColumnWidthMode.RELATIVE) {
3063
+ totalRelativePercentage += col.width;
3064
+ relatives++;
3065
+ } else {
3066
+ sizeLeft -= col.actualWidth;
3067
+ }
3068
+ }
3069
+
3070
+ sizeLeft = Math.max(1, sizeLeft);
3071
+ if (sizeLeft === 1)
3072
+ sizeLeft = p.table.clientWidth;
3073
+ sizeToSet = width / sizeLeft;
3074
+
3075
+ if (relatives > 0) {
3076
+ // When there's more than one relative overall,
3077
+ // we can do relative enlarging/shrinking.
3078
+ // Otherwise, we can end up having a 0 width.
3079
+
3080
+ let unNormalizedSizeToSet = sizeToSet / ((1 - sizeToSet) / totalRelativePercentage);
3081
+
3082
+ totalRelativePercentage += sizeToSet;
3083
+
3084
+ // Account for relative widths scaling later
3085
+ if ((totalRelativePercentage < 1 && o.relativeWidthGrowsToFillWidth) ||
3086
+ (totalRelativePercentage > 1 && o.relativeWidthShrinksToFillWidth)) {
3087
+ sizeToSet = unNormalizedSizeToSet;
3088
+ }
3089
+ }
3090
+
3091
+ sizeToSet *= 100;
3092
+ sizeToSet += '%';
3093
+ }
3094
+
3095
+ this.setColumnWidth(column.name, sizeToSet);
3096
+ }
3097
+ }
3098
+
3099
+ /**
3100
+ * @private
3101
+ * @param {DragEvent} event event
3102
+ */
3103
+ _onDragEnterColumnHeader(event) {
3104
+ let o = this._o,
3105
+ p = this._p;
3106
+
3107
+ if (o.movableColumns) {
3108
+ let dataTransferred = event.dataTransfer.getData('text');
3109
+ if (dataTransferred) {
3110
+ dataTransferred = JSON.parse(dataTransferred);
3111
+ }
3112
+ else {
3113
+ dataTransferred = null; // WebKit does not provide the dataTransfer on dragenter?..
3114
+ }
3115
+
3116
+ let headerCell = event.target.closest(`div.${o.tableClassName}-header-cell,div.${o.cellPreviewClassName}`);
3117
+ if (!dataTransferred ||
3118
+ (p.dragId === dataTransferred.dragId && headerCell['columnName'] !== dataTransferred.column)) {
3119
+
3120
+ let column = p.columns.get(headerCell['columnName']);
3121
+ if (column && (column.movable || column !== p.visibleColumns[0])) {
3122
+ headerCell.classList.add('drag-over');
3123
+ }
3124
+ }
3125
+ }
3126
+ }
3127
+
3128
+ /**
3129
+ * @private
3130
+ * @param {DragEvent} event event
3131
+ */
3132
+ _onDragOverColumnHeader(event) {
3133
+ event.preventDefault();
3134
+ }
3135
+
3136
+ /**
3137
+ * @private
3138
+ * @param {DragEvent} event event
3139
+ */
3140
+ _onDragLeaveColumnHeader(event) {
3141
+ let o = this._o;
3142
+ let headerCell = event.target.closest(`div.${o.tableClassName}-header-cell,div.${o.cellPreviewClassName}`);
3143
+ if (!event.relatedTarget.contains(headerCell.firstChild)) {
3144
+ headerCell.classList.remove('drag-over');
3145
+ }
3146
+ }
3147
+
3148
+ /**
3149
+ * @private
3150
+ * @param {DragEvent} event event
3151
+ */
3152
+ _onDropColumnHeader(event) {
3153
+ event.preventDefault();
3154
+
3155
+ let o = this._o,
3156
+ p = this._p;
3157
+
3158
+ let dataTransferred = JSON.parse(event.dataTransfer.getData('text'));
3159
+ let headerCell = event.target.closest(`div.${o.tableClassName}-header-cell,div.${o.cellPreviewClassName}`);
3160
+ if (o.movableColumns && dataTransferred.dragId === p.dragId) {
3161
+ let srcColName = dataTransferred.column,
3162
+ destColName = headerCell['columnName'],
3163
+ srcCol = p.columns.get(srcColName),
3164
+ destCol = p.columns.get(destColName);
3165
+ if (srcCol && destCol && srcCol.movable && (destCol.movable || destCol !== p.visibleColumns[0])) {
3166
+ this.moveColumn(srcColName, destColName);
3167
+ }
3168
+ }
3169
+ headerCell.classList.remove('drag-over');
3170
+ }
3171
+
3172
+ /**
3173
+ * @private
3174
+ * @returns {DGTable} self
3175
+ */
3176
+ _clearSortArrows() {
3177
+ let p = this._p;
3178
+
3179
+ if (p.table) {
3180
+ let tableClassName = this._o.tableClassName;
3181
+ let sortedColumns = scopedSelectorAll(p.headerRow, `>div.${tableClassName}-header-cell.sorted`);
3182
+ let arrows = Array.prototype.slice.call(sortedColumns, 0).map((el) => scopedSelector(el, '>div>.sort-arrow')).filter((el) => !!el);
3183
+ for (const arrow of arrows) {
3184
+ let col = p.columns.get(arrow.parentNode.parentNode['columnName']);
3185
+ if (col) {
3186
+ col.arrowProposedWidth = 0;
3187
+ }
3188
+ arrow.remove();
3189
+ }
3190
+ for (const sortedColumn of sortedColumns) {
3191
+ sortedColumn.classList.remove('sorted', 'desc');
3192
+ }
3193
+ }
3194
+ return this;
3195
+ }
3196
+
3197
+ /**
3198
+ * @private
3199
+ * @param {string} column the name of the sort column
3200
+ * @param {boolean} descending table is sorted descending
3201
+ * @returns {boolean} self
3202
+ */
3203
+ _showSortArrow(column, descending) {
3204
+ let p = this._p;
3205
+
3206
+ let col = p.columns.get(column);
3207
+ if (!col) return false;
3208
+
3209
+ let arrow = createElement('span');
3210
+ arrow.className = 'sort-arrow';
3211
+
3212
+ if (col.element) {
3213
+ col.element.className += descending ? ' sorted desc' : ' sorted';
3214
+ col.element.firstChild.insertBefore(arrow, col.element.firstChild.firstChild);
3215
+ }
3216
+
3217
+ if (col.widthMode !== ColumnWidthMode.RELATIVE && this._o.adjustColumnWidthForSortArrow) {
3218
+ col.arrowProposedWidth = arrow.scrollWidth +
3219
+ (parseFloat(getComputedStyle(arrow).marginRight) || 0) +
3220
+ (parseFloat(getComputedStyle(arrow).marginLeft) || 0);
3221
+ }
3222
+
3223
+ return true;
3224
+ }
3225
+
3226
+ /**
3227
+ * @private
3228
+ * @param {number} cellIndex index of the column in the DOM
3229
+ * @returns {DGTable} self
3230
+ */
3231
+ _resizeColumnElements(cellIndex) {
3232
+ let p = this._p;
3233
+
3234
+ const headerCells = p.headerRow.querySelectorAll(`div.${this._o.tableClassName}-header-cell`);
3235
+ const headerCell = headerCells[cellIndex];
3236
+ let col = p.columns.get(headerCell['columnName']);
3237
+
3238
+ if (col) {
3239
+ headerCell.style.width = (col.actualWidthConsideringScrollbarWidth || col.actualWidth) + 'px';
3240
+
3241
+ let width = (col.actualWidthConsideringScrollbarWidth || col.actualWidth) + 'px';
3242
+ let tbodyChildren = p.tbody.childNodes;
3243
+ for (let i = 0, count = tbodyChildren.length; i < count; i++) {
3244
+ let rowEl = tbodyChildren[i];
3245
+ if (rowEl.nodeType !== 1) continue;
3246
+ rowEl.childNodes[cellIndex].style.width = width;
3247
+ }
3248
+ }
3249
+
3250
+ return this;
3251
+ }
3252
+
3253
+ /**
3254
+ * @returns {DGTable} self
3255
+ * */
3256
+ _destroyHeaderCells() {
3257
+ let p = this._p;
3258
+
3259
+ if (p.headerRow) {
3260
+ p.headerRow = null;
3261
+ }
3262
+ return this;
3263
+ }
3264
+
3265
+ /**
3266
+ * @private
3267
+ * @returns {DGTable} self
3268
+ */
3269
+ _renderSkeletonBase() {
3270
+ let p = this._p,
3271
+ o = this._o;
3272
+
3273
+ // Clean up old elements
3274
+
3275
+ p.virtualListHelper?.destroy();
3276
+ p.virtualListHelper = null;
3277
+
3278
+ if (p.table && o.virtualTable) {
3279
+ p.table.remove();
3280
+ p.table = p.tbody = null;
3281
+ }
3282
+
3283
+ this._destroyHeaderCells();
3284
+ p.currentTouchId = null;
3285
+ if (p.header) {
3286
+ p.header.remove();
3287
+ }
3288
+
3289
+ // Create new base elements
3290
+ let tableClassName = o.tableClassName,
3291
+ header = createElement('div'),
3292
+ headerRow = createElement('div');
3293
+
3294
+ header.className = `${tableClassName}-header`;
3295
+ headerRow.className = `${tableClassName}-header-row`;
3296
+
3297
+ p.header = header;
3298
+ p.headerRow = headerRow;
3299
+ header.appendChild(headerRow);
3300
+ this.el.prepend(header);
3301
+
3302
+ relativizeElement(this.el);
3303
+
3304
+ if (o.width === DGTable.Width.SCROLL) {
3305
+ this.el.style.overflow = 'hidden';
3306
+ } else {
3307
+ this.el.style.overflow = '';
3308
+ }
3309
+
3310
+ if (!o.height && o.virtualTable) {
3311
+ o.height = getElementHeight(this.el, true);
3312
+ }
3313
+
3314
+ return this;
3315
+ }
3316
+
3317
+ _bindHeaderColumnEvents(columnEl) {
3318
+ const inner = columnEl.firstChild;
3319
+ columnEl.addEventListener('mousedown', this._onMouseDownColumnHeader.bind(this));
3320
+ columnEl.addEventListener('mousemove', this._onMouseMoveColumnHeader.bind(this));
3321
+ columnEl.addEventListener('mouseup', this._onMouseUpColumnHeader.bind(this));
3322
+ columnEl.addEventListener('mouseleave', this._onMouseLeaveColumnHeader.bind(this));
3323
+ columnEl.addEventListener('touchstart', this._onTouchStartColumnHeader.bind(this));
3324
+ columnEl.addEventListener('dragstart', this._onStartDragColumnHeader.bind(this));
3325
+ columnEl.addEventListener('click', this._onClickColumnHeader.bind(this));
3326
+ columnEl.addEventListener('contextmenu', event => { event.preventDefault(); });
3327
+ inner.addEventListener('dragenter', this._onDragEnterColumnHeader.bind(this));
3328
+ inner.addEventListener('dragover', this._onDragOverColumnHeader.bind(this));
3329
+ inner.addEventListener('dragleave', this._onDragLeaveColumnHeader.bind(this));
3330
+ inner.addEventListener('drop', this._onDropColumnHeader.bind(this));
3331
+ }
3332
+
3333
+ /**
3334
+ * @private
3335
+ * @returns {DGTable} self
3336
+ */
3337
+ _renderSkeletonHeaderCells() {
3338
+ let p = this._p,
3339
+ o = this._o;
3340
+
3341
+ let allowCellPreview = o.allowCellPreview,
3342
+ allowHeaderCellPreview = o.allowHeaderCellPreview;
3343
+
3344
+ let tableClassName = o.tableClassName,
3345
+ headerCellClassName = tableClassName + '-header-cell',
3346
+ headerRow = p.headerRow;
3347
+
3348
+ // Create header cells
3349
+ for (let i = 0; i < p.visibleColumns.length; i++) {
3350
+ let column = p.visibleColumns[i];
3351
+ if (column.visible) {
3352
+ let cell = createElement('div');
3353
+ cell.draggable = true;
3354
+ cell.className = headerCellClassName;
3355
+ cell.style.width = column.actualWidth + 'px';
3356
+ if (o.sortableColumns && column.sortable) {
3357
+ cell.className += ' sortable';
3358
+ }
3359
+ cell['columnName'] = column.name;
3360
+ cell.setAttribute('data-column', column.name);
3361
+
3362
+ let cellInside = createElement('div');
3363
+ cellInside.innerHTML = o.headerCellFormatter(column.label, column.name);
3364
+ cell.appendChild(cellInside);
3365
+ if (allowCellPreview && allowHeaderCellPreview) {
3366
+ p._bindCellHoverIn(cell);
3367
+ }
3368
+ headerRow.appendChild(cell);
3369
+
3370
+ p.visibleColumns[i].element = cell;
3371
+
3372
+ this._bindHeaderColumnEvents(cell);
3373
+ this._disableCssSelect(cell);
3374
+ }
3375
+ }
3376
+
3377
+ this.emit('headerrowcreate', headerRow);
3378
+
3379
+ return this;
3380
+ }
3381
+
3382
+ /**
3383
+ * @private
3384
+ * @returns {DGTable} self
3385
+ */
3386
+ _renderSkeletonBody() {
3387
+ let p = this._p,
3388
+ o = this._o;
3389
+
3390
+ let tableClassName = o.tableClassName;
3391
+
3392
+ // Calculate virtual row heights
3393
+ if (o.virtualTable && !p.virtualRowHeight) {
3394
+ let createDummyRow = () => {
3395
+ let row = createElement('div'),
3396
+ cell = row.appendChild(createElement('div')),
3397
+ cellInner = cell.appendChild(createElement('div'));
3398
+ row.className = `${tableClassName}-row`;
3399
+ cell.className = `${tableClassName}-cell`;
3400
+ cellInner.innerHTML = '0';
3401
+ row.style.visibility = 'hidden';
3402
+ row.style.position = 'absolute';
3403
+ return row;
3404
+ };
3405
+
3406
+ const dummyWrapper = createElement('div');
3407
+ dummyWrapper.className = this.el.className;
3408
+ setCssProps(dummyWrapper, {
3409
+ 'z-index': -1,
3410
+ 'position': 'absolute',
3411
+ 'left': '0',
3412
+ 'top': '-9999px',
3413
+ 'width': '1px',
3414
+ 'overflow': 'hidden',
3415
+ });
3416
+
3417
+ const dummyTable = createElement('div');
3418
+ dummyTable.className = tableClassName;
3419
+ dummyWrapper.appendChild(dummyTable);
3420
+
3421
+ const dummyTbody = createElement('div');
3422
+ dummyTbody.className = `${tableClassName}-body`;
3423
+ dummyTbody.style.width = '99999px';
3424
+ dummyTable.appendChild(dummyTbody);
3425
+
3426
+ document.body.appendChild(dummyWrapper);
3427
+
3428
+ let row1 = createDummyRow(), row2 = createDummyRow(), row3 = createDummyRow();
3429
+ dummyTbody.appendChild(row1);
3430
+ dummyTbody.appendChild(row2);
3431
+ dummyTbody.appendChild(row3);
3432
+
3433
+ p.virtualRowHeightFirst = getElementHeight(row1, true, true, true);
3434
+ p.virtualRowHeight = getElementHeight(row2, true, true, true);
3435
+ p.virtualRowHeightLast = getElementHeight(row3, true, true, true);
3436
+
3437
+ dummyWrapper.remove();
3438
+ }
3439
+
3440
+ // Create inner table and tbody
3441
+ if (!p.table) {
3442
+ let fragment = document.createDocumentFragment();
3443
+
3444
+ // Create the inner table element
3445
+ let table = createElement('div');
3446
+ table.className = tableClassName;
3447
+
3448
+ if (o.virtualTable) {
3449
+ table.className += ' virtual';
3450
+ }
3451
+
3452
+ const tableStyle = getComputedStyle(table);
3453
+
3454
+ let tableHeight = (o.height - getElementHeight(p.header, true, true, true));
3455
+ if (tableStyle.boxSizing !== 'border-box') {
3456
+ tableHeight -= parseFloat(tableStyle.borderTopWidth) || 0;
3457
+ tableHeight -= parseFloat(tableStyle.borderBottomWidth) || 0;
3458
+ tableHeight -= parseFloat(tableStyle.paddingTop) || 0;
3459
+ tableHeight -= parseFloat(tableStyle.paddingBottom) || 0;
3460
+ }
3461
+ p.visibleHeight = tableHeight;
3462
+ setCssProps(table, {
3463
+ height: o.height ? tableHeight + 'px' : 'auto',
3464
+ display: 'block',
3465
+ overflowY: 'auto',
3466
+ overflowX: o.width === DGTable.Width.SCROLL ? 'auto' : 'hidden',
3467
+ });
3468
+ fragment.appendChild(table);
3469
+
3470
+ // Create the "tbody" element
3471
+ let tbody = createElement('div');
3472
+ tbody.className = o.tableClassName + '-body';
3473
+ tbody.style.minHeight = '1px';
3474
+ p.table = table;
3475
+ p.tbody = tbody;
3476
+
3477
+ relativizeElement(tbody);
3478
+ relativizeElement(table);
3479
+
3480
+ table.appendChild(tbody);
3481
+ this.el.appendChild(fragment);
3482
+
3483
+ this._setupVirtualTable();
3484
+ }
3485
+
3486
+ return this;
3487
+ }
3488
+
3489
+ /**
3490
+ * @private
3491
+ * @returns {DGTable} self
3492
+ * @deprecated
3493
+ */
3494
+ _renderSkeleton() {
3495
+ return this;
3496
+ }
3497
+
3498
+ /**
3499
+ * @private
3500
+ * @returns {DGTable} self
3501
+ */
3502
+ _updateVirtualHeight() {
3503
+ const o = this._o, p = this._p;
3504
+
3505
+ if (!p.tbody)
3506
+ return this;
3507
+
3508
+ if (o.virtualTable) {
3509
+ const virtualHeight = p.virtualListHelper.estimateFullHeight();
3510
+ p.lastVirtualScrollHeight = virtualHeight;
3511
+ p.tbody.style.height = virtualHeight + 'px';
3512
+ } else {
3513
+ p.tbody.style.height = '';
3514
+ }
3515
+
3516
+ return this;
3517
+ }
3518
+
3519
+ /**
3520
+ * @private
3521
+ * @returns {DGTable} self
3522
+ */
3523
+ _updateLastCellWidthFromScrollbar(force) {
3524
+
3525
+ const p = this._p;
3526
+
3527
+ // Calculate scrollbar's width and reduce from lat column's width
3528
+ let scrollbarWidth = p.table.offsetWidth - p.table.clientWidth;
3529
+ if (scrollbarWidth !== p.scrollbarWidth || force) {
3530
+ p.scrollbarWidth = scrollbarWidth;
3531
+ for (let i = 0; i < p.columns.length; i++) {
3532
+ p.columns[i].actualWidthConsideringScrollbarWidth = null;
3533
+ }
3534
+
3535
+ if (p.scrollbarWidth > 0 && p.visibleColumns.length > 0) {
3536
+ // (There should always be at least 1 column visible, but just in case)
3537
+ let lastColIndex = p.visibleColumns.length - 1;
3538
+
3539
+ p.visibleColumns[lastColIndex].actualWidthConsideringScrollbarWidth = p.visibleColumns[lastColIndex].actualWidth - p.scrollbarWidth;
3540
+ let lastColWidth = p.visibleColumns[lastColIndex].actualWidthConsideringScrollbarWidth + 'px';
3541
+ let tbodyChildren = p.tbody.childNodes;
3542
+ for (let i = 0, count = tbodyChildren.length; i < count; i++) {
3543
+ let row = tbodyChildren[i];
3544
+ if (row.nodeType !== 1) continue;
3545
+ row.childNodes[lastColIndex].style.width = lastColWidth;
3546
+ }
3547
+
3548
+ p.headerRow.childNodes[lastColIndex].style.width = lastColWidth;
3549
+ }
3550
+
3551
+ p.notifyRendererOfColumnsConfig?.();
3552
+ }
3553
+
3554
+ return this;
3555
+ }
3556
+
3557
+ /**
3558
+ * Explicitly set the width of the table based on the sum of the column widths
3559
+ * @private
3560
+ * @param {boolean} parentSizeMayHaveChanged Parent size may have changed, treat rendering accordingly
3561
+ * @returns {DGTable} self
3562
+ */
3563
+ _updateTableWidth(parentSizeMayHaveChanged) {
3564
+ const o = this._o, p = this._p;
3565
+ let width = this._calculateTbodyWidth();
3566
+
3567
+ p.tbody.style.minWidth = width + 'px';
3568
+ p.headerRow.style.minWidth = (width + (p.scrollbarWidth || 0)) + 'px';
3569
+
3570
+ p.eventsSink.remove(p.table, 'scroll');
3571
+
3572
+ if (o.width === DGTable.Width.AUTO) {
3573
+ // Update wrapper element's size to fully contain the table body
3574
+
3575
+ setElementWidth(p.table, getElementWidth(p.tbody, true, true, true));
3576
+ setElementWidth(this.el, getElementWidth(p.table, true, true, true));
3577
+
3578
+ } else if (o.width === DGTable.Width.SCROLL) {
3579
+
3580
+ if (parentSizeMayHaveChanged) {
3581
+ let lastScrollTop = p.table ? p.table.scrollTop : 0,
3582
+ lastScrollLeft = p.table ? p.table.scrollLeft : 0;
3583
+
3584
+ // BUGFIX: Relayout before recording the widths
3585
+ webkitRenderBugfix(this.el);
3586
+
3587
+ p.table.crollTop = lastScrollTop;
3588
+ p.table.scrollLeft = lastScrollLeft;
3589
+ p.header.scrollLeft = lastScrollLeft;
3590
+ }
3591
+
3592
+ p.eventsSink.add(p.table, 'scroll', this._onTableScrolledHorizontally.bind(this));
3593
+ }
3594
+
3595
+ return this;
3596
+ }
3597
+
3598
+ /**
3599
+ * @private
3600
+ * @returns {boolean}
3601
+ */
3602
+ _isTableRtl() {
3603
+ return getComputedStyle(this._p.table).direction === 'rtl';
3604
+ }
3605
+
3606
+ /**
3607
+ * @private
3608
+ * @param {Object} column column object
3609
+ * @returns {string}
3610
+ */
3611
+ _serializeColumnWidth(column) {
3612
+ return column.widthMode === ColumnWidthMode.AUTO ? 'auto' :
3613
+ column.widthMode === ColumnWidthMode.RELATIVE ? column.width * 100 + '%' :
3614
+ column.width;
3615
+ }
3616
+
3617
+ /**
3618
+ * @private
3619
+ * @param {HTMLElement} el
3620
+ */
3621
+ _disableCssSelect(el) {
3622
+ const style = el.style;
3623
+ // Disable these to allow our own context menu events without interruption
3624
+ style['-webkit-touch-callout'] = 'none';
3625
+ style['-webkit-user-select'] = 'none';
3626
+ style['-moz-user-select'] = 'none';
3627
+ style['-ms-user-select'] = 'none';
3628
+ style['-o-user-select'] = 'none';
3629
+ style['user-select'] = 'none';
3630
+ }
3631
+
3632
+ /**
3633
+ * @private
3634
+ * @param {HTMLElement} el
3635
+ */
3636
+ _cellMouseOverEvent(el) {
3637
+ const o = this._o, p = this._p;
3638
+
3639
+ let elInner = el.firstElementChild;
3640
+
3641
+ if (!((elInner.scrollWidth - elInner.clientWidth > 1) ||
3642
+ (elInner.scrollHeight - elInner.clientHeight > 1)))
3643
+ return;
3644
+
3645
+ this.hideCellPreview();
3646
+ p.abortCellPreview = false;
3647
+
3648
+ const rowEl = el.parentElement;
3649
+ const previewCell = createElement('div');
3650
+ previewCell.innerHTML = el.innerHTML;
3651
+ previewCell.className = o.cellPreviewClassName;
3652
+
3653
+ let isHeaderCell = el.classList.contains(`${o.tableClassName}-header-cell`);
3654
+ if (isHeaderCell) {
3655
+ previewCell.classList.add('header');
3656
+ if (el.classList.contains('sortable')) {
3657
+ previewCell.classList.add('sortable');
3658
+ }
3659
+
3660
+ previewCell.draggable = true;
3661
+
3662
+ this._bindHeaderColumnEvents(previewCell);
3663
+ }
3664
+
3665
+ const elStyle = getComputedStyle(el);
3666
+ const elInnerStyle = getComputedStyle(elInner);
3667
+
3668
+ let paddingL = parseFloat(elStyle.paddingLeft) || 0,
3669
+ paddingR = parseFloat(elStyle.paddingRight) || 0,
3670
+ paddingT = parseFloat(elStyle.paddingTop) || 0,
3671
+ paddingB = parseFloat(elStyle.paddingBottom) || 0;
3672
+
3673
+ let requiredWidth = elInner.scrollWidth + (el.clientWidth - elInner.offsetWidth);
3674
+
3675
+ let borderBox = elStyle.boxSizing === 'border-box';
3676
+ if (borderBox) {
3677
+ previewCell.style.boxSizing = 'border-box';
3678
+ } else {
3679
+ requiredWidth -= paddingL + paddingR;
3680
+ previewCell.style.marginTop = (parseFloat(elStyle.borderTopWidth) || 0) + 'px';
3681
+ }
3682
+
3683
+ if (!p.transparentBgColor1) {
3684
+ // Detect browser's transparent spec
3685
+ let tempDiv = document.createElement('div');
3686
+ document.body.appendChild(tempDiv);
3687
+ tempDiv.style.backgroundColor = 'transparent';
3688
+ p.transparentBgColor1 = getComputedStyle(tempDiv).backgroundColor;
3689
+ tempDiv.style.backgroundColor = 'rgba(0,0,0,0)';
3690
+ p.transparentBgColor2 = getComputedStyle(tempDiv).backgroundColor;
3691
+ tempDiv.remove();
3692
+ }
3693
+
3694
+ let css = {
3695
+ 'box-sizing': borderBox ? 'border-box' : 'content-box',
3696
+ 'width': requiredWidth,
3697
+ 'min-height': Math.max(getElementHeight(el), parseFloat(elStyle.minHeight) || 0) + 'px',
3698
+ 'padding-left': paddingL,
3699
+ 'padding-right': paddingR,
3700
+ 'padding-top': paddingT,
3701
+ 'padding-bottom': paddingB,
3702
+ 'overflow': 'hidden',
3703
+ 'position': 'absolute',
3704
+ 'z-index': '-1',
3705
+ 'left': '0',
3706
+ 'top': '0',
3707
+ 'cursor': elStyle.cursor,
3708
+ };
3709
+
3710
+ let bgColor = elStyle.backgroundColor;
3711
+ if (bgColor === p.transparentBgColor1 || bgColor === p.transparentBgColor2) {
3712
+ bgColor = getComputedStyle(rowEl).backgroundColor;
3713
+ }
3714
+ if (bgColor === p.transparentBgColor1 || bgColor === p.transparentBgColor2) {
3715
+ bgColor = '#fff';
3716
+ }
3717
+ css['background-color'] = bgColor;
3718
+
3719
+ setCssProps(previewCell, css);
3720
+ setCssProps(previewCell.firstChild, {
3721
+ 'direction': elInnerStyle.direction,
3722
+ 'white-space': elInnerStyle.whiteSpace,
3723
+ 'min-height': elInnerStyle.minHeight,
3724
+ 'line-height': elInnerStyle.lineHeight,
3725
+ 'font': elInnerStyle.font,
3726
+ });
3727
+
3728
+ this.el.appendChild(previewCell);
3729
+
3730
+ if (isHeaderCell) {
3731
+ this._disableCssSelect(previewCell);
3732
+ }
3733
+
3734
+ previewCell['rowVIndex'] = rowEl['vIndex'];
3735
+ let rowIndex = previewCell['rowIndex'] = rowEl['index'];
3736
+ previewCell['columnName'] = p.visibleColumns[nativeIndexOf.call(rowEl.childNodes, el)].name;
3737
+
3738
+ try {
3739
+ let selection = SelectionHelper.saveSelection(el);
3740
+ if (selection)
3741
+ SelectionHelper.restoreSelection(previewCell, selection);
3742
+ } catch (ignored) { /* we're ok with this */ }
3743
+
3744
+ this.emit(
3745
+ 'cellpreview', {
3746
+ el: previewCell.firstElementChild,
3747
+ name: previewCell['columnName'],
3748
+ rowIndex: rowIndex,
3749
+ rowData: rowIndex == null ? null : p.rows[rowIndex],
3750
+ cell: el,
3751
+ cellEl: elInner,
3752
+ },
3753
+ );
3754
+
3755
+ if (p.abortCellPreview) {
3756
+ previewCell.remove();
3757
+ return;
3758
+ }
3759
+
3760
+ if (rowIndex != null) {
3761
+ previewCell.addEventListener('click', event => {
3762
+ this.emit('rowclick', {
3763
+ event: event,
3764
+ filteredRowIndex: rowEl['vIndex'],
3765
+ rowIndex: rowIndex,
3766
+ rowEl: rowEl,
3767
+ rowData: p.rows[rowIndex],
3768
+ });
3769
+ });
3770
+ }
3771
+
3772
+ let parent = this.el;
3773
+ let scrollParent = parent === window ? document : parent;
3774
+
3775
+ const parentStyle = getComputedStyle(parent);
3776
+
3777
+ let offset = getElementOffset(el);
3778
+ let parentOffset = getElementOffset(parent);
3779
+ let rtl = elStyle.float === 'right';
3780
+ let prop = rtl ? 'right' : 'left';
3781
+
3782
+ // Handle RTL, go from the other side
3783
+ if (rtl) {
3784
+ let windowWidth = window.innerWidth;
3785
+ offset.right = windowWidth - (offset.left + getElementWidth(el, true, true, true));
3786
+ parentOffset.right = windowWidth - (parentOffset.left + getElementWidth(parent, true, true, true));
3787
+ }
3788
+
3789
+ // If the parent has borders, then it would offset the offset...
3790
+ offset.left -= parseFloat(parentStyle.borderLeftWidth) || 0;
3791
+ if (prop === 'right')
3792
+ offset.right -= parseFloat(parentStyle.borderRightWidth) || 0;
3793
+ offset.top -= parseFloat(parentStyle.borderTopWidth) || 0;
3794
+
3795
+ // Handle border widths of the element being offset
3796
+ offset[prop] += parseFloat(elStyle[`border-${prop}-width`]) || 0;
3797
+ offset.top += parseFloat(elStyle.borderTopWidth) || parseFloat(elStyle.borderBottomWidth) || 0;
3798
+
3799
+ // Subtract offsets to get offset relative to parent
3800
+ offset.left -= parentOffset.left;
3801
+ if (prop === 'right')
3802
+ offset.right -= parentOffset.right;
3803
+ offset.top -= parentOffset.top;
3804
+
3805
+ // Constrain horizontally
3806
+ let minHorz = 0,
3807
+ maxHorz = getElementWidth(parent, false, false, false) - getElementWidth(previewCell, true, true, true);
3808
+ offset[prop] = offset[prop] < minHorz ?
3809
+ minHorz :
3810
+ (offset[prop] > maxHorz ? maxHorz : offset[prop]);
3811
+
3812
+ // Constrain vertically
3813
+ let totalHeight = getElementHeight(el, true, true, true);
3814
+ let maxTop = scrollParent.scrollTop + getElementHeight(parent, true) - totalHeight;
3815
+ if (offset.top > maxTop) {
3816
+ offset.top = Math.max(0, maxTop);
3817
+ }
3818
+
3819
+ // Apply css to preview cell
3820
+ let previewCss = {
3821
+ 'top': offset.top + 'px',
3822
+ 'z-index': 9999,
3823
+ };
3824
+ previewCss[prop] = offset[prop] + 'px';
3825
+ setCssProps(previewCell, previewCss);
3826
+
3827
+ previewCell[OriginalCellSymbol] = el;
3828
+ p.cellPreviewCell = previewCell;
3829
+ el[PreviewCellSymbol] = previewCell;
3830
+
3831
+ p._bindCellHoverOut(el);
3832
+ p._bindCellHoverOut(previewCell);
3833
+
3834
+ // Avoid interfering with wheel scrolling the table
3835
+ previewCell.addEventListener('wheel', () => {
3836
+ // Let the table naturally scroll with the wheel
3837
+ this.hideCellPreview();
3838
+ });
3839
+ }
3840
+
3841
+ /**
3842
+ * @private
3843
+ * @param {HTMLElement} _el
3844
+ */
3845
+ _cellMouseOutEvent(_el) {
3846
+ this.hideCellPreview();
3847
+ }
3848
+
3849
+ /**
3850
+ * Hides the current cell preview,
3851
+ * or prevents the one that is currently trying to show (in the 'cellpreview' event)
3852
+ * @public
3853
+ * @expose
3854
+ * @returns {DGTable} self
3855
+ */
3856
+ hideCellPreview() {
3857
+ const p = this._p;
3858
+
3859
+ if (p.cellPreviewCell) {
3860
+ let previewCell = p.cellPreviewCell;
3861
+ let origCell = previewCell[OriginalCellSymbol];
3862
+ let selection;
3863
+
3864
+ try {
3865
+ selection = SelectionHelper.saveSelection(previewCell);
3866
+ } catch (ignored) { /* we're ok with this */ }
3867
+
3868
+ p.cellPreviewCell.remove();
3869
+ p._unbindCellHoverOut(origCell);
3870
+ p._unbindCellHoverOut(previewCell);
3871
+
3872
+ try {
3873
+ if (selection)
3874
+ SelectionHelper.restoreSelection(origCell, selection);
3875
+ } catch (ignored) { /* we're ok with this */ }
3876
+
3877
+ this.emit('cellpreviewdestroy', {
3878
+ el: previewCell.firstChild,
3879
+ name: previewCell['columnName'],
3880
+ rowIndex: previewCell['rowIndex'],
3881
+ rowData: previewCell['rowIndex'] == null ? null : p.rows[previewCell['rowIndex']],
3882
+ cell: origCell,
3883
+ cellEl: origCell.firstChild,
3884
+ });
3885
+
3886
+ delete origCell[PreviewCellSymbol];
3887
+ delete previewCell[OriginalCellSymbol];
3888
+
3889
+ p.cellPreviewCell = null;
3890
+ p.abortCellPreview = false;
3891
+ } else {
3892
+ p.abortCellPreview = true;
3893
+ }
3894
+
3895
+ return this;
3896
+ }
3897
+ }
3898
+
3899
+ /**
3900
+ * @public
3901
+ * @expose
3902
+ * @type {string}
3903
+ */
3904
+ DGTable.VERSION = '@@VERSION';
3905
+
3906
+ // It's a shame the Google Closure Compiler does not support exposing a nested @param
3907
+
3908
+ /**
3909
+ * @typedef {Object} SERIALIZED_COLUMN
3910
+ * @property {number|null|undefined} [order=0]
3911
+ * @property {string|null|undefined} [width='auto']
3912
+ * @property {boolean|null|undefined} [visible=true]
3913
+ * */
3914
+
3915
+ /**
3916
+ * @typedef {Object} SERIALIZED_COLUMN_SORT
3917
+ * @property {string|null|undefined} [column='']
3918
+ * @property {boolean|null|undefined} [descending=false]
3919
+ * */
3920
+
3921
+ /**
3922
+ * @enum {ColumnWidthMode|number|undefined}
3923
+ * @const
3924
+ * @typedef {ColumnWidthMode}
3925
+ */
3926
+ const ColumnWidthMode = {
3927
+ /** @const*/ AUTO: 0,
3928
+ /** @const*/ ABSOLUTE: 1,
3929
+ /** @const*/ RELATIVE: 2,
3930
+ };
3931
+
3932
+ /**
3933
+ * @enum {DGTable.Width|string|undefined}
3934
+ * @const
3935
+ * @typedef {DGTable.Width}
3936
+ */
3937
+ DGTable.Width = {
3938
+ /** @const*/ NONE: 'none',
3939
+ /** @const*/ AUTO: 'auto',
3940
+ /** @const*/ SCROLL: 'scroll',
3941
+ };
3942
+
3943
+ /**
3944
+ * @expose
3945
+ * @typedef {Object} COLUMN_SORT_OPTIONS
3946
+ * @property {string|null|undefined} column
3947
+ * @property {boolean|null|undefined} [descending=false]
3948
+ * */
3949
+
3950
+ /**
3951
+ * @expose
3952
+ * @typedef {Object} COLUMN_OPTIONS
3953
+ * @property {string|null|undefined} width
3954
+ * @property {string|null|undefined} name
3955
+ * @property {string|null|undefined} label
3956
+ * @property {string|null|undefined} dataPath - defaults to `name`
3957
+ * @property {string|null|undefined} comparePath - defaults to `dataPath`
3958
+ * @property {number|string|null|undefined} comparePath
3959
+ * @property {boolean|null|undefined} [resizable=true]
3960
+ * @property {boolean|null|undefined} [movable=true]
3961
+ * @property {boolean|null|undefined} [sortable=true]
3962
+ * @property {boolean|null|undefined} [visible=true]
3963
+ * @property {string|null|undefined} [cellClasses]
3964
+ * @property {boolean|null|undefined} [ignoreMin=false]
3965
+ * */
3966
+
3967
+ /**
3968
+ * @typedef {Object} DGTable.Options
3969
+ * @property {COLUMN_OPTIONS[]} [columns]
3970
+ * @property {number} [height]
3971
+ * @property {DGTable.Width} [width]
3972
+ * @property {boolean|null|undefined} [virtualTable=true]
3973
+ * @property {number|null|undefined} [estimatedRowHeight=40]
3974
+ * @property {boolean|null|undefined} [resizableColumns=true]
3975
+ * @property {boolean|null|undefined} [movableColumns=true]
3976
+ * @property {number|null|undefined} [sortableColumns=1]
3977
+ * @property {boolean|null|undefined} [adjustColumnWidthForSortArrow=true]
3978
+ * @property {boolean|null|undefined} [relativeWidthGrowsToFillWidth=true]
3979
+ * @property {boolean|null|undefined} [relativeWidthShrinksToFillWidth=false]
3980
+ * @property {boolean|null|undefined} [convertColumnWidthsToRelative=false]
3981
+ * @property {boolean|null|undefined} [autoFillTableWidth=false]
3982
+ * @property {boolean|null|undefined} [allowCancelSort=true]
3983
+ * @property {string|null|undefined} [cellClasses]
3984
+ * @property {string|string[]|COLUMN_SORT_OPTIONS|COLUMN_SORT_OPTIONS[]} [sortColumn]
3985
+ * @property {Function|null|undefined} [cellFormatter=null]
3986
+ * @property {Function|null|undefined} [headerCellFormatter=null]
3987
+ * @property {number|null|undefined} [rowsBufferSize=10]
3988
+ * @property {number|null|undefined} [minColumnWidth=35]
3989
+ * @property {number|null|undefined} [resizeAreaWidth=8]
3990
+ * @property {function(columnName: string, descending: boolean, defaultComparator: function(a,b):number):{function(a,b):number}} [onComparatorRequired]
3991
+ * @property {function(data: any[], sort: function(any[]):any[]):any[]} [customSortingProvider]
3992
+ * @property {string|null|undefined} [resizerClassName=undefined]
3993
+ * @property {string|null|undefined} [tableClassName=undefined]
3994
+ * @property {boolean|null|undefined} [allowCellPreview=true]
3995
+ * @property {boolean|null|undefined} [allowHeaderCellPreview=true]
3996
+ * @property {string|null|undefined} [cellPreviewClassName=undefined]
3997
+ * @property {boolean|null|undefined} [cellPreviewAutoBackground=true]
3998
+ * @property {Element|null|undefined} [el=undefined]
3999
+ * @property {string|null|undefined} [className=undefined]
4000
+ * @property {Function|null|undefined} [filter=undefined]
4001
+ * */
4002
+
4003
+ export default DGTable;