Building an Email Client with AG Grid

  |   Javascript

What is AG Grid?

AG Grid is a client-side JavaScript grid that can be integrated natively with any major framework.

This is one of many blog posts designed to prove why we call it "the best grid in the world".

AG Grid Mail

This example leverages some AG Grid features to produce an interface similar to the one you can see in Gmail. You can find a running example and its code below.

Open Source Code on Github

You can see the finished app in action hosted here.


Summary of features used

What follows are the list of features that have been used from AG Grid and how they have been used for this particular example.  This blog post assumes that you know the basics to work with AG Grid, but if you don't you can find the following links helpful:

Documentation / Getting started (JS / React / Angular / Vue)

Cell Renderers

MIT/Free
Documentation: JS / React / Angular / Vue
More information on cell rendering: Cell Renderers / Cell Content
More information on registering components: Registering Components
More Cell Renderer Examples (available only to customers and trial users)

Cell Renderers are used in this example to fully customise the body of the row with a purple label component and to provide the hover effect showing the action icons.

Creating a Cell Renderer is very simple, you can see this in src/index.js. Below you can find the main highlights for the Cell Renderers:

// src/index.js

// applying a cell renderer only on ungrouped rows using a Cell Renderer Selector, you can read more about this in the above link 'Cell Renderers'
// referencing the Cell Renderer Component directly by its class
const columnDefs = [
    {
        field: 'title',
        cellRendererSelector: params => {
            return params.node.group ? undefined : { component: CustomCellRenderer }
        },
    },
    // ...
];

   
// src/customCellRenderer.js 
class CustomCellRenderer {

    init(params) {
        this.params = params;
        this.eGui = document.createElement('div');
        // [...]

    }

    getGui() {
        return this.eGui;
    }

    // [...]

}

export default CustomCellRenderer;

Events/API

MIT/Free
Documentation: Grid Events, Grid API
More Grid Events Examples (available only to customers and trial users)

The application is listening to several Grid Events, for example to render the action icons as you hover over the rows, or to render the email in a popup when clicking on a a row.

The Grid Events are registered on the gridOptions as shown here:

// src/index.js

const gridOptions = {
    // [...]
    onCellMouseOver: onCellMouseOver,
    onCellMouseOut: onCellMouseOut,
    onRowClicked: onRowClicked,
    onGridReady: onGridReady
};

To look at an example, onCellMouseOver is fired when moving over the cell with the mouse, which will trigger the action icons to be rendered:

// src/index.js
const onCellMouseOver = params => {
    const instances = params.api.getCellRendererInstances({ rowNodes: [params.node], columns: [params.column] })
    if (instances.length > 0) {
        const instance = instances[0];
        instance.showRenderer();
    }
}

showRenderer is a method that exists on CustomCellRenderer objects:

// src/customCellRenderer.js
class CustomCellRenderer {
    // [...]
    
    showRenderer() {
        this.eActionContainer.style.display = '';
        this.eDateLabel.style.display = 'none';
    }    
};

Data updating

MIT/Free
Documentation: Updating Data
More Updating Data Examples (available only to customers and trial users)

In several places in the example we update data, for instance, the data is updated when the delete icon is clicked so that the row is removed from the grid.

We've implemented getRowId which the grid uses the Row ID work out which rows to add/update/delete rather than changing the entire row data. This allows the grid to keep state such as Row Selection:

// gridOptions
const gridOptions {
    // [...]
    getRowId: ({ data }) => (data.id)
};

An example of how we are using the API to update the data in the grid, when a delete icon on a row is clicked:

// src/customCellRenderer.js
onClicked(ev) {
	const action = ev.currentTarget.dataset.action;

    switch (action) {
        case 'delete':
            this.params.api.applyTransaction({remove: [this.params.data]})
            break;
    }
}

Pagination

MIT/Free
Documentation: Pagination

Pagination can be enabled in the gridOptions:

// src/index.js
const gridOptions = {
    // [...]
    pagination: true,
    paginationPageSize: 10,
};

Quick Filter

MIT/Free
Documentation: Quick Filter

Quick Filter is enabled in this example to allow filtering on all the data based on data input.

The Quick Filter can be applied by calling the Grid's API setQuickFilter:

// src/index.js
const onGridReady = () => {
    // [...]
    const textField = new MDCTextField(document.querySelector('.mdc-text-field'));
    textField.listen('keyup', (ev) => {
        gridOptions.api.setQuickFilter(textField.value);
    });
};

Styling

MIT/Free
Documentation: Cell Styling, Row Styling, CSS Variables
More Styling examples (available only to customers and trial users)

Row Styling is used in this example to distinguish between which emails are read or unread, and if they are unread then the text is made bold and the background colour is changed.

There are several ways to style the grid, in this case we are using getRowStyle and Global CSS Variables:

// src/index.js
const gridOptions = {
    // [...]
    getRowStyle: ({ data, node }) => {
    if (!node.group) {
        return data.read ?
            { fontWeight: 'normal', backgroundColor: 'rgba(242, 245, 245, 0.8)' } :
            { fontWeight: 'bold', backgroundColor: 'rgb(255, 255, 255)' }
    }
};
}
/* styles.css */
.ag-theme-material {
    --ag-font-size: 14px;
    --ag-font-family: Roboto, RobotoDraft, Helvetica, Arial, sans-serif;
    --ag-selected-row-background-color: rgb(194, 219, 255, 0.4);
    --ag-material-accent-color: black;
}

Row Selection

MIT/Free
Documentation: Row Selection, Range Selection
More Row Selection Examples (available only to customers and trial users)

As we are enabling checkboxes in this example, we also need to be able to select multiple emails. To achieve this, we enable Row Selection.

Multiple Row Selection

Row Selection can be enabled in the gridOptions as follows:

// index.js
const gridOptions = {
    // [...]
    rowSelection: 'multiple',
};

Row Grouping

Enterprise license users only
Documentation: Row Grouping
More Examples (available only to customers and trial users)

Row Grouping allows us to take all the data and group by a common property, in this example we have the button 'Group by Sender' which groups all the emails based on the sender.

You may notice that the group row spans the entire row, this is because of Full Row Grouping, which can be enabled as follows:

// src/index.js
const gridOptions = {
    // [...]
    groupDisplayType: 'groupRows'
};

To dynamically group/ungroup, you can call columnApi.applyColumnState:

// src/index.js
const onGroupButtonClicked = event => {
    // [...]
    const rowGroup = gridOptions.columnApi.getColumnState().find(col => (col.colId === action)).rowGroup;
    
    gridOptions.columnApi.applyColumnState({ state: [{ colId: action, rowGroup: !rowGroup }], defaultState: { rowGroup: false } })
};

What's Next

If you want to try AG Grid, you will also find how in our getting started guides Getting started (JS / React / Angular / Vue)

Happy coding!

Read more posts about...