]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - jssource/src_files/include/javascript/yui3/build/datatable/datatable.js
Release 6.5.0
[Github/sugarcrm.git] / jssource / src_files / include / javascript / yui3 / build / datatable / datatable.js
1 /*
2 Copyright (c) 2010, Yahoo! Inc. All rights reserved.
3 Code licensed under the BSD License:
4 http://developer.yahoo.com/yui/license.html
5 version: 3.3.0
6 build: 3167
7 */
8 YUI.add('datatable-base', function(Y) {
9
10 var YLang = Y.Lang,
11     YisValue = YLang.isValue,
12     Ysubstitute = Y.Lang.substitute,
13     YNode = Y.Node,
14     Ycreate = YNode.create,
15     YgetClassName = Y.ClassNameManager.getClassName,
16
17     DATATABLE = "datatable",
18     COLUMN = "column",
19     
20     FOCUS = "focus",
21     KEYDOWN = "keydown",
22     MOUSEENTER = "mouseenter",
23     MOUSELEAVE = "mouseleave",
24     MOUSEUP = "mouseup",
25     MOUSEDOWN = "mousedown",
26     CLICK = "click",
27     DBLCLICK = "dblclick",
28
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"),
37
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>';
47     
48
49
50
51 /**
52  * The Column class defines and manages attributes of Columns for DataTable.
53  *
54  * @class Column
55  * @extends Widget
56  * @constructor
57  */
58 function Column(config) {
59     Column.superclass.constructor.apply(this, arguments);
60 }
61
62 /////////////////////////////////////////////////////////////////////////////
63 //
64 // STATIC PROPERTIES
65 //
66 /////////////////////////////////////////////////////////////////////////////
67 Y.mix(Column, {
68     /**
69      * Class name.
70      *
71      * @property NAME
72      * @type String
73      * @static
74      * @final
75      * @value "column"
76      */
77     NAME: "column",
78
79 /////////////////////////////////////////////////////////////////////////////
80 //
81 // ATTRIBUTES
82 //
83 /////////////////////////////////////////////////////////////////////////////
84     ATTRS: {
85         /**
86         * @attribute id
87         * @description Unique internal identifier, used to stamp ID on TH element.
88         * @type String
89         * @readOnly
90         */
91         id: {
92             valueFn: "_defaultId",
93             readOnly: true
94         },
95         
96         /**
97         * @attribute key
98         * @description User-supplied identifier. Defaults to id.
99         * @type String
100         */
101         key: {
102             valueFn: "_defaultKey"
103         },
104
105         /**
106         * @attribute field
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.
110         * Defaults to key.
111         * @type String
112         */
113         field: {
114             valueFn: "_defaultField"
115         },
116
117         /**
118         * @attribute label
119         * @description Display label for column header. Defaults to key.
120         * @type String
121         */
122         label: {
123             valueFn: "_defaultLabel"
124         },
125         
126         /**
127         * @attribute children
128         * @description Array of child column definitions (for nested headers).
129         * @type String
130         */
131         children: {
132             value: null
133         },
134         
135         /**
136         * @attribute abbr
137         * @description TH abbr attribute.
138         * @type String
139         */
140         abbr: {
141             value: ""
142         },
143
144         //TODO: support custom classnames
145         // TH CSS classnames
146         classnames: {
147             readOnly: true,
148             getter: "_getClassnames"
149         },
150         
151         // Column formatter
152         formatter: {},
153
154         //requires datatable-sort
155         sortable: {
156             value: false
157         },
158         //sortOptions:defaultDir, sortFn, field
159
160         //TODO: support editable columns
161         // Column editor
162         editor: {},
163
164         //TODO: support resizeable columns
165         //TODO: support setting widths
166         // requires datatable-colresize
167         width: {},
168         resizeable: {},
169         minimized: {},
170         minWidth: {},
171         maxAutoWidth: {}
172     }
173 });
174
175 /////////////////////////////////////////////////////////////////////////////
176 //
177 // PROTOTYPE
178 //
179 /////////////////////////////////////////////////////////////////////////////
180 Y.extend(Column, Y.Widget, {
181     /////////////////////////////////////////////////////////////////////////////
182     //
183     // ATTRIBUTE HELPERS
184     //
185     /////////////////////////////////////////////////////////////////////////////
186     /**
187     * @method _defaultId
188     * @description Return ID for instance.
189     * @returns String
190     * @private
191     */
192     _defaultId: function() {
193         return Y.guid();
194     },
195
196     /**
197     * @method _defaultKey
198     * @description Return key for instance. Defaults to ID if one was not
199     * provided.
200     * @returns String
201     * @private
202     */
203     _defaultKey: function(key) {
204         return key || Y.guid();
205     },
206
207     /**
208     * @method _defaultField
209     * @description Return field for instance. Defaults to key if one was not
210     * provided.
211     * @returns String
212     * @private
213     */
214     _defaultField: function(field) {
215         return field || this.get("key");
216     },
217
218     /**
219     * @method _defaultLabel
220     * @description Return label for instance. Defaults to key if one was not
221     * provided.
222     * @returns String
223     * @private
224     */
225     _defaultLabel: function(label) {
226         return label || this.get("key");
227     },
228
229     /**
230      * Updates the UI if changes are made to abbr.
231      *
232      * @method _afterAbbrChange
233      * @param e {Event} Custom event for the attribute change.
234      * @private
235      */
236     _afterAbbrChange: function (e) {
237         this._uiSetAbbr(e.newVal);
238     },
239
240     /////////////////////////////////////////////////////////////////////////////
241     //
242     // PROPERTIES
243     //
244     /////////////////////////////////////////////////////////////////////////////
245     /**
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.
249      *
250      * @property keyIndex
251      * @type Number
252      */
253     keyIndex: null,
254     
255     /**
256     * @property headers
257     * @description Array of TH IDs associated with this column, for TD "headers"
258     * attribute. Value is set by Columnset code
259     * @type String[]
260     */
261     headers: null,
262
263     /**
264      * Number of cells the header spans. Value is set by Columnset code.
265      *
266      * @property colSpan
267      * @type Number
268      * @default 1
269      */
270     colSpan: 1,
271     
272     /**
273      * Number of rows the header spans. Value is set by Columnset code.
274      *
275      * @property rowSpan
276      * @type Number
277      * @default 1
278      */
279     rowSpan: 1,
280
281     /**
282      * Column's parent Column instance, if applicable. Value is set by Columnset
283      * code.
284      *
285      * @property parent
286      * @type Y.Column
287      */
288     parent: null,
289
290     /**
291      * The Node reference to the associated TH element.
292      *
293      * @property thNode
294      * @type Y.Node
295      */
296      
297     thNode: null,
298
299     /*TODO
300      * The Node reference to the associated liner element.
301      *
302      * @property thLinerNode
303      * @type Y.Node
304      
305     thLinerNode: null,*/
306     
307     /////////////////////////////////////////////////////////////////////////////
308     //
309     // METHODS
310     //
311     /////////////////////////////////////////////////////////////////////////////
312     /**
313     * Initializer.
314     *
315     * @method initializer
316     * @param config {Object} Config object.
317     * @private
318     */
319     initializer: function(config) {
320     },
321
322     /**
323     * Destructor.
324     *
325     * @method destructor
326     * @private
327     */
328     destructor: function() {
329     },
330
331     /**
332      * Returns classnames for Column.
333      *
334      * @method _getClassnames
335      * @private
336      */
337     _getClassnames: function () {
338         return Y.ClassNameManager.getClassName(COLUMN, this.get("id"));
339         /*var allClasses;
340
341         // Add CSS classes
342         if(lang.isString(oColumn.className)) {
343             // Single custom class
344             allClasses = [oColumn.className];
345         }
346         else if(lang.isArray(oColumn.className)) {
347             // Array of custom classes
348             allClasses = oColumn.className;
349         }
350         else {
351             // no custom classes
352             allClasses = [];
353         }
354
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();
357
358         // Column key - minus any chars other than "A-Z", "a-z", "0-9", "_", "-", ".", or ":"
359         allClasses[allClasses.length] = "yui-dt-col-" +oColumn.getSanitizedKey();
360
361         var isSortedBy = this.get("sortedBy") || {};
362         // Sorted
363         if(oColumn.key === isSortedBy.key) {
364             allClasses[allClasses.length] = isSortedBy.dir || '';
365         }
366         // Hidden
367         if(oColumn.hidden) {
368             allClasses[allClasses.length] = DT.CLASS_HIDDEN;
369         }
370         // Selected
371         if(oColumn.selected) {
372             allClasses[allClasses.length] = DT.CLASS_SELECTED;
373         }
374         // Sortable
375         if(oColumn.sortable) {
376             allClasses[allClasses.length] = DT.CLASS_SORTABLE;
377         }
378         // Resizeable
379         if(oColumn.resizeable) {
380             allClasses[allClasses.length] = DT.CLASS_RESIZEABLE;
381         }
382         // Editable
383         if(oColumn.editor) {
384             allClasses[allClasses.length] = DT.CLASS_EDITABLE;
385         }
386
387         // Addtnl classes, including First/Last
388         if(aAddClasses) {
389             allClasses = allClasses.concat(aAddClasses);
390         }
391
392         return allClasses.join(' ');*/
393     },
394
395     ////////////////////////////////////////////////////////////////////////////
396     //
397     // SYNC
398     //
399     ////////////////////////////////////////////////////////////////////////////
400     /**
401     * Syncs UI to intial state.
402     *
403     * @method syncUI
404     * @private
405     */
406     syncUI: function() {
407         this._uiSetAbbr(this.get("abbr"));
408     },
409
410     /**
411      * Updates abbr.
412      *
413      * @method _uiSetAbbr
414      * @param val {String} New abbr.
415      * @protected
416      */
417     _uiSetAbbr: function(val) {
418         this.thNode.set("abbr", val);
419     }
420 });
421
422 Y.Column = Column;
423
424 /**
425  * The Columnset class defines and manages a collection of Columns.
426  *
427  * @class Columnset
428  * @extends Base
429  * @constructor
430  */
431 function Columnset(config) {
432     Columnset.superclass.constructor.apply(this, arguments);
433 }
434
435 /////////////////////////////////////////////////////////////////////////////
436 //
437 // STATIC PROPERTIES
438 //
439 /////////////////////////////////////////////////////////////////////////////
440 Y.mix(Columnset, {
441     /**
442      * Class name.
443      *
444      * @property NAME
445      * @type String
446      * @static
447      * @final
448      * @value "columnset"
449      */
450     NAME: "columnset",
451
452     /////////////////////////////////////////////////////////////////////////////
453     //
454     // ATTRIBUTES
455     //
456     /////////////////////////////////////////////////////////////////////////////
457     ATTRS: {
458         /**
459         * @attribute definitions
460         * @description Array of column definitions that will populate this Columnset.
461         * @type Array
462         */
463         definitions: {
464             setter: "_setDefinitions"
465         }
466
467     }
468 });
469
470 /////////////////////////////////////////////////////////////////////////////
471 //
472 // PROTOTYPE
473 //
474 /////////////////////////////////////////////////////////////////////////////
475 Y.extend(Columnset, Y.Base, {
476     /////////////////////////////////////////////////////////////////////////////
477     //
478     // ATTRIBUTE HELPERS
479     //
480     /////////////////////////////////////////////////////////////////////////////
481     /**
482     * @method _setDefinitions
483     * @description Clones definitions before setting.
484     * @param definitions {Array} Array of column definitions.
485     * @returns Array
486     * @private
487     */
488     _setDefinitions: function(definitions) {
489             return Y.clone(definitions);
490     },
491     
492     /////////////////////////////////////////////////////////////////////////////
493     //
494     // PROPERTIES
495     //
496     /////////////////////////////////////////////////////////////////////////////
497     /**
498      * Top-down tree representation of Column hierarchy. Used to create DOM
499      * elements.
500      *
501      * @property tree
502      * @type Y.Column[]
503      */
504     tree: null,
505
506     /**
507      * Hash of all Columns by ID.
508      *
509      * @property idHash
510      * @type Object
511      */
512     idHash: null,
513
514     /**
515      * Hash of all Columns by key.
516      *
517      * @property keyHash
518      * @type Object
519      */
520     keyHash: null,
521
522     /**
523      * Array of only Columns that are meant to be displayed in DOM.
524      *
525      * @property keys
526      * @type Y.Column[]
527      */
528     keys: null,
529
530     /////////////////////////////////////////////////////////////////////////////
531     //
532     // METHODS
533     //
534     /////////////////////////////////////////////////////////////////////////////
535     /**
536     * Initializer. Generates all internal representations of the collection of
537     * Columns.
538     *
539     * @method initializer
540     * @param config {Object} Config object.
541     * @private
542     */
543     initializer: function() {
544
545         // DOM tree representation of all Columns
546         var tree = [],
547         // Hash of all Columns by ID
548         idHash = {},
549         // Hash of all Columns by key
550         keyHash = {},
551         // Flat representation of only Columns that are meant to display data
552         keys = [],
553         // Original definitions
554         definitions = this.get("definitions"),
555
556         self = this;
557
558         // Internal recursive function to define Column instances
559         function parseColumns(depth, currentDefinitions, parent) {
560             var i=0,
561                 len = currentDefinitions.length,
562                 currentDefinition,
563                 column,
564                 currentChildren;
565
566             // One level down
567             depth++;
568
569             // Create corresponding dom node if not already there for this depth
570             if(!tree[depth]) {
571                 tree[depth] = [];
572             }
573
574             // Parse each node at this depth for attributes and any children
575             for(; i<len; ++i) {
576                 currentDefinition = currentDefinitions[i];
577
578                 currentDefinition = YLang.isString(currentDefinition) ? {key:currentDefinition} : currentDefinition;
579
580                 // Instantiate a new Column for each node
581                 column = new Y.Column(currentDefinition);
582
583                 // Cross-reference Column ID back to the original object literal definition
584                 currentDefinition.yuiColumnId = column.get("id");
585
586                 // Add the new Column to the hash
587                 idHash[column.get("id")] = column;
588                 keyHash[column.get("key")] = column;
589
590                 // Assign its parent as an attribute, if applicable
591                 if(parent) {
592                     column.parent = parent;
593                 }
594
595                 // The Column has descendants
596                 if(YLang.isArray(currentDefinition.children)) {
597                     currentChildren = currentDefinition.children;
598                     column._set("children", currentChildren);
599
600                     self._setColSpans(column, currentDefinition);
601
602                     self._cascadePropertiesToChildren(column, currentChildren);
603
604                     // The children themselves must also be parsed for Column instances
605                     if(!tree[depth+1]) {
606                         tree[depth+1] = [];
607                     }
608                     parseColumns(depth, currentChildren, column);
609                 }
610                 // This Column does not have any children
611                 else {
612                     column.keyIndex = keys.length;
613                     // Default is already 1
614                     //column.colSpan = 1;
615                     keys.push(column);
616                 }
617
618                 // Add the Column to the top-down dom tree
619                 tree[depth].push(column);
620             }
621             depth--;
622         }
623
624         // Parse out Column instances from the array of object literals
625         parseColumns(-1, definitions);
626
627
628         // Save to the Columnset instance
629         this.tree = tree;
630         this.idHash = idHash;
631         this.keyHash = keyHash;
632         this.keys = keys;
633
634         this._setRowSpans();
635         this._setHeaders();
636     },
637
638     /**
639     * Destructor.
640     *
641     * @method destructor
642     * @private
643     */
644     destructor: function() {
645     },
646
647     /////////////////////////////////////////////////////////////////////////////
648     //
649     // COLUMN HELPERS
650     //
651     /////////////////////////////////////////////////////////////////////////////
652     /**
653     * Cascade certain properties to children if not defined on their own.
654     *
655     * @method _cascadePropertiesToChildren
656     * @private
657     */
658     _cascadePropertiesToChildren: function(column, currentChildren) {
659         //TODO: this is all a giant todo
660         var i = 0,
661             len = currentChildren.length,
662             child;
663
664         // Cascade certain properties to children if not defined on their own
665         for(; i<len; ++i) {
666             child = currentChildren[i];
667             if(column.get("className") && (child.className === undefined)) {
668                 child.className = column.get("className");
669             }
670             if(column.get("editor") && (child.editor === undefined)) {
671                 child.editor = column.get("editor");
672             }
673             if(column.get("formatter") && (child.formatter === undefined)) {
674                 child.formatter = column.get("formatter");
675             }
676             if(column.get("resizeable") && (child.resizeable === undefined)) {
677                 child.resizeable = column.get("resizeable");
678             }
679             if(column.get("sortable") && (child.sortable === undefined)) {
680                 child.sortable = column.get("sortable");
681             }
682             if(column.get("hidden")) {
683                 child.hidden = true;
684             }
685             if(column.get("width") && (child.width === undefined)) {
686                 child.width = column.get("width");
687             }
688             if(column.get("minWidth") && (child.minWidth === undefined)) {
689                 child.minWidth = column.get("minWidth");
690             }
691             if(column.get("maxAutoWidth") && (child.maxAutoWidth === undefined)) {
692                 child.maxAutoWidth = column.get("maxAutoWidth");
693             }
694         }
695     },
696
697     /**
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.
702     * @private
703     */
704     _setColSpans: function(column, definition) {
705         // Determine COLSPAN value for this Column
706         var terminalChildNodes = 0;
707
708         function countTerminalChildNodes(ancestor) {
709             var descendants = ancestor.children,
710                 i = 0,
711                 len = descendants.length;
712
713             // Drill down each branch and count terminal nodes
714             for(; i<len; ++i) {
715                 // Keep drilling down
716                 if(YLang.isArray(descendants[i].children)) {
717                     countTerminalChildNodes(descendants[i]);
718                 }
719                 // Reached branch terminus
720                 else {
721                     terminalChildNodes++;
722                 }
723             }
724         }
725         countTerminalChildNodes(definition);
726         column.colSpan = terminalChildNodes;
727     },
728
729     /**
730     * @method _setRowSpans
731     * @description Calculates and sets rowSpan attribute on all Columns.
732     * @private
733     */
734     _setRowSpans: function() {
735         // Determine ROWSPAN value for each Column in the DOM tree
736         function parseDomTreeForRowSpan(tree) {
737             var maxRowDepth = 1,
738                 currentRow,
739                 currentColumn,
740                 m,p;
741
742             // Calculate the max depth of descendants for this row
743             function countMaxRowDepth(row, tmpRowDepth) {
744                 tmpRowDepth = tmpRowDepth || 1;
745
746                 var i = 0,
747                     len = row.length,
748                     col;
749
750                 for(; i<len; ++i) {
751                     col = row[i];
752                     // Column has children, so keep counting
753                     if(YLang.isArray(col.children)) {
754                         tmpRowDepth++;
755                         countMaxRowDepth(col.children, tmpRowDepth);
756                         tmpRowDepth--;
757                     }
758                     // Column has children, so keep counting
759                     else if(col.get && YLang.isArray(col.get("children"))) {
760                         tmpRowDepth++;
761                         countMaxRowDepth(col.get("children"), tmpRowDepth);
762                         tmpRowDepth--;
763                     }
764                     // No children, is it the max depth?
765                     else {
766                         if(tmpRowDepth > maxRowDepth) {
767                             maxRowDepth = tmpRowDepth;
768                         }
769                     }
770                 }
771             }
772
773             // Count max row depth for each row
774             for(m=0; m<tree.length; m++) {
775                 currentRow = tree[m];
776                 countMaxRowDepth(currentRow);
777
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;
783                     }
784                     // Default is already 1
785                     // else currentColumn.rowSpan =1;
786                 }
787
788                 // Reset counter for next row
789                 maxRowDepth = 1;
790             }
791         }
792         parseDomTreeForRowSpan(this.tree);
793     },
794
795     /**
796     * @method _setHeaders
797     * @description Calculates and sets headers attribute on all Columns.
798     * @private
799     */
800     _setHeaders: function() {
801         var headers, column,
802             allKeys = this.keys,
803             i=0, len = allKeys.length;
804
805         function recurseAncestorsForHeaders(headers, column) {
806             headers.push(column.get("id"));
807             if(column.parent) {
808                 recurseAncestorsForHeaders(headers, column.parent);
809             }
810         }
811         for(; i<len; ++i) {
812             headers = [];
813             column = allKeys[i];
814             recurseAncestorsForHeaders(headers, column);
815             column.headers = headers.reverse().join(" ");
816         }
817     },
818
819     //TODO
820     getColumn: function() {
821     }
822 });
823
824 Y.Columnset = Columnset;
825
826 /**
827  * The DataTable widget provides a progressively enhanced DHTML control for
828  * displaying tabular data across A-grade browsers.
829  *
830  * @module datatable
831  */
832
833 /**
834  * Provides the base DataTable implementation, which can be extended to add
835  * additional functionality, such as sorting or scrolling.
836  *
837  * @module datatable
838  * @submodule datatable-base
839  */
840
841 /**
842  * Base class for the DataTable widget.
843  * @class DataTable.Base
844  * @extends Widget
845  * @constructor
846  */
847 function DTBase(config) {
848     DTBase.superclass.constructor.apply(this, arguments);
849 }
850
851 /////////////////////////////////////////////////////////////////////////////
852 //
853 // STATIC PROPERTIES
854 //
855 /////////////////////////////////////////////////////////////////////////////
856 Y.mix(DTBase, {
857
858     /**
859      * Class name.
860      *
861      * @property NAME
862      * @type String
863      * @static
864      * @final
865      * @value "dataTable"
866      */
867     NAME:  "dataTable",
868
869 /////////////////////////////////////////////////////////////////////////////
870 //
871 // ATTRIBUTES
872 //
873 /////////////////////////////////////////////////////////////////////////////
874     ATTRS: {
875         /**
876         * @attribute columnset
877         * @description Pointer to Columnset instance.
878         * @type Array | Y.Columnset
879         */
880         columnset: {
881             setter: "_setColumnset"
882         },
883
884         /**
885         * @attribute recordset
886         * @description Pointer to Recordset instance.
887         * @type Array | Y.Recordset
888         */
889         recordset: {
890             value: new Y.Recordset({records:[]}),
891             setter: "_setRecordset"
892         },
893
894         /*TODO
895         * @attribute state
896         * @description Internal state.
897         * @readonly
898         * @type
899         */
900         /*state: {
901             value: new Y.State(),
902             readOnly: true
903
904         },*/
905
906         /**
907         * @attribute summary
908         * @description Summary.
909         * @type String
910         */
911         summary: {
912         },
913
914         /**
915         * @attribute caption
916         * @description Caption
917         * @type String
918         */
919         caption: {
920         },
921
922         /**
923         * @attribute thValueTemplate
924         * @description Tokenized markup template for TH value.
925         * @type String
926         * @default '{value}'
927         */
928         thValueTemplate: {
929             value: TEMPLATE_VALUE
930         },
931
932         /**
933         * @attribute tdValueTemplate
934         * @description Tokenized markup template for TD value.
935         * @type String
936         * @default '{value}'
937         */
938         tdValueTemplate: {
939             value: TEMPLATE_VALUE
940         },
941
942         /**
943         * @attribute trTemplate
944         * @description Tokenized markup template for TR node creation.
945         * @type String
946         * @default '<tr id="{id}"></tr>'
947         */
948         trTemplate: {
949             value: TEMPLATE_TR
950         }
951     },
952
953 /////////////////////////////////////////////////////////////////////////////
954 //
955 // TODO: HTML_PARSER
956 //
957 /////////////////////////////////////////////////////////////////////////////
958     HTML_PARSER: {
959         /*caption: function (srcNode) {
960             
961         }*/
962     }
963 });
964
965 /////////////////////////////////////////////////////////////////////////////
966 //
967 // PROTOTYPE
968 //
969 /////////////////////////////////////////////////////////////////////////////
970 Y.extend(DTBase, Y.Widget, {
971     /**
972     * @property thTemplate
973     * @description Tokenized markup template for TH node creation.
974     * @type String
975     * @default '<th id="{id}" rowspan="{rowspan}" colspan="{colspan}" class="{classnames}" abbr="{abbr}"><div class="'+CLASS_LINER+'">{value}</div></th>'
976     */
977     thTemplate: TEMPLATE_TH,
978
979     /**
980     * @property tdTemplate
981     * @description Tokenized markup template for TD node creation.
982     * @type String
983     * @default '<td headers="{headers}"><div class="'+CLASS_LINER+'">{value}</div></td>'
984     */
985     tdTemplate: TEMPLATE_TD,
986     
987     /**
988     * @property _theadNode
989     * @description Pointer to THEAD node.
990     * @type Y.Node
991     * @private
992     */
993     _theadNode: null,
994     
995     /**
996     * @property _tbodyNode
997     * @description Pointer to TBODY node.
998     * @type Y.Node
999     * @private
1000     */
1001     _tbodyNode: null,
1002     
1003     /**
1004     * @property _msgNode
1005     * @description Pointer to message display node.
1006     * @type Y.Node
1007     * @private
1008     */
1009     _msgNode: null,
1010
1011     /////////////////////////////////////////////////////////////////////////////
1012     //
1013     // ATTRIBUTE HELPERS
1014     //
1015     /////////////////////////////////////////////////////////////////////////////
1016     /**
1017     * @method _setColumnset
1018     * @description Converts Array to Y.Columnset.
1019     * @param columns {Array | Y.Columnset}
1020     * @returns Y.Columnset
1021     * @private
1022     */
1023     _setColumnset: function(columns) {
1024         return YLang.isArray(columns) ? new Y.Columnset({definitions:columns}) : columns;
1025     },
1026
1027     /**
1028      * Updates the UI if Columnset is changed.
1029      *
1030      * @method _afterColumnsetChange
1031      * @param e {Event} Custom event for the attribute change.
1032      * @protected
1033      */
1034     _afterColumnsetChange: function (e) {
1035         if(this.get("rendered")) {
1036             this._uiSetColumnset(e.newVal);
1037         }
1038     },
1039
1040     /**
1041     * @method _setRecordset
1042     * @description Converts Array to Y.Recordset.
1043     * @param records {Array | Y.Recordset}
1044     * @returns Y.Recordset
1045     * @private
1046     */
1047     _setRecordset: function(rs) {
1048         if(YLang.isArray(rs)) {
1049             rs = new Y.Recordset({records:rs});
1050         }
1051
1052         rs.addTarget(this);
1053         return rs;
1054     },
1055     
1056     /**
1057     * Updates the UI if Recordset is changed.
1058     *
1059     * @method _afterRecordsetChange
1060     * @param e {Event} Custom event for the attribute change.
1061     * @protected
1062     */
1063     _afterRecordsetChange: function (e) {
1064         if(this.get("rendered")) {
1065             this._uiSetRecordset(e.newVal);
1066         }
1067     },
1068
1069     /**
1070      * Updates the UI if summary is changed.
1071      *
1072      * @method _afterSummaryChange
1073      * @param e {Event} Custom event for the attribute change.
1074      * @protected
1075      */
1076     _afterSummaryChange: function (e) {
1077         if(this.get("rendered")) {
1078             this._uiSetSummary(e.newVal);
1079         }
1080     },
1081
1082     /**
1083      * Updates the UI if caption is changed.
1084      *
1085      * @method _afterCaptionChange
1086      * @param e {Event} Custom event for the attribute change.
1087      * @protected
1088      */
1089     _afterCaptionChange: function (e) {
1090         if(this.get("rendered")) {
1091             this._uiSetCaption(e.newVal);
1092         }
1093     },
1094
1095     /////////////////////////////////////////////////////////////////////////////
1096     //
1097     // METHODS
1098     //
1099     /////////////////////////////////////////////////////////////////////////////
1100     /**
1101     * Initializer.
1102     *
1103     * @method initializer
1104     * @param config {Object} Config object.
1105     * @private
1106     */
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);
1112     },
1113
1114     /**
1115     * Destructor.
1116     *
1117     * @method destructor
1118     * @private
1119     */
1120     destructor: function() {
1121          this.get("recordset").removeTarget(this);
1122     },
1123     
1124     ////////////////////////////////////////////////////////////////////////////
1125     //
1126     // RENDER
1127     //
1128     ////////////////////////////////////////////////////////////////////////////
1129
1130     /**
1131     * Renders UI.
1132     *
1133     * @method renderUI
1134     * @private
1135     */
1136     renderUI: function() {
1137         // TABLE
1138         return (this._addTableNode(this.get("contentBox")) &&
1139         // COLGROUP
1140         this._addColgroupNode(this._tableNode) &&
1141         // THEAD
1142         this._addTheadNode(this._tableNode) &&
1143         // Primary TBODY
1144         this._addTbodyNode(this._tableNode) &&
1145         // Message TBODY
1146         this._addMessageNode(this._tableNode) &&
1147         // CAPTION
1148         this._addCaptionNode(this._tableNode));
1149    },
1150
1151     /**
1152     * Creates and attaches TABLE element to given container.
1153     *
1154     * @method _addTableNode
1155     * @param containerNode {Y.Node} Parent node.
1156     * @protected
1157     * @returns Y.Node
1158     */
1159     _addTableNode: function(containerNode) {
1160         if (!this._tableNode) {
1161             this._tableNode = containerNode.appendChild(Ycreate(TEMPLATE_TABLE));
1162         }
1163         return this._tableNode;
1164     },
1165
1166     /**
1167     * Creates and attaches COLGROUP element to given TABLE.
1168     *
1169     * @method _addColgroupNode
1170     * @param tableNode {Y.Node} Parent node.
1171     * @protected
1172     * @returns Y.Node
1173     */
1174     _addColgroupNode: function(tableNode) {
1175         // Add COLs to DOCUMENT FRAGMENT
1176         var len = this.get("columnset").keys.length,
1177             i = 0,
1178             allCols = ["<colgroup>"];
1179
1180         for(; i<len; ++i) {
1181             allCols.push(TEMPLATE_COL);
1182         }
1183
1184         allCols.push("</colgroup>");
1185
1186         // Create COLGROUP
1187         this._colgroupNode = tableNode.insertBefore(Ycreate(allCols.join("")), tableNode.get("firstChild"));
1188
1189         return this._colgroupNode;
1190     },
1191
1192     /**
1193     * Creates and attaches THEAD element to given container.
1194     *
1195     * @method _addTheadNode
1196     * @param tableNode {Y.Node} Parent node.
1197     * @protected
1198     * @returns Y.Node
1199     */
1200     _addTheadNode: function(tableNode) {
1201         if(tableNode) {
1202             this._theadNode = tableNode.insertBefore(Ycreate(TEMPLATE_THEAD), this._colgroupNode.next());
1203             return this._theadNode;
1204         }
1205     },
1206
1207     /**
1208     * Creates and attaches TBODY element to given container.
1209     *
1210     * @method _addTbodyNode
1211     * @param tableNode {Y.Node} Parent node.
1212     * @protected
1213     * @returns Y.Node
1214     */
1215     _addTbodyNode: function(tableNode) {
1216         this._tbodyNode = tableNode.appendChild(Ycreate(TEMPLATE_TBODY));
1217         return this._tbodyNode;
1218     },
1219
1220     /**
1221     * Creates and attaches message display element to given container.
1222     *
1223     * @method _addMessageNode
1224     * @param tableNode {Y.Node} Parent node.
1225     * @protected
1226     * @returns Y.Node
1227     */
1228     _addMessageNode: function(tableNode) {
1229         this._msgNode = tableNode.insertBefore(Ycreate(TEMPLATE_MSG), this._tbodyNode);
1230         return this._msgNode;
1231     },
1232
1233     /**
1234     * Creates and attaches CAPTION element to given container.
1235     *
1236     * @method _addCaptionNode
1237     * @param tableNode {Y.Node} Parent node.
1238     * @protected
1239     * @returns Y.Node
1240     */
1241     _addCaptionNode: function(tableNode) {
1242         this._captionNode = tableNode.createCaption();
1243         return this._captionNode;
1244     },
1245
1246     ////////////////////////////////////////////////////////////////////////////
1247     //
1248     // BIND
1249     //
1250     ////////////////////////////////////////////////////////////////////////////
1251
1252     /**
1253     * Binds events.
1254     *
1255     * @method bindUI
1256     * @private
1257     */
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";
1262     },
1263     
1264     delegate: function(type) {
1265         //TODO: is this necessary?
1266         if(type==="dblclick") {
1267             this.get("boundingBox").delegate.apply(this.get("boundingBox"), arguments);
1268         }
1269         else {
1270             this.get("contentBox").delegate.apply(this.get("contentBox"), arguments);
1271         }
1272     },
1273     
1274
1275     ////////////////////////////////////////////////////////////////////////////
1276     //
1277     // SYNC
1278     //
1279     ////////////////////////////////////////////////////////////////////////////
1280
1281     /**
1282     * Syncs UI to intial state.
1283     *
1284     * @method syncUI
1285     * @private
1286     */
1287     syncUI: function() {
1288         // THEAD ROWS
1289         this._uiSetColumnset(this.get("columnset"));
1290         // DATA ROWS
1291         this._uiSetRecordset(this.get("recordset"));
1292         // SUMMARY
1293         this._uiSetSummary(this.get("summary"));
1294         // CAPTION
1295         this._uiSetCaption(this.get("caption"));
1296     },
1297
1298     /**
1299      * Updates summary.
1300      *
1301      * @method _uiSetSummary
1302      * @param val {String} New summary.
1303      * @protected
1304      */
1305     _uiSetSummary: function(val) {
1306         val = YisValue(val) ? val : "";
1307         this._tableNode.set("summary", val);
1308     },
1309
1310     /**
1311      * Updates caption.
1312      *
1313      * @method _uiSetCaption
1314      * @param val {String} New caption.
1315      * @protected
1316      */
1317     _uiSetCaption: function(val) {
1318         val = YisValue(val) ? val : "";
1319         this._captionNode.setContent(val);
1320     },
1321
1322
1323     ////////////////////////////////////////////////////////////////////////////
1324     //
1325     // THEAD/COLUMNSET FUNCTIONALITY
1326     //
1327     ////////////////////////////////////////////////////////////////////////////
1328     /**
1329      * Updates THEAD.
1330      *
1331      * @method _uiSetColumnset
1332      * @param cs {Y.Columnset} New Columnset.
1333      * @protected
1334      */
1335     _uiSetColumnset: function(cs) {
1336         var tree = cs.tree,
1337             thead = this._theadNode,
1338             i = 0,
1339             len = tree.length,
1340             parent = thead.get("parentNode"),
1341             nextSibling = thead.next();
1342             
1343         // Move THEAD off DOM
1344         thead.remove();
1345         
1346         thead.get("children").remove(true);
1347
1348         // Iterate tree of columns to add THEAD rows
1349         for(; i<len; ++i) {
1350             this._addTheadTrNode({thead:thead, columns:tree[i]}, (i === 0), (i === len-1));
1351         }
1352
1353         // Column helpers needs _theadNode to exist
1354         //this._createColumnHelpers();
1355
1356         
1357         // Re-attach THEAD to DOM
1358         parent.insert(thead, nextSibling);
1359
1360      },
1361      
1362     /**
1363     * Creates and attaches header row element.
1364     *
1365     * @method _addTheadTrNode
1366     * @param o {Object} {thead, columns}.
1367     * @param isFirst {Boolean} Is first row.
1368     * @param isFirst {Boolean} Is last row.
1369     * @protected
1370     */
1371      _addTheadTrNode: function(o, isFirst, isLast) {
1372         o.tr = this._createTheadTrNode(o, isFirst, isLast);
1373         this._attachTheadTrNode(o);
1374      },
1375      
1376
1377     /**
1378     * Creates header row element.
1379     *
1380     * @method _createTheadTrNode
1381     * @param o {Object} {thead, columns}.
1382     * @param isFirst {Boolean} Is first row.
1383     * @param isLast {Boolean} Is last row.
1384     * @protected
1385     * @returns Y.Node
1386     */
1387     _createTheadTrNode: function(o, isFirst, isLast) {
1388         //TODO: custom classnames
1389         var tr = Ycreate(Ysubstitute(this.get("trTemplate"), o)),
1390             i = 0,
1391             columns = o.columns,
1392             len = columns.length,
1393             column;
1394
1395          // Set FIRST/LAST class
1396         if(isFirst) {
1397             tr.addClass(CLASS_FIRST);
1398         }
1399         if(isLast) {
1400             tr.addClass(CLASS_LAST);
1401         }
1402
1403         for(; i<len; ++i) {
1404             column = columns[i];
1405             this._addTheadThNode({value:column.get("label"), column: column, tr:tr});
1406         }
1407
1408         return tr;
1409     },
1410
1411     /**
1412     * Attaches header row element.
1413     *
1414     * @method _attachTheadTrNode
1415     * @param o {Object} {thead, columns, tr}.
1416     * @protected
1417     */
1418     _attachTheadTrNode: function(o) {
1419         o.thead.appendChild(o.tr);
1420     },
1421
1422     /**
1423     * Creates and attaches header cell element.
1424     *
1425     * @method _addTheadThNode
1426     * @param o {Object} {value, column, tr}.
1427     * @protected
1428     */
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;
1434     },
1435
1436     /**
1437     * Creates header cell element.
1438     *
1439     * @method _createTheadThNode
1440     * @param o {Object} {value, column, tr}.
1441     * @protected
1442     * @returns Y.Node
1443     */
1444     _createTheadThNode: function(o) {
1445         var column = o.column;
1446         
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);
1454
1455         /*TODO
1456         // Clear minWidth on hidden Columns
1457         if(column.get("hidden")) {
1458             //this._clearMinWidth(column);
1459         }
1460         */
1461         
1462         return Ycreate(Ysubstitute(this.thTemplate, o));
1463     },
1464
1465     /**
1466     * Attaches header cell element.
1467     *
1468     * @method _attachTheadThNode
1469     * @param o {Object} {value, column, tr}.
1470     * @protected
1471     */
1472     _attachTheadThNode: function(o) {
1473         o.tr.appendChild(o.th);
1474     },
1475
1476     ////////////////////////////////////////////////////////////////////////////
1477     //
1478     // TBODY/RECORDSET FUNCTIONALITY
1479     //
1480     ////////////////////////////////////////////////////////////////////////////
1481     /**
1482      * Updates TBODY.
1483      *
1484      * @method _uiSetRecordset
1485      * @param rs {Y.Recordset} New Recordset.
1486      * @protected
1487      */
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(),
1494             o = {},
1495             newTbody;
1496
1497         // Replace TBODY with a new one
1498         //TODO: split _addTbodyNode into create/attach
1499         oldTbody.remove();
1500         oldTbody = null;
1501         newTbody = this._addTbodyNode(this._tableNode);
1502         newTbody.remove();
1503         this._tbodyNode = newTbody;
1504         o.tbody = newTbody;
1505         
1506         // Iterate Recordset to use existing TR when possible or add new TR
1507         for(; i<len; ++i) {
1508             o.record = rs.getRecord(i);
1509             o.rowindex = i;
1510             this._addTbodyTrNode(o); //TODO: sometimes rowindex != recordindex
1511         }
1512         
1513         // TBODY to DOM
1514         parent.insert(this._tbodyNode, nextSibling);
1515     },
1516
1517     /**
1518     * Creates and attaches data row element.
1519     *
1520     * @method _addTbodyTrNode
1521     * @param o {Object} {tbody, record}
1522     * @protected
1523     */
1524     _addTbodyTrNode: function(o) {
1525         var tbody = o.tbody,
1526             record = o.record;
1527         o.tr = tbody.one("#"+record.get("id")) || this._createTbodyTrNode(o);
1528         this._attachTbodyTrNode(o);
1529     },
1530
1531     /**
1532     * Creates data row element.
1533     *
1534     * @method _createTbodyTrNode
1535     * @param o {Object} {tbody, record}
1536     * @protected
1537     * @returns Y.Node
1538     */
1539     _createTbodyTrNode: function(o) {
1540         var tr = Ycreate(Ysubstitute(this.get("trTemplate"), {id:o.record.get("id")})),
1541             i = 0,
1542             allKeys = this.get("columnset").keys,
1543             len = allKeys.length;
1544
1545         o.tr = tr;
1546         
1547         for(; i<len; ++i) {
1548             o.column = allKeys[i];
1549             this._addTbodyTdNode(o);
1550         }
1551         
1552         return tr;
1553     },
1554
1555     /**
1556     * Attaches data row element.
1557     *
1558     * @method _attachTbodyTrNode
1559     * @param o {Object} {tbody, record, tr}.
1560     * @protected
1561     */
1562     _attachTbodyTrNode: function(o) {
1563         var tbody = o.tbody,
1564             tr = o.tr,
1565             index = o.rowindex,
1566             nextSibling = tbody.get("children").item(index) || null,
1567             isEven = (index%2===0);
1568             
1569         if(isEven) {
1570             tr.replaceClass(CLASS_ODD, CLASS_EVEN);
1571         }
1572         else {
1573             tr.replaceClass(CLASS_EVEN, CLASS_ODD);
1574         }
1575         
1576         tbody.insertBefore(tr, nextSibling);
1577     },
1578
1579     /**
1580     * Creates and attaches data cell element.
1581     *
1582     * @method _addTbodyTdNode
1583     * @param o {Object} {record, column, tr}.
1584     * @protected
1585     */
1586     _addTbodyTdNode: function(o) {
1587         o.td = this._createTbodyTdNode(o);
1588         this._attachTbodyTdNode(o);
1589     },
1590     
1591     /**
1592     * Creates data cell element.
1593     *
1594     * @method _createTbodyTdNode
1595     * @param o {Object} {record, column, tr}.
1596     * @protected
1597     * @returns Y.Node
1598     */
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));
1606     },
1607     
1608     /**
1609     * Attaches data cell element.
1610     *
1611     * @method _attachTbodyTdNode
1612     * @param o {Object} {record, column, tr, headers, classnames, value}.
1613     * @protected
1614     */
1615     _attachTbodyTdNode: function(o) {
1616         o.tr.appendChild(o.td);
1617     },
1618
1619     /**
1620      * Returns markup to insert into data cell element.
1621      *
1622      * @method formatDataCell
1623      * @param @param o {Object} {record, column, tr, headers, classnames}.
1624      */
1625     formatDataCell: function(o) {
1626         var record = o.record,
1627             column = o.column,
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
1636     }
1637 });
1638
1639 Y.namespace("DataTable").Base = DTBase;
1640
1641
1642
1643 }, '3.3.0' ,{requires:['recordset-base','widget','substitute','event-mouseenter']});
1644
1645 YUI.add('datatable-datasource', function(Y) {
1646
1647 /**
1648  * Plugs DataTable with DataSource integration.
1649  *
1650  * @module datatable
1651  * @submodule datatable-datasource
1652  */
1653
1654 /**
1655  * Adds DataSource integration to DataTable.
1656  * @class DataTableDataSource
1657  * @extends Plugin.Base
1658  */
1659 function DataTableDataSource() {
1660     DataTableDataSource.superclass.constructor.apply(this, arguments);
1661 }
1662
1663 /////////////////////////////////////////////////////////////////////////////
1664 //
1665 // STATIC PROPERTIES
1666 //
1667 /////////////////////////////////////////////////////////////////////////////
1668 Y.mix(DataTableDataSource, {
1669     /**
1670      * The namespace for the plugin. This will be the property on the host which
1671      * references the plugin instance.
1672      *
1673      * @property NS
1674      * @type String
1675      * @static
1676      * @final
1677      * @value "datasource"
1678      */
1679     NS: "datasource",
1680
1681     /**
1682      * Class name.
1683      *
1684      * @property NAME
1685      * @type String
1686      * @static
1687      * @final
1688      * @value "dataTableDataSource"
1689      */
1690     NAME: "dataTableDataSource",
1691
1692 /////////////////////////////////////////////////////////////////////////////
1693 //
1694 // ATTRIBUTES
1695 //
1696 /////////////////////////////////////////////////////////////////////////////
1697     ATTRS: {
1698         /**
1699         * @attribute datasource
1700         * @description Pointer to DataSource instance.
1701         * @type Y.DataSource
1702         */
1703         datasource: {
1704             setter: "_setDataSource"
1705         },
1706         
1707         /**
1708         * @attribute initialRequest
1709         * @description Request sent to DataSource immediately upon initialization.
1710         * @type Object
1711         */
1712         initialRequest: {
1713             setter: "_setInitialRequest"
1714         }
1715     }
1716 });
1717
1718 /////////////////////////////////////////////////////////////////////////////
1719 //
1720 // PROTOTYPE
1721 //
1722 /////////////////////////////////////////////////////////////////////////////
1723 Y.extend(DataTableDataSource, Y.Plugin.Base, {
1724     /////////////////////////////////////////////////////////////////////////////
1725     //
1726     // ATTRIBUTE HELPERS
1727     //
1728     /////////////////////////////////////////////////////////////////////////////
1729     /**
1730     * @method _setDataSource
1731     * @description Creates new DataSource instance if one is not provided.
1732     * @param ds {Object | Y.DataSource}
1733     * @returns Y.DataSource
1734     * @private
1735     */
1736     _setDataSource: function(ds) {
1737         return ds || new Y.DataSource.Local(ds);
1738     },
1739
1740     /**
1741     * @method _setInitialRequest
1742     * @description Sends request to DataSource.
1743     * @param request {Object} DataSource request.
1744     * @private
1745     */
1746     _setInitialRequest: function(request) {
1747     },
1748
1749     /////////////////////////////////////////////////////////////////////////////
1750     //
1751     // METHODS
1752     //
1753     /////////////////////////////////////////////////////////////////////////////
1754     /**
1755     * Initializer.
1756     *
1757     * @method initializer
1758     * @param config {Object} Config object.
1759     * @private
1760     */
1761     initializer: function(config) {
1762         if(!Y.Lang.isUndefined(config.initialRequest)) {
1763             this.load({request:config.initialRequest});
1764         }
1765     },
1766
1767     ////////////////////////////////////////////////////////////////////////////
1768     //
1769     // DATA
1770     //
1771     ////////////////////////////////////////////////////////////////////////////
1772
1773     /**
1774      * Load data by calling DataSource's sendRequest() method under the hood.
1775      *
1776      * @method load
1777      * @param config {object} Optional configuration parameters:
1778      *
1779      * <dl>
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:
1782      *    <dl>
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>
1787      *    </dl>
1788      * </dd>
1789      * <dt>datasource</dt><dd>Pass in a new DataSource instance to override the current DataSource for this transaction.</dd>
1790      * </dl>
1791      */
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
1799         };
1800
1801         var ds = (config.datasource || this.get("datasource"));
1802         if(ds) {
1803             ds.sendRequest(config);
1804         }
1805     },
1806
1807     /**
1808      * Callback function passed to DataSource's sendRequest() method populates
1809      * an entire DataTable with new data, clearing previous data, if any.
1810      *
1811      * @method onDataReturnInitializeTable
1812      * @param e {Event.Facade} DataSource Event Facade object.
1813      */
1814     onDataReturnInitializeTable : function(e) {
1815         this.get("host").set("recordset", new Y.Recordset({records: e.response.results}));
1816     }
1817 });
1818
1819 Y.namespace("Plugin").DataTableDataSource = DataTableDataSource;
1820
1821
1822
1823
1824
1825
1826 }, '3.3.0' ,{requires:['datatable-base','plugin','datasource-local']});
1827
1828 YUI.add('datatable-sort', function(Y) {
1829
1830 /**
1831  * Plugs DataTable with sorting functionality.
1832  *
1833  * @module datatable
1834  * @submodule datatable-sort
1835  */
1836
1837 /**
1838  * Adds column sorting to DataTable.
1839  * @class DataTableSort
1840  * @extends Plugin.Base
1841  */
1842 var YgetClassName = Y.ClassNameManager.getClassName,
1843
1844     DATATABLE = "datatable",
1845     COLUMN = "column",
1846     ASC = "asc",
1847     DESC = "desc",
1848
1849     //TODO: Don't use hrefs - use tab/arrow/enter
1850     TEMPLATE = '<a class="{link_class}" title="{link_title}" href="{link_href}">{value}</a>';
1851
1852
1853 function DataTableSort() {
1854     DataTableSort.superclass.constructor.apply(this, arguments);
1855 }
1856
1857 /////////////////////////////////////////////////////////////////////////////
1858 //
1859 // STATIC PROPERTIES
1860 //
1861 /////////////////////////////////////////////////////////////////////////////
1862 Y.mix(DataTableSort, {
1863     /**
1864      * The namespace for the plugin. This will be the property on the host which
1865      * references the plugin instance.
1866      *
1867      * @property NS
1868      * @type String
1869      * @static
1870      * @final
1871      * @value "sort"
1872      */
1873     NS: "sort",
1874
1875     /**
1876      * Class name.
1877      *
1878      * @property NAME
1879      * @type String
1880      * @static
1881      * @final
1882      * @value "dataTableSort"
1883      */
1884     NAME: "dataTableSort",
1885
1886 /////////////////////////////////////////////////////////////////////////////
1887 //
1888 // ATTRIBUTES
1889 //
1890 /////////////////////////////////////////////////////////////////////////////
1891     ATTRS: {
1892         /**
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.
1897         * @type Object
1898         * @default {event:"click", selector:"th"}
1899         * @writeOnce "initOnly"
1900         */
1901         trigger: {
1902             value: {event:"click", selector:"th"},
1903             writeOnce: "initOnly"
1904         },
1905         
1906         /**
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".
1910         * @type Object
1911         */
1912         lastSortedBy: {
1913             setter: "_setLastSortedBy",
1914             lazyAdd: false
1915         },
1916         
1917         /**
1918         * @attribute template
1919         * @description Tokenized markup template for TH sort element.
1920         * @type String
1921         * @default '<a class="{link_class}" title="{link_title}" href="{link_href}">{value}</a>'
1922         */
1923         template: {
1924             value: TEMPLATE
1925         }
1926     }
1927 });
1928
1929 /////////////////////////////////////////////////////////////////////////////
1930 //
1931 // PROTOTYPE
1932 //
1933 /////////////////////////////////////////////////////////////////////////////
1934 Y.extend(DataTableSort, Y.Plugin.Base, {
1935
1936     /////////////////////////////////////////////////////////////////////////////
1937     //
1938     // METHODS
1939     //
1940     /////////////////////////////////////////////////////////////////////////////
1941     /**
1942     * Initializer.
1943     *
1944     * @method initializer
1945     * @param config {Object} Config object.
1946     * @private
1947     */
1948     initializer: function(config) {
1949         var dt = this.get("host"),
1950             trigger = this.get("trigger");
1951             
1952         dt.get("recordset").plug(Y.Plugin.RecordsetSort, {dt: dt});
1953         dt.get("recordset").sort.addTarget(dt);
1954         
1955         // Wrap link around TH value
1956         this.doBefore("_createTheadThNode", this._beforeCreateTheadThNode);
1957         
1958         // Add class
1959         this.doBefore("_attachTheadThNode", this._beforeAttachTheadThNode);
1960         this.doBefore("_attachTbodyTdNode", this._beforeAttachTbodyTdNode);
1961
1962         // Attach trigger handlers
1963         dt.delegate(trigger.event, Y.bind(this._onEventSortColumn,this), trigger.selector);
1964
1965         // Attach UI hooks
1966         dt.after("recordsetSort:sort", function() {
1967             this._uiSetRecordset(this.get("recordset"));
1968         });
1969         this.on("lastSortedByChange", function(e) {
1970             this._uiSetLastSortedBy(e.prevVal, e.newVal, dt);
1971         });
1972
1973         //TODO
1974         //dt.after("recordset:mutation", function() {//reset lastSortedBy});
1975         
1976         //TODO
1977         //add Column sortFn ATTR
1978         
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);
1983         }
1984     },
1985
1986     /**
1987     * @method _setLastSortedBy
1988     * @description Normalizes lastSortedBy
1989     * @param val {String | Object} {key, dir} or "key"
1990     * @returns {key, dir, notdir}
1991     * @private
1992     */
1993     _setLastSortedBy: function(val) {
1994         if(Y.Lang.isString(val)) {
1995             return {key:val, dir:"asc", notdir:"desc"};
1996         }
1997         else if (val && val.key) {
1998             if(val.dir === "desc") {
1999                 return {key:val.key, dir:"desc", notdir:"asc"};
2000             }
2001             else {
2002                 return {key:val.key, dir:"asc", notdir:"desc"};
2003             }
2004         }
2005         else {
2006             return null;
2007         }
2008     },
2009
2010     /**
2011      * Updates sort UI.
2012      *
2013      * @method _uiSetLastSortedBy
2014      * @param val {Object} New lastSortedBy object {key,dir}.
2015      * @param dt {Y.DataTable.Base} Host.
2016      * @protected
2017      */
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;
2028
2029         // Clear previous UI
2030         if(prevColumn) {
2031             prevColumn.thNode.removeClass(YgetClassName(DATATABLE, prevDir));
2032             prevRowList = tbodyNode.all("."+YgetClassName(COLUMN, prevColumn.get("id")));
2033             prevRowList.removeClass(YgetClassName(DATATABLE, prevDir));
2034         }
2035
2036         // Add new sort UI
2037         if(newColumn) {
2038             newColumn.thNode.addClass(YgetClassName(DATATABLE, newDir));
2039             newRowList = tbodyNode.all("."+YgetClassName(COLUMN, newColumn.get("id")));
2040             newRowList.addClass(YgetClassName(DATATABLE, newDir));
2041         }
2042     },
2043
2044     /**
2045     * Before header cell element is created, inserts link markup around {value}.
2046     *
2047     * @method _beforeCreateTheadThNode
2048     * @param o {Object} {value, column, tr}.
2049     * @protected
2050     */
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",
2056                 link_href: "#",
2057                 value: o.value
2058             });
2059         }
2060     },
2061
2062     /**
2063     * Before header cell element is attached, sets applicable class names.
2064     *
2065     * @method _beforeAttachTheadThNode
2066     * @param o {Object} {value, column, tr}.
2067     * @protected
2068     */
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;
2074
2075         // This Column is sortable
2076         if(o.column.get("sortable")) {
2077             o.th.addClass(YgetClassName(DATATABLE, "sortable"));
2078         }
2079         // This Column is currently sorted
2080         if(key && (key === o.column.get("key"))) {
2081             o.th.replaceClass(YgetClassName(DATATABLE, notdir), YgetClassName(DATATABLE, dir));
2082         }
2083     },
2084
2085     /**
2086     * Before header cell element is attached, sets applicable class names.
2087     *
2088     * @method _before_beforeAttachTbodyTdNode
2089     * @param o {Object} {record, column, tr, headers, classnames, value}.
2090     * @protected
2091     */
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;
2097
2098         // This Column is sortable
2099         if(o.column.get("sortable")) {
2100             o.td.addClass(YgetClassName(DATATABLE, "sortable"));
2101         }
2102         // This Column is currently sorted
2103         if(key && (key === o.column.get("key"))) {
2104             o.td.replaceClass(YgetClassName(DATATABLE, notdir), YgetClassName(DATATABLE, dir));
2105         }
2106     },
2107     /**
2108     * In response to the "trigger" event, sorts the underlying Recordset and
2109     * updates the lastSortedBy attribute.
2110     *
2111     * @method _onEventSortColumn
2112     * @param o {Object} {value, column, tr}.
2113     * @protected
2114     */
2115     _onEventSortColumn: function(e) {
2116         e.halt();
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});
2130         }
2131     }
2132 });
2133
2134 Y.namespace("Plugin").DataTableSort = DataTableSort;
2135
2136
2137
2138
2139
2140
2141 }, '3.3.0' ,{lang:['en'], requires:['datatable-base','plugin','recordset-sort']});
2142
2143 YUI.add('datatable-scroll', function(Y) {
2144
2145 /**
2146  * Extends DataTable base to enable x,y, and xy scrolling.
2147  * @module datatable
2148  * @submodule datatable-scroll
2149  */
2150
2151
2152 var YNode = Y.Node,
2153         YLang = Y.Lang,
2154         YUA = Y.UA,
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>';
2163         
2164 /**
2165  * Adds scrolling to DataTable.
2166  * @class DataTableScroll
2167  * @extends Plugin.Base
2168  */
2169 function DataTableScroll() {
2170     DataTableScroll.superclass.constructor.apply(this, arguments);
2171 }
2172
2173 Y.mix(DataTableScroll, {
2174     NS: "scroll",
2175
2176     NAME: "dataTableScroll",
2177
2178     ATTRS: {
2179         
2180                 /**
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.
2182             *
2183             * @attribute width
2184             * @public
2185             * @type string
2186             */
2187         width: {
2188                         value: undefined,
2189                         writeOnce: "initOnly"
2190                 },
2191                 
2192                 /**
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.
2194             *
2195             * @attribute height
2196             * @public
2197             * @type string
2198             */
2199                 height: {
2200                         value: undefined,
2201                         writeOnce: "initOnly"
2202                 },
2203                 
2204                 
2205                 /**
2206             * @description The scrolling direction for the table.
2207             *
2208             * @attribute scroll
2209             * @private
2210             * @type string
2211             */
2212                 _scroll: {
2213                         //value: 'y',
2214                         valueFn: function() {
2215                             var w = this.get('width'),
2216                             h = this.get('height');
2217                             
2218                             if (w && h) {
2219                                 return 'xy';
2220                             }
2221                             else if (w) {
2222                                 return 'x';
2223                             }
2224                             else if (h) {
2225                                 return 'y';
2226                             }
2227                             else {
2228                                 return null;
2229                             }
2230                         }
2231                 },
2232                 
2233                 
2234                 /**
2235             * @description The hexadecimal colour value to set on the top-right of the table if a scrollbar exists. 
2236             *
2237             * @attribute COLOR_COLUMNFILLER
2238             * @public
2239             * @type string
2240             */
2241                 COLOR_COLUMNFILLER: {
2242                         value: '#f2f2f2',
2243                         validator: YLang.isString,
2244                         setter: function(param) {
2245                                 if (this._headerContainerNode) {
2246                                         this._headerContainerNode.setStyle('backgroundColor', param);
2247                                 }
2248                         }
2249                 }
2250     }
2251 });
2252
2253 Y.extend(DataTableScroll, Y.Plugin.Base, {
2254         
2255         /**
2256     * @description The table node created in datatable-base
2257     *
2258     * @property _parentTableNode
2259         * @private
2260     * @type Y.Node
2261     */
2262         _parentTableNode: null,
2263         
2264         
2265         /**
2266     * @description The THEAD node which resides within the table node created in datatable-base
2267     *
2268     * @property _parentTheadNode
2269         * @private
2270     * @type Y.Node
2271     */
2272         _parentTheadNode: null,
2273         
2274         
2275         /**
2276     * @description The TBODY node which resides within the table node created in datatable-base
2277     *
2278     * @property _parentTbodyNode
2279         * @private
2280     * @type Y.Node
2281     */
2282         _parentTbodyNode: null,
2283         
2284         
2285         /**
2286     * @description The TBODY Message node which resides within the table node created in datatable-base
2287     *
2288     * @property _parentMsgNode
2289         * @private
2290     * @type Y.Node
2291     */
2292         _parentMsgNode: null,
2293         
2294         
2295         /**
2296     * @description The contentBox specified for the datatable in datatable-base
2297     *
2298     * @property _parentContainer
2299         * @private
2300     * @type Y.Node
2301     */
2302         _parentContainer: null,
2303         
2304         
2305         /**
2306     * @description The DIV node that contains all the scrollable elements (a table with a tbody on it)
2307     *
2308     * @property _bodyContainerNode
2309         * @private
2310     * @type Y.Node
2311     */
2312         _bodyContainerNode: null,
2313         
2314         
2315         /**
2316     * @description The DIV node that contains a table with a THEAD in it (which syncs its horizontal scroll with the _bodyContainerNode above)
2317     *
2318     * @property _headerContainerNode
2319         * @private
2320     * @type Y.Node
2321     */
2322         _headerContainerNode: null,
2323         
2324         
2325         //--------------------------------------
2326     //  Methods
2327     //--------------------------------------
2328
2329
2330         
2331         initializer: function(config) {
2332         var dt = this.get("host");
2333                 this._parentContainer = dt.get('contentBox');
2334                 this._parentContainer.addClass(CLASS_SCROLLABLE);
2335                 this._setUpNodes();
2336         },
2337         
2338         /////////////////////////////////////////////////////////////////////////////
2339         //
2340         // Set up Table Nodes
2341         //
2342         /////////////////////////////////////////////////////////////////////////////
2343         
2344         /**
2345     * @description Set up methods to fire after host methods execute
2346     *
2347     * @method _setUpNodes
2348     * @private
2349     */                  
2350         _setUpNodes: function() {
2351                 
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);
2359                 
2360                 if (this.get('_scroll') !== 'x') {
2361                         this.afterHostMethod('_attachTheadThNode', this._attachTheadThNode);
2362                         this.afterHostMethod('_attachTbodyTdNode', this._attachTbodyTdNode);
2363                 }
2364                 
2365         },
2366                 
2367         /**
2368     * @description Stores the main &lt;table&gt; node provided by the host as a private property
2369     *
2370     * @method _setUpParentTableNode
2371     * @private
2372     */
2373         _setUpParentTableNode: function() {
2374                 this._parentTableNode = this.get('host')._tableNode;
2375         },
2376         
2377         
2378         /**
2379     * @description Stores the main &lt;thead&gt; node provided by the host as a private property
2380     *
2381     * @method _setUpParentTheadNode
2382     * @private
2383     */
2384         _setUpParentTheadNode: function() {
2385                 this._parentTheadNode = this.get('host')._theadNode;
2386         },
2387         
2388         /**
2389     * @description Stores the main &lt;tbody&gt; node provided by the host as a private property
2390     *
2391     * @method _setUpParentTbodyNode
2392     * @private
2393     */
2394         _setUpParentTbodyNode: function() {
2395                 this._parentTbodyNode = this.get('host')._tbodyNode;
2396         },
2397         
2398         
2399         /**
2400     * @description Stores the main &lt;tbody&gt; message node provided by the host as a private property
2401     *
2402     * @method _setUpParentMessageNode
2403     * @private
2404     */
2405         _setUpParentMessageNode: function() {
2406                 this._parentMsgNode = this.get('host')._msgNode;
2407         },
2408         
2409         /////////////////////////////////////////////////////////////////////////////
2410         //
2411         // Renderer
2412         //
2413         /////////////////////////////////////////////////////////////////////////////
2414         
2415         /**
2416     * @description Primary rendering method that takes the datatable rendered in
2417     * the host, and splits it up into two separate &lt;divs&gt; 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.
2420         * 
2421     * @method renderUI
2422     * @public
2423     */
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"));
2431         },
2432         
2433         
2434         /**
2435     * @description Post rendering method that is responsible for creating a column
2436         * filler, and performing width and scroll synchronization between the &lt;th&gt; 
2437         * elements and the &lt;td&gt; elements.
2438         * This method fires after syncUI is called on datatable-base
2439         * 
2440     * @method syncUI
2441     * @public
2442     */
2443         syncUI: function() {
2444                 //Y.Profiler.start('sync');
2445                 this._removeCaptionNode();
2446                 this._syncWidths();
2447                 this._syncScroll();
2448                 //Y.Profiler.stop('sync');
2449                 //console.log(Y.Profiler.getReport("sync"));
2450                 
2451         },
2452         
2453         /**
2454     * @description Remove the caption created in base. Scrolling datatables dont support captions.
2455         * 
2456     * @method _removeCaptionNode
2457     * @private
2458     */
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*/};
2463     },
2464
2465         /**
2466     * @description Adjusts the width of the TH and the TDs to make sure that the two are in sync
2467         * 
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.
2472         *       
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.
2476         * 
2477     * @method _syncWidths
2478     * @private
2479     */
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
2483                         i,
2484                         len,
2485                         thWidth, tdWidth, thLiner, tdLiner,
2486                         ie = YUA.ie;
2487                         //stylesheet = new YStyleSheet('columnsSheet'),
2488                         //className;
2489                         
2490                         /*
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)
2494                         */
2495                         for (i=0, len = th.size(); i<len; i++) { 
2496                                 
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") {
2500                                         
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');
2504                                         
2505                                         /*
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.
2510                                                         
2511                                                         TODO: Explore if there is a better way using only LINERS widths
2512                                         */
2513                                         if (!ie) {
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');
2516                                         }
2517                                         
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.
2520                                         else {
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? */
2525                                         }
2526                                                                                 
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')});
2532                                         }
2533                                         
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')});
2539                                         }
2540                                         
2541                                 //}
2542
2543                         }
2544                         
2545                         //stylesheet.enable();
2546
2547         },
2548         
2549         /**
2550     * @description Adds the approriate width to the liner divs of the TH nodes before they are appended to DOM
2551         *
2552     * @method _attachTheadThNode
2553     * @private
2554     */
2555         _attachTheadThNode: function(o) {
2556                 var w = o.column.get('width') || 'auto';
2557                 
2558                 if (w !== 'auto') {
2559                         o.th.get('firstChild').setStyles({width: w, overflow:'hidden'}); //TODO: use liner API but liner is undefined here (not created?)
2560                 }
2561                 return o;
2562         },
2563         
2564         /**
2565     * @description Adds the appropriate width to the liner divs of the TD nodes before they are appended to DOM
2566         *
2567     * @method _attachTbodyTdNode
2568     * @private
2569     */
2570         _attachTbodyTdNode: function(o) {
2571                 var w = o.column.get('width') || 'auto';
2572                 
2573                 if (w !== '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'});
2576                 }
2577                 return o;
2578         },
2579         
2580         /**
2581     * @description Creates the body DIV that contains all the data. 
2582         *
2583     * @method _createBodyContainer
2584     * @private
2585     */
2586         _createBodyContainer: function() {
2587                 var     bd = YNode.create(CONTAINER_BODY),
2588                         onScrollFn = Y.bind("_onScroll", this);
2589                         
2590                 this._bodyContainerNode = bd;           
2591                 this._setStylesForTbody();
2592                 
2593                 bd.appendChild(this._parentTableNode);
2594                 this._parentContainer.appendChild(bd);
2595                 bd.on('scroll', onScrollFn);
2596         },
2597         
2598         /**
2599     * @description Creates the DIV that contains a &lt;table&gt; with all the headers. 
2600         *
2601     * @method _createHeaderContainer
2602     * @private
2603     */
2604         _createHeaderContainer: function() {
2605                 var hd = YNode.create(CONTAINER_HEADER),
2606                         tbl = YNode.create(TEMPLATE_TABLE);
2607                         
2608                 this._headerContainerNode = hd;
2609                 
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);
2615                 
2616         },
2617         
2618         /**
2619     * @description Creates styles for the TBODY based on what type of table it is.
2620         *
2621     * @method _setStylesForTbody
2622     * @private
2623     */
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};
2630                                 
2631                 if (dir === 'x') {
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';
2634                         styles.width = w;
2635                 }
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';
2639                 }
2640                 
2641                 else if (dir === 'xy') {
2642                         styles.width = w;
2643                 }
2644                 
2645                 else {
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';
2649                     styles.width = w;
2650                 }
2651                 
2652                 el.setStyles(styles);
2653                 return el;
2654         },
2655         
2656         
2657         /**
2658     * @description Creates styles for the THEAD based on what type of datatable it is.
2659         *
2660     * @method _setStylesForThead
2661     * @private
2662     */
2663         _setStylesForThead: function() {
2664                 var w = this.get('width') || "",
2665                         el = this._headerContainerNode;
2666                 
2667                 //if (dir !== 'y') {
2668                         el.setStyles({'width': w, 'overflow': 'hidden'});
2669                 // }
2670         },
2671         
2672         /**
2673     * @description Sets an auto width on the content box if it doesn't exist or if its a y-datatable.
2674         *
2675     * @method _setContentBoxDimensions
2676     * @private
2677     */
2678         _setContentBoxDimensions: function() {
2679                 
2680                 if (this.get('_scroll') === 'y' || (!this.get('width'))) {
2681                         this._parentContainer.setStyle('width', 'auto');
2682                 }
2683                 
2684         },
2685         
2686         /////////////////////////////////////////////////////////////////////////////
2687         //
2688         // Scroll Syncing
2689         //
2690         /////////////////////////////////////////////////////////////////////////////
2691         
2692         /**
2693     * @description Ensures that scrolling is synced across the two tables
2694         *
2695     * @method _onScroll
2696     * @private
2697     */
2698         _onScroll: function() {
2699                 this._headerContainerNode.set('scrollLeft', this._bodyContainerNode.get('scrollLeft'));
2700         },
2701         
2702         /**
2703          * @description Syncs padding around scrollable tables, including Column header right-padding
2704          * and container width and height.
2705          *
2706          * @method _syncScroll
2707          * @private 
2708          */
2709         _syncScroll : function() {
2710                 this._syncScrollX();
2711                 this._syncScrollY();
2712                 this._syncScrollOverhang();
2713                 if(YUA.opera) {
2714                         // Bug 1925874
2715                         this._headerContainerNode.set('scrollLeft', this._bodyContainerNode.get('scrollLeft'));
2716                         
2717                         if(!this.get("width")) {
2718                                 // Bug 1926125
2719                                 document.body.style += '';
2720                         }
2721                 }
2722         },
2723         
2724         /**
2725         * @description Snaps container width for y-scrolling tables.
2726         *
2727         * @method _syncScrollY
2728         * @private
2729         */
2730         _syncScrollY : function() {
2731                 var tBody = this._parentTbodyNode,
2732                     tBodyContainer = this._bodyContainerNode,
2733                         w;
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);
2743                 }
2744         },
2745                 
2746         /**
2747          * @description Snaps container height for x-scrolling tables in IE. Syncs message TBODY width. 
2748          * Taken from YUI2 ScrollingDataTable.js
2749          *
2750          * @method _syncScrollX
2751          * @private
2752          */
2753         _syncScrollX: function() {
2754                 var tBody = this._parentTbodyNode,
2755                         tBodyContainer = this._bodyContainerNode,
2756                         w;
2757                         this._headerContainerNode.set('scrollLeft', this._bodyContainerNode.get('scrollLeft'));
2758                         
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";
2763                                                 
2764                                                 tBodyContainer.setStyle('height', w);
2765                                         }
2766                         
2767                 if (tBody.get('rows').length === 0) {
2768                         this._parentMsgNode.get('parentNode').setStyle('width', this._parentTheadNode.get('parentNode').get('offsetWidth')+'px');
2769                 }
2770                 else {
2771                         this._parentMsgNode.get('parentNode').setStyle('width', "");
2772                 }
2773                         
2774         },
2775         
2776         /**
2777          * @description Adds/removes Column header overhang as necesary.
2778          * Taken from YUI2 ScrollingDataTable.js
2779          *
2780          * @method _syncScrollOverhang
2781          * @private
2782          */
2783         _syncScrollOverhang: function() {
2784                 var tBodyContainer = this._bodyContainerNode,
2785                         padding = 1;
2786                 
2787                 //when its both x and y scrolling
2788                 if ((tBodyContainer.get('scrollHeight') > tBodyContainer.get('clientHeight')) || (tBodyContainer.get('scrollWidth') > tBodyContainer.get('clientWidth'))) {
2789                         padding = 18;
2790                 }
2791                 
2792                 this._setOverhangValue(padding);
2793                 
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'))
2798                 {
2799                         this._headerContainerNode.setStyle('width', this._parentContainer.get('width'));
2800                 }
2801         },
2802         
2803         
2804         /**
2805          * @description Sets Column header overhang to given width.
2806          * Taken from YUI2 ScrollingDataTable.js with minor modifications
2807          *
2808          * @method _setOverhangValue
2809          * @param nBorderWidth {Number} Value of new border for overhang. 
2810          * @private
2811          */ 
2812         _setOverhangValue: function(borderWidth) {
2813                 var host = this.get('host'),
2814                         cols = host.get('columnset').get('definitions'),
2815                         //lastHeaders = cols[cols.length-1] || [],
2816                 len = cols.length,
2817                 value = borderWidth + "px solid " + this.get("COLOR_COLUMNFILLER"),
2818                         children = YNode.all('#'+this._parentContainer.get('id')+ ' .' + CLASS_HEADER + ' table thead th');
2819
2820                 children.item(len-1).setStyle('borderRight', value);
2821         }
2822         
2823 });
2824
2825 Y.namespace("Plugin").DataTableScroll = DataTableScroll;
2826
2827
2828
2829
2830
2831
2832 }, '3.3.0' ,{requires:['datatable-base','plugin','stylesheet']});
2833
2834
2835
2836 YUI.add('datatable', function(Y){}, '3.3.0' ,{use:['datatable-base','datatable-datasource','datatable-sort','datatable-scroll']});
2837