2 Copyright (c) 2010, Yahoo! Inc. All rights reserved.
3 Code licensed under the BSD License:
4 http://developer.yahoo.com/yui/license.html
8 YUI.add('datatable-base', function(Y) {
11 YisValue = YLang.isValue,
12 Ysubstitute = Y.Lang.substitute,
14 Ycreate = YNode.create,
15 YgetClassName = Y.ClassNameManager.getClassName,
17 DATATABLE = "datatable",
22 MOUSEENTER = "mouseenter",
23 MOUSELEAVE = "mouseleave",
25 MOUSEDOWN = "mousedown",
27 DBLCLICK = "dblclick",
29 CLASS_COLUMNS = YgetClassName(DATATABLE, "columns"),
30 CLASS_DATA = YgetClassName(DATATABLE, "data"),
31 CLASS_MSG = YgetClassName(DATATABLE, "msg"),
32 CLASS_LINER = YgetClassName(DATATABLE, "liner"),
33 CLASS_FIRST = YgetClassName(DATATABLE, "first"),
34 CLASS_LAST = YgetClassName(DATATABLE, "last"),
35 CLASS_EVEN = YgetClassName(DATATABLE, "even"),
36 CLASS_ODD = YgetClassName(DATATABLE, "odd"),
38 TEMPLATE_TABLE = '<table></table>',
39 TEMPLATE_COL = '<col></col>',
40 TEMPLATE_THEAD = '<thead class="'+CLASS_COLUMNS+'"></thead>',
41 TEMPLATE_TBODY = '<tbody class="'+CLASS_DATA+'"></tbody>',
42 TEMPLATE_TH = '<th id="{id}" rowspan="{rowspan}" colspan="{colspan}" class="{classnames}" abbr="{abbr}"><div class="'+CLASS_LINER+'">{value}</div></th>',
43 TEMPLATE_TR = '<tr id="{id}"></tr>',
44 TEMPLATE_TD = '<td headers="{headers}" class="{classnames}"><div class="'+CLASS_LINER+'">{value}</div></td>',
45 TEMPLATE_VALUE = '{value}',
46 TEMPLATE_MSG = '<tbody class="'+CLASS_MSG+'"></tbody>';
52 * The Column class defines and manages attributes of Columns for DataTable.
58 function Column(config) {
59 Column.superclass.constructor.apply(this, arguments);
62 /////////////////////////////////////////////////////////////////////////////
66 /////////////////////////////////////////////////////////////////////////////
79 /////////////////////////////////////////////////////////////////////////////
83 /////////////////////////////////////////////////////////////////////////////
87 * @description Unique internal identifier, used to stamp ID on TH element.
92 valueFn: "_defaultId",
98 * @description User-supplied identifier. Defaults to id.
102 valueFn: "_defaultKey"
107 * @description Points to underlying data field (for sorting or formatting,
108 * for example). Useful when column doesn't hold any data itself, but is
109 * just a visual representation of data from another column or record field.
114 valueFn: "_defaultField"
119 * @description Display label for column header. Defaults to key.
123 valueFn: "_defaultLabel"
127 * @attribute children
128 * @description Array of child column definitions (for nested headers).
137 * @description TH abbr attribute.
144 //TODO: support custom classnames
148 getter: "_getClassnames"
154 //requires datatable-sort
158 //sortOptions:defaultDir, sortFn, field
160 //TODO: support editable columns
164 //TODO: support resizeable columns
165 //TODO: support setting widths
166 // requires datatable-colresize
175 /////////////////////////////////////////////////////////////////////////////
179 /////////////////////////////////////////////////////////////////////////////
180 Y.extend(Column, Y.Widget, {
181 /////////////////////////////////////////////////////////////////////////////
185 /////////////////////////////////////////////////////////////////////////////
188 * @description Return ID for instance.
192 _defaultId: function() {
197 * @method _defaultKey
198 * @description Return key for instance. Defaults to ID if one was not
203 _defaultKey: function(key) {
204 return key || Y.guid();
208 * @method _defaultField
209 * @description Return field for instance. Defaults to key if one was not
214 _defaultField: function(field) {
215 return field || this.get("key");
219 * @method _defaultLabel
220 * @description Return label for instance. Defaults to key if one was not
225 _defaultLabel: function(label) {
226 return label || this.get("key");
230 * Updates the UI if changes are made to abbr.
232 * @method _afterAbbrChange
233 * @param e {Event} Custom event for the attribute change.
236 _afterAbbrChange: function (e) {
237 this._uiSetAbbr(e.newVal);
240 /////////////////////////////////////////////////////////////////////////////
244 /////////////////////////////////////////////////////////////////////////////
246 * Reference to Column's current position index within its Columnset's keys
247 * array, if applicable. This property only applies to non-nested and bottom-
248 * level child Columns. Value is set by Columnset code.
257 * @description Array of TH IDs associated with this column, for TD "headers"
258 * attribute. Value is set by Columnset code
264 * Number of cells the header spans. Value is set by Columnset code.
273 * Number of rows the header spans. Value is set by Columnset code.
282 * Column's parent Column instance, if applicable. Value is set by Columnset
291 * The Node reference to the associated TH element.
300 * The Node reference to the associated liner element.
302 * @property thLinerNode
307 /////////////////////////////////////////////////////////////////////////////
311 /////////////////////////////////////////////////////////////////////////////
315 * @method initializer
316 * @param config {Object} Config object.
319 initializer: function(config) {
328 destructor: function() {
332 * Returns classnames for Column.
334 * @method _getClassnames
337 _getClassnames: function () {
338 return Y.ClassNameManager.getClassName(COLUMN, this.get("id"));
342 if(lang.isString(oColumn.className)) {
343 // Single custom class
344 allClasses = [oColumn.className];
346 else if(lang.isArray(oColumn.className)) {
347 // Array of custom classes
348 allClasses = oColumn.className;
355 // Hook for setting width with via dynamic style uses key since ID is too disposable
356 allClasses[allClasses.length] = this.getId() + "-col-" +oColumn.getSanitizedKey();
358 // Column key - minus any chars other than "A-Z", "a-z", "0-9", "_", "-", ".", or ":"
359 allClasses[allClasses.length] = "yui-dt-col-" +oColumn.getSanitizedKey();
361 var isSortedBy = this.get("sortedBy") || {};
363 if(oColumn.key === isSortedBy.key) {
364 allClasses[allClasses.length] = isSortedBy.dir || '';
368 allClasses[allClasses.length] = DT.CLASS_HIDDEN;
371 if(oColumn.selected) {
372 allClasses[allClasses.length] = DT.CLASS_SELECTED;
375 if(oColumn.sortable) {
376 allClasses[allClasses.length] = DT.CLASS_SORTABLE;
379 if(oColumn.resizeable) {
380 allClasses[allClasses.length] = DT.CLASS_RESIZEABLE;
384 allClasses[allClasses.length] = DT.CLASS_EDITABLE;
387 // Addtnl classes, including First/Last
389 allClasses = allClasses.concat(aAddClasses);
392 return allClasses.join(' ');*/
395 ////////////////////////////////////////////////////////////////////////////
399 ////////////////////////////////////////////////////////////////////////////
401 * Syncs UI to intial state.
407 this._uiSetAbbr(this.get("abbr"));
414 * @param val {String} New abbr.
417 _uiSetAbbr: function(val) {
418 this.thNode.set("abbr", val);
425 * The Columnset class defines and manages a collection of Columns.
431 function Columnset(config) {
432 Columnset.superclass.constructor.apply(this, arguments);
435 /////////////////////////////////////////////////////////////////////////////
439 /////////////////////////////////////////////////////////////////////////////
452 /////////////////////////////////////////////////////////////////////////////
456 /////////////////////////////////////////////////////////////////////////////
459 * @attribute definitions
460 * @description Array of column definitions that will populate this Columnset.
464 setter: "_setDefinitions"
470 /////////////////////////////////////////////////////////////////////////////
474 /////////////////////////////////////////////////////////////////////////////
475 Y.extend(Columnset, Y.Base, {
476 /////////////////////////////////////////////////////////////////////////////
480 /////////////////////////////////////////////////////////////////////////////
482 * @method _setDefinitions
483 * @description Clones definitions before setting.
484 * @param definitions {Array} Array of column definitions.
488 _setDefinitions: function(definitions) {
489 return Y.clone(definitions);
492 /////////////////////////////////////////////////////////////////////////////
496 /////////////////////////////////////////////////////////////////////////////
498 * Top-down tree representation of Column hierarchy. Used to create DOM
507 * Hash of all Columns by ID.
515 * Hash of all Columns by key.
523 * Array of only Columns that are meant to be displayed in DOM.
530 /////////////////////////////////////////////////////////////////////////////
534 /////////////////////////////////////////////////////////////////////////////
536 * Initializer. Generates all internal representations of the collection of
539 * @method initializer
540 * @param config {Object} Config object.
543 initializer: function() {
545 // DOM tree representation of all Columns
547 // Hash of all Columns by ID
549 // Hash of all Columns by key
551 // Flat representation of only Columns that are meant to display data
553 // Original definitions
554 definitions = this.get("definitions"),
558 // Internal recursive function to define Column instances
559 function parseColumns(depth, currentDefinitions, parent) {
561 len = currentDefinitions.length,
569 // Create corresponding dom node if not already there for this depth
574 // Parse each node at this depth for attributes and any children
576 currentDefinition = currentDefinitions[i];
578 currentDefinition = YLang.isString(currentDefinition) ? {key:currentDefinition} : currentDefinition;
580 // Instantiate a new Column for each node
581 column = new Y.Column(currentDefinition);
583 // Cross-reference Column ID back to the original object literal definition
584 currentDefinition.yuiColumnId = column.get("id");
586 // Add the new Column to the hash
587 idHash[column.get("id")] = column;
588 keyHash[column.get("key")] = column;
590 // Assign its parent as an attribute, if applicable
592 column.parent = parent;
595 // The Column has descendants
596 if(YLang.isArray(currentDefinition.children)) {
597 currentChildren = currentDefinition.children;
598 column._set("children", currentChildren);
600 self._setColSpans(column, currentDefinition);
602 self._cascadePropertiesToChildren(column, currentChildren);
604 // The children themselves must also be parsed for Column instances
608 parseColumns(depth, currentChildren, column);
610 // This Column does not have any children
612 column.keyIndex = keys.length;
613 // Default is already 1
614 //column.colSpan = 1;
618 // Add the Column to the top-down dom tree
619 tree[depth].push(column);
624 // Parse out Column instances from the array of object literals
625 parseColumns(-1, definitions);
628 // Save to the Columnset instance
630 this.idHash = idHash;
631 this.keyHash = keyHash;
644 destructor: function() {
647 /////////////////////////////////////////////////////////////////////////////
651 /////////////////////////////////////////////////////////////////////////////
653 * Cascade certain properties to children if not defined on their own.
655 * @method _cascadePropertiesToChildren
658 _cascadePropertiesToChildren: function(column, currentChildren) {
659 //TODO: this is all a giant todo
661 len = currentChildren.length,
664 // Cascade certain properties to children if not defined on their own
666 child = currentChildren[i];
667 if(column.get("className") && (child.className === undefined)) {
668 child.className = column.get("className");
670 if(column.get("editor") && (child.editor === undefined)) {
671 child.editor = column.get("editor");
673 if(column.get("formatter") && (child.formatter === undefined)) {
674 child.formatter = column.get("formatter");
676 if(column.get("resizeable") && (child.resizeable === undefined)) {
677 child.resizeable = column.get("resizeable");
679 if(column.get("sortable") && (child.sortable === undefined)) {
680 child.sortable = column.get("sortable");
682 if(column.get("hidden")) {
685 if(column.get("width") && (child.width === undefined)) {
686 child.width = column.get("width");
688 if(column.get("minWidth") && (child.minWidth === undefined)) {
689 child.minWidth = column.get("minWidth");
691 if(column.get("maxAutoWidth") && (child.maxAutoWidth === undefined)) {
692 child.maxAutoWidth = column.get("maxAutoWidth");
698 * @method _setColSpans
699 * @description Calculates and sets colSpan attribute on given Column.
700 * @param column {Array} Column instance.
701 * @param definition {Object} Column definition.
704 _setColSpans: function(column, definition) {
705 // Determine COLSPAN value for this Column
706 var terminalChildNodes = 0;
708 function countTerminalChildNodes(ancestor) {
709 var descendants = ancestor.children,
711 len = descendants.length;
713 // Drill down each branch and count terminal nodes
715 // Keep drilling down
716 if(YLang.isArray(descendants[i].children)) {
717 countTerminalChildNodes(descendants[i]);
719 // Reached branch terminus
721 terminalChildNodes++;
725 countTerminalChildNodes(definition);
726 column.colSpan = terminalChildNodes;
730 * @method _setRowSpans
731 * @description Calculates and sets rowSpan attribute on all Columns.
734 _setRowSpans: function() {
735 // Determine ROWSPAN value for each Column in the DOM tree
736 function parseDomTreeForRowSpan(tree) {
742 // Calculate the max depth of descendants for this row
743 function countMaxRowDepth(row, tmpRowDepth) {
744 tmpRowDepth = tmpRowDepth || 1;
752 // Column has children, so keep counting
753 if(YLang.isArray(col.children)) {
755 countMaxRowDepth(col.children, tmpRowDepth);
758 // Column has children, so keep counting
759 else if(col.get && YLang.isArray(col.get("children"))) {
761 countMaxRowDepth(col.get("children"), tmpRowDepth);
764 // No children, is it the max depth?
766 if(tmpRowDepth > maxRowDepth) {
767 maxRowDepth = tmpRowDepth;
773 // Count max row depth for each row
774 for(m=0; m<tree.length; m++) {
775 currentRow = tree[m];
776 countMaxRowDepth(currentRow);
778 // Assign the right ROWSPAN values to each Column in the row
779 for(p=0; p<currentRow.length; p++) {
780 currentColumn = currentRow[p];
781 if(!YLang.isArray(currentColumn.get("children"))) {
782 currentColumn.rowSpan = maxRowDepth;
784 // Default is already 1
785 // else currentColumn.rowSpan =1;
788 // Reset counter for next row
792 parseDomTreeForRowSpan(this.tree);
796 * @method _setHeaders
797 * @description Calculates and sets headers attribute on all Columns.
800 _setHeaders: function() {
803 i=0, len = allKeys.length;
805 function recurseAncestorsForHeaders(headers, column) {
806 headers.push(column.get("id"));
808 recurseAncestorsForHeaders(headers, column.parent);
814 recurseAncestorsForHeaders(headers, column);
815 column.headers = headers.reverse().join(" ");
820 getColumn: function() {
824 Y.Columnset = Columnset;
827 * The DataTable widget provides a progressively enhanced DHTML control for
828 * displaying tabular data across A-grade browsers.
834 * Provides the base DataTable implementation, which can be extended to add
835 * additional functionality, such as sorting or scrolling.
838 * @submodule datatable-base
842 * Base class for the DataTable widget.
843 * @class DataTable.Base
847 function DTBase(config) {
848 DTBase.superclass.constructor.apply(this, arguments);
851 /////////////////////////////////////////////////////////////////////////////
855 /////////////////////////////////////////////////////////////////////////////
869 /////////////////////////////////////////////////////////////////////////////
873 /////////////////////////////////////////////////////////////////////////////
876 * @attribute columnset
877 * @description Pointer to Columnset instance.
878 * @type Array | Y.Columnset
881 setter: "_setColumnset"
885 * @attribute recordset
886 * @description Pointer to Recordset instance.
887 * @type Array | Y.Recordset
890 value: new Y.Recordset({records:[]}),
891 setter: "_setRecordset"
896 * @description Internal state.
901 value: new Y.State(),
908 * @description Summary.
916 * @description Caption
923 * @attribute thValueTemplate
924 * @description Tokenized markup template for TH value.
929 value: TEMPLATE_VALUE
933 * @attribute tdValueTemplate
934 * @description Tokenized markup template for TD value.
939 value: TEMPLATE_VALUE
943 * @attribute trTemplate
944 * @description Tokenized markup template for TR node creation.
946 * @default '<tr id="{id}"></tr>'
953 /////////////////////////////////////////////////////////////////////////////
957 /////////////////////////////////////////////////////////////////////////////
959 /*caption: function (srcNode) {
965 /////////////////////////////////////////////////////////////////////////////
969 /////////////////////////////////////////////////////////////////////////////
970 Y.extend(DTBase, Y.Widget, {
972 * @property thTemplate
973 * @description Tokenized markup template for TH node creation.
975 * @default '<th id="{id}" rowspan="{rowspan}" colspan="{colspan}" class="{classnames}" abbr="{abbr}"><div class="'+CLASS_LINER+'">{value}</div></th>'
977 thTemplate: TEMPLATE_TH,
980 * @property tdTemplate
981 * @description Tokenized markup template for TD node creation.
983 * @default '<td headers="{headers}"><div class="'+CLASS_LINER+'">{value}</div></td>'
985 tdTemplate: TEMPLATE_TD,
988 * @property _theadNode
989 * @description Pointer to THEAD node.
996 * @property _tbodyNode
997 * @description Pointer to TBODY node.
1004 * @property _msgNode
1005 * @description Pointer to message display node.
1011 /////////////////////////////////////////////////////////////////////////////
1013 // ATTRIBUTE HELPERS
1015 /////////////////////////////////////////////////////////////////////////////
1017 * @method _setColumnset
1018 * @description Converts Array to Y.Columnset.
1019 * @param columns {Array | Y.Columnset}
1020 * @returns Y.Columnset
1023 _setColumnset: function(columns) {
1024 return YLang.isArray(columns) ? new Y.Columnset({definitions:columns}) : columns;
1028 * Updates the UI if Columnset is changed.
1030 * @method _afterColumnsetChange
1031 * @param e {Event} Custom event for the attribute change.
1034 _afterColumnsetChange: function (e) {
1035 if(this.get("rendered")) {
1036 this._uiSetColumnset(e.newVal);
1041 * @method _setRecordset
1042 * @description Converts Array to Y.Recordset.
1043 * @param records {Array | Y.Recordset}
1044 * @returns Y.Recordset
1047 _setRecordset: function(rs) {
1048 if(YLang.isArray(rs)) {
1049 rs = new Y.Recordset({records:rs});
1057 * Updates the UI if Recordset is changed.
1059 * @method _afterRecordsetChange
1060 * @param e {Event} Custom event for the attribute change.
1063 _afterRecordsetChange: function (e) {
1064 if(this.get("rendered")) {
1065 this._uiSetRecordset(e.newVal);
1070 * Updates the UI if summary is changed.
1072 * @method _afterSummaryChange
1073 * @param e {Event} Custom event for the attribute change.
1076 _afterSummaryChange: function (e) {
1077 if(this.get("rendered")) {
1078 this._uiSetSummary(e.newVal);
1083 * Updates the UI if caption is changed.
1085 * @method _afterCaptionChange
1086 * @param e {Event} Custom event for the attribute change.
1089 _afterCaptionChange: function (e) {
1090 if(this.get("rendered")) {
1091 this._uiSetCaption(e.newVal);
1095 /////////////////////////////////////////////////////////////////////////////
1099 /////////////////////////////////////////////////////////////////////////////
1103 * @method initializer
1104 * @param config {Object} Config object.
1107 initializer: function(config) {
1108 this.after("columnsetChange", this._afterColumnsetChange);
1109 this.after("recordsetChange", this._afterRecordsetChange);
1110 this.after("summaryChange", this._afterSummaryChange);
1111 this.after("captionChange", this._afterCaptionChange);
1117 * @method destructor
1120 destructor: function() {
1121 this.get("recordset").removeTarget(this);
1124 ////////////////////////////////////////////////////////////////////////////
1128 ////////////////////////////////////////////////////////////////////////////
1136 renderUI: function() {
1138 return (this._addTableNode(this.get("contentBox")) &&
1140 this._addColgroupNode(this._tableNode) &&
1142 this._addTheadNode(this._tableNode) &&
1144 this._addTbodyNode(this._tableNode) &&
1146 this._addMessageNode(this._tableNode) &&
1148 this._addCaptionNode(this._tableNode));
1152 * Creates and attaches TABLE element to given container.
1154 * @method _addTableNode
1155 * @param containerNode {Y.Node} Parent node.
1159 _addTableNode: function(containerNode) {
1160 if (!this._tableNode) {
1161 this._tableNode = containerNode.appendChild(Ycreate(TEMPLATE_TABLE));
1163 return this._tableNode;
1167 * Creates and attaches COLGROUP element to given TABLE.
1169 * @method _addColgroupNode
1170 * @param tableNode {Y.Node} Parent node.
1174 _addColgroupNode: function(tableNode) {
1175 // Add COLs to DOCUMENT FRAGMENT
1176 var len = this.get("columnset").keys.length,
1178 allCols = ["<colgroup>"];
1181 allCols.push(TEMPLATE_COL);
1184 allCols.push("</colgroup>");
1187 this._colgroupNode = tableNode.insertBefore(Ycreate(allCols.join("")), tableNode.get("firstChild"));
1189 return this._colgroupNode;
1193 * Creates and attaches THEAD element to given container.
1195 * @method _addTheadNode
1196 * @param tableNode {Y.Node} Parent node.
1200 _addTheadNode: function(tableNode) {
1202 this._theadNode = tableNode.insertBefore(Ycreate(TEMPLATE_THEAD), this._colgroupNode.next());
1203 return this._theadNode;
1208 * Creates and attaches TBODY element to given container.
1210 * @method _addTbodyNode
1211 * @param tableNode {Y.Node} Parent node.
1215 _addTbodyNode: function(tableNode) {
1216 this._tbodyNode = tableNode.appendChild(Ycreate(TEMPLATE_TBODY));
1217 return this._tbodyNode;
1221 * Creates and attaches message display element to given container.
1223 * @method _addMessageNode
1224 * @param tableNode {Y.Node} Parent node.
1228 _addMessageNode: function(tableNode) {
1229 this._msgNode = tableNode.insertBefore(Ycreate(TEMPLATE_MSG), this._tbodyNode);
1230 return this._msgNode;
1234 * Creates and attaches CAPTION element to given container.
1236 * @method _addCaptionNode
1237 * @param tableNode {Y.Node} Parent node.
1241 _addCaptionNode: function(tableNode) {
1242 this._captionNode = tableNode.createCaption();
1243 return this._captionNode;
1246 ////////////////////////////////////////////////////////////////////////////
1250 ////////////////////////////////////////////////////////////////////////////
1258 bindUI: function() {
1259 var theadFilter = "thead."+CLASS_COLUMNS+">tr>th",
1260 tbodyFilter ="tbody."+CLASS_DATA+">tr>td",
1261 msgFilter = "tbody."+CLASS_MSG+">tr>td";
1264 delegate: function(type) {
1265 //TODO: is this necessary?
1266 if(type==="dblclick") {
1267 this.get("boundingBox").delegate.apply(this.get("boundingBox"), arguments);
1270 this.get("contentBox").delegate.apply(this.get("contentBox"), arguments);
1275 ////////////////////////////////////////////////////////////////////////////
1279 ////////////////////////////////////////////////////////////////////////////
1282 * Syncs UI to intial state.
1287 syncUI: function() {
1289 this._uiSetColumnset(this.get("columnset"));
1291 this._uiSetRecordset(this.get("recordset"));
1293 this._uiSetSummary(this.get("summary"));
1295 this._uiSetCaption(this.get("caption"));
1301 * @method _uiSetSummary
1302 * @param val {String} New summary.
1305 _uiSetSummary: function(val) {
1306 val = YisValue(val) ? val : "";
1307 this._tableNode.set("summary", val);
1313 * @method _uiSetCaption
1314 * @param val {String} New caption.
1317 _uiSetCaption: function(val) {
1318 val = YisValue(val) ? val : "";
1319 this._captionNode.setContent(val);
1323 ////////////////////////////////////////////////////////////////////////////
1325 // THEAD/COLUMNSET FUNCTIONALITY
1327 ////////////////////////////////////////////////////////////////////////////
1331 * @method _uiSetColumnset
1332 * @param cs {Y.Columnset} New Columnset.
1335 _uiSetColumnset: function(cs) {
1337 thead = this._theadNode,
1340 parent = thead.get("parentNode"),
1341 nextSibling = thead.next();
1343 // Move THEAD off DOM
1346 thead.get("children").remove(true);
1348 // Iterate tree of columns to add THEAD rows
1350 this._addTheadTrNode({thead:thead, columns:tree[i]}, (i === 0), (i === len-1));
1353 // Column helpers needs _theadNode to exist
1354 //this._createColumnHelpers();
1357 // Re-attach THEAD to DOM
1358 parent.insert(thead, nextSibling);
1363 * Creates and attaches header row element.
1365 * @method _addTheadTrNode
1366 * @param o {Object} {thead, columns}.
1367 * @param isFirst {Boolean} Is first row.
1368 * @param isFirst {Boolean} Is last row.
1371 _addTheadTrNode: function(o, isFirst, isLast) {
1372 o.tr = this._createTheadTrNode(o, isFirst, isLast);
1373 this._attachTheadTrNode(o);
1378 * Creates header row element.
1380 * @method _createTheadTrNode
1381 * @param o {Object} {thead, columns}.
1382 * @param isFirst {Boolean} Is first row.
1383 * @param isLast {Boolean} Is last row.
1387 _createTheadTrNode: function(o, isFirst, isLast) {
1388 //TODO: custom classnames
1389 var tr = Ycreate(Ysubstitute(this.get("trTemplate"), o)),
1391 columns = o.columns,
1392 len = columns.length,
1395 // Set FIRST/LAST class
1397 tr.addClass(CLASS_FIRST);
1400 tr.addClass(CLASS_LAST);
1404 column = columns[i];
1405 this._addTheadThNode({value:column.get("label"), column: column, tr:tr});
1412 * Attaches header row element.
1414 * @method _attachTheadTrNode
1415 * @param o {Object} {thead, columns, tr}.
1418 _attachTheadTrNode: function(o) {
1419 o.thead.appendChild(o.tr);
1423 * Creates and attaches header cell element.
1425 * @method _addTheadThNode
1426 * @param o {Object} {value, column, tr}.
1429 _addTheadThNode: function(o) {
1430 o.th = this._createTheadThNode(o);
1431 this._attachTheadThNode(o);
1432 //TODO: assign all node pointers: thNode, thLinerNode, thLabelNode
1433 o.column.thNode = o.th;
1437 * Creates header cell element.
1439 * @method _createTheadThNode
1440 * @param o {Object} {value, column, tr}.
1444 _createTheadThNode: function(o) {
1445 var column = o.column;
1447 // Populate template object
1448 o.id = column.get("id");//TODO: validate 1 column ID per document
1449 o.colspan = column.colSpan;
1450 o.rowspan = column.rowSpan;
1451 o.abbr = column.get("abbr");
1452 o.classnames = column.get("classnames");
1453 o.value = Ysubstitute(this.get("thValueTemplate"), o);
1456 // Clear minWidth on hidden Columns
1457 if(column.get("hidden")) {
1458 //this._clearMinWidth(column);
1462 return Ycreate(Ysubstitute(this.thTemplate, o));
1466 * Attaches header cell element.
1468 * @method _attachTheadThNode
1469 * @param o {Object} {value, column, tr}.
1472 _attachTheadThNode: function(o) {
1473 o.tr.appendChild(o.th);
1476 ////////////////////////////////////////////////////////////////////////////
1478 // TBODY/RECORDSET FUNCTIONALITY
1480 ////////////////////////////////////////////////////////////////////////////
1484 * @method _uiSetRecordset
1485 * @param rs {Y.Recordset} New Recordset.
1488 _uiSetRecordset: function(rs) {
1489 var i = 0,//TODOthis.get("state.offsetIndex")
1490 len = rs.getLength(), //TODOthis.get("state.pageLength")
1491 oldTbody = this._tbodyNode,
1492 parent = oldTbody.get("parentNode"),
1493 nextSibling = oldTbody.next(),
1497 // Replace TBODY with a new one
1498 //TODO: split _addTbodyNode into create/attach
1501 newTbody = this._addTbodyNode(this._tableNode);
1503 this._tbodyNode = newTbody;
1506 // Iterate Recordset to use existing TR when possible or add new TR
1508 o.record = rs.getRecord(i);
1510 this._addTbodyTrNode(o); //TODO: sometimes rowindex != recordindex
1514 parent.insert(this._tbodyNode, nextSibling);
1518 * Creates and attaches data row element.
1520 * @method _addTbodyTrNode
1521 * @param o {Object} {tbody, record}
1524 _addTbodyTrNode: function(o) {
1525 var tbody = o.tbody,
1527 o.tr = tbody.one("#"+record.get("id")) || this._createTbodyTrNode(o);
1528 this._attachTbodyTrNode(o);
1532 * Creates data row element.
1534 * @method _createTbodyTrNode
1535 * @param o {Object} {tbody, record}
1539 _createTbodyTrNode: function(o) {
1540 var tr = Ycreate(Ysubstitute(this.get("trTemplate"), {id:o.record.get("id")})),
1542 allKeys = this.get("columnset").keys,
1543 len = allKeys.length;
1548 o.column = allKeys[i];
1549 this._addTbodyTdNode(o);
1556 * Attaches data row element.
1558 * @method _attachTbodyTrNode
1559 * @param o {Object} {tbody, record, tr}.
1562 _attachTbodyTrNode: function(o) {
1563 var tbody = o.tbody,
1566 nextSibling = tbody.get("children").item(index) || null,
1567 isEven = (index%2===0);
1570 tr.replaceClass(CLASS_ODD, CLASS_EVEN);
1573 tr.replaceClass(CLASS_EVEN, CLASS_ODD);
1576 tbody.insertBefore(tr, nextSibling);
1580 * Creates and attaches data cell element.
1582 * @method _addTbodyTdNode
1583 * @param o {Object} {record, column, tr}.
1586 _addTbodyTdNode: function(o) {
1587 o.td = this._createTbodyTdNode(o);
1588 this._attachTbodyTdNode(o);
1592 * Creates data cell element.
1594 * @method _createTbodyTdNode
1595 * @param o {Object} {record, column, tr}.
1599 _createTbodyTdNode: function(o) {
1600 var column = o.column;
1601 //TODO: attributes? or methods?
1602 o.headers = column.headers;
1603 o.classnames = column.get("classnames");
1604 o.value = this.formatDataCell(o);
1605 return Ycreate(Ysubstitute(this.tdTemplate, o));
1609 * Attaches data cell element.
1611 * @method _attachTbodyTdNode
1612 * @param o {Object} {record, column, tr, headers, classnames, value}.
1615 _attachTbodyTdNode: function(o) {
1616 o.tr.appendChild(o.td);
1620 * Returns markup to insert into data cell element.
1622 * @method formatDataCell
1623 * @param @param o {Object} {record, column, tr, headers, classnames}.
1625 formatDataCell: function(o) {
1626 var record = o.record,
1628 formatter = column.get("formatter");
1629 o.data = record.get("data");
1630 o.value = record.getValue(column.get("field"));
1631 return YLang.isString(formatter) ?
1632 Ysubstitute(formatter, o) : // Custom template
1633 YLang.isFunction(formatter) ?
1634 formatter.call(this, o) : // Custom function
1635 Ysubstitute(this.get("tdValueTemplate"), o); // Default template
1639 Y.namespace("DataTable").Base = DTBase;
1643 }, '3.3.0' ,{requires:['recordset-base','widget','substitute','event-mouseenter']});