API Docs for:
Show:

File: js/dt_footerview.js

  1.  /**
  2.   FooterView is a YUI View class extension that provides a simple, one row summary row
  3.   to a Datatable. This view provides
  4.   for a summary row appended to the bottom of the DataTable TBODY, typically consisting
  5.   of **one** TH element (with a colspan) and several TD elements for each desired column
  6.   where a "calculated field" is desired.

  7.   View configuration provides for calculated fields based upon the all of the available
  8.   dataset fields within the DataTable's "ModelList".

  9.   The view works with either non-scrolling or scrolling DataTables, and allows for either a
  10.   "fixed" view, wherein the footer remains fixed at the bottom of the DataTable contentBox
  11.   while the table is scrolled.

  12.   #### Calculated Fields

  13.   The current implementation supports the following calculated fields, where they are
  14.   identified by their placeholder tag for replacement via Y.sub (case insensitive);

  15.   * `{sum}` Calculate the arithmetic sum of the specified column in dataset
  16.   * `{min}` Calculate the minimum value of the specified column in dataset
  17.   * `{max}` Calculate the maximum value of the specified column in dataset
  18.   * `{avg}` Calculate the arithmetic average of the of the specified column (synonyms `{mean}`, `{average}`)

  19.   Also, non-looping calcs are;

  20.   *  `{row_count}` Returns the number of rows in the dataset
  21.   *  `{col_count}` Returns the number of columns in the dataset (no visibility check)
  22.   *  `{date}` Returns the current date
  23.   *  `{time}` Returns the current time

  24.   #### Configuration

  25.   YUI 3.6.0 DataTable supports attributes including `footerView` and `footerConfig`.

  26.   This FooterView recognizes the following attributes, which must be configured via the
  27.   DataTable {configs} (see usage example below);

  28.   * [`fixed`](#attr_fixed) : Flag indicating if footer should be fixed or floating
  29.   * [`heading`](#attr_heading) : Object, defining the single TH as;
  30.      * [`colspan`](#attr_heading.colspan) : Number of columns to merge from left for the TH
  31.      * [`content`](#attr_heading.content) : A string indicating the content of the TH for the footer
  32.      * [`className`](#attr_heading.className) : Additional classname for TH
  33.   * [`columns`](#attr_columns) : Array of objects, one per desired TD column in footer as;
  34.      * [`key`](#attr_columns.key) : `key` name from the DataTable columns
  35.      * [`content`](#attr_columns.content) : String indicating the contents of this TD
  36.      * [`className`](#attr_columns.className) : Additional classname for TD
  37.      * [`formatter`](#attr_columns.formatter) : Formatter to apply to this column result
  38.   * [`dateFormat`](#attr_dateFormat) : Format string to use for any {date} fields
  39.   * [`timeFormat`](#attr_timeFormat) : Format string to use for any {time} fields

  40.   Additionally the user can provide a valid function as a column `content` to calculate a
  41.   custom entry for
  42.   <br/>a column (see [`columns.content`](#attr_columns.content) or [`calcDatasetValue`](#method_calcDatasetValue))

  43.   #### Usage

  44.       var dtable = new Y.DataTable({
  45.           columns:    ['EmpId','FirstName','LastName','NumClients','SalesTTM'],
  46.           data:       AccountMgr.Sales,
  47.           scrollable: 'y',
  48.           height:     '250px',
  49.           width:      '400px',

  50.           footerView:   Y.FooterView,
  51.           footerConfig: {
  52.               fixed:   true,
  53.               heading: {
  54.                   colspan:      3,
  55.                   content:      "Sales Totals for {row_count} Account Mgrs : &nbsp;",
  56.                   className:    "align-right"
  57.               },
  58.               columns: [
  59.                   { key:'NumClients', content:"{Avg} avg", className:"clientAvg" },
  60.                   { key:'SalesTTM',   content:"{sum}", className:"salesTotal", formatter:fmtCurrency }
  61.               ]
  62.           }
  63.       });

  64.       dtable.render('#salesDT');


  65.   @module FooterView
  66.   @class Y.FooterView
  67.   @extends Y.View
  68.   @author Todd Smith
  69.   @version 1.1.0
  70.   @since 3.6.0
  71.   **/
  72.  Y.FooterView = Y.Base.create( 'tableFooter', Y.View, [], {

  73.       /**
  74.        Defines the default TD HTML template for a calculated field within the footer
  75.        @property TMPL_td
  76.        @type String
  77.        @default '<td class="yui3-datatable-even {tdClass}">{content}</td>'
  78.        @static
  79.        @since 3.6.0
  80.        @protected
  81.        **/
  82.       TMPL_td: '<td class="yui3-datatable-even {tdClass}">{content}</td>',

  83.       /**
  84.        Defines the default TH HTML template for the header content within the footer
  85.        @property TMPL_th
  86.        @type String
  87.        @default '<th colspan="{colspan}" class="{thClass}">{content}</th>'
  88.        @static
  89.        @since 3.6.0
  90.        @protected
  91.        **/
  92.       TMPL_th: '<th colspan="{colspan}" class="{thClass}">{content}</th>',

  93.       /**
  94.        Defines the default TR HTML template for the footer
  95.        @property TMPL_tr
  96.        @type String
  97.        @default '<tr>{th_content}{td_content}</tr>'
  98.        @static
  99.        @since 3.6.0
  100.        @protected
  101.        **/
  102.       TMPL_tr:    '<tr>{th_content}{td_content}</tr>',

  103.       /**
  104.        Defines the default TFOOT HTML template for the footer
  105.        @property TMPL_tfoot
  106.        @type String
  107.        @default '<tfoot class="{footClass}"><tr>{th_content}{td_content}</tr></tfoot>'
  108.        @static
  109.        @since 3.6.0
  110.        @protected
  111.        **/
  112.       TMPL_tfoot: '<tfoot class="{footClass}"><tr>{th_content}{td_content}</tr></tfoot>',


  113.       /**
  114.        Defines the default TABLE HTML template for the "fixed" footer type ... i.e. with scrolling
  115.        @property TMPL_table_fixed
  116.        @type String
  117.        @default '<table cellspacing="0" aria-hidden="true" class="{className}"></table>'
  118.        @static
  119.        @since 3.6.0
  120.        @protected
  121.        **/
  122.       TMPL_table_fixed: '<table cellspacing="0" aria-hidden="true" class="{className}"></table>',


  123.       dateFormat:  '%D',
  124.       timeFormat:  '%T',

  125.       // replacer function
  126.       fnReplace  : Y.Lang.sub,

  127.       /**
  128.        Storage array of objects, each object represents a rendered "cell or column" within the
  129.        footer view.  The first element is typically a TH element (with a colspan), and the
  130.        remaining elements are the TD's for each requested footer field.

  131.        Created and populated by the render() method

  132.        @property node_cols
  133.        @type Array of Object hashes
  134.        @default null
  135.        @static
  136.        @since 3.6.0
  137.        @protected
  138.        **/
  139.       node_cols : null,   // array of col_key map (e.g. '_head', null, 'f_name' )

  140.       /**
  141.        Placeholder for subscriber event handles, used to destroy cleanly
  142.        @property _subscr
  143.        @type {EventHandles} Array of eventhandles
  144.        @default null
  145.        @static
  146.        @since 3.6.0
  147.        @private
  148.        **/
  149.       _subscr : null,

  150.       /**
  151.        DataTable instance that utilizes this footerview, derived from initializer "config.host"
  152.        Used to reference changes to DT modellist, and to retrieve the underlying "data"

  153.        @property _dt
  154.        @type Y.DataTable
  155.        @default null
  156.        @static
  157.        @since 3.6.0
  158.        @private
  159.        **/
  160.       _dt: null,

  161.       /**
  162.        * Called when view is initialized.  Stores reference to calling DataTable and
  163.        *  creates listeners to link building or refreshing the footer back to the
  164.        *  parent DataTable.
  165.        *
  166.        * @method initializer
  167.        * @param {Object} config
  168.        * @protected
  169.        */
  170.       initializer: function(config) {
  171.           config || (config={});

  172.           // Set a reference to the calling DataTable
  173.           this._dt = ( config.source ) ? config.source :  config.host;    // reference to DT instance

  174.           // Setup listeners ...
  175.           this._subscr = [];

  176.           //  ... For scrollable with fixed footer, we have to build a new TABLE and append outside of scrolling ...
  177.           if ( config.footerConfig && config.footerConfig.fixed && this._dt.get('scrollable') ) {
  178.               this._subscr.push( Y.Do.after( this._buildFixedFooter, this._dt, '_syncScrollUI', this._dt) );
  179.           }

  180.           // Listen for changes on the DataTable "data" ...
  181.           this._subscr.push( this._dt.data.after(['*:change','*:add','*:create', '*:remove', '*:reset'], Y.bind('refreshFooter', this) ) );

  182.       },

  183.       /**
  184.        * Default destructor method, cleans up the listeners that were created and
  185.        *  removes and/or empties the created DOM elements for the footerView.
  186.        *
  187.        * @method destructor
  188.        * @protected
  189.        */
  190.       destructor: function () {
  191.           Y.Array.each(this._subscr,function(item){
  192.               item.detach();
  193.           });
  194.           this._dt._tfootNode.empty();
  195.           if ( this._dt._yScrollFooter ) this._dt._yScrollFooter.empty();
  196.       },


  197.       /**
  198.        * Creates the DOM elements and attaches them to the footerView container.
  199.        *  Reads the configuration parameters (i.e. from DataTable's config as "footerConfig")
  200.        *  and structures a single TR element, with a leading TH in first column, and the
  201.        *  requested TD elements following.
  202.        *
  203.        * @method render
  204.        * @public
  205.        * @chainable
  206.        * @return this
  207.        */
  208.       render: function(){
  209.           var foot_cont = this.get('container'),      // reference to the TFOOT, created by DataTable
  210.               table_obj = this._dt,                   // reference to the parent DataTable instance
  211.               columns   = table_obj.get('columns'),   // reference to the ModelList / or DataTable.data
  212.               rs_data   = table_obj.get("data"),      // reference to the ModelList / or DataTable.data
  213.               foot_cfg  = table_obj.get('footerConfig'),    // placeholder for the 'footer' config
  214.               foot_cols = foot_cfg.columns,           // placeholder for the 'footer'.config.columns entry
  215.               tfoot_th  = '',                         // the string for the TH node
  216.               tfoot_td  = '',                         // the string for the TD node
  217.               cspan     = 1;                          // colSpan entry for TH, default to 1

  218.           this.node_cols = [];

  219.           //
  220.           //  Initialize date and time formats
  221.           //
  222.           this.dateFormat = ( foot_cfg && foot_cfg.dateFormat ) ? foot_cfg.dateFormat
  223.               : ( table_obj.get('dateFormat') ) ? table_obj.get('dateFormat')
  224.               : this.dateFormat;

  225.           this.timeFormat = ( foot_cfg && foot_cfg.timeFormat ) ? foot_cfg.timeFormat
  226.               : ( table_obj.get('timeFormat') ) ? table_obj.get('timeFormat')
  227.               : this.timeFormat;

  228.           // define a default replacer object ...
  229.           var replacer_obj = {
  230.               ROW_COUNT : rs_data.size(),
  231.               COL_COUNT : columns.length,
  232.               DATE:       Y.DataType.Date.format( new Date(), { format: this.dateFormat }),
  233.               TIME:       Y.DataType.Date.format( new Date(), { format: this.timeFormat })
  234.           };
  235.           // duplicate above, for lowercase
  236.           Y.Object.each(replacer_obj,function(val,key,obj){
  237.               obj[ key.toLowerCase() ] = val;
  238.           });

  239.           //
  240.           //  Process the TH part
  241.           //
  242.           if ( foot_cfg.heading ) {
  243.               cspan = foot_cfg.heading.colspan || cspan;
  244.               tfoot_th = this.fnReplace( this.TMPL_th, {
  245.                   colspan: cspan,
  246.                   thClass: ' ' + (foot_cfg.heading.className || ''),
  247.                   content: this.fnReplace( foot_cfg.heading.content, replacer_obj )
  248.               });

  249.               var th_item = {
  250.                   index:         0,
  251.                   key:           0,
  252.                   td:            null,
  253.                   th:        foot_cfg.heading,
  254.                   className: foot_cfg.heading.className || '',
  255.                   formatter: '',
  256.                   content:   null
  257.               };

  258.               // save this for later ... used by refreshFooter
  259.               this.node_cols.push(th_item);
  260.           }

  261.           //
  262.           //  Make an array for the remainder TD's in the Footer
  263.           //
  264.           var num_tds = columns.length - cspan;
  265.           var td_html = [];     // an array of objects to hold footer TD (non-TH!) data

  266.           for(var i=cspan; i<columns.length; i++) {
  267.               var titem = columns[i];
  268.               td_html.push({
  269.                   index:         i,
  270.                   key:           titem.key,
  271.                   td:            null,
  272.                   th:        null,
  273.                   className: titem.className || '',   // copy over this DT's column class
  274.                   formatter: titem.formatter || '',   //                   and formatter
  275.                   content:   null
  276.               });
  277.           }

  278.           //
  279.           //  Augment the Footer TD's, by inserting computed values from 'footer' config
  280.           //
  281.           //   Note: Users may enter footer 'columns' in non-ascending order, thus
  282.           //         necessitating the search for column key ...
  283.           //
  284.           Y.Array.each( foot_cols, function(fitem){
  285.               var imatch = -1;
  286.               Y.Array.some( td_html, function(item,index) {
  287.                   if ( item.key === fitem.key ) {
  288.                       imatch = index;
  289.                       return true;      // true ends the loop ... so this is 'find a first'
  290.                   }
  291.               });

  292.               if ( imatch !== -1) {
  293.                   // go ahead and calculate the value for this cell, while we are building it ...
  294.                   td_html[imatch].td = this.formatFootCell( td_html[imatch], fitem );

  295.                   td_html[imatch].content = fitem.content || null;
  296.                   td_html[imatch].foot_cfg = fitem;

  297.                   if ( fitem.formatter )
  298.                       td_html[imatch].formatter = fitem.formatter;

  299.                   if ( fitem.className )
  300.                       td_html[imatch].className = fitem.className;
  301.               }

  302.           }, this);

  303.           //
  304.           //  and Build out the TD string ... looping over the non-TH columns
  305.           //
  306.           Y.Array.each( td_html, function(item){
  307.               item.td = item.td || '';  // if nothing defined, fill with ''
  308.               item.content = item.content || null;

  309.               tfoot_td += this.fnReplace( this.TMPL_td, {
  310.                   tdClass: item.className || '',
  311.                   content: item.td
  312.               });

  313.               this.node_cols.push( item );
  314.           }, this);

  315.           //
  316.           //  Now construct the TR and the outer TFOOT and add it
  317.           //
  318.           var trClass = this._dt.getClassName('footer');
  319.           tr_tmpl = this.TMPL_tfoot;

  320.           var tr = this.fnReplace( tr_tmpl, {
  321.               footClass:  trClass,
  322.               th_content: tfoot_th,
  323.               td_content: tfoot_td
  324.           });

  325.           var foot_tr = foot_cont.append( Y.Node.create(tr) );

  326.           this.fire('renderFooter');

  327.           return this;

  328.       },

  329.      /**
  330.       * Fires after the footer has been created and rendered.
  331.       * @event renderFooter
  332.       * @param none
  333.       */

  334. // --------------------------------------------------------------------------------
  335. //               Public Methods
  336. // --------------------------------------------------------------------------------

  337.       /**
  338.        * Calculates a DataSet summary item defined in 'calc' for the given colKey, by
  339.        *   looping through the current "data" (i.e. Recordset).
  340.        *
  341.        *   Currently, the 'calc' is set to lowercase ...
  342.        *
  343.        * Example calc settings are as follows (for given 'colKey');
  344.        *
  345.        * {sum}          Calculate the arithmetic sum of dataset
  346.        * {min}          Calculate the minimum value within the dataset
  347.        * {max}          Calculate the maximum value within the dataset
  348.        * {avg}          Calculate the arithmetic average of the datset
  349.        *                (synonyms are {mean}, {average})
  350.        *
  351.        * Also, non-dataset iterating calcs are;
  352.        *  {row_count}    Returns the number of rows in the dataset
  353.        *  {col_count}  Returns the number of columns in the dataset (no visibility check)
  354.        *  {date}                 Returns the current date (via dateFormat setting)
  355.        *  {time}                 Returns the current time (via timeFormat setting)
  356.        *
  357.        * If 'calc' argument is a function(), then call it (in the "this" context of this
  358.        *  FooterView) with one argument, the DataTable.data property.
  359.        *
  360.        * Doesn't handle non-numeric calculations (i.e. `Date` or `String`)
  361.        *
  362.        * TODO:  Consider one call to this (with mult keys) for one loop thru only ...
  363.        *
  364.        *  not a really possible use case, but ...
  365.        *  whatif user tries to enter calc='this is a {sum} and {min} value' ??
  366.        *
  367.        * @method calcDatasetValue
  368.        * @param {String} colKey  The column key name to be calculated
  369.        * @param {String} calc    A recognizable calc setting from above
  370.        * @return {Number} the return value
  371.        * @public
  372.        */
  373.       calcDatasetValue: function(colKey, calc) {

  374.           var rs_data = this._dt.get("data"),    // this is a modelList currently
  375.               rcalc   = 0;

  376.           // If a string, then process it ....

  377.           if ( Y.Lang.isString(calc) ) {
  378.               var lcalc = calc.toLowerCase(),
  379.                   avg   = lcalc.search(/{average}|{avg}|{mean}/i);  // a flag for knowing if averaging is to be done

  380.               //
  381.               //  initial case, if non-summary item, just return it!
  382.               //   Note: these probably shouldn't be used in a TD column,
  383.               //        but sometimes people may do this ...
  384.               //
  385.               if ( lcalc.search(/{row_count}/) !== -1 )         return rs_data.size();
  386.               if ( lcalc.search(/{col_count}/) !== -1  )        return this._dt.get("columns").length;
  387.               if ( lcalc.search(/{date}/) !== -1  )             return Y.DataType.Date.format( new Date(), { format: this.dateFormat });
  388.               if ( lcalc.search(/{time}/) !== -1  )             return Y.DataType.Date.format( new Date(), { format: this.timeFormat });

  389.               //
  390.               //  If a min or max, set initial value to first
  391.               //
  392.               if ( lcalc.search(/{min}|{max}/) !== -1 )
  393.                   rcalc = parseFloat(rs_data.item(0).get(colKey) );

  394.               //
  395.               //  March thru the dataset, operating on the 'calc' item
  396.               //
  397.               rs_data.each( function(item) {
  398.                   var colItem = item.get(colKey),
  399.                       rflt    = +colItem;
  400.            /*
  401.               TODO:  For handling date / string set calclations ...

  402.                     rflt    = (Y.Lang.isNumber(colItem) && colItem ) ? parseFloat(colItem) : null,
  403.                     rstr    = (Y.Lang.isString(colItem) && colItem ) ? colItem : null,
  404.                     rdate   = ( colItem.now ) ? rdate : null;
  405.             */

  406.                   if ( lcalc.search(/{sum}/) !== -1 || avg !==-1 )
  407.                       rcalc += rflt;

  408.                   else if ( lcalc.search(/{min}/) !== -1 )
  409.                       rcalc = Math.min( rcalc, rflt );

  410.                   else if ( lcalc.search(/{max}/) !== -1 )
  411.                       rcalc = Math.max( rcalc, rflt );

  412.                   else
  413.                       rcalc = calc;
  414.               });

  415.               //
  416.               //  Post-process the data (mostly for averages) prior to returning
  417.               //
  418.               if ( avg !== -1 )
  419.                   rcalc = ( !rs_data.isEmpty() ) ? ( parseFloat(rcalc)/parseFloat(rs_data.size()) ) : 0;

  420.               return parseFloat(rcalc);   // processed later in formatFootCell to proper output format

  421.           }

  422.           // If numeric, just return it the data .. unformatted
  423.           if ( Y.Lang.isNumber(calc) )
  424.               return calc;

  425.           // If a function was entered, execute it in DataTable context, passing the "data" set as argument
  426.           if ( Y.Lang.isFunction(calc) ) {
  427.               var rtn = calc.call(this,rs_data);
  428.               return rtn;
  429.           }
  430.       },


  431.       /**
  432.        * Calculates a DataSet summary item defined in 'calc' for the given colKey, by
  433.        *
  434.        * @method formatFootCell
  435.        * @param {String} col  The column key name to be calculated
  436.        * @param {String} foot_col    A recognizable calc setting from above
  437.        * @return {Float} the return value
  438.        * @public
  439.        */
  440.       formatFootCell: function( col, foot_col ) {

  441.           if ( !foot_col.content ) return '';

  442.           var rval = this.calcDatasetValue( foot_col.key, foot_col.content );   // get the calculated item ...

  443.           //
  444.           // See if a custom formatter is defined ...
  445.           //   first check the footer.column for a formatter,
  446.           //   then use the column.formatter,
  447.           //   or none
  448.           // TODO: allow standard named formatters and/or function names {String}
  449.           //
  450.           var fmtr = ( foot_col.formatter && Y.Lang.isFunction(foot_col.formatter) ) ? foot_col.formatter :
  451.               ( col.formatter && Y.Lang.isFunction(col.formatter) ) ? col.formatter : null;

  452.           rval = ( fmtr && fmtr.call ) ? fmtr.call( this, {value:rval, column:col} ) : rval;

  453.           if ( Y.Lang.isFunction(foot_col.content) ) {
  454.               return rval;
  455.           } else {
  456.               var ctag = foot_col.content.match(/{.*}/)[0] || null,
  457.                   srtn = foot_col.content;
  458.               if ( ctag && Y.Lang.isString(ctag) )
  459.                   srtn = srtn.replace(ctag,rval);
  460.                return srtn;
  461.              // return this.fnReplace( foot_col.content, repl_obj);
  462.           }
  463.       },

  464.       /**
  465.        * Refreshes the summary items in the footer view and populates the footer
  466.        *  elements based on the current "data" contents.
  467.        *
  468.        * @method refreshFooter
  469.        * @return this
  470.        * @chainable
  471.        * @public
  472.        */
  473.       refreshFooter: function(){
  474.           var table_obj = this._dt,
  475.               foot_cont = table_obj._tfootNode,
  476.               td_nodes  = foot_cont.all('th,td');

  477.           //
  478.           // Loop through each footer "cell" (i.e. either a TH or TD) and
  479.           //
  480.           Y.Array.each( this.node_cols, function(fitem,findex) {
  481.               var td_html;
  482.               if ( fitem.th ) {
  483.                   var replacer_obj = {
  484.                       ROW_COUNT : table_obj.data.size(),
  485.                       COL_COUNT : table_obj.get('columns').length,
  486.                       DATE:       Y.DataType.Date.format( new Date(), { format: this.get("dateFormat") }),
  487.                       TIME:       Y.DataType.Date.format( new Date(), { format: this.get("timeFormat") })
  488.                   };

  489.                   Y.Object.each(replacer_obj,function(val,key,obj){
  490.                       obj[ key.toLowerCase() ] = val;
  491.                   });

  492.                   td_html = this.fnReplace( fitem.th.content, replacer_obj );
  493.               }

  494.               // call formatFootCell, which calculates the current cell content and formats it
  495.               if ( !fitem.th && fitem.content ) {
  496.                   td_html = this.formatFootCell( fitem, fitem.foot_cfg);
  497.               }

  498.               if ( td_html ) td_nodes.item(findex).setHTML(td_html);

  499.           }, this);

  500.           this.fire('refreshFooter');

  501.           return this;

  502.       },

  503.      /**
  504.       * Fires after the footer has been recalculated and updated.
  505.       * @event refreshFooter
  506.       * @param none
  507.       */


  508.       /**
  509.        * For scrollable tables only, adjusts the sizes of the TFOOT cells to match the widths
  510.        * of the THEAD cells.
  511.        *
  512.        * @method resizeFooter
  513.        * @return this
  514.        * @public
  515.        * @chainable
  516.        **/
  517.       resizeFooter : function() {
  518.           var table_obj = this._dt,
  519.               thead = table_obj.get('contentBox').one('.'+table_obj.getClassName('scroll','columns')),
  520.               tfootNode = this._dt._tfootNode,
  521.               tfoot_nodes = tfootNode.all('th,td');

  522.           if( table_obj._yScroll ) {

  523.               function _getNumericStyle(node,style){
  524.                   var style  = node.getComputedStyle(style),
  525.                       nstyle = (style) ? +(style.replace(/px/,'')) : 0;
  526.                   return nstyle;
  527.               }

  528.               var thead_ths = thead.all('th');

  529.               Y.Array.each( this.node_cols, function(col,i) {
  530.                   var col_width = 0.,
  531.                       thead_th;
  532.                   if ( col.th ) {
  533.                       for(var j=0; j<col.th.colspan; j++) {
  534.                           thead_th = thead_ths.item(col.index+j);
  535.                           col_width += _getNumericStyle(thead_th,'width');
  536.                       }
  537.                       col_width += col.th.colspan-1;  // subtract the 1px border between columns spanned
  538.                   } else {
  539.                       thead_th  = thead_ths.item(col.index);
  540.                       col_width = _getNumericStyle(thead_th,'width')-20;  // 20 is the padding
  541.                   }
  542.                   tfoot_nodes.item(i).setStyle('width',col_width+'px');
  543.               });
  544.           }

  545.           this.fire('resizeFooter');

  546.           return this;
  547.       },

  548.      /**
  549.       * Fires after the footer has been resized to match the parent DataTable
  550.       * @event resizeFooter
  551.       * @param none
  552.       */

  553. // --------------------------------------------------------------------------------
  554. //               Protected Methods
  555. // --------------------------------------------------------------------------------

  556.       /**
  557.        * Method that builds a separate TABLE / TFOOT container outside of the Y-scrolling
  558.        *  container and places the view "container" within this.
  559.        *
  560.        * This is specifically required for a "fixed" footer ... i.e. with a scrolling DataTable,
  561.        * where the footer is expected to remain stationary as the records are scrolled through.
  562.        *
  563.        *  NOTE: A bug exists where the viewFooter container (TFOOT) is improperly placed within
  564.        *        the y-scroller container (http://yuilibrary.com/projects/yui3/ticket/2531723)
  565.        *        This function is a workaround for that.
  566.        * @method _buildFixedFooter
  567.        * @private
  568.        */
  569.       _buildFixedFooter : function() {
  570.           var fixedFooter   = this._yScrollFooter,    // Node for footer containing TABLE element
  571.               tfoot         = this._tfootNode,
  572.               yScrollHeader = this._yScrollHeader,    // header TABLE
  573.               yScroller     = this._yScrollContainer; // Node for the DIV containing header TABLE, data TABLE and footer TABLE

  574.           if (!fixedFooter) {
  575.               var tmpl = '<table cellspacing="0" aria-hidden="true" class="{className}"></table>';

  576.               //
  577.               // Create a new TABLE, to hold the TFOOT as a fixed element "outside" of yScroller
  578.               //
  579.               fixedFooter =  Y.Node.create(
  580.                   Y.Lang.sub(this._Y_SCROLL_FOOTER_TEMPLATE || this.foot.TMPL_table_fixed  || tmpl, {
  581.                       className: this.getClassName('footer')
  582.                       //    className: this.getClassName('scroll','footer')
  583.                   }));
  584.               this._yScrollFooter = fixedFooter;

  585.               yScroller.append(fixedFooter);

  586.               //
  587.               //  Move the already created TFOOT from the old incorrect location
  588.               //   to within the new TABLE in "fixedFooter" location
  589.               //
  590.               var tfootNode = this.get('contentBox').one('table > tfoot');
  591.               this._tfootNode = tfootNode;
  592.               if ( tfootNode ) {
  593.                   this._yScrollFooter.append( tfootNode );
  594.                   this.foot.resizeFooter();
  595.               }
  596.           }

  597.       }


  598. // --------------- PSEUDO-ATTRIBUTES ... i.e. attributes expected, but in DataTable's footerConfig ------------------

  599.     /**
  600.     Flag indicating if the footer is desired to be "fixed" (i.e. non-scrolling, true) or floating with Datatable scrolling (false)
  601.     @attribute fixed
  602.     @type boolean
  603.     @default false
  604.     **/

  605.     /**
  606.     Defines the TH properties for the footer row, the leftmost column (including optional colspan)
  607.     @attribute heading
  608.     @type Object
  609.     @default null
  610.     **/

  611.     /**
  612.     A string template defining the contents of the TH column.  May include any non-set related fields, including `{row_count}`, `{col_count}`, `{date}`,`{time}`

  613.     Example:

  614.         heading.content : 'Totals for {row_count} Orders as-of {date} :'

  615.     @attribute heading.content
  616.     @type String
  617.     @default null
  618.     **/

  619.     /**
  620.     The number of columns from the DataTable columns that should be spanned for the TH in the footer
  621.     @attribute heading.colspan
  622.     @type Integer
  623.     @default 1
  624.     **/

  625.     /**
  626.     A CSS class name to be added to the TH element of the footer
  627.     @attribute heading.className
  628.     @type String
  629.     @default null
  630.     **/

  631.     /**
  632.     An array of objects, one row per *desired* column of TD representing a summary from the dataset.

  633.     Only TD's with a row included in this array will be processed and rendered, otherwise any visible
  634.      columns from the DataTable, that are not within a TH colspan, will be created as empty.
  635.     @attribute columns
  636.     @type Array
  637.     @default null
  638.     **/

  639.     /**
  640.     The dataset "key" corresponding to the columns of the DataTable for this desired TD in the footer.
  641.     @attribute columns.key
  642.     @type String
  643.     @default null
  644.     **/

  645.     /**
  646.     A string template defining the contents of this TD column in the footer.  May include any set-based (i.e. `{sum}`,`{min}`,`{max}`,`{avg}`) or non-set related fields, including `{row_count}`, `{col_count}`, `{date}`,`{time}`.
  647.     <br/>The {average} and {mean} placeholders are equivalent to {avg} in this implementation.

  648.     Example:

  649.         columns[2].content : '{sum}'

  650.     @attribute columns.content
  651.     @type String
  652.     @default null
  653.     **/

  654.     /**
  655.     A CSS class name to be added to this TD element of the footer
  656.     @attribute columns.className
  657.     @type String
  658.     @default null
  659.     **/


  660.     /**
  661.     Specifies a formatter to apply to the numeric field denoted in this TD column.  A formatter from the original DataTable columns can be specified.

  662.     If this attribute is set to null (or missing), the formatter from the DataTable column associated with the "key" (if any), will be used.

  663.     If this attribute is set to '', no formatting will be applied.

  664.     @attribute columns.formatter
  665.     @type {String|Function}
  666.     @default null
  667.     **/

  668.     /**
  669.     Specifies a strftime format string to be applied for {date} entries, using Y.DataType.Date.format
  670.     @attribute dateFormat
  671.     @type String
  672.     @default "%D"
  673.     **/

  674.     /**
  675.     Specifies a strftime format string to be applied for {time} entries, using Y.DataType.Date.format
  676.     @attribute timeFormat
  677.     @type String
  678.     @default "%T"
  679.     **/



  680.   });

  681.