Cell Templates (React)

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

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 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, { useState } from 'react'; import useEvent from 'react-use-event-hook'; import * as wjGrid from '@mescius/wijmo.react.grid'; import * as wjInput from '@mescius/wijmo.react.input'; import * as wjcCore from '@mescius/wijmo'; import * as wjcGrid from '@mescius/wijmo.grid'; import './app.css'; import { itemsSource, countries } from './data'; function App() { const [customTopLeft, setCustomTopLeft] = useState(true); const [customBottomLeft, setCustomBottomLeft] = useState(true); const [customRowHeader, setCustomRowHeader] = useState(true); const [customRowHeaderEdit, setCustomRowHeaderEdit] = useState(true); const [customCell, setCustomCell] = useState(true); const [customCellEdit, setCustomCellEdit] = useState(true); const [customGroupHeader, setCustomGroupHeader] = useState(true); const [customColumnHeader, setCustomColumnHeader] = useState(true); const [customGroup, setCustomGroup] = useState(true); const [customColumnFooter, setCustomColumnFooter] = useState(true); const [highlightDownloads, setHighlightDownloads] = useState(true); // Wijmo event handlers const initialized = useEvent((grid) => { grid.columnFooters.rows.push(new wjcGrid.GroupRow()); }); // The template used in more than one cell type is defined as a method. // The rest of the template functions are inlined in FlexGridCellTemplate // definitions. const totalTemplate = (context) => { return <React.Fragment> Sum: {wjcCore.Globalize.formatNumber(context.value, 'N0')} </React.Fragment>; }; // Another template function. const rowHeaderTemplate = (context) => context.row.index + 1; // return (<div className="container-fluid"> <p> Use a <b>FlexGridCellTemplate</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>FlexGridColumn</b> component, while the others are defined as children of the <b>FlexGrid</b> component. </p> <wjGrid.FlexGrid itemsSource={itemsSource} allowSorting={false} autoSizeMode="Both" allowResizing="Both" deferResizing={true} initialized={initialized}> { // To conditionally disable a template, we can just assign // the 'template' property with a null value. } <wjGrid.FlexGridCellTemplate cellType="TopLeft" template={customTopLeft ? (context) => '№' : null}/> { // An alternative way to conditionally disable a template is // to exclude the FlexGridCellTemplate element from the rendering // element tree. } {customBottomLeft ? <wjGrid.FlexGridCellTemplate cellType="BottomLeft" template={(context) => <span>&#931;</span>}/> : null} { // The template function definition can be inlined like in the templates // above, or defined as a component method like shown in this template. } <wjGrid.FlexGridCellTemplate cellType="RowHeader" template={customRowHeader ? rowHeaderTemplate : null}/> <wjGrid.FlexGridCellTemplate cellType="RowHeaderEdit" template={customRowHeaderEdit ? (context) => '...' : null}/> <wjGrid.FlexGridColumn header="Country" binding="country" width="*"> <wjGrid.FlexGridCellTemplate cellType="Cell" template={customCell ? (context) => { return <React.Fragment> <img src={`resources/${context.item.country}.png`}/> {context.item.country} </React.Fragment>; } : null}/> <wjGrid.FlexGridCellTemplate cellType="CellEdit" template={customCellEdit ? (context) => { return <wjInput.ComboBox className="cell-editor" itemsSource={countries} isEditable={false} selectedValue={context.value} selectedIndexChanged={(cb) => context.value = cb.selectedValue}> </wjInput.ComboBox>; } : null}/> <wjGrid.FlexGridCellTemplate cellType="GroupHeader" template={customGroupHeader ? (context) => { return <React.Fragment> <input type="checkbox" checked={context.row.isCollapsed} onChange={e => context.row.isCollapsed = e.target.checked}/> {context.item.name} ({context.item.items.length} items) </React.Fragment>; } : null}/> </wjGrid.FlexGridColumn> <wjGrid.FlexGridColumn header="Downloads" binding="downloads" width={170} aggregate="Sum"> <wjGrid.FlexGridCellTemplate cellType="ColumnHeader" template={customColumnHeader ? (context) => { return <React.Fragment> <input type="checkbox" checked={highlightDownloads} onChange={(e) => { setHighlightDownloads(e.target.checked); }}/> Downloads </React.Fragment>; } : null}/> <wjGrid.FlexGridCellTemplate cellType="Cell" template={customCell ? (context) => { let style = { fontWeight: 700, color: '' }, downloads = context.item.downloads; if (highlightDownloads) { style.color = downloads > 10000 ? 'green' : 'red'; } return <span style={style}> {downloads} </span>; } : null}/> <wjGrid.FlexGridCellTemplate cellType="CellEdit" template={customCellEdit ? (context) => { return <wjInput.InputNumber className="cell-editor" step={1} value={context.value} valueChanged={(inpNum) => context.value = inpNum.value}> </wjInput.InputNumber>; } : null}/> { // The same totalTemplate render function is used in the two // templates below. Because of this, it's better to define it // as the component method, and assign the 'template' property // with the reference to it. } <wjGrid.FlexGridCellTemplate cellType="Group" template={customGroup ? totalTemplate : null}/> <wjGrid.FlexGridCellTemplate cellType="ColumnFooter" template={customColumnFooter ? totalTemplate : null}/> </wjGrid.FlexGridColumn> </wjGrid.FlexGrid> <div className="checkbox-list"> <div className="checkbox-list-title">Column level templates:</div> <div className="checkbox-cell"> <label className="checkbox"> <input type="checkbox" checked={customCell} onChange={e => setCustomCell(e.target.checked)}/> Cell </label> <label className="checkbox"> <input type="checkbox" checked={customCellEdit} onChange={e => setCustomCellEdit(e.target.checked)}/> CellEdit </label> </div> <div className="checkbox-cell"> <label className="checkbox"> <input type="checkbox" checked={customColumnHeader} onChange={e => setCustomColumnHeader(e.target.checked)}/> ColumnHeader </label> <label className="checkbox"> <input type="checkbox" checked={customColumnFooter} onChange={e => setCustomColumnFooter(e.target.checked)}/> ColumnFooter </label> </div> <div className="checkbox-cell"> <label className="checkbox"> <input type="checkbox" checked={customGroupHeader} onChange={e => setCustomGroupHeader(e.target.checked)}/> GroupHeader </label> <label className="checkbox"> <input type="checkbox" checked={customGroup} onChange={e => setCustomGroup(e.target.checked)}/> Group </label> </div> <div className="checkbox-list-title">Grid level templates:</div> <div className="checkbox-cell"> <label className="checkbox"> <input type="checkbox" checked={customTopLeft} onChange={e => setCustomTopLeft(e.target.checked)}/> TopLeft </label> <label className="checkbox"> <input type="checkbox" checked={customBottomLeft} onChange={e => setCustomBottomLeft(e.target.checked)}/> BottomLeft </label> </div> <div className="checkbox-cell"> <label className="checkbox"> <input type="checkbox" checked={customRowHeader} onChange={e => setCustomRowHeader(e.target.checked)}/> RowHeader </label> <label className="checkbox"> <input type="checkbox" checked={customRowHeaderEdit} onChange={e => setCustomRowHeaderEdit(e.target.checked)}/> RowHeaderEdit </label> </div> </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>Cell Templates</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>
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 '@mescius/wijmo'; function getData() { var countries = 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.random() * 10000, expenses: Math.random() * 5000, checked: i % 9 == 0 }); } return data; } function getCountries() { return ['US', 'Germany', 'UK', 'Japan', 'Italy', 'Greece']; } function getCv(data) { var dataCv = new wjcCore.CollectionView(data); dataCv.sortDescriptions.push(new wjcCore.SortDescription('date', true)); dataCv.groupDescriptions.push(new wjcCore.PropertyGroupDescription('country')); return dataCv; } export const itemsSource = getCv(getData()); export const countries = getCountries();
(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);