The cellTemplate Property in FlexGrid 2019 v3

The FlexGrid control does a great job in displaying and editing tabular data out of the box, allowing you to customize the appearance of cells using properties such as format, dataMap, and cssClass.

But there are cases when that is not enough. For example, you may need to render an image or a sparkline next to the data cell text. Or you want to use conditional formatting, where the cell style reflects its value.

The traditional way to add custom cell content to grid cells was to use the formatItem event to customize the HTML content of the cell. This is a powerful approach that gives you complete flexibility to add anything to any cell (data, headers, editors, etc.). For example, the code below uses the formatItem event to add conditional formatting to a cell.

Values above and below a given threshold are rendered with a "change-up" or "change-down" class selector:

columns: [  
    { binding: 'id', header: 'ID', isReadOnly: true, width: '.5*' },  
    { binding: 'date', header: 'Date', width: '*' },  
    { binding: 'product', header: 'Product', width: '*' },  
    { binding: 'value', header: 'Value', format: 'c0', width: '*' },  
        binding: 'change',  
        header: 'formatItem',  
        align: 'center',  
        format: 'p0',  
        width: '*'  
formatItem: (s, e) => {  
    if (e.panel == s.cells && s.columns[e.col].header == 'formatItem') {  
        let value = s.getCellData(e.row, e.col, false);  
        let text =  s.getCellData(e.row, e.col, true);  
        e.cell.innerHTML = '<span class="change-' +  
            (value > 0 ? 'up' : 'down') + '">' +  
            text + '</span>'  
}, …  

The formatItem function is called for every cell being rendered. It starts by checking whether the cell is a data cell and not a header cell) and whether it belongs to the column that has "formatItem" in its header. If so, it uses the grid's getCellData method to get the cell's raw value and formatted text and builds the HTML from that.

This works fine, but it takes about 10 lines of code to accomplish a pretty simple task.

To make this simpler, the latest version of the FlexGrid adds a cellTemplate property to the Column class.

The cellTemplate can be set to a function that takes an ICellTemplateContext parameter and returns a string.

The ICellTemplateContext parameter contains the following properties:

Type Description
row Row Row that contains the cell.
col Column Column that contains the cell.
item any Data item that the row is bound to.
value any Raw value of the property the cell is bound to.
text string Formatted/mapped value of the property the cell is bound to.

For example, you could render the "change" cell like this:

    binding: 'change',  
    header: 'cellTemplate fn',  
    align: 'center',  
    format: 'p0',  
    width: '*',  
    cellTemplate: (ctx: ICellTemplateContext) => {  
        return '<span class="change-' +  
            (ctx.value > 0 ? 'up' : 'down') + '">' + ctx.text + '</span>';  

This is more concise and organized than using the formatItem event. The code and logic used to render cells in the change column are contained in the column definition, not in an "if" statement within an event handler elsewhere in the code.

But we can simplify things even further. The cellTemplate property can also be set to a template string using the template literal feature. In this case, our custom rendering becomes just this:

    binding: 'change',  
    header: 'cellTemplate str',  
    align: 'center',  
    format: 'p0',  
    width: '*',  
        '<span class="change-${value > 0 ? "up" : "down"}">${text}</span>'  

And we just went from 10 lines of code to three and down to one!

Note how the template string is a regular string, not an actual template literal with back-quotes. The FlexGrid takes care of inserting the proper cell context, creating the template, and evaluating it when necessary. Because the template string is a regular string, this feature works even in Internet Explorer (which does not support actual template literals).

The syntax is the one used with JavaScript template literals. The evaluation context is provided by the ICellTemplateContext, so you can use the cell's "value," "text," etc. as shown in the example.

Now all that is left is adding some CSS to define the appearance of the "change-up" and "change-down" cells:

change-up {  
    color: darkgreen;  
    .change-up:before {  
        content: '\25b2 '; /* up triangle */  

.change-down {  
    color: darkred;  
    .change-down:before {  
        content: '\25bc '; /* down triangle */  

And the result is this:

The cellTemplate property is not as powerful as the formatItem event or the framework-specific cell templates included with the Wijmo interop modules for Angular, React, and Vue.

But if all you need is something simple like adding images or some conditional formatting, like in the example above, it is a very good option, simple and standard.

Bernardo de Castilho

comments powered by Disqus