Row Details (React)

Sometimes rows are bound to data objects that contain more information than would fit easily on a regular grid.

In these scenarios, you may want to use the FlexGridDetailProvider class that is included with the wijmo.grid.detail module. The FlexGridDetailProvider will create a details section for each row that users can expand to see the rest of the data.

You can add anything you want to the detail row, including other grids. The sample below shows both how to display the data in another grid, as well as within standard HTML elements.

Learn about FlexGrid | FlexGrid API Reference

This example uses React.

import 'bootstrap.css'; import '@mescius/wijmo.styles/wijmo.css'; import ReactDOM from 'react-dom/client'; import React, { useEffect, useRef, useState } from 'react'; import useEvent from 'react-use-event-hook'; import { FlexGrid, FlexGridColumn } from '@mescius/wijmo.react.grid'; import { FlexGridDetail } from '@mescius/wijmo.react.grid.detail'; import * as wjInput from '@mescius/wijmo.react.input'; import * as wjOdata from '@mescius/wijmo.odata'; import './app.css'; function App() { const catToProductMap = useRef(new Map()); const [state, setState] = useState({ categories: [], products: [], detailVisibilityMode: 'ExpandSingle', maxHeight: 0, isAnimated: true, keyActionEnter: 'None', evenRowHasDetail: false, }); useEffect(() => { const url = 'https://services.odata.org/Northwind/Northwind.svc'; setState(Object.assign(Object.assign({}, state), { categories: new wjOdata.ODataCollectionView(url, 'Categories', { fields: ['CategoryID', 'CategoryName', 'Description'] }), products: new wjOdata.ODataCollectionView(url, 'Products') })); }, []); const getProducts = (categoryID) => { let categoryProducts = catToProductMap.current.get(categoryID); if (!categoryProducts) { categoryProducts = state.products.items.filter((product) => product.CategoryID === categoryID); if (categoryProducts) { catToProductMap.current.set(categoryID, categoryProducts); } } return categoryProducts; }; const rowHasDetailFn = useEvent((row) => !(row.dataItem.CategoryID % 2)); const clickHandlerFactory = (prop) => { return (s, e) => { if (s.selectedIndex > -1) { setState((prevState) => (Object.assign(Object.assign({}, prevState), { [prop]: s.selectedValue }))); } }; }; return (<div className="container-fluid"> <h3>HTML in Row Details</h3> <p> This grid shows product categories on each row. Expanding the rows shows an HTML element with information about the products in that category: </p> <FlexGrid itemsSource={state.categories} isReadOnly> <FlexGridColumn header="Category Name" binding="CategoryName" width="*"/> <FlexGridColumn header="Description" binding="Description" width="2*"/> <FlexGridDetail isAnimated template={(ctx) => { var _a, _b; return (<React.Fragment> ID: <b>{ctx.item.CategoryID}</b><br /> Name: <b>{ctx.item.CategoryName}</b><br /> Description: <b>{ctx.item.Description}</b><br /> Products: <b>{(_a = getProducts(ctx.item.CategoryID)) === null || _a === void 0 ? void 0 : _a.length} items</b><br /> <ol> {(_b = getProducts(ctx.item.CategoryID)) === null || _b === void 0 ? void 0 : _b.map(p => <li key={p.ProductID}>{p.ProductName}</li>)} </ol> </React.Fragment>); }}/> </FlexGrid> <h3>Grids in Row Details</h3> <p> You can add anything you want to the detail rows, including other grids. This example shows the same categories, but the detail row uses another grid to show the products: </p> <FlexGrid itemsSource={state.categories} isReadOnly autoGenerateColumns={false}> <FlexGridColumn header="Category Name" binding="CategoryName" width="*"/> <FlexGridColumn header="Description" binding="Description" width="2*"/> <FlexGridDetail detailVisibilityMode={state.detailVisibilityMode} maxHeight={state.maxHeight} isAnimated={state.isAnimated} keyActionEnter={state.keyActionEnter} rowHasDetail={state.evenRowHasDetail ? rowHasDetailFn : null} template={(ctx) => (<FlexGrid itemsSource={getProducts(ctx.item.CategoryID)} isReadOnly> <FlexGridColumn header="ID" binding="ProductID"/> <FlexGridColumn header="Name" binding="ProductName"/> <FlexGridColumn header="Qty/Unit" binding="QuantityPerUnit"/> <FlexGridColumn header="Unit Price" binding="UnitPrice"/> <FlexGridColumn header="Discontinued" binding="Discontinued"/> </FlexGrid>)}/> </FlexGrid> <div className="grid-options"> <wjInput.Menu header="detailVisibilityMode" value={state.detailVisibilityMode} itemClicked={clickHandlerFactory('detailVisibilityMode')}> <wjInput.MenuItem value="Code">Code</wjInput.MenuItem> <wjInput.MenuItem value="Selection">Selection</wjInput.MenuItem> <wjInput.MenuItem value="ExpandSingle">ExpandSingle</wjInput.MenuItem> <wjInput.MenuItem value="ExpandMulti">ExpandMulti</wjInput.MenuItem> </wjInput.Menu> <wjInput.Menu header="maxHeight" value={state.maxHeight} itemClicked={clickHandlerFactory('maxHeight')}> <wjInput.MenuItem value={0}>null</wjInput.MenuItem> <wjInput.MenuItem value={100}>100</wjInput.MenuItem> <wjInput.MenuItem value={200}>200</wjInput.MenuItem> <wjInput.MenuItem value={300}>300</wjInput.MenuItem> <wjInput.MenuItem value={400}>400</wjInput.MenuItem> <wjInput.MenuItem value={500}>500</wjInput.MenuItem> </wjInput.Menu> <wjInput.Menu header="isAnimated" value={state.isAnimated} itemClicked={clickHandlerFactory('isAnimated')}> <wjInput.MenuItem value={true}>True</wjInput.MenuItem> <wjInput.MenuItem value={false}>False</wjInput.MenuItem> </wjInput.Menu> <wjInput.Menu header="keyActionEnter" value={state.keyActionEnter} itemClicked={clickHandlerFactory('keyActionEnter')}> <wjInput.MenuItem value="None">None</wjInput.MenuItem> <wjInput.MenuItem value="ToggleDetail">ToggleDetail</wjInput.MenuItem> </wjInput.Menu> <wjInput.Menu header="rowDetails" value={state.evenRowHasDetail} itemClicked={clickHandlerFactory('evenRowHasDetail')}> <wjInput.MenuItem value={false}>All</wjInput.MenuItem> <wjInput.MenuItem value={true}>Even rows only</wjInput.MenuItem> </wjInput.Menu> </div> </div>); } const container = document.getElementById('app'); if (container) { const root = ReactDOM.createRoot(container); root.render(<App />); }
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>MESCIUS Wijmo Wijmo FlexGrid Row Details</title> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <!-- SystemJS --> <script src="https://cdnjs.cloudflare.com/ajax/libs/systemjs/0.19.40/system.src.js" integrity="sha512-G6mEj6h18+m3MvzdviSDfPle/TfH0//cXcB33AKlNR/Rha0yQsKefDZKRTkIZos97HEGq2JMV1RT5ybMoQ3WsQ==" crossorigin="anonymous"></script> <script src="systemjs.config.js"></script> <script> System.import('./src/app'); </script> </head> <body> <div id="app"></div> </body> </html>
.wj-flexgrid { max-height: 350px; } .grid-options .wj-menu { margin: 0.25em 0.5em 0.25em 0; }
(function (global) { System.config({ transpiler: 'plugin-babel', babelOptions: { es2015: true, react: true }, meta: { '*.css': { loader: 'css' } }, paths: { // paths serve as alias 'npm:': 'node_modules/' }, // map tells the System loader where to look for things map: { 'jszip': 'npm:jszip/dist/jszip.js', '@mescius/wijmo': 'npm:@mescius/wijmo/index.js', '@mescius/wijmo.input': 'npm:@mescius/wijmo.input/index.js', '@mescius/wijmo.styles': 'npm:@mescius/wijmo.styles', '@mescius/wijmo.cultures': 'npm:@mescius/wijmo.cultures', '@mescius/wijmo.chart': 'npm:@mescius/wijmo.chart/index.js', '@mescius/wijmo.chart.analytics': 'npm:@mescius/wijmo.chart.analytics/index.js', '@mescius/wijmo.chart.animation': 'npm:@mescius/wijmo.chart.animation/index.js', '@mescius/wijmo.chart.annotation': 'npm:@mescius/wijmo.chart.annotation/index.js', '@mescius/wijmo.chart.finance': 'npm:@mescius/wijmo.chart.finance/index.js', '@mescius/wijmo.chart.finance.analytics': 'npm:@mescius/wijmo.chart.finance.analytics/index.js', '@mescius/wijmo.chart.hierarchical': 'npm:@mescius/wijmo.chart.hierarchical/index.js', '@mescius/wijmo.chart.interaction': 'npm:@mescius/wijmo.chart.interaction/index.js', '@mescius/wijmo.chart.radar': 'npm:@mescius/wijmo.chart.radar/index.js', '@mescius/wijmo.chart.render': 'npm:@mescius/wijmo.chart.render/index.js', '@mescius/wijmo.chart.webgl': 'npm:@mescius/wijmo.chart.webgl/index.js', '@mescius/wijmo.chart.map': 'npm:@mescius/wijmo.chart.map/index.js', '@mescius/wijmo.gauge': 'npm:@mescius/wijmo.gauge/index.js', '@mescius/wijmo.grid': 'npm:@mescius/wijmo.grid/index.js', '@mescius/wijmo.grid.detail': 'npm:@mescius/wijmo.grid.detail/index.js', '@mescius/wijmo.grid.filter': 'npm:@mescius/wijmo.grid.filter/index.js', '@mescius/wijmo.grid.search': 'npm:@mescius/wijmo.grid.search/index.js', '@mescius/wijmo.grid.grouppanel': 'npm:@mescius/wijmo.grid.grouppanel/index.js', '@mescius/wijmo.grid.multirow': 'npm:@mescius/wijmo.grid.multirow/index.js', '@mescius/wijmo.grid.transposed': 'npm:@mescius/wijmo.grid.transposed/index.js', '@mescius/wijmo.grid.transposedmultirow': 'npm:@mescius/wijmo.grid.transposedmultirow/index.js', '@mescius/wijmo.grid.pdf': 'npm:@mescius/wijmo.grid.pdf/index.js', '@mescius/wijmo.grid.sheet': 'npm:@mescius/wijmo.grid.sheet/index.js', '@mescius/wijmo.grid.xlsx': 'npm:@mescius/wijmo.grid.xlsx/index.js', '@mescius/wijmo.grid.selector': 'npm:@mescius/wijmo.grid.selector/index.js', '@mescius/wijmo.grid.cellmaker': 'npm:@mescius/wijmo.grid.cellmaker/index.js', '@mescius/wijmo.grid.immutable': 'npm:@mescius/wijmo.grid.immutable/index.js', '@mescius/wijmo.touch': 'npm:@mescius/wijmo.touch/index.js', '@mescius/wijmo.cloud': 'npm:@mescius/wijmo.cloud/index.js', '@mescius/wijmo.nav': 'npm:@mescius/wijmo.nav/index.js', '@mescius/wijmo.odata': 'npm:@mescius/wijmo.odata/index.js', '@mescius/wijmo.olap': 'npm:@mescius/wijmo.olap/index.js', '@mescius/wijmo.rest': 'npm:@mescius/wijmo.rest/index.js', '@mescius/wijmo.pdf': 'npm:@mescius/wijmo.pdf/index.js', '@mescius/wijmo.pdf.security': 'npm:@mescius/wijmo.pdf.security/index.js', '@mescius/wijmo.viewer': 'npm:@mescius/wijmo.viewer/index.js', '@mescius/wijmo.xlsx': 'npm:@mescius/wijmo.xlsx/index.js', '@mescius/wijmo.undo': 'npm:@mescius/wijmo.undo/index.js', '@mescius/wijmo.interop.grid': 'npm:@mescius/wijmo.interop.grid/index.js', '@mescius/wijmo.barcode': 'npm:@mescius/wijmo.barcode/index.js', '@mescius/wijmo.barcode.common': 'npm:@mescius/wijmo.barcode.common/index.js', '@mescius/wijmo.barcode.composite': 'npm:@mescius/wijmo.barcode.composite/index.js', '@mescius/wijmo.barcode.specialized': 'npm:@mescius/wijmo.barcode.specialized/index.js', "@mescius/wijmo.react.chart.analytics": "npm:@mescius/wijmo.react.chart.analytics/index.js", "@mescius/wijmo.react.chart.animation": "npm:@mescius/wijmo.react.chart.animation/index.js", "@mescius/wijmo.react.chart.annotation": "npm:@mescius/wijmo.react.chart.annotation/index.js", "@mescius/wijmo.react.chart.finance.analytics": "npm:@mescius/wijmo.react.chart.finance.analytics/index.js", "@mescius/wijmo.react.chart.finance": "npm:@mescius/wijmo.react.chart.finance/index.js", "@mescius/wijmo.react.chart.hierarchical": "npm:@mescius/wijmo.react.chart.hierarchical/index.js", "@mescius/wijmo.react.chart.interaction": "npm:@mescius/wijmo.react.chart.interaction/index.js", "@mescius/wijmo.react.chart.radar": "npm:@mescius/wijmo.react.chart.radar/index.js", "@mescius/wijmo.react.chart": "npm:@mescius/wijmo.react.chart/index.js", "@mescius/wijmo.react.core": "npm:@mescius/wijmo.react.core/index.js", '@mescius/wijmo.react.chart.map': 'npm:@mescius/wijmo.react.chart.map/index.js', "@mescius/wijmo.react.gauge": "npm:@mescius/wijmo.react.gauge/index.js", "@mescius/wijmo.react.grid.detail": "npm:@mescius/wijmo.react.grid.detail/index.js", "@mescius/wijmo.react.grid.filter": "npm:@mescius/wijmo.react.grid.filter/index.js", "@mescius/wijmo.react.grid.grouppanel": "npm:@mescius/wijmo.react.grid.grouppanel/index.js", '@mescius/wijmo.react.grid.search': 'npm:@mescius/wijmo.react.grid.search/index.js', "@mescius/wijmo.react.grid.multirow": "npm:@mescius/wijmo.react.grid.multirow/index.js", "@mescius/wijmo.react.grid.sheet": "npm:@mescius/wijmo.react.grid.sheet/index.js", '@mescius/wijmo.react.grid.transposed': 'npm:@mescius/wijmo.react.grid.transposed/index.js', '@mescius/wijmo.react.grid.transposedmultirow': 'npm:@mescius/wijmo.react.grid.transposedmultirow/index.js', '@mescius/wijmo.react.grid.immutable': 'npm:@mescius/wijmo.react.grid.immutable/index.js', "@mescius/wijmo.react.grid": "npm:@mescius/wijmo.react.grid/index.js", "@mescius/wijmo.react.input": "npm:@mescius/wijmo.react.input/index.js", "@mescius/wijmo.react.olap": "npm:@mescius/wijmo.react.olap/index.js", "@mescius/wijmo.react.viewer": "npm:@mescius/wijmo.react.viewer/index.js", "@mescius/wijmo.react.nav": "npm:@mescius/wijmo.react.nav/index.js", "@mescius/wijmo.react.base": "npm:@mescius/wijmo.react.base/index.js", '@mescius/wijmo.react.barcode.common': 'npm:@mescius/wijmo.react.barcode.common/index.js', '@mescius/wijmo.react.barcode.composite': 'npm:@mescius/wijmo.react.barcode.composite/index.js', '@mescius/wijmo.react.barcode.specialized': 'npm:@mescius/wijmo.react.barcode.specialized/index.js', 'jszip': 'npm:jszip/dist/jszip.js', 'react': 'npm:react/umd/react.production.min.js', 'react-dom': 'npm:react-dom/umd/react-dom.production.min.js', 'react-dom/client': 'npm:react-dom/umd/react-dom.production.min.js', 'redux': 'npm:redux/dist/redux.min.js', 'react-redux': 'npm:react-redux/dist/react-redux.min.js', 'bootstrap.css': 'npm:bootstrap/dist/css/bootstrap.min.css', 'css': 'npm:systemjs-plugin-css/css.js', 'plugin-babel': 'npm:systemjs-plugin-babel/plugin-babel.js', 'systemjs-babel-build':'npm:systemjs-plugin-babel/systemjs-babel-browser.js', "react-use-event-hook": "npm:react-use-event-hook/dist/esm/useEvent.js", }, // packages tells the System loader how to load when no filename and/or no extension packages: { src: { defaultExtension: 'jsx' }, "node_modules": { defaultExtension: 'js' }, } }); })(this);