- /**
- FooterView is a YUI View class extension that provides a simple, one row summary row
- to a Datatable. This view provides
- for a summary row appended to the bottom of the DataTable TBODY, typically consisting
- of **one** TH element (with a colspan) and several TD elements for each desired column
- where a "calculated field" is desired.
- View configuration provides for calculated fields based upon the all of the available
- dataset fields within the DataTable's "ModelList".
- The view works with either non-scrolling or scrolling DataTables, and allows for either a
- "fixed" view, wherein the footer remains fixed at the bottom of the DataTable contentBox
- while the table is scrolled.
- #### Calculated Fields
- The current implementation supports the following calculated fields, where they are
- identified by their placeholder tag for replacement via Y.sub (case insensitive);
- * `{sum}` Calculate the arithmetic sum of the specified column in dataset
- * `{min}` Calculate the minimum value of the specified column in dataset
- * `{max}` Calculate the maximum value of the specified column in dataset
- * `{avg}` Calculate the arithmetic average of the of the specified column (synonyms `{mean}`, `{average}`)
- Also, non-looping calcs are;
- * `{row_count}` Returns the number of rows in the dataset
- * `{col_count}` Returns the number of columns in the dataset (no visibility check)
- * `{date}` Returns the current date
- * `{time}` Returns the current time
- #### Configuration
- YUI 3.6.0 DataTable supports attributes including `footerView` and `footerConfig`.
- This FooterView recognizes the following attributes, which must be configured via the
- DataTable {configs} (see usage example below);
- * [`fixed`](#attr_fixed) : Flag indicating if footer should be fixed or floating
- * [`heading`](#attr_heading) : Object, defining the single TH as;
- * [`colspan`](#attr_heading.colspan) : Number of columns to merge from left for the TH
- * [`content`](#attr_heading.content) : A string indicating the content of the TH for the footer
- * [`className`](#attr_heading.className) : Additional classname for TH
- * [`columns`](#attr_columns) : Array of objects, one per desired TD column in footer as;
- * [`key`](#attr_columns.key) : `key` name from the DataTable columns
- * [`content`](#attr_columns.content) : String indicating the contents of this TD
- * [`className`](#attr_columns.className) : Additional classname for TD
- * [`formatter`](#attr_columns.formatter) : Formatter to apply to this column result
- * [`dateFormat`](#attr_dateFormat) : Format string to use for any {date} fields
- * [`timeFormat`](#attr_timeFormat) : Format string to use for any {time} fields
- Additionally the user can provide a valid function as a column `content` to calculate a
- custom entry for
- <br/>a column (see [`columns.content`](#attr_columns.content) or [`calcDatasetValue`](#method_calcDatasetValue))
- #### Usage
- var dtable = new Y.DataTable({
- columns: ['EmpId','FirstName','LastName','NumClients','SalesTTM'],
- data: AccountMgr.Sales,
- scrollable: 'y',
- height: '250px',
- width: '400px',
- footerView: Y.FooterView,
- footerConfig: {
- fixed: true,
- heading: {
- colspan: 3,
- content: "Sales Totals for {row_count} Account Mgrs : ",
- className: "align-right"
- },
- columns: [
- { key:'NumClients', content:"{Avg} avg", className:"clientAvg" },
- { key:'SalesTTM', content:"{sum}", className:"salesTotal", formatter:fmtCurrency }
- ]
- }
- });
- dtable.render('#salesDT');
- @module FooterView
- @class Y.FooterView
- @extends Y.View
- @author Todd Smith
- @version 1.1.0
- @since 3.6.0
- **/
- Y.FooterView = Y.Base.create( 'tableFooter', Y.View, [], {
- /**
- Defines the default TD HTML template for a calculated field within the footer
- @property TMPL_td
- @type String
- @default '<td class="yui3-datatable-even {tdClass}">{content}</td>'
- @static
- @since 3.6.0
- @protected
- **/
- TMPL_td: '<td class="yui3-datatable-even {tdClass}">{content}</td>',
- /**
- Defines the default TH HTML template for the header content within the footer
- @property TMPL_th
- @type String
- @default '<th colspan="{colspan}" class="{thClass}">{content}</th>'
- @static
- @since 3.6.0
- @protected
- **/
- TMPL_th: '<th colspan="{colspan}" class="{thClass}">{content}</th>',
- /**
- Defines the default TR HTML template for the footer
- @property TMPL_tr
- @type String
- @default '<tr>{th_content}{td_content}</tr>'
- @static
- @since 3.6.0
- @protected
- **/
- TMPL_tr: '<tr>{th_content}{td_content}</tr>',
- /**
- Defines the default TFOOT HTML template for the footer
- @property TMPL_tfoot
- @type String
- @default '<tfoot class="{footClass}"><tr>{th_content}{td_content}</tr></tfoot>'
- @static
- @since 3.6.0
- @protected
- **/
- TMPL_tfoot: '<tfoot class="{footClass}"><tr>{th_content}{td_content}</tr></tfoot>',
- /**
- Defines the default TABLE HTML template for the "fixed" footer type ... i.e. with scrolling
- @property TMPL_table_fixed
- @type String
- @default '<table cellspacing="0" aria-hidden="true" class="{className}"></table>'
- @static
- @since 3.6.0
- @protected
- **/
- TMPL_table_fixed: '<table cellspacing="0" aria-hidden="true" class="{className}"></table>',
- dateFormat: '%D',
- timeFormat: '%T',
- // replacer function
- fnReplace : Y.Lang.sub,
- /**
- Storage array of objects, each object represents a rendered "cell or column" within the
- footer view. The first element is typically a TH element (with a colspan), and the
- remaining elements are the TD's for each requested footer field.
- Created and populated by the render() method
- @property node_cols
- @type Array of Object hashes
- @default null
- @static
- @since 3.6.0
- @protected
- **/
- node_cols : null, // array of col_key map (e.g. '_head', null, 'f_name' )
- /**
- Placeholder for subscriber event handles, used to destroy cleanly
- @property _subscr
- @type {EventHandles} Array of eventhandles
- @default null
- @static
- @since 3.6.0
- @private
- **/
- _subscr : null,
- /**
- DataTable instance that utilizes this footerview, derived from initializer "config.host"
- Used to reference changes to DT modellist, and to retrieve the underlying "data"
- @property _dt
- @type Y.DataTable
- @default null
- @static
- @since 3.6.0
- @private
- **/
- _dt: null,
- /**
- * Called when view is initialized. Stores reference to calling DataTable and
- * creates listeners to link building or refreshing the footer back to the
- * parent DataTable.
- *
- * @method initializer
- * @param {Object} config
- * @protected
- */
- initializer: function(config) {
- config || (config={});
- // Set a reference to the calling DataTable
- this._dt = ( config.source ) ? config.source : config.host; // reference to DT instance
- // Setup listeners ...
- this._subscr = [];
- // ... For scrollable with fixed footer, we have to build a new TABLE and append outside of scrolling ...
- if ( config.footerConfig && config.footerConfig.fixed && this._dt.get('scrollable') ) {
- this._subscr.push( Y.Do.after( this._buildFixedFooter, this._dt, '_syncScrollUI', this._dt) );
- }
- // Listen for changes on the DataTable "data" ...
- this._subscr.push( this._dt.data.after(['*:change','*:add','*:create', '*:remove', '*:reset'], Y.bind('refreshFooter', this) ) );
- },
- /**
- * Default destructor method, cleans up the listeners that were created and
- * removes and/or empties the created DOM elements for the footerView.
- *
- * @method destructor
- * @protected
- */
- destructor: function () {
- Y.Array.each(this._subscr,function(item){
- item.detach();
- });
- this._dt._tfootNode.empty();
- if ( this._dt._yScrollFooter ) this._dt._yScrollFooter.empty();
- },
- /**
- * Creates the DOM elements and attaches them to the footerView container.
- * Reads the configuration parameters (i.e. from DataTable's config as "footerConfig")
- * and structures a single TR element, with a leading TH in first column, and the
- * requested TD elements following.
- *
- * @method render
- * @public
- * @chainable
- * @return this
- */
- render: function(){
- var foot_cont = this.get('container'), // reference to the TFOOT, created by DataTable
- table_obj = this._dt, // reference to the parent DataTable instance
- columns = table_obj.get('columns'), // reference to the ModelList / or DataTable.data
- rs_data = table_obj.get("data"), // reference to the ModelList / or DataTable.data
- foot_cfg = table_obj.get('footerConfig'), // placeholder for the 'footer' config
- foot_cols = foot_cfg.columns, // placeholder for the 'footer'.config.columns entry
- tfoot_th = '', // the string for the TH node
- tfoot_td = '', // the string for the TD node
- cspan = 1; // colSpan entry for TH, default to 1
- this.node_cols = [];
- //
- // Initialize date and time formats
- //
- this.dateFormat = ( foot_cfg && foot_cfg.dateFormat ) ? foot_cfg.dateFormat
- : ( table_obj.get('dateFormat') ) ? table_obj.get('dateFormat')
- : this.dateFormat;
- this.timeFormat = ( foot_cfg && foot_cfg.timeFormat ) ? foot_cfg.timeFormat
- : ( table_obj.get('timeFormat') ) ? table_obj.get('timeFormat')
- : this.timeFormat;
- // define a default replacer object ...
- var replacer_obj = {
- ROW_COUNT : rs_data.size(),
- COL_COUNT : columns.length,
- DATE: Y.DataType.Date.format( new Date(), { format: this.dateFormat }),
- TIME: Y.DataType.Date.format( new Date(), { format: this.timeFormat })
- };
- // duplicate above, for lowercase
- Y.Object.each(replacer_obj,function(val,key,obj){
- obj[ key.toLowerCase() ] = val;
- });
- //
- // Process the TH part
- //
- if ( foot_cfg.heading ) {
- cspan = foot_cfg.heading.colspan || cspan;
- tfoot_th = this.fnReplace( this.TMPL_th, {
- colspan: cspan,
- thClass: ' ' + (foot_cfg.heading.className || ''),
- content: this.fnReplace( foot_cfg.heading.content, replacer_obj )
- });
- var th_item = {
- index: 0,
- key: 0,
- td: null,
- th: foot_cfg.heading,
- className: foot_cfg.heading.className || '',
- formatter: '',
- content: null
- };
- // save this for later ... used by refreshFooter
- this.node_cols.push(th_item);
- }
- //
- // Make an array for the remainder TD's in the Footer
- //
- var num_tds = columns.length - cspan;
- var td_html = []; // an array of objects to hold footer TD (non-TH!) data
- for(var i=cspan; i<columns.length; i++) {
- var titem = columns[i];
- td_html.push({
- index: i,
- key: titem.key,
- td: null,
- th: null,
- className: titem.className || '', // copy over this DT's column class
- formatter: titem.formatter || '', // and formatter
- content: null
- });
- }
- //
- // Augment the Footer TD's, by inserting computed values from 'footer' config
- //
- // Note: Users may enter footer 'columns' in non-ascending order, thus
- // necessitating the search for column key ...
- //
- Y.Array.each( foot_cols, function(fitem){
- var imatch = -1;
- Y.Array.some( td_html, function(item,index) {
- if ( item.key === fitem.key ) {
- imatch = index;
- return true; // true ends the loop ... so this is 'find a first'
- }
- });
- if ( imatch !== -1) {
- // go ahead and calculate the value for this cell, while we are building it ...
- td_html[imatch].td = this.formatFootCell( td_html[imatch], fitem );
- td_html[imatch].content = fitem.content || null;
- td_html[imatch].foot_cfg = fitem;
- if ( fitem.formatter )
- td_html[imatch].formatter = fitem.formatter;
- if ( fitem.className )
- td_html[imatch].className = fitem.className;
- }
- }, this);
- //
- // and Build out the TD string ... looping over the non-TH columns
- //
- Y.Array.each( td_html, function(item){
- item.td = item.td || ''; // if nothing defined, fill with ''
- item.content = item.content || null;
- tfoot_td += this.fnReplace( this.TMPL_td, {
- tdClass: item.className || '',
- content: item.td
- });
- this.node_cols.push( item );
- }, this);
- //
- // Now construct the TR and the outer TFOOT and add it
- //
- var trClass = this._dt.getClassName('footer');
- tr_tmpl = this.TMPL_tfoot;
- var tr = this.fnReplace( tr_tmpl, {
- footClass: trClass,
- th_content: tfoot_th,
- td_content: tfoot_td
- });
- var foot_tr = foot_cont.append( Y.Node.create(tr) );
- this.fire('renderFooter');
- return this;
- },
- /**
- * Fires after the footer has been created and rendered.
- * @event renderFooter
- * @param none
- */
- // --------------------------------------------------------------------------------
- // Public Methods
- // --------------------------------------------------------------------------------
- /**
- * Calculates a DataSet summary item defined in 'calc' for the given colKey, by
- * looping through the current "data" (i.e. Recordset).
- *
- * Currently, the 'calc' is set to lowercase ...
- *
- * Example calc settings are as follows (for given 'colKey');
- *
- * {sum} Calculate the arithmetic sum of dataset
- * {min} Calculate the minimum value within the dataset
- * {max} Calculate the maximum value within the dataset
- * {avg} Calculate the arithmetic average of the datset
- * (synonyms are {mean}, {average})
- *
- * Also, non-dataset iterating calcs are;
- * {row_count} Returns the number of rows in the dataset
- * {col_count} Returns the number of columns in the dataset (no visibility check)
- * {date} Returns the current date (via dateFormat setting)
- * {time} Returns the current time (via timeFormat setting)
- *
- * If 'calc' argument is a function(), then call it (in the "this" context of this
- * FooterView) with one argument, the DataTable.data property.
- *
- * Doesn't handle non-numeric calculations (i.e. `Date` or `String`)
- *
- * TODO: Consider one call to this (with mult keys) for one loop thru only ...
- *
- * not a really possible use case, but ...
- * whatif user tries to enter calc='this is a {sum} and {min} value' ??
- *
- * @method calcDatasetValue
- * @param {String} colKey The column key name to be calculated
- * @param {String} calc A recognizable calc setting from above
- * @return {Number} the return value
- * @public
- */
- calcDatasetValue: function(colKey, calc) {
- var rs_data = this._dt.get("data"), // this is a modelList currently
- rcalc = 0;
- // If a string, then process it ....
- if ( Y.Lang.isString(calc) ) {
- var lcalc = calc.toLowerCase(),
- avg = lcalc.search(/{average}|{avg}|{mean}/i); // a flag for knowing if averaging is to be done
- //
- // initial case, if non-summary item, just return it!
- // Note: these probably shouldn't be used in a TD column,
- // but sometimes people may do this ...
- //
- if ( lcalc.search(/{row_count}/) !== -1 ) return rs_data.size();
- if ( lcalc.search(/{col_count}/) !== -1 ) return this._dt.get("columns").length;
- if ( lcalc.search(/{date}/) !== -1 ) return Y.DataType.Date.format( new Date(), { format: this.dateFormat });
- if ( lcalc.search(/{time}/) !== -1 ) return Y.DataType.Date.format( new Date(), { format: this.timeFormat });
- //
- // If a min or max, set initial value to first
- //
- if ( lcalc.search(/{min}|{max}/) !== -1 )
- rcalc = parseFloat(rs_data.item(0).get(colKey) );
- //
- // March thru the dataset, operating on the 'calc' item
- //
- rs_data.each( function(item) {
- var colItem = item.get(colKey),
- rflt = +colItem;
- /*
- TODO: For handling date / string set calclations ...
- rflt = (Y.Lang.isNumber(colItem) && colItem ) ? parseFloat(colItem) : null,
- rstr = (Y.Lang.isString(colItem) && colItem ) ? colItem : null,
- rdate = ( colItem.now ) ? rdate : null;
- */
- if ( lcalc.search(/{sum}/) !== -1 || avg !==-1 )
- rcalc += rflt;
- else if ( lcalc.search(/{min}/) !== -1 )
- rcalc = Math.min( rcalc, rflt );
- else if ( lcalc.search(/{max}/) !== -1 )
- rcalc = Math.max( rcalc, rflt );
- else
- rcalc = calc;
- });
- //
- // Post-process the data (mostly for averages) prior to returning
- //
- if ( avg !== -1 )
- rcalc = ( !rs_data.isEmpty() ) ? ( parseFloat(rcalc)/parseFloat(rs_data.size()) ) : 0;
- return parseFloat(rcalc); // processed later in formatFootCell to proper output format
- }
- // If numeric, just return it the data .. unformatted
- if ( Y.Lang.isNumber(calc) )
- return calc;
- // If a function was entered, execute it in DataTable context, passing the "data" set as argument
- if ( Y.Lang.isFunction(calc) ) {
- var rtn = calc.call(this,rs_data);
- return rtn;
- }
- },
- /**
- * Calculates a DataSet summary item defined in 'calc' for the given colKey, by
- *
- * @method formatFootCell
- * @param {String} col The column key name to be calculated
- * @param {String} foot_col A recognizable calc setting from above
- * @return {Float} the return value
- * @public
- */
- formatFootCell: function( col, foot_col ) {
- if ( !foot_col.content ) return '';
- var rval = this.calcDatasetValue( foot_col.key, foot_col.content ); // get the calculated item ...
- //
- // See if a custom formatter is defined ...
- // first check the footer.column for a formatter,
- // then use the column.formatter,
- // or none
- // TODO: allow standard named formatters and/or function names {String}
- //
- var fmtr = ( foot_col.formatter && Y.Lang.isFunction(foot_col.formatter) ) ? foot_col.formatter :
- ( col.formatter && Y.Lang.isFunction(col.formatter) ) ? col.formatter : null;
- rval = ( fmtr && fmtr.call ) ? fmtr.call( this, {value:rval, column:col} ) : rval;
- if ( Y.Lang.isFunction(foot_col.content) ) {
- return rval;
- } else {
- var ctag = foot_col.content.match(/{.*}/)[0] || null,
- srtn = foot_col.content;
- if ( ctag && Y.Lang.isString(ctag) )
- srtn = srtn.replace(ctag,rval);
- return srtn;
- // return this.fnReplace( foot_col.content, repl_obj);
- }
- },
- /**
- * Refreshes the summary items in the footer view and populates the footer
- * elements based on the current "data" contents.
- *
- * @method refreshFooter
- * @return this
- * @chainable
- * @public
- */
- refreshFooter: function(){
- var table_obj = this._dt,
- foot_cont = table_obj._tfootNode,
- td_nodes = foot_cont.all('th,td');
- //
- // Loop through each footer "cell" (i.e. either a TH or TD) and
- //
- Y.Array.each( this.node_cols, function(fitem,findex) {
- var td_html;
- if ( fitem.th ) {
- var replacer_obj = {
- ROW_COUNT : table_obj.data.size(),
- COL_COUNT : table_obj.get('columns').length,
- DATE: Y.DataType.Date.format( new Date(), { format: this.get("dateFormat") }),
- TIME: Y.DataType.Date.format( new Date(), { format: this.get("timeFormat") })
- };
- Y.Object.each(replacer_obj,function(val,key,obj){
- obj[ key.toLowerCase() ] = val;
- });
- td_html = this.fnReplace( fitem.th.content, replacer_obj );
- }
- // call formatFootCell, which calculates the current cell content and formats it
- if ( !fitem.th && fitem.content ) {
- td_html = this.formatFootCell( fitem, fitem.foot_cfg);
- }
- if ( td_html ) td_nodes.item(findex).setHTML(td_html);
- }, this);
- this.fire('refreshFooter');
- return this;
- },
- /**
- * Fires after the footer has been recalculated and updated.
- * @event refreshFooter
- * @param none
- */
- /**
- * For scrollable tables only, adjusts the sizes of the TFOOT cells to match the widths
- * of the THEAD cells.
- *
- * @method resizeFooter
- * @return this
- * @public
- * @chainable
- **/
- resizeFooter : function() {
- var table_obj = this._dt,
- thead = table_obj.get('contentBox').one('.'+table_obj.getClassName('scroll','columns')),
- tfootNode = this._dt._tfootNode,
- tfoot_nodes = tfootNode.all('th,td');
- if( table_obj._yScroll ) {
- function _getNumericStyle(node,style){
- var style = node.getComputedStyle(style),
- nstyle = (style) ? +(style.replace(/px/,'')) : 0;
- return nstyle;
- }
- var thead_ths = thead.all('th');
- Y.Array.each( this.node_cols, function(col,i) {
- var col_width = 0.,
- thead_th;
- if ( col.th ) {
- for(var j=0; j<col.th.colspan; j++) {
- thead_th = thead_ths.item(col.index+j);
- col_width += _getNumericStyle(thead_th,'width');
- }
- col_width += col.th.colspan-1; // subtract the 1px border between columns spanned
- } else {
- thead_th = thead_ths.item(col.index);
- col_width = _getNumericStyle(thead_th,'width')-20; // 20 is the padding
- }
- tfoot_nodes.item(i).setStyle('width',col_width+'px');
- });
- }
- this.fire('resizeFooter');
- return this;
- },
- /**
- * Fires after the footer has been resized to match the parent DataTable
- * @event resizeFooter
- * @param none
- */
- // --------------------------------------------------------------------------------
- // Protected Methods
- // --------------------------------------------------------------------------------
- /**
- * Method that builds a separate TABLE / TFOOT container outside of the Y-scrolling
- * container and places the view "container" within this.
- *
- * This is specifically required for a "fixed" footer ... i.e. with a scrolling DataTable,
- * where the footer is expected to remain stationary as the records are scrolled through.
- *
- * NOTE: A bug exists where the viewFooter container (TFOOT) is improperly placed within
- * the y-scroller container (http://yuilibrary.com/projects/yui3/ticket/2531723)
- * This function is a workaround for that.
- * @method _buildFixedFooter
- * @private
- */
- _buildFixedFooter : function() {
- var fixedFooter = this._yScrollFooter, // Node for footer containing TABLE element
- tfoot = this._tfootNode,
- yScrollHeader = this._yScrollHeader, // header TABLE
- yScroller = this._yScrollContainer; // Node for the DIV containing header TABLE, data TABLE and footer TABLE
- if (!fixedFooter) {
- var tmpl = '<table cellspacing="0" aria-hidden="true" class="{className}"></table>';
- //
- // Create a new TABLE, to hold the TFOOT as a fixed element "outside" of yScroller
- //
- fixedFooter = Y.Node.create(
- Y.Lang.sub(this._Y_SCROLL_FOOTER_TEMPLATE || this.foot.TMPL_table_fixed || tmpl, {
- className: this.getClassName('footer')
- // className: this.getClassName('scroll','footer')
- }));
- this._yScrollFooter = fixedFooter;
- yScroller.append(fixedFooter);
- //
- // Move the already created TFOOT from the old incorrect location
- // to within the new TABLE in "fixedFooter" location
- //
- var tfootNode = this.get('contentBox').one('table > tfoot');
- this._tfootNode = tfootNode;
- if ( tfootNode ) {
- this._yScrollFooter.append( tfootNode );
- this.foot.resizeFooter();
- }
- }
- }
- // --------------- PSEUDO-ATTRIBUTES ... i.e. attributes expected, but in DataTable's footerConfig ------------------
- /**
- Flag indicating if the footer is desired to be "fixed" (i.e. non-scrolling, true) or floating with Datatable scrolling (false)
- @attribute fixed
- @type boolean
- @default false
- **/
- /**
- Defines the TH properties for the footer row, the leftmost column (including optional colspan)
- @attribute heading
- @type Object
- @default null
- **/
- /**
- A string template defining the contents of the TH column. May include any non-set related fields, including `{row_count}`, `{col_count}`, `{date}`,`{time}`
- Example:
- heading.content : 'Totals for {row_count} Orders as-of {date} :'
- @attribute heading.content
- @type String
- @default null
- **/
- /**
- The number of columns from the DataTable columns that should be spanned for the TH in the footer
- @attribute heading.colspan
- @type Integer
- @default 1
- **/
- /**
- A CSS class name to be added to the TH element of the footer
- @attribute heading.className
- @type String
- @default null
- **/
- /**
- An array of objects, one row per *desired* column of TD representing a summary from the dataset.
- Only TD's with a row included in this array will be processed and rendered, otherwise any visible
- columns from the DataTable, that are not within a TH colspan, will be created as empty.
- @attribute columns
- @type Array
- @default null
- **/
- /**
- The dataset "key" corresponding to the columns of the DataTable for this desired TD in the footer.
- @attribute columns.key
- @type String
- @default null
- **/
- /**
- 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}`.
- <br/>The {average} and {mean} placeholders are equivalent to {avg} in this implementation.
- Example:
- columns[2].content : '{sum}'
- @attribute columns.content
- @type String
- @default null
- **/
- /**
- A CSS class name to be added to this TD element of the footer
- @attribute columns.className
- @type String
- @default null
- **/
- /**
- Specifies a formatter to apply to the numeric field denoted in this TD column. A formatter from the original DataTable columns can be specified.
- If this attribute is set to null (or missing), the formatter from the DataTable column associated with the "key" (if any), will be used.
- If this attribute is set to '', no formatting will be applied.
- @attribute columns.formatter
- @type {String|Function}
- @default null
- **/
- /**
- Specifies a strftime format string to be applied for {date} entries, using Y.DataType.Date.format
- @attribute dateFormat
- @type String
- @default "%D"
- **/
- /**
- Specifies a strftime format string to be applied for {time} entries, using Y.DataType.Date.format
- @attribute timeFormat
- @type String
- @default "%T"
- **/
- });
-