@slickgrid-universal/row-detail-view-plugin 2.4.1 → 2.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,648 +1,652 @@
1
- /***
2
- * A plugin to add Row Detail Panel View (for example providing order detail info when clicking on the order row in the grid)
3
- * Original StackOverflow question & article making this possible (thanks to violet313)
4
- * https://stackoverflow.com/questions/10535164/can-slickgrids-row-height-be-dynamically-altered#29399927
5
- * http://violet313.org/slickgrids/#intro
6
- */
7
- export class SlickRowDetailView {
8
- /** Constructor of the SlickGrid 3rd party plugin, it can optionally receive options */
9
- constructor(pubSubService) {
10
- this.pubSubService = pubSubService;
11
- this._dataViewIdProperty = 'id';
12
- this._expandableOverride = null;
13
- this._expandedRows = [];
14
- this._gridRowBuffer = 0;
15
- this._gridUid = '';
16
- this._keyPrefix = '';
17
- this._lastRange = null;
18
- this._outsideRange = 5;
19
- this._rowIdsOutOfViewport = [];
20
- this._visibleRenderedCellCount = 0;
21
- this._defaults = {
22
- alwaysRenderColumn: true,
23
- columnId: '_detail_selector',
24
- cssClass: 'detailView-toggle',
25
- collapseAllOnSort: true,
26
- collapsedClass: null,
27
- expandedClass: null,
28
- keyPrefix: '_',
29
- loadOnce: false,
30
- maxRows: null,
31
- saveDetailViewOnScroll: true,
32
- singleRowExpand: false,
33
- useSimpleViewportCalc: false,
34
- toolTip: '',
35
- width: 30,
36
- };
37
- this.pluginName = 'RowDetailView';
38
- /** Fired when the async response finished */
39
- this.onAsyncEndUpdate = new Slick.Event();
40
- /** This event must be used with the "notify" by the end user once the Asynchronous Server call returns the item detail */
41
- this.onAsyncResponse = new Slick.Event();
42
- /** Fired after the row detail gets toggled */
43
- this.onAfterRowDetailToggle = new Slick.Event();
44
- /** Fired before the row detail gets toggled */
45
- this.onBeforeRowDetailToggle = new Slick.Event();
46
- /** Fired after the row detail gets toggled */
47
- this.onRowBackToViewportRange = new Slick.Event();
48
- /** Fired after a row becomes out of viewport range (when user can't see the row anymore) */
49
- this.onRowOutOfViewportRange = new Slick.Event();
50
- this._eventHandler = new Slick.EventHandler();
51
- }
52
- get addonOptions() {
53
- return this._addonOptions;
54
- }
55
- /** Getter of SlickGrid DataView object */
56
- get dataView() {
57
- var _a;
58
- return ((_a = this._grid) === null || _a === void 0 ? void 0 : _a.getData()) || {};
59
- }
60
- get dataViewIdProperty() {
61
- return this._dataViewIdProperty;
62
- }
63
- get eventHandler() {
64
- return this._eventHandler;
65
- }
66
- /** Getter for the Grid Options pulled through the Grid Object */
67
- get gridOptions() {
68
- var _a;
69
- return ((_a = this._grid) === null || _a === void 0 ? void 0 : _a.getOptions()) || {};
70
- }
71
- get gridUid() {
72
- var _a;
73
- return this._gridUid || (((_a = this._grid) === null || _a === void 0 ? void 0 : _a.getUID()) || '');
74
- }
75
- set lastRange(range) {
76
- this._lastRange = range;
77
- }
78
- set rowIdsOutOfViewport(rowIds) {
79
- this._rowIdsOutOfViewport = rowIds;
80
- }
81
- get visibleRenderedCellCount() {
82
- return this._visibleRenderedCellCount;
83
- }
84
- /**
85
- * Initialize the Export Service
86
- * @param _grid
87
- * @param _containerService
88
- */
89
- init(grid) {
90
- var _a;
91
- this._grid = grid;
92
- if (!grid) {
93
- throw new Error('[Slickgrid-Universal] RowDetailView Plugin requires the Grid instance to be passed as argument to the "init()" method.');
94
- }
95
- this._grid = grid;
96
- this._gridUid = grid.getUID();
97
- this._addonOptions = (this.gridOptions.rowDetailView || {});
98
- this._keyPrefix = ((_a = this._addonOptions) === null || _a === void 0 ? void 0 : _a.keyPrefix) || '_';
99
- // Update the minRowBuffer so that the view doesn't disappear when it's at top of screen + the original default 3
100
- this._gridRowBuffer = this.gridOptions.minRowBuffer || 0;
101
- this.gridOptions.minRowBuffer = this._addonOptions.panelRows + 3;
102
- this._eventHandler
103
- .subscribe(this._grid.onClick, this.handleClick.bind(this))
104
- .subscribe(this._grid.onScroll, this.handleScroll.bind(this));
105
- // Sort will, by default, Collapse all of the open items (unless user implements his own onSort which deals with open row and padding)
106
- if (this._addonOptions.collapseAllOnSort) {
107
- this._eventHandler.subscribe(this._grid.onSort, this.collapseAll.bind(this));
108
- this._expandedRows = [];
109
- this._rowIdsOutOfViewport = [];
110
- }
111
- this._eventHandler.subscribe(this.dataView.onRowCountChanged, () => {
112
- this._grid.updateRowCount();
113
- this._grid.render();
114
- });
115
- this._eventHandler.subscribe(this.dataView.onRowsChanged, (_e, args) => {
116
- this._grid.invalidateRows(args.rows);
117
- this._grid.render();
118
- });
119
- // subscribe to the onAsyncResponse so that the plugin knows when the user server side calls finished
120
- this._eventHandler.subscribe(this.onAsyncResponse, this.handleOnAsyncResponse.bind(this));
121
- // after data is set, let's get the DataView Id Property name used (defaults to "id")
122
- this._eventHandler.subscribe(this.dataView.onSetItemsCalled, () => {
123
- var _a;
124
- this._dataViewIdProperty = ((_a = this.dataView) === null || _a === void 0 ? void 0 : _a.getIdPropertyName()) || 'id';
125
- });
126
- // if we use the alternative & simpler calculation of the out of viewport range
127
- // we will need to know how many rows are rendered on the screen and we need to wait for grid to be rendered
128
- // unfortunately there is no triggered event for knowing when grid is finished, so we use 250ms delay and it's typically more than enough
129
- if (this._addonOptions.useSimpleViewportCalc) {
130
- this._eventHandler.subscribe(this._grid.onRendered, (_e, args) => {
131
- if (args === null || args === void 0 ? void 0 : args.endRow) {
132
- this._visibleRenderedCellCount = args.endRow - args.startRow;
133
- }
134
- });
135
- }
136
- }
137
- /** Dispose of the Slick Row Detail View */
138
- dispose() {
139
- var _a;
140
- (_a = this._eventHandler) === null || _a === void 0 ? void 0 : _a.unsubscribeAll();
141
- }
142
- create(columnDefinitions, gridOptions) {
143
- var _a, _b;
144
- if (!gridOptions.rowDetailView) {
145
- throw new Error('[Slickgrid-Universal] The Row Detail View requires options to be passed via the "rowDetailView" property of the Grid Options');
146
- }
147
- this._addonOptions = { ...this._defaults, ...gridOptions.rowDetailView };
148
- // user could override the expandable icon logic from within the options or after instantiating the plugin
149
- if (typeof this._addonOptions.expandableOverride === 'function') {
150
- this.expandableOverride(this._addonOptions.expandableOverride);
151
- }
152
- if (Array.isArray(columnDefinitions) && gridOptions) {
153
- const newRowDetailViewColumn = this.getColumnDefinition();
154
- // add new row detail column unless it was already added
155
- if (!columnDefinitions.some(col => col.id === newRowDetailViewColumn.id)) {
156
- const rowDetailColDef = Array.isArray(columnDefinitions) && columnDefinitions.find(col => (col === null || col === void 0 ? void 0 : col.behavior) === 'selectAndMove');
157
- const finalRowDetailViewColumn = rowDetailColDef ? rowDetailColDef : newRowDetailViewColumn;
158
- // column index position in the grid
159
- const columnPosition = (_b = (_a = gridOptions === null || gridOptions === void 0 ? void 0 : gridOptions.rowDetailView) === null || _a === void 0 ? void 0 : _a.columnIndexPosition) !== null && _b !== void 0 ? _b : 0;
160
- if (columnPosition > 0) {
161
- columnDefinitions.splice(columnPosition, 0, finalRowDetailViewColumn);
162
- }
163
- else {
164
- columnDefinitions.unshift(finalRowDetailViewColumn);
165
- }
166
- this.pubSubService.publish(`onPluginColumnsChanged`, {
167
- columns: columnDefinitions,
168
- pluginName: this.pluginName
169
- });
170
- }
171
- }
172
- return this;
173
- }
174
- /** Get current plugin options */
175
- getOptions() {
176
- return this._addonOptions;
177
- }
178
- /** set or change some of the plugin options */
179
- setOptions(options) {
180
- var _a;
181
- this._addonOptions = { ...this._addonOptions, ...options };
182
- if ((_a = this._addonOptions) === null || _a === void 0 ? void 0 : _a.singleRowExpand) {
183
- this.collapseAll();
184
- }
185
- }
186
- /** Collapse all of the open items */
187
- collapseAll() {
188
- this.dataView.beginUpdate();
189
- for (const expandedRow of this._expandedRows) {
190
- this.collapseDetailView(expandedRow, true);
191
- }
192
- this.dataView.endUpdate();
193
- }
194
- /** Colapse an Item so it is not longer seen */
195
- collapseDetailView(item, isMultipleCollapsing = false) {
196
- if (!isMultipleCollapsing) {
197
- this.dataView.beginUpdate();
198
- }
199
- // Save the details on the collapse assuming onetime loading
200
- if (this._addonOptions.loadOnce) {
201
- this.saveDetailView(item);
202
- }
203
- item[`${this._keyPrefix}collapsed`] = true;
204
- for (let idx = 1; idx <= item[`${this._keyPrefix}sizePadding`]; idx++) {
205
- this.dataView.deleteItem(`${item[this._dataViewIdProperty]}.${idx}`);
206
- }
207
- item[`${this._keyPrefix}sizePadding`] = 0;
208
- this.dataView.updateItem(item[this._dataViewIdProperty], item);
209
- // Remove the item from the expandedRows
210
- this._expandedRows = this._expandedRows.filter((expRow) => {
211
- return expRow[this._dataViewIdProperty] !== item[this._dataViewIdProperty];
212
- });
213
- if (!isMultipleCollapsing) {
214
- this.dataView.endUpdate();
215
- }
216
- }
217
- /** Expand a row given the dataview item that is to be expanded */
218
- expandDetailView(item) {
219
- var _a, _b, _c;
220
- if ((_a = this._addonOptions) === null || _a === void 0 ? void 0 : _a.singleRowExpand) {
221
- this.collapseAll();
222
- }
223
- item[`${this._keyPrefix}collapsed`] = false;
224
- this._expandedRows.push(item);
225
- // In the case something went wrong loading it the first time such a scroll of screen before loaded
226
- if (!item[`${this._keyPrefix}detailContent`]) {
227
- item[`${this._keyPrefix}detailViewLoaded`] = false;
228
- }
229
- // display pre-loading template
230
- if (!item[`${this._keyPrefix}detailViewLoaded`] || this._addonOptions.loadOnce !== true) {
231
- item[`${this._keyPrefix}detailContent`] = (_c = (_b = this._addonOptions) === null || _b === void 0 ? void 0 : _b.preTemplate) === null || _c === void 0 ? void 0 : _c.call(_b, item);
232
- }
233
- else {
234
- this.onAsyncResponse.notify({
235
- item,
236
- itemDetail: item,
237
- detailView: item[`${this._keyPrefix}detailContent`]
238
- });
239
- this.applyTemplateNewLineHeight(item);
240
- this.dataView.updateItem(item[this._dataViewIdProperty], item);
241
- return;
242
- }
243
- this.applyTemplateNewLineHeight(item);
244
- this.dataView.updateItem(item[this._dataViewIdProperty], item);
245
- // async server call
246
- this._addonOptions.process(item);
247
- }
248
- /** Saves the current state of the detail view */
249
- 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;
255
- }
256
- }
257
- }
258
- /**
259
- * subscribe to the onAsyncResponse so that the plugin knows when the user server side calls finished
260
- * the response has to be as "args.item" (or "args.itemDetail") with it's data back
261
- */
262
- handleOnAsyncResponse(_e, args) {
263
- var _a, _b, _c;
264
- if (!args || (!args.item && !args.itemDetail)) {
265
- console.error('SlickRowDetailView plugin requires the onAsyncResponse() to supply "args.item" property.');
266
- return;
267
- }
268
- // we accept item/itemDetail, just get the one which has data
269
- const itemDetail = args.item || args.itemDetail;
270
- // If we just want to load in a view directly we can use detailView property to do so
271
- itemDetail[`${this._keyPrefix}detailContent`] = (_a = args.detailView) !== null && _a !== void 0 ? _a : (_c = (_b = this._addonOptions) === null || _b === void 0 ? void 0 : _b.postTemplate) === null || _c === void 0 ? void 0 : _c.call(_b, itemDetail);
272
- itemDetail[`${this._keyPrefix}detailViewLoaded`] = true;
273
- this.dataView.updateItem(itemDetail[this._dataViewIdProperty], itemDetail);
274
- // trigger an event once the post template is finished loading
275
- this.onAsyncEndUpdate.notify({
276
- grid: this._grid,
277
- item: itemDetail,
278
- itemDetail,
279
- });
280
- }
281
- /**
282
- * TODO interface only has a GETTER not a SETTER..why?
283
- * Override the logic for showing (or not) the expand icon (use case example: only every 2nd row is expandable)
284
- * Method that user can pass to override the default behavior or making every row an expandable row.
285
- * In order word, user can choose which rows to be an available row detail (or not) by providing his own logic.
286
- * @param overrideFn: override function callback
287
- */
288
- expandableOverride(overrideFn) {
289
- this._expandableOverride = overrideFn;
290
- }
291
- getExpandableOverride() {
292
- return this._expandableOverride;
293
- }
294
- /** Get the Column Definition of the first column dedicated to toggling the Row Detail View */
295
- getColumnDefinition() {
296
- var _a, _b, _c;
297
- return {
298
- id: (_b = (_a = this._addonOptions) === null || _a === void 0 ? void 0 : _a.columnId) !== null && _b !== void 0 ? _b : '_rowDetail_',
299
- field: 'sel',
300
- name: '',
301
- alwaysRenderColumn: (_c = this._addonOptions) === null || _c === void 0 ? void 0 : _c.alwaysRenderColumn,
302
- cssClass: this._addonOptions.cssClass || '',
303
- excludeFromExport: true,
304
- excludeFromColumnPicker: true,
305
- excludeFromGridMenu: true,
306
- excludeFromQuery: true,
307
- excludeFromHeaderMenu: true,
308
- formatter: this.detailSelectionFormatter.bind(this),
309
- resizable: false,
310
- sortable: false,
311
- toolTip: this._addonOptions.toolTip,
312
- width: this._addonOptions.width,
313
- };
314
- }
315
- /** return the currently expanded rows */
316
- getExpandedRows() {
317
- return this._expandedRows;
318
- }
319
- /** return the rows that are out of the viewport */
320
- getOutOfViewportRows() {
321
- return this._rowIdsOutOfViewport;
322
- }
323
- /** Takes in the item we are filtering and if it is an expanded row returns it's parents row to filter on */
324
- getFilterItem(item) {
325
- if (item[`${this._keyPrefix}isPadding`] && item[`${this._keyPrefix}parent`]) {
326
- item = item[`${this._keyPrefix}parent`];
327
- }
328
- return item;
329
- }
330
- /** Resize the Row Detail View */
331
- resizeDetailView(item) {
332
- if (!item) {
333
- return;
334
- }
335
- // Grad each of the DOM elements
336
- const mainContainer = document.querySelector(`.${this.gridUid} .detailViewContainer_${item[this._dataViewIdProperty]}`);
337
- const cellItem = document.querySelector(`.${this.gridUid} .cellDetailView_${item[this._dataViewIdProperty]}`);
338
- const inner = document.querySelector(`.${this.gridUid} .innerDetailView_${item[this._dataViewIdProperty]}`);
339
- if (!mainContainer || !cellItem || !inner) {
340
- return;
341
- }
342
- for (let idx = 1; idx <= item[`${this._keyPrefix}sizePadding`]; idx++) {
343
- this.dataView.deleteItem(`${item[this._dataViewIdProperty]}.${idx}`);
344
- }
345
- const rowHeight = this.gridOptions.rowHeight; // height of a row
346
- const lineHeight = 13; // we know cuz we wrote the custom css init ;)
347
- // remove the height so we can calculate the height
348
- mainContainer.style.minHeight = '';
349
- // Get the scroll height for the main container so we know the actual size of the view
350
- const itemHeight = mainContainer.scrollHeight;
351
- // Now work out how many rows
352
- const rowCount = Math.ceil(itemHeight / rowHeight);
353
- item[`${this._keyPrefix}sizePadding`] = Math.ceil(((rowCount * 2) * lineHeight) / rowHeight);
354
- item[`${this._keyPrefix}height`] = itemHeight;
355
- let outterHeight = (item[`${this._keyPrefix}sizePadding`] * rowHeight);
356
- if (this._addonOptions.maxRows !== null && item[`${this._keyPrefix}sizePadding`] > this._addonOptions.maxRows) {
357
- outterHeight = this._addonOptions.maxRows * rowHeight;
358
- item[`${this._keyPrefix}sizePadding`] = this._addonOptions.maxRows;
359
- }
360
- // If the padding is now more than the original minRowBuff we need to increase it
361
- if (this.gridOptions.minRowBuffer < item[`${this._keyPrefix}sizePadding`]) {
362
- // Update the minRowBuffer so that the view doesn't disappear when it's at top of screen + the original default 3
363
- this.gridOptions.minRowBuffer = item[`${this._keyPrefix}sizePadding`] + 3;
364
- }
365
- mainContainer.setAttribute('style', `min-height: ${item[this._keyPrefix + 'height']}px`);
366
- if (cellItem) {
367
- cellItem.setAttribute('style', `height: ${outterHeight}px; top: ${rowHeight}px`);
368
- }
369
- const idxParent = this.dataView.getIdxById(item[this._dataViewIdProperty]);
370
- for (let idx = 1; idx <= item[`${this._keyPrefix}sizePadding`]; idx++) {
371
- this.dataView.insertItem(idxParent + idx, this.getPaddingItem(item, idx));
372
- }
373
- // Lastly save the updated state
374
- this.saveDetailView(item);
375
- }
376
- // --
377
- // protected functions
378
- // ------------------
379
- /**
380
- * create the detail ctr node. this belongs to the dev & can be custom-styled as per
381
- */
382
- applyTemplateNewLineHeight(item) {
383
- // the height is calculated by the template row count (how many line of items does the template view have)
384
- const rowCount = this._addonOptions.panelRows;
385
- // calculate padding requirements based on detail-content..
386
- // ie. worst-case: create an invisible dom node now & find it's height.
387
- const lineHeight = 13; // we know cuz we wrote the custom css init ;)
388
- item[`${this._keyPrefix}sizePadding`] = Math.ceil(((rowCount * 2) * lineHeight) / this.gridOptions.rowHeight);
389
- item[`${this._keyPrefix}height`] = (item[`${this._keyPrefix}sizePadding`] * this.gridOptions.rowHeight);
390
- const idxParent = this.dataView.getIdxById(item[this._dataViewIdProperty]);
391
- for (let idx = 1; idx <= item[`${this._keyPrefix}sizePadding`]; idx++) {
392
- this.dataView.insertItem((idxParent || 0) + idx, this.getPaddingItem(item, idx));
393
- }
394
- }
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.length > 0) {
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
- }
415
- }
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.some(rowId => rowId === 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.some(rowId => rowId === 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
- }
472
- }
473
- checkExpandableOverride(row, dataContext, grid) {
474
- if (typeof this._expandableOverride === 'function') {
475
- return this._expandableOverride(row, dataContext, grid);
476
- }
477
- return true;
478
- }
479
- checkIsRowOutOfViewportRange(rowIndex, renderedRange) {
480
- return (Math.abs(renderedRange.bottom - this._gridRowBuffer - rowIndex) > this._visibleRenderedCellCount * 2);
481
- }
482
- /** Get the Row Detail padding (which are the rows dedicated to the detail panel) */
483
- getPaddingItem(parent, offset) {
484
- const item = {};
485
- for (const prop in this.dataView) {
486
- if (prop) {
487
- item[prop] = null;
488
- }
489
- }
490
- item[this._dataViewIdProperty] = `${parent[this._dataViewIdProperty]}.${offset}`;
491
- // additional hidden padding metadata fields
492
- item[`${this._keyPrefix}collapsed`] = true;
493
- item[`${this._keyPrefix}isPadding`] = true;
494
- item[`${this._keyPrefix}parent`] = parent;
495
- item[`${this._keyPrefix}offset`] = offset;
496
- return item;
497
- }
498
- /** The Formatter of the toggling icon of the Row Detail */
499
- detailSelectionFormatter(row, cell, value, columnDef, dataContext, grid) {
500
- if (!this.checkExpandableOverride(row, dataContext, grid)) {
501
- return '';
502
- }
503
- else {
504
- if (dataContext[`${this._keyPrefix}collapsed`] === undefined) {
505
- dataContext[`${this._keyPrefix}collapsed`] = true;
506
- dataContext[`${this._keyPrefix}sizePadding`] = 0; // the required number of pading rows
507
- dataContext[`${this._keyPrefix}height`] = 0; // the actual height in pixels of the detail field
508
- dataContext[`${this._keyPrefix}isPadding`] = false;
509
- dataContext[`${this._keyPrefix}parent`] = undefined;
510
- dataContext[`${this._keyPrefix}offset`] = 0;
511
- }
512
- if (dataContext[`${this._keyPrefix}isPadding`]) {
513
- // render nothing
514
- }
515
- else if (dataContext[`${this._keyPrefix}collapsed`]) {
516
- let collapsedClasses = `${this._addonOptions.cssClass || ''} expand `;
517
- if (this._addonOptions.collapsedClass) {
518
- collapsedClasses += this._addonOptions.collapsedClass;
519
- }
520
- return `<div class="${collapsedClasses.trim()}"></div>`;
521
- }
522
- else {
523
- const html = [];
524
- const rowHeight = this.gridOptions.rowHeight || 0;
525
- let outterHeight = (dataContext[`${this._keyPrefix}sizePadding`] || 0) * this.gridOptions.rowHeight;
526
- if (this._addonOptions.maxRows !== null && ((dataContext[`${this._keyPrefix}sizePadding`] || 0) > this._addonOptions.maxRows)) {
527
- outterHeight = this._addonOptions.maxRows * rowHeight;
528
- dataContext[`${this._keyPrefix}sizePadding`] = this._addonOptions.maxRows;
529
- }
530
- // V313HAX:
531
- // putting in an extra closing div after the closing toggle div and ommiting a
532
- // final closing div for the detail ctr div causes the slickgrid renderer to
533
- // insert our detail div as a new column ;) ~since it wraps whatever we provide
534
- // in a generic div column container. so our detail becomes a child directly of
535
- // the row not the cell. nice =) ~no need to apply a css change to the parent
536
- // slick-cell to escape the cell overflow clipping.
537
- // sneaky extra </div> inserted here-----------------v
538
- let expandedClasses = `${this._addonOptions.cssClass || ''} collapse `;
539
- if (this._addonOptions.expandedClass) {
540
- expandedClasses += this._addonOptions.expandedClass;
541
- }
542
- html.push(`<div class="${expandedClasses.trim()}"></div></div>`);
543
- html.push(`<div class="dynamic-cell-detail cellDetailView_${dataContext[this._dataViewIdProperty]}" `); // apply custom css to detail
544
- html.push(`style="height: ${outterHeight}px;`); // set total height of padding
545
- html.push(`top: ${rowHeight}px">`); // shift detail below 1st row
546
- html.push(`<div class="detail-container detailViewContainer_${dataContext[this._dataViewIdProperty]}">`); // sub ctr for custom styling
547
- html.push(`<div class="innerDetailView_${dataContext[this._dataViewIdProperty]}">${dataContext[`${this._keyPrefix}detailContent`]}</div></div>`);
548
- // omit a final closing detail container </div> that would come next
549
- return html.join('');
550
- }
551
- }
552
- return '';
553
- }
554
- /** When row is getting toggled, we will handle the action of collapsing/expanding */
555
- handleAccordionShowHide(item) {
556
- if (item) {
557
- if (!item[`${this._keyPrefix}collapsed`]) {
558
- this.collapseDetailView(item);
559
- }
560
- else {
561
- this.expandDetailView(item);
562
- }
563
- }
564
- }
565
- /** Handle mouse click event */
566
- handleClick(e, args) {
567
- const dataContext = this._grid.getDataItem(args.row);
568
- if (this.checkExpandableOverride(args.row, dataContext, this._grid)) {
569
- // clicking on a row select checkbox
570
- const columnDef = this._grid.getColumns()[args.cell];
571
- if (this._addonOptions.useRowClick || (columnDef.id === this._addonOptions.columnId && e.target.classList.contains(this._addonOptions.cssClass || ''))) {
572
- // if editing, try to commit
573
- if (this._grid.getEditorLock().isActive() && !this._grid.getEditorLock().commitCurrentEdit()) {
574
- e.preventDefault();
575
- e.stopImmediatePropagation();
576
- return;
577
- }
578
- // trigger an event before toggling
579
- this.onBeforeRowDetailToggle.notify({
580
- grid: this._grid,
581
- item: dataContext
582
- });
583
- this.toggleRowSelection(args.row, dataContext);
584
- // trigger an event after toggling
585
- this.onAfterRowDetailToggle.notify({
586
- grid: this._grid,
587
- item: dataContext,
588
- expandedRows: this._expandedRows,
589
- });
590
- e.stopPropagation();
591
- e.stopImmediatePropagation();
592
- }
593
- }
594
- }
595
- handleScroll() {
596
- if (this._addonOptions.useSimpleViewportCalc) {
597
- this.calculateOutOfRangeViewsSimplerVersion();
598
- }
599
- else {
600
- this.calculateOutOfRangeViews();
601
- }
602
- }
603
- notifyOutOfViewport(item, rowId) {
604
- const rowIndex = item.rowIndex || this.dataView.getRowById(item[this._dataViewIdProperty]);
605
- this.onRowOutOfViewportRange.notify({
606
- grid: this._grid,
607
- item,
608
- rowId,
609
- rowIndex,
610
- expandedRows: this._expandedRows,
611
- rowIdsOutOfViewport: this.syncOutOfViewportArray(rowId, true)
612
- });
613
- }
614
- notifyBackToViewportWhenDomExist(item, rowId) {
615
- const rowIndex = item.rowIndex || this.dataView.getRowById(item[this._dataViewIdProperty]);
616
- setTimeout(() => {
617
- // make sure View Row DOM Element really exist before notifying that it's a row that is visible again
618
- if (document.querySelector(`.${this.gridUid} .cellDetailView_${item[this._dataViewIdProperty]}`)) {
619
- this.onRowBackToViewportRange.notify({
620
- grid: this._grid,
621
- item,
622
- rowId,
623
- rowIndex,
624
- expandedRows: this._expandedRows,
625
- rowIdsOutOfViewport: this.syncOutOfViewportArray(rowId, false)
626
- });
627
- }
628
- }, 100);
629
- }
630
- syncOutOfViewportArray(rowId, isAdding) {
631
- const arrayRowIndex = this._rowIdsOutOfViewport.findIndex(outOfViewportRowId => outOfViewportRowId === rowId);
632
- if (isAdding && arrayRowIndex < 0) {
633
- this._rowIdsOutOfViewport.push(rowId);
634
- }
635
- else if (!isAdding && arrayRowIndex >= 0) {
636
- this._rowIdsOutOfViewport.splice(arrayRowIndex, 1);
637
- }
638
- return this._rowIdsOutOfViewport;
639
- }
640
- toggleRowSelection(rowNumber, dataContext) {
641
- if (this.checkExpandableOverride(rowNumber, dataContext, this._grid)) {
642
- this.dataView.beginUpdate();
643
- this.handleAccordionShowHide(dataContext);
644
- this.dataView.endUpdate();
645
- }
646
- }
647
- }
1
+ import { objectAssignAndExtend } from '@slickgrid-universal/utils';
2
+ /***
3
+ * A plugin to add Row Detail Panel View (for example providing order detail info when clicking on the order row in the grid)
4
+ * Original StackOverflow question & article making this possible (thanks to violet313)
5
+ * https://stackoverflow.com/questions/10535164/can-slickgrids-row-height-be-dynamically-altered#29399927
6
+ * http://violet313.org/slickgrids/#intro
7
+ */
8
+ export class SlickRowDetailView {
9
+ /** Constructor of the SlickGrid 3rd party plugin, it can optionally receive options */
10
+ constructor(pubSubService) {
11
+ this.pubSubService = pubSubService;
12
+ this._dataViewIdProperty = 'id';
13
+ this._expandableOverride = null;
14
+ this._expandedRows = [];
15
+ this._gridRowBuffer = 0;
16
+ this._gridUid = '';
17
+ this._keyPrefix = '';
18
+ this._lastRange = null;
19
+ this._outsideRange = 5;
20
+ this._rowIdsOutOfViewport = [];
21
+ this._visibleRenderedCellCount = 0;
22
+ this._defaults = {
23
+ alwaysRenderColumn: true,
24
+ columnId: '_detail_selector',
25
+ cssClass: 'detailView-toggle',
26
+ collapseAllOnSort: true,
27
+ collapsedClass: null,
28
+ expandedClass: null,
29
+ keyPrefix: '_',
30
+ loadOnce: false,
31
+ maxRows: null,
32
+ saveDetailViewOnScroll: true,
33
+ singleRowExpand: false,
34
+ useSimpleViewportCalc: false,
35
+ toolTip: '',
36
+ width: 30,
37
+ };
38
+ this.pluginName = 'RowDetailView';
39
+ /** Fired when the async response finished */
40
+ this.onAsyncEndUpdate = new Slick.Event();
41
+ /** This event must be used with the "notify" by the end user once the Asynchronous Server call returns the item detail */
42
+ this.onAsyncResponse = new Slick.Event();
43
+ /** Fired after the row detail gets toggled */
44
+ this.onAfterRowDetailToggle = new Slick.Event();
45
+ /** Fired before the row detail gets toggled */
46
+ this.onBeforeRowDetailToggle = new Slick.Event();
47
+ /** Fired after the row detail gets toggled */
48
+ this.onRowBackToViewportRange = new Slick.Event();
49
+ /** Fired after a row becomes out of viewport range (when user can't see the row anymore) */
50
+ this.onRowOutOfViewportRange = new Slick.Event();
51
+ this._eventHandler = new Slick.EventHandler();
52
+ }
53
+ get addonOptions() {
54
+ return this._addonOptions;
55
+ }
56
+ /** Getter of SlickGrid DataView object */
57
+ get dataView() {
58
+ var _a;
59
+ return ((_a = this._grid) === null || _a === void 0 ? void 0 : _a.getData()) || {};
60
+ }
61
+ get dataViewIdProperty() {
62
+ return this._dataViewIdProperty;
63
+ }
64
+ get eventHandler() {
65
+ return this._eventHandler;
66
+ }
67
+ /** Getter for the Grid Options pulled through the Grid Object */
68
+ get gridOptions() {
69
+ var _a;
70
+ return ((_a = this._grid) === null || _a === void 0 ? void 0 : _a.getOptions()) || {};
71
+ }
72
+ get gridUid() {
73
+ var _a;
74
+ return this._gridUid || (((_a = this._grid) === null || _a === void 0 ? void 0 : _a.getUID()) || '');
75
+ }
76
+ set lastRange(range) {
77
+ this._lastRange = range;
78
+ }
79
+ set rowIdsOutOfViewport(rowIds) {
80
+ this._rowIdsOutOfViewport = rowIds;
81
+ }
82
+ get visibleRenderedCellCount() {
83
+ return this._visibleRenderedCellCount;
84
+ }
85
+ /**
86
+ * Initialize the Export Service
87
+ * @param _grid
88
+ * @param _containerService
89
+ */
90
+ init(grid) {
91
+ var _a;
92
+ this._grid = grid;
93
+ if (!grid) {
94
+ throw new Error('[Slickgrid-Universal] RowDetailView Plugin requires the Grid instance to be passed as argument to the "init()" method.');
95
+ }
96
+ this._grid = grid;
97
+ this._gridUid = grid.getUID();
98
+ if (!this._addonOptions) {
99
+ this._addonOptions = objectAssignAndExtend(this.gridOptions.rowDetailView, this._defaults);
100
+ }
101
+ this._keyPrefix = ((_a = this._addonOptions) === null || _a === void 0 ? void 0 : _a.keyPrefix) || '_';
102
+ // Update the minRowBuffer so that the view doesn't disappear when it's at top of screen + the original default 3
103
+ this._gridRowBuffer = this.gridOptions.minRowBuffer || 0;
104
+ this.gridOptions.minRowBuffer = this._addonOptions.panelRows + 3;
105
+ this._eventHandler
106
+ .subscribe(this._grid.onClick, this.handleClick.bind(this))
107
+ .subscribe(this._grid.onBeforeEditCell, () => this.collapseAll())
108
+ .subscribe(this._grid.onScroll, this.handleScroll.bind(this));
109
+ // Sort will, by default, Collapse all of the open items (unless user implements his own onSort which deals with open row and padding)
110
+ if (this._addonOptions.collapseAllOnSort) {
111
+ this._eventHandler.subscribe(this._grid.onSort, this.collapseAll.bind(this));
112
+ this._expandedRows = [];
113
+ this._rowIdsOutOfViewport = [];
114
+ }
115
+ this._eventHandler.subscribe(this.dataView.onRowCountChanged, () => {
116
+ this._grid.updateRowCount();
117
+ this._grid.render();
118
+ });
119
+ this._eventHandler.subscribe(this.dataView.onRowsChanged, (_e, args) => {
120
+ this._grid.invalidateRows(args.rows);
121
+ this._grid.render();
122
+ });
123
+ // subscribe to the onAsyncResponse so that the plugin knows when the user server side calls finished
124
+ this._eventHandler.subscribe(this.onAsyncResponse, this.handleOnAsyncResponse.bind(this));
125
+ // after data is set, let's get the DataView Id Property name used (defaults to "id")
126
+ this._eventHandler.subscribe(this.dataView.onSetItemsCalled, () => {
127
+ var _a;
128
+ this._dataViewIdProperty = ((_a = this.dataView) === null || _a === void 0 ? void 0 : _a.getIdPropertyName()) || 'id';
129
+ });
130
+ // if we use the alternative & simpler calculation of the out of viewport range
131
+ // we will need to know how many rows are rendered on the screen and we need to wait for grid to be rendered
132
+ // unfortunately there is no triggered event for knowing when grid is finished, so we use 250ms delay and it's typically more than enough
133
+ if (this._addonOptions.useSimpleViewportCalc) {
134
+ this._eventHandler.subscribe(this._grid.onRendered, (_e, args) => {
135
+ if (args === null || args === void 0 ? void 0 : args.endRow) {
136
+ this._visibleRenderedCellCount = args.endRow - args.startRow;
137
+ }
138
+ });
139
+ }
140
+ }
141
+ /** Dispose of the Slick Row Detail View */
142
+ dispose() {
143
+ var _a;
144
+ (_a = this._eventHandler) === null || _a === void 0 ? void 0 : _a.unsubscribeAll();
145
+ }
146
+ create(columnDefinitions, gridOptions) {
147
+ var _a, _b;
148
+ if (!gridOptions.rowDetailView) {
149
+ throw new Error('[Slickgrid-Universal] The Row Detail View requires options to be passed via the "rowDetailView" property of the Grid Options');
150
+ }
151
+ this._addonOptions = objectAssignAndExtend(gridOptions.rowDetailView, this._defaults);
152
+ // user could override the expandable icon logic from within the options or after instantiating the plugin
153
+ if (typeof this._addonOptions.expandableOverride === 'function') {
154
+ this.expandableOverride(this._addonOptions.expandableOverride);
155
+ }
156
+ if (Array.isArray(columnDefinitions) && gridOptions) {
157
+ const newRowDetailViewColumn = this.getColumnDefinition();
158
+ // add new row detail column unless it was already added
159
+ if (!columnDefinitions.some(col => col.id === newRowDetailViewColumn.id)) {
160
+ const rowDetailColDef = Array.isArray(columnDefinitions) && columnDefinitions.find(col => (col === null || col === void 0 ? void 0 : col.behavior) === 'selectAndMove');
161
+ const finalRowDetailViewColumn = rowDetailColDef ? rowDetailColDef : newRowDetailViewColumn;
162
+ // column index position in the grid
163
+ const columnPosition = (_b = (_a = gridOptions === null || gridOptions === void 0 ? void 0 : gridOptions.rowDetailView) === null || _a === void 0 ? void 0 : _a.columnIndexPosition) !== null && _b !== void 0 ? _b : 0;
164
+ if (columnPosition > 0) {
165
+ columnDefinitions.splice(columnPosition, 0, finalRowDetailViewColumn);
166
+ }
167
+ else {
168
+ columnDefinitions.unshift(finalRowDetailViewColumn);
169
+ }
170
+ this.pubSubService.publish(`onPluginColumnsChanged`, {
171
+ columns: columnDefinitions,
172
+ pluginName: this.pluginName
173
+ });
174
+ }
175
+ }
176
+ return this;
177
+ }
178
+ /** Get current plugin options */
179
+ getOptions() {
180
+ return this._addonOptions;
181
+ }
182
+ /** set or change some of the plugin options */
183
+ setOptions(options) {
184
+ var _a;
185
+ this._addonOptions = objectAssignAndExtend(options, this._addonOptions);
186
+ if ((_a = this._addonOptions) === null || _a === void 0 ? void 0 : _a.singleRowExpand) {
187
+ this.collapseAll();
188
+ }
189
+ }
190
+ /** Collapse all of the open items */
191
+ collapseAll() {
192
+ this.dataView.beginUpdate();
193
+ for (const expandedRow of this._expandedRows) {
194
+ this.collapseDetailView(expandedRow, true);
195
+ }
196
+ this.dataView.endUpdate();
197
+ }
198
+ /** Colapse an Item so it is not longer seen */
199
+ collapseDetailView(item, isMultipleCollapsing = false) {
200
+ if (!isMultipleCollapsing) {
201
+ this.dataView.beginUpdate();
202
+ }
203
+ // Save the details on the collapse assuming onetime loading
204
+ if (this._addonOptions.loadOnce) {
205
+ this.saveDetailView(item);
206
+ }
207
+ item[`${this._keyPrefix}collapsed`] = true;
208
+ for (let idx = 1; idx <= item[`${this._keyPrefix}sizePadding`]; idx++) {
209
+ this.dataView.deleteItem(`${item[this._dataViewIdProperty]}.${idx}`);
210
+ }
211
+ item[`${this._keyPrefix}sizePadding`] = 0;
212
+ this.dataView.updateItem(item[this._dataViewIdProperty], item);
213
+ // Remove the item from the expandedRows
214
+ this._expandedRows = this._expandedRows.filter((expRow) => {
215
+ return expRow[this._dataViewIdProperty] !== item[this._dataViewIdProperty];
216
+ });
217
+ if (!isMultipleCollapsing) {
218
+ this.dataView.endUpdate();
219
+ }
220
+ }
221
+ /** Expand a row given the dataview item that is to be expanded */
222
+ expandDetailView(item) {
223
+ var _a, _b, _c;
224
+ if ((_a = this._addonOptions) === null || _a === void 0 ? void 0 : _a.singleRowExpand) {
225
+ this.collapseAll();
226
+ }
227
+ item[`${this._keyPrefix}collapsed`] = false;
228
+ this._expandedRows.push(item);
229
+ // In the case something went wrong loading it the first time such a scroll of screen before loaded
230
+ if (!item[`${this._keyPrefix}detailContent`]) {
231
+ item[`${this._keyPrefix}detailViewLoaded`] = false;
232
+ }
233
+ // display pre-loading template
234
+ if (!item[`${this._keyPrefix}detailViewLoaded`] || this._addonOptions.loadOnce !== true) {
235
+ item[`${this._keyPrefix}detailContent`] = (_c = (_b = this._addonOptions) === null || _b === void 0 ? void 0 : _b.preTemplate) === null || _c === void 0 ? void 0 : _c.call(_b, item);
236
+ }
237
+ else {
238
+ this.onAsyncResponse.notify({
239
+ item,
240
+ itemDetail: item,
241
+ detailView: item[`${this._keyPrefix}detailContent`]
242
+ });
243
+ this.applyTemplateNewLineHeight(item);
244
+ this.dataView.updateItem(item[this._dataViewIdProperty], item);
245
+ return;
246
+ }
247
+ this.applyTemplateNewLineHeight(item);
248
+ this.dataView.updateItem(item[this._dataViewIdProperty], item);
249
+ // async server call
250
+ this._addonOptions.process(item);
251
+ }
252
+ /** Saves the current state of the detail view */
253
+ saveDetailView(item) {
254
+ const view = document.querySelector(`.${this.gridUid} .innerDetailView_${item[this._dataViewIdProperty]}`);
255
+ if (view) {
256
+ const html = view.innerHTML;
257
+ if (html !== undefined) {
258
+ item[`${this._keyPrefix}detailContent`] = html;
259
+ }
260
+ }
261
+ }
262
+ /**
263
+ * subscribe to the onAsyncResponse so that the plugin knows when the user server side calls finished
264
+ * the response has to be as "args.item" (or "args.itemDetail") with it's data back
265
+ */
266
+ handleOnAsyncResponse(_e, args) {
267
+ var _a, _b, _c;
268
+ if (!args || (!args.item && !args.itemDetail)) {
269
+ console.error('SlickRowDetailView plugin requires the onAsyncResponse() to supply "args.item" property.');
270
+ return;
271
+ }
272
+ // we accept item/itemDetail, just get the one which has data
273
+ const itemDetail = args.item || args.itemDetail;
274
+ // If we just want to load in a view directly we can use detailView property to do so
275
+ itemDetail[`${this._keyPrefix}detailContent`] = (_a = args.detailView) !== null && _a !== void 0 ? _a : (_c = (_b = this._addonOptions) === null || _b === void 0 ? void 0 : _b.postTemplate) === null || _c === void 0 ? void 0 : _c.call(_b, itemDetail);
276
+ itemDetail[`${this._keyPrefix}detailViewLoaded`] = true;
277
+ this.dataView.updateItem(itemDetail[this._dataViewIdProperty], itemDetail);
278
+ // trigger an event once the post template is finished loading
279
+ this.onAsyncEndUpdate.notify({
280
+ grid: this._grid,
281
+ item: itemDetail,
282
+ itemDetail,
283
+ });
284
+ }
285
+ /**
286
+ * TODO interface only has a GETTER not a SETTER..why?
287
+ * Override the logic for showing (or not) the expand icon (use case example: only every 2nd row is expandable)
288
+ * Method that user can pass to override the default behavior or making every row an expandable row.
289
+ * In order word, user can choose which rows to be an available row detail (or not) by providing his own logic.
290
+ * @param overrideFn: override function callback
291
+ */
292
+ expandableOverride(overrideFn) {
293
+ this._expandableOverride = overrideFn;
294
+ }
295
+ getExpandableOverride() {
296
+ return this._expandableOverride;
297
+ }
298
+ /** Get the Column Definition of the first column dedicated to toggling the Row Detail View */
299
+ getColumnDefinition() {
300
+ var _a, _b, _c;
301
+ return {
302
+ id: (_b = (_a = this._addonOptions) === null || _a === void 0 ? void 0 : _a.columnId) !== null && _b !== void 0 ? _b : this._defaults.columnId,
303
+ field: 'sel',
304
+ name: '',
305
+ alwaysRenderColumn: (_c = this._addonOptions) === null || _c === void 0 ? void 0 : _c.alwaysRenderColumn,
306
+ cssClass: this._addonOptions.cssClass || '',
307
+ excludeFromExport: true,
308
+ excludeFromColumnPicker: true,
309
+ excludeFromGridMenu: true,
310
+ excludeFromQuery: true,
311
+ excludeFromHeaderMenu: true,
312
+ formatter: this.detailSelectionFormatter.bind(this),
313
+ resizable: false,
314
+ sortable: false,
315
+ toolTip: this._addonOptions.toolTip,
316
+ width: this._addonOptions.width,
317
+ };
318
+ }
319
+ /** return the currently expanded rows */
320
+ getExpandedRows() {
321
+ return this._expandedRows;
322
+ }
323
+ /** return the rows that are out of the viewport */
324
+ getOutOfViewportRows() {
325
+ return this._rowIdsOutOfViewport;
326
+ }
327
+ /** Takes in the item we are filtering and if it is an expanded row returns it's parents row to filter on */
328
+ getFilterItem(item) {
329
+ if (item[`${this._keyPrefix}isPadding`] && item[`${this._keyPrefix}parent`]) {
330
+ item = item[`${this._keyPrefix}parent`];
331
+ }
332
+ return item;
333
+ }
334
+ /** Resize the Row Detail View */
335
+ resizeDetailView(item) {
336
+ if (!item) {
337
+ return;
338
+ }
339
+ // Grad each of the DOM elements
340
+ const mainContainer = document.querySelector(`.${this.gridUid} .detailViewContainer_${item[this._dataViewIdProperty]}`);
341
+ const cellItem = document.querySelector(`.${this.gridUid} .cellDetailView_${item[this._dataViewIdProperty]}`);
342
+ const inner = document.querySelector(`.${this.gridUid} .innerDetailView_${item[this._dataViewIdProperty]}`);
343
+ if (!mainContainer || !cellItem || !inner) {
344
+ return;
345
+ }
346
+ for (let idx = 1; idx <= item[`${this._keyPrefix}sizePadding`]; idx++) {
347
+ this.dataView.deleteItem(`${item[this._dataViewIdProperty]}.${idx}`);
348
+ }
349
+ const rowHeight = this.gridOptions.rowHeight; // height of a row
350
+ const lineHeight = 13; // we know cuz we wrote the custom css init ;)
351
+ // remove the height so we can calculate the height
352
+ mainContainer.style.minHeight = '';
353
+ // Get the scroll height for the main container so we know the actual size of the view
354
+ const itemHeight = mainContainer.scrollHeight;
355
+ // Now work out how many rows
356
+ const rowCount = Math.ceil(itemHeight / rowHeight);
357
+ item[`${this._keyPrefix}sizePadding`] = Math.ceil(((rowCount * 2) * lineHeight) / rowHeight);
358
+ item[`${this._keyPrefix}height`] = itemHeight;
359
+ let outterHeight = (item[`${this._keyPrefix}sizePadding`] * rowHeight);
360
+ if (this._addonOptions.maxRows !== null && item[`${this._keyPrefix}sizePadding`] > this._addonOptions.maxRows) {
361
+ outterHeight = this._addonOptions.maxRows * rowHeight;
362
+ item[`${this._keyPrefix}sizePadding`] = this._addonOptions.maxRows;
363
+ }
364
+ // If the padding is now more than the original minRowBuff we need to increase it
365
+ if (this.gridOptions.minRowBuffer < item[`${this._keyPrefix}sizePadding`]) {
366
+ // Update the minRowBuffer so that the view doesn't disappear when it's at top of screen + the original default 3
367
+ this.gridOptions.minRowBuffer = item[`${this._keyPrefix}sizePadding`] + 3;
368
+ }
369
+ mainContainer.setAttribute('style', `min-height: ${item[this._keyPrefix + 'height']}px`);
370
+ if (cellItem) {
371
+ cellItem.setAttribute('style', `height: ${outterHeight}px; top: ${rowHeight}px`);
372
+ }
373
+ const idxParent = this.dataView.getIdxById(item[this._dataViewIdProperty]);
374
+ for (let idx = 1; idx <= item[`${this._keyPrefix}sizePadding`]; idx++) {
375
+ this.dataView.insertItem(idxParent + idx, this.getPaddingItem(item, idx));
376
+ }
377
+ // Lastly save the updated state
378
+ this.saveDetailView(item);
379
+ }
380
+ // --
381
+ // protected functions
382
+ // ------------------
383
+ /**
384
+ * create the detail ctr node. this belongs to the dev & can be custom-styled as per
385
+ */
386
+ applyTemplateNewLineHeight(item) {
387
+ // the height is calculated by the template row count (how many line of items does the template view have)
388
+ const rowCount = this._addonOptions.panelRows;
389
+ // calculate padding requirements based on detail-content..
390
+ // ie. worst-case: create an invisible dom node now & find it's height.
391
+ const lineHeight = 13; // we know cuz we wrote the custom css init ;)
392
+ item[`${this._keyPrefix}sizePadding`] = Math.ceil(((rowCount * 2) * lineHeight) / this.gridOptions.rowHeight);
393
+ item[`${this._keyPrefix}height`] = (item[`${this._keyPrefix}sizePadding`] * this.gridOptions.rowHeight);
394
+ const idxParent = this.dataView.getIdxById(item[this._dataViewIdProperty]);
395
+ for (let idx = 1; idx <= item[`${this._keyPrefix}sizePadding`]; idx++) {
396
+ this.dataView.insertItem((idxParent || 0) + idx, this.getPaddingItem(item, idx));
397
+ }
398
+ }
399
+ calculateOutOfRangeViews() {
400
+ if (this._grid) {
401
+ let scrollDir;
402
+ const renderedRange = this._grid.getRenderedRange();
403
+ // Only check if we have expanded rows
404
+ if (this._expandedRows.length > 0) {
405
+ // Assume scroll direction is down by default.
406
+ scrollDir = 'DOWN';
407
+ if (this._lastRange) {
408
+ // Some scrolling isn't anything as the range is the same
409
+ if (this._lastRange.top === renderedRange.top && this._lastRange.bottom === renderedRange.bottom) {
410
+ return;
411
+ }
412
+ // If our new top is smaller we are scrolling up
413
+ if (this._lastRange.top > renderedRange.top ||
414
+ // Or we are at very top but our bottom is increasing
415
+ (this._lastRange.top === 0 && renderedRange.top === 0 && (this._lastRange.bottom > renderedRange.bottom))) {
416
+ scrollDir = 'UP';
417
+ }
418
+ }
419
+ }
420
+ this._expandedRows.forEach((row) => {
421
+ const rowIndex = this.dataView.getRowById(row[this._dataViewIdProperty]);
422
+ const rowPadding = row[`${this._keyPrefix}sizePadding`];
423
+ const isRowOutOfRange = this._rowIdsOutOfViewport.some(rowId => rowId === row[this._dataViewIdProperty]);
424
+ if (scrollDir === 'UP') {
425
+ // save the view when asked
426
+ if (this._addonOptions.saveDetailViewOnScroll) {
427
+ // If the bottom item within buffer range is an expanded row save it.
428
+ if (rowIndex >= renderedRange.bottom - this._gridRowBuffer) {
429
+ this.saveDetailView(row);
430
+ }
431
+ }
432
+ // If the row expanded area is within the buffer notify that it is back in range
433
+ if (isRowOutOfRange && ((rowIndex - this._outsideRange) < renderedRange.top) && (rowIndex >= renderedRange.top)) {
434
+ this.notifyBackToViewportWhenDomExist(row, row[this._dataViewIdProperty]);
435
+ }
436
+ else if (!isRowOutOfRange && ((rowIndex + rowPadding) > renderedRange.bottom)) {
437
+ // if our first expanded row is about to go off the bottom
438
+ this.notifyOutOfViewport(row, row[this._dataViewIdProperty]);
439
+ }
440
+ }
441
+ else if (scrollDir === 'DOWN') {
442
+ // save the view when asked
443
+ if (this._addonOptions.saveDetailViewOnScroll) {
444
+ // If the top item within buffer range is an expanded row save it.
445
+ if (rowIndex <= renderedRange.top + this._gridRowBuffer) {
446
+ this.saveDetailView(row);
447
+ }
448
+ }
449
+ // 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
450
+ if (isRowOutOfRange && ((rowIndex + rowPadding + this._outsideRange) > renderedRange.bottom) && (rowIndex < (rowIndex + rowPadding))) {
451
+ this.notifyBackToViewportWhenDomExist(row, row[this._dataViewIdProperty]);
452
+ }
453
+ else if (!isRowOutOfRange && (rowIndex < renderedRange.top)) {
454
+ // if our row is outside top of and the buffering zone but not in the array of outOfVisable range notify it
455
+ this.notifyOutOfViewport(row, row[this._dataViewIdProperty]);
456
+ }
457
+ }
458
+ });
459
+ this._lastRange = renderedRange;
460
+ }
461
+ }
462
+ calculateOutOfRangeViewsSimplerVersion() {
463
+ if (this._grid) {
464
+ const renderedRange = this._grid.getRenderedRange();
465
+ this._expandedRows.forEach((row) => {
466
+ const rowIndex = this.dataView.getRowById(row[this._dataViewIdProperty]);
467
+ const isOutOfVisibility = this.checkIsRowOutOfViewportRange(rowIndex, renderedRange);
468
+ if (!isOutOfVisibility && this._rowIdsOutOfViewport.some(rowId => rowId === row[this._dataViewIdProperty])) {
469
+ this.notifyBackToViewportWhenDomExist(row, row[this._dataViewIdProperty]);
470
+ }
471
+ else if (isOutOfVisibility) {
472
+ this.notifyOutOfViewport(row, row[this._dataViewIdProperty]);
473
+ }
474
+ });
475
+ }
476
+ }
477
+ checkExpandableOverride(row, dataContext, grid) {
478
+ if (typeof this._expandableOverride === 'function') {
479
+ return this._expandableOverride(row, dataContext, grid);
480
+ }
481
+ return true;
482
+ }
483
+ checkIsRowOutOfViewportRange(rowIndex, renderedRange) {
484
+ return (Math.abs(renderedRange.bottom - this._gridRowBuffer - rowIndex) > this._visibleRenderedCellCount * 2);
485
+ }
486
+ /** Get the Row Detail padding (which are the rows dedicated to the detail panel) */
487
+ getPaddingItem(parent, offset) {
488
+ const item = {};
489
+ for (const prop in this.dataView) {
490
+ if (prop) {
491
+ item[prop] = null;
492
+ }
493
+ }
494
+ item[this._dataViewIdProperty] = `${parent[this._dataViewIdProperty]}.${offset}`;
495
+ // additional hidden padding metadata fields
496
+ item[`${this._keyPrefix}collapsed`] = true;
497
+ item[`${this._keyPrefix}isPadding`] = true;
498
+ item[`${this._keyPrefix}parent`] = parent;
499
+ item[`${this._keyPrefix}offset`] = offset;
500
+ return item;
501
+ }
502
+ /** The Formatter of the toggling icon of the Row Detail */
503
+ detailSelectionFormatter(row, cell, value, columnDef, dataContext, grid) {
504
+ if (!this.checkExpandableOverride(row, dataContext, grid)) {
505
+ return '';
506
+ }
507
+ else {
508
+ if (dataContext[`${this._keyPrefix}collapsed`] === undefined) {
509
+ dataContext[`${this._keyPrefix}collapsed`] = true;
510
+ dataContext[`${this._keyPrefix}sizePadding`] = 0; // the required number of pading rows
511
+ dataContext[`${this._keyPrefix}height`] = 0; // the actual height in pixels of the detail field
512
+ dataContext[`${this._keyPrefix}isPadding`] = false;
513
+ dataContext[`${this._keyPrefix}parent`] = undefined;
514
+ dataContext[`${this._keyPrefix}offset`] = 0;
515
+ }
516
+ if (dataContext[`${this._keyPrefix}isPadding`]) {
517
+ // render nothing
518
+ }
519
+ else if (dataContext[`${this._keyPrefix}collapsed`]) {
520
+ let collapsedClasses = `${this._addonOptions.cssClass || ''} expand `;
521
+ if (this._addonOptions.collapsedClass) {
522
+ collapsedClasses += this._addonOptions.collapsedClass;
523
+ }
524
+ return `<div class="${collapsedClasses.trim()}"></div>`;
525
+ }
526
+ else {
527
+ const html = [];
528
+ const rowHeight = this.gridOptions.rowHeight || 0;
529
+ let outterHeight = (dataContext[`${this._keyPrefix}sizePadding`] || 0) * this.gridOptions.rowHeight;
530
+ if (this._addonOptions.maxRows !== null && ((dataContext[`${this._keyPrefix}sizePadding`] || 0) > this._addonOptions.maxRows)) {
531
+ outterHeight = this._addonOptions.maxRows * rowHeight;
532
+ dataContext[`${this._keyPrefix}sizePadding`] = this._addonOptions.maxRows;
533
+ }
534
+ // V313HAX:
535
+ // putting in an extra closing div after the closing toggle div and ommiting a
536
+ // final closing div for the detail ctr div causes the slickgrid renderer to
537
+ // insert our detail div as a new column ;) ~since it wraps whatever we provide
538
+ // in a generic div column container. so our detail becomes a child directly of
539
+ // the row not the cell. nice =) ~no need to apply a css change to the parent
540
+ // slick-cell to escape the cell overflow clipping.
541
+ // sneaky extra </div> inserted here-----------------v
542
+ let expandedClasses = `${this._addonOptions.cssClass || ''} collapse `;
543
+ if (this._addonOptions.expandedClass) {
544
+ expandedClasses += this._addonOptions.expandedClass;
545
+ }
546
+ html.push(`<div class="${expandedClasses.trim()}"></div></div>`);
547
+ html.push(`<div class="dynamic-cell-detail cellDetailView_${dataContext[this._dataViewIdProperty]}" `); // apply custom css to detail
548
+ html.push(`style="height: ${outterHeight}px;`); // set total height of padding
549
+ html.push(`top: ${rowHeight}px">`); // shift detail below 1st row
550
+ html.push(`<div class="detail-container detailViewContainer_${dataContext[this._dataViewIdProperty]}">`); // sub ctr for custom styling
551
+ html.push(`<div class="innerDetailView_${dataContext[this._dataViewIdProperty]}">${dataContext[`${this._keyPrefix}detailContent`]}</div></div>`);
552
+ // omit a final closing detail container </div> that would come next
553
+ return html.join('');
554
+ }
555
+ }
556
+ return '';
557
+ }
558
+ /** When row is getting toggled, we will handle the action of collapsing/expanding */
559
+ handleAccordionShowHide(item) {
560
+ if (item) {
561
+ if (!item[`${this._keyPrefix}collapsed`]) {
562
+ this.collapseDetailView(item);
563
+ }
564
+ else {
565
+ this.expandDetailView(item);
566
+ }
567
+ }
568
+ }
569
+ /** Handle mouse click event */
570
+ handleClick(e, args) {
571
+ const dataContext = this._grid.getDataItem(args.row);
572
+ if (this.checkExpandableOverride(args.row, dataContext, this._grid)) {
573
+ // clicking on a row select checkbox
574
+ const columnDef = this._grid.getColumns()[args.cell];
575
+ if (this._addonOptions.useRowClick || (columnDef.id === this._addonOptions.columnId && e.target.classList.contains(this._addonOptions.cssClass || ''))) {
576
+ // if editing, try to commit
577
+ if (this._grid.getEditorLock().isActive() && !this._grid.getEditorLock().commitCurrentEdit()) {
578
+ e.preventDefault();
579
+ e.stopImmediatePropagation();
580
+ return;
581
+ }
582
+ // trigger an event before toggling
583
+ this.onBeforeRowDetailToggle.notify({
584
+ grid: this._grid,
585
+ item: dataContext
586
+ });
587
+ this.toggleRowSelection(args.row, dataContext);
588
+ // trigger an event after toggling
589
+ this.onAfterRowDetailToggle.notify({
590
+ grid: this._grid,
591
+ item: dataContext,
592
+ expandedRows: this._expandedRows,
593
+ });
594
+ e.stopPropagation();
595
+ e.stopImmediatePropagation();
596
+ }
597
+ }
598
+ }
599
+ handleScroll() {
600
+ if (this._addonOptions.useSimpleViewportCalc) {
601
+ this.calculateOutOfRangeViewsSimplerVersion();
602
+ }
603
+ else {
604
+ this.calculateOutOfRangeViews();
605
+ }
606
+ }
607
+ notifyOutOfViewport(item, rowId) {
608
+ const rowIndex = item.rowIndex || this.dataView.getRowById(item[this._dataViewIdProperty]);
609
+ this.onRowOutOfViewportRange.notify({
610
+ grid: this._grid,
611
+ item,
612
+ rowId,
613
+ rowIndex,
614
+ expandedRows: this._expandedRows,
615
+ rowIdsOutOfViewport: this.syncOutOfViewportArray(rowId, true)
616
+ });
617
+ }
618
+ notifyBackToViewportWhenDomExist(item, rowId) {
619
+ const rowIndex = item.rowIndex || this.dataView.getRowById(item[this._dataViewIdProperty]);
620
+ setTimeout(() => {
621
+ // make sure View Row DOM Element really exist before notifying that it's a row that is visible again
622
+ if (document.querySelector(`.${this.gridUid} .cellDetailView_${item[this._dataViewIdProperty]}`)) {
623
+ this.onRowBackToViewportRange.notify({
624
+ grid: this._grid,
625
+ item,
626
+ rowId,
627
+ rowIndex,
628
+ expandedRows: this._expandedRows,
629
+ rowIdsOutOfViewport: this.syncOutOfViewportArray(rowId, false)
630
+ });
631
+ }
632
+ }, 100);
633
+ }
634
+ syncOutOfViewportArray(rowId, isAdding) {
635
+ const arrayRowIndex = this._rowIdsOutOfViewport.findIndex(outOfViewportRowId => outOfViewportRowId === rowId);
636
+ if (isAdding && arrayRowIndex < 0) {
637
+ this._rowIdsOutOfViewport.push(rowId);
638
+ }
639
+ else if (!isAdding && arrayRowIndex >= 0) {
640
+ this._rowIdsOutOfViewport.splice(arrayRowIndex, 1);
641
+ }
642
+ return this._rowIdsOutOfViewport;
643
+ }
644
+ toggleRowSelection(rowNumber, dataContext) {
645
+ if (this.checkExpandableOverride(rowNumber, dataContext, this._grid)) {
646
+ this.dataView.beginUpdate();
647
+ this.handleAccordionShowHide(dataContext);
648
+ this.dataView.endUpdate();
649
+ }
650
+ }
651
+ }
648
652
  //# sourceMappingURL=slickRowDetailView.js.map