slickgrid-react 5.13.0 → 5.13.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  addToArrayWhenNotExists,
3
+ createDomElement,
3
4
  type EventSubscription,
4
5
  type OnBeforeRowDetailToggleArgs,
5
6
  type OnRowBackToViewportRangeArgs,
6
7
  SlickEventData,
7
- type SlickEventHandler,
8
8
  type SlickGrid,
9
9
  SlickRowSelectionModel,
10
10
  unsubscribeAll,
@@ -51,13 +51,6 @@ export class SlickRowDetailView extends UniversalSlickRowDetailView {
51
51
  return this.gridOptions.datasetIdPropertyName || 'id';
52
52
  }
53
53
 
54
- get eventHandler(): SlickEventHandler {
55
- return this._eventHandler;
56
- }
57
- set eventHandler(eventHandler: SlickEventHandler) {
58
- this._eventHandler = eventHandler;
59
- }
60
-
61
54
  get gridOptions(): GridOption {
62
55
  return (this._grid?.getOptions() || {}) as GridOption;
63
56
  }
@@ -78,7 +71,7 @@ export class SlickRowDetailView extends UniversalSlickRowDetailView {
78
71
  do {
79
72
  const view = this._views.pop();
80
73
  if (view) {
81
- this.disposeView(view);
74
+ this.disposeViewByItem(view);
82
75
  }
83
76
  } while (this._views.length > 0);
84
77
  }
@@ -114,11 +107,12 @@ export class SlickRowDetailView extends UniversalSlickRowDetailView {
114
107
  // when those are React Components, we need to create View Component & provide the html containers to the Plugin (preTemplate/postTemplate methods)
115
108
  if (!this.gridOptions.rowDetailView.preTemplate) {
116
109
  this._preloadComponent = this.gridOptions?.rowDetailView?.preloadComponent;
117
- this.addonOptions.preTemplate = () => this._grid.sanitizeHtmlString(`<div class="${PRELOAD_CONTAINER_PREFIX}"></div>`) as string;
110
+ this.addonOptions.preTemplate = () => createDomElement('div', { className: `${PRELOAD_CONTAINER_PREFIX}` });
118
111
  }
119
112
  if (!this.gridOptions.rowDetailView.postTemplate) {
120
113
  this._component = this.gridOptions?.rowDetailView?.viewComponent;
121
- this.addonOptions.postTemplate = (itemDetail: any) => this._grid.sanitizeHtmlString(`<div class="${ROW_DETAIL_CONTAINER_PREFIX}${itemDetail[this.datasetIdPropName]}"></div>`) as string;
114
+ this.addonOptions.postTemplate = (itemDetail: any) =>
115
+ createDomElement('div', { className: `${ROW_DETAIL_CONTAINER_PREFIX}${itemDetail[this.datasetIdPropName]}` });
122
116
  }
123
117
 
124
118
  if (this._grid && this.gridOptions) {
@@ -145,7 +139,7 @@ export class SlickRowDetailView extends UniversalSlickRowDetailView {
145
139
  this._preloadRoot?.unmount();
146
140
 
147
141
  // triggers after backend called "onAsyncResponse.notify()"
148
- await this.renderViewModel(args?.item);
142
+ this.renderViewModel(args?.item);
149
143
 
150
144
  if (typeof this.rowDetailViewOptions?.onAsyncEndUpdate === 'function') {
151
145
  this.rowDetailViewOptions.onAsyncEndUpdate(event, args);
@@ -155,7 +149,7 @@ export class SlickRowDetailView extends UniversalSlickRowDetailView {
155
149
  this._eventHandler.subscribe(this.onAfterRowDetailToggle, async (event, args) => {
156
150
  // display preload template & re-render all the other Detail Views after toggling
157
151
  // the preload View will eventually go away once the data gets loaded after the "onAsyncEndUpdate" event
158
- await this.renderPreloadView(args.item);
152
+ this.renderPreloadView(args.item);
159
153
 
160
154
  if (typeof this.rowDetailViewOptions?.onAfterRowDetailToggle === 'function') {
161
155
  this.rowDetailViewOptions.onAfterRowDetailToggle(event, args);
@@ -174,7 +168,7 @@ export class SlickRowDetailView extends UniversalSlickRowDetailView {
174
168
 
175
169
  this._eventHandler.subscribe(this.onRowBackToViewportRange, async (event, args) => {
176
170
  // when row is back to viewport range, we will re-render the View Component(s)
177
- await this.handleOnRowBackToViewportRange(event, args);
171
+ this.handleOnRowBackToViewportRange(event, args);
178
172
 
179
173
  if (typeof this.rowDetailViewOptions?.onRowBackToViewportRange === 'function') {
180
174
  this.rowDetailViewOptions.onRowBackToViewportRange(event, args);
@@ -185,7 +179,7 @@ export class SlickRowDetailView extends UniversalSlickRowDetailView {
185
179
  if (typeof this.rowDetailViewOptions?.onBeforeRowOutOfViewportRange === 'function') {
186
180
  this.rowDetailViewOptions.onBeforeRowOutOfViewportRange(event, args);
187
181
  }
188
- this.disposeView(args.item);
182
+ this.disposeViewByItem(args.item);
189
183
  });
190
184
 
191
185
  this._eventHandler.subscribe(this.onRowOutOfViewportRange, (event, args) => {
@@ -198,11 +192,11 @@ export class SlickRowDetailView extends UniversalSlickRowDetailView {
198
192
  // hook some events needed by the Plugin itself
199
193
 
200
194
  // we need to redraw the open detail views if we change column position (column reorder)
201
- this.eventHandler.subscribe(this._grid.onColumnsReordered, this.redrawAllViewComponents.bind(this));
195
+ this.eventHandler.subscribe(this._grid.onColumnsReordered, () => this.redrawAllViewComponents(false));
202
196
 
203
197
  // on row selection changed, we also need to redraw
204
198
  if (this.gridOptions.enableRowSelection || this.gridOptions.enableCheckboxSelector) {
205
- this._eventHandler.subscribe(this._grid.onSelectedRowsChanged, this.redrawAllViewComponents.bind(this));
199
+ this._eventHandler.subscribe(this._grid.onSelectedRowsChanged, () => this.redrawAllViewComponents(false));
206
200
  }
207
201
 
208
202
  // on column sort/reorder, all row detail are collapsed so we can dispose of all the Views as well
@@ -210,8 +204,15 @@ export class SlickRowDetailView extends UniversalSlickRowDetailView {
210
204
 
211
205
  // on filter changed, we need to re-render all Views
212
206
  this._subscriptions.push(
213
- this.eventPubSubService?.subscribe(['onFilterChanged', 'onGridMenuColumnsChanged', 'onColumnPickerColumnsChanged'], this.redrawAllViewComponents.bind(this)),
214
- this.eventPubSubService?.subscribe(['onGridMenuClearAllFilters', 'onGridMenuClearAllSorting'], () => window.setTimeout(() => this.redrawAllViewComponents())),
207
+ this.eventPubSubService?.subscribe([
208
+ 'onFilterChanged',
209
+ 'onGridMenuColumnsChanged',
210
+ 'onColumnPickerColumnsChanged',
211
+ 'onGridMenuClearAllFilters',
212
+ 'onGridMenuClearAllSorting'
213
+ ],
214
+ () => this.redrawAllViewComponents(true)
215
+ ),
215
216
  );
216
217
  }
217
218
  }
@@ -221,38 +222,37 @@ export class SlickRowDetailView extends UniversalSlickRowDetailView {
221
222
  }
222
223
 
223
224
  /** Redraw (re-render) all the expanded row detail View Components */
224
- async redrawAllViewComponents() {
225
- this.resetRenderedRows();
226
- const promises: Promise<void>[] = [];
227
- this._views.forEach((view) => {
228
- if (!view.rendered) {
229
- promises.push(this.redrawViewComponent(view))
230
- }
225
+ async redrawAllViewComponents(forceRedraw = false) {
226
+ setTimeout(() => {
227
+ this.resetRenderedRows();
228
+ this._views.forEach((view) => {
229
+ if (!view.rendered || forceRedraw) {
230
+ forceRedraw && this.disposeViewComponent(view);
231
+ this.redrawViewComponent(view)
232
+ }
233
+ });
231
234
  });
232
- await Promise.all(promises);
233
235
  }
234
236
 
235
237
  /** Render all the expanded row detail View Components */
236
238
  async renderAllViewModels() {
237
- const promises: Promise<void>[] = [];
238
- Array.from(this._views)
239
+ this._views
239
240
  .filter((x) => x?.dataContext)
240
- .forEach((x) => promises.push(this.renderViewModel(x.dataContext)));
241
- await Promise.all(promises);
241
+ .forEach((x) => this.renderViewModel(x.dataContext));
242
242
  }
243
243
 
244
244
  /** Redraw the necessary View Component */
245
- async redrawViewComponent(view: CreatedView) {
246
- const containerElements = this.gridContainerElement.getElementsByClassName(`${ROW_DETAIL_CONTAINER_PREFIX}${view.id}`);
247
- if (containerElements?.length >= 0) {
248
- await this.renderViewModel(view.dataContext);
245
+ redrawViewComponent(view: CreatedView) {
246
+ const containerElement = this.gridContainerElement.querySelector(`.${ROW_DETAIL_CONTAINER_PREFIX}${view.id}`);
247
+ if (containerElement) {
248
+ this.renderViewModel(view.dataContext);
249
249
  }
250
250
  }
251
251
 
252
252
  /** Render (or re-render) the View Component (Row Detail) */
253
- async renderPreloadView(item: any) {
254
- const containerElements = this.gridContainerElement.getElementsByClassName(`${PRELOAD_CONTAINER_PREFIX}`);
255
- if (this._preloadComponent && containerElements?.length) {
253
+ renderPreloadView(item: any) {
254
+ const containerElement = this.gridContainerElement.querySelector(`.${PRELOAD_CONTAINER_PREFIX}`);
255
+ if (this._preloadComponent && containerElement) {
256
256
  // render row detail
257
257
  const bindableData = {
258
258
  model: item,
@@ -262,18 +262,17 @@ export class SlickRowDetailView extends UniversalSlickRowDetailView {
262
262
  parent: this.rowDetailViewOptions?.parent,
263
263
  } as ViewModelBindableInputData;
264
264
  const detailContainer = document.createElement('section');
265
- containerElements[containerElements.length - 1]!.appendChild(detailContainer);
265
+ containerElement.appendChild(detailContainer);
266
266
 
267
- const { root } = await createReactComponentDynamically(this._preloadComponent, detailContainer as HTMLElement, bindableData);
267
+ const { root } = createReactComponentDynamically(this._preloadComponent, detailContainer, bindableData);
268
268
  this._preloadRoot = root;
269
269
  }
270
270
  }
271
271
 
272
272
  /** Render (or re-render) the View Component (Row Detail) */
273
- async renderViewModel(item: any) {
274
- const containerElements = this.gridContainerElement.getElementsByClassName(`${ROW_DETAIL_CONTAINER_PREFIX}${item[this.datasetIdPropName]}`);
275
- if (this._component && containerElements?.length) {
276
- // render row detail
273
+ renderViewModel(item: any) {
274
+ const containerElement = this.gridContainerElement.querySelector<HTMLElement>(`.${ROW_DETAIL_CONTAINER_PREFIX}${item[this.datasetIdPropName]}`);
275
+ if (this._component && containerElement) {
277
276
  const bindableData = {
278
277
  model: item,
279
278
  addon: this,
@@ -281,24 +280,17 @@ export class SlickRowDetailView extends UniversalSlickRowDetailView {
281
280
  dataView: this.dataView,
282
281
  parent: this.rowDetailViewOptions?.parent,
283
282
  } as ViewModelBindableInputData;
284
- const viewObj = this._views.find(obj => obj.id === item[this.datasetIdPropName]);
285
-
286
- // remove any previous mounted views, if found then unmount them and delete them from our references array
287
- // const viewIdx = this._views.findIndex((obj) => obj.id === item[this.datasetIdPropName]);
288
- // if (this._views[viewIdx]?.root) {
289
- // this._views[viewIdx].root.unmount();
290
- // this._views.splice(viewIdx, 1);
291
- // }
292
283
 
293
284
  // load our Row Detail React Component dynamically, typically we would want to use `root.render()` after the preload component (last argument below)
294
285
  // BUT the root render doesn't seem to work and shows a blank element, so we'll use `createRoot()` every time even though it shows a console log in Dev
295
286
  // that is the only way I got it working so let's use it anyway and console warnings are removed in production anyway
296
- const { root } = createReactComponentDynamically(this._component, containerElements[containerElements.length - 1] as HTMLElement, bindableData /*, viewObj?.root */);
287
+ const viewObj = this._views.find(obj => obj.id === item[this.datasetIdPropName]);
288
+ const { root } = createReactComponentDynamically(this._component, containerElement, bindableData);
297
289
  if (viewObj) {
298
290
  viewObj.root = root;
299
291
  viewObj.rendered = true;
300
292
  } else {
301
- this.addViewInfoToViewsRef(item, root);
293
+ this.upsertViewRefs(item, root);
302
294
  }
303
295
  }
304
296
  }
@@ -307,19 +299,26 @@ export class SlickRowDetailView extends UniversalSlickRowDetailView {
307
299
  // protected functions
308
300
  // ------------------
309
301
 
310
- protected addViewInfoToViewsRef(item: any, root: Root | null) {
302
+ protected upsertViewRefs(item: any, root: Root | null) {
303
+ const viewIdx = this._views.findIndex(obj => obj.id === item[this.datasetIdPropName]);
311
304
  const viewInfo: CreatedView = {
312
305
  id: item[this.datasetIdPropName],
313
306
  dataContext: item,
314
307
  root,
315
308
  rendered: !!root,
316
309
  };
310
+ if (viewIdx >= 0) {
311
+ this._views[viewIdx] = viewInfo;
312
+ } else {
313
+ this._views.push(viewInfo);
314
+ }
317
315
  addToArrayWhenNotExists(this._views, viewInfo, this.datasetIdPropName);
318
316
  }
319
317
 
320
- protected disposeView(item: any, removeFromArray = false): void {
318
+ protected disposeViewByItem(item: any, removeFromArray = false): void {
321
319
  const foundViewIdx = this._views.findIndex((view: CreatedView) => view.id === item[this.datasetIdPropName]);
322
- if (foundViewIdx >= 0 && this.disposeViewComponent(this._views[foundViewIdx])) {
320
+ if (foundViewIdx >= 0) {
321
+ this.disposeViewComponent(this._views[foundViewIdx]);
323
322
  if (removeFromArray) {
324
323
  this._views.splice(foundViewIdx, 1);
325
324
  }
@@ -327,11 +326,12 @@ export class SlickRowDetailView extends UniversalSlickRowDetailView {
327
326
  }
328
327
 
329
328
  protected disposeViewComponent(expandedView: CreatedView): CreatedView | void {
329
+ expandedView.rendered = false;
330
330
  if (expandedView?.root) {
331
- const container = this.gridContainerElement.getElementsByClassName(`${ROW_DETAIL_CONTAINER_PREFIX}${expandedView.id}`);
332
- if (container?.length) {
331
+ const container = this.gridContainerElement.querySelector(`.${ROW_DETAIL_CONTAINER_PREFIX}${expandedView.id}`);
332
+ if (container) {
333
333
  expandedView.root.unmount();
334
- container[0].textContent = '';
334
+ container.textContent = '';
335
335
  return expandedView;
336
336
  }
337
337
  }
@@ -340,17 +340,17 @@ export class SlickRowDetailView extends UniversalSlickRowDetailView {
340
340
  /**
341
341
  * Just before the row get expanded or collapsed we will do the following
342
342
  * First determine if the row is expanding or collapsing,
343
- * if it's expanding we will add it to our View Components reference array if we don't already have it
344
- * or if it's collapsing we will remove it from our View Components reference array
343
+ * if it's expanding we will add it to our View Components reference array,
344
+ * if we don't already have it or if it's collapsing we will remove it from our View Components reference array
345
345
  */
346
346
  protected handleOnBeforeRowDetailToggle(_e: SlickEventData<OnBeforeRowDetailToggleArgs>, args: { grid: SlickGrid; item: any; }) {
347
347
  // expanding
348
348
  if (args?.item?.__collapsed) {
349
349
  // expanding row detail
350
- this.addViewInfoToViewsRef(args.item, null);
350
+ this.upsertViewRefs(args.item, null);
351
351
  } else {
352
352
  // collapsing, so dispose of the View
353
- this.disposeView(args.item, true);
353
+ this.disposeViewByItem(args.item, true);
354
354
  }
355
355
  }
356
356
 
@@ -363,8 +363,8 @@ export class SlickRowDetailView extends UniversalSlickRowDetailView {
363
363
  rowIdsOutOfViewport: (string | number)[];
364
364
  grid: SlickGrid;
365
365
  }) {
366
- const viewModel = Array.from(this._views).find((x) => x.id === args.rowId);
367
- if (viewModel) {
366
+ const viewModel = this._views.find((x) => x.id === args.rowId);
367
+ if (viewModel && !viewModel.rendered) {
368
368
  this.redrawViewComponent(viewModel);
369
369
  }
370
370
  }
@@ -375,9 +375,7 @@ export class SlickRowDetailView extends UniversalSlickRowDetailView {
375
375
  * @param item
376
376
  */
377
377
  protected notifyTemplate(item: any) {
378
- if (this.onAsyncResponse) {
379
- this.onAsyncResponse.notify({ item, itemDetail: item }, new SlickEventData(), this);
380
- }
378
+ this.onAsyncResponse.notify({ item, itemDetail: item }, new SlickEventData(), this);
381
379
  }
382
380
 
383
381
  /**
@@ -401,8 +399,10 @@ export class SlickRowDetailView extends UniversalSlickRowDetailView {
401
399
  }
402
400
 
403
401
  if (!awaitedItemDetail || !awaitedItemDetail.hasOwnProperty(this.datasetIdPropName)) {
404
- throw new Error('[Slickgrid-React] could not process the Row Detail, please make sure that your "process" callback ' +
405
- '(a Promise or an HttpClient call returning an Observable) returns an item object that has an "${this.datasetIdPropName}" property');
402
+ throw new Error(
403
+ '[Slickgrid-React] could not process the Row Detail, please make sure that your "process" callback ' +
404
+ `returns an item object that has an "${this.datasetIdPropName}" property`
405
+ );
406
406
  }
407
407
 
408
408
  // notify the plugin with the new item details
@@ -1,11 +1,12 @@
1
1
  import type { BasePaginationComponent, BasePaginationModel, GridOption as UniversalGridOption } from '@slickgrid-universal/common';
2
2
  import type * as i18next from 'i18next';
3
+ import type { ForwardRefExoticComponent, RefAttributes } from 'react';
3
4
 
4
5
  import type { RowDetailView } from './rowDetailView.interface';
5
6
 
6
7
  export interface GridOption extends UniversalGridOption {
7
8
  /** External Custom Pagination Component that can be provided by the user */
8
- customPaginationComponent?: typeof BasePaginationComponent | (() => BasePaginationModel);
9
+ customPaginationComponent?: typeof BasePaginationComponent | (() => BasePaginationModel) | ForwardRefExoticComponent<any & RefAttributes<any>>;
9
10
 
10
11
  /** I18N translation service instance */
11
12
  i18n?: i18next.i18n;