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,496 @@
1
+ import $ from './dom';
2
+ import Sortable from 'sortablejs';
3
+ import {
4
+ linkProperties,
5
+ debounce
6
+ } from './utils';
7
+
8
+ export default class ColumnManager {
9
+ constructor(instance) {
10
+ this.instance = instance;
11
+
12
+ linkProperties(this, this.instance, [
13
+ 'options',
14
+ 'fireEvent',
15
+ 'header',
16
+ 'datamanager',
17
+ 'cellmanager',
18
+ 'style',
19
+ 'wrapper',
20
+ 'rowmanager',
21
+ 'bodyScrollable',
22
+ 'bodyRenderer'
23
+ ]);
24
+
25
+ this.bindEvents();
26
+ }
27
+
28
+ renderHeader() {
29
+ this.header.innerHTML = '<div></div>';
30
+ this.refreshHeader();
31
+ }
32
+
33
+ refreshHeader() {
34
+ const columns = this.datamanager.getColumns();
35
+
36
+ // refresh html
37
+ $('div', this.header).innerHTML = this.getHeaderHTML(columns);
38
+
39
+ this.$filterRow = $('.dt-row-filter', this.header);
40
+ if (this.$filterRow) {
41
+ $.style(this.$filterRow, { display: 'none' });
42
+ }
43
+ // reset columnMap
44
+ this.$columnMap = [];
45
+ this.bindMoveColumn();
46
+ }
47
+
48
+ getHeaderHTML(columns) {
49
+ let html = this.rowmanager.getRowHTML(columns, {
50
+ isHeader: 1
51
+ });
52
+ if (this.options.inlineFilters) {
53
+ html += this.rowmanager.getRowHTML(columns, {
54
+ isFilter: 1
55
+ });
56
+ }
57
+ return html;
58
+ }
59
+
60
+ bindEvents() {
61
+ this.bindDropdown();
62
+ this.bindResizeColumn();
63
+ this.bindPerfectColumnWidth();
64
+ this.bindFilter();
65
+ }
66
+
67
+ bindDropdown() {
68
+ let toggleClass = '.dt-dropdown__toggle';
69
+ let dropdownClass = '.dt-dropdown__list';
70
+
71
+ // attach the dropdown list to container
72
+ this.instance.dropdownContainer.innerHTML = this.getDropdownListHTML();
73
+ this.$dropdownList = this.instance.dropdownContainer.firstElementChild;
74
+
75
+ $.on(this.header, 'click', toggleClass, e => {
76
+ this.openDropdown(e);
77
+ });
78
+
79
+ const deactivateDropdownOnBodyClick = (e) => {
80
+ const selector = [
81
+ toggleClass, toggleClass + ' *',
82
+ dropdownClass, dropdownClass + ' *'
83
+ ].join(',');
84
+ if (e.target.matches(selector)) return;
85
+ deactivateDropdown();
86
+ };
87
+ $.on(document.body, 'click', deactivateDropdownOnBodyClick);
88
+ document.addEventListener('scroll', deactivateDropdown, true);
89
+
90
+ this.instance.on('onDestroy', () => {
91
+ $.off(document.body, 'click', deactivateDropdownOnBodyClick);
92
+ $.off(document, 'scroll', deactivateDropdown);
93
+ });
94
+
95
+ $.on(this.$dropdownList, 'click', '.dt-dropdown__list-item', (e, $item) => {
96
+ if (!this._dropdownActiveColIndex) return;
97
+ const dropdownItems = this.options.headerDropdown;
98
+ const { index } = $.data($item);
99
+ const colIndex = this._dropdownActiveColIndex;
100
+ let callback = dropdownItems[index].action;
101
+
102
+ callback && callback.call(this.instance, this.getColumn(colIndex));
103
+ this.hideDropdown();
104
+ });
105
+
106
+ const _this = this;
107
+ function deactivateDropdown(e) {
108
+ _this.hideDropdown();
109
+ }
110
+
111
+ this.hideDropdown();
112
+ }
113
+
114
+ openDropdown(e) {
115
+ if (!this._dropdownWidth) {
116
+ $.style(this.$dropdownList, { display: '' });
117
+ this._dropdownWidth = $.style(this.$dropdownList, 'width');
118
+ }
119
+ $.style(this.$dropdownList, {
120
+ display: '',
121
+ left: (e.clientX - this._dropdownWidth + 4) + 'px',
122
+ top: (e.clientY + 4) + 'px'
123
+ });
124
+ const $cell = $.closest('.dt-cell', e.target);
125
+ const { colIndex } = $.data($cell);
126
+ this._dropdownActiveColIndex = colIndex;
127
+ }
128
+
129
+ hideDropdown() {
130
+ $.style(this.$dropdownList, {
131
+ display: 'none'
132
+ });
133
+ this._dropdownActiveColIndex = null;
134
+ }
135
+
136
+ bindResizeColumn() {
137
+ let isDragging = false;
138
+ let $resizingCell, startWidth, startX;
139
+
140
+ $.on(this.header, 'mousedown', '.dt-cell .dt-cell__resize-handle', (e, $handle) => {
141
+ document.body.classList.add('dt-resize');
142
+ const $cell = $handle.parentNode.parentNode;
143
+ $resizingCell = $cell;
144
+ const {
145
+ colIndex
146
+ } = $.data($resizingCell);
147
+ const col = this.getColumn(colIndex);
148
+
149
+ if (col && col.resizable === false) {
150
+ return;
151
+ }
152
+
153
+ isDragging = true;
154
+ startWidth = $.style($('.dt-cell__content', $resizingCell), 'width');
155
+ startX = e.pageX;
156
+ });
157
+
158
+ const onMouseup = (e) => {
159
+ document.body.classList.remove('dt-resize');
160
+ if (!$resizingCell) return;
161
+ isDragging = false;
162
+
163
+ const {
164
+ colIndex
165
+ } = $.data($resizingCell);
166
+ this.setColumnWidth(colIndex);
167
+ this.style.setBodyStyle();
168
+ $resizingCell = null;
169
+ };
170
+ $.on(document.body, 'mouseup', onMouseup);
171
+ this.instance.on('onDestroy', () => {
172
+ $.off(document.body, 'mouseup', onMouseup);
173
+ });
174
+
175
+ const onMouseMove = (e) => {
176
+ if (!isDragging) return;
177
+ let delta = e.pageX - startX;
178
+ if (this.options.direction === 'rtl') {
179
+ delta = -1 * delta;
180
+ }
181
+ const finalWidth = startWidth + delta;
182
+ const {
183
+ colIndex
184
+ } = $.data($resizingCell);
185
+
186
+ let columnMinWidth = this.options.minimumColumnWidth;
187
+ if (columnMinWidth > finalWidth) {
188
+ // don't resize past 30 pixels
189
+ return;
190
+ }
191
+ this.datamanager.updateColumn(colIndex, {
192
+ width: finalWidth
193
+ });
194
+ this.setColumnHeaderWidth(colIndex);
195
+ };
196
+ $.on(document.body, 'mousemove', onMouseMove);
197
+ this.instance.on('onDestroy', () => {
198
+ $.off(document.body, 'mousemove', onMouseMove);
199
+ });
200
+ }
201
+
202
+ bindPerfectColumnWidth() {
203
+ $.on(this.header, 'dblclick', '.dt-cell .dt-cell__resize-handle', (e, $handle) => {
204
+ const $cell = $handle.parentNode.parentNode;
205
+ const { colIndex } = $.data($cell);
206
+
207
+ let longestCell = this.bodyRenderer.visibleRows
208
+ .map(d => d[colIndex])
209
+ .reduce((acc, curr) => acc.content.length > curr.content.length ? acc : curr);
210
+
211
+ let $longestCellHTML = this.cellmanager.getCellHTML(longestCell);
212
+ let $div = document.createElement('div');
213
+ $div.innerHTML = $longestCellHTML;
214
+ let cellText = $div.querySelector('.dt-cell__content').textContent;
215
+
216
+ let {
217
+ borderLeftWidth,
218
+ borderRightWidth,
219
+ paddingLeft,
220
+ paddingRight
221
+ } = $.getStyle(this.bodyScrollable.querySelector('.dt-cell__content'));
222
+
223
+ let padding = [borderLeftWidth, borderRightWidth, paddingLeft, paddingRight]
224
+ .map(parseFloat)
225
+ .reduce((sum, val) => sum + val);
226
+
227
+ let width = $.measureTextWidth(cellText) + padding;
228
+ this.datamanager.updateColumn(colIndex, { width });
229
+ this.setColumnHeaderWidth(colIndex);
230
+ this.setColumnWidth(colIndex);
231
+ });
232
+ }
233
+
234
+ bindMoveColumn() {
235
+ if (this.options.disableReorderColumn) return;
236
+
237
+ const $parent = $('.dt-row', this.header);
238
+
239
+ this.sortable = Sortable.create($parent, {
240
+ onEnd: (e) => {
241
+ const {
242
+ oldIndex,
243
+ newIndex
244
+ } = e;
245
+ const $draggedCell = e.item;
246
+ const {
247
+ colIndex
248
+ } = $.data($draggedCell);
249
+ if (+colIndex === newIndex) return;
250
+
251
+ this.switchColumn(oldIndex, newIndex);
252
+ },
253
+ preventOnFilter: false,
254
+ filter: '.dt-cell__resize-handle, .dt-dropdown',
255
+ chosenClass: 'dt-cell--dragging',
256
+ animation: 150
257
+ });
258
+ }
259
+
260
+ sortColumn(colIndex, nextSortOrder) {
261
+ this.instance.freeze();
262
+ this.sortRows(colIndex, nextSortOrder)
263
+ .then(() => {
264
+ this.refreshHeader();
265
+ return this.rowmanager.refreshRows();
266
+ })
267
+ .then(() => this.instance.unfreeze())
268
+ .then(() => {
269
+ this.fireEvent('onSortColumn', this.getColumn(colIndex));
270
+ this.setSortState();
271
+ });
272
+ }
273
+
274
+ saveSorting(colIndex) {
275
+ let currentColumn = this.getColumn(colIndex);
276
+ let saveSorting = {
277
+ [currentColumn.name]: {
278
+ colIndex: colIndex,
279
+ sortOrder: currentColumn.sortOrder
280
+ }
281
+ };
282
+ this.sortingKey = this.options.sortingKey ? `${this.options.sortingKey}::sortedColumns` : 'sortedColumns' ;
283
+ localStorage.setItem(this.sortingKey, JSON.stringify(saveSorting));
284
+ }
285
+ setSortState(sortOrder) {
286
+ if (sortOrder === 'none') {
287
+ this.sortState = false;
288
+ } else {
289
+ this.sortState = true;
290
+ }
291
+ }
292
+
293
+ removeColumn(colIndex) {
294
+ const removedCol = this.getColumn(colIndex);
295
+ this.instance.freeze();
296
+ this.datamanager.removeColumn(colIndex)
297
+ .then(() => {
298
+ this.refreshHeader();
299
+ return this.rowmanager.refreshRows();
300
+ })
301
+ .then(() => this.instance.unfreeze())
302
+ .then(() => {
303
+ this.fireEvent('onRemoveColumn', removedCol);
304
+ });
305
+ }
306
+
307
+ switchColumn(oldIndex, newIndex) {
308
+ this.instance.freeze();
309
+ this.datamanager.switchColumn(oldIndex, newIndex)
310
+ .then(() => {
311
+ this.refreshHeader();
312
+ return this.rowmanager.refreshRows();
313
+ })
314
+ .then(() => {
315
+ this.setColumnWidth(oldIndex);
316
+ this.setColumnWidth(newIndex);
317
+ this.instance.unfreeze();
318
+ })
319
+ .then(() => {
320
+ this.fireEvent('onSwitchColumn',
321
+ this.getColumn(oldIndex), this.getColumn(newIndex)
322
+ );
323
+ });
324
+ }
325
+
326
+ toggleFilter(flag) {
327
+ if (!this.options.inlineFilters) return;
328
+
329
+ let showFilter;
330
+ if (flag === undefined) {
331
+ showFilter = !this.isFilterShown;
332
+ } else {
333
+ showFilter = flag;
334
+ }
335
+
336
+ if (showFilter) {
337
+ $.style(this.$filterRow, { display: '' });
338
+ } else {
339
+ $.style(this.$filterRow, { display: 'none' });
340
+ }
341
+
342
+ this.isFilterShown = showFilter;
343
+ this.style.setBodyStyle();
344
+ }
345
+
346
+ focusFilter(colIndex) {
347
+ if (!this.isFilterShown) return;
348
+
349
+ const $filterInput = $(`.dt-cell--col-${colIndex} .dt-filter`, this.$filterRow);
350
+ $filterInput.focus();
351
+ }
352
+
353
+ bindFilter() {
354
+ if (!this.options.inlineFilters) return;
355
+ const handler = e => {
356
+ this.applyFilter(this.getAppliedFilters());
357
+ };
358
+ $.on(this.header, 'keydown', '.dt-filter', debounce(handler, 300));
359
+ }
360
+
361
+ applyFilter(filters) {
362
+ this.datamanager.filterRows(filters)
363
+ .then(({
364
+ rowsToShow
365
+ }) => {
366
+ this.rowmanager.showRows(rowsToShow);
367
+ });
368
+ }
369
+
370
+ getAppliedFilters() {
371
+ const filters = {};
372
+ $.each('.dt-filter', this.header).map((input) => {
373
+ const value = input.value;
374
+ if (value) {
375
+ filters[input.dataset.colIndex] = value;
376
+ }
377
+ });
378
+ return filters;
379
+ }
380
+
381
+ applyDefaultSortOrder() {
382
+ // sort rows if any 1 column has a default sortOrder set
383
+ const columnsToSort = this.getColumns().filter(col => col.sortOrder !== 'none');
384
+
385
+ if (columnsToSort.length === 1) {
386
+ const column = columnsToSort[0];
387
+ this.sortColumn(column.colIndex, column.sortOrder);
388
+ }
389
+ }
390
+
391
+ applySavedSortOrder() {
392
+
393
+ let key = this.options.sortingKey ? `${this.options.sortingKey}::sortedColumns` : 'sortedColumns' ;
394
+ let sortingConfig = JSON.parse(localStorage.getItem(key));
395
+ if (sortingConfig) {
396
+ const columnsToSort = Object.values(sortingConfig);
397
+ for (let column of columnsToSort) {
398
+ this.sortColumn(column.colIndex, column.sortOrder);
399
+ this.sortState = true;
400
+ }
401
+ }
402
+ }
403
+
404
+ sortRows(colIndex, sortOrder) {
405
+ return this.datamanager.sortRows(colIndex, sortOrder);
406
+ }
407
+
408
+ getColumn(colIndex) {
409
+ return this.datamanager.getColumn(colIndex);
410
+ }
411
+
412
+ getColumns() {
413
+ return this.datamanager.getColumns();
414
+ }
415
+
416
+ setColumnWidth(colIndex, width) {
417
+ colIndex = +colIndex;
418
+
419
+ let columnWidth = width || this.getColumn(colIndex).width;
420
+
421
+ const selector = [
422
+ `.dt-cell__content--col-${colIndex}`,
423
+ `.dt-cell__edit--col-${colIndex}`
424
+ ].join(', ');
425
+
426
+ const styles = {
427
+ width: columnWidth + 'px'
428
+ };
429
+
430
+ this.style.setStyle(selector, styles);
431
+ }
432
+
433
+ setColumnHeaderWidth(colIndex) {
434
+ colIndex = +colIndex;
435
+ this.$columnMap = this.$columnMap || [];
436
+ const selector = `.dt-cell__content--header-${colIndex}`;
437
+ const {
438
+ width
439
+ } = this.getColumn(colIndex);
440
+
441
+ let $column = this.$columnMap[colIndex];
442
+ if (!$column) {
443
+ $column = this.header.querySelector(selector);
444
+ this.$columnMap[colIndex] = $column;
445
+ }
446
+
447
+ $column.style.width = width + 'px';
448
+ }
449
+
450
+ getColumnMinWidth(colIndex) {
451
+ colIndex = +colIndex;
452
+ return this.getColumn(colIndex).minWidth || 24;
453
+ }
454
+
455
+ getFirstColumnIndex() {
456
+ return this.datamanager.getColumnIndexById('_rowIndex') + 1;
457
+ }
458
+
459
+ getHeaderCell$(colIndex) {
460
+ return $(`.dt-cell--header-${colIndex}`, this.header);
461
+ }
462
+
463
+ getLastColumnIndex() {
464
+ return this.datamanager.getColumnCount() - 1;
465
+ }
466
+
467
+ getDropdownHTML() {
468
+ const { dropdownButton } = this.options;
469
+
470
+ return `
471
+ <div class="dt-dropdown">
472
+ <div class="dt-dropdown__toggle">${dropdownButton}</div>
473
+ </div>
474
+ `;
475
+ }
476
+
477
+ getDropdownListHTML() {
478
+ const { headerDropdown: dropdownItems } = this.options;
479
+ return `
480
+ <div class="dt-dropdown__list">
481
+ ${dropdownItems.map((d, i) => `
482
+ <div
483
+ class="dt-dropdown__list-item${d.display ? ' dt-hidden' : ''}"
484
+ data-index="${i}"
485
+ >
486
+ ${d.label}
487
+ </div>
488
+ `).join('')}
489
+ </div>
490
+ `;
491
+ }
492
+
493
+ toggleDropdownItem(index) {
494
+ $('.dt-dropdown__list', this.instance.dropdownContainer).children[index].classList.toggle('dt-hidden');
495
+ }
496
+ }
package/src/dark.css ADDED
@@ -0,0 +1,11 @@
1
+ .datatable {
2
+ --dt-border-color: #424242;
3
+ --dt-light-bg: #2e3538;
4
+ --dt-text-color: #dfe2e5;
5
+ --dt-text-light: #dfe2e5;
6
+ --dt-cell-bg: #1c1f20;
7
+ --dt-focus-border-width: 1px;
8
+ --dt-selection-highlight-color: var(--dt-light-bg);
9
+ --dt-toast-message-border: 1px solid var(--dt-border-color);
10
+ --dt-header-cell-bg: #262c2e;
11
+ }