]> ToastFreeware Gitweb - philipp/winterrodeln/wradmin.git/blob - wradmin/static/yui/datatable/datatable.js
Rename public directory to static.
[philipp/winterrodeln/wradmin.git] / wradmin / static / yui / datatable / datatable.js
1 /*
2 Copyright (c) 2009, Yahoo! Inc. All rights reserved.
3 Code licensed under the BSD License:
4 http://developer.yahoo.net/yui/license.txt
5 version: 2.7.0
6 */
7 /**
8  * Mechanism to execute a series of callbacks in a non-blocking queue.  Each callback is executed via setTimout unless configured with a negative timeout, in which case it is run in blocking mode in the same execution thread as the previous callback.  Callbacks can be function references or object literals with the following keys:
9  * <ul>
10  *    <li><code>method</code> - {Function} REQUIRED the callback function.</li>
11  *    <li><code>scope</code> - {Object} the scope from which to execute the callback.  Default is the global window scope.</li>
12  *    <li><code>argument</code> - {Array} parameters to be passed to method as individual arguments.</li>
13  *    <li><code>timeout</code> - {number} millisecond delay to wait after previous callback completion before executing this callback.  Negative values cause immediate blocking execution.  Default 0.</li>
14  *    <li><code>until</code> - {Function} boolean function executed before each iteration.  Return true to indicate completion and proceed to the next callback.</li>
15  *    <li><code>iterations</code> - {Number} number of times to execute the callback before proceeding to the next callback in the chain. Incompatible with <code>until</code>.</li>
16  * </ul>
17  *
18  * @namespace YAHOO.util
19  * @class Chain
20  * @constructor
21  * @param callback* {Function|Object} Any number of callbacks to initialize the queue
22 */
23 YAHOO.util.Chain = function () {
24     /**
25      * The callback queue
26      * @property q
27      * @type {Array}
28      * @private
29      */
30     this.q = [].slice.call(arguments);
31
32     /**
33      * Event fired when the callback queue is emptied via execution (not via
34      * a call to chain.stop().
35      * @event end
36      */
37     this.createEvent('end');
38 };
39
40 YAHOO.util.Chain.prototype = {
41     /**
42      * Timeout id used to pause or stop execution and indicate the execution state of the Chain.  0 indicates paused or stopped, -1 indicates blocking execution, and any positive number indicates non-blocking execution.
43      * @property id
44      * @type {number}
45      * @private
46      */
47     id   : 0,
48
49     /**
50      * Begin executing the chain, or resume execution from the last paused position.
51      * @method run
52      * @return {Chain} the Chain instance
53      */
54     run : function () {
55         // Grab the first callback in the queue
56         var c  = this.q[0],
57             fn;
58
59         // If there is no callback in the queue or the Chain is currently
60         // in an execution mode, return
61         if (!c) {
62             this.fireEvent('end');
63             return this;
64         } else if (this.id) {
65             return this;
66         }
67
68         fn = c.method || c;
69
70         if (typeof fn === 'function') {
71             var o    = c.scope || {},
72                 args = c.argument || [],
73                 ms   = c.timeout || 0,
74                 me   = this;
75                 
76             if (!(args instanceof Array)) {
77                 args = [args];
78             }
79
80             // Execute immediately if the callback timeout is negative.
81             if (ms < 0) {
82                 this.id = ms;
83                 if (c.until) {
84                     for (;!c.until();) {
85                         // Execute the callback from scope, with argument
86                         fn.apply(o,args);
87                     }
88                 } else if (c.iterations) {
89                     for (;c.iterations-- > 0;) {
90                         fn.apply(o,args);
91                     }
92                 } else {
93                     fn.apply(o,args);
94                 }
95                 this.q.shift();
96                 this.id = 0;
97                 return this.run();
98             } else {
99                 // If the until condition is set, check if we're done
100                 if (c.until) {
101                     if (c.until()) {
102                         // Shift this callback from the queue and execute the next
103                         // callback
104                         this.q.shift();
105                         return this.run();
106                     }
107                 // Otherwise if either iterations is not set or we're
108                 // executing the last iteration, shift callback from the queue
109                 } else if (!c.iterations || !--c.iterations) {
110                     this.q.shift();
111                 }
112
113                 // Otherwise set to execute after the configured timeout
114                 this.id = setTimeout(function () {
115                     // Execute the callback from scope, with argument
116                     fn.apply(o,args);
117                     // Check if the Chain was not paused from inside the callback
118                     if (me.id) {
119                         // Indicate ready to run state
120                         me.id = 0;
121                         // Start the fun all over again
122                         me.run();
123                     }
124                 },ms);
125             }
126         }
127
128         return this;
129     },
130     
131     /**
132      * Add a callback to the end of the queue
133      * @method add
134      * @param c {Function|Object} the callback function ref or object literal
135      * @return {Chain} the Chain instance
136      */
137     add  : function (c) {
138         this.q.push(c);
139         return this;
140     },
141
142     /**
143      * Pause the execution of the Chain after the current execution of the
144      * current callback completes.  If called interstitially, clears the
145      * timeout for the pending callback. Paused Chains can be restarted with
146      * chain.run()
147      * @method pause
148      * @return {Chain} the Chain instance
149      */
150     pause: function () {
151         clearTimeout(this.id);
152         this.id = 0;
153         return this;
154     },
155
156     /**
157      * Stop and clear the Chain's queue after the current execution of the
158      * current callback completes.
159      * @method stop
160      * @return {Chain} the Chain instance
161      */
162     stop : function () { 
163         this.pause();
164         this.q = [];
165         return this;
166     }
167 };
168 YAHOO.lang.augmentProto(YAHOO.util.Chain,YAHOO.util.EventProvider);
169
170 /****************************************************************************/
171 /****************************************************************************/
172 /****************************************************************************/
173
174 /**
175  * The ColumnSet class defines and manages a DataTable's Columns,
176  * including nested hierarchies and access to individual Column instances.
177  *
178  * @namespace YAHOO.widget
179  * @class ColumnSet
180  * @uses YAHOO.util.EventProvider
181  * @constructor
182  * @param aDefinitions {Object[]} Array of object literals that define cells in
183  * the THEAD.
184  */
185 YAHOO.widget.ColumnSet = function(aDefinitions) {
186     this._sId = "yui-cs" + YAHOO.widget.ColumnSet._nCount;
187
188     // First clone the defs
189     aDefinitions = YAHOO.widget.DataTable._cloneObject(aDefinitions);
190     this._init(aDefinitions);
191
192     YAHOO.widget.ColumnSet._nCount++;
193 };
194
195 /////////////////////////////////////////////////////////////////////////////
196 //
197 // Private member variables
198 //
199 /////////////////////////////////////////////////////////////////////////////
200
201 /**
202  * Internal class variable to index multiple ColumnSet instances.
203  *
204  * @property ColumnSet._nCount
205  * @type Number
206  * @private
207  * @static
208  */
209 YAHOO.widget.ColumnSet._nCount = 0;
210
211 YAHOO.widget.ColumnSet.prototype = {
212     /**
213      * Unique instance name.
214      *
215      * @property _sId
216      * @type String
217      * @private
218      */
219     _sId : null,
220
221     /**
222      * Array of object literal Column definitions passed to the constructor.
223      *
224      * @property _aDefinitions
225      * @type Object[]
226      * @private
227      */
228     _aDefinitions : null,
229
230     /////////////////////////////////////////////////////////////////////////////
231     //
232     // Public member variables
233     //
234     /////////////////////////////////////////////////////////////////////////////
235
236     /**
237      * Top-down tree representation of Column hierarchy.
238      *
239      * @property tree
240      * @type YAHOO.widget.Column[]
241      */
242     tree : null,
243
244     /**
245      * Flattened representation of all Columns.
246      *
247      * @property flat
248      * @type YAHOO.widget.Column[]
249      * @default []
250      */
251     flat : null,
252
253     /**
254      * Array of Columns that map one-to-one to a table column.
255      *
256      * @property keys
257      * @type YAHOO.widget.Column[]
258      * @default []
259      */
260     keys : null,
261
262     /**
263      * ID index of nested parent hierarchies for HEADERS accessibility attribute.
264      *
265      * @property headers
266      * @type String[]
267      * @default []
268      */
269     headers : null,
270
271     /////////////////////////////////////////////////////////////////////////////
272     //
273     // Private methods
274     //
275     /////////////////////////////////////////////////////////////////////////////
276
277     /**
278      * Initializes ColumnSet instance with data from Column definitions.
279      *
280      * @method _init
281      * @param aDefinitions {Object[]} Array of object literals that define cells in
282      * the THEAD .
283      * @private
284      */
285
286     _init : function(aDefinitions) {        
287         // DOM tree representation of all Columns
288         var tree = [];
289         // Flat representation of all Columns
290         var flat = [];
291         // Flat representation of only Columns that are meant to display data
292         var keys = [];
293         // Array of HEADERS attribute values for all keys in the "keys" array
294         var headers = [];
295
296         // Tracks current node list depth being tracked
297         var nodeDepth = -1;
298
299         // Internal recursive function to define Column instances
300         var parseColumns = function(nodeList, parent) {
301             // One level down
302             nodeDepth++;
303
304             // Create corresponding tree node if not already there for this depth
305             if(!tree[nodeDepth]) {
306                 tree[nodeDepth] = [];
307             }
308
309
310             // Parse each node at this depth for attributes and any children
311             for(var j=0; j<nodeList.length; j++) {
312                 var currentNode = nodeList[j];
313
314                 // Instantiate a new Column for each node
315                 var oColumn = new YAHOO.widget.Column(currentNode);
316                 
317                 // Cross-reference Column ID back to the original object literal definition
318                 currentNode.yuiColumnId = oColumn._sId;
319                 
320                 // Add the new Column to the flat list
321                 flat.push(oColumn);
322
323                 // Assign its parent as an attribute, if applicable
324                 if(parent) {
325                     oColumn._oParent = parent;
326                 }
327
328                 // The Column has descendants
329                 if(YAHOO.lang.isArray(currentNode.children)) {
330                     oColumn.children = currentNode.children;
331
332                     // Determine COLSPAN value for this Column
333                     var terminalChildNodes = 0;
334                     var countTerminalChildNodes = function(ancestor) {
335                         var descendants = ancestor.children;
336                         // Drill down each branch and count terminal nodes
337                         for(var k=0; k<descendants.length; k++) {
338                             // Keep drilling down
339                             if(YAHOO.lang.isArray(descendants[k].children)) {
340                                 countTerminalChildNodes(descendants[k]);
341                             }
342                             // Reached branch terminus
343                             else {
344                                 terminalChildNodes++;
345                             }
346                         }
347                     };
348                     countTerminalChildNodes(currentNode);
349                     oColumn._nColspan = terminalChildNodes;
350
351                     // Cascade certain properties to children if not defined on their own
352                     var currentChildren = currentNode.children;
353                     for(var k=0; k<currentChildren.length; k++) {
354                         var child = currentChildren[k];
355                         if(oColumn.className && (child.className === undefined)) {
356                             child.className = oColumn.className;
357                         }
358                         if(oColumn.editor && (child.editor === undefined)) {
359                             child.editor = oColumn.editor;
360                         }
361                         //TODO: Deprecated
362                         if(oColumn.editorOptions && (child.editorOptions === undefined)) {
363                             child.editorOptions = oColumn.editorOptions;
364                         }
365                         if(oColumn.formatter && (child.formatter === undefined)) {
366                             child.formatter = oColumn.formatter;
367                         }
368                         if(oColumn.resizeable && (child.resizeable === undefined)) {
369                             child.resizeable = oColumn.resizeable;
370                         }
371                         if(oColumn.sortable && (child.sortable === undefined)) {
372                             child.sortable = oColumn.sortable;
373                         }
374                         if(oColumn.hidden) {
375                             child.hidden = true;
376                         }
377                         if(oColumn.width && (child.width === undefined)) {
378                             child.width = oColumn.width;
379                         }
380                         if(oColumn.minWidth && (child.minWidth === undefined)) {
381                             child.minWidth = oColumn.minWidth;
382                         }
383                         if(oColumn.maxAutoWidth && (child.maxAutoWidth === undefined)) {
384                             child.maxAutoWidth = oColumn.maxAutoWidth;
385                         }
386                         // Backward compatibility
387                         if(oColumn.type && (child.type === undefined)) {
388                             child.type = oColumn.type;
389                         }
390                         if(oColumn.type && !oColumn.formatter) {
391                             oColumn.formatter = oColumn.type;
392                         }
393                         if(oColumn.text && !YAHOO.lang.isValue(oColumn.label)) {
394                             oColumn.label = oColumn.text;
395                         }
396                         if(oColumn.parser) {
397                         }
398                         if(oColumn.sortOptions && ((oColumn.sortOptions.ascFunction) ||
399                                 (oColumn.sortOptions.descFunction))) {
400                         }
401                     }
402
403                     // The children themselves must also be parsed for Column instances
404                     if(!tree[nodeDepth+1]) {
405                         tree[nodeDepth+1] = [];
406                     }
407                     parseColumns(currentChildren, oColumn);
408                 }
409                 // This Column does not have any children
410                 else {
411                     oColumn._nKeyIndex = keys.length;
412                     oColumn._nColspan = 1;
413                     keys.push(oColumn);
414                 }
415
416                 // Add the Column to the top-down tree
417                 tree[nodeDepth].push(oColumn);
418             }
419             nodeDepth--;
420         };
421
422         // Parse out Column instances from the array of object literals
423         if(YAHOO.lang.isArray(aDefinitions)) {
424             parseColumns(aDefinitions);
425
426             // Store the array
427             this._aDefinitions = aDefinitions;
428         }
429         else {
430             return null;
431         }
432
433         var i;
434
435         // Determine ROWSPAN value for each Column in the tree
436         var parseTreeForRowspan = function(tree) {
437             var maxRowDepth = 1;
438             var currentRow;
439             var currentColumn;
440
441             // Calculate the max depth of descendants for this row
442             var countMaxRowDepth = function(row, tmpRowDepth) {
443                 tmpRowDepth = tmpRowDepth || 1;
444
445                 for(var n=0; n<row.length; n++) {
446                     var col = row[n];
447                     // Column has children, so keep counting
448                     if(YAHOO.lang.isArray(col.children)) {
449                         tmpRowDepth++;
450                         countMaxRowDepth(col.children, tmpRowDepth);
451                         tmpRowDepth--;
452                     }
453                     // No children, is it the max depth?
454                     else {
455                         if(tmpRowDepth > maxRowDepth) {
456                             maxRowDepth = tmpRowDepth;
457                         }
458                     }
459
460                 }
461             };
462
463             // Count max row depth for each row
464             for(var m=0; m<tree.length; m++) {
465                 currentRow = tree[m];
466                 countMaxRowDepth(currentRow);
467
468                 // Assign the right ROWSPAN values to each Column in the row
469                 for(var p=0; p<currentRow.length; p++) {
470                     currentColumn = currentRow[p];
471                     if(!YAHOO.lang.isArray(currentColumn.children)) {
472                         currentColumn._nRowspan = maxRowDepth;
473                     }
474                     else {
475                         currentColumn._nRowspan = 1;
476                     }
477                 }
478
479                 // Reset counter for next row
480                 maxRowDepth = 1;
481             }
482         };
483         parseTreeForRowspan(tree);
484
485         // Store tree index values
486         for(i=0; i<tree[0].length; i++) {
487             tree[0][i]._nTreeIndex = i;
488         }
489
490         // Store header relationships in an array for HEADERS attribute
491         var recurseAncestorsForHeaders = function(i, oColumn) {
492             headers[i].push(oColumn.getSanitizedKey());
493             if(oColumn._oParent) {
494                 recurseAncestorsForHeaders(i, oColumn._oParent);
495             }
496         };
497         for(i=0; i<keys.length; i++) {
498             headers[i] = [];
499             recurseAncestorsForHeaders(i, keys[i]);
500             headers[i] = headers[i].reverse();
501         }
502
503         // Save to the ColumnSet instance
504         this.tree = tree;
505         this.flat = flat;
506         this.keys = keys;
507         this.headers = headers;
508     },
509
510     /////////////////////////////////////////////////////////////////////////////
511     //
512     // Public methods
513     //
514     /////////////////////////////////////////////////////////////////////////////
515
516     /**
517      * Returns unique name of the ColumnSet instance.
518      *
519      * @method getId
520      * @return {String} Unique name of the ColumnSet instance.
521      */
522
523     getId : function() {
524         return this._sId;
525     },
526
527     /**
528      * ColumnSet instance name, for logging.
529      *
530      * @method toString
531      * @return {String} Unique name of the ColumnSet instance.
532      */
533
534     toString : function() {
535         return "ColumnSet instance " + this._sId;
536     },
537
538     /**
539      * Public accessor to the definitions array.
540      *
541      * @method getDefinitions
542      * @return {Object[]} Array of object literal Column definitions.
543      */
544
545     getDefinitions : function() {
546         var aDefinitions = this._aDefinitions;
547         
548         // Internal recursive function to define Column instances
549         var parseColumns = function(nodeList, oSelf) {
550             // Parse each node at this depth for attributes and any children
551             for(var j=0; j<nodeList.length; j++) {
552                 var currentNode = nodeList[j];
553                 
554                 // Get the Column for each node
555                 var oColumn = oSelf.getColumnById(currentNode.yuiColumnId);
556                 
557                 if(oColumn) {    
558                     // Update the current values
559                     var oDefinition = oColumn.getDefinition();
560                     for(var name in oDefinition) {
561                         if(YAHOO.lang.hasOwnProperty(oDefinition, name)) {
562                             currentNode[name] = oDefinition[name];
563                         }
564                     }
565                 }
566                             
567                 // The Column has descendants
568                 if(YAHOO.lang.isArray(currentNode.children)) {
569                     // The children themselves must also be parsed for Column instances
570                     parseColumns(currentNode.children, oSelf);
571                 }
572             }
573         };
574
575         parseColumns(aDefinitions, this);
576         this._aDefinitions = aDefinitions;
577         return aDefinitions;
578     },
579
580     /**
581      * Returns Column instance with given ID.
582      *
583      * @method getColumnById
584      * @param column {String} Column ID.
585      * @return {YAHOO.widget.Column} Column instance.
586      */
587
588     getColumnById : function(column) {
589         if(YAHOO.lang.isString(column)) {
590             var allColumns = this.flat;
591             for(var i=allColumns.length-1; i>-1; i--) {
592                 if(allColumns[i]._sId === column) {
593                     return allColumns[i];
594                 }
595             }
596         }
597         return null;
598     },
599
600     /**
601      * Returns Column instance with given key or ColumnSet key index.
602      *
603      * @method getColumn
604      * @param column {String | Number} Column key or ColumnSet key index.
605      * @return {YAHOO.widget.Column} Column instance.
606      */
607
608     getColumn : function(column) {
609         if(YAHOO.lang.isNumber(column) && this.keys[column]) {
610             return this.keys[column];
611         }
612         else if(YAHOO.lang.isString(column)) {
613             var allColumns = this.flat;
614             var aColumns = [];
615             for(var i=0; i<allColumns.length; i++) {
616                 if(allColumns[i].key === column) {
617                     aColumns.push(allColumns[i]);
618                 }
619             }
620             if(aColumns.length === 1) {
621                 return aColumns[0];
622             }
623             else if(aColumns.length > 1) {
624                 return aColumns;
625             }
626         }
627         return null;
628     },
629
630     /**
631      * Public accessor returns array of given Column's desendants (if any), including itself.
632      *
633      * @method getDescendants
634      * @parem {YAHOO.widget.Column} Column instance.
635      * @return {Array} Array including the Column itself and all descendants (if any).
636      */
637     getDescendants : function(oColumn) {
638         var oSelf = this;
639         var allDescendants = [];
640         var i;
641
642         // Recursive function to loop thru all children
643         var parse = function(oParent) {
644             allDescendants.push(oParent);
645             // This Column has children
646             if(oParent.children) {
647                 for(i=0; i<oParent.children.length; i++) {
648                     parse(oSelf.getColumn(oParent.children[i].key));
649                 }
650             }
651         };
652         parse(oColumn);
653
654         return allDescendants;
655     }
656 };
657
658 /****************************************************************************/
659 /****************************************************************************/
660 /****************************************************************************/
661
662 /**
663  * The Column class defines and manages attributes of DataTable Columns
664  *
665  * @namespace YAHOO.widget
666  * @class Column
667  * @constructor
668  * @param oConfigs {Object} Object literal of definitions.
669  */
670 YAHOO.widget.Column = function(oConfigs) {
671     this._sId = "yui-col" + YAHOO.widget.Column._nCount;
672     
673     // Object literal defines Column attributes
674     if(oConfigs && YAHOO.lang.isObject(oConfigs)) {
675         for(var sConfig in oConfigs) {
676             if(sConfig) {
677                 this[sConfig] = oConfigs[sConfig];
678             }
679         }
680     }
681
682     // Assign a key if not found
683     if(!YAHOO.lang.isValue(this.key)) {
684         this.key = "yui-dt-col" + YAHOO.widget.Column._nCount;
685     }
686     
687     // Assign a field if not found, defaults to key
688     if(!YAHOO.lang.isValue(this.field)) {
689         this.field = this.key;
690     }
691
692     // Increment counter
693     YAHOO.widget.Column._nCount++;
694
695     // Backward compatibility
696     if(this.width && !YAHOO.lang.isNumber(this.width)) {
697         this.width = null;
698     }
699     if(this.editor && YAHOO.lang.isString(this.editor)) {
700         this.editor = new YAHOO.widget.CellEditor(this.editor, this.editorOptions);
701     }
702 };
703
704 /////////////////////////////////////////////////////////////////////////////
705 //
706 // Private member variables
707 //
708 /////////////////////////////////////////////////////////////////////////////
709
710 YAHOO.lang.augmentObject(YAHOO.widget.Column, {
711     /**
712      * Internal class variable to index multiple Column instances.
713      *
714      * @property Column._nCount
715      * @type Number
716      * @private
717      * @static
718      */
719     _nCount : 0,
720
721     formatCheckbox : function(elCell, oRecord, oColumn, oData) {
722         YAHOO.widget.DataTable.formatCheckbox(elCell, oRecord, oColumn, oData);
723     },
724
725     formatCurrency : function(elCell, oRecord, oColumn, oData) {
726         YAHOO.widget.DataTable.formatCurrency(elCell, oRecord, oColumn, oData);
727     },
728
729     formatDate : function(elCell, oRecord, oColumn, oData) {
730         YAHOO.widget.DataTable.formatDate(elCell, oRecord, oColumn, oData);
731     },
732
733     formatEmail : function(elCell, oRecord, oColumn, oData) {
734         YAHOO.widget.DataTable.formatEmail(elCell, oRecord, oColumn, oData);
735     },
736
737     formatLink : function(elCell, oRecord, oColumn, oData) {
738         YAHOO.widget.DataTable.formatLink(elCell, oRecord, oColumn, oData);
739     },
740
741     formatNumber : function(elCell, oRecord, oColumn, oData) {
742         YAHOO.widget.DataTable.formatNumber(elCell, oRecord, oColumn, oData);
743     },
744
745     formatSelect : function(elCell, oRecord, oColumn, oData) {
746         YAHOO.widget.DataTable.formatDropdown(elCell, oRecord, oColumn, oData);
747     }
748 });
749
750 YAHOO.widget.Column.prototype = {
751     /**
752      * Unique String identifier assigned at instantiation.
753      *
754      * @property _sId
755      * @type String
756      * @private
757      */
758     _sId : null,
759
760     /**
761      * Reference to Column's current position index within its ColumnSet's keys
762      * array, if applicable. This property only applies to non-nested and bottom-
763      * level child Columns.
764      *
765      * @property _nKeyIndex
766      * @type Number
767      * @private
768      */
769     _nKeyIndex : null,
770
771     /**
772      * Reference to Column's current position index within its ColumnSet's tree
773      * array, if applicable. This property only applies to non-nested and top-
774      * level parent Columns.
775      *
776      * @property _nTreeIndex
777      * @type Number
778      * @private
779      */
780     _nTreeIndex : null,
781
782     /**
783      * Number of table cells the Column spans.
784      *
785      * @property _nColspan
786      * @type Number
787      * @private
788      */
789     _nColspan : 1,
790
791     /**
792      * Number of table rows the Column spans.
793      *
794      * @property _nRowspan
795      * @type Number
796      * @private
797      */
798     _nRowspan : 1,
799
800     /**
801      * Column's parent Column instance, or null.
802      *
803      * @property _oParent
804      * @type YAHOO.widget.Column
805      * @private
806      */
807     _oParent : null,
808
809     /**
810      * The DOM reference to the associated TH element.
811      *
812      * @property _elTh
813      * @type HTMLElement
814      * @private
815      */
816     _elTh : null,
817
818     /**
819      * The DOM reference to the associated TH element's liner DIV element.
820      *
821      * @property _elThLiner
822      * @type HTMLElement
823      * @private
824      */
825     _elThLiner : null,
826
827     /**
828      * The DOM reference to the associated TH element's label SPAN element.
829      *
830      * @property _elThLabel
831      * @type HTMLElement
832      * @private
833      */
834     _elThLabel : null,
835
836     /**
837      * The DOM reference to the associated resizerelement (if any).
838      *
839      * @property _elResizer
840      * @type HTMLElement
841      * @private
842      */
843     _elResizer : null,
844
845     /**
846      * Internal width tracker.
847      *
848      * @property _nWidth
849      * @type Number
850      * @private
851      */
852     _nWidth : null,
853
854     /**
855      * For unreg() purposes, a reference to the Column's DragDrop instance.
856      *
857      * @property _dd
858      * @type YAHOO.util.DragDrop
859      * @private
860      */
861     _dd : null,
862
863     /**
864      * For unreg() purposes, a reference to the Column resizer's DragDrop instance.
865      *
866      * @property _ddResizer
867      * @type YAHOO.util.DragDrop
868      * @private
869      */
870     _ddResizer : null,
871
872     /////////////////////////////////////////////////////////////////////////////
873     //
874     // Public member variables
875     //
876     /////////////////////////////////////////////////////////////////////////////
877
878     /**
879      * Unique name, required.
880      *
881      * @property key
882      * @type String
883      */
884     key : null,
885
886     /**
887      * Associated database field, or null.
888      *
889      * @property field
890      * @type String
891      */
892     field : null,
893
894     /**
895      * Text or HTML for display as Column's label in the TH element.
896      *
897      * @property label
898      * @type String
899      */
900     label : null,
901
902     /**
903      * Column head cell ABBR for accessibility.
904      *
905      * @property abbr
906      * @type String
907      */
908     abbr : null,
909
910     /**
911      * Array of object literals that define children (nested headers) of a Column.
912      *
913      * @property children
914      * @type Object[]
915      */
916     children : null,
917
918     /**
919      * Column width (in pixels).
920      *
921      * @property width
922      * @type Number
923      */
924     width : null,
925
926     /**
927      * Minimum Column width (in pixels).
928      *
929      * @property minWidth
930      * @type Number
931      * @default null
932      */
933     minWidth : null,
934
935     /**
936      * When a width is not defined for a Column, maxAutoWidth defines an upper
937      * limit that the Column should be auto-sized to. If resizeable is enabled, 
938      * users may still resize to a greater width. Most useful for Columns intended
939      * to hold long unbroken, unwrapped Strings, such as URLs, to prevent very
940      * wide Columns from disrupting visual readability by inducing truncation.
941      *
942      * @property maxAutoWidth
943      * @type Number
944      * @default null
945      */
946     maxAutoWidth : null,
947
948     /**
949      * True if Column is in hidden state.
950      *
951      * @property hidden
952      * @type Boolean
953      * @default false     
954      */
955     hidden : false,
956
957     /**
958      * True if Column is in selected state.
959      *
960      * @property selected
961      * @type Boolean
962      * @default false     
963      */
964     selected : false,
965
966     /**
967      * Custom CSS class or array of classes to be applied to every cell in the Column.
968      *
969      * @property className
970      * @type String || String[]
971      */
972     className : null,
973
974     /**
975      * Defines a format function.
976      *
977      * @property formatter
978      * @type String || HTMLFunction
979      */
980     formatter : null,
981     
982     /**
983      * Config passed to YAHOO.util.Number.format() by the 'currency' Column formatter.
984      *
985      * @property currencyOptions
986      * @type Object
987      * @default null
988      */
989     currencyOptions : null,
990
991     /**
992      * Config passed to YAHOO.util.Date.format() by the 'date' Column formatter.
993      *
994      * @property dateOptions
995      * @type Object
996      * @default null
997      */
998     dateOptions : null,
999
1000     /**
1001      * A CellEditor instance, otherwise Column is not editable.     
1002      *
1003      * @property editor
1004      * @type YAHOO.widget.CellEditor
1005      */
1006     editor : null,
1007
1008     /**
1009      * True if Column is resizeable, false otherwise. The Drag & Drop Utility is
1010      * required to enable this feature. Only bottom-level and non-nested Columns are
1011      * resizeble. 
1012      *
1013      * @property resizeable
1014      * @type Boolean
1015      * @default false
1016      */
1017     resizeable : false,
1018
1019     /**
1020      * True if Column is sortable, false otherwise.
1021      *
1022      * @property sortable
1023      * @type Boolean
1024      * @default false
1025      */
1026     sortable : false,
1027
1028     /**
1029      * @property sortOptions.defaultOrder
1030      * @deprecated Use sortOptions.defaultDir.
1031      */
1032     /**
1033      * Default sort direction for Column: YAHOO.widget.DataTable.CLASS_ASC or YAHOO.widget.DataTable.CLASS_DESC.
1034      *
1035      * @property sortOptions.defaultDir
1036      * @type String
1037      * @default null
1038      */
1039     /**
1040      * Custom field to sort on.
1041      *
1042      * @property sortOptions.field
1043      * @type String
1044      * @default null
1045      */
1046     /**
1047      * Custom sort handler.
1048      *
1049      * @property sortOptions.sortFunction
1050      * @type Function
1051      * @default null
1052      */
1053     sortOptions : null,
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069     /////////////////////////////////////////////////////////////////////////////
1070     //
1071     // Public methods
1072     //
1073     /////////////////////////////////////////////////////////////////////////////
1074
1075     /**
1076      * Returns unique ID string.
1077      *
1078      * @method getId
1079      * @return {String} Unique ID string.
1080      */
1081     getId : function() {
1082         return this._sId;
1083     },
1084
1085     /**
1086      * Column instance name, for logging.
1087      *
1088      * @method toString
1089      * @return {String} Column's unique name.
1090      */
1091     toString : function() {
1092         return "Column instance " + this._sId;
1093     },
1094
1095     /**
1096      * Returns object literal definition.
1097      *
1098      * @method getDefinition
1099      * @return {Object} Object literal definition.
1100      */
1101     getDefinition : function() {
1102         var oDefinition = {};
1103         
1104         // Update the definition
1105         oDefinition.abbr = this.abbr;
1106         oDefinition.className = this.className;
1107         oDefinition.editor = this.editor;
1108         oDefinition.editorOptions = this.editorOptions; //TODO: deprecated
1109         oDefinition.field = this.field;
1110         oDefinition.formatter = this.formatter;
1111         oDefinition.hidden = this.hidden;
1112         oDefinition.key = this.key;
1113         oDefinition.label = this.label;
1114         oDefinition.minWidth = this.minWidth;
1115         oDefinition.maxAutoWidth = this.maxAutoWidth;
1116         oDefinition.resizeable = this.resizeable;
1117         oDefinition.selected = this.selected;
1118         oDefinition.sortable = this.sortable;
1119         oDefinition.sortOptions = this.sortOptions;
1120         oDefinition.width = this.width;
1121
1122         return oDefinition;
1123     },
1124
1125     /**
1126      * Returns unique Column key.
1127      *
1128      * @method getKey
1129      * @return {String} Column key.
1130      */
1131     getKey : function() {
1132         return this.key;
1133     },
1134     
1135     /**
1136      * Returns field.
1137      *
1138      * @method getField
1139      * @return {String} Column field.
1140      */
1141     getField : function() {
1142         return this.field;
1143     },
1144     
1145     /**
1146      * Returns Column key which has been sanitized for DOM (class and ID) usage
1147      * starts with letter, contains only letters, numbers, hyphen, or period.
1148      *
1149      * @method getSanitizedKey
1150      * @return {String} Sanitized Column key.
1151      */
1152     getSanitizedKey : function() {
1153         return this.getKey().replace(/[^\w\-]/g,"");
1154     },
1155
1156     /**
1157      * Public accessor returns Column's current position index within its
1158      * ColumnSet's keys array, if applicable. Only non-nested and bottom-level
1159      * child Columns will return a value.
1160      *
1161      * @method getKeyIndex
1162      * @return {Number} Position index, or null.
1163      */
1164     getKeyIndex : function() {
1165         return this._nKeyIndex;
1166     },
1167
1168     /**
1169      * Public accessor returns Column's current position index within its
1170      * ColumnSet's tree array, if applicable. Only non-nested and top-level parent
1171      * Columns will return a value;
1172      *
1173      * @method getTreeIndex
1174      * @return {Number} Position index, or null.
1175      */
1176     getTreeIndex : function() {
1177         return this._nTreeIndex;
1178     },
1179
1180     /**
1181      * Public accessor returns Column's parent instance if any, or null otherwise.
1182      *
1183      * @method getParent
1184      * @return {YAHOO.widget.Column} Column's parent instance.
1185      */
1186     getParent : function() {
1187         return this._oParent;
1188     },
1189
1190     /**
1191      * Public accessor returns Column's calculated COLSPAN value.
1192      *
1193      * @method getColspan
1194      * @return {Number} Column's COLSPAN value.
1195      */
1196     getColspan : function() {
1197         return this._nColspan;
1198     },
1199     // Backward compatibility
1200     getColSpan : function() {
1201         return this.getColspan();
1202     },
1203
1204     /**
1205      * Public accessor returns Column's calculated ROWSPAN value.
1206      *
1207      * @method getRowspan
1208      * @return {Number} Column's ROWSPAN value.
1209      */
1210     getRowspan : function() {
1211         return this._nRowspan;
1212     },
1213
1214     /**
1215      * Returns DOM reference to the key TH element.
1216      *
1217      * @method getThEl
1218      * @return {HTMLElement} TH element.
1219      */
1220     getThEl : function() {
1221         return this._elTh;
1222     },
1223
1224     /**
1225      * Returns DOM reference to the TH's liner DIV element. Introduced since
1226      * resizeable Columns may have an extra resizer liner, making the DIV liner
1227      * not reliably the TH element's first child.               
1228      *
1229      * @method getThLInerEl
1230      * @return {HTMLElement} TH element.
1231      */
1232     getThLinerEl : function() {
1233         return this._elThLiner;
1234     },
1235     
1236     /**
1237      * Returns DOM reference to the resizer element, or null.
1238      *
1239      * @method getResizerEl
1240      * @return {HTMLElement} DIV element.
1241      */
1242     getResizerEl : function() {
1243         return this._elResizer;
1244     },
1245
1246     // Backward compatibility
1247     /**
1248      * @method getColEl
1249      * @deprecated Use getThEl
1250      */
1251     getColEl : function() {
1252         return this.getThEl();
1253     },
1254     getIndex : function() {
1255         return this.getKeyIndex();
1256     },
1257     format : function() {
1258     }
1259 };
1260
1261 /****************************************************************************/
1262 /****************************************************************************/
1263 /****************************************************************************/
1264
1265 /**
1266  * Sort static utility to support Column sorting.
1267  *
1268  * @namespace YAHOO.util
1269  * @class Sort
1270  * @static
1271  */
1272 YAHOO.util.Sort = {
1273     /////////////////////////////////////////////////////////////////////////////
1274     //
1275     // Public methods
1276     //
1277     /////////////////////////////////////////////////////////////////////////////
1278
1279     /**
1280      * Comparator function for simple case-insensitive string sorting.
1281      *
1282      * @method compare
1283      * @param a {Object} First sort argument.
1284      * @param b {Object} Second sort argument.
1285      * @param desc {Boolean} True if sort direction is descending, false if
1286      * sort direction is ascending.
1287      */
1288     compare: function(a, b, desc) {
1289         if((a === null) || (typeof a == "undefined")) {
1290             if((b === null) || (typeof b == "undefined")) {
1291                 return 0;
1292             }
1293             else {
1294                 return 1;
1295             }
1296         }
1297         else if((b === null) || (typeof b == "undefined")) {
1298             return -1;
1299         }
1300
1301         if(a.constructor == String) {
1302             a = a.toLowerCase();
1303         }
1304         if(b.constructor == String) {
1305             b = b.toLowerCase();
1306         }
1307         if(a < b) {
1308             return (desc) ? 1 : -1;
1309         }
1310         else if (a > b) {
1311             return (desc) ? -1 : 1;
1312         }
1313         else {
1314             return 0;
1315         }
1316     }
1317 };
1318
1319 /****************************************************************************/
1320 /****************************************************************************/
1321 /****************************************************************************/
1322
1323 /**
1324  * ColumnDD subclasses DragDrop to support rearrangeable Columns.
1325  *
1326  * @namespace YAHOO.util
1327  * @class ColumnDD
1328  * @extends YAHOO.util.DDProxy
1329  * @constructor
1330  * @param oDataTable {YAHOO.widget.DataTable} DataTable instance.
1331  * @param oColumn {YAHOO.widget.Column} Column instance.
1332  * @param elTh {HTMLElement} TH element reference.
1333  * @param elTarget {HTMLElement} Drag target element.
1334  */
1335 YAHOO.widget.ColumnDD = function(oDataTable, oColumn, elTh, elTarget) {
1336     if(oDataTable && oColumn && elTh && elTarget) {
1337         this.datatable = oDataTable;
1338         this.table = oDataTable.getTableEl();
1339         this.column = oColumn;
1340         this.headCell = elTh;
1341         this.pointer = elTarget;
1342         this.newIndex = null;
1343         this.init(elTh);
1344         this.initFrame(); // Needed for DDProxy
1345         this.invalidHandleTypes = {};
1346
1347         // Set top/bottom padding to account for children of nested columns
1348         this.setPadding(10, 0, (this.datatable.getTheadEl().offsetHeight + 10) , 0);
1349
1350         YAHOO.util.Event.on(window, 'resize', function() {
1351             this.initConstraints();
1352         }, this, true);
1353     }
1354     else {
1355     }
1356 };
1357
1358 if(YAHOO.util.DDProxy) {
1359     YAHOO.extend(YAHOO.widget.ColumnDD, YAHOO.util.DDProxy, {
1360         initConstraints: function() {
1361             //Get the top, right, bottom and left positions
1362             var region = YAHOO.util.Dom.getRegion(this.table),
1363                 //Get the element we are working on
1364                 el = this.getEl(),
1365                 //Get the xy position of it
1366                 xy = YAHOO.util.Dom.getXY(el),
1367                 //Get the width and height
1368                 width = parseInt(YAHOO.util.Dom.getStyle(el, 'width'), 10),
1369                 height = parseInt(YAHOO.util.Dom.getStyle(el, 'height'), 10),
1370                 //Set left to x minus left
1371                 left = ((xy[0] - region.left) + 15), //Buffer of 15px
1372                 //Set right to right minus x minus width
1373                 right = ((region.right - xy[0] - width) + 15);
1374     
1375             //Set the constraints based on the above calculations
1376             this.setXConstraint(left, right);
1377             this.setYConstraint(10, 10);            
1378         },
1379         _resizeProxy: function() {
1380             this.constructor.superclass._resizeProxy.apply(this, arguments);
1381             var dragEl = this.getDragEl(),
1382                 el = this.getEl();
1383
1384             YAHOO.util.Dom.setStyle(this.pointer, 'height', (this.table.parentNode.offsetHeight + 10) + 'px');
1385             YAHOO.util.Dom.setStyle(this.pointer, 'display', 'block');
1386             var xy = YAHOO.util.Dom.getXY(el);
1387             YAHOO.util.Dom.setXY(this.pointer, [xy[0], (xy[1] - 5)]);
1388             
1389             YAHOO.util.Dom.setStyle(dragEl, 'height', this.datatable.getContainerEl().offsetHeight + "px");
1390             YAHOO.util.Dom.setStyle(dragEl, 'width', (parseInt(YAHOO.util.Dom.getStyle(dragEl, 'width'),10) + 4) + 'px');
1391             YAHOO.util.Dom.setXY(this.dragEl, xy);
1392         },
1393         onMouseDown: function() {
1394                 this.initConstraints();
1395                 this.resetConstraints();
1396         },
1397         clickValidator: function(e) {
1398             if(!this.column.hidden) {
1399                 var target = YAHOO.util.Event.getTarget(e);
1400                 return ( this.isValidHandleChild(target) &&
1401                             (this.id == this.handleElId ||
1402                                 this.DDM.handleWasClicked(target, this.id)) );
1403             }
1404         },
1405         onDragOver: function(ev, id) {
1406             // Validate target as a Column
1407             var target = this.datatable.getColumn(id);
1408             if(target) {                
1409                 // Validate target as a top-level parent
1410                 var targetIndex = target.getTreeIndex();
1411                 while((targetIndex === null) && target.getParent()) {
1412                     target = target.getParent();
1413                     targetIndex = target.getTreeIndex();
1414                 }
1415                 if(targetIndex !== null) {
1416                     // Are we placing to left or right of target?
1417                     var elTarget = target.getThEl();
1418                     var newIndex = targetIndex;
1419                     var mouseX = YAHOO.util.Event.getPageX(ev),
1420                         targetX = YAHOO.util.Dom.getX(elTarget),
1421                         midX = targetX + ((YAHOO.util.Dom.get(elTarget).offsetWidth)/2),
1422                         currentIndex =  this.column.getTreeIndex();
1423                     
1424                     if (mouseX < midX) {
1425                        YAHOO.util.Dom.setX(this.pointer, targetX);
1426                     } else {
1427                         var targetWidth = parseInt(elTarget.offsetWidth, 10);
1428                         YAHOO.util.Dom.setX(this.pointer, (targetX + targetWidth));
1429                         newIndex++;
1430                     }
1431                     if (targetIndex > currentIndex) {
1432                         newIndex--;
1433                     }
1434                     if(newIndex < 0) {
1435                         newIndex = 0;
1436                     }
1437                     else if(newIndex > this.datatable.getColumnSet().tree[0].length) {
1438                         newIndex = this.datatable.getColumnSet().tree[0].length;
1439                     }
1440                     this.newIndex = newIndex;
1441                 }
1442             }
1443         },
1444         onDragDrop: function() {
1445             this.datatable.reorderColumn(this.column, this.newIndex);
1446         },
1447         endDrag: function() {
1448             this.newIndex = null;
1449             YAHOO.util.Dom.setStyle(this.pointer, 'display', 'none');
1450         }
1451     });
1452 }
1453
1454 /****************************************************************************/
1455 /****************************************************************************/
1456 /****************************************************************************/
1457
1458 /**
1459  * ColumnResizer subclasses DragDrop to support resizeable Columns.
1460  *
1461  * @namespace YAHOO.util
1462  * @class ColumnResizer
1463  * @extends YAHOO.util.DDProxy
1464  * @constructor
1465  * @param oDataTable {YAHOO.widget.DataTable} DataTable instance.
1466  * @param oColumn {YAHOO.widget.Column} Column instance.
1467  * @param elTh {HTMLElement} TH element reference.
1468  * @param sHandleElId {String} DOM ID of the handle element that causes the resize.
1469  * @param elProxy {HTMLElement} Resizer proxy element.
1470  */
1471 YAHOO.util.ColumnResizer = function(oDataTable, oColumn, elTh, sHandleId, elProxy) {
1472     if(oDataTable && oColumn && elTh && sHandleId) {
1473         this.datatable = oDataTable;
1474         this.column = oColumn;
1475         this.headCell = elTh;
1476         this.headCellLiner = oColumn.getThLinerEl();
1477         this.resizerLiner = elTh.firstChild;
1478         this.init(sHandleId, sHandleId, {dragOnly:true, dragElId: elProxy.id});
1479         this.initFrame(); // Needed for proxy
1480         this.resetResizerEl(); // Needed when rowspan > 0
1481
1482         // Set right padding for bug 1858462
1483         this.setPadding(0, 1, 0, 0);
1484     }
1485     else {
1486     }
1487 };
1488
1489 if(YAHOO.util.DD) {
1490     YAHOO.extend(YAHOO.util.ColumnResizer, YAHOO.util.DDProxy, {
1491         /////////////////////////////////////////////////////////////////////////////
1492         //
1493         // Public methods
1494         //
1495         /////////////////////////////////////////////////////////////////////////////
1496         /**
1497          * Resets resizer element.
1498          *
1499          * @method resetResizerEl
1500          */
1501         resetResizerEl : function() {
1502             var resizerStyle = YAHOO.util.Dom.get(this.handleElId).style;
1503             resizerStyle.left = "auto";
1504             resizerStyle.right = 0;
1505             resizerStyle.top = "auto";
1506             resizerStyle.bottom = 0;
1507             resizerStyle.height = this.headCell.offsetHeight+"px";
1508         },
1509     
1510         /////////////////////////////////////////////////////////////////////////////
1511         //
1512         // Public DOM event handlers
1513         //
1514         /////////////////////////////////////////////////////////////////////////////
1515     
1516         /**
1517          * Handles mouseup events on the Column resizer.
1518          *
1519          * @method onMouseUp
1520          * @param e {string} The mouseup event
1521          */
1522         onMouseUp : function(e) {
1523             // Reset height of all resizer els in case TH's have changed height
1524             var allKeys = this.datatable.getColumnSet().keys,
1525                 col;
1526             for(var i=0, len=allKeys.length; i<len; i++) {
1527                 col = allKeys[i];
1528                 if(col._ddResizer) {
1529                     col._ddResizer.resetResizerEl();
1530                 }
1531             }
1532             this.resetResizerEl();
1533             
1534             var el = this.headCellLiner;
1535             var newWidth = el.offsetWidth -
1536                 (parseInt(YAHOO.util.Dom.getStyle(el,"paddingLeft"),10)|0) -
1537                 (parseInt(YAHOO.util.Dom.getStyle(el,"paddingRight"),10)|0);
1538
1539             this.datatable.fireEvent("columnResizeEvent", {column:this.column,target:this.headCell,width:newWidth});
1540         },
1541     
1542         /**
1543          * Handles mousedown events on the Column resizer.
1544          *
1545          * @method onMouseDown
1546          * @param e {string} The mousedown event
1547          */
1548         onMouseDown : function(e) {
1549             this.startWidth = this.headCellLiner.offsetWidth;
1550             this.startX = YAHOO.util.Event.getXY(e)[0];
1551             this.nLinerPadding = (parseInt(YAHOO.util.Dom.getStyle(this.headCellLiner,"paddingLeft"),10)|0) +
1552                     (parseInt(YAHOO.util.Dom.getStyle(this.headCellLiner,"paddingRight"),10)|0);
1553         },
1554     
1555         /**
1556          * Custom clickValidator to ensure Column is not in hidden state.
1557          *
1558          * @method clickValidator
1559          * @param {Event} e
1560          * @private
1561          */
1562         clickValidator : function(e) {
1563             if(!this.column.hidden) {
1564                 var target = YAHOO.util.Event.getTarget(e);
1565                 return ( this.isValidHandleChild(target) &&
1566                             (this.id == this.handleElId ||
1567                                 this.DDM.handleWasClicked(target, this.id)) );
1568             }
1569         },
1570     
1571         /**
1572          * Handles start drag on the Column resizer.
1573          *
1574          * @method startDrag
1575          * @param e {string} The drag event
1576          */
1577         startDrag : function() {
1578             // Shrinks height of all resizer els to not hold open TH els
1579             var allKeys = this.datatable.getColumnSet().keys,
1580                 thisKey = this.column.getKeyIndex(),
1581                 col;
1582             for(var i=0, len=allKeys.length; i<len; i++) {
1583                 col = allKeys[i];
1584                 if(col._ddResizer) {
1585                     YAHOO.util.Dom.get(col._ddResizer.handleElId).style.height = "1em";
1586                 }
1587             }
1588         },
1589
1590         /**
1591          * Handles drag events on the Column resizer.
1592          *
1593          * @method onDrag
1594          * @param e {string} The drag event
1595          */
1596         onDrag : function(e) {
1597             var newX = YAHOO.util.Event.getXY(e)[0];
1598             if(newX > YAHOO.util.Dom.getX(this.headCellLiner)) {
1599                 var offsetX = newX - this.startX;
1600                 var newWidth = this.startWidth + offsetX - this.nLinerPadding;
1601                 if(newWidth > 0) {
1602                     this.datatable.setColumnWidth(this.column, newWidth);
1603                 }
1604             }
1605         }
1606     });
1607 }
1608
1609 /////////////////////////////////////////////////////////////////////////////
1610 //
1611 // Deprecated
1612 //
1613 /////////////////////////////////////////////////////////////////////////////
1614
1615 /**
1616  * @property editorOptions
1617  * @deprecated Pass configs directly to CellEditor constructor. 
1618  */
1619
1620
1621 (function () {
1622
1623 var lang   = YAHOO.lang,
1624     util   = YAHOO.util,
1625     widget = YAHOO.widget,
1626     
1627     Dom    = util.Dom,
1628     Ev     = util.Event,
1629     DT     = widget.DataTable;
1630
1631 /****************************************************************************/
1632 /****************************************************************************/
1633 /****************************************************************************/
1634
1635 /**
1636  * A RecordSet defines and manages a set of Records.
1637  *
1638  * @namespace YAHOO.widget
1639  * @class RecordSet
1640  * @param data {Object || Object[]} An object literal or an array of data.
1641  * @constructor
1642  */
1643 YAHOO.widget.RecordSet = function(data) {
1644     // Internal variables
1645     this._sId = "yui-rs" + widget.RecordSet._nCount;
1646     widget.RecordSet._nCount++;
1647     this._records = [];
1648     //this._length = 0;
1649
1650     if(data) {
1651         if(lang.isArray(data)) {
1652             this.addRecords(data);
1653         }
1654         else if(lang.isObject(data)) {
1655             this.addRecord(data);
1656         }
1657     }
1658
1659 };
1660
1661 var RS = widget.RecordSet;
1662
1663 /**
1664  * Internal class variable to name multiple Recordset instances.
1665  *
1666  * @property RecordSet._nCount
1667  * @type Number
1668  * @private
1669  * @static
1670  */
1671 RS._nCount = 0;
1672
1673 RS.prototype = {
1674
1675     /////////////////////////////////////////////////////////////////////////////
1676     //
1677     // Private member variables
1678     //
1679     /////////////////////////////////////////////////////////////////////////////
1680     /**
1681      * Unique String identifier assigned at instantiation.
1682      *
1683      * @property _sId
1684      * @type String
1685      * @private
1686      */
1687     _sId : null,
1688
1689     /**
1690      * Internal counter of how many Records are in the RecordSet.
1691      *
1692      * @property _length
1693      * @type Number
1694      * @private
1695      * @deprecated No longer used
1696      */
1697     //_length : null,
1698
1699     /////////////////////////////////////////////////////////////////////////////
1700     //
1701     // Private methods
1702     //
1703     /////////////////////////////////////////////////////////////////////////////
1704
1705     /**
1706      * Adds one Record to the RecordSet at the given index. If index is null,
1707      * then adds the Record to the end of the RecordSet.
1708      *
1709      * @method _addRecord
1710      * @param oData {Object} An object literal of data.
1711      * @param index {Number} (optional) Position index.
1712      * @return {YAHOO.widget.Record} A Record instance.
1713      * @private
1714      */
1715     _addRecord : function(oData, index) {
1716         var oRecord = new YAHOO.widget.Record(oData);
1717         
1718         if(YAHOO.lang.isNumber(index) && (index > -1)) {
1719             this._records.splice(index,0,oRecord);
1720         }
1721         else {
1722             //index = this.getLength();
1723             //this._records[index] = oRecord;
1724             this._records[this._records.length] = oRecord;
1725         }
1726         //this._length++;
1727         return oRecord;
1728     },
1729
1730     /**
1731      * Sets/replaces one Record to the RecordSet at the given index.  Existing
1732      * Records with higher indexes are not shifted.  If no index specified, the
1733      * Record is added to the end of the RecordSet.
1734      *
1735      * @method _setRecord
1736      * @param oData {Object} An object literal of data.
1737      * @param index {Number} (optional) Position index.
1738      * @return {YAHOO.widget.Record} A Record instance.
1739      * @private
1740      */
1741     _setRecord : function(oData, index) {
1742         if (!lang.isNumber(index) || index < 0) {
1743             index = this._records.length;
1744         }
1745         return (this._records[index] = new widget.Record(oData));
1746         /*
1747         if(lang.isNumber(index) && (index > -1)) {
1748             this._records[index] = oRecord;
1749             if((index+1) > this.getLength()) {
1750                 this._length = index+1;
1751             }
1752         }
1753         else {
1754             this._records[this.getLength()] = oRecord;
1755             this._length++;
1756         }
1757         return oRecord;
1758         */
1759     },
1760
1761     /**
1762      * Deletes Records from the RecordSet at the given index. If range is null,
1763      * then only one Record is deleted.
1764      *
1765      * @method _deleteRecord
1766      * @param index {Number} Position index.
1767      * @param range {Number} (optional) How many Records to delete
1768      * @private
1769      */
1770     _deleteRecord : function(index, range) {
1771         if(!lang.isNumber(range) || (range < 0)) {
1772             range = 1;
1773         }
1774         this._records.splice(index, range);
1775         //this._length = this._length - range;
1776     },
1777
1778     /////////////////////////////////////////////////////////////////////////////
1779     //
1780     // Public methods
1781     //
1782     /////////////////////////////////////////////////////////////////////////////
1783
1784     /**
1785      * Returns unique name of the RecordSet instance.
1786      *
1787      * @method getId
1788      * @return {String} Unique name of the RecordSet instance.
1789      */
1790     getId : function() {
1791         return this._sId;
1792     },
1793
1794     /**
1795      * Public accessor to the unique name of the RecordSet instance.
1796      *
1797      * @method toString
1798      * @return {String} Unique name of the RecordSet instance.
1799      */
1800     toString : function() {
1801         return "RecordSet instance " + this._sId;
1802     },
1803
1804     /**
1805      * Returns the number of Records held in the RecordSet.
1806      *
1807      * @method getLength
1808      * @return {Number} Number of records in the RecordSet.
1809      */
1810     getLength : function() {
1811             //return this._length;
1812             return this._records.length;
1813     },
1814
1815     /**
1816      * Returns Record by ID or RecordSet position index.
1817      *
1818      * @method getRecord
1819      * @param record {YAHOO.widget.Record | Number | String} Record instance,
1820      * RecordSet position index, or Record ID.
1821      * @return {YAHOO.widget.Record} Record object.
1822      */
1823     getRecord : function(record) {
1824         var i;
1825         if(record instanceof widget.Record) {
1826             for(i=0; i<this._records.length; i++) {
1827                 if(this._records[i] && (this._records[i]._sId === record._sId)) {
1828                     return record;
1829                 }
1830             }
1831         }
1832         else if(lang.isNumber(record)) {
1833             if((record > -1) && (record < this.getLength())) {
1834                 return this._records[record];
1835             }
1836         }
1837         else if(lang.isString(record)) {
1838             for(i=0; i<this._records.length; i++) {
1839                 if(this._records[i] && (this._records[i]._sId === record)) {
1840                     return this._records[i];
1841                 }
1842             }
1843         }
1844         // Not a valid Record for this RecordSet
1845         return null;
1846
1847     },
1848
1849     /**
1850      * Returns an array of Records from the RecordSet.
1851      *
1852      * @method getRecords
1853      * @param index {Number} (optional) Recordset position index of which Record to
1854      * start at.
1855      * @param range {Number} (optional) Number of Records to get.
1856      * @return {YAHOO.widget.Record[]} Array of Records starting at given index and
1857      * length equal to given range. If index is not given, all Records are returned.
1858      */
1859     getRecords : function(index, range) {
1860         if(!lang.isNumber(index)) {
1861             return this._records;
1862         }
1863         if(!lang.isNumber(range)) {
1864             return this._records.slice(index);
1865         }
1866         return this._records.slice(index, index+range);
1867     },
1868
1869     /**
1870      * Returns a boolean indicating whether Records exist in the RecordSet at the
1871      * specified index range.  Returns true if and only if a Record exists at each
1872      * index in the range.
1873      * @method hasRecords
1874      * @param index
1875      * @param range
1876      * @return {Boolean} true if all indices are populated in the RecordSet
1877      */
1878     hasRecords : function (index, range) {
1879         var recs = this.getRecords(index,range);
1880         for (var i = 0; i < range; ++i) {
1881             if (typeof recs[i] === 'undefined') {
1882                 return false;
1883             }
1884         }
1885         return true;
1886     },
1887
1888     /**
1889      * Returns current position index for the given Record.
1890      *
1891      * @method getRecordIndex
1892      * @param oRecord {YAHOO.widget.Record} Record instance.
1893      * @return {Number} Record's RecordSet position index.
1894      */
1895
1896     getRecordIndex : function(oRecord) {
1897         if(oRecord) {
1898             for(var i=this._records.length-1; i>-1; i--) {
1899                 if(this._records[i] && oRecord.getId() === this._records[i].getId()) {
1900                     return i;
1901                 }
1902             }
1903         }
1904         return null;
1905
1906     },
1907
1908     /**
1909      * Adds one Record to the RecordSet at the given index. If index is null,
1910      * then adds the Record to the end of the RecordSet.
1911      *
1912      * @method addRecord
1913      * @param oData {Object} An object literal of data.
1914      * @param index {Number} (optional) Position index.
1915      * @return {YAHOO.widget.Record} A Record instance.
1916      */
1917     addRecord : function(oData, index) {
1918         if(lang.isObject(oData)) {
1919             var oRecord = this._addRecord(oData, index);
1920             this.fireEvent("recordAddEvent",{record:oRecord,data:oData});
1921             return oRecord;
1922         }
1923         else {
1924             return null;
1925         }
1926     },
1927
1928     /**
1929      * Adds multiple Records at once to the RecordSet at the given index with the
1930      * given object literal data. If index is null, then the new Records are
1931      * added to the end of the RecordSet.
1932      *
1933      * @method addRecords
1934      * @param aData {Object[]} An object literal data or an array of data object literals.
1935      * @param index {Number} (optional) Position index.
1936      * @return {YAHOO.widget.Record[]} An array of Record instances.
1937      */
1938     addRecords : function(aData, index) {
1939         if(lang.isArray(aData)) {
1940             var newRecords = [],
1941                 idx,i,len;
1942
1943             index = lang.isNumber(index) ? index : this._records.length;
1944             idx = index;
1945
1946             // Can't go backwards bc we need to preserve order
1947             for(i=0,len=aData.length; i<len; ++i) {
1948                 if(lang.isObject(aData[i])) {
1949                     var record = this._addRecord(aData[i], idx++);
1950                     newRecords.push(record);
1951                 }
1952            }
1953             this.fireEvent("recordsAddEvent",{records:newRecords,data:aData});
1954            return newRecords;
1955         }
1956         else if(lang.isObject(aData)) {
1957             var oRecord = this._addRecord(aData);
1958             this.fireEvent("recordsAddEvent",{records:[oRecord],data:aData});
1959             return oRecord;
1960         }
1961         else {
1962             return null;
1963         }
1964     },
1965
1966     /**
1967      * Sets or replaces one Record to the RecordSet at the given index. Unlike
1968      * addRecord, an existing Record at that index is not shifted to preserve it.
1969      * If no index is specified, it adds the Record to the end of the RecordSet.
1970      *
1971      * @method setRecord
1972      * @param oData {Object} An object literal of data.
1973      * @param index {Number} (optional) Position index.
1974      * @return {YAHOO.widget.Record} A Record instance.
1975      */
1976     setRecord : function(oData, index) {
1977         if(lang.isObject(oData)) {
1978             var oRecord = this._setRecord(oData, index);
1979             this.fireEvent("recordSetEvent",{record:oRecord,data:oData});
1980             return oRecord;
1981         }
1982         else {
1983             return null;
1984         }
1985     },
1986
1987     /**
1988      * Sets or replaces multiple Records at once to the RecordSet with the given
1989      * data, starting at the given index. If index is not specified, then the new
1990      * Records are added to the end of the RecordSet.
1991      *
1992      * @method setRecords
1993      * @param aData {Object[]} An array of object literal data.
1994      * @param index {Number} (optional) Position index.
1995      * @return {YAHOO.widget.Record[]} An array of Record instances.
1996      */
1997     setRecords : function(aData, index) {
1998         var Rec   = widget.Record,
1999             a     = lang.isArray(aData) ? aData : [aData],
2000             added = [],
2001             i = 0, l = a.length, j = 0;
2002
2003         index = parseInt(index,10)|0;
2004
2005         for(; i < l; ++i) {
2006             if (typeof a[i] === 'object' && a[i]) {
2007                 added[j++] = this._records[index + i] = new Rec(a[i]);
2008             }
2009         }
2010
2011         this.fireEvent("recordsSetEvent",{records:added,data:aData});
2012         // Backward compatibility for bug 1918245
2013         this.fireEvent("recordsSet",{records:added,data:aData});
2014
2015         if (a.length && !added.length) {
2016         }
2017
2018         return added.length > 1 ? added : added[0];
2019     },
2020
2021     /**
2022      * Updates given Record with given data.
2023      *
2024      * @method updateRecord
2025      * @param record {YAHOO.widget.Record | Number | String} A Record instance,
2026      * a RecordSet position index, or a Record ID.
2027      * @param oData {Object} Object literal of new data.
2028      * @return {YAHOO.widget.Record} Updated Record, or null.
2029      */
2030     updateRecord : function(record, oData) {
2031         var oRecord = this.getRecord(record);
2032         if(oRecord && lang.isObject(oData)) {
2033             // Copy data from the Record for the event that gets fired later
2034             var oldData = {};
2035             for(var key in oRecord._oData) {
2036                 if(lang.hasOwnProperty(oRecord._oData, key)) {
2037                     oldData[key] = oRecord._oData[key];
2038                 }
2039             }
2040             oRecord._oData = oData;
2041             this.fireEvent("recordUpdateEvent",{record:oRecord,newData:oData,oldData:oldData});
2042             return oRecord;
2043         }
2044         else {
2045             return null;
2046         }
2047     },
2048
2049     /**
2050      * @method updateKey
2051      * @deprecated Use updateRecordValue
2052      */
2053     updateKey : function(record, sKey, oData) {
2054         this.updateRecordValue(record, sKey, oData);
2055     },
2056     /**
2057      * Sets given Record at given key to given data.
2058      *
2059      * @method updateRecordValue
2060      * @param record {YAHOO.widget.Record | Number | String} A Record instance,
2061      * a RecordSet position index, or a Record ID.
2062      * @param sKey {String} Key name.
2063      * @param oData {Object} New data.
2064      */
2065     updateRecordValue : function(record, sKey, oData) {
2066         var oRecord = this.getRecord(record);
2067         if(oRecord) {
2068             var oldData = null;
2069             var keyValue = oRecord._oData[sKey];
2070             // Copy data from the Record for the event that gets fired later
2071             if(keyValue && lang.isObject(keyValue)) {
2072                 oldData = {};
2073                 for(var key in keyValue)  {
2074                     if(lang.hasOwnProperty(keyValue, key)) {
2075                         oldData[key] = keyValue[key];
2076                     }
2077                 }
2078             }
2079             // Copy by value
2080             else {
2081                 oldData = keyValue;
2082             }
2083
2084             oRecord._oData[sKey] = oData;
2085             this.fireEvent("keyUpdateEvent",{record:oRecord,key:sKey,newData:oData,oldData:oldData});
2086             this.fireEvent("recordValueUpdateEvent",{record:oRecord,key:sKey,newData:oData,oldData:oldData});
2087         }
2088         else {
2089         }
2090     },
2091
2092     /**
2093      * Replaces all Records in RecordSet with new object literal data.
2094      *
2095      * @method replaceRecords
2096      * @param data {Object || Object[]} An object literal of data or an array of
2097      * data object literals.
2098      * @return {YAHOO.widget.Record || YAHOO.widget.Record[]} A Record instance or
2099      * an array of Records.
2100      */
2101     replaceRecords : function(data) {
2102         this.reset();
2103         return this.addRecords(data);
2104     },
2105
2106     /**
2107      * Sorts all Records by given function. Records keep their unique IDs but will
2108      * have new RecordSet position indexes.
2109      *
2110      * @method sortRecords
2111      * @param fnSort {Function} Reference to a sort function.
2112      * @param desc {Boolean} True if sort direction is descending, false if sort
2113      * direction is ascending.
2114      * @return {YAHOO.widget.Record[]} Sorted array of Records.
2115      */
2116     sortRecords : function(fnSort, desc) {
2117         return this._records.sort(function(a, b) {return fnSort(a, b, desc);});
2118     },
2119
2120     /**
2121      * Reverses all Records, so ["one", "two", "three"] becomes ["three", "two", "one"].
2122      *
2123      * @method reverseRecords
2124      * @return {YAHOO.widget.Record[]} Reverse-sorted array of Records.
2125      */
2126     reverseRecords : function() {
2127         return this._records.reverse();
2128     },
2129
2130     /**
2131      * Removes the Record at the given position index from the RecordSet. If a range
2132      * is also provided, removes that many Records, starting from the index. Length
2133      * of RecordSet is correspondingly shortened.
2134      *
2135      * @method deleteRecord
2136      * @param index {Number} Record's RecordSet position index.
2137      * @param range {Number} (optional) How many Records to delete.
2138      * @return {Object} A copy of the data held by the deleted Record.
2139      */
2140     deleteRecord : function(index) {
2141         if(lang.isNumber(index) && (index > -1) && (index < this.getLength())) {
2142             // Copy data from the Record for the event that gets fired later
2143             var oData = widget.DataTable._cloneObject(this.getRecord(index).getData());
2144             
2145             this._deleteRecord(index);
2146             this.fireEvent("recordDeleteEvent",{data:oData,index:index});
2147             return oData;
2148         }
2149         else {
2150             return null;
2151         }
2152     },
2153
2154     /**
2155      * Removes the Record at the given position index from the RecordSet. If a range
2156      * is also provided, removes that many Records, starting from the index. Length
2157      * of RecordSet is correspondingly shortened.
2158      *
2159      * @method deleteRecords
2160      * @param index {Number} Record's RecordSet position index.
2161      * @param range {Number} (optional) How many Records to delete.
2162      * @return {Object[]} An array of copies of the data held by the deleted Records.     
2163      */
2164     deleteRecords : function(index, range) {
2165         if(!lang.isNumber(range)) {
2166             range = 1;
2167         }
2168         if(lang.isNumber(index) && (index > -1) && (index < this.getLength())) {
2169             var recordsToDelete = this.getRecords(index, range);
2170             // Copy data from each Record for the event that gets fired later
2171             var deletedData = [];
2172             
2173             for(var i=0; i<recordsToDelete.length; i++) {
2174                 deletedData[deletedData.length] = widget.DataTable._cloneObject(recordsToDelete[i]);
2175             }
2176             this._deleteRecord(index, range);
2177
2178             this.fireEvent("recordsDeleteEvent",{data:deletedData,index:index});
2179
2180             return deletedData;
2181         }
2182         else {
2183             return null;
2184         }
2185     },
2186
2187     /**
2188      * Deletes all Records from the RecordSet.
2189      *
2190      * @method reset
2191      */
2192     reset : function() {
2193         this._records = [];
2194         //this._length = 0;
2195         this.fireEvent("resetEvent");
2196     }
2197 };
2198
2199 /////////////////////////////////////////////////////////////////////////////
2200 //
2201 // Custom Events
2202 //
2203 /////////////////////////////////////////////////////////////////////////////
2204
2205 // RecordSet uses EventProvider
2206 lang.augmentProto(RS, util.EventProvider);
2207
2208 /**
2209  * Fired when a new Record is added to the RecordSet.
2210  *
2211  * @event recordAddEvent
2212  * @param oArgs.record {YAHOO.widget.Record} The Record instance.
2213  * @param oArgs.data {Object} Data added.
2214  */
2215
2216 /**
2217  * Fired when multiple Records are added to the RecordSet at once.
2218  *
2219  * @event recordsAddEvent
2220  * @param oArgs.records {YAHOO.widget.Record[]} An array of Record instances.
2221  * @param oArgs.data {Object[]} Data added.
2222  */
2223
2224 /**
2225  * Fired when a Record is set in the RecordSet.
2226  *
2227  * @event recordSetEvent
2228  * @param oArgs.record {YAHOO.widget.Record} The Record instance.
2229  * @param oArgs.data {Object} Data added.
2230  */
2231
2232 /**
2233  * Fired when multiple Records are set in the RecordSet at once.
2234  *
2235  * @event recordsSetEvent
2236  * @param oArgs.records {YAHOO.widget.Record[]} An array of Record instances.
2237  * @param oArgs.data {Object[]} Data added.
2238  */
2239
2240 /**
2241  * Fired when a Record is updated with new data.
2242  *
2243  * @event recordUpdateEvent
2244  * @param oArgs.record {YAHOO.widget.Record} The Record instance.
2245  * @param oArgs.newData {Object} New data.
2246  * @param oArgs.oldData {Object} Old data.
2247  */
2248
2249 /**
2250  * Fired when a Record is deleted from the RecordSet.
2251  *
2252  * @event recordDeleteEvent
2253  * @param oArgs.data {Object} A copy of the data held by the Record,
2254  * or an array of data object literals if multiple Records were deleted at once.
2255  * @param oArgs.index {Object} Index of the deleted Record.
2256  */
2257
2258 /**
2259  * Fired when multiple Records are deleted from the RecordSet at once.
2260  *
2261  * @event recordsDeleteEvent
2262  * @param oArgs.data {Object[]} An array of data object literals copied
2263  * from the Records.
2264  * @param oArgs.index {Object} Index of the first deleted Record.
2265  */
2266
2267 /**
2268  * Fired when all Records are deleted from the RecordSet at once.
2269  *
2270  * @event resetEvent
2271  */
2272
2273 /**
2274  * @event keyUpdateEvent    
2275  * @deprecated Use recordValueUpdateEvent     
2276  */
2277
2278 /**
2279  * Fired when a Record value is updated with new data.
2280  *
2281  * @event recordValueUpdateEvent
2282  * @param oArgs.record {YAHOO.widget.Record} The Record instance.
2283  * @param oArgs.key {String} The updated key.
2284  * @param oArgs.newData {Object} New data.
2285  * @param oArgs.oldData {Object} Old data.
2286  *
2287  */
2288
2289
2290 /****************************************************************************/
2291 /****************************************************************************/
2292 /****************************************************************************/
2293
2294 /**
2295  * The Record class defines a DataTable record.
2296  *
2297  * @namespace YAHOO.widget
2298  * @class Record
2299  * @constructor
2300  * @param oConfigs {Object} (optional) Object literal of key/value pairs.
2301  */
2302 YAHOO.widget.Record = function(oLiteral) {
2303     this._nCount = widget.Record._nCount;
2304     this._sId = "yui-rec" + this._nCount;
2305     widget.Record._nCount++;
2306     this._oData = {};
2307     if(lang.isObject(oLiteral)) {
2308         for(var sKey in oLiteral) {
2309             if(lang.hasOwnProperty(oLiteral, sKey)) {
2310                 this._oData[sKey] = oLiteral[sKey];
2311             }
2312         }
2313     }
2314 };
2315
2316 /////////////////////////////////////////////////////////////////////////////
2317 //
2318 // Private member variables
2319 //
2320 /////////////////////////////////////////////////////////////////////////////
2321
2322 /**
2323  * Internal class variable to give unique IDs to Record instances.
2324  *
2325  * @property Record._nCount
2326  * @type Number
2327  * @private
2328  */
2329 YAHOO.widget.Record._nCount = 0;
2330
2331 YAHOO.widget.Record.prototype = {
2332     /**
2333      * Immutable unique count assigned at instantiation. Remains constant while a
2334      * Record's position index can change from sorting.
2335      *
2336      * @property _nCount
2337      * @type Number
2338      * @private
2339      */
2340     _nCount : null,
2341
2342     /**
2343      * Immutable unique ID assigned at instantiation. Remains constant while a
2344      * Record's position index can change from sorting.
2345      *
2346      * @property _sId
2347      * @type String
2348      * @private
2349      */
2350     _sId : null,
2351
2352     /**
2353      * Holds data for the Record in an object literal.
2354      *
2355      * @property _oData
2356      * @type Object
2357      * @private
2358      */
2359     _oData : null,
2360
2361     /////////////////////////////////////////////////////////////////////////////
2362     //
2363     // Public member variables
2364     //
2365     /////////////////////////////////////////////////////////////////////////////
2366
2367     /////////////////////////////////////////////////////////////////////////////
2368     //
2369     // Public methods
2370     //
2371     /////////////////////////////////////////////////////////////////////////////
2372
2373     /**
2374      * Returns unique count assigned at instantiation.
2375      *
2376      * @method getCount
2377      * @return Number
2378      */
2379     getCount : function() {
2380         return this._nCount;
2381     },
2382
2383     /**
2384      * Returns unique ID assigned at instantiation.
2385      *
2386      * @method getId
2387      * @return String
2388      */
2389     getId : function() {
2390         return this._sId;
2391     },
2392
2393     /**
2394      * Returns data for the Record for a field if given, or the entire object
2395      * literal otherwise.
2396      *
2397      * @method getData
2398      * @param sField {String} (Optional) The field from which to retrieve data value.
2399      * @return Object
2400      */
2401     getData : function(sField) {
2402         if(lang.isString(sField)) {
2403             return this._oData[sField];
2404         }
2405         else {
2406             return this._oData;
2407         }
2408     },
2409
2410     /**
2411      * Sets given data at the given key. Use the RecordSet method setValue to trigger
2412      * events. 
2413      *
2414      * @method setData
2415      * @param sKey {String} The key of the new value.
2416      * @param oData {MIXED} The new value.
2417      */
2418     setData : function(sKey, oData) {
2419         this._oData[sKey] = oData;
2420     }
2421 };
2422
2423 })();
2424
2425 (function () {
2426
2427 var lang   = YAHOO.lang,
2428     util   = YAHOO.util,
2429     widget = YAHOO.widget,
2430     ua     = YAHOO.env.ua,
2431     
2432     Dom    = util.Dom,
2433     Ev     = util.Event,
2434     DS     = util.DataSourceBase;
2435
2436 /**
2437  * The DataTable widget provides a progressively enhanced DHTML control for
2438  * displaying tabular data across A-grade browsers.
2439  *
2440  * @module datatable
2441  * @requires yahoo, dom, event, element, datasource
2442  * @optional dragdrop, dragdrop
2443  * @title DataTable Widget
2444  */
2445
2446 /****************************************************************************/
2447 /****************************************************************************/
2448 /****************************************************************************/
2449
2450 /**
2451  * DataTable class for the YUI DataTable widget.
2452  *
2453  * @namespace YAHOO.widget
2454  * @class DataTable
2455  * @extends YAHOO.util.Element
2456  * @constructor
2457  * @param elContainer {HTMLElement} Container element for the TABLE.
2458  * @param aColumnDefs {Object[]} Array of object literal Column definitions.
2459  * @param oDataSource {YAHOO.util.DataSource} DataSource instance.
2460  * @param oConfigs {object} (optional) Object literal of configuration values.
2461  */
2462 YAHOO.widget.DataTable = function(elContainer,aColumnDefs,oDataSource,oConfigs) {
2463     var DT = widget.DataTable;
2464     
2465     ////////////////////////////////////////////////////////////////////////////
2466     // Backward compatibility for SDT, but prevent infinite loops
2467     
2468     if(oConfigs && oConfigs.scrollable) {
2469         return new YAHOO.widget.ScrollingDataTable(elContainer,aColumnDefs,oDataSource,oConfigs);
2470     }
2471     
2472     ////////////////////////////////////////////////////////////////////////////
2473     // Initialization
2474
2475     // Internal vars
2476     this._nIndex = DT._nCount;
2477     this._sId = "yui-dt"+this._nIndex;
2478     this._oChainRender = new YAHOO.util.Chain();
2479     this._oChainRender.subscribe("end",this._onRenderChainEnd, this, true);
2480
2481     // Initialize configs
2482     this._initConfigs(oConfigs);
2483
2484     // Initialize DataSource
2485     this._initDataSource(oDataSource);
2486     if(!this._oDataSource) {
2487         return;
2488     }
2489
2490     // Initialize ColumnSet
2491     this._initColumnSet(aColumnDefs);
2492     if(!this._oColumnSet) {
2493         return;
2494     }
2495
2496     // Initialize RecordSet
2497     this._initRecordSet();
2498     if(!this._oRecordSet) {
2499     }
2500
2501     // Initialize Attributes
2502     DT.superclass.constructor.call(this, elContainer, this.configs);
2503
2504     // Initialize DOM elements
2505     var okDom = this._initDomElements(elContainer);
2506     if(!okDom) {
2507         return;
2508     }
2509             
2510     // Show message as soon as config is available
2511     this.showTableMessage(this.get("MSG_LOADING"), DT.CLASS_LOADING);
2512     
2513     ////////////////////////////////////////////////////////////////////////////
2514     // Once per instance
2515     this._initEvents();
2516
2517     DT._nCount++;
2518     DT._nCurrentCount++;
2519     
2520     ////////////////////////////////////////////////////////////////////////////
2521     // Data integration
2522
2523     // Send a simple initial request
2524     var oCallback = {
2525         success : this.onDataReturnSetRows,
2526         failure : this.onDataReturnSetRows,
2527         scope   : this,
2528         argument: this.getState()
2529     };
2530     
2531     var initialLoad = this.get("initialLoad");
2532     if(initialLoad === true) {
2533         this._oDataSource.sendRequest(this.get("initialRequest"), oCallback);
2534     }
2535     // Do not send an initial request at all
2536     else if(initialLoad === false) {
2537         this.showTableMessage(this.get("MSG_EMPTY"), DT.CLASS_EMPTY);
2538     }
2539     // Send an initial request with a custom payload
2540     else {
2541         var oCustom = initialLoad || {};
2542         oCallback.argument = oCustom.argument || {};
2543         this._oDataSource.sendRequest(oCustom.request, oCallback);
2544     }
2545 };
2546
2547 var DT = widget.DataTable;
2548
2549 /////////////////////////////////////////////////////////////////////////////
2550 //
2551 // Public constants
2552 //
2553 /////////////////////////////////////////////////////////////////////////////
2554
2555 lang.augmentObject(DT, {
2556
2557     /**
2558      * Class name assigned to outer DataTable container.
2559      *
2560      * @property DataTable.CLASS_DATATABLE
2561      * @type String
2562      * @static
2563      * @final
2564      * @default "yui-dt"
2565      */
2566     CLASS_DATATABLE : "yui-dt",
2567
2568     /**
2569      * Class name assigned to liner DIV elements.
2570      *
2571      * @property DataTable.CLASS_LINER
2572      * @type String
2573      * @static
2574      * @final
2575      * @default "yui-dt-liner"
2576      */
2577     CLASS_LINER : "yui-dt-liner",
2578
2579     /**
2580      * Class name assigned to display label elements.
2581      *
2582      * @property DataTable.CLASS_LABEL
2583      * @type String
2584      * @static
2585      * @final
2586      * @default "yui-dt-label"
2587      */
2588     CLASS_LABEL : "yui-dt-label",
2589
2590     /**
2591      * Class name assigned to messaging elements.
2592      *
2593      * @property DataTable.CLASS_MESSAGE
2594      * @type String
2595      * @static
2596      * @final
2597      * @default "yui-dt-message"
2598      */
2599     CLASS_MESSAGE : "yui-dt-message",
2600
2601     /**
2602      * Class name assigned to mask element when DataTable is disabled.
2603      *
2604      * @property DataTable.CLASS_MASK
2605      * @type String
2606      * @static
2607      * @final
2608      * @default "yui-dt-mask"
2609      */
2610     CLASS_MASK : "yui-dt-mask",
2611
2612     /**
2613      * Class name assigned to data elements.
2614      *
2615      * @property DataTable.CLASS_DATA
2616      * @type String
2617      * @static
2618      * @final
2619      * @default "yui-dt-data"
2620      */
2621     CLASS_DATA : "yui-dt-data",
2622
2623     /**
2624      * Class name assigned to Column drag target.
2625      *
2626      * @property DataTable.CLASS_COLTARGET
2627      * @type String
2628      * @static
2629      * @final
2630      * @default "yui-dt-coltarget"
2631      */
2632     CLASS_COLTARGET : "yui-dt-coltarget",
2633
2634     /**
2635      * Class name assigned to resizer handle elements.
2636      *
2637      * @property DataTable.CLASS_RESIZER
2638      * @type String
2639      * @static
2640      * @final
2641      * @default "yui-dt-resizer"
2642      */
2643     CLASS_RESIZER : "yui-dt-resizer",
2644
2645     /**
2646      * Class name assigned to resizer liner elements.
2647      *
2648      * @property DataTable.CLASS_RESIZERLINER
2649      * @type String
2650      * @static
2651      * @final
2652      * @default "yui-dt-resizerliner"
2653      */
2654     CLASS_RESIZERLINER : "yui-dt-resizerliner",
2655
2656     /**
2657      * Class name assigned to resizer proxy elements.
2658      *
2659      * @property DataTable.CLASS_RESIZERPROXY
2660      * @type String
2661      * @static
2662      * @final
2663      * @default "yui-dt-resizerproxy"
2664      */
2665     CLASS_RESIZERPROXY : "yui-dt-resizerproxy",
2666
2667     /**
2668      * Class name assigned to CellEditor container elements.
2669      *
2670      * @property DataTable.CLASS_EDITOR
2671      * @type String
2672      * @static
2673      * @final
2674      * @default "yui-dt-editor"
2675      */
2676     CLASS_EDITOR : "yui-dt-editor",
2677
2678     /**
2679      * Class name assigned to paginator container elements.
2680      *
2681      * @property DataTable.CLASS_PAGINATOR
2682      * @type String
2683      * @static
2684      * @final
2685      * @default "yui-dt-paginator"
2686      */
2687     CLASS_PAGINATOR : "yui-dt-paginator",
2688
2689     /**
2690      * Class name assigned to page number indicators.
2691      *
2692      * @property DataTable.CLASS_PAGE
2693      * @type String
2694      * @static
2695      * @final
2696      * @default "yui-dt-page"
2697      */
2698     CLASS_PAGE : "yui-dt-page",
2699
2700     /**
2701      * Class name assigned to default indicators.
2702      *
2703      * @property DataTable.CLASS_DEFAULT
2704      * @type String
2705      * @static
2706      * @final
2707      * @default "yui-dt-default"
2708      */
2709     CLASS_DEFAULT : "yui-dt-default",
2710
2711     /**
2712      * Class name assigned to previous indicators.
2713      *
2714      * @property DataTable.CLASS_PREVIOUS
2715      * @type String
2716      * @static
2717      * @final
2718      * @default "yui-dt-previous"
2719      */
2720     CLASS_PREVIOUS : "yui-dt-previous",
2721
2722     /**
2723      * Class name assigned next indicators.
2724      *
2725      * @property DataTable.CLASS_NEXT
2726      * @type String
2727      * @static
2728      * @final
2729      * @default "yui-dt-next"
2730      */
2731     CLASS_NEXT : "yui-dt-next",
2732
2733     /**
2734      * Class name assigned to first elements.
2735      *
2736      * @property DataTable.CLASS_FIRST
2737      * @type String
2738      * @static
2739      * @final
2740      * @default "yui-dt-first"
2741      */
2742     CLASS_FIRST : "yui-dt-first",
2743
2744     /**
2745      * Class name assigned to last elements.
2746      *
2747      * @property DataTable.CLASS_LAST
2748      * @type String
2749      * @static
2750      * @final
2751      * @default "yui-dt-last"
2752      */
2753     CLASS_LAST : "yui-dt-last",
2754
2755     /**
2756      * Class name assigned to even elements.
2757      *
2758      * @property DataTable.CLASS_EVEN
2759      * @type String
2760      * @static
2761      * @final
2762      * @default "yui-dt-even"
2763      */
2764     CLASS_EVEN : "yui-dt-even",
2765
2766     /**
2767      * Class name assigned to odd elements.
2768      *
2769      * @property DataTable.CLASS_ODD
2770      * @type String
2771      * @static
2772      * @final
2773      * @default "yui-dt-odd"
2774      */
2775     CLASS_ODD : "yui-dt-odd",
2776
2777     /**
2778      * Class name assigned to selected elements.
2779      *
2780      * @property DataTable.CLASS_SELECTED
2781      * @type String
2782      * @static
2783      * @final
2784      * @default "yui-dt-selected"
2785      */
2786     CLASS_SELECTED : "yui-dt-selected",
2787
2788     /**
2789      * Class name assigned to highlighted elements.
2790      *
2791      * @property DataTable.CLASS_HIGHLIGHTED
2792      * @type String
2793      * @static
2794      * @final
2795      * @default "yui-dt-highlighted"
2796      */
2797     CLASS_HIGHLIGHTED : "yui-dt-highlighted",
2798
2799     /**
2800      * Class name assigned to hidden elements.
2801      *
2802      * @property DataTable.CLASS_HIDDEN
2803      * @type String
2804      * @static
2805      * @final
2806      * @default "yui-dt-hidden"
2807      */
2808     CLASS_HIDDEN : "yui-dt-hidden",
2809
2810     /**
2811      * Class name assigned to disabled elements.
2812      *
2813      * @property DataTable.CLASS_DISABLED
2814      * @type String
2815      * @static
2816      * @final
2817      * @default "yui-dt-disabled"
2818      */
2819     CLASS_DISABLED : "yui-dt-disabled",
2820
2821     /**
2822      * Class name assigned to empty indicators.
2823      *
2824      * @property DataTable.CLASS_EMPTY
2825      * @type String
2826      * @static
2827      * @final
2828      * @default "yui-dt-empty"
2829      */
2830     CLASS_EMPTY : "yui-dt-empty",
2831
2832     /**
2833      * Class name assigned to loading indicatorx.
2834      *
2835      * @property DataTable.CLASS_LOADING
2836      * @type String
2837      * @static
2838      * @final
2839      * @default "yui-dt-loading"
2840      */
2841     CLASS_LOADING : "yui-dt-loading",
2842
2843     /**
2844      * Class name assigned to error indicators.
2845      *
2846      * @property DataTable.CLASS_ERROR
2847      * @type String
2848      * @static
2849      * @final
2850      * @default "yui-dt-error"
2851      */
2852     CLASS_ERROR : "yui-dt-error",
2853
2854     /**
2855      * Class name assigned to editable elements.
2856      *
2857      * @property DataTable.CLASS_EDITABLE
2858      * @type String
2859      * @static
2860      * @final
2861      * @default "yui-dt-editable"
2862      */
2863     CLASS_EDITABLE : "yui-dt-editable",
2864
2865     /**
2866      * Class name assigned to draggable elements.
2867      *
2868      * @property DataTable.CLASS_DRAGGABLE
2869      * @type String
2870      * @static
2871      * @final
2872      * @default "yui-dt-draggable"
2873      */
2874     CLASS_DRAGGABLE : "yui-dt-draggable",
2875
2876     /**
2877      * Class name assigned to resizeable elements.
2878      *
2879      * @property DataTable.CLASS_RESIZEABLE
2880      * @type String
2881      * @static
2882      * @final
2883      * @default "yui-dt-resizeable"
2884      */
2885     CLASS_RESIZEABLE : "yui-dt-resizeable",
2886
2887     /**
2888      * Class name assigned to scrollable elements.
2889      *
2890      * @property DataTable.CLASS_SCROLLABLE
2891      * @type String
2892      * @static
2893      * @final
2894      * @default "yui-dt-scrollable"
2895      */
2896     CLASS_SCROLLABLE : "yui-dt-scrollable",
2897
2898     /**
2899      * Class name assigned to sortable elements.
2900      *
2901      * @property DataTable.CLASS_SORTABLE
2902      * @type String
2903      * @static
2904      * @final
2905      * @default "yui-dt-sortable"
2906      */
2907     CLASS_SORTABLE : "yui-dt-sortable",
2908
2909     /**
2910      * Class name assigned to ascending elements.
2911      *
2912      * @property DataTable.CLASS_ASC
2913      * @type String
2914      * @static
2915      * @final
2916      * @default "yui-dt-asc"
2917      */
2918     CLASS_ASC : "yui-dt-asc",
2919
2920     /**
2921      * Class name assigned to descending elements.
2922      *
2923      * @property DataTable.CLASS_DESC
2924      * @type String
2925      * @static
2926      * @final
2927      * @default "yui-dt-desc"
2928      */
2929     CLASS_DESC : "yui-dt-desc",
2930
2931     /**
2932      * Class name assigned to BUTTON elements and/or container elements.
2933      *
2934      * @property DataTable.CLASS_BUTTON
2935      * @type String
2936      * @static
2937      * @final
2938      * @default "yui-dt-button"
2939      */
2940     CLASS_BUTTON : "yui-dt-button",
2941
2942     /**
2943      * Class name assigned to INPUT TYPE=CHECKBOX elements and/or container elements.
2944      *
2945      * @property DataTable.CLASS_CHECKBOX
2946      * @type String
2947      * @static
2948      * @final
2949      * @default "yui-dt-checkbox"
2950      */
2951     CLASS_CHECKBOX : "yui-dt-checkbox",
2952
2953     /**
2954      * Class name assigned to SELECT elements and/or container elements.
2955      *
2956      * @property DataTable.CLASS_DROPDOWN
2957      * @type String
2958      * @static
2959      * @final
2960      * @default "yui-dt-dropdown"
2961      */
2962     CLASS_DROPDOWN : "yui-dt-dropdown",
2963
2964     /**
2965      * Class name assigned to INPUT TYPE=RADIO elements and/or container elements.
2966      *
2967      * @property DataTable.CLASS_RADIO
2968      * @type String
2969      * @static
2970      * @final
2971      * @default "yui-dt-radio"
2972      */
2973     CLASS_RADIO : "yui-dt-radio",
2974
2975     /////////////////////////////////////////////////////////////////////////
2976     //
2977     // Private static properties
2978     //
2979     /////////////////////////////////////////////////////////////////////////
2980
2981     /**
2982      * Internal class variable for indexing multiple DataTable instances.
2983      *
2984      * @property DataTable._nCount
2985      * @type Number
2986      * @private
2987      * @static
2988      */
2989     _nCount : 0,
2990
2991     /**
2992      * Internal class variable tracking current number of DataTable instances,
2993      * so that certain class values can be reset when all instances are destroyed.          
2994      *
2995      * @property DataTable._nCurrentCount
2996      * @type Number
2997      * @private
2998      * @static
2999      */
3000     _nCurrentCount : 0,
3001
3002     /**
3003      * Reference to the STYLE node that is dynamically created and updated
3004      * in order to manage Column widths.
3005      *
3006      * @property DataTable._elDynStyleNode
3007      * @type HTMLElement
3008      * @private
3009      * @static     
3010      */
3011     _elDynStyleNode : null,
3012
3013     /**
3014      * Set to true if _elDynStyleNode cannot be populated due to browser incompatibility.
3015      *
3016      * @property DataTable._bDynStylesFallback
3017      * @type boolean
3018      * @private
3019      * @static     
3020      */
3021     _bDynStylesFallback : (ua.ie && (ua.ie<7)) ? true : false,
3022
3023     /**
3024      * Object literal hash of Columns and their dynamically create style rules.
3025      *
3026      * @property DataTable._oDynStyles
3027      * @type Object
3028      * @private
3029      * @static     
3030      */
3031     _oDynStyles : {},
3032
3033     /**
3034      * Element reference to shared Column drag target.
3035      *
3036      * @property DataTable._elColumnDragTarget
3037      * @type HTMLElement
3038      * @private
3039      * @static 
3040      */
3041     _elColumnDragTarget : null,
3042
3043     /**
3044      * Element reference to shared Column resizer proxy.
3045      *
3046      * @property DataTable._elColumnResizerProxy
3047      * @type HTMLElement
3048      * @private
3049      * @static 
3050      */
3051     _elColumnResizerProxy : null,
3052
3053     /////////////////////////////////////////////////////////////////////////
3054     //
3055     // Private static methods
3056     //
3057     /////////////////////////////////////////////////////////////////////////
3058
3059     /**
3060      * Clones object literal or array of object literals.
3061      *
3062      * @method DataTable._cloneObject
3063      * @param o {Object} Object.
3064      * @private
3065      * @static     
3066      */
3067     _cloneObject : function(o) {
3068         if(!lang.isValue(o)) {
3069             return o;
3070         }
3071         
3072         var copy = {};
3073         
3074         if(o instanceof YAHOO.widget.BaseCellEditor) {
3075             copy = o;
3076         }
3077         else if(lang.isFunction(o)) {
3078             copy = o;
3079         }
3080         else if(lang.isArray(o)) {
3081             var array = [];
3082             for(var i=0,len=o.length;i<len;i++) {
3083                 array[i] = DT._cloneObject(o[i]);
3084             }
3085             copy = array;
3086         }
3087         else if(lang.isObject(o)) { 
3088             for (var x in o){
3089                 if(lang.hasOwnProperty(o, x)) {
3090                     if(lang.isValue(o[x]) && lang.isObject(o[x]) || lang.isArray(o[x])) {
3091                         copy[x] = DT._cloneObject(o[x]);
3092                     }
3093                     else {
3094                         copy[x] = o[x];
3095                     }
3096                 }
3097             }
3098         }
3099         else {
3100             copy = o;
3101         }
3102     
3103         return copy;
3104     },
3105
3106     /**
3107      * Destroys shared Column drag target.
3108      *
3109      * @method DataTable._destroyColumnDragTargetEl
3110      * @private
3111      * @static 
3112      */
3113     _destroyColumnDragTargetEl : function() {
3114         if(DT._elColumnDragTarget) {
3115             var el = DT._elColumnDragTarget;
3116             YAHOO.util.Event.purgeElement(el);
3117             el.parentNode.removeChild(el);
3118             DT._elColumnDragTarget = null;
3119             
3120         }
3121     },
3122
3123     /**
3124      * Creates HTML markup for shared Column drag target.
3125      *
3126      * @method DataTable._initColumnDragTargetEl
3127      * @return {HTMLElement} Reference to Column drag target. 
3128      * @private
3129      * @static 
3130      */
3131     _initColumnDragTargetEl : function() {
3132         if(!DT._elColumnDragTarget) {
3133             // Attach Column drag target element as first child of body
3134             var elColumnDragTarget = document.createElement('div');
3135             elColumnDragTarget.className = DT.CLASS_COLTARGET;
3136             elColumnDragTarget.style.display = "none";
3137             document.body.insertBefore(elColumnDragTarget, document.body.firstChild);
3138
3139             // Internal tracker of Column drag target
3140             DT._elColumnDragTarget = elColumnDragTarget;
3141             
3142         }
3143         return DT._elColumnDragTarget;
3144     },
3145
3146     /**
3147      * Destroys shared Column resizer proxy.
3148      *
3149      * @method DataTable._destroyColumnResizerProxyEl
3150      * @return {HTMLElement} Reference to Column resizer proxy.
3151      * @private 
3152      * @static 
3153      */
3154     _destroyColumnResizerProxyEl : function() {
3155         if(DT._elColumnResizerProxy) {
3156             var el = DT._elColumnResizerProxy;
3157             YAHOO.util.Event.purgeElement(el);
3158             el.parentNode.removeChild(el);
3159             DT._elColumnResizerProxy = null;
3160         }
3161     },
3162
3163     /**
3164      * Creates HTML markup for shared Column resizer proxy.
3165      *
3166      * @method DataTable._initColumnResizerProxyEl
3167      * @return {HTMLElement} Reference to Column resizer proxy.
3168      * @private 
3169      * @static 
3170      */
3171     _initColumnResizerProxyEl : function() {
3172         if(!DT._elColumnResizerProxy) {
3173             // Attach Column resizer element as first child of body
3174             var elColumnResizerProxy = document.createElement("div");
3175             elColumnResizerProxy.id = "yui-dt-colresizerproxy"; // Needed for ColumnResizer
3176             elColumnResizerProxy.className = DT.CLASS_RESIZERPROXY;
3177             document.body.insertBefore(elColumnResizerProxy, document.body.firstChild);
3178
3179             // Internal tracker of Column resizer proxy
3180             DT._elColumnResizerProxy = elColumnResizerProxy;
3181         }
3182         return DT._elColumnResizerProxy;
3183     },
3184
3185     /**
3186      * Formats a BUTTON element.
3187      *
3188      * @method DataTable.formatButton
3189      * @param el {HTMLElement} The element to format with markup.
3190      * @param oRecord {YAHOO.widget.Record} Record instance.
3191      * @param oColumn {YAHOO.widget.Column} Column instance.
3192      * @param oData {Object | Boolean} Data value for the cell. By default, the value
3193      * is what gets written to the BUTTON.
3194      * @static
3195      */
3196     formatButton : function(el, oRecord, oColumn, oData) {
3197         var sValue = lang.isValue(oData) ? oData : "Click";
3198         //TODO: support YAHOO.widget.Button
3199         //if(YAHOO.widget.Button) {
3200
3201         //}
3202         //else {
3203             el.innerHTML = "<button type=\"button\" class=\""+
3204                     DT.CLASS_BUTTON + "\">" + sValue + "</button>";
3205         //}
3206     },
3207
3208     /**
3209      * Formats a CHECKBOX element.
3210      *
3211      * @method DataTable.formatCheckbox
3212      * @param el {HTMLElement} The element to format with markup.
3213      * @param oRecord {YAHOO.widget.Record} Record instance.
3214      * @param oColumn {YAHOO.widget.Column} Column instance.
3215      * @param oData {Object | Boolean} Data value for the cell. Can be a simple
3216      * Boolean to indicate whether checkbox is checked or not. Can be object literal
3217      * {checked:bBoolean, label:sLabel}. Other forms of oData require a custom
3218      * formatter.
3219      * @static
3220      */
3221     formatCheckbox : function(el, oRecord, oColumn, oData) {
3222         var bChecked = oData;
3223         bChecked = (bChecked) ? " checked=\"checked\"" : "";
3224         el.innerHTML = "<input type=\"checkbox\"" + bChecked +
3225                 " class=\"" + DT.CLASS_CHECKBOX + "\" />";
3226     },
3227
3228     /**
3229      * Formats currency. Default unit is USD.
3230      *
3231      * @method DataTable.formatCurrency
3232      * @param el {HTMLElement} The element to format with markup.
3233      * @param oRecord {YAHOO.widget.Record} Record instance.
3234      * @param oColumn {YAHOO.widget.Column} Column instance.
3235      * @param oData {Number} Data value for the cell.
3236      * @static
3237      */
3238     formatCurrency : function(el, oRecord, oColumn, oData) {
3239         el.innerHTML = util.Number.format(oData, oColumn.currencyOptions || this.get("currencyOptions"));
3240     },
3241
3242     /**
3243      * Formats JavaScript Dates.
3244      *
3245      * @method DataTable.formatDate
3246      * @param el {HTMLElement} The element to format with markup.
3247      * @param oRecord {YAHOO.widget.Record} Record instance.
3248      * @param oColumn {YAHOO.widget.Column} Column instance.
3249      * @param oData {Object} Data value for the cell, or null.
3250      * @static
3251      */
3252     formatDate : function(el, oRecord, oColumn, oData) {
3253         var oConfig = oColumn.dateOptions || this.get("dateOptions");
3254         el.innerHTML = util.Date.format(oData, oConfig, oConfig.locale);
3255     },
3256
3257     /**
3258      * Formats SELECT elements.
3259      *
3260      * @method DataTable.formatDropdown
3261      * @param el {HTMLElement} The element to format with markup.
3262      * @param oRecord {YAHOO.widget.Record} Record instance.
3263      * @param oColumn {YAHOO.widget.Column} Column instance.
3264      * @param oData {Object} Data value for the cell, or null.
3265      * @static
3266      */
3267     formatDropdown : function(el, oRecord, oColumn, oData) {
3268         var selectedValue = (lang.isValue(oData)) ? oData : oRecord.getData(oColumn.field),
3269             options = (lang.isArray(oColumn.dropdownOptions)) ?
3270                 oColumn.dropdownOptions : null,
3271
3272             selectEl,
3273             collection = el.getElementsByTagName("select");
3274
3275         // Create the form element only once, so we can attach the onChange listener
3276         if(collection.length === 0) {
3277             // Create SELECT element
3278             selectEl = document.createElement("select");
3279             selectEl.className = DT.CLASS_DROPDOWN;
3280             selectEl = el.appendChild(selectEl);
3281
3282             // Add event listener
3283             Ev.addListener(selectEl,"change",this._onDropdownChange,this);
3284         }
3285
3286         selectEl = collection[0];
3287
3288         // Update the form element
3289         if(selectEl) {
3290             // Clear out previous options
3291             selectEl.innerHTML = "";
3292
3293             // We have options to populate
3294             if(options) {
3295                 // Create OPTION elements
3296                 for(var i=0; i<options.length; i++) {
3297                     var option = options[i];
3298                     var optionEl = document.createElement("option");
3299                     optionEl.value = (lang.isValue(option.value)) ?
3300                             option.value : option;
3301                     // Bug 2334323: Support legacy text, support label for consistency with DropdownCellEditor
3302                     optionEl.innerHTML = (lang.isValue(option.text)) ?
3303                             option.text : (lang.isValue(option.label)) ? option.label : option;
3304                     optionEl = selectEl.appendChild(optionEl);
3305                     if (optionEl.value == selectedValue) {
3306                         optionEl.selected = true;
3307                     }
3308                 }
3309             }
3310             // Selected value is our only option
3311             else {
3312                 selectEl.innerHTML = "<option selected value=\"" + selectedValue + "\">" + selectedValue + "</option>";
3313             }
3314         }
3315         else {
3316             el.innerHTML = lang.isValue(oData) ? oData : "";
3317         }
3318     },
3319
3320     /**
3321      * Formats emails.
3322      *
3323      * @method DataTable.formatEmail
3324      * @param el {HTMLElement} The element to format with markup.
3325      * @param oRecord {YAHOO.widget.Record} Record instance.
3326      * @param oColumn {YAHOO.widget.Column} Column instance.
3327      * @param oData {Object} Data value for the cell, or null.
3328      * @static
3329      */
3330     formatEmail : function(el, oRecord, oColumn, oData) {
3331         if(lang.isString(oData)) {
3332             el.innerHTML = "<a href=\"mailto:" + oData + "\">" + oData + "</a>";
3333         }
3334         else {
3335             el.innerHTML = lang.isValue(oData) ? oData : "";
3336         }
3337     },
3338
3339     /**
3340      * Formats links.
3341      *
3342      * @method DataTable.formatLink
3343      * @param el {HTMLElement} The element to format with markup.
3344      * @param oRecord {YAHOO.widget.Record} Record instance.
3345      * @param oColumn {YAHOO.widget.Column} Column instance.
3346      * @param oData {Object} Data value for the cell, or null.
3347      * @static
3348      */
3349     formatLink : function(el, oRecord, oColumn, oData) {
3350         if(lang.isString(oData)) {
3351             el.innerHTML = "<a href=\"" + oData + "\">" + oData + "</a>";
3352         }
3353         else {
3354             el.innerHTML = lang.isValue(oData) ? oData : "";
3355         }
3356     },
3357
3358     /**
3359      * Formats numbers.
3360      *
3361      * @method DataTable.formatNumber
3362      * @param el {HTMLElement} The element to format with markup.
3363      * @param oRecord {YAHOO.widget.Record} Record instance.
3364      * @param oColumn {YAHOO.widget.Column} Column instance.
3365      * @param oData {Object} Data value for the cell, or null.
3366      * @static
3367      */
3368     formatNumber : function(el, oRecord, oColumn, oData) {
3369         el.innerHTML = util.Number.format(oData, oColumn.numberOptions || this.get("numberOptions"));
3370     },
3371
3372     /**
3373      * Formats INPUT TYPE=RADIO elements.
3374      *
3375      * @method DataTable.formatRadio
3376      * @param el {HTMLElement} The element to format with markup.
3377      * @param oRecord {YAHOO.widget.Record} Record instance.
3378      * @param oColumn {YAHOO.widget.Column} Column instance.
3379      * @param oData {Object} (Optional) Data value for the cell.
3380      * @static
3381      */
3382     formatRadio : function(el, oRecord, oColumn, oData) {
3383         var bChecked = oData;
3384         bChecked = (bChecked) ? " checked=\"checked\"" : "";
3385         el.innerHTML = "<input type=\"radio\"" + bChecked +
3386                 " name=\""+this.getId()+"-col-" + oColumn.getSanitizedKey() + "\"" +
3387                 " class=\"" + DT.CLASS_RADIO+ "\" />";
3388     },
3389
3390     /**
3391      * Formats text strings.
3392      *
3393      * @method DataTable.formatText
3394      * @param el {HTMLElement} The element to format with markup.
3395      * @param oRecord {YAHOO.widget.Record} Record instance.
3396      * @param oColumn {YAHOO.widget.Column} Column instance.
3397      * @param oData {Object} (Optional) Data value for the cell.
3398      * @static
3399      */
3400     formatText : function(el, oRecord, oColumn, oData) {
3401         var value = (lang.isValue(oData)) ? oData : "";
3402         //TODO: move to util function
3403         el.innerHTML = value.toString().replace(/&/g, "&#38;").replace(/</g, "&#60;").replace(/>/g, "&#62;");
3404     },
3405
3406     /**
3407      * Formats TEXTAREA elements.
3408      *
3409      * @method DataTable.formatTextarea
3410      * @param el {HTMLElement} The element to format with markup.
3411      * @param oRecord {YAHOO.widget.Record} Record instance.
3412      * @param oColumn {YAHOO.widget.Column} Column instance.
3413      * @param oData {Object} (Optional) Data value for the cell.
3414      * @static
3415      */
3416     formatTextarea : function(el, oRecord, oColumn, oData) {
3417         var value = (lang.isValue(oData)) ? oData : "",
3418             markup = "<textarea>" + value + "</textarea>";
3419         el.innerHTML = markup;
3420     },
3421
3422     /**
3423      * Formats INPUT TYPE=TEXT elements.
3424      *
3425      * @method DataTable.formatTextbox
3426      * @param el {HTMLElement} The element to format with markup.
3427      * @param oRecord {YAHOO.widget.Record} Record instance.
3428      * @param oColumn {YAHOO.widget.Column} Column instance.
3429      * @param oData {Object} (Optional) Data value for the cell.
3430      * @static
3431      */
3432     formatTextbox : function(el, oRecord, oColumn, oData) {
3433         var value = (lang.isValue(oData)) ? oData : "",
3434             markup = "<input type=\"text\" value=\"" + value + "\" />";
3435         el.innerHTML = markup;
3436     },
3437
3438     /**
3439      * Default cell formatter
3440      *
3441      * @method DataTable.formatDefault
3442      * @param el {HTMLElement} The element to format with markup.
3443      * @param oRecord {YAHOO.widget.Record} Record instance.
3444      * @param oColumn {YAHOO.widget.Column} Column instance.
3445      * @param oData {Object} (Optional) Data value for the cell.
3446      * @static
3447      */
3448     formatDefault : function(el, oRecord, oColumn, oData) {
3449         el.innerHTML = oData === undefined ||
3450                        oData === null ||
3451                        (typeof oData === 'number' && isNaN(oData)) ?
3452                        "&#160;" : oData.toString();
3453     },
3454
3455     /**
3456      * Validates data value to type Number, doing type conversion as
3457      * necessary. A valid Number value is return, else null is returned
3458      * if input value does not validate.
3459      *
3460      *
3461      * @method DataTable.validateNumber
3462      * @param oData {Object} Data to validate.
3463      * @static
3464     */
3465     validateNumber : function(oData) {
3466         //Convert to number
3467         var number = oData * 1;
3468
3469         // Validate
3470         if(lang.isNumber(number)) {
3471             return number;
3472         }
3473         else {
3474             return undefined;
3475         }
3476     }
3477 });
3478
3479 // Done in separate step so referenced functions are defined.
3480 /**
3481  * Cell formatting functions.
3482  * @property DataTable.Formatter
3483  * @type Object
3484  * @static
3485  */
3486 DT.Formatter = {
3487     button   : DT.formatButton,
3488     checkbox : DT.formatCheckbox,
3489     currency : DT.formatCurrency,
3490     "date"   : DT.formatDate,
3491     dropdown : DT.formatDropdown,
3492     email    : DT.formatEmail,
3493     link     : DT.formatLink,
3494     "number" : DT.formatNumber,
3495     radio    : DT.formatRadio,
3496     text     : DT.formatText,
3497     textarea : DT.formatTextarea,
3498     textbox  : DT.formatTextbox,
3499
3500     defaultFormatter : DT.formatDefault
3501 };
3502
3503 lang.extend(DT, util.Element, {
3504
3505 /////////////////////////////////////////////////////////////////////////////
3506 //
3507 // Superclass methods
3508 //
3509 /////////////////////////////////////////////////////////////////////////////
3510
3511 /**
3512  * Implementation of Element's abstract method. Sets up config values.
3513  *
3514  * @method initAttributes
3515  * @param oConfigs {Object} (Optional) Object literal definition of configuration values.
3516  * @private
3517  */
3518
3519 initAttributes : function(oConfigs) {
3520     oConfigs = oConfigs || {};
3521     DT.superclass.initAttributes.call(this, oConfigs);
3522
3523     /**
3524     * @attribute summary
3525     * @description Value for the SUMMARY attribute.
3526     * @type String
3527     * @default ""    
3528     */
3529     this.setAttributeConfig("summary", {
3530         value: "",
3531         validator: lang.isString,
3532         method: function(sSummary) {
3533             if(this._elTable) {
3534                 this._elTable.summary = sSummary;
3535             }
3536         }
3537     });
3538
3539     /**
3540     * @attribute selectionMode
3541     * @description Specifies row or cell selection mode. Accepts the following strings:
3542     *    <dl>
3543     *      <dt>"standard"</dt>
3544     *      <dd>Standard row selection with support for modifier keys to enable
3545     *      multiple selections.</dd>
3546     *
3547     *      <dt>"single"</dt>
3548     *      <dd>Row selection with modifier keys disabled to not allow
3549     *      multiple selections.</dd>
3550     *
3551     *      <dt>"singlecell"</dt>
3552     *      <dd>Cell selection with modifier keys disabled to not allow
3553     *      multiple selections.</dd>
3554     *
3555     *      <dt>"cellblock"</dt>
3556     *      <dd>Cell selection with support for modifier keys to enable multiple
3557     *      selections in a block-fashion, like a spreadsheet.</dd>
3558     *
3559     *      <dt>"cellrange"</dt>
3560     *      <dd>Cell selection with support for modifier keys to enable multiple
3561     *      selections in a range-fashion, like a calendar.</dd>
3562     *    </dl>
3563     *
3564     * @default "standard"
3565     * @type String
3566     */
3567     this.setAttributeConfig("selectionMode", {
3568         value: "standard",
3569         validator: lang.isString
3570     });
3571
3572     /**
3573     * @attribute sortedBy
3574     * @description Object literal provides metadata for initial sort values if
3575     * data will arrive pre-sorted:
3576     * <dl>
3577     *     <dt>sortedBy.key</dt>
3578     *     <dd>{String} Key of sorted Column</dd>
3579     *     <dt>sortedBy.dir</dt>
3580     *     <dd>{String} Initial sort direction, either YAHOO.widget.DataTable.CLASS_ASC or YAHOO.widget.DataTable.CLASS_DESC</dd>
3581     * </dl>
3582     * @type Object | null
3583     */
3584     this.setAttributeConfig("sortedBy", {
3585         value: null,
3586         // TODO: accepted array for nested sorts
3587         validator: function(oNewSortedBy) {
3588             if(oNewSortedBy) {
3589                 return (lang.isObject(oNewSortedBy) && oNewSortedBy.key);
3590             }
3591             else {
3592                 return (oNewSortedBy === null);
3593             }
3594         },
3595         method: function(oNewSortedBy) {
3596             // Stash the previous value
3597             var oOldSortedBy = this.get("sortedBy");
3598             
3599             // Workaround for bug 1827195
3600             this._configs.sortedBy.value = oNewSortedBy;
3601
3602             // Remove ASC/DESC from TH
3603             var oOldColumn,
3604                 nOldColumnKeyIndex,
3605                 oNewColumn,
3606                 nNewColumnKeyIndex;
3607                 
3608             if(this._elThead) {
3609                 if(oOldSortedBy && oOldSortedBy.key && oOldSortedBy.dir) {
3610                     oOldColumn = this._oColumnSet.getColumn(oOldSortedBy.key);
3611                     nOldColumnKeyIndex = oOldColumn.getKeyIndex();
3612                     
3613                     // Remove previous UI from THEAD
3614                     var elOldTh = oOldColumn.getThEl();
3615                     Dom.removeClass(elOldTh, oOldSortedBy.dir);
3616                     this.formatTheadCell(oOldColumn.getThLinerEl().firstChild, oOldColumn, oNewSortedBy);
3617                 }
3618                 if(oNewSortedBy) {
3619                     oNewColumn = (oNewSortedBy.column) ? oNewSortedBy.column : this._oColumnSet.getColumn(oNewSortedBy.key);
3620                     nNewColumnKeyIndex = oNewColumn.getKeyIndex();
3621     
3622                     // Update THEAD with new UI
3623                     var elNewTh = oNewColumn.getThEl();
3624                     // Backward compatibility
3625                     if(oNewSortedBy.dir && ((oNewSortedBy.dir == "asc") ||  (oNewSortedBy.dir == "desc"))) {
3626                         var newClass = (oNewSortedBy.dir == "desc") ?
3627                                 DT.CLASS_DESC :
3628                                 DT.CLASS_ASC;
3629                         Dom.addClass(elNewTh, newClass);
3630                     }
3631                     else {
3632                          var sortClass = oNewSortedBy.dir || DT.CLASS_ASC;
3633                          Dom.addClass(elNewTh, sortClass);
3634                     }
3635                     this.formatTheadCell(oNewColumn.getThLinerEl().firstChild, oNewColumn, oNewSortedBy);
3636                 }
3637             }
3638           
3639             if(this._elTbody) {
3640                 // Update TBODY UI
3641                 this._elTbody.style.display = "none";
3642                 var allRows = this._elTbody.rows,
3643                     allCells;
3644                 for(var i=allRows.length-1; i>-1; i--) {
3645                     allCells = allRows[i].childNodes;
3646                     if(allCells[nOldColumnKeyIndex]) {
3647                         Dom.removeClass(allCells[nOldColumnKeyIndex], oOldSortedBy.dir);
3648                     }
3649                     if(allCells[nNewColumnKeyIndex]) {
3650                         Dom.addClass(allCells[nNewColumnKeyIndex], oNewSortedBy.dir);
3651                     }
3652                 }
3653                 this._elTbody.style.display = "";
3654             }
3655                 
3656             this._clearTrTemplateEl();
3657         }
3658     });
3659     
3660     /**
3661     * @attribute paginator
3662     * @description An instance of YAHOO.widget.Paginator.
3663     * @default null
3664     * @type {Object|YAHOO.widget.Paginator}
3665     */
3666     this.setAttributeConfig("paginator", {
3667         value : null,
3668         validator : function (val) {
3669             return val === null || val instanceof widget.Paginator;
3670         },
3671         method : function () { this._updatePaginator.apply(this,arguments); }
3672     });
3673
3674     /**
3675     * @attribute caption
3676     * @description Value for the CAPTION element. NB: Not supported in
3677     * ScrollingDataTable.    
3678     * @type String
3679     */
3680     this.setAttributeConfig("caption", {
3681         value: null,
3682         validator: lang.isString,
3683         method: function(sCaption) {
3684             this._initCaptionEl(sCaption);
3685         }
3686     });
3687
3688     /**
3689     * @attribute draggableColumns
3690     * @description True if Columns are draggable to reorder, false otherwise.
3691     * The Drag & Drop Utility is required to enable this feature. Only top-level
3692     * and non-nested Columns are draggable. Write once.
3693     * @default false
3694     * @type Boolean
3695     */
3696     this.setAttributeConfig("draggableColumns", {
3697         value: false,
3698         validator: lang.isBoolean,
3699         method: function(oParam) {
3700             if(this._elThead) {
3701                 if(oParam) {
3702                     this._initDraggableColumns();
3703                 }
3704                 else {
3705                     this._destroyDraggableColumns();
3706                 }
3707             }
3708         }
3709     });
3710
3711     /**
3712     * @attribute renderLoopSize          
3713     * @description A value greater than 0 enables DOM rendering of rows to be
3714     * executed from a non-blocking timeout queue and sets how many rows to be
3715     * rendered per timeout. Recommended for very large data sets.     
3716     * @type Number       
3717     * @default 0         
3718     */   
3719      this.setAttributeConfig("renderLoopSize", {         
3720          value: 0,       
3721          validator: lang.isNumber        
3722      });         
3723
3724     /**
3725     * @attribute formatRow
3726     * @description A function that accepts a TR element and its associated Record
3727     * for custom formatting. The function must return TRUE in order to automatically
3728     * continue formatting of child TD elements, else TD elements will not be
3729     * automatically formatted.
3730     * @type function
3731     * @default null
3732     */
3733     this.setAttributeConfig("formatRow", {
3734         value: null,
3735         validator: lang.isFunction
3736     });
3737
3738     /**
3739     * @attribute generateRequest
3740     * @description A function that converts an object literal of desired DataTable
3741     * states into a request value which is then passed to the DataSource's
3742     * sendRequest method in order to retrieve data for those states. This
3743     * function is passed an object literal of state data and a reference to the
3744     * DataTable instance:
3745     *     
3746     * <dl>
3747     *   <dt>pagination<dt>
3748     *   <dd>        
3749     *         <dt>offsetRecord</dt>
3750     *         <dd>{Number} Index of the first Record of the desired page</dd>
3751     *         <dt>rowsPerPage</dt>
3752     *         <dd>{Number} Number of rows per page</dd>
3753     *   </dd>
3754     *   <dt>sortedBy</dt>
3755     *   <dd>                
3756     *         <dt>key</dt>
3757     *         <dd>{String} Key of sorted Column</dd>
3758     *         <dt>dir</dt>
3759     *         <dd>{String} Sort direction, either YAHOO.widget.DataTable.CLASS_ASC or YAHOO.widget.DataTable.CLASS_DESC</dd>
3760     *   </dd>
3761     *   <dt>self</dt>
3762     *   <dd>The DataTable instance</dd>
3763     * </dl>
3764     * 
3765     * and by default returns a String of syntax:
3766     * "sort={sortColumn}&dir={sortDir}&startIndex={pageStartIndex}&results={rowsPerPage}"
3767     * @type function
3768     * @default HTMLFunction
3769     */
3770     this.setAttributeConfig("generateRequest", {
3771         value: function(oState, oSelf) {
3772             // Set defaults
3773             oState = oState || {pagination:null, sortedBy:null};
3774             var sort = (oState.sortedBy) ? oState.sortedBy.key : oSelf.getColumnSet().keys[0].getKey();
3775             var dir = (oState.sortedBy && oState.sortedBy.dir === YAHOO.widget.DataTable.CLASS_DESC) ? "desc" : "asc";
3776             var startIndex = (oState.pagination) ? oState.pagination.recordOffset : 0;
3777             var results = (oState.pagination) ? oState.pagination.rowsPerPage : null;
3778             
3779             // Build the request
3780             return  "sort=" + sort +
3781                     "&dir=" + dir +
3782                     "&startIndex=" + startIndex +
3783                     ((results !== null) ? "&results=" + results : "");
3784         },
3785         validator: lang.isFunction
3786     });
3787
3788     /**
3789     * @attribute initialRequest
3790     * @description Defines the initial request that gets sent to the DataSource
3791     * during initialization. Value is ignored if initialLoad is set to any value
3792     * other than true.    
3793     * @type MIXED
3794     * @default null
3795     */
3796     this.setAttributeConfig("initialRequest", {
3797         value: null
3798     });
3799
3800     /**
3801     * @attribute initialLoad
3802     * @description Determines whether or not to load data at instantiation. By
3803     * default, will trigger a sendRequest() to the DataSource and pass in the
3804     * request defined by initialRequest. If set to false, data will not load
3805     * at instantiation. Alternatively, implementers who wish to work with a 
3806     * custom payload may pass in an object literal with the following values:
3807     *     
3808     *    <dl>
3809     *      <dt>request (MIXED)</dt>
3810     *      <dd>Request value.</dd>
3811     *
3812     *      <dt>argument (MIXED)</dt>
3813     *      <dd>Custom data that will be passed through to the callback function.</dd>
3814     *    </dl>
3815     *
3816     *                    
3817     * @type Boolean | Object
3818     * @default true
3819     */
3820     this.setAttributeConfig("initialLoad", {
3821         value: true
3822     });
3823     
3824     /**
3825     * @attribute dynamicData
3826     * @description If true, sorting and pagination are relegated to the DataSource
3827     * for handling, using the request returned by the "generateRequest" function.
3828     * Each new DataSource response blows away all previous Records. False by default, so 
3829     * sorting and pagination will be handled directly on the client side, without
3830     * causing any new requests for data from the DataSource.
3831     * @type Boolean
3832     * @default false
3833     */
3834     this.setAttributeConfig("dynamicData", {
3835         value: false,
3836         validator: lang.isBoolean
3837     });
3838
3839     /**
3840      * @attribute MSG_EMPTY      
3841      * @description Message to display if DataTable has no data.     
3842      * @type String      
3843      * @default "No records found."      
3844      */          
3845      this.setAttributeConfig("MSG_EMPTY", {      
3846          value: "No records found.",     
3847          validator: lang.isString        
3848      });         
3849
3850     /**
3851      * @attribute MSG_LOADING    
3852      * @description Message to display while DataTable is loading data.
3853      * @type String      
3854      * @default "Loading..."     
3855      */          
3856      this.setAttributeConfig("MSG_LOADING", {    
3857          value: "Loading...",    
3858          validator: lang.isString        
3859      });         
3860
3861     /**
3862      * @attribute MSG_ERROR      
3863      * @description Message to display while DataTable has data error.
3864      * @type String      
3865      * @default "Data error."    
3866      */          
3867      this.setAttributeConfig("MSG_ERROR", {      
3868          value: "Data error.",   
3869          validator: lang.isString        
3870      });         
3871
3872     /**
3873      * @attribute MSG_SORTASC 
3874      * @description Message to display in tooltip to sort Column in ascending order.
3875      * @type String      
3876      * @default "Click to sort ascending"        
3877      */          
3878      this.setAttributeConfig("MSG_SORTASC", {    
3879          value: "Click to sort ascending",       
3880          validator: lang.isString,
3881          method: function(sParam) {
3882             if(this._elThead) {
3883                 for(var i=0, allKeys=this.getColumnSet().keys, len=allKeys.length; i<len; i++) {
3884                     if(allKeys[i].sortable && this.getColumnSortDir(allKeys[i]) === DT.CLASS_ASC) {
3885                         allKeys[i]._elThLabel.firstChild.title = sParam;
3886                     }
3887                 }
3888             }      
3889          }
3890      });
3891
3892     /**
3893      * @attribute MSG_SORTDESC 
3894      * @description Message to display in tooltip to sort Column in descending order.
3895      * @type String      
3896      * @default "Click to sort descending"       
3897      */          
3898      this.setAttributeConfig("MSG_SORTDESC", {   
3899          value: "Click to sort descending",      
3900          validator: lang.isString,
3901          method: function(sParam) {
3902             if(this._elThead) {
3903                 for(var i=0, allKeys=this.getColumnSet().keys, len=allKeys.length; i<len; i++) {
3904                     if(allKeys[i].sortable && this.getColumnSortDir(allKeys[i]) === DT.CLASS_DESC) {
3905                         allKeys[i]._elThLabel.firstChild.title = sParam;
3906                     }
3907                 }
3908             }               
3909          }
3910      });
3911      
3912     /**
3913      * @attribute currencySymbol
3914      * @deprecated
3915      */
3916     this.setAttributeConfig("currencySymbol", {
3917         value: "$",
3918         validator: lang.isString
3919     });
3920     
3921     /**
3922      * Default config passed to YAHOO.util.Number.format() by the 'currency' Column formatter.
3923      * @attribute currencyOptions
3924      * @type Object
3925      * @default {prefix: $, decimalPlaces:2, decimalSeparator:".", thousandsSeparator:","}
3926      */
3927     this.setAttributeConfig("currencyOptions", {
3928         value: {
3929             prefix: this.get("currencySymbol"), // TODO: deprecate currencySymbol
3930             decimalPlaces:2,
3931             decimalSeparator:".",
3932             thousandsSeparator:","
3933         }
3934     });
3935     
3936     /**
3937      * Default config passed to YAHOO.util.Date.format() by the 'date' Column formatter.
3938      * @attribute dateOptions
3939      * @type Object
3940      * @default {format:"%m/%d/%Y", locale:"en"}
3941      */
3942     this.setAttributeConfig("dateOptions", {
3943         value: {format:"%m/%d/%Y", locale:"en"}
3944     });
3945     
3946     /**
3947      * Default config passed to YAHOO.util.Number.format() by the 'number' Column formatter.
3948      * @attribute numberOptions
3949      * @type Object
3950      * @default {decimalPlaces:0, thousandsSeparator:","}
3951      */
3952     this.setAttributeConfig("numberOptions", {
3953         value: {
3954             decimalPlaces:0,
3955             thousandsSeparator:","
3956         }
3957     });
3958
3959 },
3960
3961 /////////////////////////////////////////////////////////////////////////////
3962 //
3963 // Private member variables
3964 //
3965 /////////////////////////////////////////////////////////////////////////////
3966
3967 /**
3968  * True if instance is initialized, so as to fire the initEvent after render.
3969  *
3970  * @property _bInit
3971  * @type Boolean
3972  * @default true
3973  * @private
3974  */
3975 _bInit : true,
3976
3977 /**
3978  * Index assigned to instance.
3979  *
3980  * @property _nIndex
3981  * @type Number
3982  * @private
3983  */
3984 _nIndex : null,
3985
3986 /**
3987  * Counter for IDs assigned to TR elements.
3988  *
3989  * @property _nTrCount
3990  * @type Number
3991  * @private
3992  */
3993 _nTrCount : 0,
3994
3995 /**
3996  * Counter for IDs assigned to TD elements.
3997  *
3998  * @property _nTdCount
3999  * @type Number
4000  * @private
4001  */
4002 _nTdCount : 0,
4003
4004 /**
4005  * Unique id assigned to instance "yui-dtN", useful prefix for generating unique
4006  * DOM ID strings and log messages.
4007  *
4008  * @property _sId
4009  * @type String
4010  * @private
4011  */
4012 _sId : null,
4013
4014 /**
4015  * Render chain.
4016  *
4017  * @property _oChainRender
4018  * @type YAHOO.util.Chain
4019  * @private
4020  */
4021 _oChainRender : null,
4022
4023 /**
4024  * DOM reference to the container element for the DataTable instance into which
4025  * all other elements get created.
4026  *
4027  * @property _elContainer
4028  * @type HTMLElement
4029  * @private
4030  */
4031 _elContainer : null,
4032
4033 /**
4034  * DOM reference to the mask element for the DataTable instance which disables it.
4035  *
4036  * @property _elMask
4037  * @type HTMLElement
4038  * @private
4039  */
4040 _elMask : null,
4041
4042 /**
4043  * DOM reference to the TABLE element for the DataTable instance.
4044  *
4045  * @property _elTable
4046  * @type HTMLElement
4047  * @private
4048  */
4049 _elTable : null,
4050
4051 /**
4052  * DOM reference to the CAPTION element for the DataTable instance.
4053  *
4054  * @property _elCaption
4055  * @type HTMLElement
4056  * @private
4057  */
4058 _elCaption : null,
4059
4060 /**
4061  * DOM reference to the COLGROUP element for the DataTable instance.
4062  *
4063  * @property _elColgroup
4064  * @type HTMLElement
4065  * @private
4066  */
4067 _elColgroup : null,
4068
4069 /**
4070  * DOM reference to the THEAD element for the DataTable instance.
4071  *
4072  * @property _elThead
4073  * @type HTMLElement
4074  * @private
4075  */
4076 _elThead : null,
4077
4078 /**
4079  * DOM reference to the primary TBODY element for the DataTable instance.
4080  *
4081  * @property _elTbody
4082  * @type HTMLElement
4083  * @private
4084  */
4085 _elTbody : null,
4086
4087 /**
4088  * DOM reference to the secondary TBODY element used to display DataTable messages.
4089  *
4090  * @property _elMsgTbody
4091  * @type HTMLElement
4092  * @private
4093  */
4094 _elMsgTbody : null,
4095
4096 /**
4097  * DOM reference to the secondary TBODY element's single TR element used to display DataTable messages.
4098  *
4099  * @property _elMsgTr
4100  * @type HTMLElement
4101  * @private
4102  */
4103 _elMsgTr : null,
4104
4105 /**
4106  * DOM reference to the secondary TBODY element's single TD element used to display DataTable messages.
4107  *
4108  * @property _elMsgTd
4109  * @type HTMLElement
4110  * @private
4111  */
4112 _elMsgTd : null,
4113
4114 /**
4115  * DataSource instance for the DataTable instance.
4116  *
4117  * @property _oDataSource
4118  * @type YAHOO.util.DataSource
4119  * @private
4120  */
4121 _oDataSource : null,
4122
4123 /**
4124  * ColumnSet instance for the DataTable instance.
4125  *
4126  * @property _oColumnSet
4127  * @type YAHOO.widget.ColumnSet
4128  * @private
4129  */
4130 _oColumnSet : null,
4131
4132 /**
4133  * RecordSet instance for the DataTable instance.
4134  *
4135  * @property _oRecordSet
4136  * @type YAHOO.widget.RecordSet
4137  * @private
4138  */
4139 _oRecordSet : null,
4140
4141 /**
4142  * The active CellEditor instance for the DataTable instance.
4143  *
4144  * @property _oCellEditor
4145  * @type YAHOO.widget.CellEditor
4146  * @private
4147  */
4148 _oCellEditor : null,
4149
4150 /**
4151  * ID string of first TR element of the current DataTable page.
4152  *
4153  * @property _sFirstTrId
4154  * @type String
4155  * @private
4156  */
4157 _sFirstTrId : null,
4158
4159 /**
4160  * ID string of the last TR element of the current DataTable page.
4161  *
4162  * @property _sLastTrId
4163  * @type String
4164  * @private
4165  */
4166 _sLastTrId : null,
4167
4168 /**
4169  * Template row to create all new rows from.
4170  * @property _elTrTemplate
4171  * @type {HTMLElement}
4172  * @private 
4173  */
4174 _elTrTemplate : null,
4175
4176 /**
4177  * Sparse array of custom functions to set column widths for browsers that don't
4178  * support dynamic CSS rules.  Functions are added at the index representing
4179  * the number of rows they update.
4180  *
4181  * @property _aDynFunctions
4182  * @type Array
4183  * @private
4184  */
4185 _aDynFunctions : [],
4186
4187
4188
4189
4190
4191
4192
4193
4194
4195
4196
4197
4198
4199
4200
4201
4202
4203
4204
4205
4206
4207
4208
4209
4210
4211
4212
4213
4214
4215 /////////////////////////////////////////////////////////////////////////////
4216 //
4217 // Private methods
4218 //
4219 /////////////////////////////////////////////////////////////////////////////
4220
4221 /**
4222  * Clears browser text selection. Useful to call on rowSelectEvent or
4223  * cellSelectEvent to prevent clicks or dblclicks from selecting text in the
4224  * browser.
4225  *
4226  * @method clearTextSelection
4227  */
4228 clearTextSelection : function() {
4229     var sel;
4230     if(window.getSelection) {
4231         sel = window.getSelection();
4232     }
4233     else if(document.getSelection) {
4234         sel = document.getSelection();
4235     }
4236     else if(document.selection) {
4237         sel = document.selection;
4238     }
4239     if(sel) {
4240         if(sel.empty) {
4241             sel.empty();
4242         }
4243         else if (sel.removeAllRanges) {
4244             sel.removeAllRanges();
4245         }
4246         else if(sel.collapse) {
4247             sel.collapse();
4248         }
4249     }
4250 },
4251
4252 /**
4253  * Sets focus on the given element.
4254  *
4255  * @method _focusEl
4256  * @param el {HTMLElement} Element.
4257  * @private
4258  */
4259 _focusEl : function(el) {
4260     el = el || this._elTbody;
4261     // http://developer.mozilla.org/en/docs/index.php?title=Key-navigable_custom_DHTML_widgets
4262     // The timeout is necessary in both IE and Firefox 1.5, to prevent scripts from doing
4263     // strange unexpected things as the user clicks on buttons and other controls.
4264     setTimeout(function() {
4265         try {
4266             el.focus();
4267         }
4268         catch(e) {
4269         }
4270     },0);
4271 },
4272
4273 /**
4274  * Forces Gecko repaint.
4275  *
4276  * @method _repaintGecko
4277  * @el {HTMLElement} (Optional) Element to repaint, otherwise entire document body.
4278  * @private
4279  */
4280 _repaintGecko : (ua.gecko) ? 
4281     function(el) {
4282         el = el || this._elContainer;
4283         var parent = el.parentNode;
4284         var nextSibling = el.nextSibling;
4285         parent.insertBefore(parent.removeChild(el), nextSibling);
4286     } : function() {},
4287
4288 /**
4289  * Forces Opera repaint.
4290  *
4291  * @method _repaintOpera
4292  * @private 
4293  */
4294 _repaintOpera : (ua.opera) ? 
4295     function() {
4296         if(ua.opera) {
4297             document.documentElement.className += " ";
4298             document.documentElement.className.trim();
4299         }
4300     } : function() {} ,
4301
4302 /**
4303  * Forces Webkit repaint.
4304  *
4305  * @method _repaintWebkit
4306  * @el {HTMLElement} (Optional) Element to repaint, otherwise entire document body.
4307  * @private
4308  */
4309 _repaintWebkit : (ua.webkit) ? 
4310     function(el) {
4311         el = el || this._elContainer;
4312         var parent = el.parentNode;
4313         var nextSibling = el.nextSibling;
4314         parent.insertBefore(parent.removeChild(el), nextSibling);
4315     } : function() {},
4316
4317
4318
4319
4320
4321
4322
4323
4324
4325
4326
4327
4328
4329
4330
4331
4332
4333
4334
4335
4336
4337
4338 // INIT FUNCTIONS
4339
4340 /**
4341  * Initializes object literal of config values.
4342  *
4343  * @method _initConfigs
4344  * @param oConfig {Object} Object literal of config values.
4345  * @private
4346  */
4347 _initConfigs : function(oConfigs) {
4348     if(!oConfigs || !lang.isObject(oConfigs)) {
4349         oConfigs = {};
4350     }
4351     this.configs = oConfigs;
4352 },
4353
4354 /**
4355  * Initializes ColumnSet.
4356  *
4357  * @method _initColumnSet
4358  * @param aColumnDefs {Object[]} Array of object literal Column definitions.
4359  * @private
4360  */
4361 _initColumnSet : function(aColumnDefs) {
4362     var oColumn, i, len;
4363     
4364     if(this._oColumnSet) {
4365         // First clear _oDynStyles for existing ColumnSet and
4366         // uregister CellEditor Custom Events
4367         for(i=0, len=this._oColumnSet.keys.length; i<len; i++) {
4368             oColumn = this._oColumnSet.keys[i];
4369             DT._oDynStyles["."+this.getId()+"-col-"+oColumn.getSanitizedKey()+" ."+DT.CLASS_LINER] = undefined;
4370             if(oColumn.editor && oColumn.editor.unsubscribeAll) { // Backward compatibility
4371                 oColumn.editor.unsubscribeAll();
4372             }
4373         }
4374         
4375         this._oColumnSet = null;
4376         this._clearTrTemplateEl();
4377     }
4378     
4379     if(lang.isArray(aColumnDefs)) {
4380         this._oColumnSet =  new YAHOO.widget.ColumnSet(aColumnDefs);
4381     }
4382     // Backward compatibility
4383     else if(aColumnDefs instanceof YAHOO.widget.ColumnSet) {
4384         this._oColumnSet =  aColumnDefs;
4385     }
4386
4387     // Register CellEditor Custom Events
4388     var allKeys = this._oColumnSet.keys;
4389     for(i=0, len=allKeys.length; i<len; i++) {
4390         oColumn = allKeys[i];
4391         if(oColumn.editor && oColumn.editor.subscribe) { // Backward incompatibility
4392             oColumn.editor.subscribe("showEvent", this._onEditorShowEvent, this, true);
4393             oColumn.editor.subscribe("keydownEvent", this._onEditorKeydownEvent, this, true);
4394             oColumn.editor.subscribe("revertEvent", this._onEditorRevertEvent, this, true);
4395             oColumn.editor.subscribe("saveEvent", this._onEditorSaveEvent, this, true);
4396             oColumn.editor.subscribe("cancelEvent", this._onEditorCancelEvent, this, true);
4397             oColumn.editor.subscribe("blurEvent", this._onEditorBlurEvent, this, true);
4398             oColumn.editor.subscribe("blockEvent", this._onEditorBlockEvent, this, true);
4399             oColumn.editor.subscribe("unblockEvent", this._onEditorUnblockEvent, this, true);
4400         }
4401     }
4402 },
4403
4404 /**
4405  * Initializes DataSource.
4406  *
4407  * @method _initDataSource
4408  * @param oDataSource {YAHOO.util.DataSource} DataSource instance.
4409  * @private
4410  */
4411 _initDataSource : function(oDataSource) {
4412     this._oDataSource = null;
4413     if(oDataSource && (oDataSource instanceof DS)) {
4414         this._oDataSource = oDataSource;
4415     }
4416     // Backward compatibility
4417     else {
4418         var tmpTable = null;
4419         var tmpContainer = this._elContainer;
4420         var i=0;
4421         //TODO: this will break if re-initing DS at runtime for SDT
4422         // Peek in container child nodes to see if TABLE already exists
4423         if(tmpContainer.hasChildNodes()) {
4424             var tmpChildren = tmpContainer.childNodes;
4425             for(i=0; i<tmpChildren.length; i++) {
4426                 if(tmpChildren[i].nodeName && tmpChildren[i].nodeName.toLowerCase() == "table") {
4427                     tmpTable = tmpChildren[i];
4428                     break;
4429                 }
4430             }
4431             if(tmpTable) {
4432                 var tmpFieldsArray = [];
4433                 for(; i<this._oColumnSet.keys.length; i++) {
4434                     tmpFieldsArray.push({key:this._oColumnSet.keys[i].key});
4435                 }
4436
4437                 this._oDataSource = new DS(tmpTable);
4438                 this._oDataSource.responseType = DS.TYPE_HTMLTABLE;
4439                 this._oDataSource.responseSchema = {fields: tmpFieldsArray};
4440             }
4441         }
4442     }
4443 },
4444
4445 /**
4446  * Initializes RecordSet.
4447  *
4448  * @method _initRecordSet
4449  * @private
4450  */
4451 _initRecordSet : function() {
4452     if(this._oRecordSet) {
4453         this._oRecordSet.reset();
4454     }
4455     else {
4456         this._oRecordSet = new YAHOO.widget.RecordSet();
4457     }
4458 },
4459
4460 /**
4461  * Initializes DOM elements.
4462  *
4463  * @method _initDomElements
4464  * @param elContainer {HTMLElement | String} HTML DIV element by reference or ID. 
4465  * return {Boolean} False in case of error, otherwise true 
4466  * @private
4467  */
4468 _initDomElements : function(elContainer) {
4469     // Outer container
4470     this._initContainerEl(elContainer);
4471     // TABLE
4472     this._initTableEl(this._elContainer);
4473     // COLGROUP
4474     this._initColgroupEl(this._elTable);
4475     // THEAD
4476     this._initTheadEl(this._elTable);
4477     
4478     // Message TBODY
4479     this._initMsgTbodyEl(this._elTable);  
4480
4481     // Primary TBODY
4482     this._initTbodyEl(this._elTable);
4483
4484     if(!this._elContainer || !this._elTable || !this._elColgroup ||  !this._elThead || !this._elTbody || !this._elMsgTbody) {
4485         return false;
4486     }
4487     else {
4488         return true;
4489     }
4490 },
4491
4492 /**
4493  * Destroy's the DataTable outer container element, if available.
4494  *
4495  * @method _destroyContainerEl
4496  * @param elContainer {HTMLElement} Reference to the container element. 
4497  * @private
4498  */
4499 _destroyContainerEl : function(elContainer) {
4500     Dom.removeClass(elContainer, DT.CLASS_DATATABLE);
4501     Ev.purgeElement(elContainer, true);
4502     elContainer.innerHTML = "";
4503     
4504     this._elContainer = null;
4505     this._elColgroup = null;
4506     this._elThead = null;
4507     this._elTbody = null;
4508 },
4509
4510 /**
4511  * Initializes the DataTable outer container element, including a mask.
4512  *
4513  * @method _initContainerEl
4514  * @param elContainer {HTMLElement | String} HTML DIV element by reference or ID.
4515  * @private
4516  */
4517 _initContainerEl : function(elContainer) {
4518     // Validate container
4519     elContainer = Dom.get(elContainer);
4520     
4521     if(elContainer && elContainer.nodeName && (elContainer.nodeName.toLowerCase() == "div")) {
4522         // Destroy previous
4523         this._destroyContainerEl(elContainer);
4524
4525         Dom.addClass(elContainer, DT.CLASS_DATATABLE);
4526         Ev.addListener(elContainer, "focus", this._onTableFocus, this);
4527         Ev.addListener(elContainer, "dblclick", this._onTableDblclick, this);
4528         this._elContainer = elContainer;
4529         
4530         var elMask = document.createElement("div");
4531         elMask.className = DT.CLASS_MASK;
4532         elMask.style.display = "none";
4533         this._elMask = elContainer.appendChild(elMask);
4534     }
4535 },
4536
4537 /**
4538  * Destroy's the DataTable TABLE element, if available.
4539  *
4540  * @method _destroyTableEl
4541  * @private
4542  */
4543 _destroyTableEl : function() {
4544     var elTable = this._elTable;
4545     if(elTable) {
4546         Ev.purgeElement(elTable, true);
4547         elTable.parentNode.removeChild(elTable);
4548         this._elCaption = null;
4549         this._elColgroup = null;
4550         this._elThead = null;
4551         this._elTbody = null;
4552     }
4553 },
4554
4555 /**
4556  * Creates HTML markup CAPTION element.
4557  *
4558  * @method _initCaptionEl
4559  * @param sCaption {String} Text for caption.
4560  * @private
4561  */
4562 _initCaptionEl : function(sCaption) {
4563     if(this._elTable && sCaption) {
4564         // Create CAPTION element
4565         if(!this._elCaption) { 
4566             this._elCaption = this._elTable.createCaption();
4567         }
4568         // Set CAPTION value
4569         this._elCaption.innerHTML = sCaption;
4570     }
4571     else if(this._elCaption) {
4572         this._elCaption.parentNode.removeChild(this._elCaption);
4573     }
4574 },
4575
4576 /**
4577  * Creates HTML markup for TABLE, COLGROUP, THEAD and TBODY elements in outer
4578  * container element.
4579  *
4580  * @method _initTableEl
4581  * @param elContainer {HTMLElement} Container element into which to create TABLE.
4582  * @private
4583  */
4584 _initTableEl : function(elContainer) {
4585     if(elContainer) {
4586         // Destroy previous
4587         this._destroyTableEl();
4588     
4589         // Create TABLE
4590         this._elTable = elContainer.appendChild(document.createElement("table"));  
4591          
4592         // Set SUMMARY attribute
4593         this._elTable.summary = this.get("summary");
4594         
4595         // Create CAPTION element
4596         if(this.get("caption")) {
4597             this._initCaptionEl(this.get("caption"));
4598         }
4599     } 
4600 },
4601
4602 /**
4603  * Destroy's the DataTable COLGROUP element, if available.
4604  *
4605  * @method _destroyColgroupEl
4606  * @private
4607  */
4608 _destroyColgroupEl : function() {
4609     var elColgroup = this._elColgroup;
4610     if(elColgroup) {
4611         var elTable = elColgroup.parentNode;
4612         Ev.purgeElement(elColgroup, true);
4613         elTable.removeChild(elColgroup);
4614         this._elColgroup = null;
4615     }
4616 },
4617
4618 /**
4619  * Initializes COLGROUP and COL elements for managing minWidth.
4620  *
4621  * @method _initColgroupEl
4622  * @param elTable {HTMLElement} TABLE element into which to create COLGROUP.
4623  * @private
4624  */
4625 _initColgroupEl : function(elTable) {
4626     if(elTable) {
4627         // Destroy previous
4628         this._destroyColgroupEl();
4629
4630         // Add COLs to DOCUMENT FRAGMENT
4631         var allCols = this._aColIds || [],
4632             allKeys = this._oColumnSet.keys,
4633             i = 0, len = allCols.length,
4634             elCol, oColumn,
4635             elFragment = document.createDocumentFragment(),
4636             elColTemplate = document.createElement("col");
4637     
4638         for(i=0,len=allKeys.length; i<len; i++) {
4639             oColumn = allKeys[i];
4640             elCol = elFragment.appendChild(elColTemplate.cloneNode(false));
4641         }
4642     
4643         // Create COLGROUP
4644         var elColgroup = elTable.insertBefore(document.createElement("colgroup"), elTable.firstChild);
4645         elColgroup.appendChild(elFragment);
4646         this._elColgroup = elColgroup;
4647     }
4648 },
4649
4650 /**
4651  * Adds a COL element to COLGROUP at given index.
4652  *
4653  * @method _insertColgroupColEl
4654  * @param index {Number} Index of new COL element.
4655  * @private
4656  */
4657 _insertColgroupColEl : function(index) {
4658     if(lang.isNumber(index)&& this._elColgroup) {
4659         var nextSibling = this._elColgroup.childNodes[index] || null;
4660         this._elColgroup.insertBefore(document.createElement("col"), nextSibling);
4661     }
4662 },
4663
4664 /**
4665  * Removes a COL element to COLGROUP at given index.
4666  *
4667  * @method _removeColgroupColEl
4668  * @param index {Number} Index of removed COL element.
4669  * @private
4670  */
4671 _removeColgroupColEl : function(index) {
4672     if(lang.isNumber(index) && this._elColgroup && this._elColgroup.childNodes[index]) {
4673         this._elColgroup.removeChild(this._elColgroup.childNodes[index]);
4674     }
4675 },
4676
4677 /**
4678  * Reorders a COL element from old index(es) to new index.
4679  *
4680  * @method _reorderColgroupColEl
4681  * @param aKeyIndexes {Number[]} Array of indexes of removed COL element.
4682  * @param newIndex {Number} New index. 
4683  * @private
4684  */
4685 _reorderColgroupColEl : function(aKeyIndexes, newIndex) {
4686     if(lang.isArray(aKeyIndexes) && lang.isNumber(newIndex) && this._elColgroup && (this._elColgroup.childNodes.length > aKeyIndexes[aKeyIndexes.length-1])) {
4687         var i,
4688             tmpCols = [];
4689         // Remove COL
4690         for(i=aKeyIndexes.length-1; i>-1; i--) {
4691             tmpCols.push(this._elColgroup.removeChild(this._elColgroup.childNodes[aKeyIndexes[i]]));
4692         }
4693         // Insert COL
4694         var nextSibling = this._elColgroup.childNodes[newIndex] || null;
4695         for(i=tmpCols.length-1; i>-1; i--) {
4696             this._elColgroup.insertBefore(tmpCols[i], nextSibling);
4697         }
4698     }
4699 },
4700
4701 /**
4702  * Destroy's the DataTable THEAD element, if available.
4703  *
4704  * @method _destroyTheadEl
4705  * @private
4706  */
4707 _destroyTheadEl : function() {
4708     var elThead = this._elThead;
4709     if(elThead) {
4710         var elTable = elThead.parentNode;
4711         Ev.purgeElement(elThead, true);
4712         this._destroyColumnHelpers();
4713         elTable.removeChild(elThead);
4714         this._elThead = null;
4715     }
4716 },
4717
4718 /**
4719  * Initializes THEAD element.
4720  *
4721  * @method _initTheadEl
4722  * @param elTable {HTMLElement} TABLE element into which to create COLGROUP.
4723  * @param {HTMLElement} Initialized THEAD element. 
4724  * @private
4725  */
4726 _initTheadEl : function(elTable) {
4727     elTable = elTable || this._elTable;
4728     
4729     if(elTable) {
4730         // Destroy previous
4731         this._destroyTheadEl();
4732     
4733         //TODO: append to DOM later for performance
4734         var elThead = (this._elColgroup) ?
4735             elTable.insertBefore(document.createElement("thead"), this._elColgroup.nextSibling) :
4736             elTable.appendChild(document.createElement("thead"));
4737     
4738         // Set up DOM events for THEAD
4739         Ev.addListener(elThead, "focus", this._onTheadFocus, this);
4740         Ev.addListener(elThead, "keydown", this._onTheadKeydown, this);
4741         Ev.addListener(elThead, "mouseover", this._onTableMouseover, this);
4742         Ev.addListener(elThead, "mouseout", this._onTableMouseout, this);
4743         Ev.addListener(elThead, "mousedown", this._onTableMousedown, this);
4744         Ev.addListener(elThead, "mouseup", this._onTableMouseup, this);
4745         Ev.addListener(elThead, "click", this._onTheadClick, this);
4746
4747         // Since we can't listen for click and dblclick on the same element...
4748         // Attach separately to THEAD and TBODY
4749         ///Ev.addListener(elThead, "dblclick", this._onTableDblclick, this);
4750         
4751        var oColumnSet = this._oColumnSet,
4752             oColumn, i,j, l;
4753         
4754         // Add TRs to the THEAD
4755         var colTree = oColumnSet.tree;
4756         var elTh;
4757         for(i=0; i<colTree.length; i++) {
4758             var elTheadTr = elThead.appendChild(document.createElement("tr"));
4759     
4760             // ...and create TH cells
4761             for(j=0; j<colTree[i].length; j++) {
4762                 oColumn = colTree[i][j];
4763                 elTh = elTheadTr.appendChild(document.createElement("th"));
4764                 this._initThEl(elTh,oColumn);
4765             }
4766     
4767                 // Set FIRST/LAST on THEAD rows
4768                 if(i === 0) {
4769                     Dom.addClass(elTheadTr, DT.CLASS_FIRST);
4770                 }
4771                 if(i === (colTree.length-1)) {
4772                     Dom.addClass(elTheadTr, DT.CLASS_LAST);
4773                 }
4774
4775         }
4776
4777         // Set FIRST/LAST on edge TH elements using the values in ColumnSet headers array
4778         var aFirstHeaders = oColumnSet.headers[0] || [];
4779         for(i=0; i<aFirstHeaders.length; i++) {
4780             Dom.addClass(Dom.get(this.getId() +"-th-"+aFirstHeaders[i]), DT.CLASS_FIRST);
4781         }
4782         var aLastHeaders = oColumnSet.headers[oColumnSet.headers.length-1] || [];
4783         for(i=0; i<aLastHeaders.length; i++) {
4784             Dom.addClass(Dom.get(this.getId() +"-th-"+aLastHeaders[i]), DT.CLASS_LAST);
4785         }
4786         
4787
4788         ///TODO: try _repaintGecko(this._elContainer) instead
4789         // Bug 1806891
4790         if(ua.webkit && ua.webkit < 420) {
4791             var oSelf = this;
4792             setTimeout(function() {
4793                 elThead.style.display = "";
4794             },0);
4795             elThead.style.display = 'none';
4796         }
4797         
4798         this._elThead = elThead;
4799         
4800         // Column helpers needs _elThead to exist
4801         this._initColumnHelpers();  
4802     }
4803 },
4804
4805 /**
4806  * Populates TH element as defined by Column.
4807  *
4808  * @method _initThEl
4809  * @param elTh {HTMLElement} TH element reference.
4810  * @param oColumn {YAHOO.widget.Column} Column object.
4811  * @private
4812  */
4813 _initThEl : function(elTh, oColumn) {
4814     elTh.id = this.getId() + "-th-" + oColumn.getSanitizedKey(); // Needed for accessibility, getColumn by TH, and ColumnDD
4815     elTh.innerHTML = "";
4816     elTh.rowSpan = oColumn.getRowspan();
4817     elTh.colSpan = oColumn.getColspan();
4818     oColumn._elTh = elTh;
4819     
4820     var elThLiner = elTh.appendChild(document.createElement("div"));
4821     elThLiner.id = elTh.id + "-liner"; // Needed for resizer
4822     elThLiner.className = DT.CLASS_LINER;
4823     oColumn._elThLiner = elThLiner;
4824     
4825     var elThLabel = elThLiner.appendChild(document.createElement("span"));
4826     elThLabel.className = DT.CLASS_LABEL;    
4827
4828     // Assign abbr attribute
4829     if(oColumn.abbr) {
4830         elTh.abbr = oColumn.abbr;
4831     }
4832     // Clear minWidth on hidden Columns
4833     if(oColumn.hidden) {
4834         this._clearMinWidth(oColumn);
4835     }
4836         
4837     elTh.className = this._getColumnClassNames(oColumn);
4838             
4839     // Set Column width...
4840     if(oColumn.width) {
4841         // Validate minWidth
4842         var nWidth = (oColumn.minWidth && (oColumn.width < oColumn.minWidth)) ?
4843                 oColumn.minWidth : oColumn.width;
4844         // ...for fallback cases
4845         if(DT._bDynStylesFallback) {
4846             elTh.firstChild.style.overflow = 'hidden';
4847             elTh.firstChild.style.width = nWidth + 'px';        
4848         }
4849         // ...for non fallback cases
4850         else {
4851             this._setColumnWidthDynStyles(oColumn, nWidth + 'px', 'hidden');
4852         }
4853     }
4854
4855     this.formatTheadCell(elThLabel, oColumn, this.get("sortedBy"));
4856     oColumn._elThLabel = elThLabel;
4857 },
4858
4859 /**
4860  * Outputs markup into the given TH based on given Column.
4861  *
4862  * @method DataTable.formatTheadCell
4863  * @param elCellLabel {HTMLElement} The label SPAN element within the TH liner,
4864  * not the liner DIV element.     
4865  * @param oColumn {YAHOO.widget.Column} Column instance.
4866  * @param oSortedBy {Object} Sort state object literal.
4867 */
4868 formatTheadCell : function(elCellLabel, oColumn, oSortedBy) {
4869     var sKey = oColumn.getKey();
4870     var sLabel = lang.isValue(oColumn.label) ? oColumn.label : sKey;
4871
4872     // Add accessibility link for sortable Columns
4873     if(oColumn.sortable) {
4874         // Calculate the direction
4875         var sSortClass = this.getColumnSortDir(oColumn, oSortedBy);
4876         var bDesc = (sSortClass === DT.CLASS_DESC);
4877
4878         // This is the sorted Column
4879         if(oSortedBy && (oColumn.key === oSortedBy.key)) {
4880             bDesc = !(oSortedBy.dir === DT.CLASS_DESC);
4881         }
4882
4883         // Generate a unique HREF for visited status
4884         var sHref = this.getId() + "-href-" + oColumn.getSanitizedKey();
4885         
4886         // Generate a dynamic TITLE for sort status
4887         var sTitle = (bDesc) ? this.get("MSG_SORTDESC") : this.get("MSG_SORTASC");
4888         
4889         // Format the element
4890         elCellLabel.innerHTML = "<a href=\"" + sHref + "\" title=\"" + sTitle + "\" class=\"" + DT.CLASS_SORTABLE + "\">" + sLabel + "</a>";
4891     }
4892     // Just display the label for non-sortable Columns
4893     else {
4894         elCellLabel.innerHTML = sLabel;
4895     }
4896 },
4897
4898 /**
4899  * Disables DD from top-level Column TH elements.
4900  *
4901  * @method _destroyDraggableColumns
4902  * @private
4903  */
4904 _destroyDraggableColumns : function() {
4905     var oColumn, elTh;
4906     for(var i=0, len=this._oColumnSet.tree[0].length; i<len; i++) {
4907         oColumn = this._oColumnSet.tree[0][i];
4908         if(oColumn._dd) {
4909             oColumn._dd = oColumn._dd.unreg();
4910             Dom.removeClass(oColumn.getThEl(), DT.CLASS_DRAGGABLE);       
4911         }
4912     }
4913 },
4914
4915 /**
4916  * Initializes top-level Column TH elements into DD instances.
4917  *
4918  * @method _initDraggableColumns
4919  * @private
4920  */
4921 _initDraggableColumns : function() {
4922     this._destroyDraggableColumns();
4923     if(util.DD) {
4924         var oColumn, elTh, elDragTarget;
4925         for(var i=0, len=this._oColumnSet.tree[0].length; i<len; i++) {
4926             oColumn = this._oColumnSet.tree[0][i];
4927             elTh = oColumn.getThEl();
4928             Dom.addClass(elTh, DT.CLASS_DRAGGABLE);
4929             elDragTarget = DT._initColumnDragTargetEl();
4930             oColumn._dd = new YAHOO.widget.ColumnDD(this, oColumn, elTh, elDragTarget);
4931         }
4932     }
4933     else {
4934     }
4935 },
4936
4937 /**
4938  * Disables resizeability on key Column TH elements.
4939  *
4940  * @method _destroyResizeableColumns
4941  * @private
4942  */
4943 _destroyResizeableColumns : function() {
4944     var aKeys = this._oColumnSet.keys;
4945     for(var i=0, len=aKeys.length; i<len; i++) {
4946         if(aKeys[i]._ddResizer) {
4947             aKeys[i]._ddResizer = aKeys[i]._ddResizer.unreg();
4948             Dom.removeClass(aKeys[i].getThEl(), DT.CLASS_RESIZEABLE);
4949         }
4950     }
4951 },
4952
4953 /**
4954  * Initializes resizeability on key Column TH elements.
4955  *
4956  * @method _initResizeableColumns
4957  * @private
4958  */
4959 _initResizeableColumns : function() {
4960     this._destroyResizeableColumns();
4961     if(util.DD) {
4962         var oColumn, elTh, elThLiner, elThResizerLiner, elThResizer, elResizerProxy, cancelClick;
4963         for(var i=0, len=this._oColumnSet.keys.length; i<len; i++) {
4964             oColumn = this._oColumnSet.keys[i];
4965             if(oColumn.resizeable) {
4966                 elTh = oColumn.getThEl();
4967                 Dom.addClass(elTh, DT.CLASS_RESIZEABLE);
4968                 elThLiner = oColumn.getThLinerEl();
4969                 
4970                 // Bug 1915349: So resizer is as tall as TH when rowspan > 1
4971                 // Create a separate resizer liner with position:relative
4972                 elThResizerLiner = elTh.appendChild(document.createElement("div"));
4973                 elThResizerLiner.className = DT.CLASS_RESIZERLINER;
4974                 
4975                 // Move TH contents into the new resizer liner
4976                 elThResizerLiner.appendChild(elThLiner);
4977                 
4978                 // Create the resizer
4979                 elThResizer = elThResizerLiner.appendChild(document.createElement("div"));
4980                 elThResizer.id = elTh.id + "-resizer"; // Needed for ColumnResizer
4981                 elThResizer.className = DT.CLASS_RESIZER;
4982                 oColumn._elResizer = elThResizer;
4983
4984                 // Create the resizer proxy, once globally
4985                 elResizerProxy = DT._initColumnResizerProxyEl();
4986                 oColumn._ddResizer = new YAHOO.util.ColumnResizer(
4987                         this, oColumn, elTh, elThResizer, elResizerProxy);
4988                 cancelClick = function(e) {
4989                     Ev.stopPropagation(e);
4990                 };
4991                 Ev.addListener(elThResizer,"click",cancelClick);
4992             }
4993         }
4994     }
4995     else {
4996     }
4997 },
4998
4999 /**
5000  * Destroys elements associated with Column functionality: ColumnDD and ColumnResizers.
5001  *
5002  * @method _destroyColumnHelpers
5003  * @private
5004  */
5005 _destroyColumnHelpers : function() {
5006     this._destroyDraggableColumns();
5007     this._destroyResizeableColumns();
5008 },
5009
5010 /**
5011  * Initializes elements associated with Column functionality: ColumnDD and ColumnResizers.
5012  *
5013  * @method _initColumnHelpers
5014  * @private
5015  */
5016 _initColumnHelpers : function() {
5017     if(this.get("draggableColumns")) {
5018         this._initDraggableColumns();
5019     }
5020     this._initResizeableColumns();
5021 },
5022
5023 /**
5024  * Destroy's the DataTable TBODY element, if available.
5025  *
5026  * @method _destroyTbodyEl
5027  * @private
5028  */
5029 _destroyTbodyEl : function() {
5030     var elTbody = this._elTbody;
5031     if(elTbody) {
5032         var elTable = elTbody.parentNode;
5033         Ev.purgeElement(elTbody, true);
5034         elTable.removeChild(elTbody);
5035         this._elTbody = null;
5036     }
5037 },
5038
5039 /**
5040  * Initializes TBODY element for data.
5041  *
5042  * @method _initTbodyEl
5043  * @param elTable {HTMLElement} TABLE element into which to create TBODY .
5044  * @private
5045  */
5046 _initTbodyEl : function(elTable) {
5047     if(elTable) {
5048         // Destroy previous
5049         this._destroyTbodyEl();
5050         
5051         // Create TBODY
5052         var elTbody = elTable.appendChild(document.createElement("tbody"));
5053         elTbody.tabIndex = 0;
5054         elTbody.className = DT.CLASS_DATA;
5055     
5056         // Set up DOM events for TBODY
5057         Ev.addListener(elTbody, "focus", this._onTbodyFocus, this);
5058         Ev.addListener(elTbody, "mouseover", this._onTableMouseover, this);
5059         Ev.addListener(elTbody, "mouseout", this._onTableMouseout, this);
5060         Ev.addListener(elTbody, "mousedown", this._onTableMousedown, this);
5061         Ev.addListener(elTbody, "mouseup", this._onTableMouseup, this);
5062         Ev.addListener(elTbody, "keydown", this._onTbodyKeydown, this);
5063         Ev.addListener(elTbody, "keypress", this._onTableKeypress, this);
5064         Ev.addListener(elTbody, "click", this._onTbodyClick, this);
5065         
5066         // Since we can't listen for click and dblclick on the same element...
5067         // Attach separately to THEAD and TBODY
5068         ///Ev.addListener(elTbody, "dblclick", this._onTableDblclick, this);
5069         
5070     
5071         // IE puts focus outline in the wrong place
5072         if(ua.ie) {
5073             elTbody.hideFocus=true;
5074         }
5075
5076         this._elTbody = elTbody;
5077     }
5078 },
5079
5080 /**
5081  * Destroy's the DataTable message TBODY element, if available.
5082  *
5083  * @method _destroyMsgTbodyEl
5084  * @private
5085  */
5086 _destroyMsgTbodyEl : function() {
5087     var elMsgTbody = this._elMsgTbody;
5088     if(elMsgTbody) {
5089         var elTable = elMsgTbody.parentNode;
5090         Ev.purgeElement(elMsgTbody, true);
5091         elTable.removeChild(elMsgTbody);
5092         this._elTbody = null;
5093     }
5094 },
5095
5096 /**
5097  * Initializes TBODY element for messaging.
5098  *
5099  * @method _initMsgTbodyEl
5100  * @param elTable {HTMLElement} TABLE element into which to create TBODY 
5101  * @private
5102  */
5103 _initMsgTbodyEl : function(elTable) {
5104     if(elTable) {
5105         var elMsgTbody = document.createElement("tbody");
5106         elMsgTbody.className = DT.CLASS_MESSAGE;
5107         var elMsgTr = elMsgTbody.appendChild(document.createElement("tr"));
5108         elMsgTr.className = DT.CLASS_FIRST + " " + DT.CLASS_LAST;
5109         this._elMsgTr = elMsgTr;
5110         var elMsgTd = elMsgTr.appendChild(document.createElement("td"));
5111         elMsgTd.colSpan = this._oColumnSet.keys.length || 1;
5112         elMsgTd.className = DT.CLASS_FIRST + " " + DT.CLASS_LAST;
5113         this._elMsgTd = elMsgTd;
5114         elMsgTbody = elTable.insertBefore(elMsgTbody, this._elTbody);
5115         var elMsgLiner = elMsgTd.appendChild(document.createElement("div"));
5116         elMsgLiner.className = DT.CLASS_LINER;
5117         this._elMsgTbody = elMsgTbody;
5118     }
5119 },
5120
5121 /**
5122  * Initialize internal event listeners
5123  *
5124  * @method _initEvents
5125  * @private
5126  */
5127 _initEvents : function () {
5128     // Initialize Column sort
5129     this._initColumnSort();
5130         
5131     // Add the document level click listener
5132     YAHOO.util.Event.addListener(document, "click", this._onDocumentClick, this);
5133
5134     // Paginator integration
5135     this.subscribe("paginatorChange",function () {
5136         this._handlePaginatorChange.apply(this,arguments);
5137     });
5138
5139     this.subscribe("initEvent",function () {
5140         this.renderPaginator();
5141     });
5142
5143     // Initialize CellEditor integration
5144     this._initCellEditing();
5145 },
5146
5147 /**      
5148   * Initializes Column sorting.          
5149   *      
5150   * @method _initColumnSort      
5151   * @private     
5152   */     
5153 _initColumnSort : function() {
5154     this.subscribe("theadCellClickEvent", this.onEventSortColumn);       
5155
5156     // Backward compatibility
5157     var oSortedBy = this.get("sortedBy");
5158     if(oSortedBy) {
5159         if(oSortedBy.dir == "desc") {
5160             this._configs.sortedBy.value.dir = DT.CLASS_DESC;
5161         }
5162         else if(oSortedBy.dir == "asc") {
5163             this._configs.sortedBy.value.dir = DT.CLASS_ASC;
5164         }
5165     }
5166 },
5167
5168 /**      
5169   * Initializes CellEditor integration.          
5170   *      
5171   * @method _initCellEditing     
5172   * @private     
5173   */     
5174 _initCellEditing : function() {
5175     this.subscribe("editorBlurEvent",function () {
5176         this.onEditorBlurEvent.apply(this,arguments);
5177     });
5178     this.subscribe("editorBlockEvent",function () {
5179         this.onEditorBlockEvent.apply(this,arguments);
5180     });
5181     this.subscribe("editorUnblockEvent",function () {
5182         this.onEditorUnblockEvent.apply(this,arguments);
5183     });
5184 },
5185
5186
5187
5188
5189
5190
5191
5192
5193
5194
5195
5196
5197
5198
5199
5200
5201
5202
5203
5204
5205
5206
5207
5208
5209
5210
5211
5212
5213
5214
5215
5216
5217
5218 // DOM MUTATION FUNCTIONS
5219
5220 /**
5221  * Retruns classnames to represent current Column states.
5222  * @method _getColumnClassnames 
5223  * @param oColumn {YAHOO.widget.Column} Column instance.
5224  * @param aAddClasses {String[]} An array of additional classnames to add to the
5225  * return value.  
5226  * @return {String} A String of classnames to be assigned to TH or TD elements
5227  * for given Column.  
5228  * @private 
5229  */
5230 _getColumnClassNames : function (oColumn, aAddClasses) {
5231     var allClasses;
5232     
5233     // Add CSS classes
5234     if(lang.isString(oColumn.className)) {
5235         // Single custom class
5236         allClasses = [oColumn.className];
5237     }
5238     else if(lang.isArray(oColumn.className)) {
5239         // Array of custom classes
5240         allClasses = oColumn.className;
5241     }
5242     else {
5243         // no custom classes
5244         allClasses = [];
5245     }
5246     
5247     // Hook for setting width with via dynamic style uses key since ID is too disposable
5248     allClasses[allClasses.length] = this.getId() + "-col-" +oColumn.getSanitizedKey();
5249
5250     // Column key - minus any chars other than "A-Z", "a-z", "0-9", "_", "-", ".", or ":"
5251     allClasses[allClasses.length] = "yui-dt-col-" +oColumn.getSanitizedKey();
5252
5253     var isSortedBy = this.get("sortedBy") || {};
5254     // Sorted
5255     if(oColumn.key === isSortedBy.key) {
5256         allClasses[allClasses.length] = isSortedBy.dir || '';
5257     }
5258     // Hidden
5259     if(oColumn.hidden) {
5260         allClasses[allClasses.length] = DT.CLASS_HIDDEN;
5261     }
5262     // Selected
5263     if(oColumn.selected) {
5264         allClasses[allClasses.length] = DT.CLASS_SELECTED;
5265     }
5266     // Sortable
5267     if(oColumn.sortable) {
5268         allClasses[allClasses.length] = DT.CLASS_SORTABLE;
5269     }
5270     // Resizeable
5271     if(oColumn.resizeable) {
5272         allClasses[allClasses.length] = DT.CLASS_RESIZEABLE;
5273     }
5274     // Editable
5275     if(oColumn.editor) {
5276         allClasses[allClasses.length] = DT.CLASS_EDITABLE;
5277     }
5278     
5279     // Addtnl classes, including First/Last
5280     if(aAddClasses) {
5281         allClasses = allClasses.concat(aAddClasses);
5282     }
5283     
5284     return allClasses.join(' ');  
5285 },
5286
5287 /**
5288  * Clears TR element template in response to any Column state change.
5289  * @method _clearTrTemplateEl
5290  * @private 
5291  */
5292 _clearTrTemplateEl : function () {
5293     this._elTrTemplate = null;
5294 },
5295
5296 /**
5297  * Returns a new TR element template with TD elements classed with current
5298  * Column states.
5299  * @method _getTrTemplateEl 
5300  * @return {HTMLElement} A TR element to be cloned and added to the DOM.
5301  * @private 
5302  */
5303 _getTrTemplateEl : function (oRecord, index) {
5304     // Template is already available
5305     if(this._elTrTemplate) {
5306         return this._elTrTemplate;
5307     }
5308     // Template needs to be created
5309     else {
5310         var d   = document,
5311             tr  = d.createElement('tr'),
5312             td  = d.createElement('td'),
5313             div = d.createElement('div');
5314     
5315         // Append the liner element
5316         td.appendChild(div);
5317
5318         // Create TD elements into DOCUMENT FRAGMENT
5319         var df = document.createDocumentFragment(),
5320             allKeys = this._oColumnSet.keys,
5321             elTd;
5322
5323         // Set state for each TD;
5324         var aAddClasses;
5325         for(var i=0, keysLen=allKeys.length; i<keysLen; i++) {
5326             // Clone the TD template
5327             elTd = td.cloneNode(true);
5328
5329             // Format the base TD
5330             elTd = this._formatTdEl(allKeys[i], elTd, i, (i===keysLen-1));
5331                         
5332             df.appendChild(elTd);
5333         }
5334         tr.appendChild(df);
5335         this._elTrTemplate = tr;
5336         return tr;
5337     }   
5338 },
5339
5340 /**
5341  * Formats a basic TD element.
5342  * @method _formatTdEl 
5343  * @param oColumn {YAHOO.widget.Column} Associated Column instance. 
5344  * @param elTd {HTMLElement} An unformatted TD element.
5345  * @param index {Number} Column key index. 
5346  * @param isLast {Boolean} True if Column is last key of the ColumnSet.
5347  * @return {HTMLElement} A formatted TD element.
5348  * @private 
5349  */
5350 _formatTdEl : function (oColumn, elTd, index, isLast) {
5351     var oColumnSet = this._oColumnSet;
5352     
5353     // Set the TD's accessibility headers
5354     var allHeaders = oColumnSet.headers,
5355         allColHeaders = allHeaders[index],
5356         sTdHeaders = "",
5357         sHeader;
5358     for(var j=0, headersLen=allColHeaders.length; j < headersLen; j++) {
5359         sHeader = this._sId + "-th-" + allColHeaders[j] + ' ';
5360         sTdHeaders += sHeader;
5361     }
5362     elTd.headers = sTdHeaders;
5363     
5364     // Class the TD element
5365     var aAddClasses = [];
5366     if(index === 0) {
5367         aAddClasses[aAddClasses.length] = DT.CLASS_FIRST;
5368     }
5369     if(isLast) {
5370         aAddClasses[aAddClasses.length] = DT.CLASS_LAST;
5371     }
5372     elTd.className = this._getColumnClassNames(oColumn, aAddClasses);
5373
5374     // Class the liner element
5375     elTd.firstChild.className = DT.CLASS_LINER;
5376
5377     // Set Column width for fallback cases
5378     if(oColumn.width && DT._bDynStylesFallback) {
5379         // Validate minWidth
5380         var nWidth = (oColumn.minWidth && (oColumn.width < oColumn.minWidth)) ?
5381                 oColumn.minWidth : oColumn.width;
5382         elTd.firstChild.style.overflow = 'hidden';
5383         elTd.firstChild.style.width = nWidth + 'px';
5384     }
5385     
5386     return elTd;
5387 },
5388
5389
5390 /**
5391  * Create a new TR element for a given Record and appends it with the correct
5392  * number of Column-state-classed TD elements. Striping is the responsibility of
5393  * the calling function, which may decide to stripe the single row, a subset of
5394  * rows, or all the rows.
5395  * @method _createTrEl
5396  * @param oRecord {YAHOO.widget.Record} Record instance
5397  * @return {HTMLElement} The new TR element.  This must be added to the DOM.
5398  * @private 
5399  */
5400 _addTrEl : function (oRecord) {
5401     var elTrTemplate = this._getTrTemplateEl();
5402     
5403     // Clone the TR template.
5404     var elTr = elTrTemplate.cloneNode(true);
5405     
5406     // Populate content
5407     return this._updateTrEl(elTr,oRecord);
5408 },
5409
5410 /**
5411  * Formats the contents of the given TR's TD elements with data from the given
5412  * Record. Only innerHTML should change, nothing structural.
5413  *
5414  * @method _updateTrEl
5415  * @param elTr {HTMLElement} The TR element to update.
5416  * @param oRecord {YAHOO.widget.Record} The associated Record instance.
5417  * @return {HTMLElement} DOM reference to the new TR element.
5418  * @private
5419  */
5420 _updateTrEl : function(elTr, oRecord) {
5421     var ok = this.get("formatRow") ? this.get("formatRow").call(this, elTr, oRecord) : true;
5422     if(ok) {
5423         // Hide the row to prevent constant reflows
5424         elTr.style.display = 'none';
5425         
5426         // Update TD elements with new data
5427         var allTds = elTr.childNodes,
5428             elTd;
5429         for(var i=0,len=allTds.length; i<len; ++i) {
5430             elTd = allTds[i];
5431             
5432             // Set the cell content
5433             this.formatCell(allTds[i].firstChild, oRecord, this._oColumnSet.keys[i]);
5434         }
5435         
5436         // Redisplay the row for reflow
5437         elTr.style.display = '';
5438     }
5439     
5440     elTr.id = oRecord.getId(); // Needed for Record association and tracking of FIRST/LAST
5441     return elTr;
5442 },
5443
5444
5445 /**
5446  * Deletes TR element by DOM reference or by DataTable page row index.
5447  *
5448  * @method _deleteTrEl
5449  * @param row {HTMLElement | Number} TR element reference or Datatable page row index.
5450  * @return {Boolean} Returns true if successful, else returns false.
5451  * @private
5452  */
5453 _deleteTrEl : function(row) {
5454     var rowIndex;
5455
5456     // Get page row index for the element
5457     if(!lang.isNumber(row)) {
5458         rowIndex = Dom.get(row).sectionRowIndex;
5459     }
5460     else {
5461         rowIndex = row;
5462     }
5463     if(lang.isNumber(rowIndex) && (rowIndex > -2) && (rowIndex < this._elTbody.rows.length)) {
5464         // Cannot use tbody.deleteRow due to IE6 instability
5465         //return this._elTbody.deleteRow(rowIndex);
5466         return this._elTbody.removeChild(this.getTrEl(row));
5467     }
5468     else {
5469         return null;
5470     }
5471 },
5472
5473
5474
5475
5476
5477
5478
5479
5480
5481
5482
5483
5484
5485
5486
5487
5488
5489
5490
5491
5492
5493
5494
5495
5496
5497
5498
5499 // CSS/STATE FUNCTIONS
5500
5501
5502
5503
5504 /**
5505  * Removes the class YAHOO.widget.DataTable.CLASS_FIRST from the first TR element
5506  * of the DataTable page and updates internal tracker.
5507  *
5508  * @method _unsetFirstRow
5509  * @private
5510  */
5511 _unsetFirstRow : function() {
5512     // Remove FIRST
5513     if(this._sFirstTrId) {
5514         Dom.removeClass(this._sFirstTrId, DT.CLASS_FIRST);
5515         this._sFirstTrId = null;
5516     }
5517 },
5518
5519 /**
5520  * Assigns the class YAHOO.widget.DataTable.CLASS_FIRST to the first TR element
5521  * of the DataTable page and updates internal tracker.
5522  *
5523  * @method _setFirstRow
5524  * @private
5525  */
5526 _setFirstRow : function() {
5527     this._unsetFirstRow();
5528     var elTr = this.getFirstTrEl();
5529     if(elTr) {
5530         // Set FIRST
5531         Dom.addClass(elTr, DT.CLASS_FIRST);
5532         this._sFirstTrId = elTr.id;
5533     }
5534 },
5535
5536 /**
5537  * Removes the class YAHOO.widget.DataTable.CLASS_LAST from the last TR element
5538  * of the DataTable page and updates internal tracker.
5539  *
5540  * @method _unsetLastRow
5541  * @private
5542  */
5543 _unsetLastRow : function() {
5544     // Unassign previous class
5545     if(this._sLastTrId) {
5546         Dom.removeClass(this._sLastTrId, DT.CLASS_LAST);
5547         this._sLastTrId = null;
5548     }   
5549 },
5550
5551 /**
5552  * Assigns the class YAHOO.widget.DataTable.CLASS_LAST to the last TR element
5553  * of the DataTable page and updates internal tracker.
5554  *
5555  * @method _setLastRow
5556  * @private
5557  */
5558 _setLastRow : function() {
5559     this._unsetLastRow();
5560     var elTr = this.getLastTrEl();
5561     if(elTr) {
5562         // Assign class
5563         Dom.addClass(elTr, DT.CLASS_LAST);
5564         this._sLastTrId = elTr.id;
5565     }
5566 },
5567
5568 /**
5569  * Assigns the classes DT.CLASS_EVEN and DT.CLASS_ODD to one, many, or all TR elements.
5570  *
5571  * @method _setRowStripes
5572  * @param row {HTMLElement | String | Number} (optional) HTML TR element reference
5573  * or string ID, or page row index of where to start striping.
5574  * @param range {Number} (optional) If given, how many rows to stripe, otherwise
5575  * stripe all the rows until the end.
5576  * @private
5577  */
5578 _setRowStripes : function(row, range) {
5579     // Default values stripe all rows
5580     var allRows = this._elTbody.rows,
5581         nStartIndex = 0,
5582         nEndIndex = allRows.length,
5583         aOdds = [], nOddIdx = 0,
5584         aEvens = [], nEvenIdx = 0;
5585
5586     // Stripe a subset
5587     if((row !== null) && (row !== undefined)) {
5588         // Validate given start row
5589         var elStartRow = this.getTrEl(row);
5590         if(elStartRow) {
5591             nStartIndex = elStartRow.sectionRowIndex;
5592
5593             // Validate given range
5594             if(lang.isNumber(range) && (range > 1)) {
5595                 nEndIndex = nStartIndex + range;
5596             }
5597         }
5598     }
5599
5600     for(var i=nStartIndex; i<nEndIndex; i++) {
5601         if(i%2) {
5602             aOdds[nOddIdx++] = allRows[i];
5603         } else {
5604             aEvens[nEvenIdx++] = allRows[i];
5605         }
5606     }
5607
5608     if (aOdds.length) {
5609         Dom.replaceClass(aOdds, DT.CLASS_EVEN, DT.CLASS_ODD);
5610     }
5611
5612     if (aEvens.length) {
5613         Dom.replaceClass(aEvens, DT.CLASS_ODD, DT.CLASS_EVEN);
5614     }
5615 },
5616
5617 /**
5618  * Assigns the class DT.CLASS_SELECTED to TR and TD elements.
5619  *
5620  * @method _setSelections
5621  * @private
5622  */
5623 _setSelections : function() {
5624     // Keep track of selected rows
5625     var allSelectedRows = this.getSelectedRows();
5626     // Keep track of selected cells
5627     var allSelectedCells = this.getSelectedCells();
5628     // Anything to select?
5629     if((allSelectedRows.length>0) || (allSelectedCells.length > 0)) {
5630         var oColumnSet = this._oColumnSet,
5631             el;
5632         // Loop over each row
5633         for(var i=0; i<allSelectedRows.length; i++) {
5634             el = Dom.get(allSelectedRows[i]);
5635             if(el) {
5636                 Dom.addClass(el, DT.CLASS_SELECTED);
5637             }
5638         }
5639         // Loop over each cell
5640         for(i=0; i<allSelectedCells.length; i++) {
5641             el = Dom.get(allSelectedCells[i].recordId);
5642             if(el) {
5643                 Dom.addClass(el.childNodes[oColumnSet.getColumn(allSelectedCells[i].columnKey).getKeyIndex()], DT.CLASS_SELECTED);
5644             }
5645         }
5646     }       
5647 },
5648
5649
5650
5651
5652
5653
5654
5655
5656
5657
5658
5659
5660
5661
5662
5663
5664
5665
5666
5667
5668
5669
5670
5671
5672
5673
5674
5675
5676
5677
5678
5679
5680
5681
5682
5683
5684
5685
5686
5687
5688
5689
5690
5691 /////////////////////////////////////////////////////////////////////////////
5692 //
5693 // Private DOM Event Handlers
5694 //
5695 /////////////////////////////////////////////////////////////////////////////
5696
5697 /**
5698  * Validates minWidths whenever the render chain ends.
5699  *
5700  * @method _onRenderChainEnd
5701  * @private
5702  */
5703 _onRenderChainEnd : function() {
5704     // Hide loading message
5705     this.hideTableMessage();
5706     
5707     // Show empty message
5708     if(this._elTbody.rows.length === 0) {
5709         this.showTableMessage(this.get("MSG_EMPTY"), DT.CLASS_EMPTY);        
5710     }
5711
5712     // Execute in timeout thread to give implementers a chance
5713     // to subscribe after the constructor
5714     var oSelf = this;
5715     setTimeout(function() {
5716         if((oSelf instanceof DT) && oSelf._sId) {        
5717             // Init event
5718             if(oSelf._bInit) {
5719                 oSelf._bInit = false;
5720                 oSelf.fireEvent("initEvent");
5721             }
5722     
5723             // Render event
5724             oSelf.fireEvent("renderEvent");
5725             // Backward compatibility
5726             oSelf.fireEvent("refreshEvent");
5727     
5728             // Post-render routine
5729             oSelf.validateColumnWidths();
5730     
5731             // Post-render event
5732             oSelf.fireEvent("postRenderEvent");
5733             
5734             /*if(YAHOO.example.Performance.trialStart) {
5735                 YAHOO.example.Performance.trialStart = null;
5736             }*/
5737             
5738         }
5739     }, 0);
5740 },
5741
5742 /**
5743  * Handles click events on the DOCUMENT.
5744  *
5745  * @method _onDocumentClick
5746  * @param e {HTMLEvent} The click event.
5747  * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
5748  * @private
5749  */
5750 _onDocumentClick : function(e, oSelf) {
5751     var elTarget = Ev.getTarget(e);
5752     var elTag = elTarget.nodeName.toLowerCase();
5753
5754     if(!Dom.isAncestor(oSelf._elContainer, elTarget)) {
5755         oSelf.fireEvent("tableBlurEvent");
5756
5757         // Fires editorBlurEvent when click is not within the TABLE.
5758         // For cases when click is within the TABLE, due to timing issues,
5759         // the editorBlurEvent needs to get fired by the lower-level DOM click
5760         // handlers below rather than by the TABLE click handler directly.
5761         if(oSelf._oCellEditor) {
5762             if(oSelf._oCellEditor.getContainerEl) {
5763                 var elContainer = oSelf._oCellEditor.getContainerEl();
5764                 // Only if the click was not within the CellEditor container
5765                 if(!Dom.isAncestor(elContainer, elTarget) &&
5766                         (elContainer.id !== elTarget.id)) {
5767                     oSelf._oCellEditor.fireEvent("blurEvent", {editor: oSelf._oCellEditor});
5768                 }
5769             }
5770             // Backward Compatibility
5771             else if(oSelf._oCellEditor.isActive) {
5772                 // Only if the click was not within the Cell Editor container
5773                 if(!Dom.isAncestor(oSelf._oCellEditor.container, elTarget) &&
5774                         (oSelf._oCellEditor.container.id !== elTarget.id)) {
5775                     oSelf.fireEvent("editorBlurEvent", {editor:oSelf._oCellEditor});
5776                 }
5777             }
5778         }
5779     }
5780 },
5781
5782 /**
5783  * Handles focus events on the DataTable instance.
5784  *
5785  * @method _onTableFocus
5786  * @param e {HTMLEvent} The focus event.
5787  * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
5788  * @private
5789  */
5790 _onTableFocus : function(e, oSelf) {
5791     oSelf.fireEvent("tableFocusEvent");
5792 },
5793
5794 /**
5795  * Handles focus events on the THEAD element.
5796  *
5797  * @method _onTheadFocus
5798  * @param e {HTMLEvent} The focus event.
5799  * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
5800  * @private
5801  */
5802 _onTheadFocus : function(e, oSelf) {
5803     oSelf.fireEvent("theadFocusEvent");
5804     oSelf.fireEvent("tableFocusEvent");
5805 },
5806
5807 /**
5808  * Handles focus events on the TBODY element.
5809  *
5810  * @method _onTbodyFocus
5811  * @param e {HTMLEvent} The focus event.
5812  * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
5813  * @private
5814  */
5815 _onTbodyFocus : function(e, oSelf) {
5816     oSelf.fireEvent("tbodyFocusEvent");
5817     oSelf.fireEvent("tableFocusEvent");
5818 },
5819
5820 /**
5821  * Handles mouseover events on the DataTable instance.
5822  *
5823  * @method _onTableMouseover
5824  * @param e {HTMLEvent} The mouseover event.
5825  * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
5826  * @private
5827  */
5828 _onTableMouseover : function(e, oSelf) {
5829     var elTarget = Ev.getTarget(e);
5830         var elTag = elTarget.nodeName.toLowerCase();
5831         var bKeepBubbling = true;
5832         while(elTarget && (elTag != "table")) {
5833             switch(elTag) {
5834                 case "body":
5835                      return;
5836                 case "a":
5837                     break;
5838                 case "td":
5839                     bKeepBubbling = oSelf.fireEvent("cellMouseoverEvent",{target:elTarget,event:e});
5840                     break;
5841                 case "span":
5842                     if(Dom.hasClass(elTarget, DT.CLASS_LABEL)) {
5843                         bKeepBubbling = oSelf.fireEvent("theadLabelMouseoverEvent",{target:elTarget,event:e});
5844                         // Backward compatibility
5845                         bKeepBubbling = oSelf.fireEvent("headerLabelMouseoverEvent",{target:elTarget,event:e});
5846                     }
5847                     break;
5848                 case "th":
5849                     bKeepBubbling = oSelf.fireEvent("theadCellMouseoverEvent",{target:elTarget,event:e});
5850                     // Backward compatibility
5851                     bKeepBubbling = oSelf.fireEvent("headerCellMouseoverEvent",{target:elTarget,event:e});
5852                     break;
5853                 case "tr":
5854                     if(elTarget.parentNode.nodeName.toLowerCase() == "thead") {
5855                         bKeepBubbling = oSelf.fireEvent("theadRowMouseoverEvent",{target:elTarget,event:e});
5856                         // Backward compatibility
5857                         bKeepBubbling = oSelf.fireEvent("headerRowMouseoverEvent",{target:elTarget,event:e});
5858                     }
5859                     else {
5860                         bKeepBubbling = oSelf.fireEvent("rowMouseoverEvent",{target:elTarget,event:e});
5861                     }
5862                     break;
5863                 default:
5864                     break;
5865             }
5866             if(bKeepBubbling === false) {
5867                 return;
5868             }
5869             else {
5870                 elTarget = elTarget.parentNode;
5871                 if(elTarget) {
5872                     elTag = elTarget.nodeName.toLowerCase();
5873                 }
5874             }
5875         }
5876         oSelf.fireEvent("tableMouseoverEvent",{target:(elTarget || oSelf._elContainer),event:e});
5877 },
5878
5879 /**
5880  * Handles mouseout events on the DataTable instance.
5881  *
5882  * @method _onTableMouseout
5883  * @param e {HTMLEvent} The mouseout event.
5884  * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
5885  * @private
5886  */
5887 _onTableMouseout : function(e, oSelf) {
5888     var elTarget = Ev.getTarget(e);
5889     var elTag = elTarget.nodeName.toLowerCase();
5890     var bKeepBubbling = true;
5891     while(elTarget && (elTag != "table")) {
5892         switch(elTag) {
5893             case "body":
5894                 return;
5895             case "a":
5896                 break;
5897             case "td":
5898                 bKeepBubbling = oSelf.fireEvent("cellMouseoutEvent",{target:elTarget,event:e});
5899                 break;
5900             case "span":
5901                 if(Dom.hasClass(elTarget, DT.CLASS_LABEL)) {
5902                     bKeepBubbling = oSelf.fireEvent("theadLabelMouseoutEvent",{target:elTarget,event:e});
5903                     // Backward compatibility
5904                     bKeepBubbling = oSelf.fireEvent("headerLabelMouseoutEvent",{target:elTarget,event:e});
5905                 }
5906                 break;
5907             case "th":
5908                 bKeepBubbling = oSelf.fireEvent("theadCellMouseoutEvent",{target:elTarget,event:e});
5909                 // Backward compatibility
5910                 bKeepBubbling = oSelf.fireEvent("headerCellMouseoutEvent",{target:elTarget,event:e});
5911                 break;
5912             case "tr":
5913                 if(elTarget.parentNode.nodeName.toLowerCase() == "thead") {
5914                     bKeepBubbling = oSelf.fireEvent("theadRowMouseoutEvent",{target:elTarget,event:e});
5915                     // Backward compatibility
5916                     bKeepBubbling = oSelf.fireEvent("headerRowMouseoutEvent",{target:elTarget,event:e});
5917                 }
5918                 else {
5919                     bKeepBubbling = oSelf.fireEvent("rowMouseoutEvent",{target:elTarget,event:e});
5920                 }
5921                 break;
5922             default:
5923                 break;
5924         }
5925         if(bKeepBubbling === false) {
5926             return;
5927         }
5928         else {
5929             elTarget = elTarget.parentNode;
5930             if(elTarget) {
5931                 elTag = elTarget.nodeName.toLowerCase();
5932             }
5933         }
5934     }
5935     oSelf.fireEvent("tableMouseoutEvent",{target:(elTarget || oSelf._elContainer),event:e});
5936 },
5937
5938 /**
5939  * Handles mousedown events on the DataTable instance.
5940  *
5941  * @method _onTableMousedown
5942  * @param e {HTMLEvent} The mousedown event.
5943  * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
5944  * @private
5945  */
5946 _onTableMousedown : function(e, oSelf) {
5947     var elTarget = Ev.getTarget(e);
5948     var elTag = elTarget.nodeName.toLowerCase();
5949     var bKeepBubbling = true;
5950     while(elTarget && (elTag != "table")) {
5951         switch(elTag) {
5952             case "body":
5953                 return;
5954             case "a":
5955                 break;
5956             case "td":
5957                 bKeepBubbling = oSelf.fireEvent("cellMousedownEvent",{target:elTarget,event:e});
5958                 break;
5959             case "span":
5960                 if(Dom.hasClass(elTarget, DT.CLASS_LABEL)) {
5961                     bKeepBubbling = oSelf.fireEvent("theadLabelMousedownEvent",{target:elTarget,event:e});
5962                     // Backward compatibility
5963                     bKeepBubbling = oSelf.fireEvent("headerLabelMousedownEvent",{target:elTarget,event:e});
5964                 }
5965                 break;
5966             case "th":
5967                 bKeepBubbling = oSelf.fireEvent("theadCellMousedownEvent",{target:elTarget,event:e});
5968                 // Backward compatibility
5969                 bKeepBubbling = oSelf.fireEvent("headerCellMousedownEvent",{target:elTarget,event:e});
5970                 break;
5971             case "tr":
5972                 if(elTarget.parentNode.nodeName.toLowerCase() == "thead") {
5973                     bKeepBubbling = oSelf.fireEvent("theadRowMousedownEvent",{target:elTarget,event:e});
5974                     // Backward compatibility
5975                     bKeepBubbling = oSelf.fireEvent("headerRowMousedownEvent",{target:elTarget,event:e});
5976                 }
5977                 else {
5978                     bKeepBubbling = oSelf.fireEvent("rowMousedownEvent",{target:elTarget,event:e});
5979                 }
5980                 break;
5981             default:
5982                 break;
5983         }
5984         if(bKeepBubbling === false) {
5985             return;
5986         }
5987         else {
5988             elTarget = elTarget.parentNode;
5989             if(elTarget) {
5990                 elTag = elTarget.nodeName.toLowerCase();
5991             }
5992         }
5993     }
5994     oSelf.fireEvent("tableMousedownEvent",{target:(elTarget || oSelf._elContainer),event:e});
5995 },
5996
5997 /**
5998  * Handles mouseup events on the DataTable instance.
5999  *
6000  * @method _onTableMouseup
6001  * @param e {HTMLEvent} The mouseup event.
6002  * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
6003  * @private
6004  */
6005 _onTableMouseup : function(e, oSelf) {
6006     var elTarget = Ev.getTarget(e);
6007     var elTag = elTarget.nodeName.toLowerCase();
6008     var bKeepBubbling = true;
6009     while(elTarget && (elTag != "table")) {
6010         switch(elTag) {
6011             case "body":
6012                 return;
6013             case "a":
6014                 break;
6015             case "td":
6016                 bKeepBubbling = oSelf.fireEvent("cellMouseupEvent",{target:elTarget,event:e});
6017                 break;
6018             case "span":
6019                 if(Dom.hasClass(elTarget, DT.CLASS_LABEL)) {
6020                     bKeepBubbling = oSelf.fireEvent("theadLabelMouseupEvent",{target:elTarget,event:e});
6021                     // Backward compatibility
6022                     bKeepBubbling = oSelf.fireEvent("headerLabelMouseupEvent",{target:elTarget,event:e});
6023                 }
6024                 break;
6025             case "th":
6026                 bKeepBubbling = oSelf.fireEvent("theadCellMouseupEvent",{target:elTarget,event:e});
6027                 // Backward compatibility
6028                 bKeepBubbling = oSelf.fireEvent("headerCellMouseupEvent",{target:elTarget,event:e});
6029                 break;
6030             case "tr":
6031                 if(elTarget.parentNode.nodeName.toLowerCase() == "thead") {
6032                     bKeepBubbling = oSelf.fireEvent("theadRowMouseupEvent",{target:elTarget,event:e});
6033                     // Backward compatibility
6034                     bKeepBubbling = oSelf.fireEvent("headerRowMouseupEvent",{target:elTarget,event:e});
6035                 }
6036                 else {
6037                     bKeepBubbling = oSelf.fireEvent("rowMouseupEvent",{target:elTarget,event:e});
6038                 }
6039                 break;
6040             default:
6041                 break;
6042         }
6043         if(bKeepBubbling === false) {
6044             return;
6045         }
6046         else {
6047             elTarget = elTarget.parentNode;
6048             if(elTarget) {
6049                 elTag = elTarget.nodeName.toLowerCase();
6050             }
6051         }
6052     }
6053     oSelf.fireEvent("tableMouseupEvent",{target:(elTarget || oSelf._elContainer),event:e});
6054 },
6055
6056 /**
6057  * Handles dblclick events on the DataTable instance.
6058  *
6059  * @method _onTableDblclick
6060  * @param e {HTMLEvent} The dblclick event.
6061  * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
6062  * @private
6063  */
6064 _onTableDblclick : function(e, oSelf) {
6065     var elTarget = Ev.getTarget(e);
6066     var elTag = elTarget.nodeName.toLowerCase();
6067     var bKeepBubbling = true;
6068     while(elTarget && (elTag != "table")) {
6069         switch(elTag) {
6070             case "body":
6071                 return;
6072             case "td":
6073                 bKeepBubbling = oSelf.fireEvent("cellDblclickEvent",{target:elTarget,event:e});
6074                 break;
6075             case "span":
6076                 if(Dom.hasClass(elTarget, DT.CLASS_LABEL)) {
6077                     bKeepBubbling = oSelf.fireEvent("theadLabelDblclickEvent",{target:elTarget,event:e});
6078                     // Backward compatibility
6079                     bKeepBubbling = oSelf.fireEvent("headerLabelDblclickEvent",{target:elTarget,event:e});
6080                 }
6081                 break;
6082             case "th":
6083                 bKeepBubbling = oSelf.fireEvent("theadCellDblclickEvent",{target:elTarget,event:e});
6084                 // Backward compatibility
6085                 bKeepBubbling = oSelf.fireEvent("headerCellDblclickEvent",{target:elTarget,event:e});
6086                 break;
6087             case "tr":
6088                 if(elTarget.parentNode.nodeName.toLowerCase() == "thead") {
6089                     bKeepBubbling = oSelf.fireEvent("theadRowDblclickEvent",{target:elTarget,event:e});
6090                     // Backward compatibility
6091                     bKeepBubbling = oSelf.fireEvent("headerRowDblclickEvent",{target:elTarget,event:e});
6092                 }
6093                 else {
6094                     bKeepBubbling = oSelf.fireEvent("rowDblclickEvent",{target:elTarget,event:e});
6095                 }
6096                 break;
6097             default:
6098                 break;
6099         }
6100         if(bKeepBubbling === false) {
6101             return;
6102         }
6103         else {
6104             elTarget = elTarget.parentNode;
6105             if(elTarget) {
6106                 elTag = elTarget.nodeName.toLowerCase();
6107             }
6108         }
6109     }
6110     oSelf.fireEvent("tableDblclickEvent",{target:(elTarget || oSelf._elContainer),event:e});
6111 },
6112 /**
6113  * Handles keydown events on the THEAD element.
6114  *
6115  * @method _onTheadKeydown
6116  * @param e {HTMLEvent} The key event.
6117  * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
6118  * @private
6119  */
6120 _onTheadKeydown : function(e, oSelf) {
6121     var elTarget = Ev.getTarget(e);
6122     var elTag = elTarget.nodeName.toLowerCase();
6123     var bKeepBubbling = true;
6124     while(elTarget && (elTag != "table")) {
6125         switch(elTag) {
6126             case "body":
6127                 return;
6128             case "input":
6129             case "textarea":
6130                 // TODO: implement textareaKeyEvent
6131                 break;
6132             case "thead":
6133                 bKeepBubbling = oSelf.fireEvent("theadKeyEvent",{target:elTarget,event:e});
6134                 break;
6135             default:
6136                 break;
6137         }
6138         if(bKeepBubbling === false) {
6139             return;
6140         }
6141         else {
6142             elTarget = elTarget.parentNode;
6143             if(elTarget) {
6144                 elTag = elTarget.nodeName.toLowerCase();
6145             }
6146         }
6147     }
6148     oSelf.fireEvent("tableKeyEvent",{target:(elTarget || oSelf._elContainer),event:e});
6149 },
6150
6151 /**
6152  * Handles keydown events on the TBODY element. Handles selection behavior,
6153  * provides hooks for ENTER to edit functionality.
6154  *
6155  * @method _onTbodyKeydown
6156  * @param e {HTMLEvent} The key event.
6157  * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
6158  * @private
6159  */
6160 _onTbodyKeydown : function(e, oSelf) {
6161     var sMode = oSelf.get("selectionMode");
6162
6163     if(sMode == "standard") {
6164         oSelf._handleStandardSelectionByKey(e);
6165     }
6166     else if(sMode == "single") {
6167         oSelf._handleSingleSelectionByKey(e);
6168     }
6169     else if(sMode == "cellblock") {
6170         oSelf._handleCellBlockSelectionByKey(e);
6171     }
6172     else if(sMode == "cellrange") {
6173         oSelf._handleCellRangeSelectionByKey(e);
6174     }
6175     else if(sMode == "singlecell") {
6176         oSelf._handleSingleCellSelectionByKey(e);
6177     }
6178     
6179     if(oSelf._oCellEditor) {
6180         if(oSelf._oCellEditor.fireEvent) {
6181             oSelf._oCellEditor.fireEvent("blurEvent", {editor: oSelf._oCellEditor});
6182         }
6183         else if(oSelf._oCellEditor.isActive) {
6184             oSelf.fireEvent("editorBlurEvent", {editor:oSelf._oCellEditor});
6185         }
6186     }
6187
6188     var elTarget = Ev.getTarget(e);
6189     var elTag = elTarget.nodeName.toLowerCase();
6190     var bKeepBubbling = true;
6191     while(elTarget && (elTag != "table")) {
6192         switch(elTag) {
6193             case "body":
6194                 return;
6195             case "tbody":
6196                 bKeepBubbling = oSelf.fireEvent("tbodyKeyEvent",{target:elTarget,event:e});
6197                 break;
6198             default:
6199                 break;
6200         }
6201         if(bKeepBubbling === false) {
6202             return;
6203         }
6204         else {
6205             elTarget = elTarget.parentNode;
6206             if(elTarget) {
6207                 elTag = elTarget.nodeName.toLowerCase();
6208             }
6209         }
6210     }
6211     oSelf.fireEvent("tableKeyEvent",{target:(elTarget || oSelf._elContainer),event:e});
6212 },
6213
6214 /**
6215  * Handles keypress events on the TABLE. Mainly to support stopEvent on Mac.
6216  *
6217  * @method _onTableKeypress
6218  * @param e {HTMLEvent} The key event.
6219  * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
6220  * @private
6221  */
6222 _onTableKeypress : function(e, oSelf) {
6223     if(ua.opera || (navigator.userAgent.toLowerCase().indexOf("mac") !== -1) && (ua.webkit < 420)) {
6224         var nKey = Ev.getCharCode(e);
6225         // arrow down
6226         if(nKey == 40) {
6227             Ev.stopEvent(e);
6228         }
6229         // arrow up
6230         else if(nKey == 38) {
6231             Ev.stopEvent(e);
6232         }
6233     }
6234 },
6235
6236 /**
6237  * Handles click events on the THEAD element.
6238  *
6239  * @method _onTheadClick
6240  * @param e {HTMLEvent} The click event.
6241  * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
6242  * @private
6243  */
6244 _onTheadClick : function(e, oSelf) {
6245     // This blurs the CellEditor
6246     if(oSelf._oCellEditor) {
6247         if(oSelf._oCellEditor.fireEvent) {
6248             oSelf._oCellEditor.fireEvent("blurEvent", {editor: oSelf._oCellEditor});
6249         }
6250         // Backward compatibility
6251         else if(oSelf._oCellEditor.isActive) {
6252             oSelf.fireEvent("editorBlurEvent", {editor:oSelf._oCellEditor});
6253         }
6254     }
6255
6256     var elTarget = Ev.getTarget(e),
6257         elTag = elTarget.nodeName.toLowerCase(),
6258         bKeepBubbling = true;
6259     while(elTarget && (elTag != "table")) {
6260         switch(elTag) {
6261             case "body":
6262                 return;
6263             case "input":
6264                 var sType = elTarget.type.toLowerCase();
6265                 if(sType == "checkbox") {
6266                     bKeepBubbling = oSelf.fireEvent("theadCheckboxClickEvent",{target:elTarget,event:e});
6267                 }
6268                 else if(sType == "radio") {
6269                     bKeepBubbling = oSelf.fireEvent("theadRadioClickEvent",{target:elTarget,event:e});
6270                 }
6271                 else if((sType == "button") || (sType == "image") || (sType == "submit") || (sType == "reset")) {
6272                     bKeepBubbling = oSelf.fireEvent("theadButtonClickEvent",{target:elTarget,event:e});
6273                 }
6274                 break;
6275             case "a":
6276                 bKeepBubbling = oSelf.fireEvent("theadLinkClickEvent",{target:elTarget,event:e});
6277                 break;
6278             case "button":
6279                 bKeepBubbling = oSelf.fireEvent("theadButtonClickEvent",{target:elTarget,event:e});
6280                 break;
6281             case "span":
6282                 if(Dom.hasClass(elTarget, DT.CLASS_LABEL)) {
6283                     bKeepBubbling = oSelf.fireEvent("theadLabelClickEvent",{target:elTarget,event:e});
6284                     // Backward compatibility
6285                     bKeepBubbling = oSelf.fireEvent("headerLabelClickEvent",{target:elTarget,event:e});
6286                 }
6287                 break;
6288             case "th":
6289                 bKeepBubbling = oSelf.fireEvent("theadCellClickEvent",{target:elTarget,event:e});
6290                 // Backward compatibility
6291                 bKeepBubbling = oSelf.fireEvent("headerCellClickEvent",{target:elTarget,event:e});
6292                 break;
6293             case "tr":
6294                 bKeepBubbling = oSelf.fireEvent("theadRowClickEvent",{target:elTarget,event:e});
6295                 // Backward compatibility
6296                 bKeepBubbling = oSelf.fireEvent("headerRowClickEvent",{target:elTarget,event:e});
6297                 break;
6298             default:
6299                 break;
6300         }
6301         if(bKeepBubbling === false) {
6302             return;
6303         }
6304         else {
6305             elTarget = elTarget.parentNode;
6306             if(elTarget) {
6307                 elTag = elTarget.nodeName.toLowerCase();
6308             }
6309         }
6310     }
6311     oSelf.fireEvent("tableClickEvent",{target:(elTarget || oSelf._elContainer),event:e});
6312 },
6313
6314 /**
6315  * Handles click events on the primary TBODY element.
6316  *
6317  * @method _onTbodyClick
6318  * @param e {HTMLEvent} The click event.
6319  * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
6320  * @private
6321  */
6322 _onTbodyClick : function(e, oSelf) {
6323     // This blurs the CellEditor
6324     if(oSelf._oCellEditor) {
6325         if(oSelf._oCellEditor.fireEvent) {
6326             oSelf._oCellEditor.fireEvent("blurEvent", {editor: oSelf._oCellEditor});
6327         }
6328         else if(oSelf._oCellEditor.isActive) {
6329             oSelf.fireEvent("editorBlurEvent", {editor:oSelf._oCellEditor});
6330         }
6331     }
6332
6333     // Fire Custom Events
6334     var elTarget = Ev.getTarget(e),
6335         elTag = elTarget.nodeName.toLowerCase(),
6336         bKeepBubbling = true;
6337     while(elTarget && (elTag != "table")) {
6338         switch(elTag) {
6339             case "body":
6340                 return;
6341             case "input":
6342                 var sType = elTarget.type.toLowerCase();
6343                 if(sType == "checkbox") {
6344                     bKeepBubbling = oSelf.fireEvent("checkboxClickEvent",{target:elTarget,event:e});
6345                 }
6346                 else if(sType == "radio") {
6347                     bKeepBubbling = oSelf.fireEvent("radioClickEvent",{target:elTarget,event:e});
6348                 }
6349                 else if((sType == "button") || (sType == "image") || (sType == "submit") || (sType == "reset")) {
6350                     bKeepBubbling = oSelf.fireEvent("buttonClickEvent",{target:elTarget,event:e});
6351                 }
6352                 break;
6353             case "a":
6354                 bKeepBubbling = oSelf.fireEvent("linkClickEvent",{target:elTarget,event:e});
6355                 break;
6356             case "button":
6357                 bKeepBubbling = oSelf.fireEvent("buttonClickEvent",{target:elTarget,event:e});
6358                 break;
6359             case "td":
6360                 bKeepBubbling = oSelf.fireEvent("cellClickEvent",{target:elTarget,event:e});
6361                 break;
6362             case "tr":
6363                 bKeepBubbling = oSelf.fireEvent("rowClickEvent",{target:elTarget,event:e});
6364                 break;
6365             default:
6366                 break;
6367         }
6368         if(bKeepBubbling === false) {
6369             return;
6370         }
6371         else {
6372             elTarget = elTarget.parentNode;
6373             if(elTarget) {
6374                 elTag = elTarget.nodeName.toLowerCase();
6375             }
6376         }
6377     }
6378     oSelf.fireEvent("tableClickEvent",{target:(elTarget || oSelf._elContainer),event:e});
6379 },
6380
6381 /**
6382  * Handles change events on SELECT elements within DataTable.
6383  *
6384  * @method _onDropdownChange
6385  * @param e {HTMLEvent} The change event.
6386  * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
6387  * @private
6388  */
6389 _onDropdownChange : function(e, oSelf) {
6390     var elTarget = Ev.getTarget(e);
6391     oSelf.fireEvent("dropdownChangeEvent", {event:e, target:elTarget});
6392 },
6393
6394
6395
6396
6397
6398
6399
6400
6401
6402
6403
6404
6405
6406
6407
6408
6409
6410
6411
6412
6413
6414
6415
6416
6417
6418
6419
6420
6421
6422
6423
6424
6425 /////////////////////////////////////////////////////////////////////////////
6426 //
6427 // Public member variables
6428 //
6429 /////////////////////////////////////////////////////////////////////////////
6430 /**
6431  * Returns object literal of initial configs.
6432  *
6433  * @property configs
6434  * @type Object
6435  * @default {} 
6436  */
6437 configs: null,
6438
6439
6440 /////////////////////////////////////////////////////////////////////////////
6441 //
6442 // Public methods
6443 //
6444 /////////////////////////////////////////////////////////////////////////////
6445
6446 /**
6447  * Returns unique id assigned to instance, which is a useful prefix for
6448  * generating unique DOM ID strings.
6449  *
6450  * @method getId
6451  * @return {String} Unique ID of the DataSource instance.
6452  */
6453 getId : function() {
6454     return this._sId;
6455 },
6456
6457 /**
6458  * DataSource instance name, for logging.
6459  *
6460  * @method toString
6461  * @return {String} Unique name of the DataSource instance.
6462  */
6463
6464 toString : function() {
6465     return "DataTable instance " + this._sId;
6466 },
6467
6468 /**
6469  * Returns the DataTable instance's DataSource instance.
6470  *
6471  * @method getDataSource
6472  * @return {YAHOO.util.DataSource} DataSource instance.
6473  */
6474 getDataSource : function() {
6475     return this._oDataSource;
6476 },
6477
6478 /**
6479  * Returns the DataTable instance's ColumnSet instance.
6480  *
6481  * @method getColumnSet
6482  * @return {YAHOO.widget.ColumnSet} ColumnSet instance.
6483  */
6484 getColumnSet : function() {
6485     return this._oColumnSet;
6486 },
6487
6488 /**
6489  * Returns the DataTable instance's RecordSet instance.
6490  *
6491  * @method getRecordSet
6492  * @return {YAHOO.widget.RecordSet} RecordSet instance.
6493  */
6494 getRecordSet : function() {
6495     return this._oRecordSet;
6496 },
6497
6498 /**
6499  * Returns on object literal representing the DataTable instance's current
6500  * state with the following properties:
6501  * <dl>
6502  * <dt>pagination</dt>
6503  * <dd>Instance of YAHOO.widget.Paginator</dd>
6504  *
6505  * <dt>sortedBy</dt>
6506  * <dd>
6507  *     <dl>
6508  *         <dt>sortedBy.key</dt>
6509  *         <dd>{String} Key of sorted Column</dd>
6510  *         <dt>sortedBy.dir</dt>
6511  *         <dd>{String} Initial sort direction, either YAHOO.widget.DataTable.CLASS_ASC or YAHOO.widget.DataTable.CLASS_DESC</dd>
6512  *     </dl>
6513  * </dd>
6514  *
6515  * <dt>selectedRows</dt>
6516  * <dd>Array of selected rows by Record ID.</dd>
6517  *
6518  * <dt>selectedCells</dt>
6519  * <dd>Selected cells as an array of object literals:
6520  *     {recordId:sRecordId, columnKey:sColumnKey}</dd>
6521  * </dl>
6522  *  
6523  * @method getState
6524  * @return {Object} DataTable instance state object literal values.
6525  */
6526 getState : function() {
6527     return {
6528         totalRecords: this.get('paginator') ? this.get('paginator').get("totalRecords") : this._oRecordSet.getLength(),
6529         pagination: this.get("paginator") ? this.get("paginator").getState() : null,
6530         sortedBy: this.get("sortedBy"),
6531         selectedRows: this.getSelectedRows(),
6532         selectedCells: this.getSelectedCells()
6533     };
6534 },
6535
6536
6537
6538
6539
6540
6541
6542
6543
6544
6545
6546
6547
6548
6549
6550
6551
6552
6553
6554
6555
6556
6557
6558
6559
6560
6561
6562
6563
6564
6565
6566
6567
6568
6569
6570
6571
6572
6573
6574
6575
6576
6577
6578 // DOM ACCESSORS
6579
6580 /**
6581  * Returns DOM reference to the DataTable's container element.
6582  *
6583  * @method getContainerEl
6584  * @return {HTMLElement} Reference to DIV element.
6585  */
6586 getContainerEl : function() {
6587     return this._elContainer;
6588 },
6589
6590 /**
6591  * Returns DOM reference to the DataTable's TABLE element.
6592  *
6593  * @method getTableEl
6594  * @return {HTMLElement} Reference to TABLE element.
6595  */
6596 getTableEl : function() {
6597     return this._elTable;
6598 },
6599
6600 /**
6601  * Returns DOM reference to the DataTable's THEAD element.
6602  *
6603  * @method getTheadEl
6604  * @return {HTMLElement} Reference to THEAD element.
6605  */
6606 getTheadEl : function() {
6607     return this._elThead;
6608 },
6609
6610 /**
6611  * Returns DOM reference to the DataTable's primary TBODY element.
6612  *
6613  * @method getTbodyEl
6614  * @return {HTMLElement} Reference to TBODY element.
6615  */
6616 getTbodyEl : function() {
6617     return this._elTbody;
6618 },
6619
6620 /**
6621  * Returns DOM reference to the DataTable's secondary TBODY element that is
6622  * used to display messages.
6623  *
6624  * @method getMsgTbodyEl
6625  * @return {HTMLElement} Reference to TBODY element.
6626  */
6627 getMsgTbodyEl : function() {
6628     return this._elMsgTbody;
6629 },
6630
6631 /**
6632  * Returns DOM reference to the TD element within the secondary TBODY that is
6633  * used to display messages.
6634  *
6635  * @method getMsgTdEl
6636  * @return {HTMLElement} Reference to TD element.
6637  */
6638 getMsgTdEl : function() {
6639     return this._elMsgTd;
6640 },
6641
6642 /**
6643  * Returns the corresponding TR reference for a given DOM element, ID string or
6644  * directly page row index. If the given identifier is a child of a TR element,
6645  * then DOM tree is traversed until a parent TR element is returned, otherwise
6646  * null.
6647  *
6648  * @method getTrEl
6649  * @param row {HTMLElement | String | Number | YAHOO.widget.Record} Which row to
6650  * get: by element reference, ID string, page row index, or Record.
6651  * @return {HTMLElement} Reference to TR element, or null.
6652  */
6653 getTrEl : function(row) {
6654     // By Record
6655     if(row instanceof YAHOO.widget.Record) {
6656         return document.getElementById(row.getId());
6657     }
6658     // By page row index
6659     else if(lang.isNumber(row)) {
6660         var allRows = this._elTbody.rows;
6661         return ((row > -1) && (row < allRows.length)) ? allRows[row] : null;
6662     }
6663     // By ID string or element reference
6664     else {
6665         var elRow = (lang.isString(row)) ? document.getElementById(row) : row;
6666
6667         // Validate HTML element
6668         if(elRow && (elRow.ownerDocument == document)) {
6669             // Validate TR element
6670             if(elRow.nodeName.toLowerCase() != "tr") {
6671                 // Traverse up the DOM to find the corresponding TR element
6672                 elRow = Dom.getAncestorByTagName(elRow,"tr");
6673             }
6674
6675             return elRow;
6676         }
6677     }
6678
6679     return null;
6680 },
6681
6682 /**
6683  * Returns DOM reference to the first TR element in the DataTable page, or null.
6684  *
6685  * @method getFirstTrEl
6686  * @return {HTMLElement} Reference to TR element.
6687  */
6688 getFirstTrEl : function() {
6689     return this._elTbody.rows[0] || null;
6690 },
6691
6692 /**
6693  * Returns DOM reference to the last TR element in the DataTable page, or null.
6694  *
6695  * @method getLastTrEl
6696  * @return {HTMLElement} Reference to last TR element.
6697  */
6698 getLastTrEl : function() {
6699     var allRows = this._elTbody.rows;
6700         if(allRows.length > 0) {
6701             return allRows[allRows.length-1] || null;
6702         }
6703 },
6704
6705 /**
6706  * Returns DOM reference to the next TR element from the given TR element, or null.
6707  *
6708  * @method getNextTrEl
6709  * @param row {HTMLElement | String | Number | YAHOO.widget.Record} Element
6710  * reference, ID string, page row index, or Record from which to get next TR element.
6711  * @return {HTMLElement} Reference to next TR element.
6712  */
6713 getNextTrEl : function(row) {
6714     var nThisTrIndex = this.getTrIndex(row);
6715     if(nThisTrIndex !== null) {
6716         var allRows = this._elTbody.rows;
6717         if(nThisTrIndex < allRows.length-1) {
6718             return allRows[nThisTrIndex+1];
6719         }
6720     }
6721
6722     return null;
6723 },
6724
6725 /**
6726  * Returns DOM reference to the previous TR element from the given TR element, or null.
6727  *
6728  * @method getPreviousTrEl
6729  * @param row {HTMLElement | String | Number | YAHOO.widget.Record} Element
6730  * reference, ID string, page row index, or Record from which to get previous TR element.
6731  * @return {HTMLElement} Reference to previous TR element.
6732  */
6733 getPreviousTrEl : function(row) {
6734     var nThisTrIndex = this.getTrIndex(row);
6735     if(nThisTrIndex !== null) {
6736         var allRows = this._elTbody.rows;
6737         if(nThisTrIndex > 0) {
6738             return allRows[nThisTrIndex-1];
6739         }
6740     }
6741
6742     return null;
6743 },
6744
6745 /**
6746  * Returns DOM reference to a TD liner element.
6747  *
6748  * @method getTdLinerEl
6749  * @param cell {HTMLElement | Object} TD element or child of a TD element, or
6750  * object literal of syntax {record:oRecord, column:oColumn}.
6751  * @return {HTMLElement} Reference to TD liner element.
6752  */
6753 getTdLinerEl : function(cell) {
6754     var elCell = this.getTdEl(cell);
6755     return elCell.firstChild || null;
6756 },
6757
6758 /**
6759  * Returns DOM reference to a TD element.
6760  *
6761  * @method getTdEl
6762  * @param cell {HTMLElement | String | Object} TD element or child of a TD element, or
6763  * object literal of syntax {record:oRecord, column:oColumn}.
6764  * @return {HTMLElement} Reference to TD element.
6765  */
6766 getTdEl : function(cell) {
6767     var elCell;
6768     var el = Dom.get(cell);
6769
6770     // Validate HTML element
6771     if(el && (el.ownerDocument == document)) {
6772         // Validate TD element
6773         if(el.nodeName.toLowerCase() != "td") {
6774             // Traverse up the DOM to find the corresponding TR element
6775             elCell = Dom.getAncestorByTagName(el, "td");
6776         }
6777         else {
6778             elCell = el;
6779         }
6780
6781         return elCell;
6782     }
6783     else if(cell) {
6784         var oRecord, nColKeyIndex;
6785
6786         if(lang.isString(cell.columnKey) && lang.isString(cell.recordId)) {
6787             oRecord = this.getRecord(cell.recordId);
6788             var oColumn = this.getColumn(cell.columnKey);
6789             if(oColumn) {
6790                 nColKeyIndex = oColumn.getKeyIndex();
6791             }
6792
6793         }
6794         if(cell.record && cell.column && cell.column.getKeyIndex) {
6795             oRecord = cell.record;
6796             nColKeyIndex = cell.column.getKeyIndex();
6797         }
6798         var elRow = this.getTrEl(oRecord);
6799         if((nColKeyIndex !== null) && elRow && elRow.cells && elRow.cells.length > 0) {
6800             return elRow.cells[nColKeyIndex] || null;
6801         }
6802     }
6803
6804     return null;
6805 },
6806
6807 /**
6808  * Returns DOM reference to the first TD element in the DataTable page (by default),
6809  * the first TD element of the optionally given row, or null.
6810  *
6811  * @method getFirstTdEl
6812  * @param row {HTMLElement} (optional) row from which to get first TD
6813  * @return {HTMLElement} Reference to TD element.
6814  */
6815 getFirstTdEl : function(row) {
6816     var elRow = this.getTrEl(row) || this.getFirstTrEl();
6817     if(elRow && (elRow.cells.length > 0)) {
6818         return elRow.cells[0];
6819     }
6820     return null;
6821 },
6822
6823 /**
6824  * Returns DOM reference to the last TD element in the DataTable page (by default),
6825  * the first TD element of the optionally given row, or null.
6826  *
6827  * @method getLastTdEl
6828  * @return {HTMLElement} Reference to last TD element.
6829  */
6830 getLastTdEl : function(row) {
6831     var elRow = this.getTrEl(row) || this.getLastTrEl();
6832     if(elRow && (elRow.cells.length > 0)) {
6833         return elRow.cells[elRow.cells.length-1];
6834     }
6835     return null;
6836 },
6837
6838 /**
6839  * Returns DOM reference to the next TD element from the given cell, or null.
6840  *
6841  * @method getNextTdEl
6842  * @param cell {HTMLElement | String | Object} DOM element reference or string ID, or
6843  * object literal of syntax {record:oRecord, column:oColumn} from which to get next TD element.
6844  * @return {HTMLElement} Reference to next TD element, or null.
6845  */
6846 getNextTdEl : function(cell) {
6847     var elCell = this.getTdEl(cell);
6848     if(elCell) {
6849         var nThisTdIndex = elCell.cellIndex;
6850         var elRow = this.getTrEl(elCell);
6851         if(nThisTdIndex < elRow.cells.length-1) {
6852             return elRow.cells[nThisTdIndex+1];
6853         }
6854         else {
6855             var elNextRow = this.getNextTrEl(elRow);
6856             if(elNextRow) {
6857                 return elNextRow.cells[0];
6858             }
6859         }
6860     }
6861     return null;
6862 },
6863
6864 /**
6865  * Returns DOM reference to the previous TD element from the given cell, or null.
6866  *
6867  * @method getPreviousTdEl
6868  * @param cell {HTMLElement | String | Object} DOM element reference or string ID, or
6869  * object literal of syntax {record:oRecord, column:oColumn} from which to get previous TD element.
6870  * @return {HTMLElement} Reference to previous TD element, or null.
6871  */
6872 getPreviousTdEl : function(cell) {
6873     var elCell = this.getTdEl(cell);
6874     if(elCell) {
6875         var nThisTdIndex = elCell.cellIndex;
6876         var elRow = this.getTrEl(elCell);
6877         if(nThisTdIndex > 0) {
6878             return elRow.cells[nThisTdIndex-1];
6879         }
6880         else {
6881             var elPreviousRow = this.getPreviousTrEl(elRow);
6882             if(elPreviousRow) {
6883                 return this.getLastTdEl(elPreviousRow);
6884             }
6885         }
6886     }
6887     return null;
6888 },
6889
6890 /**
6891  * Returns DOM reference to the above TD element from the given cell, or null.
6892  *
6893  * @method getAboveTdEl
6894  * @param cell {HTMLElement | String | Object} DOM element reference or string ID, or
6895  * object literal of syntax {record:oRecord, column:oColumn} from which to get next TD element.
6896  * @return {HTMLElement} Reference to next TD element, or null.
6897  */
6898 getAboveTdEl : function(cell) {
6899     var elCell = this.getTdEl(cell);
6900     if(elCell) {
6901         var elPreviousRow = this.getPreviousTrEl(elCell);
6902         if(elPreviousRow) {
6903             return elPreviousRow.cells[elCell.cellIndex];
6904         }
6905     }
6906     return null;
6907 },
6908
6909 /**
6910  * Returns DOM reference to the below TD element from the given cell, or null.
6911  *
6912  * @method getBelowTdEl
6913  * @param cell {HTMLElement | String | Object} DOM element reference or string ID, or
6914  * object literal of syntax {record:oRecord, column:oColumn} from which to get previous TD element.
6915  * @return {HTMLElement} Reference to previous TD element, or null.
6916  */
6917 getBelowTdEl : function(cell) {
6918     var elCell = this.getTdEl(cell);
6919     if(elCell) {
6920         var elNextRow = this.getNextTrEl(elCell);
6921         if(elNextRow) {
6922             return elNextRow.cells[elCell.cellIndex];
6923         }
6924     }
6925     return null;
6926 },
6927
6928 /**
6929  * Returns DOM reference to a TH liner element. Needed to normalize for resizeable 
6930  * Columns, which have an additional resizer liner DIV element between the TH
6931  * element and the liner DIV element. 
6932  *
6933  * @method getThLinerEl
6934  * @param theadCell {YAHOO.widget.Column | HTMLElement | String} Column instance,
6935  * DOM element reference, or string ID.
6936  * @return {HTMLElement} Reference to TH liner element.
6937  */
6938 getThLinerEl : function(theadCell) {
6939     var oColumn = this.getColumn(theadCell);
6940     return (oColumn) ? oColumn.getThLinerEl() : null;
6941 },
6942
6943 /**
6944  * Returns DOM reference to a TH element.
6945  *
6946  * @method getThEl
6947  * @param theadCell {YAHOO.widget.Column | HTMLElement | String} Column instance,
6948  * DOM element reference, or string ID.
6949  * @return {HTMLElement} Reference to TH element.
6950  */
6951 getThEl : function(theadCell) {
6952     var elTh;
6953
6954     // Validate Column instance
6955     if(theadCell instanceof YAHOO.widget.Column) {
6956         var oColumn = theadCell;
6957         elTh = oColumn.getThEl();
6958         if(elTh) {
6959             return elTh;
6960         }
6961     }
6962     // Validate HTML element
6963     else {
6964         var el = Dom.get(theadCell);
6965
6966         if(el && (el.ownerDocument == document)) {
6967             // Validate TH element
6968             if(el.nodeName.toLowerCase() != "th") {
6969                 // Traverse up the DOM to find the corresponding TR element
6970                 elTh = Dom.getAncestorByTagName(el,"th");
6971             }
6972             else {
6973                 elTh = el;
6974             }
6975
6976             return elTh;
6977         }
6978     }
6979
6980     return null;
6981 },
6982
6983 /**
6984  * Returns the page row index of given row. Returns null if the row is not on the
6985  * current DataTable page.
6986  *
6987  * @method getTrIndex
6988  * @param row {HTMLElement | String | YAHOO.widget.Record | Number} DOM or ID
6989  * string reference to an element within the DataTable page, a Record instance,
6990  * or a Record's RecordSet index.
6991  * @return {Number} Page row index, or null if row does not exist or is not on current page.
6992  */
6993 getTrIndex : function(row) {
6994     var nRecordIndex;
6995
6996     // By Record
6997     if(row instanceof YAHOO.widget.Record) {
6998         nRecordIndex = this._oRecordSet.getRecordIndex(row);
6999         if(nRecordIndex === null) {
7000             // Not a valid Record
7001             return null;
7002         }
7003     }
7004     // Calculate page row index from Record index
7005     else if(lang.isNumber(row)) {
7006         nRecordIndex = row;
7007     }
7008     if(lang.isNumber(nRecordIndex)) {
7009         // Validate the number
7010         if((nRecordIndex > -1) && (nRecordIndex < this._oRecordSet.getLength())) {
7011             // DataTable is paginated
7012             var oPaginator = this.get('paginator');
7013             if(oPaginator) {
7014                 // Check the record index is within the indices of the
7015                 // current page
7016                 var rng = oPaginator.getPageRecords();
7017                 if (rng && nRecordIndex >= rng[0] && nRecordIndex <= rng[1]) {
7018                     // This Record is on current page
7019                     return nRecordIndex - rng[0];
7020                 }
7021                 // This Record is not on current page
7022                 else {
7023                     return null;
7024                 }
7025             }
7026             // Not paginated, just return the Record index
7027             else {
7028                 return nRecordIndex;
7029             }
7030         }
7031         // RecordSet index is out of range
7032         else {
7033             return null;
7034         }
7035     }
7036     // By element reference or ID string
7037     else {
7038         // Validate TR element
7039         var elRow = this.getTrEl(row);
7040         if(elRow && (elRow.ownerDocument == document) &&
7041                 (elRow.parentNode == this._elTbody)) {
7042             return elRow.sectionRowIndex;
7043         }
7044     }
7045
7046     return null;
7047 },
7048
7049
7050
7051
7052
7053
7054
7055
7056
7057
7058
7059
7060
7061
7062
7063
7064
7065
7066
7067
7068
7069
7070
7071
7072
7073
7074
7075
7076
7077
7078
7079
7080
7081
7082
7083
7084
7085
7086
7087
7088
7089
7090
7091
7092
7093
7094 // TABLE FUNCTIONS
7095
7096 /**
7097  * Resets a RecordSet with the given data and populates the page view
7098  * with the new data. Any previous data, and selection and sort states are
7099  * cleared. New data should be added as a separate step. 
7100  *
7101  * @method initializeTable
7102  */
7103 initializeTable : function() {
7104     // Reset init flag
7105     this._bInit = true;
7106     
7107     // Clear the RecordSet
7108     this._oRecordSet.reset();
7109
7110     // Clear the Paginator's totalRecords if paginating
7111     var pag = this.get('paginator');
7112     if (pag) {
7113         pag.set('totalRecords',0);
7114     }
7115
7116     // Clear selections
7117     this._unselectAllTrEls();
7118     this._unselectAllTdEls();
7119     this._aSelections = null;
7120     this._oAnchorRecord = null;
7121     this._oAnchorCell = null;
7122     
7123     // Clear sort
7124     this.set("sortedBy", null);
7125 },
7126
7127 /**
7128  * Internal wrapper calls run() on render Chain instance.
7129  *
7130  * @method _runRenderChain
7131  * @private 
7132  */
7133 _runRenderChain : function() {
7134     this._oChainRender.run();
7135 },
7136
7137 /**
7138  * Renders the view with existing Records from the RecordSet while
7139  * maintaining sort, pagination, and selection states. For performance, reuses
7140  * existing DOM elements when possible while deleting extraneous elements.
7141  *
7142  * @method render
7143  */
7144 render : function() {
7145 //YAHOO.example.Performance.trialStart = new Date();
7146
7147     this._oChainRender.stop();
7148
7149     var i, j, k, len, allRecords;
7150
7151     var oPaginator = this.get('paginator');
7152     // Paginator is enabled, show a subset of Records and update Paginator UI
7153     if(oPaginator) {
7154         allRecords = this._oRecordSet.getRecords(
7155                         oPaginator.getStartIndex(),
7156                         oPaginator.getRowsPerPage());
7157     }
7158     // Not paginated, show all records
7159     else {
7160         allRecords = this._oRecordSet.getRecords();
7161     }
7162
7163     // From the top, update in-place existing rows, so as to reuse DOM elements
7164     var elTbody = this._elTbody,
7165         loopN = this.get("renderLoopSize"),
7166         nRecordsLength = allRecords.length;
7167     
7168     // Table has rows
7169     if(nRecordsLength > 0) {                
7170         elTbody.style.display = "none";
7171         while(elTbody.lastChild) {
7172             elTbody.removeChild(elTbody.lastChild);
7173         }
7174         elTbody.style.display = "";
7175
7176         // Set up the loop Chain to render rows
7177         this._oChainRender.add({
7178             method: function(oArg) {
7179                 if((this instanceof DT) && this._sId) {
7180                     var i = oArg.nCurrentRecord,
7181                         endRecordIndex = ((oArg.nCurrentRecord+oArg.nLoopLength) > nRecordsLength) ?
7182                                 nRecordsLength : (oArg.nCurrentRecord+oArg.nLoopLength),
7183                         elRow, nextSibling;
7184
7185                     elTbody.style.display = "none";
7186                     
7187                     for(; i<endRecordIndex; i++) {
7188                         elRow = Dom.get(allRecords[i].getId());
7189                         elRow = elRow || this._addTrEl(allRecords[i]);
7190                         nextSibling = elTbody.childNodes[i] || null;
7191                         elTbody.insertBefore(elRow, nextSibling);
7192                     }
7193                     elTbody.style.display = "";
7194                     
7195                     // Set up for the next loop
7196                     oArg.nCurrentRecord = i;
7197                 }
7198             },
7199             scope: this,
7200             iterations: (loopN > 0) ? Math.ceil(nRecordsLength/loopN) : 1,
7201             argument: {
7202                 nCurrentRecord: 0,//nRecordsLength-1,  // Start at first Record
7203                 nLoopLength: (loopN > 0) ? loopN : nRecordsLength
7204             },
7205             timeout: (loopN > 0) ? 0 : -1
7206         });
7207         
7208         // Post-render tasks
7209         this._oChainRender.add({
7210             method: function(oArg) {
7211                 if((this instanceof DT) && this._sId) {
7212                     while(elTbody.rows.length > nRecordsLength) {
7213                         elTbody.removeChild(elTbody.lastChild);
7214                     }
7215                     this._setFirstRow();
7216                     this._setLastRow();
7217                     this._setRowStripes();
7218                     this._setSelections();
7219                 }
7220             },
7221             scope: this,
7222             timeout: (loopN > 0) ? 0 : -1
7223         });
7224      
7225     }
7226     // Table has no rows
7227     else {
7228         // Set up the loop Chain to delete rows
7229         var nTotal = elTbody.rows.length;
7230         if(nTotal > 0) {
7231             this._oChainRender.add({
7232                 method: function(oArg) {
7233                     if((this instanceof DT) && this._sId) {
7234                         var i = oArg.nCurrent,
7235                             loopN = oArg.nLoopLength,
7236                             nIterEnd = (i - loopN < 0) ? -1 : i - loopN;
7237     
7238                         elTbody.style.display = "none";
7239                         
7240                         for(; i>nIterEnd; i--) {
7241                             elTbody.deleteRow(-1);
7242                         }
7243                         elTbody.style.display = "";
7244                         
7245                         // Set up for the next loop
7246                         oArg.nCurrent = i;
7247                     }
7248                 },
7249                 scope: this,
7250                 iterations: (loopN > 0) ? Math.ceil(nTotal/loopN) : 1,
7251                 argument: {
7252                     nCurrent: nTotal, 
7253                     nLoopLength: (loopN > 0) ? loopN : nTotal
7254                 },
7255                 timeout: (loopN > 0) ? 0 : -1
7256             });
7257         }
7258     }
7259     this._runRenderChain();
7260 },
7261
7262 /**
7263  * Disables DataTable UI.
7264  *
7265  * @method disable
7266  */
7267 disable : function() {
7268     var elTable = this._elTable;
7269     var elMask = this._elMask;
7270     elMask.style.width = elTable.offsetWidth + "px";
7271     elMask.style.height = elTable.offsetHeight + "px";
7272     elMask.style.display = "";
7273     this.fireEvent("disableEvent");
7274 },
7275
7276 /**
7277  * Undisables DataTable UI.
7278  *
7279  * @method undisable
7280  */
7281 undisable : function() {
7282     this._elMask.style.display = "none";
7283     this.fireEvent("undisableEvent");
7284 },
7285
7286 /**
7287  * Nulls out the entire DataTable instance and related objects, removes attached
7288  * event listeners, and clears out DOM elements inside the container. After
7289  * calling this method, the instance reference should be expliclitly nulled by
7290  * implementer, as in myDataTable = null. Use with caution!
7291  *
7292  * @method destroy
7293  */
7294 destroy : function() {
7295     // Store for later
7296     var instanceName = this.toString();
7297
7298     this._oChainRender.stop();
7299     
7300     // Destroy static resizer proxy and column proxy
7301     DT._destroyColumnDragTargetEl();
7302     DT._destroyColumnResizerProxyEl();
7303     
7304     // Destroy ColumnDD and ColumnResizers
7305     this._destroyColumnHelpers();
7306     
7307     // Destroy all CellEditors
7308     var oCellEditor;
7309     for(var i=0, len=this._oColumnSet.flat.length; i<len; i++) {
7310         oCellEditor = this._oColumnSet.flat[i].editor;
7311         if(oCellEditor && oCellEditor.destroy) {
7312             oCellEditor.destroy();
7313             this._oColumnSet.flat[i].editor = null;
7314         }
7315     }
7316
7317     // Unhook custom events
7318     this._oRecordSet.unsubscribeAll();
7319     this.unsubscribeAll();
7320
7321     // Unhook DOM events
7322     Ev.removeListener(document, "click", this._onDocumentClick);
7323     
7324     // Clear out the container
7325     this._destroyContainerEl(this._elContainer);
7326
7327     // Null out objects
7328     for(var param in this) {
7329         if(lang.hasOwnProperty(this, param)) {
7330             this[param] = null;
7331         }
7332     }
7333     
7334     // Clean up static values
7335     DT._nCurrentCount--;
7336     
7337     if(DT._nCurrentCount < 1) {
7338         if(DT._elDynStyleNode) {
7339             document.getElementsByTagName('head')[0].removeChild(DT._elDynStyleNode);
7340             DT._elDynStyleNode = null;
7341         }
7342     }
7343
7344 },
7345
7346 /**
7347  * Displays message within secondary TBODY.
7348  *
7349  * @method showTableMessage
7350  * @param sHTML {String} (optional) Value for innerHTMlang.
7351  * @param sClassName {String} (optional) Classname.
7352  */
7353 showTableMessage : function(sHTML, sClassName) {
7354     var elCell = this._elMsgTd;
7355     if(lang.isString(sHTML)) {
7356         elCell.firstChild.innerHTML = sHTML;
7357     }
7358     if(lang.isString(sClassName)) {
7359         elCell.className = sClassName;
7360     }
7361
7362     this._elMsgTbody.style.display = "";
7363
7364     this.fireEvent("tableMsgShowEvent", {html:sHTML, className:sClassName});
7365 },
7366
7367 /**
7368  * Hides secondary TBODY.
7369  *
7370  * @method hideTableMessage
7371  */
7372 hideTableMessage : function() {
7373     if(this._elMsgTbody.style.display != "none") {
7374         this._elMsgTbody.style.display = "none";
7375         this._elMsgTbody.parentNode.style.width = "";
7376         this.fireEvent("tableMsgHideEvent");
7377     }
7378 },
7379
7380 /**
7381  * Brings focus to the TBODY element. Alias to focusTbodyEl.
7382  *
7383  * @method focus
7384  */
7385 focus : function() {
7386     this.focusTbodyEl();
7387 },
7388
7389 /**
7390  * Brings focus to the THEAD element.
7391  *
7392  * @method focusTheadEl
7393  */
7394 focusTheadEl : function() {
7395     this._focusEl(this._elThead);
7396 },
7397
7398 /**
7399  * Brings focus to the TBODY element.
7400  *
7401  * @method focusTbodyEl
7402  */
7403 focusTbodyEl : function() {
7404     this._focusEl(this._elTbody);
7405 },
7406
7407 /**
7408  * Setting display:none on DataTable or any parent may impact width validations.
7409  * After setting display back to "", implementers should call this method to 
7410  * manually perform those validations.
7411  *
7412  * @method onShow
7413  */
7414 onShow : function() {
7415     this.validateColumnWidths();
7416     
7417     for(var allKeys = this._oColumnSet.keys, i=0, len=allKeys.length, col; i<len; i++) {
7418         col = allKeys[i];
7419         if(col._ddResizer) {
7420             col._ddResizer.resetResizerEl();
7421         }
7422     }
7423 },
7424
7425
7426
7427
7428
7429
7430
7431
7432
7433
7434
7435
7436
7437
7438
7439
7440
7441
7442
7443
7444
7445
7446
7447
7448
7449
7450
7451
7452
7453
7454
7455
7456
7457
7458
7459
7460
7461
7462
7463
7464
7465
7466
7467
7468
7469
7470
7471
7472
7473
7474
7475
7476
7477
7478
7479
7480
7481
7482
7483
7484
7485
7486
7487
7488
7489
7490
7491 // RECORDSET FUNCTIONS
7492
7493 /**
7494  * Returns Record index for given TR element or page row index.
7495  *
7496  * @method getRecordIndex
7497  * @param row {YAHOO.widget.Record | HTMLElement | Number} Record instance, TR
7498  * element reference or page row index.
7499  * @return {Number} Record's RecordSet index, or null.
7500  */
7501 getRecordIndex : function(row) {
7502     var nTrIndex;
7503
7504     if(!lang.isNumber(row)) {
7505         // By Record
7506         if(row instanceof YAHOO.widget.Record) {
7507             return this._oRecordSet.getRecordIndex(row);
7508         }
7509         // By element reference
7510         else {
7511             // Find the TR element
7512             var el = this.getTrEl(row);
7513             if(el) {
7514                 nTrIndex = el.sectionRowIndex;
7515             }
7516         }
7517     }
7518     // By page row index
7519     else {
7520         nTrIndex = row;
7521     }
7522
7523     if(lang.isNumber(nTrIndex)) {
7524         var oPaginator = this.get("paginator");
7525         if(oPaginator) {
7526             return oPaginator.get('recordOffset') + nTrIndex;
7527         }
7528         else {
7529             return nTrIndex;
7530         }
7531     }
7532
7533     return null;
7534 },
7535
7536 /**
7537  * For the given identifier, returns the associated Record instance.
7538  *
7539  * @method getRecord
7540  * @param row {HTMLElement | Number | String} DOM reference to a TR element (or
7541  * child of a TR element), RecordSet position index, or Record ID.
7542  * @return {YAHOO.widget.Record} Record instance.
7543  */
7544 getRecord : function(row) {
7545     var oRecord = this._oRecordSet.getRecord(row);
7546
7547     if(!oRecord) {
7548         // Validate TR element
7549         var elRow = this.getTrEl(row);
7550         if(elRow) {
7551             oRecord = this._oRecordSet.getRecord(this.getRecordIndex(elRow.sectionRowIndex));
7552         }
7553     }
7554
7555     if(oRecord instanceof YAHOO.widget.Record) {
7556         return this._oRecordSet.getRecord(oRecord);
7557     }
7558     else {
7559         return null;
7560     }
7561 },
7562
7563
7564
7565
7566
7567
7568
7569
7570
7571
7572
7573
7574
7575
7576
7577
7578
7579
7580
7581
7582
7583
7584
7585
7586
7587
7588
7589
7590
7591
7592
7593
7594
7595
7596
7597
7598
7599
7600
7601
7602
7603
7604
7605
7606
7607
7608 // COLUMN FUNCTIONS
7609
7610 /**
7611  * For the given identifier, returns the associated Column instance. Note: For
7612  * getting Columns by Column ID string, please use the method getColumnById().
7613  *
7614  * @method getColumn
7615  * @param column {HTMLElement | String | Number} TH/TD element (or child of a
7616  * TH/TD element), a Column key, or a ColumnSet key index.
7617  * @return {YAHOO.widget.Column} Column instance.
7618  */
7619 getColumn : function(column) {
7620     var oColumn = this._oColumnSet.getColumn(column);
7621
7622     if(!oColumn) {
7623         // Validate TD element
7624         var elCell = this.getTdEl(column);
7625         if(elCell) {
7626             oColumn = this._oColumnSet.getColumn(elCell.cellIndex);
7627         }
7628         // Validate TH element
7629         else {
7630             elCell = this.getThEl(column);
7631             if(elCell) {
7632                 // Find by TH el ID
7633                 var allColumns = this._oColumnSet.flat;
7634                 for(var i=0, len=allColumns.length; i<len; i++) {
7635                     if(allColumns[i].getThEl().id === elCell.id) {
7636                         oColumn = allColumns[i];
7637                     } 
7638                 }
7639             }
7640         }
7641     }
7642     if(!oColumn) {
7643     }
7644     return oColumn;
7645 },
7646
7647 /**
7648  * For the given Column ID, returns the associated Column instance. Note: For
7649  * getting Columns by key, please use the method getColumn().
7650  *
7651  * @method getColumnById
7652  * @param column {String} Column ID string.
7653  * @return {YAHOO.widget.Column} Column instance.
7654  */
7655 getColumnById : function(column) {
7656     return this._oColumnSet.getColumnById(column);
7657 },
7658
7659 /**
7660  * For the given Column instance, returns next direction to sort.
7661  *
7662  * @method getColumnSortDir
7663  * @param oColumn {YAHOO.widget.Column} Column instance.
7664  * @param oSortedBy {Object} (optional) Specify the state, or use current state. 
7665  * @return {String} YAHOO.widget.DataTable.CLASS_ASC or YAHOO.widget.DataTableCLASS_DESC.
7666  */
7667 getColumnSortDir : function(oColumn, oSortedBy) {
7668     // Backward compatibility
7669     if(oColumn.sortOptions && oColumn.sortOptions.defaultOrder) {
7670         if(oColumn.sortOptions.defaultOrder == "asc") {
7671             oColumn.sortOptions.defaultDir = DT.CLASS_ASC;
7672         }
7673         else if (oColumn.sortOptions.defaultOrder == "desc") {
7674             oColumn.sortOptions.defaultDir = DT.CLASS_DESC;
7675         }
7676     }
7677     
7678     // What is the Column's default sort direction?
7679     var sortDir = (oColumn.sortOptions && oColumn.sortOptions.defaultDir) ? oColumn.sortOptions.defaultDir : DT.CLASS_ASC;
7680
7681     // Is the Column currently sorted?
7682     var bSorted = false;
7683     oSortedBy = oSortedBy || this.get("sortedBy");
7684     if(oSortedBy && (oSortedBy.key === oColumn.key)) {
7685         bSorted = true;
7686         if(oSortedBy.dir) {
7687             sortDir = (oSortedBy.dir === DT.CLASS_ASC) ? DT.CLASS_DESC : DT.CLASS_ASC;
7688         }
7689         else {
7690             sortDir = (sortDir === DT.CLASS_ASC) ? DT.CLASS_DESC : DT.CLASS_ASC;
7691         }
7692     }
7693     return sortDir;
7694 },
7695
7696 /**
7697  * Overridable method gives implementers a hook to show loading message before
7698  * sorting Column.
7699  *
7700  * @method doBeforeSortColumn
7701  * @param oColumn {YAHOO.widget.Column} Column instance.
7702  * @param sSortDir {String} YAHOO.widget.DataTable.CLASS_ASC or
7703  * YAHOO.widget.DataTable.CLASS_DESC.
7704  * @return {Boolean} Return true to continue sorting Column.
7705  */
7706 doBeforeSortColumn : function(oColumn, sSortDir) {
7707     this.showTableMessage(this.get("MSG_LOADING"), DT.CLASS_LOADING);
7708     return true;
7709 },
7710
7711 /**
7712  * Sorts given Column. If "dynamicData" is true, current selections are purged before
7713  * a request is sent to the DataSource for data for the new state (using the
7714  * request returned by "generateRequest()").
7715  *
7716  * @method sortColumn
7717  * @param oColumn {YAHOO.widget.Column} Column instance.
7718  * @param sDir {String} (Optional) YAHOO.widget.DataTable.CLASS_ASC or
7719  * YAHOO.widget.DataTable.CLASS_DESC
7720  */
7721 sortColumn : function(oColumn, sDir) {
7722     if(oColumn && (oColumn instanceof YAHOO.widget.Column)) {
7723         if(!oColumn.sortable) {
7724             Dom.addClass(this.getThEl(oColumn), DT.CLASS_SORTABLE);
7725         }
7726         
7727         // Validate given direction
7728         if(sDir && (sDir !== DT.CLASS_ASC) && (sDir !== DT.CLASS_DESC)) {
7729             sDir = null;
7730         }
7731         
7732         // Get the sort dir
7733         var sSortDir = sDir || this.getColumnSortDir(oColumn);
7734
7735         // Is the Column currently sorted?
7736         var oSortedBy = this.get("sortedBy") || {};
7737         var bSorted = (oSortedBy.key === oColumn.key) ? true : false;
7738
7739         var ok = this.doBeforeSortColumn(oColumn, sSortDir);
7740         if(ok) {
7741             // Server-side sort
7742             if(this.get("dynamicData")) {
7743                 // Get current state
7744                 var oState = this.getState();
7745                 
7746                 // Reset record offset, if paginated
7747                 if(oState.pagination) {
7748                     oState.pagination.recordOffset = 0;
7749                 }
7750                 
7751                 // Update sortedBy to new values
7752                 oState.sortedBy = {
7753                     key: oColumn.key,
7754                     dir: sSortDir
7755                 };
7756                 
7757                 // Get the request for the new state
7758                 var request = this.get("generateRequest")(oState, this);
7759
7760                 // Purge selections
7761                 this.unselectAllRows();
7762                 this.unselectAllCells();
7763
7764                 // Send request for new data
7765                 var callback = {
7766                     success : this.onDataReturnSetRows,
7767                     failure : this.onDataReturnSetRows,
7768                     argument : oState, // Pass along the new state to the callback
7769                     scope : this
7770                 };
7771                 this._oDataSource.sendRequest(request, callback);            
7772             }
7773             // Client-side sort
7774             else {
7775                 // Is there a custom sort handler function defined?
7776                 var sortFnc = (oColumn.sortOptions && lang.isFunction(oColumn.sortOptions.sortFunction)) ?
7777                         // Custom sort function
7778                         oColumn.sortOptions.sortFunction : null;
7779                    
7780                 // Sort the Records
7781                 if(!bSorted || sDir || sortFnc) {
7782                     // Get the field to sort
7783                     var sField = (oColumn.sortOptions && oColumn.sortOptions.field) ? oColumn.sortOptions.field : oColumn.field;
7784
7785                     // Default sort function if necessary
7786                     sortFnc = sortFnc || 
7787                         function(a, b, desc) {
7788                             var sorted = YAHOO.util.Sort.compare(a.getData(sField),b.getData(sField), desc);
7789                             if(sorted === 0) {
7790                                 return YAHOO.util.Sort.compare(a.getCount(),b.getCount(), desc); // Bug 1932978
7791                             }
7792                             else {
7793                                 return sorted;
7794                             }
7795                         };
7796                     // Sort the Records        
7797                     this._oRecordSet.sortRecords(sortFnc, ((sSortDir == DT.CLASS_DESC) ? true : false));
7798                 }
7799                 // Just reverse the Records
7800                 else {
7801                     this._oRecordSet.reverseRecords();
7802                 }
7803         
7804                 // Reset to first page if paginated
7805                 var oPaginator = this.get('paginator');
7806                 if (oPaginator) {
7807                     // Set page silently, so as not to fire change event.
7808                     oPaginator.setPage(1,true);
7809                 }
7810         
7811                 // Update UI via sortedBy
7812                 this.render();
7813                 this.set("sortedBy", {key:oColumn.key, dir:sSortDir, column:oColumn}); 
7814             }       
7815             
7816             this.fireEvent("columnSortEvent",{column:oColumn,dir:sSortDir});
7817             return;
7818         }
7819     }
7820 },
7821
7822 /**
7823  * Sets given Column to given pixel width. If new width is less than minimum
7824  * width, sets to minimum width. Updates oColumn.width value.
7825  *
7826  * @method setColumnWidth
7827  * @param oColumn {YAHOO.widget.Column} Column instance.
7828  * @param nWidth {Number} New width in pixels. A null value auto-sizes Column,
7829  * subject to minWidth and maxAutoWidth validations. 
7830  */
7831 setColumnWidth : function(oColumn, nWidth) {
7832     if(!(oColumn instanceof YAHOO.widget.Column)) {
7833         oColumn = this.getColumn(oColumn);
7834     }
7835     if(oColumn) {
7836         // Validate new width against minimum width
7837         if(lang.isNumber(nWidth)) {
7838             // This is why we must require a Number... :-|
7839             nWidth = (nWidth > oColumn.minWidth) ? nWidth : oColumn.minWidth;
7840
7841             // Save state
7842             oColumn.width = nWidth;
7843             
7844             // Resize the DOM elements
7845             this._setColumnWidth(oColumn, nWidth+"px");
7846             
7847             this.fireEvent("columnSetWidthEvent",{column:oColumn,width:nWidth});
7848         }
7849         // Unsets a width to auto-size
7850         else if(nWidth === null) {
7851             // Save state
7852             oColumn.width = nWidth;
7853             
7854             // Resize the DOM elements
7855             this._setColumnWidth(oColumn, "auto");
7856             this.validateColumnWidths(oColumn);
7857             this.fireEvent("columnUnsetWidthEvent",{column:oColumn});
7858         }
7859                 
7860         // Bug 2339454: resize then sort misaligment
7861         this._clearTrTemplateEl();
7862     }
7863     else {
7864     }
7865 },
7866
7867 /**
7868  * Sets liner DIV elements of given Column to given width. When value should be
7869  * auto-calculated to fit content overflow is set to visible, otherwise overflow
7870  * is set to hidden. No validations against minimum width and no updating
7871  * Column.width value.
7872  *
7873  * @method _setColumnWidth
7874  * @param oColumn {YAHOO.widget.Column} Column instance.
7875  * @param sWidth {String} New width value.
7876  * @param sOverflow {String} Should be "hidden" when Column width is explicitly
7877  * being set to a value, but should be "visible" when Column is meant to auto-fit content.  
7878  * @private
7879  */
7880 _setColumnWidth : function(oColumn, sWidth, sOverflow) {
7881     if(oColumn && (oColumn.getKeyIndex() !== null)) {
7882         sOverflow = sOverflow || (((sWidth === '') || (sWidth === 'auto')) ? 'visible' : 'hidden');
7883     
7884         // Dynamic style algorithm
7885         if(!DT._bDynStylesFallback) {
7886             this._setColumnWidthDynStyles(oColumn, sWidth, sOverflow);
7887         }
7888         // Dynamic function algorithm
7889         else {
7890             this._setColumnWidthDynFunction(oColumn, sWidth, sOverflow);
7891         }
7892     }
7893     else {
7894     }
7895 },
7896
7897 /**
7898  * Updates width of a Column's liner DIV elements by dynamically creating a
7899  * STYLE node and writing and updating CSS style rules to it. If this fails during
7900  * runtime, the fallback method _setColumnWidthDynFunction() will be called.
7901  * Notes: This technique is not performant in IE6. IE7 crashes if DataTable is
7902  * nested within another TABLE element. For these cases, it is recommended to
7903  * use the method _setColumnWidthDynFunction by setting _bDynStylesFallback to TRUE.
7904  *
7905  * @method _setColumnWidthDynStyles
7906  * @param oColumn {YAHOO.widget.Column} Column instance.
7907  * @param sWidth {String} New width value.
7908  * @private
7909  */
7910 _setColumnWidthDynStyles : function(oColumn, sWidth, sOverflow) {
7911     var s = DT._elDynStyleNode,
7912         rule;
7913     
7914     // Create a new STYLE node
7915     if(!s) {
7916         s = document.createElement('style');
7917         s.type = 'text/css';
7918         s = document.getElementsByTagName('head').item(0).appendChild(s);
7919         DT._elDynStyleNode = s;
7920     }
7921     
7922     // We have a STYLE node to update
7923     if(s) {
7924         // Use unique classname for this Column instance as a hook for resizing
7925         var sClassname = "." + this.getId() + "-col-" + oColumn.getSanitizedKey() + " ." + DT.CLASS_LINER;
7926         
7927         // Hide for performance
7928         if(this._elTbody) {
7929             this._elTbody.style.display = 'none';
7930         }
7931         
7932         rule = DT._oDynStyles[sClassname];
7933
7934         // The Column does not yet have a rule
7935         if(!rule) {
7936             if(s.styleSheet && s.styleSheet.addRule) {
7937                 s.styleSheet.addRule(sClassname,"overflow:"+sOverflow);
7938                 s.styleSheet.addRule(sClassname,'width:'+sWidth);
7939                 rule = s.styleSheet.rules[s.styleSheet.rules.length-1];
7940                 DT._oDynStyles[sClassname] = rule;
7941             }
7942             else if(s.sheet && s.sheet.insertRule) {
7943                 s.sheet.insertRule(sClassname+" {overflow:"+sOverflow+";width:"+sWidth+";}",s.sheet.cssRules.length);
7944                 rule = s.sheet.cssRules[s.sheet.cssRules.length-1];
7945                 DT._oDynStyles[sClassname] = rule;
7946             }
7947         }
7948         // We have a rule to update
7949         else {
7950             rule.style.overflow = sOverflow;
7951             rule.style.width = sWidth;
7952         } 
7953         
7954         // Unhide
7955         if(this._elTbody) {
7956             this._elTbody.style.display = '';
7957         }
7958     }
7959     
7960     // That was not a success, we must call the fallback routine
7961     if(!rule) {
7962         DT._bDynStylesFallback = true;
7963         this._setColumnWidthDynFunction(oColumn, sWidth);
7964     }
7965 },
7966
7967 /**
7968  * Updates width of a Column's liner DIV elements by dynamically creating a
7969  * function to update all element style properties in one pass. Note: This
7970  * technique is not supported in sandboxed environments that prohibit EVALs.    
7971  *
7972  * @method _setColumnWidthDynFunction
7973  * @param oColumn {YAHOO.widget.Column} Column instance.
7974  * @param sWidth {String} New width value.
7975  * @private
7976  */
7977 _setColumnWidthDynFunction : function(oColumn, sWidth, sOverflow) {
7978     // TODO: why is this here?
7979     if(sWidth == 'auto') {
7980         sWidth = ''; 
7981     }
7982     
7983     // Create one function for each value of rows.length
7984     var rowslen = this._elTbody ? this._elTbody.rows.length : 0;
7985     
7986     // Dynamically create the function
7987     if (!this._aDynFunctions[rowslen]) {
7988         
7989         //Compile a custom function to do all the liner div width
7990         //assignments at the same time.  A unique function is required
7991         //for each unique number of rows in _elTbody.  This will
7992         //result in a function declaration like:
7993         //function (oColumn,sWidth,sOverflow) {
7994         //    var colIdx = oColumn.getKeyIndex();
7995         //    oColumn.getThLinerEl().style.overflow =
7996         //    this._elTbody.rows[0].cells[colIdx].firstChild.style.overflow =
7997         //    this._elTbody.rows[1].cells[colIdx].firstChild.style.overflow =
7998         //    ... (for all row indices in this._elTbody.rows.length - 1)
7999         //    this._elTbody.rows[99].cells[colIdx].firstChild.style.overflow =
8000         //    sOverflow;
8001         //    oColumn.getThLinerEl().style.width =
8002         //    this._elTbody.rows[0].cells[colIdx].firstChild.style.width =
8003         //    this._elTbody.rows[1].cells[colIdx].firstChild.style.width =
8004         //    ... (for all row indices in this._elTbody.rows.length - 1)
8005         //    this._elTbody.rows[99].cells[colIdx].firstChild.style.width =
8006         //    sWidth;
8007         //}
8008         
8009         var i,j,k;
8010         var resizerDef = [
8011             'var colIdx=oColumn.getKeyIndex();',
8012             'oColumn.getThLinerEl().style.overflow='
8013         ];
8014         for (i=rowslen-1, j=2; i >= 0; --i) {
8015             resizerDef[j++] = 'this._elTbody.rows[';
8016             resizerDef[j++] = i;
8017             resizerDef[j++] = '].cells[colIdx].firstChild.style.overflow=';
8018         }
8019         resizerDef[j] = 'sOverflow;';
8020         resizerDef[j+1] = 'oColumn.getThLinerEl().style.width=';
8021         for (i=rowslen-1, k=j+2; i >= 0; --i) {
8022             resizerDef[k++] = 'this._elTbody.rows[';
8023             resizerDef[k++] = i;
8024             resizerDef[k++] = '].cells[colIdx].firstChild.style.width=';
8025         }
8026         resizerDef[k] = 'sWidth;';
8027         this._aDynFunctions[rowslen] =
8028             new Function('oColumn','sWidth','sOverflow',resizerDef.join(''));
8029     }
8030     
8031     // Get the function to execute
8032     var resizerFn = this._aDynFunctions[rowslen];
8033
8034     // TODO: Hide TBODY for performance in _setColumnWidthDynFunction?
8035     if (resizerFn) {
8036         resizerFn.call(this,oColumn,sWidth,sOverflow);
8037     }
8038 },
8039
8040 /**
8041  * For one or all Columns, when Column is not hidden, width is not set, and minWidth
8042  * and/or maxAutoWidth is set, validates auto-width against minWidth and maxAutoWidth.
8043  *
8044  * @method validateColumnWidths
8045  * @param oArg.column {YAHOO.widget.Column} (optional) One Column to validate. If null, all Columns' widths are validated.
8046  */
8047 validateColumnWidths : function(oColumn) {
8048     var elColgroup = this._elColgroup;
8049     var elColgroupClone = elColgroup.cloneNode(true);
8050     var bNeedsValidation = false;
8051     var allKeys = this._oColumnSet.keys;
8052     var elThLiner;
8053     // Validate just one Column's minWidth and/or maxAutoWidth
8054     if(oColumn && !oColumn.hidden && !oColumn.width && (oColumn.getKeyIndex() !== null)) {
8055             elThLiner = oColumn.getThLinerEl();
8056             if((oColumn.minWidth > 0) && (elThLiner.offsetWidth < oColumn.minWidth)) {
8057                 elColgroupClone.childNodes[oColumn.getKeyIndex()].style.width = 
8058                         oColumn.minWidth + 
8059                         (parseInt(Dom.getStyle(elThLiner,"paddingLeft"),10)|0) +
8060                         (parseInt(Dom.getStyle(elThLiner,"paddingRight"),10)|0) + "px";
8061                 bNeedsValidation = true;
8062             }
8063             else if((oColumn.maxAutoWidth > 0) && (elThLiner.offsetWidth > oColumn.maxAutoWidth)) {
8064                 this._setColumnWidth(oColumn, oColumn.maxAutoWidth+"px", "hidden");
8065             }
8066     }
8067     // Validate all Columns
8068     else {
8069         for(var i=0, len=allKeys.length; i<len; i++) {
8070             oColumn = allKeys[i];
8071             if(!oColumn.hidden && !oColumn.width) {
8072                 elThLiner = oColumn.getThLinerEl();
8073                 if((oColumn.minWidth > 0) && (elThLiner.offsetWidth < oColumn.minWidth)) {
8074                     elColgroupClone.childNodes[i].style.width = 
8075                             oColumn.minWidth + 
8076                             (parseInt(Dom.getStyle(elThLiner,"paddingLeft"),10)|0) +
8077                             (parseInt(Dom.getStyle(elThLiner,"paddingRight"),10)|0) + "px";
8078                     bNeedsValidation = true;
8079                 }
8080                 else if((oColumn.maxAutoWidth > 0) && (elThLiner.offsetWidth > oColumn.maxAutoWidth)) {
8081                     this._setColumnWidth(oColumn, oColumn.maxAutoWidth+"px", "hidden");
8082                 }
8083             }
8084         }
8085     }
8086     if(bNeedsValidation) {
8087         elColgroup.parentNode.replaceChild(elColgroupClone, elColgroup);
8088         this._elColgroup = elColgroupClone;
8089     }
8090 },
8091
8092 /**
8093  * Clears minWidth.
8094  *
8095  * @method _clearMinWidth
8096  * @param oColumn {YAHOO.widget.Column} Which Column.
8097  * @private
8098  */
8099 _clearMinWidth : function(oColumn) {
8100     if(oColumn.getKeyIndex() !== null) {
8101         this._elColgroup.childNodes[oColumn.getKeyIndex()].style.width = '';
8102     }
8103 },
8104
8105 /**
8106  * Restores minWidth.
8107  *
8108  * @method _restoreMinWidth
8109  * @param oColumn {YAHOO.widget.Column} Which Column.
8110  * @private
8111  */
8112 _restoreMinWidth : function(oColumn) {
8113     if(oColumn.minWidth && (oColumn.getKeyIndex() !== null)) {
8114         this._elColgroup.childNodes[oColumn.getKeyIndex()].style.width = oColumn.minWidth + 'px';
8115     }
8116 },
8117
8118 /**
8119  * Hides given Column. NOTE: You cannot hide/show nested Columns. You can only
8120  * hide/show non-nested Columns, and top-level parent Columns (which will
8121  * hide/show all children Columns).
8122  *
8123  * @method hideColumn
8124  * @param oColumn {YAHOO.widget.Column} Column instance.
8125  */
8126 hideColumn : function(oColumn) {
8127     if(!(oColumn instanceof YAHOO.widget.Column)) {
8128         oColumn = this.getColumn(oColumn);
8129     }
8130     // Only top-level Columns can get hidden due to issues in FF2 and SF3
8131     if(oColumn && !oColumn.hidden && oColumn.getTreeIndex() !== null) {
8132         
8133         var allrows = this.getTbodyEl().rows;
8134         var l = allrows.length;
8135         var allDescendants = this._oColumnSet.getDescendants(oColumn);
8136         
8137         // Hide each nested Column
8138         for(var i=0; i<allDescendants.length; i++) {
8139             var thisColumn = allDescendants[i];
8140             thisColumn.hidden = true;
8141
8142             // Style the head cell
8143             Dom.addClass(thisColumn.getThEl(), DT.CLASS_HIDDEN);
8144             
8145             // Does this Column have body cells?
8146             var thisKeyIndex = thisColumn.getKeyIndex();
8147             if(thisKeyIndex !== null) {                    
8148                 // Clear minWidth
8149                 this._clearMinWidth(oColumn);
8150                 
8151                 // Style the body cells
8152                 for(var j=0;j<l;j++) {
8153                     Dom.addClass(allrows[j].cells[thisKeyIndex],DT.CLASS_HIDDEN);
8154                 }
8155             }
8156             
8157             this.fireEvent("columnHideEvent",{column:thisColumn});
8158         }
8159       
8160         this._repaintOpera();
8161         this._clearTrTemplateEl();
8162     }
8163     else {
8164     }
8165 },
8166
8167 /**
8168  * Shows given Column. NOTE: You cannot hide/show nested Columns. You can only
8169  * hide/show non-nested Columns, and top-level parent Columns (which will
8170  * hide/show all children Columns).
8171  *
8172  * @method showColumn
8173  * @param oColumn {YAHOO.widget.Column} Column instance.
8174  */
8175 showColumn : function(oColumn) {
8176     if(!(oColumn instanceof YAHOO.widget.Column)) {
8177         oColumn = this.getColumn(oColumn);
8178     }
8179     // Only top-level Columns can get hidden
8180     if(oColumn && oColumn.hidden && (oColumn.getTreeIndex() !== null)) {
8181         var allrows = this.getTbodyEl().rows;
8182         var l = allrows.length;
8183         var allDescendants = this._oColumnSet.getDescendants(oColumn);
8184         
8185         // Show each nested Column
8186         for(var i=0; i<allDescendants.length; i++) {
8187             var thisColumn = allDescendants[i];
8188             thisColumn.hidden = false;
8189             
8190             // Unstyle the head cell
8191             Dom.removeClass(thisColumn.getThEl(), DT.CLASS_HIDDEN);
8192
8193             // Does this Column have body cells?
8194             var thisKeyIndex = thisColumn.getKeyIndex();
8195             if(thisKeyIndex !== null) {
8196                 // Restore minWidth
8197                 this._restoreMinWidth(oColumn);
8198                 
8199             
8200                 // Unstyle the body cells
8201                 for(var j=0;j<l;j++) {
8202                     Dom.removeClass(allrows[j].cells[thisKeyIndex],DT.CLASS_HIDDEN);
8203                 }
8204             }
8205
8206             this.fireEvent("columnShowEvent",{column:thisColumn});
8207         }
8208         this._clearTrTemplateEl();
8209     }
8210     else {
8211     }
8212 },
8213
8214 /**
8215  * Removes given Column. NOTE: You cannot remove nested Columns. You can only remove
8216  * non-nested Columns, and top-level parent Columns (which will remove all
8217  * children Columns).
8218  *
8219  * @method removeColumn
8220  * @param oColumn {YAHOO.widget.Column} Column instance.
8221  * @return oColumn {YAHOO.widget.Column} Removed Column instance.
8222  */
8223 removeColumn : function(oColumn) {
8224     // Validate Column
8225     if(!(oColumn instanceof YAHOO.widget.Column)) {
8226         oColumn = this.getColumn(oColumn);
8227     }
8228     if(oColumn) {
8229         var nColTreeIndex = oColumn.getTreeIndex();
8230         if(nColTreeIndex !== null) {
8231             // Which key index(es)
8232             var i, len,
8233                 aKeyIndexes = oColumn.getKeyIndex();
8234             // Must be a parent Column
8235             if(aKeyIndexes === null) {
8236                 var descKeyIndexes = [];
8237                 var allDescendants = this._oColumnSet.getDescendants(oColumn);
8238                 for(i=0, len=allDescendants.length; i<len; i++) {
8239                     // Is this descendant a key Column?
8240                     var thisKey = allDescendants[i].getKeyIndex();
8241                     if(thisKey !== null) {
8242                         descKeyIndexes[descKeyIndexes.length] = thisKey;
8243                     }
8244                 }
8245                 if(descKeyIndexes.length > 0) {
8246                     aKeyIndexes = descKeyIndexes;
8247                 }
8248             }
8249             // Must be a key Column
8250             else {
8251                 aKeyIndexes = [aKeyIndexes];
8252             }
8253             
8254             if(aKeyIndexes !== null) {
8255                 // Sort the indexes so we can remove from the right
8256                 aKeyIndexes.sort(function(a, b) {return YAHOO.util.Sort.compare(a, b);});
8257                 
8258                 // Destroy previous THEAD
8259                 this._destroyTheadEl();
8260     
8261                 // Create new THEAD
8262                 var aOrigColumnDefs = this._oColumnSet.getDefinitions();
8263                 oColumn = aOrigColumnDefs.splice(nColTreeIndex,1)[0];
8264                 this._initColumnSet(aOrigColumnDefs);
8265                 this._initTheadEl();
8266                 
8267                 // Remove COL
8268                 for(i=aKeyIndexes.length-1; i>-1; i--) {
8269                     this._removeColgroupColEl(aKeyIndexes[i]);
8270                 }
8271                 
8272                 // Remove TD
8273                 var allRows = this._elTbody.rows;
8274                 if(allRows.length > 0) {
8275                     var loopN = this.get("renderLoopSize"),
8276                         loopEnd = allRows.length;
8277                     this._oChainRender.add({
8278                         method: function(oArg) {
8279                             if((this instanceof DT) && this._sId) {
8280                                 var i = oArg.nCurrentRow,
8281                                     len = loopN > 0 ? Math.min(i + loopN,allRows.length) : allRows.length,
8282                                     aIndexes = oArg.aIndexes,
8283                                     j;
8284                                 for(; i < len; ++i) {
8285                                     for(j = aIndexes.length-1; j>-1; j--) {
8286                                         allRows[i].removeChild(allRows[i].childNodes[aIndexes[j]]);
8287                                     }
8288                                 }
8289                                 oArg.nCurrentRow = i;
8290                             }
8291                         },
8292                         iterations: (loopN > 0) ? Math.ceil(loopEnd/loopN) : 1,
8293                         argument: {nCurrentRow:0, aIndexes:aKeyIndexes},
8294                         scope: this,
8295                         timeout: (loopN > 0) ? 0 : -1
8296                     });
8297                     this._runRenderChain();
8298                 }
8299         
8300                 this.fireEvent("columnRemoveEvent",{column:oColumn});
8301                 return oColumn;
8302             }
8303         }
8304     }
8305 },
8306
8307 /**
8308  * Inserts given Column at the index if given, otherwise at the end. NOTE: You
8309  * can only add non-nested Columns and top-level parent Columns. You cannot add
8310  * a nested Column to an existing parent.
8311  *
8312  * @method insertColumn
8313  * @param oColumn {Object | YAHOO.widget.Column} Object literal Column
8314  * definition or a Column instance.
8315  * @param index {Number} (optional) New tree index.
8316  * @return oColumn {YAHOO.widget.Column} Inserted Column instance. 
8317  */
8318 insertColumn : function(oColumn, index) {
8319     // Validate Column
8320     if(oColumn instanceof YAHOO.widget.Column) {
8321         oColumn = oColumn.getDefinition();
8322     }
8323     else if(oColumn.constructor !== Object) {
8324         return;
8325     }
8326     
8327     // Validate index or append new Column to the end of the ColumnSet
8328     var oColumnSet = this._oColumnSet;
8329     if(!lang.isValue(index) || !lang.isNumber(index)) {
8330         index = oColumnSet.tree[0].length;
8331     }
8332     
8333     // Destroy previous THEAD
8334     this._destroyTheadEl();
8335     
8336     // Create new THEAD
8337     var aNewColumnDefs = this._oColumnSet.getDefinitions();
8338     aNewColumnDefs.splice(index, 0, oColumn);
8339     this._initColumnSet(aNewColumnDefs);
8340     this._initTheadEl();
8341     
8342     // Need to refresh the reference
8343     oColumnSet = this._oColumnSet;
8344     var oNewColumn = oColumnSet.tree[0][index];
8345     
8346     // Get key index(es) for new Column
8347     var i, len,
8348         descKeyIndexes = [];
8349     var allDescendants = oColumnSet.getDescendants(oNewColumn);
8350     for(i=0, len=allDescendants.length; i<len; i++) {
8351         // Is this descendant a key Column?
8352         var thisKey = allDescendants[i].getKeyIndex();
8353         if(thisKey !== null) {
8354             descKeyIndexes[descKeyIndexes.length] = thisKey;
8355         }
8356     }
8357     
8358     if(descKeyIndexes.length > 0) {  
8359         // Sort the indexes
8360         var newIndex = descKeyIndexes.sort(function(a, b) {return YAHOO.util.Sort.compare(a, b);})[0];
8361         
8362         // Add COL
8363         for(i=descKeyIndexes.length-1; i>-1; i--) {
8364             this._insertColgroupColEl(descKeyIndexes[i]);
8365         }
8366             
8367         // Add TD
8368         var allRows = this._elTbody.rows;
8369         if(allRows.length > 0) {
8370             var loopN = this.get("renderLoopSize"),
8371                 loopEnd = allRows.length;
8372             
8373             // Get templates for each new TD
8374             var aTdTemplates = [],
8375                 elTdTemplate;
8376             for(i=0, len=descKeyIndexes.length; i<len; i++) {
8377                 var thisKeyIndex = descKeyIndexes[i];
8378                 elTdTemplate = this._getTrTemplateEl().childNodes[i].cloneNode(true);
8379                 elTdTemplate = this._formatTdEl(this._oColumnSet.keys[thisKeyIndex], elTdTemplate, thisKeyIndex, (thisKeyIndex===this._oColumnSet.keys.length-1));
8380                 aTdTemplates[thisKeyIndex] = elTdTemplate;
8381             }
8382             
8383             this._oChainRender.add({
8384                 method: function(oArg) {
8385                     if((this instanceof DT) && this._sId) {
8386                         var i = oArg.nCurrentRow, j,
8387                             descKeyIndexes = oArg.descKeyIndexes,
8388                             len = loopN > 0 ? Math.min(i + loopN,allRows.length) : allRows.length,
8389                             nextSibling;
8390                         for(; i < len; ++i) {
8391                             nextSibling = allRows[i].childNodes[newIndex] || null;
8392                             for(j=descKeyIndexes.length-1; j>-1; j--) {
8393                                 allRows[i].insertBefore(oArg.aTdTemplates[descKeyIndexes[j]].cloneNode(true), nextSibling);
8394                             }
8395                         }
8396                         oArg.nCurrentRow = i;
8397                     }
8398                 },
8399                 iterations: (loopN > 0) ? Math.ceil(loopEnd/loopN) : 1,
8400                 argument: {nCurrentRow:0,aTdTemplates:aTdTemplates,descKeyIndexes:descKeyIndexes},
8401                 scope: this,
8402                 timeout: (loopN > 0) ? 0 : -1
8403             });
8404             this._runRenderChain(); 
8405         }
8406
8407         this.fireEvent("columnInsertEvent",{column:oColumn,index:index});
8408         return oNewColumn;
8409     }
8410 },
8411
8412 /**
8413  * Removes given Column and inserts into given tree index. NOTE: You
8414  * can only reorder non-nested Columns and top-level parent Columns. You cannot
8415  * reorder a nested Column to an existing parent.
8416  *
8417  * @method reorderColumn
8418  * @param oColumn {YAHOO.widget.Column} Column instance.
8419  * @param index {Number} New tree index.
8420  * @return oColumn {YAHOO.widget.Column} Reordered Column instance. 
8421  */
8422 reorderColumn : function(oColumn, index) {
8423     // Validate Column and new index
8424     if(!(oColumn instanceof YAHOO.widget.Column)) {
8425         oColumn = this.getColumn(oColumn);
8426     }
8427     if(oColumn && YAHOO.lang.isNumber(index)) {
8428         var nOrigTreeIndex = oColumn.getTreeIndex();
8429         if((nOrigTreeIndex !== null) && (nOrigTreeIndex !== index)) {
8430             // Which key index(es)
8431             var i, len,
8432                 aOrigKeyIndexes = oColumn.getKeyIndex(),
8433                 allDescendants,
8434                 descKeyIndexes = [],
8435                 thisKey;
8436             // Must be a parent Column...
8437             if(aOrigKeyIndexes === null) {
8438                 allDescendants = this._oColumnSet.getDescendants(oColumn);
8439                 for(i=0, len=allDescendants.length; i<len; i++) {
8440                     // Is this descendant a key Column?
8441                     thisKey = allDescendants[i].getKeyIndex();
8442                     if(thisKey !== null) {
8443                         descKeyIndexes[descKeyIndexes.length] = thisKey;
8444                     }
8445                 }
8446                 if(descKeyIndexes.length > 0) {
8447                     aOrigKeyIndexes = descKeyIndexes;
8448                 }
8449             }
8450             // ...or else must be a key Column
8451             else {
8452                 aOrigKeyIndexes = [aOrigKeyIndexes];
8453             }
8454             
8455             if(aOrigKeyIndexes !== null) {                   
8456                 // Sort the indexes
8457                 aOrigKeyIndexes.sort(function(a, b) {return YAHOO.util.Sort.compare(a, b);});
8458                 
8459                 // Destroy previous THEAD
8460                 this._destroyTheadEl();
8461     
8462                 // Create new THEAD
8463                 var aColumnDefs = this._oColumnSet.getDefinitions();
8464                 var oColumnDef = aColumnDefs.splice(nOrigTreeIndex,1)[0];
8465                 aColumnDefs.splice(index, 0, oColumnDef);
8466                 this._initColumnSet(aColumnDefs);
8467                 this._initTheadEl();
8468                 
8469                 // Need to refresh the reference
8470                 var oNewColumn = this._oColumnSet.tree[0][index];
8471
8472                 // What are new key index(es)
8473                 var aNewKeyIndexes = oNewColumn.getKeyIndex();
8474                 // Must be a parent Column
8475                 if(aNewKeyIndexes === null) {
8476                     descKeyIndexes = [];
8477                     allDescendants = this._oColumnSet.getDescendants(oNewColumn);
8478                     for(i=0, len=allDescendants.length; i<len; i++) {
8479                         // Is this descendant a key Column?
8480                         thisKey = allDescendants[i].getKeyIndex();
8481                         if(thisKey !== null) {
8482                             descKeyIndexes[descKeyIndexes.length] = thisKey;
8483                         }
8484                     }
8485                     if(descKeyIndexes.length > 0) {
8486                         aNewKeyIndexes = descKeyIndexes;
8487                     }
8488                 }
8489                 // Must be a key Column
8490                 else {
8491                     aNewKeyIndexes = [aNewKeyIndexes];
8492                 }
8493                 
8494                 // Sort the new indexes and grab the first one for the new location
8495                 var newIndex = aNewKeyIndexes.sort(function(a, b) {return YAHOO.util.Sort.compare(a, b);})[0];
8496
8497                 // Reorder COL
8498                 this._reorderColgroupColEl(aOrigKeyIndexes, newIndex);
8499                 
8500                 // Reorder TD
8501                 var allRows = this._elTbody.rows;
8502                 if(allRows.length > 0) {
8503                     var loopN = this.get("renderLoopSize"),
8504                         loopEnd = allRows.length;
8505                     this._oChainRender.add({
8506                         method: function(oArg) {
8507                             if((this instanceof DT) && this._sId) {
8508                                 var i = oArg.nCurrentRow, j, tmpTds, nextSibling,
8509                                     len = loopN > 0 ? Math.min(i + loopN,allRows.length) : allRows.length,
8510                                     aIndexes = oArg.aIndexes, thisTr;
8511                                 // For each row
8512                                 for(; i < len; ++i) {
8513                                     tmpTds = [];
8514                                     thisTr = allRows[i];
8515                                     
8516                                     // Remove each TD
8517                                     for(j=aIndexes.length-1; j>-1; j--) {
8518                                         tmpTds.push(thisTr.removeChild(thisTr.childNodes[aIndexes[j]]));
8519                                     }
8520                                     
8521                                     // Insert each TD
8522                                     nextSibling = thisTr.childNodes[newIndex] || null;
8523                                     for(j=tmpTds.length-1; j>-1; j--) {
8524                                         thisTr.insertBefore(tmpTds[j], nextSibling);
8525                                     }                                    
8526                                 }
8527                                 oArg.nCurrentRow = i;
8528                             }
8529                         },
8530                         iterations: (loopN > 0) ? Math.ceil(loopEnd/loopN) : 1,
8531                         argument: {nCurrentRow:0, aIndexes:aOrigKeyIndexes},
8532                         scope: this,
8533                         timeout: (loopN > 0) ? 0 : -1
8534                     });
8535                     this._runRenderChain();
8536                 }
8537         
8538                 this.fireEvent("columnReorderEvent",{column:oNewColumn});
8539                 return oNewColumn;
8540             }
8541         }
8542     }
8543 },
8544
8545 /**
8546  * Selects given Column. NOTE: You cannot select/unselect nested Columns. You can only
8547  * select/unselect non-nested Columns, and bottom-level key Columns.
8548  *
8549  * @method selectColumn
8550  * @param column {HTMLElement | String | Number} DOM reference or ID string to a
8551  * TH/TD element (or child of a TH/TD element), a Column key, or a ColumnSet key index.
8552  */
8553 selectColumn : function(oColumn) {
8554     oColumn = this.getColumn(oColumn);
8555     if(oColumn && !oColumn.selected) {
8556         // Only bottom-level Columns can get hidden
8557         if(oColumn.getKeyIndex() !== null) {
8558             oColumn.selected = true;
8559             
8560             // Update head cell
8561             var elTh = oColumn.getThEl();
8562             Dom.addClass(elTh,DT.CLASS_SELECTED);
8563
8564             // Update body cells
8565             var allRows = this.getTbodyEl().rows;
8566             var oChainRender = this._oChainRender;
8567             oChainRender.add({
8568                 method: function(oArg) {
8569                     if((this instanceof DT) && this._sId && allRows[oArg.rowIndex] && allRows[oArg.rowIndex].cells[oArg.cellIndex]) {
8570                         Dom.addClass(allRows[oArg.rowIndex].cells[oArg.cellIndex],DT.CLASS_SELECTED);                    
8571                     }
8572                     oArg.rowIndex++;
8573                 },
8574                 scope: this,
8575                 iterations: allRows.length,
8576                 argument: {rowIndex:0,cellIndex:oColumn.getKeyIndex()}
8577             });
8578
8579             this._clearTrTemplateEl();
8580             
8581             this._elTbody.style.display = "none";
8582             this._runRenderChain();
8583             this._elTbody.style.display = "";      
8584             
8585             this.fireEvent("columnSelectEvent",{column:oColumn});
8586         }
8587         else {
8588         }
8589     }
8590 },
8591
8592 /**
8593  * Unselects given Column. NOTE: You cannot select/unselect nested Columns. You can only
8594  * select/unselect non-nested Columns, and bottom-level key Columns.
8595  *
8596  * @method unselectColumn
8597  * @param column {HTMLElement | String | Number} DOM reference or ID string to a
8598  * TH/TD element (or child of a TH/TD element), a Column key, or a ColumnSet key index.
8599  */
8600 unselectColumn : function(oColumn) {
8601     oColumn = this.getColumn(oColumn);
8602     if(oColumn && oColumn.selected) {
8603         // Only bottom-level Columns can get hidden
8604         if(oColumn.getKeyIndex() !== null) {
8605             oColumn.selected = false;
8606             
8607             // Update head cell
8608             var elTh = oColumn.getThEl();
8609             Dom.removeClass(elTh,DT.CLASS_SELECTED);
8610
8611             // Update body cells
8612             var allRows = this.getTbodyEl().rows;
8613             var oChainRender = this._oChainRender;
8614             oChainRender.add({
8615                 method: function(oArg) {
8616                     if((this instanceof DT) && this._sId && allRows[oArg.rowIndex] && allRows[oArg.rowIndex].cells[oArg.cellIndex]) {
8617                         Dom.removeClass(allRows[oArg.rowIndex].cells[oArg.cellIndex],DT.CLASS_SELECTED); 
8618                     }                   
8619                     oArg.rowIndex++;
8620                 },
8621                 scope: this,
8622                 iterations:allRows.length,
8623                 argument: {rowIndex:0,cellIndex:oColumn.getKeyIndex()}
8624             });
8625             
8626             this._clearTrTemplateEl();
8627
8628             this._elTbody.style.display = "none";
8629             this._runRenderChain();
8630             this._elTbody.style.display = "";      
8631             
8632             this.fireEvent("columnUnselectEvent",{column:oColumn});
8633         }
8634         else {
8635         }
8636     }
8637 },
8638
8639 /**
8640  * Returns an array selected Column instances.
8641  *
8642  * @method getSelectedColumns
8643  * @return {YAHOO.widget.Column[]} Array of Column instances.
8644  */
8645 getSelectedColumns : function(oColumn) {
8646     var selectedColumns = [];
8647     var aKeys = this._oColumnSet.keys;
8648     for(var i=0,len=aKeys.length; i<len; i++) {
8649         if(aKeys[i].selected) {
8650             selectedColumns[selectedColumns.length] = aKeys[i];
8651         }
8652     }
8653     return selectedColumns;
8654 },
8655
8656 /**
8657  * Assigns the class YAHOO.widget.DataTable.CLASS_HIGHLIGHTED to cells of the given Column.
8658  * NOTE: You cannot highlight/unhighlight nested Columns. You can only
8659  * highlight/unhighlight non-nested Columns, and bottom-level key Columns.
8660  *
8661  * @method highlightColumn
8662  * @param column {HTMLElement | String | Number} DOM reference or ID string to a
8663  * TH/TD element (or child of a TH/TD element), a Column key, or a ColumnSet key index.
8664  */
8665 highlightColumn : function(column) {
8666     var oColumn = this.getColumn(column);
8667     // Only bottom-level Columns can get highlighted
8668     if(oColumn && (oColumn.getKeyIndex() !== null)) {            
8669         // Update head cell
8670         var elTh = oColumn.getThEl();
8671         Dom.addClass(elTh,DT.CLASS_HIGHLIGHTED);
8672
8673         // Update body cells
8674         var allRows = this.getTbodyEl().rows;
8675         var oChainRender = this._oChainRender;
8676         oChainRender.add({
8677             method: function(oArg) {
8678                 if((this instanceof DT) && this._sId && allRows[oArg.rowIndex] && allRows[oArg.rowIndex].cells[oArg.cellIndex]) {
8679                     Dom.addClass(allRows[oArg.rowIndex].cells[oArg.cellIndex],DT.CLASS_HIGHLIGHTED);   
8680                 }                 
8681                 oArg.rowIndex++;
8682             },
8683             scope: this,
8684             iterations:allRows.length,
8685             argument: {rowIndex:0,cellIndex:oColumn.getKeyIndex()},
8686             timeout: -1
8687         });
8688         this._elTbody.style.display = "none";
8689         this._runRenderChain();
8690         this._elTbody.style.display = "";      
8691             
8692         this.fireEvent("columnHighlightEvent",{column:oColumn});
8693     }
8694     else {
8695     }
8696 },
8697
8698 /**
8699  * Removes the class YAHOO.widget.DataTable.CLASS_HIGHLIGHTED to cells of the given Column.
8700  * NOTE: You cannot highlight/unhighlight nested Columns. You can only
8701  * highlight/unhighlight non-nested Columns, and bottom-level key Columns.
8702  *
8703  * @method unhighlightColumn
8704  * @param column {HTMLElement | String | Number} DOM reference or ID string to a
8705  * TH/TD element (or child of a TH/TD element), a Column key, or a ColumnSet key index.
8706  */
8707 unhighlightColumn : function(column) {
8708     var oColumn = this.getColumn(column);
8709     // Only bottom-level Columns can get highlighted
8710     if(oColumn && (oColumn.getKeyIndex() !== null)) {
8711         // Update head cell
8712         var elTh = oColumn.getThEl();
8713         Dom.removeClass(elTh,DT.CLASS_HIGHLIGHTED);
8714
8715         // Update body cells
8716         var allRows = this.getTbodyEl().rows;
8717         var oChainRender = this._oChainRender;
8718         oChainRender.add({
8719             method: function(oArg) {
8720                 if((this instanceof DT) && this._sId && allRows[oArg.rowIndex] && allRows[oArg.rowIndex].cells[oArg.cellIndex]) {
8721                     Dom.removeClass(allRows[oArg.rowIndex].cells[oArg.cellIndex],DT.CLASS_HIGHLIGHTED);
8722                 }                 
8723                 oArg.rowIndex++;
8724             },
8725             scope: this,
8726             iterations:allRows.length,
8727             argument: {rowIndex:0,cellIndex:oColumn.getKeyIndex()},
8728             timeout: -1
8729         });
8730         this._elTbody.style.display = "none";
8731         this._runRenderChain();
8732         this._elTbody.style.display = "";     
8733             
8734         this.fireEvent("columnUnhighlightEvent",{column:oColumn});
8735     }
8736     else {
8737     }
8738 },
8739
8740
8741
8742
8743
8744
8745
8746
8747
8748
8749
8750
8751
8752
8753
8754
8755
8756
8757
8758
8759
8760
8761
8762
8763
8764
8765
8766
8767
8768
8769
8770
8771
8772
8773
8774
8775
8776
8777
8778
8779
8780
8781
8782
8783 // ROW FUNCTIONS
8784
8785 /**
8786  * Adds one new Record of data into the RecordSet at the index if given,
8787  * otherwise at the end. If the new Record is in page view, the
8788  * corresponding DOM elements are also updated.
8789  *
8790  * @method addRow
8791  * @param oData {Object} Object literal of data for the row.
8792  * @param index {Number} (optional) RecordSet position index at which to add data.
8793  */
8794 addRow : function(oData, index) {
8795     if(lang.isNumber(index) && (index < 0 || index > this._oRecordSet.getLength())) {
8796         return;
8797     }
8798
8799     if(oData && lang.isObject(oData)) {
8800         var oRecord = this._oRecordSet.addRecord(oData, index);
8801         if(oRecord) {
8802             var recIndex;
8803             var oPaginator = this.get('paginator');
8804
8805             // Paginated
8806             if (oPaginator) {     
8807                 // Update the paginator's totalRecords
8808                 var totalRecords = oPaginator.get('totalRecords');
8809                 if (totalRecords !== widget.Paginator.VALUE_UNLIMITED) {
8810                     oPaginator.set('totalRecords',totalRecords + 1);
8811                 }
8812
8813                 recIndex = this.getRecordIndex(oRecord);
8814                 var endRecIndex = (oPaginator.getPageRecords())[1];
8815
8816                 // New record affects the view
8817                 if (recIndex <= endRecIndex) {
8818                     // Defer UI updates to the render method
8819                     this.render();
8820                 }
8821                 
8822                 this.fireEvent("rowAddEvent", {record:oRecord});
8823                 return;
8824             }
8825             // Not paginated
8826             else {
8827                 recIndex = this.getTrIndex(oRecord);
8828                 if(lang.isNumber(recIndex)) {
8829                     // Add the TR element
8830                     this._oChainRender.add({
8831                         method: function(oArg) {
8832                             if((this instanceof DT) && this._sId) {
8833                                 var oRecord = oArg.record;
8834                                 var recIndex = oArg.recIndex;
8835                                 var elNewTr = this._addTrEl(oRecord);
8836                                 if(elNewTr) {
8837                                     var elNext = (this._elTbody.rows[recIndex]) ? this._elTbody.rows[recIndex] : null;
8838                                     this._elTbody.insertBefore(elNewTr, elNext);
8839
8840                                     // Set FIRST/LAST
8841                                     if(recIndex === 0) {
8842                                         this._setFirstRow();
8843                                     }
8844                                     if(elNext === null) {
8845                                         this._setLastRow();
8846                                     }
8847                                     // Set EVEN/ODD
8848                                     this._setRowStripes();                           
8849                                     
8850                                     this.hideTableMessage();
8851             
8852                                     this.fireEvent("rowAddEvent", {record:oRecord});
8853                                 }
8854                             }
8855                         },
8856                         argument: {record: oRecord, recIndex: recIndex},
8857                         scope: this,
8858                         timeout: (this.get("renderLoopSize") > 0) ? 0 : -1
8859                     });
8860                     this._runRenderChain();
8861                     return;
8862                 }
8863             }            
8864         }
8865     }
8866 },
8867
8868 /**
8869  * Convenience method to add multiple rows.
8870  *
8871  * @method addRows
8872  * @param aData {Object[]} Array of object literal data for the rows.
8873  * @param index {Number} (optional) RecordSet position index at which to add data.
8874  */
8875 addRows : function(aData, index) {
8876     if(lang.isNumber(index) && (index < 0 || index > this._oRecordSet.getLength())) {
8877         return;
8878     }
8879
8880     if(lang.isArray(aData)) {
8881         var aRecords = this._oRecordSet.addRecords(aData, index);
8882         if(aRecords) {
8883             var recIndex = this.getRecordIndex(aRecords[0]);
8884             
8885             // Paginated
8886             var oPaginator = this.get('paginator');
8887             if (oPaginator) {
8888                 // Update the paginator's totalRecords
8889                 var totalRecords = oPaginator.get('totalRecords');
8890                 if (totalRecords !== widget.Paginator.VALUE_UNLIMITED) {
8891                     oPaginator.set('totalRecords',totalRecords + aRecords.length);
8892                 }
8893     
8894                 var endRecIndex = (oPaginator.getPageRecords())[1];
8895
8896                 // At least one of the new records affects the view
8897                 if (recIndex <= endRecIndex) {
8898                     this.render();
8899                 }
8900                 
8901                 this.fireEvent("rowsAddEvent", {records:aRecords});
8902                 return;
8903             }
8904             // Not paginated
8905             else {
8906                 // Add the TR elements
8907                 var loopN = this.get("renderLoopSize");
8908                 var loopEnd = recIndex + aData.length;
8909                 var nRowsNeeded = (loopEnd - recIndex); // how many needed
8910                 var isLast = (recIndex >= this._elTbody.rows.length);
8911                 this._oChainRender.add({
8912                     method: function(oArg) {
8913                         if((this instanceof DT) && this._sId) {
8914                             var aRecords = oArg.aRecords,
8915                                 i = oArg.nCurrentRow,
8916                                 j = oArg.nCurrentRecord,
8917                                 len = loopN > 0 ? Math.min(i + loopN,loopEnd) : loopEnd,
8918                                 df = document.createDocumentFragment(),
8919                                 elNext = (this._elTbody.rows[i]) ? this._elTbody.rows[i] : null;
8920                             for(; i < len; i++, j++) {
8921                                 df.appendChild(this._addTrEl(aRecords[j]));
8922                             }
8923                             this._elTbody.insertBefore(df, elNext);
8924                             oArg.nCurrentRow = i;
8925                             oArg.nCurrentRecord = j;
8926                         }
8927                     },
8928                     iterations: (loopN > 0) ? Math.ceil(loopEnd/loopN) : 1,
8929                     argument: {nCurrentRow:recIndex,nCurrentRecord:0,aRecords:aRecords},
8930                     scope: this,
8931                     timeout: (loopN > 0) ? 0 : -1
8932                 });
8933                 this._oChainRender.add({
8934                     method: function(oArg) {
8935                         var recIndex = oArg.recIndex;
8936                         // Set FIRST/LAST
8937                         if(recIndex === 0) {
8938                             this._setFirstRow();
8939                         }
8940                         if(oArg.isLast) {
8941                             this._setLastRow();
8942                         }
8943                         // Set EVEN/ODD
8944                         this._setRowStripes();                           
8945
8946                         this.fireEvent("rowsAddEvent", {records:aRecords});
8947                     },
8948                     argument: {recIndex: recIndex, isLast: isLast},
8949                     scope: this,
8950                     timeout: -1 // Needs to run immediately after the DOM insertions above
8951                 });
8952                 this._runRenderChain();
8953                 this.hideTableMessage();                
8954                 return;
8955             }            
8956         }
8957     }
8958 },
8959
8960 /**
8961  * For the given row, updates the associated Record with the given data. If the
8962  * row is on current page, the corresponding DOM elements are also updated.
8963  *
8964  * @method updateRow
8965  * @param row {YAHOO.widget.Record | Number | HTMLElement | String}
8966  * Which row to update: By Record instance, by Record's RecordSet
8967  * position index, by HTMLElement reference to the TR element, or by ID string
8968  * of the TR element.
8969  * @param oData {Object} Object literal of data for the row.
8970  */
8971 updateRow : function(row, oData) {
8972     var index = row;
8973     if (!lang.isNumber(index)) {
8974         index = this.getRecordIndex(row);
8975     }
8976
8977     // Update the Record
8978     if(lang.isNumber(index) && (index >= 0)) {
8979         var oRecordSet = this._oRecordSet,
8980             oldRecord = oRecordSet.getRecord(index);
8981             
8982         
8983         if(oldRecord) {
8984             var updatedRecord = this._oRecordSet.setRecord(oData, index),
8985                 elRow = this.getTrEl(oldRecord),
8986                 // Copy data from the Record for the event that gets fired later
8987                 oldData = oldRecord ? oldRecord.getData() : null;
8988                
8989             if(updatedRecord) {
8990                 // Update selected rows as necessary
8991                 var tracker = this._aSelections || [],
8992                 i=0,
8993                 oldId = oldRecord.getId(),
8994                 newId = updatedRecord.getId();
8995                 for(; i<tracker.length; i++) {
8996                     if((tracker[i] === oldId)) {
8997                         tracker[i] = newId;
8998                     }
8999                     else if(tracker[i].recordId === oldId) {
9000                         tracker[i].recordId = newId;
9001                     }
9002                 }
9003
9004                 // Update the TR only if row is on current page
9005                 this._oChainRender.add({
9006                     method: function() {
9007                         if((this instanceof DT) && this._sId) {
9008                             // Paginated
9009                             var oPaginator = this.get('paginator');
9010                             if (oPaginator) {
9011                                 var pageStartIndex = (oPaginator.getPageRecords())[0],
9012                                     pageLastIndex = (oPaginator.getPageRecords())[1];
9013         
9014                                 // At least one of the new records affects the view
9015                                 if ((index >= pageStartIndex) || (index <= pageLastIndex)) {
9016                                     this.render();
9017                                 }
9018                             }
9019                             else {
9020                                 if(elRow) {
9021                                     this._updateTrEl(elRow, updatedRecord);
9022                                 }
9023                                 else {
9024                                     this.getTbodyEl().appendChild(this._addTrEl(updatedRecord));
9025                                 }
9026                             }
9027                             this.fireEvent("rowUpdateEvent", {record:updatedRecord, oldData:oldData});
9028                         }
9029                     },
9030                     scope: this,
9031                     timeout: (this.get("renderLoopSize") > 0) ? 0 : -1
9032                 });
9033                 this._runRenderChain();
9034                 return;
9035             }
9036         }
9037     }
9038     return;
9039 },
9040
9041 /**
9042  * Starting with the given row, updates associated Records with the given data.
9043  * The number of rows to update are determined by the array of data provided.
9044  * Undefined data (i.e., not an object literal) causes a row to be skipped. If
9045  * any of the rows are on current page, the corresponding DOM elements are also
9046  * updated.
9047  *
9048  * @method updateRows
9049  * @param startrow {YAHOO.widget.Record | Number | HTMLElement | String}
9050  * Starting row to update: By Record instance, by Record's RecordSet
9051  * position index, by HTMLElement reference to the TR element, or by ID string
9052  * of the TR element.
9053  * @param aData {Object[]} Array of object literal of data for the rows.
9054  */
9055 updateRows : function(startrow, aData) {
9056     if(lang.isArray(aData)) {
9057         var startIndex = startrow,
9058             oRecordSet = this._oRecordSet;
9059             
9060         if (!lang.isNumber(startrow)) {
9061             startIndex = this.getRecordIndex(startrow);
9062         }
9063             
9064         if(lang.isNumber(startIndex) && (startIndex >= 0) && (startIndex < oRecordSet.getLength())) {
9065             var lastIndex = startIndex + aData.length,
9066                 aOldRecords = oRecordSet.getRecords(startIndex, aData.length),
9067                 aNewRecords = oRecordSet.setRecords(aData, startIndex);
9068             if(aNewRecords) {
9069                 // Update selected rows as necessary
9070                 var tracker = this._aSelections || [],
9071                     i=0, j, newId, oldId;
9072                 for(; i<tracker.length; i++) {
9073                     for(j=0; j<aOldRecords.length; j++) {
9074                         oldId = aOldRecords[j].getId();
9075                         if((tracker[i] === oldId)) {
9076                             tracker[i] = aNewRecords[j].getId();
9077                         }
9078                         else if(tracker[i].recordId === oldId) {
9079                             tracker[i].recordId = aNewRecords[j].getId();
9080                         }
9081                     }
9082                 }
9083             
9084                 // Paginated
9085                 var oPaginator = this.get('paginator');
9086                 if (oPaginator) {
9087                     var pageStartIndex = (oPaginator.getPageRecords())[0],
9088                         pageLastIndex = (oPaginator.getPageRecords())[1];
9089     
9090                     // At least one of the new records affects the view
9091                     if ((startIndex >= pageStartIndex) || (lastIndex <= pageLastIndex)) {
9092                         this.render();
9093                     }
9094                     
9095                     this.fireEvent("rowsAddEvent", {newRecords:aNewRecords, oldRecords:aOldRecords});
9096                     return;
9097                 }
9098                 // Not paginated
9099                 else {
9100                     // Update the TR elements
9101                     var loopN = this.get("renderLoopSize"),
9102                         rowCount = aData.length, // how many needed
9103                         lastRowIndex = this._elTbody.rows.length,
9104                         isLast = (lastIndex >= lastRowIndex),
9105                         isAdding = (lastIndex > lastRowIndex);
9106                                            
9107                     this._oChainRender.add({
9108                         method: function(oArg) {
9109                             if((this instanceof DT) && this._sId) {
9110                                 var aRecords = oArg.aRecords,
9111                                     i = oArg.nCurrentRow,
9112                                     j = oArg.nDataPointer,
9113                                     len = loopN > 0 ? Math.min(i+loopN, startIndex+aRecords.length) : startIndex+aRecords.length;
9114                                     
9115                                 for(; i < len; i++,j++) {
9116                                     if(isAdding && (i>=lastRowIndex)) {
9117                                         this._elTbody.appendChild(this._addTrEl(aRecords[j]));
9118                                     }
9119                                     else {
9120                                         this._updateTrEl(this._elTbody.rows[i], aRecords[j]);
9121                                     }
9122                                 }
9123                                 oArg.nCurrentRow = i;
9124                                 oArg.nDataPointer = j;
9125                             }
9126                         },
9127                         iterations: (loopN > 0) ? Math.ceil(rowCount/loopN) : 1,
9128                         argument: {nCurrentRow:startIndex,aRecords:aNewRecords,nDataPointer:0,isAdding:isAdding},
9129                         scope: this,
9130                         timeout: (loopN > 0) ? 0 : -1
9131                     });
9132                     this._oChainRender.add({
9133                         method: function(oArg) {
9134                             var recIndex = oArg.recIndex;
9135                             // Set FIRST/LAST
9136                             if(recIndex === 0) {
9137                                 this._setFirstRow();
9138                             }
9139                             if(oArg.isLast) {
9140                                 this._setLastRow();
9141                             }
9142                             // Set EVEN/ODD
9143                             this._setRowStripes();                           
9144     
9145                             this.fireEvent("rowsAddEvent", {newRecords:aNewRecords, oldRecords:aOldRecords});
9146                         },
9147                         argument: {recIndex: startIndex, isLast: isLast},
9148                         scope: this,
9149                         timeout: -1 // Needs to run immediately after the DOM insertions above
9150                     });
9151                     this._runRenderChain();
9152                     this.hideTableMessage();                
9153                     return;
9154                 }            
9155             }
9156         }
9157     }
9158 },
9159
9160 /**
9161  * Deletes the given row's Record from the RecordSet. If the row is on current page,
9162  * the corresponding DOM elements are also deleted.
9163  *
9164  * @method deleteRow
9165  * @param row {HTMLElement | String | Number} DOM element reference or ID string
9166  * to DataTable page element or RecordSet index.
9167  */
9168 deleteRow : function(row) {
9169     var nRecordIndex = (lang.isNumber(row)) ? row : this.getRecordIndex(row);
9170     if(lang.isNumber(nRecordIndex)) {
9171         var oRecord = this.getRecord(nRecordIndex);
9172         if(oRecord) {
9173             var nTrIndex = this.getTrIndex(nRecordIndex);
9174             
9175             // Remove from selection tracker if there
9176             var sRecordId = oRecord.getId();
9177             var tracker = this._aSelections || [];
9178             for(var j=tracker.length-1; j>-1; j--) {
9179                 if((lang.isString(tracker[j]) && (tracker[j] === sRecordId)) ||
9180                         (lang.isObject(tracker[j]) && (tracker[j].recordId === sRecordId))) {
9181                     tracker.splice(j,1);
9182                 }
9183             }
9184     
9185             // Delete Record from RecordSet
9186             var oData = this._oRecordSet.deleteRecord(nRecordIndex);
9187     
9188             // Update the UI
9189             if(oData) {
9190                 // If paginated and the deleted row was on this or a prior page, just
9191                 // re-render
9192                 var oPaginator = this.get('paginator');
9193                 if (oPaginator) {
9194                     // Update the paginator's totalRecords
9195                     var totalRecords = oPaginator.get('totalRecords'),
9196                         // must capture before the totalRecords change because
9197                         // Paginator shifts to previous page automatically
9198                         rng = oPaginator.getPageRecords();
9199
9200                     if (totalRecords !== widget.Paginator.VALUE_UNLIMITED) {
9201                         oPaginator.set('totalRecords',totalRecords - 1);
9202                     }
9203     
9204                     // The deleted record was on this or a prior page, re-render
9205                     if (!rng || nRecordIndex <= rng[1]) {
9206                         this.render();
9207                     }
9208
9209                     this._oChainRender.add({
9210                         method: function() {
9211                             if((this instanceof DT) && this._sId) {
9212                                 this.fireEvent("rowDeleteEvent", {recordIndex:nRecordIndex, oldData:oData, trElIndex:nTrIndex});
9213                             }
9214                         },
9215                         scope: this,
9216                         timeout: (this.get("renderLoopSize") > 0) ? 0 : -1
9217                     });
9218                     this._runRenderChain();
9219                 }
9220                 // Not paginated
9221                 else {
9222                     if(lang.isNumber(nTrIndex)) {
9223                         this._oChainRender.add({
9224                             method: function() {
9225                                 if((this instanceof DT) && this._sId) {
9226                                     var isLast = (nTrIndex == this.getLastTrEl().sectionRowIndex);
9227                                     this._deleteTrEl(nTrIndex);
9228                     
9229                                     // Post-delete tasks
9230                                     if(this._elTbody.rows.length > 0) {
9231                                         // Set FIRST/LAST
9232                                         if(nTrIndex === 0) {
9233                                             this._setFirstRow();
9234                                         }
9235                                         if(isLast) {
9236                                             this._setLastRow();
9237                                         }
9238                                         // Set EVEN/ODD
9239                                         if(nTrIndex != this._elTbody.rows.length) {
9240                                             this._setRowStripes(nTrIndex);
9241                                         }                                
9242                                     }
9243                     
9244                                     this.fireEvent("rowDeleteEvent", {recordIndex:nRecordIndex,oldData:oData, trElIndex:nTrIndex});
9245                                 }
9246                             },
9247                             scope: this,
9248                             timeout: (this.get("renderLoopSize") > 0) ? 0 : -1
9249                         });
9250                         this._runRenderChain();
9251                         return;
9252                     }
9253                 }
9254             }
9255         }
9256     }
9257     return null;
9258 },
9259
9260 /**
9261  * Convenience method to delete multiple rows.
9262  *
9263  * @method deleteRows
9264  * @param row {HTMLElement | String | Number} DOM element reference or ID string
9265  * to DataTable page element or RecordSet index.
9266  * @param count {Number} (optional) How many rows to delete. A negative value
9267  * will delete towards the beginning.
9268  */
9269 deleteRows : function(row, count) {
9270     var nRecordIndex = (lang.isNumber(row)) ? row : this.getRecordIndex(row);
9271     if(lang.isNumber(nRecordIndex)) {
9272         var oRecord = this.getRecord(nRecordIndex);
9273         if(oRecord) {
9274             var nTrIndex = this.getTrIndex(nRecordIndex);
9275             
9276             // Remove from selection tracker if there
9277             var sRecordId = oRecord.getId();
9278             var tracker = this._aSelections || [];
9279             for(var j=tracker.length-1; j>-1; j--) {
9280                 if((lang.isString(tracker[j]) && (tracker[j] === sRecordId)) ||
9281                         (lang.isObject(tracker[j]) && (tracker[j].recordId === sRecordId))) {
9282                     tracker.splice(j,1);
9283                 }
9284             }
9285     
9286             // Delete Record from RecordSet
9287             var highIndex = nRecordIndex;
9288             var lowIndex = nRecordIndex;
9289         
9290             // Validate count and account for negative value
9291             if(count && lang.isNumber(count)) {
9292                 highIndex = (count > 0) ? nRecordIndex + count -1 : nRecordIndex;
9293                 lowIndex = (count > 0) ? nRecordIndex : nRecordIndex + count + 1;
9294                 count = (count > 0) ? count : count*-1;
9295                 if(lowIndex < 0) {
9296                     lowIndex = 0;
9297                     count = highIndex - lowIndex + 1;
9298                 }
9299             }
9300             else {
9301                 count = 1;
9302             }
9303             
9304             var aData = this._oRecordSet.deleteRecords(lowIndex, count);
9305     
9306             // Update the UI
9307             if(aData) {
9308                 var oPaginator = this.get('paginator'),
9309                     loopN = this.get("renderLoopSize");
9310                 // If paginated and the deleted row was on this or a prior page, just
9311                 // re-render
9312                 if (oPaginator) {
9313                     // Update the paginator's totalRecords
9314                     var totalRecords = oPaginator.get('totalRecords'),
9315                         // must capture before the totalRecords change because
9316                         // Paginator shifts to previous page automatically
9317                         rng = oPaginator.getPageRecords();
9318
9319                     if (totalRecords !== widget.Paginator.VALUE_UNLIMITED) {
9320                         oPaginator.set('totalRecords',totalRecords - aData.length);
9321                     }
9322     
9323                     // The records were on this or a prior page, re-render
9324                     if (!rng || lowIndex <= rng[1]) {
9325                         this.render();
9326                     }
9327
9328                     this._oChainRender.add({
9329                         method: function(oArg) {
9330                             if((this instanceof DT) && this._sId) {
9331                                 this.fireEvent("rowsDeleteEvent", {recordIndex:lowIndex, oldData:aData, count:count});
9332                             }
9333                         },
9334                         scope: this,
9335                         timeout: (loopN > 0) ? 0 : -1
9336                     });
9337                     this._runRenderChain();
9338                     return;
9339                 }
9340                 // Not paginated
9341                 else {
9342                     if(lang.isNumber(nTrIndex)) {
9343                         // Delete the TR elements starting with highest index
9344                         var loopEnd = lowIndex;
9345                         var nRowsNeeded = count; // how many needed
9346                         this._oChainRender.add({
9347                             method: function(oArg) {
9348                                 if((this instanceof DT) && this._sId) {
9349                                     var i = oArg.nCurrentRow,
9350                                         len = (loopN > 0) ? (Math.max(i - loopN,loopEnd)-1) : loopEnd-1;
9351                                     for(; i>len; --i) {
9352                                         this._deleteTrEl(i);
9353                                     }
9354                                     oArg.nCurrentRow = i;
9355                                 }
9356                             },
9357                             iterations: (loopN > 0) ? Math.ceil(count/loopN) : 1,
9358                             argument: {nCurrentRow:highIndex},
9359                             scope: this,
9360                             timeout: (loopN > 0) ? 0 : -1
9361                         });
9362                         this._oChainRender.add({
9363                             method: function() {    
9364                                 // Post-delete tasks
9365                                 if(this._elTbody.rows.length > 0) {
9366                                     this._setFirstRow();
9367                                     this._setLastRow();
9368                                     this._setRowStripes();
9369                                 }
9370                                 
9371                                 this.fireEvent("rowsDeleteEvent", {recordIndex:lowIndex, oldData:aData, count:count});
9372                             },
9373                             scope: this,
9374                             timeout: -1 // Needs to run immediately after the DOM deletions above
9375                         });
9376                         this._runRenderChain();
9377                         return;
9378                     }
9379                 }
9380             }
9381         }
9382     }
9383     return null;
9384 },
9385
9386
9387
9388
9389
9390
9391
9392
9393
9394
9395
9396
9397
9398
9399
9400
9401
9402
9403
9404
9405
9406
9407
9408
9409
9410
9411
9412
9413
9414
9415
9416
9417
9418
9419
9420
9421
9422
9423
9424
9425
9426
9427
9428
9429
9430
9431 // CELL FUNCTIONS
9432
9433 /**
9434  * Outputs markup into the given TD based on given Record.
9435  *
9436  * @method formatCell
9437  * @param elCell {HTMLElement} The liner DIV element within the TD.
9438  * @param oRecord {YAHOO.widget.Record} (Optional) Record instance.
9439  * @param oColumn {YAHOO.widget.Column} (Optional) Column instance.
9440  */
9441 formatCell : function(elCell, oRecord, oColumn) {
9442     if(!oRecord) {
9443         oRecord = this.getRecord(elCell);
9444     }
9445     if(!oColumn) {
9446         oColumn = this.getColumn(elCell.parentNode.cellIndex);
9447     }
9448
9449     if(oRecord && oColumn) {
9450         var sField = oColumn.field;
9451         var oData = oRecord.getData(sField);
9452
9453         var fnFormatter = typeof oColumn.formatter === 'function' ?
9454                           oColumn.formatter :
9455                           DT.Formatter[oColumn.formatter+''] ||
9456                           DT.Formatter.defaultFormatter;
9457
9458         // Apply special formatter
9459         if(fnFormatter) {
9460             fnFormatter.call(this, elCell, oRecord, oColumn, oData);
9461         }
9462         else {
9463             elCell.innerHTML = oData;
9464         }
9465
9466         this.fireEvent("cellFormatEvent", {record:oRecord, column:oColumn, key:oColumn.key, el:elCell});
9467     }
9468     else {
9469     }
9470 },
9471
9472 /**
9473  * For the given row and column, updates the Record with the given data. If the
9474  * cell is on current page, the corresponding DOM elements are also updated.
9475  *
9476  * @method updateCell
9477  * @param oRecord {YAHOO.widget.Record} Record instance.
9478  * @param oColumn {YAHOO.widget.Column | String | Number} A Column key, or a ColumnSet key index.
9479  * @param oData {Object} New data value for the cell.
9480  */
9481 updateCell : function(oRecord, oColumn, oData) {    
9482     // Validate Column and Record
9483     oColumn = (oColumn instanceof YAHOO.widget.Column) ? oColumn : this.getColumn(oColumn);
9484     if(oColumn && oColumn.getKey() && (oRecord instanceof YAHOO.widget.Record)) {
9485         var sKey = oColumn.getKey(),
9486         
9487         // Copy data from the Record for the event that gets fired later
9488         //var oldData = YAHOO.widget.DataTable._cloneObject(oRecord.getData());
9489             oldData = oRecord.getData(sKey);
9490
9491         // Update Record with new data
9492         this._oRecordSet.updateRecordValue(oRecord, sKey, oData);
9493     
9494         // Update the TD only if row is on current page
9495         var elTd = this.getTdEl({record: oRecord, column: oColumn});
9496         if(elTd) {
9497             this._oChainRender.add({
9498                 method: function() {
9499                     if((this instanceof DT) && this._sId) {
9500                         this.formatCell(elTd.firstChild);
9501                         this.fireEvent("cellUpdateEvent", {record:oRecord, column: oColumn, oldData:oldData});
9502                     }
9503                 },
9504                 scope: this,
9505                 timeout: (this.get("renderLoopSize") > 0) ? 0 : -1
9506             });
9507             this._runRenderChain();
9508         }
9509         else {
9510             this.fireEvent("cellUpdateEvent", {record:oRecord, column: oColumn, oldData:oldData});
9511         }
9512     }
9513 },
9514
9515
9516
9517
9518
9519
9520
9521
9522
9523
9524
9525
9526
9527
9528
9529
9530
9531
9532
9533
9534
9535
9536
9537
9538
9539
9540
9541
9542
9543
9544
9545
9546
9547
9548
9549
9550
9551
9552
9553
9554
9555
9556
9557
9558
9559
9560
9561
9562
9563
9564
9565 // PAGINATION
9566 /**
9567  * Method executed during set() operation for the "paginator" attribute.
9568  * Adds and/or severs event listeners between DataTable and Paginator
9569  *
9570  * @method _updatePaginator
9571  * @param newPag {Paginator} Paginator instance (or null) for DataTable to use
9572  * @private
9573  */
9574 _updatePaginator : function (newPag) {
9575     var oldPag = this.get('paginator');
9576     if (oldPag && newPag !== oldPag) {
9577         oldPag.unsubscribe('changeRequest', this.onPaginatorChangeRequest, this, true);
9578     }
9579     if (newPag) {
9580         newPag.subscribe('changeRequest', this.onPaginatorChangeRequest, this, true);
9581     }
9582 },
9583
9584 /**
9585  * Update the UI infrastructure in response to a "paginator" attribute change.
9586  *
9587  * @method _handlePaginatorChange
9588  * @param e {Object} Change event object containing keys 'type','newValue',
9589  *                   and 'prevValue'
9590  * @private
9591  */
9592 _handlePaginatorChange : function (e) {
9593     if (e.prevValue === e.newValue) { return; }
9594
9595     var newPag     = e.newValue,
9596         oldPag     = e.prevValue,
9597         containers = this._defaultPaginatorContainers();
9598
9599     if (oldPag) {
9600         if (oldPag.getContainerNodes()[0] == containers[0]) {
9601             oldPag.set('containers',[]);
9602         }
9603         oldPag.destroy();
9604
9605         // Convenience: share the default containers if possible.
9606         // Otherwise, remove the default containers from the DOM.
9607         if (containers[0]) {
9608             if (newPag && !newPag.getContainerNodes().length) {
9609                 newPag.set('containers',containers);
9610             } else {
9611                 // No new Paginator to use existing containers, OR new
9612                 // Paginator has configured containers.
9613                 for (var i = containers.length - 1; i >= 0; --i) {
9614                     if (containers[i]) {
9615                         containers[i].parentNode.removeChild(containers[i]);
9616                     }
9617                 }
9618             }
9619         }
9620     }
9621
9622     if (!this._bInit) {
9623         this.render();
9624
9625     }
9626
9627     if (newPag) {
9628         this.renderPaginator();
9629     }
9630
9631 },
9632
9633 /**
9634  * Returns the default containers used for Paginators.  If create param is
9635  * passed, the containers will be created and added to the DataTable container.
9636  *
9637  * @method _defaultPaginatorContainers
9638  * @param create {boolean} Create the default containers if not found
9639  * @private
9640  */
9641 _defaultPaginatorContainers : function (create) {
9642     var above_id = this._sId + '-paginator0',
9643         below_id = this._sId + '-paginator1',
9644         above    = Dom.get(above_id),
9645         below    = Dom.get(below_id);
9646
9647     if (create && (!above || !below)) {
9648         // One above and one below the table
9649         if (!above) {
9650             above    = document.createElement('div');
9651             above.id = above_id;
9652             Dom.addClass(above, DT.CLASS_PAGINATOR);
9653
9654             this._elContainer.insertBefore(above,this._elContainer.firstChild);
9655         }
9656
9657         if (!below) {
9658             below    = document.createElement('div');
9659             below.id = below_id;
9660             Dom.addClass(below, DT.CLASS_PAGINATOR);
9661
9662             this._elContainer.appendChild(below);
9663         }
9664     }
9665
9666     return [above,below];
9667 },
9668
9669 /**
9670  * Renders the Paginator to the DataTable UI
9671  *
9672  * @method renderPaginator
9673  */
9674 renderPaginator : function () {
9675     var pag = this.get("paginator");
9676     if (!pag) { return; }
9677
9678     // Add the containers if the Paginator is not configured with containers
9679     if (!pag.getContainerNodes().length) {
9680         pag.set('containers',this._defaultPaginatorContainers(true));
9681     }
9682
9683     pag.render();
9684 },
9685
9686 /**
9687  * Overridable method gives implementers a hook to show loading message before
9688  * changing Paginator value.
9689  *
9690  * @method doBeforePaginatorChange
9691  * @param oPaginatorState {Object} An object literal describing the proposed pagination state.
9692  * @return {Boolean} Return true to continue changing Paginator value.
9693  */
9694 doBeforePaginatorChange : function(oPaginatorState) {
9695     this.showTableMessage(this.get("MSG_LOADING"), DT.CLASS_LOADING);
9696     return true;
9697 },
9698
9699 /**
9700  * Responds to new Pagination states. By default, updates the UI to reflect the
9701  * new state. If "dynamicData" is true, current selections are purged before
9702  * a request is sent to the DataSource for data for the new state (using the
9703  * request returned by "generateRequest()").
9704  *  
9705  * @method onPaginatorChangeRequest
9706  * @param oPaginatorState {Object} An object literal describing the proposed pagination state.
9707  */
9708 onPaginatorChangeRequest : function (oPaginatorState) {
9709     var ok = this.doBeforePaginatorChange(oPaginatorState);
9710     if(ok) {
9711         // Server-side pagination
9712         if(this.get("dynamicData")) {
9713             // Get the current state
9714             var oState = this.getState();
9715             
9716             // Update pagination values
9717             oState.pagination = oPaginatorState;
9718     
9719             // Get the request for the new state
9720             var request = this.get("generateRequest")(oState, this);
9721             
9722             // Purge selections
9723             this.unselectAllRows();
9724             this.unselectAllCells();
9725             
9726             // Get the new data from the server
9727             var callback = {
9728                 success : this.onDataReturnSetRows,
9729                 failure : this.onDataReturnSetRows,
9730                 argument : oState, // Pass along the new state to the callback
9731                 scope : this
9732             };
9733             this._oDataSource.sendRequest(request, callback);
9734         }
9735         // Client-side pagination
9736         else {
9737             // Set the core pagination values silently (the second param)
9738             // to avoid looping back through the changeRequest mechanism
9739             oPaginatorState.paginator.setStartIndex(oPaginatorState.recordOffset,true);
9740             oPaginatorState.paginator.setRowsPerPage(oPaginatorState.rowsPerPage,true);
9741     
9742             // Update the UI
9743             this.render();
9744         }
9745     }
9746     else {
9747     }
9748 },
9749
9750
9751
9752
9753
9754
9755
9756
9757
9758
9759
9760
9761
9762
9763
9764
9765
9766
9767
9768
9769
9770
9771
9772
9773
9774
9775
9776
9777
9778
9779
9780
9781
9782
9783
9784
9785
9786
9787
9788
9789
9790
9791
9792
9793
9794
9795
9796
9797
9798
9799 // SELECTION/HIGHLIGHTING
9800
9801 /*
9802  * Reference to last highlighted cell element
9803  *
9804  * @property _elLastHighlightedTd
9805  * @type HTMLElement
9806  * @private
9807  */
9808 _elLastHighlightedTd : null,
9809
9810 /*
9811  * ID string of last highlighted row element
9812  *
9813  * @property _sLastHighlightedTrElId
9814  * @type String
9815  * @private
9816  */
9817 //_sLastHighlightedTrElId : null,
9818
9819 /**
9820  * Array to track row selections (by sRecordId) and/or cell selections
9821  * (by {recordId:sRecordId, columnKey:sColumnKey})
9822  *
9823  * @property _aSelections
9824  * @type Object[]
9825  * @private
9826  */
9827 _aSelections : null,
9828
9829 /**
9830  * Record instance of the row selection anchor.
9831  *
9832  * @property _oAnchorRecord
9833  * @type YAHOO.widget.Record
9834  * @private
9835  */
9836 _oAnchorRecord : null,
9837
9838 /**
9839  * Object literal representing cell selection anchor:
9840  * {recordId:sRecordId, columnKey:sColumnKey}.
9841  *
9842  * @property _oAnchorCell
9843  * @type Object
9844  * @private
9845  */
9846 _oAnchorCell : null,
9847
9848 /**
9849  * Convenience method to remove the class YAHOO.widget.DataTable.CLASS_SELECTED
9850  * from all TR elements on the page.
9851  *
9852  * @method _unselectAllTrEls
9853  * @private
9854  */
9855 _unselectAllTrEls : function() {
9856     var selectedRows = Dom.getElementsByClassName(DT.CLASS_SELECTED,"tr",this._elTbody);
9857     Dom.removeClass(selectedRows, DT.CLASS_SELECTED);
9858 },
9859
9860 /**
9861  * Returns object literal of values that represent the selection trigger. Used
9862  * to determine selection behavior resulting from a key event.
9863  *
9864  * @method _getSelectionTrigger
9865  * @private
9866  */
9867 _getSelectionTrigger : function() {
9868     var sMode = this.get("selectionMode");
9869     var oTrigger = {};
9870     var oTriggerCell, oTriggerRecord, nTriggerRecordIndex, elTriggerRow, nTriggerTrIndex;
9871
9872     // Cell mode
9873     if((sMode == "cellblock") || (sMode == "cellrange") || (sMode == "singlecell")) {
9874         oTriggerCell = this.getLastSelectedCell();
9875         // No selected cells found
9876         if(!oTriggerCell) {
9877             return null;
9878         }
9879         else {
9880             oTriggerRecord = this.getRecord(oTriggerCell.recordId);
9881             nTriggerRecordIndex = this.getRecordIndex(oTriggerRecord);
9882             elTriggerRow = this.getTrEl(oTriggerRecord);
9883             nTriggerTrIndex = this.getTrIndex(elTriggerRow);
9884
9885             // Selected cell not found on this page
9886             if(nTriggerTrIndex === null) {
9887                 return null;
9888             }
9889             else {
9890                 oTrigger.record = oTriggerRecord;
9891                 oTrigger.recordIndex = nTriggerRecordIndex;
9892                 oTrigger.el = this.getTdEl(oTriggerCell);
9893                 oTrigger.trIndex = nTriggerTrIndex;
9894                 oTrigger.column = this.getColumn(oTriggerCell.columnKey);
9895                 oTrigger.colKeyIndex = oTrigger.column.getKeyIndex();
9896                 oTrigger.cell = oTriggerCell;
9897                 return oTrigger;
9898             }
9899         }
9900     }
9901     // Row mode
9902     else {
9903         oTriggerRecord = this.getLastSelectedRecord();
9904         // No selected rows found
9905         if(!oTriggerRecord) {
9906                 return null;
9907         }
9908         else {
9909             // Selected row found, but is it on current page?
9910             oTriggerRecord = this.getRecord(oTriggerRecord);
9911             nTriggerRecordIndex = this.getRecordIndex(oTriggerRecord);
9912             elTriggerRow = this.getTrEl(oTriggerRecord);
9913             nTriggerTrIndex = this.getTrIndex(elTriggerRow);
9914
9915             // Selected row not found on this page
9916             if(nTriggerTrIndex === null) {
9917                 return null;
9918             }
9919             else {
9920                 oTrigger.record = oTriggerRecord;
9921                 oTrigger.recordIndex = nTriggerRecordIndex;
9922                 oTrigger.el = elTriggerRow;
9923                 oTrigger.trIndex = nTriggerTrIndex;
9924                 return oTrigger;
9925             }
9926         }
9927     }
9928 },
9929
9930 /**
9931  * Returns object literal of values that represent the selection anchor. Used
9932  * to determine selection behavior resulting from a user event.
9933  *
9934  * @method _getSelectionAnchor
9935  * @param oTrigger {Object} (Optional) Object literal of selection trigger values
9936  * (for key events).
9937  * @private
9938  */
9939 _getSelectionAnchor : function(oTrigger) {
9940     var sMode = this.get("selectionMode");
9941     var oAnchor = {};
9942     var oAnchorRecord, nAnchorRecordIndex, nAnchorTrIndex;
9943
9944     // Cell mode
9945     if((sMode == "cellblock") || (sMode == "cellrange") || (sMode == "singlecell")) {
9946         // Validate anchor cell
9947         var oAnchorCell = this._oAnchorCell;
9948         if(!oAnchorCell) {
9949             if(oTrigger) {
9950                 oAnchorCell = this._oAnchorCell = oTrigger.cell;
9951             }
9952             else {
9953                 return null;
9954             }
9955         }
9956         oAnchorRecord = this._oAnchorCell.record;
9957         nAnchorRecordIndex = this._oRecordSet.getRecordIndex(oAnchorRecord);
9958         nAnchorTrIndex = this.getTrIndex(oAnchorRecord);
9959         // If anchor cell is not on this page...
9960         if(nAnchorTrIndex === null) {
9961             // ...set TR index equal to top TR
9962             if(nAnchorRecordIndex < this.getRecordIndex(this.getFirstTrEl())) {
9963                 nAnchorTrIndex = 0;
9964             }
9965             // ...set TR index equal to bottom TR
9966             else {
9967                 nAnchorTrIndex = this.getRecordIndex(this.getLastTrEl());
9968             }
9969         }
9970
9971         oAnchor.record = oAnchorRecord;
9972         oAnchor.recordIndex = nAnchorRecordIndex;
9973         oAnchor.trIndex = nAnchorTrIndex;
9974         oAnchor.column = this._oAnchorCell.column;
9975         oAnchor.colKeyIndex = oAnchor.column.getKeyIndex();
9976         oAnchor.cell = oAnchorCell;
9977         return oAnchor;
9978     }
9979     // Row mode
9980     else {
9981         oAnchorRecord = this._oAnchorRecord;
9982         if(!oAnchorRecord) {
9983             if(oTrigger) {
9984                 oAnchorRecord = this._oAnchorRecord = oTrigger.record;
9985             }
9986             else {
9987                 return null;
9988             }
9989         }
9990
9991         nAnchorRecordIndex = this.getRecordIndex(oAnchorRecord);
9992         nAnchorTrIndex = this.getTrIndex(oAnchorRecord);
9993         // If anchor row is not on this page...
9994         if(nAnchorTrIndex === null) {
9995             // ...set TR index equal to top TR
9996             if(nAnchorRecordIndex < this.getRecordIndex(this.getFirstTrEl())) {
9997                 nAnchorTrIndex = 0;
9998             }
9999             // ...set TR index equal to bottom TR
10000             else {
10001                 nAnchorTrIndex = this.getRecordIndex(this.getLastTrEl());
10002             }
10003         }
10004
10005         oAnchor.record = oAnchorRecord;
10006         oAnchor.recordIndex = nAnchorRecordIndex;
10007         oAnchor.trIndex = nAnchorTrIndex;
10008         return oAnchor;
10009     }
10010 },
10011
10012 /**
10013  * Determines selection behavior resulting from a mouse event when selection mode
10014  * is set to "standard".
10015  *
10016  * @method _handleStandardSelectionByMouse
10017  * @param oArgs.event {HTMLEvent} Event object.
10018  * @param oArgs.target {HTMLElement} Target element.
10019  * @private
10020  */
10021 _handleStandardSelectionByMouse : function(oArgs) {
10022     var elTarget = oArgs.target;
10023
10024     // Validate target row
10025     var elTargetRow = this.getTrEl(elTarget);
10026     if(elTargetRow) {
10027         var e = oArgs.event;
10028         var bSHIFT = e.shiftKey;
10029         var bCTRL = e.ctrlKey || ((navigator.userAgent.toLowerCase().indexOf("mac") != -1) && e.metaKey);
10030
10031         var oTargetRecord = this.getRecord(elTargetRow);
10032         var nTargetRecordIndex = this._oRecordSet.getRecordIndex(oTargetRecord);
10033
10034         var oAnchor = this._getSelectionAnchor();
10035
10036         var i;
10037
10038         // Both SHIFT and CTRL
10039         if(bSHIFT && bCTRL) {
10040             // Validate anchor
10041             if(oAnchor) {
10042                 if(this.isSelected(oAnchor.record)) {
10043                     // Select all rows between anchor row and target row, including target row
10044                     if(oAnchor.recordIndex < nTargetRecordIndex) {
10045                         for(i=oAnchor.recordIndex+1; i<=nTargetRecordIndex; i++) {
10046                             if(!this.isSelected(i)) {
10047                                 this.selectRow(i);
10048                             }
10049                         }
10050                     }
10051                     // Select all rows between target row and anchor row, including target row
10052                     else {
10053                         for(i=oAnchor.recordIndex-1; i>=nTargetRecordIndex; i--) {
10054                             if(!this.isSelected(i)) {
10055                                 this.selectRow(i);
10056                             }
10057                         }
10058                     }
10059                 }
10060                 else {
10061                     // Unselect all rows between anchor row and target row
10062                     if(oAnchor.recordIndex < nTargetRecordIndex) {
10063                         for(i=oAnchor.recordIndex+1; i<=nTargetRecordIndex-1; i++) {
10064                             if(this.isSelected(i)) {
10065                                 this.unselectRow(i);
10066                             }
10067                         }
10068                     }
10069                     // Unselect all rows between target row and anchor row
10070                     else {
10071                         for(i=nTargetRecordIndex+1; i<=oAnchor.recordIndex-1; i++) {
10072                             if(this.isSelected(i)) {
10073                                 this.unselectRow(i);
10074                             }
10075                         }
10076                     }
10077                     // Select the target row
10078                     this.selectRow(oTargetRecord);
10079                 }
10080             }
10081             // Invalid anchor
10082             else {
10083                 // Set anchor
10084                 this._oAnchorRecord = oTargetRecord;
10085
10086                 // Toggle selection of target
10087                 if(this.isSelected(oTargetRecord)) {
10088                     this.unselectRow(oTargetRecord);
10089                 }
10090                 else {
10091                     this.selectRow(oTargetRecord);
10092                 }
10093             }
10094         }
10095          // Only SHIFT
10096         else if(bSHIFT) {
10097             this.unselectAllRows();
10098
10099             // Validate anchor
10100             if(oAnchor) {
10101                 // Select all rows between anchor row and target row,
10102                 // including the anchor row and target row
10103                 if(oAnchor.recordIndex < nTargetRecordIndex) {
10104                     for(i=oAnchor.recordIndex; i<=nTargetRecordIndex; i++) {
10105                         this.selectRow(i);
10106                     }
10107                 }
10108                 // Select all rows between target row and anchor row,
10109                 // including the target row and anchor row
10110                 else {
10111                     for(i=oAnchor.recordIndex; i>=nTargetRecordIndex; i--) {
10112                         this.selectRow(i);
10113                     }
10114                 }
10115             }
10116             // Invalid anchor
10117             else {
10118                 // Set anchor
10119                 this._oAnchorRecord = oTargetRecord;
10120
10121                 // Select target row only
10122                 this.selectRow(oTargetRecord);
10123             }
10124         }
10125         // Only CTRL
10126         else if(bCTRL) {
10127             // Set anchor
10128             this._oAnchorRecord = oTargetRecord;
10129
10130             // Toggle selection of target
10131             if(this.isSelected(oTargetRecord)) {
10132                 this.unselectRow(oTargetRecord);
10133             }
10134             else {
10135                 this.selectRow(oTargetRecord);
10136             }
10137         }
10138         // Neither SHIFT nor CTRL
10139         else {
10140             this._handleSingleSelectionByMouse(oArgs);
10141             return;
10142         }
10143     }
10144 },
10145
10146 /**
10147  * Determines selection behavior resulting from a key event when selection mode
10148  * is set to "standard".
10149  *
10150  * @method _handleStandardSelectionByKey
10151  * @param e {HTMLEvent} Event object.
10152  * @private
10153  */
10154 _handleStandardSelectionByKey : function(e) {
10155     var nKey = Ev.getCharCode(e);
10156
10157     if((nKey == 38) || (nKey == 40)) {
10158         var bSHIFT = e.shiftKey;
10159
10160         // Validate trigger
10161         var oTrigger = this._getSelectionTrigger();
10162         // Arrow selection only works if last selected row is on current page
10163         if(!oTrigger) {
10164             return null;
10165         }
10166
10167         Ev.stopEvent(e);
10168
10169         // Validate anchor
10170         var oAnchor = this._getSelectionAnchor(oTrigger);
10171
10172         // Determine which direction we're going to
10173         if(bSHIFT) {
10174             // Selecting down away from anchor row
10175             if((nKey == 40) && (oAnchor.recordIndex <= oTrigger.trIndex)) {
10176                 this.selectRow(this.getNextTrEl(oTrigger.el));
10177             }
10178             // Selecting up away from anchor row
10179             else if((nKey == 38) && (oAnchor.recordIndex >= oTrigger.trIndex)) {
10180                 this.selectRow(this.getPreviousTrEl(oTrigger.el));
10181             }
10182             // Unselect trigger
10183             else {
10184                 this.unselectRow(oTrigger.el);
10185             }
10186         }
10187         else {
10188             this._handleSingleSelectionByKey(e);
10189         }
10190     }
10191 },
10192
10193 /**
10194  * Determines selection behavior resulting from a mouse event when selection mode
10195  * is set to "single".
10196  *
10197  * @method _handleSingleSelectionByMouse
10198  * @param oArgs.event {HTMLEvent} Event object.
10199  * @param oArgs.target {HTMLElement} Target element.
10200  * @private
10201  */
10202 _handleSingleSelectionByMouse : function(oArgs) {
10203     var elTarget = oArgs.target;
10204
10205     // Validate target row
10206     var elTargetRow = this.getTrEl(elTarget);
10207     if(elTargetRow) {
10208         var oTargetRecord = this.getRecord(elTargetRow);
10209
10210         // Set anchor
10211         this._oAnchorRecord = oTargetRecord;
10212
10213         // Select only target
10214         this.unselectAllRows();
10215         this.selectRow(oTargetRecord);
10216     }
10217 },
10218
10219 /**
10220  * Determines selection behavior resulting from a key event when selection mode
10221  * is set to "single".
10222  *
10223  * @method _handleSingleSelectionByKey
10224  * @param e {HTMLEvent} Event object.
10225  * @private
10226  */
10227 _handleSingleSelectionByKey : function(e) {
10228     var nKey = Ev.getCharCode(e);
10229
10230     if((nKey == 38) || (nKey == 40)) {
10231         // Validate trigger
10232         var oTrigger = this._getSelectionTrigger();
10233         // Arrow selection only works if last selected row is on current page
10234         if(!oTrigger) {
10235             return null;
10236         }
10237
10238         Ev.stopEvent(e);
10239
10240         // Determine the new row to select
10241         var elNew;
10242         if(nKey == 38) { // arrow up
10243             elNew = this.getPreviousTrEl(oTrigger.el);
10244
10245             // Validate new row
10246             if(elNew === null) {
10247                 //TODO: wrap around to last tr on current page
10248                 //elNew = this.getLastTrEl();
10249
10250                 //TODO: wrap back to last tr of previous page
10251
10252                 // Top row selection is sticky
10253                 elNew = this.getFirstTrEl();
10254             }
10255         }
10256         else if(nKey == 40) { // arrow down
10257             elNew = this.getNextTrEl(oTrigger.el);
10258
10259             // Validate new row
10260             if(elNew === null) {
10261                 //TODO: wrap around to first tr on current page
10262                 //elNew = this.getFirstTrEl();
10263
10264                 //TODO: wrap forward to first tr of previous page
10265
10266                 // Bottom row selection is sticky
10267                 elNew = this.getLastTrEl();
10268             }
10269         }
10270
10271         // Unselect all rows
10272         this.unselectAllRows();
10273
10274         // Select the new row
10275         this.selectRow(elNew);
10276
10277         // Set new anchor
10278         this._oAnchorRecord = this.getRecord(elNew);
10279     }
10280 },
10281
10282 /**
10283  * Determines selection behavior resulting from a mouse event when selection mode
10284  * is set to "cellblock".
10285  *
10286  * @method _handleCellBlockSelectionByMouse
10287  * @param oArgs.event {HTMLEvent} Event object.
10288  * @param oArgs.target {HTMLElement} Target element.
10289  * @private
10290  */
10291 _handleCellBlockSelectionByMouse : function(oArgs) {
10292     var elTarget = oArgs.target;
10293
10294     // Validate target cell
10295     var elTargetCell = this.getTdEl(elTarget);
10296     if(elTargetCell) {
10297         var e = oArgs.event;
10298         var bSHIFT = e.shiftKey;
10299         var bCTRL = e.ctrlKey || ((navigator.userAgent.toLowerCase().indexOf("mac") != -1) && e.metaKey);
10300
10301         var elTargetRow = this.getTrEl(elTargetCell);
10302         var nTargetTrIndex = this.getTrIndex(elTargetRow);
10303         var oTargetColumn = this.getColumn(elTargetCell);
10304         var nTargetColKeyIndex = oTargetColumn.getKeyIndex();
10305         var oTargetRecord = this.getRecord(elTargetRow);
10306         var nTargetRecordIndex = this._oRecordSet.getRecordIndex(oTargetRecord);
10307         var oTargetCell = {record:oTargetRecord, column:oTargetColumn};
10308
10309         var oAnchor = this._getSelectionAnchor();
10310
10311         var allRows = this.getTbodyEl().rows;
10312         var startIndex, endIndex, currentRow, i, j;
10313
10314         // Both SHIFT and CTRL
10315         if(bSHIFT && bCTRL) {
10316
10317             // Validate anchor
10318             if(oAnchor) {
10319                 // Anchor is selected
10320                 if(this.isSelected(oAnchor.cell)) {
10321                     // All cells are on the same row
10322                     if(oAnchor.recordIndex === nTargetRecordIndex) {
10323                         // Select all cells between anchor cell and target cell, including target cell
10324                         if(oAnchor.colKeyIndex < nTargetColKeyIndex) {
10325                             for(i=oAnchor.colKeyIndex+1; i<=nTargetColKeyIndex; i++) {
10326                                 this.selectCell(elTargetRow.cells[i]);
10327                             }
10328                         }
10329                         // Select all cells between target cell and anchor cell, including target cell
10330                         else if(nTargetColKeyIndex < oAnchor.colKeyIndex) {
10331                             for(i=nTargetColKeyIndex; i<oAnchor.colKeyIndex; i++) {
10332                                 this.selectCell(elTargetRow.cells[i]);
10333                             }
10334                         }
10335                     }
10336                     // Anchor row is above target row
10337                     else if(oAnchor.recordIndex < nTargetRecordIndex) {
10338                         startIndex = Math.min(oAnchor.colKeyIndex, nTargetColKeyIndex);
10339                         endIndex = Math.max(oAnchor.colKeyIndex, nTargetColKeyIndex);
10340
10341                         // Select all cells from startIndex to endIndex on rows between anchor row and target row
10342                         for(i=oAnchor.trIndex; i<=nTargetTrIndex; i++) {
10343                             for(j=startIndex; j<=endIndex; j++) {
10344                                 this.selectCell(allRows[i].cells[j]);
10345                             }
10346                         }
10347                     }
10348                     // Anchor row is below target row
10349                     else {
10350                         startIndex = Math.min(oAnchor.trIndex, nTargetColKeyIndex);
10351                         endIndex = Math.max(oAnchor.trIndex, nTargetColKeyIndex);
10352
10353                         // Select all cells from startIndex to endIndex on rows between target row and anchor row
10354                         for(i=oAnchor.trIndex; i>=nTargetTrIndex; i--) {
10355                             for(j=endIndex; j>=startIndex; j--) {
10356                                 this.selectCell(allRows[i].cells[j]);
10357                             }
10358                         }
10359                     }
10360                 }
10361                 // Anchor cell is unselected
10362                 else {
10363                     // All cells are on the same row
10364                     if(oAnchor.recordIndex === nTargetRecordIndex) {
10365                         // Unselect all cells between anchor cell and target cell
10366                         if(oAnchor.colKeyIndex < nTargetColKeyIndex) {
10367                             for(i=oAnchor.colKeyIndex+1; i<nTargetColKeyIndex; i++) {
10368                                 this.unselectCell(elTargetRow.cells[i]);
10369                             }
10370                         }
10371                         // Select all cells between target cell and anchor cell
10372                         else if(nTargetColKeyIndex < oAnchor.colKeyIndex) {
10373                             for(i=nTargetColKeyIndex+1; i<oAnchor.colKeyIndex; i++) {
10374                                 this.unselectCell(elTargetRow.cells[i]);
10375                             }
10376                         }
10377                     }
10378                     // Anchor row is above target row
10379                     if(oAnchor.recordIndex < nTargetRecordIndex) {
10380                         // Unselect all cells from anchor cell to target cell
10381                         for(i=oAnchor.trIndex; i<=nTargetTrIndex; i++) {
10382                             currentRow = allRows[i];
10383                             for(j=0; j<currentRow.cells.length; j++) {
10384                                 // This is the anchor row, only unselect cells after the anchor cell
10385                                 if(currentRow.sectionRowIndex === oAnchor.trIndex) {
10386                                     if(j>oAnchor.colKeyIndex) {
10387                                         this.unselectCell(currentRow.cells[j]);
10388                                     }
10389                                 }
10390                                 // This is the target row, only unelect cells before the target cell
10391                                 else if(currentRow.sectionRowIndex === nTargetTrIndex) {
10392                                     if(j<nTargetColKeyIndex) {
10393                                         this.unselectCell(currentRow.cells[j]);
10394                                     }
10395                                 }
10396                                 // Unselect all cells on this row
10397                                 else {
10398                                     this.unselectCell(currentRow.cells[j]);
10399                                 }
10400                             }
10401                         }
10402                     }
10403                     // Anchor row is below target row
10404                     else {
10405                         // Unselect all cells from target cell to anchor cell
10406                         for(i=nTargetTrIndex; i<=oAnchor.trIndex; i++) {
10407                             currentRow = allRows[i];
10408                             for(j=0; j<currentRow.cells.length; j++) {
10409                                 // This is the target row, only unselect cells after the target cell
10410                                 if(currentRow.sectionRowIndex == nTargetTrIndex) {
10411                                     if(j>nTargetColKeyIndex) {
10412                                         this.unselectCell(currentRow.cells[j]);
10413                                     }
10414                                 }
10415                                 // This is the anchor row, only unselect cells before the anchor cell
10416                                 else if(currentRow.sectionRowIndex == oAnchor.trIndex) {
10417                                     if(j<oAnchor.colKeyIndex) {
10418                                         this.unselectCell(currentRow.cells[j]);
10419                                     }
10420                                 }
10421                                 // Unselect all cells on this row
10422                                 else {
10423                                     this.unselectCell(currentRow.cells[j]);
10424                                 }
10425                             }
10426                         }
10427                     }
10428
10429                     // Select the target cell
10430                     this.selectCell(elTargetCell);
10431                 }
10432             }
10433             // Invalid anchor
10434             else {
10435                 // Set anchor
10436                 this._oAnchorCell = oTargetCell;
10437
10438                 // Toggle selection of target
10439                 if(this.isSelected(oTargetCell)) {
10440                     this.unselectCell(oTargetCell);
10441                 }
10442                 else {
10443                     this.selectCell(oTargetCell);
10444                 }
10445             }
10446
10447         }
10448          // Only SHIFT
10449         else if(bSHIFT) {
10450             this.unselectAllCells();
10451
10452             // Validate anchor
10453             if(oAnchor) {
10454                 // All cells are on the same row
10455                 if(oAnchor.recordIndex === nTargetRecordIndex) {
10456                     // Select all cells between anchor cell and target cell,
10457                     // including the anchor cell and target cell
10458                     if(oAnchor.colKeyIndex < nTargetColKeyIndex) {
10459                         for(i=oAnchor.colKeyIndex; i<=nTargetColKeyIndex; i++) {
10460                             this.selectCell(elTargetRow.cells[i]);
10461                         }
10462                     }
10463                     // Select all cells between target cell and anchor cell
10464                     // including the target cell and anchor cell
10465                     else if(nTargetColKeyIndex < oAnchor.colKeyIndex) {
10466                         for(i=nTargetColKeyIndex; i<=oAnchor.colKeyIndex; i++) {
10467                             this.selectCell(elTargetRow.cells[i]);
10468                         }
10469                     }
10470                 }
10471                 // Anchor row is above target row
10472                 else if(oAnchor.recordIndex < nTargetRecordIndex) {
10473                     // Select the cellblock from anchor cell to target cell
10474                     // including the anchor cell and the target cell
10475                     startIndex = Math.min(oAnchor.colKeyIndex, nTargetColKeyIndex);
10476                     endIndex = Math.max(oAnchor.colKeyIndex, nTargetColKeyIndex);
10477
10478                     for(i=oAnchor.trIndex; i<=nTargetTrIndex; i++) {
10479                         for(j=startIndex; j<=endIndex; j++) {
10480                             this.selectCell(allRows[i].cells[j]);
10481                         }
10482                     }
10483                 }
10484                 // Anchor row is below target row
10485                 else {
10486                     // Select the cellblock from target cell to anchor cell
10487                     // including the target cell and the anchor cell
10488                     startIndex = Math.min(oAnchor.colKeyIndex, nTargetColKeyIndex);
10489                     endIndex = Math.max(oAnchor.colKeyIndex, nTargetColKeyIndex);
10490
10491                     for(i=nTargetTrIndex; i<=oAnchor.trIndex; i++) {
10492                         for(j=startIndex; j<=endIndex; j++) {
10493                             this.selectCell(allRows[i].cells[j]);
10494                         }
10495                     }
10496                 }
10497             }
10498             // Invalid anchor
10499             else {
10500                 // Set anchor
10501                 this._oAnchorCell = oTargetCell;
10502
10503                 // Select target only
10504                 this.selectCell(oTargetCell);
10505             }
10506         }
10507         // Only CTRL
10508         else if(bCTRL) {
10509
10510             // Set anchor
10511             this._oAnchorCell = oTargetCell;
10512
10513             // Toggle selection of target
10514             if(this.isSelected(oTargetCell)) {
10515                 this.unselectCell(oTargetCell);
10516             }
10517             else {
10518                 this.selectCell(oTargetCell);
10519             }
10520
10521         }
10522         // Neither SHIFT nor CTRL
10523         else {
10524             this._handleSingleCellSelectionByMouse(oArgs);
10525         }
10526     }
10527 },
10528
10529 /**
10530  * Determines selection behavior resulting from a key event when selection mode
10531  * is set to "cellblock".
10532  *
10533  * @method _handleCellBlockSelectionByKey
10534  * @param e {HTMLEvent} Event object.
10535  * @private
10536  */
10537 _handleCellBlockSelectionByKey : function(e) {
10538     var nKey = Ev.getCharCode(e);
10539     var bSHIFT = e.shiftKey;
10540     if((nKey == 9) || !bSHIFT) {
10541         this._handleSingleCellSelectionByKey(e);
10542         return;
10543     }
10544
10545     if((nKey > 36) && (nKey < 41)) {
10546         // Validate trigger
10547         var oTrigger = this._getSelectionTrigger();
10548         // Arrow selection only works if last selected row is on current page
10549         if(!oTrigger) {
10550             return null;
10551         }
10552
10553         Ev.stopEvent(e);
10554
10555         // Validate anchor
10556         var oAnchor = this._getSelectionAnchor(oTrigger);
10557
10558         var i, startIndex, endIndex, elNew, elNewRow;
10559         var allRows = this.getTbodyEl().rows;
10560         var elThisRow = oTrigger.el.parentNode;
10561
10562         // Determine which direction we're going to
10563
10564         if(nKey == 40) { // arrow down
10565             // Selecting away from anchor cell
10566             if(oAnchor.recordIndex <= oTrigger.recordIndex) {
10567                 // Select the horiz block on the next row...
10568                 // ...making sure there is room below the trigger row
10569                 elNewRow = this.getNextTrEl(oTrigger.el);
10570                 if(elNewRow) {
10571                     startIndex = oAnchor.colKeyIndex;
10572                     endIndex = oTrigger.colKeyIndex;
10573                     // ...going left
10574                     if(startIndex > endIndex) {
10575                         for(i=startIndex; i>=endIndex; i--) {
10576                             elNew = elNewRow.cells[i];
10577                             this.selectCell(elNew);
10578                         }
10579                     }
10580                     // ... going right
10581                     else {
10582                         for(i=startIndex; i<=endIndex; i++) {
10583                             elNew = elNewRow.cells[i];
10584                             this.selectCell(elNew);
10585                         }
10586                     }
10587                 }
10588             }
10589             // Unselecting towards anchor cell
10590             else {
10591                 startIndex = Math.min(oAnchor.colKeyIndex, oTrigger.colKeyIndex);
10592                 endIndex = Math.max(oAnchor.colKeyIndex, oTrigger.colKeyIndex);
10593                 // Unselect the horiz block on this row towards the next row
10594                 for(i=startIndex; i<=endIndex; i++) {
10595                     this.unselectCell(elThisRow.cells[i]);
10596                 }
10597             }
10598         }
10599         // Arrow up
10600         else if(nKey == 38) {
10601             // Selecting away from anchor cell
10602             if(oAnchor.recordIndex >= oTrigger.recordIndex) {
10603                 // Select the horiz block on the previous row...
10604                 // ...making sure there is room
10605                 elNewRow = this.getPreviousTrEl(oTrigger.el);
10606                 if(elNewRow) {
10607                     // Select in order from anchor to trigger...
10608                     startIndex = oAnchor.colKeyIndex;
10609                     endIndex = oTrigger.colKeyIndex;
10610                     // ...going left
10611                     if(startIndex > endIndex) {
10612                         for(i=startIndex; i>=endIndex; i--) {
10613                             elNew = elNewRow.cells[i];
10614                             this.selectCell(elNew);
10615                         }
10616                     }
10617                     // ... going right
10618                     else {
10619                         for(i=startIndex; i<=endIndex; i++) {
10620                             elNew = elNewRow.cells[i];
10621                             this.selectCell(elNew);
10622                         }
10623                     }
10624                 }
10625             }
10626             // Unselecting towards anchor cell
10627             else {
10628                 startIndex = Math.min(oAnchor.colKeyIndex, oTrigger.colKeyIndex);
10629                 endIndex = Math.max(oAnchor.colKeyIndex, oTrigger.colKeyIndex);
10630                 // Unselect the horiz block on this row towards the previous row
10631                 for(i=startIndex; i<=endIndex; i++) {
10632                     this.unselectCell(elThisRow.cells[i]);
10633                 }
10634             }
10635         }
10636         // Arrow right
10637         else if(nKey == 39) {
10638             // Selecting away from anchor cell
10639             if(oAnchor.colKeyIndex <= oTrigger.colKeyIndex) {
10640                 // Select the next vert block to the right...
10641                 // ...making sure there is room
10642                 if(oTrigger.colKeyIndex < elThisRow.cells.length-1) {
10643                     // Select in order from anchor to trigger...
10644                     startIndex = oAnchor.trIndex;
10645                     endIndex = oTrigger.trIndex;
10646                     // ...going up
10647                     if(startIndex > endIndex) {
10648                         for(i=startIndex; i>=endIndex; i--) {
10649                             elNew = allRows[i].cells[oTrigger.colKeyIndex+1];
10650                             this.selectCell(elNew);
10651                         }
10652                     }
10653                     // ... going down
10654                     else {
10655                         for(i=startIndex; i<=endIndex; i++) {
10656                             elNew = allRows[i].cells[oTrigger.colKeyIndex+1];
10657                             this.selectCell(elNew);
10658                         }
10659                     }
10660                 }
10661             }
10662             // Unselecting towards anchor cell
10663             else {
10664                 // Unselect the vert block on this column towards the right
10665                 startIndex = Math.min(oAnchor.trIndex, oTrigger.trIndex);
10666                 endIndex = Math.max(oAnchor.trIndex, oTrigger.trIndex);
10667                 for(i=startIndex; i<=endIndex; i++) {
10668                     this.unselectCell(allRows[i].cells[oTrigger.colKeyIndex]);
10669                 }
10670             }
10671         }
10672         // Arrow left
10673         else if(nKey == 37) {
10674             // Selecting away from anchor cell
10675             if(oAnchor.colKeyIndex >= oTrigger.colKeyIndex) {
10676                 //Select the previous vert block to the left
10677                 if(oTrigger.colKeyIndex > 0) {
10678                     // Select in order from anchor to trigger...
10679                     startIndex = oAnchor.trIndex;
10680                     endIndex = oTrigger.trIndex;
10681                     // ...going up
10682                     if(startIndex > endIndex) {
10683                         for(i=startIndex; i>=endIndex; i--) {
10684                             elNew = allRows[i].cells[oTrigger.colKeyIndex-1];
10685                             this.selectCell(elNew);
10686                         }
10687                     }
10688                     // ... going down
10689                     else {
10690                         for(i=startIndex; i<=endIndex; i++) {
10691                             elNew = allRows[i].cells[oTrigger.colKeyIndex-1];
10692                             this.selectCell(elNew);
10693                         }
10694                     }
10695                 }
10696             }
10697             // Unselecting towards anchor cell
10698             else {
10699                 // Unselect the vert block on this column towards the left
10700                 startIndex = Math.min(oAnchor.trIndex, oTrigger.trIndex);
10701                 endIndex = Math.max(oAnchor.trIndex, oTrigger.trIndex);
10702                 for(i=startIndex; i<=endIndex; i++) {
10703                     this.unselectCell(allRows[i].cells[oTrigger.colKeyIndex]);
10704                 }
10705             }
10706         }
10707     }
10708 },
10709
10710 /**
10711  * Determines selection behavior resulting from a mouse event when selection mode
10712  * is set to "cellrange".
10713  *
10714  * @method _handleCellRangeSelectionByMouse
10715  * @param oArgs.event {HTMLEvent} Event object.
10716  * @param oArgs.target {HTMLElement} Target element.
10717  * @private
10718  */
10719 _handleCellRangeSelectionByMouse : function(oArgs) {
10720     var elTarget = oArgs.target;
10721
10722     // Validate target cell
10723     var elTargetCell = this.getTdEl(elTarget);
10724     if(elTargetCell) {
10725         var e = oArgs.event;
10726         var bSHIFT = e.shiftKey;
10727         var bCTRL = e.ctrlKey || ((navigator.userAgent.toLowerCase().indexOf("mac") != -1) && e.metaKey);
10728
10729         var elTargetRow = this.getTrEl(elTargetCell);
10730         var nTargetTrIndex = this.getTrIndex(elTargetRow);
10731         var oTargetColumn = this.getColumn(elTargetCell);
10732         var nTargetColKeyIndex = oTargetColumn.getKeyIndex();
10733         var oTargetRecord = this.getRecord(elTargetRow);
10734         var nTargetRecordIndex = this._oRecordSet.getRecordIndex(oTargetRecord);
10735         var oTargetCell = {record:oTargetRecord, column:oTargetColumn};
10736
10737         var oAnchor = this._getSelectionAnchor();
10738
10739         var allRows = this.getTbodyEl().rows;
10740         var currentRow, i, j;
10741
10742         // Both SHIFT and CTRL
10743         if(bSHIFT && bCTRL) {
10744
10745             // Validate anchor
10746             if(oAnchor) {
10747                 // Anchor is selected
10748                 if(this.isSelected(oAnchor.cell)) {
10749                     // All cells are on the same row
10750                     if(oAnchor.recordIndex === nTargetRecordIndex) {
10751                         // Select all cells between anchor cell and target cell, including target cell
10752                         if(oAnchor.colKeyIndex < nTargetColKeyIndex) {
10753                             for(i=oAnchor.colKeyIndex+1; i<=nTargetColKeyIndex; i++) {
10754                                 this.selectCell(elTargetRow.cells[i]);
10755                             }
10756                         }
10757                         // Select all cells between target cell and anchor cell, including target cell
10758                         else if(nTargetColKeyIndex < oAnchor.colKeyIndex) {
10759                             for(i=nTargetColKeyIndex; i<oAnchor.colKeyIndex; i++) {
10760                                 this.selectCell(elTargetRow.cells[i]);
10761                             }
10762                         }
10763                     }
10764                     // Anchor row is above target row
10765                     else if(oAnchor.recordIndex < nTargetRecordIndex) {
10766                         // Select all cells on anchor row from anchor cell to the end of the row
10767                         for(i=oAnchor.colKeyIndex+1; i<elTargetRow.cells.length; i++) {
10768                             this.selectCell(elTargetRow.cells[i]);
10769                         }
10770
10771                         // Select all cells on all rows between anchor row and target row
10772                         for(i=oAnchor.trIndex+1; i<nTargetTrIndex; i++) {
10773                             for(j=0; j<allRows[i].cells.length; j++){
10774                                 this.selectCell(allRows[i].cells[j]);
10775                             }
10776                         }
10777
10778                         // Select all cells on target row from first cell to the target cell
10779                         for(i=0; i<=nTargetColKeyIndex; i++) {
10780                             this.selectCell(elTargetRow.cells[i]);
10781                         }
10782                     }
10783                     // Anchor row is below target row
10784                     else {
10785                         // Select all cells on target row from target cell to the end of the row
10786                         for(i=nTargetColKeyIndex; i<elTargetRow.cells.length; i++) {
10787                             this.selectCell(elTargetRow.cells[i]);
10788                         }
10789
10790                         // Select all cells on all rows between target row and anchor row
10791                         for(i=nTargetTrIndex+1; i<oAnchor.trIndex; i++) {
10792                             for(j=0; j<allRows[i].cells.length; j++){
10793                                 this.selectCell(allRows[i].cells[j]);
10794                             }
10795                         }
10796
10797                         // Select all cells on anchor row from first cell to the anchor cell
10798                         for(i=0; i<oAnchor.colKeyIndex; i++) {
10799                             this.selectCell(elTargetRow.cells[i]);
10800                         }
10801                     }
10802                 }
10803                 // Anchor cell is unselected
10804                 else {
10805                     // All cells are on the same row
10806                     if(oAnchor.recordIndex === nTargetRecordIndex) {
10807                         // Unselect all cells between anchor cell and target cell
10808                         if(oAnchor.colKeyIndex < nTargetColKeyIndex) {
10809                             for(i=oAnchor.colKeyIndex+1; i<nTargetColKeyIndex; i++) {
10810                                 this.unselectCell(elTargetRow.cells[i]);
10811                             }
10812                         }
10813                         // Select all cells between target cell and anchor cell
10814                         else if(nTargetColKeyIndex < oAnchor.colKeyIndex) {
10815                             for(i=nTargetColKeyIndex+1; i<oAnchor.colKeyIndex; i++) {
10816                                 this.unselectCell(elTargetRow.cells[i]);
10817                             }
10818                         }
10819                     }
10820                     // Anchor row is above target row
10821                     if(oAnchor.recordIndex < nTargetRecordIndex) {
10822                         // Unselect all cells from anchor cell to target cell
10823                         for(i=oAnchor.trIndex; i<=nTargetTrIndex; i++) {
10824                             currentRow = allRows[i];
10825                             for(j=0; j<currentRow.cells.length; j++) {
10826                                 // This is the anchor row, only unselect cells after the anchor cell
10827                                 if(currentRow.sectionRowIndex === oAnchor.trIndex) {
10828                                     if(j>oAnchor.colKeyIndex) {
10829                                         this.unselectCell(currentRow.cells[j]);
10830                                     }
10831                                 }
10832                                 // This is the target row, only unelect cells before the target cell
10833                                 else if(currentRow.sectionRowIndex === nTargetTrIndex) {
10834                                     if(j<nTargetColKeyIndex) {
10835                                         this.unselectCell(currentRow.cells[j]);
10836                                     }
10837                                 }
10838                                 // Unselect all cells on this row
10839                                 else {
10840                                     this.unselectCell(currentRow.cells[j]);
10841                                 }
10842                             }
10843                         }
10844                     }
10845                     // Anchor row is below target row
10846                     else {
10847                         // Unselect all cells from target cell to anchor cell
10848                         for(i=nTargetTrIndex; i<=oAnchor.trIndex; i++) {
10849                             currentRow = allRows[i];
10850                             for(j=0; j<currentRow.cells.length; j++) {
10851                                 // This is the target row, only unselect cells after the target cell
10852                                 if(currentRow.sectionRowIndex == nTargetTrIndex) {
10853                                     if(j>nTargetColKeyIndex) {
10854                                         this.unselectCell(currentRow.cells[j]);
10855                                     }
10856                                 }
10857                                 // This is the anchor row, only unselect cells before the anchor cell
10858                                 else if(currentRow.sectionRowIndex == oAnchor.trIndex) {
10859                                     if(j<oAnchor.colKeyIndex) {
10860                                         this.unselectCell(currentRow.cells[j]);
10861                                     }
10862                                 }
10863                                 // Unselect all cells on this row
10864                                 else {
10865                                     this.unselectCell(currentRow.cells[j]);
10866                                 }
10867                             }
10868                         }
10869                     }
10870
10871                     // Select the target cell
10872                     this.selectCell(elTargetCell);
10873                 }
10874             }
10875             // Invalid anchor
10876             else {
10877                 // Set anchor
10878                 this._oAnchorCell = oTargetCell;
10879
10880                 // Toggle selection of target
10881                 if(this.isSelected(oTargetCell)) {
10882                     this.unselectCell(oTargetCell);
10883                 }
10884                 else {
10885                     this.selectCell(oTargetCell);
10886                 }
10887             }
10888         }
10889          // Only SHIFT
10890         else if(bSHIFT) {
10891
10892             this.unselectAllCells();
10893
10894             // Validate anchor
10895             if(oAnchor) {
10896                 // All cells are on the same row
10897                 if(oAnchor.recordIndex === nTargetRecordIndex) {
10898                     // Select all cells between anchor cell and target cell,
10899                     // including the anchor cell and target cell
10900                     if(oAnchor.colKeyIndex < nTargetColKeyIndex) {
10901                         for(i=oAnchor.colKeyIndex; i<=nTargetColKeyIndex; i++) {
10902                             this.selectCell(elTargetRow.cells[i]);
10903                         }
10904                     }
10905                     // Select all cells between target cell and anchor cell
10906                     // including the target cell and anchor cell
10907                     else if(nTargetColKeyIndex < oAnchor.colKeyIndex) {
10908                         for(i=nTargetColKeyIndex; i<=oAnchor.colKeyIndex; i++) {
10909                             this.selectCell(elTargetRow.cells[i]);
10910                         }
10911                     }
10912                 }
10913                 // Anchor row is above target row
10914                 else if(oAnchor.recordIndex < nTargetRecordIndex) {
10915                     // Select all cells from anchor cell to target cell
10916                     // including the anchor cell and target cell
10917                     for(i=oAnchor.trIndex; i<=nTargetTrIndex; i++) {
10918                         currentRow = allRows[i];
10919                         for(j=0; j<currentRow.cells.length; j++) {
10920                             // This is the anchor row, only select the anchor cell and after
10921                             if(currentRow.sectionRowIndex == oAnchor.trIndex) {
10922                                 if(j>=oAnchor.colKeyIndex) {
10923                                     this.selectCell(currentRow.cells[j]);
10924                                 }
10925                             }
10926                             // This is the target row, only select the target cell and before
10927                             else if(currentRow.sectionRowIndex == nTargetTrIndex) {
10928                                 if(j<=nTargetColKeyIndex) {
10929                                     this.selectCell(currentRow.cells[j]);
10930                                 }
10931                             }
10932                             // Select all cells on this row
10933                             else {
10934                                 this.selectCell(currentRow.cells[j]);
10935                             }
10936                         }
10937                     }
10938                 }
10939                 // Anchor row is below target row
10940                 else {
10941                     // Select all cells from target cell to anchor cell,
10942                     // including the target cell and anchor cell
10943                     for(i=nTargetTrIndex; i<=oAnchor.trIndex; i++) {
10944                         currentRow = allRows[i];
10945                         for(j=0; j<currentRow.cells.length; j++) {
10946                             // This is the target row, only select the target cell and after
10947                             if(currentRow.sectionRowIndex == nTargetTrIndex) {
10948                                 if(j>=nTargetColKeyIndex) {
10949                                     this.selectCell(currentRow.cells[j]);
10950                                 }
10951                             }
10952                             // This is the anchor row, only select the anchor cell and before
10953                             else if(currentRow.sectionRowIndex == oAnchor.trIndex) {
10954                                 if(j<=oAnchor.colKeyIndex) {
10955                                     this.selectCell(currentRow.cells[j]);
10956                                 }
10957                             }
10958                             // Select all cells on this row
10959                             else {
10960                                 this.selectCell(currentRow.cells[j]);
10961                             }
10962                         }
10963                     }
10964                 }
10965             }
10966             // Invalid anchor
10967             else {
10968                 // Set anchor
10969                 this._oAnchorCell = oTargetCell;
10970
10971                 // Select target only
10972                 this.selectCell(oTargetCell);
10973             }
10974
10975
10976         }
10977         // Only CTRL
10978         else if(bCTRL) {
10979
10980             // Set anchor
10981             this._oAnchorCell = oTargetCell;
10982
10983             // Toggle selection of target
10984             if(this.isSelected(oTargetCell)) {
10985                 this.unselectCell(oTargetCell);
10986             }
10987             else {
10988                 this.selectCell(oTargetCell);
10989             }
10990
10991         }
10992         // Neither SHIFT nor CTRL
10993         else {
10994             this._handleSingleCellSelectionByMouse(oArgs);
10995         }
10996     }
10997 },
10998
10999 /**
11000  * Determines selection behavior resulting from a key event when selection mode
11001  * is set to "cellrange".
11002  *
11003  * @method _handleCellRangeSelectionByKey
11004  * @param e {HTMLEvent} Event object.
11005  * @private
11006  */
11007 _handleCellRangeSelectionByKey : function(e) {
11008     var nKey = Ev.getCharCode(e);
11009     var bSHIFT = e.shiftKey;
11010     if((nKey == 9) || !bSHIFT) {
11011         this._handleSingleCellSelectionByKey(e);
11012         return;
11013     }
11014
11015     if((nKey > 36) && (nKey < 41)) {
11016         // Validate trigger
11017         var oTrigger = this._getSelectionTrigger();
11018         // Arrow selection only works if last selected row is on current page
11019         if(!oTrigger) {
11020             return null;
11021         }
11022
11023         Ev.stopEvent(e);
11024
11025         // Validate anchor
11026         var oAnchor = this._getSelectionAnchor(oTrigger);
11027
11028         var i, elNewRow, elNew;
11029         var allRows = this.getTbodyEl().rows;
11030         var elThisRow = oTrigger.el.parentNode;
11031
11032         // Arrow down
11033         if(nKey == 40) {
11034             elNewRow = this.getNextTrEl(oTrigger.el);
11035
11036             // Selecting away from anchor cell
11037             if(oAnchor.recordIndex <= oTrigger.recordIndex) {
11038                 // Select all cells to the end of this row
11039                 for(i=oTrigger.colKeyIndex+1; i<elThisRow.cells.length; i++){
11040                     elNew = elThisRow.cells[i];
11041                     this.selectCell(elNew);
11042                 }
11043
11044                 // Select some of the cells on the next row down
11045                 if(elNewRow) {
11046                     for(i=0; i<=oTrigger.colKeyIndex; i++){
11047                         elNew = elNewRow.cells[i];
11048                         this.selectCell(elNew);
11049                     }
11050                 }
11051             }
11052             // Unselecting towards anchor cell
11053             else {
11054                 // Unselect all cells to the end of this row
11055                 for(i=oTrigger.colKeyIndex; i<elThisRow.cells.length; i++){
11056                     this.unselectCell(elThisRow.cells[i]);
11057                 }
11058
11059                 // Unselect some of the cells on the next row down
11060                 if(elNewRow) {
11061                     for(i=0; i<oTrigger.colKeyIndex; i++){
11062                         this.unselectCell(elNewRow.cells[i]);
11063                     }
11064                 }
11065             }
11066         }
11067         // Arrow up
11068         else if(nKey == 38) {
11069             elNewRow = this.getPreviousTrEl(oTrigger.el);
11070
11071             // Selecting away from anchor cell
11072             if(oAnchor.recordIndex >= oTrigger.recordIndex) {
11073                 // Select all the cells to the beginning of this row
11074                 for(i=oTrigger.colKeyIndex-1; i>-1; i--){
11075                     elNew = elThisRow.cells[i];
11076                     this.selectCell(elNew);
11077                 }
11078
11079                 // Select some of the cells from the end of the previous row
11080                 if(elNewRow) {
11081                     for(i=elThisRow.cells.length-1; i>=oTrigger.colKeyIndex; i--){
11082                         elNew = elNewRow.cells[i];
11083                         this.selectCell(elNew);
11084                     }
11085                 }
11086             }
11087             // Unselecting towards anchor cell
11088             else {
11089                 // Unselect all the cells to the beginning of this row
11090                 for(i=oTrigger.colKeyIndex; i>-1; i--){
11091                     this.unselectCell(elThisRow.cells[i]);
11092                 }
11093
11094                 // Unselect some of the cells from the end of the previous row
11095                 if(elNewRow) {
11096                     for(i=elThisRow.cells.length-1; i>oTrigger.colKeyIndex; i--){
11097                         this.unselectCell(elNewRow.cells[i]);
11098                     }
11099                 }
11100             }
11101         }
11102         // Arrow right
11103         else if(nKey == 39) {
11104             elNewRow = this.getNextTrEl(oTrigger.el);
11105
11106             // Selecting away from anchor cell
11107             if(oAnchor.recordIndex < oTrigger.recordIndex) {
11108                 // Select the next cell to the right
11109                 if(oTrigger.colKeyIndex < elThisRow.cells.length-1) {
11110                     elNew = elThisRow.cells[oTrigger.colKeyIndex+1];
11111                     this.selectCell(elNew);
11112                 }
11113                 // Select the first cell of the next row
11114                 else if(elNewRow) {
11115                     elNew = elNewRow.cells[0];
11116                     this.selectCell(elNew);
11117                 }
11118             }
11119             // Unselecting towards anchor cell
11120             else if(oAnchor.recordIndex > oTrigger.recordIndex) {
11121                 this.unselectCell(elThisRow.cells[oTrigger.colKeyIndex]);
11122
11123                 // Unselect this cell towards the right
11124                 if(oTrigger.colKeyIndex < elThisRow.cells.length-1) {
11125                 }
11126                 // Unselect this cells towards the first cell of the next row
11127                 else {
11128                 }
11129             }
11130             // Anchor is on this row
11131             else {
11132                 // Selecting away from anchor
11133                 if(oAnchor.colKeyIndex <= oTrigger.colKeyIndex) {
11134                     // Select the next cell to the right
11135                     if(oTrigger.colKeyIndex < elThisRow.cells.length-1) {
11136                         elNew = elThisRow.cells[oTrigger.colKeyIndex+1];
11137                         this.selectCell(elNew);
11138                     }
11139                     // Select the first cell on the next row
11140                     else if(oTrigger.trIndex < allRows.length-1){
11141                         elNew = elNewRow.cells[0];
11142                         this.selectCell(elNew);
11143                     }
11144                 }
11145                 // Unselecting towards anchor
11146                 else {
11147                     // Unselect this cell towards the right
11148                     this.unselectCell(elThisRow.cells[oTrigger.colKeyIndex]);
11149                 }
11150             }
11151         }
11152         // Arrow left
11153         else if(nKey == 37) {
11154             elNewRow = this.getPreviousTrEl(oTrigger.el);
11155
11156             // Unselecting towards the anchor
11157             if(oAnchor.recordIndex < oTrigger.recordIndex) {
11158                 this.unselectCell(elThisRow.cells[oTrigger.colKeyIndex]);
11159
11160                 // Unselect this cell towards the left
11161                 if(oTrigger.colKeyIndex > 0) {
11162                 }
11163                 // Unselect this cell towards the last cell of the previous row
11164                 else {
11165                 }
11166             }
11167             // Selecting towards the anchor
11168             else if(oAnchor.recordIndex > oTrigger.recordIndex) {
11169                 // Select the next cell to the left
11170                 if(oTrigger.colKeyIndex > 0) {
11171                     elNew = elThisRow.cells[oTrigger.colKeyIndex-1];
11172                     this.selectCell(elNew);
11173                 }
11174                 // Select the last cell of the previous row
11175                 else if(oTrigger.trIndex > 0){
11176                     elNew = elNewRow.cells[elNewRow.cells.length-1];
11177                     this.selectCell(elNew);
11178                 }
11179             }
11180             // Anchor is on this row
11181             else {
11182                 // Selecting away from anchor cell
11183                 if(oAnchor.colKeyIndex >= oTrigger.colKeyIndex) {
11184                     // Select the next cell to the left
11185                     if(oTrigger.colKeyIndex > 0) {
11186                         elNew = elThisRow.cells[oTrigger.colKeyIndex-1];
11187                         this.selectCell(elNew);
11188                     }
11189                     // Select the last cell of the previous row
11190                     else if(oTrigger.trIndex > 0){
11191                         elNew = elNewRow.cells[elNewRow.cells.length-1];
11192                         this.selectCell(elNew);
11193                     }
11194                 }
11195                 // Unselecting towards anchor cell
11196                 else {
11197                     this.unselectCell(elThisRow.cells[oTrigger.colKeyIndex]);
11198
11199                     // Unselect this cell towards the left
11200                     if(oTrigger.colKeyIndex > 0) {
11201                     }
11202                     // Unselect this cell towards the last cell of the previous row
11203                     else {
11204                     }
11205                 }
11206             }
11207         }
11208     }
11209 },
11210
11211 /**
11212  * Determines selection behavior resulting from a mouse event when selection mode
11213  * is set to "singlecell".
11214  *
11215  * @method _handleSingleCellSelectionByMouse
11216  * @param oArgs.event {HTMLEvent} Event object.
11217  * @param oArgs.target {HTMLElement} Target element.
11218  * @private
11219  */
11220 _handleSingleCellSelectionByMouse : function(oArgs) {
11221     var elTarget = oArgs.target;
11222
11223     // Validate target cell
11224     var elTargetCell = this.getTdEl(elTarget);
11225     if(elTargetCell) {
11226         var elTargetRow = this.getTrEl(elTargetCell);
11227         var oTargetRecord = this.getRecord(elTargetRow);
11228         var oTargetColumn = this.getColumn(elTargetCell);
11229         var oTargetCell = {record:oTargetRecord, column:oTargetColumn};
11230
11231         // Set anchor
11232         this._oAnchorCell = oTargetCell;
11233
11234         // Select only target
11235         this.unselectAllCells();
11236         this.selectCell(oTargetCell);
11237     }
11238 },
11239
11240 /**
11241  * Determines selection behavior resulting from a key event when selection mode
11242  * is set to "singlecell".
11243  *
11244  * @method _handleSingleCellSelectionByKey
11245  * @param e {HTMLEvent} Event object.
11246  * @private
11247  */
11248 _handleSingleCellSelectionByKey : function(e) {
11249     var nKey = Ev.getCharCode(e);
11250     if((nKey == 9) || ((nKey > 36) && (nKey < 41))) {
11251         var bSHIFT = e.shiftKey;
11252
11253         // Validate trigger
11254         var oTrigger = this._getSelectionTrigger();
11255         // Arrow selection only works if last selected row is on current page
11256         if(!oTrigger) {
11257             return null;
11258         }
11259
11260         // Determine the new cell to select
11261         var elNew;
11262         if(nKey == 40) { // Arrow down
11263             elNew = this.getBelowTdEl(oTrigger.el);
11264
11265             // Validate new cell
11266             if(elNew === null) {
11267                 //TODO: wrap around to first tr on current page
11268
11269                 //TODO: wrap forward to first tr of next page
11270
11271                 // Bottom selection is sticky
11272                 elNew = oTrigger.el;
11273             }
11274         }
11275         else if(nKey == 38) { // Arrow up
11276             elNew = this.getAboveTdEl(oTrigger.el);
11277
11278             // Validate new cell
11279             if(elNew === null) {
11280                 //TODO: wrap around to last tr on current page
11281
11282                 //TODO: wrap back to last tr of previous page
11283
11284                 // Top selection is sticky
11285                 elNew = oTrigger.el;
11286             }
11287         }
11288         else if((nKey == 39) || (!bSHIFT && (nKey == 9))) { // Arrow right or tab
11289             elNew = this.getNextTdEl(oTrigger.el);
11290
11291             // Validate new cell
11292             if(elNew === null) {
11293                 //TODO: wrap around to first td on current page
11294
11295                 //TODO: wrap forward to first td of next page
11296
11297                 // Top-left selection is sticky, and release TAB focus
11298                 //elNew = oTrigger.el;
11299                 return;
11300             }
11301         }
11302         else if((nKey == 37) || (bSHIFT && (nKey == 9))) { // Arrow left or shift-tab
11303             elNew = this.getPreviousTdEl(oTrigger.el);
11304
11305             // Validate new cell
11306             if(elNew === null) {
11307                 //TODO: wrap around to last td on current page
11308
11309                 //TODO: wrap back to last td of previous page
11310
11311                 // Bottom-right selection is sticky, and release TAB focus
11312                 //elNew = oTrigger.el;
11313                 return;
11314             }
11315         }
11316
11317         Ev.stopEvent(e);
11318         
11319         // Unselect all cells
11320         this.unselectAllCells();
11321
11322         // Select the new cell
11323         this.selectCell(elNew);
11324
11325         // Set new anchor
11326         this._oAnchorCell = {record:this.getRecord(elNew), column:this.getColumn(elNew)};
11327     }
11328 },
11329
11330 /**
11331  * Returns array of selected TR elements on the page.
11332  *
11333  * @method getSelectedTrEls
11334  * @return {HTMLElement[]} Array of selected TR elements.
11335  */
11336 getSelectedTrEls : function() {
11337     return Dom.getElementsByClassName(DT.CLASS_SELECTED,"tr",this._elTbody);
11338 },
11339
11340 /**
11341  * Sets given row to the selected state.
11342  *
11343  * @method selectRow
11344  * @param row {HTMLElement | String | YAHOO.widget.Record | Number} HTML element
11345  * reference or ID string, Record instance, or RecordSet position index.
11346  */
11347 selectRow : function(row) {
11348     var oRecord, elRow;
11349
11350     if(row instanceof YAHOO.widget.Record) {
11351         oRecord = this._oRecordSet.getRecord(row);
11352         elRow = this.getTrEl(oRecord);
11353     }
11354     else if(lang.isNumber(row)) {
11355         oRecord = this.getRecord(row);
11356         elRow = this.getTrEl(oRecord);
11357     }
11358     else {
11359         elRow = this.getTrEl(row);
11360         oRecord = this.getRecord(elRow);
11361     }
11362
11363     if(oRecord) {
11364         // Update selection trackers
11365         var tracker = this._aSelections || [];
11366         var sRecordId = oRecord.getId();
11367         var index = -1;
11368
11369         // Remove if already there:
11370         // Use Array.indexOf if available...
11371         /*if(tracker.indexOf && (tracker.indexOf(sRecordId) >  -1)) {
11372             tracker.splice(tracker.indexOf(sRecordId),1);
11373         }*/
11374         if(tracker.indexOf) {
11375             index = tracker.indexOf(sRecordId);
11376             
11377         }
11378         // ...or do it the old-fashioned way
11379         else {
11380             for(var j=tracker.length-1; j>-1; j--) {
11381                 if(tracker[j] === sRecordId){
11382                     index = j;
11383                     break;
11384                 }
11385             }
11386         }
11387         if(index > -1) {
11388             tracker.splice(index,1);
11389         }
11390         
11391         // Add to the end
11392         tracker.push(sRecordId);
11393         this._aSelections = tracker;
11394
11395         // Update trackers
11396         if(!this._oAnchorRecord) {
11397             this._oAnchorRecord = oRecord;
11398         }
11399
11400         // Update UI
11401         if(elRow) {
11402             Dom.addClass(elRow, DT.CLASS_SELECTED);
11403         }
11404
11405         this.fireEvent("rowSelectEvent", {record:oRecord, el:elRow});
11406     }
11407     else {
11408     }
11409 },
11410
11411 /**
11412  * Sets given row to the unselected state.
11413  *
11414  * @method unselectRow
11415  * @param row {HTMLElement | String | YAHOO.widget.Record | Number} HTML element
11416  * reference or ID string, Record instance, or RecordSet position index.
11417  */
11418 unselectRow : function(row) {
11419     var elRow = this.getTrEl(row);
11420
11421     var oRecord;
11422     if(row instanceof YAHOO.widget.Record) {
11423         oRecord = this._oRecordSet.getRecord(row);
11424     }
11425     else if(lang.isNumber(row)) {
11426         oRecord = this.getRecord(row);
11427     }
11428     else {
11429         oRecord = this.getRecord(elRow);
11430     }
11431
11432     if(oRecord) {
11433         // Update selection trackers
11434         var tracker = this._aSelections || [];
11435         var sRecordId = oRecord.getId();
11436         var index = -1;
11437
11438         // Use Array.indexOf if available...
11439         if(tracker.indexOf) {
11440             index = tracker.indexOf(sRecordId);
11441         }
11442         // ...or do it the old-fashioned way
11443         else {
11444             for(var j=tracker.length-1; j>-1; j--) {
11445                 if(tracker[j] === sRecordId){
11446                     index = j;
11447                     break;
11448                 }
11449             }
11450         }
11451         if(index > -1) {
11452             // Update tracker
11453             tracker.splice(index,1);
11454             this._aSelections = tracker;
11455
11456             // Update the UI
11457             Dom.removeClass(elRow, DT.CLASS_SELECTED);
11458
11459             this.fireEvent("rowUnselectEvent", {record:oRecord, el:elRow});
11460
11461             return;
11462         }
11463     }
11464 },
11465
11466 /**
11467  * Clears out all row selections.
11468  *
11469  * @method unselectAllRows
11470  */
11471 unselectAllRows : function() {
11472     // Remove all rows from tracker
11473     var tracker = this._aSelections || [],
11474         recId,
11475         removed = [];
11476     for(var j=tracker.length-1; j>-1; j--) {
11477        if(lang.isString(tracker[j])){
11478             recId = tracker.splice(j,1);
11479             removed[removed.length] = this.getRecord(lang.isArray(recId) ? recId[0] : recId);
11480         }
11481     }
11482
11483     // Update tracker
11484     this._aSelections = tracker;
11485
11486     // Update UI
11487     this._unselectAllTrEls();
11488
11489     this.fireEvent("unselectAllRowsEvent", {records: removed});
11490 },
11491
11492 /**
11493  * Convenience method to remove the class YAHOO.widget.DataTable.CLASS_SELECTED
11494  * from all TD elements in the internal tracker.
11495  *
11496  * @method _unselectAllTdEls
11497  * @private
11498  */
11499 _unselectAllTdEls : function() {
11500     var selectedCells = Dom.getElementsByClassName(DT.CLASS_SELECTED,"td",this._elTbody);
11501     Dom.removeClass(selectedCells, DT.CLASS_SELECTED);
11502 },
11503
11504 /**
11505  * Returns array of selected TD elements on the page.
11506  *
11507  * @method getSelectedTdEls
11508  * @return {HTMLElement[]} Array of selected TD elements.
11509  */
11510 getSelectedTdEls : function() {
11511     return Dom.getElementsByClassName(DT.CLASS_SELECTED,"td",this._elTbody);
11512 },
11513
11514 /**
11515  * Sets given cell to the selected state.
11516  *
11517  * @method selectCell
11518  * @param cell {HTMLElement | String} DOM element reference or ID string
11519  * to DataTable page element or RecordSet index.
11520  */
11521 selectCell : function(cell) {
11522 //TODO: accept {record} in selectCell()
11523     var elCell = this.getTdEl(cell);
11524
11525     if(elCell) {
11526         var oRecord = this.getRecord(elCell);
11527         var sColumnKey = this.getColumn(elCell.cellIndex).getKey();
11528
11529         if(oRecord && sColumnKey) {
11530             // Get Record ID
11531             var tracker = this._aSelections || [];
11532             var sRecordId = oRecord.getId();
11533
11534             // Remove if there
11535             for(var j=tracker.length-1; j>-1; j--) {
11536                if((tracker[j].recordId === sRecordId) && (tracker[j].columnKey === sColumnKey)){
11537                     tracker.splice(j,1);
11538                     break;
11539                 }
11540             }
11541
11542             // Add to the end
11543             tracker.push({recordId:sRecordId, columnKey:sColumnKey});
11544
11545             // Update trackers
11546             this._aSelections = tracker;
11547             if(!this._oAnchorCell) {
11548                 this._oAnchorCell = {record:oRecord, column:this.getColumn(sColumnKey)};
11549             }
11550
11551             // Update the UI
11552             Dom.addClass(elCell, DT.CLASS_SELECTED);
11553
11554             this.fireEvent("cellSelectEvent", {record:oRecord, column:this.getColumn(elCell.cellIndex), key: this.getColumn(elCell.cellIndex).getKey(), el:elCell});
11555             return;
11556         }
11557     }
11558 },
11559
11560 /**
11561  * Sets given cell to the unselected state.
11562  *
11563  * @method unselectCell
11564  * @param cell {HTMLElement | String} DOM element reference or ID string
11565  * to DataTable page element or RecordSet index.
11566  */
11567 unselectCell : function(cell) {
11568     var elCell = this.getTdEl(cell);
11569
11570     if(elCell) {
11571         var oRecord = this.getRecord(elCell);
11572         var sColumnKey = this.getColumn(elCell.cellIndex).getKey();
11573
11574         if(oRecord && sColumnKey) {
11575             // Get Record ID
11576             var tracker = this._aSelections || [];
11577             var id = oRecord.getId();
11578
11579             // Is it selected?
11580             for(var j=tracker.length-1; j>-1; j--) {
11581                 if((tracker[j].recordId === id) && (tracker[j].columnKey === sColumnKey)){
11582                     // Remove from tracker
11583                     tracker.splice(j,1);
11584
11585                     // Update tracker
11586                     this._aSelections = tracker;
11587
11588                     // Update the UI
11589                     Dom.removeClass(elCell, DT.CLASS_SELECTED);
11590
11591                     this.fireEvent("cellUnselectEvent", {record:oRecord, column: this.getColumn(elCell.cellIndex), key:this.getColumn(elCell.cellIndex).getKey(), el:elCell});
11592                     return;
11593                 }
11594             }
11595         }
11596     }
11597 },
11598
11599 /**
11600  * Clears out all cell selections.
11601  *
11602  * @method unselectAllCells
11603  */
11604 unselectAllCells : function() {
11605     // Remove all cells from tracker
11606     var tracker = this._aSelections || [];
11607     for(var j=tracker.length-1; j>-1; j--) {
11608        if(lang.isObject(tracker[j])){
11609             tracker.splice(j,1);
11610         }
11611     }
11612
11613     // Update tracker
11614     this._aSelections = tracker;
11615
11616     // Update UI
11617     this._unselectAllTdEls();
11618
11619     //TODO: send data to unselectAllCellsEvent handler
11620     this.fireEvent("unselectAllCellsEvent");
11621 },
11622
11623 /**
11624  * Returns true if given item is selected, false otherwise.
11625  *
11626  * @method isSelected
11627  * @param o {String | HTMLElement | YAHOO.widget.Record | Number
11628  * {record:YAHOO.widget.Record, column:YAHOO.widget.Column} } TR or TD element by
11629  * reference or ID string, a Record instance, a RecordSet position index,
11630  * or an object literal representation
11631  * of a cell.
11632  * @return {Boolean} True if item is selected.
11633  */
11634 isSelected : function(o) {
11635     if(o && (o.ownerDocument == document)) {
11636         return (Dom.hasClass(this.getTdEl(o),DT.CLASS_SELECTED) || Dom.hasClass(this.getTrEl(o),DT.CLASS_SELECTED));
11637     }
11638     else {
11639         var oRecord, sRecordId, j;
11640         var tracker = this._aSelections;
11641         if(tracker && tracker.length > 0) {
11642             // Looking for a Record?
11643             if(o instanceof YAHOO.widget.Record) {
11644                 oRecord = o;
11645             }
11646             else if(lang.isNumber(o)) {
11647                 oRecord = this.getRecord(o);
11648             }
11649             if(oRecord) {
11650                 sRecordId = oRecord.getId();
11651
11652                 // Is it there?
11653                 // Use Array.indexOf if available...
11654                 if(tracker.indexOf) {
11655                     if(tracker.indexOf(sRecordId) >  -1) {
11656                         return true;
11657                     }
11658                 }
11659                 // ...or do it the old-fashioned way
11660                 else {
11661                     for(j=tracker.length-1; j>-1; j--) {
11662                        if(tracker[j] === sRecordId){
11663                         return true;
11664                        }
11665                     }
11666                 }
11667             }
11668             // Looking for a cell
11669             else if(o.record && o.column){
11670                 sRecordId = o.record.getId();
11671                 var sColumnKey = o.column.getKey();
11672
11673                 for(j=tracker.length-1; j>-1; j--) {
11674                     if((tracker[j].recordId === sRecordId) && (tracker[j].columnKey === sColumnKey)){
11675                         return true;
11676                     }
11677                 }
11678             }
11679         }
11680     }
11681     return false;
11682 },
11683
11684 /**
11685  * Returns selected rows as an array of Record IDs.
11686  *
11687  * @method getSelectedRows
11688  * @return {String[]} Array of selected rows by Record ID.
11689  */
11690 getSelectedRows : function() {
11691     var aSelectedRows = [];
11692     var tracker = this._aSelections || [];
11693     for(var j=0; j<tracker.length; j++) {
11694        if(lang.isString(tracker[j])){
11695             aSelectedRows.push(tracker[j]);
11696         }
11697     }
11698     return aSelectedRows;
11699 },
11700
11701 /**
11702  * Returns selected cells as an array of object literals:
11703  *     {recordId:sRecordId, columnKey:sColumnKey}.
11704  *
11705  * @method getSelectedCells
11706  * @return {Object[]} Array of selected cells by Record ID and Column ID.
11707  */
11708 getSelectedCells : function() {
11709     var aSelectedCells = [];
11710     var tracker = this._aSelections || [];
11711     for(var j=0; j<tracker.length; j++) {
11712        if(tracker[j] && lang.isObject(tracker[j])){
11713             aSelectedCells.push(tracker[j]);
11714         }
11715     }
11716     return aSelectedCells;
11717 },
11718
11719 /**
11720  * Returns last selected Record ID.
11721  *
11722  * @method getLastSelectedRecord
11723  * @return {String} Record ID of last selected row.
11724  */
11725 getLastSelectedRecord : function() {
11726     var tracker = this._aSelections;
11727     if(tracker && tracker.length > 0) {
11728         for(var i=tracker.length-1; i>-1; i--) {
11729            if(lang.isString(tracker[i])){
11730                 return tracker[i];
11731             }
11732         }
11733     }
11734 },
11735
11736 /**
11737  * Returns last selected cell as an object literal:
11738  *     {recordId:sRecordId, columnKey:sColumnKey}.
11739  *
11740  * @method getLastSelectedCell
11741  * @return {Object} Object literal representation of a cell.
11742  */
11743 getLastSelectedCell : function() {
11744     var tracker = this._aSelections;
11745     if(tracker && tracker.length > 0) {
11746         for(var i=tracker.length-1; i>-1; i--) {
11747            if(tracker[i].recordId && tracker[i].columnKey){
11748                 return tracker[i];
11749             }
11750         }
11751     }
11752 },
11753
11754 /**
11755  * Assigns the class YAHOO.widget.DataTable.CLASS_HIGHLIGHTED to the given row.
11756  *
11757  * @method highlightRow
11758  * @param row {HTMLElement | String} DOM element reference or ID string.
11759  */
11760 highlightRow : function(row) {
11761     var elRow = this.getTrEl(row);
11762
11763     if(elRow) {
11764         // Make sure previous row is unhighlighted
11765 /*        if(this._sLastHighlightedTrElId) {
11766             Dom.removeClass(this._sLastHighlightedTrElId,DT.CLASS_HIGHLIGHTED);
11767         }*/
11768         var oRecord = this.getRecord(elRow);
11769         Dom.addClass(elRow,DT.CLASS_HIGHLIGHTED);
11770         //this._sLastHighlightedTrElId = elRow.id;
11771         this.fireEvent("rowHighlightEvent", {record:oRecord, el:elRow});
11772         return;
11773     }
11774 },
11775
11776 /**
11777  * Removes the class YAHOO.widget.DataTable.CLASS_HIGHLIGHTED from the given row.
11778  *
11779  * @method unhighlightRow
11780  * @param row {HTMLElement | String} DOM element reference or ID string.
11781  */
11782 unhighlightRow : function(row) {
11783     var elRow = this.getTrEl(row);
11784
11785     if(elRow) {
11786         var oRecord = this.getRecord(elRow);
11787         Dom.removeClass(elRow,DT.CLASS_HIGHLIGHTED);
11788         this.fireEvent("rowUnhighlightEvent", {record:oRecord, el:elRow});
11789         return;
11790     }
11791 },
11792
11793 /**
11794  * Assigns the class YAHOO.widget.DataTable.CLASS_HIGHLIGHTED to the given cell.
11795  *
11796  * @method highlightCell
11797  * @param cell {HTMLElement | String} DOM element reference or ID string.
11798  */
11799 highlightCell : function(cell) {
11800     var elCell = this.getTdEl(cell);
11801
11802     if(elCell) {
11803         // Make sure previous cell is unhighlighted
11804         if(this._elLastHighlightedTd) {
11805             this.unhighlightCell(this._elLastHighlightedTd);
11806         }
11807
11808         var oRecord = this.getRecord(elCell);
11809         var sColumnKey = this.getColumn(elCell.cellIndex).getKey();
11810         Dom.addClass(elCell,DT.CLASS_HIGHLIGHTED);
11811         this._elLastHighlightedTd = elCell;
11812         this.fireEvent("cellHighlightEvent", {record:oRecord, column:this.getColumn(elCell.cellIndex), key:this.getColumn(elCell.cellIndex).getKey(), el:elCell});
11813         return;
11814     }
11815 },
11816
11817 /**
11818  * Removes the class YAHOO.widget.DataTable.CLASS_HIGHLIGHTED from the given cell.
11819  *
11820  * @method unhighlightCell
11821  * @param cell {HTMLElement | String} DOM element reference or ID string.
11822  */
11823 unhighlightCell : function(cell) {
11824     var elCell = this.getTdEl(cell);
11825
11826     if(elCell) {
11827         var oRecord = this.getRecord(elCell);
11828         Dom.removeClass(elCell,DT.CLASS_HIGHLIGHTED);
11829         this._elLastHighlightedTd = null;
11830         this.fireEvent("cellUnhighlightEvent", {record:oRecord, column:this.getColumn(elCell.cellIndex), key:this.getColumn(elCell.cellIndex).getKey(), el:elCell});
11831         return;
11832     }
11833 },
11834
11835
11836
11837
11838
11839
11840
11841
11842
11843
11844
11845
11846
11847
11848
11849
11850
11851
11852
11853
11854
11855
11856
11857
11858
11859
11860
11861
11862
11863
11864
11865
11866
11867
11868
11869
11870
11871
11872
11873
11874
11875
11876
11877
11878
11879 // INLINE EDITING
11880
11881 /**
11882  * Returns current CellEditor instance, or null.
11883  * @method getCellEditor
11884  * @return {YAHOO.widget.CellEditor} CellEditor instance.
11885  */
11886 getCellEditor : function() {
11887     return this._oCellEditor;
11888 },
11889
11890
11891 /**
11892  * Activates and shows CellEditor instance for the given cell while deactivating and
11893  * canceling previous CellEditor. It is baked into DataTable that only one CellEditor
11894  * can be active at any given time. 
11895  *
11896  * @method showCellEditor
11897  * @param elCell {HTMLElement | String} Cell to edit.
11898  */
11899 showCellEditor : function(elCell, oRecord, oColumn) {
11900     // Get a particular CellEditor
11901     elCell = this.getTdEl(elCell);
11902     if(elCell) {
11903         oColumn = this.getColumn(elCell);
11904         if(oColumn && oColumn.editor) {
11905             var oCellEditor = this._oCellEditor;
11906             // Clean up active CellEditor
11907             if(oCellEditor) {
11908                 if(this._oCellEditor.cancel) {
11909                     this._oCellEditor.cancel();
11910                 }
11911                 else if(oCellEditor.isActive) {
11912                     this.cancelCellEditor();
11913                 }
11914             }
11915             
11916             if(oColumn.editor instanceof YAHOO.widget.BaseCellEditor) {
11917                 // Get CellEditor
11918                 oCellEditor = oColumn.editor;
11919                 var ok = oCellEditor.attach(this, elCell);
11920                 if(ok) {
11921                     oCellEditor.move();
11922                     ok = this.doBeforeShowCellEditor(oCellEditor);
11923                     if(ok) {
11924                         oCellEditor.show();
11925                         this._oCellEditor = oCellEditor;
11926                     }
11927                 }
11928             }
11929             // Backward compatibility
11930             else {
11931                     if(!oRecord || !(oRecord instanceof YAHOO.widget.Record)) {
11932                         oRecord = this.getRecord(elCell);
11933                     }
11934                     if(!oColumn || !(oColumn instanceof YAHOO.widget.Column)) {
11935                         oColumn = this.getColumn(elCell);
11936                     }
11937                     if(oRecord && oColumn) {
11938                         if(!this._oCellEditor || this._oCellEditor.container) {
11939                             this._initCellEditorEl();
11940                         }
11941                         
11942                         // Update Editor values
11943                         oCellEditor = this._oCellEditor;
11944                         oCellEditor.cell = elCell;
11945                         oCellEditor.record = oRecord;
11946                         oCellEditor.column = oColumn;
11947                         oCellEditor.validator = (oColumn.editorOptions &&
11948                                 lang.isFunction(oColumn.editorOptions.validator)) ?
11949                                 oColumn.editorOptions.validator : null;
11950                         oCellEditor.value = oRecord.getData(oColumn.key);
11951                         oCellEditor.defaultValue = null;
11952             
11953                         // Move Editor
11954                         var elContainer = oCellEditor.container;
11955                         var x = Dom.getX(elCell);
11956                         var y = Dom.getY(elCell);
11957             
11958                         // SF doesn't get xy for cells in scrolling table
11959                         // when tbody display is set to block
11960                         if(isNaN(x) || isNaN(y)) {
11961                             x = elCell.offsetLeft + // cell pos relative to table
11962                                     Dom.getX(this._elTbody.parentNode) - // plus table pos relative to document
11963                                     this._elTbody.scrollLeft; // minus tbody scroll
11964                             y = elCell.offsetTop + // cell pos relative to table
11965                                     Dom.getY(this._elTbody.parentNode) - // plus table pos relative to document
11966                                     this._elTbody.scrollTop + // minus tbody scroll
11967                                     this._elThead.offsetHeight; // account for fixed THEAD cells
11968                         }
11969             
11970                         elContainer.style.left = x + "px";
11971                         elContainer.style.top = y + "px";
11972             
11973                         // Hook to customize the UI
11974                         this.doBeforeShowCellEditor(this._oCellEditor);
11975             
11976                         //TODO: This is temporarily up here due so elements can be focused
11977                         // Show Editor
11978                         elContainer.style.display = "";
11979             
11980                         // Handle ESC key
11981                         Ev.addListener(elContainer, "keydown", function(e, oSelf) {
11982                             // ESC hides Cell Editor
11983                             if((e.keyCode == 27)) {
11984                                 oSelf.cancelCellEditor();
11985                                 oSelf.focusTbodyEl();
11986                             }
11987                             else {
11988                                 oSelf.fireEvent("editorKeydownEvent", {editor:oSelf._oCellEditor, event:e});
11989                             }
11990                         }, this);
11991             
11992                         // Render Editor markup
11993                         var fnEditor;
11994                         if(lang.isString(oColumn.editor)) {
11995                             switch(oColumn.editor) {
11996                                 case "checkbox":
11997                                     fnEditor = DT.editCheckbox;
11998                                     break;
11999                                 case "date":
12000                                     fnEditor = DT.editDate;
12001                                     break;
12002                                 case "dropdown":
12003                                     fnEditor = DT.editDropdown;
12004                                     break;
12005                                 case "radio":
12006                                     fnEditor = DT.editRadio;
12007                                     break;
12008                                 case "textarea":
12009                                     fnEditor = DT.editTextarea;
12010                                     break;
12011                                 case "textbox":
12012                                     fnEditor = DT.editTextbox;
12013                                     break;
12014                                 default:
12015                                     fnEditor = null;
12016                             }
12017                         }
12018                         else if(lang.isFunction(oColumn.editor)) {
12019                             fnEditor = oColumn.editor;
12020                         }
12021             
12022                         if(fnEditor) {
12023                             // Create DOM input elements
12024                             fnEditor(this._oCellEditor, this);
12025             
12026                             // Show Save/Cancel buttons
12027                             if(!oColumn.editorOptions || !oColumn.editorOptions.disableBtns) {
12028                                 this.showCellEditorBtns(elContainer);
12029                             }
12030             
12031                             oCellEditor.isActive = true;
12032             
12033                             //TODO: verify which args to pass
12034                             this.fireEvent("editorShowEvent", {editor:oCellEditor});
12035                             return;
12036                         }
12037                     }
12038
12039
12040
12041             
12042             }
12043         }
12044     }
12045 },
12046
12047 /**
12048  * Backward compatibility.
12049  *
12050  * @method _initCellEditorEl
12051  * @private
12052  * @deprecated 
12053  */
12054 _initCellEditorEl : function() {
12055     // Attach Cell Editor container element as first child of body
12056     var elCellEditor = document.createElement("div");
12057     elCellEditor.id = this._sId + "-celleditor";
12058     elCellEditor.style.display = "none";
12059     elCellEditor.tabIndex = 0;
12060     Dom.addClass(elCellEditor, DT.CLASS_EDITOR);
12061     var elFirstChild = Dom.getFirstChild(document.body);
12062     if(elFirstChild) {
12063         elCellEditor = Dom.insertBefore(elCellEditor, elFirstChild);
12064     }
12065     else {
12066         elCellEditor = document.body.appendChild(elCellEditor);
12067     }
12068     
12069     // Internal tracker of Cell Editor values
12070     var oCellEditor = {};
12071     oCellEditor.container = elCellEditor;
12072     oCellEditor.value = null;
12073     oCellEditor.isActive = false;
12074     this._oCellEditor = oCellEditor;
12075 },
12076
12077 /**
12078  * Overridable abstract method to customize CellEditor before showing.
12079  *
12080  * @method doBeforeShowCellEditor
12081  * @param oCellEditor {YAHOO.widget.CellEditor} The CellEditor instance.
12082  * @return {Boolean} Return true to continue showing CellEditor.
12083  */
12084 doBeforeShowCellEditor : function(oCellEditor) {
12085     return true;
12086 },
12087
12088 /**
12089  * Saves active CellEditor input to Record and upates DOM UI.
12090  *
12091  * @method saveCellEditor
12092  */
12093 saveCellEditor : function() {
12094     if(this._oCellEditor) {
12095         if(this._oCellEditor.save) {
12096             this._oCellEditor.save();
12097         }
12098         // Backward compatibility
12099         else if(this._oCellEditor.isActive) {
12100             var newData = this._oCellEditor.value;
12101             // Copy the data to pass to the event
12102             //var oldData = YAHOO.widget.DataTable._cloneObject(this._oCellEditor.record.getData(this._oCellEditor.column.key));
12103             var oldData = this._oCellEditor.record.getData(this._oCellEditor.column.key);
12104     
12105             // Validate input data
12106             if(this._oCellEditor.validator) {
12107                 newData = this._oCellEditor.value = this._oCellEditor.validator.call(this, newData, oldData, this._oCellEditor);
12108                 if(newData === null ) {
12109                     this.resetCellEditor();
12110                     this.fireEvent("editorRevertEvent",
12111                             {editor:this._oCellEditor, oldData:oldData, newData:newData});
12112                     return;
12113                 }
12114             }
12115             // Update the Record
12116             this._oRecordSet.updateRecordValue(this._oCellEditor.record, this._oCellEditor.column.key, this._oCellEditor.value);
12117             // Update the UI
12118             this.formatCell(this._oCellEditor.cell.firstChild);
12119             
12120             // Bug fix 1764044
12121             this._oChainRender.add({
12122                 method: function() {
12123                     this.validateColumnWidths();
12124                 },
12125                 scope: this
12126             });
12127             this._oChainRender.run();
12128             // Clear out the Cell Editor
12129             this.resetCellEditor();
12130     
12131             this.fireEvent("editorSaveEvent",
12132                     {editor:this._oCellEditor, oldData:oldData, newData:newData});
12133         }
12134     }   
12135 },
12136
12137 /**
12138  * Cancels active CellEditor.
12139  *
12140  * @method cancelCellEditor
12141  */
12142 cancelCellEditor : function() {
12143     if(this._oCellEditor) {
12144         if(this._oCellEditor.cancel) {
12145             this._oCellEditor.cancel();
12146         }
12147         // Backward compatibility
12148         else if(this._oCellEditor.isActive) {
12149             this.resetCellEditor();
12150             //TODO: preserve values for the event?
12151             this.fireEvent("editorCancelEvent", {editor:this._oCellEditor});
12152         }
12153
12154     }
12155 },
12156
12157 /**
12158  * Destroys active CellEditor instance and UI.
12159  *
12160  * @method destroyCellEditor
12161  */
12162 destroyCellEditor : function() {
12163     if(this._oCellEditor) {
12164         this._oCellEditor.destroy();
12165         this._oCellEditor = null;
12166     }   
12167 },
12168
12169 /**
12170  * Passes through showEvent of the active CellEditor.
12171  *
12172  * @method _onEditorShowEvent
12173  * @param oArgs {Object}  Custom Event args.
12174  * @private 
12175  */
12176 _onEditorShowEvent : function(oArgs) {
12177     this.fireEvent("editorShowEvent", oArgs);
12178 },
12179
12180 /**
12181  * Passes through keydownEvent of the active CellEditor.
12182  * @param oArgs {Object}  Custom Event args. 
12183  *
12184  * @method _onEditorKeydownEvent
12185  * @private 
12186  */
12187 _onEditorKeydownEvent : function(oArgs) {
12188     this.fireEvent("editorKeydownEvent", oArgs);
12189 },
12190
12191 /**
12192  * Passes through revertEvent of the active CellEditor.
12193  *
12194  * @method _onEditorRevertEvent
12195  * @param oArgs {Object}  Custom Event args. 
12196  * @private  
12197  */
12198 _onEditorRevertEvent : function(oArgs) {
12199     this.fireEvent("editorRevertEvent", oArgs);
12200 },
12201
12202 /**
12203  * Passes through saveEvent of the active CellEditor.
12204  *
12205  * @method _onEditorSaveEvent
12206  * @param oArgs {Object}  Custom Event args.  
12207  * @private 
12208  */
12209 _onEditorSaveEvent : function(oArgs) {
12210     this.fireEvent("editorSaveEvent", oArgs);
12211 },
12212
12213 /**
12214  * Passes through cancelEvent of the active CellEditor.
12215  *
12216  * @method _onEditorCancelEvent
12217  * @param oArgs {Object}  Custom Event args.
12218  * @private   
12219  */
12220 _onEditorCancelEvent : function(oArgs) {
12221     this.fireEvent("editorCancelEvent", oArgs);
12222 },
12223
12224 /**
12225  * Passes through blurEvent of the active CellEditor.
12226  *
12227  * @method _onEditorBlurEvent
12228  * @param oArgs {Object}  Custom Event args. 
12229  * @private  
12230  */
12231 _onEditorBlurEvent : function(oArgs) {
12232     this.fireEvent("editorBlurEvent", oArgs);
12233 },
12234
12235 /**
12236  * Passes through blockEvent of the active CellEditor.
12237  *
12238  * @method _onEditorBlockEvent
12239  * @param oArgs {Object}  Custom Event args. 
12240  * @private  
12241  */
12242 _onEditorBlockEvent : function(oArgs) {
12243     this.fireEvent("editorBlockEvent", oArgs);
12244 },
12245
12246 /**
12247  * Passes through unblockEvent of the active CellEditor.
12248  *
12249  * @method _onEditorUnblockEvent
12250  * @param oArgs {Object}  Custom Event args. 
12251  * @private  
12252  */
12253 _onEditorUnblockEvent : function(oArgs) {
12254     this.fireEvent("editorUnblockEvent", oArgs);
12255 },
12256
12257 /**
12258  * Public handler of the editorBlurEvent. By default, saves on blur if
12259  * disableBtns is true, otherwise cancels on blur. 
12260  *
12261  * @method onEditorBlurEvent
12262  * @param oArgs {Object}  Custom Event args.  
12263  */
12264 onEditorBlurEvent : function(oArgs) {
12265     if(oArgs.editor.disableBtns) {
12266         // Save on blur
12267         if(oArgs.editor.save) { // Backward incompatible
12268             oArgs.editor.save();
12269         }
12270     }      
12271     else if(oArgs.editor.cancel) { // Backward incompatible
12272         // Cancel on blur
12273         oArgs.editor.cancel();
12274     }      
12275 },
12276
12277 /**
12278  * Public handler of the editorBlockEvent. By default, disables DataTable UI.
12279  *
12280  * @method onEditorBlockEvent
12281  * @param oArgs {Object}  Custom Event args.  
12282  */
12283 onEditorBlockEvent : function(oArgs) {
12284     this.disable();
12285 },
12286
12287 /**
12288  * Public handler of the editorUnblockEvent. By default, undisables DataTable UI.
12289  *
12290  * @method onEditorUnblockEvent
12291  * @param oArgs {Object}  Custom Event args.  
12292  */
12293 onEditorUnblockEvent : function(oArgs) {
12294     this.undisable();
12295 },
12296
12297
12298
12299
12300
12301
12302
12303
12304
12305
12306
12307
12308
12309
12310
12311
12312
12313
12314
12315
12316
12317
12318
12319
12320
12321
12322
12323
12324
12325
12326
12327
12328
12329
12330
12331
12332
12333
12334 // ABSTRACT METHODS
12335
12336 /**
12337  * Overridable method gives implementers a hook to access data before
12338  * it gets added to RecordSet and rendered to the TBODY.
12339  *
12340  * @method doBeforeLoadData
12341  * @param sRequest {String} Original request.
12342  * @param oResponse {Object} Response object.
12343  * @param oPayload {MIXED} additional arguments
12344  * @return {Boolean} Return true to continue loading data into RecordSet and
12345  * updating DataTable with new Records, false to cancel.
12346  */
12347 doBeforeLoadData : function(sRequest, oResponse, oPayload) {
12348     return true;
12349 },
12350
12351
12352
12353
12354
12355
12356
12357
12358
12359
12360
12361
12362
12363
12364
12365
12366
12367
12368
12369
12370
12371
12372
12373
12374
12375
12376
12377
12378
12379
12380
12381
12382
12383
12384
12385
12386
12387
12388
12389
12390
12391
12392
12393
12394
12395
12396
12397
12398
12399
12400
12401
12402
12403
12404
12405
12406
12407
12408
12409
12410
12411
12412
12413 /////////////////////////////////////////////////////////////////////////////
12414 //
12415 // Public Custom Event Handlers
12416 //
12417 /////////////////////////////////////////////////////////////////////////////
12418
12419 /**
12420  * Overridable custom event handler to sort Column.
12421  *
12422  * @method onEventSortColumn
12423  * @param oArgs.event {HTMLEvent} Event object.
12424  * @param oArgs.target {HTMLElement} Target element.
12425  */
12426 onEventSortColumn : function(oArgs) {
12427 //TODO: support form elements in sortable columns
12428     var evt = oArgs.event;
12429     var target = oArgs.target;
12430
12431     var el = this.getThEl(target) || this.getTdEl(target);
12432     if(el) {
12433         var oColumn = this.getColumn(el);
12434         if(oColumn.sortable) {
12435             Ev.stopEvent(evt);
12436             this.sortColumn(oColumn);
12437         }
12438     }
12439     else {
12440     }
12441 },
12442
12443 /**
12444  * Overridable custom event handler to select Column.
12445  *
12446  * @method onEventSelectColumn
12447  * @param oArgs.event {HTMLEvent} Event object.
12448  * @param oArgs.target {HTMLElement} Target element.
12449  */
12450 onEventSelectColumn : function(oArgs) {
12451     this.selectColumn(oArgs.target);
12452 },
12453
12454 /**
12455  * Overridable custom event handler to highlight Column. Accounts for spurious
12456  * caused-by-child events. 
12457  *
12458  * @method onEventHighlightColumn
12459  * @param oArgs.event {HTMLEvent} Event object.
12460  * @param oArgs.target {HTMLElement} Target element.
12461  */
12462 onEventHighlightColumn : function(oArgs) {
12463     //TODO: filter for all spurious events at a lower level
12464     if(!Dom.isAncestor(oArgs.target,Ev.getRelatedTarget(oArgs.event))) {
12465         this.highlightColumn(oArgs.target);
12466     }
12467 },
12468
12469 /**
12470  * Overridable custom event handler to unhighlight Column. Accounts for spurious
12471  * caused-by-child events. 
12472  *
12473  * @method onEventUnhighlightColumn
12474  * @param oArgs.event {HTMLEvent} Event object.
12475  * @param oArgs.target {HTMLElement} Target element.
12476  */
12477 onEventUnhighlightColumn : function(oArgs) {
12478     //TODO: filter for all spurious events at a lower level
12479     if(!Dom.isAncestor(oArgs.target,Ev.getRelatedTarget(oArgs.event))) {
12480         this.unhighlightColumn(oArgs.target);
12481     }
12482 },
12483
12484 /**
12485  * Overridable custom event handler to manage selection according to desktop paradigm.
12486  *
12487  * @method onEventSelectRow
12488  * @param oArgs.event {HTMLEvent} Event object.
12489  * @param oArgs.target {HTMLElement} Target element.
12490  */
12491 onEventSelectRow : function(oArgs) {
12492     var sMode = this.get("selectionMode");
12493     if(sMode == "single") {
12494         this._handleSingleSelectionByMouse(oArgs);
12495     }
12496     else {
12497         this._handleStandardSelectionByMouse(oArgs);
12498     }
12499 },
12500
12501 /**
12502  * Overridable custom event handler to select cell.
12503  *
12504  * @method onEventSelectCell
12505  * @param oArgs.event {HTMLEvent} Event object.
12506  * @param oArgs.target {HTMLElement} Target element.
12507  */
12508 onEventSelectCell : function(oArgs) {
12509     var sMode = this.get("selectionMode");
12510     if(sMode == "cellblock") {
12511         this._handleCellBlockSelectionByMouse(oArgs);
12512     }
12513     else if(sMode == "cellrange") {
12514         this._handleCellRangeSelectionByMouse(oArgs);
12515     }
12516     else {
12517         this._handleSingleCellSelectionByMouse(oArgs);
12518     }
12519 },
12520
12521 /**
12522  * Overridable custom event handler to highlight row. Accounts for spurious
12523  * caused-by-child events. 
12524  *
12525  * @method onEventHighlightRow
12526  * @param oArgs.event {HTMLEvent} Event object.
12527  * @param oArgs.target {HTMLElement} Target element.
12528  */
12529 onEventHighlightRow : function(oArgs) {
12530     //TODO: filter for all spurious events at a lower level
12531     if(!Dom.isAncestor(oArgs.target,Ev.getRelatedTarget(oArgs.event))) {
12532         this.highlightRow(oArgs.target);
12533     }
12534 },
12535
12536 /**
12537  * Overridable custom event handler to unhighlight row. Accounts for spurious
12538  * caused-by-child events. 
12539  *
12540  * @method onEventUnhighlightRow
12541  * @param oArgs.event {HTMLEvent} Event object.
12542  * @param oArgs.target {HTMLElement} Target element.
12543  */
12544 onEventUnhighlightRow : function(oArgs) {
12545     //TODO: filter for all spurious events at a lower level
12546     if(!Dom.isAncestor(oArgs.target,Ev.getRelatedTarget(oArgs.event))) {
12547         this.unhighlightRow(oArgs.target);
12548     }
12549 },
12550
12551 /**
12552  * Overridable custom event handler to highlight cell. Accounts for spurious
12553  * caused-by-child events. 
12554  *
12555  * @method onEventHighlightCell
12556  * @param oArgs.event {HTMLEvent} Event object.
12557  * @param oArgs.target {HTMLElement} Target element.
12558  */
12559 onEventHighlightCell : function(oArgs) {
12560     //TODO: filter for all spurious events at a lower level
12561     if(!Dom.isAncestor(oArgs.target,Ev.getRelatedTarget(oArgs.event))) {
12562         this.highlightCell(oArgs.target);
12563     }
12564 },
12565
12566 /**
12567  * Overridable custom event handler to unhighlight cell. Accounts for spurious
12568  * caused-by-child events. 
12569  *
12570  * @method onEventUnhighlightCell
12571  * @param oArgs.event {HTMLEvent} Event object.
12572  * @param oArgs.target {HTMLElement} Target element.
12573  */
12574 onEventUnhighlightCell : function(oArgs) {
12575     //TODO: filter for all spurious events at a lower level
12576     if(!Dom.isAncestor(oArgs.target,Ev.getRelatedTarget(oArgs.event))) {
12577         this.unhighlightCell(oArgs.target);
12578     }
12579 },
12580
12581 /**
12582  * Overridable custom event handler to format cell.
12583  *
12584  * @method onEventFormatCell
12585  * @param oArgs.event {HTMLEvent} Event object.
12586  * @param oArgs.target {HTMLElement} Target element.
12587  */
12588 onEventFormatCell : function(oArgs) {
12589     var target = oArgs.target;
12590
12591     var elCell = this.getTdEl(target);
12592     if(elCell) {
12593         var oColumn = this.getColumn(elCell.cellIndex);
12594         this.formatCell(elCell.firstChild, this.getRecord(elCell), oColumn);
12595     }
12596     else {
12597     }
12598 },
12599
12600 /**
12601  * Overridable custom event handler to edit cell.
12602  *
12603  * @method onEventShowCellEditor
12604  * @param oArgs.event {HTMLEvent} Event object.
12605  * @param oArgs.target {HTMLElement} Target element.
12606  */
12607 onEventShowCellEditor : function(oArgs) {
12608     this.showCellEditor(oArgs.target);
12609 },
12610
12611 /**
12612  * Overridable custom event handler to save active CellEditor input.
12613  *
12614  * @method onEventSaveCellEditor
12615  */
12616 onEventSaveCellEditor : function(oArgs) {
12617     if(this._oCellEditor) {
12618         if(this._oCellEditor.save) {
12619             this._oCellEditor.save();
12620         }
12621         // Backward compatibility
12622         else {
12623             this.saveCellEditor();
12624         }
12625     }
12626 },
12627
12628 /**
12629  * Overridable custom event handler to cancel active CellEditor.
12630  *
12631  * @method onEventCancelCellEditor
12632  */
12633 onEventCancelCellEditor : function(oArgs) {
12634     if(this._oCellEditor) {
12635         if(this._oCellEditor.cancel) {
12636             this._oCellEditor.cancel();
12637         }
12638         // Backward compatibility
12639         else {
12640             this.cancelCellEditor();
12641         }
12642     }
12643 },
12644
12645 /**
12646  * Callback function receives data from DataSource and populates an entire
12647  * DataTable with Records and TR elements, clearing previous Records, if any.
12648  *
12649  * @method onDataReturnInitializeTable
12650  * @param sRequest {String} Original request.
12651  * @param oResponse {Object} Response object.
12652  * @param oPayload {MIXED} (optional) Additional argument(s)
12653  */
12654 onDataReturnInitializeTable : function(sRequest, oResponse, oPayload) {
12655     if((this instanceof DT) && this._sId) {
12656         this.initializeTable();
12657     
12658         this.onDataReturnSetRows(sRequest,oResponse,oPayload);
12659     }
12660 },
12661
12662 /**
12663  * Callback function receives reponse from DataSource, replaces all existing
12664  * Records in  RecordSet, updates TR elements with new data, and updates state
12665  * UI for pagination and sorting from payload data, if necessary. 
12666  *  
12667  * @method onDataReturnReplaceRows
12668  * @param oRequest {MIXED} Original generated request.
12669  * @param oResponse {Object} Response object.
12670  * @param oPayload {MIXED} (optional) Additional argument(s)
12671  */
12672 onDataReturnReplaceRows : function(oRequest, oResponse, oPayload) {
12673     if((this instanceof DT) && this._sId) {
12674         this.fireEvent("dataReturnEvent", {request:oRequest,response:oResponse,payload:oPayload});
12675     
12676         // Pass data through abstract method for any transformations
12677         var ok    = this.doBeforeLoadData(oRequest, oResponse, oPayload),
12678             pag   = this.get('paginator'),
12679             index = 0;
12680     
12681         // Data ok to set
12682         if(ok && oResponse && !oResponse.error && lang.isArray(oResponse.results)) {
12683             // Update Records
12684             this._oRecordSet.reset();
12685     
12686             if (this.get('dynamicData')) {
12687                 if (oPayload && oPayload.pagination &&
12688                     lang.isNumber(oPayload.pagination.recordOffset)) {
12689                     index = oPayload.pagination.recordOffset;
12690                 } else if (pag) {
12691                     index = pag.getStartIndex();
12692                 }
12693             }
12694     
12695             this._oRecordSet.setRecords(oResponse.results, index | 0);
12696             
12697             // Update state
12698             this._handleDataReturnPayload(oRequest, oResponse, oPayload);
12699             
12700             // Update UI
12701             this.render();    
12702         }
12703         // Error
12704         else if(ok && oResponse.error) {
12705             this.showTableMessage(this.get("MSG_ERROR"), DT.CLASS_ERROR);
12706         }
12707     }
12708 },
12709
12710 /**
12711  * Callback function receives data from DataSource and appends to an existing
12712  * DataTable new Records and, if applicable, creates or updates
12713  * corresponding TR elements.
12714  *
12715  * @method onDataReturnAppendRows
12716  * @param sRequest {String} Original request.
12717  * @param oResponse {Object} Response object.
12718  * @param oPayload {MIXED} (optional) Additional argument(s)
12719  */
12720 onDataReturnAppendRows : function(sRequest, oResponse, oPayload) {
12721     if((this instanceof DT) && this._sId) {
12722         this.fireEvent("dataReturnEvent", {request:sRequest,response:oResponse,payload:oPayload});
12723     
12724         // Pass data through abstract method for any transformations
12725         var ok = this.doBeforeLoadData(sRequest, oResponse, oPayload);
12726     
12727         // Data ok to append
12728         if(ok && oResponse && !oResponse.error && lang.isArray(oResponse.results)) {        
12729             // Append rows
12730             this.addRows(oResponse.results);
12731     
12732             // Update state
12733             this._handleDataReturnPayload(sRequest, oResponse, oPayload);
12734         }
12735         // Error
12736         else if(ok && oResponse.error) {
12737             this.showTableMessage(this.get("MSG_ERROR"), DT.CLASS_ERROR);
12738         }
12739     }
12740 },
12741
12742 /**
12743  * Callback function receives data from DataSource and inserts new records
12744  * starting at the index specified in oPayload.insertIndex. The value for
12745  * oPayload.insertIndex can be populated when sending the request to the DataSource,
12746  * or by accessing oPayload.insertIndex with the doBeforeLoadData() method at runtime.
12747  * If applicable, creates or updates corresponding TR elements.
12748  *
12749  * @method onDataReturnInsertRows
12750  * @param sRequest {String} Original request.
12751  * @param oResponse {Object} Response object.
12752  * @param oPayload {MIXED} Argument payload, looks in oPayload.insertIndex.
12753  */
12754 onDataReturnInsertRows : function(sRequest, oResponse, oPayload) {
12755     if((this instanceof DT) && this._sId) {
12756         this.fireEvent("dataReturnEvent", {request:sRequest,response:oResponse,payload:oPayload});
12757     
12758         // Pass data through abstract method for any transformations
12759         var ok = this.doBeforeLoadData(sRequest, oResponse, oPayload);
12760     
12761         // Data ok to append
12762         if(ok && oResponse && !oResponse.error && lang.isArray(oResponse.results)) {
12763             // Insert rows
12764             this.addRows(oResponse.results, (oPayload ? oPayload.insertIndex : 0));
12765     
12766             // Update state
12767             this._handleDataReturnPayload(sRequest, oResponse, oPayload);
12768         }
12769         // Error
12770         else if(ok && oResponse.error) {
12771             this.showTableMessage(this.get("MSG_ERROR"), DT.CLASS_ERROR);
12772         }
12773     }
12774 },
12775
12776 /**
12777  * Callback function receives data from DataSource and incrementally updates Records
12778  * starting at the index specified in oPayload.updateIndex. The value for
12779  * oPayload.updateIndex can be populated when sending the request to the DataSource,
12780  * or by accessing oPayload.updateIndex with the doBeforeLoadData() method at runtime.
12781  * If applicable, creates or updates corresponding TR elements.
12782  *
12783  * @method onDataReturnUpdateRows
12784  * @param sRequest {String} Original request.
12785  * @param oResponse {Object} Response object.
12786  * @param oPayload {MIXED} Argument payload, looks in oPayload.updateIndex.
12787  */
12788 onDataReturnUpdateRows : function(sRequest, oResponse, oPayload) {
12789     if((this instanceof DT) && this._sId) {
12790         this.fireEvent("dataReturnEvent", {request:sRequest,response:oResponse,payload:oPayload});
12791     
12792         // Pass data through abstract method for any transformations
12793         var ok = this.doBeforeLoadData(sRequest, oResponse, oPayload);
12794     
12795         // Data ok to append
12796         if(ok && oResponse && !oResponse.error && lang.isArray(oResponse.results)) {
12797             // Insert rows
12798             this.updateRows((oPayload ? oPayload.updateIndex : 0), oResponse.results);
12799     
12800             // Update state
12801             this._handleDataReturnPayload(sRequest, oResponse, oPayload);
12802         }
12803         // Error
12804         else if(ok && oResponse.error) {
12805             this.showTableMessage(this.get("MSG_ERROR"), DT.CLASS_ERROR);
12806         }
12807     }
12808 },
12809
12810 /**
12811  * Callback function receives reponse from DataSource and populates the
12812  * RecordSet with the results.
12813  *  
12814  * @method onDataReturnSetRows
12815  * @param oRequest {MIXED} Original generated request.
12816  * @param oResponse {Object} Response object.
12817  * @param oPayload {MIXED} (optional) Additional argument(s)
12818  */
12819 onDataReturnSetRows : function(oRequest, oResponse, oPayload) {
12820     if((this instanceof DT) && this._sId) {
12821         this.fireEvent("dataReturnEvent", {request:oRequest,response:oResponse,payload:oPayload});
12822     
12823         // Pass data through abstract method for any transformations
12824         var ok    = this.doBeforeLoadData(oRequest, oResponse, oPayload),
12825             pag   = this.get('paginator'),
12826             index = 0;
12827     
12828         // Data ok to set
12829         if(ok && oResponse && !oResponse.error && lang.isArray(oResponse.results)) {
12830             // Update Records
12831             if (this.get('dynamicData')) {
12832                 if (oPayload && oPayload.pagination &&
12833                     lang.isNumber(oPayload.pagination.recordOffset)) {
12834                     index = oPayload.pagination.recordOffset;
12835                 } else if (pag) {
12836                     index = pag.getStartIndex();
12837                 }
12838                 
12839                 this._oRecordSet.reset(); // Bug 2290604: dyanmic data shouldn't keep accumulating by default
12840             }
12841     
12842             this._oRecordSet.setRecords(oResponse.results, index | 0);
12843     
12844             // Update state
12845             this._handleDataReturnPayload(oRequest, oResponse, oPayload);
12846             
12847             // Update UI
12848             this.render();
12849         }
12850         // Error
12851         else if(ok && oResponse.error) {
12852             this.showTableMessage(this.get("MSG_ERROR"), DT.CLASS_ERROR);
12853         }
12854     }
12855     else {
12856     }
12857 },
12858
12859 /**
12860  * Hook to update oPayload before consumption.
12861  *  
12862  * @method handleDataReturnPayload
12863  * @param oRequest {MIXED} Original generated request.
12864  * @param oResponse {Object} Response object.
12865  * @param oPayload {MIXED} State values.
12866  * @return oPayload {MIXED} State values.
12867  */
12868 handleDataReturnPayload : function (oRequest, oResponse, oPayload) {
12869     return oPayload;
12870 },
12871
12872 /**
12873  * Updates the DataTable with state data sent in an onDataReturn* payload.
12874  *  
12875  * @method handleDataReturnPayload
12876  * @param oRequest {MIXED} Original generated request.
12877  * @param oResponse {Object} Response object.
12878  * @param oPayload {MIXED} State values
12879  */
12880 _handleDataReturnPayload : function (oRequest, oResponse, oPayload) {
12881     oPayload = this.handleDataReturnPayload(oRequest, oResponse, oPayload);
12882     if(oPayload) {
12883         // Update pagination
12884         var oPaginator = this.get('paginator');
12885         if (oPaginator) {
12886             // Update totalRecords
12887             if(this.get("dynamicData")) {
12888                 if (widget.Paginator.isNumeric(oPayload.totalRecords)) {
12889                     oPaginator.set('totalRecords',oPayload.totalRecords);
12890                 }
12891             }
12892             else {
12893                 oPaginator.set('totalRecords',this._oRecordSet.getLength());
12894             }
12895             // Update other paginator values
12896             if (lang.isObject(oPayload.pagination)) {
12897                 oPaginator.set('rowsPerPage',oPayload.pagination.rowsPerPage);
12898                 oPaginator.set('recordOffset',oPayload.pagination.recordOffset);
12899             }
12900         }
12901
12902         // Update sorting
12903         if (oPayload.sortedBy) {
12904             // Set the sorting values in preparation for refresh
12905             this.set('sortedBy', oPayload.sortedBy);
12906         }
12907         // Backwards compatibility for sorting
12908         else if (oPayload.sorting) {
12909             // Set the sorting values in preparation for refresh
12910             this.set('sortedBy', oPayload.sorting);
12911         }
12912     }
12913 },
12914
12915
12916
12917
12918
12919
12920
12921
12922
12923
12924
12925
12926
12927
12928
12929
12930
12931
12932
12933
12934
12935
12936
12937
12938
12939
12940
12941
12942
12943
12944
12945
12946
12947     /////////////////////////////////////////////////////////////////////////////
12948     //
12949     // Custom Events
12950     //
12951     /////////////////////////////////////////////////////////////////////////////
12952
12953     /**
12954      * Fired when the DataTable's rows are rendered from an initialized state.
12955      *
12956      * @event initEvent
12957      */
12958
12959     /**
12960      * Fired when the DataTable's DOM is rendered or modified.
12961      *
12962      * @event renderEvent
12963      */
12964
12965     /**
12966      * Fired when the DataTable's post-render routine is complete, including
12967      * Column width validations.
12968      *
12969      * @event postRenderEvent
12970      */
12971
12972     /**
12973      * Fired when the DataTable is disabled.
12974      *
12975      * @event disableEvent
12976      */
12977
12978     /**
12979      * Fired when the DataTable is undisabled.
12980      *
12981      * @event undisableEvent
12982      */
12983
12984     /**
12985      * Fired when data is returned from DataSource but before it is consumed by
12986      * DataTable.
12987      *
12988      * @event dataReturnEvent
12989      * @param oArgs.request {String} Original request.
12990      * @param oArgs.response {Object} Response object.
12991      */
12992
12993     /**
12994      * Fired when the DataTable has a focus event.
12995      *
12996      * @event tableFocusEvent
12997      */
12998
12999     /**
13000      * Fired when the DataTable THEAD element has a focus event.
13001      *
13002      * @event theadFocusEvent
13003      */
13004
13005     /**
13006      * Fired when the DataTable TBODY element has a focus event.
13007      *
13008      * @event tbodyFocusEvent
13009      */
13010
13011     /**
13012      * Fired when the DataTable has a blur event.
13013      *
13014      * @event tableBlurEvent
13015      */
13016
13017     /*TODO implement theadBlurEvent
13018      * Fired when the DataTable THEAD element has a blur event.
13019      *
13020      * @event theadBlurEvent
13021      */
13022
13023     /*TODO: implement tbodyBlurEvent
13024      * Fired when the DataTable TBODY element has a blur event.
13025      *
13026      * @event tbodyBlurEvent
13027      */
13028
13029     /**
13030      * Fired when the DataTable has a key event.
13031      *
13032      * @event tableKeyEvent
13033      * @param oArgs.event {HTMLEvent} The event object.
13034      * @param oArgs.target {HTMLElement} The DataTable's TABLE element.
13035      */
13036
13037     /**
13038      * Fired when the DataTable THEAD element has a key event.
13039      *
13040      * @event theadKeyEvent
13041      * @param oArgs.event {HTMLEvent} The event object.
13042      * @param oArgs.target {HTMLElement} The DataTable's TABLE element.
13043      */
13044
13045     /**
13046      * Fired when the DataTable TBODY element has a key event.
13047      *
13048      * @event tbodyKeyEvent
13049      * @param oArgs.event {HTMLEvent} The event object.
13050      * @param oArgs.target {HTMLElement} The DataTable's TABLE element.
13051      */
13052
13053     /**
13054      * Fired when the DataTable has a mouseover.
13055      *
13056      * @event tableMouseoverEvent
13057      * @param oArgs.event {HTMLEvent} The event object.
13058      * @param oArgs.target {HTMLElement} The DataTable's TABLE element.
13059      *
13060      */
13061
13062     /**
13063      * Fired when the DataTable has a mouseout.
13064      *
13065      * @event tableMouseoutEvent
13066      * @param oArgs.event {HTMLEvent} The event object.
13067      * @param oArgs.target {HTMLElement} The DataTable's TABLE element.
13068      *
13069      */
13070
13071     /**
13072      * Fired when the DataTable has a mousedown.
13073      *
13074      * @event tableMousedownEvent
13075      * @param oArgs.event {HTMLEvent} The event object.
13076      * @param oArgs.target {HTMLElement} The DataTable's TABLE element.
13077      *
13078      */
13079
13080     /**
13081      * Fired when the DataTable has a mouseup.
13082      *
13083      * @event tableMouseupEvent
13084      * @param oArgs.event {HTMLEvent} The event object.
13085      * @param oArgs.target {HTMLElement} The DataTable's TABLE element.
13086      *
13087      */
13088
13089     /**
13090      * Fired when the DataTable has a click.
13091      *
13092      * @event tableClickEvent
13093      * @param oArgs.event {HTMLEvent} The event object.
13094      * @param oArgs.target {HTMLElement} The DataTable's TABLE element.
13095      *
13096      */
13097
13098     /**
13099      * Fired when the DataTable has a dblclick.
13100      *
13101      * @event tableDblclickEvent
13102      * @param oArgs.event {HTMLEvent} The event object.
13103      * @param oArgs.target {HTMLElement} The DataTable's TABLE element.
13104      *
13105      */
13106
13107     /**
13108      * Fired when a message is shown in the DataTable's message element.
13109      *
13110      * @event tableMsgShowEvent
13111      * @param oArgs.html {String} The HTML displayed.
13112      * @param oArgs.className {String} The className assigned.
13113      *
13114      */
13115
13116     /**
13117      * Fired when the DataTable's message element is hidden.
13118      *
13119      * @event tableMsgHideEvent
13120      */
13121
13122     /**
13123      * Fired when a THEAD row has a mouseover.
13124      *
13125      * @event theadRowMouseoverEvent
13126      * @param oArgs.event {HTMLEvent} The event object.
13127      * @param oArgs.target {HTMLElement} The TR element.
13128      */
13129
13130     /**
13131      * Fired when a THEAD row has a mouseout.
13132      *
13133      * @event theadRowMouseoutEvent
13134      * @param oArgs.event {HTMLEvent} The event object.
13135      * @param oArgs.target {HTMLElement} The TR element.
13136      */
13137
13138     /**
13139      * Fired when a THEAD row has a mousedown.
13140      *
13141      * @event theadRowMousedownEvent
13142      * @param oArgs.event {HTMLEvent} The event object.
13143      * @param oArgs.target {HTMLElement} The TR element.
13144      */
13145
13146     /**
13147      * Fired when a THEAD row has a mouseup.
13148      *
13149      * @event theadRowMouseupEvent
13150      * @param oArgs.event {HTMLEvent} The event object.
13151      * @param oArgs.target {HTMLElement} The TR element.
13152      */
13153
13154     /**
13155      * Fired when a THEAD row has a click.
13156      *
13157      * @event theadRowClickEvent
13158      * @param oArgs.event {HTMLEvent} The event object.
13159      * @param oArgs.target {HTMLElement} The TR element.
13160      */
13161
13162     /**
13163      * Fired when a THEAD row has a dblclick.
13164      *
13165      * @event theadRowDblclickEvent
13166      * @param oArgs.event {HTMLEvent} The event object.
13167      * @param oArgs.target {HTMLElement} The TR element.
13168      */
13169
13170     /**
13171      * Fired when a THEAD cell has a mouseover.
13172      *
13173      * @event theadCellMouseoverEvent
13174      * @param oArgs.event {HTMLEvent} The event object.
13175      * @param oArgs.target {HTMLElement} The TH element.
13176      *
13177      */
13178
13179     /**
13180      * Fired when a THEAD cell has a mouseout.
13181      *
13182      * @event theadCellMouseoutEvent
13183      * @param oArgs.event {HTMLEvent} The event object.
13184      * @param oArgs.target {HTMLElement} The TH element.
13185      *
13186      */
13187
13188     /**
13189      * Fired when a THEAD cell has a mousedown.
13190      *
13191      * @event theadCellMousedownEvent
13192      * @param oArgs.event {HTMLEvent} The event object.
13193      * @param oArgs.target {HTMLElement} The TH element.
13194      */
13195
13196     /**
13197      * Fired when a THEAD cell has a mouseup.
13198      *
13199      * @event theadCellMouseupEvent
13200      * @param oArgs.event {HTMLEvent} The event object.
13201      * @param oArgs.target {HTMLElement} The TH element.
13202      */
13203
13204     /**
13205      * Fired when a THEAD cell has a click.
13206      *
13207      * @event theadCellClickEvent
13208      * @param oArgs.event {HTMLEvent} The event object.
13209      * @param oArgs.target {HTMLElement} The TH element.
13210      */
13211
13212     /**
13213      * Fired when a THEAD cell has a dblclick.
13214      *
13215      * @event theadCellDblclickEvent
13216      * @param oArgs.event {HTMLEvent} The event object.
13217      * @param oArgs.target {HTMLElement} The TH element.
13218      */
13219
13220     /**
13221      * Fired when a THEAD label has a mouseover.
13222      *
13223      * @event theadLabelMouseoverEvent
13224      * @param oArgs.event {HTMLEvent} The event object.
13225      * @param oArgs.target {HTMLElement} The SPAN element.
13226      *
13227      */
13228
13229     /**
13230      * Fired when a THEAD label has a mouseout.
13231      *
13232      * @event theadLabelMouseoutEvent
13233      * @param oArgs.event {HTMLEvent} The event object.
13234      * @param oArgs.target {HTMLElement} The SPAN element.
13235      *
13236      */
13237
13238     /**
13239      * Fired when a THEAD label has a mousedown.
13240      *
13241      * @event theadLabelMousedownEvent
13242      * @param oArgs.event {HTMLEvent} The event object.
13243      * @param oArgs.target {HTMLElement} The SPAN element.
13244      */
13245
13246     /**
13247      * Fired when a THEAD label has a mouseup.
13248      *
13249      * @event theadLabelMouseupEvent
13250      * @param oArgs.event {HTMLEvent} The event object.
13251      * @param oArgs.target {HTMLElement} The SPAN element.
13252      */
13253
13254     /**
13255      * Fired when a THEAD label has a click.
13256      *
13257      * @event theadLabelClickEvent
13258      * @param oArgs.event {HTMLEvent} The event object.
13259      * @param oArgs.target {HTMLElement} The SPAN element.
13260      */
13261
13262     /**
13263      * Fired when a THEAD label has a dblclick.
13264      *
13265      * @event theadLabelDblclickEvent
13266      * @param oArgs.event {HTMLEvent} The event object.
13267      * @param oArgs.target {HTMLElement} The SPAN element.
13268      */
13269
13270     /**
13271      * Fired when a column is sorted.
13272      *
13273      * @event columnSortEvent
13274      * @param oArgs.column {YAHOO.widget.Column} The Column instance.
13275      * @param oArgs.dir {String} Sort direction: YAHOO.widget.DataTable.CLASS_ASC
13276      * or YAHOO.widget.DataTable.CLASS_DESC.
13277      */
13278
13279     /**
13280      * Fired when a column width is set.
13281      *
13282      * @event columnSetWidthEvent
13283      * @param oArgs.column {YAHOO.widget.Column} The Column instance.
13284      * @param oArgs.width {Number} The width in pixels.
13285      */
13286
13287     /**
13288      * Fired when a column width is unset.
13289      *
13290      * @event columnUnsetWidthEvent
13291      * @param oArgs.column {YAHOO.widget.Column} The Column instance.
13292      */
13293
13294     /**
13295      * Fired when a column is drag-resized.
13296      *
13297      * @event columnResizeEvent
13298      * @param oArgs.column {YAHOO.widget.Column} The Column instance.
13299      * @param oArgs.target {HTMLElement} The TH element.
13300      * @param oArgs.width {Number} Width in pixels.     
13301      */
13302
13303     /**
13304      * Fired when a Column is moved to a new index.
13305      *
13306      * @event columnReorderEvent
13307      * @param oArgs.column {YAHOO.widget.Column} The Column instance.
13308      * @param oArgs.oldIndex {Number} The previous index position.
13309      */
13310
13311     /**
13312      * Fired when a column is hidden.
13313      *
13314      * @event columnHideEvent
13315      * @param oArgs.column {YAHOO.widget.Column} The Column instance.
13316      */
13317
13318     /**
13319      * Fired when a column is shown.
13320      *
13321      * @event columnShowEvent
13322      * @param oArgs.column {YAHOO.widget.Column} The Column instance.
13323      */
13324
13325     /**
13326      * Fired when a column is selected.
13327      *
13328      * @event columnSelectEvent
13329      * @param oArgs.column {YAHOO.widget.Column} The Column instance.
13330      */
13331
13332     /**
13333      * Fired when a column is unselected.
13334      *
13335      * @event columnUnselectEvent
13336      * @param oArgs.column {YAHOO.widget.Column} The Column instance.
13337      */
13338     /**
13339      * Fired when a column is removed.
13340      *
13341      * @event columnRemoveEvent
13342      * @param oArgs.column {YAHOO.widget.Column} The Column instance.
13343      */
13344
13345     /**
13346      * Fired when a column is inserted.
13347      *
13348      * @event columnInsertEvent
13349      * @param oArgs.column {YAHOO.widget.Column} The Column instance.
13350      * @param oArgs.index {Number} The index position.
13351      */
13352
13353     /**
13354      * Fired when a column is highlighted.
13355      *
13356      * @event columnHighlightEvent
13357      * @param oArgs.column {YAHOO.widget.Column} The highlighted Column.
13358      */
13359
13360     /**
13361      * Fired when a column is unhighlighted.
13362      *
13363      * @event columnUnhighlightEvent
13364      * @param oArgs.column {YAHOO.widget.Column} The unhighlighted Column.
13365      */
13366
13367
13368     /**
13369      * Fired when a row has a mouseover.
13370      *
13371      * @event rowMouseoverEvent
13372      * @param oArgs.event {HTMLEvent} The event object.
13373      * @param oArgs.target {HTMLElement} The TR element.
13374      */
13375
13376     /**
13377      * Fired when a row has a mouseout.
13378      *
13379      * @event rowMouseoutEvent
13380      * @param oArgs.event {HTMLEvent} The event object.
13381      * @param oArgs.target {HTMLElement} The TR element.
13382      */
13383
13384     /**
13385      * Fired when a row has a mousedown.
13386      *
13387      * @event rowMousedownEvent
13388      * @param oArgs.event {HTMLEvent} The event object.
13389      * @param oArgs.target {HTMLElement} The TR element.
13390      */
13391
13392     /**
13393      * Fired when a row has a mouseup.
13394      *
13395      * @event rowMouseupEvent
13396      * @param oArgs.event {HTMLEvent} The event object.
13397      * @param oArgs.target {HTMLElement} The TR element.
13398      */
13399
13400     /**
13401      * Fired when a row has a click.
13402      *
13403      * @event rowClickEvent
13404      * @param oArgs.event {HTMLEvent} The event object.
13405      * @param oArgs.target {HTMLElement} The TR element.
13406      */
13407
13408     /**
13409      * Fired when a row has a dblclick.
13410      *
13411      * @event rowDblclickEvent
13412      * @param oArgs.event {HTMLEvent} The event object.
13413      * @param oArgs.target {HTMLElement} The TR element.
13414      */
13415
13416     /**
13417      * Fired when a row is added.
13418      *
13419      * @event rowAddEvent
13420      * @param oArgs.record {YAHOO.widget.Record} The added Record.
13421      */
13422      
13423     /**
13424      * Fired when rows are added.
13425      *
13426      * @event rowsAddEvent
13427      * @param oArgs.record {YAHOO.widget.Record[]} The added Records.
13428      */
13429
13430     /**
13431      * Fired when a row is updated.
13432      *
13433      * @event rowUpdateEvent
13434      * @param oArgs.record {YAHOO.widget.Record} The updated Record.
13435      * @param oArgs.oldData {Object} Object literal of the old data.
13436      */
13437
13438     /**
13439      * Fired when a row is deleted.
13440      *
13441      * @event rowDeleteEvent
13442      * @param oArgs.oldData {Object} Object literal of the deleted data.
13443      * @param oArgs.recordIndex {Number} Index of the deleted Record.
13444      * @param oArgs.trElIndex {Number} Index of the deleted TR element, if on current page.
13445      */
13446      
13447     /**
13448      * Fired when rows are deleted.
13449      *
13450      * @event rowsDeleteEvent
13451      * @param oArgs.oldData {Object[]} Array of object literals of the deleted data.
13452      * @param oArgs.recordIndex {Number} Index of the first deleted Record.
13453      * @param oArgs.count {Number} Number of deleted Records.
13454      */
13455
13456     /**
13457      * Fired when a row is selected.
13458      *
13459      * @event rowSelectEvent
13460      * @param oArgs.el {HTMLElement} The selected TR element, if applicable.
13461      * @param oArgs.record {YAHOO.widget.Record} The selected Record.
13462      */
13463
13464     /**
13465      * Fired when a row is unselected.
13466      *
13467      * @event rowUnselectEvent
13468      * @param oArgs.el {HTMLElement} The unselected TR element, if applicable.
13469      * @param oArgs.record {YAHOO.widget.Record} The unselected Record.
13470      */
13471
13472     /**
13473      * Fired when all row selections are cleared.
13474      *
13475      * @event unselectAllRowsEvent
13476      */
13477
13478     /**
13479      * Fired when a row is highlighted.
13480      *
13481      * @event rowHighlightEvent
13482      * @param oArgs.el {HTMLElement} The highlighted TR element.
13483      * @param oArgs.record {YAHOO.widget.Record} The highlighted Record.
13484      */
13485
13486     /**
13487      * Fired when a row is unhighlighted.
13488      *
13489      * @event rowUnhighlightEvent
13490      * @param oArgs.el {HTMLElement} The highlighted TR element.
13491      * @param oArgs.record {YAHOO.widget.Record} The highlighted Record.
13492      */
13493
13494     /**
13495      * Fired when a cell is updated.
13496      *
13497      * @event cellUpdateEvent
13498      * @param oArgs.record {YAHOO.widget.Record} The updated Record.
13499      * @param oArgs.column {YAHOO.widget.Column} The updated Column.
13500      * @param oArgs.oldData {Object} Original data value of the updated cell.
13501      */
13502
13503     /**
13504      * Fired when a cell has a mouseover.
13505      *
13506      * @event cellMouseoverEvent
13507      * @param oArgs.event {HTMLEvent} The event object.
13508      * @param oArgs.target {HTMLElement} The TD element.
13509      */
13510
13511     /**
13512      * Fired when a cell has a mouseout.
13513      *
13514      * @event cellMouseoutEvent
13515      * @param oArgs.event {HTMLEvent} The event object.
13516      * @param oArgs.target {HTMLElement} The TD element.
13517      */
13518
13519     /**
13520      * Fired when a cell has a mousedown.
13521      *
13522      * @event cellMousedownEvent
13523      * @param oArgs.event {HTMLEvent} The event object.
13524      * @param oArgs.target {HTMLElement} The TD element.
13525      */
13526
13527     /**
13528      * Fired when a cell has a mouseup.
13529      *
13530      * @event cellMouseupEvent
13531      * @param oArgs.event {HTMLEvent} The event object.
13532      * @param oArgs.target {HTMLElement} The TD element.
13533      */
13534
13535     /**
13536      * Fired when a cell has a click.
13537      *
13538      * @event cellClickEvent
13539      * @param oArgs.event {HTMLEvent} The event object.
13540      * @param oArgs.target {HTMLElement} The TD element.
13541      */
13542
13543     /**
13544      * Fired when a cell has a dblclick.
13545      *
13546      * @event cellDblclickEvent
13547      * @param oArgs.event {HTMLEvent} The event object.
13548      * @param oArgs.target {HTMLElement} The TD element.
13549      */
13550
13551     /**
13552      * Fired when a cell is formatted.
13553      *
13554      * @event cellFormatEvent
13555      * @param oArgs.el {HTMLElement} The formatted TD element.
13556      * @param oArgs.record {YAHOO.widget.Record} The associated Record instance.
13557      * @param oArgs.column {YAHOO.widget.Column} The associated Column instance.
13558      * @param oArgs.key {String} (deprecated) The key of the formatted cell.
13559      */
13560
13561     /**
13562      * Fired when a cell is selected.
13563      *
13564      * @event cellSelectEvent
13565      * @param oArgs.el {HTMLElement} The selected TD element.
13566      * @param oArgs.record {YAHOO.widget.Record} The associated Record instance.
13567      * @param oArgs.column {YAHOO.widget.Column} The associated Column instance.
13568      * @param oArgs.key {String} (deprecated) The key of the selected cell.
13569      */
13570
13571     /**
13572      * Fired when a cell is unselected.
13573      *
13574      * @event cellUnselectEvent
13575      * @param oArgs.el {HTMLElement} The unselected TD element.
13576      * @param oArgs.record {YAHOO.widget.Record} The associated Record.
13577      * @param oArgs.column {YAHOO.widget.Column} The associated Column instance.
13578      * @param oArgs.key {String} (deprecated) The key of the unselected cell.
13579
13580      */
13581
13582     /**
13583      * Fired when a cell is highlighted.
13584      *
13585      * @event cellHighlightEvent
13586      * @param oArgs.el {HTMLElement} The highlighted TD element.
13587      * @param oArgs.record {YAHOO.widget.Record} The associated Record instance.
13588      * @param oArgs.column {YAHOO.widget.Column} The associated Column instance.
13589      * @param oArgs.key {String} (deprecated) The key of the highlighted cell.
13590
13591      */
13592
13593     /**
13594      * Fired when a cell is unhighlighted.
13595      *
13596      * @event cellUnhighlightEvent
13597      * @param oArgs.el {HTMLElement} The unhighlighted TD element.
13598      * @param oArgs.record {YAHOO.widget.Record} The associated Record instance.
13599      * @param oArgs.column {YAHOO.widget.Column} The associated Column instance.
13600      * @param oArgs.key {String} (deprecated) The key of the unhighlighted cell.
13601
13602      */
13603
13604     /**
13605      * Fired when all cell selections are cleared.
13606      *
13607      * @event unselectAllCellsEvent
13608      */
13609
13610     /**
13611      * Fired when a CellEditor is shown.
13612      *
13613      * @event editorShowEvent
13614      * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
13615      */
13616
13617     /**
13618      * Fired when a CellEditor has a keydown.
13619      *
13620      * @event editorKeydownEvent
13621      * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
13622      * @param oArgs.event {HTMLEvent} The event object.
13623      */
13624
13625     /**
13626      * Fired when a CellEditor input is reverted.
13627      *
13628      * @event editorRevertEvent
13629      * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
13630      * @param oArgs.newData {Object} New data value from form input field.
13631      * @param oArgs.oldData {Object} Old data value.
13632      */
13633
13634     /**
13635      * Fired when a CellEditor input is saved.
13636      *
13637      * @event editorSaveEvent
13638      * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
13639      * @param oArgs.newData {Object} New data value from form input field.
13640      * @param oArgs.oldData {Object} Old data value.
13641      */
13642
13643     /**
13644      * Fired when a CellEditor input is canceled.
13645      *
13646      * @event editorCancelEvent
13647      * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
13648      */
13649
13650     /**
13651      * Fired when a CellEditor has a blur event.
13652      *
13653      * @event editorBlurEvent
13654      * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
13655      */
13656
13657     /**
13658      * Fired when a CellEditor is blocked.
13659      *
13660      * @event editorBlockEvent
13661      * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
13662      */
13663
13664     /**
13665      * Fired when a CellEditor is unblocked.
13666      *
13667      * @event editorUnblockEvent
13668      * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
13669      */
13670
13671
13672
13673
13674
13675     /**
13676      * Fired when a link is clicked.
13677      *
13678      * @event linkClickEvent
13679      * @param oArgs.event {HTMLEvent} The event object.
13680      * @param oArgs.target {HTMLElement} The A element.
13681      */
13682
13683     /**
13684      * Fired when a BUTTON element or INPUT element of type "button", "image",
13685      * "submit", "reset" is clicked.
13686      *
13687      * @event buttonClickEvent
13688      * @param oArgs.event {HTMLEvent} The event object.
13689      * @param oArgs.target {HTMLElement} The BUTTON element.
13690      */
13691
13692     /**
13693      * Fired when a CHECKBOX element is clicked.
13694      *
13695      * @event checkboxClickEvent
13696      * @param oArgs.event {HTMLEvent} The event object.
13697      * @param oArgs.target {HTMLElement} The CHECKBOX element.
13698      */
13699
13700     /**
13701      * Fired when a SELECT element is changed.
13702      *
13703      * @event dropdownChangeEvent
13704      * @param oArgs.event {HTMLEvent} The event object.
13705      * @param oArgs.target {HTMLElement} The SELECT element.
13706      */
13707
13708     /**
13709      * Fired when a RADIO element is clicked.
13710      *
13711      * @event radioClickEvent
13712      * @param oArgs.event {HTMLEvent} The event object.
13713      * @param oArgs.target {HTMLElement} The RADIO element.
13714      */
13715
13716
13717
13718
13719
13720
13721
13722
13723
13724
13725
13726
13727
13728
13729
13730
13731
13732
13733
13734
13735
13736
13737
13738
13739
13740
13741 /////////////////////////////////////////////////////////////////////////////
13742 //
13743 // Deprecated APIs
13744 //
13745 /////////////////////////////////////////////////////////////////////////////
13746   
13747 /*
13748  * @method showCellEditorBtns
13749  * @deprecated Use CellEditor.renderBtns() 
13750  */
13751 showCellEditorBtns : function(elContainer) {
13752     // Buttons
13753     var elBtnsDiv = elContainer.appendChild(document.createElement("div"));
13754     Dom.addClass(elBtnsDiv, DT.CLASS_BUTTON);
13755
13756     // Save button
13757     var elSaveBtn = elBtnsDiv.appendChild(document.createElement("button"));
13758     Dom.addClass(elSaveBtn, DT.CLASS_DEFAULT);
13759     elSaveBtn.innerHTML = "OK";
13760     Ev.addListener(elSaveBtn, "click", function(oArgs, oSelf) {
13761         oSelf.onEventSaveCellEditor(oArgs, oSelf);
13762         oSelf.focusTbodyEl();
13763     }, this, true);
13764
13765     // Cancel button
13766     var elCancelBtn = elBtnsDiv.appendChild(document.createElement("button"));
13767     elCancelBtn.innerHTML = "Cancel";
13768     Ev.addListener(elCancelBtn, "click", function(oArgs, oSelf) {
13769         oSelf.onEventCancelCellEditor(oArgs, oSelf);
13770         oSelf.focusTbodyEl();
13771     }, this, true);
13772
13773 },
13774
13775 /**
13776  * @method resetCellEditor
13777  * @deprecated Use destroyCellEditor 
13778  */
13779 resetCellEditor : function() {
13780     var elContainer = this._oCellEditor.container;
13781     elContainer.style.display = "none";
13782     Ev.purgeElement(elContainer, true);
13783     elContainer.innerHTML = "";
13784     this._oCellEditor.value = null;
13785     this._oCellEditor.isActive = false;
13786
13787 },
13788
13789 /**
13790  * @event editorUpdateEvent
13791  * @deprecated Use CellEditor class.
13792  */
13793
13794 /**
13795  * @method getBody
13796  * @deprecated Use getTbodyEl().
13797  */
13798 getBody : function() {
13799     // Backward compatibility
13800     return this.getTbodyEl();
13801 },
13802
13803 /**
13804  * @method getCell
13805  * @deprecated Use getTdEl().
13806  */
13807 getCell : function(index) {
13808     // Backward compatibility
13809     return this.getTdEl(index);
13810 },
13811
13812 /**
13813  * @method getRow
13814  * @deprecated Use getTrEl().
13815  */
13816 getRow : function(index) {
13817     // Backward compatibility
13818     return this.getTrEl(index);
13819 },
13820
13821 /**
13822  * @method refreshView
13823  * @deprecated Use render.
13824  */
13825 refreshView : function() {
13826     // Backward compatibility
13827     this.render();
13828 },
13829
13830 /**
13831  * @method select
13832  * @deprecated Use selectRow.
13833  */
13834 select : function(els) {
13835     // Backward compatibility
13836     if(!lang.isArray(els)) {
13837         els = [els];
13838     }
13839     for(var i=0; i<els.length; i++) {
13840         this.selectRow(els[i]);
13841     }
13842 },
13843
13844 /**
13845  * @method onEventEditCell
13846  * @deprecated Use onEventShowCellEditor.
13847  */
13848 onEventEditCell : function(oArgs) {
13849     // Backward compatibility
13850     this.onEventShowCellEditor(oArgs);
13851 },
13852
13853 /**
13854  * @method _syncColWidths
13855  * @deprecated Use validateColumnWidths.
13856  */
13857 _syncColWidths : function() {
13858     // Backward compatibility
13859     this.validateColumnWidths();
13860 }
13861
13862 /**
13863  * @event headerRowMouseoverEvent
13864  * @deprecated Use theadRowMouseoverEvent.
13865  */
13866
13867 /**
13868  * @event headerRowMouseoutEvent
13869  * @deprecated Use theadRowMouseoutEvent.
13870  */
13871
13872 /**
13873  * @event headerRowMousedownEvent
13874  * @deprecated Use theadRowMousedownEvent.
13875  */
13876
13877 /**
13878  * @event headerRowClickEvent
13879  * @deprecated Use theadRowClickEvent.
13880  */
13881
13882 /**
13883  * @event headerRowDblclickEvent
13884  * @deprecated Use theadRowDblclickEvent.
13885  */
13886
13887 /**
13888  * @event headerCellMouseoverEvent
13889  * @deprecated Use theadCellMouseoverEvent.
13890  */
13891
13892 /**
13893  * @event headerCellMouseoutEvent
13894  * @deprecated Use theadCellMouseoutEvent.
13895  */
13896
13897 /**
13898  * @event headerCellMousedownEvent
13899  * @deprecated Use theadCellMousedownEvent.
13900  */
13901
13902 /**
13903  * @event headerCellClickEvent
13904  * @deprecated Use theadCellClickEvent.
13905  */
13906
13907 /**
13908  * @event headerCellDblclickEvent
13909  * @deprecated Use theadCellDblclickEvent.
13910  */
13911
13912 /**
13913  * @event headerLabelMouseoverEvent
13914  * @deprecated Use theadLabelMouseoverEvent.
13915  */
13916
13917 /**
13918  * @event headerLabelMouseoutEvent
13919  * @deprecated Use theadLabelMouseoutEvent.
13920  */
13921
13922 /**
13923  * @event headerLabelMousedownEvent
13924  * @deprecated Use theadLabelMousedownEvent.
13925  */
13926
13927 /**
13928  * @event headerLabelClickEvent
13929  * @deprecated Use theadLabelClickEvent.
13930  */
13931
13932 /**
13933  * @event headerLabelDbllickEvent
13934  * @deprecated Use theadLabelDblclickEvent.
13935  */
13936
13937 });
13938
13939 /**
13940  * Alias for onDataReturnSetRows for backward compatibility
13941  * @method onDataReturnSetRecords
13942  * @deprecated Use onDataReturnSetRows
13943  */
13944 DT.prototype.onDataReturnSetRecords = DT.prototype.onDataReturnSetRows;
13945
13946 /**
13947  * Alias for onPaginatorChange for backward compatibility
13948  * @method onPaginatorChange
13949  * @deprecated Use onPaginatorChangeRequest
13950  */
13951 DT.prototype.onPaginatorChange = DT.prototype.onPaginatorChangeRequest;
13952
13953 /////////////////////////////////////////////////////////////////////////////
13954 //
13955 // Deprecated static APIs
13956 //
13957 /////////////////////////////////////////////////////////////////////////////
13958 /**
13959  * @method DataTable.formatTheadCell
13960  * @deprecated  Use formatTheadCell.
13961  */
13962 DT.formatTheadCell = function() {};
13963
13964 /**
13965  * @method DataTable.editCheckbox
13966  * @deprecated  Use YAHOO.widget.CheckboxCellEditor.
13967  */
13968 DT.editCheckbox = function() {};
13969
13970 /**
13971  * @method DataTable.editDate
13972  * @deprecated Use YAHOO.widget.DateCellEditor.
13973  */
13974 DT.editDate = function() {};
13975
13976 /**
13977  * @method DataTable.editDropdown
13978  * @deprecated Use YAHOO.widget.DropdownCellEditor.
13979  */
13980 DT.editDropdown = function() {};
13981
13982 /**
13983  * @method DataTable.editRadio
13984  * @deprecated Use YAHOO.widget.RadioCellEditor.
13985  */
13986 DT.editRadio = function() {};
13987
13988 /**
13989  * @method DataTable.editTextarea
13990  * @deprecated Use YAHOO.widget.TextareaCellEditor
13991  */
13992 DT.editTextarea = function() {};
13993
13994 /**
13995  * @method DataTable.editTextbox
13996  * @deprecated Use YAHOO.widget.TextboxCellEditor
13997  */
13998 DT.editTextbox= function() {};
13999
14000 })();
14001
14002 (function () {
14003
14004 var lang   = YAHOO.lang,
14005     util   = YAHOO.util,
14006     widget = YAHOO.widget,
14007     ua     = YAHOO.env.ua,
14008     
14009     Dom    = util.Dom,
14010     Ev     = util.Event,
14011     DS     = util.DataSourceBase,
14012     DT     = widget.DataTable,
14013     Pag    = widget.Paginator;
14014     
14015 /**
14016  * The ScrollingDataTable class extends the DataTable class to provide
14017  * functionality for x-scrolling, y-scrolling, and xy-scrolling.
14018  *
14019  * @namespace YAHOO.widget
14020  * @class ScrollingDataTable
14021  * @extends YAHOO.widget.DataTable
14022  * @constructor
14023  * @param elContainer {HTMLElement} Container element for the TABLE.
14024  * @param aColumnDefs {Object[]} Array of object literal Column definitions.
14025  * @param oDataSource {YAHOO.util.DataSource} DataSource instance.
14026  * @param oConfigs {object} (optional) Object literal of configuration values.
14027  */
14028 widget.ScrollingDataTable = function(elContainer,aColumnDefs,oDataSource,oConfigs) {
14029     oConfigs = oConfigs || {};
14030     
14031     // Prevent infinite loop
14032     if(oConfigs.scrollable) {
14033         oConfigs.scrollable = false;
14034     }
14035
14036     widget.ScrollingDataTable.superclass.constructor.call(this, elContainer,aColumnDefs,oDataSource,oConfigs); 
14037
14038     // Once per instance
14039     this.subscribe("columnShowEvent", this._onColumnChange);
14040 };
14041
14042 var SDT = widget.ScrollingDataTable;
14043
14044 /////////////////////////////////////////////////////////////////////////////
14045 //
14046 // Public constants
14047 //
14048 /////////////////////////////////////////////////////////////////////////////
14049 lang.augmentObject(SDT, {
14050
14051     /**
14052      * Class name assigned to inner DataTable header container.
14053      *
14054      * @property DataTable.CLASS_HEADER
14055      * @type String
14056      * @static
14057      * @final
14058      * @default "yui-dt-hd"
14059      */
14060     CLASS_HEADER : "yui-dt-hd",
14061     
14062     /**
14063      * Class name assigned to inner DataTable body container.
14064      *
14065      * @property DataTable.CLASS_BODY
14066      * @type String
14067      * @static
14068      * @final
14069      * @default "yui-dt-bd"
14070      */
14071     CLASS_BODY : "yui-dt-bd"
14072 });
14073
14074 lang.extend(SDT, DT, {
14075
14076 /**
14077  * Container for fixed header TABLE element.
14078  *
14079  * @property _elHdContainer
14080  * @type HTMLElement
14081  * @private
14082  */
14083 _elHdContainer : null,
14084
14085 /**
14086  * Fixed header TABLE element.
14087  *
14088  * @property _elHdTable
14089  * @type HTMLElement
14090  * @private
14091  */
14092 _elHdTable : null,
14093
14094 /**
14095  * Container for scrolling body TABLE element.
14096  *
14097  * @property _elBdContainer
14098  * @type HTMLElement
14099  * @private
14100  */
14101 _elBdContainer : null,
14102
14103 /**
14104  * Body THEAD element.
14105  *
14106  * @property _elBdThead
14107  * @type HTMLElement
14108  * @private
14109  */
14110 _elBdThead : null,
14111
14112 /**
14113  * Offscreen container to temporarily clone SDT for auto-width calculation.
14114  *
14115  * @property _elTmpContainer
14116  * @type HTMLElement
14117  * @private
14118  */
14119 _elTmpContainer : null,
14120
14121 /**
14122  * Offscreen TABLE element for auto-width calculation.
14123  *
14124  * @property _elTmpTable
14125  * @type HTMLElement
14126  * @private
14127  */
14128 _elTmpTable : null,
14129
14130 /**
14131  * True if x-scrollbar is currently visible.
14132  * @property _bScrollbarX
14133  * @type Boolean
14134  * @private 
14135  */
14136 _bScrollbarX : null,
14137
14138
14139
14140
14141
14142
14143
14144
14145
14146
14147
14148
14149
14150
14151
14152 /////////////////////////////////////////////////////////////////////////////
14153 //
14154 // Superclass methods
14155 //
14156 /////////////////////////////////////////////////////////////////////////////
14157
14158 /**
14159  * Implementation of Element's abstract method. Sets up config values.
14160  *
14161  * @method initAttributes
14162  * @param oConfigs {Object} (Optional) Object literal definition of configuration values.
14163  * @private
14164  */
14165
14166 initAttributes : function(oConfigs) {
14167     oConfigs = oConfigs || {};
14168     SDT.superclass.initAttributes.call(this, oConfigs);
14169
14170     /**
14171     * @attribute width
14172     * @description Table width for scrollable tables (e.g., "40em").
14173     * @type String
14174     */
14175     this.setAttributeConfig("width", {
14176         value: null,
14177         validator: lang.isString,
14178         method: function(oParam) {
14179             if(this._elHdContainer && this._elBdContainer) {
14180                 this._elHdContainer.style.width = oParam;
14181                 this._elBdContainer.style.width = oParam;            
14182                 this._syncScrollX();      
14183                 this._syncScrollOverhang();
14184             }
14185         }
14186     });
14187
14188     /**
14189     * @attribute height
14190     * @description Table body height for scrollable tables, not including headers (e.g., "40em").
14191     * @type String
14192     */
14193     this.setAttributeConfig("height", {
14194         value: null,
14195         validator: lang.isString,
14196         method: function(oParam) {
14197             if(this._elHdContainer && this._elBdContainer) {
14198                 this._elBdContainer.style.height = oParam;    
14199                 this._syncScrollX();   
14200                 this._syncScrollY();
14201                 this._syncScrollOverhang();
14202             }
14203         }
14204     });
14205
14206     /**
14207     * @attribute COLOR_COLUMNFILLER
14208     * @description CSS color value assigned to header filler on scrollable tables.  
14209     * @type String
14210     * @default "#F2F2F2"
14211     */
14212     this.setAttributeConfig("COLOR_COLUMNFILLER", {
14213         value: "#F2F2F2",
14214         validator: lang.isString,
14215         method: function(oParam) {
14216             this._elHdContainer.style.backgroundColor = oParam;
14217         }
14218     });
14219 },
14220
14221 /**
14222  * Initializes DOM elements for a ScrollingDataTable, including creation of
14223  * two separate TABLE elements.
14224  *
14225  * @method _initDomElements
14226  * @param elContainer {HTMLElement | String} HTML DIV element by reference or ID. 
14227  * return {Boolean} False in case of error, otherwise true 
14228  * @private
14229  */
14230 _initDomElements : function(elContainer) {
14231     // Outer and inner containers
14232     this._initContainerEl(elContainer);
14233     if(this._elContainer && this._elHdContainer && this._elBdContainer) {
14234         // TABLEs
14235         this._initTableEl();
14236         
14237         if(this._elHdTable && this._elTable) {
14238             // COLGROUPs
14239             ///this._initColgroupEl(this._elHdTable, this._elTable);  
14240             this._initColgroupEl(this._elHdTable);        
14241             
14242             // THEADs
14243             this._initTheadEl(this._elHdTable, this._elTable);
14244             
14245             // Primary TBODY
14246             this._initTbodyEl(this._elTable);
14247             // Message TBODY
14248             this._initMsgTbodyEl(this._elTable);            
14249         }
14250     }
14251     if(!this._elContainer || !this._elTable || !this._elColgroup ||  !this._elThead || !this._elTbody || !this._elMsgTbody ||
14252             !this._elHdTable || !this._elBdThead) {
14253         return false;
14254     }
14255     else {
14256         return true;
14257     }
14258 },
14259
14260 /**
14261  * Destroy's the DataTable outer and inner container elements, if available.
14262  *
14263  * @method _destroyContainerEl
14264  * @param elContainer {HTMLElement} Reference to the container element. 
14265  * @private
14266  */
14267 _destroyContainerEl : function(elContainer) {
14268     Dom.removeClass(elContainer, DT.CLASS_SCROLLABLE);
14269     SDT.superclass._destroyContainerEl.call(this, elContainer);
14270     this._elHdContainer = null;
14271     this._elBdContainer = null;
14272 },
14273
14274 /**
14275  * Initializes the DataTable outer container element and creates inner header
14276  * and body container elements.
14277  *
14278  * @method _initContainerEl
14279  * @param elContainer {HTMLElement | String} HTML DIV element by reference or ID.
14280  * @private
14281  */
14282 _initContainerEl : function(elContainer) {
14283     SDT.superclass._initContainerEl.call(this, elContainer);
14284     
14285     if(this._elContainer) {
14286         elContainer = this._elContainer; // was constructor input, now is DOM ref
14287         Dom.addClass(elContainer, DT.CLASS_SCROLLABLE);
14288         
14289         // Container for header TABLE
14290         var elHdContainer = document.createElement("div");
14291         elHdContainer.style.width = this.get("width") || "";
14292         elHdContainer.style.backgroundColor = this.get("COLOR_COLUMNFILLER");
14293         Dom.addClass(elHdContainer, SDT.CLASS_HEADER);
14294         this._elHdContainer = elHdContainer;
14295         elContainer.appendChild(elHdContainer);
14296     
14297         // Container for body TABLE
14298         var elBdContainer = document.createElement("div");
14299         elBdContainer.style.width = this.get("width") || "";
14300         elBdContainer.style.height = this.get("height") || "";
14301         Dom.addClass(elBdContainer, SDT.CLASS_BODY);
14302         Ev.addListener(elBdContainer, "scroll", this._onScroll, this); // to sync horiz scroll headers
14303         this._elBdContainer = elBdContainer;
14304         elContainer.appendChild(elBdContainer);
14305     }
14306 },
14307
14308 /**
14309  * Creates HTML markup CAPTION element.
14310  *
14311  * @method _initCaptionEl
14312  * @param sCaption {String} Text for caption.
14313  * @private
14314  */
14315 _initCaptionEl : function(sCaption) {
14316     // Not yet supported
14317     /*if(this._elHdTable && sCaption) {
14318         // Create CAPTION element
14319         if(!this._elCaption) { 
14320             this._elCaption = this._elHdTable.createCaption();
14321         }
14322         // Set CAPTION value
14323         this._elCaption.innerHTML = sCaption;
14324     }
14325     else if(this._elCaption) {
14326         this._elCaption.parentNode.removeChild(this._elCaption);
14327     }*/
14328 },
14329
14330 /**
14331  * Destroy's the DataTable head TABLE element, if available.
14332  *
14333  * @method _destroyHdTableEl
14334  * @private
14335  */
14336 _destroyHdTableEl : function() {
14337     var elTable = this._elHdTable;
14338     if(elTable) {
14339         Ev.purgeElement(elTable, true);
14340         elTable.parentNode.removeChild(elTable);
14341         
14342         // A little out of place, but where else can we null out these extra elements?
14343         ///this._elBdColgroup = null;
14344         this._elBdThead = null;
14345     }
14346 },
14347
14348 /**
14349  * Initializes ScrollingDataTable TABLE elements into the two inner containers.
14350  *
14351  * @method _initTableEl
14352  * @private
14353  */
14354 _initTableEl : function() {
14355     // Head TABLE
14356     if(this._elHdContainer) {
14357         this._destroyHdTableEl();
14358     
14359         // Create TABLE
14360         this._elHdTable = this._elHdContainer.appendChild(document.createElement("table"));   
14361     } 
14362     // Body TABLE
14363     SDT.superclass._initTableEl.call(this, this._elBdContainer);
14364 },
14365
14366 /**
14367  * Initializes ScrollingDataTable THEAD elements into the two inner containers.
14368  *
14369  * @method _initTheadEl
14370  * @param elHdTable {HTMLElement} (optional) Fixed header TABLE element reference.
14371  * @param elTable {HTMLElement} (optional) TABLE element reference.
14372  * @private
14373  */
14374 _initTheadEl : function(elHdTable, elTable) {
14375     elHdTable = elHdTable || this._elHdTable;
14376     elTable = elTable || this._elTable;
14377     
14378     // Scrolling body's THEAD
14379     this._initBdTheadEl(elTable);
14380     // Standard fixed head THEAD
14381     SDT.superclass._initTheadEl.call(this, elHdTable);
14382 },
14383
14384 /**
14385  * SDT changes ID so as not to duplicate the accessibility TH IDs.
14386  *
14387  * @method _initThEl
14388  * @param elTh {HTMLElement} TH element reference.
14389  * @param oColumn {YAHOO.widget.Column} Column object.
14390  * @private
14391  */
14392 _initThEl : function(elTh, oColumn) {
14393     SDT.superclass._initThEl.call(this, elTh, oColumn);
14394     elTh.id = this.getId() +"-fixedth-" + oColumn.getSanitizedKey(); // Needed for getColumn by TH and ColumnDD
14395 },
14396
14397 /**
14398  * Destroy's the DataTable body THEAD element, if available.
14399  *
14400  * @method _destroyBdTheadEl
14401  * @private
14402  */
14403 _destroyBdTheadEl : function() {
14404     var elBdThead = this._elBdThead;
14405     if(elBdThead) {
14406         var elTable = elBdThead.parentNode;
14407         Ev.purgeElement(elBdThead, true);
14408         elTable.removeChild(elBdThead);
14409         this._elBdThead = null;
14410
14411         this._destroyColumnHelpers();
14412     }
14413 },
14414
14415 /**
14416  * Initializes body THEAD element.
14417  *
14418  * @method _initBdTheadEl
14419  * @param elTable {HTMLElement} TABLE element into which to create THEAD.
14420  * @return {HTMLElement} Initialized THEAD element. 
14421  * @private
14422  */
14423 _initBdTheadEl : function(elTable) {
14424     if(elTable) {
14425         // Destroy previous
14426         this._destroyBdTheadEl();
14427
14428         var elThead = elTable.insertBefore(document.createElement("thead"), elTable.firstChild);
14429         
14430         // Add TRs to the THEAD;
14431         var oColumnSet = this._oColumnSet,
14432             colTree = oColumnSet.tree,
14433             elTh, elTheadTr, oColumn, i, j, k, len;
14434
14435         for(i=0, k=colTree.length; i<k; i++) {
14436             elTheadTr = elThead.appendChild(document.createElement("tr"));
14437     
14438             // ...and create TH cells
14439             for(j=0, len=colTree[i].length; j<len; j++) {
14440                 oColumn = colTree[i][j];
14441                 elTh = elTheadTr.appendChild(document.createElement("th"));
14442                 this._initBdThEl(elTh,oColumn,i,j);
14443             }
14444         }
14445         this._elBdThead = elThead;
14446     }
14447 },
14448
14449 /**
14450  * Populates TH element for the body THEAD element.
14451  *
14452  * @method _initBdThEl
14453  * @param elTh {HTMLElement} TH element reference.
14454  * @param oColumn {YAHOO.widget.Column} Column object.
14455  * @private
14456  */
14457 _initBdThEl : function(elTh, oColumn) {
14458     elTh.id = this.getId()+"-th-" + oColumn.getSanitizedKey(); // Needed for accessibility
14459     elTh.rowSpan = oColumn.getRowspan();
14460     elTh.colSpan = oColumn.getColspan();
14461     // Assign abbr attribute
14462     if(oColumn.abbr) {
14463         elTh.abbr = oColumn.abbr;
14464     }
14465
14466     // TODO: strip links and form elements
14467     var sKey = oColumn.getKey();
14468     var sLabel = lang.isValue(oColumn.label) ? oColumn.label : sKey;
14469     elTh.innerHTML = sLabel;
14470 },
14471
14472 /**
14473  * Initializes ScrollingDataTable TBODY element for data
14474  *
14475  * @method _initTbodyEl
14476  * @param elTable {HTMLElement} TABLE element into which to create TBODY .
14477  * @private
14478  */
14479 _initTbodyEl : function(elTable) {
14480     SDT.superclass._initTbodyEl.call(this, elTable);
14481     
14482     // Bug 2105534 - Safari 3 gap
14483     // Bug 2492591 - IE8 offsetTop
14484     elTable.style.marginTop = (this._elTbody.offsetTop > 0) ?
14485             "-"+this._elTbody.offsetTop+"px" : 0;
14486 },
14487
14488
14489
14490
14491
14492
14493
14494
14495
14496
14497
14498
14499
14500
14501
14502
14503
14504
14505
14506
14507
14508
14509
14510
14511
14512
14513
14514
14515
14516 /**
14517  * Sets focus on the given element.
14518  *
14519  * @method _focusEl
14520  * @param el {HTMLElement} Element.
14521  * @private
14522  */
14523 _focusEl : function(el) {
14524     el = el || this._elTbody;
14525     var oSelf = this;
14526     this._storeScrollPositions();
14527     // http://developer.mozilla.org/en/docs/index.php?title=Key-navigable_custom_DHTML_widgets
14528     // The timeout is necessary in both IE and Firefox 1.5, to prevent scripts from doing
14529     // strange unexpected things as the user clicks on buttons and other controls.
14530     
14531     // Bug 1921135: Wrap the whole thing in a setTimeout
14532     setTimeout(function() {
14533         setTimeout(function() {
14534             try {
14535                 el.focus();
14536                 oSelf._restoreScrollPositions();
14537             }
14538             catch(e) {
14539             }
14540         },0);
14541     }, 0);
14542 },
14543
14544
14545
14546
14547
14548
14549
14550
14551
14552
14553
14554
14555
14556
14557
14558
14559
14560
14561
14562 /**
14563  * Internal wrapper calls run() on render Chain instance.
14564  *
14565  * @method _runRenderChain
14566  * @private 
14567  */
14568 _runRenderChain : function() {
14569     this._storeScrollPositions();
14570     this._oChainRender.run();
14571 },
14572
14573 /**
14574  * Stores scroll positions so they can be restored after a render. 
14575  *
14576  * @method _storeScrollPositions
14577  * @private 
14578  */
14579  _storeScrollPositions : function() {
14580     this._nScrollTop = this._elBdContainer.scrollTop;
14581     this._nScrollLeft = this._elBdContainer.scrollLeft;
14582 },
14583
14584 /**
14585  * Restores scroll positions to stored value. 
14586  *
14587  * @method _retoreScrollPositions
14588  * @private 
14589  */
14590  _restoreScrollPositions : function() {
14591     // Reset scroll positions
14592     if(this._nScrollTop) {
14593         this._elBdContainer.scrollTop = this._nScrollTop;
14594         this._nScrollTop = null;
14595     } 
14596     if(this._nScrollLeft) {
14597         this._elBdContainer.scrollLeft = this._nScrollLeft;
14598         this._nScrollLeft = null;
14599     } 
14600 },
14601
14602 /**
14603  * Helper function calculates and sets a validated width for a Column in a ScrollingDataTable.
14604  *
14605  * @method _validateColumnWidth
14606  * @param oColumn {YAHOO.widget.Column} Column instance.
14607  * @param elTd {HTMLElement} TD element to validate against.
14608  * @private
14609  */
14610 _validateColumnWidth : function(oColumn, elTd) {
14611     // Only Columns without widths that are not hidden
14612     if(!oColumn.width && !oColumn.hidden) {
14613         var elTh = oColumn.getThEl();
14614         // Unset a calculated auto-width
14615         if(oColumn._calculatedWidth) {
14616             this._setColumnWidth(oColumn, "auto", "visible");
14617         }
14618         // Compare auto-widths
14619         if(elTh.offsetWidth !== elTd.offsetWidth) {
14620             var elWider = (elTh.offsetWidth > elTd.offsetWidth) ?
14621                     oColumn.getThLinerEl() : elTd.firstChild;               
14622
14623             // Grab the wider liner width, unless the minWidth is wider
14624             var newWidth = Math.max(0,
14625                 (elWider.offsetWidth -(parseInt(Dom.getStyle(elWider,"paddingLeft"),10)|0) - (parseInt(Dom.getStyle(elWider,"paddingRight"),10)|0)),
14626                 oColumn.minWidth);
14627                 
14628             var sOverflow = 'visible';
14629             
14630             // Now validate against maxAutoWidth
14631             if((oColumn.maxAutoWidth > 0) && (newWidth > oColumn.maxAutoWidth)) {
14632                 newWidth = oColumn.maxAutoWidth;
14633                 sOverflow = "hidden";
14634             }
14635
14636             // Set to the wider auto-width
14637             this._elTbody.style.display = "none";
14638             this._setColumnWidth(oColumn, newWidth+'px', sOverflow);
14639             oColumn._calculatedWidth = newWidth;
14640             this._elTbody.style.display = "";
14641         }
14642     }
14643 },
14644
14645 /**
14646  * For one or all Columns of a ScrollingDataTable, when Column is not hidden,
14647  * and width is not set, syncs widths of header and body cells and 
14648  * validates that width against minWidth and/or maxAutoWidth as necessary.
14649  *
14650  * @method validateColumnWidths
14651  * @param oArg.column {YAHOO.widget.Column} (optional) One Column to validate. If null, all Columns' widths are validated.
14652  */
14653 validateColumnWidths : function(oColumn) {
14654     // Validate there is at least one TR with proper TDs
14655     var allKeys   = this._oColumnSet.keys,
14656         allKeysLength = allKeys.length,
14657         elRow     = this.getFirstTrEl();
14658
14659     // Reset overhang for IE
14660     if(ua.ie) {
14661         this._setOverhangValue(1);
14662     }
14663
14664     if(allKeys && elRow && (elRow.childNodes.length === allKeysLength)) {
14665         // Temporarily unsnap container since it causes inaccurate calculations
14666         var sWidth = this.get("width");
14667         if(sWidth) {
14668             this._elHdContainer.style.width = "";
14669             this._elBdContainer.style.width = "";
14670         }
14671         this._elContainer.style.width = "";
14672         
14673         //Validate just one Column
14674         if(oColumn && lang.isNumber(oColumn.getKeyIndex())) {
14675             this._validateColumnWidth(oColumn, elRow.childNodes[oColumn.getKeyIndex()]);
14676         }
14677         // Iterate through all Columns to unset calculated widths in one pass
14678         else {
14679             var elTd, todos = [], thisTodo, i, len;
14680             for(i=0; i<allKeysLength; i++) {
14681                 oColumn = allKeys[i];
14682                 // Only Columns without widths that are not hidden, unset a calculated auto-width
14683                 if(!oColumn.width && !oColumn.hidden && oColumn._calculatedWidth) {
14684                     todos[todos.length] = oColumn;      
14685                 }
14686             }
14687             
14688             this._elTbody.style.display = "none";
14689             for(i=0, len=todos.length; i<len; i++) {
14690                 this._setColumnWidth(todos[i], "auto", "visible");
14691             }
14692             this._elTbody.style.display = "";
14693             
14694             todos = [];
14695
14696             // Iterate through all Columns and make the store the adjustments to make in one pass
14697             for(i=0; i<allKeysLength; i++) {
14698                 oColumn = allKeys[i];
14699                 elTd = elRow.childNodes[i];
14700                 // Only Columns without widths that are not hidden
14701                 if(!oColumn.width && !oColumn.hidden) {
14702                     var elTh = oColumn.getThEl();
14703
14704                     // Compare auto-widths
14705                     if(elTh.offsetWidth !== elTd.offsetWidth) {
14706                         var elWider = (elTh.offsetWidth > elTd.offsetWidth) ?
14707                                 oColumn.getThLinerEl() : elTd.firstChild;               
14708                 
14709                         // Grab the wider liner width, unless the minWidth is wider
14710                         var newWidth = Math.max(0,
14711                             (elWider.offsetWidth -(parseInt(Dom.getStyle(elWider,"paddingLeft"),10)|0) - (parseInt(Dom.getStyle(elWider,"paddingRight"),10)|0)),
14712                             oColumn.minWidth);
14713                             
14714                         var sOverflow = 'visible';
14715                         
14716                         // Now validate against maxAutoWidth
14717                         if((oColumn.maxAutoWidth > 0) && (newWidth > oColumn.maxAutoWidth)) {
14718                             newWidth = oColumn.maxAutoWidth;
14719                             sOverflow = "hidden";
14720                         }
14721                 
14722                         todos[todos.length] = [oColumn, newWidth, sOverflow];
14723                     }
14724                 }
14725             }
14726             
14727             this._elTbody.style.display = "none";
14728             for(i=0, len=todos.length; i<len; i++) {
14729                 thisTodo = todos[i];
14730                 // Set to the wider auto-width
14731                 this._setColumnWidth(thisTodo[0], thisTodo[1]+"px", thisTodo[2]);
14732                 thisTodo[0]._calculatedWidth = thisTodo[1];
14733             }
14734             this._elTbody.style.display = "";
14735         }
14736     
14737         // Resnap unsnapped containers
14738         if(sWidth) {
14739             this._elHdContainer.style.width = sWidth;
14740             this._elBdContainer.style.width = sWidth;
14741         } 
14742     }
14743     
14744     this._syncScroll();
14745     this._restoreScrollPositions();
14746 },
14747
14748 /**
14749  * Syncs padding around scrollable tables, including Column header right-padding
14750  * and container width and height.
14751  *
14752  * @method _syncScroll
14753  * @private 
14754  */
14755 _syncScroll : function() {
14756     this._syncScrollX();
14757     this._syncScrollY();
14758     this._syncScrollOverhang();
14759     if(ua.opera) {
14760         // Bug 1925874
14761         this._elHdContainer.scrollLeft = this._elBdContainer.scrollLeft;
14762         if(!this.get("width")) {
14763             // Bug 1926125
14764             document.body.style += '';
14765         }
14766     }
14767  },
14768
14769 /**
14770  * Snaps container width for y-scrolling tables.
14771  *
14772  * @method _syncScrollY
14773  * @private
14774  */
14775 _syncScrollY : function() {
14776     var elTbody = this._elTbody,
14777         elBdContainer = this._elBdContainer;
14778     
14779     // X-scrolling not enabled
14780     if(!this.get("width")) {
14781         // Snap outer container width to content
14782         this._elContainer.style.width = 
14783                 (elBdContainer.scrollHeight > elBdContainer.clientHeight) ?
14784                 // but account for y-scrollbar since it is visible
14785                 (elTbody.parentNode.clientWidth + 19) + "px" :
14786                 // no y-scrollbar, just borders
14787                 (elTbody.parentNode.clientWidth + 2) + "px";
14788     }
14789 },
14790
14791 /**
14792  * Snaps container height for x-scrolling tables in IE. Syncs message TBODY width.
14793  *
14794  * @method _syncScrollX
14795  * @private
14796  */
14797 _syncScrollX : function() {
14798     var elTbody = this._elTbody,
14799         elBdContainer = this._elBdContainer;
14800     
14801     // IE 6 and 7 only when y-scrolling not enabled
14802     if(!this.get("height") && (ua.ie)) {
14803         // Snap outer container height to content
14804         elBdContainer.style.height = 
14805                 // but account for x-scrollbar if it is visible
14806                 (elBdContainer.scrollWidth > elBdContainer.offsetWidth ) ?
14807                 (elTbody.parentNode.offsetHeight + 18) + "px" : 
14808                 elTbody.parentNode.offsetHeight + "px";
14809     }
14810
14811     // Sync message tbody
14812     if(this._elTbody.rows.length === 0) {
14813         this._elMsgTbody.parentNode.style.width = this.getTheadEl().parentNode.offsetWidth + "px";
14814     }
14815     else {
14816         this._elMsgTbody.parentNode.style.width = "";
14817     }
14818 },
14819
14820 /**
14821  * Adds/removes Column header overhang as necesary.
14822  *
14823  * @method _syncScrollOverhang
14824  * @private
14825  */
14826 _syncScrollOverhang : function() {
14827     var elBdContainer = this._elBdContainer,
14828         // Overhang should be either 1 (default) or 18px, depending on the location of the right edge of the table
14829         nPadding = 1;
14830     
14831     // Y-scrollbar is visible, which is when the overhang needs to jut out
14832     if((elBdContainer.scrollHeight > elBdContainer.clientHeight) &&
14833         // X-scrollbar is also visible, which means the right is jagged, not flush with the Column
14834         (elBdContainer.scrollWidth > elBdContainer.clientWidth)) {
14835         nPadding = 18;
14836     }
14837     
14838     this._setOverhangValue(nPadding);
14839     
14840 },
14841
14842 /**
14843  * Sets Column header overhang to given width.
14844  *
14845  * @method _setOverhangValue
14846  * @param nBorderWidth {Number} Value of new border for overhang. 
14847  * @private
14848  */
14849 _setOverhangValue : function(nBorderWidth) {
14850     var aLastHeaders = this._oColumnSet.headers[this._oColumnSet.headers.length-1] || [],
14851         len = aLastHeaders.length,
14852         sPrefix = this._sId+"-fixedth-",
14853         sValue = nBorderWidth + "px solid " + this.get("COLOR_COLUMNFILLER");
14854
14855     this._elThead.style.display = "none";
14856     for(var i=0; i<len; i++) {
14857         Dom.get(sPrefix+aLastHeaders[i]).style.borderRight = sValue;
14858     }
14859     this._elThead.style.display = "";
14860 },
14861
14862
14863
14864
14865
14866
14867
14868
14869
14870
14871
14872
14873
14874
14875
14876
14877
14878
14879
14880
14881
14882
14883
14884
14885
14886
14887
14888
14889
14890
14891
14892
14893
14894
14895
14896
14897
14898
14899 /**
14900  * Returns DOM reference to the DataTable's fixed header container element.
14901  *
14902  * @method getHdContainerEl
14903  * @return {HTMLElement} Reference to DIV element.
14904  */
14905 getHdContainerEl : function() {
14906     return this._elHdContainer;
14907 },
14908
14909 /**
14910  * Returns DOM reference to the DataTable's scrolling body container element.
14911  *
14912  * @method getBdContainerEl
14913  * @return {HTMLElement} Reference to DIV element.
14914  */
14915 getBdContainerEl : function() {
14916     return this._elBdContainer;
14917 },
14918
14919 /**
14920  * Returns DOM reference to the DataTable's fixed header TABLE element.
14921  *
14922  * @method getHdTableEl
14923  * @return {HTMLElement} Reference to TABLE element.
14924  */
14925 getHdTableEl : function() {
14926     return this._elHdTable;
14927 },
14928
14929 /**
14930  * Returns DOM reference to the DataTable's scrolling body TABLE element.
14931  *
14932  * @method getBdTableEl
14933  * @return {HTMLElement} Reference to TABLE element.
14934  */
14935 getBdTableEl : function() {
14936     return this._elTable;
14937 },
14938
14939 /**
14940  * Disables ScrollingDataTable UI.
14941  *
14942  * @method disable
14943  */
14944 disable : function() {
14945     var elMask = this._elMask;
14946     elMask.style.width = this._elBdContainer.offsetWidth + "px";
14947     elMask.style.height = this._elHdContainer.offsetHeight + this._elBdContainer.offsetHeight + "px";
14948     elMask.style.display = "";
14949     this.fireEvent("disableEvent");
14950 },
14951
14952 /**
14953  * Removes given Column. NOTE: You cannot remove nested Columns. You can only remove
14954  * non-nested Columns, and top-level parent Columns (which will remove all
14955  * children Columns).
14956  *
14957  * @method removeColumn
14958  * @param oColumn {YAHOO.widget.Column} Column instance.
14959  * @return oColumn {YAHOO.widget.Column} Removed Column instance.
14960  */
14961 removeColumn : function(oColumn) {
14962     // Store scroll pos
14963     var hdPos = this._elHdContainer.scrollLeft;
14964     var bdPos = this._elBdContainer.scrollLeft;
14965     
14966     // Call superclass method
14967     oColumn = SDT.superclass.removeColumn.call(this, oColumn);
14968     
14969     // Restore scroll pos
14970     this._elHdContainer.scrollLeft = hdPos;
14971     this._elBdContainer.scrollLeft = bdPos;
14972     
14973     return oColumn;
14974 },
14975
14976 /**
14977  * Inserts given Column at the index if given, otherwise at the end. NOTE: You
14978  * can only add non-nested Columns and top-level parent Columns. You cannot add
14979  * a nested Column to an existing parent.
14980  *
14981  * @method insertColumn
14982  * @param oColumn {Object | YAHOO.widget.Column} Object literal Column
14983  * definition or a Column instance.
14984  * @param index {Number} (optional) New tree index.
14985  * @return oColumn {YAHOO.widget.Column} Inserted Column instance. 
14986  */
14987 insertColumn : function(oColumn, index) {
14988     // Store scroll pos
14989     var hdPos = this._elHdContainer.scrollLeft;
14990     var bdPos = this._elBdContainer.scrollLeft;
14991     
14992     // Call superclass method
14993     var oNewColumn = SDT.superclass.insertColumn.call(this, oColumn, index);
14994     
14995     // Restore scroll pos
14996     this._elHdContainer.scrollLeft = hdPos;
14997     this._elBdContainer.scrollLeft = bdPos;
14998     
14999     return oNewColumn;
15000 },
15001
15002 /**
15003  * Removes given Column and inserts into given tree index. NOTE: You
15004  * can only reorder non-nested Columns and top-level parent Columns. You cannot
15005  * reorder a nested Column to an existing parent.
15006  *
15007  * @method reorderColumn
15008  * @param oColumn {YAHOO.widget.Column} Column instance.
15009  * @param index {Number} New tree index.
15010  */
15011 reorderColumn : function(oColumn, index) {
15012     // Store scroll pos
15013     var hdPos = this._elHdContainer.scrollLeft;
15014     var bdPos = this._elBdContainer.scrollLeft;
15015     
15016     // Call superclass method
15017     var oNewColumn = SDT.superclass.reorderColumn.call(this, oColumn, index);
15018     
15019     // Restore scroll pos
15020     this._elHdContainer.scrollLeft = hdPos;
15021     this._elBdContainer.scrollLeft = bdPos;
15022
15023     return oNewColumn;
15024 },
15025
15026 /**
15027  * Sets given Column to given pixel width. If new width is less than minWidth
15028  * width, sets to minWidth. Updates oColumn.width value.
15029  *
15030  * @method setColumnWidth
15031  * @param oColumn {YAHOO.widget.Column} Column instance.
15032  * @param nWidth {Number} New width in pixels.
15033  */
15034 setColumnWidth : function(oColumn, nWidth) {
15035     oColumn = this.getColumn(oColumn);
15036     if(oColumn) {
15037         // Validate new width against minWidth
15038         if(lang.isNumber(nWidth)) {
15039             nWidth = (nWidth > oColumn.minWidth) ? nWidth : oColumn.minWidth;
15040
15041             // Save state
15042             oColumn.width = nWidth;
15043             
15044             // Resize the DOM elements
15045             this._setColumnWidth(oColumn, nWidth+"px");
15046             this._syncScroll();
15047             
15048             this.fireEvent("columnSetWidthEvent",{column:oColumn,width:nWidth});
15049         }
15050         // Unsets a width to auto-size
15051         else if(nWidth === null) {
15052             // Save state
15053             oColumn.width = nWidth;
15054             
15055             // Resize the DOM elements
15056             this._setColumnWidth(oColumn, "auto");
15057             this.validateColumnWidths(oColumn);
15058             this.fireEvent("columnUnsetWidthEvent",{column:oColumn});
15059         }
15060         
15061         // Bug 2339454: resize then sort misaligment
15062         this._clearTrTemplateEl();
15063     }
15064     else {
15065     }
15066 },
15067
15068 /**
15069  * Displays message within secondary TBODY.
15070  *
15071  * @method showTableMessage
15072  * @param sHTML {String} (optional) Value for innerHTMlang.
15073  * @param sClassName {String} (optional) Classname.
15074  */
15075 showTableMessage : function(sHTML, sClassName) {
15076     var elCell = this._elMsgTd;
15077     if(lang.isString(sHTML)) {
15078         elCell.firstChild.innerHTML = sHTML;
15079     }
15080     if(lang.isString(sClassName)) {
15081         Dom.addClass(elCell.firstChild, sClassName);
15082     }
15083
15084     // Needed for SDT only
15085     var elThead = this.getTheadEl();
15086     var elTable = elThead.parentNode;
15087     var newWidth = elTable.offsetWidth;
15088     this._elMsgTbody.parentNode.style.width = this.getTheadEl().parentNode.offsetWidth + "px";
15089
15090     this._elMsgTbody.style.display = "";
15091
15092     this.fireEvent("tableMsgShowEvent", {html:sHTML, className:sClassName});
15093 },
15094
15095
15096
15097
15098
15099
15100
15101
15102
15103
15104
15105
15106
15107 /////////////////////////////////////////////////////////////////////////////
15108 //
15109 // Private Custom Event Handlers
15110 //
15111 /////////////////////////////////////////////////////////////////////////////
15112
15113 /**
15114  * Handles Column mutations
15115  *
15116  * @method onColumnChange
15117  * @param oArgs {Object} Custom Event data.
15118  */
15119 _onColumnChange : function(oArg) {
15120     // Figure out which Column changed
15121     var oColumn = (oArg.column) ? oArg.column :
15122             (oArg.editor) ? oArg.editor.column : null;
15123     this._storeScrollPositions();
15124     this.validateColumnWidths(oColumn);
15125 },
15126
15127
15128
15129
15130
15131
15132
15133
15134
15135
15136
15137
15138
15139
15140
15141 /////////////////////////////////////////////////////////////////////////////
15142 //
15143 // Private DOM Event Handlers
15144 //
15145 /////////////////////////////////////////////////////////////////////////////
15146
15147 /**
15148  * Syncs scrolltop and scrollleft of all TABLEs.
15149  *
15150  * @method _onScroll
15151  * @param e {HTMLEvent} The scroll event.
15152  * @param oSelf {YAHOO.widget.ScrollingDataTable} ScrollingDataTable instance.
15153  * @private
15154  */
15155 _onScroll : function(e, oSelf) {
15156     oSelf._elHdContainer.scrollLeft = oSelf._elBdContainer.scrollLeft;
15157
15158     if(oSelf._oCellEditor && oSelf._oCellEditor.isActive) {
15159         oSelf.fireEvent("editorBlurEvent", {editor:oSelf._oCellEditor});
15160         oSelf.cancelCellEditor();
15161     }
15162
15163     var elTarget = Ev.getTarget(e);
15164     var elTag = elTarget.nodeName.toLowerCase();
15165     oSelf.fireEvent("tableScrollEvent", {event:e, target:elTarget});
15166 },
15167
15168 /**
15169  * Handles keydown events on the THEAD element.
15170  *
15171  * @method _onTheadKeydown
15172  * @param e {HTMLEvent} The key event.
15173  * @param oSelf {YAHOO.widget.ScrollingDataTable} ScrollingDataTable instance.
15174  * @private
15175  */
15176 _onTheadKeydown : function(e, oSelf) {
15177     // If tabbing to next TH label link causes THEAD to scroll,
15178     // need to sync scrollLeft with TBODY
15179     if(Ev.getCharCode(e) === 9) {
15180         setTimeout(function() {
15181             if((oSelf instanceof SDT) && oSelf._sId) {
15182                 oSelf._elBdContainer.scrollLeft = oSelf._elHdContainer.scrollLeft;
15183             }
15184         },0);
15185     }
15186     
15187     var elTarget = Ev.getTarget(e);
15188     var elTag = elTarget.nodeName.toLowerCase();
15189     var bKeepBubbling = true;
15190     while(elTarget && (elTag != "table")) {
15191         switch(elTag) {
15192             case "body":
15193                 return;
15194             case "input":
15195             case "textarea":
15196                 // TODO: implement textareaKeyEvent
15197                 break;
15198             case "thead":
15199                 bKeepBubbling = oSelf.fireEvent("theadKeyEvent",{target:elTarget,event:e});
15200                 break;
15201             default:
15202                 break;
15203         }
15204         if(bKeepBubbling === false) {
15205             return;
15206         }
15207         else {
15208             elTarget = elTarget.parentNode;
15209             if(elTarget) {
15210                 elTag = elTarget.nodeName.toLowerCase();
15211             }
15212         }
15213     }
15214     oSelf.fireEvent("tableKeyEvent",{target:(elTarget || oSelf._elContainer),event:e});
15215 }
15216
15217
15218
15219
15220 /**
15221  * Fired when a fixed scrolling DataTable has a scroll.
15222  *
15223  * @event tableScrollEvent
15224  * @param oArgs.event {HTMLEvent} The event object.
15225  * @param oArgs.target {HTMLElement} The DataTable's CONTAINER element (in IE)
15226  * or the DataTable's TBODY element (everyone else).
15227  *
15228  */
15229
15230
15231
15232
15233 });
15234
15235 })();
15236
15237 (function () {
15238
15239 var lang   = YAHOO.lang,
15240     util   = YAHOO.util,
15241     widget = YAHOO.widget,
15242     ua     = YAHOO.env.ua,
15243     
15244     Dom    = util.Dom,
15245     Ev     = util.Event,
15246     
15247     DT     = widget.DataTable;
15248 /****************************************************************************/
15249 /****************************************************************************/
15250 /****************************************************************************/
15251     
15252 /**
15253  * The BaseCellEditor class provides base functionality common to all inline cell
15254  * editors for a DataTable widget.
15255  *
15256  * @namespace YAHOO.widget
15257  * @class BaseCellEditor
15258  * @uses YAHOO.util.EventProvider 
15259  * @constructor
15260  * @param sType {String} Type indicator, to map to YAHOO.widget.DataTable.Editors.
15261  * @param oConfigs {Object} (Optional) Object literal of configs.
15262  */
15263 widget.BaseCellEditor = function(sType, oConfigs) {
15264     this._sId = this._sId || "yui-ceditor" + YAHOO.widget.BaseCellEditor._nCount++;
15265     this._sType = sType;
15266     
15267     // Validate inputs
15268     this._initConfigs(oConfigs); 
15269     
15270     // Create Custom Events
15271     this._initEvents();
15272              
15273     // Draw UI
15274     this.render();
15275 };
15276
15277 var BCE = widget.BaseCellEditor;
15278
15279 /////////////////////////////////////////////////////////////////////////////
15280 //
15281 // Static members
15282 //
15283 /////////////////////////////////////////////////////////////////////////////
15284 lang.augmentObject(BCE, {
15285
15286 /**
15287  * Global instance counter.
15288  *
15289  * @property CellEditor._nCount
15290  * @type Number
15291  * @static
15292  * @default 0
15293  * @private 
15294  */
15295 _nCount : 0,
15296
15297 /**
15298  * Class applied to CellEditor container.
15299  *
15300  * @property CellEditor.CLASS_CELLEDITOR
15301  * @type String
15302  * @static
15303  * @default "yui-ceditor"
15304  */
15305 CLASS_CELLEDITOR : "yui-ceditor"
15306
15307 });
15308
15309 BCE.prototype = {
15310 /////////////////////////////////////////////////////////////////////////////
15311 //
15312 // Private members
15313 //
15314 /////////////////////////////////////////////////////////////////////////////
15315 /**
15316  * Unique id assigned to instance "yui-ceditorN", useful prefix for generating unique
15317  * DOM ID strings and log messages.
15318  *
15319  * @property _sId
15320  * @type String
15321  * @private
15322  */
15323 _sId : null,
15324
15325 /**
15326  * Editor type.
15327  *
15328  * @property _sType
15329  * @type String
15330  * @private
15331  */
15332 _sType : null,
15333
15334 /**
15335  * DataTable instance.
15336  *
15337  * @property _oDataTable
15338  * @type YAHOO.widget.DataTable
15339  * @private 
15340  */
15341 _oDataTable : null,
15342
15343 /**
15344  * Column instance.
15345  *
15346  * @property _oColumn
15347  * @type YAHOO.widget.Column
15348  * @default null
15349  */
15350 _oColumn : null,
15351
15352 /**
15353  * Record instance.
15354  *
15355  * @property _oRecord
15356  * @type YAHOO.widget.Record
15357  * @default null
15358  * @private 
15359  */
15360 _oRecord : null,
15361
15362 /**
15363  * TD element.
15364  *
15365  * @property _elTd
15366  * @type HTMLElement
15367  * @default null
15368  * @private
15369  */
15370 _elTd : null,
15371
15372 /**
15373  * Container for inline editor.
15374  *
15375  * @property _elContainer
15376  * @type HTMLElement
15377  * @private 
15378  */
15379 _elContainer : null,
15380
15381 /**
15382  * Reference to Cancel button, if available.
15383  *
15384  * @property _elCancelBtn
15385  * @type HTMLElement
15386  * @default null
15387  * @private 
15388  */
15389 _elCancelBtn : null,
15390
15391 /**
15392  * Reference to Save button, if available.
15393  *
15394  * @property _elSaveBtn
15395  * @type HTMLElement
15396  * @default null
15397  * @private 
15398  */
15399 _elSaveBtn : null,
15400
15401
15402
15403
15404
15405
15406
15407
15408 /////////////////////////////////////////////////////////////////////////////
15409 //
15410 // Private methods
15411 //
15412 /////////////////////////////////////////////////////////////////////////////
15413
15414 /**
15415  * Initialize configs.
15416  *
15417  * @method _initConfigs
15418  * @private   
15419  */
15420 _initConfigs : function(oConfigs) {
15421     // Object literal defines CellEditor configs
15422     if(oConfigs && YAHOO.lang.isObject(oConfigs)) {
15423         for(var sConfig in oConfigs) {
15424             if(sConfig) {
15425                 this[sConfig] = oConfigs[sConfig];
15426             }
15427         }
15428     }
15429 },
15430
15431 /**
15432  * Initialize Custom Events.
15433  *
15434  * @method _initEvents
15435  * @private   
15436  */
15437 _initEvents : function() {
15438     this.createEvent("showEvent");
15439     this.createEvent("keydownEvent");
15440     this.createEvent("invalidDataEvent");
15441     this.createEvent("revertEvent");
15442     this.createEvent("saveEvent");
15443     this.createEvent("cancelEvent");
15444     this.createEvent("blurEvent");
15445     this.createEvent("blockEvent");
15446     this.createEvent("unblockEvent");
15447 },
15448
15449
15450
15451
15452
15453
15454
15455
15456
15457
15458
15459
15460
15461 /////////////////////////////////////////////////////////////////////////////
15462 //
15463 // Public properties
15464 //
15465 /////////////////////////////////////////////////////////////////////////////
15466 /**
15467  * Implementer defined function that can submit the input value to a server. This
15468  * function must accept the arguments fnCallback and oNewValue. When the submission
15469  * is complete, the function must also call fnCallback(bSuccess, oNewValue) to 
15470  * finish the save routine in the CellEditor. This function can also be used to 
15471  * perform extra validation or input value manipulation. 
15472  *
15473  * @property asyncSubmitter
15474  * @type HTMLFunction
15475  */
15476 asyncSubmitter : null,
15477
15478 /**
15479  * Current value.
15480  *
15481  * @property value
15482  * @type MIXED
15483  */
15484 value : null,
15485
15486 /**
15487  * Default value in case Record data is undefined. NB: Null values will not trigger
15488  * the default value.
15489  *
15490  * @property defaultValue
15491  * @type MIXED
15492  * @default null
15493  */
15494 defaultValue : null,
15495
15496 /**
15497  * Validator function for input data, called from the DataTable instance scope,
15498  * receives the arguments (inputValue, currentValue, editorInstance) and returns
15499  * either the validated (or type-converted) value or undefined.
15500  *
15501  * @property validator
15502  * @type HTMLFunction
15503  * @default null
15504  */
15505 validator : null,
15506
15507 /**
15508  * If validation is enabled, resets input field of invalid data.
15509  *
15510  * @property resetInvalidData
15511  * @type Boolean
15512  * @default true
15513  */
15514 resetInvalidData : true,
15515
15516 /**
15517  * True if currently active.
15518  *
15519  * @property isActive
15520  * @type Boolean
15521  */
15522 isActive : false,
15523
15524 /**
15525  * Text to display on Save button.
15526  *
15527  * @property LABEL_SAVE
15528  * @type String
15529  * @default "Save"
15530  */
15531 LABEL_SAVE : "Save",
15532
15533 /**
15534  * Text to display on Cancel button.
15535  *
15536  * @property LABEL_CANCEL
15537  * @type String
15538  * @default "Cancel"
15539  */
15540 LABEL_CANCEL : "Cancel",
15541
15542 /**
15543  * True if Save/Cancel buttons should not be displayed in the CellEditor.
15544  *
15545  * @property disableBtns
15546  * @type Boolean
15547  * @default false
15548  */
15549 disableBtns : false,
15550
15551
15552
15553
15554
15555
15556
15557 /////////////////////////////////////////////////////////////////////////////
15558 //
15559 // Public methods
15560 //
15561 /////////////////////////////////////////////////////////////////////////////
15562 /**
15563  * CellEditor instance name, for logging.
15564  *
15565  * @method toString
15566  * @return {String} Unique name of the CellEditor instance.
15567  */
15568
15569 toString : function() {
15570     return "CellEditor instance " + this._sId;
15571 },
15572
15573 /**
15574  * CellEditor unique ID.
15575  *
15576  * @method getId
15577  * @return {String} Unique ID of the CellEditor instance.
15578  */
15579
15580 getId : function() {
15581     return this._sId;
15582 },
15583
15584 /**
15585  * Returns reference to associated DataTable instance.
15586  *
15587  * @method getDataTable
15588  * @return {YAHOO.widget.DataTable} DataTable instance.
15589  */
15590
15591 getDataTable : function() {
15592     return this._oDataTable;
15593 },
15594
15595 /**
15596  * Returns reference to associated Column instance.
15597  *
15598  * @method getColumn
15599  * @return {YAHOO.widget.Column} Column instance.
15600  */
15601
15602 getColumn : function() {
15603     return this._oColumn;
15604 },
15605
15606 /**
15607  * Returns reference to associated Record instance.
15608  *
15609  * @method getRecord
15610  * @return {YAHOO.widget.Record} Record instance.
15611  */
15612
15613 getRecord : function() {
15614     return this._oRecord;
15615 },
15616
15617
15618
15619 /**
15620  * Returns reference to associated TD element.
15621  *
15622  * @method getTdEl
15623  * @return {HTMLElement} TD element.
15624  */
15625
15626 getTdEl : function() {
15627     return this._elTd;
15628 },
15629
15630 /**
15631  * Returns container element.
15632  *
15633  * @method getContainerEl
15634  * @return {HTMLElement} Reference to container element.
15635  */
15636
15637 getContainerEl : function() {
15638     return this._elContainer;
15639 },
15640
15641 /**
15642  * Nulls out the entire CellEditor instance and related objects, removes attached
15643  * event listeners, and clears out DOM elements inside the container, removes
15644  * container from the DOM.
15645  *
15646  * @method destroy
15647  */
15648 destroy : function() {
15649     this.unsubscribeAll();
15650     
15651     // Column is late-binding in attach()
15652     var oColumn = this.getColumn();
15653     if(oColumn) {
15654         oColumn.editor = null;
15655     }
15656     
15657     var elContainer = this.getContainerEl();
15658     Ev.purgeElement(elContainer, true);
15659     elContainer.parentNode.removeChild(elContainer);
15660 },
15661
15662 /**
15663  * Renders DOM elements and attaches event listeners.
15664  *
15665  * @method render
15666  */
15667 render : function() {
15668     if(this._elContainer) {
15669         YAHOO.util.Event.purgeElement(this._elContainer, true);
15670         this._elContainer.innerHTML = "";
15671     }
15672
15673     // Render Cell Editor container element as first child of body
15674     var elContainer = document.createElement("div");
15675     elContainer.id = this.getId() + "-container"; // Needed for tracking blur event
15676     elContainer.style.display = "none";
15677     elContainer.tabIndex = 0;
15678     elContainer.className = DT.CLASS_EDITOR;
15679     document.body.insertBefore(elContainer, document.body.firstChild);
15680     this._elContainer = elContainer;
15681     
15682     // Handle ESC key
15683     Ev.addListener(elContainer, "keydown", function(e, oSelf) {
15684         // ESC cancels Cell Editor
15685         if((e.keyCode == 27)) {
15686             var target = Ev.getTarget(e);
15687             // workaround for Mac FF3 bug that disabled clicks when ESC hit when
15688             // select is open. [bug 2273056]
15689             if (target.nodeName && target.nodeName.toLowerCase() === 'select') {
15690                 target.blur();
15691             }
15692             oSelf.cancel();
15693         }
15694         // Pass through event
15695         oSelf.fireEvent("keydownEvent", {editor:this, event:e});
15696     }, this);
15697     
15698     this.renderForm();
15699
15700     // Show Save/Cancel buttons
15701     if(!this.disableBtns) {
15702         this.renderBtns();
15703     }
15704     
15705     this.doAfterRender();
15706 },
15707
15708 /**
15709  * Renders Save/Cancel buttons.
15710  *
15711  * @method renderBtns
15712  */
15713 renderBtns : function() {
15714     // Buttons
15715     var elBtnsDiv = this.getContainerEl().appendChild(document.createElement("div"));
15716     elBtnsDiv.className = DT.CLASS_BUTTON;
15717
15718     // Save button
15719     var elSaveBtn = elBtnsDiv.appendChild(document.createElement("button"));
15720     elSaveBtn.className = DT.CLASS_DEFAULT;
15721     elSaveBtn.innerHTML = this.LABEL_SAVE;
15722     Ev.addListener(elSaveBtn, "click", function(oArgs) {
15723         this.save();
15724     }, this, true);
15725     this._elSaveBtn = elSaveBtn;
15726
15727     // Cancel button
15728     var elCancelBtn = elBtnsDiv.appendChild(document.createElement("button"));
15729     elCancelBtn.innerHTML = this.LABEL_CANCEL;
15730     Ev.addListener(elCancelBtn, "click", function(oArgs) {
15731         this.cancel();
15732     }, this, true);
15733     this._elCancelBtn = elCancelBtn;
15734 },
15735
15736 /**
15737  * Attach CellEditor for a new interaction.
15738  *
15739  * @method attach
15740  * @param oDataTable {YAHOO.widget.DataTable} Associated DataTable instance.
15741  * @param elCell {HTMLElement} Cell to edit.  
15742  */
15743 attach : function(oDataTable, elCell) {
15744     // Validate 
15745     if(oDataTable instanceof YAHOO.widget.DataTable) {
15746         this._oDataTable = oDataTable;
15747         
15748         // Validate cell
15749         elCell = oDataTable.getTdEl(elCell);
15750         if(elCell) {
15751             this._elTd = elCell;
15752
15753             // Validate Column
15754             var oColumn = oDataTable.getColumn(elCell);
15755             if(oColumn) {
15756                 this._oColumn = oColumn;
15757                 
15758                 // Validate Record
15759                 var oRecord = oDataTable.getRecord(elCell);
15760                 if(oRecord) {
15761                     this._oRecord = oRecord;
15762                     var value = oRecord.getData(this.getColumn().getKey());
15763                     this.value = (value !== undefined) ? value : this.defaultValue;
15764                     return true;
15765                 }
15766             }            
15767         }
15768     }
15769     return false;
15770 },
15771
15772 /**
15773  * Moves container into position for display.
15774  *
15775  * @method move
15776  */
15777 move : function() {
15778     // Move Editor
15779     var elContainer = this.getContainerEl(),
15780         elTd = this.getTdEl(),
15781         x = Dom.getX(elTd),
15782         y = Dom.getY(elTd);
15783
15784     //TODO: remove scrolling logic
15785     // SF doesn't get xy for cells in scrolling table
15786     // when tbody display is set to block
15787     if(isNaN(x) || isNaN(y)) {
15788         var elTbody = this.getDataTable().getTbodyEl();
15789         x = elTd.offsetLeft + // cell pos relative to table
15790                 Dom.getX(elTbody.parentNode) - // plus table pos relative to document
15791                 elTbody.scrollLeft; // minus tbody scroll
15792         y = elTd.offsetTop + // cell pos relative to table
15793                 Dom.getY(elTbody.parentNode) - // plus table pos relative to document
15794                 elTbody.scrollTop + // minus tbody scroll
15795                 this.getDataTable().getTheadEl().offsetHeight; // account for fixed THEAD cells
15796     }
15797
15798     elContainer.style.left = x + "px";
15799     elContainer.style.top = y + "px";
15800 },
15801
15802 /**
15803  * Displays CellEditor UI in the correct position.
15804  *
15805  * @method show
15806  */
15807 show : function() {
15808     this.resetForm();
15809     this.isActive = true;
15810     this.getContainerEl().style.display = "";
15811     this.focus();
15812     this.fireEvent("showEvent", {editor:this});
15813 },
15814
15815 /**
15816  * Fires blockEvent
15817  *
15818  * @method block
15819  */
15820 block : function() {
15821     this.fireEvent("blockEvent", {editor:this});
15822 },
15823
15824 /**
15825  * Fires unblockEvent
15826  *
15827  * @method unblock
15828  */
15829 unblock : function() {
15830     this.fireEvent("unblockEvent", {editor:this});
15831 },
15832
15833 /**
15834  * Saves value of CellEditor and hides UI.
15835  *
15836  * @method save
15837  */
15838 save : function() {
15839     // Get new value
15840     var inputValue = this.getInputValue();
15841     var validValue = inputValue;
15842     
15843     // Validate new value
15844     if(this.validator) {
15845         validValue = this.validator.call(this.getDataTable(), inputValue, this.value, this);
15846         if(validValue === undefined ) {
15847             if(this.resetInvalidData) {
15848                 this.resetForm();
15849             }
15850             this.fireEvent("invalidDataEvent",
15851                     {editor:this, oldData:this.value, newData:inputValue});
15852             return;
15853         }
15854     }
15855         
15856     var oSelf = this;
15857     var finishSave = function(bSuccess, oNewValue) {
15858         var oOrigValue = oSelf.value;
15859         if(bSuccess) {
15860             // Update new value
15861             oSelf.value = oNewValue;
15862             oSelf.getDataTable().updateCell(oSelf.getRecord(), oSelf.getColumn(), oNewValue);
15863             
15864             // Hide CellEditor
15865             oSelf.getContainerEl().style.display = "none";
15866             oSelf.isActive = false;
15867             oSelf.getDataTable()._oCellEditor =  null;
15868             
15869             oSelf.fireEvent("saveEvent",
15870                     {editor:oSelf, oldData:oOrigValue, newData:oSelf.value});
15871         }
15872         else {
15873             oSelf.resetForm();
15874             oSelf.fireEvent("revertEvent",
15875                     {editor:oSelf, oldData:oOrigValue, newData:oNewValue});
15876         }
15877         oSelf.unblock();
15878     };
15879     
15880     this.block();
15881     if(lang.isFunction(this.asyncSubmitter)) {
15882         this.asyncSubmitter.call(this, finishSave, validValue);
15883     } 
15884     else {   
15885         finishSave(true, validValue);
15886     }
15887 },
15888
15889 /**
15890  * Cancels CellEditor input and hides UI.
15891  *
15892  * @method cancel
15893  */
15894 cancel : function() {
15895     if(this.isActive) {
15896         this.getContainerEl().style.display = "none";
15897         this.isActive = false;
15898         this.getDataTable()._oCellEditor =  null;
15899         this.fireEvent("cancelEvent", {editor:this});
15900     }
15901     else {
15902     }
15903 },
15904
15905 /**
15906  * Renders form elements.
15907  *
15908  * @method renderForm
15909  */
15910 renderForm : function() {
15911     // To be implemented by subclass
15912 },
15913
15914 /**
15915  * Access to add additional event listeners.
15916  *
15917  * @method doAfterRender
15918  */
15919 doAfterRender : function() {
15920     // To be implemented by subclass
15921 },
15922
15923
15924 /**
15925  * After rendering form, if disabledBtns is set to true, then sets up a mechanism
15926  * to save input without them. 
15927  *
15928  * @method handleDisabledBtns
15929  */
15930 handleDisabledBtns : function() {
15931     // To be implemented by subclass
15932 },
15933
15934 /**
15935  * Resets CellEditor UI to initial state.
15936  *
15937  * @method resetForm
15938  */
15939 resetForm : function() {
15940     // To be implemented by subclass
15941 },
15942
15943 /**
15944  * Sets focus in CellEditor.
15945  *
15946  * @method focus
15947  */
15948 focus : function() {
15949     // To be implemented by subclass
15950 },
15951
15952 /**
15953  * Retrieves input value from CellEditor.
15954  *
15955  * @method getInputValue
15956  */
15957 getInputValue : function() {
15958     // To be implemented by subclass
15959 }
15960
15961 };
15962
15963 lang.augmentProto(BCE, util.EventProvider);
15964
15965
15966 /////////////////////////////////////////////////////////////////////////////
15967 //
15968 // Custom Events
15969 //
15970 /////////////////////////////////////////////////////////////////////////////
15971
15972 /**
15973  * Fired when a CellEditor is shown.
15974  *
15975  * @event showEvent
15976  * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
15977  */
15978
15979 /**
15980  * Fired when a CellEditor has a keydown.
15981  *
15982  * @event keydownEvent
15983  * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance. 
15984  * @param oArgs.event {HTMLEvent} The event object.
15985  */
15986
15987 /**
15988  * Fired when a CellEditor input is reverted due to invalid data.
15989  *
15990  * @event invalidDataEvent
15991  * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance. 
15992  * @param oArgs.newData {Object} New data value from form input field.
15993  * @param oArgs.oldData {Object} Old data value.
15994  */
15995
15996 /**
15997  * Fired when a CellEditor input is reverted due to asyncSubmitter failure.
15998  *
15999  * @event revertEvent
16000  * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance. 
16001  * @param oArgs.newData {Object} New data value from form input field.
16002  * @param oArgs.oldData {Object} Old data value.
16003  */
16004
16005 /**
16006  * Fired when a CellEditor input is saved.
16007  *
16008  * @event saveEvent
16009  * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance. 
16010  * @param oArgs.newData {Object} New data value from form input field.
16011  * @param oArgs.oldData {Object} Old data value.
16012  */
16013
16014 /**
16015  * Fired when a CellEditor input is canceled.
16016  *
16017  * @event cancelEvent
16018  * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance. 
16019  */
16020
16021 /**
16022  * Fired when a CellEditor has a blur event.
16023  *
16024  * @event blurEvent
16025  * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance. 
16026  */
16027
16028
16029
16030
16031
16032
16033
16034
16035
16036
16037
16038
16039
16040
16041 /****************************************************************************/
16042 /****************************************************************************/
16043 /****************************************************************************/
16044     
16045 /**
16046  * The CheckboxCellEditor class provides functionality for inline editing
16047  * DataTable cell data with checkboxes.
16048  *
16049  * @namespace YAHOO.widget
16050  * @class CheckboxCellEditor
16051  * @extends YAHOO.widget.BaseCellEditor
16052  * @constructor
16053  * @param oConfigs {Object} (Optional) Object literal of configs.
16054  */
16055 widget.CheckboxCellEditor = function(oConfigs) {
16056     this._sId = "yui-checkboxceditor" + YAHOO.widget.BaseCellEditor._nCount++;
16057     widget.CheckboxCellEditor.superclass.constructor.call(this, "checkbox", oConfigs); 
16058 };
16059
16060 // CheckboxCellEditor extends BaseCellEditor
16061 lang.extend(widget.CheckboxCellEditor, BCE, {
16062
16063 /////////////////////////////////////////////////////////////////////////////
16064 //
16065 // CheckboxCellEditor public properties
16066 //
16067 /////////////////////////////////////////////////////////////////////////////
16068 /**
16069  * Array of checkbox values. Can either be a simple array (e.g., ["red","green","blue"])
16070  * or a an array of objects (e.g., [{label:"red", value:"#FF0000"},
16071  * {label:"green", value:"#00FF00"}, {label:"blue", value:"#0000FF"}]). 
16072  *
16073  * @property checkboxOptions
16074  * @type String[] | Object[]
16075  */
16076 checkboxOptions : null,
16077
16078 /**
16079  * Reference to the checkbox elements.
16080  *
16081  * @property checkboxes
16082  * @type HTMLElement[] 
16083  */
16084 checkboxes : null,
16085
16086 /**
16087  * Array of checked values
16088  *
16089  * @property value
16090  * @type String[] 
16091  */
16092 value : null,
16093
16094 /////////////////////////////////////////////////////////////////////////////
16095 //
16096 // CheckboxCellEditor public methods
16097 //
16098 /////////////////////////////////////////////////////////////////////////////
16099
16100 /**
16101  * Render a form with input(s) type=checkbox.
16102  *
16103  * @method renderForm
16104  */
16105 renderForm : function() {
16106     if(lang.isArray(this.checkboxOptions)) {
16107         var checkboxOption, checkboxValue, checkboxId, elLabel, j, len;
16108         
16109         // Create the checkbox buttons in an IE-friendly way...
16110         for(j=0,len=this.checkboxOptions.length; j<len; j++) {
16111             checkboxOption = this.checkboxOptions[j];
16112             checkboxValue = lang.isValue(checkboxOption.value) ?
16113                     checkboxOption.value : checkboxOption;
16114
16115             checkboxId = this.getId() + "-chk" + j;
16116             this.getContainerEl().innerHTML += "<input type=\"checkbox\"" +
16117                     " id=\"" + checkboxId + "\"" + // Needed for label
16118                     " value=\"" + checkboxValue + "\" />";
16119             
16120             // Create the labels in an IE-friendly way
16121             elLabel = this.getContainerEl().appendChild(document.createElement("label"));
16122             elLabel.htmlFor = checkboxId;
16123             elLabel.innerHTML = lang.isValue(checkboxOption.label) ?
16124                     checkboxOption.label : checkboxOption;
16125         }
16126         
16127         // Store the reference to the checkbox elements
16128         var allCheckboxes = [];
16129         for(j=0; j<len; j++) {
16130             allCheckboxes[allCheckboxes.length] = this.getContainerEl().childNodes[j*2];
16131         }
16132         this.checkboxes = allCheckboxes;
16133
16134         if(this.disableBtns) {
16135             this.handleDisabledBtns();
16136         }
16137     }
16138     else {
16139     }
16140 },
16141
16142 /**
16143  * After rendering form, if disabledBtns is set to true, then sets up a mechanism
16144  * to save input without them. 
16145  *
16146  * @method handleDisabledBtns
16147  */
16148 handleDisabledBtns : function() {
16149     Ev.addListener(this.getContainerEl(), "click", function(v){
16150         if(Ev.getTarget(v).tagName.toLowerCase() === "input") {
16151             // Save on blur
16152             this.save();
16153         }
16154     }, this, true);
16155 },
16156
16157 /**
16158  * Resets CheckboxCellEditor UI to initial state.
16159  *
16160  * @method resetForm
16161  */
16162 resetForm : function() {
16163     // Normalize to array
16164     var originalValues = lang.isArray(this.value) ? this.value : [this.value];
16165     
16166     // Match checks to value
16167     for(var i=0, j=this.checkboxes.length; i<j; i++) {
16168         this.checkboxes[i].checked = false;
16169         for(var k=0, len=originalValues.length; k<len; k++) {
16170             if(this.checkboxes[i].value === originalValues[k]) {
16171                 this.checkboxes[i].checked = true;
16172             }
16173         }
16174     }
16175 },
16176
16177 /**
16178  * Sets focus in CheckboxCellEditor.
16179  *
16180  * @method focus
16181  */
16182 focus : function() {
16183     this.checkboxes[0].focus();
16184 },
16185
16186 /**
16187  * Retrieves input value from CheckboxCellEditor.
16188  *
16189  * @method getInputValue
16190  */
16191 getInputValue : function() {
16192     var checkedValues = [];
16193     for(var i=0, j=this.checkboxes.length; i<j; i++) {
16194         if(this.checkboxes[i].checked) {
16195             checkedValues[checkedValues.length] = this.checkboxes[i].value;
16196         }
16197     }  
16198     return checkedValues;
16199 }
16200
16201 });
16202
16203 // Copy static members to CheckboxCellEditor class
16204 lang.augmentObject(widget.CheckboxCellEditor, BCE);
16205
16206
16207
16208
16209
16210
16211
16212
16213 /****************************************************************************/
16214 /****************************************************************************/
16215 /****************************************************************************/
16216     
16217 /**
16218  * The DataCellEditor class provides functionality for inline editing
16219  * DataTable cell data with a YUI Calendar.
16220  *
16221  * @namespace YAHOO.widget
16222  * @class DateCellEditor
16223  * @extends YAHOO.widget.BaseCellEditor 
16224  * @constructor
16225  * @param oConfigs {Object} (Optional) Object literal of configs.
16226  */
16227 widget.DateCellEditor = function(oConfigs) {
16228     this._sId = "yui-dateceditor" + YAHOO.widget.BaseCellEditor._nCount++;
16229     widget.DateCellEditor.superclass.constructor.call(this, "date", oConfigs); 
16230 };
16231
16232 // CheckboxCellEditor extends BaseCellEditor
16233 lang.extend(widget.DateCellEditor, BCE, {
16234
16235 /////////////////////////////////////////////////////////////////////////////
16236 //
16237 // DateCellEditor public properties
16238 //
16239 /////////////////////////////////////////////////////////////////////////////
16240 /**
16241  * Reference to Calendar instance.
16242  *
16243  * @property calendar
16244  * @type YAHOO.widget.Calendar
16245  */
16246 calendar : null,
16247
16248 /**
16249  * Configs for the calendar instance, to be passed to Calendar constructor.
16250  *
16251  * @property calendarOptions
16252  * @type Object
16253  */
16254 calendarOptions : null,
16255
16256 /**
16257  * Default value.
16258  *
16259  * @property defaultValue
16260  * @type Date
16261  * @default new Date()
16262  */
16263 defaultValue : new Date(),
16264
16265
16266 /////////////////////////////////////////////////////////////////////////////
16267 //
16268 // DateCellEditor public methods
16269 //
16270 /////////////////////////////////////////////////////////////////////////////
16271
16272 /**
16273  * Render a Calendar.
16274  *
16275  * @method renderForm
16276  */
16277 renderForm : function() {
16278     // Calendar widget
16279     if(YAHOO.widget.Calendar) {
16280         var calContainer = this.getContainerEl().appendChild(document.createElement("div"));
16281         calContainer.id = this.getId() + "-dateContainer"; // Needed for Calendar constructor
16282         var calendar =
16283                 new YAHOO.widget.Calendar(this.getId() + "-date",
16284                 calContainer.id, this.calendarOptions);
16285         calendar.render();
16286         calContainer.style.cssFloat = "none";
16287
16288         if(ua.ie) {
16289             var calFloatClearer = this.getContainerEl().appendChild(document.createElement("div"));
16290             calFloatClearer.style.clear = "both";
16291         }
16292         
16293         this.calendar = calendar;
16294
16295         if(this.disableBtns) {
16296             this.handleDisabledBtns();
16297         }
16298     }
16299     else {
16300     }
16301     
16302 },
16303
16304 /**
16305  * After rendering form, if disabledBtns is set to true, then sets up a mechanism
16306  * to save input without them. 
16307  *
16308  * @method handleDisabledBtns
16309  */
16310 handleDisabledBtns : function() {
16311     this.calendar.selectEvent.subscribe(function(v){
16312         // Save on select
16313         this.save();
16314     }, this, true);
16315 },
16316
16317 /**
16318  * Resets DateCellEditor UI to initial state.
16319  *
16320  * @method resetForm
16321  */
16322 resetForm : function() {
16323     var value = this.value;
16324     var selectedValue = (value.getMonth()+1)+"/"+value.getDate()+"/"+value.getFullYear();
16325     this.calendar.cfg.setProperty("selected",selectedValue,false);
16326         this.calendar.render();
16327 },
16328
16329 /**
16330  * Sets focus in DateCellEditor.
16331  *
16332  * @method focus
16333  */
16334 focus : function() {
16335     // To be impmlemented by subclass
16336 },
16337
16338 /**
16339  * Retrieves input value from DateCellEditor.
16340  *
16341  * @method getInputValue
16342  */
16343 getInputValue : function() {
16344     return this.calendar.getSelectedDates()[0];
16345 }
16346
16347 });
16348
16349 // Copy static members to DateCellEditor class
16350 lang.augmentObject(widget.DateCellEditor, BCE);
16351
16352
16353
16354
16355
16356
16357
16358
16359
16360 /****************************************************************************/
16361 /****************************************************************************/
16362 /****************************************************************************/
16363     
16364 /**
16365  * The DropdownCellEditor class provides functionality for inline editing
16366  * DataTable cell data a SELECT element.
16367  *
16368  * @namespace YAHOO.widget
16369  * @class DropdownCellEditor
16370  * @extends YAHOO.widget.BaseCellEditor 
16371  * @constructor
16372  * @param oConfigs {Object} (Optional) Object literal of configs.
16373  */
16374 widget.DropdownCellEditor = function(oConfigs) {
16375     this._sId = "yui-dropdownceditor" + YAHOO.widget.BaseCellEditor._nCount++;
16376     widget.DropdownCellEditor.superclass.constructor.call(this, "dropdown", oConfigs); 
16377 };
16378
16379 // DropdownCellEditor extends BaseCellEditor
16380 lang.extend(widget.DropdownCellEditor, BCE, {
16381
16382 /////////////////////////////////////////////////////////////////////////////
16383 //
16384 // DropdownCellEditor public properties
16385 //
16386 /////////////////////////////////////////////////////////////////////////////
16387 /**
16388  * Array of dropdown values. Can either be a simple array (e.g., 
16389  * ["Alabama","Alaska","Arizona","Arkansas"]) or a an array of objects (e.g., 
16390  * [{label:"Alabama", value:"AL"}, {label:"Alaska", value:"AK"},
16391  * {label:"Arizona", value:"AZ"}, {label:"Arkansas", value:"AR"}]). 
16392  *
16393  * @property dropdownOptions
16394  * @type String[] | Object[]
16395  */
16396 dropdownOptions : null,
16397
16398 /**
16399  * Reference to Dropdown element.
16400  *
16401  * @property dropdown
16402  * @type HTMLElement
16403  */
16404 dropdown : null,
16405
16406
16407 /////////////////////////////////////////////////////////////////////////////
16408 //
16409 // DropdownCellEditor public methods
16410 //
16411 /////////////////////////////////////////////////////////////////////////////
16412
16413 /**
16414  * Render a form with select element.
16415  *
16416  * @method renderForm
16417  */
16418 renderForm : function() {
16419     var elDropdown = this.getContainerEl().appendChild(document.createElement("select"));
16420     elDropdown.style.zoom = 1;
16421     this.dropdown = elDropdown;
16422     
16423     if(lang.isArray(this.dropdownOptions)) {
16424         var dropdownOption, elOption;
16425         for(var i=0, j=this.dropdownOptions.length; i<j; i++) {
16426             dropdownOption = this.dropdownOptions[i];
16427             elOption = document.createElement("option");
16428             elOption.value = (lang.isValue(dropdownOption.value)) ?
16429                     dropdownOption.value : dropdownOption;
16430             elOption.innerHTML = (lang.isValue(dropdownOption.label)) ?
16431                     dropdownOption.label : dropdownOption;
16432             elOption = elDropdown.appendChild(elOption);
16433         }
16434         
16435         if(this.disableBtns) {
16436             this.handleDisabledBtns();
16437         }
16438     }
16439 },
16440
16441 /**
16442  * After rendering form, if disabledBtns is set to true, then sets up a mechanism
16443  * to save input without them. 
16444  *
16445  * @method handleDisabledBtns
16446  */
16447 handleDisabledBtns : function() {
16448     Ev.addListener(this.dropdown, "change", function(v){
16449         // Save on change
16450         this.save();
16451     }, this, true);        
16452 },
16453
16454 /**
16455  * Resets DropdownCellEditor UI to initial state.
16456  *
16457  * @method resetForm
16458  */
16459 resetForm : function() {
16460     for(var i=0, j=this.dropdown.options.length; i<j; i++) {
16461         if(this.value === this.dropdown.options[i].value) {
16462             this.dropdown.options[i].selected = true;
16463         }
16464     }    
16465 },
16466
16467 /**
16468  * Sets focus in DropdownCellEditor.
16469  *
16470  * @method focus
16471  */
16472 focus : function() {
16473     this.getDataTable()._focusEl(this.dropdown);
16474 },
16475
16476 /**
16477  * Retrieves input value from DropdownCellEditor.
16478  *
16479  * @method getInputValue
16480  */
16481 getInputValue : function() {
16482     return this.dropdown.options[this.dropdown.options.selectedIndex].value;
16483 }
16484
16485 });
16486
16487 // Copy static members to DropdownCellEditor class
16488 lang.augmentObject(widget.DropdownCellEditor, BCE);
16489
16490
16491
16492
16493
16494
16495 /****************************************************************************/
16496 /****************************************************************************/
16497 /****************************************************************************/
16498     
16499 /**
16500  * The RadioCellEditor class provides functionality for inline editing
16501  * DataTable cell data with radio buttons.
16502  *
16503  * @namespace YAHOO.widget
16504  * @class RadioCellEditor
16505  * @extends YAHOO.widget.BaseCellEditor 
16506  * @constructor
16507  * @param oConfigs {Object} (Optional) Object literal of configs.
16508  */
16509 widget.RadioCellEditor = function(oConfigs) {
16510     this._sId = "yui-radioceditor" + YAHOO.widget.BaseCellEditor._nCount++;
16511     widget.RadioCellEditor.superclass.constructor.call(this, "radio", oConfigs); 
16512 };
16513
16514 // RadioCellEditor extends BaseCellEditor
16515 lang.extend(widget.RadioCellEditor, BCE, {
16516
16517 /////////////////////////////////////////////////////////////////////////////
16518 //
16519 // RadioCellEditor public properties
16520 //
16521 /////////////////////////////////////////////////////////////////////////////
16522 /**
16523  * Reference to radio elements.
16524  *
16525  * @property radios
16526  * @type HTMLElement[]
16527  */
16528 radios : null,
16529
16530 /**
16531  * Array of radio values. Can either be a simple array (e.g., ["yes","no","maybe"])
16532  * or a an array of objects (e.g., [{label:"yes", value:1}, {label:"no", value:-1},
16533  * {label:"maybe", value:0}]). 
16534  *
16535  * @property radioOptions
16536  * @type String[] | Object[]
16537  */
16538 radioOptions : null,
16539
16540 /////////////////////////////////////////////////////////////////////////////
16541 //
16542 // RadioCellEditor public methods
16543 //
16544 /////////////////////////////////////////////////////////////////////////////
16545
16546 /**
16547  * Render a form with input(s) type=radio.
16548  *
16549  * @method renderForm
16550  */
16551 renderForm : function() {
16552     if(lang.isArray(this.radioOptions)) {
16553         var radioOption, radioValue, radioId, elLabel;
16554         
16555         // Create the radio buttons in an IE-friendly way
16556         for(var i=0, len=this.radioOptions.length; i<len; i++) {
16557             radioOption = this.radioOptions[i];
16558             radioValue = lang.isValue(radioOption.value) ?
16559                     radioOption.value : radioOption;
16560             radioId = this.getId() + "-radio" + i;
16561             this.getContainerEl().innerHTML += "<input type=\"radio\"" +
16562                     " name=\"" + this.getId() + "\"" +
16563                     " value=\"" + radioValue + "\"" +
16564                     " id=\"" +  radioId + "\" />"; // Needed for label
16565             
16566             // Create the labels in an IE-friendly way
16567             elLabel = this.getContainerEl().appendChild(document.createElement("label"));
16568             elLabel.htmlFor = radioId;
16569             elLabel.innerHTML = (lang.isValue(radioOption.label)) ?
16570                     radioOption.label : radioOption;
16571         }
16572         
16573         // Store the reference to the checkbox elements
16574         var allRadios = [],
16575             elRadio;
16576         for(var j=0; j<len; j++) {
16577             elRadio = this.getContainerEl().childNodes[j*2];
16578             allRadios[allRadios.length] = elRadio;
16579         }
16580         this.radios = allRadios;
16581
16582         if(this.disableBtns) {
16583             this.handleDisabledBtns();
16584         }
16585     }
16586     else {
16587     }
16588 },
16589
16590 /**
16591  * After rendering form, if disabledBtns is set to true, then sets up a mechanism
16592  * to save input without them. 
16593  *
16594  * @method handleDisabledBtns
16595  */
16596 handleDisabledBtns : function() {
16597     Ev.addListener(this.getContainerEl(), "click", function(v){
16598         if(Ev.getTarget(v).tagName.toLowerCase() === "input") {
16599             // Save on blur
16600             this.save();
16601         }
16602     }, this, true);
16603 },
16604
16605 /**
16606  * Resets RadioCellEditor UI to initial state.
16607  *
16608  * @method resetForm
16609  */
16610 resetForm : function() {
16611     for(var i=0, j=this.radios.length; i<j; i++) {
16612         var elRadio = this.radios[i];
16613         if(this.value === elRadio.value) {
16614             elRadio.checked = true;
16615             return;
16616         }
16617     }
16618 },
16619
16620 /**
16621  * Sets focus in RadioCellEditor.
16622  *
16623  * @method focus
16624  */
16625 focus : function() {
16626     for(var i=0, j=this.radios.length; i<j; i++) {
16627         if(this.radios[i].checked) {
16628             this.radios[i].focus();
16629             return;
16630         }
16631     }
16632 },
16633
16634 /**
16635  * Retrieves input value from RadioCellEditor.
16636  *
16637  * @method getInputValue
16638  */
16639 getInputValue : function() {
16640     for(var i=0, j=this.radios.length; i<j; i++) {
16641         if(this.radios[i].checked) {
16642             return this.radios[i].value;
16643         }
16644     }
16645 }
16646
16647 });
16648
16649 // Copy static members to RadioCellEditor class
16650 lang.augmentObject(widget.RadioCellEditor, BCE);
16651
16652
16653
16654
16655
16656
16657 /****************************************************************************/
16658 /****************************************************************************/
16659 /****************************************************************************/
16660     
16661 /**
16662  * The TextareaCellEditor class provides functionality for inline editing
16663  * DataTable cell data with a TEXTAREA element.
16664  *
16665  * @namespace YAHOO.widget
16666  * @class TextareaCellEditor
16667  * @extends YAHOO.widget.BaseCellEditor 
16668  * @constructor
16669  * @param oConfigs {Object} (Optional) Object literal of configs.
16670  */
16671 widget.TextareaCellEditor = function(oConfigs) {
16672     this._sId = "yui-textareaceditor" + YAHOO.widget.BaseCellEditor._nCount++;
16673     widget.TextareaCellEditor.superclass.constructor.call(this, "textarea", oConfigs); 
16674 };
16675
16676 // TextareaCellEditor extends BaseCellEditor
16677 lang.extend(widget.TextareaCellEditor, BCE, {
16678
16679 /////////////////////////////////////////////////////////////////////////////
16680 //
16681 // TextareaCellEditor public properties
16682 //
16683 /////////////////////////////////////////////////////////////////////////////
16684 /**
16685  * Reference to textarea element.
16686  *
16687  * @property textarea
16688  * @type HTMLElement
16689  */
16690 textarea : null,
16691
16692
16693 /////////////////////////////////////////////////////////////////////////////
16694 //
16695 // TextareaCellEditor public methods
16696 //
16697 /////////////////////////////////////////////////////////////////////////////
16698
16699 /**
16700  * Render a form with textarea.
16701  *
16702  * @method renderForm
16703  */
16704 renderForm : function() {
16705     var elTextarea = this.getContainerEl().appendChild(document.createElement("textarea"));
16706     this.textarea = elTextarea;
16707
16708     if(this.disableBtns) {
16709         this.handleDisabledBtns();
16710     }
16711 },
16712
16713 /**
16714  * After rendering form, if disabledBtns is set to true, then sets up a mechanism
16715  * to save input without them. 
16716  *
16717  * @method handleDisabledBtns
16718  */
16719 handleDisabledBtns : function() {
16720     Ev.addListener(this.textarea, "blur", function(v){
16721         // Save on blur
16722         this.save();
16723     }, this, true);        
16724 },
16725
16726 /**
16727  * Moves TextareaCellEditor UI to a cell.
16728  *
16729  * @method move
16730  */
16731 move : function() {
16732     this.textarea.style.width = this.getTdEl().offsetWidth + "px";
16733     this.textarea.style.height = "3em";
16734     YAHOO.widget.TextareaCellEditor.superclass.move.call(this);
16735 },
16736
16737 /**
16738  * Resets TextareaCellEditor UI to initial state.
16739  *
16740  * @method resetForm
16741  */
16742 resetForm : function() {
16743     this.textarea.value = this.value;
16744 },
16745
16746 /**
16747  * Sets focus in TextareaCellEditor.
16748  *
16749  * @method focus
16750  */
16751 focus : function() {
16752     // Bug 2303181, Bug 2263600
16753     this.getDataTable()._focusEl(this.textarea);
16754     this.textarea.select();
16755 },
16756
16757 /**
16758  * Retrieves input value from TextareaCellEditor.
16759  *
16760  * @method getInputValue
16761  */
16762 getInputValue : function() {
16763     return this.textarea.value;
16764 }
16765
16766 });
16767
16768 // Copy static members to TextareaCellEditor class
16769 lang.augmentObject(widget.TextareaCellEditor, BCE);
16770
16771
16772
16773
16774
16775
16776
16777
16778
16779 /****************************************************************************/
16780 /****************************************************************************/
16781 /****************************************************************************/
16782     
16783 /**
16784  * The TextboxCellEditor class provides functionality for inline editing
16785  * DataTable cell data with an INPUT TYPE=TEXT element.
16786  *
16787  * @namespace YAHOO.widget
16788  * @class TextboxCellEditor
16789  * @extends YAHOO.widget.BaseCellEditor 
16790  * @constructor
16791  * @param oConfigs {Object} (Optional) Object literal of configs.
16792  */
16793 widget.TextboxCellEditor = function(oConfigs) {
16794     this._sId = "yui-textboxceditor" + YAHOO.widget.BaseCellEditor._nCount++;
16795     widget.TextboxCellEditor.superclass.constructor.call(this, "textbox", oConfigs); 
16796 };
16797
16798 // TextboxCellEditor extends BaseCellEditor
16799 lang.extend(widget.TextboxCellEditor, BCE, {
16800
16801 /////////////////////////////////////////////////////////////////////////////
16802 //
16803 // TextboxCellEditor public properties
16804 //
16805 /////////////////////////////////////////////////////////////////////////////
16806 /**
16807  * Reference to the textbox element.
16808  *
16809  * @property textbox
16810  */
16811 textbox : null,
16812
16813 /////////////////////////////////////////////////////////////////////////////
16814 //
16815 // TextboxCellEditor public methods
16816 //
16817 /////////////////////////////////////////////////////////////////////////////
16818
16819 /**
16820  * Render a form with input type=text.
16821  *
16822  * @method renderForm
16823  */
16824 renderForm : function() {
16825     var elTextbox;
16826     // Bug 1802582: SF3/Mac needs a form element wrapping the input
16827     if(ua.webkit>420) {
16828         elTextbox = this.getContainerEl().appendChild(document.createElement("form")).appendChild(document.createElement("input"));
16829     }
16830     else {
16831         elTextbox = this.getContainerEl().appendChild(document.createElement("input"));
16832     }
16833     elTextbox.type = "text";
16834     this.textbox = elTextbox;
16835
16836     // Save on enter by default
16837     // Bug: 1802582 Set up a listener on each textbox to track on keypress
16838     // since SF/OP can't preventDefault on keydown
16839     Ev.addListener(elTextbox, "keypress", function(v){
16840         if((v.keyCode === 13)) {
16841             // Prevent form submit
16842             YAHOO.util.Event.preventDefault(v);
16843             this.save();
16844         }
16845     }, this, true);
16846
16847     if(this.disableBtns) {
16848         // By default this is no-op since enter saves by default
16849         this.handleDisabledBtns();
16850     }
16851 },
16852
16853 /**
16854  * Moves TextboxCellEditor UI to a cell.
16855  *
16856  * @method move
16857  */
16858 move : function() {
16859     this.textbox.style.width = this.getTdEl().offsetWidth + "px";
16860     widget.TextboxCellEditor.superclass.move.call(this);
16861 },
16862
16863 /**
16864  * Resets TextboxCellEditor UI to initial state.
16865  *
16866  * @method resetForm
16867  */
16868 resetForm : function() {
16869     this.textbox.value = lang.isValue(this.value) ? this.value.toString() : "";
16870 },
16871
16872 /**
16873  * Sets focus in TextboxCellEditor.
16874  *
16875  * @method focus
16876  */
16877 focus : function() {
16878     // Bug 2303181, Bug 2263600
16879     this.getDataTable()._focusEl(this.textbox);
16880     this.textbox.select();
16881 },
16882
16883 /**
16884  * Returns new value for TextboxCellEditor.
16885  *
16886  * @method getInputValue
16887  */
16888 getInputValue : function() {
16889     return this.textbox.value;
16890 }
16891
16892 });
16893
16894 // Copy static members to TextboxCellEditor class
16895 lang.augmentObject(widget.TextboxCellEditor, BCE);
16896
16897
16898
16899
16900
16901
16902
16903 /////////////////////////////////////////////////////////////////////////////
16904 //
16905 // DataTable extension
16906 //
16907 /////////////////////////////////////////////////////////////////////////////
16908
16909 /**
16910  * CellEditor subclasses.
16911  * @property DataTable.Editors
16912  * @type Object
16913  * @static
16914  */
16915 DT.Editors = {
16916     checkbox : widget.CheckboxCellEditor,
16917     "date"   : widget.DateCellEditor,
16918     dropdown : widget.DropdownCellEditor,
16919     radio    : widget.RadioCellEditor,
16920     textarea : widget.TextareaCellEditor,
16921     textbox  : widget.TextboxCellEditor
16922 };
16923
16924 /****************************************************************************/
16925 /****************************************************************************/
16926 /****************************************************************************/
16927     
16928 /**
16929  * Factory class for instantiating a BaseCellEditor subclass.
16930  *
16931  * @namespace YAHOO.widget
16932  * @class CellEditor
16933  * @extends YAHOO.widget.BaseCellEditor 
16934  * @constructor
16935  * @param sType {String} Type indicator, to map to YAHOO.widget.DataTable.Editors.
16936  * @param oConfigs {Object} (Optional) Object literal of configs.
16937  */
16938 widget.CellEditor = function(sType, oConfigs) {
16939     // Point to one of the subclasses
16940     if(sType && DT.Editors[sType]) {
16941         lang.augmentObject(BCE, DT.Editors[sType]);
16942         return new DT.Editors[sType](oConfigs);
16943     }
16944     else {
16945         return new BCE(null, oConfigs);
16946     }
16947 };
16948
16949 var CE = widget.CellEditor;
16950
16951 // Copy static members to CellEditor class
16952 lang.augmentObject(CE, BCE);
16953
16954
16955 })();
16956
16957 YAHOO.register("datatable", YAHOO.widget.DataTable, {version: "2.7.0", build: "1799"});