weekly release 2.4dev
[moodle.git] / lib / yuilib / 3.7.1 / build / datatable-base / datatable-base-debug.js
blob639756898dcb5fca8864e02b96e9b633241f3a82
1 /*
2 YUI 3.7.1 (build 5627)
3 Copyright 2012 Yahoo! Inc. All rights reserved.
4 Licensed under the BSD License.
5 http://yuilibrary.com/license/
6 */
7 YUI.add('datatable-base', function (Y, NAME) {
9 /**
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
15 `Y.DataTable.Base`.
17 @module datatable
18 @submodule datatable-base
19 @main datatable
20 @since 3.5.0
21 **/
23 // DataTable API docs included before DataTable.Base to make yuidoc work
24 /**
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
28 functionality.
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.
35 <pre><code>
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({
40     columns: [
41         { type: 'checkbox', defaultChecked: true },
42         { key: 'firstName', sortable: true, resizable: true },
43         { key: 'lastName', sortable: true },
44         { key: 'role', formatter: toRoleName }
45     ],
46     data: {
47         source: 'http://myserver.com/service/json',
48         type: 'json',
49         schema: {
50             resultListLocator: 'results.users',
51             fields: [
52                 'username',
53                 'firstName',
54                 'lastName',
55                 { key: 'role', type: 'number' }
56             ]
57         }
58     },
59     recordType: UserModel,
60     pagedData: {
61         location: 'footer',
62         pageSizes: [20, 50, 'all'],
63         rowsPerPage: 20,
64         pageLinks: 5
65     },
66     editable: true
67 });
68 </code></pre>
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:
89 <pre><code>
90 // Basic
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)
97 var columns = [
98     {
99         label: 'Name',
100         children: [
101             { key: 'firstName' },
102             { key: 'lastName' }
103         ]
104     },
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)
110 var columns = [
111     {
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';
117             }
118             return o.data.lastName + ', ' + o.data.firstName;
119         }
120     },
121     'age'
124 // Columns that include feature configurations (for illustration; not all
125 // features are available today).
126 var columns = [
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>' }
132 </code></pre>
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
140    instances)
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.
155 ### Rendering
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
166 rendered.
168 @class DataTable
169 @extends DataTable.Base
170 @since 3.5.0
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
179 are loaded.
181 Example usage might look like this:
183 <pre><code>
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'],
191     data: [
192         { firstName: 'Frank', lastName: 'Zappa', age: 71 },
193         { firstName: 'Frank', lastName: 'Lloyd Wright', age: 144 },
194         { firstName: 'Albert', lastName: 'Einstein', age: 132 },
195         ...
196     ]
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'],
204     data: [
205         { firstName: 'Frank', lastName: 'Zappa', age: 71 },
206         { firstName: 'Frank', lastName: 'Lloyd Wright', age: 144 },
207         { firstName: 'Albert', lastName: 'Einstein', age: 132 },
208         ...
209     ]
212 table.render('#in-here');
213 </code></pre>
215 DataTable.Base is built from DataTable.Core, and sets the default `view`
216 to `Y.DataTable.TableView`.
218 @class Base
219 @extends Widget
220 @uses DataTable.Core
221 @namespace DataTable
222 @since 3.5.0
224 Y.DataTable.Base = Y.Base.create('datatable', Y.Widget, [Y.DataTable.Core], {
226     /**
227     Pass through to `delegate()` called from the `contentBox`.
229     @method delegate
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
239     @since 3.5.0
240     **/
241     delegate: function () {
242         var contentBox = this.get('contentBox');
244         return contentBox.delegate.apply(contentBox, arguments);
245     },
247     /**
248     Destroys the table `View` if it's been created.
250     @method destructor
251     @protected
252     @since 3.6.0
253     **/
254     destructor: function () {
255         if (this.view) {
256             this.view.destroy();
257         }
258     },
260     /**
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]);
274     // Next cell
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
279     by the same name.
281     @method getCell
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
285         cell Node
286     @return {Node}
287     @since 3.5.0
288     **/
289     getCell: function (seed, shift) {
290         return this.view && this.view.getCell &&
291             this.view.getCell.apply(this.view, arguments);
292     },
294     /**
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
300     by the same name.
302     @method getRow
303     @param {Number|String|Model} id Row index, Model instance, or clientId
304     @return {Node}
305     @since 3.5.0
306     **/
307     getRow: function (id) {
308         return this.view && this.view.getRow &&
309             this.view.getRow.apply(this.view, arguments);
310     },
312     /**
313     Updates the `_displayColumns` property.
315     @method _afterDisplayColumnsChange
316     @param {EventFacade} e The `columnsChange` event
317     @protected
318     @since 3.6.0
319     **/
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 || []);
324     },
326     /**
327     Attaches subscriptions to relay core change events to the view.
329     @method bindUI
330     @protected
331     @since 3.6.0
332     **/
333     bindUI: function () {
334         this._eventHandles.relayCoreChanges = this.after(
335             ['columnsChange',
336              'dataChange',
337              'summaryChange',
338              'captionChange',
339              'widthChange'],
340             Y.bind('_relayCoreAttrChange', this));
341     },
343     /**
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
349     @protected
350     **/
351     _defRenderViewFn: function (e) {
352         e.view.render();
353     },
355     /**
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
361     @protected
362     **/
363     // FIXME: this is a kludge for back compat, duplicating logic in the
364     // tableView
365     _extractDisplayColumns: function (columns) {
366         var displayColumns = [];
368         function process(cols) {
369             var i, len, col;
371             for (i = 0, len = cols.length; i < len; ++i) {
372                 col = cols[i];
374                 if (Y.Lang.isArray(col.children)) {
375                     process(col.children);
376                 } else {
377                     displayColumns.push(col);
378                 }
379             }
380         }
382         process(columns);
384         /**
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
389         @type {Object[]}
390         @since 3.5.0
391         **/
392         this._displayColumns = displayColumns;
393     },
395     /**
396     Sets up the instance's events.
398     @method initializer
399     @param {Object} [config] Configuration object passed at construction
400     @protected
401     @since 3.6.0
402     **/
403     initializer: function () {
404         this.publish('renderView', {
405             defaultFn: Y.bind('_defRenderViewFn', this)
406         });
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));
416     },
418     /**
419     Relays attribute changes to the instance's `view`.
421     @method _relayCoreAttrChange
422     @param {EventFacade} e The change event
423     @protected
424     @since 3.6.0
425     **/
426     _relayCoreAttrChange: function (e) {
427         var attr = (e.attrName === 'data') ? 'modelList' : e.attrName;
429         this.view.set(attr, e.newVal);
430     },
432     /**
433     Instantiates the configured `view` class that will be responsible for
434     setting up the View class.
436     @method @renderUI
437     @protected
438     @since 3.6.0
439     **/
440     renderUI: function () {
441         var self = this,
442             View = this.get('view');
444         if (View) {
445             this.view = new View(
446                 Y.merge(
447                     this.getAttrs(),
448                     {
449                         host     : this,
450                         container: this.get('contentBox'),
451                         modelList: this.data
452                     },
453                     this.get('viewConfig')));
455             // For back compat, share the view instances and primary nodes
456             // on this instance.
457             // TODO: Remove this?
458             if (!this._eventHandles.legacyFeatureProps) {
459                 this._eventHandles.legacyFeatureProps = this.view.after({
460                     renderHeader: function (e) {
461                         self.head = e.view;
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');
467                     },
468                     renderFooter: function (e) {
469                         self.foot = e.view;
470                         self._tfootNode = e.view.tfootNode;
471                         self._tableNode = e.view.get('container');
472                     },
473                     renderBody: function (e) {
474                         self.body = e.view;
475                         self._tbodyNode = e.view.tbodyNode;
476                         self._tableNode = e.view.get('container');
477                     },
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') +
485                                            ', 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');
497                         }
499                         if (!self._tbodyNode) {
500                             self._tbodyNode = contentBox.one(
501                                 '.' + this.getClassName('data') + ', tbody');
502                         }
504                         if (!self._tfootNode) {
505                             self._tfootNode = contentBox.one(
506                                 '.' + this.getClassName('footer') + ', tfoot');
507                         }
508                     }
509                 });
510             }
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);
516         }
517     },
519     /**
520     Fires the `renderView` event, delegating UI updates to the configured View.
522     @method syncUI
523     @since 3.5.0
524     **/
525     syncUI: function () {
526         if (this.view) {
527             this.fire('renderView', { view: this.view });
528         }
529     },
531     /**
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
536     @protected
537     @since 3.5.0
538     **/
539     _validateView: function (val) {
540         // TODO support View instances?
541         return val === null || (Y.Lang.isFunction(val) && val.prototype.render);
542     }
543 }, {
544     ATTRS: {
545         /**
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.
557         @attribute view
558         @type {Function}
559         @default Y.DataTable.TableView
560         @since 3.6.0
561         **/
562         view: {
563             value: Y.DataTable.TableView,
564             validator: '_validateView'
565         },
567         /**
568         Configuration object passed to the class constructor in `view`
569         during render.
571         @attribute viewConfig
572         @type {Object}
573         @default undefined (initially unset)
574         @protected
575         @since 3.6.0
576         **/
577         viewConfig: {}
579         /**
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>`&mdash;the column headers for the table.
583         
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
593         @since 3.5.0
594         **/
595         /*
596         headerView: {
597             value: Y.DataTable.HeaderView,
598             validator: '_validateView'
599         },
600         */
602         /**
603         Configuration object passed to the class constructor in `headerView`
604         during render.
606         @attribute headerConfig
607         @type {Object}
608         @default undefined (initially unset)
609         @protected
610         @since 3.6.0
611         **/
612         //headConfig: {},
614         /**
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>`.
617         
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}
626         @since 3.5.0
627         **/
628         /*
629         footerView: {
630             validator: '_validateView'
631         },
632         */
634         /**
635         Configuration object passed to the class constructor in `footerView`
636         during render.
638         @attribute footerConfig
639         @type {Object}
640         @default undefined (initially unset)
641         @protected
642         @since 3.6.0
643         **/
644         //footerConfig: {},
646         /**
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.
650         
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.
657         @attribute bodyView
658         @type {Function}
659         @default Y.DataTable.BodyView
660         @since 3.5.0
661         **/
662         /*
663         bodyView: {
664             value: Y.DataTable.BodyView,
665             validator: '_validateView'
666         },
667         */
669         /**
670         Configuration object passed to the class constructor in `bodyView`
671         during render.
673         @attribute bodyConfig
674         @type {Object}
675         @default undefined (initially unset)
676         @protected
677         @since 3.6.0
678         **/
679         //bodyConfig: {}
680     }
683 // The DataTable API docs are above DataTable.Base docs.
684 Y.DataTable = Y.mix(
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});