adore-datatable 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.
@@ -0,0 +1,934 @@
1
+ import {
2
+ copyTextToClipboard,
3
+ makeDataAttributeString,
4
+ throttle,
5
+ linkProperties,
6
+ escapeHTML,
7
+ } from './utils';
8
+ import $ from './dom';
9
+ import icons from './icons';
10
+
11
+ export default class CellManager {
12
+ constructor(instance) {
13
+ this.instance = instance;
14
+ linkProperties(this, this.instance, [
15
+ 'wrapper',
16
+ 'options',
17
+ 'style',
18
+ 'header',
19
+ 'bodyScrollable',
20
+ 'columnmanager',
21
+ 'rowmanager',
22
+ 'datamanager',
23
+ 'keyboard',
24
+ 'footer'
25
+ ]);
26
+
27
+ this.bindEvents();
28
+ }
29
+
30
+ bindEvents() {
31
+ this.bindFocusCell();
32
+ this.bindEditCell();
33
+ this.bindKeyboardSelection();
34
+ this.bindCopyCellContents();
35
+ this.bindMouseEvents();
36
+ this.bindTreeEvents();
37
+ }
38
+
39
+ bindFocusCell() {
40
+ this.bindKeyboardNav();
41
+ }
42
+
43
+ bindEditCell() {
44
+ this.$editingCell = null;
45
+
46
+ $.on(this.bodyScrollable, 'dblclick', '.dt-cell', (e, cell) => {
47
+ this.activateEditing(cell);
48
+ });
49
+
50
+ this.keyboard.on('enter', () => {
51
+ if (this.$focusedCell && !this.$editingCell) {
52
+ // enter keypress on focused cell
53
+ this.activateEditing(this.$focusedCell);
54
+ } else if (this.$editingCell) {
55
+ // enter keypress on editing cell
56
+ this.deactivateEditing();
57
+ }
58
+ });
59
+ }
60
+
61
+ bindKeyboardNav() {
62
+ const focusLastCell = (direction) => {
63
+ if (!this.$focusedCell || this.$editingCell) {
64
+ return false;
65
+ }
66
+
67
+ let $cell = this.$focusedCell;
68
+ const {
69
+ rowIndex,
70
+ colIndex
71
+ } = $.data($cell);
72
+
73
+ if (direction === 'left') {
74
+ $cell = this.getLeftMostCell$(rowIndex);
75
+ } else if (direction === 'right') {
76
+ $cell = this.getRightMostCell$(rowIndex);
77
+ } else if (direction === 'up') {
78
+ $cell = this.getTopMostCell$(colIndex);
79
+ } else if (direction === 'down') {
80
+ $cell = this.getBottomMostCell$(colIndex);
81
+ }
82
+
83
+ this.focusCell($cell);
84
+ return true;
85
+ };
86
+
87
+ ['left', 'right', 'up', 'down', 'tab', 'shift+tab']
88
+ .map(direction => this.keyboard.on(direction, () => this.focusCellInDirection(direction)));
89
+
90
+ ['left', 'right', 'up', 'down']
91
+ .map(direction => this.keyboard.on(`ctrl+${direction}`, () => focusLastCell(direction)));
92
+
93
+ this.keyboard.on('esc', () => {
94
+ this.deactivateEditing(false);
95
+ this.columnmanager.toggleFilter(false);
96
+ });
97
+
98
+ if (this.options.inlineFilters) {
99
+ this.keyboard.on('ctrl+f', (e) => {
100
+ const $cell = $.closest('.dt-cell', e.target);
101
+ const { colIndex } = $.data($cell);
102
+
103
+ this.activateFilter(colIndex);
104
+ return true;
105
+ });
106
+
107
+ $.on(this.header, 'focusin', '.dt-filter', () => {
108
+ this.unfocusCell(this.$focusedCell);
109
+ });
110
+ }
111
+ }
112
+
113
+ bindKeyboardSelection() {
114
+ const getNextSelectionCursor = (direction) => {
115
+ let $selectionCursor = this.getSelectionCursor();
116
+
117
+ if (direction === 'left') {
118
+ $selectionCursor = this.getLeftCell$($selectionCursor);
119
+ } else if (direction === 'right') {
120
+ $selectionCursor = this.getRightCell$($selectionCursor);
121
+ } else if (direction === 'up') {
122
+ $selectionCursor = this.getAboveCell$($selectionCursor);
123
+ } else if (direction === 'down') {
124
+ $selectionCursor = this.getBelowCell$($selectionCursor);
125
+ }
126
+
127
+ return $selectionCursor;
128
+ };
129
+
130
+ ['left', 'right', 'up', 'down']
131
+ .map(direction =>
132
+ this.keyboard.on(`shift+${direction}`, () => this.selectArea(getNextSelectionCursor(direction))));
133
+ }
134
+
135
+ bindCopyCellContents() {
136
+ this.keyboard.on('ctrl+c', () => {
137
+ const noOfCellsCopied = this.copyCellContents(this.$focusedCell, this.$selectionCursor);
138
+ const message = this.instance.translate('{count} cells copied', {
139
+ count: noOfCellsCopied
140
+ });
141
+
142
+ if (noOfCellsCopied) {
143
+ this.instance.showToastMessage(message, 2);
144
+ }
145
+ });
146
+
147
+ if (this.options.pasteFromClipboard) {
148
+ this.keyboard.on('ctrl+v', (e) => {
149
+ // hack
150
+ // https://stackoverflow.com/a/2177059/5353542
151
+ this.instance.pasteTarget.focus();
152
+
153
+ setTimeout(() => {
154
+ const data = this.instance.pasteTarget.value;
155
+ this.instance.pasteTarget.value = '';
156
+ this.pasteContentInCell(data);
157
+ }, 10);
158
+
159
+ return false;
160
+ });
161
+ }
162
+ }
163
+
164
+ bindMouseEvents() {
165
+ let mouseDown = null;
166
+
167
+ $.on(this.bodyScrollable, 'mousedown', '.dt-cell', (e) => {
168
+ mouseDown = true;
169
+ this.focusCell($(e.delegatedTarget));
170
+ });
171
+
172
+ $.on(this.bodyScrollable, 'mouseup', () => {
173
+ mouseDown = false;
174
+ });
175
+
176
+ if (this.options.showTotalRow) {
177
+ $.on(this.footer, 'click', '.dt-cell', (e) => {
178
+
179
+ this.focusCell($(e.delegatedTarget));
180
+ });
181
+
182
+ }
183
+
184
+ const selectArea = (e) => {
185
+ if (!mouseDown) return;
186
+ this.selectArea($(e.delegatedTarget));
187
+ };
188
+
189
+ $.on(this.bodyScrollable, 'mousemove', '.dt-cell', throttle(selectArea, 50));
190
+ }
191
+
192
+ bindTreeEvents() {
193
+ $.on(this.bodyScrollable, 'click', '.dt-tree-node__toggle', (e, $toggle) => {
194
+ const $cell = $.closest('.dt-cell', $toggle);
195
+ const { rowIndex } = $.data($cell);
196
+
197
+ if ($cell.classList.contains('dt-cell--tree-close')) {
198
+ this.rowmanager.openSingleNode(rowIndex);
199
+ } else {
200
+ this.rowmanager.closeSingleNode(rowIndex);
201
+ }
202
+ });
203
+ }
204
+
205
+ focusCell($cell, {
206
+ skipClearSelection = 0,
207
+ skipDOMFocus = 0,
208
+ skipScrollToCell = 0
209
+ } = {}) {
210
+ if (!$cell) return;
211
+
212
+ // don't focus if already editing cell
213
+ if ($cell === this.$editingCell) return;
214
+
215
+ const {
216
+ colIndex,
217
+ isHeader
218
+ } = $.data($cell);
219
+ if (isHeader) {
220
+ return;
221
+ }
222
+
223
+ const column = this.columnmanager.getColumn(colIndex);
224
+ if (column.focusable === false) {
225
+ return;
226
+ }
227
+
228
+ if (!skipScrollToCell) {
229
+ this.scrollToCell($cell);
230
+ }
231
+
232
+ this.deactivateEditing();
233
+ if (!skipClearSelection) {
234
+ this.clearSelection();
235
+ }
236
+
237
+ if (this.$focusedCell) {
238
+ this.$focusedCell.classList.remove('dt-cell--focus');
239
+ }
240
+
241
+ this.$focusedCell = $cell;
242
+ $cell.classList.add('dt-cell--focus');
243
+
244
+ if (!skipDOMFocus) {
245
+ // so that keyboard nav works
246
+ $cell.focus();
247
+ }
248
+
249
+ this.highlightRowColumnHeader($cell);
250
+ }
251
+
252
+ unfocusCell($cell) {
253
+ if (!$cell) return;
254
+
255
+ // remove cell border
256
+ $cell.classList.remove('dt-cell--focus');
257
+ this.$focusedCell = null;
258
+
259
+ // reset header background
260
+ if (this.lastHeaders) {
261
+ this.lastHeaders.forEach(header => header && header.classList.remove('dt-cell--highlight'));
262
+ }
263
+ }
264
+
265
+ highlightRowColumnHeader($cell) {
266
+ const {
267
+ colIndex,
268
+ rowIndex
269
+ } = $.data($cell);
270
+
271
+ const srNoColIndex = this.datamanager.getColumnIndexById('_rowIndex');
272
+ const colHeaderSelector = `.dt-cell--header-${colIndex}`;
273
+ const rowHeaderSelector = `.dt-cell--${srNoColIndex}-${rowIndex}`;
274
+
275
+ if (this.lastHeaders) {
276
+ this.lastHeaders.forEach(header => header && header.classList.remove('dt-cell--highlight'));
277
+ }
278
+
279
+ const colHeader = $(colHeaderSelector, this.wrapper);
280
+ const rowHeader = $(rowHeaderSelector, this.wrapper);
281
+
282
+ this.lastHeaders = [colHeader, rowHeader];
283
+ this.lastHeaders.forEach(header => header && header.classList.add('dt-cell--highlight'));
284
+ }
285
+
286
+ selectAreaOnClusterChanged() {
287
+ if (!(this.$focusedCell && this.$selectionCursor)) return;
288
+ const {
289
+ colIndex,
290
+ rowIndex
291
+ } = $.data(this.$selectionCursor);
292
+ const $cell = this.getCell$(colIndex, rowIndex);
293
+
294
+ if (!$cell || $cell === this.$selectionCursor) return;
295
+
296
+ // selectArea needs $focusedCell
297
+ const fCell = $.data(this.$focusedCell);
298
+ this.$focusedCell = this.getCell$(fCell.colIndex, fCell.rowIndex);
299
+
300
+ this.selectArea($cell);
301
+ }
302
+
303
+ focusCellOnClusterChanged() {
304
+ if (!this.$focusedCell) return;
305
+
306
+ const {
307
+ colIndex,
308
+ rowIndex
309
+ } = $.data(this.$focusedCell);
310
+ const $cell = this.getCell$(colIndex, rowIndex);
311
+
312
+ if (!$cell) return;
313
+ // this function is called after hyperlist renders the rows after scroll,
314
+ // focusCell calls clearSelection which resets the area selection
315
+ // so a flag to skip it
316
+ // we also skip DOM focus and scroll to cell
317
+ // because it fights with the user scroll
318
+ this.focusCell($cell, {
319
+ skipClearSelection: 1,
320
+ skipDOMFocus: 1,
321
+ skipScrollToCell: 1
322
+ });
323
+ }
324
+
325
+ selectArea($selectionCursor) {
326
+ if (!this.$focusedCell) return;
327
+
328
+ if (this._selectArea(this.$focusedCell, $selectionCursor)) {
329
+ // valid selection
330
+ this.$selectionCursor = $selectionCursor;
331
+ }
332
+ }
333
+
334
+ _selectArea($cell1, $cell2) {
335
+ if ($cell1 === $cell2) return false;
336
+ const cells = this.getCellsInRange($cell1, $cell2);
337
+ if (!cells) return false;
338
+
339
+ this.clearSelection();
340
+ this._selectedCells = cells.map(index => this.getCell$(...index));
341
+ requestAnimationFrame(() => {
342
+ this._selectedCells.map($cell => $cell.classList.add('dt-cell--highlight'));
343
+ });
344
+ return true;
345
+ }
346
+
347
+ getCellsInRange($cell1, $cell2) {
348
+ let colIndex1, rowIndex1, colIndex2, rowIndex2;
349
+
350
+ if (typeof $cell1 === 'number') {
351
+ [colIndex1, rowIndex1, colIndex2, rowIndex2] = arguments;
352
+ } else
353
+ if (typeof $cell1 === 'object') {
354
+ if (!($cell1 && $cell2)) {
355
+ return false;
356
+ }
357
+
358
+ const cell1 = $.data($cell1);
359
+ const cell2 = $.data($cell2);
360
+
361
+ colIndex1 = +cell1.colIndex;
362
+ colIndex2 = +cell2.colIndex;
363
+
364
+ if (this.columnmanager.sortState) {
365
+ this.sortedColumn = true;
366
+ rowIndex1 = this.datamanager.rowViewOrder.indexOf(parseInt(cell1.rowIndex, 10));
367
+ rowIndex2 = this.datamanager.rowViewOrder.indexOf(parseInt(cell2.rowIndex, 10));
368
+ } else {
369
+ rowIndex1 = +cell1.rowIndex;
370
+ rowIndex2 = +cell2.rowIndex;
371
+ }
372
+
373
+ }
374
+
375
+ if (rowIndex1 > rowIndex2) {
376
+ [rowIndex1, rowIndex2] = [rowIndex2, rowIndex1];
377
+ }
378
+
379
+ if (colIndex1 > colIndex2) {
380
+ [colIndex1, colIndex2] = [colIndex2, colIndex1];
381
+ }
382
+
383
+ if (this.isStandardCell(colIndex1) || this.isStandardCell(colIndex2)) {
384
+ return false;
385
+ }
386
+
387
+ const cells = [];
388
+ let colIndex = colIndex1;
389
+ let rowIndex = rowIndex1;
390
+ const rowIndices = [];
391
+
392
+ while (rowIndex <= rowIndex2) {
393
+ rowIndices.push(rowIndex);
394
+ rowIndex += 1;
395
+ }
396
+
397
+ rowIndices.map((rowIndex) => {
398
+ while (colIndex <= colIndex2) {
399
+ cells.push([colIndex, rowIndex]);
400
+ colIndex++;
401
+ }
402
+ colIndex = colIndex1;
403
+ });
404
+ if (this.columnmanager.sortState) {
405
+ cells.forEach(selectedCells => {
406
+ selectedCells[1] = this.datamanager.rowViewOrder[selectedCells[1]];
407
+ });
408
+ }
409
+ return cells;
410
+ }
411
+
412
+ clearSelection() {
413
+ (this._selectedCells || [])
414
+ .forEach($cell => $cell.classList.remove('dt-cell--highlight'));
415
+
416
+ this._selectedCells = [];
417
+ this.$selectionCursor = null;
418
+ }
419
+
420
+ getSelectionCursor() {
421
+ return this.$selectionCursor || this.$focusedCell;
422
+ }
423
+
424
+ activateEditing($cell) {
425
+ this.focusCell($cell);
426
+ const {
427
+ rowIndex,
428
+ colIndex
429
+ } = $.data($cell);
430
+
431
+ const col = this.columnmanager.getColumn(colIndex);
432
+ if (col && (col.editable === false || col.focusable === false)) {
433
+ return;
434
+ }
435
+
436
+ const cell = this.getCell(colIndex, rowIndex);
437
+ if (cell && cell.editable === false) {
438
+ return;
439
+ }
440
+
441
+ if (this.$editingCell) {
442
+ const {
443
+ _rowIndex,
444
+ _colIndex
445
+ } = $.data(this.$editingCell);
446
+
447
+ if (rowIndex === _rowIndex && colIndex === _colIndex) {
448
+ // editing the same cell
449
+ return;
450
+ }
451
+ }
452
+
453
+ this.$editingCell = $cell;
454
+ $cell.classList.add('dt-cell--editing');
455
+
456
+ const $editCell = $('.dt-cell__edit', $cell);
457
+ $editCell.innerHTML = '';
458
+
459
+ const editor = this.getEditor(colIndex, rowIndex, cell.content, $editCell);
460
+
461
+ if (editor) {
462
+ this.currentCellEditor = editor;
463
+ // initialize editing input with cell value
464
+ editor.initValue(cell.content, rowIndex, col);
465
+ }
466
+ }
467
+
468
+ deactivateEditing(submitValue = true) {
469
+ if (submitValue) {
470
+ this.submitEditing();
471
+ }
472
+ // keep focus on the cell so that keyboard navigation works
473
+ if (this.$focusedCell) this.$focusedCell.focus();
474
+
475
+ if (!this.$editingCell) return;
476
+ this.$editingCell.classList.remove('dt-cell--editing');
477
+ this.$editingCell = null;
478
+ }
479
+
480
+ getEditor(colIndex, rowIndex, value, parent) {
481
+ const column = this.datamanager.getColumn(colIndex);
482
+ const row = this.datamanager.getRow(rowIndex);
483
+ const data = this.datamanager.getData(rowIndex);
484
+ let editor = this.options.getEditor ?
485
+ this.options.getEditor(colIndex, rowIndex, value, parent, column, row, data) :
486
+ this.getDefaultEditor(parent);
487
+
488
+ if (editor === false) {
489
+ // explicitly returned false
490
+ return false;
491
+ }
492
+ if (editor === undefined) {
493
+ // didn't return editor, fallback to default
494
+ editor = this.getDefaultEditor(parent);
495
+ }
496
+
497
+ return editor;
498
+ }
499
+
500
+ getDefaultEditor(parent) {
501
+ const $input = $.create('input', {
502
+ class: 'dt-input',
503
+ type: 'text',
504
+ inside: parent
505
+ });
506
+
507
+ return {
508
+ initValue(value) {
509
+ $input.focus();
510
+ $input.value = value;
511
+ },
512
+ getValue() {
513
+ return $input.value;
514
+ },
515
+ setValue(value) {
516
+ $input.value = value;
517
+ }
518
+ };
519
+ }
520
+
521
+ submitEditing() {
522
+ let promise = Promise.resolve();
523
+ if (!this.$editingCell) return promise;
524
+
525
+ const $cell = this.$editingCell;
526
+ const {
527
+ rowIndex,
528
+ colIndex
529
+ } = $.data($cell);
530
+ const col = this.datamanager.getColumn(colIndex);
531
+
532
+ if ($cell) {
533
+ const editor = this.currentCellEditor;
534
+
535
+ if (editor) {
536
+ let valuePromise = editor.getValue();
537
+
538
+ // convert to stubbed Promise
539
+ if (!valuePromise.then) {
540
+ valuePromise = Promise.resolve(valuePromise);
541
+ }
542
+
543
+ promise = valuePromise.then((value) => {
544
+ const oldValue = this.getCell(colIndex, rowIndex).content;
545
+
546
+ if (oldValue === value) return false;
547
+
548
+ const done = editor.setValue(value, rowIndex, col);
549
+
550
+ // update cell immediately
551
+ this.updateCell(colIndex, rowIndex, value, true);
552
+ $cell.focus();
553
+
554
+ if (done && done.then) {
555
+ // revert to oldValue if promise fails
556
+ done.catch((e) => {
557
+ console.log(e);
558
+ this.updateCell(colIndex, rowIndex, oldValue);
559
+ });
560
+ }
561
+ return done;
562
+ });
563
+ }
564
+ }
565
+
566
+ this.currentCellEditor = null;
567
+ return promise;
568
+ }
569
+
570
+ copyCellContents($cell1, $cell2) {
571
+ if (!$cell2 && $cell1) {
572
+ // copy only focusedCell
573
+ const {
574
+ colIndex,
575
+ rowIndex,
576
+ isTotalRow
577
+ } = $.data($cell1);
578
+ let copiedContent = '';
579
+ if (isTotalRow) {
580
+ let choosenFooterCell = this.$focusedCell;
581
+ copiedContent = choosenFooterCell.children[0].title;
582
+ } else {
583
+ const cell = this.getCell(colIndex, rowIndex);
584
+ copiedContent = cell.content;
585
+ }
586
+ copyTextToClipboard(copiedContent);
587
+ return 1;
588
+ }
589
+ const cells = this.getCellsInRange($cell1, $cell2);
590
+
591
+ if (!cells) return 0;
592
+
593
+ const rows = cells
594
+ // get cell objects
595
+ .map(index => this.getCell(...index))
596
+ // convert to array of rows
597
+ .reduce((acc, curr) => {
598
+ const rowIndex = curr.rowIndex;
599
+
600
+ acc[rowIndex] = acc[rowIndex] || [];
601
+ acc[rowIndex].push(curr.content);
602
+
603
+ return acc;
604
+ }, []);
605
+
606
+ const values = rows
607
+ // join values by tab
608
+ .map(row => row.join('\t'))
609
+ // join rows by newline
610
+ .join('\n');
611
+
612
+ copyTextToClipboard(values);
613
+
614
+ // return no of cells copied
615
+ return rows.reduce((total, row) => total + row.length, 0);
616
+ }
617
+
618
+ pasteContentInCell(data) {
619
+ if (!this.$focusedCell) return;
620
+
621
+ const matrix = data
622
+ .split('\n')
623
+ .map(row => row.split('\t'))
624
+ .filter(row => row.length && row.every(it => it));
625
+
626
+ let { colIndex, rowIndex } = $.data(this.$focusedCell);
627
+
628
+ let focusedCell = {
629
+ colIndex: +colIndex,
630
+ rowIndex: +rowIndex
631
+ };
632
+
633
+ matrix.forEach((row, i) => {
634
+ let rowIndex = i + focusedCell.rowIndex;
635
+ row.forEach((cell, j) => {
636
+ let colIndex = j + focusedCell.colIndex;
637
+ this.updateCell(colIndex, rowIndex, cell, true);
638
+ });
639
+ });
640
+ }
641
+
642
+ activateFilter(colIndex) {
643
+ this.columnmanager.toggleFilter();
644
+ this.columnmanager.focusFilter(colIndex);
645
+
646
+ if (!this.columnmanager.isFilterShown) {
647
+ // put focus back on cell
648
+ this.$focusedCell && this.$focusedCell.focus();
649
+ }
650
+ }
651
+
652
+ updateCell(colIndex, rowIndex, value, refreshHtml = false) {
653
+ const cell = this.datamanager.updateCell(colIndex, rowIndex, {
654
+ content: value
655
+ });
656
+ this.refreshCell(cell, refreshHtml);
657
+ }
658
+
659
+ refreshCell(cell, refreshHtml = false) {
660
+ const $cell = $(this.selector(cell.colIndex, cell.rowIndex), this.bodyScrollable);
661
+ $cell.innerHTML = this.getCellContent(cell, refreshHtml);
662
+ }
663
+
664
+ toggleTreeButton(rowIndex, flag) {
665
+ const colIndex = this.columnmanager.getFirstColumnIndex();
666
+ const $cell = this.getCell$(colIndex, rowIndex);
667
+ if ($cell) {
668
+ $cell.classList[flag ? 'remove' : 'add']('dt-cell--tree-close');
669
+ }
670
+ }
671
+
672
+ isStandardCell(colIndex) {
673
+ // Standard cells are in Sr. No and Checkbox column
674
+ return colIndex < this.columnmanager.getFirstColumnIndex();
675
+ }
676
+
677
+ focusCellInDirection(direction) {
678
+ if (!this.$focusedCell || (this.$editingCell && ['left', 'right', 'up', 'down'].includes(direction))) {
679
+ return false;
680
+ } else if (this.$editingCell && ['tab', 'shift+tab'].includes(direction)) {
681
+ this.deactivateEditing();
682
+ }
683
+
684
+ let $cell = this.$focusedCell;
685
+
686
+ if (direction === 'left' || direction === 'shift+tab') {
687
+ $cell = this.getLeftCell$($cell);
688
+ } else if (direction === 'right' || direction === 'tab') {
689
+ $cell = this.getRightCell$($cell);
690
+ } else if (direction === 'up') {
691
+ $cell = this.getAboveCell$($cell);
692
+ } else if (direction === 'down') {
693
+ $cell = this.getBelowCell$($cell);
694
+ }
695
+
696
+ if (!$cell) {
697
+ return false;
698
+ }
699
+
700
+ const {
701
+ colIndex
702
+ } = $.data($cell);
703
+ const column = this.columnmanager.getColumn(colIndex);
704
+
705
+ if (!column.focusable) {
706
+ let $prevFocusedCell = this.$focusedCell;
707
+ this.unfocusCell($prevFocusedCell);
708
+ this.$focusedCell = $cell;
709
+ let ret = this.focusCellInDirection(direction);
710
+ if (!ret) {
711
+ this.focusCell($prevFocusedCell);
712
+ }
713
+ return ret;
714
+ }
715
+
716
+ this.focusCell($cell);
717
+ return true;
718
+ }
719
+
720
+ getCell$(colIndex, rowIndex) {
721
+ return $(this.selector(colIndex, rowIndex), this.bodyScrollable);
722
+ }
723
+
724
+ getAboveCell$($cell) {
725
+ const {
726
+ colIndex
727
+ } = $.data($cell);
728
+
729
+ let $aboveRow = $cell.parentElement.previousElementSibling;
730
+ while ($aboveRow && $aboveRow.classList.contains('dt-row--hide')) {
731
+ $aboveRow = $aboveRow.previousElementSibling;
732
+ }
733
+
734
+ if (!$aboveRow) return $cell;
735
+ return $(`.dt-cell--col-${colIndex}`, $aboveRow);
736
+ }
737
+
738
+ getBelowCell$($cell) {
739
+ const {
740
+ colIndex
741
+ } = $.data($cell);
742
+
743
+ let $belowRow = $cell.parentElement.nextElementSibling;
744
+ while ($belowRow && $belowRow.classList.contains('dt-row--hide')) {
745
+ $belowRow = $belowRow.nextElementSibling;
746
+ }
747
+
748
+ if (!$belowRow) return $cell;
749
+ return $(`.dt-cell--col-${colIndex}`, $belowRow);
750
+ }
751
+
752
+ getLeftCell$($cell) {
753
+ return $cell.previousElementSibling;
754
+ }
755
+
756
+ getRightCell$($cell) {
757
+ return $cell.nextElementSibling;
758
+ }
759
+
760
+ getLeftMostCell$(rowIndex) {
761
+ return this.getCell$(this.columnmanager.getFirstColumnIndex(), rowIndex);
762
+ }
763
+
764
+ getRightMostCell$(rowIndex) {
765
+ return this.getCell$(this.columnmanager.getLastColumnIndex(), rowIndex);
766
+ }
767
+
768
+ getTopMostCell$(colIndex) {
769
+ return this.getCell$(colIndex, this.rowmanager.getFirstRowIndex());
770
+ }
771
+
772
+ getBottomMostCell$(colIndex) {
773
+ return this.getCell$(colIndex, this.rowmanager.getLastRowIndex());
774
+ }
775
+
776
+ getCell(colIndex, rowIndex) {
777
+ return this.instance.datamanager.getCell(colIndex, rowIndex);
778
+ }
779
+
780
+ getRowHeight() {
781
+ return $.style($('.dt-row', this.bodyScrollable), 'height');
782
+ }
783
+
784
+ scrollToCell($cell) {
785
+ if ($.inViewport($cell, this.bodyScrollable) || $.inViewport($cell, this.footer)) return false;
786
+
787
+ const {
788
+ rowIndex
789
+ } = $.data($cell);
790
+ this.rowmanager.scrollToRow(rowIndex);
791
+ return false;
792
+ }
793
+
794
+ getRowCountPerPage() {
795
+ return Math.ceil(this.instance.getViewportHeight() / this.getRowHeight());
796
+ }
797
+
798
+ getCellHTML(cell) {
799
+ const {
800
+ rowIndex,
801
+ colIndex,
802
+ isHeader,
803
+ isFilter,
804
+ isTotalRow
805
+ } = cell;
806
+ const dataAttr = makeDataAttributeString({
807
+ rowIndex,
808
+ colIndex,
809
+ isHeader,
810
+ isFilter,
811
+ isTotalRow
812
+ });
813
+
814
+ const row = this.datamanager.getRow(rowIndex);
815
+
816
+ const isBodyCell = !(isHeader || isFilter || isTotalRow);
817
+
818
+ const className = [
819
+ 'dt-cell',
820
+ 'dt-cell--col-' + colIndex,
821
+ isBodyCell ? `dt-cell--${colIndex}-${rowIndex}` : '',
822
+ isBodyCell ? 'dt-cell--row-' + rowIndex : '',
823
+ isHeader ? 'dt-cell--header' : '',
824
+ isHeader ? `dt-cell--header-${colIndex}` : '',
825
+ isFilter ? 'dt-cell--filter' : '',
826
+ isBodyCell && (row && row.meta.isTreeNodeClose) ? 'dt-cell--tree-close' : ''
827
+ ].join(' ');
828
+
829
+ return `
830
+ <div class="${className}" ${dataAttr} tabindex="0">
831
+ ${this.getCellContent(cell)}
832
+ </div>
833
+ `;
834
+ }
835
+
836
+ getCellContent(cell, refreshHtml = false) {
837
+ const {
838
+ isHeader,
839
+ isFilter,
840
+ colIndex
841
+ } = cell;
842
+
843
+ const editable = !isHeader && cell.editable !== false;
844
+ const editCellHTML = editable ? this.getEditCellHTML(colIndex) : '';
845
+
846
+ const sortable = isHeader && cell.sortable !== false;
847
+ const sortIndicator = sortable ?
848
+ `<span class="sort-indicator">
849
+ ${this.options.sortIndicator[cell.sortOrder]}
850
+ </span>` :
851
+ '';
852
+
853
+ const resizable = isHeader && cell.resizable !== false;
854
+ const resizeColumn = resizable ? '<span class="dt-cell__resize-handle"></span>' : '';
855
+
856
+ const hasDropdown = isHeader && cell.dropdown !== false;
857
+ const dropdown = hasDropdown ? this.columnmanager.getDropdownHTML() : '';
858
+
859
+ let customFormatter = CellManager.getCustomCellFormatter(cell);
860
+ let contentHTML;
861
+ if (isHeader || isFilter || !customFormatter) {
862
+ contentHTML = cell.content;
863
+ } else {
864
+ if (!cell.html || refreshHtml) {
865
+ const row = this.datamanager.getRow(cell.rowIndex);
866
+ const data = this.datamanager.getData(cell.rowIndex);
867
+ contentHTML = customFormatter(cell.content, row, cell.column, data);
868
+ } else {
869
+ contentHTML = cell.html;
870
+ }
871
+ }
872
+
873
+ cell.html = contentHTML;
874
+
875
+ if (this.options.treeView && !(isHeader || isFilter) && cell.indent !== undefined) {
876
+ const nextRow = this.datamanager.getRow(cell.rowIndex + 1);
877
+ const addToggle = nextRow && nextRow.meta.indent > cell.indent;
878
+ const leftPadding = 20;
879
+ const unit = 'px';
880
+
881
+ // Add toggle and indent in the first column
882
+ const firstColumnIndex = this.datamanager.getColumnIndexById('_rowIndex') + 1;
883
+ if (firstColumnIndex === cell.colIndex) {
884
+ const padding = ((cell.indent || 0)) * leftPadding;
885
+ const toggleHTML = addToggle ?
886
+ `<span class="dt-tree-node__toggle" style="left: ${padding - leftPadding}${unit}">
887
+ <span class="icon-open">${icons.chevronDown}</span>
888
+ <span class="icon-close">${icons.chevronRight}</span>
889
+ </span>` : '';
890
+ contentHTML = `<span class="dt-tree-node" style="padding-left: ${padding}${unit}">
891
+ ${toggleHTML}
892
+ <span>${contentHTML}</span>
893
+ </span>`;
894
+ }
895
+ }
896
+
897
+ const className = [
898
+ 'dt-cell__content',
899
+ isHeader ? `dt-cell__content--header-${colIndex}` : `dt-cell__content--col-${colIndex}`
900
+ ].join(' ');
901
+
902
+ let cellContentHTML = `
903
+ <div class="${className}">
904
+ ${contentHTML}
905
+ ${sortIndicator}
906
+ ${resizeColumn}
907
+ ${dropdown}
908
+ </div>
909
+ ${editCellHTML}
910
+ `;
911
+
912
+ let div = document.createElement('div');
913
+ div.innerHTML = contentHTML;
914
+
915
+ let textContent = div.textContent;
916
+ textContent = textContent.replace(/\s+/g, ' ').trim();
917
+
918
+ cellContentHTML = cellContentHTML.replace('>', ` title="${escapeHTML(textContent)}">`);
919
+
920
+ return cellContentHTML;
921
+ }
922
+
923
+ getEditCellHTML(colIndex) {
924
+ return `<div class="dt-cell__edit dt-cell__edit--col-${colIndex}"></div>`;
925
+ }
926
+
927
+ selector(colIndex, rowIndex) {
928
+ return `.dt-cell--${colIndex}-${rowIndex}`;
929
+ }
930
+
931
+ static getCustomCellFormatter(cell) {
932
+ return cell.format || (cell.column && cell.column.format) || null;
933
+ }
934
+ }