Jump to Table of Contents

DataTable

The DataTable widget is responsible for rendering columnar data into a highly customizable and fully accessible HTML table. The core functionality of DataTable is to visualize structured data as a table. A variety of class extensions can then be used to add features to the table such as sorting and scrolling.

Getting Started

To include the source files for DataTable and its dependencies, first load the YUI seed file if you haven't already loaded it.

<script src="http://yui.yahooapis.com/3.18.1/build/yui/yui-min.js"></script>

Next, create a new YUI instance for your application and populate it with the modules you need by specifying them as arguments to the YUI().use() method. YUI will automatically load any dependencies required by the modules you specify.

<script>
// Create a new YUI instance and populate it with the required modules.
YUI().use('datatable', function (Y) {
    // DataTable is available and ready for use. Add implementation
    // code here.
});
</script>

For more information on creating YUI instances and on the use() method, see the documentation for the YUI Global Object.

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.

Upgrading from version 3.4.1 or older?

DataTable was refactored for 3.5.0. Some APIs were changed in backward incompatible ways.

Read the 3.5.0 Migration Guide for tips to avoid unpleasant surprises. If you still run into issues, please file a ticket.

If you are unable to upgrade due to unresolvable issues, you can use the datatable-deprecated module suite, which is equivalent to the 3.4.1 implementation. But be aware that these modules will be removed in a future version of YUI.

DataTable Basics

A basic DataTable is made of columns and rows. Define the columns you want to display in your DataTable with the columns attribute. Rows are created for you based on the data you provide to the data attribute.

Under the hood, the DataTable class uses a ModelList instance to manage the row data properties. Read the Table Data Configuration section below for details about how to load, configure, and work with the table data.

// Columns must match data object property names
var data = [
    { id: "ga-3475", name: "gadget",   price: "$6.99", cost: "$5.99" },
    { id: "sp-9980", name: "sprocket", price: "$3.75", cost: "$3.25" },
    { id: "wi-0650", name: "widget",   price: "$4.25", cost: "$3.75" }
];

var table = new Y.DataTable({
    columns: ["id", "name", "price"],
    data: data,

    // Optionally configure your table with a caption
    caption: "My first DataTable!",

    // and/or a summary (table attribute)
    summary: "Example DataTable showing basic instantiation configuration"
});

table.render("#example");

This code produces this table:

Column Configuration

The columns attribute takes an array of field names that correspond to property names in the data objects. These field names are called "keys". As long as these keys exist in your data, DataTable will display the values in the table. By default, the key is also used as the label of the column header.

Use objects instead of key strings to customize how the cells in a column display.

// Columns must match data object property names
var data = [
    { id: "ga-3475", name: "gadget",   price: "$6.99", cost: "$5.99" },
    { id: "sp-9980", name: "sprocket", price: "$3.75", cost: "$3.25" },
    { id: "wi-0650", name: "widget",   /* missing */   cost: "$3.75" }
];

var table = new Y.DataTable({
    columns: [
        "id",
        { key: "name", label: "part name" },
        { key: "price", allowHTML: true, emptyCellValue: "<em>(not set)</em>" },
        "cost"
    ],
    data: data
});

table.render("#example");

This code produces this table:

Some column configurations affect the table headers and others affect the data cells.

Use the key property to reference the associated data field when configuring columns with objects. Other supported configuration properties are listed in Appendix A below.

Stacked Column Headers

Use the children column configuration to create multiple rows of column headers.

var columns = [
    'username',
    {
        // Important: Parent columns do NOT get a key...

        // but DO get a label
        label: "Access",

        // Pass an array of column configurations (strings or objects) as children
        children: [
            'read',
            'write',
        ]
    }
];

var data = [
    { username: "root", read: true, write: true },
    { username: "spilgrim", read: true, write: false },
    { username: "fizzgig", read: false, write: false }
];

var table = new Y.DataTable({
    columns: columns,
    data   : data
}).render("#example");

This code produces this table:

children takes an array of column configurations, just like the columns attribute itself. The columns defined in the children property will have header cells rendered below the parent column's header.

Columns that have children don't relate directly to the data cells in the table rows, so they should not have a key configured. They should, however, include a label to provide the header's content.

Formatting Cell Data

Customizing the content of the cells in your table is done using column configurations. The most common formatting-related column configurations are:

  • allowHTML - set this to true if your cell data, emptyCellValue, or formatter outputs HTML. By default, cell data is HTML escaped for security.
  • emptyCellValue - string to populate cells where no data (empty string, undefined, or null) is available in a record.
  • formatter - string or function used to translate the raw record data for each cell in a given column into a format better suited to display.
  • nodeFormatter - function used to customize the DOM structure of a cell, its row, or its surrounding elements. Use with caution.

When the formatter configuration setting contains a string it will be assumed to be the key into the hash of formatting functions at Y.DataTable.BodyView.Formatters. If any such function is found, it will be used, otherwise, the string will be presumed to be a template which may contain placeholders for data enclosed in curly braces. The {value} placeholder would use the value destined for the current cell. The values of other fields in the record corresponding to the current row can be shown by providing their name enclosed in curly braces. These other fields don't need to have column definitions of their own, they will simply be read from the underlying Model instance.

The Y.DataTable.BodyView.Formatters is empty for the developers to provide their own formatting functions. A basic set is provided in module datatable-formatters that has to be explicitly loaded. Some of these named formatters accept extra configuration settings in the column definition, as described in their API docs.

formatter functions are expected to return the string content to populate each cell in that column, and nodeFormatters are provided with the cell Nodes and expected to populate them using the Node API.

For best performance, avoid nodeFormatters unless absolutely necessary.

var columns = [
    {
        key: 'item',
        formatter: '<a href="#{value}">{value}</a>',
        allowHTML: true // Must be set or the html will be escaped
    },
    {
        key: 'cost',
        formatter: '${value}' // formatter template string
    },
    {
        key: 'price',
        formatter: function (o) {
            if (o.value > 3) {
                o.className += 'expensive';
            }

            return '$' + o.value.toFixed(2);
        }
    },
    {
        label: 'profit',
        nodeFormatter: function (o) {
            var profit = o.data.price - o.data.cost,
                prefix = '$',
                row;

            if (profit < 0) {
                prefix = '-' + prefix;
                profit = Math.abs(profit);
                row = o.cell.ancestor();

                o.cell.addClass('negative');

                // Assign a rowspan to the first cell and add a new row
                // below this one to span the last three columns
                row.one('td').setAttribute('rowspan', 2);

                row.insert(
                    '<tr class="auth"><td colspan="3">' +
                        '<button class="ok">authorize</button>' +
                        '<button class="stop">discontinue</button>' +
                    '</td></tr>',
                    'after');
            }

            o.cell.set('text', prefix + profit.toFixed(2));
            return false;
        }
    }
];

This code produces this table:

The parameters passed to formatter functions and nodeFormatter functions are described in Appendix B and Appendix C, respectively. Also look for what can be passed in to the columns in Appendix A.

Note: It's highly recommended to keep the data in the underlying data ModelList as pure data, free from presentational concerns. For example, use real numbers, not numeric strings, and store link urls and labels either in separate data fields or in a single data field, but as separate properties of a value object. This allows the data to be used for calculations such as sorting or averaging.

Setting content with formatter functions

Set the cell content with column formatters by returning the desired content string from the function. Alternately, just update o.value with the new value in the object passed as an argument to the formatter. When updating o.value do not include a return statement.

formatters are very powerful because not only do they have access to the record's value for that column's field, but they also receive the rest of the record's data, the record Model instance itself, and the column configuration object. This allows you to include any extra configurations in your column configuration that might be useful to customizing how cells in the column are rendered.

function currency(o) {
    return Y.DataType.Number.format(o.value, {
        prefix            : o.column.currencySymbol     || '$',
        decimalPlaces     : o.column.decimalPlaces      || 2,
        decimalSeparator  : o.column.decimalSeparator   || '.',
        thousandsSeparator: o.column.thousandsSeparator || ','
    });
}

var cols = [
    { key: "price", formatter: currency, decimalPlaces: 3 },
    ...

If such a formatter will be used regularly, it is best to store it in the Y.DataTable.BodyView.Formatters hash. The formatter can later be used by its name.

Named formatters are structured slightly differently in order to improve performance:

Y.DataTable.BodyView.Formatters.currency = function (col) {
    // This is done just once per rendering cycle:
    var fn = Y.DataType.Number.format,
        format = {
            prefix            : col.currencySymbol     || '$',
            decimalPlaces     : col.decimalPlaces      || 2,
            decimalSeparator  : col.decimalSeparator   || '.',
            thousandsSeparator: col.thousandsSeparator || ','
        };
    return function (o) {
        // This is done once per row:
        return fn(o.value, format);
    }
}

The function stored in the Formatters table is not the formatter function itself, instead, it returns the formatting function. The outer function is called just once per rendering cycle and does any preliminary setup usually based on the column configuration which it receives as its only argument, storing any information in local variables. The returned formatting function is then run once per row accessing all the setup information via closure.

An optional datatable-formatters module provides a collection of such formatters. See the API docs for more information on them.

See Appendix B for a list of all properties passed to formatter functions.

Setting content with nodeFormatter functions

Unlike formatters which can effectively default to the normal rendering logic by leaving o.value unchanged, nodeFormatters must assign content to the cells themselves. The cell's initial classes will be set up, but that's it. Everything else is your responsibility.

nodeFormatters should return false. See below for details.

While there are few scenarios that require nodeFormatters, they do have the benefits of having the Node API for constructing more complex DOM subtrees and the ability to access all nodes in the <tbody>. This means they can reference, and even modify, cells in other rows.

Like formatters, nodeFormatters are provided with the data field value, the record data, the record Model instance, and the column configuration object.

See Appendix C for a list of all properties passed to nodeFormatter functions.

Why formatter and nodeFormatter?

For good rendering performance and memory management, DataTable creates table content by assembling innerHTML strings from templates, with {placeholder} tokens replaced with your data. However, this means that the Nodes don't exist yet when a column's formatters are applied.

To minimize the need to create Nodes for each cell, the default rendering logic supports the addition of cell classes as well as row classes via formatter functions. Event subscriptions should be delegated from the DataTable instance itself using the delegate() method.

On the rare occasion that you must use Nodes to supply the cell data, DataTable allows a second pass over the generated DOM elements once the initial string concatenation has been completed and the full HTML content created.

It is important to note that nodeFormatters will necessarily create a Node instance for each cell in that column, which will increase the memory footprint of your application. If the Node instance wrappers around the DOM elements don't need to be maintained beyond the life of the nodeFormatter, return false to remove them from the internal object cache. This will not remove the rendered DOM, but it will remove event subscriptions made on those Nodes.

In general, nodeFormatters should only be used if absolutely necessary, and should always return false.

Formatters vs. emptyCellValue

The emptyCellValue configuration is useful to provide fallback content in the case of missing or empty column data, but it interacts with each type of formatter differently.

String formatters will only be applied if the field data for that cell is not undefined. This allows the emptyCellValue to populate the cell.

Function formatters are applied before the return value or (potentially altered) o.value property is tested for undefined, null, or the empty string. In any of these cases, the emptyCellValue populates the cell.

The emptyCellValue configuration is ignored by columns configured with nodeFormatters.

Table Data Configuration

Each record in the table is stored as a Model instance, where the keys of the record objects become Model attributes. This allows you to interact with the models as you would any other Base-based class, with get(attr), set(attr, value), and subscribing to attribute change events.

var data = [
    { item: "widget",   cost: 23.57, price: 47.5 },
    { item: "gadget",   cost: 0.11, price: 6.99 },
    { item: "sprocket", cost: 4.08, price: 3.75 },
    { item: "nut",      cost: 0.01, price: 0.25 }
];

var table = new Y.DataTable({
    columns: ["item", "cost", "price"],
    data: data
});

var sprocket = table.getRecord(2);

// Fires a costChange event, and the table is updated if rendered
sprocket.set('cost', 2.65);

The Model class used to store the record data is created for you, based on the objects in the data array. If data is not set, the column keys identified in the columns configuration is used.

Specifying the Record Model

To use a custom Model for your records, pass your Model subclass to the recordType attribute.

var pieTable = new Y.DataTable({
    recordType: Y.PieModel,
    columns: ['slices', 'type'],
    data: [
        // Y.PieModel has attributes 'slices', which defaults to 6, and 'type',
        // which defaults to 'apple'. Records can use these defaults.
        { type: 'lemon meringue' },
        { type: 'chocolate creme', slices: 8 },
        {} // equivalent to { type: 'apple', slices: 6 }
    ]
});

// Y.PieModel has its idAttribute assigned to 'type', overriding the default
// of 'id'.  Fetch a PieModel by its id.
var applePie = pieTable.getRecord('apple');

// eatSlice is a method on the Y.PieModel prototype
applePie.eatSlice();

Alternately, recordType will accept an array of attribute strings or an ATTRS configuration object to make it easier to create custom attribute behaviors without needing to explicitly build the Model subclass.

If the columns configuration is omitted, but the recordType is set, the columns will default to the recordType's attributes.

var data = [
    { item: "widget",   cost: 23.57, price: 47.5 },
    { item: "gadget",   cost: 0.11, price: 6.99 },
    { item: "sprocket", cost: 4.08, price: 3.75 },
    { item: "nut",      cost: 0.01, price: 0.25 }
];

// Effectively synonymous with setting the columns attribute if no special
// column configuration is needed.
var table = new Y.DataTable({
    recordType: [ 'item', 'cost', 'price' ],
    data: data
});

// Or for more control, pass an ATTRS configuration object
var table = new Y.DataTable({
    recordType: {
        item: {},
        cost: {
            value: 0,
            setter: function (val) { return +val || 0; }
        },
        price: {
            valueFn: function () { return (this.get('cost') + 0.1) * 10; },
            setter: function (val) { return +val || 0; }
        }
    },
    data: data
});

When the table data is loaded asychronously, it is often a good idea to configure the recordType. This can prevent the generation of a record Model that is missing fields that are omitted from the columns configuration because they aren't intended for viewing.

The data ModelList

The record Models are stored in a ModelList, which is assigned to the data property on the instance (for easier access than going through table.get('data')).

var records = [
    { item: "widget",   cost: 23.57, price: 47.5 },
    { item: "gadget",   cost: 0.11, price: 6.99 },
    { item: "sprocket", cost: 4.08, price: 3.75 }
];

var table = new Y.DataTable({
    columns: ["item", "cost", "price"],
    data   : records
});

// Add a new Model using the ModelList API. This will fire
// events and change the table if rendered.
table.data.add({ item: "nut", cost: 0.01, price: 0.25 });

When assigning the DataTable's data attribute with an array, a ModelList is created for you. But you can also pass a ModelList instance if you are sharing a ModelList between widgets on the page, or you have created custom Model and ModelList classes with additional logic, such as adding a data sync layer.

var table = new Y.DataTable({
    columns: ['type', 'slices'],
    data: new Y.PieList()
});

// The Y.PieList class implements a sync layer, enabling its load() method
table.data.load(function () {
    table.render('#pies');
});

Getting Remote Table Data

To fetch remote data, you have three options:

  1. For quick one-offs, you can load and parse the data manually, using Y.io(...), Y.jsonp(...), etc., then assign that data to the DataTable's data attribute. This isn't very elegant or maintainable, so is best avoided for anything other than proofs of concept.

  2. For the most control, better maintainability, and better encapsulation of business logic, create Model and ModelList subclasses that implement a sync layer as suggested above.

  3. For common read-only scenarios, use the Y.Plugin.DataTableDataSource plugin to bind your table to a DataSource instance. Use plugins to add DataSource features.

// Create a JSONP DataSource to query YQL
var myDataSource = new Y.DataSource.Get({
    source: 'http://query.yahooapis.com/v1/public/yql?format=json&' +
            'env=store%3A%2F%2Fdatatables.org%2Falltableswithkeys&q='
});

myDataSource.plug(Y.Plugin.DataSourceJSONSchema, {
        schema: {
            resultListLocator: 'query.results.Result',
            resultFields: [
                'Title',
                'Phone',
                {
                    // Important that record fields NOT include ".", so
                    // extract nested data with locators
                    key: 'Rating',
                    locator: "Rating.AverageRating"
                }
            ]
        }
    })
    .plug(Y.Plugin.DataSourceCache, {
        max: 3
    });

// No data is provided at construction because it will load via the
// DataTableDataSource plugin
var table = new Y.DataTable({
    columns: ['Title', 'Phone', 'Rating'],
    summary: 'Pizza places near 98089'
});

table.plug(Y.Plugin.DataTableDataSource, {
    datasource: myDataSource
})

// Initially render an empty table and show a loading message
table.render('#pizza')
     .showMessage('loadingMessage');

// Load the data into the table
table.datasource.load({
    request: encodeURIComponent(
        'select *' +
        ' from   local.search' +
        ' where  zip="94089"' +
        ' and    query="pizza"');
});

DataTable Modules and Features

For a basic, stripped down Y.DataTable class, include the datatable-base module in your use().

Feature modules, such as datatable-sort, will bring in datatable-base automatically. By including only feature modules in your use(), you will get a Y.DataTable that supports specifically those features, without extra code for other features you won't be using.

The datatable module is a bundle of datatable-base plus a set of common feature modules. Other feature modules need to be included explicitly in use().

Module Description In datatable?
datatable-core The core API for DataTable, implemented as a class extension, used by datatable-base to create Y.DataTable and Y.DataTable.Base. yes
datatable-base Creates the Y.DataTable and Y.DataTable.Base classes, and defaults the headerView and bodyView to Y.DataTable.HeaderView and Y.DataTable.BodyView respectively. yes
datatable-head Creates the Y.DataTable.HeaderView class as a subclass of Y.View. DataTable defers rendering of the <thead> content to this View when it is passed as the DataTable's headerView attribute (the default, as set by datatable-base). yes
datatable-body Creates the Y.DataTable.BodyView class as a subclass of Y.View. DataTable defers rendering of the <tbody> content to this View when it is passed as the DataTable's bodyView attribute (the default, as set by datatable-base). yes
datatable-message Creates the Y.DataTable.Message class extension and adds showMessage and hideMessage methods to Y.DataTable. yes
datatable-column-widths Creates the Y.DataTable.ColumnWidths class extension, and adds support for the width property in column configuration objects to Y.DataTable. yes
datatable-mutable Creates the Y.DataTable.Mutable class extension and adds methods such as addRow, removeRow, and moveColumn to Y.DataTable. yes
datatable-sort Creates the Y.DataTable.Sortable class extension and adds methods sort and toggleSort as well as attributes sortable and sortBy to Y.DataTable. Enables sorting the table rows by clicking on column headers. yes
datatable-datasource Creates the Y.Plugin.DataTableDataSource plugin for binding a DataSource instance to the table as its source of record data. yes
datatable-scroll Creates the Y.DataTable.Scroll class extension and adds attribute scrollable to Y.DataTable. Adds support for vertically and/or horizontally scrolling table rows within fixed table dimensions. no
datatable-formatters Populates Y.DataTable.BodyView.Formatters with a collection of cell formatting functions. no
datatable-paginator Adds paginator functionality to the DataTable. Can also add paginator UIs to the DataTable or any other specified Y.Node. no
datatable-keynav Provides keyboard navigation within the DataTable. no

Features in DataTable.Base

By including only datatable-base in your use() line, you get both Y.DataTable and Y.DataTable.Base classes. With no other module inclusion, these classes are effectively the same. When additional DataTable related modules are included, those modules' features will usually be added to Y.DataTable, but never to Y.DataTable.Base.

Though it can be instantiated, the purpose of Y.DataTable.Base is primarily as a superclass to a custom DataTable implementation that has a locked set of features that will not be modified, as Y.DataTable can be, by the inclusion of other modules.

// Create a custom DataTable that includes only the core set of APIs, plus
// sorting and message support.
Y.MyDataTable = Y.Base.create('myTable', Y.DataTable.Base,
    [ Y.DataTable.Sortable, Y.DataTable.Message ]);

Y.use('datatable-scroll', function (Y) {
    // Y.DataTable now has support for scrolling
    var table = new Y.DataTable({ scrollable: 'y', ... });

    // Y.MyDataTable does not (the config does nothing)
    var myTable = new Y.MyDataTable({ scrollable: 'y', ... });
});

Y.DataTable.Base includes the columns, data, caption, and other basic table attributes, the underlying ModelList and View rendering architecture, as well as methods to fetch rows and cells or columns and records.

Rendering features include most column configurations, such as children and allowHTML, as well as column formatting options formatter, nodeFormatter, cellTemplate, etc.

Table Messages

The datatable-message module adds the ability to display a message in the table body. By default, the "emptyMessage" will display when the table's ModelList has no data records. The message will hide when data is added.

var table = new Y.DataTable({
    columns: ["id", "name", "price"],
    data: []
}).render('#example');

This code produces this table:

Use table.showMessage("message") and table.hideMessage() to toggle the message display.

showMessage supports internationalized strings by using a few named strings, which are registered in the language packs for the datatable-message module . These strings are currently:

  • table.showMessage("emptyMessage") defaults to "No data to display".
  • table.showMessage("loadingMessage") defaults to "Loading...".

Other values passed to showMessage will pass that content directly through to the message Node.

Column Width Configuration

The datatable-column-widths module adds basic support for specifying column widths.

var table = new Y.DataTable({
    columns: [
        { key: 'item', width: '125px' },
        { key: 'cost', formatter: '${value}' },
        ...
    ],
    data   : data
}).render("#example");

This code produces this table:

CAVEAT: Column widths will expand beyond the configured value if column cells contain data that is long and can't line-wrap. Also, column widths may be reduced below the configured value if the table width (by configuring the DataTable's width attribute, or constrained by a narrow containing element) is too narrow to fit all data at the configured widths.

To force column widths, including cell data truncation and allowing the table to spill beyond its configured or inherited width, wrap the cell content in a <div> either by configuring the column's formatter or cellTemplate, then assign the <div>'s CSS style with the desired width (or "inherit"), plus overflow: hidden;. Then set the DataTable column's width configuration accordingly.

Column sorting

The datatable-sort module adds support for sorting the table rows either through the added APIs or by clicking on the table headers.

By default, when datatable-sort is included, DataTables will inspects the columns objects, looking for sortable: true to enable table sorting by those columns, triggered by clicking on their respective headers.

var cols = [
    { key: "Company", sortable: true },
    { key: "Phone" },
    { key: "Contact", sortable: true }
];

For convenience, you can enable header-click sorting for all columns by setting the sortable attribute to true, or pass an array of column keys to enable just those column's headers.

// Set all columns to be sortable
var table = new Y.DataTable({
    columns: ["Company", "Phone", "Contact"],
    data: ...
    sortable: true
}).render("#example");

This code produces this table:

Hold down the shift key while clicking on column headers to subsort by that column. Doing so repeatedly will toggle the subsort direction.

As long as the datatable-sort module has been included, you will always be able to sort the table data through the API, even by columns that aren't configured to accept header-click sorting.

When a table is sorted, any new records added to the DataTable's ModelList will be inserted at the proper sorted index, as will the created table rows.

Disable header-click sorting by setting sortable to false.

The default sort order is case insensitive, the sort order can be set to case sensitive by using the caseSensitive attribute, see Appendix A below.

Custom Sorting

Assign a function to a column's sortFn to support customized sorting. The function will receive the two records being compared and a boolean flag indicating a descending sort was requested.

var columns = [
    {
        key: 'id',
        label: '&#9679;', // a big dot
        formatter: function (o) {
            return o.value ? '' : '&#9679;'; // only new records have a dot
        },
        sortable: true,
        sortFn: function (a, b, desc) {
            var aid   = a.get('id'),
                bid   = b.get('id'),
                acid  = a.get('clientId'),
                bcid  = b.get('clientId'),
                order = // existing records are equivalent
                        (aid && bid) ? 0 :
                        // new records are grouped apart from existing records
                        (aid && -1) || (bid && 1) ||
                        // new records are sorted by insertion order
                        (acid > bcid) ? 1 : -(acid < bcid);

            return desc ? -order : order;
        }
    },
    ...

The function must return 1, 0, or -1. 1 specifies that the Model passed as the first parameter should sort below the Model passed as the second parameter. -1 for above, and 0 if they are equivalent for the purpose of this sort.

Sorting Methods

To sort the table in the code, call table.sort(NAME OR KEY). To toggle the sort direction, call table.toggleSort(NAME OR KEY).

// Sorts the table by values in the price field in ascending order
table.sort('price');

// Flips to descending
table.toggleSort('price');

To sort by multiple columns, pass an array of column keys to sort or toggleSort.

Calling toggleSort with no arguments will reverse all current sort directions. Calling with specific column names or keys will toggle only those columns.

// Sort first by author, subsort by title in ascending order
table.sort(['author', 'title']);

// Now descending by author then title
// same as table.toggleSort(['author', 'title']);
table.toggleSort();

// Now ascending by author, descending by title
table.toggleSort('author');

To specify a sort direction, pass an object instead of a string to sort. The object should have the column name as the key, and sort direction as its value.

// Explicitly sort by price in descending order
table.sort({ price: 'desc' });

// Each column gets its own object
table.sort([{ author: 'desc' }, { title: 'desc' }]);

Acceptable values for the sort direction are "asc", "desc", 1, and -1. 1 is equivalent to "asc", and -1 to "desc".

The sortBy Attribute

Every sort operation updates the sortBy attribute. You can also trigger a sort by setting this attribute directly. It accepts the same values as the sort method.

// Sort by author in descending order, then by title in ascending order
table.set('sortBy', [{ author: -1 }, 'title']);

To specify an initial sort order for your table, assign this attribute during instantiation. This will sort the data as soon as it is added to the table's ModelList.

// Pre-sort the data
var table = new Y.DataTable({
    columns: ['item', 'cost', 'price'],
    data: [...],
    sortBy: { price: -1 }
});

The sort Event

Clicking on a column header, or calling the sort or toggleSort methods will fire a sort method containing an e.sortBy property that corresponds to the requested sort column and direction. The value will be in either string or object format, depending on how each method was used.

Preventing the sort event will prevent the sortBy attribute from being updated. Updating the sortBy attribute directly will not fire the sort event, but will still sort the data and update the table.

Table Mutation APIs (addRow, etc)

The datatable-mutable module adds APIs for adding, removing, and modifying records and columns.

Column Mutation Methods

Use the methods addColumn, removeColumn, modifyColumn, and moveColumn to update the table's configured columns.

// Insert a column for the profit field in the data records as the third column
table.addColumn('profit', 2);

// Actually, make that the fourth column
table.moveColumn('profit', 3);

// Actually, strike that.  Don't show it after all
table.removeColumn('profit');

// Instead, add a formatter to the price column that includes the profit data
table.modifyColumn('price', {
    formatter: function (o) {
        return o.value + ' (' + (o.data.profit / o.data.cost).toFixed(2) + '%)';
    }
});

Each column mutation method fires an identically named event. See the API docs for details.

Row Mutation Methods

Use the methods addRow, addRows, removeRow, and modifyRow to update the table's ModelList.

table.addRow({ item: 'collet', cost: 0.42, price: 2.65 });

table.addRows([
    { item: 'nut',    cost: 0.42, price: 2.65 },
    { item: 'washer', cost: 0.01, price: 0.08 },
    { item: 'bit',    cost: 0.19, price: 0.97 }
]);

// Remove table records by their Model, id, clientId, or index
table.removeRow(0);

// Modify a record by passing its id, clientId, or index, followed by an
// object with new field values
table.modifyRow('record_4', { cost: 0.74 });

Everything that's done by these methods can be accomplished through the table's ModelList instance methods, but having methods on the table itself can make the code more readable.

// Same as table.addRow(...);
table.data.add({ item: 'collet', cost: 0.42, price: 2.65 });

By default, changes made to the table are only local, they don't update the server or other data origin if the data was served remotely. However, if your table's ModelList is built with a sync layer, the mutation methods can also trigger the appropriate sync behavior by passing an additional argument to the methods, an object with the property sync set to true.

// Tell the server we're down to one slice of apple pie!
table.modifyRow('apple', { slices: 1 }, { sync: true });

// Uh oh, make that 0.  No more apple pie :(
table.removeRow('apple', { sync: true });

If all modifications are destined for the server/origin, you can set the autoSync attribute to true, and the row mutation methods will automatically call into the sync layer.

var pies = new Y.DataTable({
    columns: ['type', 'slices'],
    data: new Y.PieList()
    autoSync: true
});

pies.data.load(function () {

    pies.render('#pie-cart');

    // The new PieModel's save() method is called, notifying the server
    pies.addRow({ type: 'pecan', slices: 8 });

    // Let us eat some pie!
    pies.modifyRow('lemon meringue', { slices: 5 });
});

Scrolling

Note: Scrolling is not currently supported on the Android WebKit browser.

Scrolling functionality can be added to Y.DataTable by including datatable-scroll module in your use(). datatable-scroll is NOT included in the datatable rollup module, so must be included separately.

Enable scrolling by setting the scrollable attribute, which accepts values "x", "y", "xy", true (same as "xy"), or false (the default).

Note, vertical scrolling also requires the table's height attribute to be set, and horizontal scrolling requires the width to be set.

// Data from the seafoodwatch YQL table as of 3/16/2012
var data = [
    { "fish": "Barramundi (Imported Farmed in Open Systems)", "recommendation": "avoid" },
    { "fish": "Caviar, Paddlefish (Wild caught from U.S.)", "recommendation": "avoid" },
    { "fish": "Caviar, Sturgeon (Imported Wild-caught)", "recommendation": "avoid" },
    ...
];

// Enable vertical scrolling with scrollable "y". The width is also set, but
// because scrollable is not "x" or "xy", this just sets the table width.
var table = new Y.DataTable({
    caption: 'Seafood tips for the US West Coast',
    columns: ['fish', 'recommendation'],
    data: data,
    scrollable: "y",
    height: "200px",
    width:  "400px"
}).render("#scroll");

This code produces this table:

DataTable Paginator

Paginator for DataTable brings the simpliciy of Y.Paginator to DataTable ready to help you manage the amount of data displayed on the screen at on time. This will keep readability up and render time down.

Paginator Methods

Out of the box, DataTable Paginator will add four methods to DataTable to navigate pages of data being displayed. All of these methods are chainable.

Method Description
firstPage Sets the defined paginatorModel to the first page.
lastPage Sets the defined paginatorModel to the last page.
prevPage Sets the defined paginatorModel to the prev page.
nextPage Sets the defined paginatorModel to the next page.

Paginator Attributes

There are also a handful of attributes to apply to the DataTable configuration when you have datatable-paginator included in your sandbox. The default configurations will keep the UI hidden until rowsPerPage is set to a number greater than zero. When rowPerPage is null, the UI is hidden.

Attribute Data Type Description Default
pageSizes Array Array of values used to populate the values in the Paginator UI allowing the end user to select the number of items to display per page. [10, 50, 100,
{ label: 'Show All', value: -1 }
]
paginatorLocation String|Array|Y.Node String of footer or header, a Y.Node, or an Array or any combination of those values. "footer"
paginatorModel Y.Model|Object A model instance or a configuration object for the Model.
paginatorModelType Y.Model|String A pointer to a Model object to be instantiated, or a String off of the Y namespace.

This is only used if the pagiantorModel is a configuration object or is null.
"DataTable.Paginator.Model"
paginatorView Y.Model|String A pointer to a Y.View object to be instantiated. A new view will be created for each location provided. Each view created will be given the same model instance. "DataTable.Paginator.View"
rowsPerPage Number|null Number of rows to display per page. As the UI changes the number of pages to display, this will update to reflect the value selected in the UI null

Paginator UI

The Paginator UI out of the box consists of:

  • Four (4) buttons — first, previous, next and last
  • A page selector to go to a specific page
  • A select box to change the number of rows to display per page

The UI is set up in a template located under Y.DataTable.Templates.Paginator and can be updated any time before the UI is rendered to the page. The template has a few points of interest that can be updated.

Key Description
rowWrapper Template used to create the containing row when placed inside the table.
content Template used to place and order the content
button Template used to create a button
buttons Template used to place buttons in a button group
gotoPage Template used to display the Go To Page control
perPage Template used to display the Rows Per Page select option

Paginator in action

Paginator for DataTable has introduced a few things, but let's take a look and see it in action.

var dt = new Y.DataTable({
        columns: ['name', 'qty'],
        data: [
            { name: 'Apple',  qty: 16 },
            { name: 'Banana', qty: 10 },
            { name: 'Carrot', qty: 34 },
            { name: 'Date',   qty: 92 },
            { name: 'Grape',  qty: 14 },
            { name: 'Orange', qty: 74 },
            { name: 'Rasin',  qty: 39 }
        ],
        rowsPerPage: 4,
        pageSizes: [ 4, 'Show All' ]
    });

dt.render();

This code produces this table:

Keyboard Navigation

The datatable-keynav optional module provides navigation within the DataTable using the keyboard. It is based on the WAI-ARIA suggested keyboard interaction for the Grid Widget, though it does not assign aria roles, states or other accessibility features to the table.

Two attributes control the behaviour of the module. The keyActions property contains a mapping of keys to actions to be performed. It is initially loaded with a copy of the set of actions stored in the static Y.DataTable.KeyNav.ARIA_ACTIONS table but can easily be modified or completely replaced.

As a key, each entry in the table uses a key code or any of the predefined strings in the static Y.DataTable.KeyNav.KEY_NAMES table, optionally preceeded by the alt, ctrl, meta or shift modifier keys, each followed by a hyphen. Multiple modifier keys can be specified, but they always have to be given in alphabetical order, for example: 'ctrl-shift-up': . When a key has an alias in KEY_NAMES the alias must be used and not the numerical keycode.

Each entry in the keyActions table contains either a function or a string. The function will be executed when the key combination used as the key for the entry is detected. It will receive the Event Facade of the keydown event as its only argument. If the argument is a string it will try to resolve it to a method within that DataTable instance by that name and execute it, ie.: this[name].call(this, e).

If the entry is a string and cannot be resolved into a method, it will assume it is the name of an event to fire. The event listener will be provided with references to the focused cell (a Node instance), the row (the tr Node) and column where it is contained. If the cell is a data cell it will also contain a reference to the record while for header rows it will be null. The listener will also receive the original EventFacade of the keydown event as its second argument.

var dt = new Y.DataTable({...});

// Add an event in response to pressing shift-space
dt.keyActions['shift-space'] = 'shiftSpaceEvent';

// You can now subscribe to that event
dt.on('shiftSpaceEvent', function (ev, keyEv) {
    // The first event facade will contain the following properties:
    console.log(ev.cell, ev.row, ev.column, ev.record);

    // The second event facade is the original for keydown:
    console.log(keyEv.keyCode, keyEv.shiftKey);

    // The listener may call the keydown event facade methods:
    keyEv.preventDefault();
});

The keyIntoHeaders attribute determines whether navigating into the header cells is possible. If set, the default, the first header cell will initially receive the focus and navigation in between the header and data sections of the table is possible. If false, the first data cell will be initially focused and navigation into the header section is not possible.

The focusedCell attribute holds a reference to the cell that has the focus or that would have the focus if the DataTable gets tabbed into. Setting focusedCell will bring the focus into that cell.

DataTable Events

DataTable is a composition of supporting class instances and extensions, so to centralize event reporting, it is a bubble target for its data ModelList as well as the View instances used for rendering.

In other words, some events you may need to subscribe to using an event prefix to be notified. Often, using a wildcard prefix is the simplest method to ensure your subscribers will be notified, even if classes change.

// The sort event is from an extension, so it originates from DataTable
table.after('sort', function (e) { ... });

// Model changes originate from the record's Model instance, propagate to the
// table's ModelList, then finally to the DataTable, so they must be
// subscribed with an event prefix.  In this case, we'll use a wildcard
// prefix.
table.after('*:priceChange', function (e) { ... });

DataTable generates a custom Model class with the "record" event prefix, if you want to be more specific. Otherwise, if your table uses a custom Model class for its recordType, you can prefix Model events with the appropriate prefix.

// Allow DataTable to generate the Model class automatically
var table = new Y.DataTable({
    columns: ['items', 'cost', 'price'],
    data: [
        { item: "widget", cost: 23.57, price: 47.5 },
        { item: "gadget", cost: 0.11, price: 6.99 },
        ...
    ]
});

// generated Model classes have prefix "record"
table.after('record:change', function (e) { ... });

// PieList uses PieModels, which have a prefix of, you guessed it, "pie"
var pies = new Y.DataTable({
    columns: ['type', 'slices'],
    data: new Y.PieList()
});

pies.on('pie:slicesChange', function (e) {
    if (e.target.get('type') === 'chocolate creme') {
        // Oh no you don't!
        e.preventDefault();
    }
});

The full list of events is included in the DataTable API docs.

Known Issues

Appendix A: Column Configurations

The properties below are supported in the column configuration objects passed in the columns attribute array.

Configuration Description Module
key
{ key: 'username' }

Binds the column values to the named property in the data.

Optional if formatter, nodeFormatter, or cellTemplate is used to populate the content.

It should not be set if children is set.

The value is used for the _id property unless the name property is also set.

datatable-base
name
{ name: 'fullname', formatter: ... }

Use this to assign a name to pass to table.getColumn(NAME) or style columns with class "yui3-datatable-col-NAME" if a column isn't assigned a key.

The value is used for the _id property.

datatable-base
field
{ field: 'fullname', formatter: ... }

An alias for name for backward compatibility.

datatable-base
id
{
  name: 'checkAll',
  id: 'check-all',
  label: ...
  formatter: ...
}

Overrides the default unique id assigned <th id="HERE">.

Use this with caution, since it can result in duplicate ids in the DOM.

datatable-base
label
{ key: 'MfgPrtNum', label: 'Part Number' }

HTML to populate the header <th> for the column.

datatable-base
children

Used to create stacked headers. See the example above.

Child columns may also contain children. There is no limit to the depth of nesting.

Columns configured with children are for display only and should not be configured with a key. Configurations relating to the display of data, such as formatter, nodeFormatter, emptyCellValue, etc. are ignored.

datatable-base
abbr
{
  key  : 'forecast',
  label: '1yr Target Forecast',
  abbr : 'Forecast'
}

Assigns the value <th abbr="HERE">.

datatable-base
title
{
  key  : 'forecast',
  label: '1yr Target Forecast',
  title: 'Target Forecast for the Next 12 Months'
}

Assigns the value <th title="HERE">.

datatable-base
headerTemplate
{
  headerTemplate:
    '<th id="{id}" ' +
        'title="Unread" ' +
        'class="{className}" ' +
        '{_id}>&#9679;</th>'
}

Overrides the default CELL_TEMPLATE used by Y.DataTable.HeaderView to render the header cell for this column. This is necessary when more control is needed over the markup for the header itself, rather than its content.

Use the label configuration if you don't need to customize the <th> iteself.

Implementers are strongly encouraged to preserve at least the {id} and {_id} placeholders in the custom value.

datatable-base
cellTemplate
{
  key: 'id',
  cellTemplate:
    '<td class="{className}">' +
      '<input type="checkbox" ' +
             'id="{content}">' +
    '</td>'
}

Overrides the default CELL_TEMPLATE used by Y.DataTable.BodyView to render the data cells for this column. This is necessary when more control is needed over the markup for the <td> itself, rather than its content.

datatable-base
formatter

Used to customize the content of the data cells for this column.

See the example above

datatable-base
nodeFormatter

Used to customize the content of the data cells for this column.

See the example above

datatable-base
emptyCellValue
{
  key: 'price',
  emptyCellValue: '???'
}

Provides the default value to populate the cell if the data for that cell is undefined, null, or an empty string.

datatable-base
allowHTML
{
  key: 'preview',
  allowHTML: true
}

Skips the security step of HTML escaping the value for cells in this column. This is also necessary if emptyCellValue is set with an HTML string.

nodeFormatters ignore this configuration. If using a nodeFormatter, it is recommended to use Y.Escape.html() on any user supplied content that is to be displayed.

datatable-base
className
{
  key: 'symbol',
  className: 'no-hide'
}

A string of CSS classes that will be added to the <td>'s class attribute.

Note, all cells will automatically have a class in the form of "yui3-datatable-col-KEY" added to the <td>, where KEY is the column's configured name, key, or id (in that order of preference).

datatable-base
width
{ key: 'a', width: '400px' },
{ key: 'b', width: '10em' }

Adds a style width setting to an associated <col> element for the column.

Note, the assigned width will not truncate cell content, and it will not preserve the configured width if doing so would compromise either the instance's width configuration or the natural width of the table's containing DOM elements.

If absolute widths are required, it can be accomplished with some custom CSS and the use of a cellTemplate, or formatter. See the description of datatable-column-widths for an example of how to do this.

datatable-column-widths
sortable
{ key: 'lastLogin', sortable: true }

Used when the instance's sortable attribute is set to "auto" (the default) to determine which columns will support user sorting by clicking on the header.

If the instance's key attribute is not set, this configuration is ignored.

datatable-sort
caseSensitive
{ key: 'lastLogin', sortable: true, caseSensitive: true }

When the instance's caseSensitive attribute is set to "true" the sort order is case sensitive (relevant to string columns only).

Case sensitive sort is marginally more efficient and should be considered for large data sets when case insensitive sort is not required.

datatable-sort
sortFn
{
  label: 'Name',
  sortFn: function (a, b, desc) {
    var an = a.get('lname') + b.get('fname'),
        bn = a.get('lname') + b.get('fname'),
        order = (an > bn) ? 1 : -(an < bn);

    return desc ? -order : order;
  },
  formatter: function (o) {
    return o.data.lname + ', ' + o.data.fname;
  }
}

Allows a column to be sorted using a custom algorithm. The function receives three parameters, the first two being the two record Models to compare, and the third being a boolean true if the sort order should be descending.

The function should return -1 to sort a above b, -1 to sort a below b, and 0 if they are equal. Keep in mind that the order should be reversed when desc is true.

The desc parameter is provided to allow sortFns to always sort certain values above or below others, such as always sorting nulls on top.

datatable-sort
sortDir

(read-only) If a column is sorted, this will be set to 1 for ascending order or -1 for descending. This configuration is public for inspection, but can't be used during DataTable instantiation to set the sort direction of the column. Use the table's sortBy attribute for that.

datatable-sort
_yuid

(read-only) The unique identifier assigned to each column. This is used for the id if not set, and the _id if none of name, 'field, key, or id` are set.

datatable-base
_id

(read-only) A unique-to-this-instance name used extensively in the rendering process. It is also used to create the column's classname, as the input name table.getColumn(HERE), and in the column header's <th data-yui3-col-id="HERE">.

The value is populated by the first of name, field, key, id, or _yuid to have a value. If that value has already been used (such as when multiple columns have the same key), an incrementer is added to the end. For example, two columns with key: "id" will have _ids of "id" and "id2". table.getColumn("id") will return the first column, and table.getColumn("id2") will return the second.

datatable-base
_colspan

(read-only) Used by Y.DataTable.HeaderView when building stacked column headers.

datatable-base
_rowspan

(read-only) Used by Y.DataTable.HeaderView when building stacked column headers.

datatable-base
_parent

(read-only) Assigned to all columns in a column's children collection. References the parent column object.

datatable-base
_headers

(read-only) Array of the ids of the column and all parent columns. Used by Y.DataTable.BodyView to populate <td headers="THIS"> when a cell references more than one header.

datatable-base

Appendix B: Formatter Argument Properties

The properties below are found on the object passed to formatter functions defined in a column configuration. See Appendix C for the object properties passed to nodeFormatters.

Property Description
value
formatter: function (o) {
    // assumes a numeric value for this column
    return '$' + o.value.toFixed(2);
}

The raw value from the record Model to populate this cell. Equivalent to o.record.get(o.column.key) or o.data[o.column.key].

data
formatter: function (o) {
    return o.data.lname + ', ' + o.data.fname;
}

The Model data for this row in simple object format.

record
formatter: function (o) {
    return '<a href="/service/' + o.record.get('id') + '">' +
        o.value + '</a>';
}

The Model for this row.

column
formatter: function (o) {
    // Use a custom column property
    var format = o.column.dateFormat || '%D';

    return Y.DataType.Data.format(o.value, format);
}

The column configuration object.

className
formatter: function (o) {
    if (o.value < 0) {
        o.className += 'loss';
    }
}

A string of class names to add <td class="HERE"> in addition to the column class and any classes in the column's className configuration.

rowIndex
formatter: function (o) {
    return (o.rowIndex + 1) + ' - ' + o.value;
}

The index of the current Model in the ModelList. Typically correlates to the row index as well.

rowClass
formatter: function (o) {
    if (o.value < 0) {
        o.rowClass += 'loss';
    }
}

A string of css classes to add <tr class="HERE"><td....

This is useful to avoid the need for nodeFormatters to add classes to the containing row.

Appendix C: nodeFormatter Argument Properties

The properties below are found on the object passed to nodeFormatter functions defined in a column configuration. See Appendix B for the object properties passed to formatters.

// Reference nodeFormatter
nodeFormatter: function (o) {
    if (o.value < o.data.quota) {
        o.td.setAttribute('rowspan', 2);
        o.td.setAttribute('data-term-id', this.record.get('id'));

        o.td.ancestor().insert(
            '<tr><td colspan"3">' +
                '<button class="term">terminate</button>' +
            '</td></tr>',
            'after');
    }

    o.cell.setHTML(o.value);
    return false;
}
Property Description
td The <td> Node for this cell.
cell

If the cell <td> contains an element with class "yui3-datatable-liner", this will refer to that Node. Otherwise, it is equivalent to o.td (default behavior).

By default, liner elements aren't rendered into cells, but to implement absolute column widths, some cell liner element with width and overflow style is required (barring a table style of table-layout: fixed). This may be applied to the columns cellTemplate configuration or to the bodyView instance's CELL_TEMPLATE for all columns.

Generally, the liner, if present, corresponds to where the content should go, so use o.cell to add content and o.td to specifically work with the <td> Node.

value The raw value from the record Model to populate this cell. Equivalent to o.record.get(o.column.key) or o.data[o.column.key].
data The Model data for this row in simple object format.
record The Model for this row.
column The column configuration object.
rowIndex The index of the current Model in the ModelList. Typically correlates to the row index as well.