You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
701 lines
23 KiB
701 lines
23 KiB
/*
|
|
YUI 3.17.2 (build 9c3c78e)
|
|
Copyright 2014 Yahoo! Inc. All rights reserved.
|
|
Licensed under the BSD License.
|
|
http://yuilibrary.com/license/
|
|
*/
|
|
|
|
YUI.add('datatable-base', function (Y, NAME) {
|
|
|
|
/**
|
|
A Widget for displaying tabular data. The base implementation of DataTable
|
|
provides the ability to dynamically generate an HTML table from a set of column
|
|
configurations and row data.
|
|
|
|
Two classes are included in the `datatable-base` module: `Y.DataTable` and
|
|
`Y.DataTable.Base`.
|
|
|
|
@module datatable
|
|
@submodule datatable-base
|
|
@main datatable
|
|
@since 3.5.0
|
|
**/
|
|
|
|
// DataTable API docs included before DataTable.Base to make yuidoc work
|
|
/**
|
|
A Widget for displaying tabular data. Before feature modules are `use()`d,
|
|
this class is functionally equivalent to DataTable.Base. However, feature
|
|
modules can modify this class in non-destructive ways, expanding the API and
|
|
functionality.
|
|
|
|
This is the primary DataTable class. Out of the box, it provides the ability
|
|
to dynamically generate an HTML table from a set of column configurations and
|
|
row data. But feature module inclusion can add table sorting, pagintaion,
|
|
highlighting, selection, and more.
|
|
|
|
<pre><code>
|
|
// The functionality of this table would require additional modules be use()d,
|
|
// but the feature APIs are aggregated onto Y.DataTable.
|
|
// (Snippet is for illustration. Not all features are available today.)
|
|
var table = new Y.DataTable({
|
|
columns: [
|
|
{ type: 'checkbox', defaultChecked: true },
|
|
{ key: 'firstName', sortable: true, resizable: true },
|
|
{ key: 'lastName', sortable: true },
|
|
{ key: 'role', formatter: toRoleName }
|
|
],
|
|
data: {
|
|
source: 'http://myserver.com/service/json',
|
|
type: 'json',
|
|
schema: {
|
|
resultListLocator: 'results.users',
|
|
fields: [
|
|
'username',
|
|
'firstName',
|
|
'lastName',
|
|
{ key: 'role', type: 'number' }
|
|
]
|
|
}
|
|
},
|
|
recordType: UserModel,
|
|
pagedData: {
|
|
location: 'footer',
|
|
pageSizes: [20, 50, 'all'],
|
|
rowsPerPage: 20,
|
|
pageLinks: 5
|
|
},
|
|
editable: true
|
|
});
|
|
</code></pre>
|
|
|
|
### Column Configuration
|
|
|
|
The column configurations are set in the form of an array of objects, where
|
|
each object corresponds to a column. For columns populated directly from the
|
|
row data, a 'key' property is required to bind the column to that property or
|
|
attribute in the row data.
|
|
|
|
Not all columns need to relate to row data, nor do all properties or attributes
|
|
of the row data need to have a corresponding column. However, only those
|
|
columns included in the `columns` configuration attribute will be rendered.
|
|
|
|
Other column configuration properties are supported by the configured
|
|
`view`, class as well as any features added by plugins or class extensions.
|
|
See the description of DataTable.TableView and its subviews
|
|
DataTable.HeaderView, DataTable.BodyView, and DataTable.FooterView (and other
|
|
DataTable feature classes) to see what column properties they support.
|
|
|
|
Some examples of column configurations would be:
|
|
|
|
<pre><code>
|
|
// Basic
|
|
var columns = [{ key: 'firstName' }, { key: 'lastName' }, { key: 'age' }];
|
|
|
|
// For columns without any additional configuration, strings can be used
|
|
var columns = ['firstName', 'lastName', 'age'];
|
|
|
|
// Multi-row column headers (see DataTable.HeaderView for details)
|
|
var columns = [
|
|
{
|
|
label: 'Name',
|
|
children: [
|
|
{ key: 'firstName' },
|
|
{ key: 'lastName' }
|
|
]
|
|
},
|
|
'age' // mixing and matching objects and strings is ok
|
|
];
|
|
|
|
// Including columns that are not related 1:1 to row data fields/attributes
|
|
// (See DataTable.BodyView for details)
|
|
var columns = [
|
|
{
|
|
label: 'Name', // Needed for the column header
|
|
formatter: function (o) {
|
|
// Fill the column cells with data from firstName and lastName
|
|
if (o.data.age > 55) {
|
|
o.className += ' senior';
|
|
}
|
|
return o.data.lastName + ', ' + o.data.firstName;
|
|
}
|
|
},
|
|
'age'
|
|
];
|
|
|
|
// Columns that include feature configurations (for illustration; not all
|
|
// features are available today).
|
|
var columns = [
|
|
{ type: 'checkbox', defaultChecked: true },
|
|
{ key: 'firstName', sortable: true, resizable: true, min-width: '300px' },
|
|
{ key: 'lastName', sortable: true, resizable: true, min-width: '300px' },
|
|
{ key: 'age', emptyCellValue: '<em>unknown</em>' }
|
|
];
|
|
</code></pre>
|
|
|
|
### Row Data Configuration
|
|
|
|
The `data` configuration attribute is responsible for housing the data objects
|
|
that will be rendered as rows. You can provide this information in two ways by default:
|
|
|
|
1. An array of simple objects with key:value pairs
|
|
2. A ModelList of Base-based class instances (presumably Model subclass
|
|
instances)
|
|
|
|
If an array of objects is passed, it will be translated into a ModelList filled
|
|
with instances of the class provided to the `recordType` attribute. This
|
|
attribute can also create a custom Model subclass from an array of field names
|
|
or an object of attribute configurations. If no `recordType` is provided, one
|
|
will be created for you from available information (see `_initRecordType`).
|
|
Providing either your own ModelList instance for `data`, or at least Model
|
|
class for `recordType`, is the best way to control client-server
|
|
synchronization when modifying data on the client side.
|
|
|
|
The ModelList instance that manages the table's data is available in the `data`
|
|
property on the DataTable instance.
|
|
|
|
|
|
### Rendering
|
|
|
|
Table rendering is a collaborative process between the DataTable and its
|
|
configured `view`. The DataTable creates an instance of the configured `view`
|
|
(DataTable.TableView by default), and calls its `render()` method.
|
|
DataTable.TableView, for instance, then creates the `<table>` and `<caption>`,
|
|
then delegates the rendering of the specific sections of the table to subviews,
|
|
which can be configured as `headerView`, `bodyView`, and `footerView`.
|
|
DataTable.TableView defaults the `headerView` to DataTable.HeaderView and the
|
|
`bodyView` to DataTable.BodyView, but leaves the `footerView` unassigned.
|
|
Setting any subview to `null` will result in that table section not being
|
|
rendered.
|
|
|
|
@class DataTable
|
|
@extends DataTable.Base
|
|
@since 3.5.0
|
|
**/
|
|
|
|
// DataTable API docs included before DataTable.Base to make yuidoc work
|
|
/**
|
|
The baseline implementation of a DataTable. This class should be used
|
|
primarily as a superclass for a custom DataTable with a specific set of
|
|
features. Because features can be composed onto `Y.DataTable`, custom
|
|
subclasses of DataTable.Base will remain unmodified when new feature modules
|
|
are loaded.
|
|
|
|
Example usage might look like this:
|
|
|
|
<pre><code>
|
|
// Custom subclass with only sorting and mutability added. If other datatable
|
|
// feature modules are loaded, this class will not be affected.
|
|
var MyTableClass = Y.Base.create('table', Y.DataTable.Base,
|
|
[ Y.DataTable.Sortable, Y.DataTable.Mutable ]);
|
|
|
|
var table = new MyTableClass({
|
|
columns: ['firstName', 'lastName', 'age'],
|
|
data: [
|
|
{ firstName: 'Frank', lastName: 'Zappa', age: 71 },
|
|
{ firstName: 'Frank', lastName: 'Lloyd Wright', age: 144 },
|
|
{ firstName: 'Albert', lastName: 'Einstein', age: 132 },
|
|
...
|
|
]
|
|
});
|
|
|
|
table.render('#over-there');
|
|
|
|
// DataTable.Base can be instantiated if a featureless table is needed.
|
|
var table = new Y.DataTable.Base({
|
|
columns: ['firstName', 'lastName', 'age'],
|
|
data: [
|
|
{ firstName: 'Frank', lastName: 'Zappa', age: 71 },
|
|
{ firstName: 'Frank', lastName: 'Lloyd Wright', age: 144 },
|
|
{ firstName: 'Albert', lastName: 'Einstein', age: 132 },
|
|
...
|
|
]
|
|
});
|
|
|
|
table.render('#in-here');
|
|
</code></pre>
|
|
|
|
DataTable.Base is built from DataTable.Core, and sets the default `view`
|
|
to `Y.DataTable.TableView`.
|
|
|
|
@class Base
|
|
@extends Widget
|
|
@uses DataTable.Core
|
|
@namespace DataTable
|
|
@since 3.5.0
|
|
**/
|
|
Y.DataTable.Base = Y.Base.create('datatable', Y.Widget, [Y.DataTable.Core], {
|
|
|
|
/**
|
|
Pass through to `delegate()` called from the `contentBox`.
|
|
|
|
@method delegate
|
|
@param type {String} the event type to delegate
|
|
@param fn {Function} the callback function to execute. This function
|
|
will be provided the event object for the delegated event.
|
|
@param spec {String|Function} a selector that must match the target of the
|
|
event or a function to test target and its parents for a match
|
|
@param context {Object} optional argument that specifies what 'this' refers to
|
|
@param args* {any} 0..n additional arguments to pass on to the callback
|
|
function. These arguments will be added after the event object.
|
|
@return {EventHandle} the detach handle
|
|
@since 3.5.0
|
|
**/
|
|
delegate: function () {
|
|
var contentBox = this.get('contentBox');
|
|
|
|
return contentBox.delegate.apply(contentBox, arguments);
|
|
},
|
|
|
|
/**
|
|
Destroys the table `View` if it's been created.
|
|
|
|
@method destructor
|
|
@protected
|
|
@since 3.6.0
|
|
**/
|
|
destructor: function () {
|
|
if (this.view) {
|
|
this.view.destroy();
|
|
}
|
|
},
|
|
|
|
/**
|
|
Returns the `<td>` Node from the given row and column index. Alternately,
|
|
the `seed` can be a Node. If so, the nearest ancestor cell is returned.
|
|
If the `seed` is a cell, it is returned. If there is no cell at the given
|
|
coordinates, `null` is returned.
|
|
|
|
Optionally, include an offset array or string to return a cell near the
|
|
cell identified by the `seed`. The offset can be an array containing the
|
|
number of rows to shift followed by the number of columns to shift, or one
|
|
of "above", "below", "next", or "previous".
|
|
|
|
<pre><code>// Previous cell in the previous row
|
|
var cell = table.getCell(e.target, [-1, -1]);
|
|
|
|
// Next cell
|
|
var cell = table.getCell(e.target, 'next');
|
|
var cell = table.getCell(e.taregt, [0, 1];</pre></code>
|
|
|
|
This is actually just a pass through to the `view` instance's method
|
|
by the same name.
|
|
|
|
@method getCell
|
|
@param {Number[]|Node} seed Array of row and column indexes, or a Node that
|
|
is either the cell itself or a descendant of one.
|
|
@param {Number[]|String} [shift] Offset by which to identify the returned
|
|
cell Node
|
|
@return {Node}
|
|
@since 3.5.0
|
|
**/
|
|
getCell: function (/* seed, shift */) {
|
|
return this.view && this.view.getCell &&
|
|
this.view.getCell.apply(this.view, arguments);
|
|
},
|
|
|
|
/**
|
|
Returns the `<tr>` Node from the given row index, Model, or Model's
|
|
`clientId`. If the rows haven't been rendered yet, or if the row can't be
|
|
found by the input, `null` is returned.
|
|
|
|
This is actually just a pass through to the `view` instance's method
|
|
by the same name.
|
|
|
|
@method getRow
|
|
@param {Number|String|Model} id Row index, Model instance, or clientId
|
|
@return {Node}
|
|
@since 3.5.0
|
|
**/
|
|
getRow: function (/* id */) {
|
|
return this.view && this.view.getRow &&
|
|
this.view.getRow.apply(this.view, arguments);
|
|
},
|
|
|
|
/**
|
|
Updates the `_displayColumns` property.
|
|
|
|
@method _afterDisplayColumnsChange
|
|
@param {EventFacade} e The `columnsChange` event
|
|
@protected
|
|
@since 3.6.0
|
|
**/
|
|
// FIXME: This is a kludge for back compat with features that reference
|
|
// _displayColumns. They should be updated to TableView plugins.
|
|
_afterDisplayColumnsChange: function (e) {
|
|
this._extractDisplayColumns(e.newVal || []);
|
|
},
|
|
|
|
/**
|
|
Attaches subscriptions to relay core change events to the view.
|
|
|
|
@method bindUI
|
|
@protected
|
|
@since 3.6.0
|
|
**/
|
|
bindUI: function () {
|
|
this._eventHandles.relayCoreChanges = this.after(
|
|
['columnsChange',
|
|
'dataChange',
|
|
'summaryChange',
|
|
'captionChange',
|
|
'widthChange'],
|
|
Y.bind('_relayCoreAttrChange', this));
|
|
},
|
|
|
|
/**
|
|
The default behavior of the `renderView` event. Calls `render()` on the
|
|
`View` instance on the event.
|
|
|
|
@method _defRenderViewFn
|
|
@param {EventFacade} e The `renderView` event
|
|
@protected
|
|
**/
|
|
_defRenderViewFn: function (e) {
|
|
e.view.render();
|
|
},
|
|
|
|
/**
|
|
Processes the full column array, distilling the columns down to those that
|
|
correspond to cell data columns.
|
|
|
|
@method _extractDisplayColumns
|
|
@param {Object[]} columns The full set of table columns
|
|
@protected
|
|
**/
|
|
// FIXME: this is a kludge for back compat, duplicating logic in the
|
|
// tableView
|
|
_extractDisplayColumns: function (columns) {
|
|
var displayColumns = [];
|
|
|
|
function process(cols) {
|
|
var i, len, col;
|
|
|
|
for (i = 0, len = cols.length; i < len; ++i) {
|
|
col = cols[i];
|
|
|
|
if (Y.Lang.isArray(col.children)) {
|
|
process(col.children);
|
|
} else {
|
|
displayColumns.push(col);
|
|
}
|
|
}
|
|
}
|
|
|
|
process(columns);
|
|
|
|
/**
|
|
Array of the columns that correspond to those with value cells in the
|
|
data rows. Excludes colspan header columns (configured with `children`).
|
|
|
|
@property _displayColumns
|
|
@type {Object[]}
|
|
@since 3.5.0
|
|
**/
|
|
this._displayColumns = displayColumns;
|
|
},
|
|
|
|
/**
|
|
Sets up the instance's events.
|
|
|
|
@method initializer
|
|
@param {Object} [config] Configuration object passed at construction
|
|
@protected
|
|
@since 3.6.0
|
|
**/
|
|
initializer: function () {
|
|
this.publish('renderView', {
|
|
defaultFn: Y.bind('_defRenderViewFn', this)
|
|
});
|
|
|
|
// Have to use get('columns'), not config.columns because the setter
|
|
// needs to transform string columns to objects.
|
|
this._extractDisplayColumns(this.get('columns') || []);
|
|
|
|
// FIXME: kludge for back compat of features that reference
|
|
// _displayColumns on the instance. They need to be updated to
|
|
// TableView plugins, most likely.
|
|
this.after('columnsChange', Y.bind('_afterDisplayColumnsChange', this));
|
|
},
|
|
|
|
/**
|
|
Relays attribute changes to the instance's `view`.
|
|
|
|
@method _relayCoreAttrChange
|
|
@param {EventFacade} e The change event
|
|
@protected
|
|
@since 3.6.0
|
|
**/
|
|
_relayCoreAttrChange: function (e) {
|
|
var attr = (e.attrName === 'data') ? 'modelList' : e.attrName;
|
|
|
|
this.view.set(attr, e.newVal);
|
|
},
|
|
|
|
/**
|
|
Instantiates the configured `view` class that will be responsible for
|
|
setting up the View class.
|
|
|
|
@method @renderUI
|
|
@protected
|
|
@since 3.6.0
|
|
**/
|
|
renderUI: function () {
|
|
var self = this,
|
|
View = this.get('view');
|
|
|
|
if (View) {
|
|
this.view = new View(
|
|
Y.merge(
|
|
this.getAttrs(),
|
|
{
|
|
host : this,
|
|
container: this.get('contentBox'),
|
|
modelList: this.data
|
|
},
|
|
this.get('viewConfig')));
|
|
|
|
// For back compat, share the view instances and primary nodes
|
|
// on this instance.
|
|
// TODO: Remove this?
|
|
if (!this._eventHandles.legacyFeatureProps) {
|
|
this._eventHandles.legacyFeatureProps = this.view.after({
|
|
renderHeader: function (e) {
|
|
self.head = e.view;
|
|
self._theadNode = e.view.theadNode;
|
|
// TODO: clean up the repetition.
|
|
// This is here so that subscribers to renderHeader etc
|
|
// have access to this._tableNode from the DT instance
|
|
self._tableNode = e.view.get('container');
|
|
},
|
|
renderFooter: function (e) {
|
|
self.foot = e.view;
|
|
self._tfootNode = e.view.tfootNode;
|
|
self._tableNode = e.view.get('container');
|
|
},
|
|
renderBody: function (e) {
|
|
self.body = e.view;
|
|
self._tbodyNode = e.view.tbodyNode;
|
|
self._tableNode = e.view.get('container');
|
|
},
|
|
// FIXME: guarantee that the properties are available, even
|
|
// if the configured (or omitted) views don't create them
|
|
renderTable: function () {
|
|
var contentBox = this.get('container');
|
|
|
|
self._tableNode = this.tableNode ||
|
|
contentBox.one('.' + this.getClassName('table') +
|
|
', table');
|
|
|
|
// FIXME: _captionNode isn't available until after
|
|
// renderTable unless in the renderX subs I look for
|
|
// it under the container's parentNode (to account for
|
|
// scroll breaking out the caption table).
|
|
self._captionNode = this.captionNode ||
|
|
contentBox.one('caption');
|
|
|
|
if (!self._theadNode) {
|
|
self._theadNode = contentBox.one(
|
|
'.' + this.getClassName('columns') + ', thead');
|
|
}
|
|
|
|
if (!self._tbodyNode) {
|
|
self._tbodyNode = contentBox.one(
|
|
'.' + this.getClassName('data') + ', tbody');
|
|
}
|
|
|
|
if (!self._tfootNode) {
|
|
self._tfootNode = contentBox.one(
|
|
'.' + this.getClassName('footer') + ', tfoot');
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
// To *somewhat* preserve table.on('renderHeader', fn) in the
|
|
// form of table.on('table:renderHeader', fn), because I couldn't
|
|
// figure out another option.
|
|
this.view.addTarget(this);
|
|
}
|
|
},
|
|
|
|
/**
|
|
Fires the `renderView` event, delegating UI updates to the configured View.
|
|
|
|
@method syncUI
|
|
@since 3.5.0
|
|
**/
|
|
syncUI: function () {
|
|
if (this.view) {
|
|
this.fire('renderView', { view: this.view });
|
|
}
|
|
},
|
|
|
|
/**
|
|
Verifies the input value is a function with a `render` method on its
|
|
prototype. `null` is also accepted to remove the default View.
|
|
|
|
@method _validateView
|
|
@protected
|
|
@since 3.5.0
|
|
**/
|
|
_validateView: function (val) {
|
|
// TODO support View instances?
|
|
return val === null || (Y.Lang.isFunction(val) && val.prototype.render);
|
|
}
|
|
}, {
|
|
ATTRS: {
|
|
/**
|
|
The View class used to render the `<table>` into the Widget's
|
|
`contentBox`. This View can handle the entire table rendering itself
|
|
or delegate to other Views.
|
|
|
|
It is not strictly necessary that the class function assigned here be
|
|
a View subclass. It must however have a `render()` method.
|
|
|
|
When the DataTable is rendered, an instance of this View will be
|
|
created and its `render()` method called. The View instance will be
|
|
assigned to the DataTable instance's `view` property.
|
|
|
|
@attribute view
|
|
@type {Function}
|
|
@default Y.DataTable.TableView
|
|
@since 3.6.0
|
|
**/
|
|
view: {
|
|
value: Y.DataTable.TableView,
|
|
validator: '_validateView'
|
|
},
|
|
|
|
/**
|
|
Configuration object passed to the class constructor in `view`
|
|
during render.
|
|
|
|
@attribute viewConfig
|
|
@type {Object}
|
|
@default undefined (initially unset)
|
|
@protected
|
|
@since 3.6.0
|
|
**/
|
|
viewConfig: {}
|
|
|
|
/**
|
|
If the View class assigned to the DataTable's `view` attribute supports
|
|
it, this class will be used for rendering the contents of the
|
|
`<thead>`—the column headers for the table.
|
|
|
|
Similar to `view`, the instance of this View will be assigned to the
|
|
DataTable instance's `head` property.
|
|
|
|
It is not strictly necessary that the class function assigned here be
|
|
a View subclass. It must however have a `render()` method.
|
|
|
|
@attribute headerView
|
|
@type {Function|Object}
|
|
@default Y.DataTable.HeaderView
|
|
@since 3.5.0
|
|
**/
|
|
/*
|
|
headerView: {
|
|
value: Y.DataTable.HeaderView,
|
|
validator: '_validateView'
|
|
},
|
|
*/
|
|
|
|
/**
|
|
Configuration object passed to the class constructor in `headerView`
|
|
during render.
|
|
|
|
@attribute headerConfig
|
|
@type {Object}
|
|
@default undefined (initially unset)
|
|
@protected
|
|
@since 3.6.0
|
|
**/
|
|
//headConfig: {},
|
|
|
|
/**
|
|
If the View class assigned to the DataTable's `view` attribute supports
|
|
it, this class will be used for rendering the contents of the `<tfoot>`.
|
|
|
|
Similar to `view`, the instance of this View will be assigned to the
|
|
DataTable instance's `foot` property.
|
|
|
|
It is not strictly necessary that the class function assigned here be
|
|
a View subclass. It must however have a `render()` method.
|
|
|
|
@attribute footerView
|
|
@type {Function|Object}
|
|
@since 3.5.0
|
|
**/
|
|
/*
|
|
footerView: {
|
|
validator: '_validateView'
|
|
},
|
|
*/
|
|
|
|
/**
|
|
Configuration object passed to the class constructor in `footerView`
|
|
during render.
|
|
|
|
@attribute footerConfig
|
|
@type {Object}
|
|
@default undefined (initially unset)
|
|
@protected
|
|
@since 3.6.0
|
|
**/
|
|
//footerConfig: {},
|
|
|
|
/**
|
|
If the View class assigned to the DataTable's `view` attribute supports
|
|
it, this class will be used for rendering the contents of the `<tbody>`
|
|
including all data rows.
|
|
|
|
Similar to `view`, the instance of this View will be assigned to the
|
|
DataTable instance's `body` property.
|
|
|
|
It is not strictly necessary that the class function assigned here be
|
|
a View subclass. It must however have a `render()` method.
|
|
|
|
@attribute bodyView
|
|
@type {Function}
|
|
@default Y.DataTable.BodyView
|
|
@since 3.5.0
|
|
**/
|
|
/*
|
|
bodyView: {
|
|
value: Y.DataTable.BodyView,
|
|
validator: '_validateView'
|
|
},
|
|
*/
|
|
|
|
/**
|
|
Configuration object passed to the class constructor in `bodyView`
|
|
during render.
|
|
|
|
@attribute bodyConfig
|
|
@type {Object}
|
|
@default undefined (initially unset)
|
|
@protected
|
|
@since 3.6.0
|
|
**/
|
|
//bodyConfig: {}
|
|
}
|
|
});
|
|
|
|
// The DataTable API docs are above DataTable.Base docs.
|
|
Y.DataTable = Y.mix(
|
|
Y.Base.create('datatable', Y.DataTable.Base, []), // Create the class
|
|
Y.DataTable); // Migrate static and namespaced classes
|
|
|
|
|
|
}, '3.17.2', {
|
|
"requires": [
|
|
"datatable-core",
|
|
"datatable-table",
|
|
"datatable-head",
|
|
"datatable-body",
|
|
"base-build",
|
|
"widget"
|
|
],
|
|
"skinnable": true
|
|
});
|
|
|