]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - jssource/src_files/include/javascript/yui3/build/datatable/datatable-base.js
Release 6.5.0
[Github/sugarcrm.git] / jssource / src_files / include / javascript / yui3 / build / datatable / datatable-base.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']});