MDL-32843 import YUI 3.5.1
[moodle.git] / lib / yui / 3.5.1 / build / datatable-base-deprecated / datatable-base-deprecated-debug.js
blob148913ed4ba441f00a40d06a5ca7f4799334dfa7
1 /*
2 YUI 3.5.1 (build 22)
3 Copyright 2012 Yahoo! Inc. All rights reserved.
4 Licensed under the BSD License.
5 http://yuilibrary.com/license/
6 */
7 YUI.add('datatable-base-deprecated', function(Y) {
9 var YLang = Y.Lang,
10     YisValue = YLang.isValue,
11     fromTemplate = Y.Lang.sub,
12     YNode = Y.Node,
13     Ycreate = YNode.create,
14     YgetClassName = Y.ClassNameManager.getClassName,
16     DATATABLE = "datatable",
17     COLUMN = "column",
18     
19     FOCUS = "focus",
20     KEYDOWN = "keydown",
21     MOUSEENTER = "mouseenter",
22     MOUSELEAVE = "mouseleave",
23     MOUSEUP = "mouseup",
24     MOUSEDOWN = "mousedown",
25     CLICK = "click",
26     DBLCLICK = "dblclick",
28     CLASS_COLUMNS = YgetClassName(DATATABLE, "columns"),
29     CLASS_DATA = YgetClassName(DATATABLE, "data"),
30     CLASS_MSG = YgetClassName(DATATABLE, "msg"),
31     CLASS_LINER = YgetClassName(DATATABLE, "liner"),
32     CLASS_FIRST = YgetClassName(DATATABLE, "first"),
33     CLASS_LAST = YgetClassName(DATATABLE, "last"),
34     CLASS_EVEN = YgetClassName(DATATABLE, "even"),
35     CLASS_ODD = YgetClassName(DATATABLE, "odd"),
37     TEMPLATE_TABLE = '<table></table>',
38     TEMPLATE_COL = '<col></col>',
39     TEMPLATE_THEAD = '<thead class="'+CLASS_COLUMNS+'"></thead>',
40     TEMPLATE_TBODY = '<tbody class="'+CLASS_DATA+'"></tbody>',
41     TEMPLATE_TH = '<th id="{id}" rowspan="{rowspan}" colspan="{colspan}" class="{classnames}" abbr="{abbr}"><div class="'+CLASS_LINER+'">{value}</div></th>',
42     TEMPLATE_TR = '<tr id="{id}"></tr>',
43     TEMPLATE_TD = '<td headers="{headers}" class="{classnames}"><div class="'+CLASS_LINER+'">{value}</div></td>',
44     TEMPLATE_VALUE = '{value}',
45     TEMPLATE_MSG = '<tbody class="'+CLASS_MSG+'"></tbody>';
46     
49 /**
50  * The Column class defines and manages attributes of Columns for DataTable.
51  *
52  * @class Column
53  * @extends Widget
54  * @constructor
55  */
56 function Column(config) {
57     Column.superclass.constructor.apply(this, arguments);
60 /////////////////////////////////////////////////////////////////////////////
62 // STATIC PROPERTIES
64 /////////////////////////////////////////////////////////////////////////////
65 Y.mix(Column, {
66     /**
67      * Class name.
68      *
69      * @property NAME
70      * @type {String}
71      * @static
72      * @final
73      * @value "column"
74      */
75     NAME: "column",
77 /////////////////////////////////////////////////////////////////////////////
79 // ATTRIBUTES
81 /////////////////////////////////////////////////////////////////////////////
82     ATTRS: {
83         /**
84         Unique internal identifier, used to stamp ID on TH element.
85         
86         @attribute id
87         @type {String}
88         @readOnly
89         **/
90         id: {
91             valueFn: "_defaultId",
92             readOnly: true
93         },
94         
95         /**
96         User-supplied identifier. Defaults to id.
97         @attribute key
98         @type {String}
99         **/
100         key: {
101             valueFn: "_defaultKey"
102         },
104         /**
105         Points to underlying data field (for sorting or formatting, for
106         example). Useful when column doesn't hold any data itself, but is just
107         a visual representation of data from another column or record field.
108         Defaults to key.
110         @attribute field
111         @type {String}
112         @default (column key)
113         **/
114         field: {
115             valueFn: "_defaultField"
116         },
118         /**
119         Display label for column header. Defaults to key.
121         @attribute label
122         @type {String}
123         **/
124         label: {
125             valueFn: "_defaultLabel"
126         },
127         
128         /**
129         Array of child column definitions (for nested headers).
131         @attribute children
132         @type {String}
133         @default null
134         **/
135         children: {
136             value: null
137         },
138         
139         /**
140         TH abbr attribute.
142         @attribute abbr
143         @type {String}
144         @default ""
145         **/
146         abbr: {
147             value: ""
148         },
150         //TODO: support custom classnames
151         // TH CSS classnames
152         classnames: {
153             readOnly: true,
154             getter: "_getClassnames"
155         },
156         
157         /**
158         Formating template string or function for cells in this column.
160         Function formatters receive a single object (described below) and are
161         expected to output the `innerHTML` of the cell.
163         String templates can include markup and {placeholder} tokens to be
164         filled in from the object passed to function formatters.
166         @attribute formatter
167         @type {String|Function}
168         @param {Object} data Data relevant to the rendering of this cell
169             @param {String} data.classnames CSS classes to add to the cell
170             @param {Column} data.column This Column instance
171             @param {Object} data.data The raw object data from the Record
172             @param {String} data.field This Column's "field" attribute value
173             @param {String} data.headers TH ids to reference in the cell's
174                             "headers" attribute
175             @param {Record} data.record The Record instance for this row
176             @param {Number} data.rowindex The index for this row
177             @param {Node}   data.tbody The TBODY Node that will house the cell
178             @param {Node}   data.tr The row TR Node that will house the cell
179             @param {Any}    data.value The raw Record data for this cell
180         **/
181         formatter: {},
183         /**
184         The default markup to display in cells that have no corresponding record
185         data or content from formatters.
187         @attribute emptyCellValue
188         @type {String}
189         @default ''
190         **/
191         emptyCellValue: {
192             value: '',
193             validator: Y.Lang.isString
194         },
196         //requires datatable-sort
197         sortable: {
198             value: false
199         },
200         //sortOptions:defaultDir, sortFn, field
202         //TODO: support editable columns
203         // Column editor
204         editor: {},
206         //TODO: support resizeable columns
207         //TODO: support setting widths
208         // requires datatable-colresize
209         width: {},
210         resizeable: {},
211         minimized: {},
212         minWidth: {},
213         maxAutoWidth: {}
214     }
217 /////////////////////////////////////////////////////////////////////////////
219 // PROTOTYPE
221 /////////////////////////////////////////////////////////////////////////////
222 Y.extend(Column, Y.Widget, {
223     /////////////////////////////////////////////////////////////////////////////
224     //
225     // ATTRIBUTE HELPERS
226     //
227     /////////////////////////////////////////////////////////////////////////////
228     /**
229     * Return ID for instance.
230     *
231     * @method _defaultId
232     * @return {String}
233     * @private
234     */
235     _defaultId: function() {
236         return Y.guid();
237     },
239     /**
240     * Return key for instance. Defaults to ID if one was not provided.
241     *
242     * @method _defaultKey
243     * @return {String}
244     * @private
245     */
246     _defaultKey: function() {
247         return Y.guid();
248     },
250     /**
251     * Return field for instance. Defaults to key if one was not provided.
252     *
253     * @method _defaultField
254     * @return {String}
255     * @private
256     */
257     _defaultField: function() {
258         return this.get("key");
259     },
261     /**
262     * Return label for instance. Defaults to key if one was not provided.
263     *
264     * @method _defaultLabel
265     * @return {String}
266     * @private
267     */
268     _defaultLabel: function() {
269         return this.get("key");
270     },
272     /**
273      * Updates the UI if changes are made to abbr.
274      *
275      * @method _afterAbbrChange
276      * @param e {Event} Custom event for the attribute change.
277      * @private
278      */
279     _afterAbbrChange: function (e) {
280         this._uiSetAbbr(e.newVal);
281     },
283     /////////////////////////////////////////////////////////////////////////////
284     //
285     // PROPERTIES
286     //
287     /////////////////////////////////////////////////////////////////////////////
288     /**
289      * Reference to Column's current position index within its Columnset's keys
290      * array, if applicable. This property only applies to non-nested and bottom-
291      * level child Columns. Value is set by Columnset code.
292      *
293      * @property keyIndex
294      * @type {Number}
295      */
296     keyIndex: null,
297     
298     /**
299     * Array of TH IDs associated with this column, for TD "headers" attribute.
300     * Value is set by Columnset code
301     *
302     * @property headers
303     * @type {String[]}
304     */
305     headers: null,
307     /**
308      * Number of cells the header spans. Value is set by Columnset code.
309      *
310      * @property colSpan
311      * @type {Number}
312      * @default 1
313      */
314     colSpan: 1,
315     
316     /**
317      * Number of rows the header spans. Value is set by Columnset code.
318      *
319      * @property rowSpan
320      * @type {Number}
321      * @default 1
322      */
323     rowSpan: 1,
325     /**
326      * Column's parent Column instance, if applicable. Value is set by Columnset
327      * code.
328      *
329      * @property parent
330      * @type {Column}
331      */
332     parent: null,
334     /**
335      * The Node reference to the associated TH element.
336      *
337      * @property thNode
338      * @type {Node}
339      */
340      
341     thNode: null,
343     /*TODO
344      * The Node reference to the associated liner element.
345      *
346      * @property thLinerNode
347      * @type {Node}
348      
349     thLinerNode: null,*/
350     
351     /////////////////////////////////////////////////////////////////////////////
352     //
353     // METHODS
354     //
355     /////////////////////////////////////////////////////////////////////////////
356     /**
357     * Initializer.
358     *
359     * @method initializer
360     * @param config {Object} Config object.
361     * @private
362     */
363     initializer: function(config) {
364     },
366     /**
367     * Destructor.
368     *
369     * @method destructor
370     * @private
371     */
372     destructor: function() {
373     },
375     /**
376      * Returns classnames for Column.
377      *
378      * @method _getClassnames
379      * @private
380      */
381     _getClassnames: function () {
382         return Y.ClassNameManager.getClassName(COLUMN, this.get("key").replace(/[^\w\-]/g,""));
383     },
385     ////////////////////////////////////////////////////////////////////////////
386     //
387     // SYNC
388     //
389     ////////////////////////////////////////////////////////////////////////////
390     /**
391     * Syncs UI to intial state.
392     *
393     * @method syncUI
394     * @private
395     */
396     syncUI: function() {
397         this._uiSetAbbr(this.get("abbr"));
398     },
400     /**
401      * Updates abbr.
402      *
403      * @method _uiSetAbbr
404      * @param val {String} New abbr.
405      * @protected
406      */
407     _uiSetAbbr: function(val) {
408         this.thNode.set("abbr", val);
409     }
412 Y.Column = Column;
414  * The Columnset class defines and manages a collection of Columns.
416  * @class Columnset
417  * @extends Base
418  * @constructor
419  */
420 function Columnset(config) {
421     Columnset.superclass.constructor.apply(this, arguments);
424 /////////////////////////////////////////////////////////////////////////////
426 // STATIC PROPERTIES
428 /////////////////////////////////////////////////////////////////////////////
429 Y.mix(Columnset, {
430     /**
431      * Class name.
432      *
433      * @property NAME
434      * @type String
435      * @static
436      * @final
437      * @value "columnset"
438      */
439     NAME: "columnset",
441     /////////////////////////////////////////////////////////////////////////////
442     //
443     // ATTRIBUTES
444     //
445     /////////////////////////////////////////////////////////////////////////////
446     ATTRS: {
447         /**
448         * @attribute definitions
449         * @description Array of column definitions that will populate this Columnset.
450         * @type Array
451         */
452         definitions: {
453             setter: "_setDefinitions"
454         }
456     }
459 /////////////////////////////////////////////////////////////////////////////
461 // PROTOTYPE
463 /////////////////////////////////////////////////////////////////////////////
464 Y.extend(Columnset, Y.Base, {
465     /////////////////////////////////////////////////////////////////////////////
466     //
467     // ATTRIBUTE HELPERS
468     //
469     /////////////////////////////////////////////////////////////////////////////
470     /**
471     * @method _setDefinitions
472     * @description Clones definitions before setting.
473     * @param definitions {Array} Array of column definitions.
474     * @return Array
475     * @private
476     */
477     _setDefinitions: function(definitions) {
478             return Y.clone(definitions);
479     },
480     
481     /////////////////////////////////////////////////////////////////////////////
482     //
483     // PROPERTIES
484     //
485     /////////////////////////////////////////////////////////////////////////////
486     /**
487      * Top-down tree representation of Column hierarchy. Used to create DOM
488      * elements.
489      *
490      * @property tree
491      * @type {Column[]}
492      */
493     tree: null,
495     /**
496      * Hash of all Columns by ID.
497      *
498      * @property idHash
499      * @type Object
500      */
501     idHash: null,
503     /**
504      * Hash of all Columns by key.
505      *
506      * @property keyHash
507      * @type Object
508      */
509     keyHash: null,
511     /**
512      * Array of only Columns that are meant to be displayed in DOM.
513      *
514      * @property keys
515      * @type {Column[]}
516      */
517     keys: null,
519     /////////////////////////////////////////////////////////////////////////////
520     //
521     // METHODS
522     //
523     /////////////////////////////////////////////////////////////////////////////
524     /**
525     * Initializer. Generates all internal representations of the collection of
526     * Columns.
527     *
528     * @method initializer
529     * @param config {Object} Config object.
530     * @private
531     */
532     initializer: function() {
534         // DOM tree representation of all Columns
535         var tree = [],
536         // Hash of all Columns by ID
537         idHash = {},
538         // Hash of all Columns by key
539         keyHash = {},
540         // Flat representation of only Columns that are meant to display data
541         keys = [],
542         // Original definitions
543         definitions = this.get("definitions"),
545         self = this;
547         // Internal recursive function to define Column instances
548         function parseColumns(depth, currentDefinitions, parent) {
549             var i=0,
550                 len = currentDefinitions.length,
551                 currentDefinition,
552                 column,
553                 currentChildren;
555             // One level down
556             depth++;
558             // Create corresponding dom node if not already there for this depth
559             if(!tree[depth]) {
560                 tree[depth] = [];
561             }
563             // Parse each node at this depth for attributes and any children
564             for(; i<len; ++i) {
565                 currentDefinition = currentDefinitions[i];
567                 currentDefinition = YLang.isString(currentDefinition) ? {key:currentDefinition} : currentDefinition;
569                 // Instantiate a new Column for each node
570                 column = new Y.Column(currentDefinition);
572                 // Cross-reference Column ID back to the original object literal definition
573                 currentDefinition.yuiColumnId = column.get("id");
575                 // Add the new Column to the hash
576                 idHash[column.get("id")] = column;
577                 keyHash[column.get("key")] = column;
579                 // Assign its parent as an attribute, if applicable
580                 if(parent) {
581                     column.parent = parent;
582                 }
584                 // The Column has descendants
585                 if(YLang.isArray(currentDefinition.children)) {
586                     currentChildren = currentDefinition.children;
587                     column._set("children", currentChildren);
589                     self._setColSpans(column, currentDefinition);
591                     self._cascadePropertiesToChildren(column, currentChildren);
593                     // The children themselves must also be parsed for Column instances
594                     if(!tree[depth+1]) {
595                         tree[depth+1] = [];
596                     }
597                     parseColumns(depth, currentChildren, column);
598                 }
599                 // This Column does not have any children
600                 else {
601                     column.keyIndex = keys.length;
602                     // Default is already 1
603                     //column.colSpan = 1;
604                     keys.push(column);
605                 }
607                 // Add the Column to the top-down dom tree
608                 tree[depth].push(column);
609             }
610             depth--;
611         }
613         // Parse out Column instances from the array of object literals
614         parseColumns(-1, definitions);
617         // Save to the Columnset instance
618         this.tree = tree;
619         this.idHash = idHash;
620         this.keyHash = keyHash;
621         this.keys = keys;
623         this._setRowSpans();
624         this._setHeaders();
625     },
627     /**
628     * Destructor.
629     *
630     * @method destructor
631     * @private
632     */
633     destructor: function() {
634     },
636     /////////////////////////////////////////////////////////////////////////////
637     //
638     // COLUMN HELPERS
639     //
640     /////////////////////////////////////////////////////////////////////////////
641     /**
642     * Cascade certain properties to children if not defined on their own.
643     *
644     * @method _cascadePropertiesToChildren
645     * @private
646     */
647     _cascadePropertiesToChildren: function(column, currentChildren) {
648         //TODO: this is all a giant todo
649         var i = 0,
650             len = currentChildren.length,
651             child;
653         // Cascade certain properties to children if not defined on their own
654         for(; i<len; ++i) {
655             child = currentChildren[i];
656             if(column.get("className") && (child.className === undefined)) {
657                 child.className = column.get("className");
658             }
659             if(column.get("editor") && (child.editor === undefined)) {
660                 child.editor = column.get("editor");
661             }
662             if(column.get("formatter") && (child.formatter === undefined)) {
663                 child.formatter = column.get("formatter");
664             }
665             if(column.get("resizeable") && (child.resizeable === undefined)) {
666                 child.resizeable = column.get("resizeable");
667             }
668             if(column.get("sortable") && (child.sortable === undefined)) {
669                 child.sortable = column.get("sortable");
670             }
671             if(column.get("hidden")) {
672                 child.hidden = true;
673             }
674             if(column.get("width") && (child.width === undefined)) {
675                 child.width = column.get("width");
676             }
677             if(column.get("minWidth") && (child.minWidth === undefined)) {
678                 child.minWidth = column.get("minWidth");
679             }
680             if(column.get("maxAutoWidth") && (child.maxAutoWidth === undefined)) {
681                 child.maxAutoWidth = column.get("maxAutoWidth");
682             }
683         }
684     },
686     /**
687     * @method _setColSpans
688     * @description Calculates and sets colSpan attribute on given Column.
689     * @param column {Array} Column instance.
690     * @param definition {Object} Column definition.
691     * @private
692     */
693     _setColSpans: function(column, definition) {
694         // Determine COLSPAN value for this Column
695         var terminalChildNodes = 0;
697         function countTerminalChildNodes(ancestor) {
698             var descendants = ancestor.children,
699                 i = 0,
700                 len = descendants.length;
702             // Drill down each branch and count terminal nodes
703             for(; i<len; ++i) {
704                 // Keep drilling down
705                 if(YLang.isArray(descendants[i].children)) {
706                     countTerminalChildNodes(descendants[i]);
707                 }
708                 // Reached branch terminus
709                 else {
710                     terminalChildNodes++;
711                 }
712             }
713         }
714         countTerminalChildNodes(definition);
715         column.colSpan = terminalChildNodes;
716     },
718     /**
719     * @method _setRowSpans
720     * @description Calculates and sets rowSpan attribute on all Columns.
721     * @private
722     */
723     _setRowSpans: function() {
724         // Determine ROWSPAN value for each Column in the DOM tree
725         function parseDomTreeForRowSpan(tree) {
726             var maxRowDepth = 1,
727                 currentRow,
728                 currentColumn,
729                 m,p;
731             // Calculate the max depth of descendants for this row
732             function countMaxRowDepth(row, tmpRowDepth) {
733                 tmpRowDepth = tmpRowDepth || 1;
735                 var i = 0,
736                     len = row.length,
737                     col;
739                 for(; i<len; ++i) {
740                     col = row[i];
741                     // Column has children, so keep counting
742                     if(YLang.isArray(col.children)) {
743                         tmpRowDepth++;
744                         countMaxRowDepth(col.children, tmpRowDepth);
745                         tmpRowDepth--;
746                     }
747                     // Column has children, so keep counting
748                     else if(col.get && YLang.isArray(col.get("children"))) {
749                         tmpRowDepth++;
750                         countMaxRowDepth(col.get("children"), tmpRowDepth);
751                         tmpRowDepth--;
752                     }
753                     // No children, is it the max depth?
754                     else {
755                         if(tmpRowDepth > maxRowDepth) {
756                             maxRowDepth = tmpRowDepth;
757                         }
758                     }
759                 }
760             }
762             // Count max row depth for each row
763             for(m=0; m<tree.length; m++) {
764                 currentRow = tree[m];
765                 countMaxRowDepth(currentRow);
767                 // Assign the right ROWSPAN values to each Column in the row
768                 for(p=0; p<currentRow.length; p++) {
769                     currentColumn = currentRow[p];
770                     if(!YLang.isArray(currentColumn.get("children"))) {
771                         currentColumn.rowSpan = maxRowDepth;
772                     }
773                     // Default is already 1
774                     // else currentColumn.rowSpan =1;
775                 }
777                 // Reset counter for next row
778                 maxRowDepth = 1;
779             }
780         }
781         parseDomTreeForRowSpan(this.tree);
782     },
784     /**
785     * @method _setHeaders
786     * @description Calculates and sets headers attribute on all Columns.
787     * @private
788     */
789     _setHeaders: function() {
790         var headers, column,
791             allKeys = this.keys,
792             i=0, len = allKeys.length;
794         function recurseAncestorsForHeaders(headers, column) {
795             headers.push(column.get("id"));
796             if(column.parent) {
797                 recurseAncestorsForHeaders(headers, column.parent);
798             }
799         }
800         for(; i<len; ++i) {
801             headers = [];
802             column = allKeys[i];
803             recurseAncestorsForHeaders(headers, column);
804             column.headers = headers.reverse().join(" ");
805         }
806     },
808     //TODO
809     getColumn: function() {
810     }
813 Y.Columnset = Columnset;
815  * The DataTable widget provides a progressively enhanced DHTML control for
816  * displaying tabular data across A-grade browsers.
818  * @module datatable
819  * @main datatable
820  */
823  * Provides the base DataTable implementation, which can be extended to add
824  * additional functionality, such as sorting or scrolling.
826  * @module datatable
827  * @submodule datatable-base
828  */
831  * Base class for the DataTable widget.
832  * @class DataTable.Base
833  * @extends Widget
834  * @constructor
835  */
836 function DTBase(config) {
837     DTBase.superclass.constructor.apply(this, arguments);
840 /////////////////////////////////////////////////////////////////////////////
842 // STATIC PROPERTIES
844 /////////////////////////////////////////////////////////////////////////////
845 Y.mix(DTBase, {
847     /**
848      * Class name.
849      *
850      * @property NAME
851      * @type String
852      * @static
853      * @final
854      * @value "dataTable"
855      */
856     NAME:  "dataTable",
858 /////////////////////////////////////////////////////////////////////////////
860 // ATTRIBUTES
862 /////////////////////////////////////////////////////////////////////////////
863     ATTRS: {
864         /**
865         * @attribute columnset
866         * @description Pointer to Columnset instance.
867         * @type Array | Y.Columnset
868         */
869         columnset: {
870             setter: "_setColumnset"
871         },
873         /**
874         * @attribute recordset
875         * @description Pointer to Recordset instance.
876         * @type Array | Y.Recordset
877         */
878         recordset: {
879             valueFn: '_initRecordset',
880             setter: "_setRecordset"
881         },
883         /*TODO
884         * @attribute state
885         * @description Internal state.
886         * @readonly
887         * @type
888         */
889         /*state: {
890             value: new Y.State(),
891             readOnly: true
893         },*/
895         /**
896         * @attribute summary
897         * @description Summary.
898         * @type String
899         */
900         summary: {
901         },
903         /**
904         * @attribute caption
905         * @description Caption
906         * @type String
907         */
908         caption: {
909         },
911         /**
912         * @attribute thValueTemplate
913         * @description Tokenized markup template for TH value.
914         * @type String
915         * @default '{value}'
916         */
917         thValueTemplate: {
918             value: TEMPLATE_VALUE
919         },
921         /**
922         * @attribute tdValueTemplate
923         * @description Tokenized markup template for TD value.
924         * @type String
925         * @default '{value}'
926         */
927         tdValueTemplate: {
928             value: TEMPLATE_VALUE
929         },
931         /**
932         * @attribute trTemplate
933         * @description Tokenized markup template for TR node creation.
934         * @type String
935         * @default '<tr id="{id}"></tr>'
936         */
937         trTemplate: {
938             value: TEMPLATE_TR
939         }
940     },
942 /////////////////////////////////////////////////////////////////////////////
944 // TODO: HTML_PARSER
946 /////////////////////////////////////////////////////////////////////////////
947     HTML_PARSER: {
948         /*caption: function (srcNode) {
949             
950         }*/
951     }
954 /////////////////////////////////////////////////////////////////////////////
956 // PROTOTYPE
958 /////////////////////////////////////////////////////////////////////////////
959 Y.extend(DTBase, Y.Widget, {
960     /**
961     * @property thTemplate
962     * @description Tokenized markup template for TH node creation.
963     * @type String
964     * @default '<th id="{id}" rowspan="{rowspan}" colspan="{colspan}" class="{classnames}" abbr="{abbr}"><div class="'+CLASS_LINER+'">{value}</div></th>'
965     */
966     thTemplate: TEMPLATE_TH,
968     /**
969     * @property tdTemplate
970     * @description Tokenized markup template for TD node creation.
971     * @type String
972     * @default '<td headers="{headers}" class="{classnames}"><div class="yui3-datatable-liner">{value}</div></td>'
973     */
974     tdTemplate: TEMPLATE_TD,
975     
976     /**
977     * @property _theadNode
978     * @description Pointer to THEAD node.
979     * @type {Node}
980     * @private
981     */
982     _theadNode: null,
983     
984     /**
985     * @property _tbodyNode
986     * @description Pointer to TBODY node.
987     * @type {Node}
988     * @private
989     */
990     _tbodyNode: null,
991     
992     /**
993     * @property _msgNode
994     * @description Pointer to message display node.
995     * @type {Node}
996     * @private
997     */
998     _msgNode: null,
1000     /////////////////////////////////////////////////////////////////////////////
1001     //
1002     // ATTRIBUTE HELPERS
1003     //
1004     /////////////////////////////////////////////////////////////////////////////
1005     /**
1006     * @method _setColumnset
1007     * @description Converts Array to Y.Columnset.
1008     * @param columns {Array | Y.Columnset}
1009     * @return {Columnset}
1010     * @private
1011     */
1012     _setColumnset: function(columns) {
1013         return YLang.isArray(columns) ? new Y.Columnset({definitions:columns}) : columns;
1014     },
1016     /**
1017      * Updates the UI if Columnset is changed.
1018      *
1019      * @method _afterColumnsetChange
1020      * @param e {Event} Custom event for the attribute change.
1021      * @protected
1022      */
1023     _afterColumnsetChange: function (e) {
1024         this._uiSetColumnset(e.newVal);
1025     },
1027     /**
1028     * @method _setRecordset
1029     * @description Converts Array to Y.Recordset.
1030     * @param records {Array | Recordset}
1031     * @return {Recordset}
1032     * @private
1033     */
1034     _setRecordset: function(rs) {
1035         if(YLang.isArray(rs)) {
1036             rs = new Y.Recordset({records:rs});
1037         }
1039         rs.addTarget(this);
1040         return rs;
1041     },
1042     
1043     /**
1044     * Updates the UI if Recordset is changed.
1045     *
1046     * @method _afterRecordsetChange
1047     * @param e {Event} Custom event for the attribute change.
1048     * @protected
1049     */
1050     _afterRecordsetChange: function (e) {
1051         this._uiSetRecordset(e.newVal);
1052     },
1054     /**
1055     * Updates the UI if Recordset records are changed.
1056     *
1057     * @method _afterRecordsChange
1058     * @param e {Event} Custom event for the attribute change.
1059     * @protected
1060     */
1061     _afterRecordsChange: function (e) {
1062         this._uiSetRecordset(this.get('recordset'));
1063     },
1065     /**
1066      * Updates the UI if summary is changed.
1067      *
1068      * @method _afterSummaryChange
1069      * @param e {Event} Custom event for the attribute change.
1070      * @protected
1071      */
1072     _afterSummaryChange: function (e) {
1073         this._uiSetSummary(e.newVal);
1074     },
1076     /**
1077      * Updates the UI if caption is changed.
1078      *
1079      * @method _afterCaptionChange
1080      * @param e {Event} Custom event for the attribute change.
1081      * @protected
1082      */
1083     _afterCaptionChange: function (e) {
1084         this._uiSetCaption(e.newVal);
1085     },
1087     ////////////////////////////////////////////////////////////////////////////
1088     //
1089     // METHODS
1090     //
1091     ////////////////////////////////////////////////////////////////////////////
1093     /**
1094     * Destructor.
1095     *
1096     * @method destructor
1097     * @private
1098     */
1099     destructor: function() {
1100          this.get("recordset").removeTarget(this);
1101     },
1102     
1103     ////////////////////////////////////////////////////////////////////////////
1104     //
1105     // RENDER
1106     //
1107     ////////////////////////////////////////////////////////////////////////////
1109     /**
1110     * Renders UI.
1111     *
1112     * @method renderUI
1113     * @private
1114     */
1115     renderUI: function() {
1116         // TABLE
1117         this._addTableNode(this.get("contentBox"));
1119         // COLGROUP
1120         this._addColgroupNode(this._tableNode);
1122         // THEAD
1123         this._addTheadNode(this._tableNode);
1125         // Primary TBODY
1126         this._addTbodyNode(this._tableNode);
1128         // Message TBODY
1129         this._addMessageNode(this._tableNode);
1131         // CAPTION
1132         this._addCaptionNode(this._tableNode);
1133    },
1135     /**
1136     * Creates and attaches TABLE element to given container.
1137     *
1138     * @method _addTableNode
1139     * @param containerNode {Node} Parent node.
1140     * @protected
1141     * @return {Node}
1142     */
1143     _addTableNode: function(containerNode) {
1144         if (!this._tableNode) {
1145             this._tableNode = containerNode.appendChild(Ycreate(TEMPLATE_TABLE));
1146         }
1147         return this._tableNode;
1148     },
1150     /**
1151     * Creates and attaches COLGROUP element to given TABLE.
1152     *
1153     * @method _addColgroupNode
1154     * @param tableNode {Node} Parent node.
1155     * @protected
1156     * @return {Node}
1157     */
1158     _addColgroupNode: function(tableNode) {
1159         // Add COLs to DOCUMENT FRAGMENT
1160         var len = this.get("columnset").keys.length,
1161             i = 0,
1162             allCols = ["<colgroup>"];
1164         for(; i<len; ++i) {
1165             allCols.push(TEMPLATE_COL);
1166         }
1168         allCols.push("</colgroup>");
1170         // Create COLGROUP
1171         this._colgroupNode = tableNode.insertBefore(Ycreate(allCols.join("")), tableNode.get("firstChild"));
1173         return this._colgroupNode;
1174     },
1176     /**
1177     * Creates and attaches THEAD element to given container.
1178     *
1179     * @method _addTheadNode
1180     * @param tableNode {Node} Parent node.
1181     * @protected
1182     * @return {Node}
1183     */
1184     _addTheadNode: function(tableNode) {
1185         if(tableNode) {
1186             this._theadNode = tableNode.insertBefore(Ycreate(TEMPLATE_THEAD), this._colgroupNode.next());
1187             return this._theadNode;
1188         }
1189     },
1191     /**
1192     * Creates and attaches TBODY element to given container.
1193     *
1194     * @method _addTbodyNode
1195     * @param tableNode {Node} Parent node.
1196     * @protected
1197     * @return {Node}
1198     */
1199     _addTbodyNode: function(tableNode) {
1200         this._tbodyNode = tableNode.appendChild(Ycreate(TEMPLATE_TBODY));
1201         return this._tbodyNode;
1202     },
1204     /**
1205     * Creates and attaches message display element to given container.
1206     *
1207     * @method _addMessageNode
1208     * @param tableNode {Node} Parent node.
1209     * @protected
1210     * @return {Node}
1211     */
1212     _addMessageNode: function(tableNode) {
1213         this._msgNode = tableNode.insertBefore(Ycreate(TEMPLATE_MSG), this._tbodyNode);
1214         return this._msgNode;
1215     },
1217     /**
1218     * Creates and attaches CAPTION element to given container.
1219     *
1220     * @method _addCaptionNode
1221     * @param tableNode {Node} Parent node.
1222     * @protected
1223     * @return {Node}
1224     */
1225     _addCaptionNode: function(tableNode) {
1226         this._captionNode = Y.Node.create('<caption></caption>');
1227     },
1229     ////////////////////////////////////////////////////////////////////////////
1230     //
1231     // BIND
1232     //
1233     ////////////////////////////////////////////////////////////////////////////
1235     /**
1236     * Binds events.
1237     *
1238     * @method bindUI
1239     * @private
1240     */
1241     bindUI: function() {
1242         this.after({
1243             columnsetChange: this._afterColumnsetChange,
1244             summaryChange  : this._afterSummaryChange,
1245             captionChange  : this._afterCaptionChange,
1246             recordsetChange: this._afterRecordsChange,
1247             "recordset:tableChange": this._afterRecordsChange
1248         });
1249     },
1250     
1251     delegate: function(type) {
1252         //TODO: is this necessary?
1253         if(type==="dblclick") {
1254             this.get("boundingBox").delegate.apply(this.get("boundingBox"), arguments);
1255         }
1256         else {
1257             this.get("contentBox").delegate.apply(this.get("contentBox"), arguments);
1258         }
1259     },
1260     
1262     ////////////////////////////////////////////////////////////////////////////
1263     //
1264     // SYNC
1265     //
1266     ////////////////////////////////////////////////////////////////////////////
1268     /**
1269     * Syncs UI to intial state.
1270     *
1271     * @method syncUI
1272     * @private
1273     */
1274     syncUI: function() {
1275         // THEAD ROWS
1276         this._uiSetColumnset(this.get("columnset"));
1277         // DATA ROWS
1278         this._uiSetRecordset(this.get("recordset"));
1279         // SUMMARY
1280         this._uiSetSummary(this.get("summary"));
1281         // CAPTION
1282         this._uiSetCaption(this.get("caption"));
1283     },
1285     /**
1286      * Updates summary.
1287      *
1288      * @method _uiSetSummary
1289      * @param val {String} New summary.
1290      * @protected
1291      */
1292     _uiSetSummary: function(val) {
1293         val = YisValue(val) ? val : "";
1294         this._tableNode.set("summary", val);
1295     },
1297     /**
1298      * Updates caption.
1299      *
1300      * @method _uiSetCaption
1301      * @param val {String} New caption.
1302      * @protected
1303      */
1304     _uiSetCaption: function(val) {
1305         var caption = this._captionNode,
1306             inDoc   = caption.inDoc(),
1307             method  = val ? (!inDoc && 'prepend') : (inDoc && 'removeChild');
1309         caption.setContent(val || '');
1311         if (method) {
1312             // prepend of remove necessary
1313             this._tableNode[method](caption);
1314         }
1315     },
1318     ////////////////////////////////////////////////////////////////////////////
1319     //
1320     // THEAD/COLUMNSET FUNCTIONALITY
1321     //
1322     ////////////////////////////////////////////////////////////////////////////
1323     /**
1324      * Updates THEAD.
1325      *
1326      * @method _uiSetColumnset
1327      * @param cs {Columnset} New Columnset.
1328      * @protected
1329      */
1330     _uiSetColumnset: function(cs) {
1331         var tree = cs.tree,
1332             thead = this._theadNode,
1333             i = 0,
1334             len = tree.length,
1335             parent = thead.get("parentNode"),
1336             nextSibling = thead.next();
1337             
1338         // Move THEAD off DOM
1339         thead.remove();
1340         
1341         thead.get("children").remove(true);
1343         // Iterate tree of columns to add THEAD rows
1344         for(; i<len; ++i) {
1345             this._addTheadTrNode({
1346                 thead:   thead,
1347                 columns: tree[i],
1348                 id     : '' // to avoid {id} leftovers from the trTemplate
1349             }, (i === 0), (i === len - 1));
1350         }
1352         // Column helpers needs _theadNode to exist
1353         //this._createColumnHelpers();
1355         
1356         // Re-attach THEAD to DOM
1357         parent.insert(thead, nextSibling);
1359      },
1360      
1361     /**
1362     * Creates and attaches header row element.
1363     *
1364     * @method _addTheadTrNode
1365     * @param o {Object} {thead, columns}.
1366     * @param isFirst {Boolean} Is first row.
1367     * @param isFirst {Boolean} Is last row.
1368     * @protected
1369     */
1370      _addTheadTrNode: function(o, isFirst, isLast) {
1371         o.tr = this._createTheadTrNode(o, isFirst, isLast);
1372         this._attachTheadTrNode(o);
1373      },
1374      
1376     /**
1377     * Creates header row element.
1378     *
1379     * @method _createTheadTrNode
1380     * @param o {Object} {thead, columns}.
1381     * @param isFirst {Boolean} Is first row.
1382     * @param isLast {Boolean} Is last row.
1383     * @protected
1384     * @return {Node}
1385     */
1386     _createTheadTrNode: function(o, isFirst, isLast) {
1387         //TODO: custom classnames
1388         var tr = Ycreate(fromTemplate(this.get("trTemplate"), o)),
1389             i = 0,
1390             columns = o.columns,
1391             len = columns.length,
1392             column;
1394          // Set FIRST/LAST class
1395         if(isFirst) {
1396             tr.addClass(CLASS_FIRST);
1397         }
1398         if(isLast) {
1399             tr.addClass(CLASS_LAST);
1400         }
1402         for(; i<len; ++i) {
1403             column = columns[i];
1404             this._addTheadThNode({value:column.get("label"), column: column, tr:tr});
1405         }
1407         return tr;
1408     },
1410     /**
1411     * Attaches header row element.
1412     *
1413     * @method _attachTheadTrNode
1414     * @param o {Object} {thead, columns, tr}.
1415     * @protected
1416     */
1417     _attachTheadTrNode: function(o) {
1418         o.thead.appendChild(o.tr);
1419     },
1421     /**
1422     * Creates and attaches header cell element.
1423     *
1424     * @method _addTheadThNode
1425     * @param o {Object} {value, column, tr}.
1426     * @protected
1427     */
1428     _addTheadThNode: function(o) {
1429         o.th = this._createTheadThNode(o);
1430         this._attachTheadThNode(o);
1431         //TODO: assign all node pointers: thNode, thLinerNode, thLabelNode
1432         o.column.thNode = o.th;
1433     },
1435     /**
1436     * Creates header cell element.
1437     *
1438     * @method _createTheadThNode
1439     * @param o {Object} {value, column, tr}.
1440     * @protected
1441     * @return {Node}
1442     */
1443     _createTheadThNode: function(o) {
1444         var column = o.column;
1445         
1446         // Populate template object
1447         o.id = column.get("id");//TODO: validate 1 column ID per document
1448         o.colspan = column.colSpan;
1449         o.rowspan = column.rowSpan;
1450         o.abbr = column.get("abbr");
1451         o.classnames = column.get("classnames");
1452         o.value = fromTemplate(this.get("thValueTemplate"), o);
1454         /*TODO
1455         // Clear minWidth on hidden Columns
1456         if(column.get("hidden")) {
1457             //this._clearMinWidth(column);
1458         }
1459         */
1460         
1461         return Ycreate(fromTemplate(this.thTemplate, o));
1462     },
1464     /**
1465     * Attaches header cell element.
1466     *
1467     * @method _attachTheadThNode
1468     * @param o {Object} {value, column, tr}.
1469     * @protected
1470     */
1471     _attachTheadThNode: function(o) {
1472         o.tr.appendChild(o.th);
1473     },
1475     ////////////////////////////////////////////////////////////////////////////
1476     //
1477     // TBODY/RECORDSET FUNCTIONALITY
1478     //
1479     ////////////////////////////////////////////////////////////////////////////
1480     /**
1481      * Updates TBODY.
1482      *
1483      * @method _uiSetRecordset
1484      * @param rs {Recordset} New Recordset.
1485      * @protected
1486      */
1487     _uiSetRecordset: function(rs) {
1488         var self = this,
1489             oldTbody = this._tbodyNode,
1490             parent = oldTbody.get("parentNode"),
1491             nextSibling = oldTbody.next(),
1492             columns = this.get('columnset').keys,
1493             cellValueTemplate = this.get('tdValueTemplate'),
1494             o = {},
1495             newTbody, i, len, column, formatter;
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;
1506         o.rowTemplate = this.get('trTemplate');
1507         o.columns = [];
1509         // Build up column data to avoid passing through Attribute APIs inside
1510         // render loops for rows and cells
1511         for (i = columns.length - 1; i >= 0; --i) {
1512             column = columns[i];
1513             o.columns[i] = {
1514                 column        : column,
1515                 fields        : column.get('field'),
1516                 classnames    : column.get('classnames'),
1517                 emptyCellValue: column.get('emptyCellValue')
1518             }
1520             formatter = column.get('formatter');
1522             if (YLang.isFunction(formatter)) {
1523                 // function formatters need to run before checking if the value
1524                 // needs defaulting from column.emptyCellValue
1525                 formatter = Y.bind(this._functionFormatter, this, formatter);
1526             } else {
1527                 if (!YLang.isString(formatter)) {
1528                     formatter = cellValueTemplate;
1529                 }
1531                 // string formatters need the value defaulted before processing
1532                 formatter = Y.bind(this._templateFormatter, this, formatter);
1533             }
1535             o.columns[i].formatter = formatter;
1536         }
1539         // Iterate Recordset to use existing TR when possible or add new TR
1540         // TODO i = this.get("state.offsetIndex")
1541         // TODO len =this.get("state.pageLength")
1542         for (i = 0, len = rs.size(); i < len; ++i) {
1543             o.record = rs.item(i);
1544             o.data   = o.record.get("data");
1545             o.rowindex = i;
1546             this._addTbodyTrNode(o); //TODO: sometimes rowindex != recordindex
1547         }
1548         
1549         // TBODY to DOM
1550         parent.insert(this._tbodyNode, nextSibling);
1551     },
1553     _functionFormatter: function (formatter, o) {
1554         var value = formatter.call(this, o);
1556         return (value !== undefined) ? value : o.emptyCellValue;
1557     },
1559     _templateFormatter: function (template, o) {
1560         if (o.value === undefined) {
1561             o.value = o.emptyCellValue;
1562         }
1564         return fromTemplate(template, o);
1565     },
1567     /**
1568     * Creates and attaches data row element.
1569     *
1570     * @method _addTbodyTrNode
1571     * @param o {Object} {tbody, record}
1572     * @protected
1573     */
1574     _addTbodyTrNode: function(o) {
1575         var row = o.tbody.one("#" + o.record.get("id"));
1577         o.tr = row || this._createTbodyTrNode(o);
1579         this._attachTbodyTrNode(o);
1580     },
1582     /**
1583     * Creates data row element.
1584     *
1585     * @method _createTbodyTrNode
1586     * @param o {Object} {tbody, record}
1587     * @protected
1588     * @return {Node}
1589     */
1590     _createTbodyTrNode: function(o) {
1591         var columns = o.columns,
1592             i, len, columnInfo;
1594         o.tr = Ycreate(fromTemplate(o.rowTemplate, { id: o.record.get('id') }));
1595         
1596         for (i = 0, len = columns.length; i < len; ++i) {
1597             columnInfo      = columns[i];
1598             o.column        = columnInfo.column;
1599             o.field         = columnInfo.fields;
1600             o.classnames    = columnInfo.classnames;
1601             o.formatter     = columnInfo.formatter;
1602             o.emptyCellValue= columnInfo.emptyCellValue;
1604             this._addTbodyTdNode(o);
1605         }
1606         
1607         return o.tr;
1608     },
1610     /**
1611     * Attaches data row element.
1612     *
1613     * @method _attachTbodyTrNode
1614     * @param o {Object} {tbody, record, tr}.
1615     * @protected
1616     */
1617     _attachTbodyTrNode: function(o) {
1618         var tbody = o.tbody,
1619             tr = o.tr,
1620             index = o.rowindex,
1621             nextSibling = tbody.get("children").item(index) || null,
1622             isOdd = (index % 2);
1623             
1624         if(isOdd) {
1625             tr.replaceClass(CLASS_EVEN, CLASS_ODD);
1626         } else {
1627             tr.replaceClass(CLASS_ODD, CLASS_EVEN);
1628         }
1629         
1630         tbody.insertBefore(tr, nextSibling);
1631     },
1633     /**
1634     * Creates and attaches data cell element.
1635     *
1636     * @method _addTbodyTdNode
1637     * @param o {Object} {record, column, tr}.
1638     * @protected
1639     */
1640     _addTbodyTdNode: function(o) {
1641         o.td = this._createTbodyTdNode(o);
1642         this._attachTbodyTdNode(o);
1643         delete o.td;
1644     },
1645     
1646     /**
1647     Creates a TD Node from the tdTemplate property using the input object as
1648     template {placeholder} values.  The created Node is also assigned to the
1649     `td` property on the input object.
1651     If the input object already has a `td` property, it is returned an no new
1652     Node is created.
1654     @method createCell
1655     @param {Object} data Template values
1656     @return {Node}
1657     **/
1658     createCell: function (data) {
1659         return data && (data.td ||
1660             (data.td = Ycreate(fromTemplate(this.tdTemplate, data))));
1661     },
1663     /**
1664     * Creates data cell element.
1665     *
1666     * @method _createTbodyTdNode
1667     * @param o {Object} {record, column, tr}.
1668     * @protected
1669     * @return {Node}
1670     */
1671     _createTbodyTdNode: function(o) {
1672         o.headers = o.column.headers;
1673         o.value   = this.formatDataCell(o);
1675         return o.td || this.createCell(o);
1676     },
1677     
1678     /**
1679     * Attaches data cell element.
1680     *
1681     * @method _attachTbodyTdNode
1682     * @param o {Object} {record, column, tr, headers, classnames, value}.
1683     * @protected
1684     */
1685     _attachTbodyTdNode: function(o) {
1686         o.tr.appendChild(o.td);
1687     },
1689     /**
1690      * Returns markup to insert into data cell element.
1691      *
1692      * @method formatDataCell
1693      * @param @param o {Object} {record, column, tr, headers, classnames}.
1694      */
1695     formatDataCell: function (o) {
1696         o.value = o.data[o.field];
1698         return o.formatter.call(this, o);
1699     },
1701     _initRecordset: function () {
1702         return new Y.Recordset({ records: [] });
1703     }
1706 Y.namespace("DataTable").Base = DTBase;
1709 }, '3.5.1' ,{requires:['recordset-base','widget','substitute','event-mouseenter']});