@slickgrid-universal/react-row-detail-plugin 0.0.1 → 10.0.0-beta.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.
package/LICENSE ADDED
@@ -0,0 +1,25 @@
1
+ Copyright (c) 2020-present, Ghislain B.
2
+ https://github.com/ghiscoding/slickgrid-universal
3
+
4
+ and the original author of SlickGrid
5
+ Michael Leibman, michael{dot}leibman{at}gmail{dot}com
6
+ http://github.com/mleibman/slickgrid
7
+
8
+ Permission is hereby granted, free of charge, to any person obtaining
9
+ a copy of this software and associated documentation files (the
10
+ "Software"), to deal in the Software without restriction, including
11
+ without limitation the rights to use, copy, modify, merge, publish,
12
+ distribute, sublicense, and/or sell copies of the Software, and to
13
+ permit persons to whom the Software is furnished to do so, subject to
14
+ the following conditions:
15
+
16
+ The above copyright notice and this permission notice shall be
17
+ included in all copies or substantial portions of the Software.
18
+
19
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
20
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
21
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
22
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
23
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
24
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
25
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md CHANGED
@@ -1,45 +1,19 @@
1
- # @slickgrid-universal/react-row-detail-plugin
1
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
2
+ [![TypeScript](https://img.shields.io/badge/%3C%2F%3E-TypeScript-%230074c1.svg)](http://www.typescriptlang.org/)
3
+ [![lerna--lite](https://img.shields.io/badge/maintained%20with-lerna--lite-e137ff)](https://github.com/ghiscoding/lerna-lite)
4
+ [![npm](https://img.shields.io/npm/v/@slickgrid-universal/react-row-detail-plugin.svg)](https://www.npmjs.com/package/@slickgrid-universal/react-row-detail-plugin)
5
+ [![npm](https://img.shields.io/npm/dy/@slickgrid-universal/react-row-detail-plugin)](https://www.npmjs.com/package/@slickgrid-universal/react-row-detail-plugin)
6
+ [![npm bundle size](https://img.shields.io/bundlephobia/minzip/@slickgrid-universal/react-row-detail-plugin?color=success&label=gzip)](https://bundlephobia.com/result?p=@slickgrid-universal/react-row-detail-plugin)
2
7
 
3
- ## ⚠️ IMPORTANT NOTICE ⚠️
8
+ [![Actions Status](https://github.com/ghiscoding/slickgrid-universal/workflows/CI%20Build/badge.svg)](https://github.com/ghiscoding/slickgrid-universal/actions)
9
+ [![Cypress.io](https://img.shields.io/badge/tested%20with-Cypress-04C38E.svg)](https://www.cypress.io/)
10
+ [![Vitest](https://img.shields.io/badge/tested%20with-vitest-fcc72b.svg?logo=vitest)](https://vitest.dev/)
11
+ [![codecov](https://codecov.io/gh/ghiscoding/slickgrid-universal/branch/master/graph/badge.svg)](https://codecov.io/gh/ghiscoding/slickgrid-universal)
4
12
 
5
- **This package is created solely for the purpose of setting up OIDC (OpenID Connect) trusted publishing with npm.**
13
+ ## Slickgrid-React Row Detail plugin
14
+ #### @slickgrid-universal/react-row-detail-plugin
6
15
 
7
- This is **NOT** a functional package and contains **NO** code or functionality beyond the OIDC setup configuration.
16
+ Slickgrid-React Row Detail plugin
8
17
 
9
- ## Purpose
10
-
11
- This package exists to:
12
- 1. Configure OIDC trusted publishing for the package name `@slickgrid-universal/react-row-detail-plugin`
13
- 2. Enable secure, token-less publishing from CI/CD workflows
14
- 3. Establish provenance for packages published under this name
15
-
16
- ## What is OIDC Trusted Publishing?
17
-
18
- OIDC trusted publishing allows package maintainers to publish packages directly from their CI/CD workflows without needing to manage npm access tokens. Instead, it uses OpenID Connect to establish trust between the CI/CD provider (like GitHub Actions) and npm.
19
-
20
- ## Setup Instructions
21
-
22
- To properly configure OIDC trusted publishing for this package:
23
-
24
- 1. Go to [npmjs.com](https://www.npmjs.com/) and navigate to your package settings
25
- 2. Configure the trusted publisher (e.g., GitHub Actions)
26
- 3. Specify the repository and workflow that should be allowed to publish
27
- 4. Use the configured workflow to publish your actual package
28
-
29
- ## DO NOT USE THIS PACKAGE
30
-
31
- This package is a placeholder for OIDC configuration only. It:
32
- - Contains no executable code
33
- - Provides no functionality
34
- - Should not be installed as a dependency
35
- - Exists only for administrative purposes
36
-
37
- ## More Information
38
-
39
- For more details about npm's trusted publishing feature, see:
40
- - [npm Trusted Publishing Documentation](https://docs.npmjs.com/generating-provenance-statements)
41
- - [GitHub Actions OIDC Documentation](https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect)
42
-
43
- ---
44
-
45
- **Maintained for OIDC setup purposes only**
18
+ ### Installation
19
+ Follow the instruction provided in the main [README](https://github.com/ghiscoding/slickgrid-universal#installation)
@@ -0,0 +1 @@
1
+ export * from './reactSlickRowDetailView.js';
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ export * from './reactSlickRowDetailView.js';
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,8BAA8B,CAAC","sourcesContent":["export * from './reactSlickRowDetailView.js';\n"]}
@@ -0,0 +1,12 @@
1
+ import type { RowDetailView as UniversalRowDetailView } from '@slickgrid-universal/common';
2
+ export interface RowDetailView extends UniversalRowDetailView {
3
+ /**
4
+ * Optionally pass your Parent Component reference to your Child Component (row detail component).
5
+ * note:: If anyone finds a better way of passing the parent to the row detail extension, please reach out and/or create a PR
6
+ */
7
+ parentRef?: any;
8
+ /** View Model of the preload template which shows after opening row detail & before row detail data shows up */
9
+ preloadComponent?: any;
10
+ /** View Model template that will be loaded once the async function finishes */
11
+ viewComponent?: any;
12
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=interfaces.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"interfaces.js","sourceRoot":"","sources":["../src/interfaces.ts"],"names":[],"mappings":"","sourcesContent":["import type { RowDetailView as UniversalRowDetailView } from '@slickgrid-universal/common';\n\nexport interface RowDetailView extends UniversalRowDetailView {\n /**\n * Optionally pass your Parent Component reference to your Child Component (row detail component).\n * note:: If anyone finds a better way of passing the parent to the row detail extension, please reach out and/or create a PR\n */\n parentRef?: any;\n\n /** View Model of the preload template which shows after opening row detail & before row detail data shows up */\n preloadComponent?: any;\n\n /** View Model template that will be loaded once the async function finishes */\n viewComponent?: any;\n}\n"]}
@@ -0,0 +1,84 @@
1
+ import { SlickEventData, type EventSubscription, type OnBeforeRowDetailToggleArgs, type OnRowBackOrOutOfViewportRangeArgs, type SelectionModel, type SlickGrid } from '@slickgrid-universal/common';
2
+ import { type EventPubSubService } from '@slickgrid-universal/event-pub-sub';
3
+ import { SlickRowDetailView as UniversalSlickRowDetailView } from '@slickgrid-universal/row-detail-view-plugin';
4
+ import type { Root } from 'react-dom/client';
5
+ import { type GridOption } from 'slickgrid-react';
6
+ import type { RowDetailView } from './interfaces';
7
+ export interface CreatedView {
8
+ id: string | number;
9
+ dataContext: any;
10
+ root: Root | null;
11
+ rendered?: boolean;
12
+ }
13
+ export declare class ReactSlickRowDetailView extends UniversalSlickRowDetailView {
14
+ private readonly eventPubSubService;
15
+ static readonly pluginName = "ReactSlickRowDetailView";
16
+ protected _component?: any;
17
+ protected _preloadComponent?: any;
18
+ protected _preloadRoot?: Root;
19
+ protected _views: CreatedView[];
20
+ protected _subscriptions: EventSubscription[];
21
+ protected _userProcessFn?: (item: any) => Promise<any>;
22
+ protected gridContainerElement: HTMLElement;
23
+ _root?: Root;
24
+ constructor(eventPubSubService: EventPubSubService);
25
+ get addonOptions(): import("slickgrid-react").RowDetailViewOption;
26
+ protected get datasetIdPropName(): string;
27
+ get gridOptions(): GridOption;
28
+ get rowDetailViewOptions(): RowDetailView | undefined;
29
+ /** Dispose of the RowDetailView Extension */
30
+ dispose(): void;
31
+ /** Dispose of all the opened Row Detail Panels Components */
32
+ disposeAllViewComponents(): void;
33
+ /** Get the instance of the SlickGrid addon (control or plugin). */
34
+ getAddonInstance(): ReactSlickRowDetailView | null;
35
+ init(grid: SlickGrid): void;
36
+ /**
37
+ * Create the plugin before the Grid creation, else it will behave oddly.
38
+ * Mostly because the column definitions might change after the grid creation
39
+ */
40
+ register(rowSelectionPlugin?: SelectionModel): this;
41
+ /** Redraw (re-render) all the expanded row detail View Components */
42
+ redrawAllViewComponents(forceRedraw?: boolean): Promise<void>;
43
+ /** Render all the expanded row detail View Components */
44
+ renderAllViewModels(): Promise<void>;
45
+ /** Redraw the necessary View Component */
46
+ redrawViewComponent(view: CreatedView): void;
47
+ /** Render (or re-render) the View Component (Row Detail) */
48
+ renderPreloadView(item: any): void;
49
+ /** Render (or re-render) the View Component (Row Detail) */
50
+ renderViewModel(item: any): void;
51
+ protected upsertViewRefs(item: any, root: Root | null): void;
52
+ protected disposeViewByItem(item: any, removeFromArray?: boolean): void;
53
+ protected disposeViewComponent(expandedView: CreatedView): CreatedView | void;
54
+ /**
55
+ * Just before the row get expanded or collapsed we will do the following
56
+ * First determine if the row is expanding or collapsing,
57
+ * if it's expanding we will add it to our View Components reference array,
58
+ * if we don't already have it or if it's collapsing we will remove it from our View Components reference array
59
+ */
60
+ protected handleOnBeforeRowDetailToggle(_e: SlickEventData<OnBeforeRowDetailToggleArgs>, args: {
61
+ grid: SlickGrid;
62
+ item: any;
63
+ }): void;
64
+ /** When Row comes back to Viewport Range, we need to redraw the View */
65
+ protected handleOnRowBackToViewportRange(_e: SlickEventData<OnRowBackOrOutOfViewportRangeArgs>, args: {
66
+ item: any;
67
+ rowId: string | number;
68
+ rowIndex: number;
69
+ expandedRows: (string | number)[];
70
+ rowIdsOutOfViewport: (string | number)[];
71
+ grid: SlickGrid;
72
+ }): Promise<void>;
73
+ /**
74
+ * notify the onAsyncResponse with the "args.item" (required property)
75
+ * the plugin will then use item to populate the row detail panel with the "postTemplate"
76
+ * @param item
77
+ */
78
+ protected notifyTemplate(item: any): void;
79
+ /**
80
+ * On Processing, we will notify the plugin with the new item detail once backend server call completes
81
+ * @param item
82
+ */
83
+ protected onProcessing(item: any): Promise<void>;
84
+ }
@@ -0,0 +1,335 @@
1
+ import { addToArrayWhenNotExists, createDomElement, SlickEventData, SlickHybridSelectionModel, unsubscribeAll, } from '@slickgrid-universal/common';
2
+ import { SlickRowDetailView as UniversalSlickRowDetailView } from '@slickgrid-universal/row-detail-view-plugin';
3
+ import { createReactComponentDynamically } from 'slickgrid-react';
4
+ const ROW_DETAIL_CONTAINER_PREFIX = 'container_';
5
+ const PRELOAD_CONTAINER_PREFIX = 'container_loading';
6
+ export class ReactSlickRowDetailView extends UniversalSlickRowDetailView {
7
+ eventPubSubService;
8
+ static pluginName = 'ReactSlickRowDetailView';
9
+ _component;
10
+ _preloadComponent;
11
+ _preloadRoot;
12
+ _views = [];
13
+ _subscriptions = [];
14
+ _userProcessFn;
15
+ gridContainerElement;
16
+ _root;
17
+ constructor(eventPubSubService) {
18
+ super(eventPubSubService);
19
+ this.eventPubSubService = eventPubSubService;
20
+ }
21
+ get addonOptions() {
22
+ return this.getOptions();
23
+ }
24
+ get datasetIdPropName() {
25
+ return this.gridOptions.datasetIdPropertyName || 'id';
26
+ }
27
+ get gridOptions() {
28
+ return (this._grid?.getOptions() || {});
29
+ }
30
+ get rowDetailViewOptions() {
31
+ return this.gridOptions.rowDetailView;
32
+ }
33
+ /** Dispose of the RowDetailView Extension */
34
+ dispose() {
35
+ this.disposeAllViewComponents();
36
+ unsubscribeAll(this._subscriptions);
37
+ super.dispose();
38
+ }
39
+ /** Dispose of all the opened Row Detail Panels Components */
40
+ disposeAllViewComponents() {
41
+ do {
42
+ const view = this._views.pop();
43
+ if (view) {
44
+ this.disposeViewByItem(view);
45
+ }
46
+ } while (this._views.length > 0);
47
+ }
48
+ /** Get the instance of the SlickGrid addon (control or plugin). */
49
+ getAddonInstance() {
50
+ return this;
51
+ }
52
+ init(grid) {
53
+ this._grid = grid;
54
+ super.init(grid);
55
+ this.gridContainerElement = grid.getContainerNode();
56
+ this.register(grid.getSelectionModel());
57
+ }
58
+ /**
59
+ * Create the plugin before the Grid creation, else it will behave oddly.
60
+ * Mostly because the column definitions might change after the grid creation
61
+ */
62
+ register(rowSelectionPlugin) {
63
+ if (typeof this.gridOptions.rowDetailView?.process === 'function') {
64
+ // we need to keep the user "process" method and replace it with our own execution method
65
+ // we do this because when we get the item detail, we need to call "onAsyncResponse.notify" for the plugin to work
66
+ this._userProcessFn = this.gridOptions.rowDetailView.process; // keep user's process method
67
+ this.addonOptions.process = (item) => this.onProcessing(item); // replace process method & run our internal one
68
+ }
69
+ else {
70
+ throw new Error('[Slickgrid-React] You need to provide a "process" function for the Row Detail Extension to work properly');
71
+ }
72
+ if (this._grid && this.gridOptions?.rowDetailView) {
73
+ // load the Preload & RowDetail Templates (could be straight HTML or React Components)
74
+ // when those are React Components, we need to create View Component & provide the html containers to the Plugin (preTemplate/postTemplate methods)
75
+ if (!this.gridOptions.rowDetailView.preTemplate) {
76
+ this._preloadComponent = this.gridOptions.rowDetailView.preloadComponent;
77
+ this.addonOptions.preTemplate = () => createDomElement('div', { className: `${PRELOAD_CONTAINER_PREFIX}` });
78
+ }
79
+ if (!this.gridOptions.rowDetailView.postTemplate) {
80
+ this._component = this.gridOptions.rowDetailView.viewComponent;
81
+ this.addonOptions.postTemplate = (itemDetail) => createDomElement('div', { className: `${ROW_DETAIL_CONTAINER_PREFIX}${itemDetail[this.datasetIdPropName]}` });
82
+ }
83
+ if (this._grid && this.gridOptions) {
84
+ // this also requires the Row Selection Model to be registered as well
85
+ if (!rowSelectionPlugin || !this._grid.getSelectionModel()) {
86
+ const selectionType = this.gridOptions.selectionOptions?.selectionType || 'row';
87
+ const selectActiveRow = this.gridOptions.selectionOptions?.selectActiveRow ?? true;
88
+ rowSelectionPlugin = new SlickHybridSelectionModel({ ...this.gridOptions.selectionOptions, selectionType, selectActiveRow });
89
+ this._grid.setSelectionModel(rowSelectionPlugin);
90
+ }
91
+ // hook all events
92
+ if (this._grid && this.rowDetailViewOptions) {
93
+ if (this.rowDetailViewOptions.onExtensionRegistered) {
94
+ this.rowDetailViewOptions.onExtensionRegistered(this);
95
+ }
96
+ this._eventHandler.subscribe(this.onAsyncResponse, (event, args) => {
97
+ if (typeof this.rowDetailViewOptions?.onAsyncResponse === 'function') {
98
+ this.rowDetailViewOptions.onAsyncResponse(event, args);
99
+ }
100
+ });
101
+ this._eventHandler.subscribe(this.onAsyncEndUpdate, async (event, args) => {
102
+ // dispose preload if exists
103
+ this._preloadRoot?.unmount();
104
+ // triggers after backend called "onAsyncResponse.notify()"
105
+ // because of the preload destroy above, we need a small delay to make sure the DOM element is ready to render the Row Detail
106
+ queueMicrotask(() => {
107
+ this.renderViewModel(args?.item);
108
+ if (typeof this.rowDetailViewOptions?.onAsyncEndUpdate === 'function') {
109
+ this.rowDetailViewOptions.onAsyncEndUpdate(event, args);
110
+ }
111
+ });
112
+ });
113
+ this._eventHandler.subscribe(this.onAfterRowDetailToggle, async (event, args) => {
114
+ // display preload template & re-render all the other Detail Views after toggling
115
+ // the preload View will eventually go away once the data gets loaded after the "onAsyncEndUpdate" event
116
+ this.renderPreloadView(args.item);
117
+ if (typeof this.rowDetailViewOptions?.onAfterRowDetailToggle === 'function') {
118
+ this.rowDetailViewOptions.onAfterRowDetailToggle(event, args);
119
+ }
120
+ });
121
+ this._eventHandler.subscribe(this.onBeforeRowDetailToggle, (event, args) => {
122
+ // before toggling row detail, we need to create View Component if it doesn't exist
123
+ this.handleOnBeforeRowDetailToggle(event, args);
124
+ if (typeof this.rowDetailViewOptions?.onBeforeRowDetailToggle === 'function') {
125
+ return this.rowDetailViewOptions.onBeforeRowDetailToggle(event, args);
126
+ }
127
+ return true;
128
+ });
129
+ this._eventHandler.subscribe(this.onRowBackToViewportRange, async (event, args) => {
130
+ // when row is back to viewport range, we will re-render the View Component(s)
131
+ this.handleOnRowBackToViewportRange(event, args);
132
+ if (typeof this.rowDetailViewOptions?.onRowBackToViewportRange === 'function') {
133
+ this.rowDetailViewOptions.onRowBackToViewportRange(event, args);
134
+ }
135
+ });
136
+ this._eventHandler.subscribe(this.onBeforeRowOutOfViewportRange, (event, args) => {
137
+ if (typeof this.rowDetailViewOptions?.onBeforeRowOutOfViewportRange === 'function') {
138
+ this.rowDetailViewOptions.onBeforeRowOutOfViewportRange(event, args);
139
+ }
140
+ this.disposeViewByItem(args.item);
141
+ });
142
+ this._eventHandler.subscribe(this.onRowOutOfViewportRange, (event, args) => {
143
+ if (typeof this.rowDetailViewOptions?.onRowOutOfViewportRange === 'function') {
144
+ this.rowDetailViewOptions.onRowOutOfViewportRange(event, args);
145
+ }
146
+ });
147
+ // --
148
+ // hook some events needed by the Plugin itself
149
+ // we need to redraw the open detail views if we change column position (column reorder)
150
+ this.eventHandler.subscribe(this._grid.onColumnsReordered, () => this.redrawAllViewComponents(false));
151
+ // on row selection changed, we also need to redraw
152
+ if (this.gridOptions.enableSelection || this.gridOptions.enableCheckboxSelector) {
153
+ this._eventHandler.subscribe(this._grid.onSelectedRowsChanged, () => this.redrawAllViewComponents(false));
154
+ }
155
+ // on column sort/reorder, all row detail are collapsed so we can dispose of all the Views as well
156
+ this._eventHandler.subscribe(this._grid.onSort, this.disposeAllViewComponents.bind(this));
157
+ // on filter changed, we need to re-render all Views
158
+ this._subscriptions.push(this.eventPubSubService?.subscribe([
159
+ 'onFilterChanged',
160
+ 'onGridMenuColumnsChanged',
161
+ 'onColumnPickerColumnsChanged',
162
+ 'onGridMenuClearAllFilters',
163
+ 'onGridMenuClearAllSorting',
164
+ ], () => this.redrawAllViewComponents(true)));
165
+ }
166
+ }
167
+ }
168
+ return this;
169
+ }
170
+ /** Redraw (re-render) all the expanded row detail View Components */
171
+ async redrawAllViewComponents(forceRedraw = false) {
172
+ setTimeout(() => {
173
+ this.resetRenderedRows();
174
+ this._views.forEach((view) => {
175
+ if (!view.rendered || forceRedraw) {
176
+ forceRedraw && this.disposeViewComponent(view);
177
+ this.redrawViewComponent(view);
178
+ }
179
+ });
180
+ });
181
+ }
182
+ /** Render all the expanded row detail View Components */
183
+ async renderAllViewModels() {
184
+ this._views.filter((x) => x?.dataContext).forEach((x) => this.renderViewModel(x.dataContext));
185
+ }
186
+ /** Redraw the necessary View Component */
187
+ redrawViewComponent(view) {
188
+ const containerElement = this.gridContainerElement.querySelector(`.${ROW_DETAIL_CONTAINER_PREFIX}${view.id}`);
189
+ if (containerElement) {
190
+ this.renderViewModel(view.dataContext);
191
+ }
192
+ }
193
+ /** Render (or re-render) the View Component (Row Detail) */
194
+ renderPreloadView(item) {
195
+ const containerElement = this.gridContainerElement.querySelector(`.${PRELOAD_CONTAINER_PREFIX}`);
196
+ if (this._preloadComponent && containerElement) {
197
+ // render row detail
198
+ const bindableData = {
199
+ model: item,
200
+ addon: this,
201
+ grid: this._grid,
202
+ dataView: this.dataView,
203
+ parentRef: this.rowDetailViewOptions?.parentRef,
204
+ };
205
+ const detailContainer = document.createElement('section');
206
+ containerElement.appendChild(detailContainer);
207
+ const { root } = createReactComponentDynamically(this._preloadComponent, detailContainer, bindableData);
208
+ this._preloadRoot = root;
209
+ }
210
+ }
211
+ /** Render (or re-render) the View Component (Row Detail) */
212
+ renderViewModel(item) {
213
+ const containerElement = this.gridContainerElement.querySelector(`.${ROW_DETAIL_CONTAINER_PREFIX}${item[this.datasetIdPropName]}`);
214
+ if (this._component && containerElement) {
215
+ const bindableData = {
216
+ model: item,
217
+ addon: this,
218
+ grid: this._grid,
219
+ dataView: this.dataView,
220
+ parentRef: this.rowDetailViewOptions?.parentRef,
221
+ };
222
+ // load our Row Detail React Component dynamically, typically we would want to use `root.render()` after the preload component (last argument below)
223
+ // 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
224
+ // that is the only way I got it working so let's use it anyway and console warnings are removed in production anyway
225
+ const viewObj = this._views.find((obj) => obj.id === item[this.datasetIdPropName]);
226
+ const { root } = createReactComponentDynamically(this._component, containerElement, bindableData);
227
+ if (viewObj) {
228
+ viewObj.root = root;
229
+ viewObj.rendered = true;
230
+ }
231
+ else {
232
+ this.upsertViewRefs(item, root);
233
+ }
234
+ }
235
+ }
236
+ // --
237
+ // protected functions
238
+ // ------------------
239
+ upsertViewRefs(item, root) {
240
+ const viewIdx = this._views.findIndex((obj) => obj.id === item[this.datasetIdPropName]);
241
+ const viewInfo = {
242
+ id: item[this.datasetIdPropName],
243
+ dataContext: item,
244
+ root,
245
+ rendered: !!root,
246
+ };
247
+ if (viewIdx >= 0) {
248
+ this._views[viewIdx] = viewInfo;
249
+ }
250
+ else {
251
+ this._views.push(viewInfo);
252
+ }
253
+ addToArrayWhenNotExists(this._views, viewInfo, this.datasetIdPropName);
254
+ }
255
+ disposeViewByItem(item, removeFromArray = false) {
256
+ const foundViewIdx = this._views.findIndex((view) => view.id === item[this.datasetIdPropName]);
257
+ if (foundViewIdx >= 0) {
258
+ this.disposeViewComponent(this._views[foundViewIdx]);
259
+ if (removeFromArray) {
260
+ this._views.splice(foundViewIdx, 1);
261
+ }
262
+ }
263
+ }
264
+ disposeViewComponent(expandedView) {
265
+ expandedView.rendered = false;
266
+ if (expandedView?.root) {
267
+ const container = this.gridContainerElement.querySelector(`.${ROW_DETAIL_CONTAINER_PREFIX}${expandedView.id}`);
268
+ if (container) {
269
+ expandedView.root.unmount();
270
+ container.textContent = '';
271
+ return expandedView;
272
+ }
273
+ }
274
+ }
275
+ /**
276
+ * Just before the row get expanded or collapsed we will do the following
277
+ * First determine if the row is expanding or collapsing,
278
+ * if it's expanding we will add it to our View Components reference array,
279
+ * if we don't already have it or if it's collapsing we will remove it from our View Components reference array
280
+ */
281
+ handleOnBeforeRowDetailToggle(_e, args) {
282
+ // expanding
283
+ if (args?.item?.__collapsed) {
284
+ // expanding row detail
285
+ this.upsertViewRefs(args.item, null);
286
+ }
287
+ else {
288
+ // collapsing, so dispose of the View
289
+ this.disposeViewByItem(args.item, true);
290
+ }
291
+ }
292
+ /** When Row comes back to Viewport Range, we need to redraw the View */
293
+ async handleOnRowBackToViewportRange(_e, args) {
294
+ const viewModel = this._views.find((x) => x.id === args.rowId);
295
+ if (viewModel && !viewModel.rendered) {
296
+ this.redrawViewComponent(viewModel);
297
+ }
298
+ }
299
+ /**
300
+ * notify the onAsyncResponse with the "args.item" (required property)
301
+ * the plugin will then use item to populate the row detail panel with the "postTemplate"
302
+ * @param item
303
+ */
304
+ notifyTemplate(item) {
305
+ this.onAsyncResponse.notify({ item }, new SlickEventData(), this);
306
+ }
307
+ /**
308
+ * On Processing, we will notify the plugin with the new item detail once backend server call completes
309
+ * @param item
310
+ */
311
+ async onProcessing(item) {
312
+ if (item && typeof this._userProcessFn === 'function') {
313
+ let awaitedItemDetail;
314
+ const userProcessFn = this._userProcessFn(item);
315
+ // wait for the "userProcessFn", once resolved we will save it into the "collection"
316
+ const response = await userProcessFn;
317
+ if (response.hasOwnProperty(this.datasetIdPropName)) {
318
+ awaitedItemDetail = response; // from Promise
319
+ }
320
+ else if (response instanceof Response && typeof response['json'] === 'function') {
321
+ awaitedItemDetail = await response['json'](); // from Fetch
322
+ }
323
+ else if (response && response['content']) {
324
+ awaitedItemDetail = response['content']; // from http-client
325
+ }
326
+ if (!awaitedItemDetail || !awaitedItemDetail.hasOwnProperty(this.datasetIdPropName)) {
327
+ throw new Error('[Slickgrid-React] could not process the Row Detail, please make sure that your "process" callback ' +
328
+ `returns an item object that has an "${this.datasetIdPropName}" property`);
329
+ }
330
+ // notify the plugin with the new item details
331
+ this.notifyTemplate(awaitedItemDetail || {});
332
+ }
333
+ }
334
+ }
335
+ //# sourceMappingURL=reactSlickRowDetailView.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reactSlickRowDetailView.js","sourceRoot":"","sources":["../src/reactSlickRowDetailView.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n addToArrayWhenNotExists,\n createDomElement,\n SlickEventData,\n SlickHybridSelectionModel,\n unsubscribeAll,\n type EventSubscription,\n type OnBeforeRowDetailToggleArgs,\n type OnRowBackOrOutOfViewportRangeArgs,\n type SelectionModel,\n type SlickGrid,\n} from '@slickgrid-universal/common';\nimport { type EventPubSubService } from '@slickgrid-universal/event-pub-sub';\nimport { SlickRowDetailView as UniversalSlickRowDetailView } from '@slickgrid-universal/row-detail-view-plugin';\nimport type { Root } from 'react-dom/client';\nimport { createReactComponentDynamically, type GridOption, type ViewModelBindableInputData } from 'slickgrid-react';\nimport type { RowDetailView } from './interfaces';\n\nconst ROW_DETAIL_CONTAINER_PREFIX = 'container_';\nconst PRELOAD_CONTAINER_PREFIX = 'container_loading';\n\nexport interface CreatedView {\n id: string | number;\n dataContext: any;\n root: Root | null;\n rendered?: boolean;\n}\n\nexport class ReactSlickRowDetailView extends UniversalSlickRowDetailView {\n static readonly pluginName = 'ReactSlickRowDetailView';\n protected _component?: any;\n protected _preloadComponent?: any;\n protected _preloadRoot?: Root;\n protected _views: CreatedView[] = [];\n protected _subscriptions: EventSubscription[] = [];\n protected _userProcessFn?: (item: any) => Promise<any>;\n protected gridContainerElement!: HTMLElement;\n _root?: Root;\n\n constructor(private readonly eventPubSubService: EventPubSubService) {\n super(eventPubSubService);\n }\n\n get addonOptions() {\n return this.getOptions();\n }\n\n protected get datasetIdPropName(): string {\n return this.gridOptions.datasetIdPropertyName || 'id';\n }\n\n get gridOptions(): GridOption {\n return (this._grid?.getOptions() || {}) as GridOption;\n }\n\n get rowDetailViewOptions(): RowDetailView | undefined {\n return this.gridOptions.rowDetailView;\n }\n\n /** Dispose of the RowDetailView Extension */\n dispose() {\n this.disposeAllViewComponents();\n unsubscribeAll(this._subscriptions);\n super.dispose();\n }\n\n /** Dispose of all the opened Row Detail Panels Components */\n disposeAllViewComponents() {\n do {\n const view = this._views.pop();\n if (view) {\n this.disposeViewByItem(view);\n }\n } while (this._views.length > 0);\n }\n\n /** Get the instance of the SlickGrid addon (control or plugin). */\n getAddonInstance(): ReactSlickRowDetailView | null {\n return this;\n }\n\n init(grid: SlickGrid) {\n this._grid = grid;\n super.init(grid);\n this.gridContainerElement = grid.getContainerNode();\n this.register(grid.getSelectionModel());\n }\n\n /**\n * Create the plugin before the Grid creation, else it will behave oddly.\n * Mostly because the column definitions might change after the grid creation\n */\n register(rowSelectionPlugin?: SelectionModel) {\n if (typeof this.gridOptions.rowDetailView?.process === 'function') {\n // we need to keep the user \"process\" method and replace it with our own execution method\n // we do this because when we get the item detail, we need to call \"onAsyncResponse.notify\" for the plugin to work\n this._userProcessFn = this.gridOptions.rowDetailView.process as (item: any) => Promise<any>; // keep user's process method\n this.addonOptions.process = (item) => this.onProcessing(item); // replace process method & run our internal one\n } else {\n throw new Error('[Slickgrid-React] You need to provide a \"process\" function for the Row Detail Extension to work properly');\n }\n\n if (this._grid && this.gridOptions?.rowDetailView) {\n // load the Preload & RowDetail Templates (could be straight HTML or React Components)\n // when those are React Components, we need to create View Component & provide the html containers to the Plugin (preTemplate/postTemplate methods)\n if (!this.gridOptions.rowDetailView.preTemplate) {\n this._preloadComponent = this.gridOptions.rowDetailView.preloadComponent;\n this.addonOptions.preTemplate = () => createDomElement('div', { className: `${PRELOAD_CONTAINER_PREFIX}` });\n }\n if (!this.gridOptions.rowDetailView.postTemplate) {\n this._component = this.gridOptions.rowDetailView.viewComponent;\n this.addonOptions.postTemplate = (itemDetail: any) =>\n createDomElement('div', { className: `${ROW_DETAIL_CONTAINER_PREFIX}${itemDetail[this.datasetIdPropName]}` });\n }\n\n if (this._grid && this.gridOptions) {\n // this also requires the Row Selection Model to be registered as well\n if (!rowSelectionPlugin || !this._grid.getSelectionModel()) {\n const selectionType = this.gridOptions.selectionOptions?.selectionType || 'row';\n const selectActiveRow = this.gridOptions.selectionOptions?.selectActiveRow ?? true;\n rowSelectionPlugin = new SlickHybridSelectionModel({ ...this.gridOptions.selectionOptions, selectionType, selectActiveRow });\n this._grid.setSelectionModel(rowSelectionPlugin);\n }\n\n // hook all events\n if (this._grid && this.rowDetailViewOptions) {\n if (this.rowDetailViewOptions.onExtensionRegistered) {\n this.rowDetailViewOptions.onExtensionRegistered(this);\n }\n\n this._eventHandler.subscribe(this.onAsyncResponse, (event, args) => {\n if (typeof this.rowDetailViewOptions?.onAsyncResponse === 'function') {\n this.rowDetailViewOptions.onAsyncResponse(event, args);\n }\n });\n\n this._eventHandler.subscribe(this.onAsyncEndUpdate, async (event, args) => {\n // dispose preload if exists\n this._preloadRoot?.unmount();\n\n // triggers after backend called \"onAsyncResponse.notify()\"\n // because of the preload destroy above, we need a small delay to make sure the DOM element is ready to render the Row Detail\n queueMicrotask(() => {\n this.renderViewModel(args?.item);\n\n if (typeof this.rowDetailViewOptions?.onAsyncEndUpdate === 'function') {\n this.rowDetailViewOptions.onAsyncEndUpdate(event, args);\n }\n });\n });\n\n this._eventHandler.subscribe(this.onAfterRowDetailToggle, async (event, args) => {\n // display preload template & re-render all the other Detail Views after toggling\n // the preload View will eventually go away once the data gets loaded after the \"onAsyncEndUpdate\" event\n this.renderPreloadView(args.item);\n\n if (typeof this.rowDetailViewOptions?.onAfterRowDetailToggle === 'function') {\n this.rowDetailViewOptions.onAfterRowDetailToggle(event, args);\n }\n });\n\n this._eventHandler.subscribe(this.onBeforeRowDetailToggle, (event, args) => {\n // before toggling row detail, we need to create View Component if it doesn't exist\n this.handleOnBeforeRowDetailToggle(event, args);\n\n if (typeof this.rowDetailViewOptions?.onBeforeRowDetailToggle === 'function') {\n return this.rowDetailViewOptions.onBeforeRowDetailToggle(event, args);\n }\n return true;\n });\n\n this._eventHandler.subscribe(this.onRowBackToViewportRange, async (event, args) => {\n // when row is back to viewport range, we will re-render the View Component(s)\n this.handleOnRowBackToViewportRange(event, args);\n\n if (typeof this.rowDetailViewOptions?.onRowBackToViewportRange === 'function') {\n this.rowDetailViewOptions.onRowBackToViewportRange(event, args);\n }\n });\n\n this._eventHandler.subscribe(this.onBeforeRowOutOfViewportRange, (event, args) => {\n if (typeof this.rowDetailViewOptions?.onBeforeRowOutOfViewportRange === 'function') {\n this.rowDetailViewOptions.onBeforeRowOutOfViewportRange(event, args);\n }\n this.disposeViewByItem(args.item);\n });\n\n this._eventHandler.subscribe(this.onRowOutOfViewportRange, (event, args) => {\n if (typeof this.rowDetailViewOptions?.onRowOutOfViewportRange === 'function') {\n this.rowDetailViewOptions.onRowOutOfViewportRange(event, args);\n }\n });\n\n // --\n // hook some events needed by the Plugin itself\n\n // we need to redraw the open detail views if we change column position (column reorder)\n this.eventHandler.subscribe(this._grid.onColumnsReordered, () => this.redrawAllViewComponents(false));\n\n // on row selection changed, we also need to redraw\n if (this.gridOptions.enableSelection || this.gridOptions.enableCheckboxSelector) {\n this._eventHandler.subscribe(this._grid.onSelectedRowsChanged, () => this.redrawAllViewComponents(false));\n }\n\n // on column sort/reorder, all row detail are collapsed so we can dispose of all the Views as well\n this._eventHandler.subscribe(this._grid.onSort, this.disposeAllViewComponents.bind(this));\n\n // on filter changed, we need to re-render all Views\n this._subscriptions.push(\n this.eventPubSubService?.subscribe(\n [\n 'onFilterChanged',\n 'onGridMenuColumnsChanged',\n 'onColumnPickerColumnsChanged',\n 'onGridMenuClearAllFilters',\n 'onGridMenuClearAllSorting',\n ],\n () => this.redrawAllViewComponents(true)\n )\n );\n }\n }\n }\n\n return this;\n }\n\n /** Redraw (re-render) all the expanded row detail View Components */\n async redrawAllViewComponents(forceRedraw = false) {\n setTimeout(() => {\n this.resetRenderedRows();\n this._views.forEach((view) => {\n if (!view.rendered || forceRedraw) {\n forceRedraw && this.disposeViewComponent(view);\n this.redrawViewComponent(view);\n }\n });\n });\n }\n\n /** Render all the expanded row detail View Components */\n async renderAllViewModels() {\n this._views.filter((x) => x?.dataContext).forEach((x) => this.renderViewModel(x.dataContext));\n }\n\n /** Redraw the necessary View Component */\n redrawViewComponent(view: CreatedView) {\n const containerElement = this.gridContainerElement.querySelector(`.${ROW_DETAIL_CONTAINER_PREFIX}${view.id}`);\n if (containerElement) {\n this.renderViewModel(view.dataContext);\n }\n }\n\n /** Render (or re-render) the View Component (Row Detail) */\n renderPreloadView(item: any) {\n const containerElement = this.gridContainerElement.querySelector(`.${PRELOAD_CONTAINER_PREFIX}`);\n if (this._preloadComponent && containerElement) {\n // render row detail\n const bindableData = {\n model: item,\n addon: this,\n grid: this._grid,\n dataView: this.dataView,\n parentRef: this.rowDetailViewOptions?.parentRef,\n } as ViewModelBindableInputData;\n const detailContainer = document.createElement('section');\n containerElement.appendChild(detailContainer);\n\n const { root } = createReactComponentDynamically(this._preloadComponent, detailContainer, bindableData);\n this._preloadRoot = root;\n }\n }\n\n /** Render (or re-render) the View Component (Row Detail) */\n renderViewModel(item: any) {\n const containerElement = this.gridContainerElement.querySelector<HTMLElement>(\n `.${ROW_DETAIL_CONTAINER_PREFIX}${item[this.datasetIdPropName]}`\n );\n if (this._component && containerElement) {\n const bindableData = {\n model: item,\n addon: this,\n grid: this._grid,\n dataView: this.dataView,\n parentRef: this.rowDetailViewOptions?.parentRef,\n } as ViewModelBindableInputData;\n\n // load our Row Detail React Component dynamically, typically we would want to use `root.render()` after the preload component (last argument below)\n // 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\n // that is the only way I got it working so let's use it anyway and console warnings are removed in production anyway\n const viewObj = this._views.find((obj) => obj.id === item[this.datasetIdPropName]);\n const { root } = createReactComponentDynamically(this._component, containerElement, bindableData);\n if (viewObj) {\n viewObj.root = root;\n viewObj.rendered = true;\n } else {\n this.upsertViewRefs(item, root);\n }\n }\n }\n\n // --\n // protected functions\n // ------------------\n\n protected upsertViewRefs(item: any, root: Root | null) {\n const viewIdx = this._views.findIndex((obj) => obj.id === item[this.datasetIdPropName]);\n const viewInfo: CreatedView = {\n id: item[this.datasetIdPropName],\n dataContext: item,\n root,\n rendered: !!root,\n };\n if (viewIdx >= 0) {\n this._views[viewIdx] = viewInfo;\n } else {\n this._views.push(viewInfo);\n }\n addToArrayWhenNotExists(this._views, viewInfo, this.datasetIdPropName);\n }\n\n protected disposeViewByItem(item: any, removeFromArray = false): void {\n const foundViewIdx = this._views.findIndex((view: CreatedView) => view.id === item[this.datasetIdPropName]);\n if (foundViewIdx >= 0) {\n this.disposeViewComponent(this._views[foundViewIdx]);\n if (removeFromArray) {\n this._views.splice(foundViewIdx, 1);\n }\n }\n }\n\n protected disposeViewComponent(expandedView: CreatedView): CreatedView | void {\n expandedView.rendered = false;\n if (expandedView?.root) {\n const container = this.gridContainerElement.querySelector(`.${ROW_DETAIL_CONTAINER_PREFIX}${expandedView.id}`);\n if (container) {\n expandedView.root.unmount();\n container.textContent = '';\n return expandedView;\n }\n }\n }\n\n /**\n * Just before the row get expanded or collapsed we will do the following\n * First determine if the row is expanding or collapsing,\n * if it's expanding we will add it to our View Components reference array,\n * if we don't already have it or if it's collapsing we will remove it from our View Components reference array\n */\n protected handleOnBeforeRowDetailToggle(_e: SlickEventData<OnBeforeRowDetailToggleArgs>, args: { grid: SlickGrid; item: any }) {\n // expanding\n if (args?.item?.__collapsed) {\n // expanding row detail\n this.upsertViewRefs(args.item, null);\n } else {\n // collapsing, so dispose of the View\n this.disposeViewByItem(args.item, true);\n }\n }\n\n /** When Row comes back to Viewport Range, we need to redraw the View */\n protected async handleOnRowBackToViewportRange(\n _e: SlickEventData<OnRowBackOrOutOfViewportRangeArgs>,\n args: {\n item: any;\n rowId: string | number;\n rowIndex: number;\n expandedRows: (string | number)[];\n rowIdsOutOfViewport: (string | number)[];\n grid: SlickGrid;\n }\n ) {\n const viewModel = this._views.find((x) => x.id === args.rowId);\n if (viewModel && !viewModel.rendered) {\n this.redrawViewComponent(viewModel);\n }\n }\n\n /**\n * notify the onAsyncResponse with the \"args.item\" (required property)\n * the plugin will then use item to populate the row detail panel with the \"postTemplate\"\n * @param item\n */\n protected notifyTemplate(item: any) {\n this.onAsyncResponse.notify({ item }, new SlickEventData(), this);\n }\n\n /**\n * On Processing, we will notify the plugin with the new item detail once backend server call completes\n * @param item\n */\n protected async onProcessing(item: any) {\n if (item && typeof this._userProcessFn === 'function') {\n let awaitedItemDetail: any;\n const userProcessFn = this._userProcessFn(item);\n\n // wait for the \"userProcessFn\", once resolved we will save it into the \"collection\"\n const response: any | any[] = await userProcessFn;\n\n if (response.hasOwnProperty(this.datasetIdPropName)) {\n awaitedItemDetail = response; // from Promise\n } else if (response instanceof Response && typeof response['json'] === 'function') {\n awaitedItemDetail = await response['json'](); // from Fetch\n } else if (response && response['content']) {\n awaitedItemDetail = response['content']; // from http-client\n }\n\n if (!awaitedItemDetail || !awaitedItemDetail.hasOwnProperty(this.datasetIdPropName)) {\n throw new Error(\n '[Slickgrid-React] could not process the Row Detail, please make sure that your \"process\" callback ' +\n `returns an item object that has an \"${this.datasetIdPropName}\" property`\n );\n }\n\n // notify the plugin with the new item details\n this.notifyTemplate(awaitedItemDetail || {});\n }\n }\n}\n"]}
package/package.json CHANGED
@@ -1,10 +1,43 @@
1
1
  {
2
2
  "name": "@slickgrid-universal/react-row-detail-plugin",
3
- "version": "0.0.1",
4
- "description": "OIDC trusted publishing setup package for @slickgrid-universal/react-row-detail-plugin",
5
- "keywords": [
6
- "oidc",
7
- "trusted-publishing",
8
- "setup"
9
- ]
3
+ "version": "10.0.0-beta.0",
4
+ "description": "React Row Detail Plugin for SlickGrid-React",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "exports": {
8
+ ".": {
9
+ "types": "./dist/index.d.ts",
10
+ "default": "./dist/index.js"
11
+ },
12
+ "./package.json": "./package.json"
13
+ },
14
+ "types": "./dist/index.d.ts",
15
+ "publishConfig": {
16
+ "access": "public"
17
+ },
18
+ "files": [
19
+ "/dist",
20
+ "/src"
21
+ ],
22
+ "license": "MIT",
23
+ "author": "Ghislain B.",
24
+ "homepage": "https://github.com/ghiscoding/slickgrid-universal",
25
+ "repository": {
26
+ "type": "git",
27
+ "url": "git+https://github.com/ghiscoding/slickgrid-universal.git",
28
+ "directory": "packages/react-row-detail-plugin"
29
+ },
30
+ "bugs": {
31
+ "url": "https://github.com/ghiscoding/slickgrid-universal/issues"
32
+ },
33
+ "dependencies": {
34
+ "@slickgrid-universal/common": "10.0.0-beta.0",
35
+ "@slickgrid-universal/event-pub-sub": "10.0.0-beta.0",
36
+ "@slickgrid-universal/row-detail-view-plugin": "10.0.0-beta.0",
37
+ "react": "^19.2.4",
38
+ "react-dom": "^19.2.4",
39
+ "slickgrid-react": "10.0.0-beta.0",
40
+ "tslib": "^2.8.1"
41
+ },
42
+ "gitHead": "9ee3b4ce76e171dd3375f1e68e232e315364600e"
10
43
  }
package/src/index.ts ADDED
@@ -0,0 +1 @@
1
+ export * from './reactSlickRowDetailView.js';
@@ -0,0 +1,15 @@
1
+ import type { RowDetailView as UniversalRowDetailView } from '@slickgrid-universal/common';
2
+
3
+ export interface RowDetailView extends UniversalRowDetailView {
4
+ /**
5
+ * Optionally pass your Parent Component reference to your Child Component (row detail component).
6
+ * note:: If anyone finds a better way of passing the parent to the row detail extension, please reach out and/or create a PR
7
+ */
8
+ parentRef?: any;
9
+
10
+ /** View Model of the preload template which shows after opening row detail & before row detail data shows up */
11
+ preloadComponent?: any;
12
+
13
+ /** View Model template that will be loaded once the async function finishes */
14
+ viewComponent?: any;
15
+ }
@@ -0,0 +1,419 @@
1
+ import {
2
+ addToArrayWhenNotExists,
3
+ createDomElement,
4
+ SlickEventData,
5
+ SlickHybridSelectionModel,
6
+ unsubscribeAll,
7
+ type EventSubscription,
8
+ type OnBeforeRowDetailToggleArgs,
9
+ type OnRowBackOrOutOfViewportRangeArgs,
10
+ type SelectionModel,
11
+ type SlickGrid,
12
+ } from '@slickgrid-universal/common';
13
+ import { type EventPubSubService } from '@slickgrid-universal/event-pub-sub';
14
+ import { SlickRowDetailView as UniversalSlickRowDetailView } from '@slickgrid-universal/row-detail-view-plugin';
15
+ import type { Root } from 'react-dom/client';
16
+ import { createReactComponentDynamically, type GridOption, type ViewModelBindableInputData } from 'slickgrid-react';
17
+ import type { RowDetailView } from './interfaces';
18
+
19
+ const ROW_DETAIL_CONTAINER_PREFIX = 'container_';
20
+ const PRELOAD_CONTAINER_PREFIX = 'container_loading';
21
+
22
+ export interface CreatedView {
23
+ id: string | number;
24
+ dataContext: any;
25
+ root: Root | null;
26
+ rendered?: boolean;
27
+ }
28
+
29
+ export class ReactSlickRowDetailView extends UniversalSlickRowDetailView {
30
+ static readonly pluginName = 'ReactSlickRowDetailView';
31
+ protected _component?: any;
32
+ protected _preloadComponent?: any;
33
+ protected _preloadRoot?: Root;
34
+ protected _views: CreatedView[] = [];
35
+ protected _subscriptions: EventSubscription[] = [];
36
+ protected _userProcessFn?: (item: any) => Promise<any>;
37
+ protected gridContainerElement!: HTMLElement;
38
+ _root?: Root;
39
+
40
+ constructor(private readonly eventPubSubService: EventPubSubService) {
41
+ super(eventPubSubService);
42
+ }
43
+
44
+ get addonOptions() {
45
+ return this.getOptions();
46
+ }
47
+
48
+ protected get datasetIdPropName(): string {
49
+ return this.gridOptions.datasetIdPropertyName || 'id';
50
+ }
51
+
52
+ get gridOptions(): GridOption {
53
+ return (this._grid?.getOptions() || {}) as GridOption;
54
+ }
55
+
56
+ get rowDetailViewOptions(): RowDetailView | undefined {
57
+ return this.gridOptions.rowDetailView;
58
+ }
59
+
60
+ /** Dispose of the RowDetailView Extension */
61
+ dispose() {
62
+ this.disposeAllViewComponents();
63
+ unsubscribeAll(this._subscriptions);
64
+ super.dispose();
65
+ }
66
+
67
+ /** Dispose of all the opened Row Detail Panels Components */
68
+ disposeAllViewComponents() {
69
+ do {
70
+ const view = this._views.pop();
71
+ if (view) {
72
+ this.disposeViewByItem(view);
73
+ }
74
+ } while (this._views.length > 0);
75
+ }
76
+
77
+ /** Get the instance of the SlickGrid addon (control or plugin). */
78
+ getAddonInstance(): ReactSlickRowDetailView | null {
79
+ return this;
80
+ }
81
+
82
+ init(grid: SlickGrid) {
83
+ this._grid = grid;
84
+ super.init(grid);
85
+ this.gridContainerElement = grid.getContainerNode();
86
+ this.register(grid.getSelectionModel());
87
+ }
88
+
89
+ /**
90
+ * Create the plugin before the Grid creation, else it will behave oddly.
91
+ * Mostly because the column definitions might change after the grid creation
92
+ */
93
+ register(rowSelectionPlugin?: SelectionModel) {
94
+ if (typeof this.gridOptions.rowDetailView?.process === 'function') {
95
+ // we need to keep the user "process" method and replace it with our own execution method
96
+ // we do this because when we get the item detail, we need to call "onAsyncResponse.notify" for the plugin to work
97
+ this._userProcessFn = this.gridOptions.rowDetailView.process as (item: any) => Promise<any>; // keep user's process method
98
+ this.addonOptions.process = (item) => this.onProcessing(item); // replace process method & run our internal one
99
+ } else {
100
+ throw new Error('[Slickgrid-React] You need to provide a "process" function for the Row Detail Extension to work properly');
101
+ }
102
+
103
+ if (this._grid && this.gridOptions?.rowDetailView) {
104
+ // load the Preload & RowDetail Templates (could be straight HTML or React Components)
105
+ // when those are React Components, we need to create View Component & provide the html containers to the Plugin (preTemplate/postTemplate methods)
106
+ if (!this.gridOptions.rowDetailView.preTemplate) {
107
+ this._preloadComponent = this.gridOptions.rowDetailView.preloadComponent;
108
+ this.addonOptions.preTemplate = () => createDomElement('div', { className: `${PRELOAD_CONTAINER_PREFIX}` });
109
+ }
110
+ if (!this.gridOptions.rowDetailView.postTemplate) {
111
+ this._component = this.gridOptions.rowDetailView.viewComponent;
112
+ this.addonOptions.postTemplate = (itemDetail: any) =>
113
+ createDomElement('div', { className: `${ROW_DETAIL_CONTAINER_PREFIX}${itemDetail[this.datasetIdPropName]}` });
114
+ }
115
+
116
+ if (this._grid && this.gridOptions) {
117
+ // this also requires the Row Selection Model to be registered as well
118
+ if (!rowSelectionPlugin || !this._grid.getSelectionModel()) {
119
+ const selectionType = this.gridOptions.selectionOptions?.selectionType || 'row';
120
+ const selectActiveRow = this.gridOptions.selectionOptions?.selectActiveRow ?? true;
121
+ rowSelectionPlugin = new SlickHybridSelectionModel({ ...this.gridOptions.selectionOptions, selectionType, selectActiveRow });
122
+ this._grid.setSelectionModel(rowSelectionPlugin);
123
+ }
124
+
125
+ // hook all events
126
+ if (this._grid && this.rowDetailViewOptions) {
127
+ if (this.rowDetailViewOptions.onExtensionRegistered) {
128
+ this.rowDetailViewOptions.onExtensionRegistered(this);
129
+ }
130
+
131
+ this._eventHandler.subscribe(this.onAsyncResponse, (event, args) => {
132
+ if (typeof this.rowDetailViewOptions?.onAsyncResponse === 'function') {
133
+ this.rowDetailViewOptions.onAsyncResponse(event, args);
134
+ }
135
+ });
136
+
137
+ this._eventHandler.subscribe(this.onAsyncEndUpdate, async (event, args) => {
138
+ // dispose preload if exists
139
+ this._preloadRoot?.unmount();
140
+
141
+ // triggers after backend called "onAsyncResponse.notify()"
142
+ // because of the preload destroy above, we need a small delay to make sure the DOM element is ready to render the Row Detail
143
+ queueMicrotask(() => {
144
+ this.renderViewModel(args?.item);
145
+
146
+ if (typeof this.rowDetailViewOptions?.onAsyncEndUpdate === 'function') {
147
+ this.rowDetailViewOptions.onAsyncEndUpdate(event, args);
148
+ }
149
+ });
150
+ });
151
+
152
+ this._eventHandler.subscribe(this.onAfterRowDetailToggle, async (event, args) => {
153
+ // display preload template & re-render all the other Detail Views after toggling
154
+ // the preload View will eventually go away once the data gets loaded after the "onAsyncEndUpdate" event
155
+ this.renderPreloadView(args.item);
156
+
157
+ if (typeof this.rowDetailViewOptions?.onAfterRowDetailToggle === 'function') {
158
+ this.rowDetailViewOptions.onAfterRowDetailToggle(event, args);
159
+ }
160
+ });
161
+
162
+ this._eventHandler.subscribe(this.onBeforeRowDetailToggle, (event, args) => {
163
+ // before toggling row detail, we need to create View Component if it doesn't exist
164
+ this.handleOnBeforeRowDetailToggle(event, args);
165
+
166
+ if (typeof this.rowDetailViewOptions?.onBeforeRowDetailToggle === 'function') {
167
+ return this.rowDetailViewOptions.onBeforeRowDetailToggle(event, args);
168
+ }
169
+ return true;
170
+ });
171
+
172
+ this._eventHandler.subscribe(this.onRowBackToViewportRange, async (event, args) => {
173
+ // when row is back to viewport range, we will re-render the View Component(s)
174
+ this.handleOnRowBackToViewportRange(event, args);
175
+
176
+ if (typeof this.rowDetailViewOptions?.onRowBackToViewportRange === 'function') {
177
+ this.rowDetailViewOptions.onRowBackToViewportRange(event, args);
178
+ }
179
+ });
180
+
181
+ this._eventHandler.subscribe(this.onBeforeRowOutOfViewportRange, (event, args) => {
182
+ if (typeof this.rowDetailViewOptions?.onBeforeRowOutOfViewportRange === 'function') {
183
+ this.rowDetailViewOptions.onBeforeRowOutOfViewportRange(event, args);
184
+ }
185
+ this.disposeViewByItem(args.item);
186
+ });
187
+
188
+ this._eventHandler.subscribe(this.onRowOutOfViewportRange, (event, args) => {
189
+ if (typeof this.rowDetailViewOptions?.onRowOutOfViewportRange === 'function') {
190
+ this.rowDetailViewOptions.onRowOutOfViewportRange(event, args);
191
+ }
192
+ });
193
+
194
+ // --
195
+ // hook some events needed by the Plugin itself
196
+
197
+ // we need to redraw the open detail views if we change column position (column reorder)
198
+ this.eventHandler.subscribe(this._grid.onColumnsReordered, () => this.redrawAllViewComponents(false));
199
+
200
+ // on row selection changed, we also need to redraw
201
+ if (this.gridOptions.enableSelection || this.gridOptions.enableCheckboxSelector) {
202
+ this._eventHandler.subscribe(this._grid.onSelectedRowsChanged, () => this.redrawAllViewComponents(false));
203
+ }
204
+
205
+ // on column sort/reorder, all row detail are collapsed so we can dispose of all the Views as well
206
+ this._eventHandler.subscribe(this._grid.onSort, this.disposeAllViewComponents.bind(this));
207
+
208
+ // on filter changed, we need to re-render all Views
209
+ this._subscriptions.push(
210
+ this.eventPubSubService?.subscribe(
211
+ [
212
+ 'onFilterChanged',
213
+ 'onGridMenuColumnsChanged',
214
+ 'onColumnPickerColumnsChanged',
215
+ 'onGridMenuClearAllFilters',
216
+ 'onGridMenuClearAllSorting',
217
+ ],
218
+ () => this.redrawAllViewComponents(true)
219
+ )
220
+ );
221
+ }
222
+ }
223
+ }
224
+
225
+ return this;
226
+ }
227
+
228
+ /** Redraw (re-render) all the expanded row detail View Components */
229
+ async redrawAllViewComponents(forceRedraw = false) {
230
+ setTimeout(() => {
231
+ this.resetRenderedRows();
232
+ this._views.forEach((view) => {
233
+ if (!view.rendered || forceRedraw) {
234
+ forceRedraw && this.disposeViewComponent(view);
235
+ this.redrawViewComponent(view);
236
+ }
237
+ });
238
+ });
239
+ }
240
+
241
+ /** Render all the expanded row detail View Components */
242
+ async renderAllViewModels() {
243
+ this._views.filter((x) => x?.dataContext).forEach((x) => this.renderViewModel(x.dataContext));
244
+ }
245
+
246
+ /** Redraw the necessary View Component */
247
+ redrawViewComponent(view: CreatedView) {
248
+ const containerElement = this.gridContainerElement.querySelector(`.${ROW_DETAIL_CONTAINER_PREFIX}${view.id}`);
249
+ if (containerElement) {
250
+ this.renderViewModel(view.dataContext);
251
+ }
252
+ }
253
+
254
+ /** Render (or re-render) the View Component (Row Detail) */
255
+ renderPreloadView(item: any) {
256
+ const containerElement = this.gridContainerElement.querySelector(`.${PRELOAD_CONTAINER_PREFIX}`);
257
+ if (this._preloadComponent && containerElement) {
258
+ // render row detail
259
+ const bindableData = {
260
+ model: item,
261
+ addon: this,
262
+ grid: this._grid,
263
+ dataView: this.dataView,
264
+ parentRef: this.rowDetailViewOptions?.parentRef,
265
+ } as ViewModelBindableInputData;
266
+ const detailContainer = document.createElement('section');
267
+ containerElement.appendChild(detailContainer);
268
+
269
+ const { root } = createReactComponentDynamically(this._preloadComponent, detailContainer, bindableData);
270
+ this._preloadRoot = root;
271
+ }
272
+ }
273
+
274
+ /** Render (or re-render) the View Component (Row Detail) */
275
+ renderViewModel(item: any) {
276
+ const containerElement = this.gridContainerElement.querySelector<HTMLElement>(
277
+ `.${ROW_DETAIL_CONTAINER_PREFIX}${item[this.datasetIdPropName]}`
278
+ );
279
+ if (this._component && containerElement) {
280
+ const bindableData = {
281
+ model: item,
282
+ addon: this,
283
+ grid: this._grid,
284
+ dataView: this.dataView,
285
+ parentRef: this.rowDetailViewOptions?.parentRef,
286
+ } as ViewModelBindableInputData;
287
+
288
+ // load our Row Detail React Component dynamically, typically we would want to use `root.render()` after the preload component (last argument below)
289
+ // 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
290
+ // that is the only way I got it working so let's use it anyway and console warnings are removed in production anyway
291
+ const viewObj = this._views.find((obj) => obj.id === item[this.datasetIdPropName]);
292
+ const { root } = createReactComponentDynamically(this._component, containerElement, bindableData);
293
+ if (viewObj) {
294
+ viewObj.root = root;
295
+ viewObj.rendered = true;
296
+ } else {
297
+ this.upsertViewRefs(item, root);
298
+ }
299
+ }
300
+ }
301
+
302
+ // --
303
+ // protected functions
304
+ // ------------------
305
+
306
+ protected upsertViewRefs(item: any, root: Root | null) {
307
+ const viewIdx = this._views.findIndex((obj) => obj.id === item[this.datasetIdPropName]);
308
+ const viewInfo: CreatedView = {
309
+ id: item[this.datasetIdPropName],
310
+ dataContext: item,
311
+ root,
312
+ rendered: !!root,
313
+ };
314
+ if (viewIdx >= 0) {
315
+ this._views[viewIdx] = viewInfo;
316
+ } else {
317
+ this._views.push(viewInfo);
318
+ }
319
+ addToArrayWhenNotExists(this._views, viewInfo, this.datasetIdPropName);
320
+ }
321
+
322
+ protected disposeViewByItem(item: any, removeFromArray = false): void {
323
+ const foundViewIdx = this._views.findIndex((view: CreatedView) => view.id === item[this.datasetIdPropName]);
324
+ if (foundViewIdx >= 0) {
325
+ this.disposeViewComponent(this._views[foundViewIdx]);
326
+ if (removeFromArray) {
327
+ this._views.splice(foundViewIdx, 1);
328
+ }
329
+ }
330
+ }
331
+
332
+ protected disposeViewComponent(expandedView: CreatedView): CreatedView | void {
333
+ expandedView.rendered = false;
334
+ if (expandedView?.root) {
335
+ const container = this.gridContainerElement.querySelector(`.${ROW_DETAIL_CONTAINER_PREFIX}${expandedView.id}`);
336
+ if (container) {
337
+ expandedView.root.unmount();
338
+ container.textContent = '';
339
+ return expandedView;
340
+ }
341
+ }
342
+ }
343
+
344
+ /**
345
+ * Just before the row get expanded or collapsed we will do the following
346
+ * First determine if the row is expanding or collapsing,
347
+ * if it's expanding we will add it to our View Components reference array,
348
+ * if we don't already have it or if it's collapsing we will remove it from our View Components reference array
349
+ */
350
+ protected handleOnBeforeRowDetailToggle(_e: SlickEventData<OnBeforeRowDetailToggleArgs>, args: { grid: SlickGrid; item: any }) {
351
+ // expanding
352
+ if (args?.item?.__collapsed) {
353
+ // expanding row detail
354
+ this.upsertViewRefs(args.item, null);
355
+ } else {
356
+ // collapsing, so dispose of the View
357
+ this.disposeViewByItem(args.item, true);
358
+ }
359
+ }
360
+
361
+ /** When Row comes back to Viewport Range, we need to redraw the View */
362
+ protected async handleOnRowBackToViewportRange(
363
+ _e: SlickEventData<OnRowBackOrOutOfViewportRangeArgs>,
364
+ args: {
365
+ item: any;
366
+ rowId: string | number;
367
+ rowIndex: number;
368
+ expandedRows: (string | number)[];
369
+ rowIdsOutOfViewport: (string | number)[];
370
+ grid: SlickGrid;
371
+ }
372
+ ) {
373
+ const viewModel = this._views.find((x) => x.id === args.rowId);
374
+ if (viewModel && !viewModel.rendered) {
375
+ this.redrawViewComponent(viewModel);
376
+ }
377
+ }
378
+
379
+ /**
380
+ * notify the onAsyncResponse with the "args.item" (required property)
381
+ * the plugin will then use item to populate the row detail panel with the "postTemplate"
382
+ * @param item
383
+ */
384
+ protected notifyTemplate(item: any) {
385
+ this.onAsyncResponse.notify({ item }, new SlickEventData(), this);
386
+ }
387
+
388
+ /**
389
+ * On Processing, we will notify the plugin with the new item detail once backend server call completes
390
+ * @param item
391
+ */
392
+ protected async onProcessing(item: any) {
393
+ if (item && typeof this._userProcessFn === 'function') {
394
+ let awaitedItemDetail: any;
395
+ const userProcessFn = this._userProcessFn(item);
396
+
397
+ // wait for the "userProcessFn", once resolved we will save it into the "collection"
398
+ const response: any | any[] = await userProcessFn;
399
+
400
+ if (response.hasOwnProperty(this.datasetIdPropName)) {
401
+ awaitedItemDetail = response; // from Promise
402
+ } else if (response instanceof Response && typeof response['json'] === 'function') {
403
+ awaitedItemDetail = await response['json'](); // from Fetch
404
+ } else if (response && response['content']) {
405
+ awaitedItemDetail = response['content']; // from http-client
406
+ }
407
+
408
+ if (!awaitedItemDetail || !awaitedItemDetail.hasOwnProperty(this.datasetIdPropName)) {
409
+ throw new Error(
410
+ '[Slickgrid-React] could not process the Row Detail, please make sure that your "process" callback ' +
411
+ `returns an item object that has an "${this.datasetIdPropName}" property`
412
+ );
413
+ }
414
+
415
+ // notify the plugin with the new item details
416
+ this.notifyTemplate(awaitedItemDetail || {});
417
+ }
418
+ }
419
+ }