API Docs for: 3.8.0
Show:

File: datatable/js/table.js

  1. /**
  2. View class responsible for rendering a `<table>` from provided data.  Used as
  3. the default `view` for `Y.DataTable.Base` and `Y.DataTable` classes.

  4. @module datatable
  5. @submodule datatable-table
  6. @since 3.6.0
  7. **/
  8. var toArray = Y.Array,
  9.     YLang   = Y.Lang,
  10.     fromTemplate = YLang.sub,

  11.     isArray    = YLang.isArray,
  12.     isFunction = YLang.isFunction;

  13. /**
  14. View class responsible for rendering a `<table>` from provided data.  Used as
  15. the default `view` for `Y.DataTable.Base` and `Y.DataTable` classes.



  16. @class TableView
  17. @namespace DataTable
  18. @extends View
  19. @since 3.6.0
  20. **/
  21. Y.namespace('DataTable').TableView = Y.Base.create('table', Y.View, [], {

  22.     /**
  23.     The HTML template used to create the caption Node if the `caption`
  24.     attribute is set.

  25.     @property CAPTION_TEMPLATE
  26.     @type {HTML}
  27.     @default '<caption class="{className}"/>'
  28.     @since 3.6.0
  29.     **/
  30.     CAPTION_TEMPLATE: '<caption class="{className}"/>',

  31.     /**
  32.     The HTML template used to create the table Node.

  33.     @property TABLE_TEMPLATE
  34.     @type {HTML}
  35.     @default '<table cellspacing="0" class="{className}"/>'
  36.     @since 3.6.0
  37.     **/
  38.     TABLE_TEMPLATE  : '<table cellspacing="0" class="{className}"/>',

  39.     /**
  40.     The object or instance of the class assigned to `bodyView` that is
  41.     responsible for rendering and managing the table's `<tbody>`(s) and its
  42.     content.

  43.     @property body
  44.     @type {Object}
  45.     @default undefined (initially unset)
  46.     @since 3.5.0
  47.     **/
  48.     //body: null,

  49.     /**
  50.     The object or instance of the class assigned to `footerView` that is
  51.     responsible for rendering and managing the table's `<tfoot>` and its
  52.     content.

  53.     @property foot
  54.     @type {Object}
  55.     @default undefined (initially unset)
  56.     @since 3.5.0
  57.     **/
  58.     //foot: null,

  59.     /**
  60.     The object or instance of the class assigned to `headerView` that is
  61.     responsible for rendering and managing the table's `<thead>` and its
  62.     content.

  63.     @property head
  64.     @type {Object}
  65.     @default undefined (initially unset)
  66.     @since 3.5.0
  67.     **/
  68.     //head: null,

  69.     //-----------------------------------------------------------------------//
  70.     // Public methods
  71.     //-----------------------------------------------------------------------//

  72.     /**
  73.     Returns the `<td>` Node from the given row and column index.  Alternately,
  74.     the `seed` can be a Node.  If so, the nearest ancestor cell is returned.
  75.     If the `seed` is a cell, it is returned.  If there is no cell at the given
  76.     coordinates, `null` is returned.

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

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

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

  86.     This is actually just a pass through to the `bodyView` instance's method
  87.     by the same name.

  88.     @method getCell
  89.     @param {Number[]|Node} seed Array of row and column indexes, or a Node that
  90.         is either the cell itself or a descendant of one.
  91.     @param {Number[]|String} [shift] Offset by which to identify the returned
  92.         cell Node
  93.     @return {Node}
  94.     @since 3.5.0
  95.     **/
  96.     getCell: function (seed, shift) {
  97.         return this.body && this.body.getCell &&
  98.             this.body.getCell.apply(this.body, arguments);
  99.     },

  100.     /**
  101.     Returns the generated CSS classname based on the input.  If the `host`
  102.     attribute is configured, it will attempt to relay to its `getClassName`
  103.     or use its static `NAME` property as a string base.
  104.    
  105.     If `host` is absent or has neither method nor `NAME`, a CSS classname
  106.     will be generated using this class's `NAME`.

  107.     @method getClassName
  108.     @param {String} token* Any number of token strings to assemble the
  109.         classname from.
  110.     @return {String}
  111.     @protected
  112.     **/
  113.     getClassName: function () {
  114.         // TODO: add attr with setter for host?
  115.         var host = this.host,
  116.             NAME = (host && host.constructor.NAME) ||
  117.                     this.constructor.NAME;

  118.         if (host && host.getClassName) {
  119.             return host.getClassName.apply(host, arguments);
  120.         } else {
  121.             return Y.ClassNameManager.getClassName
  122.                 .apply(Y.ClassNameManager,
  123.                        [NAME].concat(toArray(arguments, 0, true)));
  124.         }
  125.     },

  126.     /**
  127.     Relays call to the `bodyView`'s `getRecord` method if it has one.

  128.     @method getRecord
  129.     @param {String|Node} seed Node or identifier for a row or child element
  130.     @return {Model}
  131.     @since 3.6.0
  132.     **/
  133.     getRecord: function () {
  134.         return this.body && this.body.getRecord &&
  135.             this.body.getRecord.apply(this.body, arguments);
  136.     },

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

  141.     This is actually just a pass through to the `bodyView` instance's method
  142.     by the same name.

  143.     @method getRow
  144.     @param {Number|String|Model} id Row index, Model instance, or clientId
  145.     @return {Node}
  146.     @since 3.5.0
  147.     **/
  148.     getRow: function (id) {
  149.         return this.body && this.body.getRow &&
  150.             this.body.getRow.apply(this.body, arguments);
  151.     },


  152.     //-----------------------------------------------------------------------//
  153.     // Protected and private methods
  154.     //-----------------------------------------------------------------------//
  155.     /**
  156.     Updates the table's `summary` attribute.

  157.     @method _afterSummaryChange
  158.     @param {EventHandle} e The change event
  159.     @protected
  160.     @since 3.6.0
  161.     **/
  162.     _afterSummaryChange: function (e) {
  163.         this._uiSetSummary(e.newVal);
  164.     },

  165.     /**
  166.     Updates the table's `<caption>`.

  167.     @method _afterCaptionChange
  168.     @param {EventHandle} e The change event
  169.     @protected
  170.     @since 3.6.0
  171.     **/
  172.     _afterCaptionChange: function (e) {
  173.         this._uiSetCaption(e.newVal);
  174.     },

  175.     /**
  176.     Updates the table's width.

  177.     @method _afterWidthChange
  178.     @param {EventHandle} e The change event
  179.     @protected
  180.     @since 3.6.0
  181.     **/
  182.     _afterWidthChange: function (e) {
  183.         this._uiSetWidth(e.newVal);
  184.     },

  185.     /**
  186.     Attaches event subscriptions to relay attribute changes to the child Views.

  187.     @method _bindUI
  188.     @protected
  189.     @since 3.6.0
  190.     **/
  191.     _bindUI: function () {
  192.         var relay;

  193.         if (!this._eventHandles) {
  194.             relay = Y.bind('_relayAttrChange', this);

  195.             this._eventHandles = this.after({
  196.                 columnsChange  : relay,
  197.                 modelListChange: relay,
  198.                 summaryChange  : Y.bind('_afterSummaryChange', this),
  199.                 captionChange  : Y.bind('_afterCaptionChange', this),
  200.                 widthChange    : Y.bind('_afterWidthChange', this)
  201.             });
  202.         }
  203.     },

  204.     /**
  205.     Creates the `<table>`.

  206.     @method _createTable
  207.     @return {Node} The `<table>` node
  208.     @protected
  209.     @since 3.5.0
  210.     **/
  211.     _createTable: function () {
  212.         return Y.Node.create(fromTemplate(this.TABLE_TEMPLATE, {
  213.             className: this.getClassName('table')
  214.         })).empty();
  215.     },

  216.     /**
  217.     Calls `render()` on the `bodyView` class instance.

  218.     @method _defRenderBodyFn
  219.     @param {EventFacade} e The renderBody event
  220.     @protected
  221.     @since 3.5.0
  222.     **/
  223.     _defRenderBodyFn: function (e) {
  224.         e.view.render();
  225.     },

  226.     /**
  227.     Calls `render()` on the `footerView` class instance.

  228.     @method _defRenderFooterFn
  229.     @param {EventFacade} e The renderFooter event
  230.     @protected
  231.     @since 3.5.0
  232.     **/
  233.     _defRenderFooterFn: function (e) {
  234.         e.view.render();
  235.     },

  236.     /**
  237.     Calls `render()` on the `headerView` class instance.

  238.     @method _defRenderHeaderFn
  239.     @param {EventFacade} e The renderHeader event
  240.     @protected
  241.     @since 3.5.0
  242.     **/
  243.     _defRenderHeaderFn: function (e) {
  244.         e.view.render();
  245.     },

  246.     /**
  247.     Renders the `<table>` and, if there are associated Views, the `<thead>`,
  248.     `<tfoot>`, and `<tbody>` (empty until `syncUI`).

  249.     Assigns the generated table nodes to the `tableNode`, `_theadNode`,
  250.     `_tfootNode`, and `_tbodyNode` properties.  Assigns the instantiated Views
  251.     to the `head`, `foot`, and `body` properties.


  252.     @method _defRenderTableFn
  253.     @param {EventFacade} e The renderTable event
  254.     @protected
  255.     @since 3.5.0
  256.     **/
  257.     _defRenderTableFn: function (e) {
  258.         var container = this.get('container'),
  259.             attrs = this.getAttrs();

  260.         if (!this.tableNode) {
  261.             this.tableNode = this._createTable();
  262.         }

  263.         attrs.host  = this.get('host') || this;
  264.         attrs.table = this;
  265.         attrs.container = this.tableNode;

  266.         this._uiSetCaption(this.get('caption'));
  267.         this._uiSetSummary(this.get('summary'));
  268.         this._uiSetWidth(this.get('width'));

  269.         if (this.head || e.headerView) {
  270.             if (!this.head) {
  271.                 this.head = new e.headerView(Y.merge(attrs, e.headerConfig));
  272.             }

  273.             this.fire('renderHeader', { view: this.head });
  274.         }

  275.         if (this.foot || e.footerView) {
  276.             if (!this.foot) {
  277.                 this.foot = new e.footerView(Y.merge(attrs, e.footerConfig));
  278.             }

  279.             this.fire('renderFooter', { view: this.foot });
  280.         }

  281.         attrs.columns = this.displayColumns;

  282.         if (this.body || e.bodyView) {
  283.             if (!this.body) {
  284.                 this.body = new e.bodyView(Y.merge(attrs, e.bodyConfig));
  285.             }

  286.             this.fire('renderBody', { view: this.body });
  287.         }

  288.         if (!container.contains(this.tableNode)) {
  289.             container.append(this.tableNode);
  290.         }

  291.         this._bindUI();
  292.     },

  293.     /**
  294.     Cleans up state, destroys child views, etc.

  295.     @method destructor
  296.     @protected
  297.     **/
  298.     destructor: function () {
  299.         if (this.head && this.head.destroy) {
  300.             this.head.destroy();
  301.         }
  302.         delete this.head;

  303.         if (this.foot && this.foot.destroy) {
  304.             this.foot.destroy();
  305.         }
  306.         delete this.foot;

  307.         if (this.body && this.body.destroy) {
  308.             this.body.destroy();
  309.         }
  310.         delete this.body;

  311.         if (this._eventHandles) {
  312.             this._eventHandles.detach();
  313.             delete this._eventHandles;
  314.         }

  315.         if (this.tableNode) {
  316.             this.tableNode.remove().destroy(true);
  317.         }
  318.     },

  319.     /**
  320.     Processes the full column array, distilling the columns down to those that
  321.     correspond to cell data columns.

  322.     @method _extractDisplayColumns
  323.     @protected
  324.     **/
  325.     _extractDisplayColumns: function () {
  326.         var columns = this.get('columns'),
  327.             displayColumns = [];

  328.         function process(cols) {
  329.             var i, len, col;

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

  332.                 if (isArray(col.children)) {
  333.                     process(col.children);
  334.                 } else {
  335.                     displayColumns.push(col);
  336.                 }
  337.             }
  338.         }

  339.         process(columns);

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

  343.         @property displayColumns
  344.         @type {Object[]}
  345.         @since 3.6.0
  346.         **/
  347.         this.displayColumns = displayColumns;
  348.     },

  349.     /**
  350.     Publishes core events.

  351.     @method _initEvents
  352.     @protected
  353.     @since 3.5.0
  354.     **/
  355.     _initEvents: function () {
  356.         this.publish({
  357.             // Y.bind used to allow late binding for method override support
  358.             renderTable : { defaultFn: Y.bind('_defRenderTableFn', this) },
  359.             renderHeader: { defaultFn: Y.bind('_defRenderHeaderFn', this) },
  360.             renderBody  : { defaultFn: Y.bind('_defRenderBodyFn', this) },
  361.             renderFooter: { defaultFn: Y.bind('_defRenderFooterFn', this) }
  362.         });
  363.     },

  364.     /**
  365.     Constructor logic.

  366.     @method intializer
  367.     @param {Object} config Configuration object passed to the constructor
  368.     @protected
  369.     @since 3.6.0
  370.     **/
  371.     initializer: function (config) {
  372.         this.host = config.host;

  373.         this._initEvents();

  374.         this._extractDisplayColumns();

  375.         this.after('columnsChange', this._extractDisplayColumns, this);
  376.     },

  377.     /**
  378.     Relays attribute changes to the child Views.

  379.     @method _relayAttrChange
  380.     @param {EventHandle} e The change event
  381.     @protected
  382.     @since 3.6.0
  383.     **/
  384.     _relayAttrChange: function (e) {
  385.         var attr = e.attrName,
  386.             val  = e.newVal;

  387.         if (this.head) {
  388.             this.head.set(attr, val);
  389.         }

  390.         if (this.foot) {
  391.             this.foot.set(attr, val);
  392.         }

  393.         if (this.body) {
  394.             if (attr === 'columns') {
  395.                 val = this.displayColumns;
  396.             }

  397.             this.body.set(attr, val);
  398.         }
  399.     },

  400.     /**
  401.     Creates the UI in the configured `container`.

  402.     @method render
  403.     @return {TableView}
  404.     @chainable
  405.     **/
  406.     render: function () {
  407.         if (this.get('container')) {
  408.             this.fire('renderTable', {
  409.                 headerView  : this.get('headerView'),
  410.                 headerConfig: this.get('headerConfig'),

  411.                 bodyView    : this.get('bodyView'),
  412.                 bodyConfig  : this.get('bodyConfig'),

  413.                 footerView  : this.get('footerView'),
  414.                 footerConfig: this.get('footerConfig')
  415.             });
  416.         }

  417.         return this;
  418.     },

  419.     /**
  420.     Creates, removes, or updates the table's `<caption>` element per the input
  421.     value.  Empty values result in the caption being removed.

  422.     @method _uiSetCaption
  423.     @param {HTML} htmlContent The content to populate the table caption
  424.     @protected
  425.     @since 3.5.0
  426.     **/
  427.     _uiSetCaption: function (htmlContent) {
  428.         var table   = this.tableNode,
  429.             caption = this.captionNode;

  430.         if (htmlContent) {
  431.             if (!caption) {
  432.                 this.captionNode = caption = Y.Node.create(
  433.                     fromTemplate(this.CAPTION_TEMPLATE, {
  434.                         className: this.getClassName('caption')
  435.                     }));

  436.                 table.prepend(this.captionNode);
  437.             }

  438.             caption.setHTML(htmlContent);

  439.         } else if (caption) {
  440.             caption.remove(true);

  441.             delete this.captionNode;
  442.         }
  443.     },

  444.     /**
  445.     Updates the table's `summary` attribute with the input value.

  446.     @method _uiSetSummary
  447.     @protected
  448.     @since 3.5.0
  449.     **/
  450.     _uiSetSummary: function (summary) {
  451.         if (summary) {
  452.             this.tableNode.setAttribute('summary', summary);
  453.         } else {
  454.             this.tableNode.removeAttribute('summary');
  455.         }
  456.     },

  457.     /**
  458.     Sets the `boundingBox` and table width per the input value.

  459.     @method _uiSetWidth
  460.     @param {Number|String} width The width to make the table
  461.     @protected
  462.     @since 3.5.0
  463.     **/
  464.     _uiSetWidth: function (width) {
  465.         var table = this.tableNode;

  466.         // Table width needs to account for borders
  467.         table.setStyle('width', !width ? '' :
  468.             (this.get('container').get('offsetWidth') -
  469.              (parseInt(table.getComputedStyle('borderLeftWidth'), 10)|0) -
  470.              (parseInt(table.getComputedStyle('borderLeftWidth'), 10)|0)) +
  471.              'px');

  472.         table.setStyle('width', width);
  473.     },

  474.     /**
  475.     Ensures that the input is a View class or at least has a `render` method.

  476.     @method _validateView
  477.     @param {View|Function} val The View class
  478.     @return {Boolean}
  479.     @protected
  480.     **/
  481.     _validateView: function (val) {
  482.         return isFunction(val) && val.prototype.render;
  483.     }
  484. }, {
  485.     ATTRS: {
  486.         /**
  487.         Content for the `<table summary="ATTRIBUTE VALUE HERE">`.  Values
  488.         assigned to this attribute will be HTML escaped for security.

  489.         @attribute summary
  490.         @type {String}
  491.         @default '' (empty string)
  492.         @since 3.5.0
  493.         **/
  494.         //summary: {},

  495.         /**
  496.         HTML content of an optional `<caption>` element to appear above the
  497.         table.  Leave this config unset or set to a falsy value to remove the
  498.         caption.

  499.         @attribute caption
  500.         @type HTML
  501.         @default undefined (initially unset)
  502.         @since 3.6.0
  503.         **/
  504.         //caption: {},

  505.         /**
  506.         Columns to include in the rendered table.

  507.         This attribute takes an array of objects. Each object is considered a
  508.         data column or header cell to be rendered.  How the objects are
  509.         translated into markup is delegated to the `headerView`, `bodyView`,
  510.         and `footerView`.

  511.         The raw value is passed to the `headerView` and `footerView`.  The
  512.         `bodyView` receives the instance's `displayColumns` array, which is
  513.         parsed from the columns array.  If there are no nested columns (columns
  514.         configured with a `children` array), the `displayColumns` is the same
  515.         as the raw value.
  516.        
  517.         @attribute columns
  518.         @type {Object[]}
  519.         @since 3.6.0
  520.         **/
  521.         columns: {
  522.             validator: isArray
  523.         },

  524.         /**
  525.         Width of the table including borders.  This value requires units, so
  526.         `200` is invalid, but `'200px'` is valid.  Setting the empty string
  527.         (the default) will allow the browser to set the table width.

  528.         @attribute width
  529.         @type {String}
  530.         @default ''
  531.         @since 3.6.0
  532.         **/
  533.         width: {
  534.             value: '',
  535.             validator: YLang.isString
  536.         },

  537.         /**
  538.         An instance of this class is used to render the contents of the
  539.         `<thead>`&mdash;the column headers for the table.
  540.        
  541.         The instance of this View will be assigned to the instance's `head`
  542.         property.

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

  545.         @attribute headerView
  546.         @type {Function|Object}
  547.         @default Y.DataTable.HeaderView
  548.         @since 3.6.0
  549.         **/
  550.         headerView: {
  551.             value: Y.DataTable.HeaderView,
  552.             validator: '_validateView'
  553.         },

  554.         /**
  555.         Configuration overrides used when instantiating the `headerView`
  556.         instance.

  557.         @attribute headerConfig
  558.         @type {Object}
  559.         @since 3.6.0
  560.         **/
  561.         //headerConfig: {},

  562.         /**
  563.         An instance of this class is used to render the contents of the
  564.         `<tfoot>` (if appropriate).
  565.        
  566.         The instance of this View will be assigned to the instance's `foot`
  567.         property.

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

  570.         @attribute footerView
  571.         @type {Function|Object}
  572.         @since 3.6.0
  573.         **/
  574.         footerView: {
  575.             validator: '_validateView'
  576.         },

  577.         /**
  578.         Configuration overrides used when instantiating the `footerView`
  579.         instance.

  580.         @attribute footerConfig
  581.         @type {Object}
  582.         @since 3.6.0
  583.         **/
  584.         //footerConfig: {},

  585.         /**
  586.         An instance of this class is used to render the contents of the table's
  587.         `<tbody>`&mdash;the data cells in the table.
  588.        
  589.         The instance of this View will be assigned to the instance's `body`
  590.         property.

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

  593.         @attribute bodyView
  594.         @type {Function|Object}
  595.         @default Y.DataTable.BodyView
  596.         @since 3.6.0
  597.         **/
  598.         bodyView: {
  599.             value: Y.DataTable.BodyView,
  600.             validator: '_validateView'
  601.         }

  602.         /**
  603.         Configuration overrides used when instantiating the `bodyView`
  604.         instance.

  605.         @attribute bodyConfig
  606.         @type {Object}
  607.         @since 3.6.0
  608.         **/
  609.         //bodyConfig: {}
  610.     }
  611. });



  612.