2 YUI 3.13.0 (build 508226d)
3 Copyright 2013 Yahoo! Inc. All rights reserved.
4 Licensed under the BSD License.
5 http://yuilibrary.com/license/
8 YUI.add('datatable-base', function (Y, NAME) {
11 A Widget for displaying tabular data. The base implementation of DataTable
12 provides the ability to dynamically generate an HTML table from a set of column
13 configurations and row data.
15 Two classes are included in the `datatable-base` module: `Y.DataTable` and
19 @submodule datatable-base
24 // DataTable API docs included before DataTable.Base to make yuidoc work
26 A Widget for displaying tabular data. Before feature modules are `use()`d,
27 this class is functionally equivalent to DataTable.Base. However, feature
28 modules can modify this class in non-destructive ways, expanding the API and
31 This is the primary DataTable class. Out of the box, it provides the ability
32 to dynamically generate an HTML table from a set of column configurations and
33 row data. But feature module inclusion can add table sorting, pagintaion,
34 highlighting, selection, and more.
37 // The functionality of this table would require additional modules be use()d,
38 // but the feature APIs are aggregated onto Y.DataTable.
39 // (Snippet is for illustration. Not all features are available today.)
40 var table = new Y.DataTable({
42 { type: 'checkbox', defaultChecked: true },
43 { key: 'firstName', sortable: true, resizable: true },
44 { key: 'lastName', sortable: true },
45 { key: 'role', formatter: toRoleName }
48 source: 'http://myserver.com/service/json',
51 resultListLocator: 'results.users',
56 { key: 'role', type: 'number' }
60 recordType: UserModel,
63 pageSizes: [20, 50, 'all'],
71 ### Column Configuration
73 The column configurations are set in the form of an array of objects, where
74 each object corresponds to a column. For columns populated directly from the
75 row data, a 'key' property is required to bind the column to that property or
76 attribute in the row data.
78 Not all columns need to relate to row data, nor do all properties or attributes
79 of the row data need to have a corresponding column. However, only those
80 columns included in the `columns` configuration attribute will be rendered.
82 Other column configuration properties are supported by the configured
83 `view`, class as well as any features added by plugins or class extensions.
84 See the description of DataTable.TableView and its subviews
85 DataTable.HeaderView, DataTable.BodyView, and DataTable.FooterView (and other
86 DataTable feature classes) to see what column properties they support.
88 Some examples of column configurations would be:
92 var columns = [{ key: 'firstName' }, { key: 'lastName' }, { key: 'age' }];
94 // For columns without any additional configuration, strings can be used
95 var columns = ['firstName', 'lastName', 'age'];
97 // Multi-row column headers (see DataTable.HeaderView for details)
102 { key: 'firstName' },
106 'age' // mixing and matching objects and strings is ok
109 // Including columns that are not related 1:1 to row data fields/attributes
110 // (See DataTable.BodyView for details)
113 label: 'Name', // Needed for the column header
114 formatter: function (o) {
115 // Fill the column cells with data from firstName and lastName
116 if (o.data.age > 55) {
117 o.className += ' senior';
119 return o.data.lastName + ', ' + o.data.firstName;
125 // Columns that include feature configurations (for illustration; not all
126 // features are available today).
128 { type: 'checkbox', defaultChecked: true },
129 { key: 'firstName', sortable: true, resizable: true, min-width: '300px' },
130 { key: 'lastName', sortable: true, resizable: true, min-width: '300px' },
131 { key: 'age', emptyCellValue: '<em>unknown</em>' }
135 ### Row Data Configuration
137 The `data` configuration attribute is responsible for housing the data objects
138 that will be rendered as rows. You can provide this information in two ways by default:
140 1. An array of simple objects with key:value pairs
141 2. A ModelList of Base-based class instances (presumably Model subclass
144 If an array of objects is passed, it will be translated into a ModelList filled
145 with instances of the class provided to the `recordType` attribute. This
146 attribute can also create a custom Model subclass from an array of field names
147 or an object of attribute configurations. If no `recordType` is provided, one
148 will be created for you from available information (see `_initRecordType`).
149 Providing either your own ModelList instance for `data`, or at least Model
150 class for `recordType`, is the best way to control client-server
151 synchronization when modifying data on the client side.
153 The ModelList instance that manages the table's data is available in the `data`
154 property on the DataTable instance.
159 Table rendering is a collaborative process between the DataTable and its
160 configured `view`. The DataTable creates an instance of the configured `view`
161 (DataTable.TableView by default), and calls its `render()` method.
162 DataTable.TableView, for instance, then creates the `<table>` and `<caption>`,
163 then delegates the rendering of the specific sections of the table to subviews,
164 which can be configured as `headerView`, `bodyView`, and `footerView`.
165 DataTable.TableView defaults the `headerView` to DataTable.HeaderView and the
166 `bodyView` to DataTable.BodyView, but leaves the `footerView` unassigned.
167 Setting any subview to `null` will result in that table section not being
171 @extends DataTable.Base
175 // DataTable API docs included before DataTable.Base to make yuidoc work
177 The baseline implementation of a DataTable. This class should be used
178 primarily as a superclass for a custom DataTable with a specific set of
179 features. Because features can be composed onto `Y.DataTable`, custom
180 subclasses of DataTable.Base will remain unmodified when new feature modules
183 Example usage might look like this:
186 // Custom subclass with only sorting and mutability added. If other datatable
187 // feature modules are loaded, this class will not be affected.
188 var MyTableClass = Y.Base.create('table', Y.DataTable.Base,
189 [ Y.DataTable.Sortable, Y.DataTable.Mutable ]);
191 var table = new MyTableClass({
192 columns: ['firstName', 'lastName', 'age'],
194 { firstName: 'Frank', lastName: 'Zappa', age: 71 },
195 { firstName: 'Frank', lastName: 'Lloyd Wright', age: 144 },
196 { firstName: 'Albert', lastName: 'Einstein', age: 132 },
201 table.render('#over-there');
203 // DataTable.Base can be instantiated if a featureless table is needed.
204 var table = new Y.DataTable.Base({
205 columns: ['firstName', 'lastName', 'age'],
207 { firstName: 'Frank', lastName: 'Zappa', age: 71 },
208 { firstName: 'Frank', lastName: 'Lloyd Wright', age: 144 },
209 { firstName: 'Albert', lastName: 'Einstein', age: 132 },
214 table.render('#in-here');
217 DataTable.Base is built from DataTable.Core, and sets the default `view`
218 to `Y.DataTable.TableView`.
226 Y.DataTable.Base = Y.Base.create('datatable', Y.Widget, [Y.DataTable.Core], {
229 Pass through to `delegate()` called from the `contentBox`.
232 @param type {String} the event type to delegate
233 @param fn {Function} the callback function to execute. This function
234 will be provided the event object for the delegated event.
235 @param spec {String|Function} a selector that must match the target of the
236 event or a function to test target and its parents for a match
237 @param context {Object} optional argument that specifies what 'this' refers to
238 @param args* {any} 0..n additional arguments to pass on to the callback
239 function. These arguments will be added after the event object.
240 @return {EventHandle} the detach handle
243 delegate: function () {
244 var contentBox = this.get('contentBox');
246 return contentBox.delegate.apply(contentBox, arguments);
250 Destroys the table `View` if it's been created.
256 destructor: function () {
263 Returns the `<td>` Node from the given row and column index. Alternately,
264 the `seed` can be a Node. If so, the nearest ancestor cell is returned.
265 If the `seed` is a cell, it is returned. If there is no cell at the given
266 coordinates, `null` is returned.
268 Optionally, include an offset array or string to return a cell near the
269 cell identified by the `seed`. The offset can be an array containing the
270 number of rows to shift followed by the number of columns to shift, or one
271 of "above", "below", "next", or "previous".
273 <pre><code>// Previous cell in the previous row
274 var cell = table.getCell(e.target, [-1, -1]);
277 var cell = table.getCell(e.target, 'next');
278 var cell = table.getCell(e.taregt, [0, 1];</pre></code>
280 This is actually just a pass through to the `view` instance's method
284 @param {Number[]|Node} seed Array of row and column indexes, or a Node that
285 is either the cell itself or a descendant of one.
286 @param {Number[]|String} [shift] Offset by which to identify the returned
291 getCell: function (/* seed, shift */) {
292 return this.view && this.view.getCell &&
293 this.view.getCell.apply(this.view, arguments);
297 Returns the `<tr>` Node from the given row index, Model, or Model's
298 `clientId`. If the rows haven't been rendered yet, or if the row can't be
299 found by the input, `null` is returned.
301 This is actually just a pass through to the `view` instance's method
305 @param {Number|String|Model} id Row index, Model instance, or clientId
309 getRow: function (/* id */) {
310 return this.view && this.view.getRow &&
311 this.view.getRow.apply(this.view, arguments);
315 Updates the `_displayColumns` property.
317 @method _afterDisplayColumnsChange
318 @param {EventFacade} e The `columnsChange` event
322 // FIXME: This is a kludge for back compat with features that reference
323 // _displayColumns. They should be updated to TableView plugins.
324 _afterDisplayColumnsChange: function (e) {
325 this._extractDisplayColumns(e.newVal || []);
329 Attaches subscriptions to relay core change events to the view.
335 bindUI: function () {
336 this._eventHandles.relayCoreChanges = this.after(
342 Y.bind('_relayCoreAttrChange', this));
346 The default behavior of the `renderView` event. Calls `render()` on the
347 `View` instance on the event.
349 @method _defRenderViewFn
350 @param {EventFacade} e The `renderView` event
353 _defRenderViewFn: function (e) {
358 Processes the full column array, distilling the columns down to those that
359 correspond to cell data columns.
361 @method _extractDisplayColumns
362 @param {Object[]} columns The full set of table columns
365 // FIXME: this is a kludge for back compat, duplicating logic in the
367 _extractDisplayColumns: function (columns) {
368 var displayColumns = [];
370 function process(cols) {
373 for (i = 0, len = cols.length; i < len; ++i) {
376 if (Y.Lang.isArray(col.children)) {
377 process(col.children);
379 displayColumns.push(col);
387 Array of the columns that correspond to those with value cells in the
388 data rows. Excludes colspan header columns (configured with `children`).
390 @property _displayColumns
394 this._displayColumns = displayColumns;
398 Sets up the instance's events.
401 @param {Object} [config] Configuration object passed at construction
405 initializer: function () {
406 this.publish('renderView', {
407 defaultFn: Y.bind('_defRenderViewFn', this)
410 // Have to use get('columns'), not config.columns because the setter
411 // needs to transform string columns to objects.
412 this._extractDisplayColumns(this.get('columns') || []);
414 // FIXME: kludge for back compat of features that reference
415 // _displayColumns on the instance. They need to be updated to
416 // TableView plugins, most likely.
417 this.after('columnsChange', Y.bind('_afterDisplayColumnsChange', this));
421 Relays attribute changes to the instance's `view`.
423 @method _relayCoreAttrChange
424 @param {EventFacade} e The change event
428 _relayCoreAttrChange: function (e) {
429 var attr = (e.attrName === 'data') ? 'modelList' : e.attrName;
431 this.view.set(attr, e.newVal);
435 Instantiates the configured `view` class that will be responsible for
436 setting up the View class.
442 renderUI: function () {
444 View = this.get('view');
447 this.view = new View(
452 container: this.get('contentBox'),
455 this.get('viewConfig')));
457 // For back compat, share the view instances and primary nodes
459 // TODO: Remove this?
460 if (!this._eventHandles.legacyFeatureProps) {
461 this._eventHandles.legacyFeatureProps = this.view.after({
462 renderHeader: function (e) {
464 self._theadNode = e.view.theadNode;
465 // TODO: clean up the repetition.
466 // This is here so that subscribers to renderHeader etc
467 // have access to this._tableNode from the DT instance
468 self._tableNode = e.view.get('container');
470 renderFooter: function (e) {
472 self._tfootNode = e.view.tfootNode;
473 self._tableNode = e.view.get('container');
475 renderBody: function (e) {
477 self._tbodyNode = e.view.tbodyNode;
478 self._tableNode = e.view.get('container');
480 // FIXME: guarantee that the properties are available, even
481 // if the configured (or omitted) views don't create them
482 renderTable: function () {
483 var contentBox = this.get('container');
485 self._tableNode = this.tableNode ||
486 contentBox.one('.' + this.getClassName('table') +
489 // FIXME: _captionNode isn't available until after
490 // renderTable unless in the renderX subs I look for
491 // it under the container's parentNode (to account for
492 // scroll breaking out the caption table).
493 self._captionNode = this.captionNode ||
494 contentBox.one('caption');
496 if (!self._theadNode) {
497 self._theadNode = contentBox.one(
498 '.' + this.getClassName('columns') + ', thead');
501 if (!self._tbodyNode) {
502 self._tbodyNode = contentBox.one(
503 '.' + this.getClassName('data') + ', tbody');
506 if (!self._tfootNode) {
507 self._tfootNode = contentBox.one(
508 '.' + this.getClassName('footer') + ', tfoot');
514 // To *somewhat* preserve table.on('renderHeader', fn) in the
515 // form of table.on('table:renderHeader', fn), because I couldn't
516 // figure out another option.
517 this.view.addTarget(this);
522 Fires the `renderView` event, delegating UI updates to the configured View.
527 syncUI: function () {
529 this.fire('renderView', { view: this.view });
534 Verifies the input value is a function with a `render` method on its
535 prototype. `null` is also accepted to remove the default View.
537 @method _validateView
541 _validateView: function (val) {
542 // TODO support View instances?
543 return val === null || (Y.Lang.isFunction(val) && val.prototype.render);
548 The View class used to render the `<table>` into the Widget's
549 `contentBox`. This View can handle the entire table rendering itself
550 or delegate to other Views.
552 It is not strictly necessary that the class function assigned here be
553 a View subclass. It must however have a `render()` method.
555 When the DataTable is rendered, an instance of this View will be
556 created and its `render()` method called. The View instance will be
557 assigned to the DataTable instance's `view` property.
561 @default Y.DataTable.TableView
565 value: Y.DataTable.TableView,
566 validator: '_validateView'
570 Configuration object passed to the class constructor in `view`
573 @attribute viewConfig
575 @default undefined (initially unset)
582 If the View class assigned to the DataTable's `view` attribute supports
583 it, this class will be used for rendering the contents of the
584 `<thead>`—the column headers for the table.
586 Similar to `view`, the instance of this View will be assigned to the
587 DataTable instance's `head` property.
589 It is not strictly necessary that the class function assigned here be
590 a View subclass. It must however have a `render()` method.
592 @attribute headerView
593 @type {Function|Object}
594 @default Y.DataTable.HeaderView
599 value: Y.DataTable.HeaderView,
600 validator: '_validateView'
605 Configuration object passed to the class constructor in `headerView`
608 @attribute headerConfig
610 @default undefined (initially unset)
617 If the View class assigned to the DataTable's `view` attribute supports
618 it, this class will be used for rendering the contents of the `<tfoot>`.
620 Similar to `view`, the instance of this View will be assigned to the
621 DataTable instance's `foot` property.
623 It is not strictly necessary that the class function assigned here be
624 a View subclass. It must however have a `render()` method.
626 @attribute footerView
627 @type {Function|Object}
632 validator: '_validateView'
637 Configuration object passed to the class constructor in `footerView`
640 @attribute footerConfig
642 @default undefined (initially unset)
649 If the View class assigned to the DataTable's `view` attribute supports
650 it, this class will be used for rendering the contents of the `<tbody>`
651 including all data rows.
653 Similar to `view`, the instance of this View will be assigned to the
654 DataTable instance's `body` property.
656 It is not strictly necessary that the class function assigned here be
657 a View subclass. It must however have a `render()` method.
661 @default Y.DataTable.BodyView
666 value: Y.DataTable.BodyView,
667 validator: '_validateView'
672 Configuration object passed to the class constructor in `bodyView`
675 @attribute bodyConfig
677 @default undefined (initially unset)
685 // The DataTable API docs are above DataTable.Base docs.
687 Y.Base.create('datatable', Y.DataTable.Base, []), // Create the class
688 Y.DataTable); // Migrate static and namespaced classes