canvas-datagrid 0.4.5 → 0.4.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +24 -0
- package/dist/canvas-datagrid.debug.js +3052 -1520
- package/dist/canvas-datagrid.debug.map +1 -1
- package/dist/canvas-datagrid.js +1 -1
- package/dist/canvas-datagrid.module.js +1 -1
- package/dist/canvas-datagrid.module.js.map +1 -1
- package/dist/types.d.ts +301 -75
- package/lib/component.js +1 -2
- package/lib/contextMenu.js +10 -102
- package/lib/defaults.js +3 -0
- package/lib/docs.js +13 -5
- package/lib/dom.js +2 -1
- package/lib/draw.js +75 -58
- package/lib/events/index.js +155 -231
- package/lib/intf.js +24 -219
- package/lib/main.js +2 -0
- package/lib/publicMethods.js +9 -378
- package/lib/selections/index.js +986 -0
- package/lib/selections/type.d.ts +38 -0
- package/lib/selections/util.js +1319 -0
- package/package.json +5 -5
|
@@ -0,0 +1,986 @@
|
|
|
1
|
+
/*jslint browser: true, unparam: true, todo: true*/
|
|
2
|
+
/*globals define: true, MutationObserver: false, requestAnimationFrame: false, performance: false, btoa: false, Event: false*/
|
|
3
|
+
/// <reference path="./type.d.ts" />
|
|
4
|
+
'use strict';
|
|
5
|
+
|
|
6
|
+
import { createHTMLString, createTextString } from '../events/util';
|
|
7
|
+
import {
|
|
8
|
+
addIntoSelections,
|
|
9
|
+
areSelectionsComplex,
|
|
10
|
+
areSelectionsNeat,
|
|
11
|
+
cleanupSelections,
|
|
12
|
+
cloneSelections,
|
|
13
|
+
getSelectedContiguousColumns,
|
|
14
|
+
getSelectedContiguousRows,
|
|
15
|
+
getSelectionBounds,
|
|
16
|
+
getSelectionStateFromCells,
|
|
17
|
+
isCellSelected,
|
|
18
|
+
isColumnSelected,
|
|
19
|
+
isRowSelected,
|
|
20
|
+
moveSelections,
|
|
21
|
+
normalizeSelection,
|
|
22
|
+
removeFromSelections,
|
|
23
|
+
SelectionType,
|
|
24
|
+
shrinkOrExpandSelections,
|
|
25
|
+
} from './util';
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Before reading the code of this module, you must know these things:
|
|
29
|
+
*
|
|
30
|
+
* You may see words like "obsolete", "old API", "compatibility" in the code later.
|
|
31
|
+
* Because this grid component has used a different selection mechanism in the past.
|
|
32
|
+
*
|
|
33
|
+
* The old mechanism use a 2D array to represent the selection state.
|
|
34
|
+
* For example: `[undefined x 10, [0, 5]]` indicates
|
|
35
|
+
* there are two cells are selected in current grid. they are located in row 11.
|
|
36
|
+
* and one in column 1, another in column 6.
|
|
37
|
+
* And `[undefined x 5, [-1, 0,1,2,3,4,5,6...], undefined x 2, [1]]` indicates
|
|
38
|
+
* the row 6 is selected and the cell that located in row 9 column 2 is selected.
|
|
39
|
+
*
|
|
40
|
+
* But this implementation has performance issue when the grid is shipping a large dataset.
|
|
41
|
+
* You can just imagine about what will happen when you select all cells in a grid with 1 million rows.
|
|
42
|
+
*
|
|
43
|
+
* So we create a new way to implement the selection.
|
|
44
|
+
* In this way, we use a selection list to represent the selection state.
|
|
45
|
+
* Each element in this selection list is a selection descriptor.
|
|
46
|
+
* Each descriptor represents an area whose selected or unselected by the user.
|
|
47
|
+
* And we have four types for the descriptor: UnselectedCells, Cells, Rows and Columns
|
|
48
|
+
*
|
|
49
|
+
* Because the old selection mechanism has been in used for a long time and
|
|
50
|
+
* we have many public APIs about it.
|
|
51
|
+
* We still need to keep these APIs working in the 0.x versions in the future.
|
|
52
|
+
* So we create some functions to make sure these APIs and context objects work like previous version.
|
|
53
|
+
* This is the reason why you may see there are some functions in this file
|
|
54
|
+
* be marked "for compatibility" without best practice.
|
|
55
|
+
*/
|
|
56
|
+
export default function (self) {
|
|
57
|
+
/**
|
|
58
|
+
* Here are explanation for the item of this array:
|
|
59
|
+
*
|
|
60
|
+
* The type of properties about the row in it is viewRowIndex (startRow, endRow).
|
|
61
|
+
* Here is an example of accessing the row data from the selection object:
|
|
62
|
+
* `const rowData = originalData[self.getBoundRowIndexFromViewRowIndex(selection.startRow)]`
|
|
63
|
+
*
|
|
64
|
+
* The type of properties about the column in it is columnOrderIndex (startColumn, endColumn)
|
|
65
|
+
* Here is an example of accessing thr column schema from the selection object:
|
|
66
|
+
* `const columnSchema = schema[self.orders.columns[selection.startColumn]]`
|
|
67
|
+
* @type {SelectionDescriptor[]}
|
|
68
|
+
*/
|
|
69
|
+
self.selections = [];
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* TODO support the cache
|
|
73
|
+
*/
|
|
74
|
+
self.selectionCache = [];
|
|
75
|
+
self.selectionCacheWidth = 0;
|
|
76
|
+
self.selectionCacheHeight = 0;
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* This method is used for adapting old API and it has performance issue for the large data set.
|
|
80
|
+
* We should keep this function and its related functions until version 1.x,
|
|
81
|
+
* because this action can cause incompatible API changes <https://semver.org/>
|
|
82
|
+
*
|
|
83
|
+
* Generate a selection 2 dimensional array from the selection list.
|
|
84
|
+
* Each row in this 2D array is a number array that indicates which columns are selected in this row.
|
|
85
|
+
* And -1 in the result means entire cells are selected in particular row/column
|
|
86
|
+
*
|
|
87
|
+
* @param {object[]} [otherSelections] The function create a 2d array based on this selection list
|
|
88
|
+
* if this parameter is provided. Otherwise, this function uses self.selections as the list input.
|
|
89
|
+
* @returns {number[][]}
|
|
90
|
+
*/
|
|
91
|
+
self.getObsoleteSelectionMatrix = (otherSelections) => {
|
|
92
|
+
const bounds = self.getSelectionBounds(true, otherSelections);
|
|
93
|
+
if (!bounds) return [];
|
|
94
|
+
const { top, bottom, left, right } = bounds;
|
|
95
|
+
const height = bottom - top + 1;
|
|
96
|
+
const width = right - left + 1;
|
|
97
|
+
const states = getSelectionStateFromCells(self.selections, {
|
|
98
|
+
startRow: top,
|
|
99
|
+
endRow: bottom,
|
|
100
|
+
startColumn: left,
|
|
101
|
+
endColumn: right,
|
|
102
|
+
});
|
|
103
|
+
if (states === false) return [];
|
|
104
|
+
|
|
105
|
+
const result = [];
|
|
106
|
+
for (let row = top, row2 = top + height; row < row2; row++) {
|
|
107
|
+
if (states === true) {
|
|
108
|
+
result[row] = new Array(width).fill(0).map((_, i) => left + i);
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const rowState = states[row - top];
|
|
113
|
+
if (!rowState) continue;
|
|
114
|
+
result[row] = [];
|
|
115
|
+
for (let col = left, col2 = left + width; col < col2; col++) {
|
|
116
|
+
if (rowState !== true && !rowState[col - left]) continue;
|
|
117
|
+
result[row].push(col);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
for (let i = 0; i < self.selections.length; i++) {
|
|
121
|
+
const selection = self.selections[i];
|
|
122
|
+
if (selection.type !== SelectionType.Rows) continue;
|
|
123
|
+
for (let row = selection.startRow; row <= selection.endRow; row++) {
|
|
124
|
+
if (!result[row]) {
|
|
125
|
+
result[row] = [-1];
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
if (result[row][0] === -1) continue;
|
|
129
|
+
result[row].unshift(-1);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return result;
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Return a tuple if the user selected contiguous columns, otherwise `null`.
|
|
137
|
+
* Info: Because the user may reorder the columns,
|
|
138
|
+
* the schemaIndex of the first item may be greater than the schemaIndex of the second item,
|
|
139
|
+
* but the columnIndex of the firs item must less than the columnIndex of the second item.
|
|
140
|
+
* @param {object[]} [schema] from `self.getSchema()`
|
|
141
|
+
* @returns {object[]} column schemas tuple (each schema has an additional field `schemaIndex`)
|
|
142
|
+
*/
|
|
143
|
+
self.getSelectedContiguousColumns = function (schema) {
|
|
144
|
+
if (!schema) schema = self.getSchema();
|
|
145
|
+
const columnOrderIndexes = getSelectedContiguousColumns(
|
|
146
|
+
self.selections,
|
|
147
|
+
false,
|
|
148
|
+
);
|
|
149
|
+
if (!Array.isArray(columnOrderIndexes)) return;
|
|
150
|
+
|
|
151
|
+
/** @type {number[]} orders[index] => columnIndex */
|
|
152
|
+
const orders = self.orders.columns;
|
|
153
|
+
return columnOrderIndexes.map((orderIndex) => {
|
|
154
|
+
const schemaIndex = orders[orderIndex];
|
|
155
|
+
const columnSchema = schema[schemaIndex];
|
|
156
|
+
return Object.assign({}, columnSchema, { orderIndex });
|
|
157
|
+
});
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* @param {boolean} [allowOnlyOneRow]
|
|
162
|
+
* @returns {number[]} a viewRowIndex tuple. It can contains one row index or two row indexes.
|
|
163
|
+
*/
|
|
164
|
+
self.getSelectedContiguousRows = function (allowOnlyOneRow) {
|
|
165
|
+
const viewRowIndexes = getSelectedContiguousRows(self.selections, false);
|
|
166
|
+
if (!Array.isArray(viewRowIndexes)) return;
|
|
167
|
+
if (!allowOnlyOneRow && viewRowIndexes[0] === viewRowIndexes[1]) return;
|
|
168
|
+
return viewRowIndexes;
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Check if current selection are copyable for the system clipbaord
|
|
173
|
+
* @returns {boolean}
|
|
174
|
+
*/
|
|
175
|
+
self.canSelectionsBeCopied = function () {
|
|
176
|
+
return self.selections.length > 0 && !areSelectionsComplex(self.selections);
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Return the height of the selection area.
|
|
181
|
+
* This function is used for rendering the overlay for the dnd(drag and drop)
|
|
182
|
+
* @returns {number} The height of the selection area
|
|
183
|
+
*/
|
|
184
|
+
self.getSelectedRowsHeight = function () {
|
|
185
|
+
const selected = getSelectedContiguousRows(self.selections, true);
|
|
186
|
+
if (!selected) return 0;
|
|
187
|
+
let height = 0;
|
|
188
|
+
for (let row = selected[0]; row <= selected[1]; row++)
|
|
189
|
+
height += self.getRowHeight(row);
|
|
190
|
+
return height;
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Return the width of the selection area.
|
|
195
|
+
* This function is used for rendering the overlay for the dnd(drag and drop)
|
|
196
|
+
* @returns {number} The width of the selection area
|
|
197
|
+
*/
|
|
198
|
+
self.getSelectedColumnsWidth = function () {
|
|
199
|
+
const selected = getSelectedContiguousColumns(self.selections, true);
|
|
200
|
+
if (!selected) return 0;
|
|
201
|
+
let width = 0;
|
|
202
|
+
for (let col = selected[0]; col <= selected[1]; col++)
|
|
203
|
+
width += self.getColumnWidth(self.orders.columns[col]);
|
|
204
|
+
return width;
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* @param {object} cell This method needs two properties in this parameter: `rowIndex`
|
|
209
|
+
* and `viewColumnIndex`
|
|
210
|
+
*/
|
|
211
|
+
self.isCellSelected = (cell) => {
|
|
212
|
+
return isCellSelected(self.selections, cell.rowIndex, cell.viewColumnIndex);
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Returns true if the selected columnIndex is selected on every row.
|
|
217
|
+
* @memberof canvasDatagrid
|
|
218
|
+
* @name isColumnSelected
|
|
219
|
+
* @method
|
|
220
|
+
* @param {number} columnIndex The column index to check.
|
|
221
|
+
*/
|
|
222
|
+
self.isColumnSelected = (viewColumnIndex) => {
|
|
223
|
+
if (typeof viewColumnIndex !== 'number') return false;
|
|
224
|
+
return isColumnSelected(self.selections, viewColumnIndex);
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Returns true if the selected rowIndex is selected on every column.
|
|
229
|
+
* @memberof canvasDatagrid
|
|
230
|
+
* @name isRowSelected
|
|
231
|
+
* @method
|
|
232
|
+
* @param {number} rowIndex The row index to check.
|
|
233
|
+
*/
|
|
234
|
+
self.isRowSelected = (rowIndex) => {
|
|
235
|
+
if (typeof rowIndex !== 'number') return false;
|
|
236
|
+
return isRowSelected(self.selections, rowIndex);
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Removes the selection.
|
|
241
|
+
* @param {boolean} triggerEvent true for trigger a event
|
|
242
|
+
*/
|
|
243
|
+
self.clearSelections = (triggerEvent) => {
|
|
244
|
+
self.selections = [];
|
|
245
|
+
if (triggerEvent) self.dispatchSelectionChangedEvent();
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Removes the selection.
|
|
250
|
+
* @memberof canvasDatagrid
|
|
251
|
+
* @name selectNone
|
|
252
|
+
* @param {boolean} dontDraw Suppress the draw method after the selection change.
|
|
253
|
+
* @method
|
|
254
|
+
*/
|
|
255
|
+
self.selectNone = (dontDraw) => {
|
|
256
|
+
self.clearSelections(true);
|
|
257
|
+
if (dontDraw) return;
|
|
258
|
+
self.draw();
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Selects every visible cell.
|
|
263
|
+
* @memberof canvasDatagrid
|
|
264
|
+
* @name selectAll
|
|
265
|
+
* @param {boolean} dontDraw Suppress the draw method after the selection change.
|
|
266
|
+
* @method
|
|
267
|
+
*/
|
|
268
|
+
self.selectAll = function (dontDraw) {
|
|
269
|
+
const changed = addIntoSelections(self.selections, {
|
|
270
|
+
type: SelectionType.Columns,
|
|
271
|
+
startColumn: 0,
|
|
272
|
+
endColumn: self.getSchema().length - 1,
|
|
273
|
+
});
|
|
274
|
+
if (!changed || dontDraw) return;
|
|
275
|
+
self.draw();
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* @param {object} cell This method needs two properties in this parameter: `rowIndex`
|
|
280
|
+
* and `viewColumnIndex`
|
|
281
|
+
* @param {boolean} [suppressEvent]
|
|
282
|
+
*/
|
|
283
|
+
self.unselectCell = (cell, suppressEvent) => {
|
|
284
|
+
const result = removeFromSelections(
|
|
285
|
+
self.selections,
|
|
286
|
+
normalizeSelection({
|
|
287
|
+
type: SelectionType.Cells,
|
|
288
|
+
startRow: cell.rowIndex,
|
|
289
|
+
startColumn: cell.viewColumnIndex,
|
|
290
|
+
}),
|
|
291
|
+
);
|
|
292
|
+
if (result && !suppressEvent) self.dispatchSelectionChangedEvent();
|
|
293
|
+
return result;
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* @param {number} startRow rowViewIndex
|
|
298
|
+
* @param {number} endRow rowViewIndex
|
|
299
|
+
* @param {boolean} suppressEvent When true, prevents the `selectionchanged` event from firing.
|
|
300
|
+
* @returns {boolean} It returns whether the selection changed
|
|
301
|
+
*/
|
|
302
|
+
self.unselectRows = (startRow, endRow, suppressEvent) => {
|
|
303
|
+
const result = removeFromSelections(
|
|
304
|
+
self.selections,
|
|
305
|
+
normalizeSelection({
|
|
306
|
+
type: SelectionType.Rows,
|
|
307
|
+
startRow: startRow,
|
|
308
|
+
endRow: endRow,
|
|
309
|
+
}),
|
|
310
|
+
);
|
|
311
|
+
if (result && !suppressEvent) self.dispatchSelectionChangedEvent();
|
|
312
|
+
return result;
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* @param {object} cell This method needs two properties in this parameter: `rowIndex`
|
|
317
|
+
* and `viewColumnIndex`
|
|
318
|
+
* @param {boolean} [suppressEvent]
|
|
319
|
+
*/
|
|
320
|
+
self.selectCell = (cell, suppressEvent) => {
|
|
321
|
+
const result = addIntoSelections(
|
|
322
|
+
self.selections,
|
|
323
|
+
normalizeSelection({
|
|
324
|
+
type: SelectionType.Cells,
|
|
325
|
+
startRow: cell.rowIndex,
|
|
326
|
+
startColumn: cell.viewColumnIndex,
|
|
327
|
+
}),
|
|
328
|
+
);
|
|
329
|
+
if (result && !suppressEvent) self.dispatchSelectionChangedEvent();
|
|
330
|
+
return result;
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Moves the current selection relative to the its current position. Note: this method does not move the selected data, just the selection itself.
|
|
335
|
+
* @memberof canvasDatagrid
|
|
336
|
+
* @name moveSelection
|
|
337
|
+
* @method
|
|
338
|
+
* @param {number} offsetX The number of columns to offset the selection.
|
|
339
|
+
* @param {number} offsetY The number of rows to offset the selection.
|
|
340
|
+
*/
|
|
341
|
+
self.moveSelection = function (offsetX, offsetY) {
|
|
342
|
+
moveSelections(self.selections, offsetX, offsetY);
|
|
343
|
+
};
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Dispatch a event named `selectionchanged` with context info
|
|
347
|
+
*/
|
|
348
|
+
self.dispatchSelectionChangedEvent = () => {
|
|
349
|
+
self.dispatchEvent('selectionchanged', self.getContextOfSelectionEvent());
|
|
350
|
+
};
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Return the event context for event `selectionchanged`
|
|
354
|
+
* @returns {object} the context for the event
|
|
355
|
+
*/
|
|
356
|
+
self.getContextOfSelectionEvent = () => {
|
|
357
|
+
const context = { selectionList: self.cloneSelections() };
|
|
358
|
+
//#region for API compatibility
|
|
359
|
+
Object.defineProperty(context, 'selections', {
|
|
360
|
+
get: function () {
|
|
361
|
+
return self.getObsoleteSelectionMatrix();
|
|
362
|
+
},
|
|
363
|
+
});
|
|
364
|
+
Object.defineProperty(context, 'selectedData', {
|
|
365
|
+
get: function () {
|
|
366
|
+
return self.getSelectedData();
|
|
367
|
+
},
|
|
368
|
+
});
|
|
369
|
+
Object.defineProperty(context, 'selectedCells', {
|
|
370
|
+
get: function () {
|
|
371
|
+
return self.getSelectedCells();
|
|
372
|
+
},
|
|
373
|
+
});
|
|
374
|
+
Object.defineProperty(context, 'selectionBounds', {
|
|
375
|
+
get: function () {
|
|
376
|
+
return self.getSelectionBounds();
|
|
377
|
+
},
|
|
378
|
+
});
|
|
379
|
+
//#endregion for API compatibility
|
|
380
|
+
return context;
|
|
381
|
+
};
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* Return a cloned selection list from current selection list
|
|
385
|
+
* @returns {object[]} A cloned selection list
|
|
386
|
+
*/
|
|
387
|
+
self.cloneSelections = () => {
|
|
388
|
+
return cloneSelections(self.selections);
|
|
389
|
+
};
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* Gets the bounds of current selection.
|
|
393
|
+
* @param {boolean} [sanitized] sanitize the bound object if the value of thie paramater is `true`
|
|
394
|
+
* @memberof canvasDatagrid
|
|
395
|
+
* @name getSelectionBounds
|
|
396
|
+
* @method
|
|
397
|
+
* @returns {rect} two situations:
|
|
398
|
+
* 1. The result is always a bound object if `sanitized` is not `true`.
|
|
399
|
+
* And the result is `{top: Infinity, left: Infinity, bottom: -Infinity, right: -Infinity}` if there haven't selections.
|
|
400
|
+
* This is used for keeping compatibility with existing APIs.
|
|
401
|
+
* 2. When the parameter `sanitized` is true. The result will be null if there haven't selections.
|
|
402
|
+
*/
|
|
403
|
+
self.getSelectionBounds = (sanitized, otherSelections) => {
|
|
404
|
+
const bounds = getSelectionBounds(otherSelections || self.selections);
|
|
405
|
+
if (sanitized) {
|
|
406
|
+
// nothing is selected
|
|
407
|
+
if (bounds.top > bounds.bottom || bounds.left > bounds.right) return null;
|
|
408
|
+
self.sanitizeSelectionBounds(bounds);
|
|
409
|
+
}
|
|
410
|
+
return bounds;
|
|
411
|
+
};
|
|
412
|
+
self.sanitizeSelectionBounds = (bounds) => {
|
|
413
|
+
if (!bounds) return bounds;
|
|
414
|
+
if (bounds.top < 0) bounds.top = 0;
|
|
415
|
+
if (bounds.left < 0) bounds.left = 0;
|
|
416
|
+
|
|
417
|
+
const viewDataLength = self.viewData.length;
|
|
418
|
+
if (bounds.bottom > viewDataLength) bounds.bottom = viewDataLength - 1;
|
|
419
|
+
|
|
420
|
+
const schemaLength = self.getSchema().length;
|
|
421
|
+
if (bounds.right > schemaLength) bounds.right = schemaLength - 1;
|
|
422
|
+
return bounds;
|
|
423
|
+
};
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* This function is migrated from the old API,
|
|
427
|
+
* It needs to be refactoring/deprecated to work with the large dataset.
|
|
428
|
+
* @param {boolean} [expandToRow]
|
|
429
|
+
* @returns {any[]}
|
|
430
|
+
*/
|
|
431
|
+
self.getSelectedData = (expandToRow) => {
|
|
432
|
+
const bounds = self.getSelectionBounds(true);
|
|
433
|
+
if (!bounds) return [];
|
|
434
|
+
|
|
435
|
+
const selectedData = [];
|
|
436
|
+
const { top, bottom, left, right } = bounds;
|
|
437
|
+
const height = bottom - top + 1;
|
|
438
|
+
const width = right - left + 1;
|
|
439
|
+
const states = getSelectionStateFromCells(self.selections, {
|
|
440
|
+
startRow: top,
|
|
441
|
+
endRow: bottom,
|
|
442
|
+
startColumn: left,
|
|
443
|
+
endColumn: right,
|
|
444
|
+
});
|
|
445
|
+
if (states === false) return [];
|
|
446
|
+
|
|
447
|
+
const orderedSchema = getSchemaOrderByViewIndex(left, width);
|
|
448
|
+
for (let row = top, row2 = top + height; row < row2; row++) {
|
|
449
|
+
const viewData = self.viewData[row] || {};
|
|
450
|
+
const rowData = {};
|
|
451
|
+
let hasData = false;
|
|
452
|
+
const rowState = states === true || states[row - top];
|
|
453
|
+
if (!rowState) continue;
|
|
454
|
+
|
|
455
|
+
for (let col = left, col2 = left + width; col < col2; col++) {
|
|
456
|
+
if (states !== true && !rowState[col - left]) continue;
|
|
457
|
+
const header = orderedSchema[col];
|
|
458
|
+
if (!header || header.hidden) continue;
|
|
459
|
+
hasData = true;
|
|
460
|
+
if (expandToRow) break;
|
|
461
|
+
rowData[header.name] = viewData[header.name];
|
|
462
|
+
}
|
|
463
|
+
if (hasData)
|
|
464
|
+
selectedData[row] = expandToRow ? Object.assign({}, viewData) : rowData;
|
|
465
|
+
}
|
|
466
|
+
return selectedData;
|
|
467
|
+
};
|
|
468
|
+
|
|
469
|
+
/**
|
|
470
|
+
* This function is migrated from the old API,
|
|
471
|
+
* @param {boolean} [expandToRow]
|
|
472
|
+
* @returns {any[]}
|
|
473
|
+
*/
|
|
474
|
+
self.getSelectedCells = (expandToRow) => {
|
|
475
|
+
const selectedCells = [];
|
|
476
|
+
const bounds = self.getSelectionBounds(true);
|
|
477
|
+
iterateSelectedCells(
|
|
478
|
+
(cell) => selectedCells.push(cell),
|
|
479
|
+
bounds,
|
|
480
|
+
expandToRow,
|
|
481
|
+
);
|
|
482
|
+
return selectedCells;
|
|
483
|
+
};
|
|
484
|
+
|
|
485
|
+
/**
|
|
486
|
+
* This function is migrated from the old API,
|
|
487
|
+
* @returns {any[]} affected cells
|
|
488
|
+
*/
|
|
489
|
+
self.clearSelectedCells = () => {
|
|
490
|
+
const affectedCells = [];
|
|
491
|
+
const bounds = self.getSelectionBounds(true);
|
|
492
|
+
iterateSelectedCells(
|
|
493
|
+
(cell) => {
|
|
494
|
+
affectedCells.push(cell);
|
|
495
|
+
self.viewData[cell.viewRowIndex][cell.columnName] = '';
|
|
496
|
+
},
|
|
497
|
+
bounds,
|
|
498
|
+
false,
|
|
499
|
+
);
|
|
500
|
+
return affectedCells;
|
|
501
|
+
};
|
|
502
|
+
|
|
503
|
+
/**
|
|
504
|
+
* Deletes currently selected data.
|
|
505
|
+
* @memberof canvasDatagrid
|
|
506
|
+
* @name deleteSelectedData
|
|
507
|
+
* @method
|
|
508
|
+
* @param {boolean} dontDraw Suppress the draw method after the selection change.
|
|
509
|
+
*/
|
|
510
|
+
self.deleteSelectedData = (dontDraw) => {
|
|
511
|
+
const affectedCells = self.clearSelectedCells();
|
|
512
|
+
const apiCompatibleCells = affectedCells.map((cell) => {
|
|
513
|
+
return [
|
|
514
|
+
cell.viewRowIndex,
|
|
515
|
+
cell.viewColumnIndex,
|
|
516
|
+
cell.boundRowIndex,
|
|
517
|
+
cell.boundColumnIndex,
|
|
518
|
+
];
|
|
519
|
+
});
|
|
520
|
+
self.dispatchEvent('afterdelete', {
|
|
521
|
+
cells: apiCompatibleCells,
|
|
522
|
+
});
|
|
523
|
+
if (dontDraw) return;
|
|
524
|
+
requestAnimationFrame(() => self.draw());
|
|
525
|
+
};
|
|
526
|
+
|
|
527
|
+
/**
|
|
528
|
+
* Runs the defined method on each selected cell.
|
|
529
|
+
* @memberof canvasDatagrid
|
|
530
|
+
* @name forEachSelectedCell
|
|
531
|
+
* @method
|
|
532
|
+
* @param {number} fn The function to execute. The signature of the function is: (data, rowIndex, columnName).
|
|
533
|
+
* @param {number} expandToRow When true the data in the array is expanded to the entire row.
|
|
534
|
+
*/
|
|
535
|
+
self.forEachSelectedCell = function (fn, expandToRow) {
|
|
536
|
+
const bounds = self.getSelectionBounds(true);
|
|
537
|
+
iterateSelectedCells(
|
|
538
|
+
(cell) => fn(self.viewData, cell.viewRowIndex, cell.columnName, cell),
|
|
539
|
+
bounds,
|
|
540
|
+
expandToRow,
|
|
541
|
+
);
|
|
542
|
+
};
|
|
543
|
+
|
|
544
|
+
/**
|
|
545
|
+
* @param {ClipboardInterface} clipboardData
|
|
546
|
+
*/
|
|
547
|
+
self.copySelectedCellsToClipboard = (clipboardData) => {
|
|
548
|
+
const isNeat = areSelectionsNeat(self.selections);
|
|
549
|
+
const selectedData = self.getSelectedData();
|
|
550
|
+
const data = selectedData.filter((row) => row != null);
|
|
551
|
+
|
|
552
|
+
if (data.length > 0) {
|
|
553
|
+
const textString = createTextString(data, isNeat);
|
|
554
|
+
const htmlString = createHTMLString(data, isNeat);
|
|
555
|
+
|
|
556
|
+
const copiedData = {
|
|
557
|
+
'text/plain': textString,
|
|
558
|
+
'text/html': htmlString,
|
|
559
|
+
'text/csv': textString,
|
|
560
|
+
'application/json': JSON.stringify(data),
|
|
561
|
+
};
|
|
562
|
+
for (const [mimeType, data] of Object.entries(copiedData)) {
|
|
563
|
+
clipboardData.setData(mimeType, data);
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
};
|
|
567
|
+
|
|
568
|
+
/**
|
|
569
|
+
* (This function is migrated from the old API)
|
|
570
|
+
* Modify the width of columns that contain selected cells to fit the content.
|
|
571
|
+
* @param {number} [width] Custom width for each column
|
|
572
|
+
*/
|
|
573
|
+
self.fitSelectedColumns = (width) => {
|
|
574
|
+
const bounds = self.getSelectionBounds(true);
|
|
575
|
+
if (!bounds) return;
|
|
576
|
+
|
|
577
|
+
const hasCustomWidth = typeof width === 'number' && width > 0;
|
|
578
|
+
// convert rectangle's height to only 1 row
|
|
579
|
+
bounds.bottom = bounds.top;
|
|
580
|
+
iterateSelectedCells(
|
|
581
|
+
(cell) => {
|
|
582
|
+
const { columnName, viewColumnIndex } = cell;
|
|
583
|
+
if (hasCustomWidth) {
|
|
584
|
+
self.sizes.columns[viewColumnIndex] = width;
|
|
585
|
+
self.dispatchEvent('resizecolumn', {
|
|
586
|
+
// TODO: Drop x, y from all 'resizecolumn' events and use height and width instead.
|
|
587
|
+
x: width,
|
|
588
|
+
y: self.resizingStartingHeight,
|
|
589
|
+
width: width,
|
|
590
|
+
height: self.resizingStartingHeight,
|
|
591
|
+
columnIndex: viewColumnIndex,
|
|
592
|
+
draggingItem: self.currentCell,
|
|
593
|
+
});
|
|
594
|
+
} else {
|
|
595
|
+
self.fitColumnToValues(columnName);
|
|
596
|
+
}
|
|
597
|
+
},
|
|
598
|
+
bounds,
|
|
599
|
+
false,
|
|
600
|
+
);
|
|
601
|
+
};
|
|
602
|
+
|
|
603
|
+
/**
|
|
604
|
+
* Set the height of the selected rows to the given value.
|
|
605
|
+
* @param {number} [height]
|
|
606
|
+
*/
|
|
607
|
+
self.fitSelectedRows = (height) => {
|
|
608
|
+
const bounds = self.getSelectionBounds(true);
|
|
609
|
+
if (!bounds) return;
|
|
610
|
+
|
|
611
|
+
for (let row = bounds.top; row <= bounds.bottom; row++) {
|
|
612
|
+
if (!self.isRowSelected(row)) {
|
|
613
|
+
continue;
|
|
614
|
+
}
|
|
615
|
+
self.sizes.rows[row] = height;
|
|
616
|
+
self.dispatchEvent('resizerow', {
|
|
617
|
+
width: self.resizingStartingWidth,
|
|
618
|
+
height: height,
|
|
619
|
+
rowIndex: row,
|
|
620
|
+
draggingItem: self.currentCell,
|
|
621
|
+
});
|
|
622
|
+
}
|
|
623
|
+
};
|
|
624
|
+
|
|
625
|
+
/**
|
|
626
|
+
* Selects a column.
|
|
627
|
+
* @memberof canvasDatagrid
|
|
628
|
+
* @name selectColumn
|
|
629
|
+
* @method
|
|
630
|
+
* @param {number} columnIndex The column index to select.
|
|
631
|
+
* @param {boolean} toggleSelectMode When true, behaves as if you were holding control/command when you clicked the column.
|
|
632
|
+
* @param {boolean} shift When true, behaves as if you were holding shift when you clicked the column.
|
|
633
|
+
* @param {boolean} suppressEvent When true, prevents the selectionchanged event from firing.
|
|
634
|
+
*/
|
|
635
|
+
self.selectColumn = (columnIndex, ctrl, shift, suppressEvent) => {
|
|
636
|
+
self.isMultiRowsSelected = false;
|
|
637
|
+
self.isMultiColumnsSelected = false;
|
|
638
|
+
function addCol(i) {
|
|
639
|
+
addIntoSelections(
|
|
640
|
+
self.selections,
|
|
641
|
+
normalizeSelection({
|
|
642
|
+
type: SelectionType.Columns,
|
|
643
|
+
startColumn: i,
|
|
644
|
+
}),
|
|
645
|
+
);
|
|
646
|
+
}
|
|
647
|
+
function removeCol(i) {
|
|
648
|
+
removeFromSelections(
|
|
649
|
+
self.selections,
|
|
650
|
+
normalizeSelection({
|
|
651
|
+
type: SelectionType.Columns,
|
|
652
|
+
startColumn: i,
|
|
653
|
+
}),
|
|
654
|
+
);
|
|
655
|
+
}
|
|
656
|
+
if (shift) {
|
|
657
|
+
if (!self.activeCell) {
|
|
658
|
+
return;
|
|
659
|
+
}
|
|
660
|
+
const s = Math.min(self.activeCell.columnIndex, columnIndex);
|
|
661
|
+
const e = Math.max(self.activeCell.columnIndex, columnIndex);
|
|
662
|
+
addIntoSelections(
|
|
663
|
+
self.selections,
|
|
664
|
+
normalizeSelection({
|
|
665
|
+
type: SelectionType.Columns,
|
|
666
|
+
startColumn: s,
|
|
667
|
+
endColumn: e - 1,
|
|
668
|
+
}),
|
|
669
|
+
);
|
|
670
|
+
if (s != e) self.isMultiColumnsSelected = true;
|
|
671
|
+
}
|
|
672
|
+
if (!ctrl && !shift) {
|
|
673
|
+
self.selections = [];
|
|
674
|
+
self.activeCell.columnIndex = columnIndex;
|
|
675
|
+
self.activeCell.rowIndex = self.scrollIndexTop;
|
|
676
|
+
}
|
|
677
|
+
if (ctrl && self.isColumnSelected(columnIndex)) {
|
|
678
|
+
removeCol(columnIndex);
|
|
679
|
+
} else {
|
|
680
|
+
addCol(columnIndex);
|
|
681
|
+
}
|
|
682
|
+
if (suppressEvent) {
|
|
683
|
+
return;
|
|
684
|
+
}
|
|
685
|
+
self.dispatchSelectionChangedEvent();
|
|
686
|
+
};
|
|
687
|
+
|
|
688
|
+
/**
|
|
689
|
+
* Selects a row.
|
|
690
|
+
* @memberof canvasDatagrid
|
|
691
|
+
* @name selectRow
|
|
692
|
+
* @method
|
|
693
|
+
* @param {number} rowIndex The row index to select.
|
|
694
|
+
* @param {boolean} ctrl When true, behaves as if you were holding control/command when you clicked the row.
|
|
695
|
+
* @param {boolean} shift When true, behaves as if you were holding shift when you clicked the row.
|
|
696
|
+
* @param {boolean} supressSelectionchangedEvent When true, prevents the selectionchanged event from firing.
|
|
697
|
+
*/
|
|
698
|
+
self.selectRow = (rowIndex, ctrl, shift, suppressEvent) => {
|
|
699
|
+
self.isMultiRowsSelected = false;
|
|
700
|
+
function de() {
|
|
701
|
+
if (suppressEvent) {
|
|
702
|
+
return;
|
|
703
|
+
}
|
|
704
|
+
self.dispatchSelectionChangedEvent();
|
|
705
|
+
}
|
|
706
|
+
function addRow(ri) {
|
|
707
|
+
addIntoSelections(
|
|
708
|
+
self.selections,
|
|
709
|
+
normalizeSelection({
|
|
710
|
+
type: SelectionType.Rows,
|
|
711
|
+
startRow: ri,
|
|
712
|
+
}),
|
|
713
|
+
);
|
|
714
|
+
}
|
|
715
|
+
if (self.dragAddToSelection === false || self.dragObject === undefined) {
|
|
716
|
+
if (isRowSelected(self.selections, rowIndex) && ctrl) {
|
|
717
|
+
removeFromSelections(
|
|
718
|
+
self.selections,
|
|
719
|
+
normalizeSelection({
|
|
720
|
+
type: SelectionType.Rows,
|
|
721
|
+
startRow: rowIndex,
|
|
722
|
+
}),
|
|
723
|
+
);
|
|
724
|
+
de();
|
|
725
|
+
return;
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
if (self.dragAddToSelection === true || self.dragObject === undefined) {
|
|
729
|
+
if (shift && self.dragObject === undefined) {
|
|
730
|
+
if (!self.activeCell) {
|
|
731
|
+
return;
|
|
732
|
+
}
|
|
733
|
+
const st = Math.min(self.activeCell.rowIndex, rowIndex);
|
|
734
|
+
const en = Math.max(self.activeCell.rowIndex, rowIndex);
|
|
735
|
+
addIntoSelections(
|
|
736
|
+
self.selections,
|
|
737
|
+
normalizeSelection({
|
|
738
|
+
type: SelectionType.Rows,
|
|
739
|
+
startRow: st,
|
|
740
|
+
endRow: en,
|
|
741
|
+
}),
|
|
742
|
+
);
|
|
743
|
+
} else {
|
|
744
|
+
addRow(rowIndex);
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
de();
|
|
748
|
+
};
|
|
749
|
+
|
|
750
|
+
/**
|
|
751
|
+
* @param {function} iterator signature: `(cell: any) => any`
|
|
752
|
+
* @param {object} bounds type: `{left:number;right:number;top:number;bottom:number}`
|
|
753
|
+
* @param {boolean} [expandToRow]
|
|
754
|
+
*/
|
|
755
|
+
function iterateSelectedCells(iterator, bounds, expandToRow) {
|
|
756
|
+
if (!bounds) return;
|
|
757
|
+
const { top, bottom, left, right } = bounds;
|
|
758
|
+
const height = bottom - top + 1;
|
|
759
|
+
const width = right - left + 1;
|
|
760
|
+
const states = getSelectionStateFromCells(self.selections, {
|
|
761
|
+
startRow: top,
|
|
762
|
+
endRow: bottom,
|
|
763
|
+
startColumn: left,
|
|
764
|
+
endColumn: right,
|
|
765
|
+
});
|
|
766
|
+
if (states === false) return;
|
|
767
|
+
|
|
768
|
+
const orderedSchema = getSchemaOrderByViewIndex(left, width);
|
|
769
|
+
for (let row = top, row2 = top + height; row < row2; row++) {
|
|
770
|
+
const viewData = self.viewData[row] || {};
|
|
771
|
+
const rowState = states === true || states[row - top];
|
|
772
|
+
if (!rowState) continue;
|
|
773
|
+
for (let col = left, col2 = left + width; col < col2; col++) {
|
|
774
|
+
if (rowState !== true && !rowState[col - left]) continue;
|
|
775
|
+
|
|
776
|
+
const header = orderedSchema[col];
|
|
777
|
+
if (!header || (header.hidden && !expandToRow)) continue;
|
|
778
|
+
|
|
779
|
+
const cell = {
|
|
780
|
+
value: viewData[header.name],
|
|
781
|
+
header,
|
|
782
|
+
columnName: header.name,
|
|
783
|
+
boundRowIndex: self.getBoundRowIndexFromViewRowIndex(row),
|
|
784
|
+
boundColumnIndex: self.orders.columns[col],
|
|
785
|
+
viewRowIndex: row,
|
|
786
|
+
viewColumnIndex: col,
|
|
787
|
+
};
|
|
788
|
+
iterator(cell);
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
/**
|
|
794
|
+
* Selects an area of the grid.
|
|
795
|
+
* @memberof canvasDatagrid
|
|
796
|
+
* @name selectArea
|
|
797
|
+
* @method
|
|
798
|
+
* @param {rect} [bounds] A rect object representing the selected values.
|
|
799
|
+
* @param {boolean} [ctrl]
|
|
800
|
+
*/
|
|
801
|
+
self.selectArea = function (bounds, ctrl) {
|
|
802
|
+
const schemaLength = self.getSchema().length;
|
|
803
|
+
if (bounds) {
|
|
804
|
+
if (bounds.right < 0) {
|
|
805
|
+
// patch for API compatibility
|
|
806
|
+
bounds.right = Math.max(schemaLength - 1, bounds.left, 0);
|
|
807
|
+
}
|
|
808
|
+
if (bounds.top > bounds.bottom || bounds.left > bounds.right)
|
|
809
|
+
throw new Error('Impossible selection area');
|
|
810
|
+
self.selectionBounds = self.sanitizeSelectionBounds(bounds);
|
|
811
|
+
}
|
|
812
|
+
let { top, bottom, left, right } = self.selectionBounds;
|
|
813
|
+
if (!ctrl) self.selections = [];
|
|
814
|
+
|
|
815
|
+
if (bottom >= self.viewData.length)
|
|
816
|
+
bottom = Math.max(self.viewData.length - 1, top);
|
|
817
|
+
if (top < -1 || left < -1 || right > schemaLength)
|
|
818
|
+
throw new Error('Impossible selection area');
|
|
819
|
+
|
|
820
|
+
/** @type {ContextForSelectionAction} */
|
|
821
|
+
const context = { rows: self.viewData.length, columns: schemaLength };
|
|
822
|
+
let changed = false;
|
|
823
|
+
|
|
824
|
+
// In original API, number -1 indicates selecting whole row or selecting while column.
|
|
825
|
+
if (top === -1) {
|
|
826
|
+
// select whole columns
|
|
827
|
+
const startColumn = Math.max(left, 0);
|
|
828
|
+
// left eq -1 means selecting all cells
|
|
829
|
+
const endRow =
|
|
830
|
+
left === -1 ? schemaLength - 1 : Math.min(right, schemaLength - 1);
|
|
831
|
+
changed = addIntoSelections(
|
|
832
|
+
self.selections,
|
|
833
|
+
normalizeSelection({
|
|
834
|
+
type: SelectionType.Columns,
|
|
835
|
+
startColumn: startColumn,
|
|
836
|
+
endColumn: endRow,
|
|
837
|
+
}),
|
|
838
|
+
context,
|
|
839
|
+
);
|
|
840
|
+
} else if (left === -1) {
|
|
841
|
+
// select whole rows
|
|
842
|
+
const startRow = Math.max(top, 0);
|
|
843
|
+
const endRow = Math.min(bottom, self.viewData.length - 1);
|
|
844
|
+
changed = addIntoSelections(
|
|
845
|
+
self.selections,
|
|
846
|
+
normalizeSelection({
|
|
847
|
+
type: SelectionType.Rows,
|
|
848
|
+
startRow: startRow,
|
|
849
|
+
endRow: endRow,
|
|
850
|
+
}),
|
|
851
|
+
context,
|
|
852
|
+
);
|
|
853
|
+
} else {
|
|
854
|
+
changed = addIntoSelections(
|
|
855
|
+
self.selections,
|
|
856
|
+
normalizeSelection({
|
|
857
|
+
type: SelectionType.Cells,
|
|
858
|
+
startRow: top,
|
|
859
|
+
endRow: bottom,
|
|
860
|
+
startColumn: left,
|
|
861
|
+
endColumn: right,
|
|
862
|
+
}),
|
|
863
|
+
context,
|
|
864
|
+
);
|
|
865
|
+
}
|
|
866
|
+
if (changed) self.dispatchSelectionChangedEvent();
|
|
867
|
+
};
|
|
868
|
+
|
|
869
|
+
/**
|
|
870
|
+
* This private method is using for make existing API about selection works.
|
|
871
|
+
* TODO: This method can be removed by a better way.
|
|
872
|
+
* @param {number} [rowIndex] 0 by default
|
|
873
|
+
* @returns {number[]|undefined}
|
|
874
|
+
*/
|
|
875
|
+
self.getRowSelectionStates = (rowIndex) => {
|
|
876
|
+
if (typeof rowIndex !== 'number' || rowIndex < 0) rowIndex = 0;
|
|
877
|
+
const startColumn = 0;
|
|
878
|
+
const endRow = self.getSchema().length - 1;
|
|
879
|
+
const width = endRow - startColumn + 1;
|
|
880
|
+
const state = getSelectionStateFromCells(self.selections, {
|
|
881
|
+
startRow: rowIndex,
|
|
882
|
+
startColumn: startColumn,
|
|
883
|
+
endRow: rowIndex,
|
|
884
|
+
endColumn: endRow,
|
|
885
|
+
});
|
|
886
|
+
if (state === false) return;
|
|
887
|
+
if (state === true) return new Array(width).fill(true).map((_, i) => i);
|
|
888
|
+
|
|
889
|
+
const firstRow = state[0];
|
|
890
|
+
if (!firstRow) return;
|
|
891
|
+
|
|
892
|
+
const result = [];
|
|
893
|
+
for (let col = 0; col < firstRow.length; col++)
|
|
894
|
+
if (firstRow[col]) result.push(col);
|
|
895
|
+
return result;
|
|
896
|
+
};
|
|
897
|
+
|
|
898
|
+
/**
|
|
899
|
+
* Replace current selection list with new selection list
|
|
900
|
+
* @param {object[]} newSelections New selection list
|
|
901
|
+
*/
|
|
902
|
+
self.replaceAllSelections = (newSelections) => {
|
|
903
|
+
cleanupSelections(newSelections);
|
|
904
|
+
self.selections = newSelections;
|
|
905
|
+
};
|
|
906
|
+
|
|
907
|
+
/**
|
|
908
|
+
* Select given columns and remove other existing selection area
|
|
909
|
+
* @param {number[]} viewIndexes A number array that represents column view indexes
|
|
910
|
+
*/
|
|
911
|
+
self.selectColumnViewIndexes = (viewIndexes) => {
|
|
912
|
+
const selections = viewIndexes.map((col) => ({
|
|
913
|
+
type: SelectionType.Columns,
|
|
914
|
+
startColumn: col,
|
|
915
|
+
endRow: col,
|
|
916
|
+
}));
|
|
917
|
+
self.replaceAllSelections(selections);
|
|
918
|
+
};
|
|
919
|
+
|
|
920
|
+
/**
|
|
921
|
+
* This method will be a bottleneck in the future if this component
|
|
922
|
+
* need to ship large dataset. But why I wrote this function:
|
|
923
|
+
* 1. it is used for compatibility with existing APIs
|
|
924
|
+
* 2. this component still has a bottleneck on the selection even there is not this function.
|
|
925
|
+
* @returns {number[]}
|
|
926
|
+
*/
|
|
927
|
+
self.getRowViewIndexesFromSelection = () => {
|
|
928
|
+
if (self.selections.length === 0) return [];
|
|
929
|
+
const bounds = getSelectionBounds(self.selections, true);
|
|
930
|
+
const result = [];
|
|
931
|
+
for (let rowIndex = bounds.top; rowIndex <= bounds.bottom; rowIndex++)
|
|
932
|
+
result.push(rowIndex);
|
|
933
|
+
return result;
|
|
934
|
+
};
|
|
935
|
+
|
|
936
|
+
/**
|
|
937
|
+
* @param {object} cell Signature: `{rowIndex:number;columnIndex:number}`
|
|
938
|
+
* @param {object} keyEvent Signature: `{key:string;shiftKey:boolean}`
|
|
939
|
+
* @param {boolean} [suppressEvent]
|
|
940
|
+
*/
|
|
941
|
+
self.shrinkOrExpandSelections = (cell, keyEvent, suppressEvent) => {
|
|
942
|
+
const result = shrinkOrExpandSelections(self.selections, cell, keyEvent, {
|
|
943
|
+
columns: self.getSchema().length,
|
|
944
|
+
rows: self.viewData.length,
|
|
945
|
+
});
|
|
946
|
+
if (result && !suppressEvent) self.dispatchSelectionChangedEvent();
|
|
947
|
+
return result;
|
|
948
|
+
};
|
|
949
|
+
|
|
950
|
+
/**
|
|
951
|
+
* @param {number} [fromIndex]
|
|
952
|
+
* @param {number} [len]
|
|
953
|
+
*/
|
|
954
|
+
function getSchemaOrderByViewIndex(fromIndex, len) {
|
|
955
|
+
const schema = self.getSchema();
|
|
956
|
+
const orderedSchema = [];
|
|
957
|
+
for (let col = fromIndex, col2 = fromIndex + len; col < col2; col++) {
|
|
958
|
+
const orderedIndex = self.orders.columns[col];
|
|
959
|
+
const header = schema[orderedIndex];
|
|
960
|
+
orderedSchema[col] = header;
|
|
961
|
+
}
|
|
962
|
+
return orderedSchema;
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
/**
|
|
966
|
+
* TODO: this function will be used for caching
|
|
967
|
+
* Concatenating two 2-dimensional array.
|
|
968
|
+
* It is simuliar with the method `concatenating` method of the Python module named `numpy`
|
|
969
|
+
* @template T
|
|
970
|
+
* @param {T[][]} a the first 2d array
|
|
971
|
+
* @param {T[][]} b the second 2d array
|
|
972
|
+
* @param {number} axis 1 implies that it is being done column-wise, otherwise row-wise
|
|
973
|
+
* @returns {T[][]}
|
|
974
|
+
*/
|
|
975
|
+
function concatenate2DArray(a, b, axis) {
|
|
976
|
+
if (axis === 1) {
|
|
977
|
+
const result = new Array(a.length);
|
|
978
|
+
for (let i = 0; i < a.length; i++) {
|
|
979
|
+
const rowInA = a[i];
|
|
980
|
+
const rowInB = b[i];
|
|
981
|
+
result[i] = rowInA.concat(rowInB);
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
return a.concat(b);
|
|
985
|
+
}
|
|
986
|
+
}
|