@slickgrid-universal/vanilla-bundle 4.7.0 → 5.0.0-beta.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.
@@ -1,1511 +1,1489 @@
1
- import { dequal } from 'dequal/lite';
2
- import 'flatpickr/dist/l10n/fr';
3
- import type {
4
- BackendServiceApi,
5
- BackendServiceOption,
6
- Column,
7
- DataViewOption,
8
- ExtensionList,
9
- ExternalResource,
10
- GridOption,
11
- Metrics,
12
- Pagination,
13
- SelectEditor,
14
- ServicePagination,
15
- Subscription,
16
- RxJsFacade,
17
- } from '@slickgrid-universal/common';
18
-
19
- import {
20
- autoAddEditorFormatterToColumnsWithEditor,
21
- type AutocompleterEditor,
22
- GlobalGridOptions,
23
- GridStateType,
24
- SlickGroupItemMetadataProvider,
25
-
26
- // services
27
- BackendUtilityService,
28
- CollectionService,
29
- ExtensionService,
30
- ExtensionUtility,
31
- FilterFactory,
32
- FilterService,
33
- GridEventService,
34
- GridService,
35
- GridStateService,
36
- GroupingAndColspanService,
37
- type Observable,
38
- PaginationService,
39
- ResizerService,
40
- SharedService,
41
- SortService,
42
- SlickgridConfig,
43
- type TranslaterService,
44
- TreeDataService,
45
-
46
- // utilities
47
- deepCopy,
48
- emptyElement,
49
- unsubscribeAll,
50
- SlickEventHandler,
51
- SlickDataView,
52
- SlickGrid
53
- } from '@slickgrid-universal/common';
54
- import { EventNamingStyle, EventPubSubService } from '@slickgrid-universal/event-pub-sub';
55
- import { SlickEmptyWarningComponent } from '@slickgrid-universal/empty-warning-component';
56
- import { SlickFooterComponent } from '@slickgrid-universal/custom-footer-component';
57
- import { SlickPaginationComponent } from '@slickgrid-universal/pagination-component';
58
- import { extend } from '@slickgrid-universal/utils';
59
-
60
- import { type SlickerGridInstance } from '../interfaces/slickerGridInstance.interface';
61
- import { UniversalContainerService } from '../services/universalContainer.service';
62
-
63
- export class SlickVanillaGridBundle<TData = any> {
64
- protected _currentDatasetLength = 0;
65
- protected _eventPubSubService!: EventPubSubService;
66
- protected _darkMode = false;
67
- protected _columnDefinitions?: Column<TData>[];
68
- protected _gridOptions?: GridOption;
69
- protected _gridContainerElm!: HTMLElement;
70
- protected _gridParentContainerElm!: HTMLElement;
71
- protected _hideHeaderRowAfterPageLoad = false;
72
- protected _isAutosizeColsCalled = false;
73
- protected _isDatasetInitialized = false;
74
- protected _isDatasetHierarchicalInitialized = false;
75
- protected _isGridInitialized = false;
76
- protected _isLocalGrid = true;
77
- protected _isPaginationInitialized = false;
78
- protected _eventHandler!: SlickEventHandler;
79
- protected _extensions: ExtensionList<any> | undefined;
80
- protected _paginationOptions: Pagination | undefined;
81
- protected _registeredResources: ExternalResource[] = [];
82
- protected _slickgridInitialized = false;
83
- protected _slickerGridInstances: SlickerGridInstance | undefined;
84
- backendServiceApi: BackendServiceApi | undefined;
85
- dataView?: SlickDataView<TData>;
86
- slickGrid?: SlickGrid;
87
- metrics?: Metrics;
88
- customDataView = false;
89
- paginationData?: {
90
- gridOptions: GridOption;
91
- paginationService: PaginationService;
92
- };
93
- totalItems = 0;
94
- groupItemMetadataProvider?: SlickGroupItemMetadataProvider;
95
- resizerService!: ResizerService;
96
- subscriptions: Subscription[] = [];
97
- showPagination = false;
98
-
99
- // extensions
100
- extensionUtility!: ExtensionUtility;
101
-
102
- // services
103
- backendUtilityService!: BackendUtilityService;
104
- collectionService!: CollectionService;
105
- extensionService!: ExtensionService;
106
- filterFactory!: FilterFactory;
107
- filterService!: FilterService;
108
- gridClass!: string;
109
- gridClassName!: string;
110
- gridEventService!: GridEventService;
111
- gridService!: GridService;
112
- gridStateService!: GridStateService;
113
- groupingService!: GroupingAndColspanService;
114
- paginationService!: PaginationService;
115
- rxjs?: RxJsFacade;
116
- sharedService!: SharedService;
117
- sortService!: SortService;
118
- translaterService: TranslaterService | undefined;
119
- treeDataService!: TreeDataService;
120
- universalContainerService!: UniversalContainerService;
121
-
122
- // components
123
- slickEmptyWarning: SlickEmptyWarningComponent | undefined;
124
- slickFooter: SlickFooterComponent | undefined;
125
- slickPagination: SlickPaginationComponent | undefined;
126
-
127
- get eventHandler(): SlickEventHandler {
128
- return this._eventHandler;
129
- }
130
-
131
- get columnDefinitions(): Column<TData>[] {
132
- return this._columnDefinitions || [];
133
- }
134
- set columnDefinitions(columnDefinitions: Column<TData>[]) {
135
- this._columnDefinitions = columnDefinitions;
136
- if (this._slickgridInitialized) {
137
- this.updateColumnDefinitionsList(this._columnDefinitions);
138
- }
139
- if (columnDefinitions.length > 0) {
140
- this.copyColumnWidthsReference(columnDefinitions);
141
- }
142
- }
143
-
144
- get dataset(): TData[] {
145
- return this.dataView?.getItems() || [];
146
- }
147
- set dataset(newDataset: TData[]) {
148
- const prevDatasetLn = this._currentDatasetLength;
149
- const isDatasetEqual = dequal(newDataset, this.dataset || []);
150
- const isDeepCopyDataOnPageLoadEnabled = !!(this._gridOptions?.enableDeepCopyDatasetOnPageLoad);
151
- let data = isDeepCopyDataOnPageLoadEnabled ? deepCopy(newDataset || []) : newDataset;
152
-
153
- // when Tree Data is enabled and we don't yet have the hierarchical dataset filled, we can force a convert+sort of the array
154
- if (this.slickGrid && this.gridOptions?.enableTreeData && Array.isArray(newDataset) && (newDataset.length > 0 || newDataset.length !== prevDatasetLn || !isDatasetEqual)) {
155
- this._isDatasetHierarchicalInitialized = false;
156
- data = this.sortTreeDataset(newDataset, !isDatasetEqual); // if dataset changed, then force a refresh anyway
157
- }
158
-
159
- this.refreshGridData(data || []);
160
- this._currentDatasetLength = (newDataset || []).length;
161
-
162
- // expand/autofit columns on first page load
163
- // we can assume that if the prevDataset was empty then we are on first load
164
- if (this.slickGrid && this.gridOptions.autoFitColumnsOnFirstLoad && prevDatasetLn === 0 && !this._isAutosizeColsCalled) {
165
- this.slickGrid.autosizeColumns();
166
- this._isAutosizeColsCalled = true;
167
- }
168
- }
169
-
170
- get datasetHierarchical(): any[] | undefined {
171
- return this.sharedService.hierarchicalDataset;
172
- }
173
-
174
- set datasetHierarchical(newHierarchicalDataset: any[] | undefined) {
175
- const isDatasetEqual = dequal(newHierarchicalDataset, this.sharedService.hierarchicalDataset || []);
176
- const prevFlatDatasetLn = this._currentDatasetLength;
177
- this.sharedService.hierarchicalDataset = newHierarchicalDataset;
178
-
179
- if (newHierarchicalDataset && this.columnDefinitions && this.filterService?.clearFilters) {
180
- this.filterService.clearFilters();
181
- }
182
-
183
- // when a hierarchical dataset is set afterward, we can reset the flat dataset and call a tree data sort that will overwrite the flat dataset
184
- if (this.dataView && newHierarchicalDataset && this.slickGrid && this.sortService?.processTreeDataInitialSort) {
185
- this.sortService.processTreeDataInitialSort();
186
-
187
- // we also need to reset/refresh the Tree Data filters because if we inserted new item(s) then it might not show up without doing this refresh
188
- // however we need 1 cpu cycle before having the DataView refreshed, so we need to wrap this check in a setTimeout
189
- setTimeout(() => {
190
- const flatDatasetLn = this.dataView?.getItemCount() ?? 0;
191
- if (flatDatasetLn > 0 && (flatDatasetLn !== prevFlatDatasetLn || !isDatasetEqual)) {
192
- this.filterService.refreshTreeDataFilters();
193
- }
194
- });
195
- }
196
-
197
- this._isDatasetHierarchicalInitialized = true;
198
- }
199
-
200
- set eventPubSubService(pubSub: EventPubSubService) {
201
- this._eventPubSubService = pubSub;
202
- }
203
-
204
- set isDatasetHierarchicalInitialized(isInitialized: boolean) {
205
- this._isDatasetHierarchicalInitialized = isInitialized;
206
- }
207
-
208
- get gridOptions(): GridOption {
209
- return this._gridOptions || {} as GridOption;
210
- }
211
-
212
- set gridOptions(options: GridOption) {
213
- let mergedOptions: GridOption;
214
-
215
- // if we already have grid options, when grid was already initialized, we'll merge with those options
216
- // else we'll merge with global grid options
217
- if (this.slickGrid?.getOptions) {
218
- mergedOptions = (extend<GridOption>(true, {} as GridOption, this.slickGrid.getOptions() as GridOption, options)) as GridOption;
219
- } else {
220
- mergedOptions = this.mergeGridOptions(options);
221
- }
222
-
223
- if (this.sharedService?.gridOptions && this.slickGrid?.setOptions) {
224
- this.sharedService.gridOptions = mergedOptions;
225
- this.slickGrid.setOptions(mergedOptions as any, false, true); // make sure to supressColumnCheck (3rd arg) to avoid problem with changeColumnsArrangement() and custom grid view
226
- this.slickGrid.reRenderColumns(true); // then call a re-render since we did supressColumnCheck on previous setOptions
227
- }
228
-
229
- // add/remove dark mode CSS class to parent container
230
- this.setDarkMode(options.darkMode);
231
-
232
- this._gridOptions = mergedOptions;
233
- }
234
-
235
- get paginationOptions(): Pagination | undefined {
236
- return this._paginationOptions;
237
- }
238
- set paginationOptions(newPaginationOptions: Pagination | undefined) {
239
- if (newPaginationOptions && this._paginationOptions) {
240
- this._paginationOptions = { ...this._paginationOptions, ...newPaginationOptions };
241
- } else {
242
- this._paginationOptions = newPaginationOptions;
243
- }
244
- this.gridOptions.pagination = this._paginationOptions;
245
- this.paginationService.updateTotalItems(newPaginationOptions?.totalItems ?? 0, true);
246
- }
247
-
248
- get isDatasetInitialized(): boolean {
249
- return this._isDatasetInitialized;
250
- }
251
- set isDatasetInitialized(isInitialized: boolean) {
252
- this._isDatasetInitialized = isInitialized;
253
- }
254
- get isGridInitialized(): boolean {
255
- return this._isGridInitialized;
256
- }
257
-
258
- get instances(): SlickerGridInstance | undefined {
259
- return this._slickerGridInstances;
260
- }
261
-
262
- get extensions(): ExtensionList<any> | undefined {
263
- return this._extensions;
264
- }
265
-
266
- get registeredResources(): any[] {
267
- return this._registeredResources;
268
- }
269
-
270
- /**
271
- * Slicker Grid Bundle constructor
272
- * @param {Object} gridParentContainerElm - div HTML DOM element container
273
- * @param {Array<Column>} columnDefs - Column Definitions
274
- * @param {Object} options - Grid Options
275
- * @param {Array<Object>} dataset - Dataset
276
- * @param {Array<Object>} hierarchicalDataset - Hierarchical Dataset
277
- * @param {Object} services - Typically only used for Unit Testing when we want to pass Mocked/Stub Services
278
- */
279
- constructor(
280
- gridParentContainerElm: HTMLElement,
281
- columnDefs?: Column<TData>[],
282
- options?: Partial<GridOption>,
283
- dataset?: TData[],
284
- hierarchicalDataset?: any[],
285
- services?: {
286
- backendUtilityService?: BackendUtilityService,
287
- collectionService?: CollectionService,
288
- eventPubSubService?: EventPubSubService,
289
- extensionService?: ExtensionService,
290
- extensionUtility?: ExtensionUtility,
291
- filterService?: FilterService,
292
- gridEventService?: GridEventService,
293
- gridService?: GridService,
294
- gridStateService?: GridStateService,
295
- groupingAndColspanService?: GroupingAndColspanService,
296
- paginationService?: PaginationService,
297
- resizerService?: ResizerService,
298
- rxjs?: RxJsFacade,
299
- sharedService?: SharedService,
300
- sortService?: SortService,
301
- treeDataService?: TreeDataService,
302
- translaterService?: TranslaterService,
303
- universalContainerService?: UniversalContainerService,
304
- }
305
- ) {
306
- // make sure that the grid container doesn't already have the "slickgrid-container" css class
307
- // if it does then we won't create yet another grid, just stop there
308
- if (gridParentContainerElm.querySelectorAll('.slickgrid-container').length !== 0) {
309
- return;
310
- }
311
-
312
- gridParentContainerElm.classList.add('grid-pane');
313
- this._gridParentContainerElm = gridParentContainerElm as HTMLDivElement;
314
- this._gridContainerElm = document.createElement('div') as HTMLDivElement;
315
- this._gridContainerElm.classList.add('slickgrid-container');
316
- gridParentContainerElm.appendChild(this._gridContainerElm);
317
-
318
- // check if the user wants to hide the header row from the start
319
- // we only want to do this check once in the constructor
320
- this._hideHeaderRowAfterPageLoad = (options?.showHeaderRow === false);
321
-
322
- this._columnDefinitions = columnDefs || [];
323
- if (this._columnDefinitions.length > 0) {
324
- this.copyColumnWidthsReference(this._columnDefinitions);
325
- }
326
-
327
- // save resource refs to register before the grid options are merged and possibly deep copied
328
- // since a deep copy of grid options would lose original resource refs but we want to keep them as singleton
329
- this._registeredResources = options?.externalResources || [];
330
-
331
- this._gridOptions = this.mergeGridOptions(options || {});
332
- const isDeepCopyDataOnPageLoadEnabled = !!(this._gridOptions?.enableDeepCopyDatasetOnPageLoad);
333
-
334
- // add dark mode CSS class when enabled
335
- if (this._gridOptions.darkMode) {
336
- this.setDarkMode(true);
337
- }
338
-
339
- this.universalContainerService = services?.universalContainerService ?? new UniversalContainerService();
340
-
341
- // if user is providing a Translate Service, it has to be passed under the "translater" grid option
342
- this.translaterService = services?.translaterService ?? this._gridOptions?.translater;
343
-
344
- // initialize and assign all Service Dependencies
345
- this._eventPubSubService = services?.eventPubSubService ?? new EventPubSubService(gridParentContainerElm);
346
- this._eventPubSubService.eventNamingStyle = this._gridOptions?.eventNamingStyle ?? EventNamingStyle.camelCase;
347
-
348
- const slickgridConfig = new SlickgridConfig();
349
- this.backendUtilityService = services?.backendUtilityService ?? new BackendUtilityService();
350
- this.gridEventService = services?.gridEventService ?? new GridEventService();
351
- this.sharedService = services?.sharedService ?? new SharedService();
352
- this.collectionService = services?.collectionService ?? new CollectionService(this.translaterService);
353
- this.extensionUtility = services?.extensionUtility ?? new ExtensionUtility(this.sharedService, this.backendUtilityService, this.translaterService);
354
- this.filterFactory = new FilterFactory(slickgridConfig, this.translaterService, this.collectionService);
355
- this.filterService = services?.filterService ?? new FilterService(this.filterFactory, this._eventPubSubService, this.sharedService, this.backendUtilityService);
356
- this.resizerService = services?.resizerService ?? new ResizerService(this._eventPubSubService);
357
- this.sortService = services?.sortService ?? new SortService(this.sharedService, this._eventPubSubService, this.backendUtilityService);
358
- this.treeDataService = services?.treeDataService ?? new TreeDataService(this._eventPubSubService, this.sharedService, this.sortService);
359
- this.paginationService = services?.paginationService ?? new PaginationService(this._eventPubSubService, this.sharedService, this.backendUtilityService);
360
-
361
- this.extensionService = services?.extensionService ?? new ExtensionService(
362
- this.extensionUtility,
363
- this.filterService,
364
- this._eventPubSubService,
365
- this.sharedService,
366
- this.sortService,
367
- this.treeDataService,
368
- this.translaterService,
369
- () => this.gridService
370
- );
371
-
372
- this.gridStateService = services?.gridStateService ?? new GridStateService(this.extensionService, this.filterService, this._eventPubSubService, this.sharedService, this.sortService, this.treeDataService);
373
- this.gridService = services?.gridService ?? new GridService(this.gridStateService, this.filterService, this._eventPubSubService, this.paginationService, this.sharedService, this.sortService, this.treeDataService);
374
- this.groupingService = services?.groupingAndColspanService ?? new GroupingAndColspanService(this.extensionUtility, this._eventPubSubService);
375
-
376
- if (hierarchicalDataset) {
377
- this.sharedService.hierarchicalDataset = (isDeepCopyDataOnPageLoadEnabled ? deepCopy(hierarchicalDataset || []) : hierarchicalDataset) || [];
378
- }
379
- const eventHandler = new SlickEventHandler();
380
-
381
- // register all service instances in the container
382
- this.universalContainerService.registerInstance('PubSubService', this._eventPubSubService); // external resources require this one registration (ExcelExport, TextExport)
383
- this.universalContainerService.registerInstance('EventPubSubService', this._eventPubSubService);
384
- this.universalContainerService.registerInstance('ExtensionUtility', this.extensionUtility);
385
- this.universalContainerService.registerInstance('FilterService', this.filterService);
386
- this.universalContainerService.registerInstance('CollectionService', this.collectionService);
387
- this.universalContainerService.registerInstance('ExtensionService', this.extensionService);
388
- this.universalContainerService.registerInstance('GridEventService', this.gridEventService);
389
- this.universalContainerService.registerInstance('GridService', this.gridService);
390
- this.universalContainerService.registerInstance('GridStateService', this.gridStateService);
391
- this.universalContainerService.registerInstance('GroupingAndColspanService', this.groupingService);
392
- this.universalContainerService.registerInstance('PaginationService', this.paginationService);
393
- this.universalContainerService.registerInstance('ResizerService', this.resizerService);
394
- this.universalContainerService.registerInstance('SharedService', this.sharedService);
395
- this.universalContainerService.registerInstance('SortService', this.sortService);
396
- this.universalContainerService.registerInstance('TranslaterService', this.translaterService);
397
- this.universalContainerService.registerInstance('TreeDataService', this.treeDataService);
398
-
399
- this.initialization(this._gridContainerElm, eventHandler, dataset);
400
- }
401
-
402
- emptyGridContainerElm() {
403
- const gridContainerId = this.gridOptions?.gridContainerId ?? 'grid1';
404
- const gridContainerElm = document.querySelector(`#${gridContainerId}`);
405
- emptyElement(gridContainerElm);
406
- }
407
-
408
- /** Dispose of the Component */
409
- dispose(shouldEmptyDomElementContainer = false) {
410
- this._eventPubSubService?.publish('onBeforeGridDestroy', this.slickGrid);
411
- this._eventHandler?.unsubscribeAll();
412
- this._eventPubSubService?.publish('onAfterGridDestroyed', true);
413
-
414
- // dispose the Services
415
- this.extensionService?.dispose();
416
- this.filterService?.dispose();
417
- this.gridEventService?.dispose();
418
- this.gridService?.dispose();
419
- this.gridStateService?.dispose();
420
- this.groupingService?.dispose();
421
- this.paginationService?.dispose();
422
- this.resizerService?.dispose();
423
- this.sortService?.dispose();
424
- this.treeDataService?.dispose();
425
- this.universalContainerService?.dispose();
426
-
427
- // dispose all registered external resources
428
- this.disposeExternalResources();
429
-
430
- // dispose the Components
431
- this.slickFooter?.dispose();
432
- this.slickEmptyWarning?.dispose();
433
- this.slickPagination?.dispose();
434
-
435
- unsubscribeAll(this.subscriptions);
436
- this._eventPubSubService?.unsubscribeAll();
437
- this.dataView?.setItems([]);
438
- if (typeof this.dataView?.destroy === 'function') {
439
- this.dataView?.destroy();
440
- }
441
- this.slickGrid?.destroy(true);
442
- this.slickGrid = null as any;
443
-
444
- emptyElement(this._gridContainerElm);
445
- emptyElement(this._gridParentContainerElm);
446
- this._gridContainerElm?.remove();
447
- this._gridParentContainerElm?.remove();
448
-
449
- if (this.backendServiceApi) {
450
- for (const prop of Object.keys(this.backendServiceApi)) {
451
- this.backendServiceApi[prop as keyof BackendServiceApi] = null;
452
- }
453
- this.backendServiceApi = undefined;
454
- }
455
- for (const prop of Object.keys(this.columnDefinitions)) {
456
- (this.columnDefinitions as any)[prop] = null;
457
- }
458
- for (const prop of Object.keys(this.sharedService)) {
459
- (this.sharedService as any)[prop] = null;
460
- }
461
- this.datasetHierarchical = undefined;
462
- this._columnDefinitions = [];
463
-
464
- // we could optionally also empty the content of the grid container DOM element
465
- if (shouldEmptyDomElementContainer) {
466
- this.emptyGridContainerElm();
467
- }
468
- this._eventPubSubService?.dispose();
469
- this._slickerGridInstances = null as any;
470
- }
471
-
472
- disposeExternalResources() {
473
- if (Array.isArray(this._registeredResources)) {
474
- while (this._registeredResources.length > 0) {
475
- const res = this._registeredResources.pop();
476
- if (res?.dispose) {
477
- res.dispose();
478
- }
479
- }
480
- }
481
- this._registeredResources = [];
482
- }
483
-
484
- initialization(gridContainerElm: HTMLElement, eventHandler: SlickEventHandler, inputDataset?: TData[]) {
485
- // when detecting a frozen grid, we'll automatically enable the mousewheel scroll handler so that we can scroll from both left/right frozen containers
486
- if (this.gridOptions && ((this.gridOptions.frozenRow !== undefined && this.gridOptions.frozenRow >= 0) || this.gridOptions.frozenColumn !== undefined && this.gridOptions.frozenColumn >= 0) && this.gridOptions.enableMouseWheelScrollHandler === undefined) {
487
- this.gridOptions.enableMouseWheelScrollHandler = true;
488
- }
489
-
490
- // create the slickgrid container and add it to the user's grid container
491
- this._gridContainerElm = gridContainerElm;
492
- this._eventPubSubService.publish('onBeforeGridCreate', true);
493
-
494
- this._isAutosizeColsCalled = false;
495
- this._eventHandler = eventHandler;
496
- this._gridOptions = this.mergeGridOptions(this._gridOptions || {} as GridOption);
497
- this.backendServiceApi = this._gridOptions?.backendServiceApi;
498
- this._isLocalGrid = !this.backendServiceApi; // considered a local grid if it doesn't have a backend service set
499
- this._eventPubSubService.eventNamingStyle = this._gridOptions?.eventNamingStyle ?? EventNamingStyle.camelCase;
500
- this._paginationOptions = this.gridOptions?.pagination;
501
-
502
- this.createBackendApiInternalPostProcessCallback(this._gridOptions);
503
-
504
- if (!this.customDataView) {
505
- const dataviewInlineFilters = this._gridOptions?.dataView?.inlineFilters ?? false;
506
- let dataViewOptions: Partial<DataViewOption> = { ...this._gridOptions.dataView, inlineFilters: dataviewInlineFilters };
507
-
508
- if (this.gridOptions.draggableGrouping || this.gridOptions.enableGrouping) {
509
- this.groupItemMetadataProvider = new SlickGroupItemMetadataProvider();
510
- this.sharedService.groupItemMetadataProvider = this.groupItemMetadataProvider;
511
- dataViewOptions = { ...dataViewOptions, groupItemMetadataProvider: this.groupItemMetadataProvider };
512
- }
513
- this.dataView = new SlickDataView<TData>(dataViewOptions, this._eventPubSubService);
514
- this._eventPubSubService.publish('onDataviewCreated', this.dataView);
515
- }
516
-
517
- // get any possible Services that user want to register which don't require SlickGrid to be instantiated
518
- // RxJS Resource is in this lot because it has to be registered before anything else and doesn't require SlickGrid to be initialized
519
- this.preRegisterResources();
520
-
521
- // for convenience to the user, we provide the property "editor" as an Slickgrid-Universal editor complex object
522
- // however "editor" is used internally by SlickGrid for it's own Editor Factory
523
- // so in our lib we will swap "editor" and copy it into a new property called "internalColumnEditor"
524
- // then take back "editor.model" and make it the new "editor" so that SlickGrid Editor Factory still works
525
- this._columnDefinitions = this.swapInternalEditorToSlickGridFactoryEditor(this._columnDefinitions || []);
526
-
527
- // if the user wants to automatically add a Custom Editor Formatter, we need to call the auto add function again
528
- if (this._gridOptions?.autoAddCustomEditorFormatter) {
529
- autoAddEditorFormatterToColumnsWithEditor(this._columnDefinitions, this._gridOptions.autoAddCustomEditorFormatter);
530
- }
531
-
532
- // save reference for all columns before they optionally become hidden/visible
533
- this.sharedService.allColumns = this._columnDefinitions;
534
- this.sharedService.visibleColumns = this._columnDefinitions;
535
-
536
- // TODO: revisit later, this is conflicting with Grid State & Presets
537
- // before certain extentions/plugins potentially adds extra columns not created by the user itself (RowMove, RowDetail, RowSelections)
538
- // we'll subscribe to the event and push back the change to the user so they always use full column defs array including extra cols
539
- // this.subscriptions.push(
540
- // this._eventPubSubService.subscribe<{ columns: Column[]; pluginName: string }>('onPluginColumnsChanged', data => {
541
- // this._columnDefinitions = this.columnDefinitions = data.columns;
542
- // })
543
- // );
544
-
545
- // after subscribing to potential columns changed, we are ready to create these optional extensions
546
- // when we did find some to create (RowMove, RowDetail, RowSelections), it will automatically modify column definitions (by previous subscribe)
547
- this.extensionService.createExtensionsBeforeGridCreation(this._columnDefinitions, this._gridOptions);
548
-
549
- // if user entered some Pinning/Frozen "presets", we need to apply them in the grid options
550
- if (this.gridOptions.presets?.pinning) {
551
- this.gridOptions = { ...this.gridOptions, ...this.gridOptions.presets.pinning };
552
- }
553
-
554
- this.slickGrid = new SlickGrid<TData, Column<TData>, GridOption<Column<TData>>>(gridContainerElm, this.dataView as SlickDataView<TData>, this._columnDefinitions, this._gridOptions, this._eventPubSubService);
555
- this.sharedService.dataView = this.dataView as SlickDataView;
556
- this.sharedService.slickGrid = this.slickGrid as SlickGrid;
557
- this.sharedService.gridContainerElement = this._gridContainerElm;
558
- if (this.groupItemMetadataProvider) {
559
- this.slickGrid.registerPlugin(this.groupItemMetadataProvider); // register GroupItemMetadataProvider when Grouping is enabled
560
- }
561
-
562
- this.extensionService.bindDifferentExtensions();
563
- this.bindDifferentHooks(this.slickGrid, this._gridOptions, this.dataView as SlickDataView);
564
- this._slickgridInitialized = true;
565
-
566
- // when it's a frozen grid, we need to keep the frozen column id for reference if we ever show/hide column from ColumnPicker/GridMenu afterward
567
- const frozenColumnIndex = this._gridOptions?.frozenColumn ?? -1;
568
- if (frozenColumnIndex >= 0 && frozenColumnIndex <= this._columnDefinitions.length && this._columnDefinitions.length > 0) {
569
- this.sharedService.frozenVisibleColumnId = this._columnDefinitions[frozenColumnIndex]?.id ?? '';
570
- }
571
-
572
- // get any possible Services that user want to register
573
- this.registerResources();
574
-
575
- // initialize the SlickGrid grid
576
- this.slickGrid.init();
577
-
578
- // initialized the resizer service only after SlickGrid is initialized
579
- // if we don't we end up binding our resize to a grid element that doesn't yet exist in the DOM and the resizer service will fail silently (because it has a try/catch that unbinds the resize without throwing back)
580
- this.resizerService.init(this.slickGrid, this._gridParentContainerElm);
581
-
582
- // user could show a custom footer with the data metrics (dataset length and last updated timestamp)
583
- if (!this.gridOptions.enablePagination && this.gridOptions.showCustomFooter && this.gridOptions.customFooterOptions) {
584
- this.slickFooter = new SlickFooterComponent(this.slickGrid, this.gridOptions.customFooterOptions, this._eventPubSubService, this.translaterService);
585
- this.slickFooter.renderFooter(this._gridParentContainerElm);
586
- }
587
-
588
- // load the data in the DataView (unless it's a hierarchical dataset, if so it will be loaded after the initial tree sort)
589
- if (this.dataView) {
590
- inputDataset = inputDataset || [];
591
- const initialDataset = this.gridOptions?.enableTreeData ? this.sortTreeDataset(inputDataset) : inputDataset;
592
- this.dataView.beginUpdate();
593
- this.dataView.setItems(initialDataset, this._gridOptions.datasetIdPropertyName);
594
- this._currentDatasetLength = inputDataset.length;
595
- this.dataView.endUpdate();
596
- }
597
-
598
- // if you don't want the items that are not visible (due to being filtered out or being on a different page)
599
- // to stay selected, pass 'false' to the second arg
600
- if (this.slickGrid?.getSelectionModel() && this._gridOptions?.dataView?.hasOwnProperty('syncGridSelection')) {
601
- // if we are using a Backend Service, we will do an extra flag check, the reason is because it might have some unintended behaviors
602
- // with the BackendServiceApi because technically the data in the page changes the DataView on every page change.
603
- let preservedRowSelectionWithBackend = false;
604
- if (this._gridOptions.backendServiceApi && this._gridOptions.dataView.hasOwnProperty('syncGridSelectionWithBackendService')) {
605
- preservedRowSelectionWithBackend = this._gridOptions.dataView.syncGridSelectionWithBackendService as boolean;
606
- }
607
-
608
- const syncGridSelection = this._gridOptions.dataView.syncGridSelection;
609
- if (typeof syncGridSelection === 'boolean') {
610
- let preservedRowSelection = syncGridSelection;
611
- if (!this._isLocalGrid) {
612
- // when using BackendServiceApi, we'll be using the "syncGridSelectionWithBackendService" flag BUT "syncGridSelection" must also be set to True
613
- preservedRowSelection = syncGridSelection && preservedRowSelectionWithBackend;
614
- }
615
- this.dataView?.syncGridSelection(this.slickGrid, preservedRowSelection);
616
- } else if (typeof syncGridSelection === 'object') {
617
- this.dataView?.syncGridSelection(this.slickGrid, syncGridSelection.preserveHidden, syncGridSelection.preserveHiddenOnSelectionChange);
618
- }
619
- }
620
-
621
- if ((this.dataView?.getLength() ?? 0) > 0) {
622
- if (!this._isDatasetInitialized && (this._gridOptions.enableCheckboxSelector || this._gridOptions.enableRowSelection)) {
623
- this.loadRowSelectionPresetWhenExists();
624
- }
625
- this.loadFilterPresetsWhenDatasetInitialized();
626
- this._isDatasetInitialized = true;
627
- } else {
628
- this.displayEmptyDataWarning(true);
629
- }
630
-
631
- // user might want to hide the header row on page load but still have `enableFiltering: true`
632
- // if that is the case, we need to hide the headerRow ONLY AFTER all filters got created & dataView exist
633
- if (this._hideHeaderRowAfterPageLoad) {
634
- this.showHeaderRow(false);
635
- this.sharedService.hideHeaderRowAfterPageLoad = this._hideHeaderRowAfterPageLoad;
636
- }
637
-
638
- // on cell click, mainly used with the columnDef.action callback
639
- this.gridEventService.bindOnBeforeEditCell(this.slickGrid);
640
- this.gridEventService.bindOnCellChange(this.slickGrid);
641
- this.gridEventService.bindOnClick(this.slickGrid);
642
-
643
- // bind the Backend Service API callback functions only after the grid is initialized
644
- // because the preProcess() and onInit() might get triggered
645
- if (this.gridOptions?.backendServiceApi) {
646
- this.bindBackendCallbackFunctions(this.gridOptions);
647
- }
648
-
649
- // publish & dispatch certain events
650
- this._eventPubSubService.publish('onGridCreated', this.slickGrid);
651
-
652
- // after the DataView is created & updated execute some processes & dispatch some events
653
- if (!this.customDataView) {
654
- this.executeAfterDataviewCreated(this.gridOptions);
655
- }
656
-
657
- // bind resize ONLY after the dataView is ready
658
- this.bindResizeHook(this.slickGrid, this.gridOptions);
659
-
660
- // local grid, check if we need to show the Pagination
661
- // if so then also check if there's any presets and finally initialize the PaginationService
662
- // a local grid with Pagination presets will potentially have a different total of items, we'll need to get it from the DataView and update our total
663
- if (this.gridOptions?.enablePagination && this._isLocalGrid) {
664
- this.showPagination = true;
665
- this.loadLocalGridPagination(this.dataset);
666
- }
667
-
668
- // once the grid is created, we'll return its instance (we do this to return Transient Services from DI)
669
- this._slickerGridInstances = {
670
- // Slick Grid & DataView objects
671
- dataView: this.dataView as SlickDataView,
672
- slickGrid: this.slickGrid,
673
-
674
- // public methods
675
- dispose: this.dispose.bind(this),
676
-
677
- // return all available Services (non-singleton)
678
- backendService: this.gridOptions?.backendServiceApi?.service,
679
- eventPubSubService: this._eventPubSubService,
680
- filterService: this.filterService,
681
- gridEventService: this.gridEventService,
682
- gridStateService: this.gridStateService,
683
- gridService: this.gridService,
684
- groupingService: this.groupingService,
685
- extensionService: this.extensionService,
686
- extensionUtility: this.extensionUtility,
687
- paginationService: this.paginationService,
688
- resizerService: this.resizerService,
689
- sortService: this.sortService,
690
- treeDataService: this.treeDataService,
691
- };
692
-
693
- // addons (SlickGrid extra plugins/controls)
694
- this._extensions = this.extensionService?.extensionList;
695
-
696
- // all instances (SlickGrid, DataView & all Services)
697
- this._eventPubSubService.publish('onSlickerGridCreated', this.instances);
698
- this._isGridInitialized = true;
699
- }
700
-
701
- mergeGridOptions(gridOptions: GridOption) {
702
- const options = extend<GridOption>(true, {}, GlobalGridOptions, gridOptions);
703
-
704
- // also make sure to show the header row if user have enabled filtering
705
- if (options.enableFiltering && !options.showHeaderRow) {
706
- options.showHeaderRow = options.enableFiltering;
707
- }
708
-
709
- // using copy extend to do a deep clone has an unwanted side on objects and pageSizes but ES6 spread has other worst side effects
710
- // so we will just overwrite the pageSizes when needed, this is the only one causing issues so far.
711
- // On a deep extend, Object and Array are extended, but object wrappers on primitive types such as String, Boolean, and Number are not.
712
- if (options?.pagination && (gridOptions.enablePagination || gridOptions.backendServiceApi) && gridOptions.pagination && Array.isArray(gridOptions.pagination.pageSizes)) {
713
- options.pagination.pageSizes = gridOptions.pagination.pageSizes;
714
- }
715
-
716
- // when we use Pagination on Local Grid, it doesn't seem to work without enableFiltering
717
- // so we'll enable the filtering but we'll keep the header row hidden
718
- if (this.sharedService && !options.enableFiltering && options.enablePagination && this._isLocalGrid) {
719
- options.enableFiltering = true;
720
- options.showHeaderRow = false;
721
- this._hideHeaderRowAfterPageLoad = true;
722
- this.sharedService.hideHeaderRowAfterPageLoad = true;
723
- }
724
-
725
- return options;
726
- }
727
-
728
- /**
729
- * Define our internal Post Process callback, it will execute internally after we get back result from the Process backend call
730
- * For now, this is GraphQL Service ONLY feature and it will basically
731
- * refresh the Dataset & Pagination without having the user to create his own PostProcess every time
732
- */
733
- createBackendApiInternalPostProcessCallback(gridOptions?: GridOption) {
734
- const backendApi = gridOptions?.backendServiceApi;
735
- if (backendApi?.service) {
736
- const backendApiService = backendApi.service;
737
-
738
- // internalPostProcess only works (for now) with a GraphQL Service, so make sure it is of that type
739
- if (/* backendApiService instanceof GraphqlService || */ typeof backendApiService.getDatasetName === 'function') {
740
- backendApi.internalPostProcess = (processResult: any) => {
741
- const datasetName = (backendApi && backendApiService && typeof backendApiService.getDatasetName === 'function') ? backendApiService.getDatasetName() : '';
742
- if (processResult && processResult.data && processResult.data[datasetName]) {
743
- const data = processResult.data[datasetName].hasOwnProperty('nodes') ? (processResult as any).data[datasetName].nodes : (processResult as any).data[datasetName];
744
- const totalCount = processResult.data[datasetName].hasOwnProperty('totalCount') ? (processResult as any).data[datasetName].totalCount : (processResult as any).data[datasetName].length;
745
- this.refreshGridData(data, totalCount || 0);
746
- }
747
- };
748
- }
749
- }
750
- }
751
-
752
- bindDifferentHooks(grid: SlickGrid, gridOptions: GridOption, dataView: SlickDataView<TData>) {
753
- // if user is providing a Translate Service, we need to add our PubSub Service (but only after creating all dependencies)
754
- // so that we can later subscribe to the "onLanguageChange" event and translate any texts whenever that get triggered
755
- if (gridOptions.enableTranslate && this.translaterService?.addPubSubMessaging) {
756
- this.translaterService.addPubSubMessaging(this._eventPubSubService);
757
- }
758
-
759
- // translate them all on first load, then on each language change
760
- if (gridOptions.enableTranslate) {
761
- this.extensionService.translateAllExtensions();
762
- }
763
-
764
- // on locale change, we have to manually translate the Headers, GridMenu
765
- this.subscriptions.push(
766
- this._eventPubSubService.subscribe('onLanguageChange', (args: { language: string; }) => {
767
- if (gridOptions.enableTranslate) {
768
- this.extensionService.translateAllExtensions(args.language);
769
- if (gridOptions.createPreHeaderPanel && !gridOptions.enableDraggableGrouping) {
770
- this.groupingService.translateGroupingAndColSpan();
771
- }
772
- }
773
- })
774
- );
775
-
776
- // if user set an onInit Backend, we'll run it right away (and if so, we also need to run preProcess, internalPostProcess & postProcess)
777
- if (gridOptions.backendServiceApi) {
778
- const backendApi = gridOptions.backendServiceApi;
779
-
780
- if (backendApi?.service?.init) {
781
- backendApi.service.init(backendApi.options, gridOptions.pagination, this.slickGrid, this.sharedService);
782
- }
783
- }
784
-
785
- if (dataView && grid) {
786
- // after all events are exposed
787
- // we can bind external filter (backend) when available or default onFilter (dataView)
788
- if (gridOptions.enableFiltering) {
789
- this.filterService.init(grid);
790
-
791
- // bind external filter (backend) unless specified to use the local one
792
- if (gridOptions.backendServiceApi && !gridOptions.backendServiceApi.useLocalFiltering) {
793
- this.filterService.bindBackendOnFilter(grid);
794
- } else {
795
- this.filterService.bindLocalOnFilter(grid);
796
- }
797
- }
798
-
799
- // bind external sorting (backend) when available or default onSort (dataView)
800
- if (gridOptions.enableSorting) {
801
- // bind external sorting (backend) unless specified to use the local one
802
- if (gridOptions.backendServiceApi && !gridOptions.backendServiceApi.useLocalSorting) {
803
- this.sortService.bindBackendOnSort(grid);
804
- } else {
805
- this.sortService.bindLocalOnSort(grid);
806
- }
807
- }
808
-
809
- // When data changes in the DataView, we need to refresh the metrics and/or display a warning if the dataset is empty
810
- this._eventHandler.subscribe(dataView.onRowCountChanged, () => {
811
- grid.invalidate();
812
- this.handleOnItemCountChanged(this.dataView?.getFilteredItemCount() || 0, this.dataView?.getItemCount() ?? 0);
813
- });
814
- this._eventHandler.subscribe(dataView.onSetItemsCalled, (_e, args) => {
815
- this.handleOnItemCountChanged(this.dataView?.getFilteredItemCount() || 0, args.itemCount);
816
-
817
- // when user has resize by content enabled, we'll force a full width calculation since we change our entire dataset
818
- if (args.itemCount > 0 && (this.gridOptions.autosizeColumnsByCellContentOnFirstLoad || this.gridOptions.enableAutoResizeColumnsByCellContent)) {
819
- this.resizerService.resizeColumnsByCellContent(!this.gridOptions?.resizeByContentOnlyOnFirstLoad);
820
- }
821
- });
822
-
823
- // when filtering data with local dataset, we need to update each row else it will not always show correctly in the UI
824
- // also don't use "invalidateRows" since it destroys the entire row and as bad user experience when updating a row
825
- if (gridOptions?.enableFiltering && !gridOptions.enableRowDetailView) {
826
- this._eventHandler.subscribe(dataView.onRowsChanged, (_e, args) => {
827
- if (args?.rows && Array.isArray(args.rows)) {
828
- args.rows.forEach((row: number) => grid.updateRow(row));
829
- grid.render();
830
- }
831
- });
832
- }
833
-
834
- // when column are reordered, we need to update the visibleColumn array
835
- this._eventHandler.subscribe(grid.onColumnsReordered, (_e, args) => {
836
- this.sharedService.hasColumnsReordered = true;
837
- this.sharedService.visibleColumns = args.impactedColumns;
838
- });
839
-
840
- this._eventHandler.subscribe(grid.onSetOptions, (_e, args) => {
841
- // add/remove dark mode CSS class when enabled
842
- if (args.optionsBefore.darkMode !== args.optionsAfter.darkMode) {
843
- this.setDarkMode(args.optionsAfter.darkMode);
844
- }
845
- });
846
-
847
- // load any presets if any (after dataset is initialized)
848
- this.loadColumnPresetsWhenDatasetInitialized();
849
- this.loadFilterPresetsWhenDatasetInitialized();
850
- }
851
-
852
- // did the user add a colspan callback? If so, hook it into the DataView getItemMetadata
853
- if (gridOptions?.colspanCallback && dataView?.getItem && dataView?.getItemMetadata) {
854
- dataView.getItemMetadata = (rowNumber: number) => {
855
- let callbackResult = null;
856
- if (gridOptions.colspanCallback) {
857
- callbackResult = gridOptions.colspanCallback(dataView.getItem(rowNumber));
858
- }
859
- return callbackResult;
860
- };
861
- }
862
- }
863
-
864
- bindBackendCallbackFunctions(gridOptions: GridOption) {
865
- const backendApi = gridOptions.backendServiceApi;
866
- const backendApiService = backendApi?.service;
867
- const serviceOptions: BackendServiceOption = backendApiService?.options ?? {};
868
- const isExecuteCommandOnInit = (!serviceOptions) ? false : ((serviceOptions?.hasOwnProperty('executeProcessCommandOnInit')) ? serviceOptions['executeProcessCommandOnInit'] : true);
869
-
870
- if (backendApiService) {
871
- // update backend filters (if need be) BEFORE the query runs (via the onInit command a few lines below)
872
- // if user entered some any "presets", we need to reflect them all in the grid
873
- if (gridOptions?.presets) {
874
- // Filters "presets"
875
- if (backendApiService.updateFilters && Array.isArray(gridOptions.presets.filters) && gridOptions.presets.filters.length > 0) {
876
- backendApiService.updateFilters(gridOptions.presets.filters, true);
877
- }
878
- // Sorters "presets"
879
- if (backendApiService.updateSorters && Array.isArray(gridOptions.presets.sorters) && gridOptions.presets.sorters.length > 0) {
880
- // when using multi-column sort, we can have multiple but on single sort then only grab the first sort provided
881
- const sortColumns = this._gridOptions?.multiColumnSort ? gridOptions.presets.sorters : gridOptions.presets.sorters.slice(0, 1);
882
- backendApiService.updateSorters(undefined, sortColumns);
883
- }
884
- // Pagination "presets"
885
- if (backendApiService.updatePagination && gridOptions.presets.pagination) {
886
- const { pageNumber, pageSize } = gridOptions.presets.pagination;
887
- backendApiService.updatePagination(pageNumber, pageSize);
888
- }
889
- } else {
890
- const columnFilters = this.filterService.getColumnFilters();
891
- if (columnFilters && backendApiService.updateFilters) {
892
- backendApiService.updateFilters(columnFilters, false);
893
- }
894
- }
895
-
896
- // execute onInit command when necessary
897
- if (backendApi && backendApiService && (backendApi.onInit || isExecuteCommandOnInit)) {
898
- const query = (typeof backendApiService.buildQuery === 'function') ? backendApiService.buildQuery() : '';
899
- const process = isExecuteCommandOnInit ? (backendApi.process?.(query) ?? null) : (backendApi.onInit?.(query) ?? null);
900
-
901
- // wrap this inside a setTimeout to avoid timing issue since the gridOptions needs to be ready before running this onInit
902
- setTimeout(() => {
903
- const backendUtilityService = this.backendUtilityService as BackendUtilityService;
904
- // keep start time & end timestamps & return it after process execution
905
- const startTime = new Date();
906
-
907
- // run any pre-process, if defined, for example a spinner
908
- if (backendApi.preProcess) {
909
- backendApi.preProcess();
910
- }
911
-
912
- // the processes can be a Promise (like Http)
913
- const totalItems = this.gridOptions?.pagination?.totalItems ?? 0;
914
- if (process instanceof Promise) {
915
- process
916
- .then((processResult: any) => backendUtilityService.executeBackendProcessesCallback(startTime, processResult, backendApi, totalItems))
917
- .catch((error) => backendUtilityService.onBackendError(error, backendApi));
918
- } else if (process && this.rxjs?.isObservable(process)) {
919
- this.subscriptions.push(
920
- (process as Observable<any>).subscribe(
921
- (processResult: any) => backendUtilityService.executeBackendProcessesCallback(startTime, processResult, backendApi, totalItems),
922
- (error: any) => backendUtilityService.onBackendError(error, backendApi)
923
- )
924
- );
925
- }
926
- });
927
- }
928
- }
929
- }
930
-
931
- bindResizeHook(grid: SlickGrid, options: GridOption) {
932
- if ((options.autoFitColumnsOnFirstLoad && options.autosizeColumnsByCellContentOnFirstLoad) || (options.enableAutoSizeColumns && options.enableAutoResizeColumnsByCellContent)) {
933
- throw new Error(`[Slickgrid-Universal] You cannot enable both autosize/fit viewport & resize by content, you must choose which resize technique to use. You can enable these 2 options ("autoFitColumnsOnFirstLoad" and "enableAutoSizeColumns") OR these other 2 options ("autosizeColumnsByCellContentOnFirstLoad" and "enableAutoResizeColumnsByCellContent").`);
934
- }
935
-
936
- // auto-resize grid on browser resize (optionally provide grid height or width)
937
- if (options.gridHeight || options.gridWidth) {
938
- this.resizerService.resizeGrid(0, { height: options.gridHeight, width: options.gridWidth });
939
- } else {
940
- this.resizerService.resizeGrid();
941
- }
942
-
943
- // expand/autofit columns on first page load
944
- if (grid && options?.enableAutoResize && options.autoFitColumnsOnFirstLoad && options.enableAutoSizeColumns && !this._isAutosizeColsCalled) {
945
- grid.autosizeColumns();
946
- this._isAutosizeColsCalled = true;
947
- }
948
- }
949
-
950
- executeAfterDataviewCreated(gridOptions: GridOption) {
951
- // if user entered some Sort "presets", we need to reflect them all in the DOM
952
- if (gridOptions.enableSorting) {
953
- if (gridOptions.presets && Array.isArray(gridOptions.presets.sorters)) {
954
- // when using multi-column sort, we can have multiple but on single sort then only grab the first sort provided
955
- const sortColumns = this._gridOptions?.multiColumnSort ? gridOptions.presets.sorters : gridOptions.presets.sorters.slice(0, 1);
956
- this.sortService.loadGridSorters(sortColumns);
957
- }
958
- }
959
- }
960
-
961
- /**
962
- * On a Pagination changed, we will trigger a Grid State changed with the new pagination info
963
- * Also if we use Row Selection or the Checkbox Selector with a Backend Service (Odata, GraphQL), we need to reset any selection
964
- */
965
- paginationChanged(pagination: ServicePagination) {
966
- const isSyncGridSelectionEnabled = this.gridStateService?.needToPreserveRowSelection() ?? false;
967
- if (this.slickGrid && !isSyncGridSelectionEnabled && this._gridOptions?.backendServiceApi && (this.gridOptions.enableRowSelection || this.gridOptions.enableCheckboxSelector)) {
968
- this.slickGrid.setSelectedRows([]);
969
- }
970
- const { pageNumber, pageSize } = pagination;
971
- if (this.sharedService) {
972
- if (pageSize !== undefined && pageNumber !== undefined) {
973
- this.sharedService.currentPagination = { pageNumber, pageSize };
974
- }
975
- }
976
- this._eventPubSubService.publish('onGridStateChanged', {
977
- change: { newValues: { pageNumber, pageSize }, type: GridStateType.pagination },
978
- gridState: this.gridStateService.getCurrentGridState()
979
- });
980
- }
981
-
982
- /**
983
- * When dataset changes, we need to refresh the entire grid UI & possibly resize it as well
984
- * @param dataset
985
- */
986
- refreshGridData(dataset: TData[], totalCount?: number) {
987
- // local grid, check if we need to show the Pagination
988
- // if so then also check if there's any presets and finally initialize the PaginationService
989
- // a local grid with Pagination presets will potentially have a different total of items, we'll need to get it from the DataView and update our total
990
- if (this.slickGrid && this._gridOptions) {
991
- if (this._gridOptions.enableEmptyDataWarningMessage && Array.isArray(dataset)) {
992
- const finalTotalCount = totalCount || dataset.length;
993
- this.displayEmptyDataWarning(finalTotalCount < 1);
994
- }
995
-
996
- if (Array.isArray(dataset) && this.slickGrid && this.dataView?.setItems) {
997
- this.dataView.setItems(dataset, this._gridOptions.datasetIdPropertyName);
998
- if (!this._gridOptions.backendServiceApi && !this._gridOptions.enableTreeData) {
999
- this.dataView.reSort();
1000
- }
1001
-
1002
- if (dataset.length > 0) {
1003
- if (!this._isDatasetInitialized) {
1004
- this.loadFilterPresetsWhenDatasetInitialized();
1005
-
1006
- if (this._gridOptions.enableCheckboxSelector) {
1007
- this.loadRowSelectionPresetWhenExists();
1008
- }
1009
- }
1010
- this._isDatasetInitialized = true;
1011
- }
1012
-
1013
- if (dataset) {
1014
- this.slickGrid.invalidate();
1015
- }
1016
-
1017
- // display the Pagination component only after calling this refresh data first, we call it here so that if we preset pagination page number it will be shown correctly
1018
- this.showPagination = (this._gridOptions && (this._gridOptions.enablePagination || (this._gridOptions.backendServiceApi && this._gridOptions.enablePagination === undefined))) ? true : false;
1019
-
1020
- if (this._paginationOptions && this._gridOptions?.pagination && this._gridOptions?.backendServiceApi) {
1021
- const paginationOptions = this.setPaginationOptionsWhenPresetDefined(this._gridOptions, this._paginationOptions);
1022
-
1023
- // when we have a totalCount use it, else we'll take it from the pagination object
1024
- // only update the total items if it's different to avoid refreshing the UI
1025
- const totalRecords = (totalCount !== undefined) ? totalCount : (this._gridOptions?.pagination?.totalItems);
1026
- if (totalRecords !== undefined && totalRecords !== this.totalItems) {
1027
- this.totalItems = +totalRecords;
1028
- }
1029
- // initialize the Pagination Service with new pagination options (which might have presets)
1030
- if (!this._isPaginationInitialized) {
1031
- this.initializePaginationService(paginationOptions);
1032
- } else {
1033
- // update the pagination service with the new total
1034
- this.paginationService.updateTotalItems(this.totalItems);
1035
- }
1036
- }
1037
-
1038
- // resize the grid inside a slight timeout, in case other DOM element changed prior to the resize (like a filter/pagination changed)
1039
- if (this.slickGrid && this._gridOptions.enableAutoResize) {
1040
- const delay = this._gridOptions.autoResize && this._gridOptions.autoResize.delay;
1041
- this.resizerService.resizeGrid(delay || 10);
1042
- }
1043
- }
1044
- }
1045
- }
1046
-
1047
- /**
1048
- * Dynamically change or update the column definitions list.
1049
- * We will re-render the grid so that the new header and data shows up correctly.
1050
- * If using translater, we also need to trigger a re-translate of the column headers
1051
- */
1052
- updateColumnDefinitionsList(newColumnDefinitions: Column<TData>[]) {
1053
- if (this.slickGrid && this._gridOptions && Array.isArray(newColumnDefinitions)) {
1054
- // map/swap the internal library Editor to the SlickGrid Editor factory
1055
- newColumnDefinitions = this.swapInternalEditorToSlickGridFactoryEditor(newColumnDefinitions);
1056
-
1057
- // if the user wants to automatically add a Custom Editor Formatter, we need to call the auto add function again
1058
- if (this._gridOptions.autoAddCustomEditorFormatter) {
1059
- autoAddEditorFormatterToColumnsWithEditor(newColumnDefinitions, this._gridOptions.autoAddCustomEditorFormatter);
1060
- }
1061
-
1062
- if (this._gridOptions.enableTranslate) {
1063
- this.extensionService.translateColumnHeaders(undefined, newColumnDefinitions);
1064
- } else {
1065
- this.extensionService.renderColumnHeaders(newColumnDefinitions, true);
1066
- }
1067
-
1068
- if (this.slickGrid && this._gridOptions?.enableAutoSizeColumns) {
1069
- this.slickGrid.autosizeColumns();
1070
- } else if (this._gridOptions?.enableAutoResizeColumnsByCellContent && this.resizerService?.resizeColumnsByCellContent) {
1071
- this.resizerService.resizeColumnsByCellContent();
1072
- }
1073
- }
1074
- }
1075
-
1076
- /**
1077
- * Show the filter row displayed on first row, we can optionally pass false to hide it.
1078
- * @param showing
1079
- */
1080
- showHeaderRow(showing = true) {
1081
- this.slickGrid?.setHeaderRowVisibility(showing);
1082
- if (this.slickGrid && showing === true && this._isGridInitialized) {
1083
- this.slickGrid.setColumns(this.columnDefinitions);
1084
- }
1085
- return showing;
1086
- }
1087
-
1088
- setData(data: TData[], shouldAutosizeColumns = false) {
1089
- if (shouldAutosizeColumns) {
1090
- this._isAutosizeColsCalled = false;
1091
- this._currentDatasetLength = 0;
1092
- }
1093
- this.dataset = data || [];
1094
- }
1095
-
1096
- /**
1097
- * Check if there's any Pagination Presets defined in the Grid Options,
1098
- * if there are then load them in the paginationOptions object
1099
- */
1100
- setPaginationOptionsWhenPresetDefined(gridOptions: GridOption, paginationOptions: Pagination): Pagination {
1101
- if (gridOptions.presets?.pagination && paginationOptions && !this._isPaginationInitialized) {
1102
- paginationOptions.pageSize = gridOptions.presets.pagination.pageSize;
1103
- paginationOptions.pageNumber = gridOptions.presets.pagination.pageNumber;
1104
- }
1105
- return paginationOptions;
1106
- }
1107
-
1108
- setDarkMode(dark = false) {
1109
- if (dark) {
1110
- this._gridParentContainerElm.classList.add('slick-dark-mode');
1111
- } else {
1112
- this._gridParentContainerElm.classList.remove('slick-dark-mode');
1113
- }
1114
- }
1115
-
1116
- // --
1117
- // protected functions
1118
- // ------------------
1119
-
1120
- /**
1121
- * Loop through all column definitions and copy the original optional `width` properties optionally provided by the user.
1122
- * We will use this when doing a resize by cell content, if user provided a `width` it won't override it.
1123
- */
1124
- protected copyColumnWidthsReference(columnDefinitions: Column<TData>[]) {
1125
- columnDefinitions.forEach(col => col.originalWidth = col.width);
1126
- }
1127
-
1128
- protected displayEmptyDataWarning(showWarning = true) {
1129
- if (this.gridOptions.enableEmptyDataWarningMessage) {
1130
- this.slickEmptyWarning?.showEmptyDataMessage(showWarning);
1131
- }
1132
- }
1133
-
1134
- /** When data changes in the DataView, we'll refresh the metrics and/or display a warning if the dataset is empty */
1135
- protected handleOnItemCountChanged(currentPageRowItemCount: number, totalItemCount: number) {
1136
- this._currentDatasetLength = totalItemCount;
1137
- this.metrics = {
1138
- startTime: new Date(),
1139
- endTime: new Date(),
1140
- itemCount: currentPageRowItemCount,
1141
- totalItemCount
1142
- };
1143
- // if custom footer is enabled, then we'll update its metrics
1144
- if (this.slickFooter) {
1145
- this.slickFooter.metrics = this.metrics;
1146
- }
1147
-
1148
- // when using local (in-memory) dataset, we'll display a warning message when filtered data is empty
1149
- if (this._isLocalGrid && this._gridOptions?.enableEmptyDataWarningMessage) {
1150
- this.displayEmptyDataWarning(currentPageRowItemCount === 0);
1151
- }
1152
- }
1153
-
1154
- /** Initialize the Pagination Service once */
1155
- protected initializePaginationService(paginationOptions: Pagination) {
1156
- if (this.slickGrid && this.gridOptions) {
1157
- this.paginationData = {
1158
- gridOptions: this.gridOptions,
1159
- paginationService: this.paginationService,
1160
- };
1161
- this.paginationService.totalItems = this.totalItems;
1162
- this.paginationService.init(this.slickGrid, paginationOptions, this.backendServiceApi);
1163
- this.subscriptions.push(
1164
- this._eventPubSubService.subscribe<ServicePagination>('onPaginationChanged', paginationChanges => this.paginationChanged(paginationChanges)),
1165
- this._eventPubSubService.subscribe<{ visible: boolean; }>('onPaginationVisibilityChanged', visibility => {
1166
- this.showPagination = visibility?.visible ?? false;
1167
- if (this.gridOptions?.backendServiceApi) {
1168
- this.backendUtilityService?.refreshBackendDataset(this.gridOptions);
1169
- }
1170
- this.renderPagination(this.showPagination);
1171
- })
1172
- );
1173
-
1174
- // also initialize (render) the pagination component
1175
- this.renderPagination();
1176
- this._isPaginationInitialized = true;
1177
- }
1178
- }
1179
-
1180
- /**
1181
- * Render (or dispose) the Pagination Component, user can optionally provide False (to not show it) which will in term dispose of the Pagination,
1182
- * also while disposing we can choose to omit the disposable of the Pagination Service (if we are simply toggling the Pagination, we want to keep the Service alive)
1183
- * @param {Boolean} showPagination - show (new render) or not (dispose) the Pagination
1184
- * @param {Boolean} shouldDisposePaginationService - when disposing the Pagination, do we also want to dispose of the Pagination Service? (defaults to True)
1185
- */
1186
- protected renderPagination(showPagination = true) {
1187
- if (this._gridOptions?.enablePagination && !this._isPaginationInitialized && showPagination) {
1188
- this.slickPagination = new SlickPaginationComponent(this.paginationService, this._eventPubSubService, this.sharedService, this.translaterService);
1189
- this.slickPagination.renderPagination(this._gridParentContainerElm);
1190
- this._isPaginationInitialized = true;
1191
- } else if (!showPagination) {
1192
- if (this.slickPagination) {
1193
- this.slickPagination.dispose();
1194
- }
1195
- this._isPaginationInitialized = false;
1196
- }
1197
- }
1198
-
1199
- /** Load the Editor Collection asynchronously and replace the "collection" property when Promise resolves */
1200
- protected loadEditorCollectionAsync(column: Column<TData>) {
1201
- if (column?.editor) {
1202
- const collectionAsync = column.editor.collectionAsync;
1203
- column.editor.disabled = true; // disable the Editor DOM element, we'll re-enable it after receiving the collection with "updateEditorCollection()"
1204
-
1205
- if (collectionAsync instanceof Promise) {
1206
- // wait for the "collectionAsync", once resolved we will save it into the "collection"
1207
- // the collectionAsync can be of 3 types HttpClient, HttpFetch or a Promise
1208
- collectionAsync.then((response: any | any[]) => {
1209
- if (Array.isArray(response)) {
1210
- this.updateEditorCollection(column, response); // from Promise
1211
- } else if (response?.status >= 200 && response.status < 300 && typeof response.json === 'function') {
1212
- if (response.bodyUsed) {
1213
- console.warn(`[SlickGrid-Universal] The response body passed to collectionAsync was already read.`
1214
- + `Either pass the dataset from the Response or clone the response first using response.clone()`);
1215
- } else {
1216
- // from Fetch
1217
- (response as Response).json().then(data => this.updateEditorCollection(column, data));
1218
- }
1219
- } else if (response?.content) {
1220
- this.updateEditorCollection(column, response['content']); // from http-client
1221
- }
1222
- });
1223
- } else if (this.rxjs?.isObservable(collectionAsync)) {
1224
- // wrap this inside a setTimeout to avoid timing issue since updateEditorCollection requires to call SlickGrid getColumns() method
1225
- setTimeout(() => {
1226
- this.subscriptions.push(
1227
- (collectionAsync as Observable<any>).subscribe((resolvedCollection) => this.updateEditorCollection(column, resolvedCollection))
1228
- );
1229
- });
1230
- }
1231
- }
1232
- }
1233
-
1234
- protected insertDynamicPresetColumns(columnId: string, gridPresetColumns: Column<TData>[]) {
1235
- if (this._columnDefinitions) {
1236
- const columnPosition = this._columnDefinitions.findIndex(c => c.id === columnId);
1237
- if (columnPosition >= 0) {
1238
- const dynColumn = this._columnDefinitions[columnPosition];
1239
- if (dynColumn?.id === columnId && !gridPresetColumns.some(c => c.id === columnId)) {
1240
- columnPosition > 0
1241
- ? gridPresetColumns.splice(columnPosition, 0, dynColumn)
1242
- : gridPresetColumns.unshift(dynColumn);
1243
- }
1244
- }
1245
- }
1246
- }
1247
-
1248
- /** Load any possible Columns Grid Presets */
1249
- protected loadColumnPresetsWhenDatasetInitialized() {
1250
- // if user entered some Columns "presets", we need to reflect them all in the grid
1251
- if (this.slickGrid && this.gridOptions.presets && Array.isArray(this.gridOptions.presets.columns) && this.gridOptions.presets.columns.length > 0) {
1252
- const gridPresetColumns: Column<TData>[] = this.gridStateService.getAssociatedGridColumns(this.slickGrid, this.gridOptions.presets.columns);
1253
- if (gridPresetColumns && Array.isArray(gridPresetColumns) && gridPresetColumns.length > 0 && Array.isArray(this._columnDefinitions)) {
1254
- // make sure that the dynamic columns are included in presets (1.Row Move, 2. Row Selection, 3. Row Detail)
1255
- if (this.gridOptions.enableRowMoveManager) {
1256
- const rmmColId = this.gridOptions?.rowMoveManager?.columnId ?? '_move';
1257
- this.insertDynamicPresetColumns(rmmColId, gridPresetColumns);
1258
- }
1259
- if (this.gridOptions.enableCheckboxSelector) {
1260
- const chkColId = this.gridOptions?.checkboxSelector?.columnId ?? '_checkbox_selector';
1261
- this.insertDynamicPresetColumns(chkColId, gridPresetColumns);
1262
- }
1263
- if (this.gridOptions.enableRowDetailView) {
1264
- const rdvColId = this.gridOptions?.rowDetailView?.columnId ?? '_detail_selector';
1265
- this.insertDynamicPresetColumns(rdvColId, gridPresetColumns);
1266
- }
1267
-
1268
- // keep copy the original optional `width` properties optionally provided by the user.
1269
- // We will use this when doing a resize by cell content, if user provided a `width` it won't override it.
1270
- gridPresetColumns.forEach(col => col.originalWidth = col.width);
1271
-
1272
- // finally set the new presets columns (including checkbox selector if need be)
1273
- this.slickGrid.setColumns(gridPresetColumns);
1274
- this.sharedService.visibleColumns = gridPresetColumns;
1275
- }
1276
- }
1277
- }
1278
-
1279
- /** Load any possible Filters Grid Presets */
1280
- protected loadFilterPresetsWhenDatasetInitialized() {
1281
- if (this.gridOptions && !this.customDataView) {
1282
- // if user entered some Filter "presets", we need to reflect them all in the DOM
1283
- // also note that a presets of Tree Data Toggling will also call this method because Tree Data toggling does work with data filtering
1284
- // (collapsing a parent will basically use Filter for hidding (aka collapsing) away the child underneat it)
1285
- if (this.gridOptions.presets && (Array.isArray(this.gridOptions.presets.filters) || Array.isArray(this.gridOptions.presets?.treeData?.toggledItems))) {
1286
- this.filterService.populateColumnFilterSearchTermPresets(this.gridOptions.presets?.filters || []);
1287
- }
1288
- }
1289
- }
1290
-
1291
- /**
1292
- * local grid, check if we need to show the Pagination
1293
- * if so then also check if there's any presets and finally initialize the PaginationService
1294
- * a local grid with Pagination presets will potentially have a different total of items, we'll need to get it from the DataView and update our total
1295
- */
1296
- protected loadLocalGridPagination(dataset?: TData[]) {
1297
- if (this.gridOptions && this._paginationOptions) {
1298
- this.totalItems = Array.isArray(dataset) ? dataset.length : 0;
1299
- if (this._paginationOptions && this.dataView?.getPagingInfo) {
1300
- const slickPagingInfo = this.dataView.getPagingInfo();
1301
- if (slickPagingInfo?.hasOwnProperty('totalRows') && this._paginationOptions.totalItems !== slickPagingInfo.totalRows) {
1302
- this.totalItems = slickPagingInfo?.totalRows || 0;
1303
- }
1304
- }
1305
- this._paginationOptions.totalItems = this.totalItems;
1306
- const paginationOptions = this.setPaginationOptionsWhenPresetDefined(this.gridOptions, this._paginationOptions);
1307
- this.initializePaginationService(paginationOptions);
1308
- }
1309
- }
1310
-
1311
- /** Load any Row Selections into the DataView that were presets by the user */
1312
- protected loadRowSelectionPresetWhenExists() {
1313
- // if user entered some Row Selections "presets"
1314
- const presets = this.gridOptions?.presets;
1315
- const selectionModel = this.slickGrid?.getSelectionModel();
1316
- const enableRowSelection = this.gridOptions && (this.gridOptions.enableCheckboxSelector || this.gridOptions.enableRowSelection);
1317
- if (this.slickGrid && this.dataView && enableRowSelection && selectionModel && presets?.rowSelection && (Array.isArray(presets.rowSelection.gridRowIndexes) || Array.isArray(presets.rowSelection.dataContextIds))) {
1318
- let dataContextIds = presets.rowSelection.dataContextIds;
1319
- let gridRowIndexes = presets.rowSelection.gridRowIndexes;
1320
-
1321
- // maps the IDs to the Grid Rows and vice versa, the "dataContextIds" has precedence over the other
1322
- if (Array.isArray(dataContextIds) && dataContextIds.length > 0) {
1323
- gridRowIndexes = this.dataView.mapIdsToRows(dataContextIds) || [];
1324
- } else if (Array.isArray(gridRowIndexes) && gridRowIndexes.length > 0) {
1325
- dataContextIds = this.dataView.mapRowsToIds(gridRowIndexes) || [];
1326
- }
1327
-
1328
- // apply row selection when defined as grid presets
1329
- if (this.slickGrid && Array.isArray(gridRowIndexes)) {
1330
- this.slickGrid.setSelectedRows(gridRowIndexes);
1331
- this.dataView!.setSelectedIds(dataContextIds || [], {
1332
- isRowBeingAdded: true,
1333
- shouldTriggerEvent: false, // do not trigger when presetting the grid
1334
- applyRowSelectionToGrid: true
1335
- });
1336
- }
1337
- }
1338
- }
1339
-
1340
- /** Add a register a new external resource, user could also optional dispose all previous resources before pushing any new resources to the resources array list. */
1341
- registerExternalResources(resources: ExternalResource[], disposePreviousResources = false) {
1342
- if (disposePreviousResources) {
1343
- this.disposeExternalResources();
1344
- }
1345
- resources.forEach(res => this._registeredResources.push(res));
1346
- this.initializeExternalResources(resources);
1347
- }
1348
-
1349
- resetExternalResources() {
1350
- this._registeredResources = [];
1351
- }
1352
-
1353
- /** Pre-Register any Resource that don't require SlickGrid to be instantiated (for example RxJS Resource) */
1354
- protected preRegisterResources() {
1355
- // bind & initialize all Components/Services that were tagged as enabled
1356
- // register all services by executing their init method and providing them with the Grid object
1357
- if (Array.isArray(this._registeredResources)) {
1358
- for (const resource of this._registeredResources) {
1359
- if (resource?.className === 'RxJsResource') {
1360
- this.registerRxJsResource(resource as RxJsFacade);
1361
- }
1362
- }
1363
- }
1364
- }
1365
-
1366
- protected initializeExternalResources(resources: ExternalResource[]) {
1367
- if (Array.isArray(resources)) {
1368
- for (const resource of resources) {
1369
- if (this.slickGrid && typeof resource.init === 'function') {
1370
- resource.init(this.slickGrid, this.universalContainerService);
1371
- }
1372
- }
1373
- }
1374
- }
1375
-
1376
- protected registerResources() {
1377
- // at this point, we consider all the registered services as external services, anything else registered afterward aren't external
1378
- if (Array.isArray(this._registeredResources)) {
1379
- this.sharedService.externalRegisteredResources = this._registeredResources;
1380
- }
1381
-
1382
- // push all other Services that we want to be registered
1383
- this._registeredResources.push(this.gridService, this.gridStateService);
1384
-
1385
- // when using Grouping/DraggableGrouping/Colspan register its Service
1386
- if (this.gridOptions.createPreHeaderPanel && !this.gridOptions.enableDraggableGrouping) {
1387
- this._registeredResources.push(this.groupingService);
1388
- }
1389
-
1390
- // when using Tree Data View, register its Service
1391
- if (this.gridOptions.enableTreeData) {
1392
- this._registeredResources.push(this.treeDataService);
1393
- }
1394
-
1395
- // when user enables translation, we need to translate Headers on first pass & subsequently in the bindDifferentHooks
1396
- if (this.gridOptions.enableTranslate) {
1397
- this.extensionService.translateColumnHeaders();
1398
- }
1399
-
1400
- // also initialize (render) the empty warning component
1401
- this.slickEmptyWarning = new SlickEmptyWarningComponent();
1402
- this._registeredResources.push(this.slickEmptyWarning);
1403
-
1404
- // bind & initialize all Components/Services that were tagged as enabled
1405
- // register all services by executing their init method and providing them with the Grid object
1406
- this.initializeExternalResources(this._registeredResources);
1407
- }
1408
-
1409
- /** Register the RxJS Resource in all necessary services which uses */
1410
- protected registerRxJsResource(resource: RxJsFacade) {
1411
- this.rxjs = resource;
1412
- this.backendUtilityService.addRxJsResource(this.rxjs);
1413
- this.filterFactory.addRxJsResource(this.rxjs);
1414
- this.filterService.addRxJsResource(this.rxjs);
1415
- this.sortService.addRxJsResource(this.rxjs);
1416
- this.paginationService.addRxJsResource(this.rxjs);
1417
- this.universalContainerService.registerInstance('RxJsFacade', this.rxjs);
1418
- this.universalContainerService.registerInstance('RxJsResource', this.rxjs);
1419
- }
1420
-
1421
- /**
1422
- * Takes a flat dataset with parent/child relationship, sort it (via its tree structure) and return the sorted flat array
1423
- * @returns {Array<Object>} sort flat parent/child dataset
1424
- */
1425
- protected sortTreeDataset<U>(flatDatasetInput: U[], forceGridRefresh = false): U[] {
1426
- const prevDatasetLn = this._currentDatasetLength;
1427
- let sortedDatasetResult;
1428
- let flatDatasetOutput: any[] = [];
1429
-
1430
- // if the hierarchical dataset was already initialized then no need to re-convert it, we can use it directly from the shared service ref
1431
- if (this._isDatasetHierarchicalInitialized && this.datasetHierarchical) {
1432
- sortedDatasetResult = this.treeDataService.sortHierarchicalDataset(this.datasetHierarchical);
1433
- flatDatasetOutput = sortedDatasetResult.flat;
1434
- } else if (Array.isArray(flatDatasetInput) && flatDatasetInput.length > 0) {
1435
- if (this.gridOptions?.treeDataOptions?.initialSort) {
1436
- // else we need to first convert the flat dataset to a hierarchical dataset and then sort
1437
- sortedDatasetResult = this.treeDataService.convertFlatParentChildToTreeDatasetAndSort(flatDatasetInput, this._columnDefinitions || [], this.gridOptions);
1438
- this.sharedService.hierarchicalDataset = sortedDatasetResult.hierarchical;
1439
- flatDatasetOutput = sortedDatasetResult.flat;
1440
- } else {
1441
- // else we assume that the user provided an array that is already sorted (user's responsability)
1442
- // and so we can simply convert the array to a tree structure and we're done, no need to sort
1443
- this.sharedService.hierarchicalDataset = this.treeDataService.convertFlatParentChildToTreeDataset(flatDatasetInput, this.gridOptions);
1444
- flatDatasetOutput = flatDatasetInput || [];
1445
- }
1446
- }
1447
-
1448
- // if we add/remove item(s) from the dataset, we need to also refresh our tree data filters
1449
- if (flatDatasetInput.length > 0 && (forceGridRefresh || flatDatasetInput.length !== prevDatasetLn)) {
1450
- this.filterService.refreshTreeDataFilters(flatDatasetOutput);
1451
- }
1452
-
1453
- return flatDatasetOutput;
1454
- }
1455
-
1456
- /**
1457
- * For convenience to the user, we provide the property "editor" as an Slickgrid-Universal editor complex object
1458
- * however "editor" is used internally by SlickGrid for it's own Editor Factory
1459
- * so in our lib we will swap "editor" and copy it into a new property called "internalColumnEditor"
1460
- * then take back "editor.model" and make it the new "editor" so that SlickGrid Editor Factory still works
1461
- */
1462
- protected swapInternalEditorToSlickGridFactoryEditor(columnDefinitions: Column<TData>[]): Column<TData>[] {
1463
- const columns = Array.isArray(columnDefinitions) ? columnDefinitions : [];
1464
-
1465
- if (columns.some(col => `${col.id}`.includes('.'))) {
1466
- console.error('[Slickgrid-Universal] Make sure that none of your Column Definition "id" property includes a dot in its name because that will cause some problems with the Editors. For example if your column definition "field" property is "user.firstName" then use "firstName" as the column "id".');
1467
- }
1468
-
1469
- return columns.map((column) => {
1470
- // on every Editor that have a "collectionAsync", resolve the data and assign it to the "collection" property
1471
- if (column.editor?.collectionAsync) {
1472
- this.loadEditorCollectionAsync(column);
1473
- }
1474
-
1475
- // @deprecated `internalColumnEditor`, if there's already an internalColumnEditor we'll use it, else it would be inside the editor
1476
- const columnEditor = column.internalColumnEditor || column.editor;
1477
-
1478
- return { ...column, editorClass: columnEditor?.model, internalColumnEditor: { ...columnEditor } };
1479
- });
1480
- }
1481
-
1482
- /**
1483
- * Update the "internalColumnEditor.collection" property.
1484
- * Since this is called after the async call resolves, the pointer will not be the same as the "column" argument passed.
1485
- * Once we found the new pointer, we will reassign the "editor" and "collection" to the "internalColumnEditor" so it has newest collection
1486
- */
1487
- protected updateEditorCollection<U extends TData = any>(column: Column<U>, newCollection: U[]) {
1488
- if (this.slickGrid && column.editor) {
1489
- column.editor.collection = newCollection;
1490
- column.editor.disabled = false;
1491
-
1492
- // find the new column reference pointer & re-assign the new editor to the internalColumnEditor
1493
- if (Array.isArray(this.columnDefinitions)) {
1494
- const columnRef = this.columnDefinitions.find((col: Column) => col.id === column.id);
1495
- if (columnRef) {
1496
- columnRef.internalColumnEditor = column.editor;
1497
- }
1498
- }
1499
-
1500
- // get current Editor, remove it from the DOm then re-enable it and re-render it with the new collection.
1501
- const currentEditor = this.slickGrid.getCellEditor() as AutocompleterEditor | SelectEditor;
1502
- if (currentEditor?.disable && currentEditor?.renderDomElement) {
1503
- if (typeof currentEditor.destroy === 'function') {
1504
- currentEditor.destroy();
1505
- }
1506
- currentEditor.disable(false);
1507
- currentEditor.renderDomElement(newCollection);
1508
- }
1509
- }
1510
- }
1511
- }
1
+ import { dequal } from 'dequal/lite';
2
+ import type {
3
+ BackendServiceApi,
4
+ BackendServiceOption,
5
+ Column,
6
+ DataViewOption,
7
+ ExtensionList,
8
+ ExternalResource,
9
+ GridOption,
10
+ Metrics,
11
+ Pagination,
12
+ SelectEditor,
13
+ ServicePagination,
14
+ Subscription,
15
+ RxJsFacade,
16
+ } from '@slickgrid-universal/common';
17
+
18
+ import {
19
+ autoAddEditorFormatterToColumnsWithEditor,
20
+ type AutocompleterEditor,
21
+ GlobalGridOptions,
22
+ GridStateType,
23
+ SlickGroupItemMetadataProvider,
24
+
25
+ // services
26
+ BackendUtilityService,
27
+ CollectionService,
28
+ ExtensionService,
29
+ ExtensionUtility,
30
+ FilterFactory,
31
+ FilterService,
32
+ GridEventService,
33
+ GridService,
34
+ GridStateService,
35
+ GroupingAndColspanService,
36
+ type Observable,
37
+ PaginationService,
38
+ ResizerService,
39
+ SharedService,
40
+ SortService,
41
+ SlickgridConfig,
42
+ type TranslaterService,
43
+ TreeDataService,
44
+
45
+ // utilities
46
+ deepCopy,
47
+ emptyElement,
48
+ unsubscribeAll,
49
+ SlickEventHandler,
50
+ SlickDataView,
51
+ SlickGrid
52
+ } from '@slickgrid-universal/common';
53
+ import { EventNamingStyle, EventPubSubService } from '@slickgrid-universal/event-pub-sub';
54
+ import { SlickEmptyWarningComponent } from '@slickgrid-universal/empty-warning-component';
55
+ import { SlickFooterComponent } from '@slickgrid-universal/custom-footer-component';
56
+ import { SlickPaginationComponent } from '@slickgrid-universal/pagination-component';
57
+ import { extend } from '@slickgrid-universal/utils';
58
+
59
+ import { type SlickerGridInstance } from '../interfaces/slickerGridInstance.interface';
60
+ import { UniversalContainerService } from '../services/universalContainer.service';
61
+
62
+ export class SlickVanillaGridBundle<TData = any> {
63
+ protected _currentDatasetLength = 0;
64
+ protected _eventPubSubService!: EventPubSubService;
65
+ protected _darkMode = false;
66
+ protected _columnDefinitions?: Column<TData>[];
67
+ protected _gridOptions?: GridOption;
68
+ protected _gridContainerElm!: HTMLElement;
69
+ protected _gridParentContainerElm!: HTMLElement;
70
+ protected _hideHeaderRowAfterPageLoad = false;
71
+ protected _isAutosizeColsCalled = false;
72
+ protected _isDatasetInitialized = false;
73
+ protected _isDatasetHierarchicalInitialized = false;
74
+ protected _isGridInitialized = false;
75
+ protected _isLocalGrid = true;
76
+ protected _isPaginationInitialized = false;
77
+ protected _eventHandler!: SlickEventHandler;
78
+ protected _extensions: ExtensionList<any> | undefined;
79
+ protected _paginationOptions: Pagination | undefined;
80
+ protected _registeredResources: ExternalResource[] = [];
81
+ protected _slickgridInitialized = false;
82
+ protected _slickerGridInstances: SlickerGridInstance | undefined;
83
+ backendServiceApi: BackendServiceApi | undefined;
84
+ dataView?: SlickDataView<TData>;
85
+ slickGrid?: SlickGrid;
86
+ metrics?: Metrics;
87
+ customDataView = false;
88
+ paginationData?: {
89
+ gridOptions: GridOption;
90
+ paginationService: PaginationService;
91
+ };
92
+ totalItems = 0;
93
+ groupItemMetadataProvider?: SlickGroupItemMetadataProvider;
94
+ resizerService!: ResizerService;
95
+ subscriptions: Subscription[] = [];
96
+ showPagination = false;
97
+
98
+ // extensions
99
+ extensionUtility!: ExtensionUtility;
100
+
101
+ // services
102
+ backendUtilityService!: BackendUtilityService;
103
+ collectionService!: CollectionService;
104
+ extensionService!: ExtensionService;
105
+ filterFactory!: FilterFactory;
106
+ filterService!: FilterService;
107
+ gridClass!: string;
108
+ gridClassName!: string;
109
+ gridEventService!: GridEventService;
110
+ gridService!: GridService;
111
+ gridStateService!: GridStateService;
112
+ groupingService!: GroupingAndColspanService;
113
+ paginationService!: PaginationService;
114
+ rxjs?: RxJsFacade;
115
+ sharedService!: SharedService;
116
+ sortService!: SortService;
117
+ translaterService: TranslaterService | undefined;
118
+ treeDataService!: TreeDataService;
119
+ universalContainerService!: UniversalContainerService;
120
+
121
+ // components
122
+ slickEmptyWarning: SlickEmptyWarningComponent | undefined;
123
+ slickFooter: SlickFooterComponent | undefined;
124
+ slickPagination: SlickPaginationComponent | undefined;
125
+
126
+ get eventHandler(): SlickEventHandler {
127
+ return this._eventHandler;
128
+ }
129
+
130
+ get columnDefinitions(): Column<TData>[] {
131
+ return this._columnDefinitions || [];
132
+ }
133
+ set columnDefinitions(columnDefinitions: Column<TData>[]) {
134
+ this._columnDefinitions = columnDefinitions;
135
+ if (this._slickgridInitialized) {
136
+ this.updateColumnDefinitionsList(this._columnDefinitions);
137
+ }
138
+ if (columnDefinitions.length > 0) {
139
+ this.copyColumnWidthsReference(columnDefinitions);
140
+ }
141
+ }
142
+
143
+ get dataset(): TData[] {
144
+ return this.dataView?.getItems() || [];
145
+ }
146
+ set dataset(newDataset: TData[]) {
147
+ const prevDatasetLn = this._currentDatasetLength;
148
+ const isDatasetEqual = dequal(newDataset, this.dataset || []);
149
+ const isDeepCopyDataOnPageLoadEnabled = !!(this._gridOptions?.enableDeepCopyDatasetOnPageLoad);
150
+ let data = isDeepCopyDataOnPageLoadEnabled ? deepCopy(newDataset || []) : newDataset;
151
+
152
+ // when Tree Data is enabled and we don't yet have the hierarchical dataset filled, we can force a convert+sort of the array
153
+ if (this.slickGrid && this.gridOptions?.enableTreeData && Array.isArray(newDataset) && (newDataset.length > 0 || newDataset.length !== prevDatasetLn || !isDatasetEqual)) {
154
+ this._isDatasetHierarchicalInitialized = false;
155
+ data = this.sortTreeDataset(newDataset, !isDatasetEqual); // if dataset changed, then force a refresh anyway
156
+ }
157
+
158
+ this.refreshGridData(data || []);
159
+ this._currentDatasetLength = (newDataset || []).length;
160
+
161
+ // expand/autofit columns on first page load
162
+ // we can assume that if the prevDataset was empty then we are on first load
163
+ if (this.slickGrid && this.gridOptions.autoFitColumnsOnFirstLoad && prevDatasetLn === 0 && !this._isAutosizeColsCalled) {
164
+ this.slickGrid.autosizeColumns();
165
+ this._isAutosizeColsCalled = true;
166
+ }
167
+ }
168
+
169
+ get datasetHierarchical(): any[] | undefined {
170
+ return this.sharedService.hierarchicalDataset;
171
+ }
172
+
173
+ set datasetHierarchical(newHierarchicalDataset: any[] | undefined) {
174
+ const isDatasetEqual = dequal(newHierarchicalDataset, this.sharedService.hierarchicalDataset || []);
175
+ const prevFlatDatasetLn = this._currentDatasetLength;
176
+ this.sharedService.hierarchicalDataset = newHierarchicalDataset;
177
+
178
+ if (newHierarchicalDataset && this.columnDefinitions && this.filterService?.clearFilters) {
179
+ this.filterService.clearFilters();
180
+ }
181
+
182
+ // when a hierarchical dataset is set afterward, we can reset the flat dataset and call a tree data sort that will overwrite the flat dataset
183
+ if (this.dataView && newHierarchicalDataset && this.slickGrid && this.sortService?.processTreeDataInitialSort) {
184
+ this.sortService.processTreeDataInitialSort();
185
+
186
+ // we also need to reset/refresh the Tree Data filters because if we inserted new item(s) then it might not show up without doing this refresh
187
+ // however we need 1 cpu cycle before having the DataView refreshed, so we need to wrap this check in a setTimeout
188
+ setTimeout(() => {
189
+ const flatDatasetLn = this.dataView?.getItemCount() ?? 0;
190
+ if (flatDatasetLn > 0 && (flatDatasetLn !== prevFlatDatasetLn || !isDatasetEqual)) {
191
+ this.filterService.refreshTreeDataFilters();
192
+ }
193
+ });
194
+ }
195
+
196
+ this._isDatasetHierarchicalInitialized = true;
197
+ }
198
+
199
+ set eventPubSubService(pubSub: EventPubSubService) {
200
+ this._eventPubSubService = pubSub;
201
+ }
202
+
203
+ set isDatasetHierarchicalInitialized(isInitialized: boolean) {
204
+ this._isDatasetHierarchicalInitialized = isInitialized;
205
+ }
206
+
207
+ get gridOptions(): GridOption {
208
+ return this._gridOptions || {} as GridOption;
209
+ }
210
+
211
+ set gridOptions(options: GridOption) {
212
+ let mergedOptions: GridOption;
213
+
214
+ // if we already have grid options, when grid was already initialized, we'll merge with those options
215
+ // else we'll merge with global grid options
216
+ if (this.slickGrid?.getOptions) {
217
+ mergedOptions = (extend<GridOption>(true, {} as GridOption, this.slickGrid.getOptions() as GridOption, options)) as GridOption;
218
+ } else {
219
+ mergedOptions = this.mergeGridOptions(options);
220
+ }
221
+
222
+ if (this.sharedService?.gridOptions && this.slickGrid?.setOptions) {
223
+ this.sharedService.gridOptions = mergedOptions;
224
+ this.slickGrid.setOptions(mergedOptions as any, false, true); // make sure to supressColumnCheck (3rd arg) to avoid problem with changeColumnsArrangement() and custom grid view
225
+ this.slickGrid.reRenderColumns(true); // then call a re-render since we did supressColumnCheck on previous setOptions
226
+ }
227
+
228
+ // add/remove dark mode CSS class to parent container
229
+ this.setDarkMode(options.darkMode);
230
+
231
+ this._gridOptions = mergedOptions;
232
+ }
233
+
234
+ get paginationOptions(): Pagination | undefined {
235
+ return this._paginationOptions;
236
+ }
237
+ set paginationOptions(newPaginationOptions: Pagination | undefined) {
238
+ if (newPaginationOptions && this._paginationOptions) {
239
+ this._paginationOptions = { ...this._paginationOptions, ...newPaginationOptions };
240
+ } else {
241
+ this._paginationOptions = newPaginationOptions;
242
+ }
243
+ this.gridOptions.pagination = this._paginationOptions;
244
+ this.paginationService.updateTotalItems(newPaginationOptions?.totalItems ?? 0, true);
245
+ }
246
+
247
+ get isDatasetInitialized(): boolean {
248
+ return this._isDatasetInitialized;
249
+ }
250
+ set isDatasetInitialized(isInitialized: boolean) {
251
+ this._isDatasetInitialized = isInitialized;
252
+ }
253
+ get isGridInitialized(): boolean {
254
+ return this._isGridInitialized;
255
+ }
256
+
257
+ get instances(): SlickerGridInstance | undefined {
258
+ return this._slickerGridInstances;
259
+ }
260
+
261
+ get extensions(): ExtensionList<any> | undefined {
262
+ return this._extensions;
263
+ }
264
+
265
+ get registeredResources(): any[] {
266
+ return this._registeredResources;
267
+ }
268
+
269
+ /**
270
+ * Slicker Grid Bundle constructor
271
+ * @param {Object} gridParentContainerElm - div HTML DOM element container
272
+ * @param {Array<Column>} columnDefs - Column Definitions
273
+ * @param {Object} options - Grid Options
274
+ * @param {Array<Object>} dataset - Dataset
275
+ * @param {Array<Object>} hierarchicalDataset - Hierarchical Dataset
276
+ * @param {Object} services - Typically only used for Unit Testing when we want to pass Mocked/Stub Services
277
+ */
278
+ constructor(
279
+ gridParentContainerElm: HTMLElement,
280
+ columnDefs?: Column<TData>[],
281
+ options?: Partial<GridOption>,
282
+ dataset?: TData[],
283
+ hierarchicalDataset?: any[],
284
+ services?: {
285
+ backendUtilityService?: BackendUtilityService,
286
+ collectionService?: CollectionService,
287
+ eventPubSubService?: EventPubSubService,
288
+ extensionService?: ExtensionService,
289
+ extensionUtility?: ExtensionUtility,
290
+ filterService?: FilterService,
291
+ gridEventService?: GridEventService,
292
+ gridService?: GridService,
293
+ gridStateService?: GridStateService,
294
+ groupingAndColspanService?: GroupingAndColspanService,
295
+ paginationService?: PaginationService,
296
+ resizerService?: ResizerService,
297
+ rxjs?: RxJsFacade,
298
+ sharedService?: SharedService,
299
+ sortService?: SortService,
300
+ treeDataService?: TreeDataService,
301
+ translaterService?: TranslaterService,
302
+ universalContainerService?: UniversalContainerService,
303
+ }
304
+ ) {
305
+ // make sure that the grid container doesn't already have the "slickgrid-container" css class
306
+ // if it does then we won't create yet another grid, just stop there
307
+ if (gridParentContainerElm.querySelectorAll('.slickgrid-container').length !== 0) {
308
+ return;
309
+ }
310
+
311
+ gridParentContainerElm.classList.add('grid-pane');
312
+ this._gridParentContainerElm = gridParentContainerElm as HTMLDivElement;
313
+ this._gridContainerElm = document.createElement('div') as HTMLDivElement;
314
+ this._gridContainerElm.classList.add('slickgrid-container');
315
+ gridParentContainerElm.appendChild(this._gridContainerElm);
316
+
317
+ // check if the user wants to hide the header row from the start
318
+ // we only want to do this check once in the constructor
319
+ this._hideHeaderRowAfterPageLoad = (options?.showHeaderRow === false);
320
+
321
+ this._columnDefinitions = columnDefs || [];
322
+ if (this._columnDefinitions.length > 0) {
323
+ this.copyColumnWidthsReference(this._columnDefinitions);
324
+ }
325
+
326
+ // save resource refs to register before the grid options are merged and possibly deep copied
327
+ // since a deep copy of grid options would lose original resource refs but we want to keep them as singleton
328
+ this._registeredResources = options?.externalResources || [];
329
+
330
+ this._gridOptions = this.mergeGridOptions(options || {});
331
+ const isDeepCopyDataOnPageLoadEnabled = !!(this._gridOptions?.enableDeepCopyDatasetOnPageLoad);
332
+
333
+ // add dark mode CSS class when enabled
334
+ if (this._gridOptions.darkMode) {
335
+ this.setDarkMode(true);
336
+ }
337
+
338
+ this.universalContainerService = services?.universalContainerService ?? new UniversalContainerService();
339
+
340
+ // if user is providing a Translate Service, it has to be passed under the "translater" grid option
341
+ this.translaterService = services?.translaterService ?? this._gridOptions?.translater;
342
+
343
+ // initialize and assign all Service Dependencies
344
+ this._eventPubSubService = services?.eventPubSubService ?? new EventPubSubService(gridParentContainerElm);
345
+ this._eventPubSubService.eventNamingStyle = this._gridOptions?.eventNamingStyle ?? EventNamingStyle.camelCase;
346
+
347
+ const slickgridConfig = new SlickgridConfig();
348
+ this.backendUtilityService = services?.backendUtilityService ?? new BackendUtilityService();
349
+ this.gridEventService = services?.gridEventService ?? new GridEventService();
350
+ this.sharedService = services?.sharedService ?? new SharedService();
351
+ this.collectionService = services?.collectionService ?? new CollectionService(this.translaterService);
352
+ this.extensionUtility = services?.extensionUtility ?? new ExtensionUtility(this.sharedService, this.backendUtilityService, this.translaterService);
353
+ this.filterFactory = new FilterFactory(slickgridConfig, this.translaterService, this.collectionService);
354
+ this.filterService = services?.filterService ?? new FilterService(this.filterFactory, this._eventPubSubService, this.sharedService, this.backendUtilityService);
355
+ this.resizerService = services?.resizerService ?? new ResizerService(this._eventPubSubService);
356
+ this.sortService = services?.sortService ?? new SortService(this.sharedService, this._eventPubSubService, this.backendUtilityService);
357
+ this.treeDataService = services?.treeDataService ?? new TreeDataService(this._eventPubSubService, this.sharedService, this.sortService);
358
+ this.paginationService = services?.paginationService ?? new PaginationService(this._eventPubSubService, this.sharedService, this.backendUtilityService);
359
+
360
+ this.extensionService = services?.extensionService ?? new ExtensionService(
361
+ this.extensionUtility,
362
+ this.filterService,
363
+ this._eventPubSubService,
364
+ this.sharedService,
365
+ this.sortService,
366
+ this.treeDataService,
367
+ this.translaterService,
368
+ () => this.gridService
369
+ );
370
+
371
+ this.gridStateService = services?.gridStateService ?? new GridStateService(this.extensionService, this.filterService, this._eventPubSubService, this.sharedService, this.sortService, this.treeDataService);
372
+ this.gridService = services?.gridService ?? new GridService(this.gridStateService, this.filterService, this._eventPubSubService, this.paginationService, this.sharedService, this.sortService, this.treeDataService);
373
+ this.groupingService = services?.groupingAndColspanService ?? new GroupingAndColspanService(this.extensionUtility, this._eventPubSubService);
374
+
375
+ if (hierarchicalDataset) {
376
+ this.sharedService.hierarchicalDataset = (isDeepCopyDataOnPageLoadEnabled ? deepCopy(hierarchicalDataset || []) : hierarchicalDataset) || [];
377
+ }
378
+ const eventHandler = new SlickEventHandler();
379
+
380
+ // register all service instances in the container
381
+ this.universalContainerService.registerInstance('PubSubService', this._eventPubSubService); // external resources require this one registration (ExcelExport, TextExport)
382
+ this.universalContainerService.registerInstance('EventPubSubService', this._eventPubSubService);
383
+ this.universalContainerService.registerInstance('ExtensionUtility', this.extensionUtility);
384
+ this.universalContainerService.registerInstance('FilterService', this.filterService);
385
+ this.universalContainerService.registerInstance('CollectionService', this.collectionService);
386
+ this.universalContainerService.registerInstance('ExtensionService', this.extensionService);
387
+ this.universalContainerService.registerInstance('GridEventService', this.gridEventService);
388
+ this.universalContainerService.registerInstance('GridService', this.gridService);
389
+ this.universalContainerService.registerInstance('GridStateService', this.gridStateService);
390
+ this.universalContainerService.registerInstance('GroupingAndColspanService', this.groupingService);
391
+ this.universalContainerService.registerInstance('PaginationService', this.paginationService);
392
+ this.universalContainerService.registerInstance('ResizerService', this.resizerService);
393
+ this.universalContainerService.registerInstance('SharedService', this.sharedService);
394
+ this.universalContainerService.registerInstance('SortService', this.sortService);
395
+ this.universalContainerService.registerInstance('TranslaterService', this.translaterService);
396
+ this.universalContainerService.registerInstance('TreeDataService', this.treeDataService);
397
+
398
+ this.initialization(this._gridContainerElm, eventHandler, dataset);
399
+ }
400
+
401
+ emptyGridContainerElm() {
402
+ const gridContainerId = this.gridOptions?.gridContainerId ?? 'grid1';
403
+ const gridContainerElm = document.querySelector(`#${gridContainerId}`);
404
+ emptyElement(gridContainerElm);
405
+ }
406
+
407
+ /** Dispose of the Component */
408
+ dispose(shouldEmptyDomElementContainer = false) {
409
+ this._eventPubSubService?.publish('onBeforeGridDestroy', this.slickGrid);
410
+ this._eventHandler?.unsubscribeAll();
411
+ this._eventPubSubService?.publish('onAfterGridDestroyed', true);
412
+
413
+ // dispose the Services
414
+ this.extensionService?.dispose();
415
+ this.filterService?.dispose();
416
+ this.gridEventService?.dispose();
417
+ this.gridService?.dispose();
418
+ this.gridStateService?.dispose();
419
+ this.groupingService?.dispose();
420
+ this.paginationService?.dispose();
421
+ this.resizerService?.dispose();
422
+ this.sortService?.dispose();
423
+ this.treeDataService?.dispose();
424
+ this.universalContainerService?.dispose();
425
+
426
+ // dispose all registered external resources
427
+ this.disposeExternalResources();
428
+
429
+ // dispose the Components
430
+ this.slickFooter?.dispose();
431
+ this.slickEmptyWarning?.dispose();
432
+ this.slickPagination?.dispose();
433
+
434
+ unsubscribeAll(this.subscriptions);
435
+ this._eventPubSubService?.unsubscribeAll();
436
+ this.dataView?.setItems([]);
437
+ if (typeof this.dataView?.destroy === 'function') {
438
+ this.dataView?.destroy();
439
+ }
440
+ this.slickGrid?.destroy(true);
441
+ this.slickGrid = null as any;
442
+
443
+ emptyElement(this._gridContainerElm);
444
+ emptyElement(this._gridParentContainerElm);
445
+ this._gridContainerElm?.remove();
446
+ this._gridParentContainerElm?.remove();
447
+
448
+ if (this.backendServiceApi) {
449
+ for (const prop of Object.keys(this.backendServiceApi)) {
450
+ this.backendServiceApi[prop as keyof BackendServiceApi] = null;
451
+ }
452
+ this.backendServiceApi = undefined;
453
+ }
454
+ for (const prop of Object.keys(this.columnDefinitions)) {
455
+ (this.columnDefinitions as any)[prop] = null;
456
+ }
457
+ for (const prop of Object.keys(this.sharedService)) {
458
+ (this.sharedService as any)[prop] = null;
459
+ }
460
+ this.datasetHierarchical = undefined;
461
+ this._columnDefinitions = [];
462
+
463
+ // we could optionally also empty the content of the grid container DOM element
464
+ if (shouldEmptyDomElementContainer) {
465
+ this.emptyGridContainerElm();
466
+ }
467
+ this._eventPubSubService?.dispose();
468
+ this._slickerGridInstances = null as any;
469
+ }
470
+
471
+ disposeExternalResources() {
472
+ if (Array.isArray(this._registeredResources)) {
473
+ while (this._registeredResources.length > 0) {
474
+ const res = this._registeredResources.pop();
475
+ if (res?.dispose) {
476
+ res.dispose();
477
+ }
478
+ }
479
+ }
480
+ this._registeredResources = [];
481
+ }
482
+
483
+ initialization(gridContainerElm: HTMLElement, eventHandler: SlickEventHandler, inputDataset?: TData[]) {
484
+ // when detecting a frozen grid, we'll automatically enable the mousewheel scroll handler so that we can scroll from both left/right frozen containers
485
+ if (this.gridOptions && ((this.gridOptions.frozenRow !== undefined && this.gridOptions.frozenRow >= 0) || this.gridOptions.frozenColumn !== undefined && this.gridOptions.frozenColumn >= 0) && this.gridOptions.enableMouseWheelScrollHandler === undefined) {
486
+ this.gridOptions.enableMouseWheelScrollHandler = true;
487
+ }
488
+
489
+ // create the slickgrid container and add it to the user's grid container
490
+ this._gridContainerElm = gridContainerElm;
491
+ this._eventPubSubService.publish('onBeforeGridCreate', true);
492
+
493
+ this._isAutosizeColsCalled = false;
494
+ this._eventHandler = eventHandler;
495
+ this._gridOptions = this.mergeGridOptions(this._gridOptions || {} as GridOption);
496
+ this.backendServiceApi = this._gridOptions?.backendServiceApi;
497
+ this._isLocalGrid = !this.backendServiceApi; // considered a local grid if it doesn't have a backend service set
498
+ this._eventPubSubService.eventNamingStyle = this._gridOptions?.eventNamingStyle ?? EventNamingStyle.camelCase;
499
+ this._paginationOptions = this.gridOptions?.pagination;
500
+
501
+ this.createBackendApiInternalPostProcessCallback(this._gridOptions);
502
+
503
+ if (!this.customDataView) {
504
+ const dataviewInlineFilters = this._gridOptions?.dataView?.inlineFilters ?? false;
505
+ let dataViewOptions: Partial<DataViewOption> = { ...this._gridOptions.dataView, inlineFilters: dataviewInlineFilters };
506
+
507
+ if (this.gridOptions.draggableGrouping || this.gridOptions.enableGrouping) {
508
+ this.groupItemMetadataProvider = new SlickGroupItemMetadataProvider();
509
+ this.sharedService.groupItemMetadataProvider = this.groupItemMetadataProvider;
510
+ dataViewOptions = { ...dataViewOptions, groupItemMetadataProvider: this.groupItemMetadataProvider };
511
+ }
512
+ this.dataView = new SlickDataView<TData>(dataViewOptions, this._eventPubSubService);
513
+ this._eventPubSubService.publish('onDataviewCreated', this.dataView);
514
+ }
515
+
516
+ // get any possible Services that user want to register which don't require SlickGrid to be instantiated
517
+ // RxJS Resource is in this lot because it has to be registered before anything else and doesn't require SlickGrid to be initialized
518
+ this.preRegisterResources();
519
+
520
+ // prepare and load all SlickGrid editors, if an async editor is found then we'll also execute it.
521
+ this._columnDefinitions = this.loadSlickGridEditors(this._columnDefinitions || []);
522
+
523
+ // if the user wants to automatically add a Custom Editor Formatter, we need to call the auto add function again
524
+ if (this._gridOptions?.autoAddCustomEditorFormatter) {
525
+ autoAddEditorFormatterToColumnsWithEditor(this._columnDefinitions, this._gridOptions.autoAddCustomEditorFormatter);
526
+ }
527
+
528
+ // save reference for all columns before they optionally become hidden/visible
529
+ this.sharedService.allColumns = this._columnDefinitions;
530
+ this.sharedService.visibleColumns = this._columnDefinitions;
531
+
532
+ // TODO: revisit later, this is conflicting with Grid State & Presets
533
+ // before certain extentions/plugins potentially adds extra columns not created by the user itself (RowMove, RowDetail, RowSelections)
534
+ // we'll subscribe to the event and push back the change to the user so they always use full column defs array including extra cols
535
+ // this.subscriptions.push(
536
+ // this._eventPubSubService.subscribe<{ columns: Column[]; pluginName: string }>('onPluginColumnsChanged', data => {
537
+ // this._columnDefinitions = this.columnDefinitions = data.columns;
538
+ // })
539
+ // );
540
+
541
+ // after subscribing to potential columns changed, we are ready to create these optional extensions
542
+ // when we did find some to create (RowMove, RowDetail, RowSelections), it will automatically modify column definitions (by previous subscribe)
543
+ this.extensionService.createExtensionsBeforeGridCreation(this._columnDefinitions, this._gridOptions);
544
+
545
+ // if user entered some Pinning/Frozen "presets", we need to apply them in the grid options
546
+ if (this.gridOptions.presets?.pinning) {
547
+ this.gridOptions = { ...this.gridOptions, ...this.gridOptions.presets.pinning };
548
+ }
549
+
550
+ this.slickGrid = new SlickGrid<TData, Column<TData>, GridOption<Column<TData>>>(gridContainerElm, this.dataView as SlickDataView<TData>, this._columnDefinitions, this._gridOptions, this._eventPubSubService);
551
+ this.sharedService.dataView = this.dataView as SlickDataView;
552
+ this.sharedService.slickGrid = this.slickGrid as SlickGrid;
553
+ this.sharedService.gridContainerElement = this._gridContainerElm;
554
+ if (this.groupItemMetadataProvider) {
555
+ this.slickGrid.registerPlugin(this.groupItemMetadataProvider); // register GroupItemMetadataProvider when Grouping is enabled
556
+ }
557
+
558
+ this.extensionService.bindDifferentExtensions();
559
+ this.bindDifferentHooks(this.slickGrid, this._gridOptions, this.dataView as SlickDataView);
560
+ this._slickgridInitialized = true;
561
+
562
+ // when it's a frozen grid, we need to keep the frozen column id for reference if we ever show/hide column from ColumnPicker/GridMenu afterward
563
+ const frozenColumnIndex = this._gridOptions?.frozenColumn ?? -1;
564
+ if (frozenColumnIndex >= 0 && frozenColumnIndex <= this._columnDefinitions.length && this._columnDefinitions.length > 0) {
565
+ this.sharedService.frozenVisibleColumnId = this._columnDefinitions[frozenColumnIndex]?.id ?? '';
566
+ }
567
+
568
+ // get any possible Services that user want to register
569
+ this.registerResources();
570
+
571
+ // initialize the SlickGrid grid
572
+ this.slickGrid.init();
573
+
574
+ // initialized the resizer service only after SlickGrid is initialized
575
+ // if we don't we end up binding our resize to a grid element that doesn't yet exist in the DOM and the resizer service will fail silently (because it has a try/catch that unbinds the resize without throwing back)
576
+ this.resizerService.init(this.slickGrid, this._gridParentContainerElm);
577
+
578
+ // user could show a custom footer with the data metrics (dataset length and last updated timestamp)
579
+ if (!this.gridOptions.enablePagination && this.gridOptions.showCustomFooter && this.gridOptions.customFooterOptions) {
580
+ this.slickFooter = new SlickFooterComponent(this.slickGrid, this.gridOptions.customFooterOptions, this._eventPubSubService, this.translaterService);
581
+ this.slickFooter.renderFooter(this._gridParentContainerElm);
582
+ }
583
+
584
+ // load the data in the DataView (unless it's a hierarchical dataset, if so it will be loaded after the initial tree sort)
585
+ if (this.dataView) {
586
+ inputDataset = inputDataset || [];
587
+ const initialDataset = this.gridOptions?.enableTreeData ? this.sortTreeDataset(inputDataset) : inputDataset;
588
+ this.dataView.beginUpdate();
589
+ this.dataView.setItems(initialDataset, this._gridOptions.datasetIdPropertyName);
590
+ this._currentDatasetLength = inputDataset.length;
591
+ this.dataView.endUpdate();
592
+ }
593
+
594
+ // if you don't want the items that are not visible (due to being filtered out or being on a different page)
595
+ // to stay selected, pass 'false' to the second arg
596
+ if (this.slickGrid?.getSelectionModel() && this._gridOptions?.dataView?.hasOwnProperty('syncGridSelection')) {
597
+ // if we are using a Backend Service, we will do an extra flag check, the reason is because it might have some unintended behaviors
598
+ // with the BackendServiceApi because technically the data in the page changes the DataView on every page change.
599
+ let preservedRowSelectionWithBackend = false;
600
+ if (this._gridOptions.backendServiceApi && this._gridOptions.dataView.hasOwnProperty('syncGridSelectionWithBackendService')) {
601
+ preservedRowSelectionWithBackend = this._gridOptions.dataView.syncGridSelectionWithBackendService as boolean;
602
+ }
603
+
604
+ const syncGridSelection = this._gridOptions.dataView.syncGridSelection;
605
+ if (typeof syncGridSelection === 'boolean') {
606
+ let preservedRowSelection = syncGridSelection;
607
+ if (!this._isLocalGrid) {
608
+ // when using BackendServiceApi, we'll be using the "syncGridSelectionWithBackendService" flag BUT "syncGridSelection" must also be set to True
609
+ preservedRowSelection = syncGridSelection && preservedRowSelectionWithBackend;
610
+ }
611
+ this.dataView?.syncGridSelection(this.slickGrid, preservedRowSelection);
612
+ } else if (typeof syncGridSelection === 'object') {
613
+ this.dataView?.syncGridSelection(this.slickGrid, syncGridSelection.preserveHidden, syncGridSelection.preserveHiddenOnSelectionChange);
614
+ }
615
+ }
616
+
617
+ if ((this.dataView?.getLength() ?? 0) > 0) {
618
+ if (!this._isDatasetInitialized && (this._gridOptions.enableCheckboxSelector || this._gridOptions.enableRowSelection)) {
619
+ this.loadRowSelectionPresetWhenExists();
620
+ }
621
+ this.loadFilterPresetsWhenDatasetInitialized();
622
+ this._isDatasetInitialized = true;
623
+ } else {
624
+ this.displayEmptyDataWarning(true);
625
+ }
626
+
627
+ // user might want to hide the header row on page load but still have `enableFiltering: true`
628
+ // if that is the case, we need to hide the headerRow ONLY AFTER all filters got created & dataView exist
629
+ if (this._hideHeaderRowAfterPageLoad) {
630
+ this.showHeaderRow(false);
631
+ this.sharedService.hideHeaderRowAfterPageLoad = this._hideHeaderRowAfterPageLoad;
632
+ }
633
+
634
+ // on cell click, mainly used with the columnDef.action callback
635
+ this.gridEventService.bindOnBeforeEditCell(this.slickGrid);
636
+ this.gridEventService.bindOnCellChange(this.slickGrid);
637
+ this.gridEventService.bindOnClick(this.slickGrid);
638
+
639
+ // bind the Backend Service API callback functions only after the grid is initialized
640
+ // because the preProcess() and onInit() might get triggered
641
+ if (this.gridOptions?.backendServiceApi) {
642
+ this.bindBackendCallbackFunctions(this.gridOptions);
643
+ }
644
+
645
+ // publish & dispatch certain events
646
+ this._eventPubSubService.publish('onGridCreated', this.slickGrid);
647
+
648
+ // after the DataView is created & updated execute some processes & dispatch some events
649
+ if (!this.customDataView) {
650
+ this.executeAfterDataviewCreated(this.gridOptions);
651
+ }
652
+
653
+ // bind resize ONLY after the dataView is ready
654
+ this.bindResizeHook(this.slickGrid, this.gridOptions);
655
+
656
+ // local grid, check if we need to show the Pagination
657
+ // if so then also check if there's any presets and finally initialize the PaginationService
658
+ // a local grid with Pagination presets will potentially have a different total of items, we'll need to get it from the DataView and update our total
659
+ if (this.gridOptions?.enablePagination && this._isLocalGrid) {
660
+ this.showPagination = true;
661
+ this.loadLocalGridPagination(this.dataset);
662
+ }
663
+
664
+ // once the grid is created, we'll return its instance (we do this to return Transient Services from DI)
665
+ this._slickerGridInstances = {
666
+ // Slick Grid & DataView objects
667
+ dataView: this.dataView as SlickDataView,
668
+ slickGrid: this.slickGrid,
669
+
670
+ // public methods
671
+ dispose: this.dispose.bind(this),
672
+
673
+ // return all available Services (non-singleton)
674
+ backendService: this.gridOptions?.backendServiceApi?.service,
675
+ eventPubSubService: this._eventPubSubService,
676
+ filterService: this.filterService,
677
+ gridEventService: this.gridEventService,
678
+ gridStateService: this.gridStateService,
679
+ gridService: this.gridService,
680
+ groupingService: this.groupingService,
681
+ extensionService: this.extensionService,
682
+ extensionUtility: this.extensionUtility,
683
+ paginationService: this.paginationService,
684
+ resizerService: this.resizerService,
685
+ sortService: this.sortService,
686
+ treeDataService: this.treeDataService,
687
+ };
688
+
689
+ // addons (SlickGrid extra plugins/controls)
690
+ this._extensions = this.extensionService?.extensionList;
691
+
692
+ // all instances (SlickGrid, DataView & all Services)
693
+ this._eventPubSubService.publish('onSlickerGridCreated', this.instances);
694
+ this._isGridInitialized = true;
695
+ }
696
+
697
+ mergeGridOptions(gridOptions: GridOption) {
698
+ const options = extend<GridOption>(true, {}, GlobalGridOptions, gridOptions);
699
+
700
+ // also make sure to show the header row if user have enabled filtering
701
+ if (options.enableFiltering && !options.showHeaderRow) {
702
+ options.showHeaderRow = options.enableFiltering;
703
+ }
704
+
705
+ // using copy extend to do a deep clone has an unwanted side on objects and pageSizes but ES6 spread has other worst side effects
706
+ // so we will just overwrite the pageSizes when needed, this is the only one causing issues so far.
707
+ // On a deep extend, Object and Array are extended, but object wrappers on primitive types such as String, Boolean, and Number are not.
708
+ if (options?.pagination && (gridOptions.enablePagination || gridOptions.backendServiceApi) && gridOptions.pagination && Array.isArray(gridOptions.pagination.pageSizes)) {
709
+ options.pagination.pageSizes = gridOptions.pagination.pageSizes;
710
+ }
711
+
712
+ // when we use Pagination on Local Grid, it doesn't seem to work without enableFiltering
713
+ // so we'll enable the filtering but we'll keep the header row hidden
714
+ if (this.sharedService && !options.enableFiltering && options.enablePagination && this._isLocalGrid) {
715
+ options.enableFiltering = true;
716
+ options.showHeaderRow = false;
717
+ this._hideHeaderRowAfterPageLoad = true;
718
+ this.sharedService.hideHeaderRowAfterPageLoad = true;
719
+ }
720
+
721
+ return options;
722
+ }
723
+
724
+ /**
725
+ * Define our internal Post Process callback, it will execute internally after we get back result from the Process backend call
726
+ * For now, this is GraphQL Service ONLY feature and it will basically
727
+ * refresh the Dataset & Pagination without having the user to create his own PostProcess every time
728
+ */
729
+ createBackendApiInternalPostProcessCallback(gridOptions?: GridOption) {
730
+ const backendApi = gridOptions?.backendServiceApi;
731
+ if (backendApi?.service) {
732
+ const backendApiService = backendApi.service;
733
+
734
+ // internalPostProcess only works (for now) with a GraphQL Service, so make sure it is of that type
735
+ if (/* backendApiService instanceof GraphqlService || */ typeof backendApiService.getDatasetName === 'function') {
736
+ backendApi.internalPostProcess = (processResult: any) => {
737
+ const datasetName = (backendApi && backendApiService && typeof backendApiService.getDatasetName === 'function') ? backendApiService.getDatasetName() : '';
738
+ if (processResult && processResult.data && processResult.data[datasetName]) {
739
+ const data = processResult.data[datasetName].hasOwnProperty('nodes') ? (processResult as any).data[datasetName].nodes : (processResult as any).data[datasetName];
740
+ const totalCount = processResult.data[datasetName].hasOwnProperty('totalCount') ? (processResult as any).data[datasetName].totalCount : (processResult as any).data[datasetName].length;
741
+ this.refreshGridData(data, totalCount || 0);
742
+ }
743
+ };
744
+ }
745
+ }
746
+ }
747
+
748
+ bindDifferentHooks(grid: SlickGrid, gridOptions: GridOption, dataView: SlickDataView<TData>) {
749
+ // if user is providing a Translate Service, we need to add our PubSub Service (but only after creating all dependencies)
750
+ // so that we can later subscribe to the "onLanguageChange" event and translate any texts whenever that get triggered
751
+ if (gridOptions.enableTranslate && this.translaterService?.addPubSubMessaging) {
752
+ this.translaterService.addPubSubMessaging(this._eventPubSubService);
753
+ }
754
+
755
+ // translate them all on first load, then on each language change
756
+ if (gridOptions.enableTranslate) {
757
+ this.extensionService.translateAllExtensions();
758
+ }
759
+
760
+ // on locale change, we have to manually translate the Headers, GridMenu
761
+ this.subscriptions.push(
762
+ this._eventPubSubService.subscribe('onLanguageChange', (args: { language: string; }) => {
763
+ if (gridOptions.enableTranslate) {
764
+ this.extensionService.translateAllExtensions(args.language);
765
+ if (gridOptions.createPreHeaderPanel && !gridOptions.enableDraggableGrouping) {
766
+ this.groupingService.translateGroupingAndColSpan();
767
+ }
768
+ }
769
+ })
770
+ );
771
+
772
+ // if user set an onInit Backend, we'll run it right away (and if so, we also need to run preProcess, internalPostProcess & postProcess)
773
+ if (gridOptions.backendServiceApi) {
774
+ const backendApi = gridOptions.backendServiceApi;
775
+
776
+ if (backendApi?.service?.init) {
777
+ backendApi.service.init(backendApi.options, gridOptions.pagination, this.slickGrid, this.sharedService);
778
+ }
779
+ }
780
+
781
+ if (dataView && grid) {
782
+ // after all events are exposed
783
+ // we can bind external filter (backend) when available or default onFilter (dataView)
784
+ if (gridOptions.enableFiltering) {
785
+ this.filterService.init(grid);
786
+
787
+ // bind external filter (backend) unless specified to use the local one
788
+ if (gridOptions.backendServiceApi && !gridOptions.backendServiceApi.useLocalFiltering) {
789
+ this.filterService.bindBackendOnFilter(grid);
790
+ } else {
791
+ this.filterService.bindLocalOnFilter(grid);
792
+ }
793
+ }
794
+
795
+ // bind external sorting (backend) when available or default onSort (dataView)
796
+ if (gridOptions.enableSorting) {
797
+ // bind external sorting (backend) unless specified to use the local one
798
+ if (gridOptions.backendServiceApi && !gridOptions.backendServiceApi.useLocalSorting) {
799
+ this.sortService.bindBackendOnSort(grid);
800
+ } else {
801
+ this.sortService.bindLocalOnSort(grid);
802
+ }
803
+ }
804
+
805
+ // When data changes in the DataView, we need to refresh the metrics and/or display a warning if the dataset is empty
806
+ this._eventHandler.subscribe(dataView.onRowCountChanged, () => {
807
+ grid.invalidate();
808
+ this.handleOnItemCountChanged(this.dataView?.getFilteredItemCount() || 0, this.dataView?.getItemCount() ?? 0);
809
+ });
810
+ this._eventHandler.subscribe(dataView.onSetItemsCalled, (_e, args) => {
811
+ this.handleOnItemCountChanged(this.dataView?.getFilteredItemCount() || 0, args.itemCount);
812
+
813
+ // when user has resize by content enabled, we'll force a full width calculation since we change our entire dataset
814
+ if (args.itemCount > 0 && (this.gridOptions.autosizeColumnsByCellContentOnFirstLoad || this.gridOptions.enableAutoResizeColumnsByCellContent)) {
815
+ this.resizerService.resizeColumnsByCellContent(!this.gridOptions?.resizeByContentOnlyOnFirstLoad);
816
+ }
817
+ });
818
+
819
+ // when filtering data with local dataset, we need to update each row else it will not always show correctly in the UI
820
+ // also don't use "invalidateRows" since it destroys the entire row and as bad user experience when updating a row
821
+ if (gridOptions?.enableFiltering && !gridOptions.enableRowDetailView) {
822
+ this._eventHandler.subscribe(dataView.onRowsChanged, (_e, args) => {
823
+ if (args?.rows && Array.isArray(args.rows)) {
824
+ args.rows.forEach((row: number) => grid.updateRow(row));
825
+ grid.render();
826
+ }
827
+ });
828
+ }
829
+
830
+ // when column are reordered, we need to update the visibleColumn array
831
+ this._eventHandler.subscribe(grid.onColumnsReordered, (_e, args) => {
832
+ this.sharedService.hasColumnsReordered = true;
833
+ this.sharedService.visibleColumns = args.impactedColumns;
834
+ });
835
+
836
+ this._eventHandler.subscribe(grid.onSetOptions, (_e, args) => {
837
+ // add/remove dark mode CSS class when enabled
838
+ if (args.optionsBefore.darkMode !== args.optionsAfter.darkMode) {
839
+ this.setDarkMode(args.optionsAfter.darkMode);
840
+ }
841
+ });
842
+
843
+ // load any presets if any (after dataset is initialized)
844
+ this.loadColumnPresetsWhenDatasetInitialized();
845
+ this.loadFilterPresetsWhenDatasetInitialized();
846
+ }
847
+
848
+ // did the user add a colspan callback? If so, hook it into the DataView getItemMetadata
849
+ if (gridOptions?.colspanCallback && dataView?.getItem && dataView?.getItemMetadata) {
850
+ dataView.getItemMetadata = (rowNumber: number) => {
851
+ let callbackResult = null;
852
+ if (gridOptions.colspanCallback) {
853
+ callbackResult = gridOptions.colspanCallback(dataView.getItem(rowNumber));
854
+ }
855
+ return callbackResult;
856
+ };
857
+ }
858
+ }
859
+
860
+ bindBackendCallbackFunctions(gridOptions: GridOption) {
861
+ const backendApi = gridOptions.backendServiceApi;
862
+ const backendApiService = backendApi?.service;
863
+ const serviceOptions: BackendServiceOption = backendApiService?.options ?? {};
864
+ const isExecuteCommandOnInit = (!serviceOptions) ? false : ((serviceOptions?.hasOwnProperty('executeProcessCommandOnInit')) ? serviceOptions['executeProcessCommandOnInit'] : true);
865
+
866
+ if (backendApiService) {
867
+ // update backend filters (if need be) BEFORE the query runs (via the onInit command a few lines below)
868
+ // if user entered some any "presets", we need to reflect them all in the grid
869
+ if (gridOptions?.presets) {
870
+ // Filters "presets"
871
+ if (backendApiService.updateFilters && Array.isArray(gridOptions.presets.filters) && gridOptions.presets.filters.length > 0) {
872
+ backendApiService.updateFilters(gridOptions.presets.filters, true);
873
+ }
874
+ // Sorters "presets"
875
+ if (backendApiService.updateSorters && Array.isArray(gridOptions.presets.sorters) && gridOptions.presets.sorters.length > 0) {
876
+ // when using multi-column sort, we can have multiple but on single sort then only grab the first sort provided
877
+ const sortColumns = this._gridOptions?.multiColumnSort ? gridOptions.presets.sorters : gridOptions.presets.sorters.slice(0, 1);
878
+ backendApiService.updateSorters(undefined, sortColumns);
879
+ }
880
+ // Pagination "presets"
881
+ if (backendApiService.updatePagination && gridOptions.presets.pagination) {
882
+ const { pageNumber, pageSize } = gridOptions.presets.pagination;
883
+ backendApiService.updatePagination(pageNumber, pageSize);
884
+ }
885
+ } else {
886
+ const columnFilters = this.filterService.getColumnFilters();
887
+ if (columnFilters && backendApiService.updateFilters) {
888
+ backendApiService.updateFilters(columnFilters, false);
889
+ }
890
+ }
891
+
892
+ // execute onInit command when necessary
893
+ if (backendApi && backendApiService && (backendApi.onInit || isExecuteCommandOnInit)) {
894
+ const query = (typeof backendApiService.buildQuery === 'function') ? backendApiService.buildQuery() : '';
895
+ const process = isExecuteCommandOnInit ? (backendApi.process?.(query) ?? null) : (backendApi.onInit?.(query) ?? null);
896
+
897
+ // wrap this inside a setTimeout to avoid timing issue since the gridOptions needs to be ready before running this onInit
898
+ setTimeout(() => {
899
+ const backendUtilityService = this.backendUtilityService as BackendUtilityService;
900
+ // keep start time & end timestamps & return it after process execution
901
+ const startTime = new Date();
902
+
903
+ // run any pre-process, if defined, for example a spinner
904
+ if (backendApi.preProcess) {
905
+ backendApi.preProcess();
906
+ }
907
+
908
+ // the processes can be a Promise (like Http)
909
+ const totalItems = this.gridOptions?.pagination?.totalItems ?? 0;
910
+ if (process instanceof Promise) {
911
+ process
912
+ .then((processResult: any) => backendUtilityService.executeBackendProcessesCallback(startTime, processResult, backendApi, totalItems))
913
+ .catch((error) => backendUtilityService.onBackendError(error, backendApi));
914
+ } else if (process && this.rxjs?.isObservable(process)) {
915
+ this.subscriptions.push(
916
+ (process as Observable<any>).subscribe(
917
+ (processResult: any) => backendUtilityService.executeBackendProcessesCallback(startTime, processResult, backendApi, totalItems),
918
+ (error: any) => backendUtilityService.onBackendError(error, backendApi)
919
+ )
920
+ );
921
+ }
922
+ });
923
+ }
924
+ }
925
+ }
926
+
927
+ bindResizeHook(grid: SlickGrid, options: GridOption) {
928
+ if ((options.autoFitColumnsOnFirstLoad && options.autosizeColumnsByCellContentOnFirstLoad) || (options.enableAutoSizeColumns && options.enableAutoResizeColumnsByCellContent)) {
929
+ throw new Error(`[Slickgrid-Universal] You cannot enable both autosize/fit viewport & resize by content, you must choose which resize technique to use. You can enable these 2 options ("autoFitColumnsOnFirstLoad" and "enableAutoSizeColumns") OR these other 2 options ("autosizeColumnsByCellContentOnFirstLoad" and "enableAutoResizeColumnsByCellContent").`);
930
+ }
931
+
932
+ // auto-resize grid on browser resize (optionally provide grid height or width)
933
+ if (options.gridHeight || options.gridWidth) {
934
+ this.resizerService.resizeGrid(0, { height: options.gridHeight, width: options.gridWidth });
935
+ } else {
936
+ this.resizerService.resizeGrid();
937
+ }
938
+
939
+ // expand/autofit columns on first page load
940
+ if (grid && options?.enableAutoResize && options.autoFitColumnsOnFirstLoad && options.enableAutoSizeColumns && !this._isAutosizeColsCalled) {
941
+ grid.autosizeColumns();
942
+ this._isAutosizeColsCalled = true;
943
+ }
944
+ }
945
+
946
+ executeAfterDataviewCreated(gridOptions: GridOption) {
947
+ // if user entered some Sort "presets", we need to reflect them all in the DOM
948
+ if (gridOptions.enableSorting) {
949
+ if (gridOptions.presets && Array.isArray(gridOptions.presets.sorters)) {
950
+ // when using multi-column sort, we can have multiple but on single sort then only grab the first sort provided
951
+ const sortColumns = this._gridOptions?.multiColumnSort ? gridOptions.presets.sorters : gridOptions.presets.sorters.slice(0, 1);
952
+ this.sortService.loadGridSorters(sortColumns);
953
+ }
954
+ }
955
+ }
956
+
957
+ /**
958
+ * On a Pagination changed, we will trigger a Grid State changed with the new pagination info
959
+ * Also if we use Row Selection or the Checkbox Selector with a Backend Service (Odata, GraphQL), we need to reset any selection
960
+ */
961
+ paginationChanged(pagination: ServicePagination) {
962
+ const isSyncGridSelectionEnabled = this.gridStateService?.needToPreserveRowSelection() ?? false;
963
+ if (this.slickGrid && !isSyncGridSelectionEnabled && this._gridOptions?.backendServiceApi && (this.gridOptions.enableRowSelection || this.gridOptions.enableCheckboxSelector)) {
964
+ this.slickGrid.setSelectedRows([]);
965
+ }
966
+ const { pageNumber, pageSize } = pagination;
967
+ if (this.sharedService) {
968
+ if (pageSize !== undefined && pageNumber !== undefined) {
969
+ this.sharedService.currentPagination = { pageNumber, pageSize };
970
+ }
971
+ }
972
+ this._eventPubSubService.publish('onGridStateChanged', {
973
+ change: { newValues: { pageNumber, pageSize }, type: GridStateType.pagination },
974
+ gridState: this.gridStateService.getCurrentGridState()
975
+ });
976
+ }
977
+
978
+ /**
979
+ * When dataset changes, we need to refresh the entire grid UI & possibly resize it as well
980
+ * @param dataset
981
+ */
982
+ refreshGridData(dataset: TData[], totalCount?: number) {
983
+ // local grid, check if we need to show the Pagination
984
+ // if so then also check if there's any presets and finally initialize the PaginationService
985
+ // a local grid with Pagination presets will potentially have a different total of items, we'll need to get it from the DataView and update our total
986
+ if (this.slickGrid && this._gridOptions) {
987
+ if (this._gridOptions.enableEmptyDataWarningMessage && Array.isArray(dataset)) {
988
+ const finalTotalCount = totalCount || dataset.length;
989
+ this.displayEmptyDataWarning(finalTotalCount < 1);
990
+ }
991
+
992
+ if (Array.isArray(dataset) && this.slickGrid && this.dataView?.setItems) {
993
+ this.dataView.setItems(dataset, this._gridOptions.datasetIdPropertyName);
994
+ if (!this._gridOptions.backendServiceApi && !this._gridOptions.enableTreeData) {
995
+ this.dataView.reSort();
996
+ }
997
+
998
+ if (dataset.length > 0) {
999
+ if (!this._isDatasetInitialized) {
1000
+ this.loadFilterPresetsWhenDatasetInitialized();
1001
+
1002
+ if (this._gridOptions.enableCheckboxSelector) {
1003
+ this.loadRowSelectionPresetWhenExists();
1004
+ }
1005
+ }
1006
+ this._isDatasetInitialized = true;
1007
+ }
1008
+
1009
+ if (dataset) {
1010
+ this.slickGrid.invalidate();
1011
+ }
1012
+
1013
+ // display the Pagination component only after calling this refresh data first, we call it here so that if we preset pagination page number it will be shown correctly
1014
+ this.showPagination = (this._gridOptions && (this._gridOptions.enablePagination || (this._gridOptions.backendServiceApi && this._gridOptions.enablePagination === undefined))) ? true : false;
1015
+
1016
+ if (this._paginationOptions && this._gridOptions?.pagination && this._gridOptions?.backendServiceApi) {
1017
+ const paginationOptions = this.setPaginationOptionsWhenPresetDefined(this._gridOptions, this._paginationOptions);
1018
+
1019
+ // when we have a totalCount use it, else we'll take it from the pagination object
1020
+ // only update the total items if it's different to avoid refreshing the UI
1021
+ const totalRecords = (totalCount !== undefined) ? totalCount : (this._gridOptions?.pagination?.totalItems);
1022
+ if (totalRecords !== undefined && totalRecords !== this.totalItems) {
1023
+ this.totalItems = +totalRecords;
1024
+ }
1025
+ // initialize the Pagination Service with new pagination options (which might have presets)
1026
+ if (!this._isPaginationInitialized) {
1027
+ this.initializePaginationService(paginationOptions);
1028
+ } else {
1029
+ // update the pagination service with the new total
1030
+ this.paginationService.updateTotalItems(this.totalItems);
1031
+ }
1032
+ }
1033
+
1034
+ // resize the grid inside a slight timeout, in case other DOM element changed prior to the resize (like a filter/pagination changed)
1035
+ if (this.slickGrid && this._gridOptions.enableAutoResize) {
1036
+ const delay = this._gridOptions.autoResize && this._gridOptions.autoResize.delay;
1037
+ this.resizerService.resizeGrid(delay || 10);
1038
+ }
1039
+ }
1040
+ }
1041
+ }
1042
+
1043
+ /**
1044
+ * Dynamically change or update the column definitions list.
1045
+ * We will re-render the grid so that the new header and data shows up correctly.
1046
+ * If using translater, we also need to trigger a re-translate of the column headers
1047
+ */
1048
+ updateColumnDefinitionsList(newColumnDefinitions: Column<TData>[]) {
1049
+ if (this.slickGrid && this._gridOptions && Array.isArray(newColumnDefinitions)) {
1050
+ // map the Editor model to editorClass and load editor collectionAsync
1051
+ newColumnDefinitions = this.loadSlickGridEditors(newColumnDefinitions);
1052
+
1053
+ // if the user wants to automatically add a Custom Editor Formatter, we need to call the auto add function again
1054
+ if (this._gridOptions.autoAddCustomEditorFormatter) {
1055
+ autoAddEditorFormatterToColumnsWithEditor(newColumnDefinitions, this._gridOptions.autoAddCustomEditorFormatter);
1056
+ }
1057
+
1058
+ if (this._gridOptions.enableTranslate) {
1059
+ this.extensionService.translateColumnHeaders(undefined, newColumnDefinitions);
1060
+ } else {
1061
+ this.extensionService.renderColumnHeaders(newColumnDefinitions, true);
1062
+ }
1063
+
1064
+ if (this.slickGrid && this._gridOptions?.enableAutoSizeColumns) {
1065
+ this.slickGrid.autosizeColumns();
1066
+ } else if (this._gridOptions?.enableAutoResizeColumnsByCellContent && this.resizerService?.resizeColumnsByCellContent) {
1067
+ this.resizerService.resizeColumnsByCellContent();
1068
+ }
1069
+ }
1070
+ }
1071
+
1072
+ /**
1073
+ * Show the filter row displayed on first row, we can optionally pass false to hide it.
1074
+ * @param showing
1075
+ */
1076
+ showHeaderRow(showing = true) {
1077
+ this.slickGrid?.setHeaderRowVisibility(showing);
1078
+ if (this.slickGrid && showing === true && this._isGridInitialized) {
1079
+ this.slickGrid.setColumns(this.columnDefinitions);
1080
+ }
1081
+ return showing;
1082
+ }
1083
+
1084
+ setData(data: TData[], shouldAutosizeColumns = false) {
1085
+ if (shouldAutosizeColumns) {
1086
+ this._isAutosizeColsCalled = false;
1087
+ this._currentDatasetLength = 0;
1088
+ }
1089
+ this.dataset = data || [];
1090
+ }
1091
+
1092
+ /**
1093
+ * Check if there's any Pagination Presets defined in the Grid Options,
1094
+ * if there are then load them in the paginationOptions object
1095
+ */
1096
+ setPaginationOptionsWhenPresetDefined(gridOptions: GridOption, paginationOptions: Pagination): Pagination {
1097
+ if (gridOptions.presets?.pagination && paginationOptions && !this._isPaginationInitialized) {
1098
+ paginationOptions.pageSize = gridOptions.presets.pagination.pageSize;
1099
+ paginationOptions.pageNumber = gridOptions.presets.pagination.pageNumber;
1100
+ }
1101
+ return paginationOptions;
1102
+ }
1103
+
1104
+ setDarkMode(dark = false) {
1105
+ if (dark) {
1106
+ this._gridParentContainerElm.classList.add('slick-dark-mode');
1107
+ } else {
1108
+ this._gridParentContainerElm.classList.remove('slick-dark-mode');
1109
+ }
1110
+ }
1111
+
1112
+ // --
1113
+ // protected functions
1114
+ // ------------------
1115
+
1116
+ /**
1117
+ * Loop through all column definitions and copy the original optional `width` properties optionally provided by the user.
1118
+ * We will use this when doing a resize by cell content, if user provided a `width` it won't override it.
1119
+ */
1120
+ protected copyColumnWidthsReference(columnDefinitions: Column<TData>[]) {
1121
+ columnDefinitions.forEach(col => col.originalWidth = col.width);
1122
+ }
1123
+
1124
+ protected displayEmptyDataWarning(showWarning = true) {
1125
+ if (this.gridOptions.enableEmptyDataWarningMessage) {
1126
+ this.slickEmptyWarning?.showEmptyDataMessage(showWarning);
1127
+ }
1128
+ }
1129
+
1130
+ /** When data changes in the DataView, we'll refresh the metrics and/or display a warning if the dataset is empty */
1131
+ protected handleOnItemCountChanged(currentPageRowItemCount: number, totalItemCount: number) {
1132
+ this._currentDatasetLength = totalItemCount;
1133
+ this.metrics = {
1134
+ startTime: new Date(),
1135
+ endTime: new Date(),
1136
+ itemCount: currentPageRowItemCount,
1137
+ totalItemCount
1138
+ };
1139
+ // if custom footer is enabled, then we'll update its metrics
1140
+ if (this.slickFooter) {
1141
+ this.slickFooter.metrics = this.metrics;
1142
+ }
1143
+
1144
+ // when using local (in-memory) dataset, we'll display a warning message when filtered data is empty
1145
+ if (this._isLocalGrid && this._gridOptions?.enableEmptyDataWarningMessage) {
1146
+ this.displayEmptyDataWarning(currentPageRowItemCount === 0);
1147
+ }
1148
+ }
1149
+
1150
+ /** Initialize the Pagination Service once */
1151
+ protected initializePaginationService(paginationOptions: Pagination) {
1152
+ if (this.slickGrid && this.gridOptions) {
1153
+ this.paginationData = {
1154
+ gridOptions: this.gridOptions,
1155
+ paginationService: this.paginationService,
1156
+ };
1157
+ this.paginationService.totalItems = this.totalItems;
1158
+ this.paginationService.init(this.slickGrid, paginationOptions, this.backendServiceApi);
1159
+ this.subscriptions.push(
1160
+ this._eventPubSubService.subscribe<ServicePagination>('onPaginationChanged', paginationChanges => this.paginationChanged(paginationChanges)),
1161
+ this._eventPubSubService.subscribe<{ visible: boolean; }>('onPaginationVisibilityChanged', visibility => {
1162
+ this.showPagination = visibility?.visible ?? false;
1163
+ if (this.gridOptions?.backendServiceApi) {
1164
+ this.backendUtilityService?.refreshBackendDataset(this.gridOptions);
1165
+ }
1166
+ this.renderPagination(this.showPagination);
1167
+ })
1168
+ );
1169
+
1170
+ // also initialize (render) the pagination component
1171
+ this.renderPagination();
1172
+ this._isPaginationInitialized = true;
1173
+ }
1174
+ }
1175
+
1176
+ /**
1177
+ * Render (or dispose) the Pagination Component, user can optionally provide False (to not show it) which will in term dispose of the Pagination,
1178
+ * also while disposing we can choose to omit the disposable of the Pagination Service (if we are simply toggling the Pagination, we want to keep the Service alive)
1179
+ * @param {Boolean} showPagination - show (new render) or not (dispose) the Pagination
1180
+ * @param {Boolean} shouldDisposePaginationService - when disposing the Pagination, do we also want to dispose of the Pagination Service? (defaults to True)
1181
+ */
1182
+ protected renderPagination(showPagination = true) {
1183
+ if (this._gridOptions?.enablePagination && !this._isPaginationInitialized && showPagination) {
1184
+ this.slickPagination = new SlickPaginationComponent(this.paginationService, this._eventPubSubService, this.sharedService, this.translaterService);
1185
+ this.slickPagination.renderPagination(this._gridParentContainerElm);
1186
+ this._isPaginationInitialized = true;
1187
+ } else if (!showPagination) {
1188
+ if (this.slickPagination) {
1189
+ this.slickPagination.dispose();
1190
+ }
1191
+ this._isPaginationInitialized = false;
1192
+ }
1193
+ }
1194
+
1195
+ /** Load the Editor Collection asynchronously and replace the "collection" property when Promise resolves */
1196
+ protected loadEditorCollectionAsync(column: Column<TData>) {
1197
+ if (column?.editor) {
1198
+ const collectionAsync = column.editor.collectionAsync;
1199
+ column.editor.disabled = true; // disable the Editor DOM element, we'll re-enable it after receiving the collection with "updateEditorCollection()"
1200
+
1201
+ if (collectionAsync instanceof Promise) {
1202
+ // wait for the "collectionAsync", once resolved we will save it into the "collection"
1203
+ // the collectionAsync can be of 3 types HttpClient, HttpFetch or a Promise
1204
+ collectionAsync.then((response: any | any[]) => {
1205
+ if (Array.isArray(response)) {
1206
+ this.updateEditorCollection(column, response); // from Promise
1207
+ } else if (response?.status >= 200 && response.status < 300 && typeof response.json === 'function') {
1208
+ if (response.bodyUsed) {
1209
+ console.warn(`[SlickGrid-Universal] The response body passed to collectionAsync was already read.`
1210
+ + `Either pass the dataset from the Response or clone the response first using response.clone()`);
1211
+ } else {
1212
+ // from Fetch
1213
+ (response as Response).json().then(data => this.updateEditorCollection(column, data));
1214
+ }
1215
+ } else if (response?.content) {
1216
+ this.updateEditorCollection(column, response['content']); // from http-client
1217
+ }
1218
+ });
1219
+ } else if (this.rxjs?.isObservable(collectionAsync)) {
1220
+ // wrap this inside a setTimeout to avoid timing issue since updateEditorCollection requires to call SlickGrid getColumns() method
1221
+ setTimeout(() => {
1222
+ this.subscriptions.push(
1223
+ (collectionAsync as Observable<any>).subscribe((resolvedCollection) => this.updateEditorCollection(column, resolvedCollection))
1224
+ );
1225
+ });
1226
+ }
1227
+ }
1228
+ }
1229
+
1230
+ protected insertDynamicPresetColumns(columnId: string, gridPresetColumns: Column<TData>[]) {
1231
+ if (this._columnDefinitions) {
1232
+ const columnPosition = this._columnDefinitions.findIndex(c => c.id === columnId);
1233
+ if (columnPosition >= 0) {
1234
+ const dynColumn = this._columnDefinitions[columnPosition];
1235
+ if (dynColumn?.id === columnId && !gridPresetColumns.some(c => c.id === columnId)) {
1236
+ columnPosition > 0
1237
+ ? gridPresetColumns.splice(columnPosition, 0, dynColumn)
1238
+ : gridPresetColumns.unshift(dynColumn);
1239
+ }
1240
+ }
1241
+ }
1242
+ }
1243
+
1244
+ /** Load any possible Columns Grid Presets */
1245
+ protected loadColumnPresetsWhenDatasetInitialized() {
1246
+ // if user entered some Columns "presets", we need to reflect them all in the grid
1247
+ if (this.slickGrid && this.gridOptions.presets && Array.isArray(this.gridOptions.presets.columns) && this.gridOptions.presets.columns.length > 0) {
1248
+ const gridPresetColumns: Column<TData>[] = this.gridStateService.getAssociatedGridColumns(this.slickGrid, this.gridOptions.presets.columns);
1249
+ if (gridPresetColumns && Array.isArray(gridPresetColumns) && gridPresetColumns.length > 0 && Array.isArray(this._columnDefinitions)) {
1250
+ // make sure that the dynamic columns are included in presets (1.Row Move, 2. Row Selection, 3. Row Detail)
1251
+ if (this.gridOptions.enableRowMoveManager) {
1252
+ const rmmColId = this.gridOptions?.rowMoveManager?.columnId ?? '_move';
1253
+ this.insertDynamicPresetColumns(rmmColId, gridPresetColumns);
1254
+ }
1255
+ if (this.gridOptions.enableCheckboxSelector) {
1256
+ const chkColId = this.gridOptions?.checkboxSelector?.columnId ?? '_checkbox_selector';
1257
+ this.insertDynamicPresetColumns(chkColId, gridPresetColumns);
1258
+ }
1259
+ if (this.gridOptions.enableRowDetailView) {
1260
+ const rdvColId = this.gridOptions?.rowDetailView?.columnId ?? '_detail_selector';
1261
+ this.insertDynamicPresetColumns(rdvColId, gridPresetColumns);
1262
+ }
1263
+
1264
+ // keep copy the original optional `width` properties optionally provided by the user.
1265
+ // We will use this when doing a resize by cell content, if user provided a `width` it won't override it.
1266
+ gridPresetColumns.forEach(col => col.originalWidth = col.width);
1267
+
1268
+ // finally set the new presets columns (including checkbox selector if need be)
1269
+ this.slickGrid.setColumns(gridPresetColumns);
1270
+ this.sharedService.visibleColumns = gridPresetColumns;
1271
+ }
1272
+ }
1273
+ }
1274
+
1275
+ /** Load any possible Filters Grid Presets */
1276
+ protected loadFilterPresetsWhenDatasetInitialized() {
1277
+ if (this.gridOptions && !this.customDataView) {
1278
+ // if user entered some Filter "presets", we need to reflect them all in the DOM
1279
+ // also note that a presets of Tree Data Toggling will also call this method because Tree Data toggling does work with data filtering
1280
+ // (collapsing a parent will basically use Filter for hidding (aka collapsing) away the child underneat it)
1281
+ if (this.gridOptions.presets && (Array.isArray(this.gridOptions.presets.filters) || Array.isArray(this.gridOptions.presets?.treeData?.toggledItems))) {
1282
+ this.filterService.populateColumnFilterSearchTermPresets(this.gridOptions.presets?.filters || []);
1283
+ }
1284
+ }
1285
+ }
1286
+
1287
+ /**
1288
+ * local grid, check if we need to show the Pagination
1289
+ * if so then also check if there's any presets and finally initialize the PaginationService
1290
+ * a local grid with Pagination presets will potentially have a different total of items, we'll need to get it from the DataView and update our total
1291
+ */
1292
+ protected loadLocalGridPagination(dataset?: TData[]) {
1293
+ if (this.gridOptions && this._paginationOptions) {
1294
+ this.totalItems = Array.isArray(dataset) ? dataset.length : 0;
1295
+ if (this._paginationOptions && this.dataView?.getPagingInfo) {
1296
+ const slickPagingInfo = this.dataView.getPagingInfo();
1297
+ if (slickPagingInfo?.hasOwnProperty('totalRows') && this._paginationOptions.totalItems !== slickPagingInfo.totalRows) {
1298
+ this.totalItems = slickPagingInfo?.totalRows || 0;
1299
+ }
1300
+ }
1301
+ this._paginationOptions.totalItems = this.totalItems;
1302
+ const paginationOptions = this.setPaginationOptionsWhenPresetDefined(this.gridOptions, this._paginationOptions);
1303
+ this.initializePaginationService(paginationOptions);
1304
+ }
1305
+ }
1306
+
1307
+ /** Load any Row Selections into the DataView that were presets by the user */
1308
+ protected loadRowSelectionPresetWhenExists() {
1309
+ // if user entered some Row Selections "presets"
1310
+ const presets = this.gridOptions?.presets;
1311
+ const selectionModel = this.slickGrid?.getSelectionModel();
1312
+ const enableRowSelection = this.gridOptions && (this.gridOptions.enableCheckboxSelector || this.gridOptions.enableRowSelection);
1313
+ if (this.slickGrid && this.dataView && enableRowSelection && selectionModel && presets?.rowSelection && (Array.isArray(presets.rowSelection.gridRowIndexes) || Array.isArray(presets.rowSelection.dataContextIds))) {
1314
+ let dataContextIds = presets.rowSelection.dataContextIds;
1315
+ let gridRowIndexes = presets.rowSelection.gridRowIndexes;
1316
+
1317
+ // maps the IDs to the Grid Rows and vice versa, the "dataContextIds" has precedence over the other
1318
+ if (Array.isArray(dataContextIds) && dataContextIds.length > 0) {
1319
+ gridRowIndexes = this.dataView.mapIdsToRows(dataContextIds) || [];
1320
+ } else if (Array.isArray(gridRowIndexes) && gridRowIndexes.length > 0) {
1321
+ dataContextIds = this.dataView.mapRowsToIds(gridRowIndexes) || [];
1322
+ }
1323
+
1324
+ // apply row selection when defined as grid presets
1325
+ if (this.slickGrid && Array.isArray(gridRowIndexes)) {
1326
+ this.slickGrid.setSelectedRows(gridRowIndexes);
1327
+ this.dataView!.setSelectedIds(dataContextIds || [], {
1328
+ isRowBeingAdded: true,
1329
+ shouldTriggerEvent: false, // do not trigger when presetting the grid
1330
+ applyRowSelectionToGrid: true
1331
+ });
1332
+ }
1333
+ }
1334
+ }
1335
+
1336
+ /** Add a register a new external resource, user could also optional dispose all previous resources before pushing any new resources to the resources array list. */
1337
+ registerExternalResources(resources: ExternalResource[], disposePreviousResources = false) {
1338
+ if (disposePreviousResources) {
1339
+ this.disposeExternalResources();
1340
+ }
1341
+ resources.forEach(res => this._registeredResources.push(res));
1342
+ this.initializeExternalResources(resources);
1343
+ }
1344
+
1345
+ resetExternalResources() {
1346
+ this._registeredResources = [];
1347
+ }
1348
+
1349
+ /** Pre-Register any Resource that don't require SlickGrid to be instantiated (for example RxJS Resource) */
1350
+ protected preRegisterResources() {
1351
+ // bind & initialize all Components/Services that were tagged as enabled
1352
+ // register all services by executing their init method and providing them with the Grid object
1353
+ if (Array.isArray(this._registeredResources)) {
1354
+ for (const resource of this._registeredResources) {
1355
+ if (resource?.className === 'RxJsResource') {
1356
+ this.registerRxJsResource(resource as RxJsFacade);
1357
+ }
1358
+ }
1359
+ }
1360
+ }
1361
+
1362
+ protected initializeExternalResources(resources: ExternalResource[]) {
1363
+ if (Array.isArray(resources)) {
1364
+ for (const resource of resources) {
1365
+ if (this.slickGrid && typeof resource.init === 'function') {
1366
+ resource.init(this.slickGrid, this.universalContainerService);
1367
+ }
1368
+ }
1369
+ }
1370
+ }
1371
+
1372
+ protected registerResources() {
1373
+ // at this point, we consider all the registered services as external services, anything else registered afterward aren't external
1374
+ if (Array.isArray(this._registeredResources)) {
1375
+ this.sharedService.externalRegisteredResources = this._registeredResources;
1376
+ }
1377
+
1378
+ // push all other Services that we want to be registered
1379
+ this._registeredResources.push(this.gridService, this.gridStateService);
1380
+
1381
+ // when using Grouping/DraggableGrouping/Colspan register its Service
1382
+ if (this.gridOptions.createPreHeaderPanel && !this.gridOptions.enableDraggableGrouping) {
1383
+ this._registeredResources.push(this.groupingService);
1384
+ }
1385
+
1386
+ // when using Tree Data View, register its Service
1387
+ if (this.gridOptions.enableTreeData) {
1388
+ this._registeredResources.push(this.treeDataService);
1389
+ }
1390
+
1391
+ // when user enables translation, we need to translate Headers on first pass & subsequently in the bindDifferentHooks
1392
+ if (this.gridOptions.enableTranslate) {
1393
+ this.extensionService.translateColumnHeaders();
1394
+ }
1395
+
1396
+ // also initialize (render) the empty warning component
1397
+ this.slickEmptyWarning = new SlickEmptyWarningComponent();
1398
+ this._registeredResources.push(this.slickEmptyWarning);
1399
+
1400
+ // bind & initialize all Components/Services that were tagged as enabled
1401
+ // register all services by executing their init method and providing them with the Grid object
1402
+ this.initializeExternalResources(this._registeredResources);
1403
+ }
1404
+
1405
+ /** Register the RxJS Resource in all necessary services which uses */
1406
+ protected registerRxJsResource(resource: RxJsFacade) {
1407
+ this.rxjs = resource;
1408
+ this.backendUtilityService.addRxJsResource(this.rxjs);
1409
+ this.filterFactory.addRxJsResource(this.rxjs);
1410
+ this.filterService.addRxJsResource(this.rxjs);
1411
+ this.sortService.addRxJsResource(this.rxjs);
1412
+ this.paginationService.addRxJsResource(this.rxjs);
1413
+ this.universalContainerService.registerInstance('RxJsFacade', this.rxjs);
1414
+ this.universalContainerService.registerInstance('RxJsResource', this.rxjs);
1415
+ }
1416
+
1417
+ /**
1418
+ * Takes a flat dataset with parent/child relationship, sort it (via its tree structure) and return the sorted flat array
1419
+ * @returns {Array<Object>} sort flat parent/child dataset
1420
+ */
1421
+ protected sortTreeDataset<U>(flatDatasetInput: U[], forceGridRefresh = false): U[] {
1422
+ const prevDatasetLn = this._currentDatasetLength;
1423
+ let sortedDatasetResult;
1424
+ let flatDatasetOutput: any[] = [];
1425
+
1426
+ // if the hierarchical dataset was already initialized then no need to re-convert it, we can use it directly from the shared service ref
1427
+ if (this._isDatasetHierarchicalInitialized && this.datasetHierarchical) {
1428
+ sortedDatasetResult = this.treeDataService.sortHierarchicalDataset(this.datasetHierarchical);
1429
+ flatDatasetOutput = sortedDatasetResult.flat;
1430
+ } else if (Array.isArray(flatDatasetInput) && flatDatasetInput.length > 0) {
1431
+ if (this.gridOptions?.treeDataOptions?.initialSort) {
1432
+ // else we need to first convert the flat dataset to a hierarchical dataset and then sort
1433
+ sortedDatasetResult = this.treeDataService.convertFlatParentChildToTreeDatasetAndSort(flatDatasetInput, this._columnDefinitions || [], this.gridOptions);
1434
+ this.sharedService.hierarchicalDataset = sortedDatasetResult.hierarchical;
1435
+ flatDatasetOutput = sortedDatasetResult.flat;
1436
+ } else {
1437
+ // else we assume that the user provided an array that is already sorted (user's responsability)
1438
+ // and so we can simply convert the array to a tree structure and we're done, no need to sort
1439
+ this.sharedService.hierarchicalDataset = this.treeDataService.convertFlatParentChildToTreeDataset(flatDatasetInput, this.gridOptions);
1440
+ flatDatasetOutput = flatDatasetInput || [];
1441
+ }
1442
+ }
1443
+
1444
+ // if we add/remove item(s) from the dataset, we need to also refresh our tree data filters
1445
+ if (flatDatasetInput.length > 0 && (forceGridRefresh || flatDatasetInput.length !== prevDatasetLn)) {
1446
+ this.filterService.refreshTreeDataFilters(flatDatasetOutput);
1447
+ }
1448
+
1449
+ return flatDatasetOutput;
1450
+ }
1451
+
1452
+ /** Prepare and load all SlickGrid editors, if an async editor is found then we'll also execute it. */
1453
+ protected loadSlickGridEditors(columnDefinitions: Column<TData>[]): Column<TData>[] {
1454
+ const columns = Array.isArray(columnDefinitions) ? columnDefinitions : [];
1455
+
1456
+ if (columns.some(col => `${col.id}`.includes('.'))) {
1457
+ console.error('[Slickgrid-Universal] Make sure that none of your Column Definition "id" property includes a dot in its name because that will cause some problems with the Editors. For example if your column definition "field" property is "user.firstName" then use "firstName" as the column "id".');
1458
+ }
1459
+
1460
+ return columns.map((column) => {
1461
+ // on every Editor that have a "collectionAsync", resolve the data and assign it to the "collection" property
1462
+ if (column.editor?.collectionAsync) {
1463
+ this.loadEditorCollectionAsync(column);
1464
+ }
1465
+ return { ...column, editorClass: column.editor?.model };
1466
+ });
1467
+ }
1468
+
1469
+ /**
1470
+ * When the Editor(s) has a "editor.collection" property, we'll load the async collection.
1471
+ * Since this is called after the async call resolves, the pointer will not be the same as the "column" argument passed.
1472
+ */
1473
+ protected updateEditorCollection<U extends TData = any>(column: Column<U>, newCollection: U[]) {
1474
+ if (this.slickGrid && column.editor) {
1475
+ column.editor.collection = newCollection;
1476
+ column.editor.disabled = false;
1477
+
1478
+ // get current Editor, remove it from the DOm then re-enable it and re-render it with the new collection.
1479
+ const currentEditor = this.slickGrid.getCellEditor() as AutocompleterEditor | SelectEditor;
1480
+ if (currentEditor?.disable && currentEditor?.renderDomElement) {
1481
+ if (typeof currentEditor.destroy === 'function') {
1482
+ currentEditor.destroy();
1483
+ }
1484
+ currentEditor.disable(false);
1485
+ currentEditor.renderDomElement(newCollection);
1486
+ }
1487
+ }
1488
+ }
1489
+ }