@slickgrid-universal/row-detail-view-plugin 5.12.2 → 5.13.1

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.
@@ -18,14 +18,14 @@ class SlickRowDetailView {
18
18
  this.pluginName = 'RowDetailView';
19
19
  this._dataViewIdProperty = 'id';
20
20
  this._expandableOverride = null;
21
- this._expandedRows = new Set();
21
+ this._expandedRowIds = new Set();
22
22
  this._gridRowBuffer = 0;
23
23
  this._gridUid = '';
24
24
  this._keyPrefix = '';
25
- this._lastRange = null;
26
- this._outsideRange = 5;
25
+ this._disposedRows = new Set();
27
26
  this._rowIdsOutOfViewport = new Set();
28
- this._visibleRenderedCellCount = 0;
27
+ this._renderedViewportRowIds = new Set();
28
+ this._renderedIds = new Set();
29
29
  this._defaults = {
30
30
  alwaysRenderColumn: true,
31
31
  columnId: '_detail_selector',
@@ -34,13 +34,12 @@ class SlickRowDetailView {
34
34
  collapseAllOnSort: true,
35
35
  collapsedClass: undefined,
36
36
  expandedClass: undefined,
37
- keyPrefix: '_',
37
+ keyPrefix: '__',
38
38
  loadOnce: false,
39
39
  maxRows: undefined,
40
40
  reorderable: false,
41
41
  saveDetailViewOnScroll: true,
42
42
  singleRowExpand: false,
43
- useSimpleViewportCalc: false,
44
43
  toolTip: '',
45
44
  width: 30,
46
45
  };
@@ -49,6 +48,7 @@ class SlickRowDetailView {
49
48
  this.onAsyncResponse = new common_1.SlickEvent('onAsyncResponse');
50
49
  this.onAfterRowDetailToggle = new common_1.SlickEvent('onAfterRowDetailToggle');
51
50
  this.onBeforeRowDetailToggle = new common_1.SlickEvent('onBeforeRowDetailToggle');
51
+ this.onBeforeRowOutOfViewportRange = new common_1.SlickEvent('onBeforeRowOutOfViewportRange');
52
52
  this.onRowBackToViewportRange = new common_1.SlickEvent('onRowBackToViewportRange');
53
53
  this.onRowOutOfViewportRange = new common_1.SlickEvent('onRowOutOfViewportRange');
54
54
  }
@@ -72,21 +72,14 @@ class SlickRowDetailView {
72
72
  get gridUid() {
73
73
  return this._gridUid || this._grid?.getUID() || '';
74
74
  }
75
- set lastRange(range) {
76
- this._lastRange = range;
77
- }
78
75
  set rowIdsOutOfViewport(rowIds) {
79
76
  this._rowIdsOutOfViewport = new Set(rowIds);
80
77
  }
81
- get visibleRenderedCellCount() {
82
- return this._visibleRenderedCellCount;
83
- }
84
78
  /**
85
79
  * Initialize the Export Service
86
80
  * @param _grid
87
81
  */
88
82
  init(grid) {
89
- this._grid = grid;
90
83
  if (!grid) {
91
84
  throw new Error('[Slickgrid-Universal] RowDetailView Plugin requires the Grid instance to be passed as argument to the "init()" method.');
92
85
  }
@@ -95,7 +88,7 @@ class SlickRowDetailView {
95
88
  if (!this._addonOptions) {
96
89
  this._addonOptions = (0, utils_1.extend)(true, {}, this._defaults, this.gridOptions.rowDetailView);
97
90
  }
98
- this._keyPrefix = this._addonOptions?.keyPrefix || '_';
91
+ this._keyPrefix = this._addonOptions?.keyPrefix || '__';
99
92
  // add PubSub instance to all SlickEvent
100
93
  common_1.Utils.addSlickEventPubSubWhenDefined(this.pubSubService, this);
101
94
  // Update the minRowBuffer so that the view doesn't disappear when it's at top of screen + the original default 3
@@ -104,20 +97,40 @@ class SlickRowDetailView {
104
97
  this._eventHandler
105
98
  .subscribe(this._grid.onClick, this.handleClick.bind(this))
106
99
  .subscribe(this._grid.onBeforeEditCell, () => this.collapseAll())
107
- .subscribe(this._grid.onScroll, this.handleScroll.bind(this));
100
+ .subscribe(this._grid.onScroll, () => this.recalculateOutOfRangeViews(true, 0))
101
+ .subscribe(this._grid.onBeforeRemoveCachedRow, (_e, args) => this.handleRemoveRow(args.row));
108
102
  // Sort will, by default, Collapse all of the open items (unless user implements his own onSort which deals with open row and padding)
109
103
  if (this._addonOptions.collapseAllOnSort) {
110
104
  // sort event can be triggered by column header click or from header menu
111
105
  this.pubSubService.subscribe('onSortChanged', () => this.collapseAll());
112
- this._expandedRows.clear();
113
- this._rowIdsOutOfViewport.clear();
114
106
  }
115
107
  this._eventHandler.subscribe(this.dataView.onRowCountChanged, () => {
116
108
  this._grid.updateRowCount();
117
109
  this._grid.render();
118
110
  });
119
111
  this._eventHandler.subscribe(this.dataView.onRowsChanged, (_e, args) => {
120
- this._grid.invalidateRows(args.rows);
112
+ const cachedRows = Object.keys(this._grid.getRowCache()).map(Number);
113
+ const toInvalidateRows = [];
114
+ const intersectedRows = args.rows.filter((nb) => cachedRows.includes(nb));
115
+ // only consider rows to invalidate as rows that exists in the viewport (cached rows)
116
+ this._expandedRowIds.forEach((itemId) => {
117
+ const idx = this.dataView.getRowById(itemId);
118
+ if (idx !== undefined && intersectedRows.includes(idx)) {
119
+ toInvalidateRows.push(idx);
120
+ }
121
+ });
122
+ // don't invalidate row detail that were already rendered and visible
123
+ // for example, if we open row 3 and then row 1, row 3 will be pushed down but it was already rendered so no need to re-render it
124
+ this._renderedIds.forEach((rowId) => {
125
+ const dataRowIdx = this.dataView.getRowById(rowId);
126
+ if (dataRowIdx !== undefined) {
127
+ const invRowIdx = toInvalidateRows.findIndex((r) => r === dataRowIdx);
128
+ if (invRowIdx >= 0) {
129
+ toInvalidateRows.splice(invRowIdx, 1);
130
+ }
131
+ }
132
+ });
133
+ this._grid.invalidateRows(toInvalidateRows);
121
134
  this._grid.render();
122
135
  });
123
136
  // subscribe to the onAsyncResponse so that the plugin knows when the user server side calls finished
@@ -126,22 +139,14 @@ class SlickRowDetailView {
126
139
  this._eventHandler.subscribe(this.dataView.onSetItemsCalled, () => {
127
140
  this._dataViewIdProperty = this.dataView?.getIdPropertyName() || 'id';
128
141
  });
129
- // if we use the alternative & simpler calculation of the out of viewport range
130
- // we will need to know how many rows are rendered on the screen and we need to wait for grid to be rendered
131
- // unfortunately there is no triggered event for knowing when grid is finished, so we use 250ms delay and it's typically more than enough
132
- if (this._addonOptions.useSimpleViewportCalc) {
133
- this._eventHandler.subscribe(this._grid.onRendered, (_e, args) => {
134
- if (args?.endRow) {
135
- this._visibleRenderedCellCount = args.endRow - args.startRow;
136
- }
137
- });
138
- }
139
142
  }
140
143
  /** Dispose of the Slick Row Detail View */
141
144
  dispose() {
142
145
  this._eventHandler?.unsubscribeAll();
143
- this._expandedRows.clear();
146
+ this._expandedRowIds.clear();
144
147
  this._rowIdsOutOfViewport.clear();
148
+ this._renderedViewportRowIds.clear();
149
+ clearTimeout(this._backViewportTimer);
145
150
  }
146
151
  create(columnDefinitions, gridOptions) {
147
152
  if (!gridOptions.rowDetailView) {
@@ -188,16 +193,17 @@ class SlickRowDetailView {
188
193
  /** Collapse all of the open items */
189
194
  collapseAll() {
190
195
  this.dataView.beginUpdate();
191
- this._expandedRows.forEach((expandedRow) => {
192
- this.collapseDetailView(expandedRow, true);
196
+ this._expandedRowIds.forEach((itemId) => {
197
+ this.collapseDetailView(itemId, true);
193
198
  });
194
199
  this.dataView.endUpdate();
195
200
  }
196
- /** Colapse an Item so it is not longer seen */
197
- collapseDetailView(item, isMultipleCollapsing = false) {
201
+ /** Collapse an Item so it is not longer seen */
202
+ collapseDetailView(itemId, isMultipleCollapsing = false) {
198
203
  if (!isMultipleCollapsing) {
199
204
  this.dataView.beginUpdate();
200
205
  }
206
+ const item = this.dataView.getItemById(itemId);
201
207
  // Save the details on the collapse assuming onetime loading
202
208
  if (this._addonOptions.loadOnce) {
203
209
  this.saveDetailView(item);
@@ -208,20 +214,26 @@ class SlickRowDetailView {
208
214
  }
209
215
  item[`${this._keyPrefix}sizePadding`] = 0;
210
216
  this.dataView.updateItem(item[this._dataViewIdProperty], item);
211
- // Remove the item from the expandedRows
212
- this._expandedRows = new Set(Array.from(this._expandedRows).filter((expRow) => expRow[this._dataViewIdProperty] !== item[this._dataViewIdProperty]));
217
+ // Remove the item from the expandedRows & renderedIds
218
+ this._expandedRowIds = new Set(Array.from(this._expandedRowIds).filter((expItemId) => expItemId !== item[this._dataViewIdProperty]));
219
+ this._renderedIds.delete(item[this._dataViewIdProperty]);
220
+ // we need to reevaluate & invalidate any row detail that are shown on top of the row that we're closing
221
+ this.reevaluateRenderedRowIds(item);
213
222
  if (!isMultipleCollapsing) {
214
223
  this.dataView.endUpdate();
215
224
  }
216
225
  }
217
226
  /** Expand a row given the dataview item that is to be expanded */
218
- expandDetailView(item) {
227
+ expandDetailView(itemId) {
219
228
  if (this._addonOptions?.singleRowExpand) {
220
229
  this.collapseAll();
221
230
  }
231
+ const item = this.dataView.getItemById(itemId);
232
+ // we need to reevaluate & invalidate any row detail that are shown on top of the row that we're closing
233
+ this.reevaluateRenderedRowIds(item);
222
234
  item[`${this._keyPrefix}collapsed`] = false;
223
- this._expandedRows.add(item);
224
- // In the case something went wrong loading it the first time such a scroll of screen before loaded
235
+ this._expandedRowIds.add(itemId);
236
+ // in the case something went wrong loading it the first time such a scroll of screen before loaded
225
237
  if (!item[`${this._keyPrefix}detailContent`]) {
226
238
  item[`${this._keyPrefix}detailViewLoaded`] = false;
227
239
  }
@@ -245,13 +257,20 @@ class SlickRowDetailView {
245
257
  // async server call
246
258
  this._addonOptions.process(item);
247
259
  }
260
+ /** reset all Set rows/ids cache and start empty (but keep expanded rows ref) */
261
+ resetRenderedRows() {
262
+ this._renderedViewportRowIds.clear();
263
+ this._disposedRows.clear();
264
+ }
248
265
  /** Saves the current state of the detail view */
249
266
  saveDetailView(item) {
250
- const view = document.querySelector(`.${this.gridUid} .innerDetailView_${item[this._dataViewIdProperty]}`);
251
- if (view) {
252
- const html = view.innerHTML;
253
- if (html !== undefined) {
254
- item[`${this._keyPrefix}detailContent`] = html;
267
+ if (this._addonOptions.loadOnce) {
268
+ const view = document.querySelector(`.${this.gridUid} .innerDetailView_${item[this._dataViewIdProperty]}`);
269
+ if (view) {
270
+ const html = view.innerHTML;
271
+ if (html !== undefined) {
272
+ item[`${this._keyPrefix}detailContent`] = html;
273
+ }
255
274
  }
256
275
  }
257
276
  }
@@ -264,13 +283,14 @@ class SlickRowDetailView {
264
283
  console.error('SlickRowDetailView plugin requires the onAsyncResponse() to supply "args.item" property.');
265
284
  return;
266
285
  }
267
- // we accept item/itemDetail, just get the one which has data
286
+ // @deprecated `args.itemDetail` we accept item/itemDetail, just get the one which has data
268
287
  const itemDetail = args.item || args.itemDetail;
269
- // If we just want to load in a view directly we can use detailView property to do so
288
+ // if we just want to load in a view directly we can use detailView property to do so
270
289
  itemDetail[`${this._keyPrefix}detailContent`] = args.detailView ?? this._addonOptions?.postTemplate?.(itemDetail);
271
290
  itemDetail[`${this._keyPrefix}detailViewLoaded`] = true;
272
291
  this.dataView.updateItem(itemDetail[this._dataViewIdProperty], itemDetail);
273
292
  // trigger an event once the post template is finished loading
293
+ this._renderedIds.add(itemDetail[this.dataViewIdProperty]);
274
294
  this.onAsyncEndUpdate.notify({
275
295
  grid: this._grid,
276
296
  item: itemDetail,
@@ -278,7 +298,6 @@ class SlickRowDetailView {
278
298
  }, e, this);
279
299
  }
280
300
  /**
281
- * TODO interface only has a GETTER not a SETTER..why?
282
301
  * Override the logic for showing (or not) the expand icon (use case example: only every 2nd row is expandable)
283
302
  * Method that user can pass to override the default behavior or making every row an expandable row.
284
303
  * In order word, user can choose which rows to be an available row detail (or not) by providing his own logic.
@@ -313,8 +332,8 @@ class SlickRowDetailView {
313
332
  };
314
333
  }
315
334
  /** return the currently expanded rows */
316
- getExpandedRows() {
317
- return Array.from(this._expandedRows);
335
+ getExpandedRowIds() {
336
+ return Array.from(this._expandedRowIds);
318
337
  }
319
338
  /** return the rows that are out of the viewport */
320
339
  getOutOfViewportRows() {
@@ -332,7 +351,7 @@ class SlickRowDetailView {
332
351
  if (!item) {
333
352
  return;
334
353
  }
335
- // Grad each of the DOM elements
354
+ // Grab each of the DOM elements
336
355
  const mainContainer = document.querySelector(`.${this.gridUid} .detailViewContainer_${item[this._dataViewIdProperty]}`);
337
356
  const cellItem = document.querySelector(`.${this.gridUid} .cellDetailView_${item[this._dataViewIdProperty]}`);
338
357
  const inner = document.querySelector(`.${this.gridUid} .innerDetailView_${item[this._dataViewIdProperty]}`);
@@ -373,11 +392,61 @@ class SlickRowDetailView {
373
392
  // Lastly save the updated state
374
393
  this.saveDetailView(item);
375
394
  }
395
+ /**
396
+ * (re)calculate/sync row detail views that are out of range of the viewport and trigger events (when enabled)
397
+ * @param {Boolean} [triggerEvent] - should trigger notify event which will re-render the detail view
398
+ * @param {Number} [delay] - optional delay to execute the calculation of out of range views
399
+ */
400
+ recalculateOutOfRangeViews(triggerEvent = true, delay) {
401
+ clearTimeout(this._backViewportTimer);
402
+ const calculateFn = () => this._expandedRowIds.forEach((itemId) => {
403
+ const item = this.dataView.getItemById(itemId);
404
+ const rowIdx = this.dataView.getRowById(itemId);
405
+ const cachedRows = Object.keys(this._grid.getRowCache()).map(Number);
406
+ const visible = this._grid.getRenderedRange();
407
+ const rowDetailCount = this.gridOptions.rowDetailView?.panelRows ?? 0;
408
+ this._visibleRenderedCell = { startRow: visible.top, endRow: visible.bottom };
409
+ let { startRow, endRow } = this._visibleRenderedCell;
410
+ if (rowIdx >= startRow && rowIdx <= endRow) {
411
+ const rowSum = rowIdx + (this.gridOptions.rowDetailView?.panelRows ?? 0);
412
+ if (rowSum > endRow) {
413
+ endRow = rowSum;
414
+ }
415
+ }
416
+ const rdEndRow = rowIdx + rowDetailCount;
417
+ if (startRow > rowIdx && rowIdx < rdEndRow && rdEndRow > this._visibleRenderedCell.startRow + 1) {
418
+ startRow = rowIdx;
419
+ }
420
+ this._visibleRenderedCell = { startRow, endRow };
421
+ if (!this._renderedViewportRowIds.has(itemId) &&
422
+ this._visibleRenderedCell &&
423
+ rowIdx >= this._visibleRenderedCell.startRow &&
424
+ rowIdx <= this._visibleRenderedCell.endRow &&
425
+ cachedRows.includes(rowIdx)) {
426
+ this._disposedRows.delete(rowIdx);
427
+ this.notifyViewportChange(item, 'add', triggerEvent);
428
+ }
429
+ else if ((this._disposedRows.has(rowIdx) && !cachedRows.includes(rowIdx)) ||
430
+ (!cachedRows.includes(rowIdx) &&
431
+ this._renderedViewportRowIds.has(itemId) &&
432
+ this._visibleRenderedCell &&
433
+ (rowIdx < this._visibleRenderedCell.startRow || rowIdx > this._visibleRenderedCell.endRow))) {
434
+ this.notifyViewportChange(item, 'remove', triggerEvent);
435
+ }
436
+ });
437
+ if (delay !== undefined) {
438
+ this._backViewportTimer = setTimeout(calculateFn, delay);
439
+ }
440
+ else {
441
+ calculateFn();
442
+ }
443
+ }
376
444
  // --
377
445
  // protected functions
378
446
  // ------------------
379
447
  /**
380
- * create the detail ctr node. this belongs to the dev & can be custom-styled as per
448
+ * create the row detail ctr node. this belongs to the dev & can be custom-styled as per
449
+ * @param {Object} item
381
450
  */
382
451
  applyTemplateNewLineHeight(item) {
383
452
  // the height is calculated by the template row count (how many line of items does the template view have)
@@ -392,82 +461,17 @@ class SlickRowDetailView {
392
461
  this.dataView.insertItem((idxParent || 0) + idx, this.getPaddingItem(item, idx));
393
462
  }
394
463
  }
395
- calculateOutOfRangeViews() {
396
- if (this._grid) {
397
- let scrollDir;
398
- const renderedRange = this._grid.getRenderedRange();
399
- // Only check if we have expanded rows
400
- if (this._expandedRows.size) {
401
- // Assume scroll direction is down by default.
402
- scrollDir = 'DOWN';
403
- if (this._lastRange) {
404
- // Some scrolling isn't anything as the range is the same
405
- if (this._lastRange.top === renderedRange.top && this._lastRange.bottom === renderedRange.bottom) {
406
- return;
407
- }
408
- // If our new top is smaller we are scrolling up
409
- if (this._lastRange.top > renderedRange.top ||
410
- // Or we are at very top but our bottom is increasing
411
- (this._lastRange.top === 0 && renderedRange.top === 0 && this._lastRange.bottom > renderedRange.bottom)) {
412
- scrollDir = 'UP';
413
- }
414
- }
464
+ notifyViewportChange(item, action, triggerEvent = true) {
465
+ if (item) {
466
+ const itemId = item[this._dataViewIdProperty];
467
+ if (action === 'add') {
468
+ this._renderedViewportRowIds.add(itemId);
469
+ triggerEvent && this.notifyBackToViewportWhenDomExist(item);
470
+ }
471
+ else if (action === 'remove') {
472
+ this._renderedViewportRowIds.delete(itemId);
473
+ triggerEvent && this.notifyOutOfViewport(item);
415
474
  }
416
- this._expandedRows.forEach((row) => {
417
- const rowIndex = this.dataView.getRowById(row[this._dataViewIdProperty]);
418
- const rowPadding = row[`${this._keyPrefix}sizePadding`];
419
- const isRowOutOfRange = this._rowIdsOutOfViewport.has(row[this._dataViewIdProperty]);
420
- if (scrollDir === 'UP') {
421
- // save the view when asked
422
- if (this._addonOptions.saveDetailViewOnScroll) {
423
- // If the bottom item within buffer range is an expanded row save it.
424
- if (rowIndex >= renderedRange.bottom - this._gridRowBuffer) {
425
- this.saveDetailView(row);
426
- }
427
- }
428
- // If the row expanded area is within the buffer notify that it is back in range
429
- if (isRowOutOfRange && rowIndex - this._outsideRange < renderedRange.top && rowIndex >= renderedRange.top) {
430
- this.notifyBackToViewportWhenDomExist(row, row[this._dataViewIdProperty]);
431
- }
432
- else if (!isRowOutOfRange && rowIndex + rowPadding > renderedRange.bottom) {
433
- // if our first expanded row is about to go off the bottom
434
- this.notifyOutOfViewport(row, row[this._dataViewIdProperty]);
435
- }
436
- }
437
- else if (scrollDir === 'DOWN') {
438
- // save the view when asked
439
- if (this._addonOptions.saveDetailViewOnScroll) {
440
- // If the top item within buffer range is an expanded row save it.
441
- if (rowIndex <= renderedRange.top + this._gridRowBuffer) {
442
- this.saveDetailView(row);
443
- }
444
- }
445
- // If row index is i higher than bottom with some added value (To ignore top rows off view) and is with view and was our of range
446
- if (isRowOutOfRange && rowIndex + rowPadding + this._outsideRange > renderedRange.bottom && rowIndex < rowIndex + rowPadding) {
447
- this.notifyBackToViewportWhenDomExist(row, row[this._dataViewIdProperty]);
448
- }
449
- else if (!isRowOutOfRange && rowIndex < renderedRange.top) {
450
- // if our row is outside top of and the buffering zone but not in the array of outOfVisable range notify it
451
- this.notifyOutOfViewport(row, row[this._dataViewIdProperty]);
452
- }
453
- }
454
- });
455
- this._lastRange = renderedRange;
456
- }
457
- }
458
- calculateOutOfRangeViewsSimplerVersion() {
459
- if (this._grid) {
460
- const renderedRange = this._grid.getRenderedRange();
461
- this._expandedRows.forEach((row) => {
462
- const rowIndex = this.dataView.getRowById(row[this._dataViewIdProperty]);
463
- const isOutOfVisibility = this.checkIsRowOutOfViewportRange(rowIndex, renderedRange);
464
- if (!isOutOfVisibility && this._rowIdsOutOfViewport.has(row[this._dataViewIdProperty])) {
465
- this.notifyBackToViewportWhenDomExist(row, row[this._dataViewIdProperty]);
466
- }
467
- else if (isOutOfVisibility) {
468
- this.notifyOutOfViewport(row, row[this._dataViewIdProperty]);
469
- }
470
- });
471
475
  }
472
476
  }
473
477
  checkExpandableOverride(row, dataContext, grid) {
@@ -476,9 +480,6 @@ class SlickRowDetailView {
476
480
  }
477
481
  return true;
478
482
  }
479
- checkIsRowOutOfViewportRange(rowIndex, renderedRange) {
480
- return Math.abs(renderedRange.bottom - this._gridRowBuffer - rowIndex) > this._visibleRenderedCellCount * 2;
481
- }
482
483
  /** Get the Row Detail padding (which are the rows dedicated to the detail panel) */
483
484
  getPaddingItem(parent, offset) {
484
485
  const item = {};
@@ -562,11 +563,12 @@ class SlickRowDetailView {
562
563
  /** When row is getting toggled, we will handle the action of collapsing/expanding */
563
564
  handleAccordionShowHide(item) {
564
565
  if (item) {
566
+ const itemId = item[this._dataViewIdProperty];
565
567
  if (!item[`${this._keyPrefix}collapsed`]) {
566
- this.collapseDetailView(item);
568
+ this.collapseDetailView(itemId);
567
569
  }
568
570
  else {
569
- this.expandDetailView(item);
571
+ this.expandDetailView(itemId);
570
572
  }
571
573
  }
572
574
  }
@@ -592,52 +594,89 @@ class SlickRowDetailView {
592
594
  .getReturnValue() === false) {
593
595
  return;
594
596
  }
597
+ // tag any row details that will need to be re-rendered after the row detail is toggled.
598
+ // for example if row(2) is open and we open row(1) then row(2) needs to be re-rendered,
599
+ // if however row(1) is open and we open row(2) then there is nothing to re-render
600
+ const toReRenderItems = [];
601
+ const visible = this._grid.getRenderedRange();
602
+ this._expandedRowIds.forEach((itemId) => {
603
+ const row = this.dataView.getRowById(itemId);
604
+ if (row !== undefined && row > args.row && row >= visible.top && row <= visible.bottom) {
605
+ const item = this.dataView.getItemById(itemId);
606
+ toReRenderItems.push(item);
607
+ this.notifyOutOfViewport(item);
608
+ }
609
+ });
595
610
  this.toggleRowSelection(args.row, dataContext);
596
611
  // trigger an event after toggling
597
612
  this.onAfterRowDetailToggle.notify({
598
613
  grid: this._grid,
599
614
  item: dataContext,
600
- expandedRows: Array.from(this._expandedRows),
615
+ expandedRows: Array.from(this._expandedRowIds).map((id) => this.dataView.getItemById(id)),
601
616
  }, e, this);
617
+ // re-render the row details that were tagged as
618
+ toReRenderItems.forEach(item => this.notifyViewportChange(item, 'add', true));
602
619
  e.stopPropagation();
603
620
  e.stopImmediatePropagation();
604
621
  }
605
622
  }
606
623
  }
607
- handleScroll() {
608
- if (this._addonOptions.useSimpleViewportCalc) {
609
- this.calculateOutOfRangeViewsSimplerVersion();
610
- }
611
- else {
612
- this.calculateOutOfRangeViews();
624
+ handleRemoveRow(rowIndex) {
625
+ const item = this.dataView.getItemByIdx(rowIndex);
626
+ const rowId = item[this.dataViewIdProperty];
627
+ if (this._expandedRowIds.has(rowId)) {
628
+ this.onBeforeRowOutOfViewportRange.notify({
629
+ grid: this._grid,
630
+ item,
631
+ rowId,
632
+ rowIndex,
633
+ expandedRows: Array.from(this._expandedRowIds).map((id) => this.dataView.getItemById(id)),
634
+ rowIdsOutOfViewport: Array.from(this.syncOutOfViewportArray(rowId, true)),
635
+ }, null, this);
636
+ this._disposedRows.add(rowIndex);
613
637
  }
614
638
  }
615
- notifyOutOfViewport(item, rowId) {
639
+ notifyOutOfViewport(item) {
616
640
  const rowIndex = item.rowIndex || this.dataView.getRowById(item[this._dataViewIdProperty]);
641
+ const rowId = item[this.dataViewIdProperty];
642
+ this._renderedIds.delete(rowId);
617
643
  this.onRowOutOfViewportRange.notify({
618
644
  grid: this._grid,
619
645
  item,
620
646
  rowId,
621
647
  rowIndex,
622
- expandedRows: Array.from(this._expandedRows),
648
+ expandedRows: Array.from(this._expandedRowIds).map((id) => this.dataView.getItemById(id)),
623
649
  rowIdsOutOfViewport: Array.from(this.syncOutOfViewportArray(rowId, true)),
624
650
  }, null, this);
625
651
  }
626
- notifyBackToViewportWhenDomExist(item, rowId) {
652
+ notifyBackToViewportWhenDomExist(item) {
627
653
  const rowIndex = item.rowIndex || this.dataView.getRowById(item[this._dataViewIdProperty]);
628
- window.setTimeout(() => {
629
- // make sure View Row DOM Element really exist before notifying that it's a row that is visible again
630
- if (document.querySelector(`.${this.gridUid} .cellDetailView_${item[this._dataViewIdProperty]}`)) {
631
- this.onRowBackToViewportRange.notify({
632
- grid: this._grid,
633
- item,
634
- rowId,
635
- rowIndex,
636
- expandedRows: Array.from(this._expandedRows),
637
- rowIdsOutOfViewport: Array.from(this.syncOutOfViewportArray(rowId, false)),
638
- }, null, this);
654
+ const rowId = item[this.dataViewIdProperty];
655
+ // make sure View Row DOM Element really exist before notifying that it's a row that is visible again
656
+ if (document.querySelector(`.${this.gridUid} .cellDetailView_${item[this._dataViewIdProperty]}`)) {
657
+ this.onRowBackToViewportRange.notify({
658
+ grid: this._grid,
659
+ item,
660
+ rowId,
661
+ rowIndex,
662
+ expandedRows: Array.from(this._expandedRowIds).map((id) => this.dataView.getItemById(id)),
663
+ rowIdsOutOfViewport: Array.from(this.syncOutOfViewportArray(rowId, false)),
664
+ }, null, this);
665
+ }
666
+ }
667
+ /**
668
+ * keep any row detail that are shown on top of the row that we're opening
669
+ * but invalidate any rows that are after the row that we're opening
670
+ */
671
+ reevaluateRenderedRowIds(item) {
672
+ // get current item row index
673
+ const rowIdx = this.dataView.getRowById(item[this._dataViewIdProperty]);
674
+ this._renderedViewportRowIds.forEach((rid) => {
675
+ const invRowIdx = this.dataView.getRowById(rid);
676
+ if (invRowIdx !== undefined && invRowIdx > rowIdx) {
677
+ this.notifyViewportChange(this.dataView.getItemById(rid), 'remove');
639
678
  }
640
- }, 100);
679
+ });
641
680
  }
642
681
  syncOutOfViewportArray(rowId, isAdding) {
643
682
  const hasRowId = this._rowIdsOutOfViewport.has(rowId);