-
Notifications
You must be signed in to change notification settings - Fork 3k
/
indexMapper.js
771 lines (670 loc) · 26.4 KB
/
indexMapper.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
import { arrayMap } from '../helpers/array';
import {
createIndexMap,
getListWithInsertedItems,
getListWithRemovedItems,
HidingMap,
IndexesSequence,
TrimmingMap,
} from './maps';
import {
AggregatedCollection,
MapCollection,
} from './mapCollections';
import localHooks from '../mixins/localHooks';
import { mixin } from '../helpers/object';
import { isDefined } from '../helpers/mixed';
import { ChangesObservable } from './changesObservable/observable';
/**
* A set of deprecated feature names.
*
* @type {Set<string>}
*/
// eslint-disable-next-line no-unused-vars
const deprecationWarns = new Set();
/**
* @class IndexMapper
* @description
*
* Index mapper stores, registers and manages the indexes on the basis of calculations collected from the subsidiary maps.
* It should be seen as a single source of truth (regarding row and column indexes, for example, their sequence, information if they are skipped in the process of rendering (hidden or trimmed), values linked to them)
* for any operation that considers CRUD actions such as **insertion**, **movement**, **removal** etc, and is used to properly calculate physical and visual indexes translations in both ways.
* It has a built-in cache that is updated only when the data or structure changes.
*
* **Physical index** is a type of an index from the sequence of indexes assigned to the data source rows or columns
* (from 0 to n, where n is number of the cells on the axis of data set).
* **Visual index** is a type of an index from the sequence of indexes assigned to rows or columns existing in {@link DataMap} (from 0 to n, where n is number of the cells on the axis of data set).
* **Renderable index** is a type of an index from the sequence of indexes assigned to rows or columns whose may be rendered (when they are in a viewport; from 0 to n, where n is number of the cells renderable on the axis).
*
* There are different kinds of index maps which may be registered in the collections and can be used by a reference.
* They also expose public API and trigger two local hooks such as `init` (on initialization) and `change` (on change).
*
* These are: {@link IndexesSequence}, {@link PhysicalIndexToValueMap}, {@link LinkedPhysicalIndexToValueMap}, {@link HidingMap}, and {@link TrimmingMap}.
*/
export class IndexMapper {
constructor() {
/**
* Map for storing the sequence of indexes.
*
* It is registered by default and may be used from API methods.
*
* @private
* @type {IndexesSequence}
*/
this.indexesSequence = new IndexesSequence();
/**
* Collection for different trimming maps. Indexes marked as trimmed in any map WILL NOT be included in
* the {@link DataMap} and won't be rendered.
*
* @private
* @type {MapCollection}
*/
this.trimmingMapsCollection = new AggregatedCollection(
valuesForIndex => valuesForIndex.some(value => value === true), false);
/**
* Collection for different hiding maps. Indexes marked as hidden in any map WILL be included in the {@link DataMap},
* but won't be rendered.
*
* @private
* @type {MapCollection}
*/
this.hidingMapsCollection = new AggregatedCollection(
valuesForIndex => valuesForIndex.some(value => value === true), false);
/**
* Collection for another kind of maps. There are stored mappings from indexes (visual or physical) to values.
*
* @private
* @type {MapCollection}
*/
this.variousMapsCollection = new MapCollection();
/**
* The class instance collects row and column index changes that happen while the Handsontable
* is running. The object allows creating observers that you can subscribe. Each event represents
* the index change (e.g., insert, removing, change index value), which can be consumed by a
* developer to update its logic.
*
* @private
* @type {ChangesObservable}
*/
this.hidingChangesObservable = new ChangesObservable({
initialIndexValue: false,
});
/**
* Cache for list of not trimmed indexes, respecting the indexes sequence (physical indexes).
*
* Note: Please keep in mind that trimmed index can be also hidden.
*
* @private
* @type {Array}
*/
this.notTrimmedIndexesCache = [];
/**
* Cache for list of not hidden indexes, respecting the indexes sequence (physical indexes).
*
* Note: Please keep in mind that hidden index can be also trimmed.
*
* @private
* @type {Array}
*/
this.notHiddenIndexesCache = [];
/**
* Flag determining whether actions performed on index mapper have been batched. It's used for cache management.
*
* @private
* @type {boolean}
*/
this.isBatched = false;
/**
* Flag determining whether any action on indexes sequence has been performed. It's used for cache management.
*
* @private
* @type {boolean}
*/
this.indexesSequenceChanged = false;
/**
* Flag informing about source of the change.
*
* @type {undefined|string}
*/
this.indexesChangeSource = void 0;
/**
* Flag determining whether any action on trimmed indexes has been performed. It's used for cache management.
*
* @private
* @type {boolean}
*/
this.trimmedIndexesChanged = false;
/**
* Flag determining whether any action on hidden indexes has been performed. It's used for cache management.
*
* @private
* @type {boolean}
*/
this.hiddenIndexesChanged = false;
/**
* Physical indexes (respecting the sequence of indexes) which may be rendered (when they are in a viewport).
*
* @private
* @type {Array}
*/
this.renderablePhysicalIndexesCache = [];
/**
* Visual indexes (native map's value) corresponding to physical indexes (native map's index).
*
* @private
* @type {Map}
*/
this.fromPhysicalToVisualIndexesCache = new Map();
/**
* Visual indexes (native map's value) corresponding to physical indexes (native map's index).
*
* @private
* @type {Map}
*/
this.fromVisualToRenderableIndexesCache = new Map();
this.indexesSequence.addLocalHook('change', () => {
this.indexesSequenceChanged = true;
// Sequence of stored indexes might change.
this.updateCache();
this.runLocalHooks('indexesSequenceChange', this.indexesChangeSource);
this.runLocalHooks('change', this.indexesSequence, null);
});
this.trimmingMapsCollection.addLocalHook('change', (changedMap) => {
this.trimmedIndexesChanged = true;
// Number of trimmed indexes might change.
this.updateCache();
this.runLocalHooks('change', changedMap, this.trimmingMapsCollection);
});
this.hidingMapsCollection.addLocalHook('change', (changedMap) => {
this.hiddenIndexesChanged = true;
// Number of hidden indexes might change.
this.updateCache();
this.runLocalHooks('change', changedMap, this.hidingMapsCollection);
});
this.variousMapsCollection.addLocalHook('change', (changedMap) => {
this.runLocalHooks('change', changedMap, this.variousMapsCollection);
});
}
/**
* Suspends the cache update for this map. The method is helpful to group multiple
* operations, which affects the cache. In this case, the cache will be updated once after
* calling the `resumeOperations` method.
*/
suspendOperations() {
this.isBatched = true;
}
/**
* Resumes the cache update for this map. It recalculates the cache and restores the
* default behavior where each map modification updates the cache.
*/
resumeOperations() {
this.isBatched = false;
this.updateCache();
}
/**
* It creates and returns the new instance of the ChangesObserver object. The object
* allows listening to the index changes that happen while the Handsontable is running.
*
* @param {string} indexMapType The index map type which we want to observe.
* Currently, only the 'hiding' index map types are observable.
* @returns {ChangesObserver}
*/
createChangesObserver(indexMapType) {
if (indexMapType !== 'hiding') {
throw new Error(`Unsupported index map type "${indexMapType}".`);
}
return this.hidingChangesObservable.createObserver();
}
/**
* Creates and registers a new `IndexMap` for a specified `IndexMapper` instance.
*
* @param {string} indexName A unique index name.
* @param {string} mapType The index map type (e.g., "hiding", "trimming", "physicalIndexToValue").
* @param {*} [initValueOrFn] The initial value for the index map.
* @returns {IndexMap}
*/
createAndRegisterIndexMap(indexName, mapType, initValueOrFn) {
return this.registerMap(indexName, createIndexMap(mapType, initValueOrFn));
}
/**
* Register map which provide some index mappings. Type of map determining to which collection it will be added.
*
* @param {string} uniqueName Name of the index map. It should be unique.
* @param {IndexMap} indexMap Registered index map updated on items removal and insertion.
* @returns {IndexMap}
*/
registerMap(uniqueName, indexMap) {
if (this.trimmingMapsCollection.get(uniqueName) ||
this.hidingMapsCollection.get(uniqueName) ||
this.variousMapsCollection.get(uniqueName)) {
throw Error(`Map with name "${uniqueName}" has been already registered.`);
}
if (indexMap instanceof TrimmingMap) {
this.trimmingMapsCollection.register(uniqueName, indexMap);
} else if (indexMap instanceof HidingMap) {
this.hidingMapsCollection.register(uniqueName, indexMap);
} else {
this.variousMapsCollection.register(uniqueName, indexMap);
}
const numberOfIndexes = this.getNumberOfIndexes();
/*
We initialize map ony when we have full information about number of indexes and the dataset is not empty.
Otherwise it's unnecessary. Initialization of empty array would not give any positive changes. After initializing
it with number of indexes equal to 0 the map would be still empty. What's more there would be triggered
not needed hook (no real change have occurred). Number of indexes is known after loading data (the `loadData`
function from the `Core`).
*/
if (numberOfIndexes > 0) {
indexMap.init(numberOfIndexes);
}
return indexMap;
}
/**
* Unregister a map with given name.
*
* @param {string} name Name of the index map.
*/
unregisterMap(name) {
this.trimmingMapsCollection.unregister(name);
this.hidingMapsCollection.unregister(name);
this.variousMapsCollection.unregister(name);
}
/**
* Unregisters all collected index map instances from all map collection types.
*/
unregisterAll() {
this.trimmingMapsCollection.unregisterAll();
this.hidingMapsCollection.unregisterAll();
this.variousMapsCollection.unregisterAll();
}
/**
* Get a physical index corresponding to the given visual index.
*
* @param {number} visualIndex Visual index.
* @returns {number|null} Returns translated index mapped by passed visual index.
*/
getPhysicalFromVisualIndex(visualIndex) {
// Index in the table boundaries provided by the `DataMap`.
const physicalIndex = this.notTrimmedIndexesCache[visualIndex];
if (isDefined(physicalIndex)) {
return physicalIndex;
}
return null;
}
/**
* Get a physical index corresponding to the given renderable index.
*
* @param {number} renderableIndex Renderable index.
* @returns {null|number}
*/
getPhysicalFromRenderableIndex(renderableIndex) {
const physicalIndex = this.renderablePhysicalIndexesCache[renderableIndex];
// Index in the renderable table boundaries.
if (isDefined(physicalIndex)) {
return physicalIndex;
}
return null;
}
/**
* Get a visual index corresponding to the given physical index.
*
* @param {number} physicalIndex Physical index to search.
* @returns {number|null} Returns a visual index of the index mapper.
*/
getVisualFromPhysicalIndex(physicalIndex) {
const visualIndex = this.fromPhysicalToVisualIndexesCache.get(physicalIndex);
// Index in the table boundaries provided by the `DataMap`.
if (isDefined(visualIndex)) {
return visualIndex;
}
return null;
}
/**
* Get a visual index corresponding to the given renderable index.
*
* @param {number} renderableIndex Renderable index.
* @returns {null|number}
*/
getVisualFromRenderableIndex(renderableIndex) {
return this.getVisualFromPhysicalIndex(this.getPhysicalFromRenderableIndex(renderableIndex));
}
/**
* Get a renderable index corresponding to the given visual index.
*
* @param {number} visualIndex Visual index.
* @returns {null|number}
*/
getRenderableFromVisualIndex(visualIndex) {
const renderableIndex = this.fromVisualToRenderableIndexesCache.get(visualIndex);
// Index in the renderable table boundaries.
if (isDefined(renderableIndex)) {
return renderableIndex;
}
return null;
}
/**
* Search for the nearest not-hidden row or column.
*
* @param {number} fromVisualIndex The visual index of the row or column from which the search starts.<br><br>
* If the row or column from which the search starts is not hidden, the method simply returns the `fromVisualIndex` number.
* @param {number} searchDirection The search direction.<br><br>`1`: search from `fromVisualIndex` to the end of the dataset.<br><br>
* `-1`: search from `fromVisualIndex` to the beginning of the dataset (i.e., to the row or column at visual index `0`).
* @param {boolean} searchAlsoOtherWayAround `true`: if a search in a first direction failed, try the opposite direction.<br><br>
* `false`: search in one direction only.
*
* @returns {number|null} A visual index of a row or column, or `null`.
*/
getNearestNotHiddenIndex(fromVisualIndex, searchDirection, searchAlsoOtherWayAround = false) {
const physicalIndex = this.getPhysicalFromVisualIndex(fromVisualIndex);
if (physicalIndex === null) {
return null;
}
if (this.fromVisualToRenderableIndexesCache.has(fromVisualIndex)) {
return fromVisualIndex;
}
const visibleIndexes = Array.from(this.fromVisualToRenderableIndexesCache.keys());
let index = -1;
if (searchDirection > 0) {
index = visibleIndexes.findIndex(visualIndex => visualIndex > fromVisualIndex);
} else {
index = visibleIndexes.reverse().findIndex(visualIndex => visualIndex < fromVisualIndex);
}
if (index === -1) {
if (searchAlsoOtherWayAround) {
return this.getNearestNotHiddenIndex(fromVisualIndex, -searchDirection, false);
}
return null;
}
return visibleIndexes[index];
}
/**
* Set default values for all indexes in registered index maps.
*
* @param {number} [length] Destination length for all stored index maps.
*/
initToLength(length = this.getNumberOfIndexes()) {
this.notTrimmedIndexesCache = [...new Array(length).keys()];
this.notHiddenIndexesCache = [...new Array(length).keys()];
this.suspendOperations();
this.indexesChangeSource = 'init';
this.indexesSequence.init(length);
this.indexesChangeSource = void 0;
this.trimmingMapsCollection.initEvery(length);
this.resumeOperations();
// We move initialization of hidden collection to next batch for purpose of working on sequence of already trimmed indexes.
this.suspendOperations();
this.hidingMapsCollection.initEvery(length);
// It shouldn't reset the cache.
this.variousMapsCollection.initEvery(length);
this.resumeOperations();
this.runLocalHooks('init');
}
/**
* Trim/extend the mappers to fit the desired length.
*
* @param {number} length New mapper length.
*/
fitToLength(length) {
const currentIndexCount = this.getNumberOfIndexes();
if (length < currentIndexCount) {
const indexesToBeRemoved = [
...Array(this.getNumberOfIndexes() - length).keys()
].map(i => i + length);
this.removeIndexes(indexesToBeRemoved);
} else {
this.insertIndexes(currentIndexCount, length - currentIndexCount);
}
}
/**
* Get sequence of indexes.
*
* @returns {Array} Physical indexes.
*/
getIndexesSequence() {
return this.indexesSequence.getValues();
}
/**
* Set completely new indexes sequence.
*
* @param {Array} indexes Physical indexes.
*/
setIndexesSequence(indexes) {
if (this.indexesChangeSource === void 0) {
this.indexesChangeSource = 'update';
}
this.indexesSequence.setValues(indexes);
if (this.indexesChangeSource === 'update') {
this.indexesChangeSource = void 0;
}
}
/**
* Get all NOT trimmed indexes.
*
* Note: Indexes marked as trimmed aren't included in a {@link DataMap} and aren't rendered.
*
* @param {boolean} [readFromCache=true] Determine if read indexes from cache.
* @returns {Array} List of physical indexes. Index of this native array is a "visual index",
* value of this native array is a "physical index".
*/
getNotTrimmedIndexes(readFromCache = true) {
if (readFromCache === true) {
return this.notTrimmedIndexesCache;
}
const indexesSequence = this.getIndexesSequence();
return indexesSequence.filter(physicalIndex => this.isTrimmed(physicalIndex) === false);
}
/**
* Get length of all NOT trimmed indexes.
*
* Note: Indexes marked as trimmed aren't included in a {@link DataMap} and aren't rendered.
*
* @returns {number}
*/
getNotTrimmedIndexesLength() {
return this.getNotTrimmedIndexes().length;
}
/**
* Get all NOT hidden indexes.
*
* Note: Indexes marked as hidden are included in a {@link DataMap}, but aren't rendered.
*
* @param {boolean} [readFromCache=true] Determine if read indexes from cache.
* @returns {Array} List of physical indexes. Please keep in mind that index of this native array IS NOT a "visual index".
*/
getNotHiddenIndexes(readFromCache = true) {
if (readFromCache === true) {
return this.notHiddenIndexesCache;
}
const indexesSequence = this.getIndexesSequence();
return indexesSequence.filter(physicalIndex => this.isHidden(physicalIndex) === false);
}
/**
* Get length of all NOT hidden indexes.
*
* Note: Indexes marked as hidden are included in a {@link DataMap}, but aren't rendered.
*
* @returns {number}
*/
getNotHiddenIndexesLength() {
return this.getNotHiddenIndexes().length;
}
/**
* Get list of physical indexes (respecting the sequence of indexes) which may be rendered (when they are in a viewport).
*
* @param {boolean} [readFromCache=true] Determine if read indexes from cache.
* @returns {Array} List of physical indexes. Index of this native array is a "renderable index",
* value of this native array is a "physical index".
*/
getRenderableIndexes(readFromCache = true) {
if (readFromCache === true) {
return this.renderablePhysicalIndexesCache;
}
const notTrimmedIndexes = this.getNotTrimmedIndexes();
return notTrimmedIndexes.filter(physicalIndex => this.isHidden(physicalIndex) === false);
}
/**
* Get length of all NOT trimmed and NOT hidden indexes.
*
* @returns {number}
*/
getRenderableIndexesLength() {
return this.getRenderableIndexes().length;
}
/**
* Get number of all indexes.
*
* @returns {number}
*/
getNumberOfIndexes() {
return this.getIndexesSequence().length;
}
/**
* Move indexes in the index mapper.
*
* @param {number|Array} movedIndexes Visual index(es) to move.
* @param {number} finalIndex Visual index being a start index for the moved elements.
*/
moveIndexes(movedIndexes, finalIndex) {
if (typeof movedIndexes === 'number') {
movedIndexes = [movedIndexes];
}
const physicalMovedIndexes = arrayMap(movedIndexes, visualIndex => this.getPhysicalFromVisualIndex(visualIndex));
const notTrimmedIndexesLength = this.getNotTrimmedIndexesLength();
const movedIndexesLength = movedIndexes.length;
// Removing moved indexes without re-indexing.
const notMovedIndexes = getListWithRemovedItems(this.getIndexesSequence(), physicalMovedIndexes);
const notTrimmedNotMovedItems = notMovedIndexes.filter(index => this.isTrimmed(index) === false);
// When item(s) are moved after the last visible item we assign the last possible index.
let destinationPosition = notMovedIndexes.indexOf(notTrimmedNotMovedItems[notTrimmedNotMovedItems.length - 1]) + 1;
// Otherwise, we find proper index for inserted item(s).
if (finalIndex + movedIndexesLength < notTrimmedIndexesLength) {
// Physical index at final index position.
const physicalIndex = notTrimmedNotMovedItems[finalIndex];
destinationPosition = notMovedIndexes.indexOf(physicalIndex);
}
this.indexesChangeSource = 'move';
// Adding indexes without re-indexing.
this.setIndexesSequence(getListWithInsertedItems(notMovedIndexes, destinationPosition, physicalMovedIndexes));
this.indexesChangeSource = void 0;
}
/**
* Get whether index is trimmed. Index marked as trimmed isn't included in a {@link DataMap} and isn't rendered.
*
* @param {number} physicalIndex Physical index.
* @returns {boolean}
*/
isTrimmed(physicalIndex) {
return this.trimmingMapsCollection.getMergedValueAtIndex(physicalIndex);
}
/**
* Get whether index is hidden. Index marked as hidden is included in a {@link DataMap}, but isn't rendered.
*
* @param {number} physicalIndex Physical index.
* @returns {boolean}
*/
isHidden(physicalIndex) {
return this.hidingMapsCollection.getMergedValueAtIndex(physicalIndex);
}
/**
* Insert new indexes and corresponding mapping and update values of the others, for all stored index maps.
*
* @private
* @param {number} firstInsertedVisualIndex First inserted visual index.
* @param {number} amountOfIndexes Amount of inserted indexes.
*/
insertIndexes(firstInsertedVisualIndex, amountOfIndexes) {
const nthVisibleIndex = this.getNotTrimmedIndexes()[firstInsertedVisualIndex];
const firstInsertedPhysicalIndex = isDefined(nthVisibleIndex) ? nthVisibleIndex : this.getNumberOfIndexes();
const insertionIndex = this.getIndexesSequence().includes(nthVisibleIndex) ?
this.getIndexesSequence().indexOf(nthVisibleIndex) : this.getNumberOfIndexes();
const insertedIndexes = arrayMap(new Array(amountOfIndexes).fill(firstInsertedPhysicalIndex),
(nextIndex, stepsFromStart) => nextIndex + stepsFromStart);
this.suspendOperations();
this.indexesChangeSource = 'insert';
this.indexesSequence.insert(insertionIndex, insertedIndexes);
this.indexesChangeSource = void 0;
this.trimmingMapsCollection.insertToEvery(insertionIndex, insertedIndexes);
this.hidingMapsCollection.insertToEvery(insertionIndex, insertedIndexes);
this.variousMapsCollection.insertToEvery(insertionIndex, insertedIndexes);
this.resumeOperations();
}
/**
* Remove some indexes and corresponding mappings and update values of the others, for all stored index maps.
*
* @private
* @param {Array} removedIndexes List of removed indexes.
*/
removeIndexes(removedIndexes) {
this.suspendOperations();
this.indexesChangeSource = 'remove';
this.indexesSequence.remove(removedIndexes);
this.indexesChangeSource = void 0;
this.trimmingMapsCollection.removeFromEvery(removedIndexes);
this.hidingMapsCollection.removeFromEvery(removedIndexes);
this.variousMapsCollection.removeFromEvery(removedIndexes);
this.resumeOperations();
}
/**
* Rebuild cache for some indexes. Every action on indexes sequence or indexes skipped in the process of rendering
* by default reset cache, thus batching some index maps actions is recommended.
*
* @private
* @param {boolean} [force=false] Determine if force cache update.
*/
updateCache(force = false) {
const anyCachedIndexChanged = this.indexesSequenceChanged ||
this.trimmedIndexesChanged || this.hiddenIndexesChanged;
if (force === true || (this.isBatched === false && anyCachedIndexChanged === true)) {
this.trimmingMapsCollection.updateCache();
this.hidingMapsCollection.updateCache();
this.notTrimmedIndexesCache = this.getNotTrimmedIndexes(false);
this.notHiddenIndexesCache = this.getNotHiddenIndexes(false);
this.renderablePhysicalIndexesCache = this.getRenderableIndexes(false);
this.cacheFromPhysicalToVisualIndexes();
this.cacheFromVisualToRenderableIndexes();
// Currently there's support only for the "hiding" map type.
if (this.hiddenIndexesChanged) {
this.hidingChangesObservable.emit(this.hidingMapsCollection.getMergedValues());
}
this.runLocalHooks('cacheUpdated', {
indexesSequenceChanged: this.indexesSequenceChanged,
trimmedIndexesChanged: this.trimmedIndexesChanged,
hiddenIndexesChanged: this.hiddenIndexesChanged,
});
this.indexesSequenceChanged = false;
this.trimmedIndexesChanged = false;
this.hiddenIndexesChanged = false;
}
}
/**
* Update cache for translations from physical to visual indexes.
*
* @private
*/
cacheFromPhysicalToVisualIndexes() {
const nrOfNotTrimmedIndexes = this.getNotTrimmedIndexesLength();
this.fromPhysicalToVisualIndexesCache.clear();
for (let visualIndex = 0; visualIndex < nrOfNotTrimmedIndexes; visualIndex += 1) {
const physicalIndex = this.getPhysicalFromVisualIndex(visualIndex);
// Every visual index have corresponding physical index, but some physical indexes may don't have
// corresponding visual indexes (physical indexes may represent trimmed indexes, beyond the table boundaries)
this.fromPhysicalToVisualIndexesCache.set(physicalIndex, visualIndex);
}
}
/**
* Update cache for translations from visual to renderable indexes.
*
* @private
*/
cacheFromVisualToRenderableIndexes() {
const nrOfRenderableIndexes = this.getRenderableIndexesLength();
this.fromVisualToRenderableIndexesCache.clear();
for (let renderableIndex = 0; renderableIndex < nrOfRenderableIndexes; renderableIndex += 1) {
// Can't use getRenderableFromVisualIndex here because we're building the cache here
const physicalIndex = this.getPhysicalFromRenderableIndex(renderableIndex);
const visualIndex = this.getVisualFromPhysicalIndex(physicalIndex);
this.fromVisualToRenderableIndexesCache.set(visualIndex, renderableIndex);
}
}
}
mixin(IndexMapper, localHooks);