/* Copyright (c) 2010, Yahoo! Inc. All rights reserved. Code licensed under the BSD License: http://developer.yahoo.com/yui/license.html version: 3.3.0 build: 3167 */ YUI.add('datatable-base', function(Y) { var YLang = Y.Lang, YisValue = YLang.isValue, Ysubstitute = Y.Lang.substitute, YNode = Y.Node, Ycreate = YNode.create, YgetClassName = Y.ClassNameManager.getClassName, DATATABLE = "datatable", COLUMN = "column", FOCUS = "focus", KEYDOWN = "keydown", MOUSEENTER = "mouseenter", MOUSELEAVE = "mouseleave", MOUSEUP = "mouseup", MOUSEDOWN = "mousedown", CLICK = "click", DBLCLICK = "dblclick", CLASS_COLUMNS = YgetClassName(DATATABLE, "columns"), CLASS_DATA = YgetClassName(DATATABLE, "data"), CLASS_MSG = YgetClassName(DATATABLE, "msg"), CLASS_LINER = YgetClassName(DATATABLE, "liner"), CLASS_FIRST = YgetClassName(DATATABLE, "first"), CLASS_LAST = YgetClassName(DATATABLE, "last"), CLASS_EVEN = YgetClassName(DATATABLE, "even"), CLASS_ODD = YgetClassName(DATATABLE, "odd"), TEMPLATE_TABLE = '
', TEMPLATE_COL = '', TEMPLATE_THEAD = '', TEMPLATE_TBODY = '', TEMPLATE_TH = '
{value}
', TEMPLATE_TR = '', TEMPLATE_TD = '
{value}
', TEMPLATE_VALUE = '{value}', TEMPLATE_MSG = ''; /** * The Column class defines and manages attributes of Columns for DataTable. * * @class Column * @extends Widget * @constructor */ function Column(config) { Column.superclass.constructor.apply(this, arguments); } ///////////////////////////////////////////////////////////////////////////// // // STATIC PROPERTIES // ///////////////////////////////////////////////////////////////////////////// Y.mix(Column, { /** * Class name. * * @property NAME * @type String * @static * @final * @value "column" */ NAME: "column", ///////////////////////////////////////////////////////////////////////////// // // ATTRIBUTES // ///////////////////////////////////////////////////////////////////////////// ATTRS: { /** * @attribute id * @description Unique internal identifier, used to stamp ID on TH element. * @type String * @readOnly */ id: { valueFn: "_defaultId", readOnly: true }, /** * @attribute key * @description User-supplied identifier. Defaults to id. * @type String */ key: { valueFn: "_defaultKey" }, /** * @attribute field * @description Points to underlying data field (for sorting or formatting, * for example). Useful when column doesn't hold any data itself, but is * just a visual representation of data from another column or record field. * Defaults to key. * @type String */ field: { valueFn: "_defaultField" }, /** * @attribute label * @description Display label for column header. Defaults to key. * @type String */ label: { valueFn: "_defaultLabel" }, /** * @attribute children * @description Array of child column definitions (for nested headers). * @type String */ children: { value: null }, /** * @attribute abbr * @description TH abbr attribute. * @type String */ abbr: { value: "" }, //TODO: support custom classnames // TH CSS classnames classnames: { readOnly: true, getter: "_getClassnames" }, // Column formatter formatter: {}, //requires datatable-sort sortable: { value: false }, //sortOptions:defaultDir, sortFn, field //TODO: support editable columns // Column editor editor: {}, //TODO: support resizeable columns //TODO: support setting widths // requires datatable-colresize width: {}, resizeable: {}, minimized: {}, minWidth: {}, maxAutoWidth: {} } }); ///////////////////////////////////////////////////////////////////////////// // // PROTOTYPE // ///////////////////////////////////////////////////////////////////////////// Y.extend(Column, Y.Widget, { ///////////////////////////////////////////////////////////////////////////// // // ATTRIBUTE HELPERS // ///////////////////////////////////////////////////////////////////////////// /** * @method _defaultId * @description Return ID for instance. * @returns String * @private */ _defaultId: function() { return Y.guid(); }, /** * @method _defaultKey * @description Return key for instance. Defaults to ID if one was not * provided. * @returns String * @private */ _defaultKey: function(key) { return key || Y.guid(); }, /** * @method _defaultField * @description Return field for instance. Defaults to key if one was not * provided. * @returns String * @private */ _defaultField: function(field) { return field || this.get("key"); }, /** * @method _defaultLabel * @description Return label for instance. Defaults to key if one was not * provided. * @returns String * @private */ _defaultLabel: function(label) { return label || this.get("key"); }, /** * Updates the UI if changes are made to abbr. * * @method _afterAbbrChange * @param e {Event} Custom event for the attribute change. * @private */ _afterAbbrChange: function (e) { this._uiSetAbbr(e.newVal); }, ///////////////////////////////////////////////////////////////////////////// // // PROPERTIES // ///////////////////////////////////////////////////////////////////////////// /** * Reference to Column's current position index within its Columnset's keys * array, if applicable. This property only applies to non-nested and bottom- * level child Columns. Value is set by Columnset code. * * @property keyIndex * @type Number */ keyIndex: null, /** * @property headers * @description Array of TH IDs associated with this column, for TD "headers" * attribute. Value is set by Columnset code * @type String[] */ headers: null, /** * Number of cells the header spans. Value is set by Columnset code. * * @property colSpan * @type Number * @default 1 */ colSpan: 1, /** * Number of rows the header spans. Value is set by Columnset code. * * @property rowSpan * @type Number * @default 1 */ rowSpan: 1, /** * Column's parent Column instance, if applicable. Value is set by Columnset * code. * * @property parent * @type Y.Column */ parent: null, /** * The Node reference to the associated TH element. * * @property thNode * @type Y.Node */ thNode: null, /*TODO * The Node reference to the associated liner element. * * @property thLinerNode * @type Y.Node thLinerNode: null,*/ ///////////////////////////////////////////////////////////////////////////// // // METHODS // ///////////////////////////////////////////////////////////////////////////// /** * Initializer. * * @method initializer * @param config {Object} Config object. * @private */ initializer: function(config) { }, /** * Destructor. * * @method destructor * @private */ destructor: function() { }, /** * Returns classnames for Column. * * @method _getClassnames * @private */ _getClassnames: function () { return Y.ClassNameManager.getClassName(COLUMN, this.get("id")); /*var allClasses; // Add CSS classes if(lang.isString(oColumn.className)) { // Single custom class allClasses = [oColumn.className]; } else if(lang.isArray(oColumn.className)) { // Array of custom classes allClasses = oColumn.className; } else { // no custom classes allClasses = []; } // Hook for setting width with via dynamic style uses key since ID is too disposable allClasses[allClasses.length] = this.getId() + "-col-" +oColumn.getSanitizedKey(); // Column key - minus any chars other than "A-Z", "a-z", "0-9", "_", "-", ".", or ":" allClasses[allClasses.length] = "yui-dt-col-" +oColumn.getSanitizedKey(); var isSortedBy = this.get("sortedBy") || {}; // Sorted if(oColumn.key === isSortedBy.key) { allClasses[allClasses.length] = isSortedBy.dir || ''; } // Hidden if(oColumn.hidden) { allClasses[allClasses.length] = DT.CLASS_HIDDEN; } // Selected if(oColumn.selected) { allClasses[allClasses.length] = DT.CLASS_SELECTED; } // Sortable if(oColumn.sortable) { allClasses[allClasses.length] = DT.CLASS_SORTABLE; } // Resizeable if(oColumn.resizeable) { allClasses[allClasses.length] = DT.CLASS_RESIZEABLE; } // Editable if(oColumn.editor) { allClasses[allClasses.length] = DT.CLASS_EDITABLE; } // Addtnl classes, including First/Last if(aAddClasses) { allClasses = allClasses.concat(aAddClasses); } return allClasses.join(' ');*/ }, //////////////////////////////////////////////////////////////////////////// // // SYNC // //////////////////////////////////////////////////////////////////////////// /** * Syncs UI to intial state. * * @method syncUI * @private */ syncUI: function() { this._uiSetAbbr(this.get("abbr")); }, /** * Updates abbr. * * @method _uiSetAbbr * @param val {String} New abbr. * @protected */ _uiSetAbbr: function(val) { this.thNode.set("abbr", val); } }); Y.Column = Column; /** * The Columnset class defines and manages a collection of Columns. * * @class Columnset * @extends Base * @constructor */ function Columnset(config) { Columnset.superclass.constructor.apply(this, arguments); } ///////////////////////////////////////////////////////////////////////////// // // STATIC PROPERTIES // ///////////////////////////////////////////////////////////////////////////// Y.mix(Columnset, { /** * Class name. * * @property NAME * @type String * @static * @final * @value "columnset" */ NAME: "columnset", ///////////////////////////////////////////////////////////////////////////// // // ATTRIBUTES // ///////////////////////////////////////////////////////////////////////////// ATTRS: { /** * @attribute definitions * @description Array of column definitions that will populate this Columnset. * @type Array */ definitions: { setter: "_setDefinitions" } } }); ///////////////////////////////////////////////////////////////////////////// // // PROTOTYPE // ///////////////////////////////////////////////////////////////////////////// Y.extend(Columnset, Y.Base, { ///////////////////////////////////////////////////////////////////////////// // // ATTRIBUTE HELPERS // ///////////////////////////////////////////////////////////////////////////// /** * @method _setDefinitions * @description Clones definitions before setting. * @param definitions {Array} Array of column definitions. * @returns Array * @private */ _setDefinitions: function(definitions) { return Y.clone(definitions); }, ///////////////////////////////////////////////////////////////////////////// // // PROPERTIES // ///////////////////////////////////////////////////////////////////////////// /** * Top-down tree representation of Column hierarchy. Used to create DOM * elements. * * @property tree * @type Y.Column[] */ tree: null, /** * Hash of all Columns by ID. * * @property idHash * @type Object */ idHash: null, /** * Hash of all Columns by key. * * @property keyHash * @type Object */ keyHash: null, /** * Array of only Columns that are meant to be displayed in DOM. * * @property keys * @type Y.Column[] */ keys: null, ///////////////////////////////////////////////////////////////////////////// // // METHODS // ///////////////////////////////////////////////////////////////////////////// /** * Initializer. Generates all internal representations of the collection of * Columns. * * @method initializer * @param config {Object} Config object. * @private */ initializer: function() { // DOM tree representation of all Columns var tree = [], // Hash of all Columns by ID idHash = {}, // Hash of all Columns by key keyHash = {}, // Flat representation of only Columns that are meant to display data keys = [], // Original definitions definitions = this.get("definitions"), self = this; // Internal recursive function to define Column instances function parseColumns(depth, currentDefinitions, parent) { var i=0, len = currentDefinitions.length, currentDefinition, column, currentChildren; // One level down depth++; // Create corresponding dom node if not already there for this depth if(!tree[depth]) { tree[depth] = []; } // Parse each node at this depth for attributes and any children for(; i maxRowDepth) { maxRowDepth = tmpRowDepth; } } } } // Count max row depth for each row for(m=0; m' */ 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 '
{value}
' */ thTemplate: TEMPLATE_TH, /** * @property tdTemplate * @description Tokenized markup template for TD node creation. * @type String * @default '
{value}
' */ 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 = [""]; for(; i"); // 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