slickgrid-react 4.0.2 → 4.2.0

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