Example: Sortable generated columns

This example shows how to use the DataTable's recordType attribute to create columns whose values are calculated from other columns. It was contributed by community member Todd Smith.

In the table below, the "Market Value" and "Gain or Loss" columns are created from data in the other columns by configuring them as attributes with getters in the table's recordType configuration.

recordType sets the Model

DataTable's recordType attribute is used to specify the class used to store the record instances in your table. If you don't configure your DataTable's recordType, a Y.Model subclass will be created for you based on the keys in the data that you fill the table with.

As well as accepting Model subclasses, recordType can be passed an object that corresponds to a class's ATTRS collection. From this, DataTable will create the Model subclass accordingly. This is useful if you want to provide default values, or validate or transform incoming values (such as turning numeric strings into numbers).

var table = new Y.DataTable({
    columns: ['cost', 'price', 'profit'],
    recordType: {
        cost: {
            value: 0.0001, // default value
            validator: function (val) {
                return Y.Lang.isNumber(val) && val > 0;
            }
        },
        price: {
            setter: function (val) {
                val = +val; // coerce numeric strings to numbers

                return isFinite(val) ? val : Y.Attribute.INVALID_VALUE;
            }
        }
    },
    ...
});

Use attribute getters to populate custom columns

Another thing you can configure with attributes is a getter, which is responsible for returning a value to instance.get('yourAttribute') calls. Since DataTable uses this method to get cell contents for columns, you can use getters to create columns whose content is calculated on the fly.

var table = new Y.DataTable({
    columns: ['cost', 'price', 'profit'],
    recordType: {
        cost: {},
        price: {},
        profit: {
            getter: function () {
                return this.get('price') - this.get('cost');
            },
            readOnly: true
        }
    },
    ...
});

The data populating the table need only include cost and price. The profit column will be populated automatically based on the other two.

Sorting for free

It is possible to create calculated columns using column formatters as well, but if you need that column to be sortable, you need to associate that column with a single key, which may be insufficient, or configure the column with a sortFn.

Using an attribute with a getter, you are creating a new field with a key that corresponds to the full values you want to sort by using the default sort mechanism. And you can even add formatters on top of the raw, sortable, calculated value.

Full Code Listing

JavaScript

YUI().use('datatable', 'datatype-number-format', function (Y) {

    var portfolio = [
        { stock_id:3, ticker:'XYZEE', company:'XYZ Corporation', qty:300, cost:4500, price:15.83 },
        { stock_id:11, ticker:'FUBAR', company:'FooBar Computers, Inc.', qty:100, cost:2187, price:28.90 },
        { stock_id:17, ticker:'GIFT', company:"Spinoff Technology", qty:400, cost:0, price:4.11 },
        { stock_id:19, ticker:'DOLLARS', company:"Consultants 'R Us", qty:1750, cost:6099.13, price:3.97 },
        { stock_id:5, ticker:'SAFET', company:'Stability Partners LLP', qty:25, cost:7283.41, price:58.74 }
    ];

    function formatCurrency(o) {
        return Y.DataType.Number.format(o.value, {
            prefix:"$ ",
            thousandsSeparator: ",",
            decimalSeparator: ".",
            decimalPlaces: 2
        });
    }
    
    function formatGainLoss(o) {
        o.className += (o.value < 0) ? 'loss' : 'gain';
        
        return o.value ?
            Y.DataType.Number.format(o.value, {
                suffix: ' %',
                decimalSeparator: ".",
                decimalPlaces: 2
            }) :
            'n/a';
    }
    
    var dt = new Y.DataTable({
        data: portfolio,
        columns: [
            { key: 'ticker',  label: 'Ticker' },
            { key: 'company', label: 'Company Name' },        
            { key: 'qty',     label: 'Share Qty', className: 'numeric' },
            {
              key      : 'cost',
              label    : 'Purchase Cost',
              className: 'numeric',
              formatter: formatCurrency
            },
            {
              key      : 'marketvalue',
              label    : 'Market Value',
              className: 'numeric',
              formatter: formatCurrency
            },
            {
              key      : 'gainloss',
              label    : 'Gain or Loss',
              className: 'percentage',
              formatter: formatGainLoss
            }
        ],
        recordType: {
            ticker: {},
            company: {},
            qty: {},
            cost: {},
            marketvalue: {
                getter: function () {
                    return +((this.get('price') * this.get('qty')).toFixed(2));
                }
            },
            gainloss: {
                getter: function () {
                    var cost = this.get('cost'),
                        amt  = (this.get('qty') * this.get('price')) -
                                    this.get('cost');
                    
                    return cost ? ((100 * amt) / cost) : 0;
                }
            }
        },
        sortable: ['ticket', 'company', 'cost', 'marketvalue', 'gainloss'],
        sortBy: { gainloss: 'desc' }
    });
    
    dt.render("#dtable");    
});

CSS

.numeric { text-align: right; }
.percentage { text-align: center; }
.loss { color: red; }
.gain { color: green; }

HTML

Note: be sure to add the yui3-skin-sam classname to the page's <body> element or to a parent element of the widget in order to apply the default CSS skin. See Understanding Skinning.

<body class="yui3-skin-sam"> <!-- You need this skin class -->
<div id="dtable"></div>