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']});
1645 YUI.add('datatable-datasource', function(Y) {
1648 * Plugs DataTable with DataSource integration.
1651 * @submodule datatable-datasource
1655 * Adds DataSource integration to DataTable.
1656 * @class DataTableDataSource
1657 * @extends Plugin.Base
1659 function DataTableDataSource() {
1660 DataTableDataSource.superclass.constructor.apply(this, arguments);
1663 /////////////////////////////////////////////////////////////////////////////
1665 // STATIC PROPERTIES
1667 /////////////////////////////////////////////////////////////////////////////
1668 Y.mix(DataTableDataSource, {
1670 * The namespace for the plugin. This will be the property on the host which
1671 * references the plugin instance.
1677 * @value "datasource"
1688 * @value "dataTableDataSource"
1690 NAME: "dataTableDataSource",
1692 /////////////////////////////////////////////////////////////////////////////
1696 /////////////////////////////////////////////////////////////////////////////
1699 * @attribute datasource
1700 * @description Pointer to DataSource instance.
1701 * @type Y.DataSource
1704 setter: "_setDataSource"
1708 * @attribute initialRequest
1709 * @description Request sent to DataSource immediately upon initialization.
1713 setter: "_setInitialRequest"
1718 /////////////////////////////////////////////////////////////////////////////
1722 /////////////////////////////////////////////////////////////////////////////
1723 Y.extend(DataTableDataSource, Y.Plugin.Base, {
1724 /////////////////////////////////////////////////////////////////////////////
1726 // ATTRIBUTE HELPERS
1728 /////////////////////////////////////////////////////////////////////////////
1730 * @method _setDataSource
1731 * @description Creates new DataSource instance if one is not provided.
1732 * @param ds {Object | Y.DataSource}
1733 * @returns Y.DataSource
1736 _setDataSource: function(ds) {
1737 return ds || new Y.DataSource.Local(ds);
1741 * @method _setInitialRequest
1742 * @description Sends request to DataSource.
1743 * @param request {Object} DataSource request.
1746 _setInitialRequest: function(request) {
1749 /////////////////////////////////////////////////////////////////////////////
1753 /////////////////////////////////////////////////////////////////////////////
1757 * @method initializer
1758 * @param config {Object} Config object.
1761 initializer: function(config) {
1762 if(!Y.Lang.isUndefined(config.initialRequest)) {
1763 this.load({request:config.initialRequest});
1767 ////////////////////////////////////////////////////////////////////////////
1771 ////////////////////////////////////////////////////////////////////////////
1774 * Load data by calling DataSource's sendRequest() method under the hood.
1777 * @param config {object} Optional configuration parameters:
1780 * <dt>request</dt><dd>Pass in a new request, or initialRequest is used.</dd>
1781 * <dt>callback</dt><dd>Pass in DataSource callback object, or the following default is used:
1783 * <dt>success</dt><dd>datatable.onDataReturnInitializeTable</dd>
1784 * <dt>failure</dt><dd>datatable.onDataReturnInitializeTable</dd>
1785 * <dt>scope</dt><dd>datatable</dd>
1786 * <dt>argument</dt><dd>datatable.getState()</dd>
1789 * <dt>datasource</dt><dd>Pass in a new DataSource instance to override the current DataSource for this transaction.</dd>
1792 load: function(config) {
1793 config = config || {};
1794 config.request = config.request || this.get("initialRequest");
1795 config.callback = config.callback || {
1796 success: Y.bind(this.onDataReturnInitializeTable, this),
1797 failure: Y.bind(this.onDataReturnInitializeTable, this),
1798 argument: this.get("host").get("state") //TODO
1801 var ds = (config.datasource || this.get("datasource"));
1803 ds.sendRequest(config);
1808 * Callback function passed to DataSource's sendRequest() method populates
1809 * an entire DataTable with new data, clearing previous data, if any.
1811 * @method onDataReturnInitializeTable
1812 * @param e {Event.Facade} DataSource Event Facade object.
1814 onDataReturnInitializeTable : function(e) {
1815 this.get("host").set("recordset", new Y.Recordset({records: e.response.results}));
1819 Y.namespace("Plugin").DataTableDataSource = DataTableDataSource;
1826 }, '3.3.0' ,{requires:['datatable-base','plugin','datasource-local']});
1828 YUI.add('datatable-sort', function(Y) {
1831 * Plugs DataTable with sorting functionality.
1834 * @submodule datatable-sort
1838 * Adds column sorting to DataTable.
1839 * @class DataTableSort
1840 * @extends Plugin.Base
1842 var YgetClassName = Y.ClassNameManager.getClassName,
1844 DATATABLE = "datatable",
1849 //TODO: Don't use hrefs - use tab/arrow/enter
1850 TEMPLATE = '<a class="{link_class}" title="{link_title}" href="{link_href}">{value}</a>';
1853 function DataTableSort() {
1854 DataTableSort.superclass.constructor.apply(this, arguments);
1857 /////////////////////////////////////////////////////////////////////////////
1859 // STATIC PROPERTIES
1861 /////////////////////////////////////////////////////////////////////////////
1862 Y.mix(DataTableSort, {
1864 * The namespace for the plugin. This will be the property on the host which
1865 * references the plugin instance.
1882 * @value "dataTableSort"
1884 NAME: "dataTableSort",
1886 /////////////////////////////////////////////////////////////////////////////
1890 /////////////////////////////////////////////////////////////////////////////
1893 * @attribute trigger
1894 * @description Defines the trigger that causes a column to be sorted:
1895 * {event, selector}, where "event" is an event type and "selector" is
1896 * is a node query selector.
1898 * @default {event:"click", selector:"th"}
1899 * @writeOnce "initOnly"
1902 value: {event:"click", selector:"th"},
1903 writeOnce: "initOnly"
1907 * @attribute lastSortedBy
1908 * @description Describes last known sort state: {key,dir}, where
1909 * "key" is column key and "dir" is either "asc" or "desc".
1913 setter: "_setLastSortedBy",
1918 * @attribute template
1919 * @description Tokenized markup template for TH sort element.
1921 * @default '<a class="{link_class}" title="{link_title}" href="{link_href}">{value}</a>'
1929 /////////////////////////////////////////////////////////////////////////////
1933 /////////////////////////////////////////////////////////////////////////////
1934 Y.extend(DataTableSort, Y.Plugin.Base, {
1936 /////////////////////////////////////////////////////////////////////////////
1940 /////////////////////////////////////////////////////////////////////////////
1944 * @method initializer
1945 * @param config {Object} Config object.
1948 initializer: function(config) {
1949 var dt = this.get("host"),
1950 trigger = this.get("trigger");
1952 dt.get("recordset").plug(Y.Plugin.RecordsetSort, {dt: dt});
1953 dt.get("recordset").sort.addTarget(dt);
1955 // Wrap link around TH value
1956 this.doBefore("_createTheadThNode", this._beforeCreateTheadThNode);
1959 this.doBefore("_attachTheadThNode", this._beforeAttachTheadThNode);
1960 this.doBefore("_attachTbodyTdNode", this._beforeAttachTbodyTdNode);
1962 // Attach trigger handlers
1963 dt.delegate(trigger.event, Y.bind(this._onEventSortColumn,this), trigger.selector);
1966 dt.after("recordsetSort:sort", function() {
1967 this._uiSetRecordset(this.get("recordset"));
1969 this.on("lastSortedByChange", function(e) {
1970 this._uiSetLastSortedBy(e.prevVal, e.newVal, dt);
1974 //dt.after("recordset:mutation", function() {//reset lastSortedBy});
1977 //add Column sortFn ATTR
1979 // Update UI after the fact (render-then-plug case)
1980 if(dt.get("rendered")) {
1981 dt._uiSetColumnset(dt.get("columnset"));
1982 this._uiSetLastSortedBy(null, this.get("lastSortedBy"), dt);
1987 * @method _setLastSortedBy
1988 * @description Normalizes lastSortedBy
1989 * @param val {String | Object} {key, dir} or "key"
1990 * @returns {key, dir, notdir}
1993 _setLastSortedBy: function(val) {
1994 if(Y.Lang.isString(val)) {
1995 return {key:val, dir:"asc", notdir:"desc"};
1997 else if (val && val.key) {
1998 if(val.dir === "desc") {
1999 return {key:val.key, dir:"desc", notdir:"asc"};
2002 return {key:val.key, dir:"asc", notdir:"desc"};
2013 * @method _uiSetLastSortedBy
2014 * @param val {Object} New lastSortedBy object {key,dir}.
2015 * @param dt {Y.DataTable.Base} Host.
2018 _uiSetLastSortedBy: function(prevVal, newVal, dt) {
2019 var prevKey = prevVal && prevVal.key,
2020 prevDir = prevVal && prevVal.dir,
2021 newKey = newVal && newVal.key,
2022 newDir = newVal && newVal.dir,
2023 cs = dt.get("columnset"),
2024 prevColumn = cs.keyHash[prevKey],
2025 newColumn = cs.keyHash[newKey],
2026 tbodyNode = dt._tbodyNode,
2027 prevRowList, newRowList;
2029 // Clear previous UI
2031 prevColumn.thNode.removeClass(YgetClassName(DATATABLE, prevDir));
2032 prevRowList = tbodyNode.all("."+YgetClassName(COLUMN, prevColumn.get("id")));
2033 prevRowList.removeClass(YgetClassName(DATATABLE, prevDir));
2038 newColumn.thNode.addClass(YgetClassName(DATATABLE, newDir));
2039 newRowList = tbodyNode.all("."+YgetClassName(COLUMN, newColumn.get("id")));
2040 newRowList.addClass(YgetClassName(DATATABLE, newDir));
2045 * Before header cell element is created, inserts link markup around {value}.
2047 * @method _beforeCreateTheadThNode
2048 * @param o {Object} {value, column, tr}.
2051 _beforeCreateTheadThNode: function(o) {
2052 if(o.column.get("sortable")) {
2053 o.value = Y.substitute(this.get("template"), {
2054 link_class: o.link_class || "",
2055 link_title: "title",
2063 * Before header cell element is attached, sets applicable class names.
2065 * @method _beforeAttachTheadThNode
2066 * @param o {Object} {value, column, tr}.
2069 _beforeAttachTheadThNode: function(o) {
2070 var lastSortedBy = this.get("lastSortedBy"),
2071 key = lastSortedBy && lastSortedBy.key,
2072 dir = lastSortedBy && lastSortedBy.dir,
2073 notdir = lastSortedBy && lastSortedBy.notdir;
2075 // This Column is sortable
2076 if(o.column.get("sortable")) {
2077 o.th.addClass(YgetClassName(DATATABLE, "sortable"));
2079 // This Column is currently sorted
2080 if(key && (key === o.column.get("key"))) {
2081 o.th.replaceClass(YgetClassName(DATATABLE, notdir), YgetClassName(DATATABLE, dir));
2086 * Before header cell element is attached, sets applicable class names.
2088 * @method _before_beforeAttachTbodyTdNode
2089 * @param o {Object} {record, column, tr, headers, classnames, value}.
2092 _beforeAttachTbodyTdNode: function(o) {
2093 var lastSortedBy = this.get("lastSortedBy"),
2094 key = lastSortedBy && lastSortedBy.key,
2095 dir = lastSortedBy && lastSortedBy.dir,
2096 notdir = lastSortedBy && lastSortedBy.notdir;
2098 // This Column is sortable
2099 if(o.column.get("sortable")) {
2100 o.td.addClass(YgetClassName(DATATABLE, "sortable"));
2102 // This Column is currently sorted
2103 if(key && (key === o.column.get("key"))) {
2104 o.td.replaceClass(YgetClassName(DATATABLE, notdir), YgetClassName(DATATABLE, dir));
2108 * In response to the "trigger" event, sorts the underlying Recordset and
2109 * updates the lastSortedBy attribute.
2111 * @method _onEventSortColumn
2112 * @param o {Object} {value, column, tr}.
2115 _onEventSortColumn: function(e) {
2117 //TODO: normalize e.currentTarget to TH
2118 var dt = this.get("host"),
2119 column = dt.get("columnset").idHash[e.currentTarget.get("id")],
2120 key = column.get("key"),
2121 field = column.get("field"),
2122 lastSortedBy = this.get("lastSortedBy"),
2123 dir = (lastSortedBy &&
2124 lastSortedBy.key === key &&
2125 lastSortedBy.dir === ASC) ? DESC : ASC,
2126 sorter = column.get("sortFn");
2127 if(column.get("sortable")) {
2128 dt.get("recordset").sort.sort(field, dir === DESC, sorter);
2129 this.set("lastSortedBy", {key: key, dir: dir});
2134 Y.namespace("Plugin").DataTableSort = DataTableSort;
2141 }, '3.3.0' ,{lang:['en'], requires:['datatable-base','plugin','recordset-sort']});
2143 YUI.add('datatable-scroll', function(Y) {
2146 * Extends DataTable base to enable x,y, and xy scrolling.
2148 * @submodule datatable-scroll
2155 YgetClassName = Y.ClassNameManager.getClassName,
2156 DATATABLE = "datatable",
2157 CLASS_HEADER = YgetClassName(DATATABLE, "hd"),
2158 CLASS_BODY = YgetClassName(DATATABLE, "bd"),
2159 CLASS_SCROLLABLE = YgetClassName(DATATABLE, "scrollable"),
2160 CONTAINER_HEADER = '<div class="'+CLASS_HEADER+'"></div>',
2161 CONTAINER_BODY = '<div class="'+CLASS_BODY+'"></div>',
2162 TEMPLATE_TABLE = '<table></table>';
2165 * Adds scrolling to DataTable.
2166 * @class DataTableScroll
2167 * @extends Plugin.Base
2169 function DataTableScroll() {
2170 DataTableScroll.superclass.constructor.apply(this, arguments);
2173 Y.mix(DataTableScroll, {
2176 NAME: "dataTableScroll",
2181 * @description The width for the table. Set to a string (ex: "200px", "20em") if you want the table to scroll in the x direction.
2189 writeOnce: "initOnly"
2193 * @description The height for the table. Set to a string (ex: "200px", "20em") if you want the table to scroll in the y-direction.
2201 writeOnce: "initOnly"
2206 * @description The scrolling direction for the table.
2214 valueFn: function() {
2215 var w = this.get('width'),
2216 h = this.get('height');
2235 * @description The hexadecimal colour value to set on the top-right of the table if a scrollbar exists.
2237 * @attribute COLOR_COLUMNFILLER
2241 COLOR_COLUMNFILLER: {
2243 validator: YLang.isString,
2244 setter: function(param) {
2245 if (this._headerContainerNode) {
2246 this._headerContainerNode.setStyle('backgroundColor', param);
2253 Y.extend(DataTableScroll, Y.Plugin.Base, {
2256 * @description The table node created in datatable-base
2258 * @property _parentTableNode
2262 _parentTableNode: null,
2266 * @description The THEAD node which resides within the table node created in datatable-base
2268 * @property _parentTheadNode
2272 _parentTheadNode: null,
2276 * @description The TBODY node which resides within the table node created in datatable-base
2278 * @property _parentTbodyNode
2282 _parentTbodyNode: null,
2286 * @description The TBODY Message node which resides within the table node created in datatable-base
2288 * @property _parentMsgNode
2292 _parentMsgNode: null,
2296 * @description The contentBox specified for the datatable in datatable-base
2298 * @property _parentContainer
2302 _parentContainer: null,
2306 * @description The DIV node that contains all the scrollable elements (a table with a tbody on it)
2308 * @property _bodyContainerNode
2312 _bodyContainerNode: null,
2316 * @description The DIV node that contains a table with a THEAD in it (which syncs its horizontal scroll with the _bodyContainerNode above)
2318 * @property _headerContainerNode
2322 _headerContainerNode: null,
2325 //--------------------------------------
2327 //--------------------------------------
2331 initializer: function(config) {
2332 var dt = this.get("host");
2333 this._parentContainer = dt.get('contentBox');
2334 this._parentContainer.addClass(CLASS_SCROLLABLE);
2338 /////////////////////////////////////////////////////////////////////////////
2340 // Set up Table Nodes
2342 /////////////////////////////////////////////////////////////////////////////
2345 * @description Set up methods to fire after host methods execute
2347 * @method _setUpNodes
2350 _setUpNodes: function() {
2352 this.afterHostMethod("_addTableNode", this._setUpParentTableNode);
2353 this.afterHostMethod("_addTheadNode", this._setUpParentTheadNode);
2354 this.afterHostMethod("_addTbodyNode", this._setUpParentTbodyNode);
2355 this.afterHostMethod("_addMessageNode", this._setUpParentMessageNode);
2356 //this.beforeHostMethod('renderUI', this._removeCaptionNode);
2357 this.afterHostMethod("renderUI", this.renderUI);
2358 this.afterHostMethod("syncUI", this.syncUI);
2360 if (this.get('_scroll') !== 'x') {
2361 this.afterHostMethod('_attachTheadThNode', this._attachTheadThNode);
2362 this.afterHostMethod('_attachTbodyTdNode', this._attachTbodyTdNode);
2368 * @description Stores the main <table> node provided by the host as a private property
2370 * @method _setUpParentTableNode
2373 _setUpParentTableNode: function() {
2374 this._parentTableNode = this.get('host')._tableNode;
2379 * @description Stores the main <thead> node provided by the host as a private property
2381 * @method _setUpParentTheadNode
2384 _setUpParentTheadNode: function() {
2385 this._parentTheadNode = this.get('host')._theadNode;
2389 * @description Stores the main <tbody> node provided by the host as a private property
2391 * @method _setUpParentTbodyNode
2394 _setUpParentTbodyNode: function() {
2395 this._parentTbodyNode = this.get('host')._tbodyNode;
2400 * @description Stores the main <tbody> message node provided by the host as a private property
2402 * @method _setUpParentMessageNode
2405 _setUpParentMessageNode: function() {
2406 this._parentMsgNode = this.get('host')._msgNode;
2409 /////////////////////////////////////////////////////////////////////////////
2413 /////////////////////////////////////////////////////////////////////////////
2416 * @description Primary rendering method that takes the datatable rendered in
2417 * the host, and splits it up into two separate <divs> each containing two
2418 * separate tables (one containing the head and one containing the body).
2419 * This method fires after renderUI is called on datatable-base.
2424 renderUI: function() {
2425 //Y.Profiler.start('render');
2426 this._createBodyContainer();
2427 this._createHeaderContainer();
2428 this._setContentBoxDimensions();
2429 //Y.Profiler.stop('render');
2430 //console.log(Y.Profiler.getReport("render"));
2435 * @description Post rendering method that is responsible for creating a column
2436 * filler, and performing width and scroll synchronization between the <th>
2437 * elements and the <td> elements.
2438 * This method fires after syncUI is called on datatable-base
2443 syncUI: function() {
2444 //Y.Profiler.start('sync');
2445 this._removeCaptionNode();
2448 //Y.Profiler.stop('sync');
2449 //console.log(Y.Profiler.getReport("sync"));
2454 * @description Remove the caption created in base. Scrolling datatables dont support captions.
2456 * @method _removeCaptionNode
2459 _removeCaptionNode: function() {
2460 this.get('host')._captionNode.remove();
2461 //Y.DataTable.Base.prototype.createCaption = function(v) {/*do nothing*/};
2462 //Y.DataTable.Base.prototype._uiSetCaption = function(v) {/*do nothing*/};
2466 * @description Adjusts the width of the TH and the TDs to make sure that the two are in sync
2468 * Implementation Details:
2469 * Compares the width of the TH liner div to the the width of the TD node. The TD liner width
2470 * is not actually used because the TD often stretches past the liner if the parent DIV is very
2471 * large. Measuring the TD width is more accurate.
2473 * Instead of measuring via .get('width'), 'clientWidth' is used, as it returns a number, whereas
2474 * 'width' returns a string, In IE6, 'clientWidth' is not supported, so 'offsetWidth' is used.
2475 * 'offsetWidth' is not as accurate on Chrome,FF as 'clientWidth' - thus the need for the fork.
2477 * @method _syncWidths
2480 _syncWidths: function() {
2481 var th = YNode.all('#'+this._parentContainer.get('id')+' .yui3-datatable-hd table thead th'), //nodelist of all THs
2482 td = YNode.one('#'+this._parentContainer.get('id')+' .yui3-datatable-bd table .yui3-datatable-data').get('firstChild').get('children'), //nodelist of all TDs in 1st row
2485 thWidth, tdWidth, thLiner, tdLiner,
2487 //stylesheet = new YStyleSheet('columnsSheet'),
2491 This for loop goes through the first row of TDs in the table.
2492 In a table, the width of the row is equal to the width of the longest cell in that column.
2493 Therefore, we can observe the widths of the cells in the first row only, as they will be the same in all the cells below (in each respective column)
2495 for (i=0, len = th.size(); i<len; i++) {
2497 //className = '.'+td.item(i).get('classList')._nodes[0];
2498 //If a width has not been already set on the TD:
2499 //if (td.item(i).get('firstChild').getStyle('width') === "auto") {
2501 //Get the liners for the TH and the TD cell in question
2502 thLiner = th.item(i).get('firstChild'); //TODO: use liner API - how? this is a node.
2503 tdLiner = td.item(i).get('firstChild');
2506 If browser is not IE - get the clientWidth of the Liner div and the TD.
2507 Note: We are not getting the width of the TDLiner, we are getting the width of the actual cell.
2508 Why? Because when the table is set to auto width, the cell will grow to try to fit the table in its container.
2509 The liner could potentially be much smaller than the cell width.
2511 TODO: Explore if there is a better way using only LINERS widths
2514 thWidth = thLiner.get('clientWidth'); //TODO: this should actually be done with getComputedStyle('width') but this messes up columns. Explore this option.
2515 tdWidth = td.item(i).get('clientWidth');
2518 //IE wasn't recognizing clientWidths, so we are using offsetWidths.
2519 //TODO: should use getComputedStyle('width') because offsetWidth will screw up when padding is changed.
2521 thWidth = thLiner.get('offsetWidth');
2522 tdWidth = td.item(i).get('offsetWidth');
2523 //thWidth = parseFloat(thLiner.getComputedStyle('width').split('px')[0]);
2524 //tdWidth = parseFloat(td.item(i).getComputedStyle('width').split('px')[0]); /* TODO: for some reason, using tdLiner.get('clientWidth') doesn't work - why not? */
2527 //if TH is bigger than TD, enlarge TD Liner
2528 if (thWidth > tdWidth) {
2529 tdLiner.setStyle('width', (thWidth - 20 + 'px'));
2530 //thLiner.setStyle('width', (tdWidth - 20 + 'px'));
2531 //stylesheet.set(className,{'width': (thWidth - 20 + 'px')});
2534 //if TD is bigger than TH, enlarge TH Liner
2535 else if (tdWidth > thWidth) {
2536 thLiner.setStyle('width', (tdWidth - 20 + 'px'));
2537 tdLiner.setStyle('width', (tdWidth - 20 + 'px')); //if you don't set an explicit width here, when the width is set in line 368, it will auto-shrink the widths of the other cells (because they dont have an explicit width)
2538 //stylesheet.set(className,{'width': (tdWidth - 20 + 'px')});
2545 //stylesheet.enable();
2550 * @description Adds the approriate width to the liner divs of the TH nodes before they are appended to DOM
2552 * @method _attachTheadThNode
2555 _attachTheadThNode: function(o) {
2556 var w = o.column.get('width') || 'auto';
2559 o.th.get('firstChild').setStyles({width: w, overflow:'hidden'}); //TODO: use liner API but liner is undefined here (not created?)
2565 * @description Adds the appropriate width to the liner divs of the TD nodes before they are appended to DOM
2567 * @method _attachTbodyTdNode
2570 _attachTbodyTdNode: function(o) {
2571 var w = o.column.get('width') || 'auto';
2574 o.td.get('firstChild').setStyles({width: w, overflow: 'hidden'}); //TODO: use liner API but liner is undefined here (not created?)
2575 //o.td.setStyles({'width': w, 'overflow': 'hidden'});
2581 * @description Creates the body DIV that contains all the data.
2583 * @method _createBodyContainer
2586 _createBodyContainer: function() {
2587 var bd = YNode.create(CONTAINER_BODY),
2588 onScrollFn = Y.bind("_onScroll", this);
2590 this._bodyContainerNode = bd;
2591 this._setStylesForTbody();
2593 bd.appendChild(this._parentTableNode);
2594 this._parentContainer.appendChild(bd);
2595 bd.on('scroll', onScrollFn);
2599 * @description Creates the DIV that contains a <table> with all the headers.
2601 * @method _createHeaderContainer
2604 _createHeaderContainer: function() {
2605 var hd = YNode.create(CONTAINER_HEADER),
2606 tbl = YNode.create(TEMPLATE_TABLE);
2608 this._headerContainerNode = hd;
2610 //hd.setStyle('backgroundColor',this.get("COLOR_COLUMNFILLER"));
2611 this._setStylesForThead();
2612 tbl.appendChild(this._parentTheadNode);
2613 hd.appendChild(tbl);
2614 this._parentContainer.prepend(hd);
2619 * @description Creates styles for the TBODY based on what type of table it is.
2621 * @method _setStylesForTbody
2624 _setStylesForTbody: function() {
2625 var dir = this.get('_scroll'),
2626 w = this.get('width') || "",
2627 h = this.get('height') || "",
2628 el = this._bodyContainerNode,
2629 styles = {width:"", height:h};
2632 //X-Scrolling tables should not have a Y-Scrollbar so overflow-y is hidden. THe width on x-scrolling tables must be set by user.
2633 styles.overflowY = 'hidden';
2636 else if (dir === 'y') {
2637 //Y-Scrolling tables should not have a X-Scrollbar so overflow-x is hidden. The width isn't neccessary because it can be auto.
2638 styles.overflowX = 'hidden';
2641 else if (dir === 'xy') {
2646 //scrolling is set to 'null' - ie: width and height are not set. Don't have any type of scrolling.
2647 styles.overflowX = 'hidden';
2648 styles.overflowY = 'hidden';
2652 el.setStyles(styles);
2658 * @description Creates styles for the THEAD based on what type of datatable it is.
2660 * @method _setStylesForThead
2663 _setStylesForThead: function() {
2664 var w = this.get('width') || "",
2665 el = this._headerContainerNode;
2667 //if (dir !== 'y') {
2668 el.setStyles({'width': w, 'overflow': 'hidden'});
2673 * @description Sets an auto width on the content box if it doesn't exist or if its a y-datatable.
2675 * @method _setContentBoxDimensions
2678 _setContentBoxDimensions: function() {
2680 if (this.get('_scroll') === 'y' || (!this.get('width'))) {
2681 this._parentContainer.setStyle('width', 'auto');
2686 /////////////////////////////////////////////////////////////////////////////
2690 /////////////////////////////////////////////////////////////////////////////
2693 * @description Ensures that scrolling is synced across the two tables
2698 _onScroll: function() {
2699 this._headerContainerNode.set('scrollLeft', this._bodyContainerNode.get('scrollLeft'));
2703 * @description Syncs padding around scrollable tables, including Column header right-padding
2704 * and container width and height.
2706 * @method _syncScroll
2709 _syncScroll : function() {
2710 this._syncScrollX();
2711 this._syncScrollY();
2712 this._syncScrollOverhang();
2715 this._headerContainerNode.set('scrollLeft', this._bodyContainerNode.get('scrollLeft'));
2717 if(!this.get("width")) {
2719 document.body.style += '';
2725 * @description Snaps container width for y-scrolling tables.
2727 * @method _syncScrollY
2730 _syncScrollY : function() {
2731 var tBody = this._parentTbodyNode,
2732 tBodyContainer = this._bodyContainerNode,
2734 // X-scrolling not enabled
2735 if(!this.get("width")) {
2736 // Snap outer container width to content
2737 w = (tBodyContainer.get('scrollHeight') > tBodyContainer.get('clientHeight')) ?
2738 // but account for y-scrollbar since it is visible
2739 (tBody.get('parentNode').get('clientWidth') + 19) + "px" :
2740 // no y-scrollbar, just borders
2741 (tBody.get('parentNode').get('clientWidth') + 2) + "px";
2742 this._parentContainer.setStyle('width', w);
2747 * @description Snaps container height for x-scrolling tables in IE. Syncs message TBODY width.
2748 * Taken from YUI2 ScrollingDataTable.js
2750 * @method _syncScrollX
2753 _syncScrollX: function() {
2754 var tBody = this._parentTbodyNode,
2755 tBodyContainer = this._bodyContainerNode,
2757 this._headerContainerNode.set('scrollLeft', this._bodyContainerNode.get('scrollLeft'));
2759 if(!this.get('height') && (YUA.ie)) {
2760 w = (tBodyContainer.get('scrollWidth') > tBodyContainer.get('offsetWidth')) ?
2761 (tBody.get('parentNode').get('offsetHeight') + 18) + "px" :
2762 tBody.get('parentNode').get('offsetHeight') + "px";
2764 tBodyContainer.setStyle('height', w);
2767 if (tBody.get('rows').length === 0) {
2768 this._parentMsgNode.get('parentNode').setStyle('width', this._parentTheadNode.get('parentNode').get('offsetWidth')+'px');
2771 this._parentMsgNode.get('parentNode').setStyle('width', "");
2777 * @description Adds/removes Column header overhang as necesary.
2778 * Taken from YUI2 ScrollingDataTable.js
2780 * @method _syncScrollOverhang
2783 _syncScrollOverhang: function() {
2784 var tBodyContainer = this._bodyContainerNode,
2787 //when its both x and y scrolling
2788 if ((tBodyContainer.get('scrollHeight') > tBodyContainer.get('clientHeight')) || (tBodyContainer.get('scrollWidth') > tBodyContainer.get('clientWidth'))) {
2792 this._setOverhangValue(padding);
2794 //After the widths have synced, there is a wrapping issue in the headerContainer in IE6. The header does not span the full
2795 //length of the table (does not cover all of the y-scrollbar). By adding this line in when there is a y-scroll, the header will span correctly.
2796 //TODO: this should not really occur on this.get('_scroll') === y - it should occur when scrollHeight > clientHeight, but clientHeight is not getting recognized in IE6?
2797 if (YUA.ie !== 0 && this.get('_scroll') === 'y' && this._bodyContainerNode.get('scrollHeight') > this._bodyContainerNode.get('offsetHeight'))
2799 this._headerContainerNode.setStyle('width', this._parentContainer.get('width'));
2805 * @description Sets Column header overhang to given width.
2806 * Taken from YUI2 ScrollingDataTable.js with minor modifications
2808 * @method _setOverhangValue
2809 * @param nBorderWidth {Number} Value of new border for overhang.
2812 _setOverhangValue: function(borderWidth) {
2813 var host = this.get('host'),
2814 cols = host.get('columnset').get('definitions'),
2815 //lastHeaders = cols[cols.length-1] || [],
2817 value = borderWidth + "px solid " + this.get("COLOR_COLUMNFILLER"),
2818 children = YNode.all('#'+this._parentContainer.get('id')+ ' .' + CLASS_HEADER + ' table thead th');
2820 children.item(len-1).setStyle('borderRight', value);
2825 Y.namespace("Plugin").DataTableScroll = DataTableScroll;
2832 }, '3.3.0' ,{requires:['datatable-base','plugin','stylesheet']});
2836 YUI.add('datatable', function(Y){}, '3.3.0' ,{use:['datatable-base','datatable-datasource','datatable-sort','datatable-scroll']});