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 totrue
if your cell data,emptyCellValue
, orformatter
outputs HTML. By default, cell data is HTML escaped for security. -
emptyCellValue
- string to populate cells where no data (empty string,undefined
, ornull
) 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 nodeFormatter
s are provided with the cell Nodes
and expected to populate them using the Node API.
For best performance, avoid
nodeFormatter
s 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 formatter
s 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.
nodeFormatter
s should return false
.
See below for details.
While there are few scenarios that
require nodeFormatter
s, 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 formatter
s 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, nodeFormatter
s 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
nodeFormatter
s.
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:
-
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'sdata
attribute. This isn't very elegant or maintainable, so is best avoided for anything other than proofs of concept. -
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.
-
For common read-only scenarios, use the
Y.Plugin.DataTableDataSource
plugin to bind your table to aDataSource
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: '●', // a big dot formatter: function (o) { return o.value ? '' : '●'; // 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
- Scrolling is not currently supported on Android WebKit browser.
- Scrolling DataTable may not appear scrollable on iOS browsers or OS X 10.7 depending on the system preference "Show scroll bars" (General).
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
Optional if It should not be set if
The value is used for the |
datatable-base |
name |
{ name: 'fullname', formatter: ... }
Use this to assign a name to pass to
The value is used for the |
datatable-base |
field |
{ field: 'fullname', formatter: ... } An alias for |
datatable-base |
id |
{ name: 'checkAll', id: 'check-all', label: ... formatter: ... }
Overrides the default unique id assigned 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 |
datatable-base |
children |
Used to create stacked headers. See the example above.
Child columns may also contain
Columns configured with |
datatable-base |
abbr |
{ key : 'forecast', label: '1yr Target Forecast', abbr : 'Forecast' } Assigns the value |
datatable-base |
title |
{ key : 'forecast', label: '1yr Target Forecast', title: 'Target Forecast for the Next 12 Months' } Assigns the value |
datatable-base |
headerTemplate |
{ headerTemplate: '<th id="{id}" ' + 'title="Unread" ' + 'class="{className}" ' + '{_id}>●</th>' }
Overrides the default
CELL_TEMPLATE
used by
Use the
Implementers are strongly encouraged to preserve at least
the |
datatable-base |
cellTemplate |
{ key: 'id', cellTemplate: '<td class="{className}">' + '<input type="checkbox" ' + 'id="{content}">' + '</td>' }
Overrides the default
CELL_TEMPLATE
used by |
datatable-base |
formatter |
Used to customize the content of the data cells for this column. |
datatable-base |
nodeFormatter |
Used to customize the content of the data cells for this column. |
datatable-base |
emptyCellValue |
{ key: 'price', emptyCellValue: '???' }
Provides the default value to populate the cell if the data
for that cell is |
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
|
datatable-base |
className |
{ key: 'symbol', className: 'no-hide' }
A string of CSS classes that will be added to the
Note, all cells will automatically have a class in the
form of "yui3-datatable-col-KEY" added to the |
datatable-base |
width |
{ key: 'a', width: '400px' }, { key: 'b', width: '10em' }
Adds a style
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
If absolute widths are required, it can be accomplished with
some custom CSS and the use of a |
datatable-column-widths |
sortable |
{ key: 'lastLogin', sortable: true }
Used when the instance's
If the instance's |
datatable-sort |
caseSensitive |
{ key: 'lastLogin', sortable: true, caseSensitive: true }
When the instance's 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
The function should return
The |
datatable-sort |
sortDir |
(read-only) If a column is sorted, this
will be set to |
datatable-sort |
_yuid |
(read-only) The unique identifier assigned
to each column. This is used for the |
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
The value is populated by the first of |
datatable-base |
_colspan |
(read-only) Used by
|
datatable-base |
_rowspan |
(read-only) Used by
|
datatable-base |
_parent |
(read-only) Assigned to all columns in a
column's |
datatable-base |
_headers |
(read-only) Array of the |
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 nodeFormatter
s.
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 |
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 |
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
This is useful to avoid the need for |
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 formatter
s.
// 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
By default, liner elements aren't rendered into cells, but
to implement absolute column widths, some cell liner
element with
Generally, the liner, if present, corresponds to where the
content should go, so use |
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. |