]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - include/javascript/yui/build/datatable/datatable.js
Release 6.2.0beta4
[Github/sugarcrm.git] / include / javascript / yui / build / 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.8.0r4
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         // Conditional added for Caja compatibility
152         if (this.id > 0) {
153             clearTimeout(this.id);
154         }
155         this.id = 0;
156         return this;
157     },
158
159     /**
160      * Stop and clear the Chain's queue after the current execution of the
161      * current callback completes.
162      * @method stop
163      * @return {Chain} the Chain instance
164      */
165     stop : function () { 
166         this.pause();
167         this.q = [];
168         return this;
169     }
170 };
171 YAHOO.lang.augmentProto(YAHOO.util.Chain,YAHOO.util.EventProvider);
172
173 /****************************************************************************/
174 /****************************************************************************/
175 /****************************************************************************/
176
177 /**
178  * The ColumnSet class defines and manages a DataTable's Columns,
179  * including nested hierarchies and access to individual Column instances.
180  *
181  * @namespace YAHOO.widget
182  * @class ColumnSet
183  * @uses YAHOO.util.EventProvider
184  * @constructor
185  * @param aDefinitions {Object[]} Array of object literals that define cells in
186  * the THEAD.
187  */
188 YAHOO.widget.ColumnSet = function(aDefinitions) {
189     this._sId = "yui-cs" + YAHOO.widget.ColumnSet._nCount;
190
191     // First clone the defs
192     aDefinitions = YAHOO.widget.DataTable._cloneObject(aDefinitions);
193     this._init(aDefinitions);
194
195     YAHOO.widget.ColumnSet._nCount++;
196 };
197
198 /////////////////////////////////////////////////////////////////////////////
199 //
200 // Private member variables
201 //
202 /////////////////////////////////////////////////////////////////////////////
203
204 /**
205  * Internal class variable to index multiple ColumnSet instances.
206  *
207  * @property ColumnSet._nCount
208  * @type Number
209  * @private
210  * @static
211  */
212 YAHOO.widget.ColumnSet._nCount = 0;
213
214 YAHOO.widget.ColumnSet.prototype = {
215     /**
216      * Unique instance name.
217      *
218      * @property _sId
219      * @type String
220      * @private
221      */
222     _sId : null,
223
224     /**
225      * Array of object literal Column definitions passed to the constructor.
226      *
227      * @property _aDefinitions
228      * @type Object[]
229      * @private
230      */
231     _aDefinitions : null,
232
233     /////////////////////////////////////////////////////////////////////////////
234     //
235     // Public member variables
236     //
237     /////////////////////////////////////////////////////////////////////////////
238
239     /**
240      * Top-down tree representation of Column hierarchy.
241      *
242      * @property tree
243      * @type YAHOO.widget.Column[]
244      */
245     tree : null,
246
247     /**
248      * Flattened representation of all Columns.
249      *
250      * @property flat
251      * @type YAHOO.widget.Column[]
252      * @default []
253      */
254     flat : null,
255
256     /**
257      * Array of Columns that map one-to-one to a table column.
258      *
259      * @property keys
260      * @type YAHOO.widget.Column[]
261      * @default []
262      */
263     keys : null,
264
265     /**
266      * ID index of nested parent hierarchies for HEADERS accessibility attribute.
267      *
268      * @property headers
269      * @type String[]
270      * @default []
271      */
272     headers : null,
273
274     /////////////////////////////////////////////////////////////////////////////
275     //
276     // Private methods
277     //
278     /////////////////////////////////////////////////////////////////////////////
279
280     /**
281      * Initializes ColumnSet instance with data from Column definitions.
282      *
283      * @method _init
284      * @param aDefinitions {Object[]} Array of object literals that define cells in
285      * the THEAD .
286      * @private
287      */
288
289     _init : function(aDefinitions) {        
290         // DOM tree representation of all Columns
291         var tree = [];
292         // Flat representation of all Columns
293         var flat = [];
294         // Flat representation of only Columns that are meant to display data
295         var keys = [];
296         // Array of HEADERS attribute values for all keys in the "keys" array
297         var headers = [];
298
299         // Tracks current node list depth being tracked
300         var nodeDepth = -1;
301
302         // Internal recursive function to define Column instances
303         var parseColumns = function(nodeList, parent) {
304             // One level down
305             nodeDepth++;
306
307             // Create corresponding tree node if not already there for this depth
308             if(!tree[nodeDepth]) {
309                 tree[nodeDepth] = [];
310             }
311
312
313             // Parse each node at this depth for attributes and any children
314             for(var j=0; j<nodeList.length; j++) {
315                 var currentNode = nodeList[j];
316
317                 // Instantiate a new Column for each node
318                 var oColumn = new YAHOO.widget.Column(currentNode);
319                 
320                 // Cross-reference Column ID back to the original object literal definition
321                 currentNode.yuiColumnId = oColumn._sId;
322                 
323                 // Add the new Column to the flat list
324                 flat.push(oColumn);
325
326                 // Assign its parent as an attribute, if applicable
327                 if(parent) {
328                     oColumn._oParent = parent;
329                 }
330
331                 // The Column has descendants
332                 if(YAHOO.lang.isArray(currentNode.children)) {
333                     oColumn.children = currentNode.children;
334
335                     // Determine COLSPAN value for this Column
336                     var terminalChildNodes = 0;
337                     var countTerminalChildNodes = function(ancestor) {
338                         var descendants = ancestor.children;
339                         // Drill down each branch and count terminal nodes
340                         for(var k=0; k<descendants.length; k++) {
341                             // Keep drilling down
342                             if(YAHOO.lang.isArray(descendants[k].children)) {
343                                 countTerminalChildNodes(descendants[k]);
344                             }
345                             // Reached branch terminus
346                             else {
347                                 terminalChildNodes++;
348                             }
349                         }
350                     };
351                     countTerminalChildNodes(currentNode);
352                     oColumn._nColspan = terminalChildNodes;
353
354                     // Cascade certain properties to children if not defined on their own
355                     var currentChildren = currentNode.children;
356                     for(var k=0; k<currentChildren.length; k++) {
357                         var child = currentChildren[k];
358                         if(oColumn.className && (child.className === undefined)) {
359                             child.className = oColumn.className;
360                         }
361                         if(oColumn.editor && (child.editor === undefined)) {
362                             child.editor = oColumn.editor;
363                         }
364                         //TODO: Deprecated
365                         if(oColumn.editorOptions && (child.editorOptions === undefined)) {
366                             child.editorOptions = oColumn.editorOptions;
367                         }
368                         if(oColumn.formatter && (child.formatter === undefined)) {
369                             child.formatter = oColumn.formatter;
370                         }
371                         if(oColumn.resizeable && (child.resizeable === undefined)) {
372                             child.resizeable = oColumn.resizeable;
373                         }
374                         if(oColumn.sortable && (child.sortable === undefined)) {
375                             child.sortable = oColumn.sortable;
376                         }
377                         if(oColumn.hidden) {
378                             child.hidden = true;
379                         }
380                         if(oColumn.width && (child.width === undefined)) {
381                             child.width = oColumn.width;
382                         }
383                         if(oColumn.minWidth && (child.minWidth === undefined)) {
384                             child.minWidth = oColumn.minWidth;
385                         }
386                         if(oColumn.maxAutoWidth && (child.maxAutoWidth === undefined)) {
387                             child.maxAutoWidth = oColumn.maxAutoWidth;
388                         }
389                         // Backward compatibility
390                         if(oColumn.type && (child.type === undefined)) {
391                             child.type = oColumn.type;
392                         }
393                         if(oColumn.type && !oColumn.formatter) {
394                             oColumn.formatter = oColumn.type;
395                         }
396                         if(oColumn.text && !YAHOO.lang.isValue(oColumn.label)) {
397                             oColumn.label = oColumn.text;
398                         }
399                         if(oColumn.parser) {
400                         }
401                         if(oColumn.sortOptions && ((oColumn.sortOptions.ascFunction) ||
402                                 (oColumn.sortOptions.descFunction))) {
403                         }
404                     }
405
406                     // The children themselves must also be parsed for Column instances
407                     if(!tree[nodeDepth+1]) {
408                         tree[nodeDepth+1] = [];
409                     }
410                     parseColumns(currentChildren, oColumn);
411                 }
412                 // This Column does not have any children
413                 else {
414                     oColumn._nKeyIndex = keys.length;
415                     oColumn._nColspan = 1;
416                     keys.push(oColumn);
417                 }
418
419                 // Add the Column to the top-down tree
420                 tree[nodeDepth].push(oColumn);
421             }
422             nodeDepth--;
423         };
424
425         // Parse out Column instances from the array of object literals
426         if(YAHOO.lang.isArray(aDefinitions)) {
427             parseColumns(aDefinitions);
428
429             // Store the array
430             this._aDefinitions = aDefinitions;
431         }
432         else {
433             return null;
434         }
435
436         var i;
437
438         // Determine ROWSPAN value for each Column in the tree
439         var parseTreeForRowspan = function(tree) {
440             var maxRowDepth = 1;
441             var currentRow;
442             var currentColumn;
443
444             // Calculate the max depth of descendants for this row
445             var countMaxRowDepth = function(row, tmpRowDepth) {
446                 tmpRowDepth = tmpRowDepth || 1;
447
448                 for(var n=0; n<row.length; n++) {
449                     var col = row[n];
450                     // Column has children, so keep counting
451                     if(YAHOO.lang.isArray(col.children)) {
452                         tmpRowDepth++;
453                         countMaxRowDepth(col.children, tmpRowDepth);
454                         tmpRowDepth--;
455                     }
456                     // No children, is it the max depth?
457                     else {
458                         if(tmpRowDepth > maxRowDepth) {
459                             maxRowDepth = tmpRowDepth;
460                         }
461                     }
462
463                 }
464             };
465
466             // Count max row depth for each row
467             for(var m=0; m<tree.length; m++) {
468                 currentRow = tree[m];
469                 countMaxRowDepth(currentRow);
470
471                 // Assign the right ROWSPAN values to each Column in the row
472                 for(var p=0; p<currentRow.length; p++) {
473                     currentColumn = currentRow[p];
474                     if(!YAHOO.lang.isArray(currentColumn.children)) {
475                         currentColumn._nRowspan = maxRowDepth;
476                     }
477                     else {
478                         currentColumn._nRowspan = 1;
479                     }
480                 }
481
482                 // Reset counter for next row
483                 maxRowDepth = 1;
484             }
485         };
486         parseTreeForRowspan(tree);
487
488         // Store tree index values
489         for(i=0; i<tree[0].length; i++) {
490             tree[0][i]._nTreeIndex = i;
491         }
492
493         // Store header relationships in an array for HEADERS attribute
494         var recurseAncestorsForHeaders = function(i, oColumn) {
495             headers[i].push(oColumn.getSanitizedKey());
496             if(oColumn._oParent) {
497                 recurseAncestorsForHeaders(i, oColumn._oParent);
498             }
499         };
500         for(i=0; i<keys.length; i++) {
501             headers[i] = [];
502             recurseAncestorsForHeaders(i, keys[i]);
503             headers[i] = headers[i].reverse();
504         }
505
506         // Save to the ColumnSet instance
507         this.tree = tree;
508         this.flat = flat;
509         this.keys = keys;
510         this.headers = headers;
511     },
512
513     /////////////////////////////////////////////////////////////////////////////
514     //
515     // Public methods
516     //
517     /////////////////////////////////////////////////////////////////////////////
518
519     /**
520      * Returns unique name of the ColumnSet instance.
521      *
522      * @method getId
523      * @return {String} Unique name of the ColumnSet instance.
524      */
525
526     getId : function() {
527         return this._sId;
528     },
529
530     /**
531      * ColumnSet instance name, for logging.
532      *
533      * @method toString
534      * @return {String} Unique name of the ColumnSet instance.
535      */
536
537     toString : function() {
538         return "ColumnSet instance " + this._sId;
539     },
540
541     /**
542      * Public accessor to the definitions array.
543      *
544      * @method getDefinitions
545      * @return {Object[]} Array of object literal Column definitions.
546      */
547
548     getDefinitions : function() {
549         var aDefinitions = this._aDefinitions;
550         
551         // Internal recursive function to define Column instances
552         var parseColumns = function(nodeList, oSelf) {
553             // Parse each node at this depth for attributes and any children
554             for(var j=0; j<nodeList.length; j++) {
555                 var currentNode = nodeList[j];
556                 
557                 // Get the Column for each node
558                 var oColumn = oSelf.getColumnById(currentNode.yuiColumnId);
559                 
560                 if(oColumn) {    
561                     // Update the current values
562                     var oDefinition = oColumn.getDefinition();
563                     for(var name in oDefinition) {
564                         if(YAHOO.lang.hasOwnProperty(oDefinition, name)) {
565                             currentNode[name] = oDefinition[name];
566                         }
567                     }
568                 }
569                             
570                 // The Column has descendants
571                 if(YAHOO.lang.isArray(currentNode.children)) {
572                     // The children themselves must also be parsed for Column instances
573                     parseColumns(currentNode.children, oSelf);
574                 }
575             }
576         };
577
578         parseColumns(aDefinitions, this);
579         this._aDefinitions = aDefinitions;
580         return aDefinitions;
581     },
582
583     /**
584      * Returns Column instance with given ID.
585      *
586      * @method getColumnById
587      * @param column {String} Column ID.
588      * @return {YAHOO.widget.Column} Column instance.
589      */
590
591     getColumnById : function(column) {
592         if(YAHOO.lang.isString(column)) {
593             var allColumns = this.flat;
594             for(var i=allColumns.length-1; i>-1; i--) {
595                 if(allColumns[i]._sId === column) {
596                     return allColumns[i];
597                 }
598             }
599         }
600         return null;
601     },
602
603     /**
604      * Returns Column instance with given key or ColumnSet key index.
605      *
606      * @method getColumn
607      * @param column {String | Number} Column key or ColumnSet key index.
608      * @return {YAHOO.widget.Column} Column instance.
609      */
610
611     getColumn : function(column) {
612         if(YAHOO.lang.isNumber(column) && this.keys[column]) {
613             return this.keys[column];
614         }
615         else if(YAHOO.lang.isString(column)) {
616             var allColumns = this.flat;
617             var aColumns = [];
618             for(var i=0; i<allColumns.length; i++) {
619                 if(allColumns[i].key === column) {
620                     aColumns.push(allColumns[i]);
621                 }
622             }
623             if(aColumns.length === 1) {
624                 return aColumns[0];
625             }
626             else if(aColumns.length > 1) {
627                 return aColumns;
628             }
629         }
630         return null;
631     },
632
633     /**
634      * Public accessor returns array of given Column's desendants (if any), including itself.
635      *
636      * @method getDescendants
637      * @parem {YAHOO.widget.Column} Column instance.
638      * @return {Array} Array including the Column itself and all descendants (if any).
639      */
640     getDescendants : function(oColumn) {
641         var oSelf = this;
642         var allDescendants = [];
643         var i;
644
645         // Recursive function to loop thru all children
646         var parse = function(oParent) {
647             allDescendants.push(oParent);
648             // This Column has children
649             if(oParent.children) {
650                 for(i=0; i<oParent.children.length; i++) {
651                     parse(oSelf.getColumn(oParent.children[i].key));
652                 }
653             }
654         };
655         parse(oColumn);
656
657         return allDescendants;
658     }
659 };
660
661 /****************************************************************************/
662 /****************************************************************************/
663 /****************************************************************************/
664
665 /**
666  * The Column class defines and manages attributes of DataTable Columns
667  *
668  * @namespace YAHOO.widget
669  * @class Column
670  * @constructor
671  * @param oConfigs {Object} Object literal of definitions.
672  */
673 YAHOO.widget.Column = function(oConfigs) {
674     this._sId = "yui-col" + YAHOO.widget.Column._nCount;
675     
676     // Object literal defines Column attributes
677     if(oConfigs && YAHOO.lang.isObject(oConfigs)) {
678         for(var sConfig in oConfigs) {
679             if(sConfig) {
680                 this[sConfig] = oConfigs[sConfig];
681             }
682         }
683     }
684
685     // Assign a key if not found
686     if(!YAHOO.lang.isValue(this.key)) {
687         this.key = "yui-dt-col" + YAHOO.widget.Column._nCount;
688     }
689     
690     // Assign a field if not found, defaults to key
691     if(!YAHOO.lang.isValue(this.field)) {
692         this.field = this.key;
693     }
694
695     // Increment counter
696     YAHOO.widget.Column._nCount++;
697
698     // Backward compatibility
699     if(this.width && !YAHOO.lang.isNumber(this.width)) {
700         this.width = null;
701     }
702     if(this.editor && YAHOO.lang.isString(this.editor)) {
703         this.editor = new YAHOO.widget.CellEditor(this.editor, this.editorOptions);
704     }
705 };
706
707 /////////////////////////////////////////////////////////////////////////////
708 //
709 // Private member variables
710 //
711 /////////////////////////////////////////////////////////////////////////////
712
713 YAHOO.lang.augmentObject(YAHOO.widget.Column, {
714     /**
715      * Internal class variable to index multiple Column instances.
716      *
717      * @property Column._nCount
718      * @type Number
719      * @private
720      * @static
721      */
722     _nCount : 0,
723
724     formatCheckbox : function(elCell, oRecord, oColumn, oData) {
725         YAHOO.widget.DataTable.formatCheckbox(elCell, oRecord, oColumn, oData);
726     },
727
728     formatCurrency : function(elCell, oRecord, oColumn, oData) {
729         YAHOO.widget.DataTable.formatCurrency(elCell, oRecord, oColumn, oData);
730     },
731
732     formatDate : function(elCell, oRecord, oColumn, oData) {
733         YAHOO.widget.DataTable.formatDate(elCell, oRecord, oColumn, oData);
734     },
735
736     formatEmail : function(elCell, oRecord, oColumn, oData) {
737         YAHOO.widget.DataTable.formatEmail(elCell, oRecord, oColumn, oData);
738     },
739
740     formatLink : function(elCell, oRecord, oColumn, oData) {
741         YAHOO.widget.DataTable.formatLink(elCell, oRecord, oColumn, oData);
742     },
743
744     formatNumber : function(elCell, oRecord, oColumn, oData) {
745         YAHOO.widget.DataTable.formatNumber(elCell, oRecord, oColumn, oData);
746     },
747
748     formatSelect : function(elCell, oRecord, oColumn, oData) {
749         YAHOO.widget.DataTable.formatDropdown(elCell, oRecord, oColumn, oData);
750     }
751 });
752
753 YAHOO.widget.Column.prototype = {
754     /**
755      * Unique String identifier assigned at instantiation.
756      *
757      * @property _sId
758      * @type String
759      * @private
760      */
761     _sId : null,
762
763     /**
764      * Reference to Column's current position index within its ColumnSet's keys
765      * array, if applicable. This property only applies to non-nested and bottom-
766      * level child Columns.
767      *
768      * @property _nKeyIndex
769      * @type Number
770      * @private
771      */
772     _nKeyIndex : null,
773
774     /**
775      * Reference to Column's current position index within its ColumnSet's tree
776      * array, if applicable. This property only applies to non-nested and top-
777      * level parent Columns.
778      *
779      * @property _nTreeIndex
780      * @type Number
781      * @private
782      */
783     _nTreeIndex : null,
784
785     /**
786      * Number of table cells the Column spans.
787      *
788      * @property _nColspan
789      * @type Number
790      * @private
791      */
792     _nColspan : 1,
793
794     /**
795      * Number of table rows the Column spans.
796      *
797      * @property _nRowspan
798      * @type Number
799      * @private
800      */
801     _nRowspan : 1,
802
803     /**
804      * Column's parent Column instance, or null.
805      *
806      * @property _oParent
807      * @type YAHOO.widget.Column
808      * @private
809      */
810     _oParent : null,
811
812     /**
813      * The DOM reference to the associated TH element.
814      *
815      * @property _elTh
816      * @type HTMLElement
817      * @private
818      */
819     _elTh : null,
820
821     /**
822      * The DOM reference to the associated TH element's liner DIV element.
823      *
824      * @property _elThLiner
825      * @type HTMLElement
826      * @private
827      */
828     _elThLiner : null,
829
830     /**
831      * The DOM reference to the associated TH element's label SPAN element.
832      *
833      * @property _elThLabel
834      * @type HTMLElement
835      * @private
836      */
837     _elThLabel : null,
838
839     /**
840      * The DOM reference to the associated resizerelement (if any).
841      *
842      * @property _elResizer
843      * @type HTMLElement
844      * @private
845      */
846     _elResizer : null,
847
848     /**
849      * Internal width tracker.
850      *
851      * @property _nWidth
852      * @type Number
853      * @private
854      */
855     _nWidth : null,
856
857     /**
858      * For unreg() purposes, a reference to the Column's DragDrop instance.
859      *
860      * @property _dd
861      * @type YAHOO.util.DragDrop
862      * @private
863      */
864     _dd : null,
865
866     /**
867      * For unreg() purposes, a reference to the Column resizer's DragDrop instance.
868      *
869      * @property _ddResizer
870      * @type YAHOO.util.DragDrop
871      * @private
872      */
873     _ddResizer : null,
874
875     /////////////////////////////////////////////////////////////////////////////
876     //
877     // Public member variables
878     //
879     /////////////////////////////////////////////////////////////////////////////
880
881     /**
882      * Unique name, required.
883      *
884      * @property key
885      * @type String
886      */
887     key : null,
888
889     /**
890      * Associated database field, or null.
891      *
892      * @property field
893      * @type String
894      */
895     field : null,
896
897     /**
898      * Text or HTML for display as Column's label in the TH element.
899      *
900      * @property label
901      * @type String
902      */
903     label : null,
904
905     /**
906      * Column head cell ABBR for accessibility.
907      *
908      * @property abbr
909      * @type String
910      */
911     abbr : null,
912
913     /**
914      * Array of object literals that define children (nested headers) of a Column.
915      *
916      * @property children
917      * @type Object[]
918      */
919     children : null,
920
921     /**
922      * Column width (in pixels).
923      *
924      * @property width
925      * @type Number
926      */
927     width : null,
928
929     /**
930      * Minimum Column width (in pixels).
931      *
932      * @property minWidth
933      * @type Number
934      * @default null
935      */
936     minWidth : null,
937
938     /**
939      * When a width is not defined for a Column, maxAutoWidth defines an upper
940      * limit that the Column should be auto-sized to. If resizeable is enabled, 
941      * users may still resize to a greater width. Most useful for Columns intended
942      * to hold long unbroken, unwrapped Strings, such as URLs, to prevent very
943      * wide Columns from disrupting visual readability by inducing truncation.
944      *
945      * @property maxAutoWidth
946      * @type Number
947      * @default null
948      */
949     maxAutoWidth : null,
950
951     /**
952      * True if Column is in hidden state.
953      *
954      * @property hidden
955      * @type Boolean
956      * @default false     
957      */
958     hidden : false,
959
960     /**
961      * True if Column is in selected state.
962      *
963      * @property selected
964      * @type Boolean
965      * @default false     
966      */
967     selected : false,
968
969     /**
970      * Custom CSS class or array of classes to be applied to every cell in the Column.
971      *
972      * @property className
973      * @type String || String[]
974      */
975     className : null,
976
977     /**
978      * Defines a format function.
979      *
980      * @property formatter
981      * @type String || HTMLFunction
982      */
983     formatter : null,
984     
985     /**
986      * Config passed to YAHOO.util.Number.format() by the 'currency' Column formatter.
987      *
988      * @property currencyOptions
989      * @type Object
990      * @default null
991      */
992     currencyOptions : null,
993
994     /**
995      * Config passed to YAHOO.util.Date.format() by the 'date' Column formatter.
996      *
997      * @property dateOptions
998      * @type Object
999      * @default null
1000      */
1001     dateOptions : null,
1002
1003     /**
1004      * Array of dropdown values for formatter:"dropdown" cases. Can either be a simple array (e.g.,
1005      * ["Alabama","Alaska","Arizona","Arkansas"]) or a an array of objects (e.g.,
1006      * [{label:"Alabama", value:"AL"}, {label:"Alaska", value:"AK"},
1007      * {label:"Arizona", value:"AZ"}, {label:"Arkansas", value:"AR"}]).
1008      *
1009      * @property dropdownOptions
1010      * @type String[] | Object[]
1011      */
1012     dropdownOptions : null,
1013      
1014     /**
1015      * A CellEditor instance, otherwise Column is not editable.     
1016      *
1017      * @property editor
1018      * @type YAHOO.widget.CellEditor
1019      */
1020     editor : null,
1021
1022     /**
1023      * True if Column is resizeable, false otherwise. The Drag & Drop Utility is
1024      * required to enable this feature. Only bottom-level and non-nested Columns are
1025      * resizeble. 
1026      *
1027      * @property resizeable
1028      * @type Boolean
1029      * @default false
1030      */
1031     resizeable : false,
1032
1033     /**
1034      * True if Column is sortable, false otherwise.
1035      *
1036      * @property sortable
1037      * @type Boolean
1038      * @default false
1039      */
1040     sortable : false,
1041
1042     /**
1043      * @property sortOptions.defaultOrder
1044      * @deprecated Use sortOptions.defaultDir.
1045      */
1046     /**
1047      * Default sort direction for Column: YAHOO.widget.DataTable.CLASS_ASC or YAHOO.widget.DataTable.CLASS_DESC.
1048      *
1049      * @property sortOptions.defaultDir
1050      * @type String
1051      * @default null
1052      */
1053     /**
1054      * Custom field to sort on.
1055      *
1056      * @property sortOptions.field
1057      * @type String
1058      * @default null
1059      */
1060     /**
1061      * Custom sort handler. Signature: sortFunction(a, b, desc, field) where field is the sortOptions.field value
1062      *
1063      * @property sortOptions.sortFunction
1064      * @type Function
1065      * @default null
1066      */
1067     sortOptions : null,
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083     /////////////////////////////////////////////////////////////////////////////
1084     //
1085     // Public methods
1086     //
1087     /////////////////////////////////////////////////////////////////////////////
1088
1089     /**
1090      * Returns unique ID string.
1091      *
1092      * @method getId
1093      * @return {String} Unique ID string.
1094      */
1095     getId : function() {
1096         return this._sId;
1097     },
1098
1099     /**
1100      * Column instance name, for logging.
1101      *
1102      * @method toString
1103      * @return {String} Column's unique name.
1104      */
1105     toString : function() {
1106         return "Column instance " + this._sId;
1107     },
1108
1109     /**
1110      * Returns object literal definition.
1111      *
1112      * @method getDefinition
1113      * @return {Object} Object literal definition.
1114      */
1115     getDefinition : function() {
1116         var oDefinition = {};
1117         
1118         // Update the definition
1119         oDefinition.abbr = this.abbr;
1120         oDefinition.className = this.className;
1121         oDefinition.editor = this.editor;
1122         oDefinition.editorOptions = this.editorOptions; //TODO: deprecated
1123         oDefinition.field = this.field;
1124         oDefinition.formatter = this.formatter;
1125         oDefinition.hidden = this.hidden;
1126         oDefinition.key = this.key;
1127         oDefinition.label = this.label;
1128         oDefinition.minWidth = this.minWidth;
1129         oDefinition.maxAutoWidth = this.maxAutoWidth;
1130         oDefinition.resizeable = this.resizeable;
1131         oDefinition.selected = this.selected;
1132         oDefinition.sortable = this.sortable;
1133         oDefinition.sortOptions = this.sortOptions;
1134         oDefinition.width = this.width;
1135
1136         return oDefinition;
1137     },
1138
1139     /**
1140      * Returns unique Column key.
1141      *
1142      * @method getKey
1143      * @return {String} Column key.
1144      */
1145     getKey : function() {
1146         return this.key;
1147     },
1148     
1149     /**
1150      * Returns field.
1151      *
1152      * @method getField
1153      * @return {String} Column field.
1154      */
1155     getField : function() {
1156         return this.field;
1157     },
1158     
1159     /**
1160      * Returns Column key which has been sanitized for DOM (class and ID) usage
1161      * starts with letter, contains only letters, numbers, hyphen, or period.
1162      *
1163      * @method getSanitizedKey
1164      * @return {String} Sanitized Column key.
1165      */
1166     getSanitizedKey : function() {
1167         return this.getKey().replace(/[^\w\-]/g,"");
1168     },
1169
1170     /**
1171      * Public accessor returns Column's current position index within its
1172      * ColumnSet's keys array, if applicable. Only non-nested and bottom-level
1173      * child Columns will return a value.
1174      *
1175      * @method getKeyIndex
1176      * @return {Number} Position index, or null.
1177      */
1178     getKeyIndex : function() {
1179         return this._nKeyIndex;
1180     },
1181
1182     /**
1183      * Public accessor returns Column's current position index within its
1184      * ColumnSet's tree array, if applicable. Only non-nested and top-level parent
1185      * Columns will return a value;
1186      *
1187      * @method getTreeIndex
1188      * @return {Number} Position index, or null.
1189      */
1190     getTreeIndex : function() {
1191         return this._nTreeIndex;
1192     },
1193
1194     /**
1195      * Public accessor returns Column's parent instance if any, or null otherwise.
1196      *
1197      * @method getParent
1198      * @return {YAHOO.widget.Column} Column's parent instance.
1199      */
1200     getParent : function() {
1201         return this._oParent;
1202     },
1203
1204     /**
1205      * Public accessor returns Column's calculated COLSPAN value.
1206      *
1207      * @method getColspan
1208      * @return {Number} Column's COLSPAN value.
1209      */
1210     getColspan : function() {
1211         return this._nColspan;
1212     },
1213     // Backward compatibility
1214     getColSpan : function() {
1215         return this.getColspan();
1216     },
1217
1218     /**
1219      * Public accessor returns Column's calculated ROWSPAN value.
1220      *
1221      * @method getRowspan
1222      * @return {Number} Column's ROWSPAN value.
1223      */
1224     getRowspan : function() {
1225         return this._nRowspan;
1226     },
1227
1228     /**
1229      * Returns DOM reference to the key TH element.
1230      *
1231      * @method getThEl
1232      * @return {HTMLElement} TH element.
1233      */
1234     getThEl : function() {
1235         return this._elTh;
1236     },
1237
1238     /**
1239      * Returns DOM reference to the TH's liner DIV element. Introduced since
1240      * resizeable Columns may have an extra resizer liner, making the DIV liner
1241      * not reliably the TH element's first child.               
1242      *
1243      * @method getThLInerEl
1244      * @return {HTMLElement} TH element.
1245      */
1246     getThLinerEl : function() {
1247         return this._elThLiner;
1248     },
1249     
1250     /**
1251      * Returns DOM reference to the resizer element, or null.
1252      *
1253      * @method getResizerEl
1254      * @return {HTMLElement} DIV element.
1255      */
1256     getResizerEl : function() {
1257         return this._elResizer;
1258     },
1259
1260     // Backward compatibility
1261     /**
1262      * @method getColEl
1263      * @deprecated Use getThEl
1264      */
1265     getColEl : function() {
1266         return this.getThEl();
1267     },
1268     getIndex : function() {
1269         return this.getKeyIndex();
1270     },
1271     format : function() {
1272     }
1273 };
1274
1275 /****************************************************************************/
1276 /****************************************************************************/
1277 /****************************************************************************/
1278
1279 /**
1280  * Sort static utility to support Column sorting.
1281  *
1282  * @namespace YAHOO.util
1283  * @class Sort
1284  * @static
1285  */
1286 YAHOO.util.Sort = {
1287     /////////////////////////////////////////////////////////////////////////////
1288     //
1289     // Public methods
1290     //
1291     /////////////////////////////////////////////////////////////////////////////
1292
1293     /**
1294      * Comparator function for simple case-insensitive string sorting.
1295      *
1296      * @method compare
1297      * @param a {Object} First sort argument.
1298      * @param b {Object} Second sort argument.
1299      * @param desc {Boolean} True if sort direction is descending, false if
1300      * sort direction is ascending.
1301      */
1302     compare: function(a, b, desc) {
1303         if((a === null) || (typeof a == "undefined")) {
1304             if((b === null) || (typeof b == "undefined")) {
1305                 return 0;
1306             }
1307             else {
1308                 return 1;
1309             }
1310         }
1311         else if((b === null) || (typeof b == "undefined")) {
1312             return -1;
1313         }
1314
1315         if(a.constructor == String) {
1316             a = a.toLowerCase();
1317         }
1318         if(b.constructor == String) {
1319             b = b.toLowerCase();
1320         }
1321         if(a < b) {
1322             return (desc) ? 1 : -1;
1323         }
1324         else if (a > b) {
1325             return (desc) ? -1 : 1;
1326         }
1327         else {
1328             return 0;
1329         }
1330     }
1331 };
1332
1333 /****************************************************************************/
1334 /****************************************************************************/
1335 /****************************************************************************/
1336
1337 /**
1338  * ColumnDD subclasses DragDrop to support rearrangeable Columns.
1339  *
1340  * @namespace YAHOO.util
1341  * @class ColumnDD
1342  * @extends YAHOO.util.DDProxy
1343  * @constructor
1344  * @param oDataTable {YAHOO.widget.DataTable} DataTable instance.
1345  * @param oColumn {YAHOO.widget.Column} Column instance.
1346  * @param elTh {HTMLElement} TH element reference.
1347  * @param elTarget {HTMLElement} Drag target element.
1348  */
1349 YAHOO.widget.ColumnDD = function(oDataTable, oColumn, elTh, elTarget) {
1350     if(oDataTable && oColumn && elTh && elTarget) {
1351         this.datatable = oDataTable;
1352         this.table = oDataTable.getTableEl();
1353         this.column = oColumn;
1354         this.headCell = elTh;
1355         this.pointer = elTarget;
1356         this.newIndex = null;
1357         this.init(elTh);
1358         this.initFrame(); // Needed for DDProxy
1359         this.invalidHandleTypes = {};
1360
1361         // Set top/bottom padding to account for children of nested columns
1362         this.setPadding(10, 0, (this.datatable.getTheadEl().offsetHeight + 10) , 0);
1363
1364         YAHOO.util.Event.on(window, 'resize', function() {
1365             this.initConstraints();
1366         }, this, true);
1367     }
1368     else {
1369     }
1370 };
1371
1372 if(YAHOO.util.DDProxy) {
1373     YAHOO.extend(YAHOO.widget.ColumnDD, YAHOO.util.DDProxy, {
1374         initConstraints: function() {
1375             //Get the top, right, bottom and left positions
1376             var region = YAHOO.util.Dom.getRegion(this.table),
1377                 //Get the element we are working on
1378                 el = this.getEl(),
1379                 //Get the xy position of it
1380                 xy = YAHOO.util.Dom.getXY(el),
1381                 //Get the width and height
1382                 width = parseInt(YAHOO.util.Dom.getStyle(el, 'width'), 10),
1383                 height = parseInt(YAHOO.util.Dom.getStyle(el, 'height'), 10),
1384                 //Set left to x minus left
1385                 left = ((xy[0] - region.left) + 15), //Buffer of 15px
1386                 //Set right to right minus x minus width
1387                 right = ((region.right - xy[0] - width) + 15);
1388     
1389             //Set the constraints based on the above calculations
1390             this.setXConstraint(left, right);
1391             this.setYConstraint(10, 10);            
1392         },
1393         _resizeProxy: function() {
1394             YAHOO.widget.ColumnDD.superclass._resizeProxy.apply(this, arguments);
1395             var dragEl = this.getDragEl(),
1396                 el = this.getEl();
1397
1398             YAHOO.util.Dom.setStyle(this.pointer, 'height', (this.table.parentNode.offsetHeight + 10) + 'px');
1399             YAHOO.util.Dom.setStyle(this.pointer, 'display', 'block');
1400             var xy = YAHOO.util.Dom.getXY(el);
1401             YAHOO.util.Dom.setXY(this.pointer, [xy[0], (xy[1] - 5)]);
1402             
1403             YAHOO.util.Dom.setStyle(dragEl, 'height', this.datatable.getContainerEl().offsetHeight + "px");
1404             YAHOO.util.Dom.setStyle(dragEl, 'width', (parseInt(YAHOO.util.Dom.getStyle(dragEl, 'width'),10) + 4) + 'px');
1405             YAHOO.util.Dom.setXY(this.dragEl, xy);
1406         },
1407         onMouseDown: function() {
1408                 this.initConstraints();
1409                 this.resetConstraints();
1410         },
1411         clickValidator: function(e) {
1412             if(!this.column.hidden) {
1413                 var target = YAHOO.util.Event.getTarget(e);
1414                 return ( this.isValidHandleChild(target) &&
1415                             (this.id == this.handleElId ||
1416                                 this.DDM.handleWasClicked(target, this.id)) );
1417             }
1418         },
1419         onDragOver: function(ev, id) {
1420             // Validate target as a Column
1421             var target = this.datatable.getColumn(id);
1422             if(target) {                
1423                 // Validate target as a top-level parent
1424                 var targetIndex = target.getTreeIndex();
1425                 while((targetIndex === null) && target.getParent()) {
1426                     target = target.getParent();
1427                     targetIndex = target.getTreeIndex();
1428                 }
1429                 if(targetIndex !== null) {
1430                     // Are we placing to left or right of target?
1431                     var elTarget = target.getThEl();
1432                     var newIndex = targetIndex;
1433                     var mouseX = YAHOO.util.Event.getPageX(ev),
1434                         targetX = YAHOO.util.Dom.getX(elTarget),
1435                         midX = targetX + ((YAHOO.util.Dom.get(elTarget).offsetWidth)/2),
1436                         currentIndex =  this.column.getTreeIndex();
1437                     
1438                     if (mouseX < midX) {
1439                        YAHOO.util.Dom.setX(this.pointer, targetX);
1440                     } else {
1441                         var targetWidth = parseInt(elTarget.offsetWidth, 10);
1442                         YAHOO.util.Dom.setX(this.pointer, (targetX + targetWidth));
1443                         newIndex++;
1444                     }
1445                     if (targetIndex > currentIndex) {
1446                         newIndex--;
1447                     }
1448                     if(newIndex < 0) {
1449                         newIndex = 0;
1450                     }
1451                     else if(newIndex > this.datatable.getColumnSet().tree[0].length) {
1452                         newIndex = this.datatable.getColumnSet().tree[0].length;
1453                     }
1454                     this.newIndex = newIndex;
1455                 }
1456             }
1457         },
1458         onDragDrop: function() {
1459             this.datatable.reorderColumn(this.column, this.newIndex);
1460         },
1461         endDrag: function() {
1462             this.newIndex = null;
1463             YAHOO.util.Dom.setStyle(this.pointer, 'display', 'none');
1464         }
1465     });
1466 }
1467
1468 /****************************************************************************/
1469 /****************************************************************************/
1470 /****************************************************************************/
1471
1472 /**
1473  * ColumnResizer subclasses DragDrop to support resizeable Columns.
1474  *
1475  * @namespace YAHOO.util
1476  * @class ColumnResizer
1477  * @extends YAHOO.util.DDProxy
1478  * @constructor
1479  * @param oDataTable {YAHOO.widget.DataTable} DataTable instance.
1480  * @param oColumn {YAHOO.widget.Column} Column instance.
1481  * @param elTh {HTMLElement} TH element reference.
1482  * @param sHandleElId {String} DOM ID of the handle element that causes the resize.
1483  * @param elProxy {HTMLElement} Resizer proxy element.
1484  */
1485 YAHOO.util.ColumnResizer = function(oDataTable, oColumn, elTh, sHandleId, elProxy) {
1486     if(oDataTable && oColumn && elTh && sHandleId) {
1487         this.datatable = oDataTable;
1488         this.column = oColumn;
1489         this.headCell = elTh;
1490         this.headCellLiner = oColumn.getThLinerEl();
1491         this.resizerLiner = elTh.firstChild;
1492         this.init(sHandleId, sHandleId, {dragOnly:true, dragElId: elProxy.id});
1493         this.initFrame(); // Needed for proxy
1494         this.resetResizerEl(); // Needed when rowspan > 0
1495
1496         // Set right padding for bug 1858462
1497         this.setPadding(0, 1, 0, 0);
1498     }
1499     else {
1500     }
1501 };
1502
1503 if(YAHOO.util.DD) {
1504     YAHOO.extend(YAHOO.util.ColumnResizer, YAHOO.util.DDProxy, {
1505         /////////////////////////////////////////////////////////////////////////////
1506         //
1507         // Public methods
1508         //
1509         /////////////////////////////////////////////////////////////////////////////
1510         /**
1511          * Resets resizer element.
1512          *
1513          * @method resetResizerEl
1514          */
1515         resetResizerEl : function() {
1516             var resizerStyle = YAHOO.util.Dom.get(this.handleElId).style;
1517             resizerStyle.left = "auto";
1518             resizerStyle.right = 0;
1519             resizerStyle.top = "auto";
1520             resizerStyle.bottom = 0;
1521             resizerStyle.height = this.headCell.offsetHeight+"px";
1522         },
1523     
1524         /////////////////////////////////////////////////////////////////////////////
1525         //
1526         // Public DOM event handlers
1527         //
1528         /////////////////////////////////////////////////////////////////////////////
1529     
1530         /**
1531          * Handles mouseup events on the Column resizer.
1532          *
1533          * @method onMouseUp
1534          * @param e {string} The mouseup event
1535          */
1536         onMouseUp : function(e) {
1537             // Reset height of all resizer els in case TH's have changed height
1538             var allKeys = this.datatable.getColumnSet().keys,
1539                 col;
1540             for(var i=0, len=allKeys.length; i<len; i++) {
1541                 col = allKeys[i];
1542                 if(col._ddResizer) {
1543                     col._ddResizer.resetResizerEl();
1544                 }
1545             }
1546             this.resetResizerEl();
1547             
1548             var el = this.headCellLiner;
1549             var newWidth = el.offsetWidth -
1550                 (parseInt(YAHOO.util.Dom.getStyle(el,"paddingLeft"),10)|0) -
1551                 (parseInt(YAHOO.util.Dom.getStyle(el,"paddingRight"),10)|0);
1552
1553             this.datatable.fireEvent("columnResizeEvent", {column:this.column,target:this.headCell,width:newWidth});
1554         },
1555     
1556         /**
1557          * Handles mousedown events on the Column resizer.
1558          *
1559          * @method onMouseDown
1560          * @param e {string} The mousedown event
1561          */
1562         onMouseDown : function(e) {
1563             this.startWidth = this.headCellLiner.offsetWidth;
1564             this.startX = YAHOO.util.Event.getXY(e)[0];
1565             this.nLinerPadding = (parseInt(YAHOO.util.Dom.getStyle(this.headCellLiner,"paddingLeft"),10)|0) +
1566                     (parseInt(YAHOO.util.Dom.getStyle(this.headCellLiner,"paddingRight"),10)|0);
1567         },
1568     
1569         /**
1570          * Custom clickValidator to ensure Column is not in hidden state.
1571          *
1572          * @method clickValidator
1573          * @param {Event} e
1574          * @private
1575          */
1576         clickValidator : function(e) {
1577             if(!this.column.hidden) {
1578                 var target = YAHOO.util.Event.getTarget(e);
1579                 return ( this.isValidHandleChild(target) &&
1580                             (this.id == this.handleElId ||
1581                                 this.DDM.handleWasClicked(target, this.id)) );
1582             }
1583         },
1584     
1585         /**
1586          * Handles start drag on the Column resizer.
1587          *
1588          * @method startDrag
1589          * @param e {string} The drag event
1590          */
1591         startDrag : function() {
1592             // Shrinks height of all resizer els to not hold open TH els
1593             var allKeys = this.datatable.getColumnSet().keys,
1594                 thisKey = this.column.getKeyIndex(),
1595                 col;
1596             for(var i=0, len=allKeys.length; i<len; i++) {
1597                 col = allKeys[i];
1598                 if(col._ddResizer) {
1599                     YAHOO.util.Dom.get(col._ddResizer.handleElId).style.height = "1em";
1600                 }
1601             }
1602         },
1603
1604         /**
1605          * Handles drag events on the Column resizer.
1606          *
1607          * @method onDrag
1608          * @param e {string} The drag event
1609          */
1610         onDrag : function(e) {
1611             var newX = YAHOO.util.Event.getXY(e)[0];
1612             if(newX > YAHOO.util.Dom.getX(this.headCellLiner)) {
1613                 var offsetX = newX - this.startX;
1614                 var newWidth = this.startWidth + offsetX - this.nLinerPadding;
1615                 if(newWidth > 0) {
1616                     this.datatable.setColumnWidth(this.column, newWidth);
1617                 }
1618             }
1619         }
1620     });
1621 }
1622
1623 /////////////////////////////////////////////////////////////////////////////
1624 //
1625 // Deprecated
1626 //
1627 /////////////////////////////////////////////////////////////////////////////
1628
1629 /**
1630  * @property editorOptions
1631  * @deprecated Pass configs directly to CellEditor constructor. 
1632  */
1633
1634
1635 (function () {
1636
1637 var lang   = YAHOO.lang,
1638     util   = YAHOO.util,
1639     widget = YAHOO.widget,
1640     
1641     Dom    = util.Dom,
1642     Ev     = util.Event,
1643     DT     = widget.DataTable;
1644
1645 /****************************************************************************/
1646 /****************************************************************************/
1647 /****************************************************************************/
1648
1649 /**
1650  * A RecordSet defines and manages a set of Records.
1651  *
1652  * @namespace YAHOO.widget
1653  * @class RecordSet
1654  * @param data {Object || Object[]} An object literal or an array of data.
1655  * @constructor
1656  */
1657 YAHOO.widget.RecordSet = function(data) {
1658     // Internal variables
1659     this._sId = "yui-rs" + widget.RecordSet._nCount;
1660     widget.RecordSet._nCount++;
1661     this._records = [];
1662     //this._length = 0;
1663
1664     if(data) {
1665         if(lang.isArray(data)) {
1666             this.addRecords(data);
1667         }
1668         else if(lang.isObject(data)) {
1669             this.addRecord(data);
1670         }
1671     }
1672
1673 };
1674
1675 var RS = widget.RecordSet;
1676
1677 /**
1678  * Internal class variable to name multiple Recordset instances.
1679  *
1680  * @property RecordSet._nCount
1681  * @type Number
1682  * @private
1683  * @static
1684  */
1685 RS._nCount = 0;
1686
1687 RS.prototype = {
1688
1689     /////////////////////////////////////////////////////////////////////////////
1690     //
1691     // Private member variables
1692     //
1693     /////////////////////////////////////////////////////////////////////////////
1694     /**
1695      * Unique String identifier assigned at instantiation.
1696      *
1697      * @property _sId
1698      * @type String
1699      * @private
1700      */
1701     _sId : null,
1702
1703     /**
1704      * Internal counter of how many Records are in the RecordSet.
1705      *
1706      * @property _length
1707      * @type Number
1708      * @private
1709      * @deprecated No longer used
1710      */
1711     //_length : null,
1712
1713     /////////////////////////////////////////////////////////////////////////////
1714     //
1715     // Private methods
1716     //
1717     /////////////////////////////////////////////////////////////////////////////
1718
1719     /**
1720      * Adds one Record to the RecordSet at the given index. If index is null,
1721      * then adds the Record to the end of the RecordSet.
1722      *
1723      * @method _addRecord
1724      * @param oData {Object} An object literal of data.
1725      * @param index {Number} (optional) Position index.
1726      * @return {YAHOO.widget.Record} A Record instance.
1727      * @private
1728      */
1729     _addRecord : function(oData, index) {
1730         var oRecord = new YAHOO.widget.Record(oData);
1731         
1732         if(YAHOO.lang.isNumber(index) && (index > -1)) {
1733             this._records.splice(index,0,oRecord);
1734         }
1735         else {
1736             //index = this.getLength();
1737             //this._records[index] = oRecord;
1738             this._records[this._records.length] = oRecord;
1739         }
1740         //this._length++;
1741         return oRecord;
1742     },
1743
1744     /**
1745      * Sets/replaces one Record to the RecordSet at the given index.  Existing
1746      * Records with higher indexes are not shifted.  If no index specified, the
1747      * Record is added to the end of the RecordSet.
1748      *
1749      * @method _setRecord
1750      * @param oData {Object} An object literal of data.
1751      * @param index {Number} (optional) Position index.
1752      * @return {YAHOO.widget.Record} A Record instance.
1753      * @private
1754      */
1755     _setRecord : function(oData, index) {
1756         if (!lang.isNumber(index) || index < 0) {
1757             index = this._records.length;
1758         }
1759         return (this._records[index] = new widget.Record(oData));
1760         /*
1761         if(lang.isNumber(index) && (index > -1)) {
1762             this._records[index] = oRecord;
1763             if((index+1) > this.getLength()) {
1764                 this._length = index+1;
1765             }
1766         }
1767         else {
1768             this._records[this.getLength()] = oRecord;
1769             this._length++;
1770         }
1771         return oRecord;
1772         */
1773     },
1774
1775     /**
1776      * Deletes Records from the RecordSet at the given index. If range is null,
1777      * then only one Record is deleted.
1778      *
1779      * @method _deleteRecord
1780      * @param index {Number} Position index.
1781      * @param range {Number} (optional) How many Records to delete
1782      * @private
1783      */
1784     _deleteRecord : function(index, range) {
1785         if(!lang.isNumber(range) || (range < 0)) {
1786             range = 1;
1787         }
1788         this._records.splice(index, range);
1789         //this._length = this._length - range;
1790     },
1791
1792     /////////////////////////////////////////////////////////////////////////////
1793     //
1794     // Public methods
1795     //
1796     /////////////////////////////////////////////////////////////////////////////
1797
1798     /**
1799      * Returns unique name of the RecordSet instance.
1800      *
1801      * @method getId
1802      * @return {String} Unique name of the RecordSet instance.
1803      */
1804     getId : function() {
1805         return this._sId;
1806     },
1807
1808     /**
1809      * Public accessor to the unique name of the RecordSet instance.
1810      *
1811      * @method toString
1812      * @return {String} Unique name of the RecordSet instance.
1813      */
1814     toString : function() {
1815         return "RecordSet instance " + this._sId;
1816     },
1817
1818     /**
1819      * Returns the number of Records held in the RecordSet.
1820      *
1821      * @method getLength
1822      * @return {Number} Number of records in the RecordSet.
1823      */
1824     getLength : function() {
1825             //return this._length;
1826             return this._records.length;
1827     },
1828
1829     /**
1830      * Returns Record by ID or RecordSet position index.
1831      *
1832      * @method getRecord
1833      * @param record {YAHOO.widget.Record | Number | String} Record instance,
1834      * RecordSet position index, or Record ID.
1835      * @return {YAHOO.widget.Record} Record object.
1836      */
1837     getRecord : function(record) {
1838         var i;
1839         if(record instanceof widget.Record) {
1840             for(i=0; i<this._records.length; i++) {
1841                 if(this._records[i] && (this._records[i]._sId === record._sId)) {
1842                     return record;
1843                 }
1844             }
1845         }
1846         else if(lang.isNumber(record)) {
1847             if((record > -1) && (record < this.getLength())) {
1848                 return this._records[record];
1849             }
1850         }
1851         else if(lang.isString(record)) {
1852             for(i=0; i<this._records.length; i++) {
1853                 if(this._records[i] && (this._records[i]._sId === record)) {
1854                     return this._records[i];
1855                 }
1856             }
1857         }
1858         // Not a valid Record for this RecordSet
1859         return null;
1860
1861     },
1862
1863     /**
1864      * Returns an array of Records from the RecordSet.
1865      *
1866      * @method getRecords
1867      * @param index {Number} (optional) Recordset position index of which Record to
1868      * start at.
1869      * @param range {Number} (optional) Number of Records to get.
1870      * @return {YAHOO.widget.Record[]} Array of Records starting at given index and
1871      * length equal to given range. If index is not given, all Records are returned.
1872      */
1873     getRecords : function(index, range) {
1874         if(!lang.isNumber(index)) {
1875             return this._records;
1876         }
1877         if(!lang.isNumber(range)) {
1878             return this._records.slice(index);
1879         }
1880         return this._records.slice(index, index+range);
1881     },
1882
1883     /**
1884      * Returns a boolean indicating whether Records exist in the RecordSet at the
1885      * specified index range.  Returns true if and only if a Record exists at each
1886      * index in the range.
1887      * @method hasRecords
1888      * @param index
1889      * @param range
1890      * @return {Boolean} true if all indices are populated in the RecordSet
1891      */
1892     hasRecords : function (index, range) {
1893         var recs = this.getRecords(index,range);
1894         for (var i = 0; i < range; ++i) {
1895             if (typeof recs[i] === 'undefined') {
1896                 return false;
1897             }
1898         }
1899         return true;
1900     },
1901
1902     /**
1903      * Returns current position index for the given Record.
1904      *
1905      * @method getRecordIndex
1906      * @param oRecord {YAHOO.widget.Record} Record instance.
1907      * @return {Number} Record's RecordSet position index.
1908      */
1909
1910     getRecordIndex : function(oRecord) {
1911         if(oRecord) {
1912             for(var i=this._records.length-1; i>-1; i--) {
1913                 if(this._records[i] && oRecord.getId() === this._records[i].getId()) {
1914                     return i;
1915                 }
1916             }
1917         }
1918         return null;
1919
1920     },
1921
1922     /**
1923      * Adds one Record to the RecordSet at the given index. If index is null,
1924      * then adds the Record to the end of the RecordSet.
1925      *
1926      * @method addRecord
1927      * @param oData {Object} An object literal of data.
1928      * @param index {Number} (optional) Position index.
1929      * @return {YAHOO.widget.Record} A Record instance.
1930      */
1931     addRecord : function(oData, index) {
1932         if(lang.isObject(oData)) {
1933             var oRecord = this._addRecord(oData, index);
1934             this.fireEvent("recordAddEvent",{record:oRecord,data:oData});
1935             return oRecord;
1936         }
1937         else {
1938             return null;
1939         }
1940     },
1941
1942     /**
1943      * Adds multiple Records at once to the RecordSet at the given index with the
1944      * given object literal data. If index is null, then the new Records are
1945      * added to the end of the RecordSet.
1946      *
1947      * @method addRecords
1948      * @param aData {Object[]} An object literal data or an array of data object literals.
1949      * @param index {Number} (optional) Position index.
1950      * @return {YAHOO.widget.Record[]} An array of Record instances.
1951      */
1952     addRecords : function(aData, index) {
1953         if(lang.isArray(aData)) {
1954             var newRecords = [],
1955                 idx,i,len;
1956
1957             index = lang.isNumber(index) ? index : this._records.length;
1958             idx = index;
1959
1960             // Can't go backwards bc we need to preserve order
1961             for(i=0,len=aData.length; i<len; ++i) {
1962                 if(lang.isObject(aData[i])) {
1963                     var record = this._addRecord(aData[i], idx++);
1964                     newRecords.push(record);
1965                 }
1966            }
1967             this.fireEvent("recordsAddEvent",{records:newRecords,data:aData});
1968            return newRecords;
1969         }
1970         else if(lang.isObject(aData)) {
1971             var oRecord = this._addRecord(aData);
1972             this.fireEvent("recordsAddEvent",{records:[oRecord],data:aData});
1973             return oRecord;
1974         }
1975         else {
1976             return null;
1977         }
1978     },
1979
1980     /**
1981      * Sets or replaces one Record to the RecordSet at the given index. Unlike
1982      * addRecord, an existing Record at that index is not shifted to preserve it.
1983      * If no index is specified, it adds the Record to the end of the RecordSet.
1984      *
1985      * @method setRecord
1986      * @param oData {Object} An object literal of data.
1987      * @param index {Number} (optional) Position index.
1988      * @return {YAHOO.widget.Record} A Record instance.
1989      */
1990     setRecord : function(oData, index) {
1991         if(lang.isObject(oData)) {
1992             var oRecord = this._setRecord(oData, index);
1993             this.fireEvent("recordSetEvent",{record:oRecord,data:oData});
1994             return oRecord;
1995         }
1996         else {
1997             return null;
1998         }
1999     },
2000
2001     /**
2002      * Sets or replaces multiple Records at once to the RecordSet with the given
2003      * data, starting at the given index. If index is not specified, then the new
2004      * Records are added to the end of the RecordSet.
2005      *
2006      * @method setRecords
2007      * @param aData {Object[]} An array of object literal data.
2008      * @param index {Number} (optional) Position index.
2009      * @return {YAHOO.widget.Record[]} An array of Record instances.
2010      */
2011     setRecords : function(aData, index) {
2012         var Rec   = widget.Record,
2013             a     = lang.isArray(aData) ? aData : [aData],
2014             added = [],
2015             i = 0, l = a.length, j = 0;
2016
2017         index = parseInt(index,10)|0;
2018
2019         for(; i < l; ++i) {
2020             if (typeof a[i] === 'object' && a[i]) {
2021                 added[j++] = this._records[index + i] = new Rec(a[i]);
2022             }
2023         }
2024
2025         this.fireEvent("recordsSetEvent",{records:added,data:aData});
2026         // Backward compatibility for bug 1918245
2027         this.fireEvent("recordsSet",{records:added,data:aData});
2028
2029         if (a.length && !added.length) {
2030         }
2031
2032         return added.length > 1 ? added : added[0];
2033     },
2034
2035     /**
2036      * Updates given Record with given data.
2037      *
2038      * @method updateRecord
2039      * @param record {YAHOO.widget.Record | Number | String} A Record instance,
2040      * a RecordSet position index, or a Record ID.
2041      * @param oData {Object} Object literal of new data.
2042      * @return {YAHOO.widget.Record} Updated Record, or null.
2043      */
2044     updateRecord : function(record, oData) {
2045         var oRecord = this.getRecord(record);
2046         if(oRecord && lang.isObject(oData)) {
2047             // Copy data from the Record for the event that gets fired later
2048             var oldData = {};
2049             for(var key in oRecord._oData) {
2050                 if(lang.hasOwnProperty(oRecord._oData, key)) {
2051                     oldData[key] = oRecord._oData[key];
2052                 }
2053             }
2054             oRecord._oData = oData;
2055             this.fireEvent("recordUpdateEvent",{record:oRecord,newData:oData,oldData:oldData});
2056             return oRecord;
2057         }
2058         else {
2059             return null;
2060         }
2061     },
2062
2063     /**
2064      * @method updateKey
2065      * @deprecated Use updateRecordValue
2066      */
2067     updateKey : function(record, sKey, oData) {
2068         this.updateRecordValue(record, sKey, oData);
2069     },
2070     /**
2071      * Sets given Record at given key to given data.
2072      *
2073      * @method updateRecordValue
2074      * @param record {YAHOO.widget.Record | Number | String} A Record instance,
2075      * a RecordSet position index, or a Record ID.
2076      * @param sKey {String} Key name.
2077      * @param oData {Object} New data.
2078      */
2079     updateRecordValue : function(record, sKey, oData) {
2080         var oRecord = this.getRecord(record);
2081         if(oRecord) {
2082             var oldData = null;
2083             var keyValue = oRecord._oData[sKey];
2084             // Copy data from the Record for the event that gets fired later
2085             if(keyValue && lang.isObject(keyValue)) {
2086                 oldData = {};
2087                 for(var key in keyValue)  {
2088                     if(lang.hasOwnProperty(keyValue, key)) {
2089                         oldData[key] = keyValue[key];
2090                     }
2091                 }
2092             }
2093             // Copy by value
2094             else {
2095                 oldData = keyValue;
2096             }
2097
2098             oRecord._oData[sKey] = oData;
2099             this.fireEvent("keyUpdateEvent",{record:oRecord,key:sKey,newData:oData,oldData:oldData});
2100             this.fireEvent("recordValueUpdateEvent",{record:oRecord,key:sKey,newData:oData,oldData:oldData});
2101         }
2102         else {
2103         }
2104     },
2105
2106     /**
2107      * Replaces all Records in RecordSet with new object literal data.
2108      *
2109      * @method replaceRecords
2110      * @param data {Object || Object[]} An object literal of data or an array of
2111      * data object literals.
2112      * @return {YAHOO.widget.Record || YAHOO.widget.Record[]} A Record instance or
2113      * an array of Records.
2114      */
2115     replaceRecords : function(data) {
2116         this.reset();
2117         return this.addRecords(data);
2118     },
2119
2120     /**
2121      * Sorts all Records by given function. Records keep their unique IDs but will
2122      * have new RecordSet position indexes.
2123      *
2124      * @method sortRecords
2125      * @param fnSort {Function} Reference to a sort function.
2126      * @param desc {Boolean} True if sort direction is descending, false if sort
2127      * direction is ascending.
2128      * @param field {String} The field to sort by, from sortOptions.field
2129      * @return {YAHOO.widget.Record[]} Sorted array of Records.
2130      */
2131     sortRecords : function(fnSort, desc, field) {
2132         return this._records.sort(function(a, b) {return fnSort(a, b, desc, field);});
2133     },
2134
2135     /**
2136      * Reverses all Records, so ["one", "two", "three"] becomes ["three", "two", "one"].
2137      *
2138      * @method reverseRecords
2139      * @return {YAHOO.widget.Record[]} Reverse-sorted array of Records.
2140      */
2141     reverseRecords : function() {
2142         return this._records.reverse();
2143     },
2144
2145     /**
2146      * Removes the Record at the given position index from the RecordSet. If a range
2147      * is also provided, removes that many Records, starting from the index. Length
2148      * of RecordSet is correspondingly shortened.
2149      *
2150      * @method deleteRecord
2151      * @param index {Number} Record's RecordSet position index.
2152      * @param range {Number} (optional) How many Records to delete.
2153      * @return {Object} A copy of the data held by the deleted Record.
2154      */
2155     deleteRecord : function(index) {
2156         if(lang.isNumber(index) && (index > -1) && (index < this.getLength())) {
2157             // Copy data from the Record for the event that gets fired later
2158             var oData = widget.DataTable._cloneObject(this.getRecord(index).getData());
2159             
2160             this._deleteRecord(index);
2161             this.fireEvent("recordDeleteEvent",{data:oData,index:index});
2162             return oData;
2163         }
2164         else {
2165             return null;
2166         }
2167     },
2168
2169     /**
2170      * Removes the Record at the given position index from the RecordSet. If a range
2171      * is also provided, removes that many Records, starting from the index. Length
2172      * of RecordSet is correspondingly shortened.
2173      *
2174      * @method deleteRecords
2175      * @param index {Number} Record's RecordSet position index.
2176      * @param range {Number} (optional) How many Records to delete.
2177      * @return {Object[]} An array of copies of the data held by the deleted Records.     
2178      */
2179     deleteRecords : function(index, range) {
2180         if(!lang.isNumber(range)) {
2181             range = 1;
2182         }
2183         if(lang.isNumber(index) && (index > -1) && (index < this.getLength())) {
2184             var recordsToDelete = this.getRecords(index, range);
2185             // Copy data from each Record for the event that gets fired later
2186             var deletedData = [];
2187             
2188             for(var i=0; i<recordsToDelete.length; i++) {
2189                 deletedData[deletedData.length] = widget.DataTable._cloneObject(recordsToDelete[i]);
2190             }
2191             this._deleteRecord(index, range);
2192
2193             this.fireEvent("recordsDeleteEvent",{data:deletedData,index:index});
2194
2195             return deletedData;
2196         }
2197         else {
2198             return null;
2199         }
2200     },
2201
2202     /**
2203      * Deletes all Records from the RecordSet.
2204      *
2205      * @method reset
2206      */
2207     reset : function() {
2208         this._records = [];
2209         //this._length = 0;
2210         this.fireEvent("resetEvent");
2211     }
2212 };
2213
2214 /////////////////////////////////////////////////////////////////////////////
2215 //
2216 // Custom Events
2217 //
2218 /////////////////////////////////////////////////////////////////////////////
2219
2220 // RecordSet uses EventProvider
2221 lang.augmentProto(RS, util.EventProvider);
2222
2223 /**
2224  * Fired when a new Record is added to the RecordSet.
2225  *
2226  * @event recordAddEvent
2227  * @param oArgs.record {YAHOO.widget.Record} The Record instance.
2228  * @param oArgs.data {Object} Data added.
2229  */
2230
2231 /**
2232  * Fired when multiple Records are added to the RecordSet at once.
2233  *
2234  * @event recordsAddEvent
2235  * @param oArgs.records {YAHOO.widget.Record[]} An array of Record instances.
2236  * @param oArgs.data {Object[]} Data added.
2237  */
2238
2239 /**
2240  * Fired when a Record is set in the RecordSet.
2241  *
2242  * @event recordSetEvent
2243  * @param oArgs.record {YAHOO.widget.Record} The Record instance.
2244  * @param oArgs.data {Object} Data added.
2245  */
2246
2247 /**
2248  * Fired when multiple Records are set in the RecordSet at once.
2249  *
2250  * @event recordsSetEvent
2251  * @param oArgs.records {YAHOO.widget.Record[]} An array of Record instances.
2252  * @param oArgs.data {Object[]} Data added.
2253  */
2254
2255 /**
2256  * Fired when a Record is updated with new data.
2257  *
2258  * @event recordUpdateEvent
2259  * @param oArgs.record {YAHOO.widget.Record} The Record instance.
2260  * @param oArgs.newData {Object} New data.
2261  * @param oArgs.oldData {Object} Old data.
2262  */
2263
2264 /**
2265  * Fired when a Record is deleted from the RecordSet.
2266  *
2267  * @event recordDeleteEvent
2268  * @param oArgs.data {Object} A copy of the data held by the Record,
2269  * or an array of data object literals if multiple Records were deleted at once.
2270  * @param oArgs.index {Object} Index of the deleted Record.
2271  */
2272
2273 /**
2274  * Fired when multiple Records are deleted from the RecordSet at once.
2275  *
2276  * @event recordsDeleteEvent
2277  * @param oArgs.data {Object[]} An array of data object literals copied
2278  * from the Records.
2279  * @param oArgs.index {Object} Index of the first deleted Record.
2280  */
2281
2282 /**
2283  * Fired when all Records are deleted from the RecordSet at once.
2284  *
2285  * @event resetEvent
2286  */
2287
2288 /**
2289  * @event keyUpdateEvent    
2290  * @deprecated Use recordValueUpdateEvent     
2291  */
2292
2293 /**
2294  * Fired when a Record value is updated with new data.
2295  *
2296  * @event recordValueUpdateEvent
2297  * @param oArgs.record {YAHOO.widget.Record} The Record instance.
2298  * @param oArgs.key {String} The updated key.
2299  * @param oArgs.newData {Object} New data.
2300  * @param oArgs.oldData {Object} Old data.
2301  *
2302  */
2303
2304
2305 /****************************************************************************/
2306 /****************************************************************************/
2307 /****************************************************************************/
2308
2309 /**
2310  * The Record class defines a DataTable record.
2311  *
2312  * @namespace YAHOO.widget
2313  * @class Record
2314  * @constructor
2315  * @param oConfigs {Object} (optional) Object literal of key/value pairs.
2316  */
2317 YAHOO.widget.Record = function(oLiteral) {
2318     this._nCount = widget.Record._nCount;
2319     this._sId = "yui-rec" + this._nCount;
2320     widget.Record._nCount++;
2321     this._oData = {};
2322     if(lang.isObject(oLiteral)) {
2323         for(var sKey in oLiteral) {
2324             if(lang.hasOwnProperty(oLiteral, sKey)) {
2325                 this._oData[sKey] = oLiteral[sKey];
2326             }
2327         }
2328     }
2329 };
2330
2331 /////////////////////////////////////////////////////////////////////////////
2332 //
2333 // Private member variables
2334 //
2335 /////////////////////////////////////////////////////////////////////////////
2336
2337 /**
2338  * Internal class variable to give unique IDs to Record instances.
2339  *
2340  * @property Record._nCount
2341  * @type Number
2342  * @private
2343  */
2344 YAHOO.widget.Record._nCount = 0;
2345
2346 YAHOO.widget.Record.prototype = {
2347     /**
2348      * Immutable unique count assigned at instantiation. Remains constant while a
2349      * Record's position index can change from sorting.
2350      *
2351      * @property _nCount
2352      * @type Number
2353      * @private
2354      */
2355     _nCount : null,
2356
2357     /**
2358      * Immutable unique ID assigned at instantiation. Remains constant while a
2359      * Record's position index can change from sorting.
2360      *
2361      * @property _sId
2362      * @type String
2363      * @private
2364      */
2365     _sId : null,
2366
2367     /**
2368      * Holds data for the Record in an object literal.
2369      *
2370      * @property _oData
2371      * @type Object
2372      * @private
2373      */
2374     _oData : null,
2375
2376     /////////////////////////////////////////////////////////////////////////////
2377     //
2378     // Public member variables
2379     //
2380     /////////////////////////////////////////////////////////////////////////////
2381
2382     /////////////////////////////////////////////////////////////////////////////
2383     //
2384     // Public methods
2385     //
2386     /////////////////////////////////////////////////////////////////////////////
2387
2388     /**
2389      * Returns unique count assigned at instantiation.
2390      *
2391      * @method getCount
2392      * @return Number
2393      */
2394     getCount : function() {
2395         return this._nCount;
2396     },
2397
2398     /**
2399      * Returns unique ID assigned at instantiation.
2400      *
2401      * @method getId
2402      * @return String
2403      */
2404     getId : function() {
2405         return this._sId;
2406     },
2407
2408     /**
2409      * Returns data for the Record for a field if given, or the entire object
2410      * literal otherwise.
2411      *
2412      * @method getData
2413      * @param sField {String} (Optional) The field from which to retrieve data value.
2414      * @return Object
2415      */
2416     getData : function(sField) {
2417         if(lang.isString(sField)) {
2418             return this._oData[sField];
2419         }
2420         else {
2421             return this._oData;
2422         }
2423     },
2424
2425     /**
2426      * Sets given data at the given key. Use the RecordSet method updateRecordValue to trigger
2427      * events. 
2428      *
2429      * @method setData
2430      * @param sKey {String} The key of the new value.
2431      * @param oData {MIXED} The new value.
2432      */
2433     setData : function(sKey, oData) {
2434         this._oData[sKey] = oData;
2435     }
2436 };
2437
2438 })();
2439
2440 (function () {
2441
2442 var lang   = YAHOO.lang,
2443     util   = YAHOO.util,
2444     widget = YAHOO.widget,
2445     ua     = YAHOO.env.ua,
2446     
2447     Dom    = util.Dom,
2448     Ev     = util.Event,
2449     DS     = util.DataSourceBase;
2450
2451 /**
2452  * The DataTable widget provides a progressively enhanced DHTML control for
2453  * displaying tabular data across A-grade browsers.
2454  *
2455  * @module datatable
2456  * @requires yahoo, dom, event, element, datasource
2457  * @optional dragdrop, dragdrop
2458  * @title DataTable Widget
2459  */
2460
2461 /****************************************************************************/
2462 /****************************************************************************/
2463 /****************************************************************************/
2464
2465 /**
2466  * DataTable class for the YUI DataTable widget.
2467  *
2468  * @namespace YAHOO.widget
2469  * @class DataTable
2470  * @extends YAHOO.util.Element
2471  * @constructor
2472  * @param elContainer {HTMLElement} Container element for the TABLE.
2473  * @param aColumnDefs {Object[]} Array of object literal Column definitions.
2474  * @param oDataSource {YAHOO.util.DataSource} DataSource instance.
2475  * @param oConfigs {object} (optional) Object literal of configuration values.
2476  */
2477 YAHOO.widget.DataTable = function(elContainer,aColumnDefs,oDataSource,oConfigs) {
2478     var DT = widget.DataTable;
2479     
2480     ////////////////////////////////////////////////////////////////////////////
2481     // Backward compatibility for SDT, but prevent infinite loops
2482     
2483     if(oConfigs && oConfigs.scrollable) {
2484         return new YAHOO.widget.ScrollingDataTable(elContainer,aColumnDefs,oDataSource,oConfigs);
2485     }
2486     
2487     ////////////////////////////////////////////////////////////////////////////
2488     // Initialization
2489
2490     // Internal vars
2491     this._nIndex = DT._nCount;
2492     this._sId = "yui-dt"+this._nIndex;
2493     this._oChainRender = new YAHOO.util.Chain();
2494     this._oChainRender.subscribe("end",this._onRenderChainEnd, this, true);
2495
2496     // Initialize configs
2497     this._initConfigs(oConfigs);
2498
2499     // Initialize DataSource
2500     this._initDataSource(oDataSource);
2501     if(!this._oDataSource) {
2502         return;
2503     }
2504
2505     // Initialize ColumnSet
2506     this._initColumnSet(aColumnDefs);
2507     if(!this._oColumnSet) {
2508         return;
2509     }
2510
2511     // Initialize RecordSet
2512     this._initRecordSet();
2513     if(!this._oRecordSet) {
2514     }
2515
2516     // Initialize Attributes
2517     DT.superclass.constructor.call(this, elContainer, this.configs);
2518
2519     // Initialize DOM elements
2520     var okDom = this._initDomElements(elContainer);
2521     if(!okDom) {
2522         return;
2523     }
2524             
2525     // Show message as soon as config is available
2526     this.showTableMessage(this.get("MSG_LOADING"), DT.CLASS_LOADING);
2527     
2528     ////////////////////////////////////////////////////////////////////////////
2529     // Once per instance
2530     this._initEvents();
2531
2532     DT._nCount++;
2533     DT._nCurrentCount++;
2534     
2535     ////////////////////////////////////////////////////////////////////////////
2536     // Data integration
2537
2538     // Send a simple initial request
2539     var oCallback = {
2540         success : this.onDataReturnSetRows,
2541         failure : this.onDataReturnSetRows,
2542         scope   : this,
2543         argument: this.getState()
2544     };
2545     
2546     var initialLoad = this.get("initialLoad");
2547     if(initialLoad === true) {
2548         this._oDataSource.sendRequest(this.get("initialRequest"), oCallback);
2549     }
2550     // Do not send an initial request at all
2551     else if(initialLoad === false) {
2552         this.showTableMessage(this.get("MSG_EMPTY"), DT.CLASS_EMPTY);
2553     }
2554     // Send an initial request with a custom payload
2555     else {
2556         var oCustom = initialLoad || {};
2557         oCallback.argument = oCustom.argument || {};
2558         this._oDataSource.sendRequest(oCustom.request, oCallback);
2559     }
2560 };
2561
2562 var DT = widget.DataTable;
2563
2564 /////////////////////////////////////////////////////////////////////////////
2565 //
2566 // Public constants
2567 //
2568 /////////////////////////////////////////////////////////////////////////////
2569
2570 lang.augmentObject(DT, {
2571
2572     /**
2573      * Class name assigned to outer DataTable container.
2574      *
2575      * @property DataTable.CLASS_DATATABLE
2576      * @type String
2577      * @static
2578      * @final
2579      * @default "yui-dt"
2580      */
2581     CLASS_DATATABLE : "yui-dt",
2582
2583     /**
2584      * Class name assigned to liner DIV elements.
2585      *
2586      * @property DataTable.CLASS_LINER
2587      * @type String
2588      * @static
2589      * @final
2590      * @default "yui-dt-liner"
2591      */
2592     CLASS_LINER : "yui-dt-liner",
2593
2594     /**
2595      * Class name assigned to display label elements.
2596      *
2597      * @property DataTable.CLASS_LABEL
2598      * @type String
2599      * @static
2600      * @final
2601      * @default "yui-dt-label"
2602      */
2603     CLASS_LABEL : "yui-dt-label",
2604
2605     /**
2606      * Class name assigned to messaging elements.
2607      *
2608      * @property DataTable.CLASS_MESSAGE
2609      * @type String
2610      * @static
2611      * @final
2612      * @default "yui-dt-message"
2613      */
2614     CLASS_MESSAGE : "yui-dt-message",
2615
2616     /**
2617      * Class name assigned to mask element when DataTable is disabled.
2618      *
2619      * @property DataTable.CLASS_MASK
2620      * @type String
2621      * @static
2622      * @final
2623      * @default "yui-dt-mask"
2624      */
2625     CLASS_MASK : "yui-dt-mask",
2626
2627     /**
2628      * Class name assigned to data elements.
2629      *
2630      * @property DataTable.CLASS_DATA
2631      * @type String
2632      * @static
2633      * @final
2634      * @default "yui-dt-data"
2635      */
2636     CLASS_DATA : "yui-dt-data",
2637
2638     /**
2639      * Class name assigned to Column drag target.
2640      *
2641      * @property DataTable.CLASS_COLTARGET
2642      * @type String
2643      * @static
2644      * @final
2645      * @default "yui-dt-coltarget"
2646      */
2647     CLASS_COLTARGET : "yui-dt-coltarget",
2648
2649     /**
2650      * Class name assigned to resizer handle elements.
2651      *
2652      * @property DataTable.CLASS_RESIZER
2653      * @type String
2654      * @static
2655      * @final
2656      * @default "yui-dt-resizer"
2657      */
2658     CLASS_RESIZER : "yui-dt-resizer",
2659
2660     /**
2661      * Class name assigned to resizer liner elements.
2662      *
2663      * @property DataTable.CLASS_RESIZERLINER
2664      * @type String
2665      * @static
2666      * @final
2667      * @default "yui-dt-resizerliner"
2668      */
2669     CLASS_RESIZERLINER : "yui-dt-resizerliner",
2670
2671     /**
2672      * Class name assigned to resizer proxy elements.
2673      *
2674      * @property DataTable.CLASS_RESIZERPROXY
2675      * @type String
2676      * @static
2677      * @final
2678      * @default "yui-dt-resizerproxy"
2679      */
2680     CLASS_RESIZERPROXY : "yui-dt-resizerproxy",
2681
2682     /**
2683      * Class name assigned to CellEditor container elements.
2684      *
2685      * @property DataTable.CLASS_EDITOR
2686      * @type String
2687      * @static
2688      * @final
2689      * @default "yui-dt-editor"
2690      */
2691     CLASS_EDITOR : "yui-dt-editor",
2692
2693     /**
2694      * Class name assigned to paginator container elements.
2695      *
2696      * @property DataTable.CLASS_PAGINATOR
2697      * @type String
2698      * @static
2699      * @final
2700      * @default "yui-dt-paginator"
2701      */
2702     CLASS_PAGINATOR : "yui-dt-paginator",
2703
2704     /**
2705      * Class name assigned to page number indicators.
2706      *
2707      * @property DataTable.CLASS_PAGE
2708      * @type String
2709      * @static
2710      * @final
2711      * @default "yui-dt-page"
2712      */
2713     CLASS_PAGE : "yui-dt-page",
2714
2715     /**
2716      * Class name assigned to default indicators.
2717      *
2718      * @property DataTable.CLASS_DEFAULT
2719      * @type String
2720      * @static
2721      * @final
2722      * @default "yui-dt-default"
2723      */
2724     CLASS_DEFAULT : "yui-dt-default",
2725
2726     /**
2727      * Class name assigned to previous indicators.
2728      *
2729      * @property DataTable.CLASS_PREVIOUS
2730      * @type String
2731      * @static
2732      * @final
2733      * @default "yui-dt-previous"
2734      */
2735     CLASS_PREVIOUS : "yui-dt-previous",
2736
2737     /**
2738      * Class name assigned next indicators.
2739      *
2740      * @property DataTable.CLASS_NEXT
2741      * @type String
2742      * @static
2743      * @final
2744      * @default "yui-dt-next"
2745      */
2746     CLASS_NEXT : "yui-dt-next",
2747
2748     /**
2749      * Class name assigned to first elements.
2750      *
2751      * @property DataTable.CLASS_FIRST
2752      * @type String
2753      * @static
2754      * @final
2755      * @default "yui-dt-first"
2756      */
2757     CLASS_FIRST : "yui-dt-first",
2758
2759     /**
2760      * Class name assigned to last elements.
2761      *
2762      * @property DataTable.CLASS_LAST
2763      * @type String
2764      * @static
2765      * @final
2766      * @default "yui-dt-last"
2767      */
2768     CLASS_LAST : "yui-dt-last",
2769
2770     /**
2771      * Class name assigned to even elements.
2772      *
2773      * @property DataTable.CLASS_EVEN
2774      * @type String
2775      * @static
2776      * @final
2777      * @default "yui-dt-even"
2778      */
2779     CLASS_EVEN : "yui-dt-even",
2780
2781     /**
2782      * Class name assigned to odd elements.
2783      *
2784      * @property DataTable.CLASS_ODD
2785      * @type String
2786      * @static
2787      * @final
2788      * @default "yui-dt-odd"
2789      */
2790     CLASS_ODD : "yui-dt-odd",
2791
2792     /**
2793      * Class name assigned to selected elements.
2794      *
2795      * @property DataTable.CLASS_SELECTED
2796      * @type String
2797      * @static
2798      * @final
2799      * @default "yui-dt-selected"
2800      */
2801     CLASS_SELECTED : "yui-dt-selected",
2802
2803     /**
2804      * Class name assigned to highlighted elements.
2805      *
2806      * @property DataTable.CLASS_HIGHLIGHTED
2807      * @type String
2808      * @static
2809      * @final
2810      * @default "yui-dt-highlighted"
2811      */
2812     CLASS_HIGHLIGHTED : "yui-dt-highlighted",
2813
2814     /**
2815      * Class name assigned to hidden elements.
2816      *
2817      * @property DataTable.CLASS_HIDDEN
2818      * @type String
2819      * @static
2820      * @final
2821      * @default "yui-dt-hidden"
2822      */
2823     CLASS_HIDDEN : "yui-dt-hidden",
2824
2825     /**
2826      * Class name assigned to disabled elements.
2827      *
2828      * @property DataTable.CLASS_DISABLED
2829      * @type String
2830      * @static
2831      * @final
2832      * @default "yui-dt-disabled"
2833      */
2834     CLASS_DISABLED : "yui-dt-disabled",
2835
2836     /**
2837      * Class name assigned to empty indicators.
2838      *
2839      * @property DataTable.CLASS_EMPTY
2840      * @type String
2841      * @static
2842      * @final
2843      * @default "yui-dt-empty"
2844      */
2845     CLASS_EMPTY : "yui-dt-empty",
2846
2847     /**
2848      * Class name assigned to loading indicatorx.
2849      *
2850      * @property DataTable.CLASS_LOADING
2851      * @type String
2852      * @static
2853      * @final
2854      * @default "yui-dt-loading"
2855      */
2856     CLASS_LOADING : "yui-dt-loading",
2857
2858     /**
2859      * Class name assigned to error indicators.
2860      *
2861      * @property DataTable.CLASS_ERROR
2862      * @type String
2863      * @static
2864      * @final
2865      * @default "yui-dt-error"
2866      */
2867     CLASS_ERROR : "yui-dt-error",
2868
2869     /**
2870      * Class name assigned to editable elements.
2871      *
2872      * @property DataTable.CLASS_EDITABLE
2873      * @type String
2874      * @static
2875      * @final
2876      * @default "yui-dt-editable"
2877      */
2878     CLASS_EDITABLE : "yui-dt-editable",
2879
2880     /**
2881      * Class name assigned to draggable elements.
2882      *
2883      * @property DataTable.CLASS_DRAGGABLE
2884      * @type String
2885      * @static
2886      * @final
2887      * @default "yui-dt-draggable"
2888      */
2889     CLASS_DRAGGABLE : "yui-dt-draggable",
2890
2891     /**
2892      * Class name assigned to resizeable elements.
2893      *
2894      * @property DataTable.CLASS_RESIZEABLE
2895      * @type String
2896      * @static
2897      * @final
2898      * @default "yui-dt-resizeable"
2899      */
2900     CLASS_RESIZEABLE : "yui-dt-resizeable",
2901
2902     /**
2903      * Class name assigned to scrollable elements.
2904      *
2905      * @property DataTable.CLASS_SCROLLABLE
2906      * @type String
2907      * @static
2908      * @final
2909      * @default "yui-dt-scrollable"
2910      */
2911     CLASS_SCROLLABLE : "yui-dt-scrollable",
2912
2913     /**
2914      * Class name assigned to sortable elements.
2915      *
2916      * @property DataTable.CLASS_SORTABLE
2917      * @type String
2918      * @static
2919      * @final
2920      * @default "yui-dt-sortable"
2921      */
2922     CLASS_SORTABLE : "yui-dt-sortable",
2923
2924     /**
2925      * Class name assigned to ascending elements.
2926      *
2927      * @property DataTable.CLASS_ASC
2928      * @type String
2929      * @static
2930      * @final
2931      * @default "yui-dt-asc"
2932      */
2933     CLASS_ASC : "yui-dt-asc",
2934
2935     /**
2936      * Class name assigned to descending elements.
2937      *
2938      * @property DataTable.CLASS_DESC
2939      * @type String
2940      * @static
2941      * @final
2942      * @default "yui-dt-desc"
2943      */
2944     CLASS_DESC : "yui-dt-desc",
2945
2946     /**
2947      * Class name assigned to BUTTON elements and/or container elements.
2948      *
2949      * @property DataTable.CLASS_BUTTON
2950      * @type String
2951      * @static
2952      * @final
2953      * @default "yui-dt-button"
2954      */
2955     CLASS_BUTTON : "yui-dt-button",
2956
2957     /**
2958      * Class name assigned to INPUT TYPE=CHECKBOX elements and/or container elements.
2959      *
2960      * @property DataTable.CLASS_CHECKBOX
2961      * @type String
2962      * @static
2963      * @final
2964      * @default "yui-dt-checkbox"
2965      */
2966     CLASS_CHECKBOX : "yui-dt-checkbox",
2967
2968     /**
2969      * Class name assigned to SELECT elements and/or container elements.
2970      *
2971      * @property DataTable.CLASS_DROPDOWN
2972      * @type String
2973      * @static
2974      * @final
2975      * @default "yui-dt-dropdown"
2976      */
2977     CLASS_DROPDOWN : "yui-dt-dropdown",
2978
2979     /**
2980      * Class name assigned to INPUT TYPE=RADIO elements and/or container elements.
2981      *
2982      * @property DataTable.CLASS_RADIO
2983      * @type String
2984      * @static
2985      * @final
2986      * @default "yui-dt-radio"
2987      */
2988     CLASS_RADIO : "yui-dt-radio",
2989
2990     /////////////////////////////////////////////////////////////////////////
2991     //
2992     // Private static properties
2993     //
2994     /////////////////////////////////////////////////////////////////////////
2995
2996     /**
2997      * Internal class variable for indexing multiple DataTable instances.
2998      *
2999      * @property DataTable._nCount
3000      * @type Number
3001      * @private
3002      * @static
3003      */
3004     _nCount : 0,
3005
3006     /**
3007      * Internal class variable tracking current number of DataTable instances,
3008      * so that certain class values can be reset when all instances are destroyed.          
3009      *
3010      * @property DataTable._nCurrentCount
3011      * @type Number
3012      * @private
3013      * @static
3014      */
3015     _nCurrentCount : 0,
3016
3017     /**
3018      * Reference to the STYLE node that is dynamically created and updated
3019      * in order to manage Column widths.
3020      *
3021      * @property DataTable._elDynStyleNode
3022      * @type HTMLElement
3023      * @private
3024      * @static     
3025      */
3026     _elDynStyleNode : null,
3027
3028     /**
3029      * Set to true if _elDynStyleNode cannot be populated due to browser incompatibility.
3030      *
3031      * @property DataTable._bDynStylesFallback
3032      * @type boolean
3033      * @private
3034      * @static     
3035      */
3036     _bDynStylesFallback : (ua.ie) ? true : false,
3037
3038     /**
3039      * Object literal hash of Columns and their dynamically create style rules.
3040      *
3041      * @property DataTable._oDynStyles
3042      * @type Object
3043      * @private
3044      * @static     
3045      */
3046     _oDynStyles : {},
3047
3048     /**
3049      * Element reference to shared Column drag target.
3050      *
3051      * @property DataTable._elColumnDragTarget
3052      * @type HTMLElement
3053      * @private
3054      * @static 
3055      */
3056     _elColumnDragTarget : null,
3057
3058     /**
3059      * Element reference to shared Column resizer proxy.
3060      *
3061      * @property DataTable._elColumnResizerProxy
3062      * @type HTMLElement
3063      * @private
3064      * @static 
3065      */
3066     _elColumnResizerProxy : null,
3067
3068     /////////////////////////////////////////////////////////////////////////
3069     //
3070     // Private static methods
3071     //
3072     /////////////////////////////////////////////////////////////////////////
3073
3074     /**
3075      * Clones object literal or array of object literals.
3076      *
3077      * @method DataTable._cloneObject
3078      * @param o {Object} Object.
3079      * @private
3080      * @static     
3081      */
3082     _cloneObject : function(o) {
3083         if(!lang.isValue(o)) {
3084             return o;
3085         }
3086         
3087         var copy = {};
3088         
3089         if(o instanceof YAHOO.widget.BaseCellEditor) {
3090             copy = o;
3091         }
3092         else if(lang.isFunction(o)) {
3093             copy = o;
3094         }
3095         else if(lang.isArray(o)) {
3096             var array = [];
3097             for(var i=0,len=o.length;i<len;i++) {
3098                 array[i] = DT._cloneObject(o[i]);
3099             }
3100             copy = array;
3101         }
3102         else if(lang.isObject(o)) { 
3103             for (var x in o){
3104                 if(lang.hasOwnProperty(o, x)) {
3105                     if(lang.isValue(o[x]) && lang.isObject(o[x]) || lang.isArray(o[x])) {
3106                         copy[x] = DT._cloneObject(o[x]);
3107                     }
3108                     else {
3109                         copy[x] = o[x];
3110                     }
3111                 }
3112             }
3113         }
3114         else {
3115             copy = o;
3116         }
3117     
3118         return copy;
3119     },
3120
3121     /**
3122      * Destroys shared Column drag target.
3123      *
3124      * @method DataTable._destroyColumnDragTargetEl
3125      * @private
3126      * @static 
3127      */
3128     _destroyColumnDragTargetEl : function() {
3129         if(DT._elColumnDragTarget) {
3130             var el = DT._elColumnDragTarget;
3131             YAHOO.util.Event.purgeElement(el);
3132             el.parentNode.removeChild(el);
3133             DT._elColumnDragTarget = null;
3134             
3135         }
3136     },
3137
3138     /**
3139      * Creates HTML markup for shared Column drag target.
3140      *
3141      * @method DataTable._initColumnDragTargetEl
3142      * @return {HTMLElement} Reference to Column drag target. 
3143      * @private
3144      * @static 
3145      */
3146     _initColumnDragTargetEl : function() {
3147         if(!DT._elColumnDragTarget) {
3148             // Attach Column drag target element as first child of body
3149             var elColumnDragTarget = document.createElement('div');
3150             elColumnDragTarget.className = DT.CLASS_COLTARGET;
3151             elColumnDragTarget.style.display = "none";
3152             document.body.insertBefore(elColumnDragTarget, document.body.firstChild);
3153
3154             // Internal tracker of Column drag target
3155             DT._elColumnDragTarget = elColumnDragTarget;
3156             
3157         }
3158         return DT._elColumnDragTarget;
3159     },
3160
3161     /**
3162      * Destroys shared Column resizer proxy.
3163      *
3164      * @method DataTable._destroyColumnResizerProxyEl
3165      * @return {HTMLElement} Reference to Column resizer proxy.
3166      * @private 
3167      * @static 
3168      */
3169     _destroyColumnResizerProxyEl : function() {
3170         if(DT._elColumnResizerProxy) {
3171             var el = DT._elColumnResizerProxy;
3172             YAHOO.util.Event.purgeElement(el);
3173             el.parentNode.removeChild(el);
3174             DT._elColumnResizerProxy = null;
3175         }
3176     },
3177
3178     /**
3179      * Creates HTML markup for shared Column resizer proxy.
3180      *
3181      * @method DataTable._initColumnResizerProxyEl
3182      * @return {HTMLElement} Reference to Column resizer proxy.
3183      * @private 
3184      * @static 
3185      */
3186     _initColumnResizerProxyEl : function() {
3187         if(!DT._elColumnResizerProxy) {
3188             // Attach Column resizer element as first child of body
3189             var elColumnResizerProxy = document.createElement("div");
3190             elColumnResizerProxy.id = "yui-dt-colresizerproxy"; // Needed for ColumnResizer
3191             elColumnResizerProxy.className = DT.CLASS_RESIZERPROXY;
3192             document.body.insertBefore(elColumnResizerProxy, document.body.firstChild);
3193
3194             // Internal tracker of Column resizer proxy
3195             DT._elColumnResizerProxy = elColumnResizerProxy;
3196         }
3197         return DT._elColumnResizerProxy;
3198     },
3199
3200     /**
3201      * Formats a BUTTON element.
3202      *
3203      * @method DataTable.formatButton
3204      * @param el {HTMLElement} The element to format with markup.
3205      * @param oRecord {YAHOO.widget.Record} Record instance.
3206      * @param oColumn {YAHOO.widget.Column} Column instance.
3207      * @param oData {Object | Boolean} Data value for the cell. By default, the value
3208      * is what gets written to the BUTTON.
3209      * @static
3210      */
3211     formatButton : function(el, oRecord, oColumn, oData) {
3212         var sValue = lang.isValue(oData) ? oData : "Click";
3213         //TODO: support YAHOO.widget.Button
3214         //if(YAHOO.widget.Button) {
3215
3216         //}
3217         //else {
3218             el.innerHTML = "<button type=\"button\" class=\""+
3219                     DT.CLASS_BUTTON + "\">" + sValue + "</button>";
3220         //}
3221     },
3222
3223     /**
3224      * Formats a CHECKBOX element.
3225      *
3226      * @method DataTable.formatCheckbox
3227      * @param el {HTMLElement} The element to format with markup.
3228      * @param oRecord {YAHOO.widget.Record} Record instance.
3229      * @param oColumn {YAHOO.widget.Column} Column instance.
3230      * @param oData {Object | Boolean} Data value for the cell. Can be a simple
3231      * Boolean to indicate whether checkbox is checked or not. Can be object literal
3232      * {checked:bBoolean, label:sLabel}. Other forms of oData require a custom
3233      * formatter.
3234      * @static
3235      */
3236     formatCheckbox : function(el, oRecord, oColumn, oData) {
3237         var bChecked = oData;
3238         bChecked = (bChecked) ? " checked=\"checked\"" : "";
3239         el.innerHTML = "<input type=\"checkbox\"" + bChecked +
3240                 " class=\"" + DT.CLASS_CHECKBOX + "\" />";
3241     },
3242
3243     /**
3244      * Formats currency. Default unit is USD.
3245      *
3246      * @method DataTable.formatCurrency
3247      * @param el {HTMLElement} The element to format with markup.
3248      * @param oRecord {YAHOO.widget.Record} Record instance.
3249      * @param oColumn {YAHOO.widget.Column} Column instance.
3250      * @param oData {Number} Data value for the cell.
3251      * @static
3252      */
3253     formatCurrency : function(el, oRecord, oColumn, oData) {
3254         el.innerHTML = util.Number.format(oData, oColumn.currencyOptions || this.get("currencyOptions"));
3255     },
3256
3257     /**
3258      * Formats JavaScript Dates.
3259      *
3260      * @method DataTable.formatDate
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     formatDate : function(el, oRecord, oColumn, oData) {
3268         var oConfig = oColumn.dateOptions || this.get("dateOptions");
3269         el.innerHTML = util.Date.format(oData, oConfig, oConfig.locale);
3270     },
3271
3272     /**
3273      * Formats SELECT elements.
3274      *
3275      * @method DataTable.formatDropdown
3276      * @param el {HTMLElement} The element to format with markup.
3277      * @param oRecord {YAHOO.widget.Record} Record instance.
3278      * @param oColumn {YAHOO.widget.Column} Column instance.
3279      * @param oData {Object} Data value for the cell, or null.
3280      * @static
3281      */
3282     formatDropdown : function(el, oRecord, oColumn, oData) {
3283         var selectedValue = (lang.isValue(oData)) ? oData : oRecord.getData(oColumn.field),
3284             options = (lang.isArray(oColumn.dropdownOptions)) ?
3285                 oColumn.dropdownOptions : null,
3286
3287             selectEl,
3288             collection = el.getElementsByTagName("select");
3289
3290         // Create the form element only once, so we can attach the onChange listener
3291         if(collection.length === 0) {
3292             // Create SELECT element
3293             selectEl = document.createElement("select");
3294             selectEl.className = DT.CLASS_DROPDOWN;
3295             selectEl = el.appendChild(selectEl);
3296
3297             // Add event listener
3298             Ev.addListener(selectEl,"change",this._onDropdownChange,this);
3299         }
3300
3301         selectEl = collection[0];
3302
3303         // Update the form element
3304         if(selectEl) {
3305             // Clear out previous options
3306             selectEl.innerHTML = "";
3307
3308             // We have options to populate
3309             if(options) {
3310                 // Create OPTION elements
3311                 for(var i=0; i<options.length; i++) {
3312                     var option = options[i];
3313                     var optionEl = document.createElement("option");
3314                     optionEl.value = (lang.isValue(option.value)) ?
3315                             option.value : option;
3316                     // Bug 2334323: Support legacy text, support label for consistency with DropdownCellEditor
3317                     optionEl.innerHTML = (lang.isValue(option.text)) ?
3318                             option.text : (lang.isValue(option.label)) ? option.label : option;
3319                     optionEl = selectEl.appendChild(optionEl);
3320                     if (optionEl.value == selectedValue) {
3321                         optionEl.selected = true;
3322                     }
3323                 }
3324             }
3325             // Selected value is our only option
3326             else {
3327                 selectEl.innerHTML = "<option selected value=\"" + selectedValue + "\">" + selectedValue + "</option>";
3328             }
3329         }
3330         else {
3331             el.innerHTML = lang.isValue(oData) ? oData : "";
3332         }
3333     },
3334
3335     /**
3336      * Formats emails.
3337      *
3338      * @method DataTable.formatEmail
3339      * @param el {HTMLElement} The element to format with markup.
3340      * @param oRecord {YAHOO.widget.Record} Record instance.
3341      * @param oColumn {YAHOO.widget.Column} Column instance.
3342      * @param oData {Object} Data value for the cell, or null.
3343      * @static
3344      */
3345     formatEmail : function(el, oRecord, oColumn, oData) {
3346         if(lang.isString(oData)) {
3347             el.innerHTML = "<a href=\"mailto:" + oData + "\">" + oData + "</a>";
3348         }
3349         else {
3350             el.innerHTML = lang.isValue(oData) ? oData : "";
3351         }
3352     },
3353
3354     /**
3355      * Formats links.
3356      *
3357      * @method DataTable.formatLink
3358      * @param el {HTMLElement} The element to format with markup.
3359      * @param oRecord {YAHOO.widget.Record} Record instance.
3360      * @param oColumn {YAHOO.widget.Column} Column instance.
3361      * @param oData {Object} Data value for the cell, or null.
3362      * @static
3363      */
3364     formatLink : function(el, oRecord, oColumn, oData) {
3365         if(lang.isString(oData)) {
3366             el.innerHTML = "<a href=\"" + oData + "\">" + oData + "</a>";
3367         }
3368         else {
3369             el.innerHTML = lang.isValue(oData) ? oData : "";
3370         }
3371     },
3372
3373     /**
3374      * Formats numbers.
3375      *
3376      * @method DataTable.formatNumber
3377      * @param el {HTMLElement} The element to format with markup.
3378      * @param oRecord {YAHOO.widget.Record} Record instance.
3379      * @param oColumn {YAHOO.widget.Column} Column instance.
3380      * @param oData {Object} Data value for the cell, or null.
3381      * @static
3382      */
3383     formatNumber : function(el, oRecord, oColumn, oData) {
3384         el.innerHTML = util.Number.format(oData, oColumn.numberOptions || this.get("numberOptions"));
3385     },
3386
3387     /**
3388      * Formats INPUT TYPE=RADIO elements.
3389      *
3390      * @method DataTable.formatRadio
3391      * @param el {HTMLElement} The element to format with markup.
3392      * @param oRecord {YAHOO.widget.Record} Record instance.
3393      * @param oColumn {YAHOO.widget.Column} Column instance.
3394      * @param oData {Object} (Optional) Data value for the cell.
3395      * @static
3396      */
3397     formatRadio : function(el, oRecord, oColumn, oData) {
3398         var bChecked = oData;
3399         bChecked = (bChecked) ? " checked=\"checked\"" : "";
3400         el.innerHTML = "<input type=\"radio\"" + bChecked +
3401                 " name=\""+this.getId()+"-col-" + oColumn.getSanitizedKey() + "\"" +
3402                 " class=\"" + DT.CLASS_RADIO+ "\" />";
3403     },
3404
3405     /**
3406      * Formats text strings.
3407      *
3408      * @method DataTable.formatText
3409      * @param el {HTMLElement} The element to format with markup.
3410      * @param oRecord {YAHOO.widget.Record} Record instance.
3411      * @param oColumn {YAHOO.widget.Column} Column instance.
3412      * @param oData {Object} (Optional) Data value for the cell.
3413      * @static
3414      */
3415     formatText : function(el, oRecord, oColumn, oData) {
3416         var value = (lang.isValue(oData)) ? oData : "";
3417         //TODO: move to util function
3418         el.innerHTML = value.toString().replace(/&/g, "&#38;").replace(/</g, "&#60;").replace(/>/g, "&#62;");
3419     },
3420
3421     /**
3422      * Formats TEXTAREA elements.
3423      *
3424      * @method DataTable.formatTextarea
3425      * @param el {HTMLElement} The element to format with markup.
3426      * @param oRecord {YAHOO.widget.Record} Record instance.
3427      * @param oColumn {YAHOO.widget.Column} Column instance.
3428      * @param oData {Object} (Optional) Data value for the cell.
3429      * @static
3430      */
3431     formatTextarea : function(el, oRecord, oColumn, oData) {
3432         var value = (lang.isValue(oData)) ? oData : "",
3433             markup = "<textarea>" + value + "</textarea>";
3434         el.innerHTML = markup;
3435     },
3436
3437     /**
3438      * Formats INPUT TYPE=TEXT elements.
3439      *
3440      * @method DataTable.formatTextbox
3441      * @param el {HTMLElement} The element to format with markup.
3442      * @param oRecord {YAHOO.widget.Record} Record instance.
3443      * @param oColumn {YAHOO.widget.Column} Column instance.
3444      * @param oData {Object} (Optional) Data value for the cell.
3445      * @static
3446      */
3447     formatTextbox : function(el, oRecord, oColumn, oData) {
3448         var value = (lang.isValue(oData)) ? oData : "",
3449             markup = "<input type=\"text\" value=\"" + value + "\" />";
3450         el.innerHTML = markup;
3451     },
3452
3453     /**
3454      * Default cell formatter
3455      *
3456      * @method DataTable.formatDefault
3457      * @param el {HTMLElement} The element to format with markup.
3458      * @param oRecord {YAHOO.widget.Record} Record instance.
3459      * @param oColumn {YAHOO.widget.Column} Column instance.
3460      * @param oData {Object} (Optional) Data value for the cell.
3461      * @static
3462      */
3463     formatDefault : function(el, oRecord, oColumn, oData) {
3464         el.innerHTML = oData === undefined ||
3465                        oData === null ||
3466                        (typeof oData === 'number' && isNaN(oData)) ?
3467                        "&#160;" : oData.toString();
3468     },
3469
3470     /**
3471      * Validates data value to type Number, doing type conversion as
3472      * necessary. A valid Number value is return, else null is returned
3473      * if input value does not validate.
3474      *
3475      *
3476      * @method DataTable.validateNumber
3477      * @param oData {Object} Data to validate.
3478      * @static
3479     */
3480     validateNumber : function(oData) {
3481         //Convert to number
3482         var number = oData * 1;
3483
3484         // Validate
3485         if(lang.isNumber(number)) {
3486             return number;
3487         }
3488         else {
3489             return undefined;
3490         }
3491     }
3492 });
3493
3494 // Done in separate step so referenced functions are defined.
3495 /**
3496  * Cell formatting functions.
3497  * @property DataTable.Formatter
3498  * @type Object
3499  * @static
3500  */
3501 DT.Formatter = {
3502     button   : DT.formatButton,
3503     checkbox : DT.formatCheckbox,
3504     currency : DT.formatCurrency,
3505     "date"   : DT.formatDate,
3506     dropdown : DT.formatDropdown,
3507     email    : DT.formatEmail,
3508     link     : DT.formatLink,
3509     "number" : DT.formatNumber,
3510     radio    : DT.formatRadio,
3511     text     : DT.formatText,
3512     textarea : DT.formatTextarea,
3513     textbox  : DT.formatTextbox,
3514
3515     defaultFormatter : DT.formatDefault
3516 };
3517
3518 lang.extend(DT, util.Element, {
3519
3520 /////////////////////////////////////////////////////////////////////////////
3521 //
3522 // Superclass methods
3523 //
3524 /////////////////////////////////////////////////////////////////////////////
3525
3526 /**
3527  * Implementation of Element's abstract method. Sets up config values.
3528  *
3529  * @method initAttributes
3530  * @param oConfigs {Object} (Optional) Object literal definition of configuration values.
3531  * @private
3532  */
3533
3534 initAttributes : function(oConfigs) {
3535     oConfigs = oConfigs || {};
3536     DT.superclass.initAttributes.call(this, oConfigs);
3537
3538     /**
3539     * @attribute summary
3540     * @description Value for the SUMMARY attribute.
3541     * @type String
3542     * @default ""    
3543     */
3544     this.setAttributeConfig("summary", {
3545         value: "",
3546         validator: lang.isString,
3547         method: function(sSummary) {
3548             if(this._elTable) {
3549                 this._elTable.summary = sSummary;
3550             }
3551         }
3552     });
3553
3554     /**
3555     * @attribute selectionMode
3556     * @description Specifies row or cell selection mode. Accepts the following strings:
3557     *    <dl>
3558     *      <dt>"standard"</dt>
3559     *      <dd>Standard row selection with support for modifier keys to enable
3560     *      multiple selections.</dd>
3561     *
3562     *      <dt>"single"</dt>
3563     *      <dd>Row selection with modifier keys disabled to not allow
3564     *      multiple selections.</dd>
3565     *
3566     *      <dt>"singlecell"</dt>
3567     *      <dd>Cell selection with modifier keys disabled to not allow
3568     *      multiple selections.</dd>
3569     *
3570     *      <dt>"cellblock"</dt>
3571     *      <dd>Cell selection with support for modifier keys to enable multiple
3572     *      selections in a block-fashion, like a spreadsheet.</dd>
3573     *
3574     *      <dt>"cellrange"</dt>
3575     *      <dd>Cell selection with support for modifier keys to enable multiple
3576     *      selections in a range-fashion, like a calendar.</dd>
3577     *    </dl>
3578     *
3579     * @default "standard"
3580     * @type String
3581     */
3582     this.setAttributeConfig("selectionMode", {
3583         value: "standard",
3584         validator: lang.isString
3585     });
3586
3587     /**
3588     * @attribute sortedBy
3589     * @description Object literal provides metadata for initial sort values if
3590     * data will arrive pre-sorted:
3591     * <dl>
3592     *     <dt>sortedBy.key</dt>
3593     *     <dd>{String} Key of sorted Column</dd>
3594     *     <dt>sortedBy.dir</dt>
3595     *     <dd>{String} Initial sort direction, either YAHOO.widget.DataTable.CLASS_ASC or YAHOO.widget.DataTable.CLASS_DESC</dd>
3596     * </dl>
3597     * @type Object | null
3598     */
3599     this.setAttributeConfig("sortedBy", {
3600         value: null,
3601         // TODO: accepted array for nested sorts
3602         validator: function(oNewSortedBy) {
3603             if(oNewSortedBy) {
3604                 return (lang.isObject(oNewSortedBy) && oNewSortedBy.key);
3605             }
3606             else {
3607                 return (oNewSortedBy === null);
3608             }
3609         },
3610         method: function(oNewSortedBy) {
3611             // Stash the previous value
3612             var oOldSortedBy = this.get("sortedBy");
3613             
3614             // Workaround for bug 1827195
3615             this._configs.sortedBy.value = oNewSortedBy;
3616
3617             // Remove ASC/DESC from TH
3618             var oOldColumn,
3619                 nOldColumnKeyIndex,
3620                 oNewColumn,
3621                 nNewColumnKeyIndex;
3622                 
3623             if(this._elThead) {
3624                 if(oOldSortedBy && oOldSortedBy.key && oOldSortedBy.dir) {
3625                     oOldColumn = this._oColumnSet.getColumn(oOldSortedBy.key);
3626                     nOldColumnKeyIndex = oOldColumn.getKeyIndex();
3627                     
3628                     // Remove previous UI from THEAD
3629                     var elOldTh = oOldColumn.getThEl();
3630                     Dom.removeClass(elOldTh, oOldSortedBy.dir);
3631                     this.formatTheadCell(oOldColumn.getThLinerEl().firstChild, oOldColumn, oNewSortedBy);
3632                 }
3633                 if(oNewSortedBy) {
3634                     oNewColumn = (oNewSortedBy.column) ? oNewSortedBy.column : this._oColumnSet.getColumn(oNewSortedBy.key);
3635                     nNewColumnKeyIndex = oNewColumn.getKeyIndex();
3636     
3637                     // Update THEAD with new UI
3638                     var elNewTh = oNewColumn.getThEl();
3639                     // Backward compatibility
3640                     if(oNewSortedBy.dir && ((oNewSortedBy.dir == "asc") ||  (oNewSortedBy.dir == "desc"))) {
3641                         var newClass = (oNewSortedBy.dir == "desc") ?
3642                                 DT.CLASS_DESC :
3643                                 DT.CLASS_ASC;
3644                         Dom.addClass(elNewTh, newClass);
3645                     }
3646                     else {
3647                          var sortClass = oNewSortedBy.dir || DT.CLASS_ASC;
3648                          Dom.addClass(elNewTh, sortClass);
3649                     }
3650                     this.formatTheadCell(oNewColumn.getThLinerEl().firstChild, oNewColumn, oNewSortedBy);
3651                 }
3652             }
3653           
3654             if(this._elTbody) {
3655                 // Update TBODY UI
3656                 this._elTbody.style.display = "none";
3657                 var allRows = this._elTbody.rows,
3658                     allCells;
3659                 for(var i=allRows.length-1; i>-1; i--) {
3660                     allCells = allRows[i].childNodes;
3661                     if(allCells[nOldColumnKeyIndex]) {
3662                         Dom.removeClass(allCells[nOldColumnKeyIndex], oOldSortedBy.dir);
3663                     }
3664                     if(allCells[nNewColumnKeyIndex]) {
3665                         Dom.addClass(allCells[nNewColumnKeyIndex], oNewSortedBy.dir);
3666                     }
3667                 }
3668                 this._elTbody.style.display = "";
3669             }
3670                 
3671             this._clearTrTemplateEl();
3672         }
3673     });
3674     
3675     /**
3676     * @attribute paginator
3677     * @description An instance of YAHOO.widget.Paginator.
3678     * @default null
3679     * @type {Object|YAHOO.widget.Paginator}
3680     */
3681     this.setAttributeConfig("paginator", {
3682         value : null,
3683         validator : function (val) {
3684             return val === null || val instanceof widget.Paginator;
3685         },
3686         method : function () { this._updatePaginator.apply(this,arguments); }
3687     });
3688
3689     /**
3690     * @attribute caption
3691     * @description Value for the CAPTION element. NB: Not supported in
3692     * ScrollingDataTable.    
3693     * @type String
3694     */
3695     this.setAttributeConfig("caption", {
3696         value: null,
3697         validator: lang.isString,
3698         method: function(sCaption) {
3699             this._initCaptionEl(sCaption);
3700         }
3701     });
3702
3703     /**
3704     * @attribute draggableColumns
3705     * @description True if Columns are draggable to reorder, false otherwise.
3706     * The Drag & Drop Utility is required to enable this feature. Only top-level
3707     * and non-nested Columns are draggable. Write once.
3708     * @default false
3709     * @type Boolean
3710     */
3711     this.setAttributeConfig("draggableColumns", {
3712         value: false,
3713         validator: lang.isBoolean,
3714         method: function(oParam) {
3715             if(this._elThead) {
3716                 if(oParam) {
3717                     this._initDraggableColumns();
3718                 }
3719                 else {
3720                     this._destroyDraggableColumns();
3721                 }
3722             }
3723         }
3724     });
3725
3726     /**
3727     * @attribute renderLoopSize          
3728     * @description A value greater than 0 enables DOM rendering of rows to be
3729     * executed from a non-blocking timeout queue and sets how many rows to be
3730     * rendered per timeout. Recommended for very large data sets.     
3731     * @type Number       
3732     * @default 0         
3733     */   
3734      this.setAttributeConfig("renderLoopSize", {         
3735          value: 0,       
3736          validator: lang.isNumber        
3737      });         
3738
3739     /**
3740     * @attribute formatRow
3741     * @description A function that accepts a TR element and its associated Record
3742     * for custom formatting. The function must return TRUE in order to automatically
3743     * continue formatting of child TD elements, else TD elements will not be
3744     * automatically formatted.
3745     * @type function
3746     * @default null
3747     */
3748     this.setAttributeConfig("formatRow", {
3749         value: null,
3750         validator: lang.isFunction
3751     });
3752
3753     /**
3754     * @attribute generateRequest
3755     * @description A function that converts an object literal of desired DataTable
3756     * states into a request value which is then passed to the DataSource's
3757     * sendRequest method in order to retrieve data for those states. This
3758     * function is passed an object literal of state data and a reference to the
3759     * DataTable instance:
3760     *     
3761     * <dl>
3762     *   <dt>pagination<dt>
3763     *   <dd>        
3764     *         <dt>offsetRecord</dt>
3765     *         <dd>{Number} Index of the first Record of the desired page</dd>
3766     *         <dt>rowsPerPage</dt>
3767     *         <dd>{Number} Number of rows per page</dd>
3768     *   </dd>
3769     *   <dt>sortedBy</dt>
3770     *   <dd>                
3771     *         <dt>key</dt>
3772     *         <dd>{String} Key of sorted Column</dd>
3773     *         <dt>dir</dt>
3774     *         <dd>{String} Sort direction, either YAHOO.widget.DataTable.CLASS_ASC or YAHOO.widget.DataTable.CLASS_DESC</dd>
3775     *   </dd>
3776     *   <dt>self</dt>
3777     *   <dd>The DataTable instance</dd>
3778     * </dl>
3779     * 
3780     * and by default returns a String of syntax:
3781     * "sort={sortColumn}&dir={sortDir}&startIndex={pageStartIndex}&results={rowsPerPage}"
3782     * @type function
3783     * @default HTMLFunction
3784     */
3785     this.setAttributeConfig("generateRequest", {
3786         value: function(oState, oSelf) {
3787             // Set defaults
3788             oState = oState || {pagination:null, sortedBy:null};
3789             var sort = encodeURIComponent((oState.sortedBy) ? oState.sortedBy.key : oSelf.getColumnSet().keys[0].getKey());
3790             var dir = (oState.sortedBy && oState.sortedBy.dir === YAHOO.widget.DataTable.CLASS_DESC) ? "desc" : "asc";
3791             var startIndex = (oState.pagination) ? oState.pagination.recordOffset : 0;
3792             var results = (oState.pagination) ? oState.pagination.rowsPerPage : null;
3793             
3794             // Build the request
3795             return  "sort=" + sort +
3796                     "&dir=" + dir +
3797                     "&startIndex=" + startIndex +
3798                     ((results !== null) ? "&results=" + results : "");
3799         },
3800         validator: lang.isFunction
3801     });
3802
3803     /**
3804     * @attribute initialRequest
3805     * @description Defines the initial request that gets sent to the DataSource
3806     * during initialization. Value is ignored if initialLoad is set to any value
3807     * other than true.    
3808     * @type MIXED
3809     * @default null
3810     */
3811     this.setAttributeConfig("initialRequest", {
3812         value: null
3813     });
3814
3815     /**
3816     * @attribute initialLoad
3817     * @description Determines whether or not to load data at instantiation. By
3818     * default, will trigger a sendRequest() to the DataSource and pass in the
3819     * request defined by initialRequest. If set to false, data will not load
3820     * at instantiation. Alternatively, implementers who wish to work with a 
3821     * custom payload may pass in an object literal with the following values:
3822     *     
3823     *    <dl>
3824     *      <dt>request (MIXED)</dt>
3825     *      <dd>Request value.</dd>
3826     *
3827     *      <dt>argument (MIXED)</dt>
3828     *      <dd>Custom data that will be passed through to the callback function.</dd>
3829     *    </dl>
3830     *
3831     *                    
3832     * @type Boolean | Object
3833     * @default true
3834     */
3835     this.setAttributeConfig("initialLoad", {
3836         value: true
3837     });
3838     
3839     /**
3840     * @attribute dynamicData
3841     * @description If true, sorting and pagination are relegated to the DataSource
3842     * for handling, using the request returned by the "generateRequest" function.
3843     * Each new DataSource response blows away all previous Records. False by default, so 
3844     * sorting and pagination will be handled directly on the client side, without
3845     * causing any new requests for data from the DataSource.
3846     * @type Boolean
3847     * @default false
3848     */
3849     this.setAttributeConfig("dynamicData", {
3850         value: false,
3851         validator: lang.isBoolean
3852     });
3853
3854     /**
3855      * @attribute MSG_EMPTY      
3856      * @description Message to display if DataTable has no data.     
3857      * @type String      
3858      * @default "No records found."      
3859      */          
3860      this.setAttributeConfig("MSG_EMPTY", {      
3861          value: "No records found.",     
3862          validator: lang.isString        
3863      });         
3864
3865     /**
3866      * @attribute MSG_LOADING    
3867      * @description Message to display while DataTable is loading data.
3868      * @type String      
3869      * @default "Loading..."     
3870      */          
3871      this.setAttributeConfig("MSG_LOADING", {    
3872          value: "Loading...",    
3873          validator: lang.isString        
3874      });         
3875
3876     /**
3877      * @attribute MSG_ERROR      
3878      * @description Message to display while DataTable has data error.
3879      * @type String      
3880      * @default "Data error."    
3881      */          
3882      this.setAttributeConfig("MSG_ERROR", {      
3883          value: "Data error.",   
3884          validator: lang.isString        
3885      });         
3886
3887     /**
3888      * @attribute MSG_SORTASC 
3889      * @description Message to display in tooltip to sort Column in ascending order.
3890      * @type String      
3891      * @default "Click to sort ascending"        
3892      */          
3893      this.setAttributeConfig("MSG_SORTASC", {    
3894          value: "Click to sort ascending",       
3895          validator: lang.isString,
3896          method: function(sParam) {
3897             if(this._elThead) {
3898                 for(var i=0, allKeys=this.getColumnSet().keys, len=allKeys.length; i<len; i++) {
3899                     if(allKeys[i].sortable && this.getColumnSortDir(allKeys[i]) === DT.CLASS_ASC) {
3900                         allKeys[i]._elThLabel.firstChild.title = sParam;
3901                     }
3902                 }
3903             }      
3904          }
3905      });
3906
3907     /**
3908      * @attribute MSG_SORTDESC 
3909      * @description Message to display in tooltip to sort Column in descending order.
3910      * @type String      
3911      * @default "Click to sort descending"       
3912      */          
3913      this.setAttributeConfig("MSG_SORTDESC", {   
3914          value: "Click to sort descending",      
3915          validator: lang.isString,
3916          method: function(sParam) {
3917             if(this._elThead) {
3918                 for(var i=0, allKeys=this.getColumnSet().keys, len=allKeys.length; i<len; i++) {
3919                     if(allKeys[i].sortable && this.getColumnSortDir(allKeys[i]) === DT.CLASS_DESC) {
3920                         allKeys[i]._elThLabel.firstChild.title = sParam;
3921                     }
3922                 }
3923             }               
3924          }
3925      });
3926      
3927     /**
3928      * @attribute currencySymbol
3929      * @deprecated
3930      */
3931     this.setAttributeConfig("currencySymbol", {
3932         value: "$",
3933         validator: lang.isString
3934     });
3935     
3936     /**
3937      * Default config passed to YAHOO.util.Number.format() by the 'currency' Column formatter.
3938      * @attribute currencyOptions
3939      * @type Object
3940      * @default {prefix: $, decimalPlaces:2, decimalSeparator:".", thousandsSeparator:","}
3941      */
3942     this.setAttributeConfig("currencyOptions", {
3943         value: {
3944             prefix: this.get("currencySymbol"), // TODO: deprecate currencySymbol
3945             decimalPlaces:2,
3946             decimalSeparator:".",
3947             thousandsSeparator:","
3948         }
3949     });
3950     
3951     /**
3952      * Default config passed to YAHOO.util.Date.format() by the 'date' Column formatter.
3953      * @attribute dateOptions
3954      * @type Object
3955      * @default {format:"%m/%d/%Y", locale:"en"}
3956      */
3957     this.setAttributeConfig("dateOptions", {
3958         value: {format:"%m/%d/%Y", locale:"en"}
3959     });
3960     
3961     /**
3962      * Default config passed to YAHOO.util.Number.format() by the 'number' Column formatter.
3963      * @attribute numberOptions
3964      * @type Object
3965      * @default {decimalPlaces:0, thousandsSeparator:","}
3966      */
3967     this.setAttributeConfig("numberOptions", {
3968         value: {
3969             decimalPlaces:0,
3970             thousandsSeparator:","
3971         }
3972     });
3973
3974 },
3975
3976 /////////////////////////////////////////////////////////////////////////////
3977 //
3978 // Private member variables
3979 //
3980 /////////////////////////////////////////////////////////////////////////////
3981
3982 /**
3983  * True if instance is initialized, so as to fire the initEvent after render.
3984  *
3985  * @property _bInit
3986  * @type Boolean
3987  * @default true
3988  * @private
3989  */
3990 _bInit : true,
3991
3992 /**
3993  * Index assigned to instance.
3994  *
3995  * @property _nIndex
3996  * @type Number
3997  * @private
3998  */
3999 _nIndex : null,
4000
4001 /**
4002  * Counter for IDs assigned to TR elements.
4003  *
4004  * @property _nTrCount
4005  * @type Number
4006  * @private
4007  */
4008 _nTrCount : 0,
4009
4010 /**
4011  * Counter for IDs assigned to TD elements.
4012  *
4013  * @property _nTdCount
4014  * @type Number
4015  * @private
4016  */
4017 _nTdCount : 0,
4018
4019 /**
4020  * Unique id assigned to instance "yui-dtN", useful prefix for generating unique
4021  * DOM ID strings and log messages.
4022  *
4023  * @property _sId
4024  * @type String
4025  * @private
4026  */
4027 _sId : null,
4028
4029 /**
4030  * Render chain.
4031  *
4032  * @property _oChainRender
4033  * @type YAHOO.util.Chain
4034  * @private
4035  */
4036 _oChainRender : null,
4037
4038 /**
4039  * DOM reference to the container element for the DataTable instance into which
4040  * all other elements get created.
4041  *
4042  * @property _elContainer
4043  * @type HTMLElement
4044  * @private
4045  */
4046 _elContainer : null,
4047
4048 /**
4049  * DOM reference to the mask element for the DataTable instance which disables it.
4050  *
4051  * @property _elMask
4052  * @type HTMLElement
4053  * @private
4054  */
4055 _elMask : null,
4056
4057 /**
4058  * DOM reference to the TABLE element for the DataTable instance.
4059  *
4060  * @property _elTable
4061  * @type HTMLElement
4062  * @private
4063  */
4064 _elTable : null,
4065
4066 /**
4067  * DOM reference to the CAPTION element for the DataTable instance.
4068  *
4069  * @property _elCaption
4070  * @type HTMLElement
4071  * @private
4072  */
4073 _elCaption : null,
4074
4075 /**
4076  * DOM reference to the COLGROUP element for the DataTable instance.
4077  *
4078  * @property _elColgroup
4079  * @type HTMLElement
4080  * @private
4081  */
4082 _elColgroup : null,
4083
4084 /**
4085  * DOM reference to the THEAD element for the DataTable instance.
4086  *
4087  * @property _elThead
4088  * @type HTMLElement
4089  * @private
4090  */
4091 _elThead : null,
4092
4093 /**
4094  * DOM reference to the primary TBODY element for the DataTable instance.
4095  *
4096  * @property _elTbody
4097  * @type HTMLElement
4098  * @private
4099  */
4100 _elTbody : null,
4101
4102 /**
4103  * DOM reference to the secondary TBODY element used to display DataTable messages.
4104  *
4105  * @property _elMsgTbody
4106  * @type HTMLElement
4107  * @private
4108  */
4109 _elMsgTbody : null,
4110
4111 /**
4112  * DOM reference to the secondary TBODY element's single TR element used to display DataTable messages.
4113  *
4114  * @property _elMsgTr
4115  * @type HTMLElement
4116  * @private
4117  */
4118 _elMsgTr : null,
4119
4120 /**
4121  * DOM reference to the secondary TBODY element's single TD element used to display DataTable messages.
4122  *
4123  * @property _elMsgTd
4124  * @type HTMLElement
4125  * @private
4126  */
4127 _elMsgTd : null,
4128
4129 /**
4130  * DataSource instance for the DataTable instance.
4131  *
4132  * @property _oDataSource
4133  * @type YAHOO.util.DataSource
4134  * @private
4135  */
4136 _oDataSource : null,
4137
4138 /**
4139  * ColumnSet instance for the DataTable instance.
4140  *
4141  * @property _oColumnSet
4142  * @type YAHOO.widget.ColumnSet
4143  * @private
4144  */
4145 _oColumnSet : null,
4146
4147 /**
4148  * RecordSet instance for the DataTable instance.
4149  *
4150  * @property _oRecordSet
4151  * @type YAHOO.widget.RecordSet
4152  * @private
4153  */
4154 _oRecordSet : null,
4155
4156 /**
4157  * The active CellEditor instance for the DataTable instance.
4158  *
4159  * @property _oCellEditor
4160  * @type YAHOO.widget.CellEditor
4161  * @private
4162  */
4163 _oCellEditor : null,
4164
4165 /**
4166  * ID string of first TR element of the current DataTable page.
4167  *
4168  * @property _sFirstTrId
4169  * @type String
4170  * @private
4171  */
4172 _sFirstTrId : null,
4173
4174 /**
4175  * ID string of the last TR element of the current DataTable page.
4176  *
4177  * @property _sLastTrId
4178  * @type String
4179  * @private
4180  */
4181 _sLastTrId : null,
4182
4183 /**
4184  * Template row to create all new rows from.
4185  * @property _elTrTemplate
4186  * @type {HTMLElement}
4187  * @private 
4188  */
4189 _elTrTemplate : null,
4190
4191 /**
4192  * Sparse array of custom functions to set column widths for browsers that don't
4193  * support dynamic CSS rules.  Functions are added at the index representing
4194  * the number of rows they update.
4195  *
4196  * @property _aDynFunctions
4197  * @type Array
4198  * @private
4199  */
4200 _aDynFunctions : [],
4201
4202
4203
4204
4205
4206
4207
4208
4209
4210
4211
4212
4213
4214
4215
4216
4217
4218
4219
4220
4221
4222
4223
4224
4225
4226
4227
4228
4229
4230 /////////////////////////////////////////////////////////////////////////////
4231 //
4232 // Private methods
4233 //
4234 /////////////////////////////////////////////////////////////////////////////
4235
4236 /**
4237  * Clears browser text selection. Useful to call on rowSelectEvent or
4238  * cellSelectEvent to prevent clicks or dblclicks from selecting text in the
4239  * browser.
4240  *
4241  * @method clearTextSelection
4242  */
4243 clearTextSelection : function() {
4244     var sel;
4245     if(window.getSelection) {
4246         sel = window.getSelection();
4247     }
4248     else if(document.getSelection) {
4249         sel = document.getSelection();
4250     }
4251     else if(document.selection) {
4252         sel = document.selection;
4253     }
4254     if(sel) {
4255         if(sel.empty) {
4256             sel.empty();
4257         }
4258         else if (sel.removeAllRanges) {
4259             sel.removeAllRanges();
4260         }
4261         else if(sel.collapse) {
4262             sel.collapse();
4263         }
4264     }
4265 },
4266
4267 /**
4268  * Sets focus on the given element.
4269  *
4270  * @method _focusEl
4271  * @param el {HTMLElement} Element.
4272  * @private
4273  */
4274 _focusEl : function(el) {
4275     el = el || this._elTbody;
4276     // http://developer.mozilla.org/en/docs/index.php?title=Key-navigable_custom_DHTML_widgets
4277     // The timeout is necessary in both IE and Firefox 1.5, to prevent scripts from doing
4278     // strange unexpected things as the user clicks on buttons and other controls.
4279     setTimeout(function() {
4280         try {
4281             el.focus();
4282         }
4283         catch(e) {
4284         }
4285     },0);
4286 },
4287
4288 /**
4289  * Forces Gecko repaint.
4290  *
4291  * @method _repaintGecko
4292  * @el {HTMLElement} (Optional) Element to repaint, otherwise entire document body.
4293  * @private
4294  */
4295 _repaintGecko : (ua.gecko) ? 
4296     function(el) {
4297         el = el || this._elContainer;
4298         var parent = el.parentNode;
4299         var nextSibling = el.nextSibling;
4300         parent.insertBefore(parent.removeChild(el), nextSibling);
4301     } : function() {},
4302
4303 /**
4304  * Forces Opera repaint.
4305  *
4306  * @method _repaintOpera
4307  * @private 
4308  */
4309 _repaintOpera : (ua.opera) ? 
4310     function() {
4311         if(ua.opera) {
4312             document.documentElement.className += " ";
4313             document.documentElement.className = YAHOO.lang.trim(document.documentElement.className);
4314         }
4315     } : function() {} ,
4316
4317 /**
4318  * Forces Webkit repaint.
4319  *
4320  * @method _repaintWebkit
4321  * @el {HTMLElement} (Optional) Element to repaint, otherwise entire document body.
4322  * @private
4323  */
4324 _repaintWebkit : (ua.webkit) ? 
4325     function(el) {
4326         el = el || this._elContainer;
4327         var parent = el.parentNode;
4328         var nextSibling = el.nextSibling;
4329         parent.insertBefore(parent.removeChild(el), nextSibling);
4330     } : function() {},
4331
4332
4333
4334
4335
4336
4337
4338
4339
4340
4341
4342
4343
4344
4345
4346
4347
4348
4349
4350
4351
4352
4353 // INIT FUNCTIONS
4354
4355 /**
4356  * Initializes object literal of config values.
4357  *
4358  * @method _initConfigs
4359  * @param oConfig {Object} Object literal of config values.
4360  * @private
4361  */
4362 _initConfigs : function(oConfigs) {
4363     if(!oConfigs || !lang.isObject(oConfigs)) {
4364         oConfigs = {};
4365     }
4366     this.configs = oConfigs;
4367 },
4368
4369 /**
4370  * Initializes ColumnSet.
4371  *
4372  * @method _initColumnSet
4373  * @param aColumnDefs {Object[]} Array of object literal Column definitions.
4374  * @private
4375  */
4376 _initColumnSet : function(aColumnDefs) {
4377     var oColumn, i, len;
4378     
4379     if(this._oColumnSet) {
4380         // First clear _oDynStyles for existing ColumnSet and
4381         // uregister CellEditor Custom Events
4382         for(i=0, len=this._oColumnSet.keys.length; i<len; i++) {
4383             oColumn = this._oColumnSet.keys[i];
4384             DT._oDynStyles["."+this.getId()+"-col-"+oColumn.getSanitizedKey()+" ."+DT.CLASS_LINER] = undefined;
4385             if(oColumn.editor && oColumn.editor.unsubscribeAll) { // Backward compatibility
4386                 oColumn.editor.unsubscribeAll();
4387             }
4388         }
4389         
4390         this._oColumnSet = null;
4391         this._clearTrTemplateEl();
4392     }
4393     
4394     if(lang.isArray(aColumnDefs)) {
4395         this._oColumnSet =  new YAHOO.widget.ColumnSet(aColumnDefs);
4396     }
4397     // Backward compatibility
4398     else if(aColumnDefs instanceof YAHOO.widget.ColumnSet) {
4399         this._oColumnSet =  aColumnDefs;
4400     }
4401
4402     // Register CellEditor Custom Events
4403     var allKeys = this._oColumnSet.keys;
4404     for(i=0, len=allKeys.length; i<len; i++) {
4405         oColumn = allKeys[i];
4406         if(oColumn.editor && oColumn.editor.subscribe) { // Backward incompatibility
4407             oColumn.editor.subscribe("showEvent", this._onEditorShowEvent, this, true);
4408             oColumn.editor.subscribe("keydownEvent", this._onEditorKeydownEvent, this, true);
4409             oColumn.editor.subscribe("revertEvent", this._onEditorRevertEvent, this, true);
4410             oColumn.editor.subscribe("saveEvent", this._onEditorSaveEvent, this, true);
4411             oColumn.editor.subscribe("cancelEvent", this._onEditorCancelEvent, this, true);
4412             oColumn.editor.subscribe("blurEvent", this._onEditorBlurEvent, this, true);
4413             oColumn.editor.subscribe("blockEvent", this._onEditorBlockEvent, this, true);
4414             oColumn.editor.subscribe("unblockEvent", this._onEditorUnblockEvent, this, true);
4415         }
4416     }
4417 },
4418
4419 /**
4420  * Initializes DataSource.
4421  *
4422  * @method _initDataSource
4423  * @param oDataSource {YAHOO.util.DataSource} DataSource instance.
4424  * @private
4425  */
4426 _initDataSource : function(oDataSource) {
4427     this._oDataSource = null;
4428     if(oDataSource && (lang.isFunction(oDataSource.sendRequest))) {
4429         this._oDataSource = oDataSource;
4430     }
4431     // Backward compatibility
4432     else {
4433         var tmpTable = null;
4434         var tmpContainer = this._elContainer;
4435         var i=0;
4436         //TODO: this will break if re-initing DS at runtime for SDT
4437         // Peek in container child nodes to see if TABLE already exists
4438         if(tmpContainer.hasChildNodes()) {
4439             var tmpChildren = tmpContainer.childNodes;
4440             for(i=0; i<tmpChildren.length; i++) {
4441                 if(tmpChildren[i].nodeName && tmpChildren[i].nodeName.toLowerCase() == "table") {
4442                     tmpTable = tmpChildren[i];
4443                     break;
4444                 }
4445             }
4446             if(tmpTable) {
4447                 var tmpFieldsArray = [];
4448                 for(; i<this._oColumnSet.keys.length; i++) {
4449                     tmpFieldsArray.push({key:this._oColumnSet.keys[i].key});
4450                 }
4451
4452                 this._oDataSource = new DS(tmpTable);
4453                 this._oDataSource.responseType = DS.TYPE_HTMLTABLE;
4454                 this._oDataSource.responseSchema = {fields: tmpFieldsArray};
4455             }
4456         }
4457     }
4458 },
4459
4460 /**
4461  * Initializes RecordSet.
4462  *
4463  * @method _initRecordSet
4464  * @private
4465  */
4466 _initRecordSet : function() {
4467     if(this._oRecordSet) {
4468         this._oRecordSet.reset();
4469     }
4470     else {
4471         this._oRecordSet = new YAHOO.widget.RecordSet();
4472     }
4473 },
4474
4475 /**
4476  * Initializes DOM elements.
4477  *
4478  * @method _initDomElements
4479  * @param elContainer {HTMLElement | String} HTML DIV element by reference or ID. 
4480  * return {Boolean} False in case of error, otherwise true 
4481  * @private
4482  */
4483 _initDomElements : function(elContainer) {
4484     // Outer container
4485     this._initContainerEl(elContainer);
4486     // TABLE
4487     this._initTableEl(this._elContainer);
4488     // COLGROUP
4489     this._initColgroupEl(this._elTable);
4490     // THEAD
4491     this._initTheadEl(this._elTable);
4492     
4493     // Message TBODY
4494     this._initMsgTbodyEl(this._elTable);  
4495
4496     // Primary TBODY
4497     this._initTbodyEl(this._elTable);
4498
4499     if(!this._elContainer || !this._elTable || !this._elColgroup ||  !this._elThead || !this._elTbody || !this._elMsgTbody) {
4500         return false;
4501     }
4502     else {
4503         return true;
4504     }
4505 },
4506
4507 /**
4508  * Destroy's the DataTable outer container element, if available.
4509  *
4510  * @method _destroyContainerEl
4511  * @param elContainer {HTMLElement} Reference to the container element. 
4512  * @private
4513  */
4514 _destroyContainerEl : function(elContainer) {
4515     Dom.removeClass(elContainer, DT.CLASS_DATATABLE);
4516     Ev.purgeElement(elContainer, true);
4517     elContainer.innerHTML = "";
4518     
4519     this._elContainer = null;
4520     this._elColgroup = null;
4521     this._elThead = null;
4522     this._elTbody = null;
4523 },
4524
4525 /**
4526  * Initializes the DataTable outer container element, including a mask.
4527  *
4528  * @method _initContainerEl
4529  * @param elContainer {HTMLElement | String} HTML DIV element by reference or ID.
4530  * @private
4531  */
4532 _initContainerEl : function(elContainer) {
4533     // Validate container
4534     elContainer = Dom.get(elContainer);
4535     
4536     if(elContainer && elContainer.nodeName && (elContainer.nodeName.toLowerCase() == "div")) {
4537         // Destroy previous
4538         this._destroyContainerEl(elContainer);
4539
4540         Dom.addClass(elContainer, DT.CLASS_DATATABLE);
4541         Ev.addListener(elContainer, "focus", this._onTableFocus, this);
4542         Ev.addListener(elContainer, "dblclick", this._onTableDblclick, this);
4543         this._elContainer = elContainer;
4544         
4545         var elMask = document.createElement("div");
4546         elMask.className = DT.CLASS_MASK;
4547         elMask.style.display = "none";
4548         this._elMask = elContainer.appendChild(elMask);
4549     }
4550 },
4551
4552 /**
4553  * Destroy's the DataTable TABLE element, if available.
4554  *
4555  * @method _destroyTableEl
4556  * @private
4557  */
4558 _destroyTableEl : function() {
4559     var elTable = this._elTable;
4560     if(elTable) {
4561         Ev.purgeElement(elTable, true);
4562         elTable.parentNode.removeChild(elTable);
4563         this._elCaption = null;
4564         this._elColgroup = null;
4565         this._elThead = null;
4566         this._elTbody = null;
4567     }
4568 },
4569
4570 /**
4571  * Creates HTML markup CAPTION element.
4572  *
4573  * @method _initCaptionEl
4574  * @param sCaption {String} Text for caption.
4575  * @private
4576  */
4577 _initCaptionEl : function(sCaption) {
4578     if(this._elTable && sCaption) {
4579         // Create CAPTION element
4580         if(!this._elCaption) { 
4581             this._elCaption = this._elTable.createCaption();
4582         }
4583         // Set CAPTION value
4584         this._elCaption.innerHTML = sCaption;
4585     }
4586     else if(this._elCaption) {
4587         this._elCaption.parentNode.removeChild(this._elCaption);
4588     }
4589 },
4590
4591 /**
4592  * Creates HTML markup for TABLE, COLGROUP, THEAD and TBODY elements in outer
4593  * container element.
4594  *
4595  * @method _initTableEl
4596  * @param elContainer {HTMLElement} Container element into which to create TABLE.
4597  * @private
4598  */
4599 _initTableEl : function(elContainer) {
4600     if(elContainer) {
4601         // Destroy previous
4602         this._destroyTableEl();
4603     
4604         // Create TABLE
4605         this._elTable = elContainer.appendChild(document.createElement("table"));  
4606          
4607         // Set SUMMARY attribute
4608         this._elTable.summary = this.get("summary");
4609         
4610         // Create CAPTION element
4611         if(this.get("caption")) {
4612             this._initCaptionEl(this.get("caption"));
4613         }
4614     } 
4615 },
4616
4617 /**
4618  * Destroy's the DataTable COLGROUP element, if available.
4619  *
4620  * @method _destroyColgroupEl
4621  * @private
4622  */
4623 _destroyColgroupEl : function() {
4624     var elColgroup = this._elColgroup;
4625     if(elColgroup) {
4626         var elTable = elColgroup.parentNode;
4627         Ev.purgeElement(elColgroup, true);
4628         elTable.removeChild(elColgroup);
4629         this._elColgroup = null;
4630     }
4631 },
4632
4633 /**
4634  * Initializes COLGROUP and COL elements for managing minWidth.
4635  *
4636  * @method _initColgroupEl
4637  * @param elTable {HTMLElement} TABLE element into which to create COLGROUP.
4638  * @private
4639  */
4640 _initColgroupEl : function(elTable) {
4641     if(elTable) {
4642         // Destroy previous
4643         this._destroyColgroupEl();
4644
4645         // Add COLs to DOCUMENT FRAGMENT
4646         var allCols = this._aColIds || [],
4647             allKeys = this._oColumnSet.keys,
4648             i = 0, len = allCols.length,
4649             elCol, oColumn,
4650             elFragment = document.createDocumentFragment(),
4651             elColTemplate = document.createElement("col");
4652     
4653         for(i=0,len=allKeys.length; i<len; i++) {
4654             oColumn = allKeys[i];
4655             elCol = elFragment.appendChild(elColTemplate.cloneNode(false));
4656         }
4657     
4658         // Create COLGROUP
4659         var elColgroup = elTable.insertBefore(document.createElement("colgroup"), elTable.firstChild);
4660         elColgroup.appendChild(elFragment);
4661         this._elColgroup = elColgroup;
4662     }
4663 },
4664
4665 /**
4666  * Adds a COL element to COLGROUP at given index.
4667  *
4668  * @method _insertColgroupColEl
4669  * @param index {Number} Index of new COL element.
4670  * @private
4671  */
4672 _insertColgroupColEl : function(index) {
4673     if(lang.isNumber(index)&& this._elColgroup) {
4674         var nextSibling = this._elColgroup.childNodes[index] || null;
4675         this._elColgroup.insertBefore(document.createElement("col"), nextSibling);
4676     }
4677 },
4678
4679 /**
4680  * Removes a COL element to COLGROUP at given index.
4681  *
4682  * @method _removeColgroupColEl
4683  * @param index {Number} Index of removed COL element.
4684  * @private
4685  */
4686 _removeColgroupColEl : function(index) {
4687     if(lang.isNumber(index) && this._elColgroup && this._elColgroup.childNodes[index]) {
4688         this._elColgroup.removeChild(this._elColgroup.childNodes[index]);
4689     }
4690 },
4691
4692 /**
4693  * Reorders a COL element from old index(es) to new index.
4694  *
4695  * @method _reorderColgroupColEl
4696  * @param aKeyIndexes {Number[]} Array of indexes of removed COL element.
4697  * @param newIndex {Number} New index. 
4698  * @private
4699  */
4700 _reorderColgroupColEl : function(aKeyIndexes, newIndex) {
4701     if(lang.isArray(aKeyIndexes) && lang.isNumber(newIndex) && this._elColgroup && (this._elColgroup.childNodes.length > aKeyIndexes[aKeyIndexes.length-1])) {
4702         var i,
4703             tmpCols = [];
4704         // Remove COL
4705         for(i=aKeyIndexes.length-1; i>-1; i--) {
4706             tmpCols.push(this._elColgroup.removeChild(this._elColgroup.childNodes[aKeyIndexes[i]]));
4707         }
4708         // Insert COL
4709         var nextSibling = this._elColgroup.childNodes[newIndex] || null;
4710         for(i=tmpCols.length-1; i>-1; i--) {
4711             this._elColgroup.insertBefore(tmpCols[i], nextSibling);
4712         }
4713     }
4714 },
4715
4716 /**
4717  * Destroy's the DataTable THEAD element, if available.
4718  *
4719  * @method _destroyTheadEl
4720  * @private
4721  */
4722 _destroyTheadEl : function() {
4723     var elThead = this._elThead;
4724     if(elThead) {
4725         var elTable = elThead.parentNode;
4726         Ev.purgeElement(elThead, true);
4727         this._destroyColumnHelpers();
4728         elTable.removeChild(elThead);
4729         this._elThead = null;
4730     }
4731 },
4732
4733 /**
4734  * Initializes THEAD element.
4735  *
4736  * @method _initTheadEl
4737  * @param elTable {HTMLElement} TABLE element into which to create COLGROUP.
4738  * @param {HTMLElement} Initialized THEAD element. 
4739  * @private
4740  */
4741 _initTheadEl : function(elTable) {
4742     elTable = elTable || this._elTable;
4743     
4744     if(elTable) {
4745         // Destroy previous
4746         this._destroyTheadEl();
4747     
4748         //TODO: append to DOM later for performance
4749         var elThead = (this._elColgroup) ?
4750             elTable.insertBefore(document.createElement("thead"), this._elColgroup.nextSibling) :
4751             elTable.appendChild(document.createElement("thead"));
4752     
4753         // Set up DOM events for THEAD
4754         Ev.addListener(elThead, "focus", this._onTheadFocus, this);
4755         Ev.addListener(elThead, "keydown", this._onTheadKeydown, this);
4756         Ev.addListener(elThead, "mouseover", this._onTableMouseover, this);
4757         Ev.addListener(elThead, "mouseout", this._onTableMouseout, this);
4758         Ev.addListener(elThead, "mousedown", this._onTableMousedown, this);
4759         Ev.addListener(elThead, "mouseup", this._onTableMouseup, this);
4760         Ev.addListener(elThead, "click", this._onTheadClick, this);
4761
4762         // Since we can't listen for click and dblclick on the same element...
4763         // Attach separately to THEAD and TBODY
4764         ///Ev.addListener(elThead, "dblclick", this._onTableDblclick, this);
4765         
4766        var oColumnSet = this._oColumnSet,
4767             oColumn, i,j, l;
4768         
4769         // Add TRs to the THEAD
4770         var colTree = oColumnSet.tree;
4771         var elTh;
4772         for(i=0; i<colTree.length; i++) {
4773             var elTheadTr = elThead.appendChild(document.createElement("tr"));
4774     
4775             // ...and create TH cells
4776             for(j=0; j<colTree[i].length; j++) {
4777                 oColumn = colTree[i][j];
4778                 elTh = elTheadTr.appendChild(document.createElement("th"));
4779                 this._initThEl(elTh,oColumn);
4780             }
4781     
4782                 // Set FIRST/LAST on THEAD rows
4783                 if(i === 0) {
4784                     Dom.addClass(elTheadTr, DT.CLASS_FIRST);
4785                 }
4786                 if(i === (colTree.length-1)) {
4787                     Dom.addClass(elTheadTr, DT.CLASS_LAST);
4788                 }
4789
4790         }
4791
4792         // Set FIRST/LAST on edge TH elements using the values in ColumnSet headers array
4793         var aFirstHeaders = oColumnSet.headers[0] || [];
4794         for(i=0; i<aFirstHeaders.length; i++) {
4795             Dom.addClass(Dom.get(this.getId() +"-th-"+aFirstHeaders[i]), DT.CLASS_FIRST);
4796         }
4797         var aLastHeaders = oColumnSet.headers[oColumnSet.headers.length-1] || [];
4798         for(i=0; i<aLastHeaders.length; i++) {
4799             Dom.addClass(Dom.get(this.getId() +"-th-"+aLastHeaders[i]), DT.CLASS_LAST);
4800         }
4801         
4802
4803         ///TODO: try _repaintGecko(this._elContainer) instead
4804         // Bug 1806891
4805         if(ua.webkit && ua.webkit < 420) {
4806             var oSelf = this;
4807             setTimeout(function() {
4808                 elThead.style.display = "";
4809             },0);
4810             elThead.style.display = 'none';
4811         }
4812         
4813         this._elThead = elThead;
4814         
4815         // Column helpers needs _elThead to exist
4816         this._initColumnHelpers();  
4817     }
4818 },
4819
4820 /**
4821  * Populates TH element as defined by Column.
4822  *
4823  * @method _initThEl
4824  * @param elTh {HTMLElement} TH element reference.
4825  * @param oColumn {YAHOO.widget.Column} Column object.
4826  * @private
4827  */
4828 _initThEl : function(elTh, oColumn) {
4829     elTh.id = this.getId() + "-th-" + oColumn.getSanitizedKey(); // Needed for accessibility, getColumn by TH, and ColumnDD
4830     elTh.innerHTML = "";
4831     elTh.rowSpan = oColumn.getRowspan();
4832     elTh.colSpan = oColumn.getColspan();
4833     oColumn._elTh = elTh;
4834     
4835     var elThLiner = elTh.appendChild(document.createElement("div"));
4836     elThLiner.id = elTh.id + "-liner"; // Needed for resizer
4837     elThLiner.className = DT.CLASS_LINER;
4838     oColumn._elThLiner = elThLiner;
4839     
4840     var elThLabel = elThLiner.appendChild(document.createElement("span"));
4841     elThLabel.className = DT.CLASS_LABEL;    
4842
4843     // Assign abbr attribute
4844     if(oColumn.abbr) {
4845         elTh.abbr = oColumn.abbr;
4846     }
4847     // Clear minWidth on hidden Columns
4848     if(oColumn.hidden) {
4849         this._clearMinWidth(oColumn);
4850     }
4851         
4852     elTh.className = this._getColumnClassNames(oColumn);
4853             
4854     // Set Column width...
4855     if(oColumn.width) {
4856         // Validate minWidth
4857         var nWidth = (oColumn.minWidth && (oColumn.width < oColumn.minWidth)) ?
4858                 oColumn.minWidth : oColumn.width;
4859         // ...for fallback cases
4860         if(DT._bDynStylesFallback) {
4861             elTh.firstChild.style.overflow = 'hidden';
4862             elTh.firstChild.style.width = nWidth + 'px';        
4863         }
4864         // ...for non fallback cases
4865         else {
4866             this._setColumnWidthDynStyles(oColumn, nWidth + 'px', 'hidden');
4867         }
4868     }
4869
4870     this.formatTheadCell(elThLabel, oColumn, this.get("sortedBy"));
4871     oColumn._elThLabel = elThLabel;
4872 },
4873
4874 /**
4875  * Outputs markup into the given TH based on given Column.
4876  *
4877  * @method DataTable.formatTheadCell
4878  * @param elCellLabel {HTMLElement} The label SPAN element within the TH liner,
4879  * not the liner DIV element.     
4880  * @param oColumn {YAHOO.widget.Column} Column instance.
4881  * @param oSortedBy {Object} Sort state object literal.
4882 */
4883 formatTheadCell : function(elCellLabel, oColumn, oSortedBy) {
4884     var sKey = oColumn.getKey();
4885     var sLabel = lang.isValue(oColumn.label) ? oColumn.label : sKey;
4886
4887     // Add accessibility link for sortable Columns
4888     if(oColumn.sortable) {
4889         // Calculate the direction
4890         var sSortClass = this.getColumnSortDir(oColumn, oSortedBy);
4891         var bDesc = (sSortClass === DT.CLASS_DESC);
4892
4893         // This is the sorted Column
4894         if(oSortedBy && (oColumn.key === oSortedBy.key)) {
4895             bDesc = !(oSortedBy.dir === DT.CLASS_DESC);
4896         }
4897
4898         // Generate a unique HREF for visited status
4899         var sHref = this.getId() + "-href-" + oColumn.getSanitizedKey();
4900         
4901         // Generate a dynamic TITLE for sort status
4902         var sTitle = (bDesc) ? this.get("MSG_SORTDESC") : this.get("MSG_SORTASC");
4903         
4904         // Format the element
4905         elCellLabel.innerHTML = "<a href=\"" + sHref + "\" title=\"" + sTitle + "\" class=\"" + DT.CLASS_SORTABLE + "\">" + sLabel + "</a>";
4906     }
4907     // Just display the label for non-sortable Columns
4908     else {
4909         elCellLabel.innerHTML = sLabel;
4910     }
4911 },
4912
4913 /**
4914  * Disables DD from top-level Column TH elements.
4915  *
4916  * @method _destroyDraggableColumns
4917  * @private
4918  */
4919 _destroyDraggableColumns : function() {
4920     var oColumn, elTh;
4921     for(var i=0, len=this._oColumnSet.tree[0].length; i<len; i++) {
4922         oColumn = this._oColumnSet.tree[0][i];
4923         if(oColumn._dd) {
4924             oColumn._dd = oColumn._dd.unreg();
4925             Dom.removeClass(oColumn.getThEl(), DT.CLASS_DRAGGABLE);       
4926         }
4927     }
4928 },
4929
4930 /**
4931  * Initializes top-level Column TH elements into DD instances.
4932  *
4933  * @method _initDraggableColumns
4934  * @private
4935  */
4936 _initDraggableColumns : function() {
4937     this._destroyDraggableColumns();
4938     if(util.DD) {
4939         var oColumn, elTh, elDragTarget;
4940         for(var i=0, len=this._oColumnSet.tree[0].length; i<len; i++) {
4941             oColumn = this._oColumnSet.tree[0][i];
4942             elTh = oColumn.getThEl();
4943             Dom.addClass(elTh, DT.CLASS_DRAGGABLE);
4944             elDragTarget = DT._initColumnDragTargetEl();
4945             oColumn._dd = new YAHOO.widget.ColumnDD(this, oColumn, elTh, elDragTarget);
4946         }
4947     }
4948     else {
4949     }
4950 },
4951
4952 /**
4953  * Disables resizeability on key Column TH elements.
4954  *
4955  * @method _destroyResizeableColumns
4956  * @private
4957  */
4958 _destroyResizeableColumns : function() {
4959     var aKeys = this._oColumnSet.keys;
4960     for(var i=0, len=aKeys.length; i<len; i++) {
4961         if(aKeys[i]._ddResizer) {
4962             aKeys[i]._ddResizer = aKeys[i]._ddResizer.unreg();
4963             Dom.removeClass(aKeys[i].getThEl(), DT.CLASS_RESIZEABLE);
4964         }
4965     }
4966 },
4967
4968 /**
4969  * Initializes resizeability on key Column TH elements.
4970  *
4971  * @method _initResizeableColumns
4972  * @private
4973  */
4974 _initResizeableColumns : function() {
4975     this._destroyResizeableColumns();
4976     if(util.DD) {
4977         var oColumn, elTh, elThLiner, elThResizerLiner, elThResizer, elResizerProxy, cancelClick;
4978         for(var i=0, len=this._oColumnSet.keys.length; i<len; i++) {
4979             oColumn = this._oColumnSet.keys[i];
4980             if(oColumn.resizeable) {
4981                 elTh = oColumn.getThEl();
4982                 Dom.addClass(elTh, DT.CLASS_RESIZEABLE);
4983                 elThLiner = oColumn.getThLinerEl();
4984                 
4985                 // Bug 1915349: So resizer is as tall as TH when rowspan > 1
4986                 // Create a separate resizer liner with position:relative
4987                 elThResizerLiner = elTh.appendChild(document.createElement("div"));
4988                 elThResizerLiner.className = DT.CLASS_RESIZERLINER;
4989                 
4990                 // Move TH contents into the new resizer liner
4991                 elThResizerLiner.appendChild(elThLiner);
4992                 
4993                 // Create the resizer
4994                 elThResizer = elThResizerLiner.appendChild(document.createElement("div"));
4995                 elThResizer.id = elTh.id + "-resizer"; // Needed for ColumnResizer
4996                 elThResizer.className = DT.CLASS_RESIZER;
4997                 oColumn._elResizer = elThResizer;
4998
4999                 // Create the resizer proxy, once globally
5000                 elResizerProxy = DT._initColumnResizerProxyEl();
5001                 oColumn._ddResizer = new YAHOO.util.ColumnResizer(
5002                         this, oColumn, elTh, elThResizer, elResizerProxy);
5003                 cancelClick = function(e) {
5004                     Ev.stopPropagation(e);
5005                 };
5006                 Ev.addListener(elThResizer,"click",cancelClick);
5007             }
5008         }
5009     }
5010     else {
5011     }
5012 },
5013
5014 /**
5015  * Destroys elements associated with Column functionality: ColumnDD and ColumnResizers.
5016  *
5017  * @method _destroyColumnHelpers
5018  * @private
5019  */
5020 _destroyColumnHelpers : function() {
5021     this._destroyDraggableColumns();
5022     this._destroyResizeableColumns();
5023 },
5024
5025 /**
5026  * Initializes elements associated with Column functionality: ColumnDD and ColumnResizers.
5027  *
5028  * @method _initColumnHelpers
5029  * @private
5030  */
5031 _initColumnHelpers : function() {
5032     if(this.get("draggableColumns")) {
5033         this._initDraggableColumns();
5034     }
5035     this._initResizeableColumns();
5036 },
5037
5038 /**
5039  * Destroy's the DataTable TBODY element, if available.
5040  *
5041  * @method _destroyTbodyEl
5042  * @private
5043  */
5044 _destroyTbodyEl : function() {
5045     var elTbody = this._elTbody;
5046     if(elTbody) {
5047         var elTable = elTbody.parentNode;
5048         Ev.purgeElement(elTbody, true);
5049         elTable.removeChild(elTbody);
5050         this._elTbody = null;
5051     }
5052 },
5053
5054 /**
5055  * Initializes TBODY element for data.
5056  *
5057  * @method _initTbodyEl
5058  * @param elTable {HTMLElement} TABLE element into which to create TBODY .
5059  * @private
5060  */
5061 _initTbodyEl : function(elTable) {
5062     if(elTable) {
5063         // Destroy previous
5064         this._destroyTbodyEl();
5065         
5066         // Create TBODY
5067         var elTbody = elTable.appendChild(document.createElement("tbody"));
5068         elTbody.tabIndex = 0;
5069         elTbody.className = DT.CLASS_DATA;
5070     
5071         // Set up DOM events for TBODY
5072         Ev.addListener(elTbody, "focus", this._onTbodyFocus, this);
5073         Ev.addListener(elTbody, "mouseover", this._onTableMouseover, this);
5074         Ev.addListener(elTbody, "mouseout", this._onTableMouseout, this);
5075         Ev.addListener(elTbody, "mousedown", this._onTableMousedown, this);
5076         Ev.addListener(elTbody, "mouseup", this._onTableMouseup, this);
5077         Ev.addListener(elTbody, "keydown", this._onTbodyKeydown, this);
5078         Ev.addListener(elTbody, "keypress", this._onTableKeypress, this);
5079         Ev.addListener(elTbody, "click", this._onTbodyClick, this);
5080         
5081         // Since we can't listen for click and dblclick on the same element...
5082         // Attach separately to THEAD and TBODY
5083         ///Ev.addListener(elTbody, "dblclick", this._onTableDblclick, this);
5084         
5085     
5086         // IE puts focus outline in the wrong place
5087         if(ua.ie) {
5088             elTbody.hideFocus=true;
5089         }
5090
5091         this._elTbody = elTbody;
5092     }
5093 },
5094
5095 /**
5096  * Destroy's the DataTable message TBODY element, if available.
5097  *
5098  * @method _destroyMsgTbodyEl
5099  * @private
5100  */
5101 _destroyMsgTbodyEl : function() {
5102     var elMsgTbody = this._elMsgTbody;
5103     if(elMsgTbody) {
5104         var elTable = elMsgTbody.parentNode;
5105         Ev.purgeElement(elMsgTbody, true);
5106         elTable.removeChild(elMsgTbody);
5107         this._elTbody = null;
5108     }
5109 },
5110
5111 /**
5112  * Initializes TBODY element for messaging.
5113  *
5114  * @method _initMsgTbodyEl
5115  * @param elTable {HTMLElement} TABLE element into which to create TBODY 
5116  * @private
5117  */
5118 _initMsgTbodyEl : function(elTable) {
5119     if(elTable) {
5120         var elMsgTbody = document.createElement("tbody");
5121         elMsgTbody.className = DT.CLASS_MESSAGE;
5122         var elMsgTr = elMsgTbody.appendChild(document.createElement("tr"));
5123         elMsgTr.className = DT.CLASS_FIRST + " " + DT.CLASS_LAST;
5124         this._elMsgTr = elMsgTr;
5125         var elMsgTd = elMsgTr.appendChild(document.createElement("td"));
5126         elMsgTd.colSpan = this._oColumnSet.keys.length || 1;
5127         elMsgTd.className = DT.CLASS_FIRST + " " + DT.CLASS_LAST;
5128         this._elMsgTd = elMsgTd;
5129         elMsgTbody = elTable.insertBefore(elMsgTbody, this._elTbody);
5130         var elMsgLiner = elMsgTd.appendChild(document.createElement("div"));
5131         elMsgLiner.className = DT.CLASS_LINER;
5132         this._elMsgTbody = elMsgTbody;
5133
5134         // Set up DOM events for TBODY
5135         Ev.addListener(elMsgTbody, "focus", this._onTbodyFocus, this);
5136         Ev.addListener(elMsgTbody, "mouseover", this._onTableMouseover, this);
5137         Ev.addListener(elMsgTbody, "mouseout", this._onTableMouseout, this);
5138         Ev.addListener(elMsgTbody, "mousedown", this._onTableMousedown, this);
5139         Ev.addListener(elMsgTbody, "mouseup", this._onTableMouseup, this);
5140         Ev.addListener(elMsgTbody, "keydown", this._onTbodyKeydown, this);
5141         Ev.addListener(elMsgTbody, "keypress", this._onTableKeypress, this);
5142         Ev.addListener(elMsgTbody, "click", this._onTbodyClick, this);
5143     }
5144 },
5145
5146 /**
5147  * Initialize internal event listeners
5148  *
5149  * @method _initEvents
5150  * @private
5151  */
5152 _initEvents : function () {
5153     // Initialize Column sort
5154     this._initColumnSort();
5155         
5156     // Add the document level click listener
5157     YAHOO.util.Event.addListener(document, "click", this._onDocumentClick, this);
5158
5159     // Paginator integration
5160     this.subscribe("paginatorChange",function () {
5161         this._handlePaginatorChange.apply(this,arguments);
5162     });
5163
5164     this.subscribe("initEvent",function () {
5165         this.renderPaginator();
5166     });
5167
5168     // Initialize CellEditor integration
5169     this._initCellEditing();
5170 },
5171
5172 /**      
5173   * Initializes Column sorting.          
5174   *      
5175   * @method _initColumnSort      
5176   * @private     
5177   */     
5178 _initColumnSort : function() {
5179     this.subscribe("theadCellClickEvent", this.onEventSortColumn);       
5180
5181     // Backward compatibility
5182     var oSortedBy = this.get("sortedBy");
5183     if(oSortedBy) {
5184         if(oSortedBy.dir == "desc") {
5185             this._configs.sortedBy.value.dir = DT.CLASS_DESC;
5186         }
5187         else if(oSortedBy.dir == "asc") {
5188             this._configs.sortedBy.value.dir = DT.CLASS_ASC;
5189         }
5190     }
5191 },
5192
5193 /**      
5194   * Initializes CellEditor integration.          
5195   *      
5196   * @method _initCellEditing     
5197   * @private     
5198   */     
5199 _initCellEditing : function() {
5200     this.subscribe("editorBlurEvent",function () {
5201         this.onEditorBlurEvent.apply(this,arguments);
5202     });
5203     this.subscribe("editorBlockEvent",function () {
5204         this.onEditorBlockEvent.apply(this,arguments);
5205     });
5206     this.subscribe("editorUnblockEvent",function () {
5207         this.onEditorUnblockEvent.apply(this,arguments);
5208     });
5209 },
5210
5211
5212
5213
5214
5215
5216
5217
5218
5219
5220
5221
5222
5223
5224
5225
5226
5227
5228
5229
5230
5231
5232
5233
5234
5235
5236
5237
5238
5239
5240
5241
5242
5243 // DOM MUTATION FUNCTIONS
5244
5245 /**
5246  * Retruns classnames to represent current Column states.
5247  * @method _getColumnClassnames 
5248  * @param oColumn {YAHOO.widget.Column} Column instance.
5249  * @param aAddClasses {String[]} An array of additional classnames to add to the
5250  * return value.  
5251  * @return {String} A String of classnames to be assigned to TH or TD elements
5252  * for given Column.  
5253  * @private 
5254  */
5255 _getColumnClassNames : function (oColumn, aAddClasses) {
5256     var allClasses;
5257     
5258     // Add CSS classes
5259     if(lang.isString(oColumn.className)) {
5260         // Single custom class
5261         allClasses = [oColumn.className];
5262     }
5263     else if(lang.isArray(oColumn.className)) {
5264         // Array of custom classes
5265         allClasses = oColumn.className;
5266     }
5267     else {
5268         // no custom classes
5269         allClasses = [];
5270     }
5271     
5272     // Hook for setting width with via dynamic style uses key since ID is too disposable
5273     allClasses[allClasses.length] = this.getId() + "-col-" +oColumn.getSanitizedKey();
5274
5275     // Column key - minus any chars other than "A-Z", "a-z", "0-9", "_", "-", ".", or ":"
5276     allClasses[allClasses.length] = "yui-dt-col-" +oColumn.getSanitizedKey();
5277
5278     var isSortedBy = this.get("sortedBy") || {};
5279     // Sorted
5280     if(oColumn.key === isSortedBy.key) {
5281         allClasses[allClasses.length] = isSortedBy.dir || '';
5282     }
5283     // Hidden
5284     if(oColumn.hidden) {
5285         allClasses[allClasses.length] = DT.CLASS_HIDDEN;
5286     }
5287     // Selected
5288     if(oColumn.selected) {
5289         allClasses[allClasses.length] = DT.CLASS_SELECTED;
5290     }
5291     // Sortable
5292     if(oColumn.sortable) {
5293         allClasses[allClasses.length] = DT.CLASS_SORTABLE;
5294     }
5295     // Resizeable
5296     if(oColumn.resizeable) {
5297         allClasses[allClasses.length] = DT.CLASS_RESIZEABLE;
5298     }
5299     // Editable
5300     if(oColumn.editor) {
5301         allClasses[allClasses.length] = DT.CLASS_EDITABLE;
5302     }
5303     
5304     // Addtnl classes, including First/Last
5305     if(aAddClasses) {
5306         allClasses = allClasses.concat(aAddClasses);
5307     }
5308     
5309     return allClasses.join(' ');  
5310 },
5311
5312 /**
5313  * Clears TR element template in response to any Column state change.
5314  * @method _clearTrTemplateEl
5315  * @private 
5316  */
5317 _clearTrTemplateEl : function () {
5318     this._elTrTemplate = null;
5319 },
5320
5321 /**
5322  * Returns a new TR element template with TD elements classed with current
5323  * Column states.
5324  * @method _getTrTemplateEl 
5325  * @return {HTMLElement} A TR element to be cloned and added to the DOM.
5326  * @private 
5327  */
5328 _getTrTemplateEl : function (oRecord, index) {
5329     // Template is already available
5330     if(this._elTrTemplate) {
5331         return this._elTrTemplate;
5332     }
5333     // Template needs to be created
5334     else {
5335         var d   = document,
5336             tr  = d.createElement('tr'),
5337             td  = d.createElement('td'),
5338             div = d.createElement('div');
5339     
5340         // Append the liner element
5341         td.appendChild(div);
5342
5343         // Create TD elements into DOCUMENT FRAGMENT
5344         var df = document.createDocumentFragment(),
5345             allKeys = this._oColumnSet.keys,
5346             elTd;
5347
5348         // Set state for each TD;
5349         var aAddClasses;
5350         for(var i=0, keysLen=allKeys.length; i<keysLen; i++) {
5351             // Clone the TD template
5352             elTd = td.cloneNode(true);
5353
5354             // Format the base TD
5355             elTd = this._formatTdEl(allKeys[i], elTd, i, (i===keysLen-1));
5356                         
5357             df.appendChild(elTd);
5358         }
5359         tr.appendChild(df);
5360         this._elTrTemplate = tr;
5361         return tr;
5362     }   
5363 },
5364
5365 /**
5366  * Formats a basic TD element.
5367  * @method _formatTdEl 
5368  * @param oColumn {YAHOO.widget.Column} Associated Column instance. 
5369  * @param elTd {HTMLElement} An unformatted TD element.
5370  * @param index {Number} Column key index. 
5371  * @param isLast {Boolean} True if Column is last key of the ColumnSet.
5372  * @return {HTMLElement} A formatted TD element.
5373  * @private 
5374  */
5375 _formatTdEl : function (oColumn, elTd, index, isLast) {
5376     var oColumnSet = this._oColumnSet;
5377     
5378     // Set the TD's accessibility headers
5379     var allHeaders = oColumnSet.headers,
5380         allColHeaders = allHeaders[index],
5381         sTdHeaders = "",
5382         sHeader;
5383     for(var j=0, headersLen=allColHeaders.length; j < headersLen; j++) {
5384         sHeader = this._sId + "-th-" + allColHeaders[j] + ' ';
5385         sTdHeaders += sHeader;
5386     }
5387     elTd.headers = sTdHeaders;
5388     
5389     // Class the TD element
5390     var aAddClasses = [];
5391     if(index === 0) {
5392         aAddClasses[aAddClasses.length] = DT.CLASS_FIRST;
5393     }
5394     if(isLast) {
5395         aAddClasses[aAddClasses.length] = DT.CLASS_LAST;
5396     }
5397     elTd.className = this._getColumnClassNames(oColumn, aAddClasses);
5398
5399     // Class the liner element
5400     elTd.firstChild.className = DT.CLASS_LINER;
5401
5402     // Set Column width for fallback cases
5403     if(oColumn.width && DT._bDynStylesFallback) {
5404         // Validate minWidth
5405         var nWidth = (oColumn.minWidth && (oColumn.width < oColumn.minWidth)) ?
5406                 oColumn.minWidth : oColumn.width;
5407         elTd.firstChild.style.overflow = 'hidden';
5408         elTd.firstChild.style.width = nWidth + 'px';
5409     }
5410     
5411     return elTd;
5412 },
5413
5414
5415 /**
5416  * Create a new TR element for a given Record and appends it with the correct
5417  * number of Column-state-classed TD elements. Striping is the responsibility of
5418  * the calling function, which may decide to stripe the single row, a subset of
5419  * rows, or all the rows.
5420  * @method _createTrEl
5421  * @param oRecord {YAHOO.widget.Record} Record instance
5422  * @return {HTMLElement} The new TR element.  This must be added to the DOM.
5423  * @private 
5424  */
5425 _addTrEl : function (oRecord) {
5426     var elTrTemplate = this._getTrTemplateEl();
5427     
5428     // Clone the TR template.
5429     var elTr = elTrTemplate.cloneNode(true);
5430     
5431     // Populate content
5432     return this._updateTrEl(elTr,oRecord);
5433 },
5434
5435 /**
5436  * Formats the contents of the given TR's TD elements with data from the given
5437  * Record. Only innerHTML should change, nothing structural.
5438  *
5439  * @method _updateTrEl
5440  * @param elTr {HTMLElement} The TR element to update.
5441  * @param oRecord {YAHOO.widget.Record} The associated Record instance.
5442  * @return {HTMLElement} DOM reference to the new TR element.
5443  * @private
5444  */
5445 _updateTrEl : function(elTr, oRecord) {
5446     var ok = this.get("formatRow") ? this.get("formatRow").call(this, elTr, oRecord) : true;
5447     if(ok) {
5448         // Hide the row to prevent constant reflows
5449         elTr.style.display = 'none';
5450         
5451         // Update TD elements with new data
5452         var allTds = elTr.childNodes,
5453             elTd;
5454         for(var i=0,len=allTds.length; i<len; ++i) {
5455             elTd = allTds[i];
5456             
5457             // Set the cell content
5458             this.formatCell(allTds[i].firstChild, oRecord, this._oColumnSet.keys[i]);
5459         }
5460         
5461         // Redisplay the row for reflow
5462         elTr.style.display = '';
5463     }
5464     
5465     elTr.id = oRecord.getId(); // Needed for Record association and tracking of FIRST/LAST
5466     return elTr;
5467 },
5468
5469
5470 /**
5471  * Deletes TR element by DOM reference or by DataTable page row index.
5472  *
5473  * @method _deleteTrEl
5474  * @param row {HTMLElement | Number} TR element reference or Datatable page row index.
5475  * @return {Boolean} Returns true if successful, else returns false.
5476  * @private
5477  */
5478 _deleteTrEl : function(row) {
5479     var rowIndex;
5480
5481     // Get page row index for the element
5482     if(!lang.isNumber(row)) {
5483         rowIndex = Dom.get(row).sectionRowIndex;
5484     }
5485     else {
5486         rowIndex = row;
5487     }
5488     if(lang.isNumber(rowIndex) && (rowIndex > -2) && (rowIndex < this._elTbody.rows.length)) {
5489         // Cannot use tbody.deleteRow due to IE6 instability
5490         //return this._elTbody.deleteRow(rowIndex);
5491         return this._elTbody.removeChild(this.getTrEl(row));
5492     }
5493     else {
5494         return null;
5495     }
5496 },
5497
5498
5499
5500
5501
5502
5503
5504
5505
5506
5507
5508
5509
5510
5511
5512
5513
5514
5515
5516
5517
5518
5519
5520
5521
5522
5523
5524 // CSS/STATE FUNCTIONS
5525
5526
5527
5528
5529 /**
5530  * Removes the class YAHOO.widget.DataTable.CLASS_FIRST from the first TR element
5531  * of the DataTable page and updates internal tracker.
5532  *
5533  * @method _unsetFirstRow
5534  * @private
5535  */
5536 _unsetFirstRow : function() {
5537     // Remove FIRST
5538     if(this._sFirstTrId) {
5539         Dom.removeClass(this._sFirstTrId, DT.CLASS_FIRST);
5540         this._sFirstTrId = null;
5541     }
5542 },
5543
5544 /**
5545  * Assigns the class YAHOO.widget.DataTable.CLASS_FIRST to the first TR element
5546  * of the DataTable page and updates internal tracker.
5547  *
5548  * @method _setFirstRow
5549  * @private
5550  */
5551 _setFirstRow : function() {
5552     this._unsetFirstRow();
5553     var elTr = this.getFirstTrEl();
5554     if(elTr) {
5555         // Set FIRST
5556         Dom.addClass(elTr, DT.CLASS_FIRST);
5557         this._sFirstTrId = elTr.id;
5558     }
5559 },
5560
5561 /**
5562  * Removes the class YAHOO.widget.DataTable.CLASS_LAST from the last TR element
5563  * of the DataTable page and updates internal tracker.
5564  *
5565  * @method _unsetLastRow
5566  * @private
5567  */
5568 _unsetLastRow : function() {
5569     // Unassign previous class
5570     if(this._sLastTrId) {
5571         Dom.removeClass(this._sLastTrId, DT.CLASS_LAST);
5572         this._sLastTrId = null;
5573     }   
5574 },
5575
5576 /**
5577  * Assigns the class YAHOO.widget.DataTable.CLASS_LAST to the last TR element
5578  * of the DataTable page and updates internal tracker.
5579  *
5580  * @method _setLastRow
5581  * @private
5582  */
5583 _setLastRow : function() {
5584     this._unsetLastRow();
5585     var elTr = this.getLastTrEl();
5586     if(elTr) {
5587         // Assign class
5588         Dom.addClass(elTr, DT.CLASS_LAST);
5589         this._sLastTrId = elTr.id;
5590     }
5591 },
5592
5593 /**
5594  * Assigns the classes DT.CLASS_EVEN and DT.CLASS_ODD to one, many, or all TR elements.
5595  *
5596  * @method _setRowStripes
5597  * @param row {HTMLElement | String | Number} (optional) HTML TR element reference
5598  * or string ID, or page row index of where to start striping.
5599  * @param range {Number} (optional) If given, how many rows to stripe, otherwise
5600  * stripe all the rows until the end.
5601  * @private
5602  */
5603 _setRowStripes : function(row, range) {
5604     // Default values stripe all rows
5605     var allRows = this._elTbody.rows,
5606         nStartIndex = 0,
5607         nEndIndex = allRows.length,
5608         aOdds = [], nOddIdx = 0,
5609         aEvens = [], nEvenIdx = 0;
5610
5611     // Stripe a subset
5612     if((row !== null) && (row !== undefined)) {
5613         // Validate given start row
5614         var elStartRow = this.getTrEl(row);
5615         if(elStartRow) {
5616             nStartIndex = elStartRow.sectionRowIndex;
5617
5618             // Validate given range
5619             if(lang.isNumber(range) && (range > 1)) {
5620                 nEndIndex = nStartIndex + range;
5621             }
5622         }
5623     }
5624
5625     for(var i=nStartIndex; i<nEndIndex; i++) {
5626         if(i%2) {
5627             aOdds[nOddIdx++] = allRows[i];
5628         } else {
5629             aEvens[nEvenIdx++] = allRows[i];
5630         }
5631     }
5632
5633     if (aOdds.length) {
5634         Dom.replaceClass(aOdds, DT.CLASS_EVEN, DT.CLASS_ODD);
5635     }
5636
5637     if (aEvens.length) {
5638         Dom.replaceClass(aEvens, DT.CLASS_ODD, DT.CLASS_EVEN);
5639     }
5640 },
5641
5642 /**
5643  * Assigns the class DT.CLASS_SELECTED to TR and TD elements.
5644  *
5645  * @method _setSelections
5646  * @private
5647  */
5648 _setSelections : function() {
5649     // Keep track of selected rows
5650     var allSelectedRows = this.getSelectedRows();
5651     // Keep track of selected cells
5652     var allSelectedCells = this.getSelectedCells();
5653     // Anything to select?
5654     if((allSelectedRows.length>0) || (allSelectedCells.length > 0)) {
5655         var oColumnSet = this._oColumnSet,
5656             el;
5657         // Loop over each row
5658         for(var i=0; i<allSelectedRows.length; i++) {
5659             el = Dom.get(allSelectedRows[i]);
5660             if(el) {
5661                 Dom.addClass(el, DT.CLASS_SELECTED);
5662             }
5663         }
5664         // Loop over each cell
5665         for(i=0; i<allSelectedCells.length; i++) {
5666             el = Dom.get(allSelectedCells[i].recordId);
5667             if(el) {
5668                 Dom.addClass(el.childNodes[oColumnSet.getColumn(allSelectedCells[i].columnKey).getKeyIndex()], DT.CLASS_SELECTED);
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
5694
5695
5696
5697
5698
5699
5700
5701
5702
5703
5704
5705
5706
5707
5708
5709
5710
5711
5712
5713
5714
5715
5716 /////////////////////////////////////////////////////////////////////////////
5717 //
5718 // Private DOM Event Handlers
5719 //
5720 /////////////////////////////////////////////////////////////////////////////
5721
5722 /**
5723  * Validates minWidths whenever the render chain ends.
5724  *
5725  * @method _onRenderChainEnd
5726  * @private
5727  */
5728 _onRenderChainEnd : function() {
5729     // Hide loading message
5730     this.hideTableMessage();
5731     
5732     // Show empty message
5733     if(this._elTbody.rows.length === 0) {
5734         this.showTableMessage(this.get("MSG_EMPTY"), DT.CLASS_EMPTY);        
5735     }
5736
5737     // Execute in timeout thread to give implementers a chance
5738     // to subscribe after the constructor
5739     var oSelf = this;
5740     setTimeout(function() {
5741         if((oSelf instanceof DT) && oSelf._sId) {        
5742             // Init event
5743             if(oSelf._bInit) {
5744                 oSelf._bInit = false;
5745                 oSelf.fireEvent("initEvent");
5746             }
5747     
5748             // Render event
5749             oSelf.fireEvent("renderEvent");
5750             // Backward compatibility
5751             oSelf.fireEvent("refreshEvent");
5752     
5753             // Post-render routine
5754             oSelf.validateColumnWidths();
5755     
5756             // Post-render event
5757             oSelf.fireEvent("postRenderEvent");
5758             
5759             /*if(YAHOO.example.Performance.trialStart) {
5760                 YAHOO.example.Performance.trialStart = null;
5761             }*/
5762             
5763         }
5764     }, 0);
5765 },
5766
5767 /**
5768  * Handles click events on the DOCUMENT.
5769  *
5770  * @method _onDocumentClick
5771  * @param e {HTMLEvent} The click event.
5772  * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
5773  * @private
5774  */
5775 _onDocumentClick : function(e, oSelf) {
5776     var elTarget = Ev.getTarget(e);
5777     var elTag = elTarget.nodeName.toLowerCase();
5778
5779     if(!Dom.isAncestor(oSelf._elContainer, elTarget)) {
5780         oSelf.fireEvent("tableBlurEvent");
5781
5782         // Fires editorBlurEvent when click is not within the TABLE.
5783         // For cases when click is within the TABLE, due to timing issues,
5784         // the editorBlurEvent needs to get fired by the lower-level DOM click
5785         // handlers below rather than by the TABLE click handler directly.
5786         if(oSelf._oCellEditor) {
5787             if(oSelf._oCellEditor.getContainerEl) {
5788                 var elContainer = oSelf._oCellEditor.getContainerEl();
5789                 // Only if the click was not within the CellEditor container
5790                 if(!Dom.isAncestor(elContainer, elTarget) &&
5791                         (elContainer.id !== elTarget.id)) {
5792                     oSelf._oCellEditor.fireEvent("blurEvent", {editor: oSelf._oCellEditor});
5793                 }
5794             }
5795             // Backward Compatibility
5796             else if(oSelf._oCellEditor.isActive) {
5797                 // Only if the click was not within the Cell Editor container
5798                 if(!Dom.isAncestor(oSelf._oCellEditor.container, elTarget) &&
5799                         (oSelf._oCellEditor.container.id !== elTarget.id)) {
5800                     oSelf.fireEvent("editorBlurEvent", {editor:oSelf._oCellEditor});
5801                 }
5802             }
5803         }
5804     }
5805 },
5806
5807 /**
5808  * Handles focus events on the DataTable instance.
5809  *
5810  * @method _onTableFocus
5811  * @param e {HTMLEvent} The focus event.
5812  * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
5813  * @private
5814  */
5815 _onTableFocus : function(e, oSelf) {
5816     oSelf.fireEvent("tableFocusEvent");
5817 },
5818
5819 /**
5820  * Handles focus events on the THEAD element.
5821  *
5822  * @method _onTheadFocus
5823  * @param e {HTMLEvent} The focus event.
5824  * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
5825  * @private
5826  */
5827 _onTheadFocus : function(e, oSelf) {
5828     oSelf.fireEvent("theadFocusEvent");
5829     oSelf.fireEvent("tableFocusEvent");
5830 },
5831
5832 /**
5833  * Handles focus events on the TBODY element.
5834  *
5835  * @method _onTbodyFocus
5836  * @param e {HTMLEvent} The focus event.
5837  * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
5838  * @private
5839  */
5840 _onTbodyFocus : function(e, oSelf) {
5841     oSelf.fireEvent("tbodyFocusEvent");
5842     oSelf.fireEvent("tableFocusEvent");
5843 },
5844
5845 /**
5846  * Handles mouseover events on the DataTable instance.
5847  *
5848  * @method _onTableMouseover
5849  * @param e {HTMLEvent} The mouseover event.
5850  * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
5851  * @private
5852  */
5853 _onTableMouseover : function(e, oSelf) {
5854     var elTarget = Ev.getTarget(e);
5855         var elTag = elTarget.nodeName.toLowerCase();
5856         var bKeepBubbling = true;
5857         while(elTarget && (elTag != "table")) {
5858             switch(elTag) {
5859                 case "body":
5860                      return;
5861                 case "a":
5862                     break;
5863                 case "td":
5864                     bKeepBubbling = oSelf.fireEvent("cellMouseoverEvent",{target:elTarget,event:e});
5865                     break;
5866                 case "span":
5867                     if(Dom.hasClass(elTarget, DT.CLASS_LABEL)) {
5868                         bKeepBubbling = oSelf.fireEvent("theadLabelMouseoverEvent",{target:elTarget,event:e});
5869                         // Backward compatibility
5870                         bKeepBubbling = oSelf.fireEvent("headerLabelMouseoverEvent",{target:elTarget,event:e});
5871                     }
5872                     break;
5873                 case "th":
5874                     bKeepBubbling = oSelf.fireEvent("theadCellMouseoverEvent",{target:elTarget,event:e});
5875                     // Backward compatibility
5876                     bKeepBubbling = oSelf.fireEvent("headerCellMouseoverEvent",{target:elTarget,event:e});
5877                     break;
5878                 case "tr":
5879                     if(elTarget.parentNode.nodeName.toLowerCase() == "thead") {
5880                         bKeepBubbling = oSelf.fireEvent("theadRowMouseoverEvent",{target:elTarget,event:e});
5881                         // Backward compatibility
5882                         bKeepBubbling = oSelf.fireEvent("headerRowMouseoverEvent",{target:elTarget,event:e});
5883                     }
5884                     else {
5885                         bKeepBubbling = oSelf.fireEvent("rowMouseoverEvent",{target:elTarget,event:e});
5886                     }
5887                     break;
5888                 default:
5889                     break;
5890             }
5891             if(bKeepBubbling === false) {
5892                 return;
5893             }
5894             else {
5895                 elTarget = elTarget.parentNode;
5896                 if(elTarget) {
5897                     elTag = elTarget.nodeName.toLowerCase();
5898                 }
5899             }
5900         }
5901         oSelf.fireEvent("tableMouseoverEvent",{target:(elTarget || oSelf._elContainer),event:e});
5902 },
5903
5904 /**
5905  * Handles mouseout events on the DataTable instance.
5906  *
5907  * @method _onTableMouseout
5908  * @param e {HTMLEvent} The mouseout event.
5909  * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
5910  * @private
5911  */
5912 _onTableMouseout : function(e, oSelf) {
5913     var elTarget = Ev.getTarget(e);
5914     var elTag = elTarget.nodeName.toLowerCase();
5915     var bKeepBubbling = true;
5916     while(elTarget && (elTag != "table")) {
5917         switch(elTag) {
5918             case "body":
5919                 return;
5920             case "a":
5921                 break;
5922             case "td":
5923                 bKeepBubbling = oSelf.fireEvent("cellMouseoutEvent",{target:elTarget,event:e});
5924                 break;
5925             case "span":
5926                 if(Dom.hasClass(elTarget, DT.CLASS_LABEL)) {
5927                     bKeepBubbling = oSelf.fireEvent("theadLabelMouseoutEvent",{target:elTarget,event:e});
5928                     // Backward compatibility
5929                     bKeepBubbling = oSelf.fireEvent("headerLabelMouseoutEvent",{target:elTarget,event:e});
5930                 }
5931                 break;
5932             case "th":
5933                 bKeepBubbling = oSelf.fireEvent("theadCellMouseoutEvent",{target:elTarget,event:e});
5934                 // Backward compatibility
5935                 bKeepBubbling = oSelf.fireEvent("headerCellMouseoutEvent",{target:elTarget,event:e});
5936                 break;
5937             case "tr":
5938                 if(elTarget.parentNode.nodeName.toLowerCase() == "thead") {
5939                     bKeepBubbling = oSelf.fireEvent("theadRowMouseoutEvent",{target:elTarget,event:e});
5940                     // Backward compatibility
5941                     bKeepBubbling = oSelf.fireEvent("headerRowMouseoutEvent",{target:elTarget,event:e});
5942                 }
5943                 else {
5944                     bKeepBubbling = oSelf.fireEvent("rowMouseoutEvent",{target:elTarget,event:e});
5945                 }
5946                 break;
5947             default:
5948                 break;
5949         }
5950         if(bKeepBubbling === false) {
5951             return;
5952         }
5953         else {
5954             elTarget = elTarget.parentNode;
5955             if(elTarget) {
5956                 elTag = elTarget.nodeName.toLowerCase();
5957             }
5958         }
5959     }
5960     oSelf.fireEvent("tableMouseoutEvent",{target:(elTarget || oSelf._elContainer),event:e});
5961 },
5962
5963 /**
5964  * Handles mousedown events on the DataTable instance.
5965  *
5966  * @method _onTableMousedown
5967  * @param e {HTMLEvent} The mousedown event.
5968  * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
5969  * @private
5970  */
5971 _onTableMousedown : function(e, oSelf) {
5972     var elTarget = Ev.getTarget(e);
5973     var elTag = elTarget.nodeName.toLowerCase();
5974     var bKeepBubbling = true;
5975     while(elTarget && (elTag != "table")) {
5976         switch(elTag) {
5977             case "body":
5978                 return;
5979             case "a":
5980                 break;
5981             case "td":
5982                 bKeepBubbling = oSelf.fireEvent("cellMousedownEvent",{target:elTarget,event:e});
5983                 break;
5984             case "span":
5985                 if(Dom.hasClass(elTarget, DT.CLASS_LABEL)) {
5986                     bKeepBubbling = oSelf.fireEvent("theadLabelMousedownEvent",{target:elTarget,event:e});
5987                     // Backward compatibility
5988                     bKeepBubbling = oSelf.fireEvent("headerLabelMousedownEvent",{target:elTarget,event:e});
5989                 }
5990                 break;
5991             case "th":
5992                 bKeepBubbling = oSelf.fireEvent("theadCellMousedownEvent",{target:elTarget,event:e});
5993                 // Backward compatibility
5994                 bKeepBubbling = oSelf.fireEvent("headerCellMousedownEvent",{target:elTarget,event:e});
5995                 break;
5996             case "tr":
5997                 if(elTarget.parentNode.nodeName.toLowerCase() == "thead") {
5998                     bKeepBubbling = oSelf.fireEvent("theadRowMousedownEvent",{target:elTarget,event:e});
5999                     // Backward compatibility
6000                     bKeepBubbling = oSelf.fireEvent("headerRowMousedownEvent",{target:elTarget,event:e});
6001                 }
6002                 else {
6003                     bKeepBubbling = oSelf.fireEvent("rowMousedownEvent",{target:elTarget,event:e});
6004                 }
6005                 break;
6006             default:
6007                 break;
6008         }
6009         if(bKeepBubbling === false) {
6010             return;
6011         }
6012         else {
6013             elTarget = elTarget.parentNode;
6014             if(elTarget) {
6015                 elTag = elTarget.nodeName.toLowerCase();
6016             }
6017         }
6018     }
6019     oSelf.fireEvent("tableMousedownEvent",{target:(elTarget || oSelf._elContainer),event:e});
6020 },
6021
6022 /**
6023  * Handles mouseup events on the DataTable instance.
6024  *
6025  * @method _onTableMouseup
6026  * @param e {HTMLEvent} The mouseup event.
6027  * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
6028  * @private
6029  */
6030 _onTableMouseup : function(e, oSelf) {
6031     var elTarget = Ev.getTarget(e);
6032     var elTag = elTarget.nodeName.toLowerCase();
6033     var bKeepBubbling = true;
6034     while(elTarget && (elTag != "table")) {
6035         switch(elTag) {
6036             case "body":
6037                 return;
6038             case "a":
6039                 break;
6040             case "td":
6041                 bKeepBubbling = oSelf.fireEvent("cellMouseupEvent",{target:elTarget,event:e});
6042                 break;
6043             case "span":
6044                 if(Dom.hasClass(elTarget, DT.CLASS_LABEL)) {
6045                     bKeepBubbling = oSelf.fireEvent("theadLabelMouseupEvent",{target:elTarget,event:e});
6046                     // Backward compatibility
6047                     bKeepBubbling = oSelf.fireEvent("headerLabelMouseupEvent",{target:elTarget,event:e});
6048                 }
6049                 break;
6050             case "th":
6051                 bKeepBubbling = oSelf.fireEvent("theadCellMouseupEvent",{target:elTarget,event:e});
6052                 // Backward compatibility
6053                 bKeepBubbling = oSelf.fireEvent("headerCellMouseupEvent",{target:elTarget,event:e});
6054                 break;
6055             case "tr":
6056                 if(elTarget.parentNode.nodeName.toLowerCase() == "thead") {
6057                     bKeepBubbling = oSelf.fireEvent("theadRowMouseupEvent",{target:elTarget,event:e});
6058                     // Backward compatibility
6059                     bKeepBubbling = oSelf.fireEvent("headerRowMouseupEvent",{target:elTarget,event:e});
6060                 }
6061                 else {
6062                     bKeepBubbling = oSelf.fireEvent("rowMouseupEvent",{target:elTarget,event:e});
6063                 }
6064                 break;
6065             default:
6066                 break;
6067         }
6068         if(bKeepBubbling === false) {
6069             return;
6070         }
6071         else {
6072             elTarget = elTarget.parentNode;
6073             if(elTarget) {
6074                 elTag = elTarget.nodeName.toLowerCase();
6075             }
6076         }
6077     }
6078     oSelf.fireEvent("tableMouseupEvent",{target:(elTarget || oSelf._elContainer),event:e});
6079 },
6080
6081 /**
6082  * Handles dblclick events on the DataTable instance.
6083  *
6084  * @method _onTableDblclick
6085  * @param e {HTMLEvent} The dblclick event.
6086  * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
6087  * @private
6088  */
6089 _onTableDblclick : function(e, oSelf) {
6090     var elTarget = Ev.getTarget(e);
6091     var elTag = elTarget.nodeName.toLowerCase();
6092     var bKeepBubbling = true;
6093     while(elTarget && (elTag != "table")) {
6094         switch(elTag) {
6095             case "body":
6096                 return;
6097             case "td":
6098                 bKeepBubbling = oSelf.fireEvent("cellDblclickEvent",{target:elTarget,event:e});
6099                 break;
6100             case "span":
6101                 if(Dom.hasClass(elTarget, DT.CLASS_LABEL)) {
6102                     bKeepBubbling = oSelf.fireEvent("theadLabelDblclickEvent",{target:elTarget,event:e});
6103                     // Backward compatibility
6104                     bKeepBubbling = oSelf.fireEvent("headerLabelDblclickEvent",{target:elTarget,event:e});
6105                 }
6106                 break;
6107             case "th":
6108                 bKeepBubbling = oSelf.fireEvent("theadCellDblclickEvent",{target:elTarget,event:e});
6109                 // Backward compatibility
6110                 bKeepBubbling = oSelf.fireEvent("headerCellDblclickEvent",{target:elTarget,event:e});
6111                 break;
6112             case "tr":
6113                 if(elTarget.parentNode.nodeName.toLowerCase() == "thead") {
6114                     bKeepBubbling = oSelf.fireEvent("theadRowDblclickEvent",{target:elTarget,event:e});
6115                     // Backward compatibility
6116                     bKeepBubbling = oSelf.fireEvent("headerRowDblclickEvent",{target:elTarget,event:e});
6117                 }
6118                 else {
6119                     bKeepBubbling = oSelf.fireEvent("rowDblclickEvent",{target:elTarget,event:e});
6120                 }
6121                 break;
6122             default:
6123                 break;
6124         }
6125         if(bKeepBubbling === false) {
6126             return;
6127         }
6128         else {
6129             elTarget = elTarget.parentNode;
6130             if(elTarget) {
6131                 elTag = elTarget.nodeName.toLowerCase();
6132             }
6133         }
6134     }
6135     oSelf.fireEvent("tableDblclickEvent",{target:(elTarget || oSelf._elContainer),event:e});
6136 },
6137 /**
6138  * Handles keydown events on the THEAD element.
6139  *
6140  * @method _onTheadKeydown
6141  * @param e {HTMLEvent} The key event.
6142  * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
6143  * @private
6144  */
6145 _onTheadKeydown : function(e, oSelf) {
6146     var elTarget = Ev.getTarget(e);
6147     var elTag = elTarget.nodeName.toLowerCase();
6148     var bKeepBubbling = true;
6149     while(elTarget && (elTag != "table")) {
6150         switch(elTag) {
6151             case "body":
6152                 return;
6153             case "input":
6154             case "textarea":
6155                 // TODO: implement textareaKeyEvent
6156                 break;
6157             case "thead":
6158                 bKeepBubbling = oSelf.fireEvent("theadKeyEvent",{target:elTarget,event:e});
6159                 break;
6160             default:
6161                 break;
6162         }
6163         if(bKeepBubbling === false) {
6164             return;
6165         }
6166         else {
6167             elTarget = elTarget.parentNode;
6168             if(elTarget) {
6169                 elTag = elTarget.nodeName.toLowerCase();
6170             }
6171         }
6172     }
6173     oSelf.fireEvent("tableKeyEvent",{target:(elTarget || oSelf._elContainer),event:e});
6174 },
6175
6176 /**
6177  * Handles keydown events on the TBODY element. Handles selection behavior,
6178  * provides hooks for ENTER to edit functionality.
6179  *
6180  * @method _onTbodyKeydown
6181  * @param e {HTMLEvent} The key event.
6182  * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
6183  * @private
6184  */
6185 _onTbodyKeydown : function(e, oSelf) {
6186     var sMode = oSelf.get("selectionMode");
6187
6188     if(sMode == "standard") {
6189         oSelf._handleStandardSelectionByKey(e);
6190     }
6191     else if(sMode == "single") {
6192         oSelf._handleSingleSelectionByKey(e);
6193     }
6194     else if(sMode == "cellblock") {
6195         oSelf._handleCellBlockSelectionByKey(e);
6196     }
6197     else if(sMode == "cellrange") {
6198         oSelf._handleCellRangeSelectionByKey(e);
6199     }
6200     else if(sMode == "singlecell") {
6201         oSelf._handleSingleCellSelectionByKey(e);
6202     }
6203     
6204     if(oSelf._oCellEditor) {
6205         if(oSelf._oCellEditor.fireEvent) {
6206             oSelf._oCellEditor.fireEvent("blurEvent", {editor: oSelf._oCellEditor});
6207         }
6208         else if(oSelf._oCellEditor.isActive) {
6209             oSelf.fireEvent("editorBlurEvent", {editor:oSelf._oCellEditor});
6210         }
6211     }
6212
6213     var elTarget = Ev.getTarget(e);
6214     var elTag = elTarget.nodeName.toLowerCase();
6215     var bKeepBubbling = true;
6216     while(elTarget && (elTag != "table")) {
6217         switch(elTag) {
6218             case "body":
6219                 return;
6220             case "tbody":
6221                 bKeepBubbling = oSelf.fireEvent("tbodyKeyEvent",{target:elTarget,event:e});
6222                 break;
6223             default:
6224                 break;
6225         }
6226         if(bKeepBubbling === false) {
6227             return;
6228         }
6229         else {
6230             elTarget = elTarget.parentNode;
6231             if(elTarget) {
6232                 elTag = elTarget.nodeName.toLowerCase();
6233             }
6234         }
6235     }
6236     oSelf.fireEvent("tableKeyEvent",{target:(elTarget || oSelf._elContainer),event:e});
6237 },
6238
6239 /**
6240  * Handles keypress events on the TABLE. Mainly to support stopEvent on Mac.
6241  *
6242  * @method _onTableKeypress
6243  * @param e {HTMLEvent} The key event.
6244  * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
6245  * @private
6246  */
6247 _onTableKeypress : function(e, oSelf) {
6248     if(ua.opera || (navigator.userAgent.toLowerCase().indexOf("mac") !== -1) && (ua.webkit < 420)) {
6249         var nKey = Ev.getCharCode(e);
6250         // arrow down
6251         if(nKey == 40) {
6252             Ev.stopEvent(e);
6253         }
6254         // arrow up
6255         else if(nKey == 38) {
6256             Ev.stopEvent(e);
6257         }
6258     }
6259 },
6260
6261 /**
6262  * Handles click events on the THEAD element.
6263  *
6264  * @method _onTheadClick
6265  * @param e {HTMLEvent} The click event.
6266  * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
6267  * @private
6268  */
6269 _onTheadClick : function(e, oSelf) {
6270     // This blurs the CellEditor
6271     if(oSelf._oCellEditor) {
6272         if(oSelf._oCellEditor.fireEvent) {
6273             oSelf._oCellEditor.fireEvent("blurEvent", {editor: oSelf._oCellEditor});
6274         }
6275         // Backward compatibility
6276         else if(oSelf._oCellEditor.isActive) {
6277             oSelf.fireEvent("editorBlurEvent", {editor:oSelf._oCellEditor});
6278         }
6279     }
6280
6281     var elTarget = Ev.getTarget(e),
6282         elTag = elTarget.nodeName.toLowerCase(),
6283         bKeepBubbling = true;
6284     while(elTarget && (elTag != "table")) {
6285         switch(elTag) {
6286             case "body":
6287                 return;
6288             case "input":
6289                 var sType = elTarget.type.toLowerCase();
6290                 if(sType == "checkbox") {
6291                     bKeepBubbling = oSelf.fireEvent("theadCheckboxClickEvent",{target:elTarget,event:e});
6292                 }
6293                 else if(sType == "radio") {
6294                     bKeepBubbling = oSelf.fireEvent("theadRadioClickEvent",{target:elTarget,event:e});
6295                 }
6296                 else if((sType == "button") || (sType == "image") || (sType == "submit") || (sType == "reset")) {
6297                     bKeepBubbling = oSelf.fireEvent("theadButtonClickEvent",{target:elTarget,event:e});
6298                 }
6299                 break;
6300             case "a":
6301                 bKeepBubbling = oSelf.fireEvent("theadLinkClickEvent",{target:elTarget,event:e});
6302                 break;
6303             case "button":
6304                 bKeepBubbling = oSelf.fireEvent("theadButtonClickEvent",{target:elTarget,event:e});
6305                 break;
6306             case "span":
6307                 if(Dom.hasClass(elTarget, DT.CLASS_LABEL)) {
6308                     bKeepBubbling = oSelf.fireEvent("theadLabelClickEvent",{target:elTarget,event:e});
6309                     // Backward compatibility
6310                     bKeepBubbling = oSelf.fireEvent("headerLabelClickEvent",{target:elTarget,event:e});
6311                 }
6312                 break;
6313             case "th":
6314                 bKeepBubbling = oSelf.fireEvent("theadCellClickEvent",{target:elTarget,event:e});
6315                 // Backward compatibility
6316                 bKeepBubbling = oSelf.fireEvent("headerCellClickEvent",{target:elTarget,event:e});
6317                 break;
6318             case "tr":
6319                 bKeepBubbling = oSelf.fireEvent("theadRowClickEvent",{target:elTarget,event:e});
6320                 // Backward compatibility
6321                 bKeepBubbling = oSelf.fireEvent("headerRowClickEvent",{target:elTarget,event:e});
6322                 break;
6323             default:
6324                 break;
6325         }
6326         if(bKeepBubbling === false) {
6327             return;
6328         }
6329         else {
6330             elTarget = elTarget.parentNode;
6331             if(elTarget) {
6332                 elTag = elTarget.nodeName.toLowerCase();
6333             }
6334         }
6335     }
6336     oSelf.fireEvent("tableClickEvent",{target:(elTarget || oSelf._elContainer),event:e});
6337 },
6338
6339 /**
6340  * Handles click events on the primary TBODY element.
6341  *
6342  * @method _onTbodyClick
6343  * @param e {HTMLEvent} The click event.
6344  * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
6345  * @private
6346  */
6347 _onTbodyClick : function(e, oSelf) {
6348     // This blurs the CellEditor
6349     if(oSelf._oCellEditor) {
6350         if(oSelf._oCellEditor.fireEvent) {
6351             oSelf._oCellEditor.fireEvent("blurEvent", {editor: oSelf._oCellEditor});
6352         }
6353         else if(oSelf._oCellEditor.isActive) {
6354             oSelf.fireEvent("editorBlurEvent", {editor:oSelf._oCellEditor});
6355         }
6356     }
6357
6358     // Fire Custom Events
6359     var elTarget = Ev.getTarget(e),
6360         elTag = elTarget.nodeName.toLowerCase(),
6361         bKeepBubbling = true;
6362     while(elTarget && (elTag != "table")) {
6363         switch(elTag) {
6364             case "body":
6365                 return;
6366             case "input":
6367                 var sType = elTarget.type.toLowerCase();
6368                 if(sType == "checkbox") {
6369                     bKeepBubbling = oSelf.fireEvent("checkboxClickEvent",{target:elTarget,event:e});
6370                 }
6371                 else if(sType == "radio") {
6372                     bKeepBubbling = oSelf.fireEvent("radioClickEvent",{target:elTarget,event:e});
6373                 }
6374                 else if((sType == "button") || (sType == "image") || (sType == "submit") || (sType == "reset")) {
6375                     bKeepBubbling = oSelf.fireEvent("buttonClickEvent",{target:elTarget,event:e});
6376                 }
6377                 break;
6378             case "a":
6379                 bKeepBubbling = oSelf.fireEvent("linkClickEvent",{target:elTarget,event:e});
6380                 break;
6381             case "button":
6382                 bKeepBubbling = oSelf.fireEvent("buttonClickEvent",{target:elTarget,event:e});
6383                 break;
6384             case "td":
6385                 bKeepBubbling = oSelf.fireEvent("cellClickEvent",{target:elTarget,event:e});
6386                 break;
6387             case "tr":
6388                 bKeepBubbling = oSelf.fireEvent("rowClickEvent",{target:elTarget,event:e});
6389                 break;
6390             default:
6391                 break;
6392         }
6393         if(bKeepBubbling === false) {
6394             return;
6395         }
6396         else {
6397             elTarget = elTarget.parentNode;
6398             if(elTarget) {
6399                 elTag = elTarget.nodeName.toLowerCase();
6400             }
6401         }
6402     }
6403     oSelf.fireEvent("tableClickEvent",{target:(elTarget || oSelf._elContainer),event:e});
6404 },
6405
6406 /**
6407  * Handles change events on SELECT elements within DataTable.
6408  *
6409  * @method _onDropdownChange
6410  * @param e {HTMLEvent} The change event.
6411  * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
6412  * @private
6413  */
6414 _onDropdownChange : function(e, oSelf) {
6415     var elTarget = Ev.getTarget(e);
6416     oSelf.fireEvent("dropdownChangeEvent", {event:e, target:elTarget});
6417 },
6418
6419
6420
6421
6422
6423
6424
6425
6426
6427
6428
6429
6430
6431
6432
6433
6434
6435
6436
6437
6438
6439
6440
6441
6442
6443
6444
6445
6446
6447
6448
6449
6450 /////////////////////////////////////////////////////////////////////////////
6451 //
6452 // Public member variables
6453 //
6454 /////////////////////////////////////////////////////////////////////////////
6455 /**
6456  * Returns object literal of initial configs.
6457  *
6458  * @property configs
6459  * @type Object
6460  * @default {} 
6461  */
6462 configs: null,
6463
6464
6465 /////////////////////////////////////////////////////////////////////////////
6466 //
6467 // Public methods
6468 //
6469 /////////////////////////////////////////////////////////////////////////////
6470
6471 /**
6472  * Returns unique id assigned to instance, which is a useful prefix for
6473  * generating unique DOM ID strings.
6474  *
6475  * @method getId
6476  * @return {String} Unique ID of the DataSource instance.
6477  */
6478 getId : function() {
6479     return this._sId;
6480 },
6481
6482 /**
6483  * DataSource instance name, for logging.
6484  *
6485  * @method toString
6486  * @return {String} Unique name of the DataSource instance.
6487  */
6488
6489 toString : function() {
6490     return "DataTable instance " + this._sId;
6491 },
6492
6493 /**
6494  * Returns the DataTable instance's DataSource instance.
6495  *
6496  * @method getDataSource
6497  * @return {YAHOO.util.DataSource} DataSource instance.
6498  */
6499 getDataSource : function() {
6500     return this._oDataSource;
6501 },
6502
6503 /**
6504  * Returns the DataTable instance's ColumnSet instance.
6505  *
6506  * @method getColumnSet
6507  * @return {YAHOO.widget.ColumnSet} ColumnSet instance.
6508  */
6509 getColumnSet : function() {
6510     return this._oColumnSet;
6511 },
6512
6513 /**
6514  * Returns the DataTable instance's RecordSet instance.
6515  *
6516  * @method getRecordSet
6517  * @return {YAHOO.widget.RecordSet} RecordSet instance.
6518  */
6519 getRecordSet : function() {
6520     return this._oRecordSet;
6521 },
6522
6523 /**
6524  * Returns on object literal representing the DataTable instance's current
6525  * state with the following properties:
6526  * <dl>
6527  * <dt>pagination</dt>
6528  * <dd>Instance of YAHOO.widget.Paginator</dd>
6529  *
6530  * <dt>sortedBy</dt>
6531  * <dd>
6532  *     <dl>
6533  *         <dt>sortedBy.key</dt>
6534  *         <dd>{String} Key of sorted Column</dd>
6535  *         <dt>sortedBy.dir</dt>
6536  *         <dd>{String} Initial sort direction, either YAHOO.widget.DataTable.CLASS_ASC or YAHOO.widget.DataTable.CLASS_DESC</dd>
6537  *     </dl>
6538  * </dd>
6539  *
6540  * <dt>selectedRows</dt>
6541  * <dd>Array of selected rows by Record ID.</dd>
6542  *
6543  * <dt>selectedCells</dt>
6544  * <dd>Selected cells as an array of object literals:
6545  *     {recordId:sRecordId, columnKey:sColumnKey}</dd>
6546  * </dl>
6547  *  
6548  * @method getState
6549  * @return {Object} DataTable instance state object literal values.
6550  */
6551 getState : function() {
6552     return {
6553         totalRecords: this.get('paginator') ? this.get('paginator').get("totalRecords") : this._oRecordSet.getLength(),
6554         pagination: this.get("paginator") ? this.get("paginator").getState() : null,
6555         sortedBy: this.get("sortedBy"),
6556         selectedRows: this.getSelectedRows(),
6557         selectedCells: this.getSelectedCells()
6558     };
6559 },
6560
6561
6562
6563
6564
6565
6566
6567
6568
6569
6570
6571
6572
6573
6574
6575
6576
6577
6578
6579
6580
6581
6582
6583
6584
6585
6586
6587
6588
6589
6590
6591
6592
6593
6594
6595
6596
6597
6598
6599
6600
6601
6602
6603 // DOM ACCESSORS
6604
6605 /**
6606  * Returns DOM reference to the DataTable's container element.
6607  *
6608  * @method getContainerEl
6609  * @return {HTMLElement} Reference to DIV element.
6610  */
6611 getContainerEl : function() {
6612     return this._elContainer;
6613 },
6614
6615 /**
6616  * Returns DOM reference to the DataTable's TABLE element.
6617  *
6618  * @method getTableEl
6619  * @return {HTMLElement} Reference to TABLE element.
6620  */
6621 getTableEl : function() {
6622     return this._elTable;
6623 },
6624
6625 /**
6626  * Returns DOM reference to the DataTable's THEAD element.
6627  *
6628  * @method getTheadEl
6629  * @return {HTMLElement} Reference to THEAD element.
6630  */
6631 getTheadEl : function() {
6632     return this._elThead;
6633 },
6634
6635 /**
6636  * Returns DOM reference to the DataTable's primary TBODY element.
6637  *
6638  * @method getTbodyEl
6639  * @return {HTMLElement} Reference to TBODY element.
6640  */
6641 getTbodyEl : function() {
6642     return this._elTbody;
6643 },
6644
6645 /**
6646  * Returns DOM reference to the DataTable's secondary TBODY element that is
6647  * used to display messages.
6648  *
6649  * @method getMsgTbodyEl
6650  * @return {HTMLElement} Reference to TBODY element.
6651  */
6652 getMsgTbodyEl : function() {
6653     return this._elMsgTbody;
6654 },
6655
6656 /**
6657  * Returns DOM reference to the TD element within the secondary TBODY that is
6658  * used to display messages.
6659  *
6660  * @method getMsgTdEl
6661  * @return {HTMLElement} Reference to TD element.
6662  */
6663 getMsgTdEl : function() {
6664     return this._elMsgTd;
6665 },
6666
6667 /**
6668  * Returns the corresponding TR reference for a given DOM element, ID string or
6669  * directly page row index. If the given identifier is a child of a TR element,
6670  * then DOM tree is traversed until a parent TR element is returned, otherwise
6671  * null.
6672  *
6673  * @method getTrEl
6674  * @param row {HTMLElement | String | Number | YAHOO.widget.Record} Which row to
6675  * get: by element reference, ID string, page row index, or Record.
6676  * @return {HTMLElement} Reference to TR element, or null.
6677  */
6678 getTrEl : function(row) {
6679     // By Record
6680     if(row instanceof YAHOO.widget.Record) {
6681         return document.getElementById(row.getId());
6682     }
6683     // By page row index
6684     else if(lang.isNumber(row)) {
6685         var allRows = this._elTbody.rows;
6686         return ((row > -1) && (row < allRows.length)) ? allRows[row] : null;
6687     }
6688     // By ID string or element reference
6689     else {
6690         var elRow = (lang.isString(row)) ? document.getElementById(row) : row;
6691
6692         // Validate HTML element
6693         if(elRow && (elRow.ownerDocument == document)) {
6694             // Validate TR element
6695             if(elRow.nodeName.toLowerCase() != "tr") {
6696                 // Traverse up the DOM to find the corresponding TR element
6697                 elRow = Dom.getAncestorByTagName(elRow,"tr");
6698             }
6699
6700             return elRow;
6701         }
6702     }
6703
6704     return null;
6705 },
6706
6707 /**
6708  * Returns DOM reference to the first TR element in the DataTable page, or null.
6709  *
6710  * @method getFirstTrEl
6711  * @return {HTMLElement} Reference to TR element.
6712  */
6713 getFirstTrEl : function() {
6714     return this._elTbody.rows[0] || null;
6715 },
6716
6717 /**
6718  * Returns DOM reference to the last TR element in the DataTable page, or null.
6719  *
6720  * @method getLastTrEl
6721  * @return {HTMLElement} Reference to last TR element.
6722  */
6723 getLastTrEl : function() {
6724     var allRows = this._elTbody.rows;
6725         if(allRows.length > 0) {
6726             return allRows[allRows.length-1] || null;
6727         }
6728 },
6729
6730 /**
6731  * Returns DOM reference to the next TR element from the given TR element, or null.
6732  *
6733  * @method getNextTrEl
6734  * @param row {HTMLElement | String | Number | YAHOO.widget.Record} Element
6735  * reference, ID string, page row index, or Record from which to get next TR element.
6736  * @return {HTMLElement} Reference to next TR element.
6737  */
6738 getNextTrEl : function(row) {
6739     var nThisTrIndex = this.getTrIndex(row);
6740     if(nThisTrIndex !== null) {
6741         var allRows = this._elTbody.rows;
6742         if(nThisTrIndex < allRows.length-1) {
6743             return allRows[nThisTrIndex+1];
6744         }
6745     }
6746
6747     return null;
6748 },
6749
6750 /**
6751  * Returns DOM reference to the previous TR element from the given TR element, or null.
6752  *
6753  * @method getPreviousTrEl
6754  * @param row {HTMLElement | String | Number | YAHOO.widget.Record} Element
6755  * reference, ID string, page row index, or Record from which to get previous TR element.
6756  * @return {HTMLElement} Reference to previous TR element.
6757  */
6758 getPreviousTrEl : function(row) {
6759     var nThisTrIndex = this.getTrIndex(row);
6760     if(nThisTrIndex !== null) {
6761         var allRows = this._elTbody.rows;
6762         if(nThisTrIndex > 0) {
6763             return allRows[nThisTrIndex-1];
6764         }
6765     }
6766
6767     return null;
6768 },
6769
6770 /**
6771  * Returns DOM reference to a TD liner element.
6772  *
6773  * @method getTdLinerEl
6774  * @param cell {HTMLElement | Object} TD element or child of a TD element, or
6775  * object literal of syntax {record:oRecord, column:oColumn}.
6776  * @return {HTMLElement} Reference to TD liner element.
6777  */
6778 getTdLinerEl : function(cell) {
6779     var elCell = this.getTdEl(cell);
6780     return elCell.firstChild || null;
6781 },
6782
6783 /**
6784  * Returns DOM reference to a TD element.
6785  *
6786  * @method getTdEl
6787  * @param cell {HTMLElement | String | Object} TD element or child of a TD element, or
6788  * object literal of syntax {record:oRecord, column:oColumn}.
6789  * @return {HTMLElement} Reference to TD element.
6790  */
6791 getTdEl : function(cell) {
6792     var elCell;
6793     var el = Dom.get(cell);
6794
6795     // Validate HTML element
6796     if(el && (el.ownerDocument == document)) {
6797         // Validate TD element
6798         if(el.nodeName.toLowerCase() != "td") {
6799             // Traverse up the DOM to find the corresponding TR element
6800             elCell = Dom.getAncestorByTagName(el, "td");
6801         }
6802         else {
6803             elCell = el;
6804         }
6805         
6806         // Make sure the TD is in this TBODY
6807         // Bug 2527707 and bug 2263558
6808         if(elCell && ((elCell.parentNode.parentNode == this._elTbody) || (elCell.parentNode.parentNode === null))) {
6809             // Now we can return the TD element
6810             return elCell;
6811         }
6812     }
6813     else if(cell) {
6814         var oRecord, nColKeyIndex;
6815
6816         if(lang.isString(cell.columnKey) && lang.isString(cell.recordId)) {
6817             oRecord = this.getRecord(cell.recordId);
6818             var oColumn = this.getColumn(cell.columnKey);
6819             if(oColumn) {
6820                 nColKeyIndex = oColumn.getKeyIndex();
6821             }
6822
6823         }
6824         if(cell.record && cell.column && cell.column.getKeyIndex) {
6825             oRecord = cell.record;
6826             nColKeyIndex = cell.column.getKeyIndex();
6827         }
6828         var elRow = this.getTrEl(oRecord);
6829         if((nColKeyIndex !== null) && elRow && elRow.cells && elRow.cells.length > 0) {
6830             return elRow.cells[nColKeyIndex] || null;
6831         }
6832     }
6833
6834     return null;
6835 },
6836
6837 /**
6838  * Returns DOM reference to the first TD element in the DataTable page (by default),
6839  * the first TD element of the optionally given row, or null.
6840  *
6841  * @method getFirstTdEl
6842  * @param row {HTMLElement} (optional) row from which to get first TD
6843  * @return {HTMLElement} Reference to TD element.
6844  */
6845 getFirstTdEl : function(row) {
6846     var elRow = this.getTrEl(row) || this.getFirstTrEl();
6847     if(elRow && (elRow.cells.length > 0)) {
6848         return elRow.cells[0];
6849     }
6850     return null;
6851 },
6852
6853 /**
6854  * Returns DOM reference to the last TD element in the DataTable page (by default),
6855  * the first TD element of the optionally given row, or null.
6856  *
6857  * @method getLastTdEl
6858  * @return {HTMLElement} Reference to last TD element.
6859  */
6860 getLastTdEl : function(row) {
6861     var elRow = this.getTrEl(row) || this.getLastTrEl();
6862     if(elRow && (elRow.cells.length > 0)) {
6863         return elRow.cells[elRow.cells.length-1];
6864     }
6865     return null;
6866 },
6867
6868 /**
6869  * Returns DOM reference to the next TD element from the given cell, or null.
6870  *
6871  * @method getNextTdEl
6872  * @param cell {HTMLElement | String | Object} DOM element reference or string ID, or
6873  * object literal of syntax {record:oRecord, column:oColumn} from which to get next TD element.
6874  * @return {HTMLElement} Reference to next TD element, or null.
6875  */
6876 getNextTdEl : function(cell) {
6877     var elCell = this.getTdEl(cell);
6878     if(elCell) {
6879         var nThisTdIndex = elCell.cellIndex;
6880         var elRow = this.getTrEl(elCell);
6881         if(nThisTdIndex < elRow.cells.length-1) {
6882             return elRow.cells[nThisTdIndex+1];
6883         }
6884         else {
6885             var elNextRow = this.getNextTrEl(elRow);
6886             if(elNextRow) {
6887                 return elNextRow.cells[0];
6888             }
6889         }
6890     }
6891     return null;
6892 },
6893
6894 /**
6895  * Returns DOM reference to the previous TD element from the given cell, or null.
6896  *
6897  * @method getPreviousTdEl
6898  * @param cell {HTMLElement | String | Object} DOM element reference or string ID, or
6899  * object literal of syntax {record:oRecord, column:oColumn} from which to get previous TD element.
6900  * @return {HTMLElement} Reference to previous TD element, or null.
6901  */
6902 getPreviousTdEl : function(cell) {
6903     var elCell = this.getTdEl(cell);
6904     if(elCell) {
6905         var nThisTdIndex = elCell.cellIndex;
6906         var elRow = this.getTrEl(elCell);
6907         if(nThisTdIndex > 0) {
6908             return elRow.cells[nThisTdIndex-1];
6909         }
6910         else {
6911             var elPreviousRow = this.getPreviousTrEl(elRow);
6912             if(elPreviousRow) {
6913                 return this.getLastTdEl(elPreviousRow);
6914             }
6915         }
6916     }
6917     return null;
6918 },
6919
6920 /**
6921  * Returns DOM reference to the above TD element from the given cell, or null.
6922  *
6923  * @method getAboveTdEl
6924  * @param cell {HTMLElement | String | Object} DOM element reference or string ID, or
6925  * object literal of syntax {record:oRecord, column:oColumn} from which to get next TD element.
6926  * @return {HTMLElement} Reference to next TD element, or null.
6927  */
6928 getAboveTdEl : function(cell) {
6929     var elCell = this.getTdEl(cell);
6930     if(elCell) {
6931         var elPreviousRow = this.getPreviousTrEl(elCell);
6932         if(elPreviousRow) {
6933             return elPreviousRow.cells[elCell.cellIndex];
6934         }
6935     }
6936     return null;
6937 },
6938
6939 /**
6940  * Returns DOM reference to the below TD element from the given cell, or null.
6941  *
6942  * @method getBelowTdEl
6943  * @param cell {HTMLElement | String | Object} DOM element reference or string ID, or
6944  * object literal of syntax {record:oRecord, column:oColumn} from which to get previous TD element.
6945  * @return {HTMLElement} Reference to previous TD element, or null.
6946  */
6947 getBelowTdEl : function(cell) {
6948     var elCell = this.getTdEl(cell);
6949     if(elCell) {
6950         var elNextRow = this.getNextTrEl(elCell);
6951         if(elNextRow) {
6952             return elNextRow.cells[elCell.cellIndex];
6953         }
6954     }
6955     return null;
6956 },
6957
6958 /**
6959  * Returns DOM reference to a TH liner element. Needed to normalize for resizeable 
6960  * Columns, which have an additional resizer liner DIV element between the TH
6961  * element and the liner DIV element. 
6962  *
6963  * @method getThLinerEl
6964  * @param theadCell {YAHOO.widget.Column | HTMLElement | String} Column instance,
6965  * DOM element reference, or string ID.
6966  * @return {HTMLElement} Reference to TH liner element.
6967  */
6968 getThLinerEl : function(theadCell) {
6969     var oColumn = this.getColumn(theadCell);
6970     return (oColumn) ? oColumn.getThLinerEl() : null;
6971 },
6972
6973 /**
6974  * Returns DOM reference to a TH element.
6975  *
6976  * @method getThEl
6977  * @param theadCell {YAHOO.widget.Column | HTMLElement | String} Column instance,
6978  * DOM element reference, or string ID.
6979  * @return {HTMLElement} Reference to TH element.
6980  */
6981 getThEl : function(theadCell) {
6982     var elTh;
6983
6984     // Validate Column instance
6985     if(theadCell instanceof YAHOO.widget.Column) {
6986         var oColumn = theadCell;
6987         elTh = oColumn.getThEl();
6988         if(elTh) {
6989             return elTh;
6990         }
6991     }
6992     // Validate HTML element
6993     else {
6994         var el = Dom.get(theadCell);
6995
6996         if(el && (el.ownerDocument == document)) {
6997             // Validate TH element
6998             if(el.nodeName.toLowerCase() != "th") {
6999                 // Traverse up the DOM to find the corresponding TR element
7000                 elTh = Dom.getAncestorByTagName(el,"th");
7001             }
7002             else {
7003                 elTh = el;
7004             }
7005
7006             return elTh;
7007         }
7008     }
7009
7010     return null;
7011 },
7012
7013 /**
7014  * Returns the page row index of given row. Returns null if the row is not on the
7015  * current DataTable page.
7016  *
7017  * @method getTrIndex
7018  * @param row {HTMLElement | String | YAHOO.widget.Record | Number} DOM or ID
7019  * string reference to an element within the DataTable page, a Record instance,
7020  * or a Record's RecordSet index.
7021  * @return {Number} Page row index, or null if row does not exist or is not on current page.
7022  */
7023 getTrIndex : function(row) {
7024     var nRecordIndex;
7025
7026     // By Record
7027     if(row instanceof YAHOO.widget.Record) {
7028         nRecordIndex = this._oRecordSet.getRecordIndex(row);
7029         if(nRecordIndex === null) {
7030             // Not a valid Record
7031             return null;
7032         }
7033     }
7034     // Calculate page row index from Record index
7035     else if(lang.isNumber(row)) {
7036         nRecordIndex = row;
7037     }
7038     if(lang.isNumber(nRecordIndex)) {
7039         // Validate the number
7040         if((nRecordIndex > -1) && (nRecordIndex < this._oRecordSet.getLength())) {
7041             // DataTable is paginated
7042             var oPaginator = this.get('paginator');
7043             if(oPaginator) {
7044                 // Check the record index is within the indices of the
7045                 // current page
7046                 var rng = oPaginator.getPageRecords();
7047                 if (rng && nRecordIndex >= rng[0] && nRecordIndex <= rng[1]) {
7048                     // This Record is on current page
7049                     return nRecordIndex - rng[0];
7050                 }
7051                 // This Record is not on current page
7052                 else {
7053                     return null;
7054                 }
7055             }
7056             // Not paginated, just return the Record index
7057             else {
7058                 return nRecordIndex;
7059             }
7060         }
7061         // RecordSet index is out of range
7062         else {
7063             return null;
7064         }
7065     }
7066     // By element reference or ID string
7067     else {
7068         // Validate TR element
7069         var elRow = this.getTrEl(row);
7070         if(elRow && (elRow.ownerDocument == document) &&
7071                 (elRow.parentNode == this._elTbody)) {
7072             return elRow.sectionRowIndex;
7073         }
7074     }
7075
7076     return null;
7077 },
7078
7079
7080
7081
7082
7083
7084
7085
7086
7087
7088
7089
7090
7091
7092
7093
7094
7095
7096
7097
7098
7099
7100
7101
7102
7103
7104
7105
7106
7107
7108
7109
7110
7111
7112
7113
7114
7115
7116
7117
7118
7119
7120
7121
7122
7123
7124 // TABLE FUNCTIONS
7125
7126 /**
7127  * Resets a RecordSet with the given data and populates the page view
7128  * with the new data. Any previous data, and selection and sort states are
7129  * cleared. New data should be added as a separate step. 
7130  *
7131  * @method initializeTable
7132  */
7133 initializeTable : function() {
7134     // Reset init flag
7135     this._bInit = true;
7136     
7137     // Clear the RecordSet
7138     this._oRecordSet.reset();
7139
7140     // Clear the Paginator's totalRecords if paginating
7141     var pag = this.get('paginator');
7142     if (pag) {
7143         pag.set('totalRecords',0);
7144     }
7145
7146     // Clear selections
7147     this._unselectAllTrEls();
7148     this._unselectAllTdEls();
7149     this._aSelections = null;
7150     this._oAnchorRecord = null;
7151     this._oAnchorCell = null;
7152     
7153     // Clear sort
7154     this.set("sortedBy", null);
7155 },
7156
7157 /**
7158  * Internal wrapper calls run() on render Chain instance.
7159  *
7160  * @method _runRenderChain
7161  * @private 
7162  */
7163 _runRenderChain : function() {
7164     this._oChainRender.run();
7165 },
7166
7167 /**
7168  * Renders the view with existing Records from the RecordSet while
7169  * maintaining sort, pagination, and selection states. For performance, reuses
7170  * existing DOM elements when possible while deleting extraneous elements.
7171  *
7172  * @method render
7173  */
7174 render : function() {
7175 //YAHOO.example.Performance.trialStart = new Date();
7176
7177     this._oChainRender.stop();
7178
7179     this.fireEvent("beforeRenderEvent");
7180
7181     var i, j, k, len, allRecords;
7182
7183     var oPaginator = this.get('paginator');
7184     // Paginator is enabled, show a subset of Records and update Paginator UI
7185     if(oPaginator) {
7186         allRecords = this._oRecordSet.getRecords(
7187                         oPaginator.getStartIndex(),
7188                         oPaginator.getRowsPerPage());
7189     }
7190     // Not paginated, show all records
7191     else {
7192         allRecords = this._oRecordSet.getRecords();
7193     }
7194
7195     // From the top, update in-place existing rows, so as to reuse DOM elements
7196     var elTbody = this._elTbody,
7197         loopN = this.get("renderLoopSize"),
7198         nRecordsLength = allRecords.length;
7199     
7200     // Table has rows
7201     if(nRecordsLength > 0) {                
7202         elTbody.style.display = "none";
7203         while(elTbody.lastChild) {
7204             elTbody.removeChild(elTbody.lastChild);
7205         }
7206         elTbody.style.display = "";
7207
7208         // Set up the loop Chain to render rows
7209         this._oChainRender.add({
7210             method: function(oArg) {
7211                 if((this instanceof DT) && this._sId) {
7212                     var i = oArg.nCurrentRecord,
7213                         endRecordIndex = ((oArg.nCurrentRecord+oArg.nLoopLength) > nRecordsLength) ?
7214                                 nRecordsLength : (oArg.nCurrentRecord+oArg.nLoopLength),
7215                         elRow, nextSibling;
7216
7217                     elTbody.style.display = "none";
7218                     
7219                     for(; i<endRecordIndex; i++) {
7220                         elRow = Dom.get(allRecords[i].getId());
7221                         elRow = elRow || this._addTrEl(allRecords[i]);
7222                         nextSibling = elTbody.childNodes[i] || null;
7223                         elTbody.insertBefore(elRow, nextSibling);
7224                     }
7225                     elTbody.style.display = "";
7226                     
7227                     // Set up for the next loop
7228                     oArg.nCurrentRecord = i;
7229                 }
7230             },
7231             scope: this,
7232             iterations: (loopN > 0) ? Math.ceil(nRecordsLength/loopN) : 1,
7233             argument: {
7234                 nCurrentRecord: 0,//nRecordsLength-1,  // Start at first Record
7235                 nLoopLength: (loopN > 0) ? loopN : nRecordsLength
7236             },
7237             timeout: (loopN > 0) ? 0 : -1
7238         });
7239         
7240         // Post-render tasks
7241         this._oChainRender.add({
7242             method: function(oArg) {
7243                 if((this instanceof DT) && this._sId) {
7244                     while(elTbody.rows.length > nRecordsLength) {
7245                         elTbody.removeChild(elTbody.lastChild);
7246                     }
7247                     this._setFirstRow();
7248                     this._setLastRow();
7249                     this._setRowStripes();
7250                     this._setSelections();
7251                 }
7252             },
7253             scope: this,
7254             timeout: (loopN > 0) ? 0 : -1
7255         });
7256      
7257     }
7258     // Table has no rows
7259     else {
7260         // Set up the loop Chain to delete rows
7261         var nTotal = elTbody.rows.length;
7262         if(nTotal > 0) {
7263             this._oChainRender.add({
7264                 method: function(oArg) {
7265                     if((this instanceof DT) && this._sId) {
7266                         var i = oArg.nCurrent,
7267                             loopN = oArg.nLoopLength,
7268                             nIterEnd = (i - loopN < 0) ? -1 : i - loopN;
7269     
7270                         elTbody.style.display = "none";
7271                         
7272                         for(; i>nIterEnd; i--) {
7273                             elTbody.deleteRow(-1);
7274                         }
7275                         elTbody.style.display = "";
7276                         
7277                         // Set up for the next loop
7278                         oArg.nCurrent = i;
7279                     }
7280                 },
7281                 scope: this,
7282                 iterations: (loopN > 0) ? Math.ceil(nTotal/loopN) : 1,
7283                 argument: {
7284                     nCurrent: nTotal, 
7285                     nLoopLength: (loopN > 0) ? loopN : nTotal
7286                 },
7287                 timeout: (loopN > 0) ? 0 : -1
7288             });
7289         }
7290     }
7291     this._runRenderChain();
7292 },
7293
7294 /**
7295  * Disables DataTable UI.
7296  *
7297  * @method disable
7298  */
7299 disable : function() {
7300     var elTable = this._elTable;
7301     var elMask = this._elMask;
7302     elMask.style.width = elTable.offsetWidth + "px";
7303     elMask.style.height = elTable.offsetHeight + "px";
7304     elMask.style.display = "";
7305     this.fireEvent("disableEvent");
7306 },
7307
7308 /**
7309  * Undisables DataTable UI.
7310  *
7311  * @method undisable
7312  */
7313 undisable : function() {
7314     this._elMask.style.display = "none";
7315     this.fireEvent("undisableEvent");
7316 },
7317
7318 /**
7319  * Nulls out the entire DataTable instance and related objects, removes attached
7320  * event listeners, and clears out DOM elements inside the container. After
7321  * calling this method, the instance reference should be expliclitly nulled by
7322  * implementer, as in myDataTable = null. Use with caution!
7323  *
7324  * @method destroy
7325  */
7326 destroy : function() {
7327     // Store for later
7328     var instanceName = this.toString();
7329
7330     this._oChainRender.stop();
7331     
7332     // Destroy static resizer proxy and column proxy
7333     DT._destroyColumnDragTargetEl();
7334     DT._destroyColumnResizerProxyEl();
7335     
7336     // Destroy ColumnDD and ColumnResizers
7337     this._destroyColumnHelpers();
7338     
7339     // Destroy all CellEditors
7340     var oCellEditor;
7341     for(var i=0, len=this._oColumnSet.flat.length; i<len; i++) {
7342         oCellEditor = this._oColumnSet.flat[i].editor;
7343         if(oCellEditor && oCellEditor.destroy) {
7344             oCellEditor.destroy();
7345             this._oColumnSet.flat[i].editor = null;
7346         }
7347     }
7348
7349     // Destroy Paginator
7350     this._destroyPaginator();
7351
7352     // Unhook custom events
7353     this._oRecordSet.unsubscribeAll();
7354     this.unsubscribeAll();
7355
7356     // Unhook DOM events
7357     Ev.removeListener(document, "click", this._onDocumentClick);
7358     
7359     // Clear out the container
7360     this._destroyContainerEl(this._elContainer);
7361
7362     // Null out objects
7363     for(var param in this) {
7364         if(lang.hasOwnProperty(this, param)) {
7365             this[param] = null;
7366         }
7367     }
7368     
7369     // Clean up static values
7370     DT._nCurrentCount--;
7371     
7372     if(DT._nCurrentCount < 1) {
7373         if(DT._elDynStyleNode) {
7374             document.getElementsByTagName('head')[0].removeChild(DT._elDynStyleNode);
7375             DT._elDynStyleNode = null;
7376         }
7377     }
7378
7379 },
7380
7381 /**
7382  * Displays message within secondary TBODY.
7383  *
7384  * @method showTableMessage
7385  * @param sHTML {String} (optional) Value for innerHTMlang.
7386  * @param sClassName {String} (optional) Classname.
7387  */
7388 showTableMessage : function(sHTML, sClassName) {
7389     var elCell = this._elMsgTd;
7390     if(lang.isString(sHTML)) {
7391         elCell.firstChild.innerHTML = sHTML;
7392     }
7393     if(lang.isString(sClassName)) {
7394         elCell.className = sClassName;
7395     }
7396
7397     this._elMsgTbody.style.display = "";
7398
7399     this.fireEvent("tableMsgShowEvent", {html:sHTML, className:sClassName});
7400 },
7401
7402 /**
7403  * Hides secondary TBODY.
7404  *
7405  * @method hideTableMessage
7406  */
7407 hideTableMessage : function() {
7408     if(this._elMsgTbody.style.display != "none") {
7409         this._elMsgTbody.style.display = "none";
7410         this._elMsgTbody.parentNode.style.width = "";
7411         this.fireEvent("tableMsgHideEvent");
7412     }
7413 },
7414
7415 /**
7416  * Brings focus to the TBODY element. Alias to focusTbodyEl.
7417  *
7418  * @method focus
7419  */
7420 focus : function() {
7421     this.focusTbodyEl();
7422 },
7423
7424 /**
7425  * Brings focus to the THEAD element.
7426  *
7427  * @method focusTheadEl
7428  */
7429 focusTheadEl : function() {
7430     this._focusEl(this._elThead);
7431 },
7432
7433 /**
7434  * Brings focus to the TBODY element.
7435  *
7436  * @method focusTbodyEl
7437  */
7438 focusTbodyEl : function() {
7439     this._focusEl(this._elTbody);
7440 },
7441
7442 /**
7443  * Setting display:none on DataTable or any parent may impact width validations.
7444  * After setting display back to "", implementers should call this method to 
7445  * manually perform those validations.
7446  *
7447  * @method onShow
7448  */
7449 onShow : function() {
7450     this.validateColumnWidths();
7451     
7452     for(var allKeys = this._oColumnSet.keys, i=0, len=allKeys.length, col; i<len; i++) {
7453         col = allKeys[i];
7454         if(col._ddResizer) {
7455             col._ddResizer.resetResizerEl();
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
7492
7493
7494
7495
7496
7497
7498
7499
7500
7501
7502
7503
7504
7505
7506
7507
7508
7509
7510
7511
7512
7513
7514
7515
7516
7517
7518
7519
7520
7521
7522
7523
7524
7525
7526 // RECORDSET FUNCTIONS
7527
7528 /**
7529  * Returns Record index for given TR element or page row index.
7530  *
7531  * @method getRecordIndex
7532  * @param row {YAHOO.widget.Record | HTMLElement | Number} Record instance, TR
7533  * element reference or page row index.
7534  * @return {Number} Record's RecordSet index, or null.
7535  */
7536 getRecordIndex : function(row) {
7537     var nTrIndex;
7538
7539     if(!lang.isNumber(row)) {
7540         // By Record
7541         if(row instanceof YAHOO.widget.Record) {
7542             return this._oRecordSet.getRecordIndex(row);
7543         }
7544         // By element reference
7545         else {
7546             // Find the TR element
7547             var el = this.getTrEl(row);
7548             if(el) {
7549                 nTrIndex = el.sectionRowIndex;
7550             }
7551         }
7552     }
7553     // By page row index
7554     else {
7555         nTrIndex = row;
7556     }
7557
7558     if(lang.isNumber(nTrIndex)) {
7559         var oPaginator = this.get("paginator");
7560         if(oPaginator) {
7561             return oPaginator.get('recordOffset') + nTrIndex;
7562         }
7563         else {
7564             return nTrIndex;
7565         }
7566     }
7567
7568     return null;
7569 },
7570
7571 /**
7572  * For the given identifier, returns the associated Record instance.
7573  *
7574  * @method getRecord
7575  * @param row {HTMLElement | Number | String} DOM reference to a TR element (or
7576  * child of a TR element), RecordSet position index, or Record ID.
7577  * @return {YAHOO.widget.Record} Record instance.
7578  */
7579 getRecord : function(row) {
7580     var oRecord = this._oRecordSet.getRecord(row);
7581
7582     if(!oRecord) {
7583         // Validate TR element
7584         var elRow = this.getTrEl(row);
7585         if(elRow) {
7586             oRecord = this._oRecordSet.getRecord(elRow.id);
7587         }
7588     }
7589
7590     if(oRecord instanceof YAHOO.widget.Record) {
7591         return this._oRecordSet.getRecord(oRecord);
7592     }
7593     else {
7594         return null;
7595     }
7596 },
7597
7598
7599
7600
7601
7602
7603
7604
7605
7606
7607
7608
7609
7610
7611
7612
7613
7614
7615
7616
7617
7618
7619
7620
7621
7622
7623
7624
7625
7626
7627
7628
7629
7630
7631
7632
7633
7634
7635
7636
7637
7638
7639
7640
7641
7642
7643 // COLUMN FUNCTIONS
7644
7645 /**
7646  * For the given identifier, returns the associated Column instance. Note: For
7647  * getting Columns by Column ID string, please use the method getColumnById().
7648  *
7649  * @method getColumn
7650  * @param column {HTMLElement | String | Number} TH/TD element (or child of a
7651  * TH/TD element), a Column key, or a ColumnSet key index.
7652  * @return {YAHOO.widget.Column} Column instance.
7653  */
7654 getColumn : function(column) {
7655     var oColumn = this._oColumnSet.getColumn(column);
7656
7657     if(!oColumn) {
7658         // Validate TD element
7659         var elCell = this.getTdEl(column);
7660         if(elCell) {
7661             oColumn = this._oColumnSet.getColumn(elCell.cellIndex);
7662         }
7663         // Validate TH element
7664         else {
7665             elCell = this.getThEl(column);
7666             if(elCell) {
7667                 // Find by TH el ID
7668                 var allColumns = this._oColumnSet.flat;
7669                 for(var i=0, len=allColumns.length; i<len; i++) {
7670                     if(allColumns[i].getThEl().id === elCell.id) {
7671                         oColumn = allColumns[i];
7672                     } 
7673                 }
7674             }
7675         }
7676     }
7677     if(!oColumn) {
7678     }
7679     return oColumn;
7680 },
7681
7682 /**
7683  * For the given Column ID, returns the associated Column instance. Note: For
7684  * getting Columns by key, please use the method getColumn().
7685  *
7686  * @method getColumnById
7687  * @param column {String} Column ID string.
7688  * @return {YAHOO.widget.Column} Column instance.
7689  */
7690 getColumnById : function(column) {
7691     return this._oColumnSet.getColumnById(column);
7692 },
7693
7694 /**
7695  * For the given Column instance, returns next direction to sort.
7696  *
7697  * @method getColumnSortDir
7698  * @param oColumn {YAHOO.widget.Column} Column instance.
7699  * @param oSortedBy {Object} (optional) Specify the state, or use current state. 
7700  * @return {String} YAHOO.widget.DataTable.CLASS_ASC or YAHOO.widget.DataTableCLASS_DESC.
7701  */
7702 getColumnSortDir : function(oColumn, oSortedBy) {
7703     // Backward compatibility
7704     if(oColumn.sortOptions && oColumn.sortOptions.defaultOrder) {
7705         if(oColumn.sortOptions.defaultOrder == "asc") {
7706             oColumn.sortOptions.defaultDir = DT.CLASS_ASC;
7707         }
7708         else if (oColumn.sortOptions.defaultOrder == "desc") {
7709             oColumn.sortOptions.defaultDir = DT.CLASS_DESC;
7710         }
7711     }
7712     
7713     // What is the Column's default sort direction?
7714     var sortDir = (oColumn.sortOptions && oColumn.sortOptions.defaultDir) ? oColumn.sortOptions.defaultDir : DT.CLASS_ASC;
7715
7716     // Is the Column currently sorted?
7717     var bSorted = false;
7718     oSortedBy = oSortedBy || this.get("sortedBy");
7719     if(oSortedBy && (oSortedBy.key === oColumn.key)) {
7720         bSorted = true;
7721         if(oSortedBy.dir) {
7722             sortDir = (oSortedBy.dir === DT.CLASS_ASC) ? DT.CLASS_DESC : DT.CLASS_ASC;
7723         }
7724         else {
7725             sortDir = (sortDir === DT.CLASS_ASC) ? DT.CLASS_DESC : DT.CLASS_ASC;
7726         }
7727     }
7728     return sortDir;
7729 },
7730
7731 /**
7732  * Overridable method gives implementers a hook to show loading message before
7733  * sorting Column.
7734  *
7735  * @method doBeforeSortColumn
7736  * @param oColumn {YAHOO.widget.Column} Column instance.
7737  * @param sSortDir {String} YAHOO.widget.DataTable.CLASS_ASC or
7738  * YAHOO.widget.DataTable.CLASS_DESC.
7739  * @return {Boolean} Return true to continue sorting Column.
7740  */
7741 doBeforeSortColumn : function(oColumn, sSortDir) {
7742     this.showTableMessage(this.get("MSG_LOADING"), DT.CLASS_LOADING);
7743     return true;
7744 },
7745
7746 /**
7747  * Sorts given Column. If "dynamicData" is true, current selections are purged before
7748  * a request is sent to the DataSource for data for the new state (using the
7749  * request returned by "generateRequest()").
7750  *
7751  * @method sortColumn
7752  * @param oColumn {YAHOO.widget.Column} Column instance.
7753  * @param sDir {String} (Optional) YAHOO.widget.DataTable.CLASS_ASC or
7754  * YAHOO.widget.DataTable.CLASS_DESC
7755  */
7756 sortColumn : function(oColumn, sDir) {
7757     if(oColumn && (oColumn instanceof YAHOO.widget.Column)) {
7758         if(!oColumn.sortable) {
7759             Dom.addClass(this.getThEl(oColumn), DT.CLASS_SORTABLE);
7760         }
7761         
7762         // Validate given direction
7763         if(sDir && (sDir !== DT.CLASS_ASC) && (sDir !== DT.CLASS_DESC)) {
7764             sDir = null;
7765         }
7766         
7767         // Get the sort dir
7768         var sSortDir = sDir || this.getColumnSortDir(oColumn);
7769
7770         // Is the Column currently sorted?
7771         var oSortedBy = this.get("sortedBy") || {};
7772         var bSorted = (oSortedBy.key === oColumn.key) ? true : false;
7773
7774         var ok = this.doBeforeSortColumn(oColumn, sSortDir);
7775         if(ok) {
7776             // Server-side sort
7777             if(this.get("dynamicData")) {
7778                 // Get current state
7779                 var oState = this.getState();
7780                 
7781                 // Reset record offset, if paginated
7782                 if(oState.pagination) {
7783                     oState.pagination.recordOffset = 0;
7784                 }
7785                 
7786                 // Update sortedBy to new values
7787                 oState.sortedBy = {
7788                     key: oColumn.key,
7789                     dir: sSortDir
7790                 };
7791                 
7792                 // Get the request for the new state
7793                 var request = this.get("generateRequest")(oState, this);
7794
7795                 // Purge selections
7796                 this.unselectAllRows();
7797                 this.unselectAllCells();
7798
7799                 // Send request for new data
7800                 var callback = {
7801                     success : this.onDataReturnSetRows,
7802                     failure : this.onDataReturnSetRows,
7803                     argument : oState, // Pass along the new state to the callback
7804                     scope : this
7805                 };
7806                 this._oDataSource.sendRequest(request, callback);            
7807             }
7808             // Client-side sort
7809             else {
7810                 // Is there a custom sort handler function defined?
7811                 var sortFnc = (oColumn.sortOptions && lang.isFunction(oColumn.sortOptions.sortFunction)) ?
7812                         // Custom sort function
7813                         oColumn.sortOptions.sortFunction : null;
7814                    
7815                 // Sort the Records
7816                 if(!bSorted || sDir || sortFnc) {
7817                     // Shortcut for the frequently-used compare method
7818                     var compare = YAHOO.util.Sort.compare;
7819
7820                     // Default sort function if necessary
7821                     sortFnc = sortFnc || 
7822                         function(a, b, desc, field) {
7823                             var sorted = compare(a.getData(field),b.getData(field), desc);
7824                             if(sorted === 0) {
7825                                 return compare(a.getCount(),b.getCount(), desc); // Bug 1932978
7826                             }
7827                             else {
7828                                 return sorted;
7829                             }
7830                         };
7831
7832                     // Get the field to sort
7833                     var sField = (oColumn.sortOptions && oColumn.sortOptions.field) ? oColumn.sortOptions.field : oColumn.field;
7834
7835                     // Sort the Records        
7836                     this._oRecordSet.sortRecords(sortFnc, ((sSortDir == DT.CLASS_DESC) ? true : false), sField);
7837                 }
7838                 // Just reverse the Records
7839                 else {
7840                     this._oRecordSet.reverseRecords();
7841                 }
7842         
7843                 // Reset to first page if paginated
7844                 var oPaginator = this.get('paginator');
7845                 if (oPaginator) {
7846                     // Set page silently, so as not to fire change event.
7847                     oPaginator.setPage(1,true);
7848                 }
7849         
7850                 // Update UI via sortedBy
7851                 this.render();
7852                 this.set("sortedBy", {key:oColumn.key, dir:sSortDir, column:oColumn}); 
7853             }       
7854             
7855             this.fireEvent("columnSortEvent",{column:oColumn,dir:sSortDir});
7856             return;
7857         }
7858     }
7859 },
7860
7861 /**
7862  * Sets given Column to given pixel width. If new width is less than minimum
7863  * width, sets to minimum width. Updates oColumn.width value.
7864  *
7865  * @method setColumnWidth
7866  * @param oColumn {YAHOO.widget.Column} Column instance.
7867  * @param nWidth {Number} New width in pixels. A null value auto-sizes Column,
7868  * subject to minWidth and maxAutoWidth validations. 
7869  */
7870 setColumnWidth : function(oColumn, nWidth) {
7871     if(!(oColumn instanceof YAHOO.widget.Column)) {
7872         oColumn = this.getColumn(oColumn);
7873     }
7874     if(oColumn) {
7875         // Validate new width against minimum width
7876         if(lang.isNumber(nWidth)) {
7877             // This is why we must require a Number... :-|
7878             nWidth = (nWidth > oColumn.minWidth) ? nWidth : oColumn.minWidth;
7879
7880             // Save state
7881             oColumn.width = nWidth;
7882             
7883             // Resize the DOM elements
7884             this._setColumnWidth(oColumn, nWidth+"px");
7885             
7886             this.fireEvent("columnSetWidthEvent",{column:oColumn,width:nWidth});
7887         }
7888         // Unsets a width to auto-size
7889         else if(nWidth === null) {
7890             // Save state
7891             oColumn.width = nWidth;
7892             
7893             // Resize the DOM elements
7894             this._setColumnWidth(oColumn, "auto");
7895             this.validateColumnWidths(oColumn);
7896             this.fireEvent("columnUnsetWidthEvent",{column:oColumn});
7897         }
7898                 
7899         // Bug 2339454: resize then sort misaligment
7900         this._clearTrTemplateEl();
7901     }
7902     else {
7903     }
7904 },
7905
7906 /**
7907  * Sets liner DIV elements of given Column to given width. When value should be
7908  * auto-calculated to fit content overflow is set to visible, otherwise overflow
7909  * is set to hidden. No validations against minimum width and no updating
7910  * Column.width value.
7911  *
7912  * @method _setColumnWidth
7913  * @param oColumn {YAHOO.widget.Column} Column instance.
7914  * @param sWidth {String} New width value.
7915  * @param sOverflow {String} Should be "hidden" when Column width is explicitly
7916  * being set to a value, but should be "visible" when Column is meant to auto-fit content.  
7917  * @private
7918  */
7919 _setColumnWidth : function(oColumn, sWidth, sOverflow) {
7920     if(oColumn && (oColumn.getKeyIndex() !== null)) {
7921         sOverflow = sOverflow || (((sWidth === '') || (sWidth === 'auto')) ? 'visible' : 'hidden');
7922     
7923         // Dynamic style algorithm
7924         if(!DT._bDynStylesFallback) {
7925             this._setColumnWidthDynStyles(oColumn, sWidth, sOverflow);
7926         }
7927         // Dynamic function algorithm
7928         else {
7929             this._setColumnWidthDynFunction(oColumn, sWidth, sOverflow);
7930         }
7931     }
7932     else {
7933     }
7934 },
7935
7936 /**
7937  * Updates width of a Column's liner DIV elements by dynamically creating a
7938  * STYLE node and writing and updating CSS style rules to it. If this fails during
7939  * runtime, the fallback method _setColumnWidthDynFunction() will be called.
7940  * Notes: This technique is not performant in IE6. IE7 crashes if DataTable is
7941  * nested within another TABLE element. For these cases, it is recommended to
7942  * use the method _setColumnWidthDynFunction by setting _bDynStylesFallback to TRUE.
7943  *
7944  * @method _setColumnWidthDynStyles
7945  * @param oColumn {YAHOO.widget.Column} Column instance.
7946  * @param sWidth {String} New width value.
7947  * @private
7948  */
7949 _setColumnWidthDynStyles : function(oColumn, sWidth, sOverflow) {
7950     var s = DT._elDynStyleNode,
7951         rule;
7952     
7953     // Create a new STYLE node
7954     if(!s) {
7955         s = document.createElement('style');
7956         s.type = 'text/css';
7957         s = document.getElementsByTagName('head').item(0).appendChild(s);
7958         DT._elDynStyleNode = s;
7959     }
7960     
7961     // We have a STYLE node to update
7962     if(s) {
7963         // Use unique classname for this Column instance as a hook for resizing
7964         var sClassname = "." + this.getId() + "-col-" + oColumn.getSanitizedKey() + " ." + DT.CLASS_LINER;
7965         
7966         // Hide for performance
7967         if(this._elTbody) {
7968             this._elTbody.style.display = 'none';
7969         }
7970         
7971         rule = DT._oDynStyles[sClassname];
7972
7973         // The Column does not yet have a rule
7974         if(!rule) {
7975             if(s.styleSheet && s.styleSheet.addRule) {
7976                 s.styleSheet.addRule(sClassname,"overflow:"+sOverflow);
7977                 s.styleSheet.addRule(sClassname,'width:'+sWidth);
7978                 rule = s.styleSheet.rules[s.styleSheet.rules.length-1];
7979                 DT._oDynStyles[sClassname] = rule;
7980             }
7981             else if(s.sheet && s.sheet.insertRule) {
7982                 s.sheet.insertRule(sClassname+" {overflow:"+sOverflow+";width:"+sWidth+";}",s.sheet.cssRules.length);
7983                 rule = s.sheet.cssRules[s.sheet.cssRules.length-1];
7984                 DT._oDynStyles[sClassname] = rule;
7985             }
7986         }
7987         // We have a rule to update
7988         else {
7989             rule.style.overflow = sOverflow;
7990             rule.style.width = sWidth;
7991         } 
7992         
7993         // Unhide
7994         if(this._elTbody) {
7995             this._elTbody.style.display = '';
7996         }
7997     }
7998     
7999     // That was not a success, we must call the fallback routine
8000     if(!rule) {
8001         DT._bDynStylesFallback = true;
8002         this._setColumnWidthDynFunction(oColumn, sWidth);
8003     }
8004 },
8005
8006 /**
8007  * Updates width of a Column's liner DIV elements by dynamically creating a
8008  * function to update all element style properties in one pass. Note: This
8009  * technique is not supported in sandboxed environments that prohibit EVALs.    
8010  *
8011  * @method _setColumnWidthDynFunction
8012  * @param oColumn {YAHOO.widget.Column} Column instance.
8013  * @param sWidth {String} New width value.
8014  * @private
8015  */
8016 _setColumnWidthDynFunction : function(oColumn, sWidth, sOverflow) {
8017     // TODO: why is this here?
8018     if(sWidth == 'auto') {
8019         sWidth = ''; 
8020     }
8021     
8022     // Create one function for each value of rows.length
8023     var rowslen = this._elTbody ? this._elTbody.rows.length : 0;
8024     
8025     // Dynamically create the function
8026     if (!this._aDynFunctions[rowslen]) {
8027         
8028         //Compile a custom function to do all the liner div width
8029         //assignments at the same time.  A unique function is required
8030         //for each unique number of rows in _elTbody.  This will
8031         //result in a function declaration like:
8032         //function (oColumn,sWidth,sOverflow) {
8033         //    var colIdx = oColumn.getKeyIndex();
8034         //    oColumn.getThLinerEl().style.overflow =
8035         //    this._elTbody.rows[0].cells[colIdx].firstChild.style.overflow =
8036         //    this._elTbody.rows[1].cells[colIdx].firstChild.style.overflow =
8037         //    ... (for all row indices in this._elTbody.rows.length - 1)
8038         //    this._elTbody.rows[99].cells[colIdx].firstChild.style.overflow =
8039         //    sOverflow;
8040         //    oColumn.getThLinerEl().style.width =
8041         //    this._elTbody.rows[0].cells[colIdx].firstChild.style.width =
8042         //    this._elTbody.rows[1].cells[colIdx].firstChild.style.width =
8043         //    ... (for all row indices in this._elTbody.rows.length - 1)
8044         //    this._elTbody.rows[99].cells[colIdx].firstChild.style.width =
8045         //    sWidth;
8046         //}
8047         
8048         var i,j,k;
8049         var resizerDef = [
8050             'var colIdx=oColumn.getKeyIndex();',
8051             'oColumn.getThLinerEl().style.overflow='
8052         ];
8053         for (i=rowslen-1, j=2; i >= 0; --i) {
8054             resizerDef[j++] = 'this._elTbody.rows[';
8055             resizerDef[j++] = i;
8056             resizerDef[j++] = '].cells[colIdx].firstChild.style.overflow=';
8057         }
8058         resizerDef[j] = 'sOverflow;';
8059         resizerDef[j+1] = 'oColumn.getThLinerEl().style.width=';
8060         for (i=rowslen-1, k=j+2; i >= 0; --i) {
8061             resizerDef[k++] = 'this._elTbody.rows[';
8062             resizerDef[k++] = i;
8063             resizerDef[k++] = '].cells[colIdx].firstChild.style.width=';
8064         }
8065         resizerDef[k] = 'sWidth;';
8066         this._aDynFunctions[rowslen] =
8067             new Function('oColumn','sWidth','sOverflow',resizerDef.join(''));
8068     }
8069     
8070     // Get the function to execute
8071     var resizerFn = this._aDynFunctions[rowslen];
8072
8073     // TODO: Hide TBODY for performance in _setColumnWidthDynFunction?
8074     if (resizerFn) {
8075         resizerFn.call(this,oColumn,sWidth,sOverflow);
8076     }
8077 },
8078
8079 /**
8080  * For one or all Columns, when Column is not hidden, width is not set, and minWidth
8081  * and/or maxAutoWidth is set, validates auto-width against minWidth and maxAutoWidth.
8082  *
8083  * @method validateColumnWidths
8084  * @param oArg.column {YAHOO.widget.Column} (optional) One Column to validate. If null, all Columns' widths are validated.
8085  */
8086 validateColumnWidths : function(oColumn) {
8087     var elColgroup = this._elColgroup;
8088     var elColgroupClone = elColgroup.cloneNode(true);
8089     var bNeedsValidation = false;
8090     var allKeys = this._oColumnSet.keys;
8091     var elThLiner;
8092     // Validate just one Column's minWidth and/or maxAutoWidth
8093     if(oColumn && !oColumn.hidden && !oColumn.width && (oColumn.getKeyIndex() !== null)) {
8094             elThLiner = oColumn.getThLinerEl();
8095             if((oColumn.minWidth > 0) && (elThLiner.offsetWidth < oColumn.minWidth)) {
8096                 elColgroupClone.childNodes[oColumn.getKeyIndex()].style.width = 
8097                         oColumn.minWidth + 
8098                         (parseInt(Dom.getStyle(elThLiner,"paddingLeft"),10)|0) +
8099                         (parseInt(Dom.getStyle(elThLiner,"paddingRight"),10)|0) + "px";
8100                 bNeedsValidation = true;
8101             }
8102             else if((oColumn.maxAutoWidth > 0) && (elThLiner.offsetWidth > oColumn.maxAutoWidth)) {
8103                 this._setColumnWidth(oColumn, oColumn.maxAutoWidth+"px", "hidden");
8104             }
8105     }
8106     // Validate all Columns
8107     else {
8108         for(var i=0, len=allKeys.length; i<len; i++) {
8109             oColumn = allKeys[i];
8110             if(!oColumn.hidden && !oColumn.width) {
8111                 elThLiner = oColumn.getThLinerEl();
8112                 if((oColumn.minWidth > 0) && (elThLiner.offsetWidth < oColumn.minWidth)) {
8113                     elColgroupClone.childNodes[i].style.width = 
8114                             oColumn.minWidth + 
8115                             (parseInt(Dom.getStyle(elThLiner,"paddingLeft"),10)|0) +
8116                             (parseInt(Dom.getStyle(elThLiner,"paddingRight"),10)|0) + "px";
8117                     bNeedsValidation = true;
8118                 }
8119                 else if((oColumn.maxAutoWidth > 0) && (elThLiner.offsetWidth > oColumn.maxAutoWidth)) {
8120                     this._setColumnWidth(oColumn, oColumn.maxAutoWidth+"px", "hidden");
8121                 }
8122             }
8123         }
8124     }
8125     if(bNeedsValidation) {
8126         elColgroup.parentNode.replaceChild(elColgroupClone, elColgroup);
8127         this._elColgroup = elColgroupClone;
8128     }
8129 },
8130
8131 /**
8132  * Clears minWidth.
8133  *
8134  * @method _clearMinWidth
8135  * @param oColumn {YAHOO.widget.Column} Which Column.
8136  * @private
8137  */
8138 _clearMinWidth : function(oColumn) {
8139     if(oColumn.getKeyIndex() !== null) {
8140         this._elColgroup.childNodes[oColumn.getKeyIndex()].style.width = '';
8141     }
8142 },
8143
8144 /**
8145  * Restores minWidth.
8146  *
8147  * @method _restoreMinWidth
8148  * @param oColumn {YAHOO.widget.Column} Which Column.
8149  * @private
8150  */
8151 _restoreMinWidth : function(oColumn) {
8152     if(oColumn.minWidth && (oColumn.getKeyIndex() !== null)) {
8153         this._elColgroup.childNodes[oColumn.getKeyIndex()].style.width = oColumn.minWidth + 'px';
8154     }
8155 },
8156
8157 /**
8158  * Hides given Column. NOTE: You cannot hide/show nested Columns. You can only
8159  * hide/show non-nested Columns, and top-level parent Columns (which will
8160  * hide/show all children Columns).
8161  *
8162  * @method hideColumn
8163  * @param oColumn {YAHOO.widget.Column} Column instance.
8164  */
8165 hideColumn : function(oColumn) {
8166     if(!(oColumn instanceof YAHOO.widget.Column)) {
8167         oColumn = this.getColumn(oColumn);
8168     }
8169     // Only top-level Columns can get hidden due to issues in FF2 and SF3
8170     if(oColumn && !oColumn.hidden && oColumn.getTreeIndex() !== null) {
8171         
8172         var allrows = this.getTbodyEl().rows;
8173         var l = allrows.length;
8174         var allDescendants = this._oColumnSet.getDescendants(oColumn);
8175         
8176         // Hide each nested Column
8177         for(var i=0; i<allDescendants.length; i++) {
8178             var thisColumn = allDescendants[i];
8179             thisColumn.hidden = true;
8180
8181             // Style the head cell
8182             Dom.addClass(thisColumn.getThEl(), DT.CLASS_HIDDEN);
8183             
8184             // Does this Column have body cells?
8185             var thisKeyIndex = thisColumn.getKeyIndex();
8186             if(thisKeyIndex !== null) {                    
8187                 // Clear minWidth
8188                 this._clearMinWidth(oColumn);
8189                 
8190                 // Style the body cells
8191                 for(var j=0;j<l;j++) {
8192                     Dom.addClass(allrows[j].cells[thisKeyIndex],DT.CLASS_HIDDEN);
8193                 }
8194             }
8195             
8196             this.fireEvent("columnHideEvent",{column:thisColumn});
8197         }
8198       
8199         this._repaintOpera();
8200         this._clearTrTemplateEl();
8201     }
8202     else {
8203     }
8204 },
8205
8206 /**
8207  * Shows given Column. NOTE: You cannot hide/show nested Columns. You can only
8208  * hide/show non-nested Columns, and top-level parent Columns (which will
8209  * hide/show all children Columns).
8210  *
8211  * @method showColumn
8212  * @param oColumn {YAHOO.widget.Column} Column instance.
8213  */
8214 showColumn : function(oColumn) {
8215     if(!(oColumn instanceof YAHOO.widget.Column)) {
8216         oColumn = this.getColumn(oColumn);
8217     }
8218     // Only top-level Columns can get hidden
8219     if(oColumn && oColumn.hidden && (oColumn.getTreeIndex() !== null)) {
8220         var allrows = this.getTbodyEl().rows;
8221         var l = allrows.length;
8222         var allDescendants = this._oColumnSet.getDescendants(oColumn);
8223         
8224         // Show each nested Column
8225         for(var i=0; i<allDescendants.length; i++) {
8226             var thisColumn = allDescendants[i];
8227             thisColumn.hidden = false;
8228             
8229             // Unstyle the head cell
8230             Dom.removeClass(thisColumn.getThEl(), DT.CLASS_HIDDEN);
8231
8232             // Does this Column have body cells?
8233             var thisKeyIndex = thisColumn.getKeyIndex();
8234             if(thisKeyIndex !== null) {
8235                 // Restore minWidth
8236                 this._restoreMinWidth(oColumn);
8237                 
8238             
8239                 // Unstyle the body cells
8240                 for(var j=0;j<l;j++) {
8241                     Dom.removeClass(allrows[j].cells[thisKeyIndex],DT.CLASS_HIDDEN);
8242                 }
8243             }
8244
8245             this.fireEvent("columnShowEvent",{column:thisColumn});
8246         }
8247         this._clearTrTemplateEl();
8248     }
8249     else {
8250     }
8251 },
8252
8253 /**
8254  * Removes given Column. NOTE: You cannot remove nested Columns. You can only remove
8255  * non-nested Columns, and top-level parent Columns (which will remove all
8256  * children Columns).
8257  *
8258  * @method removeColumn
8259  * @param oColumn {YAHOO.widget.Column} Column instance.
8260  * @return oColumn {YAHOO.widget.Column} Removed Column instance.
8261  */
8262 removeColumn : function(oColumn) {
8263     // Validate Column
8264     if(!(oColumn instanceof YAHOO.widget.Column)) {
8265         oColumn = this.getColumn(oColumn);
8266     }
8267     if(oColumn) {
8268         var nColTreeIndex = oColumn.getTreeIndex();
8269         if(nColTreeIndex !== null) {
8270             // Which key index(es)
8271             var i, len,
8272                 aKeyIndexes = oColumn.getKeyIndex();
8273             // Must be a parent Column
8274             if(aKeyIndexes === null) {
8275                 var descKeyIndexes = [];
8276                 var allDescendants = this._oColumnSet.getDescendants(oColumn);
8277                 for(i=0, len=allDescendants.length; i<len; i++) {
8278                     // Is this descendant a key Column?
8279                     var thisKey = allDescendants[i].getKeyIndex();
8280                     if(thisKey !== null) {
8281                         descKeyIndexes[descKeyIndexes.length] = thisKey;
8282                     }
8283                 }
8284                 if(descKeyIndexes.length > 0) {
8285                     aKeyIndexes = descKeyIndexes;
8286                 }
8287             }
8288             // Must be a key Column
8289             else {
8290                 aKeyIndexes = [aKeyIndexes];
8291             }
8292             
8293             if(aKeyIndexes !== null) {
8294                 // Sort the indexes so we can remove from the right
8295                 aKeyIndexes.sort(function(a, b) {return YAHOO.util.Sort.compare(a, b);});
8296                 
8297                 // Destroy previous THEAD
8298                 this._destroyTheadEl();
8299     
8300                 // Create new THEAD
8301                 var aOrigColumnDefs = this._oColumnSet.getDefinitions();
8302                 oColumn = aOrigColumnDefs.splice(nColTreeIndex,1)[0];
8303                 this._initColumnSet(aOrigColumnDefs);
8304                 this._initTheadEl();
8305                 
8306                 // Remove COL
8307                 for(i=aKeyIndexes.length-1; i>-1; i--) {
8308                     this._removeColgroupColEl(aKeyIndexes[i]);
8309                 }
8310                 
8311                 // Remove TD
8312                 var allRows = this._elTbody.rows;
8313                 if(allRows.length > 0) {
8314                     var loopN = this.get("renderLoopSize"),
8315                         loopEnd = allRows.length;
8316                     this._oChainRender.add({
8317                         method: function(oArg) {
8318                             if((this instanceof DT) && this._sId) {
8319                                 var i = oArg.nCurrentRow,
8320                                     len = loopN > 0 ? Math.min(i + loopN,allRows.length) : allRows.length,
8321                                     aIndexes = oArg.aIndexes,
8322                                     j;
8323                                 for(; i < len; ++i) {
8324                                     for(j = aIndexes.length-1; j>-1; j--) {
8325                                         allRows[i].removeChild(allRows[i].childNodes[aIndexes[j]]);
8326                                     }
8327                                 }
8328                                 oArg.nCurrentRow = i;
8329                             }
8330                         },
8331                         iterations: (loopN > 0) ? Math.ceil(loopEnd/loopN) : 1,
8332                         argument: {nCurrentRow:0, aIndexes:aKeyIndexes},
8333                         scope: this,
8334                         timeout: (loopN > 0) ? 0 : -1
8335                     });
8336                     this._runRenderChain();
8337                 }
8338         
8339                 this.fireEvent("columnRemoveEvent",{column:oColumn});
8340                 return oColumn;
8341             }
8342         }
8343     }
8344 },
8345
8346 /**
8347  * Inserts given Column at the index if given, otherwise at the end. NOTE: You
8348  * can only add non-nested Columns and top-level parent Columns. You cannot add
8349  * a nested Column to an existing parent.
8350  *
8351  * @method insertColumn
8352  * @param oColumn {Object | YAHOO.widget.Column} Object literal Column
8353  * definition or a Column instance.
8354  * @param index {Number} (optional) New tree index.
8355  * @return oColumn {YAHOO.widget.Column} Inserted Column instance. 
8356  */
8357 insertColumn : function(oColumn, index) {
8358     // Validate Column
8359     if(oColumn instanceof YAHOO.widget.Column) {
8360         oColumn = oColumn.getDefinition();
8361     }
8362     else if(oColumn.constructor !== Object) {
8363         return;
8364     }
8365     
8366     // Validate index or append new Column to the end of the ColumnSet
8367     var oColumnSet = this._oColumnSet;
8368     if(!lang.isValue(index) || !lang.isNumber(index)) {
8369         index = oColumnSet.tree[0].length;
8370     }
8371     
8372     // Destroy previous THEAD
8373     this._destroyTheadEl();
8374     
8375     // Create new THEAD
8376     var aNewColumnDefs = this._oColumnSet.getDefinitions();
8377     aNewColumnDefs.splice(index, 0, oColumn);
8378     this._initColumnSet(aNewColumnDefs);
8379     this._initTheadEl();
8380     
8381     // Need to refresh the reference
8382     oColumnSet = this._oColumnSet;
8383     var oNewColumn = oColumnSet.tree[0][index];
8384     
8385     // Get key index(es) for new Column
8386     var i, len,
8387         descKeyIndexes = [];
8388     var allDescendants = oColumnSet.getDescendants(oNewColumn);
8389     for(i=0, len=allDescendants.length; i<len; i++) {
8390         // Is this descendant a key Column?
8391         var thisKey = allDescendants[i].getKeyIndex();
8392         if(thisKey !== null) {
8393             descKeyIndexes[descKeyIndexes.length] = thisKey;
8394         }
8395     }
8396     
8397     if(descKeyIndexes.length > 0) {  
8398         // Sort the indexes
8399         var newIndex = descKeyIndexes.sort(function(a, b) {return YAHOO.util.Sort.compare(a, b);})[0];
8400         
8401         // Add COL
8402         for(i=descKeyIndexes.length-1; i>-1; i--) {
8403             this._insertColgroupColEl(descKeyIndexes[i]);
8404         }
8405             
8406         // Add TD
8407         var allRows = this._elTbody.rows;
8408         if(allRows.length > 0) {
8409             var loopN = this.get("renderLoopSize"),
8410                 loopEnd = allRows.length;
8411             
8412             // Get templates for each new TD
8413             var aTdTemplates = [],
8414                 elTdTemplate;
8415             for(i=0, len=descKeyIndexes.length; i<len; i++) {
8416                 var thisKeyIndex = descKeyIndexes[i];
8417                 elTdTemplate = this._getTrTemplateEl().childNodes[i].cloneNode(true);
8418                 elTdTemplate = this._formatTdEl(this._oColumnSet.keys[thisKeyIndex], elTdTemplate, thisKeyIndex, (thisKeyIndex===this._oColumnSet.keys.length-1));
8419                 aTdTemplates[thisKeyIndex] = elTdTemplate;
8420             }
8421             
8422             this._oChainRender.add({
8423                 method: function(oArg) {
8424                     if((this instanceof DT) && this._sId) {
8425                         var i = oArg.nCurrentRow, j,
8426                             descKeyIndexes = oArg.descKeyIndexes,
8427                             len = loopN > 0 ? Math.min(i + loopN,allRows.length) : allRows.length,
8428                             nextSibling;
8429                         for(; i < len; ++i) {
8430                             nextSibling = allRows[i].childNodes[newIndex] || null;
8431                             for(j=descKeyIndexes.length-1; j>-1; j--) {
8432                                 allRows[i].insertBefore(oArg.aTdTemplates[descKeyIndexes[j]].cloneNode(true), nextSibling);
8433                             }
8434                         }
8435                         oArg.nCurrentRow = i;
8436                     }
8437                 },
8438                 iterations: (loopN > 0) ? Math.ceil(loopEnd/loopN) : 1,
8439                 argument: {nCurrentRow:0,aTdTemplates:aTdTemplates,descKeyIndexes:descKeyIndexes},
8440                 scope: this,
8441                 timeout: (loopN > 0) ? 0 : -1
8442             });
8443             this._runRenderChain(); 
8444         }
8445
8446         this.fireEvent("columnInsertEvent",{column:oColumn,index:index});
8447         return oNewColumn;
8448     }
8449 },
8450
8451 /**
8452  * Removes given Column and inserts into given tree index. NOTE: You
8453  * can only reorder non-nested Columns and top-level parent Columns. You cannot
8454  * reorder a nested Column to an existing parent.
8455  *
8456  * @method reorderColumn
8457  * @param oColumn {YAHOO.widget.Column} Column instance.
8458  * @param index {Number} New tree index.
8459  * @return oColumn {YAHOO.widget.Column} Reordered Column instance. 
8460  */
8461 reorderColumn : function(oColumn, index) {
8462     // Validate Column and new index
8463     if(!(oColumn instanceof YAHOO.widget.Column)) {
8464         oColumn = this.getColumn(oColumn);
8465     }
8466     if(oColumn && YAHOO.lang.isNumber(index)) {
8467         var nOrigTreeIndex = oColumn.getTreeIndex();
8468         if((nOrigTreeIndex !== null) && (nOrigTreeIndex !== index)) {
8469             // Which key index(es)
8470             var i, len,
8471                 aOrigKeyIndexes = oColumn.getKeyIndex(),
8472                 allDescendants,
8473                 descKeyIndexes = [],
8474                 thisKey;
8475             // Must be a parent Column...
8476             if(aOrigKeyIndexes === null) {
8477                 allDescendants = this._oColumnSet.getDescendants(oColumn);
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                     aOrigKeyIndexes = descKeyIndexes;
8487                 }
8488             }
8489             // ...or else must be a key Column
8490             else {
8491                 aOrigKeyIndexes = [aOrigKeyIndexes];
8492             }
8493             
8494             if(aOrigKeyIndexes !== null) {                   
8495                 // Sort the indexes
8496                 aOrigKeyIndexes.sort(function(a, b) {return YAHOO.util.Sort.compare(a, b);});
8497                 
8498                 // Destroy previous THEAD
8499                 this._destroyTheadEl();
8500     
8501                 // Create new THEAD
8502                 var aColumnDefs = this._oColumnSet.getDefinitions();
8503                 var oColumnDef = aColumnDefs.splice(nOrigTreeIndex,1)[0];
8504                 aColumnDefs.splice(index, 0, oColumnDef);
8505                 this._initColumnSet(aColumnDefs);
8506                 this._initTheadEl();
8507                 
8508                 // Need to refresh the reference
8509                 var oNewColumn = this._oColumnSet.tree[0][index];
8510
8511                 // What are new key index(es)
8512                 var aNewKeyIndexes = oNewColumn.getKeyIndex();
8513                 // Must be a parent Column
8514                 if(aNewKeyIndexes === null) {
8515                     descKeyIndexes = [];
8516                     allDescendants = this._oColumnSet.getDescendants(oNewColumn);
8517                     for(i=0, len=allDescendants.length; i<len; i++) {
8518                         // Is this descendant a key Column?
8519                         thisKey = allDescendants[i].getKeyIndex();
8520                         if(thisKey !== null) {
8521                             descKeyIndexes[descKeyIndexes.length] = thisKey;
8522                         }
8523                     }
8524                     if(descKeyIndexes.length > 0) {
8525                         aNewKeyIndexes = descKeyIndexes;
8526                     }
8527                 }
8528                 // Must be a key Column
8529                 else {
8530                     aNewKeyIndexes = [aNewKeyIndexes];
8531                 }
8532                 
8533                 // Sort the new indexes and grab the first one for the new location
8534                 var newIndex = aNewKeyIndexes.sort(function(a, b) {return YAHOO.util.Sort.compare(a, b);})[0];
8535
8536                 // Reorder COL
8537                 this._reorderColgroupColEl(aOrigKeyIndexes, newIndex);
8538                 
8539                 // Reorder TD
8540                 var allRows = this._elTbody.rows;
8541                 if(allRows.length > 0) {
8542                     var loopN = this.get("renderLoopSize"),
8543                         loopEnd = allRows.length;
8544                     this._oChainRender.add({
8545                         method: function(oArg) {
8546                             if((this instanceof DT) && this._sId) {
8547                                 var i = oArg.nCurrentRow, j, tmpTds, nextSibling,
8548                                     len = loopN > 0 ? Math.min(i + loopN,allRows.length) : allRows.length,
8549                                     aIndexes = oArg.aIndexes, thisTr;
8550                                 // For each row
8551                                 for(; i < len; ++i) {
8552                                     tmpTds = [];
8553                                     thisTr = allRows[i];
8554                                     
8555                                     // Remove each TD
8556                                     for(j=aIndexes.length-1; j>-1; j--) {
8557                                         tmpTds.push(thisTr.removeChild(thisTr.childNodes[aIndexes[j]]));
8558                                     }
8559                                     
8560                                     // Insert each TD
8561                                     nextSibling = thisTr.childNodes[newIndex] || null;
8562                                     for(j=tmpTds.length-1; j>-1; j--) {
8563                                         thisTr.insertBefore(tmpTds[j], nextSibling);
8564                                     }                                    
8565                                 }
8566                                 oArg.nCurrentRow = i;
8567                             }
8568                         },
8569                         iterations: (loopN > 0) ? Math.ceil(loopEnd/loopN) : 1,
8570                         argument: {nCurrentRow:0, aIndexes:aOrigKeyIndexes},
8571                         scope: this,
8572                         timeout: (loopN > 0) ? 0 : -1
8573                     });
8574                     this._runRenderChain();
8575                 }
8576         
8577                 this.fireEvent("columnReorderEvent",{column:oNewColumn});
8578                 return oNewColumn;
8579             }
8580         }
8581     }
8582 },
8583
8584 /**
8585  * Selects given Column. NOTE: You cannot select/unselect nested Columns. You can only
8586  * select/unselect non-nested Columns, and bottom-level key Columns.
8587  *
8588  * @method selectColumn
8589  * @param column {HTMLElement | String | Number} DOM reference or ID string to a
8590  * TH/TD element (or child of a TH/TD element), a Column key, or a ColumnSet key index.
8591  */
8592 selectColumn : function(oColumn) {
8593     oColumn = this.getColumn(oColumn);
8594     if(oColumn && !oColumn.selected) {
8595         // Only bottom-level Columns can get hidden
8596         if(oColumn.getKeyIndex() !== null) {
8597             oColumn.selected = true;
8598             
8599             // Update head cell
8600             var elTh = oColumn.getThEl();
8601             Dom.addClass(elTh,DT.CLASS_SELECTED);
8602
8603             // Update body cells
8604             var allRows = this.getTbodyEl().rows;
8605             var oChainRender = this._oChainRender;
8606             oChainRender.add({
8607                 method: function(oArg) {
8608                     if((this instanceof DT) && this._sId && allRows[oArg.rowIndex] && allRows[oArg.rowIndex].cells[oArg.cellIndex]) {
8609                         Dom.addClass(allRows[oArg.rowIndex].cells[oArg.cellIndex],DT.CLASS_SELECTED);                    
8610                     }
8611                     oArg.rowIndex++;
8612                 },
8613                 scope: this,
8614                 iterations: allRows.length,
8615                 argument: {rowIndex:0,cellIndex:oColumn.getKeyIndex()}
8616             });
8617
8618             this._clearTrTemplateEl();
8619             
8620             this._elTbody.style.display = "none";
8621             this._runRenderChain();
8622             this._elTbody.style.display = "";      
8623             
8624             this.fireEvent("columnSelectEvent",{column:oColumn});
8625         }
8626         else {
8627         }
8628     }
8629 },
8630
8631 /**
8632  * Unselects given Column. NOTE: You cannot select/unselect nested Columns. You can only
8633  * select/unselect non-nested Columns, and bottom-level key Columns.
8634  *
8635  * @method unselectColumn
8636  * @param column {HTMLElement | String | Number} DOM reference or ID string to a
8637  * TH/TD element (or child of a TH/TD element), a Column key, or a ColumnSet key index.
8638  */
8639 unselectColumn : function(oColumn) {
8640     oColumn = this.getColumn(oColumn);
8641     if(oColumn && oColumn.selected) {
8642         // Only bottom-level Columns can get hidden
8643         if(oColumn.getKeyIndex() !== null) {
8644             oColumn.selected = false;
8645             
8646             // Update head cell
8647             var elTh = oColumn.getThEl();
8648             Dom.removeClass(elTh,DT.CLASS_SELECTED);
8649
8650             // Update body cells
8651             var allRows = this.getTbodyEl().rows;
8652             var oChainRender = this._oChainRender;
8653             oChainRender.add({
8654                 method: function(oArg) {
8655                     if((this instanceof DT) && this._sId && allRows[oArg.rowIndex] && allRows[oArg.rowIndex].cells[oArg.cellIndex]) {
8656                         Dom.removeClass(allRows[oArg.rowIndex].cells[oArg.cellIndex],DT.CLASS_SELECTED); 
8657                     }                   
8658                     oArg.rowIndex++;
8659                 },
8660                 scope: this,
8661                 iterations:allRows.length,
8662                 argument: {rowIndex:0,cellIndex:oColumn.getKeyIndex()}
8663             });
8664             
8665             this._clearTrTemplateEl();
8666
8667             this._elTbody.style.display = "none";
8668             this._runRenderChain();
8669             this._elTbody.style.display = "";      
8670             
8671             this.fireEvent("columnUnselectEvent",{column:oColumn});
8672         }
8673         else {
8674         }
8675     }
8676 },
8677
8678 /**
8679  * Returns an array selected Column instances.
8680  *
8681  * @method getSelectedColumns
8682  * @return {YAHOO.widget.Column[]} Array of Column instances.
8683  */
8684 getSelectedColumns : function(oColumn) {
8685     var selectedColumns = [];
8686     var aKeys = this._oColumnSet.keys;
8687     for(var i=0,len=aKeys.length; i<len; i++) {
8688         if(aKeys[i].selected) {
8689             selectedColumns[selectedColumns.length] = aKeys[i];
8690         }
8691     }
8692     return selectedColumns;
8693 },
8694
8695 /**
8696  * Assigns the class YAHOO.widget.DataTable.CLASS_HIGHLIGHTED to cells of the given Column.
8697  * NOTE: You cannot highlight/unhighlight nested Columns. You can only
8698  * highlight/unhighlight non-nested Columns, and bottom-level key Columns.
8699  *
8700  * @method highlightColumn
8701  * @param column {HTMLElement | String | Number} DOM reference or ID string to a
8702  * TH/TD element (or child of a TH/TD element), a Column key, or a ColumnSet key index.
8703  */
8704 highlightColumn : function(column) {
8705     var oColumn = this.getColumn(column);
8706     // Only bottom-level Columns can get highlighted
8707     if(oColumn && (oColumn.getKeyIndex() !== null)) {            
8708         // Update head cell
8709         var elTh = oColumn.getThEl();
8710         Dom.addClass(elTh,DT.CLASS_HIGHLIGHTED);
8711
8712         // Update body cells
8713         var allRows = this.getTbodyEl().rows;
8714         var oChainRender = this._oChainRender;
8715         oChainRender.add({
8716             method: function(oArg) {
8717                 if((this instanceof DT) && this._sId && allRows[oArg.rowIndex] && allRows[oArg.rowIndex].cells[oArg.cellIndex]) {
8718                     Dom.addClass(allRows[oArg.rowIndex].cells[oArg.cellIndex],DT.CLASS_HIGHLIGHTED);   
8719                 }                 
8720                 oArg.rowIndex++;
8721             },
8722             scope: this,
8723             iterations:allRows.length,
8724             argument: {rowIndex:0,cellIndex:oColumn.getKeyIndex()},
8725             timeout: -1
8726         });
8727         this._elTbody.style.display = "none";
8728         this._runRenderChain();
8729         this._elTbody.style.display = "";      
8730             
8731         this.fireEvent("columnHighlightEvent",{column:oColumn});
8732     }
8733     else {
8734     }
8735 },
8736
8737 /**
8738  * Removes the class YAHOO.widget.DataTable.CLASS_HIGHLIGHTED to cells of the given Column.
8739  * NOTE: You cannot highlight/unhighlight nested Columns. You can only
8740  * highlight/unhighlight non-nested Columns, and bottom-level key Columns.
8741  *
8742  * @method unhighlightColumn
8743  * @param column {HTMLElement | String | Number} DOM reference or ID string to a
8744  * TH/TD element (or child of a TH/TD element), a Column key, or a ColumnSet key index.
8745  */
8746 unhighlightColumn : function(column) {
8747     var oColumn = this.getColumn(column);
8748     // Only bottom-level Columns can get highlighted
8749     if(oColumn && (oColumn.getKeyIndex() !== null)) {
8750         // Update head cell
8751         var elTh = oColumn.getThEl();
8752         Dom.removeClass(elTh,DT.CLASS_HIGHLIGHTED);
8753
8754         // Update body cells
8755         var allRows = this.getTbodyEl().rows;
8756         var oChainRender = this._oChainRender;
8757         oChainRender.add({
8758             method: function(oArg) {
8759                 if((this instanceof DT) && this._sId && allRows[oArg.rowIndex] && allRows[oArg.rowIndex].cells[oArg.cellIndex]) {
8760                     Dom.removeClass(allRows[oArg.rowIndex].cells[oArg.cellIndex],DT.CLASS_HIGHLIGHTED);
8761                 }                 
8762                 oArg.rowIndex++;
8763             },
8764             scope: this,
8765             iterations:allRows.length,
8766             argument: {rowIndex:0,cellIndex:oColumn.getKeyIndex()},
8767             timeout: -1
8768         });
8769         this._elTbody.style.display = "none";
8770         this._runRenderChain();
8771         this._elTbody.style.display = "";     
8772             
8773         this.fireEvent("columnUnhighlightEvent",{column:oColumn});
8774     }
8775     else {
8776     }
8777 },
8778
8779
8780
8781
8782
8783
8784
8785
8786
8787
8788
8789
8790
8791
8792
8793
8794
8795
8796
8797
8798
8799
8800
8801
8802
8803
8804
8805
8806
8807
8808
8809
8810
8811
8812
8813
8814
8815
8816
8817
8818
8819
8820
8821
8822 // ROW FUNCTIONS
8823
8824 /**
8825  * Adds one new Record of data into the RecordSet at the index if given,
8826  * otherwise at the end. If the new Record is in page view, the
8827  * corresponding DOM elements are also updated.
8828  *
8829  * @method addRow
8830  * @param oData {Object} Object literal of data for the row.
8831  * @param index {Number} (optional) RecordSet position index at which to add data.
8832  */
8833 addRow : function(oData, index) {
8834     if(lang.isNumber(index) && (index < 0 || index > this._oRecordSet.getLength())) {
8835         return;
8836     }
8837
8838     if(oData && lang.isObject(oData)) {
8839         var oRecord = this._oRecordSet.addRecord(oData, index);
8840         if(oRecord) {
8841             var recIndex;
8842             var oPaginator = this.get('paginator');
8843
8844             // Paginated
8845             if (oPaginator) {     
8846                 // Update the paginator's totalRecords
8847                 var totalRecords = oPaginator.get('totalRecords');
8848                 if (totalRecords !== widget.Paginator.VALUE_UNLIMITED) {
8849                     oPaginator.set('totalRecords',totalRecords + 1);
8850                 }
8851
8852                 recIndex = this.getRecordIndex(oRecord);
8853                 var endRecIndex = (oPaginator.getPageRecords())[1];
8854
8855                 // New record affects the view
8856                 if (recIndex <= endRecIndex) {
8857                     // Defer UI updates to the render method
8858                     this.render();
8859                 }
8860                 
8861                 this.fireEvent("rowAddEvent", {record:oRecord});
8862                 return;
8863             }
8864             // Not paginated
8865             else {
8866                 recIndex = this.getTrIndex(oRecord);
8867                 if(lang.isNumber(recIndex)) {
8868                     // Add the TR element
8869                     this._oChainRender.add({
8870                         method: function(oArg) {
8871                             if((this instanceof DT) && this._sId) {
8872                                 var oRecord = oArg.record;
8873                                 var recIndex = oArg.recIndex;
8874                                 var elNewTr = this._addTrEl(oRecord);
8875                                 if(elNewTr) {
8876                                     var elNext = (this._elTbody.rows[recIndex]) ? this._elTbody.rows[recIndex] : null;
8877                                     this._elTbody.insertBefore(elNewTr, elNext);
8878
8879                                     // Set FIRST/LAST
8880                                     if(recIndex === 0) {
8881                                         this._setFirstRow();
8882                                     }
8883                                     if(elNext === null) {
8884                                         this._setLastRow();
8885                                     }
8886                                     // Set EVEN/ODD
8887                                     this._setRowStripes();                           
8888                                     
8889                                     this.hideTableMessage();
8890             
8891                                     this.fireEvent("rowAddEvent", {record:oRecord});
8892                                 }
8893                             }
8894                         },
8895                         argument: {record: oRecord, recIndex: recIndex},
8896                         scope: this,
8897                         timeout: (this.get("renderLoopSize") > 0) ? 0 : -1
8898                     });
8899                     this._runRenderChain();
8900                     return;
8901                 }
8902             }            
8903         }
8904     }
8905 },
8906
8907 /**
8908  * Convenience method to add multiple rows.
8909  *
8910  * @method addRows
8911  * @param aData {Object[]} Array of object literal data for the rows.
8912  * @param index {Number} (optional) RecordSet position index at which to add data.
8913  */
8914 addRows : function(aData, index) {
8915     if(lang.isNumber(index) && (index < 0 || index > this._oRecordSet.getLength())) {
8916         return;
8917     }
8918
8919     if(lang.isArray(aData)) {
8920         var aRecords = this._oRecordSet.addRecords(aData, index);
8921         if(aRecords) {
8922             var recIndex = this.getRecordIndex(aRecords[0]);
8923             
8924             // Paginated
8925             var oPaginator = this.get('paginator');
8926             if (oPaginator) {
8927                 // Update the paginator's totalRecords
8928                 var totalRecords = oPaginator.get('totalRecords');
8929                 if (totalRecords !== widget.Paginator.VALUE_UNLIMITED) {
8930                     oPaginator.set('totalRecords',totalRecords + aRecords.length);
8931                 }
8932     
8933                 var endRecIndex = (oPaginator.getPageRecords())[1];
8934
8935                 // At least one of the new records affects the view
8936                 if (recIndex <= endRecIndex) {
8937                     this.render();
8938                 }
8939                 
8940                 this.fireEvent("rowsAddEvent", {records:aRecords});
8941                 return;
8942             }
8943             // Not paginated
8944             else {
8945                 // Add the TR elements
8946                 var loopN = this.get("renderLoopSize");
8947                 var loopEnd = recIndex + aData.length;
8948                 var nRowsNeeded = (loopEnd - recIndex); // how many needed
8949                 var isLast = (recIndex >= this._elTbody.rows.length);
8950                 this._oChainRender.add({
8951                     method: function(oArg) {
8952                         if((this instanceof DT) && this._sId) {
8953                             var aRecords = oArg.aRecords,
8954                                 i = oArg.nCurrentRow,
8955                                 j = oArg.nCurrentRecord,
8956                                 len = loopN > 0 ? Math.min(i + loopN,loopEnd) : loopEnd,
8957                                 df = document.createDocumentFragment(),
8958                                 elNext = (this._elTbody.rows[i]) ? this._elTbody.rows[i] : null;
8959                             for(; i < len; i++, j++) {
8960                                 df.appendChild(this._addTrEl(aRecords[j]));
8961                             }
8962                             this._elTbody.insertBefore(df, elNext);
8963                             oArg.nCurrentRow = i;
8964                             oArg.nCurrentRecord = j;
8965                         }
8966                     },
8967                     iterations: (loopN > 0) ? Math.ceil(loopEnd/loopN) : 1,
8968                     argument: {nCurrentRow:recIndex,nCurrentRecord:0,aRecords:aRecords},
8969                     scope: this,
8970                     timeout: (loopN > 0) ? 0 : -1
8971                 });
8972                 this._oChainRender.add({
8973                     method: function(oArg) {
8974                         var recIndex = oArg.recIndex;
8975                         // Set FIRST/LAST
8976                         if(recIndex === 0) {
8977                             this._setFirstRow();
8978                         }
8979                         if(oArg.isLast) {
8980                             this._setLastRow();
8981                         }
8982                         // Set EVEN/ODD
8983                         this._setRowStripes();                           
8984
8985                         this.fireEvent("rowsAddEvent", {records:aRecords});
8986                     },
8987                     argument: {recIndex: recIndex, isLast: isLast},
8988                     scope: this,
8989                     timeout: -1 // Needs to run immediately after the DOM insertions above
8990                 });
8991                 this._runRenderChain();
8992                 this.hideTableMessage();                
8993                 return;
8994             }            
8995         }
8996     }
8997 },
8998
8999 /**
9000  * For the given row, updates the associated Record with the given data. If the
9001  * row is on current page, the corresponding DOM elements are also updated.
9002  *
9003  * @method updateRow
9004  * @param row {YAHOO.widget.Record | Number | HTMLElement | String}
9005  * Which row to update: By Record instance, by Record's RecordSet
9006  * position index, by HTMLElement reference to the TR element, or by ID string
9007  * of the TR element.
9008  * @param oData {Object} Object literal of data for the row.
9009  */
9010 updateRow : function(row, oData) {
9011     var index = row;
9012     if (!lang.isNumber(index)) {
9013         index = this.getRecordIndex(row);
9014     }
9015
9016     // Update the Record
9017     if(lang.isNumber(index) && (index >= 0)) {
9018         var oRecordSet = this._oRecordSet,
9019             oldRecord = oRecordSet.getRecord(index);
9020             
9021         
9022         if(oldRecord) {
9023             var updatedRecord = this._oRecordSet.setRecord(oData, index),
9024                 elRow = this.getTrEl(oldRecord),
9025                 // Copy data from the Record for the event that gets fired later
9026                 oldData = oldRecord ? oldRecord.getData() : null;
9027                
9028             if(updatedRecord) {
9029                 // Update selected rows as necessary
9030                 var tracker = this._aSelections || [],
9031                 i=0,
9032                 oldId = oldRecord.getId(),
9033                 newId = updatedRecord.getId();
9034                 for(; i<tracker.length; i++) {
9035                     if((tracker[i] === oldId)) {
9036                         tracker[i] = newId;
9037                     }
9038                     else if(tracker[i].recordId === oldId) {
9039                         tracker[i].recordId = newId;
9040                     }
9041                 }
9042
9043                 // Update the TR only if row is on current page
9044                 this._oChainRender.add({
9045                     method: function() {
9046                         if((this instanceof DT) && this._sId) {
9047                             // Paginated
9048                             var oPaginator = this.get('paginator');
9049                             if (oPaginator) {
9050                                 var pageStartIndex = (oPaginator.getPageRecords())[0],
9051                                     pageLastIndex = (oPaginator.getPageRecords())[1];
9052         
9053                                 // At least one of the new records affects the view
9054                                 if ((index >= pageStartIndex) || (index <= pageLastIndex)) {
9055                                     this.render();
9056                                 }
9057                             }
9058                             else {
9059                                 if(elRow) {
9060                                     this._updateTrEl(elRow, updatedRecord);
9061                                 }
9062                                 else {
9063                                     this.getTbodyEl().appendChild(this._addTrEl(updatedRecord));
9064                                 }
9065                             }
9066                             this.fireEvent("rowUpdateEvent", {record:updatedRecord, oldData:oldData});
9067                         }
9068                     },
9069                     scope: this,
9070                     timeout: (this.get("renderLoopSize") > 0) ? 0 : -1
9071                 });
9072                 this._runRenderChain();
9073                 return;
9074             }
9075         }
9076     }
9077     return;
9078 },
9079
9080 /**
9081  * Starting with the given row, updates associated Records with the given data.
9082  * The number of rows to update are determined by the array of data provided.
9083  * Undefined data (i.e., not an object literal) causes a row to be skipped. If
9084  * any of the rows are on current page, the corresponding DOM elements are also
9085  * updated.
9086  *
9087  * @method updateRows
9088  * @param startrow {YAHOO.widget.Record | Number | HTMLElement | String}
9089  * Starting row to update: By Record instance, by Record's RecordSet
9090  * position index, by HTMLElement reference to the TR element, or by ID string
9091  * of the TR element.
9092  * @param aData {Object[]} Array of object literal of data for the rows.
9093  */
9094 updateRows : function(startrow, aData) {
9095     if(lang.isArray(aData)) {
9096         var startIndex = startrow,
9097             oRecordSet = this._oRecordSet;
9098             
9099         if (!lang.isNumber(startrow)) {
9100             startIndex = this.getRecordIndex(startrow);
9101         }
9102             
9103         if(lang.isNumber(startIndex) && (startIndex >= 0) && (startIndex < oRecordSet.getLength())) {
9104             var lastIndex = startIndex + aData.length,
9105                 aOldRecords = oRecordSet.getRecords(startIndex, aData.length),
9106                 aNewRecords = oRecordSet.setRecords(aData, startIndex);
9107             if(aNewRecords) {
9108                 // Update selected rows as necessary
9109                 var tracker = this._aSelections || [],
9110                     i=0, j, newId, oldId;
9111                 for(; i<tracker.length; i++) {
9112                     for(j=0; j<aOldRecords.length; j++) {
9113                         oldId = aOldRecords[j].getId();
9114                         if((tracker[i] === oldId)) {
9115                             tracker[i] = aNewRecords[j].getId();
9116                         }
9117                         else if(tracker[i].recordId === oldId) {
9118                             tracker[i].recordId = aNewRecords[j].getId();
9119                         }
9120                     }
9121                 }
9122             
9123                 // Paginated
9124                 var oPaginator = this.get('paginator');
9125                 if (oPaginator) {
9126                     var pageStartIndex = (oPaginator.getPageRecords())[0],
9127                         pageLastIndex = (oPaginator.getPageRecords())[1];
9128     
9129                     // At least one of the new records affects the view
9130                     if ((startIndex >= pageStartIndex) || (lastIndex <= pageLastIndex)) {
9131                         this.render();
9132                     }
9133                     
9134                     this.fireEvent("rowsAddEvent", {newRecords:aNewRecords, oldRecords:aOldRecords});
9135                     return;
9136                 }
9137                 // Not paginated
9138                 else {
9139                     // Update the TR elements
9140                     var loopN = this.get("renderLoopSize"),
9141                         rowCount = aData.length, // how many needed
9142                         lastRowIndex = this._elTbody.rows.length,
9143                         isLast = (lastIndex >= lastRowIndex),
9144                         isAdding = (lastIndex > lastRowIndex);
9145                                            
9146                     this._oChainRender.add({
9147                         method: function(oArg) {
9148                             if((this instanceof DT) && this._sId) {
9149                                 var aRecords = oArg.aRecords,
9150                                     i = oArg.nCurrentRow,
9151                                     j = oArg.nDataPointer,
9152                                     len = loopN > 0 ? Math.min(i+loopN, startIndex+aRecords.length) : startIndex+aRecords.length;
9153                                     
9154                                 for(; i < len; i++,j++) {
9155                                     if(isAdding && (i>=lastRowIndex)) {
9156                                         this._elTbody.appendChild(this._addTrEl(aRecords[j]));
9157                                     }
9158                                     else {
9159                                         this._updateTrEl(this._elTbody.rows[i], aRecords[j]);
9160                                     }
9161                                 }
9162                                 oArg.nCurrentRow = i;
9163                                 oArg.nDataPointer = j;
9164                             }
9165                         },
9166                         iterations: (loopN > 0) ? Math.ceil(rowCount/loopN) : 1,
9167                         argument: {nCurrentRow:startIndex,aRecords:aNewRecords,nDataPointer:0,isAdding:isAdding},
9168                         scope: this,
9169                         timeout: (loopN > 0) ? 0 : -1
9170                     });
9171                     this._oChainRender.add({
9172                         method: function(oArg) {
9173                             var recIndex = oArg.recIndex;
9174                             // Set FIRST/LAST
9175                             if(recIndex === 0) {
9176                                 this._setFirstRow();
9177                             }
9178                             if(oArg.isLast) {
9179                                 this._setLastRow();
9180                             }
9181                             // Set EVEN/ODD
9182                             this._setRowStripes();                           
9183     
9184                             this.fireEvent("rowsAddEvent", {newRecords:aNewRecords, oldRecords:aOldRecords});
9185                         },
9186                         argument: {recIndex: startIndex, isLast: isLast},
9187                         scope: this,
9188                         timeout: -1 // Needs to run immediately after the DOM insertions above
9189                     });
9190                     this._runRenderChain();
9191                     this.hideTableMessage();                
9192                     return;
9193                 }            
9194             }
9195         }
9196     }
9197 },
9198
9199 /**
9200  * Deletes the given row's Record from the RecordSet. If the row is on current page,
9201  * the corresponding DOM elements are also deleted.
9202  *
9203  * @method deleteRow
9204  * @param row {HTMLElement | String | Number} DOM element reference or ID string
9205  * to DataTable page element or RecordSet index.
9206  */
9207 deleteRow : function(row) {
9208     var nRecordIndex = (lang.isNumber(row)) ? row : this.getRecordIndex(row);
9209     if(lang.isNumber(nRecordIndex)) {
9210         var oRecord = this.getRecord(nRecordIndex);
9211         if(oRecord) {
9212             var nTrIndex = this.getTrIndex(nRecordIndex);
9213             
9214             // Remove from selection tracker if there
9215             var sRecordId = oRecord.getId();
9216             var tracker = this._aSelections || [];
9217             for(var j=tracker.length-1; j>-1; j--) {
9218                 if((lang.isString(tracker[j]) && (tracker[j] === sRecordId)) ||
9219                         (lang.isObject(tracker[j]) && (tracker[j].recordId === sRecordId))) {
9220                     tracker.splice(j,1);
9221                 }
9222             }
9223     
9224             // Delete Record from RecordSet
9225             var oData = this._oRecordSet.deleteRecord(nRecordIndex);
9226     
9227             // Update the UI
9228             if(oData) {
9229                 // If paginated and the deleted row was on this or a prior page, just
9230                 // re-render
9231                 var oPaginator = this.get('paginator');
9232                 if (oPaginator) {
9233                     // Update the paginator's totalRecords
9234                     var totalRecords = oPaginator.get('totalRecords'),
9235                         // must capture before the totalRecords change because
9236                         // Paginator shifts to previous page automatically
9237                         rng = oPaginator.getPageRecords();
9238
9239                     if (totalRecords !== widget.Paginator.VALUE_UNLIMITED) {
9240                         oPaginator.set('totalRecords',totalRecords - 1);
9241                     }
9242     
9243                     // The deleted record was on this or a prior page, re-render
9244                     if (!rng || nRecordIndex <= rng[1]) {
9245                         this.render();
9246                     }
9247
9248                     this._oChainRender.add({
9249                         method: function() {
9250                             if((this instanceof DT) && this._sId) {
9251                                 this.fireEvent("rowDeleteEvent", {recordIndex:nRecordIndex, oldData:oData, trElIndex:nTrIndex});
9252                             }
9253                         },
9254                         scope: this,
9255                         timeout: (this.get("renderLoopSize") > 0) ? 0 : -1
9256                     });
9257                     this._runRenderChain();
9258                 }
9259                 // Not paginated
9260                 else {
9261                     if(lang.isNumber(nTrIndex)) {
9262                         this._oChainRender.add({
9263                             method: function() {
9264                                 if((this instanceof DT) && this._sId) {
9265                                     var isLast = (nRecordIndex === this._oRecordSet.getLength());//(nTrIndex == this.getLastTrEl().sectionRowIndex);
9266                                     this._deleteTrEl(nTrIndex);
9267                     
9268                                     // Post-delete tasks
9269                                     if(this._elTbody.rows.length > 0) {
9270                                         // Set FIRST/LAST
9271                                         if(nTrIndex === 0) {
9272                                             this._setFirstRow();
9273                                         }
9274                                         if(isLast) {
9275                                             this._setLastRow();
9276                                         }
9277                                         // Set EVEN/ODD
9278                                         if(nTrIndex != this._elTbody.rows.length) {
9279                                             this._setRowStripes(nTrIndex);
9280                                         }                                
9281                                     }
9282                     
9283                                     this.fireEvent("rowDeleteEvent", {recordIndex:nRecordIndex,oldData:oData, trElIndex:nTrIndex});
9284                                 }
9285                             },
9286                             scope: this,
9287                             timeout: (this.get("renderLoopSize") > 0) ? 0 : -1
9288                         });
9289                         this._runRenderChain();
9290                         return;
9291                     }
9292                 }
9293             }
9294         }
9295     }
9296     return null;
9297 },
9298
9299 /**
9300  * Convenience method to delete multiple rows.
9301  *
9302  * @method deleteRows
9303  * @param row {HTMLElement | String | Number} DOM element reference or ID string
9304  * to DataTable page element or RecordSet index.
9305  * @param count {Number} (optional) How many rows to delete. A negative value
9306  * will delete towards the beginning.
9307  */
9308 deleteRows : function(row, count) {
9309     var nRecordIndex = (lang.isNumber(row)) ? row : this.getRecordIndex(row);
9310     if(lang.isNumber(nRecordIndex)) {
9311         var oRecord = this.getRecord(nRecordIndex);
9312         if(oRecord) {
9313             var nTrIndex = this.getTrIndex(nRecordIndex);
9314             
9315             // Remove from selection tracker if there
9316             var sRecordId = oRecord.getId();
9317             var tracker = this._aSelections || [];
9318             for(var j=tracker.length-1; j>-1; j--) {
9319                 if((lang.isString(tracker[j]) && (tracker[j] === sRecordId)) ||
9320                         (lang.isObject(tracker[j]) && (tracker[j].recordId === sRecordId))) {
9321                     tracker.splice(j,1);
9322                 }
9323             }
9324     
9325             // Delete Record from RecordSet
9326             var highIndex = nRecordIndex;
9327             var lowIndex = nRecordIndex;
9328         
9329             // Validate count and account for negative value
9330             if(count && lang.isNumber(count)) {
9331                 highIndex = (count > 0) ? nRecordIndex + count -1 : nRecordIndex;
9332                 lowIndex = (count > 0) ? nRecordIndex : nRecordIndex + count + 1;
9333                 count = (count > 0) ? count : count*-1;
9334                 if(lowIndex < 0) {
9335                     lowIndex = 0;
9336                     count = highIndex - lowIndex + 1;
9337                 }
9338             }
9339             else {
9340                 count = 1;
9341             }
9342             
9343             var aData = this._oRecordSet.deleteRecords(lowIndex, count);
9344     
9345             // Update the UI
9346             if(aData) {
9347                 var oPaginator = this.get('paginator'),
9348                     loopN = this.get("renderLoopSize");
9349                 // If paginated and the deleted row was on this or a prior page, just
9350                 // re-render
9351                 if (oPaginator) {
9352                     // Update the paginator's totalRecords
9353                     var totalRecords = oPaginator.get('totalRecords'),
9354                         // must capture before the totalRecords change because
9355                         // Paginator shifts to previous page automatically
9356                         rng = oPaginator.getPageRecords();
9357
9358                     if (totalRecords !== widget.Paginator.VALUE_UNLIMITED) {
9359                         oPaginator.set('totalRecords',totalRecords - aData.length);
9360                     }
9361     
9362                     // The records were on this or a prior page, re-render
9363                     if (!rng || lowIndex <= rng[1]) {
9364                         this.render();
9365                     }
9366
9367                     this._oChainRender.add({
9368                         method: function(oArg) {
9369                             if((this instanceof DT) && this._sId) {
9370                                 this.fireEvent("rowsDeleteEvent", {recordIndex:lowIndex, oldData:aData, count:count});
9371                             }
9372                         },
9373                         scope: this,
9374                         timeout: (loopN > 0) ? 0 : -1
9375                     });
9376                     this._runRenderChain();
9377                     return;
9378                 }
9379                 // Not paginated
9380                 else {
9381                     if(lang.isNumber(nTrIndex)) {
9382                         // Delete the TR elements starting with highest index
9383                         var loopEnd = lowIndex;
9384                         var nRowsNeeded = count; // how many needed
9385                         this._oChainRender.add({
9386                             method: function(oArg) {
9387                                 if((this instanceof DT) && this._sId) {
9388                                     var i = oArg.nCurrentRow,
9389                                         len = (loopN > 0) ? (Math.max(i - loopN,loopEnd)-1) : loopEnd-1;
9390                                     for(; i>len; --i) {
9391                                         this._deleteTrEl(i);
9392                                     }
9393                                     oArg.nCurrentRow = i;
9394                                 }
9395                             },
9396                             iterations: (loopN > 0) ? Math.ceil(count/loopN) : 1,
9397                             argument: {nCurrentRow:highIndex},
9398                             scope: this,
9399                             timeout: (loopN > 0) ? 0 : -1
9400                         });
9401                         this._oChainRender.add({
9402                             method: function() {    
9403                                 // Post-delete tasks
9404                                 if(this._elTbody.rows.length > 0) {
9405                                     this._setFirstRow();
9406                                     this._setLastRow();
9407                                     this._setRowStripes();
9408                                 }
9409                                 
9410                                 this.fireEvent("rowsDeleteEvent", {recordIndex:lowIndex, oldData:aData, count:count});
9411                             },
9412                             scope: this,
9413                             timeout: -1 // Needs to run immediately after the DOM deletions above
9414                         });
9415                         this._runRenderChain();
9416                         return;
9417                     }
9418                 }
9419             }
9420         }
9421     }
9422     return null;
9423 },
9424
9425
9426
9427
9428
9429
9430
9431
9432
9433
9434
9435
9436
9437
9438
9439
9440
9441
9442
9443
9444
9445
9446
9447
9448
9449
9450
9451
9452
9453
9454
9455
9456
9457
9458
9459
9460
9461
9462
9463
9464
9465
9466
9467
9468
9469
9470 // CELL FUNCTIONS
9471
9472 /**
9473  * Outputs markup into the given TD based on given Record.
9474  *
9475  * @method formatCell
9476  * @param elLiner {HTMLElement} The liner DIV element within the TD.
9477  * @param oRecord {YAHOO.widget.Record} (Optional) Record instance.
9478  * @param oColumn {YAHOO.widget.Column} (Optional) Column instance.
9479  */
9480 formatCell : function(elLiner, oRecord, oColumn) {
9481     if(!oRecord) {
9482         oRecord = this.getRecord(elLiner);
9483     }
9484     if(!oColumn) {
9485         oColumn = this.getColumn(elLiner.parentNode.cellIndex);
9486     }
9487
9488     if(oRecord && oColumn) {
9489         var sField = oColumn.field;
9490         var oData = oRecord.getData(sField);
9491
9492         var fnFormatter = typeof oColumn.formatter === 'function' ?
9493                           oColumn.formatter :
9494                           DT.Formatter[oColumn.formatter+''] ||
9495                           DT.Formatter.defaultFormatter;
9496
9497         // Apply special formatter
9498         if(fnFormatter) {
9499             fnFormatter.call(this, elLiner, oRecord, oColumn, oData);
9500         }
9501         else {
9502             elLiner.innerHTML = oData;
9503         }
9504
9505         this.fireEvent("cellFormatEvent", {record:oRecord, column:oColumn, key:oColumn.key, el:elLiner});
9506     }
9507     else {
9508     }
9509 },
9510
9511 /**
9512  * For the given row and column, updates the Record with the given data. If the
9513  * cell is on current page, the corresponding DOM elements are also updated.
9514  *
9515  * @method updateCell
9516  * @param oRecord {YAHOO.widget.Record} Record instance.
9517  * @param oColumn {YAHOO.widget.Column | String | Number} A Column key, or a ColumnSet key index.
9518  * @param oData {Object} New data value for the cell.
9519  */
9520 updateCell : function(oRecord, oColumn, oData) {    
9521     // Validate Column and Record
9522     oColumn = (oColumn instanceof YAHOO.widget.Column) ? oColumn : this.getColumn(oColumn);
9523     if(oColumn && oColumn.getField() && (oRecord instanceof YAHOO.widget.Record)) {
9524         var sKey = oColumn.getField(),
9525         
9526         // Copy data from the Record for the event that gets fired later
9527         //var oldData = YAHOO.widget.DataTable._cloneObject(oRecord.getData());
9528             oldData = oRecord.getData(sKey);
9529
9530         // Update Record with new data
9531         this._oRecordSet.updateRecordValue(oRecord, sKey, oData);
9532     
9533         // Update the TD only if row is on current page
9534         var elTd = this.getTdEl({record: oRecord, column: oColumn});
9535         if(elTd) {
9536             this._oChainRender.add({
9537                 method: function() {
9538                     if((this instanceof DT) && this._sId) {
9539                         this.formatCell(elTd.firstChild);
9540                         this.fireEvent("cellUpdateEvent", {record:oRecord, column: oColumn, oldData:oldData});
9541                     }
9542                 },
9543                 scope: this,
9544                 timeout: (this.get("renderLoopSize") > 0) ? 0 : -1
9545             });
9546             this._runRenderChain();
9547         }
9548         else {
9549             this.fireEvent("cellUpdateEvent", {record:oRecord, column: oColumn, oldData:oldData});
9550         }
9551     }
9552 },
9553
9554
9555
9556
9557
9558
9559
9560
9561
9562
9563
9564
9565
9566
9567
9568
9569
9570
9571
9572
9573
9574
9575
9576
9577
9578
9579
9580
9581
9582
9583
9584
9585
9586
9587
9588
9589
9590
9591
9592
9593
9594
9595
9596
9597
9598
9599
9600
9601
9602
9603
9604 // PAGINATION
9605 /**
9606  * Method executed during set() operation for the "paginator" attribute.
9607  * Adds and/or severs event listeners between DataTable and Paginator
9608  *
9609  * @method _updatePaginator
9610  * @param newPag {Paginator} Paginator instance (or null) for DataTable to use
9611  * @private
9612  */
9613 _updatePaginator : function (newPag) {
9614     var oldPag = this.get('paginator');
9615     if (oldPag && newPag !== oldPag) {
9616         oldPag.unsubscribe('changeRequest', this.onPaginatorChangeRequest, this, true);
9617     }
9618     if (newPag) {
9619         newPag.subscribe('changeRequest', this.onPaginatorChangeRequest, this, true);
9620     }
9621 },
9622
9623 /**
9624  * Update the UI infrastructure in response to a "paginator" attribute change.
9625  *
9626  * @method _handlePaginatorChange
9627  * @param e {Object} Change event object containing keys 'type','newValue',
9628  *                   and 'prevValue'
9629  * @private
9630  */
9631 _handlePaginatorChange : function (e) {
9632     if (e.prevValue === e.newValue) { return; }
9633
9634     var newPag     = e.newValue,
9635         oldPag     = e.prevValue,
9636         containers = this._defaultPaginatorContainers();
9637
9638     if (oldPag) {
9639         if (oldPag.getContainerNodes()[0] == containers[0]) {
9640             oldPag.set('containers',[]);
9641         }
9642         oldPag.destroy();
9643
9644         // Convenience: share the default containers if possible.
9645         // Otherwise, remove the default containers from the DOM.
9646         if (containers[0]) {
9647             if (newPag && !newPag.getContainerNodes().length) {
9648                 newPag.set('containers',containers);
9649             } else {
9650                 // No new Paginator to use existing containers, OR new
9651                 // Paginator has configured containers.
9652                 for (var i = containers.length - 1; i >= 0; --i) {
9653                     if (containers[i]) {
9654                         containers[i].parentNode.removeChild(containers[i]);
9655                     }
9656                 }
9657             }
9658         }
9659     }
9660
9661     if (!this._bInit) {
9662         this.render();
9663
9664     }
9665
9666     if (newPag) {
9667         this.renderPaginator();
9668     }
9669
9670 },
9671
9672 /**
9673  * Returns the default containers used for Paginators.  If create param is
9674  * passed, the containers will be created and added to the DataTable container.
9675  *
9676  * @method _defaultPaginatorContainers
9677  * @param create {boolean} Create the default containers if not found
9678  * @private
9679  */
9680 _defaultPaginatorContainers : function (create) {
9681     var above_id = this._sId + '-paginator0',
9682         below_id = this._sId + '-paginator1',
9683         above    = Dom.get(above_id),
9684         below    = Dom.get(below_id);
9685
9686     if (create && (!above || !below)) {
9687         // One above and one below the table
9688         if (!above) {
9689             above    = document.createElement('div');
9690             above.id = above_id;
9691             Dom.addClass(above, DT.CLASS_PAGINATOR);
9692
9693             this._elContainer.insertBefore(above,this._elContainer.firstChild);
9694         }
9695
9696         if (!below) {
9697             below    = document.createElement('div');
9698             below.id = below_id;
9699             Dom.addClass(below, DT.CLASS_PAGINATOR);
9700
9701             this._elContainer.appendChild(below);
9702         }
9703     }
9704
9705     return [above,below];
9706 },
9707
9708 /**
9709  * Calls Paginator's destroy() method
9710  *
9711  * @method _destroyPaginator
9712  * @private
9713  */
9714 _destroyPaginator : function () {
9715     var oldPag = this.get('paginator');
9716     if (oldPag) {
9717         oldPag.destroy();
9718     }
9719 },
9720
9721 /**
9722  * Renders the Paginator to the DataTable UI
9723  *
9724  * @method renderPaginator
9725  */
9726 renderPaginator : function () {
9727     var pag = this.get("paginator");
9728     if (!pag) { return; }
9729
9730     // Add the containers if the Paginator is not configured with containers
9731     if (!pag.getContainerNodes().length) {
9732         pag.set('containers',this._defaultPaginatorContainers(true));
9733     }
9734
9735     pag.render();
9736 },
9737
9738 /**
9739  * Overridable method gives implementers a hook to show loading message before
9740  * changing Paginator value.
9741  *
9742  * @method doBeforePaginatorChange
9743  * @param oPaginatorState {Object} An object literal describing the proposed pagination state.
9744  * @return {Boolean} Return true to continue changing Paginator value.
9745  */
9746 doBeforePaginatorChange : function(oPaginatorState) {
9747     this.showTableMessage(this.get("MSG_LOADING"), DT.CLASS_LOADING);
9748     return true;
9749 },
9750
9751 /**
9752  * Responds to new Pagination states. By default, updates the UI to reflect the
9753  * new state. If "dynamicData" is true, current selections are purged before
9754  * a request is sent to the DataSource for data for the new state (using the
9755  * request returned by "generateRequest()").
9756  *  
9757  * @method onPaginatorChangeRequest
9758  * @param oPaginatorState {Object} An object literal describing the proposed pagination state.
9759  */
9760 onPaginatorChangeRequest : function (oPaginatorState) {
9761     var ok = this.doBeforePaginatorChange(oPaginatorState);
9762     if(ok) {
9763         // Server-side pagination
9764         if(this.get("dynamicData")) {
9765             // Get the current state
9766             var oState = this.getState();
9767             
9768             // Update pagination values
9769             oState.pagination = oPaginatorState;
9770     
9771             // Get the request for the new state
9772             var request = this.get("generateRequest")(oState, this);
9773             
9774             // Purge selections
9775             this.unselectAllRows();
9776             this.unselectAllCells();
9777             
9778             // Get the new data from the server
9779             var callback = {
9780                 success : this.onDataReturnSetRows,
9781                 failure : this.onDataReturnSetRows,
9782                 argument : oState, // Pass along the new state to the callback
9783                 scope : this
9784             };
9785             this._oDataSource.sendRequest(request, callback);
9786         }
9787         // Client-side pagination
9788         else {
9789             // Set the core pagination values silently (the second param)
9790             // to avoid looping back through the changeRequest mechanism
9791             oPaginatorState.paginator.setStartIndex(oPaginatorState.recordOffset,true);
9792             oPaginatorState.paginator.setRowsPerPage(oPaginatorState.rowsPerPage,true);
9793     
9794             // Update the UI
9795             this.render();
9796         }
9797     }
9798     else {
9799     }
9800 },
9801
9802
9803
9804
9805
9806
9807
9808
9809
9810
9811
9812
9813
9814
9815
9816
9817
9818
9819
9820
9821
9822
9823
9824
9825
9826
9827
9828
9829
9830
9831
9832
9833
9834
9835
9836
9837
9838
9839
9840
9841
9842
9843
9844
9845
9846
9847
9848
9849
9850
9851 // SELECTION/HIGHLIGHTING
9852
9853 /*
9854  * Reference to last highlighted cell element
9855  *
9856  * @property _elLastHighlightedTd
9857  * @type HTMLElement
9858  * @private
9859  */
9860 _elLastHighlightedTd : null,
9861
9862 /*
9863  * ID string of last highlighted row element
9864  *
9865  * @property _sLastHighlightedTrElId
9866  * @type String
9867  * @private
9868  */
9869 //_sLastHighlightedTrElId : null,
9870
9871 /**
9872  * Array to track row selections (by sRecordId) and/or cell selections
9873  * (by {recordId:sRecordId, columnKey:sColumnKey})
9874  *
9875  * @property _aSelections
9876  * @type Object[]
9877  * @private
9878  */
9879 _aSelections : null,
9880
9881 /**
9882  * Record instance of the row selection anchor.
9883  *
9884  * @property _oAnchorRecord
9885  * @type YAHOO.widget.Record
9886  * @private
9887  */
9888 _oAnchorRecord : null,
9889
9890 /**
9891  * Object literal representing cell selection anchor:
9892  * {recordId:sRecordId, columnKey:sColumnKey}.
9893  *
9894  * @property _oAnchorCell
9895  * @type Object
9896  * @private
9897  */
9898 _oAnchorCell : null,
9899
9900 /**
9901  * Convenience method to remove the class YAHOO.widget.DataTable.CLASS_SELECTED
9902  * from all TR elements on the page.
9903  *
9904  * @method _unselectAllTrEls
9905  * @private
9906  */
9907 _unselectAllTrEls : function() {
9908     var selectedRows = Dom.getElementsByClassName(DT.CLASS_SELECTED,"tr",this._elTbody);
9909     Dom.removeClass(selectedRows, DT.CLASS_SELECTED);
9910 },
9911
9912 /**
9913  * Returns object literal of values that represent the selection trigger. Used
9914  * to determine selection behavior resulting from a key event.
9915  *
9916  * @method _getSelectionTrigger
9917  * @private
9918  */
9919 _getSelectionTrigger : function() {
9920     var sMode = this.get("selectionMode");
9921     var oTrigger = {};
9922     var oTriggerCell, oTriggerRecord, nTriggerRecordIndex, elTriggerRow, nTriggerTrIndex;
9923
9924     // Cell mode
9925     if((sMode == "cellblock") || (sMode == "cellrange") || (sMode == "singlecell")) {
9926         oTriggerCell = this.getLastSelectedCell();
9927         // No selected cells found
9928         if(!oTriggerCell) {
9929             return null;
9930         }
9931         else {
9932             oTriggerRecord = this.getRecord(oTriggerCell.recordId);
9933             nTriggerRecordIndex = this.getRecordIndex(oTriggerRecord);
9934             elTriggerRow = this.getTrEl(oTriggerRecord);
9935             nTriggerTrIndex = this.getTrIndex(elTriggerRow);
9936
9937             // Selected cell not found on this page
9938             if(nTriggerTrIndex === null) {
9939                 return null;
9940             }
9941             else {
9942                 oTrigger.record = oTriggerRecord;
9943                 oTrigger.recordIndex = nTriggerRecordIndex;
9944                 oTrigger.el = this.getTdEl(oTriggerCell);
9945                 oTrigger.trIndex = nTriggerTrIndex;
9946                 oTrigger.column = this.getColumn(oTriggerCell.columnKey);
9947                 oTrigger.colKeyIndex = oTrigger.column.getKeyIndex();
9948                 oTrigger.cell = oTriggerCell;
9949                 return oTrigger;
9950             }
9951         }
9952     }
9953     // Row mode
9954     else {
9955         oTriggerRecord = this.getLastSelectedRecord();
9956         // No selected rows found
9957         if(!oTriggerRecord) {
9958                 return null;
9959         }
9960         else {
9961             // Selected row found, but is it on current page?
9962             oTriggerRecord = this.getRecord(oTriggerRecord);
9963             nTriggerRecordIndex = this.getRecordIndex(oTriggerRecord);
9964             elTriggerRow = this.getTrEl(oTriggerRecord);
9965             nTriggerTrIndex = this.getTrIndex(elTriggerRow);
9966
9967             // Selected row not found on this page
9968             if(nTriggerTrIndex === null) {
9969                 return null;
9970             }
9971             else {
9972                 oTrigger.record = oTriggerRecord;
9973                 oTrigger.recordIndex = nTriggerRecordIndex;
9974                 oTrigger.el = elTriggerRow;
9975                 oTrigger.trIndex = nTriggerTrIndex;
9976                 return oTrigger;
9977             }
9978         }
9979     }
9980 },
9981
9982 /**
9983  * Returns object literal of values that represent the selection anchor. Used
9984  * to determine selection behavior resulting from a user event.
9985  *
9986  * @method _getSelectionAnchor
9987  * @param oTrigger {Object} (Optional) Object literal of selection trigger values
9988  * (for key events).
9989  * @private
9990  */
9991 _getSelectionAnchor : function(oTrigger) {
9992     var sMode = this.get("selectionMode");
9993     var oAnchor = {};
9994     var oAnchorRecord, nAnchorRecordIndex, nAnchorTrIndex;
9995
9996     // Cell mode
9997     if((sMode == "cellblock") || (sMode == "cellrange") || (sMode == "singlecell")) {
9998         // Validate anchor cell
9999         var oAnchorCell = this._oAnchorCell;
10000         if(!oAnchorCell) {
10001             if(oTrigger) {
10002                 oAnchorCell = this._oAnchorCell = oTrigger.cell;
10003             }
10004             else {
10005                 return null;
10006             }
10007         }
10008         oAnchorRecord = this._oAnchorCell.record;
10009         nAnchorRecordIndex = this._oRecordSet.getRecordIndex(oAnchorRecord);
10010         nAnchorTrIndex = this.getTrIndex(oAnchorRecord);
10011         // If anchor cell is not on this page...
10012         if(nAnchorTrIndex === null) {
10013             // ...set TR index equal to top TR
10014             if(nAnchorRecordIndex < this.getRecordIndex(this.getFirstTrEl())) {
10015                 nAnchorTrIndex = 0;
10016             }
10017             // ...set TR index equal to bottom TR
10018             else {
10019                 nAnchorTrIndex = this.getRecordIndex(this.getLastTrEl());
10020             }
10021         }
10022
10023         oAnchor.record = oAnchorRecord;
10024         oAnchor.recordIndex = nAnchorRecordIndex;
10025         oAnchor.trIndex = nAnchorTrIndex;
10026         oAnchor.column = this._oAnchorCell.column;
10027         oAnchor.colKeyIndex = oAnchor.column.getKeyIndex();
10028         oAnchor.cell = oAnchorCell;
10029         return oAnchor;
10030     }
10031     // Row mode
10032     else {
10033         oAnchorRecord = this._oAnchorRecord;
10034         if(!oAnchorRecord) {
10035             if(oTrigger) {
10036                 oAnchorRecord = this._oAnchorRecord = oTrigger.record;
10037             }
10038             else {
10039                 return null;
10040             }
10041         }
10042
10043         nAnchorRecordIndex = this.getRecordIndex(oAnchorRecord);
10044         nAnchorTrIndex = this.getTrIndex(oAnchorRecord);
10045         // If anchor row is not on this page...
10046         if(nAnchorTrIndex === null) {
10047             // ...set TR index equal to top TR
10048             if(nAnchorRecordIndex < this.getRecordIndex(this.getFirstTrEl())) {
10049                 nAnchorTrIndex = 0;
10050             }
10051             // ...set TR index equal to bottom TR
10052             else {
10053                 nAnchorTrIndex = this.getRecordIndex(this.getLastTrEl());
10054             }
10055         }
10056
10057         oAnchor.record = oAnchorRecord;
10058         oAnchor.recordIndex = nAnchorRecordIndex;
10059         oAnchor.trIndex = nAnchorTrIndex;
10060         return oAnchor;
10061     }
10062 },
10063
10064 /**
10065  * Determines selection behavior resulting from a mouse event when selection mode
10066  * is set to "standard".
10067  *
10068  * @method _handleStandardSelectionByMouse
10069  * @param oArgs.event {HTMLEvent} Event object.
10070  * @param oArgs.target {HTMLElement} Target element.
10071  * @private
10072  */
10073 _handleStandardSelectionByMouse : function(oArgs) {
10074     var elTarget = oArgs.target;
10075
10076     // Validate target row
10077     var elTargetRow = this.getTrEl(elTarget);
10078     if(elTargetRow) {
10079         var e = oArgs.event;
10080         var bSHIFT = e.shiftKey;
10081         var bCTRL = e.ctrlKey || ((navigator.userAgent.toLowerCase().indexOf("mac") != -1) && e.metaKey);
10082
10083         var oTargetRecord = this.getRecord(elTargetRow);
10084         var nTargetRecordIndex = this._oRecordSet.getRecordIndex(oTargetRecord);
10085
10086         var oAnchor = this._getSelectionAnchor();
10087
10088         var i;
10089
10090         // Both SHIFT and CTRL
10091         if(bSHIFT && bCTRL) {
10092             // Validate anchor
10093             if(oAnchor) {
10094                 if(this.isSelected(oAnchor.record)) {
10095                     // Select all rows between anchor row and target row, including target row
10096                     if(oAnchor.recordIndex < nTargetRecordIndex) {
10097                         for(i=oAnchor.recordIndex+1; i<=nTargetRecordIndex; i++) {
10098                             if(!this.isSelected(i)) {
10099                                 this.selectRow(i);
10100                             }
10101                         }
10102                     }
10103                     // Select all rows between target row and anchor row, including target row
10104                     else {
10105                         for(i=oAnchor.recordIndex-1; i>=nTargetRecordIndex; i--) {
10106                             if(!this.isSelected(i)) {
10107                                 this.selectRow(i);
10108                             }
10109                         }
10110                     }
10111                 }
10112                 else {
10113                     // Unselect all rows between anchor row and target row
10114                     if(oAnchor.recordIndex < nTargetRecordIndex) {
10115                         for(i=oAnchor.recordIndex+1; i<=nTargetRecordIndex-1; i++) {
10116                             if(this.isSelected(i)) {
10117                                 this.unselectRow(i);
10118                             }
10119                         }
10120                     }
10121                     // Unselect all rows between target row and anchor row
10122                     else {
10123                         for(i=nTargetRecordIndex+1; i<=oAnchor.recordIndex-1; i++) {
10124                             if(this.isSelected(i)) {
10125                                 this.unselectRow(i);
10126                             }
10127                         }
10128                     }
10129                     // Select the target row
10130                     this.selectRow(oTargetRecord);
10131                 }
10132             }
10133             // Invalid anchor
10134             else {
10135                 // Set anchor
10136                 this._oAnchorRecord = oTargetRecord;
10137
10138                 // Toggle selection of target
10139                 if(this.isSelected(oTargetRecord)) {
10140                     this.unselectRow(oTargetRecord);
10141                 }
10142                 else {
10143                     this.selectRow(oTargetRecord);
10144                 }
10145             }
10146         }
10147          // Only SHIFT
10148         else if(bSHIFT) {
10149             this.unselectAllRows();
10150
10151             // Validate anchor
10152             if(oAnchor) {
10153                 // Select all rows between anchor row and target row,
10154                 // including the anchor row and target row
10155                 if(oAnchor.recordIndex < nTargetRecordIndex) {
10156                     for(i=oAnchor.recordIndex; i<=nTargetRecordIndex; i++) {
10157                         this.selectRow(i);
10158                     }
10159                 }
10160                 // Select all rows between target row and anchor row,
10161                 // including the target row and anchor row
10162                 else {
10163                     for(i=oAnchor.recordIndex; i>=nTargetRecordIndex; i--) {
10164                         this.selectRow(i);
10165                     }
10166                 }
10167             }
10168             // Invalid anchor
10169             else {
10170                 // Set anchor
10171                 this._oAnchorRecord = oTargetRecord;
10172
10173                 // Select target row only
10174                 this.selectRow(oTargetRecord);
10175             }
10176         }
10177         // Only CTRL
10178         else if(bCTRL) {
10179             // Set anchor
10180             this._oAnchorRecord = oTargetRecord;
10181
10182             // Toggle selection of target
10183             if(this.isSelected(oTargetRecord)) {
10184                 this.unselectRow(oTargetRecord);
10185             }
10186             else {
10187                 this.selectRow(oTargetRecord);
10188             }
10189         }
10190         // Neither SHIFT nor CTRL
10191         else {
10192             this._handleSingleSelectionByMouse(oArgs);
10193             return;
10194         }
10195     }
10196 },
10197
10198 /**
10199  * Determines selection behavior resulting from a key event when selection mode
10200  * is set to "standard".
10201  *
10202  * @method _handleStandardSelectionByKey
10203  * @param e {HTMLEvent} Event object.
10204  * @private
10205  */
10206 _handleStandardSelectionByKey : function(e) {
10207     var nKey = Ev.getCharCode(e);
10208
10209     if((nKey == 38) || (nKey == 40)) {
10210         var bSHIFT = e.shiftKey;
10211
10212         // Validate trigger
10213         var oTrigger = this._getSelectionTrigger();
10214         // Arrow selection only works if last selected row is on current page
10215         if(!oTrigger) {
10216             return null;
10217         }
10218
10219         Ev.stopEvent(e);
10220
10221         // Validate anchor
10222         var oAnchor = this._getSelectionAnchor(oTrigger);
10223
10224         // Determine which direction we're going to
10225         if(bSHIFT) {
10226             // Selecting down away from anchor row
10227             if((nKey == 40) && (oAnchor.recordIndex <= oTrigger.trIndex)) {
10228                 this.selectRow(this.getNextTrEl(oTrigger.el));
10229             }
10230             // Selecting up away from anchor row
10231             else if((nKey == 38) && (oAnchor.recordIndex >= oTrigger.trIndex)) {
10232                 this.selectRow(this.getPreviousTrEl(oTrigger.el));
10233             }
10234             // Unselect trigger
10235             else {
10236                 this.unselectRow(oTrigger.el);
10237             }
10238         }
10239         else {
10240             this._handleSingleSelectionByKey(e);
10241         }
10242     }
10243 },
10244
10245 /**
10246  * Determines selection behavior resulting from a mouse event when selection mode
10247  * is set to "single".
10248  *
10249  * @method _handleSingleSelectionByMouse
10250  * @param oArgs.event {HTMLEvent} Event object.
10251  * @param oArgs.target {HTMLElement} Target element.
10252  * @private
10253  */
10254 _handleSingleSelectionByMouse : function(oArgs) {
10255     var elTarget = oArgs.target;
10256
10257     // Validate target row
10258     var elTargetRow = this.getTrEl(elTarget);
10259     if(elTargetRow) {
10260         var oTargetRecord = this.getRecord(elTargetRow);
10261
10262         // Set anchor
10263         this._oAnchorRecord = oTargetRecord;
10264
10265         // Select only target
10266         this.unselectAllRows();
10267         this.selectRow(oTargetRecord);
10268     }
10269 },
10270
10271 /**
10272  * Determines selection behavior resulting from a key event when selection mode
10273  * is set to "single".
10274  *
10275  * @method _handleSingleSelectionByKey
10276  * @param e {HTMLEvent} Event object.
10277  * @private
10278  */
10279 _handleSingleSelectionByKey : function(e) {
10280     var nKey = Ev.getCharCode(e);
10281
10282     if((nKey == 38) || (nKey == 40)) {
10283         // Validate trigger
10284         var oTrigger = this._getSelectionTrigger();
10285         // Arrow selection only works if last selected row is on current page
10286         if(!oTrigger) {
10287             return null;
10288         }
10289
10290         Ev.stopEvent(e);
10291
10292         // Determine the new row to select
10293         var elNew;
10294         if(nKey == 38) { // arrow up
10295             elNew = this.getPreviousTrEl(oTrigger.el);
10296
10297             // Validate new row
10298             if(elNew === null) {
10299                 //TODO: wrap around to last tr on current page
10300                 //elNew = this.getLastTrEl();
10301
10302                 //TODO: wrap back to last tr of previous page
10303
10304                 // Top row selection is sticky
10305                 elNew = this.getFirstTrEl();
10306             }
10307         }
10308         else if(nKey == 40) { // arrow down
10309             elNew = this.getNextTrEl(oTrigger.el);
10310
10311             // Validate new row
10312             if(elNew === null) {
10313                 //TODO: wrap around to first tr on current page
10314                 //elNew = this.getFirstTrEl();
10315
10316                 //TODO: wrap forward to first tr of previous page
10317
10318                 // Bottom row selection is sticky
10319                 elNew = this.getLastTrEl();
10320             }
10321         }
10322
10323         // Unselect all rows
10324         this.unselectAllRows();
10325
10326         // Select the new row
10327         this.selectRow(elNew);
10328
10329         // Set new anchor
10330         this._oAnchorRecord = this.getRecord(elNew);
10331     }
10332 },
10333
10334 /**
10335  * Determines selection behavior resulting from a mouse event when selection mode
10336  * is set to "cellblock".
10337  *
10338  * @method _handleCellBlockSelectionByMouse
10339  * @param oArgs.event {HTMLEvent} Event object.
10340  * @param oArgs.target {HTMLElement} Target element.
10341  * @private
10342  */
10343 _handleCellBlockSelectionByMouse : function(oArgs) {
10344     var elTarget = oArgs.target;
10345
10346     // Validate target cell
10347     var elTargetCell = this.getTdEl(elTarget);
10348     if(elTargetCell) {
10349         var e = oArgs.event;
10350         var bSHIFT = e.shiftKey;
10351         var bCTRL = e.ctrlKey || ((navigator.userAgent.toLowerCase().indexOf("mac") != -1) && e.metaKey);
10352
10353         var elTargetRow = this.getTrEl(elTargetCell);
10354         var nTargetTrIndex = this.getTrIndex(elTargetRow);
10355         var oTargetColumn = this.getColumn(elTargetCell);
10356         var nTargetColKeyIndex = oTargetColumn.getKeyIndex();
10357         var oTargetRecord = this.getRecord(elTargetRow);
10358         var nTargetRecordIndex = this._oRecordSet.getRecordIndex(oTargetRecord);
10359         var oTargetCell = {record:oTargetRecord, column:oTargetColumn};
10360
10361         var oAnchor = this._getSelectionAnchor();
10362
10363         var allRows = this.getTbodyEl().rows;
10364         var startIndex, endIndex, currentRow, i, j;
10365
10366         // Both SHIFT and CTRL
10367         if(bSHIFT && bCTRL) {
10368
10369             // Validate anchor
10370             if(oAnchor) {
10371                 // Anchor is selected
10372                 if(this.isSelected(oAnchor.cell)) {
10373                     // All cells are on the same row
10374                     if(oAnchor.recordIndex === nTargetRecordIndex) {
10375                         // Select all cells between anchor cell and target cell, including target cell
10376                         if(oAnchor.colKeyIndex < nTargetColKeyIndex) {
10377                             for(i=oAnchor.colKeyIndex+1; i<=nTargetColKeyIndex; i++) {
10378                                 this.selectCell(elTargetRow.cells[i]);
10379                             }
10380                         }
10381                         // Select all cells between target cell and anchor cell, including target cell
10382                         else if(nTargetColKeyIndex < oAnchor.colKeyIndex) {
10383                             for(i=nTargetColKeyIndex; i<oAnchor.colKeyIndex; i++) {
10384                                 this.selectCell(elTargetRow.cells[i]);
10385                             }
10386                         }
10387                     }
10388                     // Anchor row is above target row
10389                     else if(oAnchor.recordIndex < nTargetRecordIndex) {
10390                         startIndex = Math.min(oAnchor.colKeyIndex, nTargetColKeyIndex);
10391                         endIndex = Math.max(oAnchor.colKeyIndex, nTargetColKeyIndex);
10392
10393                         // Select all cells from startIndex to endIndex on rows between anchor row and target row
10394                         for(i=oAnchor.trIndex; i<=nTargetTrIndex; i++) {
10395                             for(j=startIndex; j<=endIndex; j++) {
10396                                 this.selectCell(allRows[i].cells[j]);
10397                             }
10398                         }
10399                     }
10400                     // Anchor row is below target row
10401                     else {
10402                         startIndex = Math.min(oAnchor.trIndex, nTargetColKeyIndex);
10403                         endIndex = Math.max(oAnchor.trIndex, nTargetColKeyIndex);
10404
10405                         // Select all cells from startIndex to endIndex on rows between target row and anchor row
10406                         for(i=oAnchor.trIndex; i>=nTargetTrIndex; i--) {
10407                             for(j=endIndex; j>=startIndex; j--) {
10408                                 this.selectCell(allRows[i].cells[j]);
10409                             }
10410                         }
10411                     }
10412                 }
10413                 // Anchor cell is unselected
10414                 else {
10415                     // All cells are on the same row
10416                     if(oAnchor.recordIndex === nTargetRecordIndex) {
10417                         // Unselect all cells between anchor cell and target cell
10418                         if(oAnchor.colKeyIndex < nTargetColKeyIndex) {
10419                             for(i=oAnchor.colKeyIndex+1; i<nTargetColKeyIndex; i++) {
10420                                 this.unselectCell(elTargetRow.cells[i]);
10421                             }
10422                         }
10423                         // Select all cells between target cell and anchor cell
10424                         else if(nTargetColKeyIndex < oAnchor.colKeyIndex) {
10425                             for(i=nTargetColKeyIndex+1; i<oAnchor.colKeyIndex; i++) {
10426                                 this.unselectCell(elTargetRow.cells[i]);
10427                             }
10428                         }
10429                     }
10430                     // Anchor row is above target row
10431                     if(oAnchor.recordIndex < nTargetRecordIndex) {
10432                         // Unselect all cells from anchor cell to target cell
10433                         for(i=oAnchor.trIndex; i<=nTargetTrIndex; i++) {
10434                             currentRow = allRows[i];
10435                             for(j=0; j<currentRow.cells.length; j++) {
10436                                 // This is the anchor row, only unselect cells after the anchor cell
10437                                 if(currentRow.sectionRowIndex === oAnchor.trIndex) {
10438                                     if(j>oAnchor.colKeyIndex) {
10439                                         this.unselectCell(currentRow.cells[j]);
10440                                     }
10441                                 }
10442                                 // This is the target row, only unelect cells before the target cell
10443                                 else if(currentRow.sectionRowIndex === nTargetTrIndex) {
10444                                     if(j<nTargetColKeyIndex) {
10445                                         this.unselectCell(currentRow.cells[j]);
10446                                     }
10447                                 }
10448                                 // Unselect all cells on this row
10449                                 else {
10450                                     this.unselectCell(currentRow.cells[j]);
10451                                 }
10452                             }
10453                         }
10454                     }
10455                     // Anchor row is below target row
10456                     else {
10457                         // Unselect all cells from target cell to anchor cell
10458                         for(i=nTargetTrIndex; i<=oAnchor.trIndex; i++) {
10459                             currentRow = allRows[i];
10460                             for(j=0; j<currentRow.cells.length; j++) {
10461                                 // This is the target row, only unselect cells after the target cell
10462                                 if(currentRow.sectionRowIndex == nTargetTrIndex) {
10463                                     if(j>nTargetColKeyIndex) {
10464                                         this.unselectCell(currentRow.cells[j]);
10465                                     }
10466                                 }
10467                                 // This is the anchor row, only unselect cells before the anchor cell
10468                                 else if(currentRow.sectionRowIndex == oAnchor.trIndex) {
10469                                     if(j<oAnchor.colKeyIndex) {
10470                                         this.unselectCell(currentRow.cells[j]);
10471                                     }
10472                                 }
10473                                 // Unselect all cells on this row
10474                                 else {
10475                                     this.unselectCell(currentRow.cells[j]);
10476                                 }
10477                             }
10478                         }
10479                     }
10480
10481                     // Select the target cell
10482                     this.selectCell(elTargetCell);
10483                 }
10484             }
10485             // Invalid anchor
10486             else {
10487                 // Set anchor
10488                 this._oAnchorCell = oTargetCell;
10489
10490                 // Toggle selection of target
10491                 if(this.isSelected(oTargetCell)) {
10492                     this.unselectCell(oTargetCell);
10493                 }
10494                 else {
10495                     this.selectCell(oTargetCell);
10496                 }
10497             }
10498
10499         }
10500          // Only SHIFT
10501         else if(bSHIFT) {
10502             this.unselectAllCells();
10503
10504             // Validate anchor
10505             if(oAnchor) {
10506                 // All cells are on the same row
10507                 if(oAnchor.recordIndex === nTargetRecordIndex) {
10508                     // Select all cells between anchor cell and target cell,
10509                     // including the anchor cell and target cell
10510                     if(oAnchor.colKeyIndex < nTargetColKeyIndex) {
10511                         for(i=oAnchor.colKeyIndex; i<=nTargetColKeyIndex; i++) {
10512                             this.selectCell(elTargetRow.cells[i]);
10513                         }
10514                     }
10515                     // Select all cells between target cell and anchor cell
10516                     // including the target cell and anchor cell
10517                     else if(nTargetColKeyIndex < oAnchor.colKeyIndex) {
10518                         for(i=nTargetColKeyIndex; i<=oAnchor.colKeyIndex; i++) {
10519                             this.selectCell(elTargetRow.cells[i]);
10520                         }
10521                     }
10522                 }
10523                 // Anchor row is above target row
10524                 else if(oAnchor.recordIndex < nTargetRecordIndex) {
10525                     // Select the cellblock from anchor cell to target cell
10526                     // including the anchor cell and the target cell
10527                     startIndex = Math.min(oAnchor.colKeyIndex, nTargetColKeyIndex);
10528                     endIndex = Math.max(oAnchor.colKeyIndex, nTargetColKeyIndex);
10529
10530                     for(i=oAnchor.trIndex; i<=nTargetTrIndex; i++) {
10531                         for(j=startIndex; j<=endIndex; j++) {
10532                             this.selectCell(allRows[i].cells[j]);
10533                         }
10534                     }
10535                 }
10536                 // Anchor row is below target row
10537                 else {
10538                     // Select the cellblock from target cell to anchor cell
10539                     // including the target cell and the anchor cell
10540                     startIndex = Math.min(oAnchor.colKeyIndex, nTargetColKeyIndex);
10541                     endIndex = Math.max(oAnchor.colKeyIndex, nTargetColKeyIndex);
10542
10543                     for(i=nTargetTrIndex; i<=oAnchor.trIndex; i++) {
10544                         for(j=startIndex; j<=endIndex; j++) {
10545                             this.selectCell(allRows[i].cells[j]);
10546                         }
10547                     }
10548                 }
10549             }
10550             // Invalid anchor
10551             else {
10552                 // Set anchor
10553                 this._oAnchorCell = oTargetCell;
10554
10555                 // Select target only
10556                 this.selectCell(oTargetCell);
10557             }
10558         }
10559         // Only CTRL
10560         else if(bCTRL) {
10561
10562             // Set anchor
10563             this._oAnchorCell = oTargetCell;
10564
10565             // Toggle selection of target
10566             if(this.isSelected(oTargetCell)) {
10567                 this.unselectCell(oTargetCell);
10568             }
10569             else {
10570                 this.selectCell(oTargetCell);
10571             }
10572
10573         }
10574         // Neither SHIFT nor CTRL
10575         else {
10576             this._handleSingleCellSelectionByMouse(oArgs);
10577         }
10578     }
10579 },
10580
10581 /**
10582  * Determines selection behavior resulting from a key event when selection mode
10583  * is set to "cellblock".
10584  *
10585  * @method _handleCellBlockSelectionByKey
10586  * @param e {HTMLEvent} Event object.
10587  * @private
10588  */
10589 _handleCellBlockSelectionByKey : function(e) {
10590     var nKey = Ev.getCharCode(e);
10591     var bSHIFT = e.shiftKey;
10592     if((nKey == 9) || !bSHIFT) {
10593         this._handleSingleCellSelectionByKey(e);
10594         return;
10595     }
10596
10597     if((nKey > 36) && (nKey < 41)) {
10598         // Validate trigger
10599         var oTrigger = this._getSelectionTrigger();
10600         // Arrow selection only works if last selected row is on current page
10601         if(!oTrigger) {
10602             return null;
10603         }
10604
10605         Ev.stopEvent(e);
10606
10607         // Validate anchor
10608         var oAnchor = this._getSelectionAnchor(oTrigger);
10609
10610         var i, startIndex, endIndex, elNew, elNewRow;
10611         var allRows = this.getTbodyEl().rows;
10612         var elThisRow = oTrigger.el.parentNode;
10613
10614         // Determine which direction we're going to
10615
10616         if(nKey == 40) { // arrow down
10617             // Selecting away from anchor cell
10618             if(oAnchor.recordIndex <= oTrigger.recordIndex) {
10619                 // Select the horiz block on the next row...
10620                 // ...making sure there is room below the trigger row
10621                 elNewRow = this.getNextTrEl(oTrigger.el);
10622                 if(elNewRow) {
10623                     startIndex = oAnchor.colKeyIndex;
10624                     endIndex = oTrigger.colKeyIndex;
10625                     // ...going left
10626                     if(startIndex > endIndex) {
10627                         for(i=startIndex; i>=endIndex; i--) {
10628                             elNew = elNewRow.cells[i];
10629                             this.selectCell(elNew);
10630                         }
10631                     }
10632                     // ... going right
10633                     else {
10634                         for(i=startIndex; i<=endIndex; i++) {
10635                             elNew = elNewRow.cells[i];
10636                             this.selectCell(elNew);
10637                         }
10638                     }
10639                 }
10640             }
10641             // Unselecting towards anchor cell
10642             else {
10643                 startIndex = Math.min(oAnchor.colKeyIndex, oTrigger.colKeyIndex);
10644                 endIndex = Math.max(oAnchor.colKeyIndex, oTrigger.colKeyIndex);
10645                 // Unselect the horiz block on this row towards the next row
10646                 for(i=startIndex; i<=endIndex; i++) {
10647                     this.unselectCell(elThisRow.cells[i]);
10648                 }
10649             }
10650         }
10651         // Arrow up
10652         else if(nKey == 38) {
10653             // Selecting away from anchor cell
10654             if(oAnchor.recordIndex >= oTrigger.recordIndex) {
10655                 // Select the horiz block on the previous row...
10656                 // ...making sure there is room
10657                 elNewRow = this.getPreviousTrEl(oTrigger.el);
10658                 if(elNewRow) {
10659                     // Select in order from anchor to trigger...
10660                     startIndex = oAnchor.colKeyIndex;
10661                     endIndex = oTrigger.colKeyIndex;
10662                     // ...going left
10663                     if(startIndex > endIndex) {
10664                         for(i=startIndex; i>=endIndex; i--) {
10665                             elNew = elNewRow.cells[i];
10666                             this.selectCell(elNew);
10667                         }
10668                     }
10669                     // ... going right
10670                     else {
10671                         for(i=startIndex; i<=endIndex; i++) {
10672                             elNew = elNewRow.cells[i];
10673                             this.selectCell(elNew);
10674                         }
10675                     }
10676                 }
10677             }
10678             // Unselecting towards anchor cell
10679             else {
10680                 startIndex = Math.min(oAnchor.colKeyIndex, oTrigger.colKeyIndex);
10681                 endIndex = Math.max(oAnchor.colKeyIndex, oTrigger.colKeyIndex);
10682                 // Unselect the horiz block on this row towards the previous row
10683                 for(i=startIndex; i<=endIndex; i++) {
10684                     this.unselectCell(elThisRow.cells[i]);
10685                 }
10686             }
10687         }
10688         // Arrow right
10689         else if(nKey == 39) {
10690             // Selecting away from anchor cell
10691             if(oAnchor.colKeyIndex <= oTrigger.colKeyIndex) {
10692                 // Select the next vert block to the right...
10693                 // ...making sure there is room
10694                 if(oTrigger.colKeyIndex < elThisRow.cells.length-1) {
10695                     // Select in order from anchor to trigger...
10696                     startIndex = oAnchor.trIndex;
10697                     endIndex = oTrigger.trIndex;
10698                     // ...going up
10699                     if(startIndex > endIndex) {
10700                         for(i=startIndex; i>=endIndex; i--) {
10701                             elNew = allRows[i].cells[oTrigger.colKeyIndex+1];
10702                             this.selectCell(elNew);
10703                         }
10704                     }
10705                     // ... going down
10706                     else {
10707                         for(i=startIndex; i<=endIndex; i++) {
10708                             elNew = allRows[i].cells[oTrigger.colKeyIndex+1];
10709                             this.selectCell(elNew);
10710                         }
10711                     }
10712                 }
10713             }
10714             // Unselecting towards anchor cell
10715             else {
10716                 // Unselect the vert block on this column towards the right
10717                 startIndex = Math.min(oAnchor.trIndex, oTrigger.trIndex);
10718                 endIndex = Math.max(oAnchor.trIndex, oTrigger.trIndex);
10719                 for(i=startIndex; i<=endIndex; i++) {
10720                     this.unselectCell(allRows[i].cells[oTrigger.colKeyIndex]);
10721                 }
10722             }
10723         }
10724         // Arrow left
10725         else if(nKey == 37) {
10726             // Selecting away from anchor cell
10727             if(oAnchor.colKeyIndex >= oTrigger.colKeyIndex) {
10728                 //Select the previous vert block to the left
10729                 if(oTrigger.colKeyIndex > 0) {
10730                     // Select in order from anchor to trigger...
10731                     startIndex = oAnchor.trIndex;
10732                     endIndex = oTrigger.trIndex;
10733                     // ...going up
10734                     if(startIndex > endIndex) {
10735                         for(i=startIndex; i>=endIndex; i--) {
10736                             elNew = allRows[i].cells[oTrigger.colKeyIndex-1];
10737                             this.selectCell(elNew);
10738                         }
10739                     }
10740                     // ... going down
10741                     else {
10742                         for(i=startIndex; i<=endIndex; i++) {
10743                             elNew = allRows[i].cells[oTrigger.colKeyIndex-1];
10744                             this.selectCell(elNew);
10745                         }
10746                     }
10747                 }
10748             }
10749             // Unselecting towards anchor cell
10750             else {
10751                 // Unselect the vert block on this column towards the left
10752                 startIndex = Math.min(oAnchor.trIndex, oTrigger.trIndex);
10753                 endIndex = Math.max(oAnchor.trIndex, oTrigger.trIndex);
10754                 for(i=startIndex; i<=endIndex; i++) {
10755                     this.unselectCell(allRows[i].cells[oTrigger.colKeyIndex]);
10756                 }
10757             }
10758         }
10759     }
10760 },
10761
10762 /**
10763  * Determines selection behavior resulting from a mouse event when selection mode
10764  * is set to "cellrange".
10765  *
10766  * @method _handleCellRangeSelectionByMouse
10767  * @param oArgs.event {HTMLEvent} Event object.
10768  * @param oArgs.target {HTMLElement} Target element.
10769  * @private
10770  */
10771 _handleCellRangeSelectionByMouse : function(oArgs) {
10772     var elTarget = oArgs.target;
10773
10774     // Validate target cell
10775     var elTargetCell = this.getTdEl(elTarget);
10776     if(elTargetCell) {
10777         var e = oArgs.event;
10778         var bSHIFT = e.shiftKey;
10779         var bCTRL = e.ctrlKey || ((navigator.userAgent.toLowerCase().indexOf("mac") != -1) && e.metaKey);
10780
10781         var elTargetRow = this.getTrEl(elTargetCell);
10782         var nTargetTrIndex = this.getTrIndex(elTargetRow);
10783         var oTargetColumn = this.getColumn(elTargetCell);
10784         var nTargetColKeyIndex = oTargetColumn.getKeyIndex();
10785         var oTargetRecord = this.getRecord(elTargetRow);
10786         var nTargetRecordIndex = this._oRecordSet.getRecordIndex(oTargetRecord);
10787         var oTargetCell = {record:oTargetRecord, column:oTargetColumn};
10788
10789         var oAnchor = this._getSelectionAnchor();
10790
10791         var allRows = this.getTbodyEl().rows;
10792         var currentRow, i, j;
10793
10794         // Both SHIFT and CTRL
10795         if(bSHIFT && bCTRL) {
10796
10797             // Validate anchor
10798             if(oAnchor) {
10799                 // Anchor is selected
10800                 if(this.isSelected(oAnchor.cell)) {
10801                     // All cells are on the same row
10802                     if(oAnchor.recordIndex === nTargetRecordIndex) {
10803                         // Select all cells between anchor cell and target cell, including target cell
10804                         if(oAnchor.colKeyIndex < nTargetColKeyIndex) {
10805                             for(i=oAnchor.colKeyIndex+1; i<=nTargetColKeyIndex; i++) {
10806                                 this.selectCell(elTargetRow.cells[i]);
10807                             }
10808                         }
10809                         // Select all cells between target cell and anchor cell, including target cell
10810                         else if(nTargetColKeyIndex < oAnchor.colKeyIndex) {
10811                             for(i=nTargetColKeyIndex; i<oAnchor.colKeyIndex; i++) {
10812                                 this.selectCell(elTargetRow.cells[i]);
10813                             }
10814                         }
10815                     }
10816                     // Anchor row is above target row
10817                     else if(oAnchor.recordIndex < nTargetRecordIndex) {
10818                         // Select all cells on anchor row from anchor cell to the end of the row
10819                         for(i=oAnchor.colKeyIndex+1; i<elTargetRow.cells.length; i++) {
10820                             this.selectCell(elTargetRow.cells[i]);
10821                         }
10822
10823                         // Select all cells on all rows between anchor row and target row
10824                         for(i=oAnchor.trIndex+1; i<nTargetTrIndex; i++) {
10825                             for(j=0; j<allRows[i].cells.length; j++){
10826                                 this.selectCell(allRows[i].cells[j]);
10827                             }
10828                         }
10829
10830                         // Select all cells on target row from first cell to the target cell
10831                         for(i=0; i<=nTargetColKeyIndex; i++) {
10832                             this.selectCell(elTargetRow.cells[i]);
10833                         }
10834                     }
10835                     // Anchor row is below target row
10836                     else {
10837                         // Select all cells on target row from target cell to the end of the row
10838                         for(i=nTargetColKeyIndex; i<elTargetRow.cells.length; i++) {
10839                             this.selectCell(elTargetRow.cells[i]);
10840                         }
10841
10842                         // Select all cells on all rows between target row and anchor row
10843                         for(i=nTargetTrIndex+1; i<oAnchor.trIndex; i++) {
10844                             for(j=0; j<allRows[i].cells.length; j++){
10845                                 this.selectCell(allRows[i].cells[j]);
10846                             }
10847                         }
10848
10849                         // Select all cells on anchor row from first cell to the anchor cell
10850                         for(i=0; i<oAnchor.colKeyIndex; i++) {
10851                             this.selectCell(elTargetRow.cells[i]);
10852                         }
10853                     }
10854                 }
10855                 // Anchor cell is unselected
10856                 else {
10857                     // All cells are on the same row
10858                     if(oAnchor.recordIndex === nTargetRecordIndex) {
10859                         // Unselect all cells between anchor cell and target cell
10860                         if(oAnchor.colKeyIndex < nTargetColKeyIndex) {
10861                             for(i=oAnchor.colKeyIndex+1; i<nTargetColKeyIndex; i++) {
10862                                 this.unselectCell(elTargetRow.cells[i]);
10863                             }
10864                         }
10865                         // Select all cells between target cell and anchor cell
10866                         else if(nTargetColKeyIndex < oAnchor.colKeyIndex) {
10867                             for(i=nTargetColKeyIndex+1; i<oAnchor.colKeyIndex; i++) {
10868                                 this.unselectCell(elTargetRow.cells[i]);
10869                             }
10870                         }
10871                     }
10872                     // Anchor row is above target row
10873                     if(oAnchor.recordIndex < nTargetRecordIndex) {
10874                         // Unselect all cells from anchor cell to target cell
10875                         for(i=oAnchor.trIndex; i<=nTargetTrIndex; i++) {
10876                             currentRow = allRows[i];
10877                             for(j=0; j<currentRow.cells.length; j++) {
10878                                 // This is the anchor row, only unselect cells after the anchor cell
10879                                 if(currentRow.sectionRowIndex === oAnchor.trIndex) {
10880                                     if(j>oAnchor.colKeyIndex) {
10881                                         this.unselectCell(currentRow.cells[j]);
10882                                     }
10883                                 }
10884                                 // This is the target row, only unelect cells before the target cell
10885                                 else if(currentRow.sectionRowIndex === nTargetTrIndex) {
10886                                     if(j<nTargetColKeyIndex) {
10887                                         this.unselectCell(currentRow.cells[j]);
10888                                     }
10889                                 }
10890                                 // Unselect all cells on this row
10891                                 else {
10892                                     this.unselectCell(currentRow.cells[j]);
10893                                 }
10894                             }
10895                         }
10896                     }
10897                     // Anchor row is below target row
10898                     else {
10899                         // Unselect all cells from target cell to anchor cell
10900                         for(i=nTargetTrIndex; i<=oAnchor.trIndex; i++) {
10901                             currentRow = allRows[i];
10902                             for(j=0; j<currentRow.cells.length; j++) {
10903                                 // This is the target row, only unselect cells after the target cell
10904                                 if(currentRow.sectionRowIndex == nTargetTrIndex) {
10905                                     if(j>nTargetColKeyIndex) {
10906                                         this.unselectCell(currentRow.cells[j]);
10907                                     }
10908                                 }
10909                                 // This is the anchor row, only unselect cells before the anchor cell
10910                                 else if(currentRow.sectionRowIndex == oAnchor.trIndex) {
10911                                     if(j<oAnchor.colKeyIndex) {
10912                                         this.unselectCell(currentRow.cells[j]);
10913                                     }
10914                                 }
10915                                 // Unselect all cells on this row
10916                                 else {
10917                                     this.unselectCell(currentRow.cells[j]);
10918                                 }
10919                             }
10920                         }
10921                     }
10922
10923                     // Select the target cell
10924                     this.selectCell(elTargetCell);
10925                 }
10926             }
10927             // Invalid anchor
10928             else {
10929                 // Set anchor
10930                 this._oAnchorCell = oTargetCell;
10931
10932                 // Toggle selection of target
10933                 if(this.isSelected(oTargetCell)) {
10934                     this.unselectCell(oTargetCell);
10935                 }
10936                 else {
10937                     this.selectCell(oTargetCell);
10938                 }
10939             }
10940         }
10941          // Only SHIFT
10942         else if(bSHIFT) {
10943
10944             this.unselectAllCells();
10945
10946             // Validate anchor
10947             if(oAnchor) {
10948                 // All cells are on the same row
10949                 if(oAnchor.recordIndex === nTargetRecordIndex) {
10950                     // Select all cells between anchor cell and target cell,
10951                     // including the anchor cell and target cell
10952                     if(oAnchor.colKeyIndex < nTargetColKeyIndex) {
10953                         for(i=oAnchor.colKeyIndex; i<=nTargetColKeyIndex; i++) {
10954                             this.selectCell(elTargetRow.cells[i]);
10955                         }
10956                     }
10957                     // Select all cells between target cell and anchor cell
10958                     // including the target cell and anchor cell
10959                     else if(nTargetColKeyIndex < oAnchor.colKeyIndex) {
10960                         for(i=nTargetColKeyIndex; i<=oAnchor.colKeyIndex; i++) {
10961                             this.selectCell(elTargetRow.cells[i]);
10962                         }
10963                     }
10964                 }
10965                 // Anchor row is above target row
10966                 else if(oAnchor.recordIndex < nTargetRecordIndex) {
10967                     // Select all cells from anchor cell to target cell
10968                     // including the anchor cell and target cell
10969                     for(i=oAnchor.trIndex; i<=nTargetTrIndex; i++) {
10970                         currentRow = allRows[i];
10971                         for(j=0; j<currentRow.cells.length; j++) {
10972                             // This is the anchor row, only select the anchor cell and after
10973                             if(currentRow.sectionRowIndex == oAnchor.trIndex) {
10974                                 if(j>=oAnchor.colKeyIndex) {
10975                                     this.selectCell(currentRow.cells[j]);
10976                                 }
10977                             }
10978                             // This is the target row, only select the target cell and before
10979                             else if(currentRow.sectionRowIndex == nTargetTrIndex) {
10980                                 if(j<=nTargetColKeyIndex) {
10981                                     this.selectCell(currentRow.cells[j]);
10982                                 }
10983                             }
10984                             // Select all cells on this row
10985                             else {
10986                                 this.selectCell(currentRow.cells[j]);
10987                             }
10988                         }
10989                     }
10990                 }
10991                 // Anchor row is below target row
10992                 else {
10993                     // Select all cells from target cell to anchor cell,
10994                     // including the target cell and anchor cell
10995                     for(i=nTargetTrIndex; i<=oAnchor.trIndex; i++) {
10996                         currentRow = allRows[i];
10997                         for(j=0; j<currentRow.cells.length; j++) {
10998                             // This is the target row, only select the target cell and after
10999                             if(currentRow.sectionRowIndex == nTargetTrIndex) {
11000                                 if(j>=nTargetColKeyIndex) {
11001                                     this.selectCell(currentRow.cells[j]);
11002                                 }
11003                             }
11004                             // This is the anchor row, only select the anchor cell and before
11005                             else if(currentRow.sectionRowIndex == oAnchor.trIndex) {
11006                                 if(j<=oAnchor.colKeyIndex) {
11007                                     this.selectCell(currentRow.cells[j]);
11008                                 }
11009                             }
11010                             // Select all cells on this row
11011                             else {
11012                                 this.selectCell(currentRow.cells[j]);
11013                             }
11014                         }
11015                     }
11016                 }
11017             }
11018             // Invalid anchor
11019             else {
11020                 // Set anchor
11021                 this._oAnchorCell = oTargetCell;
11022
11023                 // Select target only
11024                 this.selectCell(oTargetCell);
11025             }
11026
11027
11028         }
11029         // Only CTRL
11030         else if(bCTRL) {
11031
11032             // Set anchor
11033             this._oAnchorCell = oTargetCell;
11034
11035             // Toggle selection of target
11036             if(this.isSelected(oTargetCell)) {
11037                 this.unselectCell(oTargetCell);
11038             }
11039             else {
11040                 this.selectCell(oTargetCell);
11041             }
11042
11043         }
11044         // Neither SHIFT nor CTRL
11045         else {
11046             this._handleSingleCellSelectionByMouse(oArgs);
11047         }
11048     }
11049 },
11050
11051 /**
11052  * Determines selection behavior resulting from a key event when selection mode
11053  * is set to "cellrange".
11054  *
11055  * @method _handleCellRangeSelectionByKey
11056  * @param e {HTMLEvent} Event object.
11057  * @private
11058  */
11059 _handleCellRangeSelectionByKey : function(e) {
11060     var nKey = Ev.getCharCode(e);
11061     var bSHIFT = e.shiftKey;
11062     if((nKey == 9) || !bSHIFT) {
11063         this._handleSingleCellSelectionByKey(e);
11064         return;
11065     }
11066
11067     if((nKey > 36) && (nKey < 41)) {
11068         // Validate trigger
11069         var oTrigger = this._getSelectionTrigger();
11070         // Arrow selection only works if last selected row is on current page
11071         if(!oTrigger) {
11072             return null;
11073         }
11074
11075         Ev.stopEvent(e);
11076
11077         // Validate anchor
11078         var oAnchor = this._getSelectionAnchor(oTrigger);
11079
11080         var i, elNewRow, elNew;
11081         var allRows = this.getTbodyEl().rows;
11082         var elThisRow = oTrigger.el.parentNode;
11083
11084         // Arrow down
11085         if(nKey == 40) {
11086             elNewRow = this.getNextTrEl(oTrigger.el);
11087
11088             // Selecting away from anchor cell
11089             if(oAnchor.recordIndex <= oTrigger.recordIndex) {
11090                 // Select all cells to the end of this row
11091                 for(i=oTrigger.colKeyIndex+1; i<elThisRow.cells.length; i++){
11092                     elNew = elThisRow.cells[i];
11093                     this.selectCell(elNew);
11094                 }
11095
11096                 // Select some of the cells on the next row down
11097                 if(elNewRow) {
11098                     for(i=0; i<=oTrigger.colKeyIndex; i++){
11099                         elNew = elNewRow.cells[i];
11100                         this.selectCell(elNew);
11101                     }
11102                 }
11103             }
11104             // Unselecting towards anchor cell
11105             else {
11106                 // Unselect all cells to the end of this row
11107                 for(i=oTrigger.colKeyIndex; i<elThisRow.cells.length; i++){
11108                     this.unselectCell(elThisRow.cells[i]);
11109                 }
11110
11111                 // Unselect some of the cells on the next row down
11112                 if(elNewRow) {
11113                     for(i=0; i<oTrigger.colKeyIndex; i++){
11114                         this.unselectCell(elNewRow.cells[i]);
11115                     }
11116                 }
11117             }
11118         }
11119         // Arrow up
11120         else if(nKey == 38) {
11121             elNewRow = this.getPreviousTrEl(oTrigger.el);
11122
11123             // Selecting away from anchor cell
11124             if(oAnchor.recordIndex >= oTrigger.recordIndex) {
11125                 // Select all the cells to the beginning of this row
11126                 for(i=oTrigger.colKeyIndex-1; i>-1; i--){
11127                     elNew = elThisRow.cells[i];
11128                     this.selectCell(elNew);
11129                 }
11130
11131                 // Select some of the cells from the end of the previous row
11132                 if(elNewRow) {
11133                     for(i=elThisRow.cells.length-1; i>=oTrigger.colKeyIndex; i--){
11134                         elNew = elNewRow.cells[i];
11135                         this.selectCell(elNew);
11136                     }
11137                 }
11138             }
11139             // Unselecting towards anchor cell
11140             else {
11141                 // Unselect all the cells to the beginning of this row
11142                 for(i=oTrigger.colKeyIndex; i>-1; i--){
11143                     this.unselectCell(elThisRow.cells[i]);
11144                 }
11145
11146                 // Unselect some of the cells from the end of the previous row
11147                 if(elNewRow) {
11148                     for(i=elThisRow.cells.length-1; i>oTrigger.colKeyIndex; i--){
11149                         this.unselectCell(elNewRow.cells[i]);
11150                     }
11151                 }
11152             }
11153         }
11154         // Arrow right
11155         else if(nKey == 39) {
11156             elNewRow = this.getNextTrEl(oTrigger.el);
11157
11158             // Selecting away from anchor cell
11159             if(oAnchor.recordIndex < oTrigger.recordIndex) {
11160                 // Select the next cell to the right
11161                 if(oTrigger.colKeyIndex < elThisRow.cells.length-1) {
11162                     elNew = elThisRow.cells[oTrigger.colKeyIndex+1];
11163                     this.selectCell(elNew);
11164                 }
11165                 // Select the first cell of the next row
11166                 else if(elNewRow) {
11167                     elNew = elNewRow.cells[0];
11168                     this.selectCell(elNew);
11169                 }
11170             }
11171             // Unselecting towards anchor cell
11172             else if(oAnchor.recordIndex > oTrigger.recordIndex) {
11173                 this.unselectCell(elThisRow.cells[oTrigger.colKeyIndex]);
11174
11175                 // Unselect this cell towards the right
11176                 if(oTrigger.colKeyIndex < elThisRow.cells.length-1) {
11177                 }
11178                 // Unselect this cells towards the first cell of the next row
11179                 else {
11180                 }
11181             }
11182             // Anchor is on this row
11183             else {
11184                 // Selecting away from anchor
11185                 if(oAnchor.colKeyIndex <= oTrigger.colKeyIndex) {
11186                     // Select the next cell to the right
11187                     if(oTrigger.colKeyIndex < elThisRow.cells.length-1) {
11188                         elNew = elThisRow.cells[oTrigger.colKeyIndex+1];
11189                         this.selectCell(elNew);
11190                     }
11191                     // Select the first cell on the next row
11192                     else if(oTrigger.trIndex < allRows.length-1){
11193                         elNew = elNewRow.cells[0];
11194                         this.selectCell(elNew);
11195                     }
11196                 }
11197                 // Unselecting towards anchor
11198                 else {
11199                     // Unselect this cell towards the right
11200                     this.unselectCell(elThisRow.cells[oTrigger.colKeyIndex]);
11201                 }
11202             }
11203         }
11204         // Arrow left
11205         else if(nKey == 37) {
11206             elNewRow = this.getPreviousTrEl(oTrigger.el);
11207
11208             // Unselecting towards the anchor
11209             if(oAnchor.recordIndex < oTrigger.recordIndex) {
11210                 this.unselectCell(elThisRow.cells[oTrigger.colKeyIndex]);
11211
11212                 // Unselect this cell towards the left
11213                 if(oTrigger.colKeyIndex > 0) {
11214                 }
11215                 // Unselect this cell towards the last cell of the previous row
11216                 else {
11217                 }
11218             }
11219             // Selecting towards the anchor
11220             else if(oAnchor.recordIndex > oTrigger.recordIndex) {
11221                 // Select the next cell to the left
11222                 if(oTrigger.colKeyIndex > 0) {
11223                     elNew = elThisRow.cells[oTrigger.colKeyIndex-1];
11224                     this.selectCell(elNew);
11225                 }
11226                 // Select the last cell of the previous row
11227                 else if(oTrigger.trIndex > 0){
11228                     elNew = elNewRow.cells[elNewRow.cells.length-1];
11229                     this.selectCell(elNew);
11230                 }
11231             }
11232             // Anchor is on this row
11233             else {
11234                 // Selecting away from anchor cell
11235                 if(oAnchor.colKeyIndex >= oTrigger.colKeyIndex) {
11236                     // Select the next cell to the left
11237                     if(oTrigger.colKeyIndex > 0) {
11238                         elNew = elThisRow.cells[oTrigger.colKeyIndex-1];
11239                         this.selectCell(elNew);
11240                     }
11241                     // Select the last cell of the previous row
11242                     else if(oTrigger.trIndex > 0){
11243                         elNew = elNewRow.cells[elNewRow.cells.length-1];
11244                         this.selectCell(elNew);
11245                     }
11246                 }
11247                 // Unselecting towards anchor cell
11248                 else {
11249                     this.unselectCell(elThisRow.cells[oTrigger.colKeyIndex]);
11250
11251                     // Unselect this cell towards the left
11252                     if(oTrigger.colKeyIndex > 0) {
11253                     }
11254                     // Unselect this cell towards the last cell of the previous row
11255                     else {
11256                     }
11257                 }
11258             }
11259         }
11260     }
11261 },
11262
11263 /**
11264  * Determines selection behavior resulting from a mouse event when selection mode
11265  * is set to "singlecell".
11266  *
11267  * @method _handleSingleCellSelectionByMouse
11268  * @param oArgs.event {HTMLEvent} Event object.
11269  * @param oArgs.target {HTMLElement} Target element.
11270  * @private
11271  */
11272 _handleSingleCellSelectionByMouse : function(oArgs) {
11273     var elTarget = oArgs.target;
11274
11275     // Validate target cell
11276     var elTargetCell = this.getTdEl(elTarget);
11277     if(elTargetCell) {
11278         var elTargetRow = this.getTrEl(elTargetCell);
11279         var oTargetRecord = this.getRecord(elTargetRow);
11280         var oTargetColumn = this.getColumn(elTargetCell);
11281         var oTargetCell = {record:oTargetRecord, column:oTargetColumn};
11282
11283         // Set anchor
11284         this._oAnchorCell = oTargetCell;
11285
11286         // Select only target
11287         this.unselectAllCells();
11288         this.selectCell(oTargetCell);
11289     }
11290 },
11291
11292 /**
11293  * Determines selection behavior resulting from a key event when selection mode
11294  * is set to "singlecell".
11295  *
11296  * @method _handleSingleCellSelectionByKey
11297  * @param e {HTMLEvent} Event object.
11298  * @private
11299  */
11300 _handleSingleCellSelectionByKey : function(e) {
11301     var nKey = Ev.getCharCode(e);
11302     if((nKey == 9) || ((nKey > 36) && (nKey < 41))) {
11303         var bSHIFT = e.shiftKey;
11304
11305         // Validate trigger
11306         var oTrigger = this._getSelectionTrigger();
11307         // Arrow selection only works if last selected row is on current page
11308         if(!oTrigger) {
11309             return null;
11310         }
11311
11312         // Determine the new cell to select
11313         var elNew;
11314         if(nKey == 40) { // Arrow down
11315             elNew = this.getBelowTdEl(oTrigger.el);
11316
11317             // Validate new cell
11318             if(elNew === null) {
11319                 //TODO: wrap around to first tr on current page
11320
11321                 //TODO: wrap forward to first tr of next page
11322
11323                 // Bottom selection is sticky
11324                 elNew = oTrigger.el;
11325             }
11326         }
11327         else if(nKey == 38) { // Arrow up
11328             elNew = this.getAboveTdEl(oTrigger.el);
11329
11330             // Validate new cell
11331             if(elNew === null) {
11332                 //TODO: wrap around to last tr on current page
11333
11334                 //TODO: wrap back to last tr of previous page
11335
11336                 // Top selection is sticky
11337                 elNew = oTrigger.el;
11338             }
11339         }
11340         else if((nKey == 39) || (!bSHIFT && (nKey == 9))) { // Arrow right or tab
11341             elNew = this.getNextTdEl(oTrigger.el);
11342
11343             // Validate new cell
11344             if(elNew === null) {
11345                 //TODO: wrap around to first td on current page
11346
11347                 //TODO: wrap forward to first td of next page
11348
11349                 // Top-left selection is sticky, and release TAB focus
11350                 //elNew = oTrigger.el;
11351                 return;
11352             }
11353         }
11354         else if((nKey == 37) || (bSHIFT && (nKey == 9))) { // Arrow left or shift-tab
11355             elNew = this.getPreviousTdEl(oTrigger.el);
11356
11357             // Validate new cell
11358             if(elNew === null) {
11359                 //TODO: wrap around to last td on current page
11360
11361                 //TODO: wrap back to last td of previous page
11362
11363                 // Bottom-right selection is sticky, and release TAB focus
11364                 //elNew = oTrigger.el;
11365                 return;
11366             }
11367         }
11368
11369         Ev.stopEvent(e);
11370         
11371         // Unselect all cells
11372         this.unselectAllCells();
11373
11374         // Select the new cell
11375         this.selectCell(elNew);
11376
11377         // Set new anchor
11378         this._oAnchorCell = {record:this.getRecord(elNew), column:this.getColumn(elNew)};
11379     }
11380 },
11381
11382 /**
11383  * Returns array of selected TR elements on the page.
11384  *
11385  * @method getSelectedTrEls
11386  * @return {HTMLElement[]} Array of selected TR elements.
11387  */
11388 getSelectedTrEls : function() {
11389     return Dom.getElementsByClassName(DT.CLASS_SELECTED,"tr",this._elTbody);
11390 },
11391
11392 /**
11393  * Sets given row to the selected state.
11394  *
11395  * @method selectRow
11396  * @param row {HTMLElement | String | YAHOO.widget.Record | Number} HTML element
11397  * reference or ID string, Record instance, or RecordSet position index.
11398  */
11399 selectRow : function(row) {
11400     var oRecord, elRow;
11401
11402     if(row instanceof YAHOO.widget.Record) {
11403         oRecord = this._oRecordSet.getRecord(row);
11404         elRow = this.getTrEl(oRecord);
11405     }
11406     else if(lang.isNumber(row)) {
11407         oRecord = this.getRecord(row);
11408         elRow = this.getTrEl(oRecord);
11409     }
11410     else {
11411         elRow = this.getTrEl(row);
11412         oRecord = this.getRecord(elRow);
11413     }
11414
11415     if(oRecord) {
11416         // Update selection trackers
11417         var tracker = this._aSelections || [];
11418         var sRecordId = oRecord.getId();
11419         var index = -1;
11420
11421         // Remove if already there:
11422         // Use Array.indexOf if available...
11423         /*if(tracker.indexOf && (tracker.indexOf(sRecordId) >  -1)) {
11424             tracker.splice(tracker.indexOf(sRecordId),1);
11425         }*/
11426         if(tracker.indexOf) {
11427             index = tracker.indexOf(sRecordId);
11428             
11429         }
11430         // ...or do it the old-fashioned way
11431         else {
11432             for(var j=tracker.length-1; j>-1; j--) {
11433                 if(tracker[j] === sRecordId){
11434                     index = j;
11435                     break;
11436                 }
11437             }
11438         }
11439         if(index > -1) {
11440             tracker.splice(index,1);
11441         }
11442         
11443         // Add to the end
11444         tracker.push(sRecordId);
11445         this._aSelections = tracker;
11446
11447         // Update trackers
11448         if(!this._oAnchorRecord) {
11449             this._oAnchorRecord = oRecord;
11450         }
11451
11452         // Update UI
11453         if(elRow) {
11454             Dom.addClass(elRow, DT.CLASS_SELECTED);
11455         }
11456
11457         this.fireEvent("rowSelectEvent", {record:oRecord, el:elRow});
11458     }
11459     else {
11460     }
11461 },
11462
11463 /**
11464  * Sets given row to the unselected state.
11465  *
11466  * @method unselectRow
11467  * @param row {HTMLElement | String | YAHOO.widget.Record | Number} HTML element
11468  * reference or ID string, Record instance, or RecordSet position index.
11469  */
11470 unselectRow : function(row) {
11471     var elRow = this.getTrEl(row);
11472
11473     var oRecord;
11474     if(row instanceof YAHOO.widget.Record) {
11475         oRecord = this._oRecordSet.getRecord(row);
11476     }
11477     else if(lang.isNumber(row)) {
11478         oRecord = this.getRecord(row);
11479     }
11480     else {
11481         oRecord = this.getRecord(elRow);
11482     }
11483
11484     if(oRecord) {
11485         // Update selection trackers
11486         var tracker = this._aSelections || [];
11487         var sRecordId = oRecord.getId();
11488         var index = -1;
11489
11490         // Use Array.indexOf if available...
11491         if(tracker.indexOf) {
11492             index = tracker.indexOf(sRecordId);
11493         }
11494         // ...or do it the old-fashioned way
11495         else {
11496             for(var j=tracker.length-1; j>-1; j--) {
11497                 if(tracker[j] === sRecordId){
11498                     index = j;
11499                     break;
11500                 }
11501             }
11502         }
11503         if(index > -1) {
11504             // Update tracker
11505             tracker.splice(index,1);
11506             this._aSelections = tracker;
11507
11508             // Update the UI
11509             Dom.removeClass(elRow, DT.CLASS_SELECTED);
11510
11511             this.fireEvent("rowUnselectEvent", {record:oRecord, el:elRow});
11512
11513             return;
11514         }
11515     }
11516 },
11517
11518 /**
11519  * Clears out all row selections.
11520  *
11521  * @method unselectAllRows
11522  */
11523 unselectAllRows : function() {
11524     // Remove all rows from tracker
11525     var tracker = this._aSelections || [],
11526         recId,
11527         removed = [];
11528     for(var j=tracker.length-1; j>-1; j--) {
11529        if(lang.isString(tracker[j])){
11530             recId = tracker.splice(j,1);
11531             removed[removed.length] = this.getRecord(lang.isArray(recId) ? recId[0] : recId);
11532         }
11533     }
11534
11535     // Update tracker
11536     this._aSelections = tracker;
11537
11538     // Update UI
11539     this._unselectAllTrEls();
11540
11541     this.fireEvent("unselectAllRowsEvent", {records: removed});
11542 },
11543
11544 /**
11545  * Convenience method to remove the class YAHOO.widget.DataTable.CLASS_SELECTED
11546  * from all TD elements in the internal tracker.
11547  *
11548  * @method _unselectAllTdEls
11549  * @private
11550  */
11551 _unselectAllTdEls : function() {
11552     var selectedCells = Dom.getElementsByClassName(DT.CLASS_SELECTED,"td",this._elTbody);
11553     Dom.removeClass(selectedCells, DT.CLASS_SELECTED);
11554 },
11555
11556 /**
11557  * Returns array of selected TD elements on the page.
11558  *
11559  * @method getSelectedTdEls
11560  * @return {HTMLElement[]} Array of selected TD elements.
11561  */
11562 getSelectedTdEls : function() {
11563     return Dom.getElementsByClassName(DT.CLASS_SELECTED,"td",this._elTbody);
11564 },
11565
11566 /**
11567  * Sets given cell to the selected state.
11568  *
11569  * @method selectCell
11570  * @param cell {HTMLElement | String} DOM element reference or ID string
11571  * to DataTable page element or RecordSet index.
11572  */
11573 selectCell : function(cell) {
11574 //TODO: accept {record} in selectCell()
11575     var elCell = this.getTdEl(cell);
11576
11577     if(elCell) {
11578         var oRecord = this.getRecord(elCell);
11579         var sColumnKey = this.getColumn(elCell.cellIndex).getKey();
11580
11581         if(oRecord && sColumnKey) {
11582             // Get Record ID
11583             var tracker = this._aSelections || [];
11584             var sRecordId = oRecord.getId();
11585
11586             // Remove if there
11587             for(var j=tracker.length-1; j>-1; j--) {
11588                if((tracker[j].recordId === sRecordId) && (tracker[j].columnKey === sColumnKey)){
11589                     tracker.splice(j,1);
11590                     break;
11591                 }
11592             }
11593
11594             // Add to the end
11595             tracker.push({recordId:sRecordId, columnKey:sColumnKey});
11596
11597             // Update trackers
11598             this._aSelections = tracker;
11599             if(!this._oAnchorCell) {
11600                 this._oAnchorCell = {record:oRecord, column:this.getColumn(sColumnKey)};
11601             }
11602
11603             // Update the UI
11604             Dom.addClass(elCell, DT.CLASS_SELECTED);
11605
11606             this.fireEvent("cellSelectEvent", {record:oRecord, column:this.getColumn(elCell.cellIndex), key: this.getColumn(elCell.cellIndex).getKey(), el:elCell});
11607             return;
11608         }
11609     }
11610 },
11611
11612 /**
11613  * Sets given cell to the unselected state.
11614  *
11615  * @method unselectCell
11616  * @param cell {HTMLElement | String} DOM element reference or ID string
11617  * to DataTable page element or RecordSet index.
11618  */
11619 unselectCell : function(cell) {
11620     var elCell = this.getTdEl(cell);
11621
11622     if(elCell) {
11623         var oRecord = this.getRecord(elCell);
11624         var sColumnKey = this.getColumn(elCell.cellIndex).getKey();
11625
11626         if(oRecord && sColumnKey) {
11627             // Get Record ID
11628             var tracker = this._aSelections || [];
11629             var id = oRecord.getId();
11630
11631             // Is it selected?
11632             for(var j=tracker.length-1; j>-1; j--) {
11633                 if((tracker[j].recordId === id) && (tracker[j].columnKey === sColumnKey)){
11634                     // Remove from tracker
11635                     tracker.splice(j,1);
11636
11637                     // Update tracker
11638                     this._aSelections = tracker;
11639
11640                     // Update the UI
11641                     Dom.removeClass(elCell, DT.CLASS_SELECTED);
11642
11643                     this.fireEvent("cellUnselectEvent", {record:oRecord, column: this.getColumn(elCell.cellIndex), key:this.getColumn(elCell.cellIndex).getKey(), el:elCell});
11644                     return;
11645                 }
11646             }
11647         }
11648     }
11649 },
11650
11651 /**
11652  * Clears out all cell selections.
11653  *
11654  * @method unselectAllCells
11655  */
11656 unselectAllCells : function() {
11657     // Remove all cells from tracker
11658     var tracker = this._aSelections || [];
11659     for(var j=tracker.length-1; j>-1; j--) {
11660        if(lang.isObject(tracker[j])){
11661             tracker.splice(j,1);
11662         }
11663     }
11664
11665     // Update tracker
11666     this._aSelections = tracker;
11667
11668     // Update UI
11669     this._unselectAllTdEls();
11670
11671     //TODO: send data to unselectAllCellsEvent handler
11672     this.fireEvent("unselectAllCellsEvent");
11673 },
11674
11675 /**
11676  * Returns true if given item is selected, false otherwise.
11677  *
11678  * @method isSelected
11679  * @param o {String | HTMLElement | YAHOO.widget.Record | Number
11680  * {record:YAHOO.widget.Record, column:YAHOO.widget.Column} } TR or TD element by
11681  * reference or ID string, a Record instance, a RecordSet position index,
11682  * or an object literal representation
11683  * of a cell.
11684  * @return {Boolean} True if item is selected.
11685  */
11686 isSelected : function(o) {
11687     if(o && (o.ownerDocument == document)) {
11688         return (Dom.hasClass(this.getTdEl(o),DT.CLASS_SELECTED) || Dom.hasClass(this.getTrEl(o),DT.CLASS_SELECTED));
11689     }
11690     else {
11691         var oRecord, sRecordId, j;
11692         var tracker = this._aSelections;
11693         if(tracker && tracker.length > 0) {
11694             // Looking for a Record?
11695             if(o instanceof YAHOO.widget.Record) {
11696                 oRecord = o;
11697             }
11698             else if(lang.isNumber(o)) {
11699                 oRecord = this.getRecord(o);
11700             }
11701             if(oRecord) {
11702                 sRecordId = oRecord.getId();
11703
11704                 // Is it there?
11705                 // Use Array.indexOf if available...
11706                 if(tracker.indexOf) {
11707                     if(tracker.indexOf(sRecordId) >  -1) {
11708                         return true;
11709                     }
11710                 }
11711                 // ...or do it the old-fashioned way
11712                 else {
11713                     for(j=tracker.length-1; j>-1; j--) {
11714                        if(tracker[j] === sRecordId){
11715                         return true;
11716                        }
11717                     }
11718                 }
11719             }
11720             // Looking for a cell
11721             else if(o.record && o.column){
11722                 sRecordId = o.record.getId();
11723                 var sColumnKey = o.column.getKey();
11724
11725                 for(j=tracker.length-1; j>-1; j--) {
11726                     if((tracker[j].recordId === sRecordId) && (tracker[j].columnKey === sColumnKey)){
11727                         return true;
11728                     }
11729                 }
11730             }
11731         }
11732     }
11733     return false;
11734 },
11735
11736 /**
11737  * Returns selected rows as an array of Record IDs.
11738  *
11739  * @method getSelectedRows
11740  * @return {String[]} Array of selected rows by Record ID.
11741  */
11742 getSelectedRows : function() {
11743     var aSelectedRows = [];
11744     var tracker = this._aSelections || [];
11745     for(var j=0; j<tracker.length; j++) {
11746        if(lang.isString(tracker[j])){
11747             aSelectedRows.push(tracker[j]);
11748         }
11749     }
11750     return aSelectedRows;
11751 },
11752
11753 /**
11754  * Returns selected cells as an array of object literals:
11755  *     {recordId:sRecordId, columnKey:sColumnKey}.
11756  *
11757  * @method getSelectedCells
11758  * @return {Object[]} Array of selected cells by Record ID and Column ID.
11759  */
11760 getSelectedCells : function() {
11761     var aSelectedCells = [];
11762     var tracker = this._aSelections || [];
11763     for(var j=0; j<tracker.length; j++) {
11764        if(tracker[j] && lang.isObject(tracker[j])){
11765             aSelectedCells.push(tracker[j]);
11766         }
11767     }
11768     return aSelectedCells;
11769 },
11770
11771 /**
11772  * Returns last selected Record ID.
11773  *
11774  * @method getLastSelectedRecord
11775  * @return {String} Record ID of last selected row.
11776  */
11777 getLastSelectedRecord : function() {
11778     var tracker = this._aSelections;
11779     if(tracker && tracker.length > 0) {
11780         for(var i=tracker.length-1; i>-1; i--) {
11781            if(lang.isString(tracker[i])){
11782                 return tracker[i];
11783             }
11784         }
11785     }
11786 },
11787
11788 /**
11789  * Returns last selected cell as an object literal:
11790  *     {recordId:sRecordId, columnKey:sColumnKey}.
11791  *
11792  * @method getLastSelectedCell
11793  * @return {Object} Object literal representation of a cell.
11794  */
11795 getLastSelectedCell : function() {
11796     var tracker = this._aSelections;
11797     if(tracker && tracker.length > 0) {
11798         for(var i=tracker.length-1; i>-1; i--) {
11799            if(tracker[i].recordId && tracker[i].columnKey){
11800                 return tracker[i];
11801             }
11802         }
11803     }
11804 },
11805
11806 /**
11807  * Assigns the class YAHOO.widget.DataTable.CLASS_HIGHLIGHTED to the given row.
11808  *
11809  * @method highlightRow
11810  * @param row {HTMLElement | String} DOM element reference or ID string.
11811  */
11812 highlightRow : function(row) {
11813     var elRow = this.getTrEl(row);
11814
11815     if(elRow) {
11816         // Make sure previous row is unhighlighted
11817 /*        if(this._sLastHighlightedTrElId) {
11818             Dom.removeClass(this._sLastHighlightedTrElId,DT.CLASS_HIGHLIGHTED);
11819         }*/
11820         var oRecord = this.getRecord(elRow);
11821         Dom.addClass(elRow,DT.CLASS_HIGHLIGHTED);
11822         //this._sLastHighlightedTrElId = elRow.id;
11823         this.fireEvent("rowHighlightEvent", {record:oRecord, el:elRow});
11824         return;
11825     }
11826 },
11827
11828 /**
11829  * Removes the class YAHOO.widget.DataTable.CLASS_HIGHLIGHTED from the given row.
11830  *
11831  * @method unhighlightRow
11832  * @param row {HTMLElement | String} DOM element reference or ID string.
11833  */
11834 unhighlightRow : function(row) {
11835     var elRow = this.getTrEl(row);
11836
11837     if(elRow) {
11838         var oRecord = this.getRecord(elRow);
11839         Dom.removeClass(elRow,DT.CLASS_HIGHLIGHTED);
11840         this.fireEvent("rowUnhighlightEvent", {record:oRecord, el:elRow});
11841         return;
11842     }
11843 },
11844
11845 /**
11846  * Assigns the class YAHOO.widget.DataTable.CLASS_HIGHLIGHTED to the given cell.
11847  *
11848  * @method highlightCell
11849  * @param cell {HTMLElement | String} DOM element reference or ID string.
11850  */
11851 highlightCell : function(cell) {
11852     var elCell = this.getTdEl(cell);
11853
11854     if(elCell) {
11855         // Make sure previous cell is unhighlighted
11856         if(this._elLastHighlightedTd) {
11857             this.unhighlightCell(this._elLastHighlightedTd);
11858         }
11859
11860         var oRecord = this.getRecord(elCell);
11861         var sColumnKey = this.getColumn(elCell.cellIndex).getKey();
11862         Dom.addClass(elCell,DT.CLASS_HIGHLIGHTED);
11863         this._elLastHighlightedTd = elCell;
11864         this.fireEvent("cellHighlightEvent", {record:oRecord, column:this.getColumn(elCell.cellIndex), key:this.getColumn(elCell.cellIndex).getKey(), el:elCell});
11865         return;
11866     }
11867 },
11868
11869 /**
11870  * Removes the class YAHOO.widget.DataTable.CLASS_HIGHLIGHTED from the given cell.
11871  *
11872  * @method unhighlightCell
11873  * @param cell {HTMLElement | String} DOM element reference or ID string.
11874  */
11875 unhighlightCell : function(cell) {
11876     var elCell = this.getTdEl(cell);
11877
11878     if(elCell) {
11879         var oRecord = this.getRecord(elCell);
11880         Dom.removeClass(elCell,DT.CLASS_HIGHLIGHTED);
11881         this._elLastHighlightedTd = null;
11882         this.fireEvent("cellUnhighlightEvent", {record:oRecord, column:this.getColumn(elCell.cellIndex), key:this.getColumn(elCell.cellIndex).getKey(), el:elCell});
11883         return;
11884     }
11885 },
11886
11887
11888
11889
11890
11891
11892
11893
11894
11895
11896
11897
11898
11899
11900
11901
11902
11903
11904
11905
11906
11907
11908
11909
11910
11911
11912
11913
11914
11915
11916
11917
11918
11919
11920
11921
11922
11923
11924
11925
11926
11927
11928
11929
11930
11931 // INLINE EDITING
11932
11933 /**
11934  * Returns current CellEditor instance, or null.
11935  * @method getCellEditor
11936  * @return {YAHOO.widget.CellEditor} CellEditor instance.
11937  */
11938 getCellEditor : function() {
11939     return this._oCellEditor;
11940 },
11941
11942
11943 /**
11944  * Activates and shows CellEditor instance for the given cell while deactivating and
11945  * canceling previous CellEditor. It is baked into DataTable that only one CellEditor
11946  * can be active at any given time. 
11947  *
11948  * @method showCellEditor
11949  * @param elCell {HTMLElement | String} Cell to edit.
11950  */
11951 showCellEditor : function(elCell, oRecord, oColumn) {
11952     // Get a particular CellEditor
11953     elCell = this.getTdEl(elCell);
11954     if(elCell) {
11955         oColumn = this.getColumn(elCell);
11956         if(oColumn && oColumn.editor) {
11957             var oCellEditor = this._oCellEditor;
11958             // Clean up active CellEditor
11959             if(oCellEditor) {
11960                 if(this._oCellEditor.cancel) {
11961                     this._oCellEditor.cancel();
11962                 }
11963                 else if(oCellEditor.isActive) {
11964                     this.cancelCellEditor();
11965                 }
11966             }
11967             
11968             if(oColumn.editor instanceof YAHOO.widget.BaseCellEditor) {
11969                 // Get CellEditor
11970                 oCellEditor = oColumn.editor;
11971                 var ok = oCellEditor.attach(this, elCell);
11972                 if(ok) {
11973                     oCellEditor.move();
11974                     ok = this.doBeforeShowCellEditor(oCellEditor);
11975                     if(ok) {
11976                         oCellEditor.show();
11977                         this._oCellEditor = oCellEditor;
11978                     }
11979                 }
11980             }
11981             // Backward compatibility
11982             else {
11983                     if(!oRecord || !(oRecord instanceof YAHOO.widget.Record)) {
11984                         oRecord = this.getRecord(elCell);
11985                     }
11986                     if(!oColumn || !(oColumn instanceof YAHOO.widget.Column)) {
11987                         oColumn = this.getColumn(elCell);
11988                     }
11989                     if(oRecord && oColumn) {
11990                         if(!this._oCellEditor || this._oCellEditor.container) {
11991                             this._initCellEditorEl();
11992                         }
11993                         
11994                         // Update Editor values
11995                         oCellEditor = this._oCellEditor;
11996                         oCellEditor.cell = elCell;
11997                         oCellEditor.record = oRecord;
11998                         oCellEditor.column = oColumn;
11999                         oCellEditor.validator = (oColumn.editorOptions &&
12000                                 lang.isFunction(oColumn.editorOptions.validator)) ?
12001                                 oColumn.editorOptions.validator : null;
12002                         oCellEditor.value = oRecord.getData(oColumn.key);
12003                         oCellEditor.defaultValue = null;
12004             
12005                         // Move Editor
12006                         var elContainer = oCellEditor.container;
12007                         var x = Dom.getX(elCell);
12008                         var y = Dom.getY(elCell);
12009             
12010                         // SF doesn't get xy for cells in scrolling table
12011                         // when tbody display is set to block
12012                         if(isNaN(x) || isNaN(y)) {
12013                             x = elCell.offsetLeft + // cell pos relative to table
12014                                     Dom.getX(this._elTbody.parentNode) - // plus table pos relative to document
12015                                     this._elTbody.scrollLeft; // minus tbody scroll
12016                             y = elCell.offsetTop + // cell pos relative to table
12017                                     Dom.getY(this._elTbody.parentNode) - // plus table pos relative to document
12018                                     this._elTbody.scrollTop + // minus tbody scroll
12019                                     this._elThead.offsetHeight; // account for fixed THEAD cells
12020                         }
12021             
12022                         elContainer.style.left = x + "px";
12023                         elContainer.style.top = y + "px";
12024             
12025                         // Hook to customize the UI
12026                         this.doBeforeShowCellEditor(this._oCellEditor);
12027             
12028                         //TODO: This is temporarily up here due so elements can be focused
12029                         // Show Editor
12030                         elContainer.style.display = "";
12031             
12032                         // Handle ESC key
12033                         Ev.addListener(elContainer, "keydown", function(e, oSelf) {
12034                             // ESC hides Cell Editor
12035                             if((e.keyCode == 27)) {
12036                                 oSelf.cancelCellEditor();
12037                                 oSelf.focusTbodyEl();
12038                             }
12039                             else {
12040                                 oSelf.fireEvent("editorKeydownEvent", {editor:oSelf._oCellEditor, event:e});
12041                             }
12042                         }, this);
12043             
12044                         // Render Editor markup
12045                         var fnEditor;
12046                         if(lang.isString(oColumn.editor)) {
12047                             switch(oColumn.editor) {
12048                                 case "checkbox":
12049                                     fnEditor = DT.editCheckbox;
12050                                     break;
12051                                 case "date":
12052                                     fnEditor = DT.editDate;
12053                                     break;
12054                                 case "dropdown":
12055                                     fnEditor = DT.editDropdown;
12056                                     break;
12057                                 case "radio":
12058                                     fnEditor = DT.editRadio;
12059                                     break;
12060                                 case "textarea":
12061                                     fnEditor = DT.editTextarea;
12062                                     break;
12063                                 case "textbox":
12064                                     fnEditor = DT.editTextbox;
12065                                     break;
12066                                 default:
12067                                     fnEditor = null;
12068                             }
12069                         }
12070                         else if(lang.isFunction(oColumn.editor)) {
12071                             fnEditor = oColumn.editor;
12072                         }
12073             
12074                         if(fnEditor) {
12075                             // Create DOM input elements
12076                             fnEditor(this._oCellEditor, this);
12077             
12078                             // Show Save/Cancel buttons
12079                             if(!oColumn.editorOptions || !oColumn.editorOptions.disableBtns) {
12080                                 this.showCellEditorBtns(elContainer);
12081                             }
12082             
12083                             oCellEditor.isActive = true;
12084             
12085                             //TODO: verify which args to pass
12086                             this.fireEvent("editorShowEvent", {editor:oCellEditor});
12087                             return;
12088                         }
12089                     }
12090
12091
12092
12093             
12094             }
12095         }
12096     }
12097 },
12098
12099 /**
12100  * Backward compatibility.
12101  *
12102  * @method _initCellEditorEl
12103  * @private
12104  * @deprecated 
12105  */
12106 _initCellEditorEl : function() {
12107     // Attach Cell Editor container element as first child of body
12108     var elCellEditor = document.createElement("div");
12109     elCellEditor.id = this._sId + "-celleditor";
12110     elCellEditor.style.display = "none";
12111     elCellEditor.tabIndex = 0;
12112     Dom.addClass(elCellEditor, DT.CLASS_EDITOR);
12113     var elFirstChild = Dom.getFirstChild(document.body);
12114     if(elFirstChild) {
12115         elCellEditor = Dom.insertBefore(elCellEditor, elFirstChild);
12116     }
12117     else {
12118         elCellEditor = document.body.appendChild(elCellEditor);
12119     }
12120     
12121     // Internal tracker of Cell Editor values
12122     var oCellEditor = {};
12123     oCellEditor.container = elCellEditor;
12124     oCellEditor.value = null;
12125     oCellEditor.isActive = false;
12126     this._oCellEditor = oCellEditor;
12127 },
12128
12129 /**
12130  * Overridable abstract method to customize CellEditor before showing.
12131  *
12132  * @method doBeforeShowCellEditor
12133  * @param oCellEditor {YAHOO.widget.CellEditor} The CellEditor instance.
12134  * @return {Boolean} Return true to continue showing CellEditor.
12135  */
12136 doBeforeShowCellEditor : function(oCellEditor) {
12137     return true;
12138 },
12139
12140 /**
12141  * Saves active CellEditor input to Record and upates DOM UI.
12142  *
12143  * @method saveCellEditor
12144  */
12145 saveCellEditor : function() {
12146     if(this._oCellEditor) {
12147         if(this._oCellEditor.save) {
12148             this._oCellEditor.save();
12149         }
12150         // Backward compatibility
12151         else if(this._oCellEditor.isActive) {
12152             var newData = this._oCellEditor.value;
12153             // Copy the data to pass to the event
12154             //var oldData = YAHOO.widget.DataTable._cloneObject(this._oCellEditor.record.getData(this._oCellEditor.column.key));
12155             var oldData = this._oCellEditor.record.getData(this._oCellEditor.column.key);
12156     
12157             // Validate input data
12158             if(this._oCellEditor.validator) {
12159                 newData = this._oCellEditor.value = this._oCellEditor.validator.call(this, newData, oldData, this._oCellEditor);
12160                 if(newData === null ) {
12161                     this.resetCellEditor();
12162                     this.fireEvent("editorRevertEvent",
12163                             {editor:this._oCellEditor, oldData:oldData, newData:newData});
12164                     return;
12165                 }
12166             }
12167             // Update the Record
12168             this._oRecordSet.updateRecordValue(this._oCellEditor.record, this._oCellEditor.column.key, this._oCellEditor.value);
12169             // Update the UI
12170             this.formatCell(this._oCellEditor.cell.firstChild);
12171             
12172             // Bug fix 1764044
12173             this._oChainRender.add({
12174                 method: function() {
12175                     this.validateColumnWidths();
12176                 },
12177                 scope: this
12178             });
12179             this._oChainRender.run();
12180             // Clear out the Cell Editor
12181             this.resetCellEditor();
12182     
12183             this.fireEvent("editorSaveEvent",
12184                     {editor:this._oCellEditor, oldData:oldData, newData:newData});
12185         }
12186     }   
12187 },
12188
12189 /**
12190  * Cancels active CellEditor.
12191  *
12192  * @method cancelCellEditor
12193  */
12194 cancelCellEditor : function() {
12195     if(this._oCellEditor) {
12196         if(this._oCellEditor.cancel) {
12197             this._oCellEditor.cancel();
12198         }
12199         // Backward compatibility
12200         else if(this._oCellEditor.isActive) {
12201             this.resetCellEditor();
12202             //TODO: preserve values for the event?
12203             this.fireEvent("editorCancelEvent", {editor:this._oCellEditor});
12204         }
12205
12206     }
12207 },
12208
12209 /**
12210  * Destroys active CellEditor instance and UI.
12211  *
12212  * @method destroyCellEditor
12213  */
12214 destroyCellEditor : function() {
12215     if(this._oCellEditor) {
12216         this._oCellEditor.destroy();
12217         this._oCellEditor = null;
12218     }   
12219 },
12220
12221 /**
12222  * Passes through showEvent of the active CellEditor.
12223  *
12224  * @method _onEditorShowEvent
12225  * @param oArgs {Object}  Custom Event args.
12226  * @private 
12227  */
12228 _onEditorShowEvent : function(oArgs) {
12229     this.fireEvent("editorShowEvent", oArgs);
12230 },
12231
12232 /**
12233  * Passes through keydownEvent of the active CellEditor.
12234  * @param oArgs {Object}  Custom Event args. 
12235  *
12236  * @method _onEditorKeydownEvent
12237  * @private 
12238  */
12239 _onEditorKeydownEvent : function(oArgs) {
12240     this.fireEvent("editorKeydownEvent", oArgs);
12241 },
12242
12243 /**
12244  * Passes through revertEvent of the active CellEditor.
12245  *
12246  * @method _onEditorRevertEvent
12247  * @param oArgs {Object}  Custom Event args. 
12248  * @private  
12249  */
12250 _onEditorRevertEvent : function(oArgs) {
12251     this.fireEvent("editorRevertEvent", oArgs);
12252 },
12253
12254 /**
12255  * Passes through saveEvent of the active CellEditor.
12256  *
12257  * @method _onEditorSaveEvent
12258  * @param oArgs {Object}  Custom Event args.  
12259  * @private 
12260  */
12261 _onEditorSaveEvent : function(oArgs) {
12262     this.fireEvent("editorSaveEvent", oArgs);
12263 },
12264
12265 /**
12266  * Passes through cancelEvent of the active CellEditor.
12267  *
12268  * @method _onEditorCancelEvent
12269  * @param oArgs {Object}  Custom Event args.
12270  * @private   
12271  */
12272 _onEditorCancelEvent : function(oArgs) {
12273     this.fireEvent("editorCancelEvent", oArgs);
12274 },
12275
12276 /**
12277  * Passes through blurEvent of the active CellEditor.
12278  *
12279  * @method _onEditorBlurEvent
12280  * @param oArgs {Object}  Custom Event args. 
12281  * @private  
12282  */
12283 _onEditorBlurEvent : function(oArgs) {
12284     this.fireEvent("editorBlurEvent", oArgs);
12285 },
12286
12287 /**
12288  * Passes through blockEvent of the active CellEditor.
12289  *
12290  * @method _onEditorBlockEvent
12291  * @param oArgs {Object}  Custom Event args. 
12292  * @private  
12293  */
12294 _onEditorBlockEvent : function(oArgs) {
12295     this.fireEvent("editorBlockEvent", oArgs);
12296 },
12297
12298 /**
12299  * Passes through unblockEvent of the active CellEditor.
12300  *
12301  * @method _onEditorUnblockEvent
12302  * @param oArgs {Object}  Custom Event args. 
12303  * @private  
12304  */
12305 _onEditorUnblockEvent : function(oArgs) {
12306     this.fireEvent("editorUnblockEvent", oArgs);
12307 },
12308
12309 /**
12310  * Public handler of the editorBlurEvent. By default, saves on blur if
12311  * disableBtns is true, otherwise cancels on blur. 
12312  *
12313  * @method onEditorBlurEvent
12314  * @param oArgs {Object}  Custom Event args.  
12315  */
12316 onEditorBlurEvent : function(oArgs) {
12317     if(oArgs.editor.disableBtns) {
12318         // Save on blur
12319         if(oArgs.editor.save) { // Backward incompatible
12320             oArgs.editor.save();
12321         }
12322     }      
12323     else if(oArgs.editor.cancel) { // Backward incompatible
12324         // Cancel on blur
12325         oArgs.editor.cancel();
12326     }      
12327 },
12328
12329 /**
12330  * Public handler of the editorBlockEvent. By default, disables DataTable UI.
12331  *
12332  * @method onEditorBlockEvent
12333  * @param oArgs {Object}  Custom Event args.  
12334  */
12335 onEditorBlockEvent : function(oArgs) {
12336     this.disable();
12337 },
12338
12339 /**
12340  * Public handler of the editorUnblockEvent. By default, undisables DataTable UI.
12341  *
12342  * @method onEditorUnblockEvent
12343  * @param oArgs {Object}  Custom Event args.  
12344  */
12345 onEditorUnblockEvent : function(oArgs) {
12346     this.undisable();
12347 },
12348
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 // ABSTRACT METHODS
12387
12388 /**
12389  * Overridable method gives implementers a hook to access data before
12390  * it gets added to RecordSet and rendered to the TBODY.
12391  *
12392  * @method doBeforeLoadData
12393  * @param sRequest {String} Original request.
12394  * @param oResponse {Object} Response object.
12395  * @param oPayload {MIXED} additional arguments
12396  * @return {Boolean} Return true to continue loading data into RecordSet and
12397  * updating DataTable with new Records, false to cancel.
12398  */
12399 doBeforeLoadData : function(sRequest, oResponse, oPayload) {
12400     return true;
12401 },
12402
12403
12404
12405
12406
12407
12408
12409
12410
12411
12412
12413
12414
12415
12416
12417
12418
12419
12420
12421
12422
12423
12424
12425
12426
12427
12428
12429
12430
12431
12432
12433
12434
12435
12436
12437
12438
12439
12440
12441
12442
12443
12444
12445
12446
12447
12448
12449
12450
12451
12452
12453
12454
12455
12456
12457
12458
12459
12460
12461
12462
12463
12464
12465 /////////////////////////////////////////////////////////////////////////////
12466 //
12467 // Public Custom Event Handlers
12468 //
12469 /////////////////////////////////////////////////////////////////////////////
12470
12471 /**
12472  * Overridable custom event handler to sort Column.
12473  *
12474  * @method onEventSortColumn
12475  * @param oArgs.event {HTMLEvent} Event object.
12476  * @param oArgs.target {HTMLElement} Target element.
12477  */
12478 onEventSortColumn : function(oArgs) {
12479 //TODO: support form elements in sortable columns
12480     var evt = oArgs.event;
12481     var target = oArgs.target;
12482
12483     var el = this.getThEl(target) || this.getTdEl(target);
12484     if(el) {
12485         var oColumn = this.getColumn(el);
12486         if(oColumn.sortable) {
12487             Ev.stopEvent(evt);
12488             this.sortColumn(oColumn);
12489         }
12490     }
12491     else {
12492     }
12493 },
12494
12495 /**
12496  * Overridable custom event handler to select Column.
12497  *
12498  * @method onEventSelectColumn
12499  * @param oArgs.event {HTMLEvent} Event object.
12500  * @param oArgs.target {HTMLElement} Target element.
12501  */
12502 onEventSelectColumn : function(oArgs) {
12503     this.selectColumn(oArgs.target);
12504 },
12505
12506 /**
12507  * Overridable custom event handler to highlight Column. Accounts for spurious
12508  * caused-by-child events. 
12509  *
12510  * @method onEventHighlightColumn
12511  * @param oArgs.event {HTMLEvent} Event object.
12512  * @param oArgs.target {HTMLElement} Target element.
12513  */
12514 onEventHighlightColumn : function(oArgs) {
12515     //TODO: filter for all spurious events at a lower level
12516     if(!Dom.isAncestor(oArgs.target,Ev.getRelatedTarget(oArgs.event))) {
12517         this.highlightColumn(oArgs.target);
12518     }
12519 },
12520
12521 /**
12522  * Overridable custom event handler to unhighlight Column. Accounts for spurious
12523  * caused-by-child events. 
12524  *
12525  * @method onEventUnhighlightColumn
12526  * @param oArgs.event {HTMLEvent} Event object.
12527  * @param oArgs.target {HTMLElement} Target element.
12528  */
12529 onEventUnhighlightColumn : 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.unhighlightColumn(oArgs.target);
12533     }
12534 },
12535
12536 /**
12537  * Overridable custom event handler to manage selection according to desktop paradigm.
12538  *
12539  * @method onEventSelectRow
12540  * @param oArgs.event {HTMLEvent} Event object.
12541  * @param oArgs.target {HTMLElement} Target element.
12542  */
12543 onEventSelectRow : function(oArgs) {
12544     var sMode = this.get("selectionMode");
12545     if(sMode == "single") {
12546         this._handleSingleSelectionByMouse(oArgs);
12547     }
12548     else {
12549         this._handleStandardSelectionByMouse(oArgs);
12550     }
12551 },
12552
12553 /**
12554  * Overridable custom event handler to select cell.
12555  *
12556  * @method onEventSelectCell
12557  * @param oArgs.event {HTMLEvent} Event object.
12558  * @param oArgs.target {HTMLElement} Target element.
12559  */
12560 onEventSelectCell : function(oArgs) {
12561     var sMode = this.get("selectionMode");
12562     if(sMode == "cellblock") {
12563         this._handleCellBlockSelectionByMouse(oArgs);
12564     }
12565     else if(sMode == "cellrange") {
12566         this._handleCellRangeSelectionByMouse(oArgs);
12567     }
12568     else {
12569         this._handleSingleCellSelectionByMouse(oArgs);
12570     }
12571 },
12572
12573 /**
12574  * Overridable custom event handler to highlight row. Accounts for spurious
12575  * caused-by-child events. 
12576  *
12577  * @method onEventHighlightRow
12578  * @param oArgs.event {HTMLEvent} Event object.
12579  * @param oArgs.target {HTMLElement} Target element.
12580  */
12581 onEventHighlightRow : function(oArgs) {
12582     //TODO: filter for all spurious events at a lower level
12583     if(!Dom.isAncestor(oArgs.target,Ev.getRelatedTarget(oArgs.event))) {
12584         this.highlightRow(oArgs.target);
12585     }
12586 },
12587
12588 /**
12589  * Overridable custom event handler to unhighlight row. Accounts for spurious
12590  * caused-by-child events. 
12591  *
12592  * @method onEventUnhighlightRow
12593  * @param oArgs.event {HTMLEvent} Event object.
12594  * @param oArgs.target {HTMLElement} Target element.
12595  */
12596 onEventUnhighlightRow : function(oArgs) {
12597     //TODO: filter for all spurious events at a lower level
12598     if(!Dom.isAncestor(oArgs.target,Ev.getRelatedTarget(oArgs.event))) {
12599         this.unhighlightRow(oArgs.target);
12600     }
12601 },
12602
12603 /**
12604  * Overridable custom event handler to highlight cell. Accounts for spurious
12605  * caused-by-child events. 
12606  *
12607  * @method onEventHighlightCell
12608  * @param oArgs.event {HTMLEvent} Event object.
12609  * @param oArgs.target {HTMLElement} Target element.
12610  */
12611 onEventHighlightCell : function(oArgs) {
12612     //TODO: filter for all spurious events at a lower level
12613     if(!Dom.isAncestor(oArgs.target,Ev.getRelatedTarget(oArgs.event))) {
12614         this.highlightCell(oArgs.target);
12615     }
12616 },
12617
12618 /**
12619  * Overridable custom event handler to unhighlight cell. Accounts for spurious
12620  * caused-by-child events. 
12621  *
12622  * @method onEventUnhighlightCell
12623  * @param oArgs.event {HTMLEvent} Event object.
12624  * @param oArgs.target {HTMLElement} Target element.
12625  */
12626 onEventUnhighlightCell : function(oArgs) {
12627     //TODO: filter for all spurious events at a lower level
12628     if(!Dom.isAncestor(oArgs.target,Ev.getRelatedTarget(oArgs.event))) {
12629         this.unhighlightCell(oArgs.target);
12630     }
12631 },
12632
12633 /**
12634  * Overridable custom event handler to format cell.
12635  *
12636  * @method onEventFormatCell
12637  * @param oArgs.event {HTMLEvent} Event object.
12638  * @param oArgs.target {HTMLElement} Target element.
12639  */
12640 onEventFormatCell : function(oArgs) {
12641     var target = oArgs.target;
12642
12643     var elCell = this.getTdEl(target);
12644     if(elCell) {
12645         var oColumn = this.getColumn(elCell.cellIndex);
12646         this.formatCell(elCell.firstChild, this.getRecord(elCell), oColumn);
12647     }
12648     else {
12649     }
12650 },
12651
12652 /**
12653  * Overridable custom event handler to edit cell.
12654  *
12655  * @method onEventShowCellEditor
12656  * @param oArgs.event {HTMLEvent} Event object.
12657  * @param oArgs.target {HTMLElement} Target element.
12658  */
12659 onEventShowCellEditor : function(oArgs) {
12660     this.showCellEditor(oArgs.target);
12661 },
12662
12663 /**
12664  * Overridable custom event handler to save active CellEditor input.
12665  *
12666  * @method onEventSaveCellEditor
12667  */
12668 onEventSaveCellEditor : function(oArgs) {
12669     if(this._oCellEditor) {
12670         if(this._oCellEditor.save) {
12671             this._oCellEditor.save();
12672         }
12673         // Backward compatibility
12674         else {
12675             this.saveCellEditor();
12676         }
12677     }
12678 },
12679
12680 /**
12681  * Overridable custom event handler to cancel active CellEditor.
12682  *
12683  * @method onEventCancelCellEditor
12684  */
12685 onEventCancelCellEditor : function(oArgs) {
12686     if(this._oCellEditor) {
12687         if(this._oCellEditor.cancel) {
12688             this._oCellEditor.cancel();
12689         }
12690         // Backward compatibility
12691         else {
12692             this.cancelCellEditor();
12693         }
12694     }
12695 },
12696
12697 /**
12698  * Callback function receives data from DataSource and populates an entire
12699  * DataTable with Records and TR elements, clearing previous Records, if any.
12700  *
12701  * @method onDataReturnInitializeTable
12702  * @param sRequest {String} Original request.
12703  * @param oResponse {Object} Response object.
12704  * @param oPayload {MIXED} (optional) Additional argument(s)
12705  */
12706 onDataReturnInitializeTable : function(sRequest, oResponse, oPayload) {
12707     if((this instanceof DT) && this._sId) {
12708         this.initializeTable();
12709     
12710         this.onDataReturnSetRows(sRequest,oResponse,oPayload);
12711     }
12712 },
12713
12714 /**
12715  * Callback function receives reponse from DataSource, replaces all existing
12716  * Records in  RecordSet, updates TR elements with new data, and updates state
12717  * UI for pagination and sorting from payload data, if necessary. 
12718  *  
12719  * @method onDataReturnReplaceRows
12720  * @param oRequest {MIXED} Original generated request.
12721  * @param oResponse {Object} Response object.
12722  * @param oPayload {MIXED} (optional) Additional argument(s)
12723  */
12724 onDataReturnReplaceRows : function(oRequest, oResponse, oPayload) {
12725     if((this instanceof DT) && this._sId) {
12726         this.fireEvent("dataReturnEvent", {request:oRequest,response:oResponse,payload:oPayload});
12727     
12728         // Pass data through abstract method for any transformations
12729         var ok    = this.doBeforeLoadData(oRequest, oResponse, oPayload),
12730             pag   = this.get('paginator'),
12731             index = 0;
12732     
12733         // Data ok to set
12734         if(ok && oResponse && !oResponse.error && lang.isArray(oResponse.results)) {
12735             // Update Records
12736             this._oRecordSet.reset();
12737     
12738             if (this.get('dynamicData')) {
12739                 if (oPayload && oPayload.pagination &&
12740                     lang.isNumber(oPayload.pagination.recordOffset)) {
12741                     index = oPayload.pagination.recordOffset;
12742                 } else if (pag) {
12743                     index = pag.getStartIndex();
12744                 }
12745             }
12746     
12747             this._oRecordSet.setRecords(oResponse.results, index | 0);
12748             
12749             // Update state
12750             this._handleDataReturnPayload(oRequest, oResponse, oPayload);
12751             
12752             // Update UI
12753             this.render();    
12754         }
12755         // Error
12756         else if(ok && oResponse.error) {
12757             this.showTableMessage(this.get("MSG_ERROR"), DT.CLASS_ERROR);
12758         }
12759     }
12760 },
12761
12762 /**
12763  * Callback function receives data from DataSource and appends to an existing
12764  * DataTable new Records and, if applicable, creates or updates
12765  * corresponding TR elements.
12766  *
12767  * @method onDataReturnAppendRows
12768  * @param sRequest {String} Original request.
12769  * @param oResponse {Object} Response object.
12770  * @param oPayload {MIXED} (optional) Additional argument(s)
12771  */
12772 onDataReturnAppendRows : function(sRequest, oResponse, oPayload) {
12773     if((this instanceof DT) && this._sId) {
12774         this.fireEvent("dataReturnEvent", {request:sRequest,response:oResponse,payload:oPayload});
12775     
12776         // Pass data through abstract method for any transformations
12777         var ok = this.doBeforeLoadData(sRequest, oResponse, oPayload);
12778     
12779         // Data ok to append
12780         if(ok && oResponse && !oResponse.error && lang.isArray(oResponse.results)) {        
12781             // Append rows
12782             this.addRows(oResponse.results);
12783     
12784             // Update state
12785             this._handleDataReturnPayload(sRequest, oResponse, oPayload);
12786         }
12787         // Error
12788         else if(ok && oResponse.error) {
12789             this.showTableMessage(this.get("MSG_ERROR"), DT.CLASS_ERROR);
12790         }
12791     }
12792 },
12793
12794 /**
12795  * Callback function receives data from DataSource and inserts new records
12796  * starting at the index specified in oPayload.insertIndex. The value for
12797  * oPayload.insertIndex can be populated when sending the request to the DataSource,
12798  * or by accessing oPayload.insertIndex with the doBeforeLoadData() method at runtime.
12799  * If applicable, creates or updates corresponding TR elements.
12800  *
12801  * @method onDataReturnInsertRows
12802  * @param sRequest {String} Original request.
12803  * @param oResponse {Object} Response object.
12804  * @param oPayload {MIXED} Argument payload, looks in oPayload.insertIndex.
12805  */
12806 onDataReturnInsertRows : function(sRequest, oResponse, oPayload) {
12807     if((this instanceof DT) && this._sId) {
12808         this.fireEvent("dataReturnEvent", {request:sRequest,response:oResponse,payload:oPayload});
12809     
12810         // Pass data through abstract method for any transformations
12811         var ok = this.doBeforeLoadData(sRequest, oResponse, oPayload);
12812     
12813         // Data ok to append
12814         if(ok && oResponse && !oResponse.error && lang.isArray(oResponse.results)) {
12815             // Insert rows
12816             this.addRows(oResponse.results, (oPayload ? oPayload.insertIndex : 0));
12817     
12818             // Update state
12819             this._handleDataReturnPayload(sRequest, oResponse, oPayload);
12820         }
12821         // Error
12822         else if(ok && oResponse.error) {
12823             this.showTableMessage(this.get("MSG_ERROR"), DT.CLASS_ERROR);
12824         }
12825     }
12826 },
12827
12828 /**
12829  * Callback function receives data from DataSource and incrementally updates Records
12830  * starting at the index specified in oPayload.updateIndex. The value for
12831  * oPayload.updateIndex can be populated when sending the request to the DataSource,
12832  * or by accessing oPayload.updateIndex with the doBeforeLoadData() method at runtime.
12833  * If applicable, creates or updates corresponding TR elements.
12834  *
12835  * @method onDataReturnUpdateRows
12836  * @param sRequest {String} Original request.
12837  * @param oResponse {Object} Response object.
12838  * @param oPayload {MIXED} Argument payload, looks in oPayload.updateIndex.
12839  */
12840 onDataReturnUpdateRows : function(sRequest, oResponse, oPayload) {
12841     if((this instanceof DT) && this._sId) {
12842         this.fireEvent("dataReturnEvent", {request:sRequest,response:oResponse,payload:oPayload});
12843     
12844         // Pass data through abstract method for any transformations
12845         var ok = this.doBeforeLoadData(sRequest, oResponse, oPayload);
12846     
12847         // Data ok to append
12848         if(ok && oResponse && !oResponse.error && lang.isArray(oResponse.results)) {
12849             // Insert rows
12850             this.updateRows((oPayload ? oPayload.updateIndex : 0), oResponse.results);
12851     
12852             // Update state
12853             this._handleDataReturnPayload(sRequest, oResponse, oPayload);
12854         }
12855         // Error
12856         else if(ok && oResponse.error) {
12857             this.showTableMessage(this.get("MSG_ERROR"), DT.CLASS_ERROR);
12858         }
12859     }
12860 },
12861
12862 /**
12863  * Callback function receives reponse from DataSource and populates the
12864  * RecordSet with the results.
12865  *  
12866  * @method onDataReturnSetRows
12867  * @param oRequest {MIXED} Original generated request.
12868  * @param oResponse {Object} Response object.
12869  * @param oPayload {MIXED} (optional) Additional argument(s)
12870  */
12871 onDataReturnSetRows : function(oRequest, oResponse, oPayload) {
12872     if((this instanceof DT) && this._sId) {
12873         this.fireEvent("dataReturnEvent", {request:oRequest,response:oResponse,payload:oPayload});
12874     
12875         // Pass data through abstract method for any transformations
12876         var ok    = this.doBeforeLoadData(oRequest, oResponse, oPayload),
12877             pag   = this.get('paginator'),
12878             index = 0;
12879     
12880         // Data ok to set
12881         if(ok && oResponse && !oResponse.error && lang.isArray(oResponse.results)) {
12882             // Update Records
12883             if (this.get('dynamicData')) {
12884                 if (oPayload && oPayload.pagination &&
12885                     lang.isNumber(oPayload.pagination.recordOffset)) {
12886                     index = oPayload.pagination.recordOffset;
12887                 } else if (pag) {
12888                     index = pag.getStartIndex();
12889                 }
12890                 
12891                 this._oRecordSet.reset(); // Bug 2290604: dyanmic data shouldn't keep accumulating by default
12892             }
12893     
12894             this._oRecordSet.setRecords(oResponse.results, index | 0);
12895     
12896             // Update state
12897             this._handleDataReturnPayload(oRequest, oResponse, oPayload);
12898             
12899             // Update UI
12900             this.render();
12901         }
12902         // Error
12903         else if(ok && oResponse.error) {
12904             this.showTableMessage(this.get("MSG_ERROR"), DT.CLASS_ERROR);
12905         }
12906     }
12907     else {
12908     }
12909 },
12910
12911 /**
12912  * Hook to update oPayload before consumption.
12913  *  
12914  * @method handleDataReturnPayload
12915  * @param oRequest {MIXED} Original generated request.
12916  * @param oResponse {Object} Response object.
12917  * @param oPayload {MIXED} State values.
12918  * @return oPayload {MIXED} State values.
12919  */
12920 handleDataReturnPayload : function (oRequest, oResponse, oPayload) {
12921     return oPayload;
12922 },
12923
12924 /**
12925  * Updates the DataTable with state data sent in an onDataReturn* payload.
12926  *  
12927  * @method handleDataReturnPayload
12928  * @param oRequest {MIXED} Original generated request.
12929  * @param oResponse {Object} Response object.
12930  * @param oPayload {MIXED} State values
12931  */
12932 _handleDataReturnPayload : function (oRequest, oResponse, oPayload) {
12933     oPayload = this.handleDataReturnPayload(oRequest, oResponse, oPayload);
12934     if(oPayload) {
12935         // Update pagination
12936         var oPaginator = this.get('paginator');
12937         if (oPaginator) {
12938             // Update totalRecords
12939             if(this.get("dynamicData")) {
12940                 if (widget.Paginator.isNumeric(oPayload.totalRecords)) {
12941                     oPaginator.set('totalRecords',oPayload.totalRecords);
12942                 }
12943             }
12944             else {
12945                 oPaginator.set('totalRecords',this._oRecordSet.getLength());
12946             }
12947             // Update other paginator values
12948             if (lang.isObject(oPayload.pagination)) {
12949                 oPaginator.set('rowsPerPage',oPayload.pagination.rowsPerPage);
12950                 oPaginator.set('recordOffset',oPayload.pagination.recordOffset);
12951             }
12952         }
12953
12954         // Update sorting
12955         if (oPayload.sortedBy) {
12956             // Set the sorting values in preparation for refresh
12957             this.set('sortedBy', oPayload.sortedBy);
12958         }
12959         // Backwards compatibility for sorting
12960         else if (oPayload.sorting) {
12961             // Set the sorting values in preparation for refresh
12962             this.set('sortedBy', oPayload.sorting);
12963         }
12964     }
12965 },
12966
12967
12968
12969
12970
12971
12972
12973
12974
12975
12976
12977
12978
12979
12980
12981
12982
12983
12984
12985
12986
12987
12988
12989
12990
12991
12992
12993
12994
12995
12996
12997
12998
12999     /////////////////////////////////////////////////////////////////////////////
13000     //
13001     // Custom Events
13002     //
13003     /////////////////////////////////////////////////////////////////////////////
13004
13005     /**
13006      * Fired when the DataTable's rows are rendered from an initialized state.
13007      *
13008      * @event initEvent
13009      */
13010
13011     /**
13012      * Fired before the DataTable's DOM is rendered or modified.
13013      *
13014      * @event beforeRenderEvent
13015      */
13016
13017     /**
13018      * Fired when the DataTable's DOM is rendered or modified.
13019      *
13020      * @event renderEvent
13021      */
13022
13023     /**
13024      * Fired when the DataTable's post-render routine is complete, including
13025      * Column width validations.
13026      *
13027      * @event postRenderEvent
13028      */
13029
13030     /**
13031      * Fired when the DataTable is disabled.
13032      *
13033      * @event disableEvent
13034      */
13035
13036     /**
13037      * Fired when the DataTable is undisabled.
13038      *
13039      * @event undisableEvent
13040      */
13041
13042     /**
13043      * Fired when data is returned from DataSource but before it is consumed by
13044      * DataTable.
13045      *
13046      * @event dataReturnEvent
13047      * @param oArgs.request {String} Original request.
13048      * @param oArgs.response {Object} Response object.
13049      */
13050
13051     /**
13052      * Fired when the DataTable has a focus event.
13053      *
13054      * @event tableFocusEvent
13055      */
13056
13057     /**
13058      * Fired when the DataTable THEAD element has a focus event.
13059      *
13060      * @event theadFocusEvent
13061      */
13062
13063     /**
13064      * Fired when the DataTable TBODY element has a focus event.
13065      *
13066      * @event tbodyFocusEvent
13067      */
13068
13069     /**
13070      * Fired when the DataTable has a blur event.
13071      *
13072      * @event tableBlurEvent
13073      */
13074
13075     /*TODO implement theadBlurEvent
13076      * Fired when the DataTable THEAD element has a blur event.
13077      *
13078      * @event theadBlurEvent
13079      */
13080
13081     /*TODO: implement tbodyBlurEvent
13082      * Fired when the DataTable TBODY element has a blur event.
13083      *
13084      * @event tbodyBlurEvent
13085      */
13086
13087     /**
13088      * Fired when the DataTable has a key event.
13089      *
13090      * @event tableKeyEvent
13091      * @param oArgs.event {HTMLEvent} The event object.
13092      * @param oArgs.target {HTMLElement} The DataTable's TABLE element.
13093      */
13094
13095     /**
13096      * Fired when the DataTable THEAD element has a key event.
13097      *
13098      * @event theadKeyEvent
13099      * @param oArgs.event {HTMLEvent} The event object.
13100      * @param oArgs.target {HTMLElement} The DataTable's TABLE element.
13101      */
13102
13103     /**
13104      * Fired when the DataTable TBODY element has a key event.
13105      *
13106      * @event tbodyKeyEvent
13107      * @param oArgs.event {HTMLEvent} The event object.
13108      * @param oArgs.target {HTMLElement} The DataTable's TABLE element.
13109      */
13110
13111     /**
13112      * Fired when the DataTable has a mouseover.
13113      *
13114      * @event tableMouseoverEvent
13115      * @param oArgs.event {HTMLEvent} The event object.
13116      * @param oArgs.target {HTMLElement} The DataTable's TABLE element.
13117      *
13118      */
13119
13120     /**
13121      * Fired when the DataTable has a mouseout.
13122      *
13123      * @event tableMouseoutEvent
13124      * @param oArgs.event {HTMLEvent} The event object.
13125      * @param oArgs.target {HTMLElement} The DataTable's TABLE element.
13126      *
13127      */
13128
13129     /**
13130      * Fired when the DataTable has a mousedown.
13131      *
13132      * @event tableMousedownEvent
13133      * @param oArgs.event {HTMLEvent} The event object.
13134      * @param oArgs.target {HTMLElement} The DataTable's TABLE element.
13135      *
13136      */
13137
13138     /**
13139      * Fired when the DataTable has a mouseup.
13140      *
13141      * @event tableMouseupEvent
13142      * @param oArgs.event {HTMLEvent} The event object.
13143      * @param oArgs.target {HTMLElement} The DataTable's TABLE element.
13144      *
13145      */
13146
13147     /**
13148      * Fired when the DataTable has a click.
13149      *
13150      * @event tableClickEvent
13151      * @param oArgs.event {HTMLEvent} The event object.
13152      * @param oArgs.target {HTMLElement} The DataTable's TABLE element.
13153      *
13154      */
13155
13156     /**
13157      * Fired when the DataTable has a dblclick.
13158      *
13159      * @event tableDblclickEvent
13160      * @param oArgs.event {HTMLEvent} The event object.
13161      * @param oArgs.target {HTMLElement} The DataTable's TABLE element.
13162      *
13163      */
13164
13165     /**
13166      * Fired when a message is shown in the DataTable's message element.
13167      *
13168      * @event tableMsgShowEvent
13169      * @param oArgs.html {String} The HTML displayed.
13170      * @param oArgs.className {String} The className assigned.
13171      *
13172      */
13173
13174     /**
13175      * Fired when the DataTable's message element is hidden.
13176      *
13177      * @event tableMsgHideEvent
13178      */
13179
13180     /**
13181      * Fired when a THEAD row has a mouseover.
13182      *
13183      * @event theadRowMouseoverEvent
13184      * @param oArgs.event {HTMLEvent} The event object.
13185      * @param oArgs.target {HTMLElement} The TR element.
13186      */
13187
13188     /**
13189      * Fired when a THEAD row has a mouseout.
13190      *
13191      * @event theadRowMouseoutEvent
13192      * @param oArgs.event {HTMLEvent} The event object.
13193      * @param oArgs.target {HTMLElement} The TR element.
13194      */
13195
13196     /**
13197      * Fired when a THEAD row has a mousedown.
13198      *
13199      * @event theadRowMousedownEvent
13200      * @param oArgs.event {HTMLEvent} The event object.
13201      * @param oArgs.target {HTMLElement} The TR element.
13202      */
13203
13204     /**
13205      * Fired when a THEAD row has a mouseup.
13206      *
13207      * @event theadRowMouseupEvent
13208      * @param oArgs.event {HTMLEvent} The event object.
13209      * @param oArgs.target {HTMLElement} The TR element.
13210      */
13211
13212     /**
13213      * Fired when a THEAD row has a click.
13214      *
13215      * @event theadRowClickEvent
13216      * @param oArgs.event {HTMLEvent} The event object.
13217      * @param oArgs.target {HTMLElement} The TR element.
13218      */
13219
13220     /**
13221      * Fired when a THEAD row has a dblclick.
13222      *
13223      * @event theadRowDblclickEvent
13224      * @param oArgs.event {HTMLEvent} The event object.
13225      * @param oArgs.target {HTMLElement} The TR element.
13226      */
13227
13228     /**
13229      * Fired when a THEAD cell has a mouseover.
13230      *
13231      * @event theadCellMouseoverEvent
13232      * @param oArgs.event {HTMLEvent} The event object.
13233      * @param oArgs.target {HTMLElement} The TH element.
13234      *
13235      */
13236
13237     /**
13238      * Fired when a THEAD cell has a mouseout.
13239      *
13240      * @event theadCellMouseoutEvent
13241      * @param oArgs.event {HTMLEvent} The event object.
13242      * @param oArgs.target {HTMLElement} The TH element.
13243      *
13244      */
13245
13246     /**
13247      * Fired when a THEAD cell has a mousedown.
13248      *
13249      * @event theadCellMousedownEvent
13250      * @param oArgs.event {HTMLEvent} The event object.
13251      * @param oArgs.target {HTMLElement} The TH element.
13252      */
13253
13254     /**
13255      * Fired when a THEAD cell has a mouseup.
13256      *
13257      * @event theadCellMouseupEvent
13258      * @param oArgs.event {HTMLEvent} The event object.
13259      * @param oArgs.target {HTMLElement} The TH element.
13260      */
13261
13262     /**
13263      * Fired when a THEAD cell has a click.
13264      *
13265      * @event theadCellClickEvent
13266      * @param oArgs.event {HTMLEvent} The event object.
13267      * @param oArgs.target {HTMLElement} The TH element.
13268      */
13269
13270     /**
13271      * Fired when a THEAD cell has a dblclick.
13272      *
13273      * @event theadCellDblclickEvent
13274      * @param oArgs.event {HTMLEvent} The event object.
13275      * @param oArgs.target {HTMLElement} The TH element.
13276      */
13277
13278     /**
13279      * Fired when a THEAD label has a mouseover.
13280      *
13281      * @event theadLabelMouseoverEvent
13282      * @param oArgs.event {HTMLEvent} The event object.
13283      * @param oArgs.target {HTMLElement} The SPAN element.
13284      *
13285      */
13286
13287     /**
13288      * Fired when a THEAD label has a mouseout.
13289      *
13290      * @event theadLabelMouseoutEvent
13291      * @param oArgs.event {HTMLEvent} The event object.
13292      * @param oArgs.target {HTMLElement} The SPAN element.
13293      *
13294      */
13295
13296     /**
13297      * Fired when a THEAD label has a mousedown.
13298      *
13299      * @event theadLabelMousedownEvent
13300      * @param oArgs.event {HTMLEvent} The event object.
13301      * @param oArgs.target {HTMLElement} The SPAN element.
13302      */
13303
13304     /**
13305      * Fired when a THEAD label has a mouseup.
13306      *
13307      * @event theadLabelMouseupEvent
13308      * @param oArgs.event {HTMLEvent} The event object.
13309      * @param oArgs.target {HTMLElement} The SPAN element.
13310      */
13311
13312     /**
13313      * Fired when a THEAD label has a click.
13314      *
13315      * @event theadLabelClickEvent
13316      * @param oArgs.event {HTMLEvent} The event object.
13317      * @param oArgs.target {HTMLElement} The SPAN element.
13318      */
13319
13320     /**
13321      * Fired when a THEAD label has a dblclick.
13322      *
13323      * @event theadLabelDblclickEvent
13324      * @param oArgs.event {HTMLEvent} The event object.
13325      * @param oArgs.target {HTMLElement} The SPAN element.
13326      */
13327
13328     /**
13329      * Fired when a column is sorted.
13330      *
13331      * @event columnSortEvent
13332      * @param oArgs.column {YAHOO.widget.Column} The Column instance.
13333      * @param oArgs.dir {String} Sort direction: YAHOO.widget.DataTable.CLASS_ASC
13334      * or YAHOO.widget.DataTable.CLASS_DESC.
13335      */
13336
13337     /**
13338      * Fired when a column width is set.
13339      *
13340      * @event columnSetWidthEvent
13341      * @param oArgs.column {YAHOO.widget.Column} The Column instance.
13342      * @param oArgs.width {Number} The width in pixels.
13343      */
13344
13345     /**
13346      * Fired when a column width is unset.
13347      *
13348      * @event columnUnsetWidthEvent
13349      * @param oArgs.column {YAHOO.widget.Column} The Column instance.
13350      */
13351
13352     /**
13353      * Fired when a column is drag-resized.
13354      *
13355      * @event columnResizeEvent
13356      * @param oArgs.column {YAHOO.widget.Column} The Column instance.
13357      * @param oArgs.target {HTMLElement} The TH element.
13358      * @param oArgs.width {Number} Width in pixels.     
13359      */
13360
13361     /**
13362      * Fired when a Column is moved to a new index.
13363      *
13364      * @event columnReorderEvent
13365      * @param oArgs.column {YAHOO.widget.Column} The Column instance.
13366      * @param oArgs.oldIndex {Number} The previous index position.
13367      */
13368
13369     /**
13370      * Fired when a column is hidden.
13371      *
13372      * @event columnHideEvent
13373      * @param oArgs.column {YAHOO.widget.Column} The Column instance.
13374      */
13375
13376     /**
13377      * Fired when a column is shown.
13378      *
13379      * @event columnShowEvent
13380      * @param oArgs.column {YAHOO.widget.Column} The Column instance.
13381      */
13382
13383     /**
13384      * Fired when a column is selected.
13385      *
13386      * @event columnSelectEvent
13387      * @param oArgs.column {YAHOO.widget.Column} The Column instance.
13388      */
13389
13390     /**
13391      * Fired when a column is unselected.
13392      *
13393      * @event columnUnselectEvent
13394      * @param oArgs.column {YAHOO.widget.Column} The Column instance.
13395      */
13396     /**
13397      * Fired when a column is removed.
13398      *
13399      * @event columnRemoveEvent
13400      * @param oArgs.column {YAHOO.widget.Column} The Column instance.
13401      */
13402
13403     /**
13404      * Fired when a column is inserted.
13405      *
13406      * @event columnInsertEvent
13407      * @param oArgs.column {YAHOO.widget.Column} The Column instance.
13408      * @param oArgs.index {Number} The index position.
13409      */
13410
13411     /**
13412      * Fired when a column is highlighted.
13413      *
13414      * @event columnHighlightEvent
13415      * @param oArgs.column {YAHOO.widget.Column} The highlighted Column.
13416      */
13417
13418     /**
13419      * Fired when a column is unhighlighted.
13420      *
13421      * @event columnUnhighlightEvent
13422      * @param oArgs.column {YAHOO.widget.Column} The unhighlighted Column.
13423      */
13424
13425
13426     /**
13427      * Fired when a row has a mouseover.
13428      *
13429      * @event rowMouseoverEvent
13430      * @param oArgs.event {HTMLEvent} The event object.
13431      * @param oArgs.target {HTMLElement} The TR element.
13432      */
13433
13434     /**
13435      * Fired when a row has a mouseout.
13436      *
13437      * @event rowMouseoutEvent
13438      * @param oArgs.event {HTMLEvent} The event object.
13439      * @param oArgs.target {HTMLElement} The TR element.
13440      */
13441
13442     /**
13443      * Fired when a row has a mousedown.
13444      *
13445      * @event rowMousedownEvent
13446      * @param oArgs.event {HTMLEvent} The event object.
13447      * @param oArgs.target {HTMLElement} The TR element.
13448      */
13449
13450     /**
13451      * Fired when a row has a mouseup.
13452      *
13453      * @event rowMouseupEvent
13454      * @param oArgs.event {HTMLEvent} The event object.
13455      * @param oArgs.target {HTMLElement} The TR element.
13456      */
13457
13458     /**
13459      * Fired when a row has a click.
13460      *
13461      * @event rowClickEvent
13462      * @param oArgs.event {HTMLEvent} The event object.
13463      * @param oArgs.target {HTMLElement} The TR element.
13464      */
13465
13466     /**
13467      * Fired when a row has a dblclick.
13468      *
13469      * @event rowDblclickEvent
13470      * @param oArgs.event {HTMLEvent} The event object.
13471      * @param oArgs.target {HTMLElement} The TR element.
13472      */
13473
13474     /**
13475      * Fired when a row is added.
13476      *
13477      * @event rowAddEvent
13478      * @param oArgs.record {YAHOO.widget.Record} The added Record.
13479      */
13480      
13481     /**
13482      * Fired when rows are added.
13483      *
13484      * @event rowsAddEvent
13485      * @param oArgs.record {YAHOO.widget.Record[]} The added Records.
13486      */
13487
13488     /**
13489      * Fired when a row is updated.
13490      *
13491      * @event rowUpdateEvent
13492      * @param oArgs.record {YAHOO.widget.Record} The updated Record.
13493      * @param oArgs.oldData {Object} Object literal of the old data.
13494      */
13495
13496     /**
13497      * Fired when a row is deleted.
13498      *
13499      * @event rowDeleteEvent
13500      * @param oArgs.oldData {Object} Object literal of the deleted data.
13501      * @param oArgs.recordIndex {Number} Index of the deleted Record.
13502      * @param oArgs.trElIndex {Number} Index of the deleted TR element, if on current page.
13503      */
13504      
13505     /**
13506      * Fired when rows are deleted.
13507      *
13508      * @event rowsDeleteEvent
13509      * @param oArgs.oldData {Object[]} Array of object literals of the deleted data.
13510      * @param oArgs.recordIndex {Number} Index of the first deleted Record.
13511      * @param oArgs.count {Number} Number of deleted Records.
13512      */
13513
13514     /**
13515      * Fired when a row is selected.
13516      *
13517      * @event rowSelectEvent
13518      * @param oArgs.el {HTMLElement} The selected TR element, if applicable.
13519      * @param oArgs.record {YAHOO.widget.Record} The selected Record.
13520      */
13521
13522     /**
13523      * Fired when a row is unselected.
13524      *
13525      * @event rowUnselectEvent
13526      * @param oArgs.el {HTMLElement} The unselected TR element, if applicable.
13527      * @param oArgs.record {YAHOO.widget.Record} The unselected Record.
13528      */
13529
13530     /**
13531      * Fired when all row selections are cleared.
13532      *
13533      * @event unselectAllRowsEvent
13534      */
13535
13536     /**
13537      * Fired when a row is highlighted.
13538      *
13539      * @event rowHighlightEvent
13540      * @param oArgs.el {HTMLElement} The highlighted TR element.
13541      * @param oArgs.record {YAHOO.widget.Record} The highlighted Record.
13542      */
13543
13544     /**
13545      * Fired when a row is unhighlighted.
13546      *
13547      * @event rowUnhighlightEvent
13548      * @param oArgs.el {HTMLElement} The highlighted TR element.
13549      * @param oArgs.record {YAHOO.widget.Record} The highlighted Record.
13550      */
13551
13552     /**
13553      * Fired when a cell is updated.
13554      *
13555      * @event cellUpdateEvent
13556      * @param oArgs.record {YAHOO.widget.Record} The updated Record.
13557      * @param oArgs.column {YAHOO.widget.Column} The updated Column.
13558      * @param oArgs.oldData {Object} Original data value of the updated cell.
13559      */
13560
13561     /**
13562      * Fired when a cell has a mouseover.
13563      *
13564      * @event cellMouseoverEvent
13565      * @param oArgs.event {HTMLEvent} The event object.
13566      * @param oArgs.target {HTMLElement} The TD element.
13567      */
13568
13569     /**
13570      * Fired when a cell has a mouseout.
13571      *
13572      * @event cellMouseoutEvent
13573      * @param oArgs.event {HTMLEvent} The event object.
13574      * @param oArgs.target {HTMLElement} The TD element.
13575      */
13576
13577     /**
13578      * Fired when a cell has a mousedown.
13579      *
13580      * @event cellMousedownEvent
13581      * @param oArgs.event {HTMLEvent} The event object.
13582      * @param oArgs.target {HTMLElement} The TD element.
13583      */
13584
13585     /**
13586      * Fired when a cell has a mouseup.
13587      *
13588      * @event cellMouseupEvent
13589      * @param oArgs.event {HTMLEvent} The event object.
13590      * @param oArgs.target {HTMLElement} The TD element.
13591      */
13592
13593     /**
13594      * Fired when a cell has a click.
13595      *
13596      * @event cellClickEvent
13597      * @param oArgs.event {HTMLEvent} The event object.
13598      * @param oArgs.target {HTMLElement} The TD element.
13599      */
13600
13601     /**
13602      * Fired when a cell has a dblclick.
13603      *
13604      * @event cellDblclickEvent
13605      * @param oArgs.event {HTMLEvent} The event object.
13606      * @param oArgs.target {HTMLElement} The TD element.
13607      */
13608
13609     /**
13610      * Fired when a cell is formatted.
13611      *
13612      * @event cellFormatEvent
13613      * @param oArgs.el {HTMLElement} The formatted TD element.
13614      * @param oArgs.record {YAHOO.widget.Record} The associated Record instance.
13615      * @param oArgs.column {YAHOO.widget.Column} The associated Column instance.
13616      * @param oArgs.key {String} (deprecated) The key of the formatted cell.
13617      */
13618
13619     /**
13620      * Fired when a cell is selected.
13621      *
13622      * @event cellSelectEvent
13623      * @param oArgs.el {HTMLElement} The selected TD element.
13624      * @param oArgs.record {YAHOO.widget.Record} The associated Record instance.
13625      * @param oArgs.column {YAHOO.widget.Column} The associated Column instance.
13626      * @param oArgs.key {String} (deprecated) The key of the selected cell.
13627      */
13628
13629     /**
13630      * Fired when a cell is unselected.
13631      *
13632      * @event cellUnselectEvent
13633      * @param oArgs.el {HTMLElement} The unselected TD element.
13634      * @param oArgs.record {YAHOO.widget.Record} The associated Record.
13635      * @param oArgs.column {YAHOO.widget.Column} The associated Column instance.
13636      * @param oArgs.key {String} (deprecated) The key of the unselected cell.
13637
13638      */
13639
13640     /**
13641      * Fired when a cell is highlighted.
13642      *
13643      * @event cellHighlightEvent
13644      * @param oArgs.el {HTMLElement} The highlighted TD element.
13645      * @param oArgs.record {YAHOO.widget.Record} The associated Record instance.
13646      * @param oArgs.column {YAHOO.widget.Column} The associated Column instance.
13647      * @param oArgs.key {String} (deprecated) The key of the highlighted cell.
13648
13649      */
13650
13651     /**
13652      * Fired when a cell is unhighlighted.
13653      *
13654      * @event cellUnhighlightEvent
13655      * @param oArgs.el {HTMLElement} The unhighlighted TD element.
13656      * @param oArgs.record {YAHOO.widget.Record} The associated Record instance.
13657      * @param oArgs.column {YAHOO.widget.Column} The associated Column instance.
13658      * @param oArgs.key {String} (deprecated) The key of the unhighlighted cell.
13659
13660      */
13661
13662     /**
13663      * Fired when all cell selections are cleared.
13664      *
13665      * @event unselectAllCellsEvent
13666      */
13667
13668     /**
13669      * Fired when a CellEditor is shown.
13670      *
13671      * @event editorShowEvent
13672      * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
13673      */
13674
13675     /**
13676      * Fired when a CellEditor has a keydown.
13677      *
13678      * @event editorKeydownEvent
13679      * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
13680      * @param oArgs.event {HTMLEvent} The event object.
13681      */
13682
13683     /**
13684      * Fired when a CellEditor input is reverted.
13685      *
13686      * @event editorRevertEvent
13687      * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
13688      * @param oArgs.newData {Object} New data value from form input field.
13689      * @param oArgs.oldData {Object} Old data value.
13690      */
13691
13692     /**
13693      * Fired when a CellEditor input is saved.
13694      *
13695      * @event editorSaveEvent
13696      * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
13697      * @param oArgs.newData {Object} New data value from form input field.
13698      * @param oArgs.oldData {Object} Old data value.
13699      */
13700
13701     /**
13702      * Fired when a CellEditor input is canceled.
13703      *
13704      * @event editorCancelEvent
13705      * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
13706      */
13707
13708     /**
13709      * Fired when a CellEditor has a blur event.
13710      *
13711      * @event editorBlurEvent
13712      * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
13713      */
13714
13715     /**
13716      * Fired when a CellEditor is blocked.
13717      *
13718      * @event editorBlockEvent
13719      * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
13720      */
13721
13722     /**
13723      * Fired when a CellEditor is unblocked.
13724      *
13725      * @event editorUnblockEvent
13726      * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
13727      */
13728
13729
13730
13731
13732
13733     /**
13734      * Fired when a link is clicked.
13735      *
13736      * @event linkClickEvent
13737      * @param oArgs.event {HTMLEvent} The event object.
13738      * @param oArgs.target {HTMLElement} The A element.
13739      */
13740
13741     /**
13742      * Fired when a BUTTON element or INPUT element of type "button", "image",
13743      * "submit", "reset" is clicked.
13744      *
13745      * @event buttonClickEvent
13746      * @param oArgs.event {HTMLEvent} The event object.
13747      * @param oArgs.target {HTMLElement} The BUTTON element.
13748      */
13749
13750     /**
13751      * Fired when a CHECKBOX element is clicked.
13752      *
13753      * @event checkboxClickEvent
13754      * @param oArgs.event {HTMLEvent} The event object.
13755      * @param oArgs.target {HTMLElement} The CHECKBOX element.
13756      */
13757
13758     /**
13759      * Fired when a SELECT element is changed.
13760      *
13761      * @event dropdownChangeEvent
13762      * @param oArgs.event {HTMLEvent} The event object.
13763      * @param oArgs.target {HTMLElement} The SELECT element.
13764      */
13765
13766     /**
13767      * Fired when a RADIO element is clicked.
13768      *
13769      * @event radioClickEvent
13770      * @param oArgs.event {HTMLEvent} The event object.
13771      * @param oArgs.target {HTMLElement} The RADIO element.
13772      */
13773
13774
13775
13776
13777
13778
13779
13780
13781
13782
13783
13784
13785
13786
13787
13788
13789
13790
13791
13792
13793
13794
13795
13796
13797
13798
13799 /////////////////////////////////////////////////////////////////////////////
13800 //
13801 // Deprecated APIs
13802 //
13803 /////////////////////////////////////////////////////////////////////////////
13804   
13805 /*
13806  * @method showCellEditorBtns
13807  * @deprecated Use CellEditor.renderBtns() 
13808  */
13809 showCellEditorBtns : function(elContainer) {
13810     // Buttons
13811     var elBtnsDiv = elContainer.appendChild(document.createElement("div"));
13812     Dom.addClass(elBtnsDiv, DT.CLASS_BUTTON);
13813
13814     // Save button
13815     var elSaveBtn = elBtnsDiv.appendChild(document.createElement("button"));
13816     Dom.addClass(elSaveBtn, DT.CLASS_DEFAULT);
13817     elSaveBtn.innerHTML = "OK";
13818     Ev.addListener(elSaveBtn, "click", function(oArgs, oSelf) {
13819         oSelf.onEventSaveCellEditor(oArgs, oSelf);
13820         oSelf.focusTbodyEl();
13821     }, this, true);
13822
13823     // Cancel button
13824     var elCancelBtn = elBtnsDiv.appendChild(document.createElement("button"));
13825     elCancelBtn.innerHTML = "Cancel";
13826     Ev.addListener(elCancelBtn, "click", function(oArgs, oSelf) {
13827         oSelf.onEventCancelCellEditor(oArgs, oSelf);
13828         oSelf.focusTbodyEl();
13829     }, this, true);
13830
13831 },
13832
13833 /**
13834  * @method resetCellEditor
13835  * @deprecated Use destroyCellEditor 
13836  */
13837 resetCellEditor : function() {
13838     var elContainer = this._oCellEditor.container;
13839     elContainer.style.display = "none";
13840     Ev.purgeElement(elContainer, true);
13841     elContainer.innerHTML = "";
13842     this._oCellEditor.value = null;
13843     this._oCellEditor.isActive = false;
13844
13845 },
13846
13847 /**
13848  * @event editorUpdateEvent
13849  * @deprecated Use CellEditor class.
13850  */
13851
13852 /**
13853  * @method getBody
13854  * @deprecated Use getTbodyEl().
13855  */
13856 getBody : function() {
13857     // Backward compatibility
13858     return this.getTbodyEl();
13859 },
13860
13861 /**
13862  * @method getCell
13863  * @deprecated Use getTdEl().
13864  */
13865 getCell : function(index) {
13866     // Backward compatibility
13867     return this.getTdEl(index);
13868 },
13869
13870 /**
13871  * @method getRow
13872  * @deprecated Use getTrEl().
13873  */
13874 getRow : function(index) {
13875     // Backward compatibility
13876     return this.getTrEl(index);
13877 },
13878
13879 /**
13880  * @method refreshView
13881  * @deprecated Use render.
13882  */
13883 refreshView : function() {
13884     // Backward compatibility
13885     this.render();
13886 },
13887
13888 /**
13889  * @method select
13890  * @deprecated Use selectRow.
13891  */
13892 select : function(els) {
13893     // Backward compatibility
13894     if(!lang.isArray(els)) {
13895         els = [els];
13896     }
13897     for(var i=0; i<els.length; i++) {
13898         this.selectRow(els[i]);
13899     }
13900 },
13901
13902 /**
13903  * @method onEventEditCell
13904  * @deprecated Use onEventShowCellEditor.
13905  */
13906 onEventEditCell : function(oArgs) {
13907     // Backward compatibility
13908     this.onEventShowCellEditor(oArgs);
13909 },
13910
13911 /**
13912  * @method _syncColWidths
13913  * @deprecated Use validateColumnWidths.
13914  */
13915 _syncColWidths : function() {
13916     // Backward compatibility
13917     this.validateColumnWidths();
13918 }
13919
13920 /**
13921  * @event headerRowMouseoverEvent
13922  * @deprecated Use theadRowMouseoverEvent.
13923  */
13924
13925 /**
13926  * @event headerRowMouseoutEvent
13927  * @deprecated Use theadRowMouseoutEvent.
13928  */
13929
13930 /**
13931  * @event headerRowMousedownEvent
13932  * @deprecated Use theadRowMousedownEvent.
13933  */
13934
13935 /**
13936  * @event headerRowClickEvent
13937  * @deprecated Use theadRowClickEvent.
13938  */
13939
13940 /**
13941  * @event headerRowDblclickEvent
13942  * @deprecated Use theadRowDblclickEvent.
13943  */
13944
13945 /**
13946  * @event headerCellMouseoverEvent
13947  * @deprecated Use theadCellMouseoverEvent.
13948  */
13949
13950 /**
13951  * @event headerCellMouseoutEvent
13952  * @deprecated Use theadCellMouseoutEvent.
13953  */
13954
13955 /**
13956  * @event headerCellMousedownEvent
13957  * @deprecated Use theadCellMousedownEvent.
13958  */
13959
13960 /**
13961  * @event headerCellClickEvent
13962  * @deprecated Use theadCellClickEvent.
13963  */
13964
13965 /**
13966  * @event headerCellDblclickEvent
13967  * @deprecated Use theadCellDblclickEvent.
13968  */
13969
13970 /**
13971  * @event headerLabelMouseoverEvent
13972  * @deprecated Use theadLabelMouseoverEvent.
13973  */
13974
13975 /**
13976  * @event headerLabelMouseoutEvent
13977  * @deprecated Use theadLabelMouseoutEvent.
13978  */
13979
13980 /**
13981  * @event headerLabelMousedownEvent
13982  * @deprecated Use theadLabelMousedownEvent.
13983  */
13984
13985 /**
13986  * @event headerLabelClickEvent
13987  * @deprecated Use theadLabelClickEvent.
13988  */
13989
13990 /**
13991  * @event headerLabelDbllickEvent
13992  * @deprecated Use theadLabelDblclickEvent.
13993  */
13994
13995 });
13996
13997 /**
13998  * Alias for onDataReturnSetRows for backward compatibility
13999  * @method onDataReturnSetRecords
14000  * @deprecated Use onDataReturnSetRows
14001  */
14002 DT.prototype.onDataReturnSetRecords = DT.prototype.onDataReturnSetRows;
14003
14004 /**
14005  * Alias for onPaginatorChange for backward compatibility
14006  * @method onPaginatorChange
14007  * @deprecated Use onPaginatorChangeRequest
14008  */
14009 DT.prototype.onPaginatorChange = DT.prototype.onPaginatorChangeRequest;
14010
14011 /////////////////////////////////////////////////////////////////////////////
14012 //
14013 // Deprecated static APIs
14014 //
14015 /////////////////////////////////////////////////////////////////////////////
14016 /**
14017  * @method DataTable.formatTheadCell
14018  * @deprecated  Use formatTheadCell.
14019  */
14020 DT.formatTheadCell = function() {};
14021
14022 /**
14023  * @method DataTable.editCheckbox
14024  * @deprecated  Use YAHOO.widget.CheckboxCellEditor.
14025  */
14026 DT.editCheckbox = function() {};
14027
14028 /**
14029  * @method DataTable.editDate
14030  * @deprecated Use YAHOO.widget.DateCellEditor.
14031  */
14032 DT.editDate = function() {};
14033
14034 /**
14035  * @method DataTable.editDropdown
14036  * @deprecated Use YAHOO.widget.DropdownCellEditor.
14037  */
14038 DT.editDropdown = function() {};
14039
14040 /**
14041  * @method DataTable.editRadio
14042  * @deprecated Use YAHOO.widget.RadioCellEditor.
14043  */
14044 DT.editRadio = function() {};
14045
14046 /**
14047  * @method DataTable.editTextarea
14048  * @deprecated Use YAHOO.widget.TextareaCellEditor
14049  */
14050 DT.editTextarea = function() {};
14051
14052 /**
14053  * @method DataTable.editTextbox
14054  * @deprecated Use YAHOO.widget.TextboxCellEditor
14055  */
14056 DT.editTextbox= function() {};
14057
14058 })();
14059
14060 (function () {
14061
14062 var lang   = YAHOO.lang,
14063     util   = YAHOO.util,
14064     widget = YAHOO.widget,
14065     ua     = YAHOO.env.ua,
14066     
14067     Dom    = util.Dom,
14068     Ev     = util.Event,
14069     DS     = util.DataSourceBase,
14070     DT     = widget.DataTable,
14071     Pag    = widget.Paginator;
14072     
14073 /**
14074  * The ScrollingDataTable class extends the DataTable class to provide
14075  * functionality for x-scrolling, y-scrolling, and xy-scrolling.
14076  *
14077  * @namespace YAHOO.widget
14078  * @class ScrollingDataTable
14079  * @extends YAHOO.widget.DataTable
14080  * @constructor
14081  * @param elContainer {HTMLElement} Container element for the TABLE.
14082  * @param aColumnDefs {Object[]} Array of object literal Column definitions.
14083  * @param oDataSource {YAHOO.util.DataSource} DataSource instance.
14084  * @param oConfigs {object} (optional) Object literal of configuration values.
14085  */
14086 widget.ScrollingDataTable = function(elContainer,aColumnDefs,oDataSource,oConfigs) {
14087     oConfigs = oConfigs || {};
14088     
14089     // Prevent infinite loop
14090     if(oConfigs.scrollable) {
14091         oConfigs.scrollable = false;
14092     }
14093
14094     widget.ScrollingDataTable.superclass.constructor.call(this, elContainer,aColumnDefs,oDataSource,oConfigs); 
14095
14096     // Once per instance
14097     this.subscribe("columnShowEvent", this._onColumnChange);
14098 };
14099
14100 var SDT = widget.ScrollingDataTable;
14101
14102 /////////////////////////////////////////////////////////////////////////////
14103 //
14104 // Public constants
14105 //
14106 /////////////////////////////////////////////////////////////////////////////
14107 lang.augmentObject(SDT, {
14108
14109     /**
14110      * Class name assigned to inner DataTable header container.
14111      *
14112      * @property DataTable.CLASS_HEADER
14113      * @type String
14114      * @static
14115      * @final
14116      * @default "yui-dt-hd"
14117      */
14118     CLASS_HEADER : "yui-dt-hd",
14119     
14120     /**
14121      * Class name assigned to inner DataTable body container.
14122      *
14123      * @property DataTable.CLASS_BODY
14124      * @type String
14125      * @static
14126      * @final
14127      * @default "yui-dt-bd"
14128      */
14129     CLASS_BODY : "yui-dt-bd"
14130 });
14131
14132 lang.extend(SDT, DT, {
14133
14134 /**
14135  * Container for fixed header TABLE element.
14136  *
14137  * @property _elHdContainer
14138  * @type HTMLElement
14139  * @private
14140  */
14141 _elHdContainer : null,
14142
14143 /**
14144  * Fixed header TABLE element.
14145  *
14146  * @property _elHdTable
14147  * @type HTMLElement
14148  * @private
14149  */
14150 _elHdTable : null,
14151
14152 /**
14153  * Container for scrolling body TABLE element.
14154  *
14155  * @property _elBdContainer
14156  * @type HTMLElement
14157  * @private
14158  */
14159 _elBdContainer : null,
14160
14161 /**
14162  * Body THEAD element.
14163  *
14164  * @property _elBdThead
14165  * @type HTMLElement
14166  * @private
14167  */
14168 _elBdThead : null,
14169
14170 /**
14171  * Offscreen container to temporarily clone SDT for auto-width calculation.
14172  *
14173  * @property _elTmpContainer
14174  * @type HTMLElement
14175  * @private
14176  */
14177 _elTmpContainer : null,
14178
14179 /**
14180  * Offscreen TABLE element for auto-width calculation.
14181  *
14182  * @property _elTmpTable
14183  * @type HTMLElement
14184  * @private
14185  */
14186 _elTmpTable : null,
14187
14188 /**
14189  * True if x-scrollbar is currently visible.
14190  * @property _bScrollbarX
14191  * @type Boolean
14192  * @private 
14193  */
14194 _bScrollbarX : null,
14195
14196
14197
14198
14199
14200
14201
14202
14203
14204
14205
14206
14207
14208
14209
14210 /////////////////////////////////////////////////////////////////////////////
14211 //
14212 // Superclass methods
14213 //
14214 /////////////////////////////////////////////////////////////////////////////
14215
14216 /**
14217  * Implementation of Element's abstract method. Sets up config values.
14218  *
14219  * @method initAttributes
14220  * @param oConfigs {Object} (Optional) Object literal definition of configuration values.
14221  * @private
14222  */
14223
14224 initAttributes : function(oConfigs) {
14225     oConfigs = oConfigs || {};
14226     SDT.superclass.initAttributes.call(this, oConfigs);
14227
14228     /**
14229     * @attribute width
14230     * @description Table width for scrollable tables (e.g., "40em").
14231     * @type String
14232     */
14233     this.setAttributeConfig("width", {
14234         value: null,
14235         validator: lang.isString,
14236         method: function(oParam) {
14237             if(this._elHdContainer && this._elBdContainer) {
14238                 this._elHdContainer.style.width = oParam;
14239                 this._elBdContainer.style.width = oParam;            
14240                 this._syncScrollX();      
14241                 this._syncScrollOverhang();
14242             }
14243         }
14244     });
14245
14246     /**
14247     * @attribute height
14248     * @description Table body height for scrollable tables, not including headers (e.g., "40em").
14249     * @type String
14250     */
14251     this.setAttributeConfig("height", {
14252         value: null,
14253         validator: lang.isString,
14254         method: function(oParam) {
14255             if(this._elHdContainer && this._elBdContainer) {
14256                 this._elBdContainer.style.height = oParam;    
14257                 this._syncScrollX();   
14258                 this._syncScrollY();
14259                 this._syncScrollOverhang();
14260             }
14261         }
14262     });
14263
14264     /**
14265     * @attribute COLOR_COLUMNFILLER
14266     * @description CSS color value assigned to header filler on scrollable tables.  
14267     * @type String
14268     * @default "#F2F2F2"
14269     */
14270     this.setAttributeConfig("COLOR_COLUMNFILLER", {
14271         value: "#F2F2F2",
14272         validator: lang.isString,
14273         method: function(oParam) {
14274             this._elHdContainer.style.backgroundColor = oParam;
14275         }
14276     });
14277 },
14278
14279 /**
14280  * Initializes DOM elements for a ScrollingDataTable, including creation of
14281  * two separate TABLE elements.
14282  *
14283  * @method _initDomElements
14284  * @param elContainer {HTMLElement | String} HTML DIV element by reference or ID. 
14285  * return {Boolean} False in case of error, otherwise true 
14286  * @private
14287  */
14288 _initDomElements : function(elContainer) {
14289     // Outer and inner containers
14290     this._initContainerEl(elContainer);
14291     if(this._elContainer && this._elHdContainer && this._elBdContainer) {
14292         // TABLEs
14293         this._initTableEl();
14294         
14295         if(this._elHdTable && this._elTable) {
14296             // COLGROUPs
14297             ///this._initColgroupEl(this._elHdTable, this._elTable);  
14298             this._initColgroupEl(this._elHdTable);        
14299             
14300             // THEADs
14301             this._initTheadEl(this._elHdTable, this._elTable);
14302             
14303             // Primary TBODY
14304             this._initTbodyEl(this._elTable);
14305             // Message TBODY
14306             this._initMsgTbodyEl(this._elTable);            
14307         }
14308     }
14309     if(!this._elContainer || !this._elTable || !this._elColgroup ||  !this._elThead || !this._elTbody || !this._elMsgTbody ||
14310             !this._elHdTable || !this._elBdThead) {
14311         return false;
14312     }
14313     else {
14314         return true;
14315     }
14316 },
14317
14318 /**
14319  * Destroy's the DataTable outer and inner container elements, if available.
14320  *
14321  * @method _destroyContainerEl
14322  * @param elContainer {HTMLElement} Reference to the container element. 
14323  * @private
14324  */
14325 _destroyContainerEl : function(elContainer) {
14326     Dom.removeClass(elContainer, DT.CLASS_SCROLLABLE);
14327     SDT.superclass._destroyContainerEl.call(this, elContainer);
14328     this._elHdContainer = null;
14329     this._elBdContainer = null;
14330 },
14331
14332 /**
14333  * Initializes the DataTable outer container element and creates inner header
14334  * and body container elements.
14335  *
14336  * @method _initContainerEl
14337  * @param elContainer {HTMLElement | String} HTML DIV element by reference or ID.
14338  * @private
14339  */
14340 _initContainerEl : function(elContainer) {
14341     SDT.superclass._initContainerEl.call(this, elContainer);
14342     
14343     if(this._elContainer) {
14344         elContainer = this._elContainer; // was constructor input, now is DOM ref
14345         Dom.addClass(elContainer, DT.CLASS_SCROLLABLE);
14346         
14347         // Container for header TABLE
14348         var elHdContainer = document.createElement("div");
14349         elHdContainer.style.width = this.get("width") || "";
14350         elHdContainer.style.backgroundColor = this.get("COLOR_COLUMNFILLER");
14351         Dom.addClass(elHdContainer, SDT.CLASS_HEADER);
14352         this._elHdContainer = elHdContainer;
14353         elContainer.appendChild(elHdContainer);
14354     
14355         // Container for body TABLE
14356         var elBdContainer = document.createElement("div");
14357         elBdContainer.style.width = this.get("width") || "";
14358         elBdContainer.style.height = this.get("height") || "";
14359         Dom.addClass(elBdContainer, SDT.CLASS_BODY);
14360         Ev.addListener(elBdContainer, "scroll", this._onScroll, this); // to sync horiz scroll headers
14361         this._elBdContainer = elBdContainer;
14362         elContainer.appendChild(elBdContainer);
14363     }
14364 },
14365
14366 /**
14367  * Creates HTML markup CAPTION element.
14368  *
14369  * @method _initCaptionEl
14370  * @param sCaption {String} Text for caption.
14371  * @private
14372  */
14373 _initCaptionEl : function(sCaption) {
14374     // Not yet supported
14375     /*if(this._elHdTable && sCaption) {
14376         // Create CAPTION element
14377         if(!this._elCaption) { 
14378             this._elCaption = this._elHdTable.createCaption();
14379         }
14380         // Set CAPTION value
14381         this._elCaption.innerHTML = sCaption;
14382     }
14383     else if(this._elCaption) {
14384         this._elCaption.parentNode.removeChild(this._elCaption);
14385     }*/
14386 },
14387
14388 /**
14389  * Destroy's the DataTable head TABLE element, if available.
14390  *
14391  * @method _destroyHdTableEl
14392  * @private
14393  */
14394 _destroyHdTableEl : function() {
14395     var elTable = this._elHdTable;
14396     if(elTable) {
14397         Ev.purgeElement(elTable, true);
14398         elTable.parentNode.removeChild(elTable);
14399         
14400         // A little out of place, but where else can we null out these extra elements?
14401         ///this._elBdColgroup = null;
14402         this._elBdThead = null;
14403     }
14404 },
14405
14406 /**
14407  * Initializes ScrollingDataTable TABLE elements into the two inner containers.
14408  *
14409  * @method _initTableEl
14410  * @private
14411  */
14412 _initTableEl : function() {
14413     // Head TABLE
14414     if(this._elHdContainer) {
14415         this._destroyHdTableEl();
14416     
14417         // Create TABLE
14418         this._elHdTable = this._elHdContainer.appendChild(document.createElement("table"));   
14419     } 
14420     // Body TABLE
14421     SDT.superclass._initTableEl.call(this, this._elBdContainer);
14422 },
14423
14424 /**
14425  * Initializes ScrollingDataTable THEAD elements into the two inner containers.
14426  *
14427  * @method _initTheadEl
14428  * @param elHdTable {HTMLElement} (optional) Fixed header TABLE element reference.
14429  * @param elTable {HTMLElement} (optional) TABLE element reference.
14430  * @private
14431  */
14432 _initTheadEl : function(elHdTable, elTable) {
14433     elHdTable = elHdTable || this._elHdTable;
14434     elTable = elTable || this._elTable;
14435     
14436     // Scrolling body's THEAD
14437     this._initBdTheadEl(elTable);
14438     // Standard fixed head THEAD
14439     SDT.superclass._initTheadEl.call(this, elHdTable);
14440 },
14441
14442 /**
14443  * SDT changes ID so as not to duplicate the accessibility TH IDs.
14444  *
14445  * @method _initThEl
14446  * @param elTh {HTMLElement} TH element reference.
14447  * @param oColumn {YAHOO.widget.Column} Column object.
14448  * @private
14449  */
14450 _initThEl : function(elTh, oColumn) {
14451     SDT.superclass._initThEl.call(this, elTh, oColumn);
14452     elTh.id = this.getId() +"-fixedth-" + oColumn.getSanitizedKey(); // Needed for getColumn by TH and ColumnDD
14453 },
14454
14455 /**
14456  * Destroy's the DataTable body THEAD element, if available.
14457  *
14458  * @method _destroyBdTheadEl
14459  * @private
14460  */
14461 _destroyBdTheadEl : function() {
14462     var elBdThead = this._elBdThead;
14463     if(elBdThead) {
14464         var elTable = elBdThead.parentNode;
14465         Ev.purgeElement(elBdThead, true);
14466         elTable.removeChild(elBdThead);
14467         this._elBdThead = null;
14468
14469         this._destroyColumnHelpers();
14470     }
14471 },
14472
14473 /**
14474  * Initializes body THEAD element.
14475  *
14476  * @method _initBdTheadEl
14477  * @param elTable {HTMLElement} TABLE element into which to create THEAD.
14478  * @return {HTMLElement} Initialized THEAD element. 
14479  * @private
14480  */
14481 _initBdTheadEl : function(elTable) {
14482     if(elTable) {
14483         // Destroy previous
14484         this._destroyBdTheadEl();
14485
14486         var elThead = elTable.insertBefore(document.createElement("thead"), elTable.firstChild);
14487         
14488         // Add TRs to the THEAD;
14489         var oColumnSet = this._oColumnSet,
14490             colTree = oColumnSet.tree,
14491             elTh, elTheadTr, oColumn, i, j, k, len;
14492
14493         for(i=0, k=colTree.length; i<k; i++) {
14494             elTheadTr = elThead.appendChild(document.createElement("tr"));
14495     
14496             // ...and create TH cells
14497             for(j=0, len=colTree[i].length; j<len; j++) {
14498                 oColumn = colTree[i][j];
14499                 elTh = elTheadTr.appendChild(document.createElement("th"));
14500                 this._initBdThEl(elTh,oColumn,i,j);
14501             }
14502         }
14503         this._elBdThead = elThead;
14504     }
14505 },
14506
14507 /**
14508  * Populates TH element for the body THEAD element.
14509  *
14510  * @method _initBdThEl
14511  * @param elTh {HTMLElement} TH element reference.
14512  * @param oColumn {YAHOO.widget.Column} Column object.
14513  * @private
14514  */
14515 _initBdThEl : function(elTh, oColumn) {
14516     elTh.id = this.getId()+"-th-" + oColumn.getSanitizedKey(); // Needed for accessibility
14517     elTh.rowSpan = oColumn.getRowspan();
14518     elTh.colSpan = oColumn.getColspan();
14519     // Assign abbr attribute
14520     if(oColumn.abbr) {
14521         elTh.abbr = oColumn.abbr;
14522     }
14523
14524     // TODO: strip links and form elements
14525     var sKey = oColumn.getKey();
14526     var sLabel = lang.isValue(oColumn.label) ? oColumn.label : sKey;
14527     elTh.innerHTML = sLabel;
14528 },
14529
14530 /**
14531  * Initializes ScrollingDataTable TBODY element for data
14532  *
14533  * @method _initTbodyEl
14534  * @param elTable {HTMLElement} TABLE element into which to create TBODY .
14535  * @private
14536  */
14537 _initTbodyEl : function(elTable) {
14538     SDT.superclass._initTbodyEl.call(this, elTable);
14539     
14540     // Bug 2105534 - Safari 3 gap
14541     // Bug 2492591 - IE8 offsetTop
14542     elTable.style.marginTop = (this._elTbody.offsetTop > 0) ?
14543             "-"+this._elTbody.offsetTop+"px" : 0;
14544 },
14545
14546
14547
14548
14549
14550
14551
14552
14553
14554
14555
14556
14557
14558
14559
14560
14561
14562
14563
14564
14565
14566
14567
14568
14569
14570
14571
14572
14573
14574 /**
14575  * Sets focus on the given element.
14576  *
14577  * @method _focusEl
14578  * @param el {HTMLElement} Element.
14579  * @private
14580  */
14581 _focusEl : function(el) {
14582     el = el || this._elTbody;
14583     var oSelf = this;
14584     this._storeScrollPositions();
14585     // http://developer.mozilla.org/en/docs/index.php?title=Key-navigable_custom_DHTML_widgets
14586     // The timeout is necessary in both IE and Firefox 1.5, to prevent scripts from doing
14587     // strange unexpected things as the user clicks on buttons and other controls.
14588     
14589     // Bug 1921135: Wrap the whole thing in a setTimeout
14590     setTimeout(function() {
14591         setTimeout(function() {
14592             try {
14593                 el.focus();
14594                 oSelf._restoreScrollPositions();
14595             }
14596             catch(e) {
14597             }
14598         },0);
14599     }, 0);
14600 },
14601
14602
14603
14604
14605
14606
14607
14608
14609
14610
14611
14612
14613
14614
14615
14616
14617
14618
14619
14620 /**
14621  * Internal wrapper calls run() on render Chain instance.
14622  *
14623  * @method _runRenderChain
14624  * @private 
14625  */
14626 _runRenderChain : function() {
14627     this._storeScrollPositions();
14628     this._oChainRender.run();
14629 },
14630
14631 /**
14632  * Stores scroll positions so they can be restored after a render.
14633  *
14634  * @method _storeScrollPositions
14635  * @private
14636  */
14637  _storeScrollPositions : function() {
14638     this._nScrollTop = this._elBdContainer.scrollTop;
14639     this._nScrollLeft = this._elBdContainer.scrollLeft;
14640 },
14641
14642 /**
14643  * Clears stored scroll positions to interrupt the automatic restore mechanism.
14644  * Useful for setting scroll positions programmatically rather than as part of
14645  * the post-render cleanup process.
14646  *
14647  * @method clearScrollPositions
14648  * @private
14649  */
14650  clearScrollPositions : function() {
14651     this._nScrollTop = 0;
14652     this._nScrollLeft = 0;
14653 },
14654
14655 /**
14656  * Restores scroll positions to stored value. 
14657  *
14658  * @method _retoreScrollPositions
14659  * @private 
14660  */
14661  _restoreScrollPositions : function() {
14662     // Reset scroll positions
14663     if(this._nScrollTop) {
14664         this._elBdContainer.scrollTop = this._nScrollTop;
14665         this._nScrollTop = null;
14666     } 
14667     if(this._nScrollLeft) {
14668         this._elBdContainer.scrollLeft = this._nScrollLeft;
14669         this._nScrollLeft = null;
14670     } 
14671 },
14672
14673 /**
14674  * Helper function calculates and sets a validated width for a Column in a ScrollingDataTable.
14675  *
14676  * @method _validateColumnWidth
14677  * @param oColumn {YAHOO.widget.Column} Column instance.
14678  * @param elTd {HTMLElement} TD element to validate against.
14679  * @private
14680  */
14681 _validateColumnWidth : function(oColumn, elTd) {
14682     // Only Columns without widths that are not hidden
14683     if(!oColumn.width && !oColumn.hidden) {
14684         var elTh = oColumn.getThEl();
14685         // Unset a calculated auto-width
14686         if(oColumn._calculatedWidth) {
14687             this._setColumnWidth(oColumn, "auto", "visible");
14688         }
14689         // Compare auto-widths
14690         if(elTh.offsetWidth !== elTd.offsetWidth) {
14691             var elWider = (elTh.offsetWidth > elTd.offsetWidth) ?
14692                     oColumn.getThLinerEl() : elTd.firstChild;               
14693
14694             // Grab the wider liner width, unless the minWidth is wider
14695             var newWidth = Math.max(0,
14696                 (elWider.offsetWidth -(parseInt(Dom.getStyle(elWider,"paddingLeft"),10)|0) - (parseInt(Dom.getStyle(elWider,"paddingRight"),10)|0)),
14697                 oColumn.minWidth);
14698                 
14699             var sOverflow = 'visible';
14700             
14701             // Now validate against maxAutoWidth
14702             if((oColumn.maxAutoWidth > 0) && (newWidth > oColumn.maxAutoWidth)) {
14703                 newWidth = oColumn.maxAutoWidth;
14704                 sOverflow = "hidden";
14705             }
14706
14707             // Set to the wider auto-width
14708             this._elTbody.style.display = "none";
14709             this._setColumnWidth(oColumn, newWidth+'px', sOverflow);
14710             oColumn._calculatedWidth = newWidth;
14711             this._elTbody.style.display = "";
14712         }
14713     }
14714 },
14715
14716 /**
14717  * For one or all Columns of a ScrollingDataTable, when Column is not hidden,
14718  * and width is not set, syncs widths of header and body cells and 
14719  * validates that width against minWidth and/or maxAutoWidth as necessary.
14720  *
14721  * @method validateColumnWidths
14722  * @param oArg.column {YAHOO.widget.Column} (optional) One Column to validate. If null, all Columns' widths are validated.
14723  */
14724 validateColumnWidths : function(oColumn) {
14725     // Validate there is at least one TR with proper TDs
14726     var allKeys   = this._oColumnSet.keys,
14727         allKeysLength = allKeys.length,
14728         elRow     = this.getFirstTrEl();
14729
14730     // Reset overhang for IE
14731     if(ua.ie) {
14732         this._setOverhangValue(1);
14733     }
14734
14735     if(allKeys && elRow && (elRow.childNodes.length === allKeysLength)) {
14736         // Temporarily unsnap container since it causes inaccurate calculations
14737         var sWidth = this.get("width");
14738         if(sWidth) {
14739             this._elHdContainer.style.width = "";
14740             this._elBdContainer.style.width = "";
14741         }
14742         this._elContainer.style.width = "";
14743         
14744         //Validate just one Column
14745         if(oColumn && lang.isNumber(oColumn.getKeyIndex())) {
14746             this._validateColumnWidth(oColumn, elRow.childNodes[oColumn.getKeyIndex()]);
14747         }
14748         // Iterate through all Columns to unset calculated widths in one pass
14749         else {
14750             var elTd, todos = [], thisTodo, i, len;
14751             for(i=0; i<allKeysLength; i++) {
14752                 oColumn = allKeys[i];
14753                 // Only Columns without widths that are not hidden, unset a calculated auto-width
14754                 if(!oColumn.width && !oColumn.hidden && oColumn._calculatedWidth) {
14755                     todos[todos.length] = oColumn;      
14756                 }
14757             }
14758             
14759             this._elTbody.style.display = "none";
14760             for(i=0, len=todos.length; i<len; i++) {
14761                 this._setColumnWidth(todos[i], "auto", "visible");
14762             }
14763             this._elTbody.style.display = "";
14764             
14765             todos = [];
14766
14767             // Iterate through all Columns and make the store the adjustments to make in one pass
14768             for(i=0; i<allKeysLength; i++) {
14769                 oColumn = allKeys[i];
14770                 elTd = elRow.childNodes[i];
14771                 // Only Columns without widths that are not hidden
14772                 if(!oColumn.width && !oColumn.hidden) {
14773                     var elTh = oColumn.getThEl();
14774
14775                     // Compare auto-widths
14776                     if(elTh.offsetWidth !== elTd.offsetWidth) {
14777                         var elWider = (elTh.offsetWidth > elTd.offsetWidth) ?
14778                                 oColumn.getThLinerEl() : elTd.firstChild;               
14779                 
14780                         // Grab the wider liner width, unless the minWidth is wider
14781                         var newWidth = Math.max(0,
14782                             (elWider.offsetWidth -(parseInt(Dom.getStyle(elWider,"paddingLeft"),10)|0) - (parseInt(Dom.getStyle(elWider,"paddingRight"),10)|0)),
14783                             oColumn.minWidth);
14784                             
14785                         var sOverflow = 'visible';
14786                         
14787                         // Now validate against maxAutoWidth
14788                         if((oColumn.maxAutoWidth > 0) && (newWidth > oColumn.maxAutoWidth)) {
14789                             newWidth = oColumn.maxAutoWidth;
14790                             sOverflow = "hidden";
14791                         }
14792                 
14793                         todos[todos.length] = [oColumn, newWidth, sOverflow];
14794                     }
14795                 }
14796             }
14797             
14798             this._elTbody.style.display = "none";
14799             for(i=0, len=todos.length; i<len; i++) {
14800                 thisTodo = todos[i];
14801                 // Set to the wider auto-width
14802                 this._setColumnWidth(thisTodo[0], thisTodo[1]+"px", thisTodo[2]);
14803                 thisTodo[0]._calculatedWidth = thisTodo[1];
14804             }
14805             this._elTbody.style.display = "";
14806         }
14807     
14808         // Resnap unsnapped containers
14809         if(sWidth) {
14810             this._elHdContainer.style.width = sWidth;
14811             this._elBdContainer.style.width = sWidth;
14812         } 
14813     }
14814     
14815     this._syncScroll();
14816     this._restoreScrollPositions();
14817 },
14818
14819 /**
14820  * Syncs padding around scrollable tables, including Column header right-padding
14821  * and container width and height.
14822  *
14823  * @method _syncScroll
14824  * @private 
14825  */
14826 _syncScroll : function() {
14827     this._syncScrollX();
14828     this._syncScrollY();
14829     this._syncScrollOverhang();
14830     if(ua.opera) {
14831         // Bug 1925874
14832         this._elHdContainer.scrollLeft = this._elBdContainer.scrollLeft;
14833         if(!this.get("width")) {
14834             // Bug 1926125
14835             document.body.style += '';
14836         }
14837     }
14838  },
14839
14840 /**
14841  * Snaps container width for y-scrolling tables.
14842  *
14843  * @method _syncScrollY
14844  * @private
14845  */
14846 _syncScrollY : function() {
14847     var elTbody = this._elTbody,
14848         elBdContainer = this._elBdContainer;
14849     
14850     // X-scrolling not enabled
14851     if(!this.get("width")) {
14852         // Snap outer container width to content
14853         this._elContainer.style.width = 
14854                 (elBdContainer.scrollHeight > elBdContainer.clientHeight) ?
14855                 // but account for y-scrollbar since it is visible
14856                 (elTbody.parentNode.clientWidth + 19) + "px" :
14857                 // no y-scrollbar, just borders
14858                 (elTbody.parentNode.clientWidth + 2) + "px";
14859     }
14860 },
14861
14862 /**
14863  * Snaps container height for x-scrolling tables in IE. Syncs message TBODY width.
14864  *
14865  * @method _syncScrollX
14866  * @private
14867  */
14868 _syncScrollX : function() {
14869     var elTbody = this._elTbody,
14870         elBdContainer = this._elBdContainer;
14871     
14872     // IE 6 and 7 only when y-scrolling not enabled
14873     if(!this.get("height") && (ua.ie)) {
14874         // Snap outer container height to content
14875         elBdContainer.style.height = 
14876                 // but account for x-scrollbar if it is visible
14877                 (elBdContainer.scrollWidth > elBdContainer.offsetWidth ) ?
14878                 (elTbody.parentNode.offsetHeight + 18) + "px" : 
14879                 elTbody.parentNode.offsetHeight + "px";
14880     }
14881
14882     // Sync message tbody
14883     if(this._elTbody.rows.length === 0) {
14884         this._elMsgTbody.parentNode.style.width = this.getTheadEl().parentNode.offsetWidth + "px";
14885     }
14886     else {
14887         this._elMsgTbody.parentNode.style.width = "";
14888     }
14889 },
14890
14891 /**
14892  * Adds/removes Column header overhang as necesary.
14893  *
14894  * @method _syncScrollOverhang
14895  * @private
14896  */
14897 _syncScrollOverhang : function() {
14898     var elBdContainer = this._elBdContainer,
14899         // Overhang should be either 1 (default) or 18px, depending on the location of the right edge of the table
14900         nPadding = 1;
14901     
14902     // Y-scrollbar is visible, which is when the overhang needs to jut out
14903     if((elBdContainer.scrollHeight > elBdContainer.clientHeight) &&
14904         // X-scrollbar is also visible, which means the right is jagged, not flush with the Column
14905         (elBdContainer.scrollWidth > elBdContainer.clientWidth)) {
14906         nPadding = 18;
14907     }
14908     
14909     this._setOverhangValue(nPadding);
14910     
14911 },
14912
14913 /**
14914  * Sets Column header overhang to given width.
14915  *
14916  * @method _setOverhangValue
14917  * @param nBorderWidth {Number} Value of new border for overhang. 
14918  * @private
14919  */
14920 _setOverhangValue : function(nBorderWidth) {
14921     var aLastHeaders = this._oColumnSet.headers[this._oColumnSet.headers.length-1] || [],
14922         len = aLastHeaders.length,
14923         sPrefix = this._sId+"-fixedth-",
14924         sValue = nBorderWidth + "px solid " + this.get("COLOR_COLUMNFILLER");
14925
14926     this._elThead.style.display = "none";
14927     for(var i=0; i<len; i++) {
14928         Dom.get(sPrefix+aLastHeaders[i]).style.borderRight = sValue;
14929     }
14930     this._elThead.style.display = "";
14931 },
14932
14933
14934
14935
14936
14937
14938
14939
14940
14941
14942
14943
14944
14945
14946
14947
14948
14949
14950
14951
14952
14953
14954
14955
14956
14957
14958
14959
14960
14961
14962
14963
14964
14965
14966
14967
14968
14969
14970 /**
14971  * Returns DOM reference to the DataTable's fixed header container element.
14972  *
14973  * @method getHdContainerEl
14974  * @return {HTMLElement} Reference to DIV element.
14975  */
14976 getHdContainerEl : function() {
14977     return this._elHdContainer;
14978 },
14979
14980 /**
14981  * Returns DOM reference to the DataTable's scrolling body container element.
14982  *
14983  * @method getBdContainerEl
14984  * @return {HTMLElement} Reference to DIV element.
14985  */
14986 getBdContainerEl : function() {
14987     return this._elBdContainer;
14988 },
14989
14990 /**
14991  * Returns DOM reference to the DataTable's fixed header TABLE element.
14992  *
14993  * @method getHdTableEl
14994  * @return {HTMLElement} Reference to TABLE element.
14995  */
14996 getHdTableEl : function() {
14997     return this._elHdTable;
14998 },
14999
15000 /**
15001  * Returns DOM reference to the DataTable's scrolling body TABLE element.
15002  *
15003  * @method getBdTableEl
15004  * @return {HTMLElement} Reference to TABLE element.
15005  */
15006 getBdTableEl : function() {
15007     return this._elTable;
15008 },
15009
15010 /**
15011  * Disables ScrollingDataTable UI.
15012  *
15013  * @method disable
15014  */
15015 disable : function() {
15016     var elMask = this._elMask;
15017     elMask.style.width = this._elBdContainer.offsetWidth + "px";
15018     elMask.style.height = this._elHdContainer.offsetHeight + this._elBdContainer.offsetHeight + "px";
15019     elMask.style.display = "";
15020     this.fireEvent("disableEvent");
15021 },
15022
15023 /**
15024  * Removes given Column. NOTE: You cannot remove nested Columns. You can only remove
15025  * non-nested Columns, and top-level parent Columns (which will remove all
15026  * children Columns).
15027  *
15028  * @method removeColumn
15029  * @param oColumn {YAHOO.widget.Column} Column instance.
15030  * @return oColumn {YAHOO.widget.Column} Removed Column instance.
15031  */
15032 removeColumn : function(oColumn) {
15033     // Store scroll pos
15034     var hdPos = this._elHdContainer.scrollLeft;
15035     var bdPos = this._elBdContainer.scrollLeft;
15036     
15037     // Call superclass method
15038     oColumn = SDT.superclass.removeColumn.call(this, oColumn);
15039     
15040     // Restore scroll pos
15041     this._elHdContainer.scrollLeft = hdPos;
15042     this._elBdContainer.scrollLeft = bdPos;
15043     
15044     return oColumn;
15045 },
15046
15047 /**
15048  * Inserts given Column at the index if given, otherwise at the end. NOTE: You
15049  * can only add non-nested Columns and top-level parent Columns. You cannot add
15050  * a nested Column to an existing parent.
15051  *
15052  * @method insertColumn
15053  * @param oColumn {Object | YAHOO.widget.Column} Object literal Column
15054  * definition or a Column instance.
15055  * @param index {Number} (optional) New tree index.
15056  * @return oColumn {YAHOO.widget.Column} Inserted Column instance. 
15057  */
15058 insertColumn : function(oColumn, index) {
15059     // Store scroll pos
15060     var hdPos = this._elHdContainer.scrollLeft;
15061     var bdPos = this._elBdContainer.scrollLeft;
15062     
15063     // Call superclass method
15064     var oNewColumn = SDT.superclass.insertColumn.call(this, oColumn, index);
15065     
15066     // Restore scroll pos
15067     this._elHdContainer.scrollLeft = hdPos;
15068     this._elBdContainer.scrollLeft = bdPos;
15069     
15070     return oNewColumn;
15071 },
15072
15073 /**
15074  * Removes given Column and inserts into given tree index. NOTE: You
15075  * can only reorder non-nested Columns and top-level parent Columns. You cannot
15076  * reorder a nested Column to an existing parent.
15077  *
15078  * @method reorderColumn
15079  * @param oColumn {YAHOO.widget.Column} Column instance.
15080  * @param index {Number} New tree index.
15081  */
15082 reorderColumn : function(oColumn, index) {
15083     // Store scroll pos
15084     var hdPos = this._elHdContainer.scrollLeft;
15085     var bdPos = this._elBdContainer.scrollLeft;
15086     
15087     // Call superclass method
15088     var oNewColumn = SDT.superclass.reorderColumn.call(this, oColumn, index);
15089     
15090     // Restore scroll pos
15091     this._elHdContainer.scrollLeft = hdPos;
15092     this._elBdContainer.scrollLeft = bdPos;
15093
15094     return oNewColumn;
15095 },
15096
15097 /**
15098  * Sets given Column to given pixel width. If new width is less than minWidth
15099  * width, sets to minWidth. Updates oColumn.width value.
15100  *
15101  * @method setColumnWidth
15102  * @param oColumn {YAHOO.widget.Column} Column instance.
15103  * @param nWidth {Number} New width in pixels.
15104  */
15105 setColumnWidth : function(oColumn, nWidth) {
15106     oColumn = this.getColumn(oColumn);
15107     if(oColumn) {
15108         this._storeScrollPositions();
15109
15110         // Validate new width against minWidth
15111         if(lang.isNumber(nWidth)) {
15112             nWidth = (nWidth > oColumn.minWidth) ? nWidth : oColumn.minWidth;
15113
15114             // Save state
15115             oColumn.width = nWidth;
15116             
15117             // Resize the DOM elements
15118             this._setColumnWidth(oColumn, nWidth+"px");
15119             this._syncScroll();
15120             
15121             this.fireEvent("columnSetWidthEvent",{column:oColumn,width:nWidth});
15122         }
15123         // Unsets a width to auto-size
15124         else if(nWidth === null) {
15125             // Save state
15126             oColumn.width = nWidth;
15127             
15128             // Resize the DOM elements
15129             this._setColumnWidth(oColumn, "auto");
15130             this.validateColumnWidths(oColumn);
15131             this.fireEvent("columnUnsetWidthEvent",{column:oColumn});
15132         }
15133         
15134         // Bug 2339454: resize then sort misaligment
15135         this._clearTrTemplateEl();
15136     }
15137     else {
15138     }
15139 },
15140
15141 /**
15142  * Scrolls to given row or cell
15143  *
15144  * @method scrollTo
15145  * @param to {YAHOO.widget.Record | HTMLElement } Itme to scroll to.
15146  */
15147 scrollTo : function(to) {
15148         var td = this.getTdEl(to);
15149         if(td) {
15150             this.clearScrollPositions();
15151             this.getBdContainerEl().scrollLeft = td.offsetLeft;
15152             this.getBdContainerEl().scrollTop = td.parentNode.offsetTop;
15153         }
15154         else {
15155             var tr = this.getTrEl(to);
15156             if(tr) {
15157                 this.clearScrollPositions();
15158                 this.getBdContainerEl().scrollTop = tr.offsetTop;
15159             }
15160         }
15161 },
15162
15163 /**
15164  * Displays message within secondary TBODY.
15165  *
15166  * @method showTableMessage
15167  * @param sHTML {String} (optional) Value for innerHTMlang.
15168  * @param sClassName {String} (optional) Classname.
15169  */
15170 showTableMessage : function(sHTML, sClassName) {
15171     var elCell = this._elMsgTd;
15172     if(lang.isString(sHTML)) {
15173         elCell.firstChild.innerHTML = sHTML;
15174     }
15175     if(lang.isString(sClassName)) {
15176         Dom.addClass(elCell.firstChild, sClassName);
15177     }
15178
15179     // Needed for SDT only
15180     var elThead = this.getTheadEl();
15181     var elTable = elThead.parentNode;
15182     var newWidth = elTable.offsetWidth;
15183     this._elMsgTbody.parentNode.style.width = this.getTheadEl().parentNode.offsetWidth + "px";
15184
15185     this._elMsgTbody.style.display = "";
15186
15187     this.fireEvent("tableMsgShowEvent", {html:sHTML, className:sClassName});
15188 },
15189
15190
15191
15192
15193
15194
15195
15196
15197
15198
15199
15200
15201
15202 /////////////////////////////////////////////////////////////////////////////
15203 //
15204 // Private Custom Event Handlers
15205 //
15206 /////////////////////////////////////////////////////////////////////////////
15207
15208 /**
15209  * Handles Column mutations
15210  *
15211  * @method onColumnChange
15212  * @param oArgs {Object} Custom Event data.
15213  */
15214 _onColumnChange : function(oArg) {
15215     // Figure out which Column changed
15216     var oColumn = (oArg.column) ? oArg.column :
15217             (oArg.editor) ? oArg.editor.column : null;
15218     this._storeScrollPositions();
15219     this.validateColumnWidths(oColumn);
15220 },
15221
15222
15223
15224
15225
15226
15227
15228
15229
15230
15231
15232
15233
15234
15235
15236 /////////////////////////////////////////////////////////////////////////////
15237 //
15238 // Private DOM Event Handlers
15239 //
15240 /////////////////////////////////////////////////////////////////////////////
15241
15242 /**
15243  * Syncs scrolltop and scrollleft of all TABLEs.
15244  *
15245  * @method _onScroll
15246  * @param e {HTMLEvent} The scroll event.
15247  * @param oSelf {YAHOO.widget.ScrollingDataTable} ScrollingDataTable instance.
15248  * @private
15249  */
15250 _onScroll : function(e, oSelf) {
15251     oSelf._elHdContainer.scrollLeft = oSelf._elBdContainer.scrollLeft;
15252
15253     if(oSelf._oCellEditor && oSelf._oCellEditor.isActive) {
15254         oSelf.fireEvent("editorBlurEvent", {editor:oSelf._oCellEditor});
15255         oSelf.cancelCellEditor();
15256     }
15257
15258     var elTarget = Ev.getTarget(e);
15259     var elTag = elTarget.nodeName.toLowerCase();
15260     oSelf.fireEvent("tableScrollEvent", {event:e, target:elTarget});
15261 },
15262
15263 /**
15264  * Handles keydown events on the THEAD element.
15265  *
15266  * @method _onTheadKeydown
15267  * @param e {HTMLEvent} The key event.
15268  * @param oSelf {YAHOO.widget.ScrollingDataTable} ScrollingDataTable instance.
15269  * @private
15270  */
15271 _onTheadKeydown : function(e, oSelf) {
15272     // If tabbing to next TH label link causes THEAD to scroll,
15273     // need to sync scrollLeft with TBODY
15274     if(Ev.getCharCode(e) === 9) {
15275         setTimeout(function() {
15276             if((oSelf instanceof SDT) && oSelf._sId) {
15277                 oSelf._elBdContainer.scrollLeft = oSelf._elHdContainer.scrollLeft;
15278             }
15279         },0);
15280     }
15281     
15282     var elTarget = Ev.getTarget(e);
15283     var elTag = elTarget.nodeName.toLowerCase();
15284     var bKeepBubbling = true;
15285     while(elTarget && (elTag != "table")) {
15286         switch(elTag) {
15287             case "body":
15288                 return;
15289             case "input":
15290             case "textarea":
15291                 // TODO: implement textareaKeyEvent
15292                 break;
15293             case "thead":
15294                 bKeepBubbling = oSelf.fireEvent("theadKeyEvent",{target:elTarget,event:e});
15295                 break;
15296             default:
15297                 break;
15298         }
15299         if(bKeepBubbling === false) {
15300             return;
15301         }
15302         else {
15303             elTarget = elTarget.parentNode;
15304             if(elTarget) {
15305                 elTag = elTarget.nodeName.toLowerCase();
15306             }
15307         }
15308     }
15309     oSelf.fireEvent("tableKeyEvent",{target:(elTarget || oSelf._elContainer),event:e});
15310 }
15311
15312
15313
15314
15315 /**
15316  * Fired when a fixed scrolling DataTable has a scroll.
15317  *
15318  * @event tableScrollEvent
15319  * @param oArgs.event {HTMLEvent} The event object.
15320  * @param oArgs.target {HTMLElement} The DataTable's CONTAINER element (in IE)
15321  * or the DataTable's TBODY element (everyone else).
15322  *
15323  */
15324
15325
15326
15327
15328 });
15329
15330 })();
15331
15332 (function () {
15333
15334 var lang   = YAHOO.lang,
15335     util   = YAHOO.util,
15336     widget = YAHOO.widget,
15337     ua     = YAHOO.env.ua,
15338     
15339     Dom    = util.Dom,
15340     Ev     = util.Event,
15341     
15342     DT     = widget.DataTable;
15343 /****************************************************************************/
15344 /****************************************************************************/
15345 /****************************************************************************/
15346     
15347 /**
15348  * The BaseCellEditor class provides base functionality common to all inline cell
15349  * editors for a DataTable widget.
15350  *
15351  * @namespace YAHOO.widget
15352  * @class BaseCellEditor
15353  * @uses YAHOO.util.EventProvider 
15354  * @constructor
15355  * @param sType {String} Type indicator, to map to YAHOO.widget.DataTable.Editors.
15356  * @param oConfigs {Object} (Optional) Object literal of configs.
15357  */
15358 widget.BaseCellEditor = function(sType, oConfigs) {
15359     this._sId = this._sId || "yui-ceditor" + YAHOO.widget.BaseCellEditor._nCount++;
15360     this._sType = sType;
15361     
15362     // Validate inputs
15363     this._initConfigs(oConfigs); 
15364     
15365     // Create Custom Events
15366     this._initEvents();
15367              
15368     // Draw UI
15369     this.render();
15370 };
15371
15372 var BCE = widget.BaseCellEditor;
15373
15374 /////////////////////////////////////////////////////////////////////////////
15375 //
15376 // Static members
15377 //
15378 /////////////////////////////////////////////////////////////////////////////
15379 lang.augmentObject(BCE, {
15380
15381 /**
15382  * Global instance counter.
15383  *
15384  * @property CellEditor._nCount
15385  * @type Number
15386  * @static
15387  * @default 0
15388  * @private 
15389  */
15390 _nCount : 0,
15391
15392 /**
15393  * Class applied to CellEditor container.
15394  *
15395  * @property CellEditor.CLASS_CELLEDITOR
15396  * @type String
15397  * @static
15398  * @default "yui-ceditor"
15399  */
15400 CLASS_CELLEDITOR : "yui-ceditor"
15401
15402 });
15403
15404 BCE.prototype = {
15405 /////////////////////////////////////////////////////////////////////////////
15406 //
15407 // Private members
15408 //
15409 /////////////////////////////////////////////////////////////////////////////
15410 /**
15411  * Unique id assigned to instance "yui-ceditorN", useful prefix for generating unique
15412  * DOM ID strings and log messages.
15413  *
15414  * @property _sId
15415  * @type String
15416  * @private
15417  */
15418 _sId : null,
15419
15420 /**
15421  * Editor type.
15422  *
15423  * @property _sType
15424  * @type String
15425  * @private
15426  */
15427 _sType : null,
15428
15429 /**
15430  * DataTable instance.
15431  *
15432  * @property _oDataTable
15433  * @type YAHOO.widget.DataTable
15434  * @private 
15435  */
15436 _oDataTable : null,
15437
15438 /**
15439  * Column instance.
15440  *
15441  * @property _oColumn
15442  * @type YAHOO.widget.Column
15443  * @default null
15444  * @private 
15445  */
15446 _oColumn : null,
15447
15448 /**
15449  * Record instance.
15450  *
15451  * @property _oRecord
15452  * @type YAHOO.widget.Record
15453  * @default null
15454  * @private 
15455  */
15456 _oRecord : null,
15457
15458 /**
15459  * TD element.
15460  *
15461  * @property _elTd
15462  * @type HTMLElement
15463  * @default null
15464  * @private
15465  */
15466 _elTd : null,
15467
15468 /**
15469  * Container for inline editor.
15470  *
15471  * @property _elContainer
15472  * @type HTMLElement
15473  * @private 
15474  */
15475 _elContainer : null,
15476
15477 /**
15478  * Reference to Cancel button, if available.
15479  *
15480  * @property _elCancelBtn
15481  * @type HTMLElement
15482  * @default null
15483  * @private 
15484  */
15485 _elCancelBtn : null,
15486
15487 /**
15488  * Reference to Save button, if available.
15489  *
15490  * @property _elSaveBtn
15491  * @type HTMLElement
15492  * @default null
15493  * @private 
15494  */
15495 _elSaveBtn : null,
15496
15497
15498
15499
15500
15501
15502
15503
15504 /////////////////////////////////////////////////////////////////////////////
15505 //
15506 // Private methods
15507 //
15508 /////////////////////////////////////////////////////////////////////////////
15509
15510 /**
15511  * Initialize configs.
15512  *
15513  * @method _initConfigs
15514  * @private   
15515  */
15516 _initConfigs : function(oConfigs) {
15517     // Object literal defines CellEditor configs
15518     if(oConfigs && YAHOO.lang.isObject(oConfigs)) {
15519         for(var sConfig in oConfigs) {
15520             if(sConfig) {
15521                 this[sConfig] = oConfigs[sConfig];
15522             }
15523         }
15524     }
15525 },
15526
15527 /**
15528  * Initialize Custom Events.
15529  *
15530  * @method _initEvents
15531  * @private   
15532  */
15533 _initEvents : function() {
15534     this.createEvent("showEvent");
15535     this.createEvent("keydownEvent");
15536     this.createEvent("invalidDataEvent");
15537     this.createEvent("revertEvent");
15538     this.createEvent("saveEvent");
15539     this.createEvent("cancelEvent");
15540     this.createEvent("blurEvent");
15541     this.createEvent("blockEvent");
15542     this.createEvent("unblockEvent");
15543 },
15544
15545
15546
15547
15548
15549
15550
15551
15552
15553
15554
15555
15556
15557 /////////////////////////////////////////////////////////////////////////////
15558 //
15559 // Public properties
15560 //
15561 /////////////////////////////////////////////////////////////////////////////
15562 /**
15563  * Implementer defined function that can submit the input value to a server. This
15564  * function must accept the arguments fnCallback and oNewValue. When the submission
15565  * is complete, the function must also call fnCallback(bSuccess, oNewValue) to 
15566  * finish the save routine in the CellEditor. This function can also be used to 
15567  * perform extra validation or input value manipulation. 
15568  *
15569  * @property asyncSubmitter
15570  * @type HTMLFunction
15571  */
15572 asyncSubmitter : null,
15573
15574 /**
15575  * Current value.
15576  *
15577  * @property value
15578  * @type MIXED
15579  */
15580 value : null,
15581
15582 /**
15583  * Default value in case Record data is undefined. NB: Null values will not trigger
15584  * the default value.
15585  *
15586  * @property defaultValue
15587  * @type MIXED
15588  * @default null
15589  */
15590 defaultValue : null,
15591
15592 /**
15593  * Validator function for input data, called from the DataTable instance scope,
15594  * receives the arguments (inputValue, currentValue, editorInstance) and returns
15595  * either the validated (or type-converted) value or undefined.
15596  *
15597  * @property validator
15598  * @type HTMLFunction
15599  * @default null
15600  */
15601 validator : null,
15602
15603 /**
15604  * If validation is enabled, resets input field of invalid data.
15605  *
15606  * @property resetInvalidData
15607  * @type Boolean
15608  * @default true
15609  */
15610 resetInvalidData : true,
15611
15612 /**
15613  * True if currently active.
15614  *
15615  * @property isActive
15616  * @type Boolean
15617  */
15618 isActive : false,
15619
15620 /**
15621  * Text to display on Save button.
15622  *
15623  * @property LABEL_SAVE
15624  * @type String
15625  * @default "Save"
15626  */
15627 LABEL_SAVE : "Save",
15628
15629 /**
15630  * Text to display on Cancel button.
15631  *
15632  * @property LABEL_CANCEL
15633  * @type String
15634  * @default "Cancel"
15635  */
15636 LABEL_CANCEL : "Cancel",
15637
15638 /**
15639  * True if Save/Cancel buttons should not be displayed in the CellEditor.
15640  *
15641  * @property disableBtns
15642  * @type Boolean
15643  * @default false
15644  */
15645 disableBtns : false,
15646
15647
15648
15649
15650
15651
15652
15653 /////////////////////////////////////////////////////////////////////////////
15654 //
15655 // Public methods
15656 //
15657 /////////////////////////////////////////////////////////////////////////////
15658 /**
15659  * CellEditor instance name, for logging.
15660  *
15661  * @method toString
15662  * @return {String} Unique name of the CellEditor instance.
15663  */
15664
15665 toString : function() {
15666     return "CellEditor instance " + this._sId;
15667 },
15668
15669 /**
15670  * CellEditor unique ID.
15671  *
15672  * @method getId
15673  * @return {String} Unique ID of the CellEditor instance.
15674  */
15675
15676 getId : function() {
15677     return this._sId;
15678 },
15679
15680 /**
15681  * Returns reference to associated DataTable instance.
15682  *
15683  * @method getDataTable
15684  * @return {YAHOO.widget.DataTable} DataTable instance.
15685  */
15686
15687 getDataTable : function() {
15688     return this._oDataTable;
15689 },
15690
15691 /**
15692  * Returns reference to associated Column instance.
15693  *
15694  * @method getColumn
15695  * @return {YAHOO.widget.Column} Column instance.
15696  */
15697
15698 getColumn : function() {
15699     return this._oColumn;
15700 },
15701
15702 /**
15703  * Returns reference to associated Record instance.
15704  *
15705  * @method getRecord
15706  * @return {YAHOO.widget.Record} Record instance.
15707  */
15708
15709 getRecord : function() {
15710     return this._oRecord;
15711 },
15712
15713
15714
15715 /**
15716  * Returns reference to associated TD element.
15717  *
15718  * @method getTdEl
15719  * @return {HTMLElement} TD element.
15720  */
15721
15722 getTdEl : function() {
15723     return this._elTd;
15724 },
15725
15726 /**
15727  * Returns container element.
15728  *
15729  * @method getContainerEl
15730  * @return {HTMLElement} Reference to container element.
15731  */
15732
15733 getContainerEl : function() {
15734     return this._elContainer;
15735 },
15736
15737 /**
15738  * Nulls out the entire CellEditor instance and related objects, removes attached
15739  * event listeners, and clears out DOM elements inside the container, removes
15740  * container from the DOM.
15741  *
15742  * @method destroy
15743  */
15744 destroy : function() {
15745     this.unsubscribeAll();
15746     
15747     // Column is late-binding in attach()
15748     var oColumn = this.getColumn();
15749     if(oColumn) {
15750         oColumn.editor = null;
15751     }
15752     
15753     var elContainer = this.getContainerEl();
15754     Ev.purgeElement(elContainer, true);
15755     elContainer.parentNode.removeChild(elContainer);
15756 },
15757
15758 /**
15759  * Renders DOM elements and attaches event listeners.
15760  *
15761  * @method render
15762  */
15763 render : function() {
15764     if(this._elContainer) {
15765         YAHOO.util.Event.purgeElement(this._elContainer, true);
15766         this._elContainer.innerHTML = "";
15767     }
15768
15769     // Render Cell Editor container element as first child of body
15770     var elContainer = document.createElement("div");
15771     elContainer.id = this.getId() + "-container"; // Needed for tracking blur event
15772     elContainer.style.display = "none";
15773     elContainer.tabIndex = 0;
15774     elContainer.className = DT.CLASS_EDITOR;
15775     document.body.insertBefore(elContainer, document.body.firstChild);
15776     this._elContainer = elContainer;
15777     
15778     // Handle ESC key
15779     Ev.addListener(elContainer, "keydown", function(e, oSelf) {
15780         // ESC cancels Cell Editor
15781         if((e.keyCode == 27)) {
15782             var target = Ev.getTarget(e);
15783             // workaround for Mac FF3 bug that disabled clicks when ESC hit when
15784             // select is open. [bug 2273056]
15785             if (target.nodeName && target.nodeName.toLowerCase() === 'select') {
15786                 target.blur();
15787             }
15788             oSelf.cancel();
15789         }
15790         // Pass through event
15791         oSelf.fireEvent("keydownEvent", {editor:this, event:e});
15792     }, this);
15793     
15794     this.renderForm();
15795
15796     // Show Save/Cancel buttons
15797     if(!this.disableBtns) {
15798         this.renderBtns();
15799     }
15800     
15801     this.doAfterRender();
15802 },
15803
15804 /**
15805  * Renders Save/Cancel buttons.
15806  *
15807  * @method renderBtns
15808  */
15809 renderBtns : function() {
15810     // Buttons
15811     var elBtnsDiv = this.getContainerEl().appendChild(document.createElement("div"));
15812     elBtnsDiv.className = DT.CLASS_BUTTON;
15813
15814     // Save button
15815     var elSaveBtn = elBtnsDiv.appendChild(document.createElement("button"));
15816     elSaveBtn.className = DT.CLASS_DEFAULT;
15817     elSaveBtn.innerHTML = this.LABEL_SAVE;
15818     Ev.addListener(elSaveBtn, "click", function(oArgs) {
15819         this.save();
15820     }, this, true);
15821     this._elSaveBtn = elSaveBtn;
15822
15823     // Cancel button
15824     var elCancelBtn = elBtnsDiv.appendChild(document.createElement("button"));
15825     elCancelBtn.innerHTML = this.LABEL_CANCEL;
15826     Ev.addListener(elCancelBtn, "click", function(oArgs) {
15827         this.cancel();
15828     }, this, true);
15829     this._elCancelBtn = elCancelBtn;
15830 },
15831
15832 /**
15833  * Attach CellEditor for a new interaction.
15834  *
15835  * @method attach
15836  * @param oDataTable {YAHOO.widget.DataTable} Associated DataTable instance.
15837  * @param elCell {HTMLElement} Cell to edit.  
15838  */
15839 attach : function(oDataTable, elCell) {
15840     // Validate 
15841     if(oDataTable instanceof YAHOO.widget.DataTable) {
15842         this._oDataTable = oDataTable;
15843         
15844         // Validate cell
15845         elCell = oDataTable.getTdEl(elCell);
15846         if(elCell) {
15847             this._elTd = elCell;
15848
15849             // Validate Column
15850             var oColumn = oDataTable.getColumn(elCell);
15851             if(oColumn) {
15852                 this._oColumn = oColumn;
15853                 
15854                 // Validate Record
15855                 var oRecord = oDataTable.getRecord(elCell);
15856                 if(oRecord) {
15857                     this._oRecord = oRecord;
15858                     var value = oRecord.getData(this.getColumn().getField());
15859                     this.value = (value !== undefined) ? value : this.defaultValue;
15860                     return true;
15861                 }
15862             }            
15863         }
15864     }
15865     return false;
15866 },
15867
15868 /**
15869  * Moves container into position for display.
15870  *
15871  * @method move
15872  */
15873 move : function() {
15874     // Move Editor
15875     var elContainer = this.getContainerEl(),
15876         elTd = this.getTdEl(),
15877         x = Dom.getX(elTd),
15878         y = Dom.getY(elTd);
15879
15880     //TODO: remove scrolling logic
15881     // SF doesn't get xy for cells in scrolling table
15882     // when tbody display is set to block
15883     if(isNaN(x) || isNaN(y)) {
15884         var elTbody = this.getDataTable().getTbodyEl();
15885         x = elTd.offsetLeft + // cell pos relative to table
15886                 Dom.getX(elTbody.parentNode) - // plus table pos relative to document
15887                 elTbody.scrollLeft; // minus tbody scroll
15888         y = elTd.offsetTop + // cell pos relative to table
15889                 Dom.getY(elTbody.parentNode) - // plus table pos relative to document
15890                 elTbody.scrollTop + // minus tbody scroll
15891                 this.getDataTable().getTheadEl().offsetHeight; // account for fixed THEAD cells
15892     }
15893
15894     elContainer.style.left = x + "px";
15895     elContainer.style.top = y + "px";
15896 },
15897
15898 /**
15899  * Displays CellEditor UI in the correct position.
15900  *
15901  * @method show
15902  */
15903 show : function() {
15904     this.resetForm();
15905     this.isActive = true;
15906     this.getContainerEl().style.display = "";
15907     this.focus();
15908     this.fireEvent("showEvent", {editor:this});
15909 },
15910
15911 /**
15912  * Fires blockEvent
15913  *
15914  * @method block
15915  */
15916 block : function() {
15917     this.fireEvent("blockEvent", {editor:this});
15918 },
15919
15920 /**
15921  * Fires unblockEvent
15922  *
15923  * @method unblock
15924  */
15925 unblock : function() {
15926     this.fireEvent("unblockEvent", {editor:this});
15927 },
15928
15929 /**
15930  * Saves value of CellEditor and hides UI.
15931  *
15932  * @method save
15933  */
15934 save : function() {
15935     // Get new value
15936     var inputValue = this.getInputValue();
15937     var validValue = inputValue;
15938     
15939     // Validate new value
15940     if(this.validator) {
15941         validValue = this.validator.call(this.getDataTable(), inputValue, this.value, this);
15942         if(validValue === undefined ) {
15943             if(this.resetInvalidData) {
15944                 this.resetForm();
15945             }
15946             this.fireEvent("invalidDataEvent",
15947                     {editor:this, oldData:this.value, newData:inputValue});
15948             return;
15949         }
15950     }
15951         
15952     var oSelf = this;
15953     var finishSave = function(bSuccess, oNewValue) {
15954         var oOrigValue = oSelf.value;
15955         if(bSuccess) {
15956             // Update new value
15957             oSelf.value = oNewValue;
15958             oSelf.getDataTable().updateCell(oSelf.getRecord(), oSelf.getColumn(), oNewValue);
15959             
15960             // Hide CellEditor
15961             oSelf.getContainerEl().style.display = "none";
15962             oSelf.isActive = false;
15963             oSelf.getDataTable()._oCellEditor =  null;
15964             
15965             oSelf.fireEvent("saveEvent",
15966                     {editor:oSelf, oldData:oOrigValue, newData:oSelf.value});
15967         }
15968         else {
15969             oSelf.resetForm();
15970             oSelf.fireEvent("revertEvent",
15971                     {editor:oSelf, oldData:oOrigValue, newData:oNewValue});
15972         }
15973         oSelf.unblock();
15974     };
15975     
15976     this.block();
15977     if(lang.isFunction(this.asyncSubmitter)) {
15978         this.asyncSubmitter.call(this, finishSave, validValue);
15979     } 
15980     else {   
15981         finishSave(true, validValue);
15982     }
15983 },
15984
15985 /**
15986  * Cancels CellEditor input and hides UI.
15987  *
15988  * @method cancel
15989  */
15990 cancel : function() {
15991     if(this.isActive) {
15992         this.getContainerEl().style.display = "none";
15993         this.isActive = false;
15994         this.getDataTable()._oCellEditor =  null;
15995         this.fireEvent("cancelEvent", {editor:this});
15996     }
15997     else {
15998     }
15999 },
16000
16001 /**
16002  * Renders form elements.
16003  *
16004  * @method renderForm
16005  */
16006 renderForm : function() {
16007     // To be implemented by subclass
16008 },
16009
16010 /**
16011  * Access to add additional event listeners.
16012  *
16013  * @method doAfterRender
16014  */
16015 doAfterRender : function() {
16016     // To be implemented by subclass
16017 },
16018
16019
16020 /**
16021  * After rendering form, if disabledBtns is set to true, then sets up a mechanism
16022  * to save input without them. 
16023  *
16024  * @method handleDisabledBtns
16025  */
16026 handleDisabledBtns : function() {
16027     // To be implemented by subclass
16028 },
16029
16030 /**
16031  * Resets CellEditor UI to initial state.
16032  *
16033  * @method resetForm
16034  */
16035 resetForm : function() {
16036     // To be implemented by subclass
16037 },
16038
16039 /**
16040  * Sets focus in CellEditor.
16041  *
16042  * @method focus
16043  */
16044 focus : function() {
16045     // To be implemented by subclass
16046 },
16047
16048 /**
16049  * Retrieves input value from CellEditor.
16050  *
16051  * @method getInputValue
16052  */
16053 getInputValue : function() {
16054     // To be implemented by subclass
16055 }
16056
16057 };
16058
16059 lang.augmentProto(BCE, util.EventProvider);
16060
16061
16062 /////////////////////////////////////////////////////////////////////////////
16063 //
16064 // Custom Events
16065 //
16066 /////////////////////////////////////////////////////////////////////////////
16067
16068 /**
16069  * Fired when a CellEditor is shown.
16070  *
16071  * @event showEvent
16072  * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
16073  */
16074
16075 /**
16076  * Fired when a CellEditor has a keydown.
16077  *
16078  * @event keydownEvent
16079  * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance. 
16080  * @param oArgs.event {HTMLEvent} The event object.
16081  */
16082
16083 /**
16084  * Fired when a CellEditor input is reverted due to invalid data.
16085  *
16086  * @event invalidDataEvent
16087  * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance. 
16088  * @param oArgs.newData {Object} New data value from form input field.
16089  * @param oArgs.oldData {Object} Old data value.
16090  */
16091
16092 /**
16093  * Fired when a CellEditor input is reverted due to asyncSubmitter failure.
16094  *
16095  * @event revertEvent
16096  * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance. 
16097  * @param oArgs.newData {Object} New data value from form input field.
16098  * @param oArgs.oldData {Object} Old data value.
16099  */
16100
16101 /**
16102  * Fired when a CellEditor input is saved.
16103  *
16104  * @event saveEvent
16105  * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance. 
16106  * @param oArgs.newData {Object} New data value from form input field.
16107  * @param oArgs.oldData {Object} Old data value.
16108  */
16109
16110 /**
16111  * Fired when a CellEditor input is canceled.
16112  *
16113  * @event cancelEvent
16114  * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance. 
16115  */
16116
16117 /**
16118  * Fired when a CellEditor has a blur event.
16119  *
16120  * @event blurEvent
16121  * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance. 
16122  */
16123
16124
16125
16126
16127
16128
16129
16130
16131
16132
16133
16134
16135
16136
16137 /****************************************************************************/
16138 /****************************************************************************/
16139 /****************************************************************************/
16140     
16141 /**
16142  * The CheckboxCellEditor class provides functionality for inline editing
16143  * DataTable cell data with checkboxes.
16144  *
16145  * @namespace YAHOO.widget
16146  * @class CheckboxCellEditor
16147  * @extends YAHOO.widget.BaseCellEditor
16148  * @constructor
16149  * @param oConfigs {Object} (Optional) Object literal of configs.
16150  */
16151 widget.CheckboxCellEditor = function(oConfigs) {
16152     this._sId = "yui-checkboxceditor" + YAHOO.widget.BaseCellEditor._nCount++;
16153     widget.CheckboxCellEditor.superclass.constructor.call(this, "checkbox", oConfigs); 
16154 };
16155
16156 // CheckboxCellEditor extends BaseCellEditor
16157 lang.extend(widget.CheckboxCellEditor, BCE, {
16158
16159 /////////////////////////////////////////////////////////////////////////////
16160 //
16161 // CheckboxCellEditor public properties
16162 //
16163 /////////////////////////////////////////////////////////////////////////////
16164 /**
16165  * Array of checkbox values. Can either be a simple array (e.g., ["red","green","blue"])
16166  * or a an array of objects (e.g., [{label:"red", value:"#FF0000"},
16167  * {label:"green", value:"#00FF00"}, {label:"blue", value:"#0000FF"}]). 
16168  *
16169  * @property checkboxOptions
16170  * @type String[] | Object[]
16171  */
16172 checkboxOptions : null,
16173
16174 /**
16175  * Reference to the checkbox elements.
16176  *
16177  * @property checkboxes
16178  * @type HTMLElement[] 
16179  */
16180 checkboxes : null,
16181
16182 /**
16183  * Array of checked values
16184  *
16185  * @property value
16186  * @type String[] 
16187  */
16188 value : null,
16189
16190 /////////////////////////////////////////////////////////////////////////////
16191 //
16192 // CheckboxCellEditor public methods
16193 //
16194 /////////////////////////////////////////////////////////////////////////////
16195
16196 /**
16197  * Render a form with input(s) type=checkbox.
16198  *
16199  * @method renderForm
16200  */
16201 renderForm : function() {
16202     if(lang.isArray(this.checkboxOptions)) {
16203         var checkboxOption, checkboxValue, checkboxId, elLabel, j, len;
16204         
16205         // Create the checkbox buttons in an IE-friendly way...
16206         for(j=0,len=this.checkboxOptions.length; j<len; j++) {
16207             checkboxOption = this.checkboxOptions[j];
16208             checkboxValue = lang.isValue(checkboxOption.value) ?
16209                     checkboxOption.value : checkboxOption;
16210
16211             checkboxId = this.getId() + "-chk" + j;
16212             this.getContainerEl().innerHTML += "<input type=\"checkbox\"" +
16213                     " id=\"" + checkboxId + "\"" + // Needed for label
16214                     " value=\"" + checkboxValue + "\" />";
16215             
16216             // Create the labels in an IE-friendly way
16217             elLabel = this.getContainerEl().appendChild(document.createElement("label"));
16218             elLabel.htmlFor = checkboxId;
16219             elLabel.innerHTML = lang.isValue(checkboxOption.label) ?
16220                     checkboxOption.label : checkboxOption;
16221         }
16222         
16223         // Store the reference to the checkbox elements
16224         var allCheckboxes = [];
16225         for(j=0; j<len; j++) {
16226             allCheckboxes[allCheckboxes.length] = this.getContainerEl().childNodes[j*2];
16227         }
16228         this.checkboxes = allCheckboxes;
16229
16230         if(this.disableBtns) {
16231             this.handleDisabledBtns();
16232         }
16233     }
16234     else {
16235     }
16236 },
16237
16238 /**
16239  * After rendering form, if disabledBtns is set to true, then sets up a mechanism
16240  * to save input without them. 
16241  *
16242  * @method handleDisabledBtns
16243  */
16244 handleDisabledBtns : function() {
16245     Ev.addListener(this.getContainerEl(), "click", function(v){
16246         if(Ev.getTarget(v).tagName.toLowerCase() === "input") {
16247             // Save on blur
16248             this.save();
16249         }
16250     }, this, true);
16251 },
16252
16253 /**
16254  * Resets CheckboxCellEditor UI to initial state.
16255  *
16256  * @method resetForm
16257  */
16258 resetForm : function() {
16259     // Normalize to array
16260     var originalValues = lang.isArray(this.value) ? this.value : [this.value];
16261     
16262     // Match checks to value
16263     for(var i=0, j=this.checkboxes.length; i<j; i++) {
16264         this.checkboxes[i].checked = false;
16265         for(var k=0, len=originalValues.length; k<len; k++) {
16266             if(this.checkboxes[i].value === originalValues[k]) {
16267                 this.checkboxes[i].checked = true;
16268             }
16269         }
16270     }
16271 },
16272
16273 /**
16274  * Sets focus in CheckboxCellEditor.
16275  *
16276  * @method focus
16277  */
16278 focus : function() {
16279     this.checkboxes[0].focus();
16280 },
16281
16282 /**
16283  * Retrieves input value from CheckboxCellEditor.
16284  *
16285  * @method getInputValue
16286  */
16287 getInputValue : function() {
16288     var checkedValues = [];
16289     for(var i=0, j=this.checkboxes.length; i<j; i++) {
16290         if(this.checkboxes[i].checked) {
16291             checkedValues[checkedValues.length] = this.checkboxes[i].value;
16292         }
16293     }  
16294     return checkedValues;
16295 }
16296
16297 });
16298
16299 // Copy static members to CheckboxCellEditor class
16300 lang.augmentObject(widget.CheckboxCellEditor, BCE);
16301
16302
16303
16304
16305
16306
16307
16308
16309 /****************************************************************************/
16310 /****************************************************************************/
16311 /****************************************************************************/
16312     
16313 /**
16314  * The DataCellEditor class provides functionality for inline editing
16315  * DataTable cell data with a YUI Calendar.
16316  *
16317  * @namespace YAHOO.widget
16318  * @class DateCellEditor
16319  * @extends YAHOO.widget.BaseCellEditor 
16320  * @constructor
16321  * @param oConfigs {Object} (Optional) Object literal of configs.
16322  */
16323 widget.DateCellEditor = function(oConfigs) {
16324     this._sId = "yui-dateceditor" + YAHOO.widget.BaseCellEditor._nCount++;
16325     widget.DateCellEditor.superclass.constructor.call(this, "date", oConfigs); 
16326 };
16327
16328 // CheckboxCellEditor extends BaseCellEditor
16329 lang.extend(widget.DateCellEditor, BCE, {
16330
16331 /////////////////////////////////////////////////////////////////////////////
16332 //
16333 // DateCellEditor public properties
16334 //
16335 /////////////////////////////////////////////////////////////////////////////
16336 /**
16337  * Reference to Calendar instance.
16338  *
16339  * @property calendar
16340  * @type YAHOO.widget.Calendar
16341  */
16342 calendar : null,
16343
16344 /**
16345  * Configs for the calendar instance, to be passed to Calendar constructor.
16346  *
16347  * @property calendarOptions
16348  * @type Object
16349  */
16350 calendarOptions : null,
16351
16352 /**
16353  * Default value.
16354  *
16355  * @property defaultValue
16356  * @type Date
16357  * @default new Date()
16358  */
16359 defaultValue : new Date(),
16360
16361
16362 /////////////////////////////////////////////////////////////////////////////
16363 //
16364 // DateCellEditor public methods
16365 //
16366 /////////////////////////////////////////////////////////////////////////////
16367
16368 /**
16369  * Render a Calendar.
16370  *
16371  * @method renderForm
16372  */
16373 renderForm : function() {
16374     // Calendar widget
16375     if(YAHOO.widget.Calendar) {
16376         var calContainer = this.getContainerEl().appendChild(document.createElement("div"));
16377         calContainer.id = this.getId() + "-dateContainer"; // Needed for Calendar constructor
16378         var calendar =
16379                 new YAHOO.widget.Calendar(this.getId() + "-date",
16380                 calContainer.id, this.calendarOptions);
16381         calendar.render();
16382         calContainer.style.cssFloat = "none";
16383
16384         if(ua.ie) {
16385             var calFloatClearer = this.getContainerEl().appendChild(document.createElement("div"));
16386             calFloatClearer.style.clear = "both";
16387         }
16388         
16389         this.calendar = calendar;
16390
16391         if(this.disableBtns) {
16392             this.handleDisabledBtns();
16393         }
16394     }
16395     else {
16396     }
16397     
16398 },
16399
16400 /**
16401  * After rendering form, if disabledBtns is set to true, then sets up a mechanism
16402  * to save input without them. 
16403  *
16404  * @method handleDisabledBtns
16405  */
16406 handleDisabledBtns : function() {
16407     this.calendar.selectEvent.subscribe(function(v){
16408         // Save on select
16409         this.save();
16410     }, this, true);
16411 },
16412
16413 /**
16414  * Resets DateCellEditor UI to initial state.
16415  *
16416  * @method resetForm
16417  */
16418 resetForm : function() {
16419     var value = this.value;
16420     var selectedValue = (value.getMonth()+1)+"/"+value.getDate()+"/"+value.getFullYear();
16421     this.calendar.cfg.setProperty("selected",selectedValue,false);
16422         this.calendar.render();
16423 },
16424
16425 /**
16426  * Sets focus in DateCellEditor.
16427  *
16428  * @method focus
16429  */
16430 focus : function() {
16431     // To be impmlemented by subclass
16432 },
16433
16434 /**
16435  * Retrieves input value from DateCellEditor.
16436  *
16437  * @method getInputValue
16438  */
16439 getInputValue : function() {
16440     return this.calendar.getSelectedDates()[0];
16441 }
16442
16443 });
16444
16445 // Copy static members to DateCellEditor class
16446 lang.augmentObject(widget.DateCellEditor, BCE);
16447
16448
16449
16450
16451
16452
16453
16454
16455
16456 /****************************************************************************/
16457 /****************************************************************************/
16458 /****************************************************************************/
16459     
16460 /**
16461  * The DropdownCellEditor class provides functionality for inline editing
16462  * DataTable cell data a SELECT element.
16463  *
16464  * @namespace YAHOO.widget
16465  * @class DropdownCellEditor
16466  * @extends YAHOO.widget.BaseCellEditor 
16467  * @constructor
16468  * @param oConfigs {Object} (Optional) Object literal of configs.
16469  */
16470 widget.DropdownCellEditor = function(oConfigs) {
16471     this._sId = "yui-dropdownceditor" + YAHOO.widget.BaseCellEditor._nCount++;
16472     widget.DropdownCellEditor.superclass.constructor.call(this, "dropdown", oConfigs);
16473 };
16474
16475 // DropdownCellEditor extends BaseCellEditor
16476 lang.extend(widget.DropdownCellEditor, BCE, {
16477
16478 /////////////////////////////////////////////////////////////////////////////
16479 //
16480 // DropdownCellEditor public properties
16481 //
16482 /////////////////////////////////////////////////////////////////////////////
16483 /**
16484  * Array of dropdown values. Can either be a simple array (e.g.,
16485  * ["Alabama","Alaska","Arizona","Arkansas"]) or a an array of objects (e.g., 
16486  * [{label:"Alabama", value:"AL"}, {label:"Alaska", value:"AK"},
16487  * {label:"Arizona", value:"AZ"}, {label:"Arkansas", value:"AR"}]). 
16488  *
16489  * @property dropdownOptions
16490  * @type String[] | Object[]
16491  */
16492 dropdownOptions : null,
16493
16494 /**
16495  * Reference to Dropdown element.
16496  *
16497  * @property dropdown
16498  * @type HTMLElement
16499  */
16500 dropdown : null,
16501
16502 /**
16503  * Enables multi-select.
16504  *
16505  * @property multiple
16506  * @type Boolean
16507  */
16508 multiple : false,
16509
16510 /**
16511  * Specifies number of visible options.
16512  *
16513  * @property size
16514  * @type Number
16515  */
16516 size : null,
16517
16518 /////////////////////////////////////////////////////////////////////////////
16519 //
16520 // DropdownCellEditor public methods
16521 //
16522 /////////////////////////////////////////////////////////////////////////////
16523
16524 /**
16525  * Render a form with select element.
16526  *
16527  * @method renderForm
16528  */
16529 renderForm : function() {
16530     var elDropdown = this.getContainerEl().appendChild(document.createElement("select"));
16531     elDropdown.style.zoom = 1;
16532     if(this.multiple) {
16533         elDropdown.multiple = "multiple";
16534     }
16535     if(lang.isNumber(this.size)) {
16536         elDropdown.size = this.size;
16537     }
16538     this.dropdown = elDropdown;
16539     
16540     if(lang.isArray(this.dropdownOptions)) {
16541         var dropdownOption, elOption;
16542         for(var i=0, j=this.dropdownOptions.length; i<j; i++) {
16543             dropdownOption = this.dropdownOptions[i];
16544             elOption = document.createElement("option");
16545             elOption.value = (lang.isValue(dropdownOption.value)) ?
16546                     dropdownOption.value : dropdownOption;
16547             elOption.innerHTML = (lang.isValue(dropdownOption.label)) ?
16548                     dropdownOption.label : dropdownOption;
16549             elOption = elDropdown.appendChild(elOption);
16550         }
16551         
16552         if(this.disableBtns) {
16553             this.handleDisabledBtns();
16554         }
16555     }
16556 },
16557
16558 /**
16559  * After rendering form, if disabledBtns is set to true, then sets up a mechanism
16560  * to save input without them. 
16561  *
16562  * @method handleDisabledBtns
16563  */
16564 handleDisabledBtns : function() {
16565     // Save on blur for multi-select
16566     if(this.multiple) {
16567         Ev.addListener(this.dropdown, "blur", function(v){
16568             // Save on change
16569             this.save();
16570         }, this, true);
16571     }
16572     // Save on change for single-select
16573     else {
16574         Ev.addListener(this.dropdown, "change", function(v){
16575             // Save on change
16576             this.save();
16577         }, this, true);
16578     }
16579 },
16580
16581 /**
16582  * Resets DropdownCellEditor UI to initial state.
16583  *
16584  * @method resetForm
16585  */
16586 resetForm : function() {
16587     var allOptions = this.dropdown.options,
16588         i=0, j=allOptions.length;
16589
16590     // Look for multi-select selections
16591     if(lang.isArray(this.value)) {
16592         var allValues = this.value,
16593             m=0, n=allValues.length,
16594             hash = {};
16595         // Reset all selections and stash options in a value hash
16596         for(; i<j; i++) {
16597             allOptions[i].selected = false;
16598             hash[allOptions[i].value] = allOptions[i];
16599         }
16600         for(; m<n; m++) {
16601             if(hash[allValues[m]]) {
16602                 hash[allValues[m]].selected = true;
16603             }
16604         }
16605     }
16606     // Only need to look for a single selection
16607     else {
16608         for(; i<j; i++) {
16609             if(this.value === allOptions[i].value) {
16610                 allOptions[i].selected = true;
16611             }
16612         }
16613     }
16614 },
16615
16616 /**
16617  * Sets focus in DropdownCellEditor.
16618  *
16619  * @method focus
16620  */
16621 focus : function() {
16622     this.getDataTable()._focusEl(this.dropdown);
16623 },
16624
16625 /**
16626  * Retrieves input value from DropdownCellEditor.
16627  *
16628  * @method getInputValue
16629  */
16630 getInputValue : function() {
16631     var allOptions = this.dropdown.options;
16632     
16633     // Look for multiple selections
16634     if(this.multiple) {
16635         var values = [],
16636             i=0, j=allOptions.length;
16637         for(; i<j; i++) {
16638             if(allOptions[i].selected) {
16639                 values.push(allOptions[i].value);
16640             }
16641         }
16642         return values;
16643     }
16644     // Only need to look for single selection
16645     else {
16646         return allOptions[allOptions.selectedIndex].value;
16647     }
16648 }
16649
16650 });
16651
16652 // Copy static members to DropdownCellEditor class
16653 lang.augmentObject(widget.DropdownCellEditor, BCE);
16654
16655
16656
16657
16658
16659
16660 /****************************************************************************/
16661 /****************************************************************************/
16662 /****************************************************************************/
16663     
16664 /**
16665  * The RadioCellEditor class provides functionality for inline editing
16666  * DataTable cell data with radio buttons.
16667  *
16668  * @namespace YAHOO.widget
16669  * @class RadioCellEditor
16670  * @extends YAHOO.widget.BaseCellEditor 
16671  * @constructor
16672  * @param oConfigs {Object} (Optional) Object literal of configs.
16673  */
16674 widget.RadioCellEditor = function(oConfigs) {
16675     this._sId = "yui-radioceditor" + YAHOO.widget.BaseCellEditor._nCount++;
16676     widget.RadioCellEditor.superclass.constructor.call(this, "radio", oConfigs); 
16677 };
16678
16679 // RadioCellEditor extends BaseCellEditor
16680 lang.extend(widget.RadioCellEditor, BCE, {
16681
16682 /////////////////////////////////////////////////////////////////////////////
16683 //
16684 // RadioCellEditor public properties
16685 //
16686 /////////////////////////////////////////////////////////////////////////////
16687 /**
16688  * Reference to radio elements.
16689  *
16690  * @property radios
16691  * @type HTMLElement[]
16692  */
16693 radios : null,
16694
16695 /**
16696  * Array of radio values. Can either be a simple array (e.g., ["yes","no","maybe"])
16697  * or a an array of objects (e.g., [{label:"yes", value:1}, {label:"no", value:-1},
16698  * {label:"maybe", value:0}]). 
16699  *
16700  * @property radioOptions
16701  * @type String[] | Object[]
16702  */
16703 radioOptions : null,
16704
16705 /////////////////////////////////////////////////////////////////////////////
16706 //
16707 // RadioCellEditor public methods
16708 //
16709 /////////////////////////////////////////////////////////////////////////////
16710
16711 /**
16712  * Render a form with input(s) type=radio.
16713  *
16714  * @method renderForm
16715  */
16716 renderForm : function() {
16717     if(lang.isArray(this.radioOptions)) {
16718         var radioOption, radioValue, radioId, elLabel;
16719         
16720         // Create the radio buttons in an IE-friendly way
16721         for(var i=0, len=this.radioOptions.length; i<len; i++) {
16722             radioOption = this.radioOptions[i];
16723             radioValue = lang.isValue(radioOption.value) ?
16724                     radioOption.value : radioOption;
16725             radioId = this.getId() + "-radio" + i;
16726             this.getContainerEl().innerHTML += "<input type=\"radio\"" +
16727                     " name=\"" + this.getId() + "\"" +
16728                     " value=\"" + radioValue + "\"" +
16729                     " id=\"" +  radioId + "\" />"; // Needed for label
16730             
16731             // Create the labels in an IE-friendly way
16732             elLabel = this.getContainerEl().appendChild(document.createElement("label"));
16733             elLabel.htmlFor = radioId;
16734             elLabel.innerHTML = (lang.isValue(radioOption.label)) ?
16735                     radioOption.label : radioOption;
16736         }
16737         
16738         // Store the reference to the checkbox elements
16739         var allRadios = [],
16740             elRadio;
16741         for(var j=0; j<len; j++) {
16742             elRadio = this.getContainerEl().childNodes[j*2];
16743             allRadios[allRadios.length] = elRadio;
16744         }
16745         this.radios = allRadios;
16746
16747         if(this.disableBtns) {
16748             this.handleDisabledBtns();
16749         }
16750     }
16751     else {
16752     }
16753 },
16754
16755 /**
16756  * After rendering form, if disabledBtns is set to true, then sets up a mechanism
16757  * to save input without them. 
16758  *
16759  * @method handleDisabledBtns
16760  */
16761 handleDisabledBtns : function() {
16762     Ev.addListener(this.getContainerEl(), "click", function(v){
16763         if(Ev.getTarget(v).tagName.toLowerCase() === "input") {
16764             // Save on blur
16765             this.save();
16766         }
16767     }, this, true);
16768 },
16769
16770 /**
16771  * Resets RadioCellEditor UI to initial state.
16772  *
16773  * @method resetForm
16774  */
16775 resetForm : function() {
16776     for(var i=0, j=this.radios.length; i<j; i++) {
16777         var elRadio = this.radios[i];
16778         if(this.value === elRadio.value) {
16779             elRadio.checked = true;
16780             return;
16781         }
16782     }
16783 },
16784
16785 /**
16786  * Sets focus in RadioCellEditor.
16787  *
16788  * @method focus
16789  */
16790 focus : function() {
16791     for(var i=0, j=this.radios.length; i<j; i++) {
16792         if(this.radios[i].checked) {
16793             this.radios[i].focus();
16794             return;
16795         }
16796     }
16797 },
16798
16799 /**
16800  * Retrieves input value from RadioCellEditor.
16801  *
16802  * @method getInputValue
16803  */
16804 getInputValue : function() {
16805     for(var i=0, j=this.radios.length; i<j; i++) {
16806         if(this.radios[i].checked) {
16807             return this.radios[i].value;
16808         }
16809     }
16810 }
16811
16812 });
16813
16814 // Copy static members to RadioCellEditor class
16815 lang.augmentObject(widget.RadioCellEditor, BCE);
16816
16817
16818
16819
16820
16821
16822 /****************************************************************************/
16823 /****************************************************************************/
16824 /****************************************************************************/
16825     
16826 /**
16827  * The TextareaCellEditor class provides functionality for inline editing
16828  * DataTable cell data with a TEXTAREA element.
16829  *
16830  * @namespace YAHOO.widget
16831  * @class TextareaCellEditor
16832  * @extends YAHOO.widget.BaseCellEditor 
16833  * @constructor
16834  * @param oConfigs {Object} (Optional) Object literal of configs.
16835  */
16836 widget.TextareaCellEditor = function(oConfigs) {
16837     this._sId = "yui-textareaceditor" + YAHOO.widget.BaseCellEditor._nCount++;
16838     widget.TextareaCellEditor.superclass.constructor.call(this, "textarea", oConfigs); 
16839 };
16840
16841 // TextareaCellEditor extends BaseCellEditor
16842 lang.extend(widget.TextareaCellEditor, BCE, {
16843
16844 /////////////////////////////////////////////////////////////////////////////
16845 //
16846 // TextareaCellEditor public properties
16847 //
16848 /////////////////////////////////////////////////////////////////////////////
16849 /**
16850  * Reference to textarea element.
16851  *
16852  * @property textarea
16853  * @type HTMLElement
16854  */
16855 textarea : null,
16856
16857
16858 /////////////////////////////////////////////////////////////////////////////
16859 //
16860 // TextareaCellEditor public methods
16861 //
16862 /////////////////////////////////////////////////////////////////////////////
16863
16864 /**
16865  * Render a form with textarea.
16866  *
16867  * @method renderForm
16868  */
16869 renderForm : function() {
16870     var elTextarea = this.getContainerEl().appendChild(document.createElement("textarea"));
16871     this.textarea = elTextarea;
16872
16873     if(this.disableBtns) {
16874         this.handleDisabledBtns();
16875     }
16876 },
16877
16878 /**
16879  * After rendering form, if disabledBtns is set to true, then sets up a mechanism
16880  * to save input without them. 
16881  *
16882  * @method handleDisabledBtns
16883  */
16884 handleDisabledBtns : function() {
16885     Ev.addListener(this.textarea, "blur", function(v){
16886         // Save on blur
16887         this.save();
16888     }, this, true);        
16889 },
16890
16891 /**
16892  * Moves TextareaCellEditor UI to a cell.
16893  *
16894  * @method move
16895  */
16896 move : function() {
16897     this.textarea.style.width = this.getTdEl().offsetWidth + "px";
16898     this.textarea.style.height = "3em";
16899     YAHOO.widget.TextareaCellEditor.superclass.move.call(this);
16900 },
16901
16902 /**
16903  * Resets TextareaCellEditor UI to initial state.
16904  *
16905  * @method resetForm
16906  */
16907 resetForm : function() {
16908     this.textarea.value = this.value;
16909 },
16910
16911 /**
16912  * Sets focus in TextareaCellEditor.
16913  *
16914  * @method focus
16915  */
16916 focus : function() {
16917     // Bug 2303181, Bug 2263600
16918     this.getDataTable()._focusEl(this.textarea);
16919     this.textarea.select();
16920 },
16921
16922 /**
16923  * Retrieves input value from TextareaCellEditor.
16924  *
16925  * @method getInputValue
16926  */
16927 getInputValue : function() {
16928     return this.textarea.value;
16929 }
16930
16931 });
16932
16933 // Copy static members to TextareaCellEditor class
16934 lang.augmentObject(widget.TextareaCellEditor, BCE);
16935
16936
16937
16938
16939
16940
16941
16942
16943
16944 /****************************************************************************/
16945 /****************************************************************************/
16946 /****************************************************************************/
16947     
16948 /**
16949  * The TextboxCellEditor class provides functionality for inline editing
16950  * DataTable cell data with an INPUT TYPE=TEXT element.
16951  *
16952  * @namespace YAHOO.widget
16953  * @class TextboxCellEditor
16954  * @extends YAHOO.widget.BaseCellEditor 
16955  * @constructor
16956  * @param oConfigs {Object} (Optional) Object literal of configs.
16957  */
16958 widget.TextboxCellEditor = function(oConfigs) {
16959     this._sId = "yui-textboxceditor" + YAHOO.widget.BaseCellEditor._nCount++;
16960     widget.TextboxCellEditor.superclass.constructor.call(this, "textbox", oConfigs); 
16961 };
16962
16963 // TextboxCellEditor extends BaseCellEditor
16964 lang.extend(widget.TextboxCellEditor, BCE, {
16965
16966 /////////////////////////////////////////////////////////////////////////////
16967 //
16968 // TextboxCellEditor public properties
16969 //
16970 /////////////////////////////////////////////////////////////////////////////
16971 /**
16972  * Reference to the textbox element.
16973  *
16974  * @property textbox
16975  */
16976 textbox : null,
16977
16978 /////////////////////////////////////////////////////////////////////////////
16979 //
16980 // TextboxCellEditor public methods
16981 //
16982 /////////////////////////////////////////////////////////////////////////////
16983
16984 /**
16985  * Render a form with input type=text.
16986  *
16987  * @method renderForm
16988  */
16989 renderForm : function() {
16990     var elTextbox;
16991     // Bug 1802582: SF3/Mac needs a form element wrapping the input
16992     if(ua.webkit>420) {
16993         elTextbox = this.getContainerEl().appendChild(document.createElement("form")).appendChild(document.createElement("input"));
16994     }
16995     else {
16996         elTextbox = this.getContainerEl().appendChild(document.createElement("input"));
16997     }
16998     elTextbox.type = "text";
16999     this.textbox = elTextbox;
17000
17001     // Save on enter by default
17002     // Bug: 1802582 Set up a listener on each textbox to track on keypress
17003     // since SF/OP can't preventDefault on keydown
17004     Ev.addListener(elTextbox, "keypress", function(v){
17005         if((v.keyCode === 13)) {
17006             // Prevent form submit
17007             YAHOO.util.Event.preventDefault(v);
17008             this.save();
17009         }
17010     }, this, true);
17011
17012     if(this.disableBtns) {
17013         // By default this is no-op since enter saves by default
17014         this.handleDisabledBtns();
17015     }
17016 },
17017
17018 /**
17019  * Moves TextboxCellEditor UI to a cell.
17020  *
17021  * @method move
17022  */
17023 move : function() {
17024     this.textbox.style.width = this.getTdEl().offsetWidth + "px";
17025     widget.TextboxCellEditor.superclass.move.call(this);
17026 },
17027
17028 /**
17029  * Resets TextboxCellEditor UI to initial state.
17030  *
17031  * @method resetForm
17032  */
17033 resetForm : function() {
17034     this.textbox.value = lang.isValue(this.value) ? this.value.toString() : "";
17035 },
17036
17037 /**
17038  * Sets focus in TextboxCellEditor.
17039  *
17040  * @method focus
17041  */
17042 focus : function() {
17043     // Bug 2303181, Bug 2263600
17044     this.getDataTable()._focusEl(this.textbox);
17045     this.textbox.select();
17046 },
17047
17048 /**
17049  * Returns new value for TextboxCellEditor.
17050  *
17051  * @method getInputValue
17052  */
17053 getInputValue : function() {
17054     return this.textbox.value;
17055 }
17056
17057 });
17058
17059 // Copy static members to TextboxCellEditor class
17060 lang.augmentObject(widget.TextboxCellEditor, BCE);
17061
17062
17063
17064
17065
17066
17067
17068 /////////////////////////////////////////////////////////////////////////////
17069 //
17070 // DataTable extension
17071 //
17072 /////////////////////////////////////////////////////////////////////////////
17073
17074 /**
17075  * CellEditor subclasses.
17076  * @property DataTable.Editors
17077  * @type Object
17078  * @static
17079  */
17080 DT.Editors = {
17081     checkbox : widget.CheckboxCellEditor,
17082     "date"   : widget.DateCellEditor,
17083     dropdown : widget.DropdownCellEditor,
17084     radio    : widget.RadioCellEditor,
17085     textarea : widget.TextareaCellEditor,
17086     textbox  : widget.TextboxCellEditor
17087 };
17088
17089 /****************************************************************************/
17090 /****************************************************************************/
17091 /****************************************************************************/
17092     
17093 /**
17094  * Factory class for instantiating a BaseCellEditor subclass.
17095  *
17096  * @namespace YAHOO.widget
17097  * @class CellEditor
17098  * @extends YAHOO.widget.BaseCellEditor 
17099  * @constructor
17100  * @param sType {String} Type indicator, to map to YAHOO.widget.DataTable.Editors.
17101  * @param oConfigs {Object} (Optional) Object literal of configs.
17102  */
17103 widget.CellEditor = function(sType, oConfigs) {
17104     // Point to one of the subclasses
17105     if(sType && DT.Editors[sType]) {
17106         lang.augmentObject(BCE, DT.Editors[sType]);
17107         return new DT.Editors[sType](oConfigs);
17108     }
17109     else {
17110         return new BCE(null, oConfigs);
17111     }
17112 };
17113
17114 var CE = widget.CellEditor;
17115
17116 // Copy static members to CellEditor class
17117 lang.augmentObject(CE, BCE);
17118
17119
17120 })();
17121
17122 YAHOO.register("datatable", YAHOO.widget.DataTable, {version: "2.8.0r4", build: "2449"});