NOBUG: Fixed file access permissions
[moodle.git] / lib / yuilib / 3.13.0 / datatable-base / datatable-base-debug.js
blobe9ba37b6db5fe9672c422712ca1cc4587164b47a
1 /*
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/
6 */
8 YUI.add('datatable-base', function (Y, NAME) {
10 /**
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
16 `Y.DataTable.Base`.
18 @module datatable
19 @submodule datatable-base
20 @main datatable
21 @since 3.5.0
22 **/
24 // DataTable API docs included before DataTable.Base to make yuidoc work
25 /**
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
29 functionality.
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.
36 <pre><code>
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({
41     columns: [
42         { type: 'checkbox', defaultChecked: true },
43         { key: 'firstName', sortable: true, resizable: true },
44         { key: 'lastName', sortable: true },
45         { key: 'role', formatter: toRoleName }
46     ],
47     data: {
48         source: 'http://myserver.com/service/json',
49         type: 'json',
50         schema: {
51             resultListLocator: 'results.users',
52             fields: [
53                 'username',
54                 'firstName',
55                 'lastName',
56                 { key: 'role', type: 'number' }
57             ]
58         }
59     },
60     recordType: UserModel,
61     pagedData: {
62         location: 'footer',
63         pageSizes: [20, 50, 'all'],
64         rowsPerPage: 20,
65         pageLinks: 5
66     },
67     editable: true
68 });
69 </code></pre>
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:
90 <pre><code>
91 // Basic
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)
98 var columns = [
99     {
100         label: 'Name',
101         children: [
102             { key: 'firstName' },
103             { key: 'lastName' }
104         ]
105     },
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)
111 var columns = [
112     {
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';
118             }
119             return o.data.lastName + ', ' + o.data.firstName;
120         }
121     },
122     'age'
125 // Columns that include feature configurations (for illustration; not all
126 // features are available today).
127 var columns = [
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>' }
133 </code></pre>
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
142    instances)
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.
157 ### Rendering
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
168 rendered.
170 @class DataTable
171 @extends DataTable.Base
172 @since 3.5.0
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
181 are loaded.
183 Example usage might look like this:
185 <pre><code>
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'],
193     data: [
194         { firstName: 'Frank', lastName: 'Zappa', age: 71 },
195         { firstName: 'Frank', lastName: 'Lloyd Wright', age: 144 },
196         { firstName: 'Albert', lastName: 'Einstein', age: 132 },
197         ...
198     ]
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'],
206     data: [
207         { firstName: 'Frank', lastName: 'Zappa', age: 71 },
208         { firstName: 'Frank', lastName: 'Lloyd Wright', age: 144 },
209         { firstName: 'Albert', lastName: 'Einstein', age: 132 },
210         ...
211     ]
214 table.render('#in-here');
215 </code></pre>
217 DataTable.Base is built from DataTable.Core, and sets the default `view`
218 to `Y.DataTable.TableView`.
220 @class Base
221 @extends Widget
222 @uses DataTable.Core
223 @namespace DataTable
224 @since 3.5.0
226 Y.DataTable.Base = Y.Base.create('datatable', Y.Widget, [Y.DataTable.Core], {
228     /**
229     Pass through to `delegate()` called from the `contentBox`.
231     @method delegate
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
241     @since 3.5.0
242     **/
243     delegate: function () {
244         var contentBox = this.get('contentBox');
246         return contentBox.delegate.apply(contentBox, arguments);
247     },
249     /**
250     Destroys the table `View` if it's been created.
252     @method destructor
253     @protected
254     @since 3.6.0
255     **/
256     destructor: function () {
257         if (this.view) {
258             this.view.destroy();
259         }
260     },
262     /**
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]);
276     // Next cell
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
281     by the same name.
283     @method getCell
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
287         cell Node
288     @return {Node}
289     @since 3.5.0
290     **/
291     getCell: function (/* seed, shift */) {
292         return this.view && this.view.getCell &&
293             this.view.getCell.apply(this.view, arguments);
294     },
296     /**
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
302     by the same name.
304     @method getRow
305     @param {Number|String|Model} id Row index, Model instance, or clientId
306     @return {Node}
307     @since 3.5.0
308     **/
309     getRow: function (/* id */) {
310         return this.view && this.view.getRow &&
311             this.view.getRow.apply(this.view, arguments);
312     },
314     /**
315     Updates the `_displayColumns` property.
317     @method _afterDisplayColumnsChange
318     @param {EventFacade} e The `columnsChange` event
319     @protected
320     @since 3.6.0
321     **/
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 || []);
326     },
328     /**
329     Attaches subscriptions to relay core change events to the view.
331     @method bindUI
332     @protected
333     @since 3.6.0
334     **/
335     bindUI: function () {
336         this._eventHandles.relayCoreChanges = this.after(
337             ['columnsChange',
338              'dataChange',
339              'summaryChange',
340              'captionChange',
341              'widthChange'],
342             Y.bind('_relayCoreAttrChange', this));
343     },
345     /**
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
351     @protected
352     **/
353     _defRenderViewFn: function (e) {
354         e.view.render();
355     },
357     /**
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
363     @protected
364     **/
365     // FIXME: this is a kludge for back compat, duplicating logic in the
366     // tableView
367     _extractDisplayColumns: function (columns) {
368         var displayColumns = [];
370         function process(cols) {
371             var i, len, col;
373             for (i = 0, len = cols.length; i < len; ++i) {
374                 col = cols[i];
376                 if (Y.Lang.isArray(col.children)) {
377                     process(col.children);
378                 } else {
379                     displayColumns.push(col);
380                 }
381             }
382         }
384         process(columns);
386         /**
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
391         @type {Object[]}
392         @since 3.5.0
393         **/
394         this._displayColumns = displayColumns;
395     },
397     /**
398     Sets up the instance's events.
400     @method initializer
401     @param {Object} [config] Configuration object passed at construction
402     @protected
403     @since 3.6.0
404     **/
405     initializer: function () {
406         this.publish('renderView', {
407             defaultFn: Y.bind('_defRenderViewFn', this)
408         });
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));
418     },
420     /**
421     Relays attribute changes to the instance's `view`.
423     @method _relayCoreAttrChange
424     @param {EventFacade} e The change event
425     @protected
426     @since 3.6.0
427     **/
428     _relayCoreAttrChange: function (e) {
429         var attr = (e.attrName === 'data') ? 'modelList' : e.attrName;
431         this.view.set(attr, e.newVal);
432     },
434     /**
435     Instantiates the configured `view` class that will be responsible for
436     setting up the View class.
438     @method @renderUI
439     @protected
440     @since 3.6.0
441     **/
442     renderUI: function () {
443         var self = this,
444             View = this.get('view');
446         if (View) {
447             this.view = new View(
448                 Y.merge(
449                     this.getAttrs(),
450                     {
451                         host     : this,
452                         container: this.get('contentBox'),
453                         modelList: this.data
454                     },
455                     this.get('viewConfig')));
457             // For back compat, share the view instances and primary nodes
458             // on this instance.
459             // TODO: Remove this?
460             if (!this._eventHandles.legacyFeatureProps) {
461                 this._eventHandles.legacyFeatureProps = this.view.after({
462                     renderHeader: function (e) {
463                         self.head = e.view;
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');
469                     },
470                     renderFooter: function (e) {
471                         self.foot = e.view;
472                         self._tfootNode = e.view.tfootNode;
473                         self._tableNode = e.view.get('container');
474                     },
475                     renderBody: function (e) {
476                         self.body = e.view;
477                         self._tbodyNode = e.view.tbodyNode;
478                         self._tableNode = e.view.get('container');
479                     },
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') +
487                                            ', 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');
499                         }
501                         if (!self._tbodyNode) {
502                             self._tbodyNode = contentBox.one(
503                                 '.' + this.getClassName('data') + ', tbody');
504                         }
506                         if (!self._tfootNode) {
507                             self._tfootNode = contentBox.one(
508                                 '.' + this.getClassName('footer') + ', tfoot');
509                         }
510                     }
511                 });
512             }
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);
518         }
519     },
521     /**
522     Fires the `renderView` event, delegating UI updates to the configured View.
524     @method syncUI
525     @since 3.5.0
526     **/
527     syncUI: function () {
528         if (this.view) {
529             this.fire('renderView', { view: this.view });
530         }
531     },
533     /**
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
538     @protected
539     @since 3.5.0
540     **/
541     _validateView: function (val) {
542         // TODO support View instances?
543         return val === null || (Y.Lang.isFunction(val) && val.prototype.render);
544     }
545 }, {
546     ATTRS: {
547         /**
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.
559         @attribute view
560         @type {Function}
561         @default Y.DataTable.TableView
562         @since 3.6.0
563         **/
564         view: {
565             value: Y.DataTable.TableView,
566             validator: '_validateView'
567         },
569         /**
570         Configuration object passed to the class constructor in `view`
571         during render.
573         @attribute viewConfig
574         @type {Object}
575         @default undefined (initially unset)
576         @protected
577         @since 3.6.0
578         **/
579         viewConfig: {}
581         /**
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>`&mdash;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
595         @since 3.5.0
596         **/
597         /*
598         headerView: {
599             value: Y.DataTable.HeaderView,
600             validator: '_validateView'
601         },
602         */
604         /**
605         Configuration object passed to the class constructor in `headerView`
606         during render.
608         @attribute headerConfig
609         @type {Object}
610         @default undefined (initially unset)
611         @protected
612         @since 3.6.0
613         **/
614         //headConfig: {},
616         /**
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}
628         @since 3.5.0
629         **/
630         /*
631         footerView: {
632             validator: '_validateView'
633         },
634         */
636         /**
637         Configuration object passed to the class constructor in `footerView`
638         during render.
640         @attribute footerConfig
641         @type {Object}
642         @default undefined (initially unset)
643         @protected
644         @since 3.6.0
645         **/
646         //footerConfig: {},
648         /**
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.
659         @attribute bodyView
660         @type {Function}
661         @default Y.DataTable.BodyView
662         @since 3.5.0
663         **/
664         /*
665         bodyView: {
666             value: Y.DataTable.BodyView,
667             validator: '_validateView'
668         },
669         */
671         /**
672         Configuration object passed to the class constructor in `bodyView`
673         during render.
675         @attribute bodyConfig
676         @type {Object}
677         @default undefined (initially unset)
678         @protected
679         @since 3.6.0
680         **/
681         //bodyConfig: {}
682     }
685 // The DataTable API docs are above DataTable.Base docs.
686 Y.DataTable = Y.mix(
687     Y.Base.create('datatable', Y.DataTable.Base, []), // Create the class
688     Y.DataTable); // Migrate static and namespaced classes
691 }, '3.13.0', {
692     "requires": [
693         "datatable-core",
694         "datatable-table",
695         "datatable-head",
696         "datatable-body",
697         "base-build",
698         "widget"
699     ],
700     "skinnable": true