Ext JS to React: Features and Plugins

   JavaScript
Ext JS to React: Features and Plugins

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; 


Ext JS to React: Features and Plugins, Controlling Cell Data



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; 


Ext JS to React: Features and Plugins, Locking Columns



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; 


Ext JS to React: Features and Plugins, Cell Editing



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; 


Ext JS to React: Features and Plugins, Custom Cell Editor



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.


Like What You See?

Got any questions?


>