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,630 @@
1
+ import {
2
+ isNumeric,
3
+ nextTick,
4
+ isNumber,
5
+ notSet
6
+ } from './utils';
7
+
8
+ export default class DataManager {
9
+ constructor(options) {
10
+ this.options = options;
11
+ this.sortRows = nextTick(this.sortRows, this);
12
+ this.switchColumn = nextTick(this.switchColumn, this);
13
+ this.removeColumn = nextTick(this.removeColumn, this);
14
+ this.options.filterRows = nextTick(this.options.filterRows, this);
15
+ }
16
+
17
+ init(data, columns) {
18
+ if (!data) {
19
+ data = this.options.data;
20
+ }
21
+ if (columns) {
22
+ this.options.columns = columns;
23
+ }
24
+
25
+ this.data = data;
26
+
27
+ this.rowCount = 0;
28
+ this.columns = [];
29
+ this.rows = [];
30
+
31
+ this.prepareColumns();
32
+ this.validateData(this.data);
33
+ this.rows = this.prepareRows(this.data);
34
+ this.prepareTreeRows();
35
+ this.prepareRowView();
36
+ this.prepareNumericColumns();
37
+ }
38
+
39
+ // computed property
40
+ get currentSort() {
41
+ const col = this.columns.find(col => col.sortOrder !== 'none');
42
+ return col || {
43
+ colIndex: -1,
44
+ sortOrder: 'none'
45
+ };
46
+ }
47
+
48
+ prepareColumns() {
49
+ this.columns = [];
50
+ this.validateColumns();
51
+ this.prepareDefaultColumns();
52
+ this.prepareHeader();
53
+ }
54
+
55
+ prepareDefaultColumns() {
56
+ if (this.options.checkboxColumn && !this.hasColumnById('_checkbox')) {
57
+ const cell = {
58
+ id: '_checkbox',
59
+ content: this.getCheckboxHTML(),
60
+ editable: false,
61
+ resizable: false,
62
+ sortable: false,
63
+ focusable: false,
64
+ dropdown: false,
65
+ width: 32
66
+ };
67
+ this.columns.push(cell);
68
+ }
69
+
70
+ if (this.options.serialNoColumn && !this.hasColumnById('_rowIndex')) {
71
+ let cell = {
72
+ id: '_rowIndex',
73
+ content: '',
74
+ align: 'center',
75
+ editable: false,
76
+ resizable: false,
77
+ focusable: false,
78
+ dropdown: false
79
+ };
80
+ if (this.options.data.length > 1000) {
81
+ cell.resizable = true;
82
+ }
83
+ this.columns.push(cell);
84
+ }
85
+ }
86
+
87
+ prepareHeader() {
88
+ let columns = this.columns.concat(this.options.columns);
89
+ const baseCell = {
90
+ isHeader: 1,
91
+ editable: true,
92
+ sortable: true,
93
+ resizable: true,
94
+ focusable: true,
95
+ dropdown: true,
96
+ width: null,
97
+ format: (value) => {
98
+ if (value === null || value === undefined) {
99
+ return '';
100
+ }
101
+ return value + '';
102
+ }
103
+ };
104
+
105
+ this.columns = columns
106
+ .map((cell, i) => this.prepareCell(cell, i))
107
+ .map(col => Object.assign({}, baseCell, col))
108
+ .map(col => {
109
+ col.content = col.content || col.name || '';
110
+ col.id = col.id || col.content;
111
+ return col;
112
+ });
113
+ }
114
+
115
+ prepareCell(content, i) {
116
+ const cell = {
117
+ content: '',
118
+ sortOrder: 'none',
119
+ colIndex: i,
120
+ column: this.columns[i]
121
+ };
122
+
123
+ if (content !== null && typeof content === 'object') {
124
+ // passed as column/header
125
+ Object.assign(cell, content);
126
+ } else {
127
+ cell.content = content;
128
+ }
129
+
130
+ return cell;
131
+ }
132
+
133
+ prepareNumericColumns() {
134
+ const row0 = this.getRow(0);
135
+ if (!row0) return;
136
+ this.columns = this.columns.map((column, i) => {
137
+
138
+ const cellValue = row0[i].content;
139
+ if (!column.align && isNumeric(cellValue)) {
140
+ column.align = 'right';
141
+ }
142
+
143
+ return column;
144
+ });
145
+ }
146
+
147
+ prepareRows(data) {
148
+ return data.map((d, i) => {
149
+ const index = this._getNextRowCount();
150
+
151
+ let row = [];
152
+ let meta = {
153
+ rowIndex: index
154
+ };
155
+
156
+ if (Array.isArray(d)) {
157
+ // row is an array
158
+ if (this.options.checkboxColumn) {
159
+ row.push(this.getCheckboxHTML());
160
+ }
161
+ if (this.options.serialNoColumn) {
162
+ row.push((index + 1) + '');
163
+ }
164
+ row = row.concat(d);
165
+
166
+ while (row.length < this.columns.length) {
167
+ row.push('');
168
+ }
169
+
170
+ } else {
171
+ // row is an object
172
+ for (let col of this.columns) {
173
+ if (col.id === '_checkbox') {
174
+ row.push(this.getCheckboxHTML());
175
+ } else if (col.id === '_rowIndex') {
176
+ row.push((index + 1) + '');
177
+ } else {
178
+ row.push(d[col.id]);
179
+ }
180
+ }
181
+
182
+ meta.indent = d.indent || 0;
183
+ }
184
+
185
+ return this.prepareRow(row, meta);
186
+ });
187
+ }
188
+
189
+ prepareTreeRows() {
190
+ this.rows.forEach((row, i) => {
191
+ if (isNumber(row.meta.indent)) {
192
+ // if (i === 36) debugger;
193
+ const nextRow = this.getRow(i + 1);
194
+ row.meta.isLeaf = !nextRow ||
195
+ notSet(nextRow.meta.indent) ||
196
+ nextRow.meta.indent <= row.meta.indent;
197
+ row.meta.isTreeNodeClose = false;
198
+ }
199
+ });
200
+ }
201
+
202
+ prepareRowView() {
203
+ // This is order in which rows will be rendered in the table.
204
+ // When sorting happens, only this.rowViewOrder will change
205
+ // and not the original this.rows
206
+ this.rowViewOrder = this.rows.map(row => row.meta.rowIndex);
207
+ }
208
+
209
+ prepareRow(row, meta) {
210
+ row = row
211
+ .map((cell, i) => this.prepareCell(cell, i))
212
+ .map(cell => {
213
+ // Following code is equivalent but avoids memory allocation and copying.
214
+ // return Object.assign({rowIndex: meta.rowIndex, indent: meta.indent}, cell)
215
+ if (cell.rowIndex == null) {
216
+ cell.rowIndex = meta.rowIndex;
217
+ }
218
+ if (cell.indent == null) {
219
+ cell.indent = meta.indent;
220
+ }
221
+ return cell;
222
+ });
223
+
224
+ // monkey patched in array object
225
+ row.meta = meta;
226
+ return row;
227
+ }
228
+
229
+ validateColumns() {
230
+ const columns = this.options.columns;
231
+ if (!Array.isArray(columns)) {
232
+ throw new DataError('`columns` must be an array');
233
+ }
234
+
235
+ columns.forEach((column, i) => {
236
+ if (typeof column !== 'string' && typeof column !== 'object') {
237
+ throw new DataError(`column "${i}" must be a string or an object`);
238
+ }
239
+ });
240
+ }
241
+
242
+ validateData(data) {
243
+ if (Array.isArray(data) &&
244
+ (data.length === 0 || Array.isArray(data[0]) || typeof data[0] === 'object')) {
245
+ return true;
246
+ }
247
+ throw new DataError('`data` must be an array of arrays or objects');
248
+ }
249
+
250
+ appendRows(rows) {
251
+ this.validateData(rows);
252
+ this.rows = this.rows.concat(this.prepareRows(rows));
253
+ this.prepareTreeRows();
254
+ this.prepareRowView();
255
+ }
256
+
257
+ sortRows(colIndex, sortOrder = 'none') {
258
+ colIndex = +colIndex;
259
+
260
+ // reset sortOrder and update for colIndex
261
+ this.getColumns()
262
+ .map(col => {
263
+ if (col.colIndex === colIndex) {
264
+ col.sortOrder = sortOrder;
265
+ } else {
266
+ col.sortOrder = 'none';
267
+ }
268
+ });
269
+
270
+ this._sortRows(colIndex, sortOrder);
271
+ }
272
+
273
+ _sortRows(colIndex, sortOrder) {
274
+
275
+ if (this.currentSort.colIndex === colIndex) {
276
+ // reverse the array if only sortOrder changed
277
+ if (
278
+ (this.currentSort.sortOrder === 'asc' && sortOrder === 'desc') ||
279
+ (this.currentSort.sortOrder === 'desc' && sortOrder === 'asc')
280
+ ) {
281
+ this.reverseArray(this.rowViewOrder);
282
+ this.currentSort.sortOrder = sortOrder;
283
+ return;
284
+ }
285
+ }
286
+
287
+ this.rowViewOrder.sort((a, b) => {
288
+ const aIndex = a;
289
+ const bIndex = b;
290
+
291
+ let aContent = this.getCell(colIndex, a).content;
292
+ let bContent = this.getCell(colIndex, b).content;
293
+ aContent = aContent == null ? '' : aContent;
294
+ bContent = bContent == null ? '' : bContent;
295
+
296
+ if (sortOrder === 'none') {
297
+ return aIndex - bIndex;
298
+ } else if (sortOrder === 'asc') {
299
+ if (aContent < bContent) return -1;
300
+ if (aContent > bContent) return 1;
301
+ if (aContent === bContent) return 0;
302
+ } else if (sortOrder === 'desc') {
303
+ if (aContent < bContent) return 1;
304
+ if (aContent > bContent) return -1;
305
+ if (aContent === bContent) return 0;
306
+ }
307
+ return 0;
308
+ });
309
+
310
+ if (this.hasColumnById('_rowIndex')) {
311
+ // update row index
312
+ const srNoColIndex = this.getColumnIndexById('_rowIndex');
313
+ this.rows.forEach((row, index) => {
314
+ const viewIndex = this.rowViewOrder.indexOf(index);
315
+ const cell = row[srNoColIndex];
316
+ cell.content = (viewIndex + 1) + '';
317
+ });
318
+ }
319
+ }
320
+
321
+ reverseArray(array) {
322
+ let left = null;
323
+ let right = null;
324
+ let length = array.length;
325
+
326
+ for (left = 0, right = length - 1; left < right; left += 1, right -= 1) {
327
+ const temporary = array[left];
328
+
329
+ array[left] = array[right];
330
+ array[right] = temporary;
331
+ }
332
+ }
333
+
334
+ switchColumn(index1, index2) {
335
+ // update columns
336
+ const temp = this.columns[index1];
337
+ this.columns[index1] = this.columns[index2];
338
+ this.columns[index2] = temp;
339
+
340
+ this.columns[index1].colIndex = index1;
341
+ this.columns[index2].colIndex = index2;
342
+
343
+ // update rows
344
+ this.rows.forEach(row => {
345
+ const newCell1 = Object.assign({}, row[index1], {
346
+ colIndex: index2
347
+ });
348
+ const newCell2 = Object.assign({}, row[index2], {
349
+ colIndex: index1
350
+ });
351
+
352
+ row[index2] = newCell1;
353
+ row[index1] = newCell2;
354
+ });
355
+ }
356
+
357
+ removeColumn(index) {
358
+ index = +index;
359
+ const filter = cell => cell.colIndex !== index;
360
+ const map = (cell, i) => Object.assign({}, cell, {
361
+ colIndex: i
362
+ });
363
+ // update columns
364
+ this.columns = this.columns
365
+ .filter(filter)
366
+ .map(map);
367
+
368
+ // update rows
369
+ this.rows.forEach(row => {
370
+ // remove cell
371
+ row.splice(index, 1);
372
+ // update colIndex
373
+ row.forEach((cell, i) => {
374
+ cell.colIndex = i;
375
+ });
376
+ });
377
+ }
378
+
379
+ updateRow(row, rowIndex) {
380
+ if (row.length < this.columns.length) {
381
+ if (this.hasColumnById('_rowIndex')) {
382
+ const val = (rowIndex + 1) + '';
383
+
384
+ row = [val].concat(row);
385
+ }
386
+
387
+ if (this.hasColumnById('_checkbox')) {
388
+ const val = '<input type="checkbox" />';
389
+
390
+ row = [val].concat(row);
391
+ }
392
+ }
393
+
394
+ const _row = this.prepareRow(row, {rowIndex});
395
+ const index = this.rows.findIndex(row => row[0].rowIndex === rowIndex);
396
+ this.rows[index] = _row;
397
+
398
+ return _row;
399
+ }
400
+
401
+ updateCell(colIndex, rowIndex, options) {
402
+ let cell;
403
+ if (typeof colIndex === 'object') {
404
+ // cell object was passed,
405
+ // must have colIndex, rowIndex
406
+ cell = colIndex;
407
+ colIndex = cell.colIndex;
408
+ rowIndex = cell.rowIndex;
409
+ // the object passed must be merged with original cell
410
+ options = cell;
411
+ }
412
+ cell = this.getCell(colIndex, rowIndex);
413
+
414
+ // mutate object directly
415
+ for (let key in options) {
416
+ const newVal = options[key];
417
+ if (newVal !== undefined) {
418
+ cell[key] = newVal;
419
+ }
420
+ }
421
+
422
+ return cell;
423
+ }
424
+
425
+ updateColumn(colIndex, keyValPairs) {
426
+ const column = this.getColumn(colIndex);
427
+ for (let key in keyValPairs) {
428
+ const newVal = keyValPairs[key];
429
+ if (newVal !== undefined) {
430
+ column[key] = newVal;
431
+ }
432
+ }
433
+ return column;
434
+ }
435
+
436
+ filterRows(filters) {
437
+ return this.options.filterRows(this.rows, filters, this)
438
+ .then(result => {
439
+ if (!result) {
440
+ result = this.getAllRowIndices();
441
+ }
442
+
443
+ if (!result.then) {
444
+ result = Promise.resolve(result);
445
+ }
446
+
447
+ return result.then(rowsToShow => {
448
+ this._filteredRows = rowsToShow;
449
+
450
+ const rowsToHide = this.getAllRowIndices()
451
+ .filter(index => !rowsToShow.includes(index));
452
+
453
+ return {
454
+ rowsToHide,
455
+ rowsToShow
456
+ };
457
+ });
458
+ });
459
+ }
460
+
461
+ getFilteredRowIndices() {
462
+ return this._filteredRows || this.getAllRowIndices();
463
+ }
464
+
465
+ getAllRowIndices() {
466
+ return this.rows.map(row => row.meta.rowIndex);
467
+ }
468
+
469
+ getRowCount() {
470
+ return this.rowCount;
471
+ }
472
+
473
+ _getNextRowCount() {
474
+ const val = this.rowCount;
475
+
476
+ this.rowCount++;
477
+ return val;
478
+ }
479
+
480
+ getRows(start, end) {
481
+ return this.rows.slice(start, end);
482
+ }
483
+
484
+ getRowsForView(start, end) {
485
+ const rows = this.rowViewOrder.map(i => this.rows[i]);
486
+ return rows.slice(start, end);
487
+ }
488
+
489
+ getColumns(skipStandardColumns) {
490
+ let columns = this.columns;
491
+
492
+ if (skipStandardColumns) {
493
+ columns = columns.slice(this.getStandardColumnCount());
494
+ }
495
+
496
+ return columns;
497
+ }
498
+
499
+ getStandardColumnCount() {
500
+ if (this.options.checkboxColumn && this.options.serialNoColumn) {
501
+ return 2;
502
+ }
503
+
504
+ if (this.options.checkboxColumn || this.options.serialNoColumn) {
505
+ return 1;
506
+ }
507
+
508
+ return 0;
509
+ }
510
+
511
+ getColumnCount(skipStandardColumns) {
512
+ let val = this.columns.length;
513
+
514
+ if (skipStandardColumns) {
515
+ val = val - this.getStandardColumnCount();
516
+ }
517
+
518
+ return val;
519
+ }
520
+
521
+ getColumn(colIndex) {
522
+ colIndex = +colIndex;
523
+
524
+ if (colIndex < 0) {
525
+ // negative indexes
526
+ colIndex = this.columns.length + colIndex;
527
+ }
528
+
529
+ return this.columns.find(col => col.colIndex === colIndex);
530
+ }
531
+
532
+ getColumnById(id) {
533
+ return this.columns.find(col => col.id === id);
534
+ }
535
+
536
+ getRow(rowIndex) {
537
+ rowIndex = +rowIndex;
538
+ return this.rows[rowIndex];
539
+ }
540
+
541
+ getCell(colIndex, rowIndex) {
542
+ rowIndex = +rowIndex;
543
+ colIndex = +colIndex;
544
+ return this.getRow(rowIndex)[colIndex];
545
+ }
546
+
547
+ getChildren(parentRowIndex) {
548
+ parentRowIndex = +parentRowIndex;
549
+ const parentIndent = this.getRow(parentRowIndex).meta.indent;
550
+ const out = [];
551
+
552
+ for (let i = parentRowIndex + 1; i < this.rowCount; i++) {
553
+ const row = this.getRow(i);
554
+ if (isNaN(row.meta.indent)) continue;
555
+
556
+ if (row.meta.indent > parentIndent) {
557
+ out.push(i);
558
+ }
559
+
560
+ if (row.meta.indent === parentIndent) {
561
+ break;
562
+ }
563
+ }
564
+
565
+ return out;
566
+ }
567
+
568
+ getImmediateChildren(parentRowIndex) {
569
+ parentRowIndex = +parentRowIndex;
570
+ const parentIndent = this.getRow(parentRowIndex).meta.indent;
571
+ const out = [];
572
+ const childIndent = parentIndent + 1;
573
+
574
+ for (let i = parentRowIndex + 1; i < this.rowCount; i++) {
575
+ const row = this.getRow(i);
576
+ if (isNaN(row.meta.indent) || row.meta.indent > childIndent) continue;
577
+
578
+ if (row.meta.indent === childIndent) {
579
+ out.push(i);
580
+ }
581
+
582
+ if (row.meta.indent === parentIndent) {
583
+ break;
584
+ }
585
+ }
586
+
587
+ return out;
588
+ }
589
+
590
+ get() {
591
+ return {
592
+ columns: this.columns,
593
+ rows: this.rows
594
+ };
595
+ }
596
+
597
+ /**
598
+ * Returns the original data which was passed
599
+ * based on rowIndex
600
+ * @param {Number} rowIndex
601
+ * @returns Array|Object
602
+ * @memberof DataManager
603
+ */
604
+ getData(rowIndex) {
605
+ return this.data[rowIndex];
606
+ }
607
+
608
+ hasColumn(name) {
609
+ return Boolean(this.columns.find(col => col.content === name));
610
+ }
611
+
612
+ hasColumnById(id) {
613
+ return Boolean(this.columns.find(col => col.id === id));
614
+ }
615
+
616
+ getColumnIndex(name) {
617
+ return this.columns.findIndex(col => col.content === name);
618
+ }
619
+
620
+ getColumnIndexById(id) {
621
+ return this.columns.findIndex(col => col.id === id);
622
+ }
623
+
624
+ getCheckboxHTML() {
625
+ return '<input type="checkbox" />';
626
+ }
627
+ }
628
+
629
+ // Custom Errors
630
+ export class DataError extends TypeError {};