3 Copyright 2012 Yahoo! Inc. All rights reserved.
4 Licensed under the BSD License.
5 http://yuilibrary.com/license/
7 YUI.add('datatable-base', function (Y, NAME) {
10 A Widget for displaying tabular data. The base implementation of DataTable
11 provides the ability to dynamically generate an HTML table from a set of column
12 configurations and row data.
14 Two classes are included in the `datatable-base` module: `Y.DataTable` and
18 @submodule datatable-base
23 // DataTable API docs included before DataTable.Base to make yuidoc work
25 A Widget for displaying tabular data. Before feature modules are `use()`d,
26 this class is functionally equivalent to DataTable.Base. However, feature
27 modules can modify this class in non-destructive ways, expanding the API and
30 This is the primary DataTable class. Out of the box, it provides the ability
31 to dynamically generate an HTML table from a set of column configurations and
32 row data. But feature module inclusion can add table sorting, pagintaion,
33 highlighting, selection, and more.
36 // The functionality of this table would require additional modules be use()d,
37 // but the feature APIs are aggregated onto Y.DataTable.
38 // (Snippet is for illustration. Not all features are available today.)
39 var table = new Y.DataTable({
41 { type: 'checkbox', defaultChecked: true },
42 { key: 'firstName', sortable: true, resizable: true },
43 { key: 'lastName', sortable: true },
44 { key: 'role', formatter: toRoleName }
47 source: 'http://myserver.com/service/json',
50 resultListLocator: 'results.users',
55 { key: 'role', type: 'number' }
59 recordType: UserModel,
62 pageSizes: [20, 50, 'all'],
70 ### Column Configuration
72 The column configurations are set in the form of an array of objects, where
73 each object corresponds to a column. For columns populated directly from the
74 row data, a 'key' property is required to bind the column to that property or
75 attribute in the row data.
77 Not all columns need to relate to row data, nor do all properties or attributes
78 of the row data need to have a corresponding column. However, only those
79 columns included in the `columns` configuration attribute will be rendered.
81 Other column configuration properties are supported by the configured
82 `view`, class as well as any features added by plugins or class extensions.
83 See the description of DataTable.TableView and its subviews
84 DataTable.HeaderView, DataTable.BodyView, and DataTable.FooterView (and other
85 DataTable feature classes) to see what column properties they support.
87 Some examples of column configurations would be:
91 var columns = [{ key: 'firstName' }, { key: 'lastName' }, { key: 'age' }];
93 // For columns without any additional configuration, strings can be used
94 var columns = ['firstName', 'lastName', 'age'];
96 // Multi-row column headers (see DataTable.HeaderView for details)
101 { key: 'firstName' },
105 'age' // mixing and matching objects and strings is ok
108 // Including columns that are not related 1:1 to row data fields/attributes
109 // (See DataTable.BodyView for details)
112 label: 'Name', // Needed for the column header
113 formatter: function (o) {
114 // Fill the column cells with data from firstName and lastName
115 if (o.data.age > 55) {
116 o.className += ' senior';
118 return o.data.lastName + ', ' + o.data.firstName;
124 // Columns that include feature configurations (for illustration; not all
125 // features are available today).
127 { type: 'checkbox', defaultChecked: true },
128 { key: 'firstName', sortable: true, resizable: true, min-width: '300px' },
129 { key: 'lastName', sortable: true, resizable: true, min-width: '300px' },
130 { key: 'age', emptyCellValue: '<em>unknown</em>' }
134 ### Row Data Configuration
136 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:
138 1. An array of simple objects with key:value pairs
139 2. A ModelList of Base-based class instances (presumably Model subclass
142 If an array of objects is passed, it will be translated into a ModelList filled
143 with instances of the class provided to the `recordType` attribute. This
144 attribute can also create a custom Model subclass from an array of field names
145 or an object of attribute configurations. If no `recordType` is provided, one
146 will be created for you from available information (see `_initRecordType`).
147 Providing either your own ModelList instance for `data`, or at least Model
148 class for `recordType`, is the best way to control client-server
149 synchronization when modifying data on the client side.
151 The ModelList instance that manages the table's data is available in the `data`
152 property on the DataTable instance.
157 Table rendering is a collaborative process between the DataTable and its
158 configured `view`. The DataTable creates an instance of the configured `view`
159 (DataTable.TableView by default), and calls its `render()` method.
160 DataTable.TableView, for instance, then creates the `<table>` and `<caption>`,
161 then delegates the rendering of the specific sections of the table to subviews,
162 which can be configured as `headerView`, `bodyView`, and `footerView`.
163 DataTable.TableView defaults the `headerView` to DataTable.HeaderView and the
164 `bodyView` to DataTable.BodyView, but leaves the `footerView` unassigned.
165 Setting any subview to `null` will result in that table section not being
169 @extends DataTable.Base
173 // DataTable API docs included before DataTable.Base to make yuidoc work
175 The baseline implementation of a DataTable. This class should be used
176 primarily as a superclass for a custom DataTable with a specific set of
177 features. Because features can be composed onto `Y.DataTable`, custom
178 subclasses of DataTable.Base will remain unmodified when new feature modules
181 Example usage might look like this:
184 // Custom subclass with only sorting and mutability added. If other datatable
185 // feature modules are loaded, this class will not be affected.
186 var MyTableClass = Y.Base.create('table', Y.DataTable.Base,
187 [ Y.DataTable.Sortable, Y.DataTable.Mutable ]);
189 var table = new MyTableClass({
190 columns: ['firstName', 'lastName', 'age'],
192 { firstName: 'Frank', lastName: 'Zappa', age: 71 },
193 { firstName: 'Frank', lastName: 'Lloyd Wright', age: 144 },
194 { firstName: 'Albert', lastName: 'Einstein', age: 132 },
199 table.render('#over-there');
201 // DataTable.Base can be instantiated if a featureless table is needed.
202 var table = new Y.DataTable.Base({
203 columns: ['firstName', 'lastName', 'age'],
205 { firstName: 'Frank', lastName: 'Zappa', age: 71 },
206 { firstName: 'Frank', lastName: 'Lloyd Wright', age: 144 },
207 { firstName: 'Albert', lastName: 'Einstein', age: 132 },
212 table.render('#in-here');
215 DataTable.Base is built from DataTable.Core, and sets the default `view`
216 to `Y.DataTable.TableView`.
224 Y.DataTable.Base = Y.Base.create('datatable', Y.Widget, [Y.DataTable.Core], {
227 Pass through to `delegate()` called from the `contentBox`.
230 @param type {String} the event type to delegate
231 @param fn {Function} the callback function to execute. This function
232 will be provided the event object for the delegated event.
233 @param spec {String|Function} a selector that must match the target of the
234 event or a function to test target and its parents for a match
235 @param context {Object} optional argument that specifies what 'this' refers to
236 @param args* {any} 0..n additional arguments to pass on to the callback
237 function. These arguments will be added after the event object.
238 @return {EventHandle} the detach handle
241 delegate: function () {
242 var contentBox = this.get('contentBox');
244 return contentBox.delegate.apply(contentBox, arguments);
248 Destroys the table `View` if it's been created.
254 destructor: function () {
261 Returns the `<td>` Node from the given row and column index. Alternately,
262 the `seed` can be a Node. If so, the nearest ancestor cell is returned.
263 If the `seed` is a cell, it is returned. If there is no cell at the given
264 coordinates, `null` is returned.
266 Optionally, include an offset array or string to return a cell near the
267 cell identified by the `seed`. The offset can be an array containing the
268 number of rows to shift followed by the number of columns to shift, or one
269 of "above", "below", "next", or "previous".
271 <pre><code>// Previous cell in the previous row
272 var cell = table.getCell(e.target, [-1, -1]);
275 var cell = table.getCell(e.target, 'next');
276 var cell = table.getCell(e.taregt, [0, 1];</pre></code>
278 This is actually just a pass through to the `view` instance's method
282 @param {Number[]|Node} seed Array of row and column indexes, or a Node that
283 is either the cell itself or a descendant of one.
284 @param {Number[]|String} [shift] Offset by which to identify the returned
289 getCell: function (seed, shift) {
290 return this.view && this.view.getCell &&
291 this.view.getCell.apply(this.view, arguments);
295 Returns the `<tr>` Node from the given row index, Model, or Model's
296 `clientId`. If the rows haven't been rendered yet, or if the row can't be
297 found by the input, `null` is returned.
299 This is actually just a pass through to the `view` instance's method
303 @param {Number|String|Model} id Row index, Model instance, or clientId
307 getRow: function (id) {
308 return this.view && this.view.getRow &&
309 this.view.getRow.apply(this.view, arguments);
313 Updates the `_displayColumns` property.
315 @method _afterDisplayColumnsChange
316 @param {EventFacade} e The `columnsChange` event
320 // FIXME: This is a kludge for back compat with features that reference
321 // _displayColumns. They should be updated to TableView plugins.
322 _afterDisplayColumnsChange: function (e) {
323 this._extractDisplayColumns(e.newVal || []);
327 Attaches subscriptions to relay core change events to the view.
333 bindUI: function () {
334 this._eventHandles.relayCoreChanges = this.after(
340 Y.bind('_relayCoreAttrChange', this));
344 The default behavior of the `renderView` event. Calls `render()` on the
345 `View` instance on the event.
347 @method _defRenderViewFn
348 @param {EventFacade} e The `renderView` event
351 _defRenderViewFn: function (e) {
356 Processes the full column array, distilling the columns down to those that
357 correspond to cell data columns.
359 @method _extractDisplayColumns
360 @param {Object[]} columns The full set of table columns
363 // FIXME: this is a kludge for back compat, duplicating logic in the
365 _extractDisplayColumns: function (columns) {
366 var displayColumns = [];
368 function process(cols) {
371 for (i = 0, len = cols.length; i < len; ++i) {
374 if (Y.Lang.isArray(col.children)) {
375 process(col.children);
377 displayColumns.push(col);
385 Array of the columns that correspond to those with value cells in the
386 data rows. Excludes colspan header columns (configured with `children`).
388 @property _displayColumns
392 this._displayColumns = displayColumns;
396 Sets up the instance's events.
399 @param {Object} [config] Configuration object passed at construction
403 initializer: function () {
404 this.publish('renderView', {
405 defaultFn: Y.bind('_defRenderViewFn', this)
408 // Have to use get('columns'), not config.columns because the setter
409 // needs to transform string columns to objects.
410 this._extractDisplayColumns(this.get('columns') || []);
412 // FIXME: kludge for back compat of features that reference
413 // _displayColumns on the instance. They need to be updated to
414 // TableView plugins, most likely.
415 this.after('columnsChange', Y.bind('_afterDisplayColumnsChange', this));
419 Relays attribute changes to the instance's `view`.
421 @method _relayCoreAttrChange
422 @param {EventFacade} e The change event
426 _relayCoreAttrChange: function (e) {
427 var attr = (e.attrName === 'data') ? 'modelList' : e.attrName;
429 this.view.set(attr, e.newVal);
433 Instantiates the configured `view` class that will be responsible for
434 setting up the View class.
440 renderUI: function () {
442 View = this.get('view');
445 this.view = new View(
450 container: this.get('contentBox'),
453 this.get('viewConfig')));
455 // For back compat, share the view instances and primary nodes
457 // TODO: Remove this?
458 if (!this._eventHandles.legacyFeatureProps) {
459 this._eventHandles.legacyFeatureProps = this.view.after({
460 renderHeader: function (e) {
462 self._theadNode = e.view.theadNode;
463 // TODO: clean up the repetition.
464 // This is here so that subscribers to renderHeader etc
465 // have access to this._tableNode from the DT instance
466 self._tableNode = e.view.get('container');
468 renderFooter: function (e) {
470 self._tfootNode = e.view.tfootNode;
471 self._tableNode = e.view.get('container');
473 renderBody: function (e) {
475 self._tbodyNode = e.view.tbodyNode;
476 self._tableNode = e.view.get('container');
478 // FIXME: guarantee that the properties are available, even
479 // if the configured (or omitted) views don't create them
480 renderTable: function (e) {
481 var contentBox = this.get('container');
483 self._tableNode = this.tableNode ||
484 contentBox.one('.' + this.getClassName('table') +
487 // FIXME: _captionNode isn't available until after
488 // renderTable unless in the renderX subs I look for
489 // it under the container's parentNode (to account for
490 // scroll breaking out the caption table).
491 self._captionNode = this.captionNode ||
492 contentBox.one('caption');
494 if (!self._theadNode) {
495 self._theadNode = contentBox.one(
496 '.' + this.getClassName('columns') + ', thead');
499 if (!self._tbodyNode) {
500 self._tbodyNode = contentBox.one(
501 '.' + this.getClassName('data') + ', tbody');
504 if (!self._tfootNode) {
505 self._tfootNode = contentBox.one(
506 '.' + this.getClassName('footer') + ', tfoot');
512 // To *somewhat* preserve table.on('renderHeader', fn) in the
513 // form of table.on('table:renderHeader', fn), because I couldn't
514 // figure out another option.
515 this.view.addTarget(this);
520 Fires the `renderView` event, delegating UI updates to the configured View.
525 syncUI: function () {
527 this.fire('renderView', { view: this.view });
532 Verifies the input value is a function with a `render` method on its
533 prototype. `null` is also accepted to remove the default View.
535 @method _validateView
539 _validateView: function (val) {
540 // TODO support View instances?
541 return val === null || (Y.Lang.isFunction(val) && val.prototype.render);
546 The View class used to render the `<table>` into the Widget's
547 `contentBox`. This View can handle the entire table rendering itself
548 or delegate to other Views.
550 It is not strictly necessary that the class function assigned here be
551 a View subclass. It must however have a `render()` method.
553 When the DataTable is rendered, an instance of this View will be
554 created and its `render()` method called. The View instance will be
555 assigned to the DataTable instance's `view` property.
559 @default Y.DataTable.TableView
563 value: Y.DataTable.TableView,
564 validator: '_validateView'
568 Configuration object passed to the class constructor in `view`
571 @attribute viewConfig
573 @default undefined (initially unset)
580 If the View class assigned to the DataTable's `view` attribute supports
581 it, this class will be used for rendering the contents of the
582 `<thead>`—the column headers for the table.
584 Similar to `view`, the instance of this View will be assigned to the
585 DataTable instance's `head` property.
587 It is not strictly necessary that the class function assigned here be
588 a View subclass. It must however have a `render()` method.
590 @attribute headerView
591 @type {Function|Object}
592 @default Y.DataTable.HeaderView
597 value: Y.DataTable.HeaderView,
598 validator: '_validateView'
603 Configuration object passed to the class constructor in `headerView`
606 @attribute headerConfig
608 @default undefined (initially unset)
615 If the View class assigned to the DataTable's `view` attribute supports
616 it, this class will be used for rendering the contents of the `<tfoot>`.
618 Similar to `view`, the instance of this View will be assigned to the
619 DataTable instance's `foot` property.
621 It is not strictly necessary that the class function assigned here be
622 a View subclass. It must however have a `render()` method.
624 @attribute footerView
625 @type {Function|Object}
630 validator: '_validateView'
635 Configuration object passed to the class constructor in `footerView`
638 @attribute footerConfig
640 @default undefined (initially unset)
647 If the View class assigned to the DataTable's `view` attribute supports
648 it, this class will be used for rendering the contents of the `<tbody>`
649 including all data rows.
651 Similar to `view`, the instance of this View will be assigned to the
652 DataTable instance's `body` property.
654 It is not strictly necessary that the class function assigned here be
655 a View subclass. It must however have a `render()` method.
659 @default Y.DataTable.BodyView
664 value: Y.DataTable.BodyView,
665 validator: '_validateView'
670 Configuration object passed to the class constructor in `bodyView`
673 @attribute bodyConfig
675 @default undefined (initially unset)
683 // The DataTable API docs are above DataTable.Base docs.
685 Y.Base.create('datatable', Y.DataTable.Base, []), // Create the class
686 Y.DataTable); // Migrate static and namespaced classes
689 }, '3.7.1', {"requires": ["datatable-core", "datatable-table", "datatable-head", "datatable-body", "base-build", "widget"], "skinnable": true});