Your search did not match any results.
Data Grid

Advanced Master-Detail View

This demo illustrates an advanced master-detail view in the DataGrid component. Master rows represent suppliers. Detail sections contain TabPanel components with two tabs: Orders and Address. In the Orders tab, you can choose a supplier's product from the SelectBox, and the DataGrid under it will show orders placed on this product. The Address tab displays the supplier's address.

Backend API
Copy to CodeSandBox
Apply
Reset
import React from 'react'; import { Column, DataGrid, MasterDetail, Paging, } from 'devextreme-react/data-grid'; import { createStore } from 'devextreme-aspnet-data-nojquery'; import MasterDetailView from './MasterDetailView.js'; const url = 'https://js.devexpress.com/Demos/Mvc/api/DataGridAdvancedMasterDetailView'; const suppliersData = createStore({ key: 'SupplierID', loadUrl: `${url}/GetSuppliers`, }); class App extends React.Component { render() { return ( <DataGrid dataSource={suppliersData} remoteOperations={true} showBorders={true} id="gridContainer" > <MasterDetail enabled={true} component={MasterDetailView} /> <Paging defaultPageSize={15} /> <Column dataField="ContactName" /> <Column dataField="ContactTitle" /> <Column dataField="CompanyName" /> <Column dataField="City" /> <Column dataField="Country" /> </DataGrid> ); } } export default App;
import React from 'react'; import ReactDOM from 'react-dom'; import App from './App.js'; ReactDOM.render( <App />, document.getElementById('app'), );
<!DOCTYPE html> <html> <head> <title>DevExtreme Demo</title> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0" /> <link rel="stylesheet" type="text/css" href="https://cdn3.devexpress.com/jslib/22.2.6/css/dx.light.css" /> <link rel="stylesheet" type="text/css" href="styles.css" /> <script src="https://unpkg.com/core-js@2.6.12/client/shim.min.js"></script> <script src="https://unpkg.com/systemjs@0.21.3/dist/system.js"></script> <script type="text/javascript" src="config.js"></script> <script type="text/javascript"> System.import("./index.js"); </script> </head> <body class="dx-viewport"> <div class="demo-container"> <div id="app"></div> </div> </body> </html>
#gridContainer { height: 620px; } .dx-datagrid-rowsview .dx-master-detail-row:not(.dx-datagrid-edit-form) > .dx-datagrid-group-space, .dx-datagrid-rowsview .dx-master-detail-row:not(.dx-datagrid-edit-form) .dx-master-detail-cell { background-color: transparent; } .form-container { padding: 20px; } .address-form label { font-weight: bold; }
import React from 'react'; import { Form, Item } from 'devextreme-react/form'; const items = ['Address', 'City', 'Region', 'PostalCode', 'Country', 'Phone']; class AddressTab extends React.Component { render() { return ( <Form formData={this.props.data} colCount={2} className="address-form form-container" > { items.map((item, index) => <Item dataField={item} key={index} render={this.renderFormItem} />) } </Form> ); } renderFormItem(item) { return <span>{item.editorOptions.value}</span>; } } export default AddressTab;
import React from 'react'; import { TabPanel, Item } from 'devextreme-react/tab-panel'; import AddressTab from './AddressTab.js'; import OrdersTab from './OrdersTab.js'; class MasterDetailView extends React.Component { constructor(props) { super(props); this.renderOrdersTab = this.renderOrdersTab.bind(this); this.renderAddressTab = this.renderAddressTab.bind(this); } render() { return ( <TabPanel> <Item title="Orders" render={this.renderOrdersTab} /> <Item title="Address" render={this.renderAddressTab} /> </TabPanel> ); } renderOrdersTab() { return <OrdersTab supplierId={this.props.data.key} />; } renderAddressTab() { return <AddressTab data={this.props.data.data} />; } } export default MasterDetailView;
import React from 'react'; import { Column, DataGrid, Paging, Summary, TotalItem, ValueFormat, } from 'devextreme-react/data-grid'; import { createStore } from 'devextreme-aspnet-data-nojquery'; const url = 'https://js.devexpress.com/Demos/Mvc/api/DataGridAdvancedMasterDetailView'; class OrderHistory extends React.Component { constructor(props) { super(props); this.state = { orderHistoryStore: null, }; } render() { return ( <DataGrid dataSource={this.state.orderHistoryStore} showBorders={true} > <Paging defaultPageSize={5} /> <Column dataField="OrderID" /> <Column dataField="OrderDate" dataType="date" /> <Column dataField="ShipCountry" /> <Column dataField="ShipCity" /> <Column dataField="UnitPrice" format="currency" /> <Column dataField="Quantity" /> <Column dataField="Discount" format="percent" /> <Summary> <TotalItem column="UnitPrice" summaryType="sum"> <ValueFormat format="currency" precision={2} /> </TotalItem> <TotalItem column="Quantity" summaryType="count" /> </Summary> </DataGrid> ); } componentDidUpdate(prevProps) { const { productId } = this.props; if (prevProps.productId !== productId) { this.setState({ orderHistoryStore: createStore({ key: 'OrderID', loadParams: { ProductID: productId }, loadUrl: `${url}/GetOrdersByProduct`, }), }); } } } export default OrderHistory;
import React from 'react'; import { Form, Item, Label } from 'devextreme-react/form'; import ProductSelectBox from './ProductSelectBox.js'; import OrderHistory from './OrderHistory.js'; class OrdersTab extends React.Component { constructor(props) { super(props); this.state = { chosenProductId: null, }; this.productChanged = this.productChanged.bind(this); this.renderSelectBox = this.renderSelectBox.bind(this); this.renderOrderHistory = this.renderOrderHistory.bind(this); } render() { return ( <Form labelLocation="top" className="form-container" > <Item render={this.renderSelectBox}> <Label text="Product" /> </Item> <Item render={this.renderOrderHistory}> <Label text="Order History" /> </Item> </Form> ); } renderSelectBox() { return <ProductSelectBox supplierId={this.props.supplierId} productId={this.state.chosenProductId} onProductChanged={this.productChanged} />; } renderOrderHistory() { return <OrderHistory productId={this.state.chosenProductId} />; } productChanged(productId) { this.setState({ chosenProductId: productId, }); } } export default OrdersTab;
import React from 'react'; import { SelectBox } from 'devextreme-react/select-box'; import { createStore } from 'devextreme-aspnet-data-nojquery'; const url = 'https://js.devexpress.com/Demos/Mvc/api/DataGridAdvancedMasterDetailView'; class ProductSelectBox extends React.Component { constructor(props) { super(props); this.productsData = createStore({ key: 'ProductID', loadParams: { SupplierID: this.props.supplierId }, loadUrl: `${url}/GetProductsBySupplier`, onLoaded: this.setDefaultValue.bind(this), }); this.valueChanged = this.valueChanged.bind(this); } render() { return ( <SelectBox value={this.props.productId} deferRendering={false} dataSource={this.productsData} valueExpr="ProductID" displayExpr="ProductName" onValueChanged={this.valueChanged} /> ); } setDefaultValue(items) { const firstItem = items[0]; if (firstItem && this.props.productId === null) { this.props.onProductChanged(firstItem.ProductID); } } valueChanged(e) { this.props.onProductChanged(e.value); } } export default ProductSelectBox;
window.config = { transpiler: 'plugin-babel', meta: { 'devextreme/localization.js': { 'esModule': true, }, 'devextreme-aspnet-data-nojquery': { 'esModule': true, }, }, paths: { 'npm:': 'https://unpkg.com/', }, defaultExtension: 'js', map: { 'react': 'npm:react@17.0.2/umd/react.development.js', 'react-dom': 'npm:react-dom@17.0.2/umd/react-dom.development.js', 'prop-types': 'npm:prop-types@15.8.1/prop-types.js', 'devextreme-aspnet-data-nojquery': 'npm:devextreme-aspnet-data-nojquery@2.9.0/index.js', 'rrule': 'npm:rrule@2.6.4/dist/es5/rrule.js', 'luxon': 'npm:luxon@1.28.0/build/global/luxon.min.js', 'es6-object-assign': 'npm:es6-object-assign@1.1.0', 'devextreme': 'npm:devextreme@22.2.6/cjs', 'devextreme-react': 'npm:devextreme-react@22.2.6', 'jszip': 'npm:jszip@3.7.1/dist/jszip.min.js', 'devextreme-quill': 'npm:devextreme-quill@1.5.20/dist/dx-quill.min.js', 'devexpress-diagram': 'npm:devexpress-diagram@2.1.72/dist/dx-diagram.js', 'devexpress-gantt': 'npm:devexpress-gantt@4.1.43/dist/dx-gantt.js', '@devextreme/runtime': 'npm:@devextreme/runtime@3.0.11', 'inferno': 'npm:inferno@7.4.11/dist/inferno.min.js', 'inferno-compat': 'npm:inferno-compat/dist/inferno-compat.min.js', 'inferno-create-element': 'npm:inferno-create-element@7.4.11/dist/inferno-create-element.min.js', 'inferno-dom': 'npm:inferno-dom/dist/inferno-dom.min.js', 'inferno-hydrate': 'npm:inferno-hydrate@7.4.11/dist/inferno-hydrate.min.js', 'inferno-clone-vnode': 'npm:inferno-clone-vnode/dist/inferno-clone-vnode.min.js', 'inferno-create-class': 'npm:inferno-create-class/dist/inferno-create-class.min.js', 'inferno-extras': 'npm:inferno-extras/dist/inferno-extras.min.js', // SystemJS plugins 'plugin-babel': 'npm:systemjs-plugin-babel@0.0.25/plugin-babel.js', 'systemjs-babel-build': 'npm:systemjs-plugin-babel@0.0.25/systemjs-babel-browser.js', // Prettier 'prettier/standalone': 'npm:prettier@2.8.4/standalone.js', 'prettier/parser-html': 'npm:prettier@2.8.4/parser-html.js', }, packages: { 'devextreme': { defaultExtension: 'js', }, 'devextreme-react': { main: 'index.js', }, 'devextreme/events/utils': { main: 'index', }, 'devextreme/events': { main: 'index', }, 'es6-object-assign': { main: './index.js', defaultExtension: 'js', }, }, packageConfigPaths: [ 'npm:@devextreme/*/package.json', 'npm:@devextreme/runtime@3.0.11/inferno/package.json', ], babelOptions: { sourceMaps: false, stage0: true, react: true, }, }; System.config(window.config);