canvas-datagrid 0.25.3 → 0.26.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.
@@ -2,6 +2,8 @@
2
2
  /*globals define: true, MutationObserver: false, requestAnimationFrame: false, performance: false, btoa: false*/
3
3
  'use strict';
4
4
 
5
+ import { mergeHiddenRowRanges } from './groups/util';
6
+
5
7
  export default function (self) {
6
8
  /**
7
9
  * Converts a integer into a letter A - ZZZZZ...
@@ -37,10 +39,14 @@ export default function (self) {
37
39
  }
38
40
  self.validateColumn(c, s);
39
41
  s.splice(index, 0, c);
40
- self.data.forEach(function (row) {
41
- self.applyDefaultValue(row, c);
42
+
43
+ self.originalData.forEach(function (row, rowIndex) {
44
+ self.applyDefaultValue(row, c, rowIndex);
42
45
  });
43
46
  self.intf.schema = s;
47
+
48
+ self.refresh();
49
+ self.dispatchDataChanged('insertcolumn', { column: c, index: index });
44
50
  };
45
51
  /**
46
52
  * Deletes a column from the schema at the specified index.
@@ -51,13 +57,18 @@ export default function (self) {
51
57
  * @param {number} index The index of the column to delete.
52
58
  */
53
59
  self.deleteColumn = function (index) {
54
- var s = self.getSchema();
60
+ var schema = self.getSchema();
61
+
55
62
  // remove data matching this column name from data
56
- self.data.forEach(function (row) {
57
- delete row[s[index].name];
63
+ self.originalData.forEach(function (row) {
64
+ delete row[schema[index].name];
58
65
  });
59
- s.splice(index, 1);
60
- self.intf.schema = s;
66
+
67
+ schema.splice(index, 1);
68
+ self.intf.schema = schema;
69
+
70
+ self.refresh();
71
+ self.dispatchDataChanged('deletecolumn', { index: index });
61
72
  };
62
73
  /**
63
74
  * Adds a new column into the schema.
@@ -71,10 +82,13 @@ export default function (self) {
71
82
  var s = self.getSchema();
72
83
  self.validateColumn(c, s);
73
84
  s.push(c);
74
- self.data.forEach(function (row) {
75
- self.applyDefaultValue(row, c);
85
+ self.originalData.forEach(function (row, rowIndex) {
86
+ self.applyDefaultValue(row, c, rowIndex);
76
87
  });
77
88
  self.intf.schema = s;
89
+
90
+ self.refresh();
91
+ self.dispatchDataChanged('addcolumn', { column: c });
78
92
  };
79
93
  /**
80
94
  * Deletes a row from the dataset at the specified index.
@@ -87,6 +101,7 @@ export default function (self) {
87
101
  self.originalData.splice(index, 1);
88
102
  self.setFilter();
89
103
  self.resize(true);
104
+ self.dispatchDataChanged('deleterow', { index: index });
90
105
  };
91
106
  /**
92
107
  * Inserts a new row into the dataset before the specified index.
@@ -103,11 +118,15 @@ export default function (self) {
103
118
  self.originalData.splice(index, 0, d);
104
119
  self.getSchema().forEach(function (c) {
105
120
  if (d[c.name] === undefined) {
106
- self.applyDefaultValue(self.originalData[index], c);
121
+ self.applyDefaultValue(self.originalData[index], c, index);
107
122
  }
108
123
  });
124
+
125
+ // setFilter calls .refresh(), so we need not call it again:
109
126
  self.setFilter();
127
+
110
128
  self.resize(true);
129
+ self.dispatchDataChanged('insertrow', { row: d, index: index });
111
130
  };
112
131
  /**
113
132
  * Adds a new row into the dataset.
@@ -123,11 +142,16 @@ export default function (self) {
123
142
  self.applyDefaultValue(
124
143
  self.originalData[self.originalData.length - 1],
125
144
  c,
145
+ self.originalData.length - 1,
126
146
  );
127
147
  }
128
148
  });
149
+
150
+ // setFilter calls .refresh(), so we need not call it again:
129
151
  self.setFilter();
152
+
130
153
  self.resize(true);
154
+ self.dispatchDataChanged('addrow', { row: d });
131
155
  };
132
156
  /**
133
157
  * Sets the height of a given row by index number.
@@ -190,8 +214,20 @@ export default function (self) {
190
214
  delete self.columnFilters[column];
191
215
  } else {
192
216
  self.columnFilters[column] = value;
217
+ if (self.attributes.showFilterInCell) {
218
+ self.filterable.rows.push(0);
219
+ self.orders.columns.forEach(function (value, index) {
220
+ self.filterable.columns.push(index);
221
+ });
222
+ }
223
+ }
224
+ if (!Object.keys(self.columnFilters).length) {
225
+ self.filterable = {
226
+ rows: [],
227
+ columns: [],
228
+ };
193
229
  }
194
- self.applyDataTransforms();
230
+ self.refresh();
195
231
  };
196
232
  /**
197
233
  * Returns the number of pixels to scroll down to line up with row rowIndex.
@@ -202,7 +238,9 @@ export default function (self) {
202
238
  */
203
239
  self.findRowScrollTop = function (rowIndex) {
204
240
  if (self.scrollCache.y[rowIndex] === undefined) {
205
- throw new RangeError('Row index out of range.');
241
+ throw new RangeError(
242
+ `Row index ${rowIndex} out of range: ${self.scrollCache.y.length}.`,
243
+ );
206
244
  }
207
245
  return self.scrollCache.y[rowIndex];
208
246
  };
@@ -220,7 +258,7 @@ export default function (self) {
220
258
  }
221
259
  return (
222
260
  self.scrollCache.x[i] -
223
- self.getColummnWidth(self.orders.columns[columnIndex])
261
+ self.getColumnWidth(self.orders.columns[columnIndex])
224
262
  );
225
263
  };
226
264
  /**
@@ -249,6 +287,11 @@ export default function (self) {
249
287
  targetY -= sbh * offsetY;
250
288
  if (x !== undefined && y !== undefined) {
251
289
  self.scrollBox.scrollTo(targetX, targetY);
290
+ if (!offsetX && !offsetY) {
291
+ // nothing to correct; a deferred re-scroll would only undo a scroll
292
+ // the user made in the meantime (#313)
293
+ return;
294
+ }
252
295
  requestAnimationFrame(function () {
253
296
  cell = self.getVisibleCellByIndex(x, y);
254
297
  // HACK: just don't offset if the target cell cannot be seen
@@ -276,6 +319,64 @@ export default function (self) {
276
319
  self.gotoRow = function (y) {
277
320
  self.gotoCell(0, y);
278
321
  };
322
+ /**
323
+ * Add a button into the cell.
324
+ * @memberof canvasDatagrid
325
+ * @name addButton
326
+ * @method
327
+ * @param {number} columnIndex The column index of the cell to to add a button.
328
+ * @param {number} rowIndex The row index of the cell to to add a button.
329
+ * @param {object} offset Offset how far go away from cell.
330
+ * @param {object} items a list of items to add into button menu.
331
+ * @param {string} imgSrc icon path to add into button.
332
+ */
333
+ self.addButton = function (columnIndex, rowIndex, offset, items, imgSrc) {
334
+ var cells = self.visibleCells.filter(function (c) {
335
+ return c.sortColumnIndex === columnIndex && c.sortRowIndex === rowIndex;
336
+ });
337
+
338
+ self.attachButton(
339
+ {
340
+ top: cells[0].y + cells[0].height + offset.y,
341
+ left: cells[0].x + cells[0].width + offset.x,
342
+ },
343
+ items,
344
+ imgSrc,
345
+ );
346
+ };
347
+
348
+ /**
349
+ * Expand/Collapse CellTree.
350
+ * @memberof canvasDatagrid
351
+ * @name toggleCellCollapseTree
352
+ * @method
353
+ * @param {array} treeData The array of cellTree to expand or collapse.
354
+ */
355
+
356
+ self.toggleCellCollapseTree = function (treeData) {
357
+ for (let type in treeData) {
358
+ for (let t of treeData[type])
359
+ if (t.length > 0) self.toggleCollapseTree(t[0], t[1], type);
360
+ }
361
+ self.draw();
362
+ };
363
+
364
+ /**
365
+ * Expand/Collapse CellTree.
366
+ * @memberof canvasDatagrid
367
+ * @name expandCollapseCellTree
368
+ * @method
369
+ * @param {array} treeData The array of cellTree to expand or collapse.
370
+ */
371
+
372
+ self.expandCollapseCellTree = function (treeData) {
373
+ for (let type in treeData) {
374
+ for (let t of treeData[type])
375
+ if (t.length > 0) self.cellTreeExpandCollapse(t[0], t[1], type);
376
+ }
377
+ self.draw();
378
+ };
379
+
279
380
  /**
280
381
  * Scrolls the cell at cell x, row y into view if it is not already.
281
382
  * @memberof canvasDatagrid
@@ -287,18 +388,25 @@ export default function (self) {
287
388
  * @param {number} [offsetY=0] Percentage offset the cell should be from the top edge (not including headers). The default is 0, meaning the cell will appear at the top edge. Valid values are 0 through 1. 1 = Bottom, 0 = Top, 0.5 = Center.
288
389
  */
289
390
  self.scrollIntoView = function (x, y, offsetX, offsetY) {
290
- if (
291
- self.visibleCells.filter(function (cell) {
292
- return (
293
- (cell.rowIndex === y || y === undefined) &&
294
- (cell.columnIndex === x || x === undefined) &&
295
- cell.x > 0 &&
296
- cell.y > 0 &&
297
- cell.x + cell.width < self.width &&
298
- cell.y + cell.height < self.height
299
- );
300
- }).length === 0
301
- ) {
391
+ const matched = self.visibleCells.filter(function (cell) {
392
+ return (
393
+ (cell.rowIndex === y || y === undefined) &&
394
+ (cell.columnIndex === x || x === undefined) &&
395
+ cell.x > 0 &&
396
+ cell.y > 0 &&
397
+ cell.x + cell.width < self.width &&
398
+ cell.y + cell.height < self.height
399
+ );
400
+ });
401
+ if (matched.length === 1 && x !== undefined && y !== undefined) {
402
+ // goto specific cell and its part be hidden by header
403
+ if (
404
+ matched[0].x < self.getRowHeaderCellWidth() ||
405
+ matched[0].y < self.getColumnHeaderCellHeight()
406
+ )
407
+ matched.length = 0;
408
+ }
409
+ if (matched.length === 0) {
302
410
  self.gotoCell(x, y, offsetX, offsetY);
303
411
  }
304
412
  };
@@ -311,6 +419,7 @@ export default function (self) {
311
419
  * @param {number} y The row index of the cell to set active.
312
420
  */
313
421
  self.setActiveCell = function (x, y) {
422
+ if (typeof x === 'undefined') return;
314
423
  if (x < 0) {
315
424
  x = 0;
316
425
  }
@@ -321,213 +430,40 @@ export default function (self) {
321
430
  rowIndex: y,
322
431
  columnIndex: x,
323
432
  };
324
- };
325
- /**
326
- * Removes the selection.
327
- * @memberof canvasDatagrid
328
- * @name selectNone
329
- * @param {boolean} dontDraw Suppress the draw method after the selection change.
330
- * @method
331
- */
332
- self.selectNone = function (dontDraw) {
333
- self.selections = [];
334
- self.dispatchEvent('selectionchanged', {
335
- selectedData: self.getSelectedData(),
336
- selections: self.selections,
337
- selectionBounds: self.selectionBounds,
338
- });
339
- if (dontDraw) {
340
- return;
341
- }
342
- self.draw();
343
- };
344
- /**
345
- * Selects every visible cell.
346
- * @memberof canvasDatagrid
347
- * @name selectAll
348
- * @param {boolean} dontDraw Suppress the draw method after the selection change.
349
- * @method
350
- */
351
- self.selectAll = function (dontDraw) {
352
- self.selectArea({
353
- top: 0,
354
- left: -1,
355
- right: self.getSchema().length - 1,
356
- bottom: self.data.length - 1,
357
- });
358
- if (dontDraw) {
359
- return;
360
- }
361
- self.draw();
362
- };
363
- /**
364
- * Returns true if the selected columnIndex is selected on every row.
365
- * @memberof canvasDatagrid
366
- * @name isColumnSelected
367
- * @method
368
- * @param {number} columnIndex The column index to check.
369
- */
370
- self.isColumnSelected = function (columnIndex) {
371
- var colIsSelected = true;
372
- self.data.forEach(function (row, rowIndex) {
373
- if (
374
- !self.selections[rowIndex] ||
375
- self.selections[rowIndex].indexOf(self.orders.columns[columnIndex]) ===
376
- -1
377
- ) {
378
- colIsSelected = false;
379
- }
380
- });
381
- return colIsSelected;
382
- };
383
- /**
384
- * Runs the defined method on each selected cell.
385
- * @memberof canvasDatagrid
386
- * @name forEachSelectedCell
387
- * @method
388
- * @param {number} fn The function to execute. The signature of the function is: (data, rowIndex, columnName).
389
- * @param {number} expandToRow When true the data in the array is expanded to the entire row.
390
- */
391
- self.forEachSelectedCell = function (fn, expandToRow) {
392
- var d = [],
393
- s = expandToRow ? self.getSchema() : self.getVisibleSchema(),
394
- l = self.data.length;
395
- self.selections.forEach(function (row, index) {
396
- if (index === l) {
397
- return;
398
- }
399
- if (row.length === 0) {
400
- d[index] = null;
401
- return;
402
- }
403
- d[index] = {};
404
- row.forEach(function (col) {
405
- if (col === -1 || !s[col]) {
406
- return;
407
- }
408
- fn(self.data, index, s[col].name);
409
- });
433
+ self.dispatchEvent('activecellchanged', {
434
+ cell: self.activeCell,
410
435
  });
411
436
  };
437
+
412
438
  /**
413
- * Selects a column.
414
- * @memberof canvasDatagrid
415
- * @name selectColumn
416
- * @method
417
- * @param {number} columnIndex The column index to select.
418
- * @param {boolean} toggleSelectMode When true, behaves as if you were holding control/command when you clicked the column.
419
- * @param {boolean} shift When true, behaves as if you were holding shift when you clicked the column.
420
- * @param {boolean} supressSelectionchangedEvent When true, prevents the selectionchanged event from firing.
439
+ * Dispatches `datachanged` with the source of the change so listeners can
440
+ * react to edits, pastes, deletions and row/column changes, not only to the
441
+ * data setter (#250, #512).
442
+ * @param {string} source One of data, edit, paste, cut, delete, fill, addrow, insertrow, deleterow, addcolumn, insertcolumn, deletecolumn.
443
+ * @param {object} [details] Extra event details.
421
444
  */
422
- self.selectColumn = function (columnIndex, ctrl, shift, supressEvent) {
423
- var s, e, x;
424
- function addCol(i) {
425
- self.data.forEach(function (row, rowIndex) {
426
- self.selections[rowIndex] = self.selections[rowIndex] || [];
427
- if (self.selections[rowIndex].indexOf(i) === -1) {
428
- self.selections[rowIndex].push(i);
429
- }
430
- });
431
- }
432
- function removeCol(i) {
433
- self.data.forEach(function (row, rowIndex) {
434
- self.selections[rowIndex] = self.selections[rowIndex] || [];
435
- if (self.selections[rowIndex].indexOf(i) !== -1) {
436
- self.selections[rowIndex].splice(
437
- self.selections[rowIndex].indexOf(i),
438
- 1,
439
- );
440
- }
441
- });
442
- }
443
- if (shift) {
444
- if (!self.activeCell) {
445
- return;
446
- }
447
- s = Math.min(self.activeCell.columnIndex, columnIndex);
448
- e = Math.max(self.activeCell.columnIndex, columnIndex);
449
- for (x = s; e > x; x += 1) {
450
- addCol(x);
451
- }
452
- }
453
- if (!ctrl && !shift) {
454
- self.selections = [];
455
- self.activeCell.columnIndex = columnIndex;
456
- self.activeCell.rowIndex = self.scrollIndexTop;
457
- }
458
- if (ctrl && self.isColumnSelected(columnIndex)) {
459
- removeCol(columnIndex);
460
- } else {
461
- addCol(columnIndex);
462
- }
463
- if (supressEvent) {
464
- return;
465
- }
466
- self.dispatchEvent('selectionchanged', {
467
- selectedData: self.getSelectedData(),
468
- selections: self.selections,
469
- selectionBounds: self.getSelectionBounds(),
470
- });
445
+ self.dispatchDataChanged = function (source, details) {
446
+ self.dispatchEvent(
447
+ 'datachanged',
448
+ Object.assign({ data: self.originalData, source: source }, details || {}),
449
+ );
471
450
  };
472
451
  /**
473
- * Selects a row.
474
- * @memberof canvasDatagrid
475
- * @name selectRow
476
- * @method
477
- * @param {number} rowIndex The row index to select.
478
- * @param {boolean} ctrl When true, behaves as if you were holding control/command when you clicked the row.
479
- * @param {boolean} shift When true, behaves as if you were holding shift when you clicked the row.
480
- * @param {boolean} supressSelectionchangedEvent When true, prevents the selectionchanged event from firing.
452
+ * Dispatches the `resizerow` event with one consistent payload (#503).
453
+ * Canonical keys: `rowIndex`, `height`, `width`, `x`, `y`, `draggingItem`,
454
+ * `NativeEvent`. `cellHeight` and `row` are kept as deprecated aliases of
455
+ * `height` for listeners written against older versions.
456
+ * @param {object} details Event details; `height` is required.
457
+ * @returns {boolean} true when a listener called preventDefault.
481
458
  */
482
- self.selectRow = function (rowIndex, ctrl, shift, supressEvent) {
483
- var x,
484
- st,
485
- en,
486
- s = self.getVisibleSchema();
487
- function de() {
488
- if (supressEvent) {
489
- return;
490
- }
491
- self.dispatchEvent('selectionchanged', {
492
- selectedData: self.getSelectedData(),
493
- selections: self.selections,
494
- selectionBounds: self.selectionBounds,
495
- });
496
- }
497
- function addRow(ri) {
498
- self.selections[ri] = [];
499
- self.selections[ri].push(-1);
500
- s.forEach(function (col, index) {
501
- self.selections[ri].push(self.orders.columns.indexOf(col.index));
502
- });
503
- }
504
- if (self.dragAddToSelection === false || self.dragObject === undefined) {
505
- if (
506
- self.selections[rowIndex] &&
507
- self.selections[rowIndex].length - 1 === s.length
508
- ) {
509
- if (ctrl) {
510
- self.selections[rowIndex] = [];
511
- de();
512
- return;
513
- }
514
- }
459
+ self.dispatchResizeRow = function (details) {
460
+ var ev = Object.assign({}, details);
461
+ if (ev.y === undefined) {
462
+ ev.y = ev.height;
515
463
  }
516
- if (self.dragAddToSelection === true || self.dragObject === undefined) {
517
- if (shift && self.dragObject === undefined) {
518
- if (!self.activeCell) {
519
- return;
520
- }
521
- st = Math.min(self.activeCell.rowIndex, rowIndex);
522
- en = Math.max(self.activeCell.rowIndex, rowIndex);
523
- for (x = st; en >= x; x += 1) {
524
- addRow(x);
525
- }
526
- } else {
527
- addRow(rowIndex);
528
- }
529
- }
530
- de();
464
+ ev.cellHeight = ev.height;
465
+ ev.row = ev.height;
466
+ return self.dispatchEvent('resizerow', ev);
531
467
  };
532
468
  /**
533
469
  * Collapse a tree grid by row index.
@@ -539,7 +475,7 @@ export default function (self) {
539
475
  self.collapseTree = function (rowIndex) {
540
476
  self.dispatchEvent('collapsetree', {
541
477
  childGrid: self.childGrids[rowIndex],
542
- data: self.data[rowIndex],
478
+ data: self.viewData[rowIndex],
543
479
  rowIndex: rowIndex,
544
480
  });
545
481
  self.openChildren[rowIndex].blur();
@@ -547,8 +483,9 @@ export default function (self) {
547
483
  delete self.openChildren[rowIndex];
548
484
  delete self.sizes.trees[rowIndex];
549
485
  delete self.childGrids[rowIndex];
550
- self.dispatchEvent('resizerow', {
551
- cellHeight: self.style.cellHeight,
486
+ self.dispatchResizeRow({
487
+ height: self.style.cellHeight,
488
+ rowIndex: rowIndex,
552
489
  });
553
490
  self.resize(true);
554
491
  self.draw(true);
@@ -561,7 +498,9 @@ export default function (self) {
561
498
  * @param {number} index The index of the row to expand.
562
499
  */
563
500
  self.expandTree = function (rowIndex) {
564
- var trArgs = self.args.treeGridAttributes || {},
501
+ // copy so the caller's object is not mutated; `self.args` is empty in
502
+ // web-component mode, the value lives on the instance (#444)
503
+ var trArgs = Object.assign({}, self.treeGridAttributes || {}),
565
504
  columnHeaderCellHeight = self.getColumnHeaderCellHeight(),
566
505
  rowHeaderCellWidth =
567
506
  self.sizes.columns.cornerCell || self.style.rowHeaderCellWidth,
@@ -584,7 +523,7 @@ export default function (self) {
584
523
  offsetParent: self.intf.parentNode,
585
524
  parentNode: self.intf.parentNode,
586
525
  style: 'tree',
587
- data: self.data[rowIndex],
526
+ data: self.viewData[rowIndex],
588
527
  };
589
528
  treeGrid = self.createGrid(trArgs);
590
529
  self.childGrids[rowIndex] = treeGrid;
@@ -593,12 +532,15 @@ export default function (self) {
593
532
  treeGrid.visible = true;
594
533
  self.dispatchEvent('expandtree', {
595
534
  treeGrid: treeGrid,
596
- data: self.data[rowIndex],
535
+ data: self.viewData[rowIndex],
597
536
  rowIndex: rowIndex,
598
537
  });
599
538
  self.openChildren[rowIndex] = treeGrid;
600
539
  self.sizes.trees[rowIndex] = h;
601
- self.dispatchEvent('resizerow', { height: self.style.cellHeight });
540
+ self.dispatchResizeRow({
541
+ height: self.style.cellHeight,
542
+ rowIndex: rowIndex,
543
+ });
602
544
  self.resize(true);
603
545
  };
604
546
  /**
@@ -633,6 +575,101 @@ export default function (self) {
633
575
  }
634
576
  }
635
577
  };
578
+ /**
579
+ * Hide column/columns
580
+ * @memberof canvasDatagrid
581
+ * @name hideColumns
582
+ * @method
583
+ * @param {number} beginColumnOrderIndex The begin column order index
584
+ * @param {number} [endColumnOrderIndex] The end column order index
585
+ */
586
+ self.hideColumns = function (beginColumnOrderIndex, endColumnOrderIndex) {
587
+ const schema = self.getSchema();
588
+ const orders = self.orders.columns;
589
+ const hiddenColumns = [];
590
+ if (typeof endColumnOrderIndex !== 'number')
591
+ endColumnOrderIndex = beginColumnOrderIndex;
592
+ for (
593
+ let orderIndex = beginColumnOrderIndex;
594
+ orderIndex <= endColumnOrderIndex;
595
+ orderIndex++
596
+ ) {
597
+ const columnIndex = orders[orderIndex];
598
+ if (columnIndex >= 0 && !schema[columnIndex].hidden) {
599
+ hiddenColumns.push(columnIndex);
600
+ schema[columnIndex].hidden = true;
601
+
602
+ self.dispatchEvent('columnhide', {
603
+ columnIndex,
604
+ });
605
+ }
606
+ }
607
+
608
+ if (hiddenColumns.length > 0) {
609
+ self.setStorageData();
610
+ setTimeout(function () {
611
+ self.resize(true);
612
+ }, 10);
613
+ }
614
+
615
+ self.dispatchEvent('hidecolumns', {
616
+ hiddenColumns,
617
+ });
618
+ };
619
+ /**
620
+ * Unihde column/columns
621
+ * @memberof canvasDatagrid
622
+ * @name unhideColumns
623
+ * @method
624
+ * @param {number} beginColumnOrderIndex The begin column order index
625
+ * @param {number} [endColumnOrderIndex] The end column order index
626
+ */
627
+ self.unhideColumns = function (beginColumnOrderIndex, endColumnOrderIndex) {
628
+ const orders = self.orders.columns;
629
+ const schema = self.getSchema();
630
+
631
+ for (let i = beginColumnOrderIndex; i <= endColumnOrderIndex; i++) {
632
+ const columnIndex = orders[i];
633
+ const s = schema[columnIndex];
634
+
635
+ if (s && s.hidden) {
636
+ s.hidden = false;
637
+
638
+ self.dispatchEvent('columnunhide', {
639
+ columnIndex,
640
+ });
641
+ }
642
+ }
643
+ self.refresh();
644
+ };
645
+ /**
646
+ * Hide rows
647
+ * @memberof canvasDatagrid
648
+ * @name hideRows
649
+ * @method
650
+ * @param {number} beginRowIndex The begin row index
651
+ * @param {number} endRowIndex The end row index
652
+ */
653
+ self.hideRows = function (beginRowIndex, endRowIndex) {
654
+ if (
655
+ mergeHiddenRowRanges(self.hiddenRowRanges, [beginRowIndex, endRowIndex])
656
+ )
657
+ self.refresh();
658
+ };
659
+ /**
660
+ * Unhide rows
661
+ * @memberof canvasDatagrid
662
+ * @name unhideRows
663
+ * @method
664
+ * @param {number} beginRowIndex The begin row index
665
+ * @param {number} endRowIndex The end row index
666
+ */
667
+ self.unhideRows = function (beginRowIndex, endRowIndex) {
668
+ self.hiddenRowRanges = self.hiddenRowRanges.filter(
669
+ (range) => range[0] !== beginRowIndex || range[1] !== endRowIndex,
670
+ );
671
+ self.refresh();
672
+ };
636
673
  /**
637
674
  * Resizes a column to fit the longest value in the column. Call without a value to resize all columns.
638
675
  * Warning, can be slow on very large record sets (1m records ~3-5 seconds on an i7).
@@ -645,9 +682,39 @@ export default function (self) {
645
682
  if (!self.canvas) {
646
683
  return;
647
684
  }
648
- self.sizes.columns[
649
- name === 'cornerCell' ? -1 : self.getHeaderByName(name).index
650
- ] = Math.max(self.findColumnMaxTextLength(name), self.style.minColumnWidth);
685
+
686
+ if (name === undefined) {
687
+ // fit every column, as documented (#193)
688
+ self.getSchema().forEach(function (column) {
689
+ self.fitColumnToValues(column.name, true);
690
+ });
691
+ if (!internal) {
692
+ self.resize();
693
+ self.draw(true);
694
+ }
695
+ return;
696
+ }
697
+
698
+ const header = name === 'cornerCell' ? null : self.getHeaderByName(name);
699
+ if (name !== 'cornerCell' && !header) {
700
+ console.warn('fitColumnToValues: no column named %s', name);
701
+ return;
702
+ }
703
+ const columnIndex = name === 'cornerCell' ? -1 : header.index;
704
+
705
+ const newSize = Math.max(
706
+ self.findColumnMaxTextLength(name),
707
+ self.style.minColumnWidth,
708
+ );
709
+
710
+ self.sizes.columns[columnIndex] = newSize;
711
+
712
+ self.dispatchEvent('resizecolumn', {
713
+ x: newSize,
714
+ y: self.resizingStartingHeight,
715
+ draggingItem: self.currentCell,
716
+ });
717
+
651
718
  if (!internal) {
652
719
  self.resize();
653
720
  self.draw(true);
@@ -700,6 +767,10 @@ export default function (self) {
700
767
  sortFunction,
701
768
  dontSetStorageData,
702
769
  ) {
770
+ if (!self.attributes.allowSorting) {
771
+ self.refresh();
772
+ return;
773
+ }
703
774
  var f,
704
775
  c = self.getSchema().filter(function (col) {
705
776
  return col.name === columnName;
@@ -714,7 +785,7 @@ export default function (self) {
714
785
  }
715
786
  self.orderBy = columnName;
716
787
  self.orderDirection = direction;
717
- if (!self.data || self.data.length === 0) {
788
+ if (!self.viewData || self.viewData.length === 0) {
718
789
  return;
719
790
  }
720
791
  if (c.length === 0) {
@@ -732,41 +803,264 @@ export default function (self) {
732
803
  direction,
733
804
  typeof f === 'function' ? f : self.sorters.string,
734
805
  );
735
- self.orderings.sort();
806
+ self.refresh();
736
807
  self.dispatchEvent('sortcolumn', {
737
808
  name: columnName,
738
809
  direction: direction,
739
810
  });
740
- self.draw(true);
811
+
741
812
  if (dontSetStorageData) {
742
813
  return;
743
814
  }
744
815
  self.setStorageData();
745
816
  };
746
- self.isInGrid = function (e) {
747
- if (e.x < 0 || e.x > self.width || e.y < 0 || e.y > self.height) {
748
- return false;
817
+
818
+ /**
819
+ * Add grouping
820
+ * @param {'columns'|'rows'} groupFor
821
+ * @method
822
+ * @param {number} from
823
+ * @param {number} to
824
+ */
825
+ function addGroup(groupFor, from, to) {
826
+ let newRow = false;
827
+ const allGroups =
828
+ groupFor === 'rows' ? self.groupedRows : self.groupedColumns;
829
+ for (let i = allGroups.length - 1; i >= 0; i--) {
830
+ const groups = allGroups[i];
831
+ const min = groups[0].from,
832
+ max = groups[groups.length - 1].to;
833
+ if (from <= min && to >= max) {
834
+ if (from === min && to === max && groups.length === 1) return; // nothings happened
835
+ // new group wrap this row
836
+ continue;
837
+ }
838
+ for (let gi = 0; gi < groups.length; gi++) {
839
+ const g = groups[gi];
840
+ if (from > g.to) continue;
841
+ if (from >= g.from) {
842
+ if (to > g.to) {
843
+ if (from === g.from) {
844
+ allGroups.splice(i, 0, [{ from, to, collapsed: false }]);
845
+ self.refresh();
846
+ return;
847
+ }
848
+ throw new Error(`Can't group these ${groupFor}`);
849
+ }
850
+ if (to === g.to) {
851
+ if (from === g.from) return; // nothings happened
852
+ }
853
+ newRow = true;
854
+ break;
855
+ }
856
+ if (to >= g.from) {
857
+ if (to < g.to) throw new Error(`Can't group these ${groupFor}`);
858
+ allGroups.splice(i, 0, [{ from, to, collapsed: false }]);
859
+ } else {
860
+ groups.splice(gi, 0, { from, to, collapsed: false });
861
+ }
862
+ self.refresh();
863
+ return;
864
+ }
865
+ if (newRow) continue;
866
+ groups.push({ from, to, collapsed: false });
867
+ self.refresh();
868
+ return;
749
869
  }
750
- return true;
870
+ if (newRow) allGroups.push([{ from, to, collapsed: false }]);
871
+ else allGroups.unshift([{ from, to, collapsed: false }]);
872
+ self.refresh();
873
+ }
874
+ /**
875
+ * Remove grouping
876
+ * @param {Array<Array<{from:number,to:number,collapsed:boolean}>>} allGroups
877
+ * @method
878
+ * @param {number} from
879
+ * @param {number} to
880
+ */
881
+ function removeGroup(allGroups, from, to) {
882
+ for (let i = 0; i < allGroups.length; i++) {
883
+ const groups = allGroups[i];
884
+ for (let gi = 0; gi < groups.length; gi++) {
885
+ const group = groups[gi];
886
+ if (group.from === from && group.to === to) {
887
+ if (groups.length <= 1) allGroups.splice(i, 1);
888
+ else groups.splice(gi, 1);
889
+ self.refresh();
890
+ return;
891
+ }
892
+ }
893
+ }
894
+ }
895
+ /**
896
+ * Grouping columns
897
+ * @memberof canvasDatagrid
898
+ * @name groupColumns
899
+ * @method
900
+ * @param {number|string} firstColumnName Name of the first column to be grouped.
901
+ * @param {number|string} lastColumnName Name of the last column to be grouped.
902
+ */
903
+ self.groupColumns = function (firstColumnName, lastColumnName) {
904
+ /** @type {Array<{name: string,columnIndex:number}>} */
905
+ const schema = self.getSchema();
906
+ let firstOne, lastOne;
907
+ for (let i = 0; i < schema.length; i++) {
908
+ const it = schema[i];
909
+ if (firstOne && lastOne) break;
910
+ if (it.name === firstColumnName) {
911
+ firstOne = it;
912
+ continue;
913
+ }
914
+ if (it.name === lastColumnName) {
915
+ lastOne = it;
916
+ continue;
917
+ }
918
+ }
919
+ if (!firstOne) throw new Error(`No such column name for first column`);
920
+ if (!lastOne) throw new Error(`No such column name for last column`);
921
+ if (lastOne.columnIndex > firstOne.columnIndex !== true)
922
+ throw new Error(`Can't group these columns`);
923
+
924
+ const from = firstOne.columnIndex;
925
+ const to = lastOne.columnIndex;
926
+ const ev = { group: { type: 'columns', from, to } };
927
+ try {
928
+ addGroup('columns', from, to);
929
+ } catch (error) {
930
+ ev.error = error;
931
+ }
932
+ self.dispatchEvent('aftercreategroup', ev);
751
933
  };
752
934
  /**
753
- * Moves the current selection relative to the its current position. Note: this method does not move the selected data, just the selection itself.
935
+ * Grouping columns
754
936
  * @memberof canvasDatagrid
755
- * @name moveSelection
937
+ * @name groupRows
756
938
  * @method
757
- * @param {number} offsetX The number of columns to offset the selection.
758
- * @param {number} offsetY The number of rows to offset the selection.
939
+ * @param {number} rowIndexFrom The row index which is the beginning of the group
940
+ * @param {number} rowIndexTo The row index which is the end of the group
759
941
  */
760
- self.moveSelection = function (offsetX, offsetY) {
761
- var sel = [];
762
- self.selections.forEach(function (row, rowIndex) {
763
- sel[rowIndex + offsetY] = [];
764
- row.forEach(function (colIndex) {
765
- sel[rowIndex + offsetY].push(colIndex + offsetX);
766
- });
767
- });
768
- self.selections = sel;
942
+ self.groupRows = function (rowIndexFrom, rowIndexTo) {
943
+ if (!Number.isInteger(rowIndexFrom) || rowIndexFrom < 0)
944
+ throw new Error(`No such row for the beginning of the group`);
945
+
946
+ const dataLength = self.viewData.length;
947
+ if (
948
+ !Number.isInteger(rowIndexFrom) ||
949
+ rowIndexTo <= rowIndexFrom ||
950
+ rowIndexTo >= dataLength
951
+ )
952
+ throw new Error(`No such row for the end of the group`);
953
+ const ev = {
954
+ group: {
955
+ type: 'rows',
956
+ from: rowIndexFrom,
957
+ to: rowIndexTo,
958
+ },
959
+ };
960
+ try {
961
+ addGroup('rows', rowIndexFrom, rowIndexTo);
962
+ } catch (error) {
963
+ ev.error = error;
964
+ }
965
+ self.dispatchEvent('aftercreategroup', ev);
966
+ };
967
+ /**
968
+ * Remove grouping columns
969
+ * @memberof canvasDatagrid
970
+ * @name removeGroupColumns
971
+ * @method
972
+ * @param {number|string} firstColumnName Name of the first column to be grouped.
973
+ * @param {number|string} lastColumnName Name of the last column to be grouped.
974
+ */
975
+ self.removeGroupColumns = function (firstColumnName, lastColumnName) {
976
+ /** @type {Array<{name:string,columnIndex:number}>} */
977
+ const schema = self.getSchema();
978
+ let firstOne, lastOne;
979
+ for (let i = 0; i < schema.length; i++) {
980
+ const it = schema[i];
981
+ if (firstOne && lastOne) break;
982
+ if (it.name === firstColumnName) {
983
+ firstOne = it;
984
+ continue;
985
+ }
986
+ if (it.name === lastColumnName) {
987
+ lastOne = it;
988
+ continue;
989
+ }
990
+ }
991
+ if (!firstOne) throw new Error(`No such column name for first column`);
992
+ if (!lastOne) throw new Error(`No such column name for last column`);
993
+ const from = firstOne.columnIndex;
994
+ const to = lastOne.columnIndex;
995
+ removeGroup(self.groupedColumns, from, to);
996
+ };
997
+ /**
998
+ * Remove grouping columns
999
+ * @memberof canvasDatagrid
1000
+ * @name removeGroupRows
1001
+ * @method
1002
+ * @param {number} rowIndexFrom The row index which is the beginning of the group
1003
+ * @param {number} rowIndexTo The row index which is the end of the group
1004
+ */
1005
+ self.removeGroupRows = function (rowIndexFrom, rowIndexTo) {
1006
+ removeGroup(self.groupedRows, rowIndexFrom, rowIndexTo);
1007
+ };
1008
+ /**
1009
+ * Toggle(expand/collapsed) grouping columns
1010
+ * @memberof canvasDatagrid
1011
+ * @name toggleGroupColumns
1012
+ * @method
1013
+ * @param {number|string} firstColumnName Name of the first column to be grouped.
1014
+ * @param {number|string} lastColumnName Name of the last column to be grouped.
1015
+ */
1016
+ self.toggleGroupColumns = function (firstColumnName, lastColumnName) {
1017
+ /** @type {Array<{name:string,columnIndex:number}>} */
1018
+ const schema = self.getSchema();
1019
+ let firstOne, lastOne;
1020
+ for (let i = 0; i < schema.length; i++) {
1021
+ const it = schema[i];
1022
+ if (firstOne && lastOne) break;
1023
+ if (it.name === firstColumnName) {
1024
+ firstOne = it;
1025
+ continue;
1026
+ }
1027
+ if (it.name === lastColumnName) {
1028
+ lastOne = it;
1029
+ continue;
1030
+ }
1031
+ }
1032
+ if (!firstOne || !lastOne) return;
1033
+ const from = firstOne.columnIndex;
1034
+ const to = lastOne.columnIndex;
1035
+ if (self.toggleGroup({ type: 'c', from, to })) {
1036
+ self.disposeContextMenu();
1037
+ self.setStorageData();
1038
+ self.refresh();
1039
+ }
769
1040
  };
1041
+ /**
1042
+ * Toggle(expand/collapsed) grouping rows
1043
+ * @memberof canvasDatagrid
1044
+ * @name toggleGroupRows
1045
+ * @method
1046
+ * @param {number} rowIndexFrom The row index which is the beginning of the group
1047
+ * @param {number} rowIndexTo The row index which is the end of the group
1048
+ */
1049
+ self.toggleGroupRows = function (rowIndexFrom, rowIndexTo) {
1050
+ if (self.toggleGroup({ type: 'r', from: rowIndexFrom, to: rowIndexTo })) {
1051
+ self.disposeContextMenu();
1052
+ self.setStorageData();
1053
+ self.refresh();
1054
+ }
1055
+ };
1056
+
1057
+ self.isInGrid = function (e) {
1058
+ if (e.x < 0 || e.x > self.width || e.y < 0 || e.y > self.height) {
1059
+ return false;
1060
+ }
1061
+ return true;
1062
+ };
1063
+
770
1064
  /**
771
1065
  * Moves data in the provided selection to another position in the grid. Moving data off the edge of the schema (columns/x) will truncate data.
772
1066
  * @memberof canvasDatagrid
@@ -777,15 +1071,21 @@ export default function (self) {
777
1071
  * @param {number} y The row index to start inserting the selection at.
778
1072
  */
779
1073
  self.moveTo = function (sel, x, y) {
780
- var d = self.getSelectedData(),
781
- s = self.getVisibleSchema(),
782
- l = sel.length,
1074
+ // TODO: optimize this function,
1075
+ // we just transform the `sel` to obselete selection matrix
1076
+ sel = self.getObsoleteSelectionMatrix(sel);
1077
+
1078
+ // TODO: this function is not migrated done
1079
+ var selectedData = self.getSelectedData(),
1080
+ visibleSchema = self.getVisibleSchema(),
1081
+ selectionLength = sel.length,
783
1082
  xi,
784
1083
  maxRowLength = -Infinity,
785
1084
  minXi = Infinity,
786
1085
  yi = y - 1;
787
- sel.forEach(function (row, index) {
788
- if (index === l) {
1086
+
1087
+ sel.forEach(function (row, rowIndex) {
1088
+ if (rowIndex === selectionLength) {
789
1089
  return;
790
1090
  }
791
1091
  if (row.length === 0) {
@@ -796,15 +1096,21 @@ export default function (self) {
796
1096
  row.forEach(function (colIndex) {
797
1097
  // intentional redef of colIndex
798
1098
  colIndex = self.getVisibleColumnIndexOf(colIndex);
799
- if (!s[colIndex]) {
1099
+ if (!visibleSchema[colIndex]) {
800
1100
  return;
801
1101
  }
802
- if (!self.data[index]) {
803
- self.data[index] = {};
1102
+ // TODO:
1103
+ if (!self.data) {
1104
+ self.data = {};
1105
+ }
1106
+ if (!self.data[rowIndex]) {
1107
+ self.data[rowIndex] = {};
804
1108
  }
805
- self.data[index][s[colIndex].name] = null;
1109
+ // TODO:
1110
+ self.data[rowIndex][visibleSchema[colIndex].name] = null;
806
1111
  });
807
1112
  });
1113
+
808
1114
  sel.forEach(function (row, index) {
809
1115
  var lastSourceIndex;
810
1116
  yi += 1;
@@ -819,20 +1125,71 @@ export default function (self) {
819
1125
  lastSourceIndex = colIndex;
820
1126
  if (
821
1127
  colIndex === -1 ||
822
- !s[xi] ||
823
- !s[colIndex] ||
1128
+ !visibleSchema[xi] ||
1129
+ !visibleSchema[colIndex] ||
1130
+ // TODO:
824
1131
  self.data.length - 1 < yi ||
825
1132
  yi < 0
826
1133
  ) {
827
1134
  return;
828
1135
  }
1136
+ // TODO:
829
1137
  if (!self.data[yi]) {
830
1138
  self.data[yi] = {};
831
1139
  }
832
- self.data[yi][s[xi].name] = d[index][s[colIndex].name];
1140
+ // TODO:
1141
+ self.data[yi][visibleSchema[xi].name] =
1142
+ selectedData[index][visibleSchema[colIndex].name];
833
1143
  });
834
1144
  });
835
1145
  };
1146
+ /**
1147
+ * Get the column group info given column belongs to
1148
+ * @memberof canvasDatagrid
1149
+ * @name getGroupsColumnBelongsTo
1150
+ * @method
1151
+ * @param {number} columnIndex Column index.
1152
+ * @returns {Array<{from:number,to:number,collapsed:boolean}>}
1153
+ */
1154
+ self.getGroupsColumnBelongsTo = function (columnIndex) {
1155
+ if (!self.attributes.allowGroupingColumns) return [];
1156
+ const result = [];
1157
+ for (let i = 0; i < self.groupedColumns.length; i++) {
1158
+ const groups = self.groupedColumns[i];
1159
+ for (let j = 0; j < groups.length; j++) {
1160
+ const group = groups[j];
1161
+ if (columnIndex >= group.from && columnIndex <= group.to) {
1162
+ result.push(group);
1163
+ break;
1164
+ }
1165
+ }
1166
+ }
1167
+ return result;
1168
+ };
1169
+ /**
1170
+ * Get the row group info given row belongs to
1171
+ * @memberof canvasDatagrid
1172
+ * @name getGroupsRowBelongsTo
1173
+ * @method
1174
+ * @param {number} rowIndex Row index.
1175
+ * @returns {Array<{from:number,to:number,collapsed:boolean}>}
1176
+ */
1177
+ self.getGroupsRowBelongsTo = function (rowIndex) {
1178
+ if (!self.attributes.allowGroupingRows) return [];
1179
+ const result = [];
1180
+ for (let i = 0; i < self.groupedRows.length; i++) {
1181
+ const groups = self.groupedRows[i];
1182
+ for (let j = 0; j < groups.length; j++) {
1183
+ const group = groups[j];
1184
+ if (rowIndex >= group.from && rowIndex <= group.to) {
1185
+ result.push(group);
1186
+ break;
1187
+ }
1188
+ }
1189
+ }
1190
+ return result;
1191
+ };
1192
+
836
1193
  /**
837
1194
  * Checks if a given column is visible.
838
1195
  * @memberof canvasDatagrid
@@ -877,6 +1234,66 @@ export default function (self) {
877
1234
  return c.columnIndex === x && c.rowIndex === y;
878
1235
  })[0];
879
1236
  };
1237
+ /**
1238
+ * Get an unhide indicator at grid pixel coordinate x and y.
1239
+ * @memberof canvasDatagrid
1240
+ * @name getUnhideIndicator
1241
+ * @method
1242
+ * @param {number} x Number of pixels from the left.
1243
+ * @param {number} y Number of pixels from the top.
1244
+ */
1245
+ self.getUnhideIndicator = function (x, y) {
1246
+ const indicators = self.visibleUnhideIndicators;
1247
+ if (indicators.length <= 0) return;
1248
+ for (let i = 0; i < indicators.length; i++) {
1249
+ const indicator = indicators[i];
1250
+ if (
1251
+ x >= indicator.x &&
1252
+ y >= indicator.y &&
1253
+ x <= indicator.x2 &&
1254
+ y <= indicator.y2
1255
+ )
1256
+ return indicator;
1257
+ }
1258
+ };
1259
+ /**
1260
+ * Get a column group at grid pixel coordinate x and y.
1261
+ * @memberof canvasDatagrid
1262
+ * @name getColumnGroupAt
1263
+ * @method
1264
+ * @param {number} x Number of pixels from the left.
1265
+ * @param {number} y Number of pixels from the top.
1266
+ */
1267
+ self.getColumnGroupAt = function (x, y) {
1268
+ const groups = self.groupedColumns.length;
1269
+ if (groups <= 0) return;
1270
+ const yZero = self.getColumnGroupAreaHeight();
1271
+ if (y >= yZero) return;
1272
+ for (let i = 0; i < self.visibleGroups.length; i++) {
1273
+ const g = self.visibleGroups[i];
1274
+ if (g.type !== 'c') continue;
1275
+ if (x >= g.x && y >= g.y && x <= g.x2 && y <= g.y2) return g;
1276
+ }
1277
+ };
1278
+ /**
1279
+ * Get a row group at grid pixel coordinate x and y.
1280
+ * @memberof canvasDatagrid
1281
+ * @name getRowGroupAt
1282
+ * @method
1283
+ * @param {number} x Number of pixels from the left.
1284
+ * @param {number} y Number of pixels from the top.
1285
+ */
1286
+ self.getRowGroupAt = function (x, y) {
1287
+ const groups = self.groupedRows.length;
1288
+ if (groups <= 0) return;
1289
+ const xZero = self.getRowGroupAreaWidth();
1290
+ if (x >= xZero) return;
1291
+ for (let i = 0; i < self.visibleGroups.length; i++) {
1292
+ const g = self.visibleGroups[i];
1293
+ if (g.type !== 'r') continue;
1294
+ if (x >= g.x && y >= g.y && x <= g.x2 && y <= g.y2) return g;
1295
+ }
1296
+ };
880
1297
  /**
881
1298
  * Gets the cell at grid pixel coordinate x and y. Author's note. This function ties drawing and events together. This is a very complex function and is core to the component.
882
1299
  * @memberof canvasDatagrid
@@ -918,6 +1335,16 @@ export default function (self) {
918
1335
  if (!self.visibleCells) {
919
1336
  return;
920
1337
  }
1338
+ x -= self.getRowGroupAreaWidth();
1339
+ y -= self.getColumnGroupAreaHeight();
1340
+
1341
+ if (self.dragStartObject !== undefined) {
1342
+ if (x <= 0) x = 1;
1343
+ if (x >= self.width) x = self.width - 1;
1344
+ if (y <= 0) y = 1;
1345
+ if (y >= self.height) y = self.height - 1;
1346
+ }
1347
+
921
1348
  var border,
922
1349
  tsz = useTouchScrollZones ? self.attributes.touchScrollZone : 0,
923
1350
  moveMode = self.attributes.borderDragBehavior === 'move',
@@ -964,13 +1391,15 @@ export default function (self) {
964
1391
  entitiy.y + entitiy.height + self.style.cellBorderWidth > y
965
1392
  ) {
966
1393
  if (/frozen-row-marker/.test(cell.style)) {
1394
+ if (self.dragMode === 'frozen-column-marker') continue;
967
1395
  cell.dragContext = cell.style;
968
- cell.context = 'row-resize';
1396
+ cell.context = 'grab';
969
1397
  return cell;
970
1398
  }
971
1399
  if (/frozen-column-marker/.test(cell.style)) {
1400
+ if (self.dragMode === 'frozen-row-marker') continue;
972
1401
  cell.dragContext = cell.style;
973
- cell.context = 'col-resize';
1402
+ cell.context = 'grab';
974
1403
  return cell;
975
1404
  }
976
1405
  if (/selection-handle-/.test(cell.style)) {
@@ -1017,6 +1446,11 @@ export default function (self) {
1017
1446
  if (
1018
1447
  ['l', 'r'].indexOf(border) !== -1 &&
1019
1448
  (self.attributes.allowColumnResize || moveBorder) &&
1449
+ !(
1450
+ self.attributes.fillLastColumn &&
1451
+ border === 'r' &&
1452
+ cell.sortColumnIndex === self.getLastVisibleColumnIndex()
1453
+ ) &&
1020
1454
  ((self.attributes.allowColumnResizeFromCell && cell.isNormal) ||
1021
1455
  !cell.isNormal ||
1022
1456
  moveBorder) &&
@@ -1066,15 +1500,29 @@ export default function (self) {
1066
1500
  }
1067
1501
  }
1068
1502
  if (cell.style === 'columnHeaderCell') {
1069
- cell.context = 'cell';
1070
- cell.dragContext = 'column-reorder';
1503
+ if (
1504
+ (!self.isColumnSelected(cell.columnIndex) &&
1505
+ cell.x +
1506
+ cell.width -
1507
+ self.attributes.columnGrabZoneSize -
1508
+ self.style.cellBorderWidth >
1509
+ x) ||
1510
+ !self.attributes.allowColumnReordering
1511
+ ) {
1512
+ cell.dragContext = 'cell';
1513
+ cell.context = 'cell';
1514
+ } else {
1515
+ cell.context = self.cursorGrab;
1516
+ cell.dragContext = 'column-reorder';
1517
+ }
1071
1518
  return cell;
1072
1519
  }
1073
1520
  if (cell.style === 'rowHeaderCell') {
1074
1521
  if (
1075
- self.attributes.rowGrabZoneSize +
1076
- (cell.y - self.style.cellBorderWidth) <
1077
- y ||
1522
+ (!self.isRowSelected(cell.rowIndex) &&
1523
+ self.attributes.rowGrabZoneSize +
1524
+ (cell.y - self.style.cellBorderWidth) <
1525
+ y) ||
1078
1526
  !self.attributes.allowRowReordering
1079
1527
  ) {
1080
1528
  cell.dragContext = 'cell';
@@ -1111,32 +1559,6 @@ export default function (self) {
1111
1559
  isBackground: true,
1112
1560
  };
1113
1561
  };
1114
- /**
1115
- * Gets the bounds of current selection.
1116
- * @returns {rect} selection.
1117
- * @memberof canvasDatagrid
1118
- * @name getSelectionBounds
1119
- * @method
1120
- */
1121
- self.getSelectionBounds = function () {
1122
- var low = { x: Infinity, y: Infinity },
1123
- high = { x: -Infinity, y: -Infinity };
1124
- self.selections.forEach(function (row, rowIndex) {
1125
- var maxCol, minCol;
1126
- low.y = rowIndex < low.y ? rowIndex : low.y;
1127
- high.y = rowIndex > high.y ? rowIndex : high.y;
1128
- maxCol = Math.max.apply(null, row);
1129
- minCol = Math.min.apply(null, row);
1130
- low.x = minCol < low.x ? minCol : low.x;
1131
- high.x = maxCol > high.x ? maxCol : high.x;
1132
- });
1133
- return {
1134
- top: low.y,
1135
- left: low.x,
1136
- bottom: high.y,
1137
- right: high.x,
1138
- };
1139
- };
1140
1562
  /**
1141
1563
  * Returns an auto generated schema based on data structure.
1142
1564
  * @memberof canvasDatagrid
@@ -1146,7 +1568,7 @@ export default function (self) {
1146
1568
  * @returns {schema} schema A schema based on the first item in the data array.
1147
1569
  */
1148
1570
  self.getSchemaFromData = function (d) {
1149
- d = d || self.data;
1571
+ d = d || self.originalData;
1150
1572
  return Object.keys(d[0] || { ' ': '' }).map(function mapEachSchemaColumn(
1151
1573
  key,
1152
1574
  index,
@@ -1183,57 +1605,6 @@ export default function (self) {
1183
1605
  self.clearChangeLog = function () {
1184
1606
  self.changes = [];
1185
1607
  };
1186
- /**
1187
- * Selects an area of the grid.
1188
- * @memberof canvasDatagrid
1189
- * @name selectArea
1190
- * @method
1191
- * @param {rect} bounds A rect object representing the selected values.
1192
- */
1193
- self.selectArea = function (bounds, ctrl) {
1194
- self.selectionBounds = bounds || self.selectionBounds;
1195
- var ev,
1196
- x,
1197
- y,
1198
- s = self.getSchema();
1199
- if (!ctrl) {
1200
- self.selections = [];
1201
- }
1202
- if (
1203
- self.selectionBounds.top < -1 ||
1204
- self.selectionBounds.bottom > self.data.length ||
1205
- self.selectionBounds.left < -1 ||
1206
- self.selectionBounds.right > s.length
1207
- ) {
1208
- throw new Error('Impossible selection area');
1209
- }
1210
- for (
1211
- x = self.selectionBounds.top;
1212
- x <= self.selectionBounds.bottom;
1213
- x += 1
1214
- ) {
1215
- self.selections[x] = [];
1216
- for (
1217
- y = self.selectionBounds.left;
1218
- y <= self.selectionBounds.right;
1219
- y += 1
1220
- ) {
1221
- if (self.selections[x].indexOf(y) === -1) {
1222
- self.selections[x].push(y);
1223
- }
1224
- }
1225
- }
1226
- ev = {
1227
- selections: self.selections,
1228
- selectionBounds: self.selectionBounds,
1229
- };
1230
- Object.defineProperty(ev, 'selectedData', {
1231
- get: function () {
1232
- return self.getSelectedData();
1233
- },
1234
- });
1235
- self.dispatchEvent('selectionchanged', ev);
1236
- };
1237
1608
  /**
1238
1609
  * Returns the maximum text width for a given column by column name.
1239
1610
  * @memberof canvasDatagrid
@@ -1248,7 +1619,9 @@ export default function (self) {
1248
1619
  self.ctx.font = self.style.rowHeaderCellFont;
1249
1620
  return (
1250
1621
  self.ctx.measureText(
1251
- (self.data.length + (self.attributes.showNewRow ? 1 : 0)).toString(),
1622
+ (
1623
+ self.viewData.length + (self.attributes.showNewRow ? 1 : 0)
1624
+ ).toString(),
1252
1625
  ).width +
1253
1626
  self.style.autosizePadding +
1254
1627
  self.style.autosizeHeaderCellPadding +
@@ -1269,12 +1642,13 @@ export default function (self) {
1269
1642
  self.ctx.font = self.style.columnHeaderCellFont;
1270
1643
  var t =
1271
1644
  self.ctx.measureText(col.title || col.name).width +
1272
- self.style.headerCellPaddingRight +
1273
- self.style.headerCellPaddingLeft;
1645
+ self.style.columnHeaderCellPaddingRight +
1646
+ self.style.columnHeaderCellPaddingLeft +
1647
+ self.style.cellAutoResizePadding;
1274
1648
  m = t > m ? t : m;
1275
1649
  formatter = self.formatters[col.type];
1276
1650
  });
1277
- self.data.forEach(function (row) {
1651
+ self.viewData.forEach(function (row) {
1278
1652
  var text = row[name];
1279
1653
  if (formatter) {
1280
1654
  text = formatter({ cell: { value: text } });
@@ -1313,16 +1687,46 @@ export default function (self) {
1313
1687
  /**
1314
1688
  * Gets the width of a column by index.
1315
1689
  * @memberof canvasDatagrid
1316
- * @name getColummnWidth
1690
+ * @name getColumnWidth
1317
1691
  * @method
1318
1692
  * @param {number} columnIndex The column index to lookup.
1319
1693
  */
1320
- self.getColummnWidth = function (columnIndex) {
1321
- return (
1694
+ self.getColumnWidth = function (columnIndex) {
1695
+ const column = self.getSchema()[columnIndex] || {};
1696
+ const configuredWidth =
1322
1697
  (self.sizes.columns[columnIndex] ||
1323
- self.getSchema()[columnIndex].width ||
1324
- self.style.cellWidth) * self.scale
1325
- );
1698
+ column.width ||
1699
+ self.style.cellWidth) * self.scale;
1700
+ if (
1701
+ self.autoLastColumnWidth &&
1702
+ self.autoLastColumnWidth.index === columnIndex
1703
+ ) {
1704
+ return Math.max(configuredWidth, self.autoLastColumnWidth.width);
1705
+ }
1706
+ return configuredWidth;
1707
+ };
1708
+ self.getLastVisibleColumnIndex = function () {
1709
+ const schema = self.getSchema();
1710
+ const collapsedColumnGroups = self.getCollapsedColumnGroups();
1711
+ const isColumnCollapsed = (columnIndex) =>
1712
+ collapsedColumnGroups.some(
1713
+ (group) => columnIndex >= group.from && columnIndex <= group.to,
1714
+ );
1715
+ for (
1716
+ let viewIndex = self.orders.columns.length - 1;
1717
+ viewIndex >= 0;
1718
+ viewIndex -= 1
1719
+ ) {
1720
+ const schemaIndex = self.orders.columns[viewIndex];
1721
+ if (
1722
+ schema[schemaIndex] &&
1723
+ !schema[schemaIndex].hidden &&
1724
+ !isColumnCollapsed(viewIndex)
1725
+ ) {
1726
+ return schemaIndex;
1727
+ }
1728
+ }
1729
+ return -1;
1326
1730
  };
1327
1731
  self.formatters.string = function cellFormatterString(e) {
1328
1732
  return e.cell.value !== undefined ? e.cell.value : '';
@@ -1334,23 +1738,15 @@ export default function (self) {
1334
1738
  self.formatters.html = self.formatters.string;
1335
1739
  self.sorters.string = function (columnName, direction) {
1336
1740
  var asc = direction === 'asc';
1741
+ // Coerce every value to a string so numbers, booleans and blanks sort
1742
+ // consistently (blanks first ascending, last descending) (#256, #548).
1743
+ function toComparable(value) {
1744
+ return value === undefined || value === null ? '' : String(value);
1745
+ }
1337
1746
  return function (a, b) {
1338
- if (a[columnName] === undefined || a[columnName] === null) {
1339
- return 1;
1340
- }
1341
- if (b[columnName] === undefined || b[columnName] === null) {
1342
- return 0;
1343
- }
1344
- if (asc) {
1345
- if (!a[columnName].localeCompare) {
1346
- return 1;
1347
- }
1348
- return a[columnName].localeCompare(b[columnName]);
1349
- }
1350
- if (!b[columnName].localeCompare) {
1351
- return 1;
1352
- }
1353
- return b[columnName].localeCompare(a[columnName]);
1747
+ const aValue = toComparable(a[columnName]);
1748
+ const bValue = toComparable(b[columnName]);
1749
+ return asc ? aValue.localeCompare(bValue) : bValue.localeCompare(aValue);
1354
1750
  };
1355
1751
  };
1356
1752
  self.sorters.number = function (columnName, direction) {