# Yahoo! UI Library

### datatable 3.3.0

Yahoo! UI Library > datatable > datatable-base.js (source view)
Search:
Filters Show Private Show Protected Show Deprecated

    /**
     * The DataTable widget provides a progressively enhanced DHTML control for
     * displaying tabular data across A-grade browsers.
     *
     * @module datatable
     */

    /**
     * Provides the base DataTable implementation, which can be extended to add
     * additional functionality, such as sorting or scrolling.
     *
     * @module datatable
     * @submodule datatable-base
     */

    /**
     * Base class for the DataTable widget.
     * @class DataTable.Base
     * @extends Widget
     * @constructor
     */
    function DTBase(config) {
        DTBase.superclass.constructor.apply(this, arguments);
    }

    /////////////////////////////////////////////////////////////////////////////
    //
    // STATIC PROPERTIES
    //
    /////////////////////////////////////////////////////////////////////////////
    Y.mix(DTBase, {

        /**
         * Class name.
         *
         * @property NAME
         * @type String
         * @static
         * @final
         * @value "dataTable"
         */
        NAME:  "dataTable",

    /////////////////////////////////////////////////////////////////////////////
    //
    // ATTRIBUTES
    //
    /////////////////////////////////////////////////////////////////////////////
        ATTRS: {
            /**
            * @attribute columnset
            * @description Pointer to Columnset instance.
            * @type Array | Y.Columnset
            */
            columnset: {
                setter: "_setColumnset"
            },

            /**
            * @attribute recordset
            * @description Pointer to Recordset instance.
            * @type Array | Y.Recordset
            */
            recordset: {
                value: new Y.Recordset({records:[]}),
                setter: "_setRecordset"
            },

            /*TODO
            * @attribute state
            * @description Internal state.
            * @readonly
            * @type
            */
            /*state: {
                value: new Y.State(),
                readOnly: true

            },*/

            /**
            * @attribute summary
            * @description Summary.
            * @type String
            */
            summary: {
            },

            /**
            * @attribute caption
            * @description Caption
            * @type String
            */
            caption: {
            },

            /**
            * @attribute thValueTemplate
            * @description Tokenized markup template for TH value.
            * @type String
            * @default '{value}'
            */
            thValueTemplate: {
                value: TEMPLATE_VALUE
            },

            /**
            * @attribute tdValueTemplate
            * @description Tokenized markup template for TD value.
            * @type String
            * @default '{value}'
            */
            tdValueTemplate: {
                value: TEMPLATE_VALUE
            },

            /**
            * @attribute trTemplate
            * @description Tokenized markup template for TR node creation.
            * @type String
            * @default '<tr id="{id}"></tr>'
            */
            trTemplate: {
                value: TEMPLATE_TR
            }
        },

    /////////////////////////////////////////////////////////////////////////////
    //
    // TODO: HTML_PARSER
    //
    /////////////////////////////////////////////////////////////////////////////
        HTML_PARSER: {
            /*caption: function (srcNode) {

            }*/
        }
    });

    /////////////////////////////////////////////////////////////////////////////
    //
    // PROTOTYPE
    //
    /////////////////////////////////////////////////////////////////////////////
    Y.extend(DTBase, Y.Widget, {
        /**
        * @property thTemplate
        * @description Tokenized markup template for TH node creation.
        * @type String
        * @default '<th id="{id}" rowspan="{rowspan}" colspan="{colspan}" class="{classnames}" abbr="{abbr}"><div class="'+CLASS_LINER+'">{value}</div></th>'
        */
        thTemplate: TEMPLATE_TH,

        /**
        * @property tdTemplate
        * @description Tokenized markup template for TD node creation.
        * @type String
        * @default '<td headers="{headers}"><div class="'+CLASS_LINER+'">{value}</div></td>'
        */
        tdTemplate: TEMPLATE_TD,

        /**
        * @property _theadNode
        * @description Pointer to THEAD node.
        * @type Y.Node
        * @private
        */
        _theadNode: null,

        /**
        * @property _tbodyNode
        * @description Pointer to TBODY node.
        * @type Y.Node
        * @private
        */
        _tbodyNode: null,

        /**
        * @property _msgNode
        * @description Pointer to message display node.
        * @type Y.Node
        * @private
        */
        _msgNode: null,

        /////////////////////////////////////////////////////////////////////////////
        //
        // ATTRIBUTE HELPERS
        //
        /////////////////////////////////////////////////////////////////////////////
        /**
        * @method _setColumnset
        * @description Converts Array to Y.Columnset.
        * @param columns {Array | Y.Columnset}
        * @returns Y.Columnset
        * @private
        */
        _setColumnset: function(columns) {
            return YLang.isArray(columns) ? new Y.Columnset({definitions:columns}) : columns;
        },

        /**
         * Updates the UI if Columnset is changed.
         *
         * @method _afterColumnsetChange
         * @param e {Event} Custom event for the attribute change.
         * @protected
         */
        _afterColumnsetChange: function (e) {
            if(this.get("rendered")) {
                this._uiSetColumnset(e.newVal);
            }
        },

        /**
        * @method _setRecordset
        * @description Converts Array to Y.Recordset.
        * @param records {Array | Y.Recordset}
        * @returns Y.Recordset
        * @private
        */
        _setRecordset: function(rs) {
            if(YLang.isArray(rs)) {
                rs = new Y.Recordset({records:rs});
            }

            rs.addTarget(this);
            return rs;
        },

        /**
        * Updates the UI if Recordset is changed.
        *
        * @method _afterRecordsetChange
        * @param e {Event} Custom event for the attribute change.
        * @protected
        */
        _afterRecordsetChange: function (e) {
            if(this.get("rendered")) {
                this._uiSetRecordset(e.newVal);
            }
        },

        /**
         * Updates the UI if summary is changed.
         *
         * @method _afterSummaryChange
         * @param e {Event} Custom event for the attribute change.
         * @protected
         */
        _afterSummaryChange: function (e) {
            if(this.get("rendered")) {
                this._uiSetSummary(e.newVal);
            }
        },

        /**
         * Updates the UI if caption is changed.
         *
         * @method _afterCaptionChange
         * @param e {Event} Custom event for the attribute change.
         * @protected
         */
        _afterCaptionChange: function (e) {
            if(this.get("rendered")) {
                this._uiSetCaption(e.newVal);
            }
        },

        /////////////////////////////////////////////////////////////////////////////
        //
        // METHODS
        //
        /////////////////////////////////////////////////////////////////////////////
        /**
        * Initializer.
        *
        * @method initializer
        * @param config {Object} Config object.
        * @private
        */
        initializer: function(config) {
            this.after("columnsetChange", this._afterColumnsetChange);
            this.after("recordsetChange", this._afterRecordsetChange);
            this.after("summaryChange", this._afterSummaryChange);
            this.after("captionChange", this._afterCaptionChange);
        },

        /**
        * Destructor.
        *
        * @method destructor
        * @private
        */
        destructor: function() {
             this.get("recordset").removeTarget(this);
        },

        ////////////////////////////////////////////////////////////////////////////
        //
        // RENDER
        //
        ////////////////////////////////////////////////////////////////////////////

        /**
        * Renders UI.
        *
        * @method renderUI
        * @private
        */
        renderUI: function() {
            // TABLE
            return (this._addTableNode(this.get("contentBox")) &&
            // COLGROUP
            this._addColgroupNode(this._tableNode) &&
            // THEAD
            this._addTheadNode(this._tableNode) &&
            // Primary TBODY
            this._addTbodyNode(this._tableNode) &&
            // Message TBODY
            this._addMessageNode(this._tableNode) &&
            // CAPTION
            this._addCaptionNode(this._tableNode));
       },

        /**
        * Creates and attaches TABLE element to given container.
        *
        * @method _addTableNode
        * @param containerNode {Y.Node} Parent node.
        * @protected
        * @returns Y.Node
        */
        _addTableNode: function(containerNode) {
            if (!this._tableNode) {
                this._tableNode = containerNode.appendChild(Ycreate(TEMPLATE_TABLE));
            }
            return this._tableNode;
        },

        /**
        * Creates and attaches COLGROUP element to given TABLE.
        *
        * @method _addColgroupNode
        * @param tableNode {Y.Node} Parent node.
        * @protected
        * @returns Y.Node
        */
        _addColgroupNode: function(tableNode) {
            // Add COLs to DOCUMENT FRAGMENT
            var len = this.get("columnset").keys.length,
                i = 0,
                allCols = ["<colgroup>"];

            for(; i<len; ++i) {
                allCols.push(TEMPLATE_COL);
            }

            allCols.push("</colgroup>");

            // Create COLGROUP
            this._colgroupNode = tableNode.insertBefore(Ycreate(allCols.join("")), tableNode.get("firstChild"));

            return this._colgroupNode;
        },

        /**
        * Creates and attaches THEAD element to given container.
        *
        * @method _addTheadNode
        * @param tableNode {Y.Node} Parent node.
        * @protected
        * @returns Y.Node
        */
        _addTheadNode: function(tableNode) {
            if(tableNode) {
                this._theadNode = tableNode.insertBefore(Ycreate(TEMPLATE_THEAD), this._colgroupNode.next());
                return this._theadNode;
            }
        },

        /**
        * Creates and attaches TBODY element to given container.
        *
        * @method _addTbodyNode
        * @param tableNode {Y.Node} Parent node.
        * @protected
        * @returns Y.Node
        */
        _addTbodyNode: function(tableNode) {
            this._tbodyNode = tableNode.appendChild(Ycreate(TEMPLATE_TBODY));
            return this._tbodyNode;
        },

        /**
        * Creates and attaches message display element to given container.
        *
        * @method _addMessageNode
        * @param tableNode {Y.Node} Parent node.
        * @protected
        * @returns Y.Node
        */
        _addMessageNode: function(tableNode) {
            this._msgNode = tableNode.insertBefore(Ycreate(TEMPLATE_MSG), this._tbodyNode);
            return this._msgNode;
        },

        /**
        * Creates and attaches CAPTION element to given container.
        *
        * @method _addCaptionNode
        * @param tableNode {Y.Node} Parent node.
        * @protected
        * @returns Y.Node
        */
        _addCaptionNode: function(tableNode) {
            this._captionNode = tableNode.createCaption();
            return this._captionNode;
        },

        ////////////////////////////////////////////////////////////////////////////
        //
        // BIND
        //
        ////////////////////////////////////////////////////////////////////////////

        /**
        * Binds events.
        *
        * @method bindUI
        * @private
        */
        bindUI: function() {
            var theadFilter = "thead."+CLASS_COLUMNS+">tr>th",
                tbodyFilter ="tbody."+CLASS_DATA+">tr>td",
                msgFilter = "tbody."+CLASS_MSG+">tr>td";
        },

        delegate: function(type) {
            //TODO: is this necessary?
            if(type==="dblclick") {
                this.get("boundingBox").delegate.apply(this.get("boundingBox"), arguments);
            }
            else {
                this.get("contentBox").delegate.apply(this.get("contentBox"), arguments);
            }
        },

        ////////////////////////////////////////////////////////////////////////////
        //
        // SYNC
        //
        ////////////////////////////////////////////////////////////////////////////

        /**
        * Syncs UI to intial state.
        *
        * @method syncUI
        * @private
        */
        syncUI: function() {
            // THEAD ROWS
            this._uiSetColumnset(this.get("columnset"));
            // DATA ROWS
            this._uiSetRecordset(this.get("recordset"));
            // SUMMARY
            this._uiSetSummary(this.get("summary"));
            // CAPTION
            this._uiSetCaption(this.get("caption"));
        },

        /**
         * Updates summary.
         *
         * @method _uiSetSummary
         * @param val {String} New summary.
         * @protected
         */
        _uiSetSummary: function(val) {
            val = YisValue(val) ? val : "";
            this._tableNode.set("summary", val);
        },

        /**
         * Updates caption.
         *
         * @method _uiSetCaption
         * @param val {String} New caption.
         * @protected
         */
        _uiSetCaption: function(val) {
            val = YisValue(val) ? val : "";
            this._captionNode.setContent(val);
        },

        ////////////////////////////////////////////////////////////////////////////
        //
        // THEAD/COLUMNSET FUNCTIONALITY
        //
        ////////////////////////////////////////////////////////////////////////////
        /**
         * Updates THEAD.
         *
         * @method _uiSetColumnset
         * @param cs {Y.Columnset} New Columnset.
         * @protected
         */
        _uiSetColumnset: function(cs) {
            var tree = cs.tree,
                thead = this._theadNode,
                i = 0,
                len = tree.length,
                parent = thead.get("parentNode"),
                nextSibling = thead.next();

            // Move THEAD off DOM
            thead.remove();

            thead.get("children").remove(true);

            // Iterate tree of columns to add THEAD rows
            for(; i<len; ++i) {
                this._addTheadTrNode({thead:thead, columns:tree[i]}, (i === 0), (i === len-1));
            }

            // Column helpers needs _theadNode to exist
            //this._createColumnHelpers();

            // Re-attach THEAD to DOM
            parent.insert(thead, nextSibling);

         },

        /**
        * Creates and attaches header row element.
        *
        * @method _addTheadTrNode
        * @param o {Object} {thead, columns}.
        * @param isFirst {Boolean} Is first row.
        * @param isFirst {Boolean} Is last row.
        * @protected
        */
         _addTheadTrNode: function(o, isFirst, isLast) {
            o.tr = this._createTheadTrNode(o, isFirst, isLast);
            this._attachTheadTrNode(o);
         },

        /**
        * Creates header row element.
        *
        * @method _createTheadTrNode
        * @param o {Object} {thead, columns}.
        * @param isFirst {Boolean} Is first row.
        * @param isLast {Boolean} Is last row.
        * @protected
        * @returns Y.Node
        */
        _createTheadTrNode: function(o, isFirst, isLast) {
            //TODO: custom classnames
            var tr = Ycreate(Ysubstitute(this.get("trTemplate"), o)),
                i = 0,
                columns = o.columns,
                len = columns.length,
                column;

             // Set FIRST/LAST class
            if(isFirst) {
                tr.addClass(CLASS_FIRST);
            }
            if(isLast) {
                tr.addClass(CLASS_LAST);
            }

            for(; i<len; ++i) {
                column = columns[i];
                this._addTheadThNode({value:column.get("label"), column: column, tr:tr});
            }

            return tr;
        },

        /**
        * Attaches header row element.
        *
        * @method _attachTheadTrNode
        * @param o {Object} {thead, columns, tr}.
        * @protected
        */
        _attachTheadTrNode: function(o) {
            o.thead.appendChild(o.tr);
        },

        /**
        * Creates and attaches header cell element.
        *
        * @method _addTheadThNode
        * @param o {Object} {value, column, tr}.
        * @protected
        */
        _addTheadThNode: function(o) {
            o.th = this._createTheadThNode(o);
            this._attachTheadThNode(o);
            //TODO: assign all node pointers: thNode, thLinerNode, thLabelNode
            o.column.thNode = o.th;
        },

        /**
        * Creates header cell element.
        *
        * @method _createTheadThNode
        * @param o {Object} {value, column, tr}.
        * @protected
        * @returns Y.Node
        */
        _createTheadThNode: function(o) {
            var column = o.column;

            // Populate template object
            o.id = column.get("id");//TODO: validate 1 column ID per document
            o.colspan = column.colSpan;
            o.rowspan = column.rowSpan;
            o.abbr = column.get("abbr");
            o.classnames = column.get("classnames");
            o.value = Ysubstitute(this.get("thValueTemplate"), o);

            /*TODO
            // Clear minWidth on hidden Columns
            if(column.get("hidden")) {
                //this._clearMinWidth(column);
            }
            */

            return Ycreate(Ysubstitute(this.thTemplate, o));
        },

        /**
        * Attaches header cell element.
        *
        * @method _attachTheadThNode
        * @param o {Object} {value, column, tr}.
        * @protected
        */
        _attachTheadThNode: function(o) {
            o.tr.appendChild(o.th);
        },

        ////////////////////////////////////////////////////////////////////////////
        //
        // TBODY/RECORDSET FUNCTIONALITY
        //
        ////////////////////////////////////////////////////////////////////////////
        /**
         * Updates TBODY.
         *
         * @method _uiSetRecordset
         * @param rs {Y.Recordset} New Recordset.
         * @protected
         */
        _uiSetRecordset: function(rs) {
            var i = 0,//TODOthis.get("state.offsetIndex")
                len = rs.getLength(), //TODOthis.get("state.pageLength")
                oldTbody = this._tbodyNode,
                parent = oldTbody.get("parentNode"),
                nextSibling = oldTbody.next(),
                o = {},
                newTbody;

            // Replace TBODY with a new one
            //TODO: split _addTbodyNode into create/attach
            oldTbody.remove();
            oldTbody = null;
            newTbody = this._addTbodyNode(this._tableNode);
            newTbody.remove();
            this._tbodyNode = newTbody;
            o.tbody = newTbody;

            // Iterate Recordset to use existing TR when possible or add new TR
            for(; i<len; ++i) {
                o.record = rs.getRecord(i);
                o.rowindex = i;
                this._addTbodyTrNode(o); //TODO: sometimes rowindex != recordindex
            }

            // TBODY to DOM
            parent.insert(this._tbodyNode, nextSibling);
        },

        /**
        * Creates and attaches data row element.
        *
        * @method _addTbodyTrNode
        * @param o {Object} {tbody, record}
        * @protected
        */
        _addTbodyTrNode: function(o) {
            var tbody = o.tbody,
                record = o.record;
            o.tr = tbody.one("#"+record.get("id")) || this._createTbodyTrNode(o);
            this._attachTbodyTrNode(o);
        },

        /**
        * Creates data row element.
        *
        * @method _createTbodyTrNode
        * @param o {Object} {tbody, record}
        * @protected
        * @returns Y.Node
        */
        _createTbodyTrNode: function(o) {
            var tr = Ycreate(Ysubstitute(this.get("trTemplate"), {id:o.record.get("id")})),
                i = 0,
                allKeys = this.get("columnset").keys,
                len = allKeys.length;

            o.tr = tr;

            for(; i<len; ++i) {
                o.column = allKeys[i];
                this._addTbodyTdNode(o);
            }

            return tr;
        },

        /**
        * Attaches data row element.
        *
        * @method _attachTbodyTrNode
        * @param o {Object} {tbody, record, tr}.
        * @protected
        */
        _attachTbodyTrNode: function(o) {
            var tbody = o.tbody,
                tr = o.tr,
                index = o.rowindex,
                nextSibling = tbody.get("children").item(index) || null,
                isEven = (index%2===0);

            if(isEven) {
                tr.replaceClass(CLASS_ODD, CLASS_EVEN);
            }
            else {
                tr.replaceClass(CLASS_EVEN, CLASS_ODD);
            }

            tbody.insertBefore(tr, nextSibling);
        },

        /**
        * Creates and attaches data cell element.
        *
        * @method _addTbodyTdNode
        * @param o {Object} {record, column, tr}.
        * @protected
        */
        _addTbodyTdNode: function(o) {
            o.td = this._createTbodyTdNode(o);
            this._attachTbodyTdNode(o);
        },

        /**
        * Creates data cell element.
        *
        * @method _createTbodyTdNode
        * @param o {Object} {record, column, tr}.
        * @protected
        * @returns Y.Node
        */
        _createTbodyTdNode: function(o) {
            var column = o.column;
            //TODO: attributes? or methods?
            o.headers = column.headers;
            o.classnames = column.get("classnames");
            o.value = this.formatDataCell(o);
            return Ycreate(Ysubstitute(this.tdTemplate, o));
        },

        /**
        * Attaches data cell element.
        *
        * @method _attachTbodyTdNode
        * @param o {Object} {record, column, tr, headers, classnames, value}.
        * @protected
        */
        _attachTbodyTdNode: function(o) {
            o.tr.appendChild(o.td);
        },

        /**
         * Returns markup to insert into data cell element.
         *
         * @method formatDataCell
         * @param @param o {Object} {record, column, tr, headers, classnames}.
         */
        formatDataCell: function(o) {
            var record = o.record,
                column = o.column,
                formatter = column.get("formatter");
            o.data = record.get("data");
            o.value = record.getValue(column.get("field"));
            return YLang.isString(formatter) ?
                Ysubstitute(formatter, o) : // Custom template
                YLang.isFunction(formatter) ?
                    formatter.call(this, o) :  // Custom function
                    Ysubstitute(this.get("tdValueTemplate"), o);  // Default template
        }
    });

    Y.namespace("DataTable").Base = DTBase;

Copyright © 2011 Yahoo! Inc. All rights reserved.