Cell Templates (React)

Wijmo interops for popular frameworks allow you to define custom content for MultiRow cells declaratively, by using each frameworks' own template markup in conjunction with Wijmo's MultiRowCellTemplate component.

Such templates may include arbitrary components and HTML elements, with property and event bindings. All types of grid cells can be defined using templates (data cells, header cells, editors, etc).

This sample shows a grid with the templates for all cell types. The templates can be dynamically enabled or disabled by toggling the checkboxes.

Learn about MultiRow | MultiRow API Reference

This example uses React.

import 'bootstrap.css'; import '@grapecity/wijmo.styles/wijmo.css'; import './app.css'; // import * as React from 'react'; import * as ReactDOM from 'react-dom/client'; import * as wjInput from '@grapecity/wijmo.react.input'; import { MultiRow, MultiRowCell, MultiRowCellGroup, MultiRowCellTemplate } from '@grapecity/wijmo.react.grid.multirow'; import * as wjcCore from '@grapecity/wijmo'; import * as dataService from './data'; // class App extends React.Component { constructor(props) { super(props); this.mrRef = React.createRef(); // // // The template used in more than one column is defined as a method. // The rest of the template functions are inlined in MultiRowCellTemplate // definitions. this.totalTemplate = (context) => { return <React.Fragment> {context.col.header}: {wjcCore.Globalize.formatNumber(context.value, 'N0')} </React.Fragment>; }; // Another template function. this.rowHeaderTemplate = (context) => context.row.index / context.row.grid.rowsPerItem + 1; this.state = { itemsSource: dataService.getCv(dataService.getData()), countries: dataService.getCountries(), customTopLeft: true, customRowHeader: true, customRowHeaderEdit: true, customCell: true, customCellEdit: true, customGroupHeader: true, customColumnHeader: true, customGroup: true, highlightDownloads: true }; this.totalTemplate = this.totalTemplate.bind(this); this.rowHeaderTemplate = this.rowHeaderTemplate.bind(this); } // render() { return <div className="container-fluid"> <p> Use a <b>MultiRowCellTemplate</b> component to define a cell template. The <b>cellType</b> property specifies the type of cell represented by the template, and the <b>template</b> <i>render prop</i> accepts a function that renders the cells content. The function parameter receives an object with the cell-specific data, including the data item (<b>item</b>), row (<b>row</b>), and column (<b>col</b>) that the cell represents. </p> <p> Note that column-specific templates should be defined as children of the corresponding <b>MultiRowCell</b> component, while the others are defined as children of the <b>MultiRow</b> component. </p> <MultiRow ref={this.mrRef} multiRowGroupHeaders={true} itemsSource={this.state.itemsSource}> <MultiRowCellTemplate cellType="TopLeft" template={this.state.customTopLeft ? (context) => '№' : null}/> {this.state.customBottomLeft ? <MultiRowCellTemplate cellType="BottomLeft" template={(context) => <span>&#931;</span>}/> : null} <MultiRowCellTemplate cellType="RowHeader" template={this.state.customRowHeader ? this.rowHeaderTemplate : null}/> <MultiRowCellTemplate cellType="RowHeaderEdit" template={this.state.customRowHeaderEdit ? (context) => '...' : null}/> <MultiRowCellGroup header="Identity"> <MultiRowCell binding='id' header='ID'> <MultiRowCellTemplate cellType="ColumnHeader" template={this.state.customColumnHeader ? (context) => <i>ID</i> : null}/> </MultiRowCell> <MultiRowCell binding='date' header='Date' colspan={1} width={140}> <MultiRowCellTemplate cellType="CellEdit" template={this.state.customCellEdit ? (context) => { return <wjInput.InputDate className="cell-editor" value={context.value} valueChanged={(idt) => context.value = idt.value}> </wjInput.InputDate>; } : null}/> </MultiRowCell> </MultiRowCellGroup> <MultiRowCellGroup header="Statistics" colspan={2}> <MultiRowCell header="Country" binding="country" colspan={2}> <MultiRowCellTemplate cellType="ColumnHeader" template={this.state.customColumnHeader ? (context) => { return <i>Country</i>; } : null}/> <MultiRowCellTemplate cellType="Cell" template={this.state.customCell ? (context) => { return <React.Fragment> <img src={`resources/${context.item.country}.png`}/> {context.item.country} </React.Fragment>; } : null}/> <MultiRowCellTemplate cellType="CellEdit" template={this.state.customCellEdit ? (context) => { return <wjInput.ComboBox className="cell-editor" itemsSource={this.state.countries} isEditable={false} selectedValue={context.value} selectedIndexChanged={(cb) => context.value = cb.selectedValue}> </wjInput.ComboBox>; } : null}/> <MultiRowCellTemplate cellType="GroupHeader" template={this.state.customGroupHeader ? (context) => { return <React.Fragment> <input type="checkbox" checked={context.row.isCollapsed} onChange={e => context.row.isCollapsed = e.target.checked}/>&nbsp; {context.item.name} ({context.item.items.length} items) </React.Fragment>; } : null}/> </MultiRowCell> <MultiRowCell header="Downloads" binding="downloads" width={170} aggregate="Sum"> <MultiRowCellTemplate cellType="Cell" template={this.state.customCell ? (context) => { let style = { fontWeight: 700, color: '' }, downloads = context.item.downloads; if (this.state.highlightDownloads) { style.color = downloads > 10000 ? 'green' : 'red'; } return <span style={style}> {downloads} </span>; } : null}/> <MultiRowCellTemplate cellType="CellEdit" template={this.state.customCellEdit ? (context) => { return <wjInput.InputNumber className="cell-editor" step={1} value={context.value} valueChanged={(inpNum) => context.value = inpNum.value}> </wjInput.InputNumber>; } : null}/> <MultiRowCellTemplate cellType="Group" template={this.state.customGroup ? this.totalTemplate : null}/> </MultiRowCell> <MultiRowCell header="Sales" binding="sales" width={170} aggregate="Sum"> <MultiRowCellTemplate cellType="Cell" template={this.state.customCell ? (context) => { let style = { fontWeight: 700, color: '' }, sales = context.item.sales; if (this.state.highlightDownloads) { style.color = sales > 3000 ? 'green' : 'red'; } return <span style={style}> {sales} </span>; } : null}/> <MultiRowCellTemplate cellType="CellEdit" template={this.state.customCellEdit ? (context) => { return <wjInput.InputNumber className="cell-editor" step={1} value={context.value} valueChanged={(inpNum) => context.value = inpNum.value}> </wjInput.InputNumber>; } : null}/> <MultiRowCellTemplate cellType="Group" template={this.state.customGroup ? this.totalTemplate : null}/> </MultiRowCell> </MultiRowCellGroup> </MultiRow> <div className="checkbox-list"> <div className="checkbox-list-title">Cell level templates:</div> <div className="checkbox-cell"> <label className="checkbox"> <input type="checkbox" checked={this.state.customCell} onChange={e => this.setState({ customCell: e.target.checked })}/> Cell </label> <label className="checkbox"> <input type="checkbox" checked={this.state.customCellEdit} onChange={e => this.setState({ customCellEdit: e.target.checked })}/> CellEdit </label> </div> <div className="checkbox-cell"> <label className="checkbox"> <input type="checkbox" checked={this.state.customColumnHeader} onChange={e => this.setState({ customColumnHeader: e.target.checked })}/> ColumnHeader </label> </div> <div className="checkbox-cell"> <label className="checkbox"> <input type="checkbox" checked={this.state.customGroupHeader} onChange={e => this.setState({ customGroupHeader: e.target.checked })}/> GroupHeader </label> <label className="checkbox"> <input type="checkbox" checked={this.state.customGroup} onChange={e => this.setState({ customGroup: e.target.checked })}/> Group </label> </div> <div className="checkbox-list-title">MultiRow level templates:</div> <div className="checkbox-cell"> <label className="checkbox"> <input type="checkbox" checked={this.state.customTopLeft} onChange={e => this.setState({ customTopLeft: e.target.checked })}/> TopLeft </label> </div> <div className="checkbox-cell"> <label className="checkbox"> <input type="checkbox" checked={this.state.customRowHeader} onChange={e => this.setState({ customRowHeader: e.target.checked })}/> RowHeader </label> <label className="checkbox"> <input type="checkbox" checked={this.state.customRowHeaderEdit} onChange={e => this.setState({ customRowHeaderEdit: e.target.checked })}/> RowHeaderEdit </label> </div> </div> </div>; } } setTimeout(() => { const container = document.getElementById('app'); if (container) { const root = ReactDOM.createRoot(container); root.render(<App />); } }, 100);
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>Cell Templates</title> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <!-- SystemJS --> <script src="node_modules/systemjs/dist/system.src.js"></script> <script src="systemjs.config.js"></script> <script> System.import('./src/app'); </script> </head> <body> <div id="app"></div> </body> </html>
body { margin-bottom: 24px; } label { margin-right: 3px; } .checkbox-list { padding: 0 20px; } .checkbox-cell { display: table-cell; padding-right: 50px; } .checkbox-list .checkbox { margin: 10px 0 0 0; } .checkbox-list-title { font-size: 18px; margin: 10px 0px 2px -20px; } .wj-flexgrid { max-height: 350px; max-width: 600px; margin: 10px 0; } .cell-editor { position: absolute; left: 0px; width: 100%; border-radius: 0px; margin: -4px 0 -3px 0; }
import * as wjcCore from '@grapecity/wijmo'; // // data used to generate random items export function getData() { var countries = this.getCountries(), data = []; for (var i = 0; i < 30; i++) { data.push({ id: i, date: new Date(2015, Math.floor(i / countries.length) % 12, (Math.floor(i / countries.length) + 1) % 28), country: countries[i % countries.length], countryMapped: i % countries.length, downloads: Math.round(Math.random() * 20000), sales: Math.round(Math.random() * 10000 * 100) / 100, expenses: Math.random() * 5000, checked: i % 9 == 0 }); } return data; } // export function getCountries() { return 'US,Germany,UK,Japan,Italy,Greece'.split(','); } // export function getCv(data) { var dataCv = new wjcCore.CollectionView(data); dataCv.groupDescriptions.push(new wjcCore.PropertyGroupDescription('country')); return dataCv; }
(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', '@grapecity/wijmo': 'npm:@grapecity/wijmo/index.js', '@grapecity/wijmo.input': 'npm:@grapecity/wijmo.input/index.js', '@grapecity/wijmo.styles': 'npm:@grapecity/wijmo.styles', '@grapecity/wijmo.cultures': 'npm:@grapecity/wijmo.cultures', '@grapecity/wijmo.chart': 'npm:@grapecity/wijmo.chart/index.js', '@grapecity/wijmo.chart.analytics': 'npm:@grapecity/wijmo.chart.analytics/index.js', '@grapecity/wijmo.chart.animation': 'npm:@grapecity/wijmo.chart.animation/index.js', '@grapecity/wijmo.chart.annotation': 'npm:@grapecity/wijmo.chart.annotation/index.js', '@grapecity/wijmo.chart.finance': 'npm:@grapecity/wijmo.chart.finance/index.js', '@grapecity/wijmo.chart.finance.analytics': 'npm:@grapecity/wijmo.chart.finance.analytics/index.js', '@grapecity/wijmo.chart.hierarchical': 'npm:@grapecity/wijmo.chart.hierarchical/index.js', '@grapecity/wijmo.chart.interaction': 'npm:@grapecity/wijmo.chart.interaction/index.js', '@grapecity/wijmo.chart.radar': 'npm:@grapecity/wijmo.chart.radar/index.js', '@grapecity/wijmo.chart.render': 'npm:@grapecity/wijmo.chart.render/index.js', '@grapecity/wijmo.chart.webgl': 'npm:@grapecity/wijmo.chart.webgl/index.js', '@grapecity/wijmo.chart.map': 'npm:@grapecity/wijmo.chart.map/index.js', '@grapecity/wijmo.gauge': 'npm:@grapecity/wijmo.gauge/index.js', '@grapecity/wijmo.grid': 'npm:@grapecity/wijmo.grid/index.js', '@grapecity/wijmo.grid.detail': 'npm:@grapecity/wijmo.grid.detail/index.js', '@grapecity/wijmo.grid.filter': 'npm:@grapecity/wijmo.grid.filter/index.js', '@grapecity/wijmo.grid.search': 'npm:@grapecity/wijmo.grid.search/index.js', '@grapecity/wijmo.grid.grouppanel': 'npm:@grapecity/wijmo.grid.grouppanel/index.js', '@grapecity/wijmo.grid.multirow': 'npm:@grapecity/wijmo.grid.multirow/index.js', '@grapecity/wijmo.grid.transposed': 'npm:@grapecity/wijmo.grid.transposed/index.js', '@grapecity/wijmo.grid.transposedmultirow': 'npm:@grapecity/wijmo.grid.transposedmultirow/index.js', '@grapecity/wijmo.grid.pdf': 'npm:@grapecity/wijmo.grid.pdf/index.js', '@grapecity/wijmo.grid.sheet': 'npm:@grapecity/wijmo.grid.sheet/index.js', '@grapecity/wijmo.grid.xlsx': 'npm:@grapecity/wijmo.grid.xlsx/index.js', '@grapecity/wijmo.grid.selector': 'npm:@grapecity/wijmo.grid.selector/index.js', '@grapecity/wijmo.grid.cellmaker': 'npm:@grapecity/wijmo.grid.cellmaker/index.js', '@grapecity/wijmo.grid.immutable': 'npm:@grapecity/wijmo.grid.immutable/index.js', '@grapecity/wijmo.touch': 'npm:@grapecity/wijmo.touch/index.js', '@grapecity/wijmo.cloud': 'npm:@grapecity/wijmo.cloud/index.js', '@grapecity/wijmo.nav': 'npm:@grapecity/wijmo.nav/index.js', '@grapecity/wijmo.odata': 'npm:@grapecity/wijmo.odata/index.js', '@grapecity/wijmo.olap': 'npm:@grapecity/wijmo.olap/index.js', '@grapecity/wijmo.rest': 'npm:@grapecity/wijmo.rest/index.js', '@grapecity/wijmo.pdf': 'npm:@grapecity/wijmo.pdf/index.js', '@grapecity/wijmo.pdf.security': 'npm:@grapecity/wijmo.pdf.security/index.js', '@grapecity/wijmo.viewer': 'npm:@grapecity/wijmo.viewer/index.js', '@grapecity/wijmo.xlsx': 'npm:@grapecity/wijmo.xlsx/index.js', '@grapecity/wijmo.undo': 'npm:@grapecity/wijmo.undo/index.js', '@grapecity/wijmo.interop.grid': 'npm:@grapecity/wijmo.interop.grid/index.js', '@grapecity/wijmo.barcode': 'npm:@grapecity/wijmo.barcode/index.js', '@grapecity/wijmo.barcode.common': 'npm:@grapecity/wijmo.barcode.common/index.js', '@grapecity/wijmo.barcode.composite': 'npm:@grapecity/wijmo.barcode.composite/index.js', '@grapecity/wijmo.barcode.specialized': 'npm:@grapecity/wijmo.barcode.specialized/index.js', "@grapecity/wijmo.react.chart.analytics": "npm:@grapecity/wijmo.react.chart.analytics/index.js", "@grapecity/wijmo.react.chart.animation": "npm:@grapecity/wijmo.react.chart.animation/index.js", "@grapecity/wijmo.react.chart.annotation": "npm:@grapecity/wijmo.react.chart.annotation/index.js", "@grapecity/wijmo.react.chart.finance.analytics": "npm:@grapecity/wijmo.react.chart.finance.analytics/index.js", "@grapecity/wijmo.react.chart.finance": "npm:@grapecity/wijmo.react.chart.finance/index.js", "@grapecity/wijmo.react.chart.hierarchical": "npm:@grapecity/wijmo.react.chart.hierarchical/index.js", "@grapecity/wijmo.react.chart.interaction": "npm:@grapecity/wijmo.react.chart.interaction/index.js", "@grapecity/wijmo.react.chart.radar": "npm:@grapecity/wijmo.react.chart.radar/index.js", "@grapecity/wijmo.react.chart": "npm:@grapecity/wijmo.react.chart/index.js", "@grapecity/wijmo.react.core": "npm:@grapecity/wijmo.react.core/index.js", '@grapecity/wijmo.react.chart.map': 'npm:@grapecity/wijmo.react.chart.map/index.js', "@grapecity/wijmo.react.gauge": "npm:@grapecity/wijmo.react.gauge/index.js", "@grapecity/wijmo.react.grid.detail": "npm:@grapecity/wijmo.react.grid.detail/index.js", "@grapecity/wijmo.react.grid.filter": "npm:@grapecity/wijmo.react.grid.filter/index.js", "@grapecity/wijmo.react.grid.grouppanel": "npm:@grapecity/wijmo.react.grid.grouppanel/index.js", '@grapecity/wijmo.react.grid.search': 'npm:@grapecity/wijmo.react.grid.search/index.js', "@grapecity/wijmo.react.grid.multirow": "npm:@grapecity/wijmo.react.grid.multirow/index.js", "@grapecity/wijmo.react.grid.sheet": "npm:@grapecity/wijmo.react.grid.sheet/index.js", '@grapecity/wijmo.react.grid.transposed': 'npm:@grapecity/wijmo.react.grid.transposed/index.js', '@grapecity/wijmo.react.grid.transposedmultirow': 'npm:@grapecity/wijmo.react.grid.transposedmultirow/index.js', '@grapecity/wijmo.react.grid.immutable': 'npm:@grapecity/wijmo.react.grid.immutable/index.js', "@grapecity/wijmo.react.grid": "npm:@grapecity/wijmo.react.grid/index.js", "@grapecity/wijmo.react.input": "npm:@grapecity/wijmo.react.input/index.js", "@grapecity/wijmo.react.olap": "npm:@grapecity/wijmo.react.olap/index.js", "@grapecity/wijmo.react.viewer": "npm:@grapecity/wijmo.react.viewer/index.js", "@grapecity/wijmo.react.nav": "npm:@grapecity/wijmo.react.nav/index.js", "@grapecity/wijmo.react.base": "npm:@grapecity/wijmo.react.base/index.js", '@grapecity/wijmo.react.barcode.common': 'npm:@grapecity/wijmo.react.barcode.common/index.js', '@grapecity/wijmo.react.barcode.composite': 'npm:@grapecity/wijmo.react.barcode.composite/index.js', '@grapecity/wijmo.react.barcode.specialized': 'npm:@grapecity/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' }, // packages tells the System loader how to load when no filename and/or no extension packages: { src: { defaultExtension: 'jsx' }, "node_modules": { defaultExtension: 'js' }, } }); })(this);