Skip to content
This repository was archived by the owner on Jun 1, 2025. It is now read-only.

Commit 377a211

Browse files
authored
feat: Row Detail with inner grids (#467)
1 parent bc093de commit 377a211

File tree

14 files changed

+1335
-131
lines changed

14 files changed

+1335
-131
lines changed

docs/grid-functionalities/row-detail.md

Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -440,3 +440,203 @@ addNewColumn() {
440440
}
441441
}
442442
```
443+
444+
## Row Detail with Inner Grid
445+
446+
You can also add an inner grid inside a Row Detail, however there are a few things to know off and remember. Any time a Row Detail is falling outside the main grid viewport, it will be unmounted and until it comes back into the viewport which is then remounted. The process of unmounting and remounting means that Row Detail previous states aren't preserved, however you could use Grid State & Presets to overcome this problem.
447+
448+
##### Component
449+
450+
Main Grid Component
451+
452+
```tsx
453+
import React from 'react';
454+
import { type Column, ExtensionName, type GridOption, SlickgridReact, type SlickgridReactInstance, SlickRowDetailView, } from 'slickgrid-react';
455+
456+
import { Preload } from './preload';
457+
import { type Distributor, InnerGridComponent, type OrderData } from './inner-grid';
458+
459+
interface State extends BaseSlickGridState { }
460+
461+
export default class Example45 extends React.Component<Props, State> {
462+
reactGrid!: SlickgridReactInstance;
463+
constructor(public readonly props: Props) {
464+
super(props);
465+
466+
this.state = {
467+
gridOptions: undefined,
468+
columnDefinitions: [],
469+
dataset: this.getData(),
470+
};
471+
}
472+
473+
get rowDetailInstance() {
474+
return this.reactGrid?.extensionService.getExtensionInstanceByName(ExtensionName.rowDetailView);
475+
}
476+
477+
componentDidMount() {
478+
this.defineGrid();
479+
}
480+
481+
reactGridReady(reactGrid: SlickgridReactInstance) {
482+
this.reactGrid = reactGrid;
483+
}
484+
485+
getColumnDefinitions(): Column[] {
486+
return [/* ... */];
487+
}
488+
489+
defineGrid() {
490+
const columnDefinitions = this.getColumnDefinitions();
491+
const gridOptions = this.getGridOptions();
492+
493+
this.setState((props: Props, state: any) => {
494+
return { ...state, columnDefinitions, gridOptions };
495+
});
496+
}
497+
498+
getGridOptions(): GridOption {
499+
return {
500+
enableRowDetailView: true,
501+
rowSelectionOptions: {
502+
selectActiveRow: true
503+
},
504+
preRegisterExternalExtensions: (pubSubService) => {
505+
// Row Detail View is a special case because of its requirement to create extra column definition dynamically
506+
// so it must be pre-registered before SlickGrid is instantiated, we can do so via this option
507+
const rowDetail = new SlickRowDetailView(pubSubService as EventPubSubService);
508+
return [{ name: ExtensionName.rowDetailView, instance: rowDetail }];
509+
},
510+
rowDetailView: {
511+
process: (item: any) => simulateServerAsyncCall(item),
512+
loadOnce: false, // IMPORTANT, you can't use loadOnce with inner grid because only HTML template are re-rendered, not JS events
513+
panelRows: 10,
514+
preloadComponent: PreloadComponent,
515+
viewComponent: InnerGridComponent,
516+
},
517+
};
518+
}
519+
520+
render() {
521+
return !this.state.gridOptions ? '' : (
522+
<div id="demo-container" className="container-fluid">
523+
<SlickgridReact gridId="grid45"
524+
columnDefinitions={this.state.columnDefinitions}
525+
gridOptions={this.state.gridOptions}
526+
dataset={this.state.dataset}
527+
onReactGridCreated={$event => this.reactGridReady($event.detail)}
528+
/>
529+
</div >
530+
);
531+
}
532+
}
533+
```
534+
535+
Now, let's define our Inner Grid Component
536+
537+
```tsx
538+
import React from 'react';
539+
import { type Column, type GridOption, type GridState, type RowDetailViewProps, SlickgridReact, type SlickgridReactInstance } from 'slickgrid-react';
540+
541+
import type MainGrid from './MainGrid';
542+
543+
export interface Distributor { /* ... */ }
544+
export interface OrderData { /* ... */ }
545+
546+
interface State {
547+
innerGridOptions?: GridOption;
548+
innerColDefs: Column[];
549+
innerDataset: any[];
550+
}
551+
interface Props { }
552+
553+
export class MainGridDetailView extends React.Component<RowDetailViewProps<Distributor, typeof MainGrid>, State> {
554+
reactGrid!: SlickgridReactInstance;
555+
innerGridClass = '';
556+
557+
constructor(public readonly props: RowDetailViewProps<Distributor, typeof MainGrid>) {
558+
super(props);
559+
this.state = {
560+
innerGridOptions: undefined,
561+
innerColDefs: [],
562+
innerDataset: [...props.model.orderData],
563+
};
564+
this.innerGridClass = `row-detail-${this.props.model.id}`;
565+
}
566+
567+
componentDidMount() {
568+
this.defineGrid();
569+
}
570+
571+
getColumnDefinitions(): Column[] {
572+
return [
573+
{ id: 'orderId', field: 'orderId', name: 'Order ID', filterable: true, sortable: true },
574+
{ id: 'shipCity', field: 'shipCity', name: 'Ship City', filterable: true, sortable: true },
575+
{ id: 'freight', field: 'freight', name: 'Freight', filterable: true, sortable: true, type: 'number' },
576+
{ id: 'shipName', field: 'shipName', name: 'Ship Name', filterable: true, sortable: true },
577+
];
578+
}
579+
580+
defineGrid() {
581+
const innerColDefs = this.getColumnDefinitions();
582+
const innerGridOptions = this.getGridOptions();
583+
584+
this.setState((props: Props, state: any) => {
585+
return {
586+
...state,
587+
innerColDefs,
588+
innerGridOptions,
589+
};
590+
});
591+
}
592+
593+
getGridOptions(): GridOption {
594+
// OPTIONALLY reapply Grid State as Presets before unmounting the compoment
595+
const gridStateStr = sessionStorage.getItem(`gridstate_${innerGridClass.value}`);
596+
let gridState: GridState | undefined;
597+
if (gridStateStr) {
598+
gridState = JSON.parse(gridStateStr);
599+
}
600+
601+
return {
602+
autoResize: {
603+
container: `.${this.innerGridClass}`,
604+
},
605+
enableFiltering: true,
606+
enableSorting: true,
607+
enableCellNavigation: true,
608+
datasetIdPropertyName: 'orderId', // reapply grid state presets
609+
presets: gridState,
610+
};
611+
}
612+
613+
// OPTIONALLY save Grid State before unmounting the compoment
614+
handleBeforeGridDestroy() {
615+
if (this.props.model.isUsingInnerGridStatePresets) {
616+
const gridState = this.reactGrid.gridStateService.getCurrentGridState();
617+
sessionStorage.setItem(`gridstate_${this.innerGridClass}`, JSON.stringify(gridState));
618+
}
619+
}
620+
621+
reactGridReady(reactGrid: SlickgridReactInstance) {
622+
this.reactGrid = reactGrid;
623+
}
624+
625+
render() {
626+
return (
627+
<div className={`${this.innerGridClass}`}>
628+
<h4>Order Details (id: {this.props.model.id})</h4>
629+
<div className="container-fluid">
630+
{!this.state.showGrid ? '' : <SlickgridReact gridId={`innergrid-${this.props.model.id}`}
631+
columnDefinitions={this.state.innerColDefs}
632+
gridOptions={this.state.innerGridOptions}
633+
dataset={this.state.innerDataset}
634+
onReactGridCreated={$event => this.reactGridReady($event.detail)}
635+
onBeforeGridDestroy={() => this.handleBeforeGridDestroy()}
636+
/>}
637+
</div>
638+
</div>
639+
);
640+
}
641+
}
642+
```

package.json

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -81,12 +81,12 @@
8181
"/src/slickgrid-react"
8282
],
8383
"dependencies": {
84-
"@slickgrid-universal/common": "~5.12.2",
85-
"@slickgrid-universal/custom-footer-component": "~5.12.2",
86-
"@slickgrid-universal/empty-warning-component": "~5.12.2",
87-
"@slickgrid-universal/event-pub-sub": "~5.12.2",
88-
"@slickgrid-universal/pagination-component": "~5.12.2",
89-
"@slickgrid-universal/row-detail-view-plugin": "~5.12.2",
84+
"@slickgrid-universal/common": "~5.13.0",
85+
"@slickgrid-universal/custom-footer-component": "~5.13.0",
86+
"@slickgrid-universal/empty-warning-component": "~5.13.0",
87+
"@slickgrid-universal/event-pub-sub": "~5.13.0",
88+
"@slickgrid-universal/pagination-component": "~5.13.0",
89+
"@slickgrid-universal/row-detail-view-plugin": "~5.13.0",
9090
"dequal": "^2.0.3",
9191
"i18next": "^23.16.8",
9292
"sortablejs": "^1.15.6"
@@ -101,13 +101,13 @@
101101
"@formkit/tempo": "^0.1.2",
102102
"@popperjs/core": "^2.11.8",
103103
"@release-it/conventional-changelog": "^10.0.0",
104-
"@slickgrid-universal/composite-editor-component": "~5.12.2",
105-
"@slickgrid-universal/custom-tooltip-plugin": "~5.12.2",
106-
"@slickgrid-universal/excel-export": "~5.12.2",
107-
"@slickgrid-universal/graphql": "~5.12.2",
108-
"@slickgrid-universal/odata": "~5.12.2",
109-
"@slickgrid-universal/rxjs-observable": "~5.12.2",
110-
"@slickgrid-universal/text-export": "~5.12.2",
104+
"@slickgrid-universal/composite-editor-component": "~5.13.0",
105+
"@slickgrid-universal/custom-tooltip-plugin": "~5.13.0",
106+
"@slickgrid-universal/excel-export": "~5.13.0",
107+
"@slickgrid-universal/graphql": "~5.13.0",
108+
"@slickgrid-universal/odata": "~5.13.0",
109+
"@slickgrid-universal/rxjs-observable": "~5.13.0",
110+
"@slickgrid-universal/text-export": "~5.13.0",
111111
"@types/fnando__sparkline": "^0.3.7",
112112
"@types/i18next-xhr-backend": "^1.4.2",
113113
"@types/node": "^22.13.1",

src/examples/slickgrid/App.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ import Example41 from './Example41';
4545
import Example42 from './Example42';
4646
import Example43 from './Example43';
4747
import Example44 from './Example44';
48+
import Example45 from './Example45';
4849

4950
const routes: Array<{ path: string; route: string; component: any; title: string; }> = [
5051
{ path: 'example1', route: '/example1', component: <Example1 />, title: '1- Basic Grid / 2 Grids' },
@@ -90,6 +91,7 @@ const routes: Array<{ path: string; route: string; component: any; title: string
9091
{ path: 'example42', route: '/example42', component: <Example42 />, title: '42- Custom Pagination' },
9192
{ path: 'example43', route: '/example43', component: <Example43 />, title: '43- Colspan/Rowspan (timesheets)' },
9293
{ path: 'example44', route: '/example44', component: <Example44 />, title: '44- Colspan/Rowspan (large data)' },
94+
{ path: 'example45', route: '/example45', component: <Example45 />, title: '45- Row Detail with inner Grid' },
9395
];
9496

9597
export default function Routes() {
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
import React from 'react';
2+
import { type Column, type GridOption, type GridState, type RowDetailViewProps, SlickgridReact, type SlickgridReactInstance } from '../../slickgrid-react';
3+
4+
import type Example45 from './Example45';
5+
import './example45-detail-view.scss';
6+
7+
export interface Distributor {
8+
id: number;
9+
companyId: number;
10+
companyName: string;
11+
city: string;
12+
streetAddress: string;
13+
zipCode: string;
14+
country: string;
15+
orderData: OrderData[];
16+
isUsingInnerGridStatePresets: boolean;
17+
}
18+
19+
export interface OrderData {
20+
orderId: string;
21+
shipCity: string;
22+
freight: number;
23+
shipName: string;
24+
}
25+
26+
interface State {
27+
showGrid: boolean;
28+
innerGridOptions?: GridOption;
29+
innerColDefs: Column[];
30+
innerDataset: any[];
31+
}
32+
interface Props { }
33+
34+
export class Example45DetailView extends React.Component<RowDetailViewProps<Distributor, typeof Example45>, State> {
35+
_isMounted = false;
36+
reactGrid!: SlickgridReactInstance;
37+
innerGridClass = '';
38+
39+
constructor(public readonly props: RowDetailViewProps<Distributor, typeof Example45>) {
40+
super(props);
41+
this.state = {
42+
innerGridOptions: undefined,
43+
innerColDefs: [],
44+
innerDataset: [...props.model.orderData],
45+
showGrid: false,
46+
};
47+
this.innerGridClass = `row-detail-${this.props.model.id}`;
48+
}
49+
50+
componentDidMount() {
51+
this._isMounted = true;
52+
this.defineGrid();
53+
}
54+
55+
componentWillUnmount(): void {
56+
this._isMounted = false;
57+
console.log('inner grid unmounting');
58+
}
59+
60+
getColumnDefinitions(): Column[] {
61+
return [
62+
{ id: 'orderId', field: 'orderId', name: 'Order ID', filterable: true, sortable: true },
63+
{ id: 'shipCity', field: 'shipCity', name: 'Ship City', filterable: true, sortable: true },
64+
{ id: 'freight', field: 'freight', name: 'Freight', filterable: true, sortable: true, type: 'number' },
65+
{ id: 'shipName', field: 'shipName', name: 'Ship Name', filterable: true, sortable: true },
66+
];
67+
}
68+
69+
defineGrid() {
70+
const innerColDefs = this.getColumnDefinitions();
71+
const innerGridOptions = this.getGridOptions();
72+
73+
if (this._isMounted) {
74+
this.setState((props: Props, state: any) => {
75+
return {
76+
...state,
77+
innerColDefs,
78+
innerGridOptions,
79+
showGrid: true,
80+
};
81+
});
82+
}
83+
}
84+
85+
getGridOptions(): GridOption {
86+
// when Grid State found in Session Storage, reapply inner Grid State then reapply it as preset
87+
let gridState: GridState | undefined;
88+
if (this.props.model.isUsingInnerGridStatePresets) {
89+
const gridStateStr = sessionStorage.getItem(`gridstate_${this.innerGridClass}`);
90+
if (gridStateStr) {
91+
gridState = JSON.parse(gridStateStr);
92+
}
93+
}
94+
95+
return {
96+
autoResize: {
97+
container: `.${this.innerGridClass}`,
98+
rightPadding: 30,
99+
minHeight: 200,
100+
},
101+
enableFiltering: true,
102+
enableSorting: true,
103+
rowHeight: 33,
104+
enableCellNavigation: true,
105+
datasetIdPropertyName: 'orderId',
106+
presets: gridState,
107+
};
108+
}
109+
110+
handleBeforeGridDestroy() {
111+
if (this.props.model.isUsingInnerGridStatePresets) {
112+
const gridState = this.reactGrid.gridStateService.getCurrentGridState();
113+
sessionStorage.setItem(`gridstate_${this.innerGridClass}`, JSON.stringify(gridState));
114+
}
115+
}
116+
117+
reactGridReady(reactGrid: SlickgridReactInstance) {
118+
this.reactGrid = reactGrid;
119+
}
120+
121+
render() {
122+
return (
123+
<div className={`${this.innerGridClass}`}>
124+
<h4>{this.props.model.companyName} - Order Details (id: {this.props.model.id})</h4>
125+
<div className="container-fluid">
126+
{!this.state.showGrid ? '' : <SlickgridReact gridId={`innergrid-${this.props.model.id}`}
127+
columnDefinitions={this.state.innerColDefs}
128+
gridOptions={this.state.innerGridOptions}
129+
dataset={this.state.innerDataset}
130+
onReactGridCreated={$event => this.reactGridReady($event.detail)}
131+
onBeforeGridDestroy={() => this.handleBeforeGridDestroy()}
132+
/>}
133+
</div>
134+
</div>
135+
);
136+
}
137+
}

0 commit comments

Comments
 (0)