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']});