API Docs for: 3.8.0
Show:

File: datatable/js/base.js

  1. /**
  2. A Widget for displaying tabular data.  The base implementation of DataTable
  3. provides the ability to dynamically generate an HTML table from a set of column
  4. configurations and row data.

  5. Two classes are included in the `datatable-base` module: `Y.DataTable` and
  6. `Y.DataTable.Base`.

  7. @module datatable
  8. @submodule datatable-base
  9. @main datatable
  10. @since 3.5.0
  11. **/

  12. // DataTable API docs included before DataTable.Base to make yuidoc work
  13. /**
  14. A Widget for displaying tabular data.  Before feature modules are `use()`d,
  15. this class is functionally equivalent to DataTable.Base.  However, feature
  16. modules can modify this class in non-destructive ways, expanding the API and
  17. functionality.

  18. This is the primary DataTable class.  Out of the box, it provides the ability
  19. to dynamically generate an HTML table from a set of column configurations and
  20. row data.  But feature module inclusion can add table sorting, pagintaion,
  21. highlighting, selection, and more.

  22. <pre><code>
  23. // The functionality of this table would require additional modules be use()d,
  24. // but the feature APIs are aggregated onto Y.DataTable.
  25. // (Snippet is for illustration. Not all features are available today.)
  26. var table = new Y.DataTable({
  27.     columns: [
  28.         { type: 'checkbox', defaultChecked: true },
  29.         { key: 'firstName', sortable: true, resizable: true },
  30.         { key: 'lastName', sortable: true },
  31.         { key: 'role', formatter: toRoleName }
  32.     ],
  33.     data: {
  34.         source: 'http://myserver.com/service/json',
  35.         type: 'json',
  36.         schema: {
  37.             resultListLocator: 'results.users',
  38.             fields: [
  39.                 'username',
  40.                 'firstName',
  41.                 'lastName',
  42.                 { key: 'role', type: 'number' }
  43.             ]
  44.         }
  45.     },
  46.     recordType: UserModel,
  47.     pagedData: {
  48.         location: 'footer',
  49.         pageSizes: [20, 50, 'all'],
  50.         rowsPerPage: 20,
  51.         pageLinks: 5
  52.     },
  53.     editable: true
  54. });
  55. </code></pre>

  56. ### Column Configuration

  57. The column configurations are set in the form of an array of objects, where
  58. each object corresponds to a column.  For columns populated directly from the
  59. row data, a 'key' property is required to bind the column to that property or
  60. attribute in the row data.

  61. Not all columns need to relate to row data, nor do all properties or attributes
  62. of the row data need to have a corresponding column.  However, only those
  63. columns included in the `columns` configuration attribute will be rendered.

  64. Other column configuration properties are supported by the configured
  65. `view`, class as well as any features added by plugins or class extensions.
  66. See the description of DataTable.TableView and its subviews
  67. DataTable.HeaderView, DataTable.BodyView, and DataTable.FooterView (and other
  68. DataTable feature classes) to see what column properties they support.

  69. Some examples of column configurations would be:

  70. <pre><code>
  71. // Basic
  72. var columns = [{ key: 'firstName' }, { key: 'lastName' }, { key: 'age' }];

  73. // For columns without any additional configuration, strings can be used
  74. var columns = ['firstName', 'lastName', 'age'];

  75. // Multi-row column headers (see DataTable.HeaderView for details)
  76. var columns = [
  77.     {
  78.         label: 'Name',
  79.         children: [
  80.             { key: 'firstName' },
  81.             { key: 'lastName' }
  82.         ]
  83.     },
  84.     'age' // mixing and matching objects and strings is ok
  85. ];

  86. // Including columns that are not related 1:1 to row data fields/attributes
  87. // (See DataTable.BodyView for details)
  88. var columns = [
  89.     {
  90.         label: 'Name', // Needed for the column header
  91.         formatter: function (o) {
  92.             // Fill the column cells with data from firstName and lastName
  93.             if (o.data.age > 55) {
  94.                 o.className += ' senior';
  95.             }
  96.             return o.data.lastName + ', ' + o.data.firstName;
  97.         }
  98.     },
  99.     'age'
  100. ];

  101. // Columns that include feature configurations (for illustration; not all
  102. // features are available today).
  103. var columns = [
  104.     { type: 'checkbox', defaultChecked: true },
  105.     { key: 'firstName', sortable: true, resizable: true, min-width: '300px' },
  106.     { key: 'lastName', sortable: true, resizable: true, min-width: '300px' },
  107.     { key: 'age', emptyCellValue: '<em>unknown</em>' }
  108. ];
  109. </code></pre>

  110. ### Row Data Configuration

  111. 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:

  112. 1. An array of simple objects with key:value pairs
  113. 2. A ModelList of Base-based class instances (presumably Model subclass
  114.    instances)

  115. If an array of objects is passed, it will be translated into a ModelList filled
  116. with instances of the class provided to the `recordType` attribute.  This
  117. attribute can also create a custom Model subclass from an array of field names
  118. or an object of attribute configurations.  If no `recordType` is provided, one
  119. will be created for you from available information (see `_initRecordType`).
  120. Providing either your own ModelList instance for `data`, or at least Model
  121. class for `recordType`, is the best way to control client-server
  122. synchronization when modifying data on the client side.

  123. The ModelList instance that manages the table's data is available in the `data`
  124. property on the DataTable instance.


  125. ### Rendering

  126. Table rendering is a collaborative process between the DataTable and its
  127. configured `view`. The DataTable creates an instance of the configured `view`
  128. (DataTable.TableView by default), and calls its `render()` method.
  129. DataTable.TableView, for instance, then creates the `<table>` and `<caption>`,
  130. then delegates the rendering of the specific sections of the table to subviews,
  131. which can be configured as `headerView`, `bodyView`, and `footerView`.
  132. DataTable.TableView defaults the `headerView` to DataTable.HeaderView and the
  133. `bodyView` to DataTable.BodyView, but leaves the `footerView` unassigned.
  134. Setting any subview to `null` will result in that table section not being
  135. rendered.

  136. @class DataTable
  137. @extends DataTable.Base
  138. @since 3.5.0
  139. **/

  140. // DataTable API docs included before DataTable.Base to make yuidoc work
  141. /**
  142. The baseline implementation of a DataTable.  This class should be used
  143. primarily as a superclass for a custom DataTable with a specific set of
  144. features.  Because features can be composed onto `Y.DataTable`, custom
  145. subclasses of DataTable.Base will remain unmodified when new feature modules
  146. are loaded.

  147. Example usage might look like this:

  148. <pre><code>
  149. // Custom subclass with only sorting and mutability added.  If other datatable
  150. // feature modules are loaded, this class will not be affected.
  151. var MyTableClass = Y.Base.create('table', Y.DataTable.Base,
  152.                        [ Y.DataTable.Sortable, Y.DataTable.Mutable ]);

  153. var table = new MyTableClass({
  154.     columns: ['firstName', 'lastName', 'age'],
  155.     data: [
  156.         { firstName: 'Frank', lastName: 'Zappa', age: 71 },
  157.         { firstName: 'Frank', lastName: 'Lloyd Wright', age: 144 },
  158.         { firstName: 'Albert', lastName: 'Einstein', age: 132 },
  159.         ...
  160.     ]
  161. });

  162. table.render('#over-there');

  163. // DataTable.Base can be instantiated if a featureless table is needed.
  164. var table = new Y.DataTable.Base({
  165.     columns: ['firstName', 'lastName', 'age'],
  166.     data: [
  167.         { firstName: 'Frank', lastName: 'Zappa', age: 71 },
  168.         { firstName: 'Frank', lastName: 'Lloyd Wright', age: 144 },
  169.         { firstName: 'Albert', lastName: 'Einstein', age: 132 },
  170.         ...
  171.     ]
  172. });

  173. table.render('#in-here');
  174. </code></pre>

  175. DataTable.Base is built from DataTable.Core, and sets the default `view`
  176. to `Y.DataTable.TableView`.

  177. @class Base
  178. @extends Widget
  179. @uses DataTable.Core
  180. @namespace DataTable
  181. @since 3.5.0
  182. **/
  183. Y.DataTable.Base = Y.Base.create('datatable', Y.Widget, [Y.DataTable.Core], {

  184.     /**
  185.     Pass through to `delegate()` called from the `contentBox`.

  186.     @method delegate
  187.     @param type {String} the event type to delegate
  188.     @param fn {Function} the callback function to execute.  This function
  189.                  will be provided the event object for the delegated event.
  190.     @param spec {String|Function} a selector that must match the target of the
  191.                  event or a function to test target and its parents for a match
  192.     @param context {Object} optional argument that specifies what 'this' refers to
  193.     @param args* {any} 0..n additional arguments to pass on to the callback
  194.                  function.  These arguments will be added after the event object.
  195.     @return {EventHandle} the detach handle
  196.     @since 3.5.0
  197.     **/
  198.     delegate: function () {
  199.         var contentBox = this.get('contentBox');

  200.         return contentBox.delegate.apply(contentBox, arguments);
  201.     },

  202.     /**
  203.     Destroys the table `View` if it's been created.

  204.     @method destructor
  205.     @protected
  206.     @since 3.6.0
  207.     **/
  208.     destructor: function () {
  209.         if (this.view) {
  210.             this.view.destroy();
  211.         }
  212.     },

  213.     /**
  214.     Returns the `<td>` Node from the given row and column index.  Alternately,
  215.     the `seed` can be a Node.  If so, the nearest ancestor cell is returned.
  216.     If the `seed` is a cell, it is returned.  If there is no cell at the given
  217.     coordinates, `null` is returned.

  218.     Optionally, include an offset array or string to return a cell near the
  219.     cell identified by the `seed`.  The offset can be an array containing the
  220.     number of rows to shift followed by the number of columns to shift, or one
  221.     of "above", "below", "next", or "previous".

  222.     <pre><code>// Previous cell in the previous row
  223.     var cell = table.getCell(e.target, [-1, -1]);

  224.     // Next cell
  225.     var cell = table.getCell(e.target, 'next');
  226.     var cell = table.getCell(e.taregt, [0, 1];</pre></code>

  227.     This is actually just a pass through to the `view` instance's method
  228.     by the same name.

  229.     @method getCell
  230.     @param {Number[]|Node} seed Array of row and column indexes, or a Node that
  231.         is either the cell itself or a descendant of one.
  232.     @param {Number[]|String} [shift] Offset by which to identify the returned
  233.         cell Node
  234.     @return {Node}
  235.     @since 3.5.0
  236.     **/
  237.     getCell: function (seed, shift) {
  238.         return this.view && this.view.getCell &&
  239.             this.view.getCell.apply(this.view, arguments);
  240.     },

  241.     /**
  242.     Returns the `<tr>` Node from the given row index, Model, or Model's
  243.     `clientId`.  If the rows haven't been rendered yet, or if the row can't be
  244.     found by the input, `null` is returned.

  245.     This is actually just a pass through to the `view` instance's method
  246.     by the same name.

  247.     @method getRow
  248.     @param {Number|String|Model} id Row index, Model instance, or clientId
  249.     @return {Node}
  250.     @since 3.5.0
  251.     **/
  252.     getRow: function (id) {
  253.         return this.view && this.view.getRow &&
  254.             this.view.getRow.apply(this.view, arguments);
  255.     },

  256.     /**
  257.     Updates the `_displayColumns` property.

  258.     @method _afterDisplayColumnsChange
  259.     @param {EventFacade} e The `columnsChange` event
  260.     @protected
  261.     @since 3.6.0
  262.     **/
  263.     // FIXME: This is a kludge for back compat with features that reference
  264.     // _displayColumns.  They should be updated to TableView plugins.
  265.     _afterDisplayColumnsChange: function (e) {
  266.         this._extractDisplayColumns(e.newVal || []);
  267.     },

  268.     /**
  269.     Attaches subscriptions to relay core change events to the view.

  270.     @method bindUI
  271.     @protected
  272.     @since 3.6.0
  273.     **/
  274.     bindUI: function () {
  275.         this._eventHandles.relayCoreChanges = this.after(
  276.             ['columnsChange',
  277.              'dataChange',
  278.              'summaryChange',
  279.              'captionChange',
  280.              'widthChange'],
  281.             Y.bind('_relayCoreAttrChange', this));
  282.     },

  283.     /**
  284.     The default behavior of the `renderView` event.  Calls `render()` on the
  285.     `View` instance on the event.

  286.     @method _defRenderViewFn
  287.     @param {EventFacade} e The `renderView` event
  288.     @protected
  289.     **/
  290.     _defRenderViewFn: function (e) {
  291.         e.view.render();
  292.     },

  293.     /**
  294.     Processes the full column array, distilling the columns down to those that
  295.     correspond to cell data columns.

  296.     @method _extractDisplayColumns
  297.     @param {Object[]} columns The full set of table columns
  298.     @protected
  299.     **/
  300.     // FIXME: this is a kludge for back compat, duplicating logic in the
  301.     // tableView
  302.     _extractDisplayColumns: function (columns) {
  303.         var displayColumns = [];

  304.         function process(cols) {
  305.             var i, len, col;

  306.             for (i = 0, len = cols.length; i < len; ++i) {
  307.                 col = cols[i];

  308.                 if (Y.Lang.isArray(col.children)) {
  309.                     process(col.children);
  310.                 } else {
  311.                     displayColumns.push(col);
  312.                 }
  313.             }
  314.         }

  315.         process(columns);

  316.         /**
  317.         Array of the columns that correspond to those with value cells in the
  318.         data rows. Excludes colspan header columns (configured with `children`).

  319.         @property _displayColumns
  320.         @type {Object[]}
  321.         @since 3.5.0
  322.         **/
  323.         this._displayColumns = displayColumns;
  324.     },

  325.     /**
  326.     Sets up the instance's events.

  327.     @method initializer
  328.     @param {Object} [config] Configuration object passed at construction
  329.     @protected
  330.     @since 3.6.0
  331.     **/
  332.     initializer: function () {
  333.         this.publish('renderView', {
  334.             defaultFn: Y.bind('_defRenderViewFn', this)
  335.         });

  336.         // Have to use get('columns'), not config.columns because the setter
  337.         // needs to transform string columns to objects.
  338.         this._extractDisplayColumns(this.get('columns') || []);

  339.         // FIXME: kludge for back compat of features that reference
  340.         // _displayColumns on the instance.  They need to be updated to
  341.         // TableView plugins, most likely.
  342.         this.after('columnsChange', Y.bind('_afterDisplayColumnsChange', this));
  343.     },

  344.     /**
  345.     Relays attribute changes to the instance's `view`.

  346.     @method _relayCoreAttrChange
  347.     @param {EventFacade} e The change event
  348.     @protected
  349.     @since 3.6.0
  350.     **/
  351.     _relayCoreAttrChange: function (e) {
  352.         var attr = (e.attrName === 'data') ? 'modelList' : e.attrName;

  353.         this.view.set(attr, e.newVal);
  354.     },

  355.     /**
  356.     Instantiates the configured `view` class that will be responsible for
  357.     setting up the View class.

  358.     @method @renderUI
  359.     @protected
  360.     @since 3.6.0
  361.     **/
  362.     renderUI: function () {
  363.         var self = this,
  364.             View = this.get('view');

  365.         if (View) {
  366.             this.view = new View(
  367.                 Y.merge(
  368.                     this.getAttrs(),
  369.                     {
  370.                         host     : this,
  371.                         container: this.get('contentBox'),
  372.                         modelList: this.data
  373.                     },
  374.                     this.get('viewConfig')));

  375.             // For back compat, share the view instances and primary nodes
  376.             // on this instance.
  377.             // TODO: Remove this?
  378.             if (!this._eventHandles.legacyFeatureProps) {
  379.                 this._eventHandles.legacyFeatureProps = this.view.after({
  380.                     renderHeader: function (e) {
  381.                         self.head = e.view;
  382.                         self._theadNode = e.view.theadNode;
  383.                         // TODO: clean up the repetition.
  384.                         // This is here so that subscribers to renderHeader etc
  385.                         // have access to this._tableNode from the DT instance
  386.                         self._tableNode = e.view.get('container');
  387.                     },
  388.                     renderFooter: function (e) {
  389.                         self.foot = e.view;
  390.                         self._tfootNode = e.view.tfootNode;
  391.                         self._tableNode = e.view.get('container');
  392.                     },
  393.                     renderBody: function (e) {
  394.                         self.body = e.view;
  395.                         self._tbodyNode = e.view.tbodyNode;
  396.                         self._tableNode = e.view.get('container');
  397.                     },
  398.                     // FIXME: guarantee that the properties are available, even
  399.                     // if the configured (or omitted) views don't create them
  400.                     renderTable: function (e) {
  401.                         var contentBox = this.get('container');

  402.                         self._tableNode = this.tableNode ||
  403.                             contentBox.one('.' + this.getClassName('table') +
  404.                                            ', table');

  405.                         // FIXME: _captionNode isn't available until after
  406.                         // renderTable unless in the renderX subs I look for
  407.                         // it under the container's parentNode (to account for
  408.                         // scroll breaking out the caption table).
  409.                         self._captionNode = this.captionNode ||
  410.                             contentBox.one('caption');

  411.                         if (!self._theadNode) {
  412.                             self._theadNode = contentBox.one(
  413.                                 '.' + this.getClassName('columns') + ', thead');
  414.                         }

  415.                         if (!self._tbodyNode) {
  416.                             self._tbodyNode = contentBox.one(
  417.                                 '.' + this.getClassName('data') + ', tbody');
  418.                         }

  419.                         if (!self._tfootNode) {
  420.                             self._tfootNode = contentBox.one(
  421.                                 '.' + this.getClassName('footer') + ', tfoot');
  422.                         }
  423.                     }
  424.                 });
  425.             }

  426.             // To *somewhat* preserve table.on('renderHeader', fn) in the
  427.             // form of table.on('table:renderHeader', fn), because I couldn't
  428.             // figure out another option.
  429.             this.view.addTarget(this);
  430.         }
  431.     },

  432.     /**
  433.     Fires the `renderView` event, delegating UI updates to the configured View.

  434.     @method syncUI
  435.     @since 3.5.0
  436.     **/
  437.     syncUI: function () {
  438.         if (this.view) {
  439.             this.fire('renderView', { view: this.view });
  440.         }
  441.     },

  442.     /**
  443.     Verifies the input value is a function with a `render` method on its
  444.     prototype.  `null` is also accepted to remove the default View.

  445.     @method _validateView
  446.     @protected
  447.     @since 3.5.0
  448.     **/
  449.     _validateView: function (val) {
  450.         // TODO support View instances?
  451.         return val === null || (Y.Lang.isFunction(val) && val.prototype.render);
  452.     }
  453. }, {
  454.     ATTRS: {
  455.         /**
  456.         The View class used to render the `<table>` into the Widget's
  457.         `contentBox`.  This View can handle the entire table rendering itself
  458.         or delegate to other Views.

  459.         It is not strictly necessary that the class function assigned here be
  460.         a View subclass.  It must however have a `render()` method.

  461.         When the DataTable is rendered, an instance of this View will be
  462.         created and its `render()` method called.  The View instance will be
  463.         assigned to the DataTable instance's `view` property.

  464.         @attribute view
  465.         @type {Function}
  466.         @default Y.DataTable.TableView
  467.         @since 3.6.0
  468.         **/
  469.         view: {
  470.             value: Y.DataTable.TableView,
  471.             validator: '_validateView'
  472.         },

  473.         /**
  474.         Configuration object passed to the class constructor in `view`
  475.         during render.

  476.         @attribute viewConfig
  477.         @type {Object}
  478.         @default undefined (initially unset)
  479.         @protected
  480.         @since 3.6.0
  481.         **/
  482.         viewConfig: {}

  483.         /**
  484.         If the View class assigned to the DataTable's `view` attribute supports
  485.         it, this class will be used for rendering the contents of the
  486.         `<thead>`&mdash;the column headers for the table.
  487.        
  488.         Similar to `view`, the instance of this View will be assigned to the
  489.         DataTable instance's `head` property.

  490.         It is not strictly necessary that the class function assigned here be
  491.         a View subclass.  It must however have a `render()` method.

  492.         @attribute headerView
  493.         @type {Function|Object}
  494.         @default Y.DataTable.HeaderView
  495.         @since 3.5.0
  496.         **/
  497.         /*
  498.         headerView: {
  499.             value: Y.DataTable.HeaderView,
  500.             validator: '_validateView'
  501.         },
  502.         */

  503.         /**
  504.         Configuration object passed to the class constructor in `headerView`
  505.         during render.

  506.         @attribute headerConfig
  507.         @type {Object}
  508.         @default undefined (initially unset)
  509.         @protected
  510.         @since 3.6.0
  511.         **/
  512.         //headConfig: {},

  513.         /**
  514.         If the View class assigned to the DataTable's `view` attribute supports
  515.         it, this class will be used for rendering the contents of the `<tfoot>`.
  516.        
  517.         Similar to `view`, the instance of this View will be assigned to the
  518.         DataTable instance's `foot` property.

  519.         It is not strictly necessary that the class function assigned here be
  520.         a View subclass.  It must however have a `render()` method.

  521.         @attribute footerView
  522.         @type {Function|Object}
  523.         @since 3.5.0
  524.         **/
  525.         /*
  526.         footerView: {
  527.             validator: '_validateView'
  528.         },
  529.         */

  530.         /**
  531.         Configuration object passed to the class constructor in `footerView`
  532.         during render.

  533.         @attribute footerConfig
  534.         @type {Object}
  535.         @default undefined (initially unset)
  536.         @protected
  537.         @since 3.6.0
  538.         **/
  539.         //footerConfig: {},

  540.         /**
  541.         If the View class assigned to the DataTable's `view` attribute supports
  542.         it, this class will be used for rendering the contents of the `<tbody>`
  543.         including all data rows.
  544.        
  545.         Similar to `view`, the instance of this View will be assigned to the
  546.         DataTable instance's `body` property.

  547.         It is not strictly necessary that the class function assigned here be
  548.         a View subclass.  It must however have a `render()` method.

  549.         @attribute bodyView
  550.         @type {Function}
  551.         @default Y.DataTable.BodyView
  552.         @since 3.5.0
  553.         **/
  554.         /*
  555.         bodyView: {
  556.             value: Y.DataTable.BodyView,
  557.             validator: '_validateView'
  558.         },
  559.         */

  560.         /**
  561.         Configuration object passed to the class constructor in `bodyView`
  562.         during render.

  563.         @attribute bodyConfig
  564.         @type {Object}
  565.         @default undefined (initially unset)
  566.         @protected
  567.         @since 3.6.0
  568.         **/
  569.         //bodyConfig: {}
  570.     }
  571. });

  572. // The DataTable API docs are above DataTable.Base docs.
  573. Y.DataTable = Y.mix(
  574.     Y.Base.create('datatable', Y.DataTable.Base, []), // Create the class
  575.     Y.DataTable); // Migrate static and namespaced classes

  576.