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