This is part of the Ext JS to React blog series. You can review the code from this article on the Ext JS to React Git repo.
In our previous articles on React grids, we’ve built a standard grid that allows for sorting and put a pre-built grid library to use to demonstrate row / checkbox selection. These are some of the basic-level features a user would expect from a grid. In this post, we will look at some popular features of the Ext JS grid and explore how to provide the same functionality in our React applications. In this article, as with the Selection Model article, we’ll be leaning on ag-Grid to supply the grid component.
Note: While not a requirement for React, this article’s code examples assume you’re starting with a starter app generated by create-react-app.
ag-Grid Setup
First, we’ll install ag-Grid including the files we’ll use to theme the grid:
npm install --save ag-grid npm install --save ag-grid-react
If you’re using React 16+ you may also need to install react-dom-factories:
npm install --save react-dom-factories
We will be reusing the same data file that we introduced in the previous article, Basic Grid.
Converting Grid Cell Data
When data is loaded from a remote server, it’s not always what we want to display to our users. Sometimes we want to add something additional to the output. For example, we might want to display a country flag next to someone’s name or render an email address as a clickable link. To do that in Ext JS, we would conventionally use column renderers to return the desired presentation. With ag-Grid, the cellRenderer
configuration operates similar to Ext JS’s renderer
config option:
import React, { Component } from 'react'; import { AgGridReact } from 'ag-grid-react'; import getData from './data'; import 'ag-grid/dist/styles/ag-grid.css'; import 'ag-grid/dist/styles/ag-theme-material.css'; const { data } = getData(); class App extends Component { columns = [ { headerName: 'Name', field: 'name' }, { headerName: 'Company', field: 'company' }, { headerName: 'Email', field: 'email', cellRenderer: params => { return `<a href="mailto:${params.value}">${params.value}</a>`; } } ] render () { return ( <div className="ag-theme-material"> <AgGridReact containerStyle={{height: '400px'}} columnDefs={this.columns} rowData={data} /> </div> ); } } export default App;
Just like with Ext JS, the cellRenderer
configuration goes on the column. The arguments, however, are different. In Ext JS, the first argument is the value of the dataIndex
for that column and the third argument is the record that correlates to that row. With ag-Grid, the first argument is an object with what you most likely will need such as the value
. If you need to use other data from the record, you can use params.data
which will give you the full object for that row.
You can review the sample code on the Git repo.
Column Locking
There are times when a grid has lots of data points to present to the user. Columns may overflow the view resulting in horizontal scrolling. However, when a user scrolls they may feel lost. Scrolling will visually disconnect the scrolled-in data from any anchoring column used for reference on the record. Therefore, locking certain columns like a “name” column allows the user to scroll while still seeing the name column so the remaining data feels connected. With Ext JS, a column is locked by setting the column’s locked
config to true
. With ag-Grid, you use the column’s pinned
config:
import React, { Component } from 'react'; import { AgGridReact } from 'ag-grid-react'; import getData from './data'; import 'ag-grid/dist/styles/ag-grid.css'; import 'ag-grid/dist/styles/ag-theme-material.css'; const { data } = getData(); class App extends Component { columns = [ { headerName: 'Name', field: 'name', pinned: 'left' }, { headerName: 'Company', field: 'company' }, { headerName: 'Email', field: 'email' } ] render () { return ( <div className="ag-theme-material"> <AgGridReact containerStyle={{height: '400px', width: '400px'}} columnDefs={this.columns} rowData={data} /> </div> ); } } export default App;
As you can see, the name column sets the pinned
config. So the name column will will be pinned (or locked) to the left. A keen eye would notice the pinned
config isn’t set to true
like with Ext JS. This is because ag-Grid supports something that Ext JS doesn’t: the ability to pin columns to the right as well as the left. As you would expect, if you set pinned
to “right
“, the column would then be locked to the right side of the grid. You can even pin columns to the left and right sides simultaneously.
You can review the sample code on the Git repo.
Cell Editing
An Ext JS grid wouldn’t be a grid without being able to edit the data right from within the grid! Ext JS made this easy by configuring a plugin on the grid and then setting the editor config (form field) on the column you want to be editable. ag-Grid actually makes this one step easier to enable by simply using the editable config on the target column:
import React, { Component } from 'react'; import { AgGridReact } from 'ag-grid-react'; import getData from './data'; import 'ag-grid/dist/styles/ag-grid.css'; import 'ag-grid/dist/styles/ag-theme-material.css'; const { data } = getData(); class App extends Component { columns = [ { headerName: 'Name', field: 'name', editable: true }, { headerName: 'Company', field: 'company' }, { headerName: 'Email', field: 'email' } ] render () { return ( <div className="ag-theme-material"> <AgGridReact containerStyle={{height: '400px'}} columnDefs={this.columns} rowData={data} /> </div> ); } } export default App;
You can review the sample code on the Git repo.
Custom Cell Editor
After double-clicking on a cell in the “name” column, the text editor will be shown within the cell as you would expect and it only took a single config. The default editor is a text field. While ag-Grid has some other built in editors like a select/combobox editor, you can also easily provide your own editor. First, let’s look at the grid that will reference a custom editor:
import React, { Component } from 'react'; import { AgGridReact } from 'ag-grid-react'; import getData from './data'; import CustomEditor from './CustomEditor'; import 'ag-grid/dist/styles/ag-grid.css'; import 'ag-grid/dist/styles/ag-theme-material.css'; const { data } = getData(); class App extends Component { columns = [ { headerName: 'Name', field: 'name', editable: true }, { headerName: 'Company', field: 'company' }, { headerName: 'Email', field: 'email', editable: true, cellEditorFramework: CustomEditor } ] render () { return ( <div className="ag-theme-material"> <AgGridReact containerStyle={{height: '400px'}} columnDefs={this.columns} rowData={data} /> </div> ); } } export default App;
Here, we import the CustomEditor
that we will see in a moment. We’ll pass that class to the “email” column using the cellEditorFramework
config. Also, notice that the “email” column did still get the required editable
config. To build the custom field, we simply have to create a React component. ag-Grid will pass a few things via props
that we can use. Here is a simple CustomEditor that renders an email-type input:
CustomEditor Class
import React, { Component } from 'react'; class CustomEditor extends Component { state = {} componentDidMount () { this.refs.input.addEventListener('blur', this.onBlur); this.focus(); } componentDidUpdate () { this.focus(); } componentWillUnmount () { this.refs.input.removeEventListener('blur', this.onBlur); } focus () { // focus the input slightly after dbl-click setTimeout(() => { const { refs: { input } } = this; input.focus(); }); } get value () { let { props, state: { value } } = this; if (value == null) { value = props.value; } return value; } getValue () { return this.value; } onBlur = () => { this.props.stopEditing(); } onChange = (event) => { this.setState({ value: event.target.value }); } render () { return <input ref="input" type="email" value={this.value} onChange={this.onChange} style={{width: "100%"}} />; } } export default CustomEditor;
Most of this is normal React. The couple of things ag-Grid provides that we see used here is the value
and stopEditing
items on the props
. The getValue
method is an API that ag-Grid will execute as well.
You can review the sample code on the Git repo.
Infinite Scrolling
If you want to render thousands of records, rendering them all to the DOM will likely not perform very well (especially on lower-powered devices). Ext JS uses a buffered renderer plugin that is automatically enabled if the grid has a static size (either from the height
/ width
configs or from a parent layout). The buffered renderer plugin only renders the number of rows that are visible (plus some for a buffer to support smooth scrolling). This enables the DOM to render only a slice of the data to preserve performance. ag-Grid does not enable buffering by default, but does support it with very little code:
import React, { Component } from 'react'; import { AgGridReact } from 'ag-grid-react'; import { dataAsync } from './data'; import 'ag-grid/dist/styles/ag-grid.css'; import 'ag-grid/dist/styles/ag-theme-material.css'; class App extends Component { columns = [ { headerName: 'Name', field: 'name' }, { headerName: 'Company', field: 'company' }, { headerName: 'Email', field: 'email' } ] datasource = { async getRows ({ endRow, startRow, successCallback }) { // retrieve data const data = await dataAsync({ startRow, num: endRow - startRow }); const lastRow = data.total <= endRow ? data.total : -1; successCallback(data.data, lastRow); } } render () { return ( <div className="ag-theme-material"> <AgGridReact containerStyle={{height: '400px'}} columnDefs={this.columns} datasource={this.datasource} rowModelType="infinite" /> </div> ); } } export default App;
All that’s needed is to set the rowModelType
prop to “infinite
” and provide the datasource
. The datasource
has a getRows
method that enables data retrieval (i.e. from a fetch request) to furnish data for the page the grid requested as the grid is scrolled. If the data is already loaded, you can execute the successCallback
function with the slice of data requested.
You can review the sample code on the Git repo.
Conclusion
Grids don’t have to be simple tables of data. They can be a rich, interactive visualization of data as well! Whether it’s parsing data into something more visual or interactive, locking columns, or allowing editing from within the grid and infinite scrolling, using ag-Grid in your React application makes the transition from Ext JS quite easy and in some cases is better than the functionality Ext JS provides. In our next article we’ll take a look at the close relative of the grid, the tree.
Mitchell Simoens
Related Posts
-
Ext JS to React: Migration to Open Source
Worried about Migrating from Ext JS? Modus has the Answers Idera’s acquisition of Sencha has…
-
Ext JS to React: FAQ
This is part of the Ext JS to React blog series. React is Facebook's breakout…