Bug 5917 / Bug 6085 : Fixing not being able to change language
[koha.git] / koha-tt / intranet-tmpl / prog / en / lib / yui / datatable / datatable-debug.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     YAHOO.log("ColumnSet initialized", "info", this.toString());
197 };
198
199 /////////////////////////////////////////////////////////////////////////////
200 //
201 // Private member variables
202 //
203 /////////////////////////////////////////////////////////////////////////////
204
205 /**
206  * Internal class variable to index multiple ColumnSet instances.
207  *
208  * @property ColumnSet._nCount
209  * @type Number
210  * @private
211  * @static
212  */
213 YAHOO.widget.ColumnSet._nCount = 0;
214
215 YAHOO.widget.ColumnSet.prototype = {
216     /**
217      * Unique instance name.
218      *
219      * @property _sId
220      * @type String
221      * @private
222      */
223     _sId : null,
224
225     /**
226      * Array of object literal Column definitions passed to the constructor.
227      *
228      * @property _aDefinitions
229      * @type Object[]
230      * @private
231      */
232     _aDefinitions : null,
233
234     /////////////////////////////////////////////////////////////////////////////
235     //
236     // Public member variables
237     //
238     /////////////////////////////////////////////////////////////////////////////
239
240     /**
241      * Top-down tree representation of Column hierarchy.
242      *
243      * @property tree
244      * @type YAHOO.widget.Column[]
245      */
246     tree : null,
247
248     /**
249      * Flattened representation of all Columns.
250      *
251      * @property flat
252      * @type YAHOO.widget.Column[]
253      * @default []
254      */
255     flat : null,
256
257     /**
258      * Array of Columns that map one-to-one to a table column.
259      *
260      * @property keys
261      * @type YAHOO.widget.Column[]
262      * @default []
263      */
264     keys : null,
265
266     /**
267      * ID index of nested parent hierarchies for HEADERS accessibility attribute.
268      *
269      * @property headers
270      * @type String[]
271      * @default []
272      */
273     headers : null,
274
275     /////////////////////////////////////////////////////////////////////////////
276     //
277     // Private methods
278     //
279     /////////////////////////////////////////////////////////////////////////////
280
281     /**
282      * Initializes ColumnSet instance with data from Column definitions.
283      *
284      * @method _init
285      * @param aDefinitions {Object[]} Array of object literals that define cells in
286      * the THEAD .
287      * @private
288      */
289
290     _init : function(aDefinitions) {        
291         // DOM tree representation of all Columns
292         var tree = [];
293         // Flat representation of all Columns
294         var flat = [];
295         // Flat representation of only Columns that are meant to display data
296         var keys = [];
297         // Array of HEADERS attribute values for all keys in the "keys" array
298         var headers = [];
299
300         // Tracks current node list depth being tracked
301         var nodeDepth = -1;
302
303         // Internal recursive function to define Column instances
304         var parseColumns = function(nodeList, parent) {
305             // One level down
306             nodeDepth++;
307
308             // Create corresponding tree node if not already there for this depth
309             if(!tree[nodeDepth]) {
310                 tree[nodeDepth] = [];
311             }
312
313
314             // Parse each node at this depth for attributes and any children
315             for(var j=0; j<nodeList.length; j++) {
316                 var currentNode = nodeList[j];
317
318                 // Instantiate a new Column for each node
319                 var oColumn = new YAHOO.widget.Column(currentNode);
320                 
321                 // Cross-reference Column ID back to the original object literal definition
322                 currentNode.yuiColumnId = oColumn._sId;
323                 
324                 // Add the new Column to the flat list
325                 flat.push(oColumn);
326
327                 // Assign its parent as an attribute, if applicable
328                 if(parent) {
329                     oColumn._oParent = parent;
330                 }
331
332                 // The Column has descendants
333                 if(YAHOO.lang.isArray(currentNode.children)) {
334                     oColumn.children = currentNode.children;
335
336                     // Determine COLSPAN value for this Column
337                     var terminalChildNodes = 0;
338                     var countTerminalChildNodes = function(ancestor) {
339                         var descendants = ancestor.children;
340                         // Drill down each branch and count terminal nodes
341                         for(var k=0; k<descendants.length; k++) {
342                             // Keep drilling down
343                             if(YAHOO.lang.isArray(descendants[k].children)) {
344                                 countTerminalChildNodes(descendants[k]);
345                             }
346                             // Reached branch terminus
347                             else {
348                                 terminalChildNodes++;
349                             }
350                         }
351                     };
352                     countTerminalChildNodes(currentNode);
353                     oColumn._nColspan = terminalChildNodes;
354
355                     // Cascade certain properties to children if not defined on their own
356                     var currentChildren = currentNode.children;
357                     for(var k=0; k<currentChildren.length; k++) {
358                         var child = currentChildren[k];
359                         if(oColumn.className && (child.className === undefined)) {
360                             child.className = oColumn.className;
361                         }
362                         if(oColumn.editor && (child.editor === undefined)) {
363                             child.editor = oColumn.editor;
364                         }
365                         //TODO: Deprecated
366                         if(oColumn.editorOptions && (child.editorOptions === undefined)) {
367                             child.editorOptions = oColumn.editorOptions;
368                         }
369                         if(oColumn.formatter && (child.formatter === undefined)) {
370                             child.formatter = oColumn.formatter;
371                         }
372                         if(oColumn.resizeable && (child.resizeable === undefined)) {
373                             child.resizeable = oColumn.resizeable;
374                         }
375                         if(oColumn.sortable && (child.sortable === undefined)) {
376                             child.sortable = oColumn.sortable;
377                         }
378                         if(oColumn.hidden) {
379                             child.hidden = true;
380                         }
381                         if(oColumn.width && (child.width === undefined)) {
382                             child.width = oColumn.width;
383                         }
384                         if(oColumn.minWidth && (child.minWidth === undefined)) {
385                             child.minWidth = oColumn.minWidth;
386                         }
387                         if(oColumn.maxAutoWidth && (child.maxAutoWidth === undefined)) {
388                             child.maxAutoWidth = oColumn.maxAutoWidth;
389                         }
390                         // Backward compatibility
391                         if(oColumn.type && (child.type === undefined)) {
392                             child.type = oColumn.type;
393                         }
394                         if(oColumn.type && !oColumn.formatter) {
395                             YAHOO.log("The property type has been" +
396                             " deprecated in favor of formatter", "warn", oColumn.toString());
397                             oColumn.formatter = oColumn.type;
398                         }
399                         if(oColumn.text && !YAHOO.lang.isValue(oColumn.label)) {
400                             YAHOO.log("The property text has been" +
401                             " deprecated in favor of label", "warn", oColumn.toString());
402                             oColumn.label = oColumn.text;
403                         }
404                         if(oColumn.parser) {
405                             YAHOO.log("The property parser is no longer supported",
406                             "warn", this.toString());
407                         }
408                         if(oColumn.sortOptions && ((oColumn.sortOptions.ascFunction) ||
409                                 (oColumn.sortOptions.descFunction))) {
410                             YAHOO.log("The properties sortOptions.ascFunction and " +
411                             " sortOptions.descFunction have been deprecated in favor " +
412                             " of sortOptions.sortFunction", "warn", oColumn.toString());
413                         }
414                     }
415
416                     // The children themselves must also be parsed for Column instances
417                     if(!tree[nodeDepth+1]) {
418                         tree[nodeDepth+1] = [];
419                     }
420                     parseColumns(currentChildren, oColumn);
421                 }
422                 // This Column does not have any children
423                 else {
424                     oColumn._nKeyIndex = keys.length;
425                     oColumn._nColspan = 1;
426                     keys.push(oColumn);
427                 }
428
429                 // Add the Column to the top-down tree
430                 tree[nodeDepth].push(oColumn);
431             }
432             nodeDepth--;
433         };
434
435         // Parse out Column instances from the array of object literals
436         if(YAHOO.lang.isArray(aDefinitions)) {
437             parseColumns(aDefinitions);
438
439             // Store the array
440             this._aDefinitions = aDefinitions;
441         }
442         else {
443             YAHOO.log("Could not initialize ColumnSet due to invalid definitions","error");
444             return null;
445         }
446
447         var i;
448
449         // Determine ROWSPAN value for each Column in the tree
450         var parseTreeForRowspan = function(tree) {
451             var maxRowDepth = 1;
452             var currentRow;
453             var currentColumn;
454
455             // Calculate the max depth of descendants for this row
456             var countMaxRowDepth = function(row, tmpRowDepth) {
457                 tmpRowDepth = tmpRowDepth || 1;
458
459                 for(var n=0; n<row.length; n++) {
460                     var col = row[n];
461                     // Column has children, so keep counting
462                     if(YAHOO.lang.isArray(col.children)) {
463                         tmpRowDepth++;
464                         countMaxRowDepth(col.children, tmpRowDepth);
465                         tmpRowDepth--;
466                     }
467                     // No children, is it the max depth?
468                     else {
469                         if(tmpRowDepth > maxRowDepth) {
470                             maxRowDepth = tmpRowDepth;
471                         }
472                     }
473
474                 }
475             };
476
477             // Count max row depth for each row
478             for(var m=0; m<tree.length; m++) {
479                 currentRow = tree[m];
480                 countMaxRowDepth(currentRow);
481
482                 // Assign the right ROWSPAN values to each Column in the row
483                 for(var p=0; p<currentRow.length; p++) {
484                     currentColumn = currentRow[p];
485                     if(!YAHOO.lang.isArray(currentColumn.children)) {
486                         currentColumn._nRowspan = maxRowDepth;
487                     }
488                     else {
489                         currentColumn._nRowspan = 1;
490                     }
491                 }
492
493                 // Reset counter for next row
494                 maxRowDepth = 1;
495             }
496         };
497         parseTreeForRowspan(tree);
498
499         // Store tree index values
500         for(i=0; i<tree[0].length; i++) {
501             tree[0][i]._nTreeIndex = i;
502         }
503
504         // Store header relationships in an array for HEADERS attribute
505         var recurseAncestorsForHeaders = function(i, oColumn) {
506             headers[i].push(oColumn.getSanitizedKey());
507             if(oColumn._oParent) {
508                 recurseAncestorsForHeaders(i, oColumn._oParent);
509             }
510         };
511         for(i=0; i<keys.length; i++) {
512             headers[i] = [];
513             recurseAncestorsForHeaders(i, keys[i]);
514             headers[i] = headers[i].reverse();
515         }
516
517         // Save to the ColumnSet instance
518         this.tree = tree;
519         this.flat = flat;
520         this.keys = keys;
521         this.headers = headers;
522     },
523
524     /////////////////////////////////////////////////////////////////////////////
525     //
526     // Public methods
527     //
528     /////////////////////////////////////////////////////////////////////////////
529
530     /**
531      * Returns unique name of the ColumnSet instance.
532      *
533      * @method getId
534      * @return {String} Unique name of the ColumnSet instance.
535      */
536
537     getId : function() {
538         return this._sId;
539     },
540
541     /**
542      * ColumnSet instance name, for logging.
543      *
544      * @method toString
545      * @return {String} Unique name of the ColumnSet instance.
546      */
547
548     toString : function() {
549         return "ColumnSet instance " + this._sId;
550     },
551
552     /**
553      * Public accessor to the definitions array.
554      *
555      * @method getDefinitions
556      * @return {Object[]} Array of object literal Column definitions.
557      */
558
559     getDefinitions : function() {
560         var aDefinitions = this._aDefinitions;
561         
562         // Internal recursive function to define Column instances
563         var parseColumns = function(nodeList, oSelf) {
564             // Parse each node at this depth for attributes and any children
565             for(var j=0; j<nodeList.length; j++) {
566                 var currentNode = nodeList[j];
567                 
568                 // Get the Column for each node
569                 var oColumn = oSelf.getColumnById(currentNode.yuiColumnId);
570                 
571                 if(oColumn) {    
572                     // Update the current values
573                     var oDefinition = oColumn.getDefinition();
574                     for(var name in oDefinition) {
575                         if(YAHOO.lang.hasOwnProperty(oDefinition, name)) {
576                             currentNode[name] = oDefinition[name];
577                         }
578                     }
579                 }
580                             
581                 // The Column has descendants
582                 if(YAHOO.lang.isArray(currentNode.children)) {
583                     // The children themselves must also be parsed for Column instances
584                     parseColumns(currentNode.children, oSelf);
585                 }
586             }
587         };
588
589         parseColumns(aDefinitions, this);
590         this._aDefinitions = aDefinitions;
591         return aDefinitions;
592     },
593
594     /**
595      * Returns Column instance with given ID.
596      *
597      * @method getColumnById
598      * @param column {String} Column ID.
599      * @return {YAHOO.widget.Column} Column instance.
600      */
601
602     getColumnById : function(column) {
603         if(YAHOO.lang.isString(column)) {
604             var allColumns = this.flat;
605             for(var i=allColumns.length-1; i>-1; i--) {
606                 if(allColumns[i]._sId === column) {
607                     return allColumns[i];
608                 }
609             }
610         }
611         return null;
612     },
613
614     /**
615      * Returns Column instance with given key or ColumnSet key index.
616      *
617      * @method getColumn
618      * @param column {String | Number} Column key or ColumnSet key index.
619      * @return {YAHOO.widget.Column} Column instance.
620      */
621
622     getColumn : function(column) {
623         if(YAHOO.lang.isNumber(column) && this.keys[column]) {
624             return this.keys[column];
625         }
626         else if(YAHOO.lang.isString(column)) {
627             var allColumns = this.flat;
628             var aColumns = [];
629             for(var i=0; i<allColumns.length; i++) {
630                 if(allColumns[i].key === column) {
631                     aColumns.push(allColumns[i]);
632                 }
633             }
634             if(aColumns.length === 1) {
635                 return aColumns[0];
636             }
637             else if(aColumns.length > 1) {
638                 return aColumns;
639             }
640         }
641         return null;
642     },
643
644     /**
645      * Public accessor returns array of given Column's desendants (if any), including itself.
646      *
647      * @method getDescendants
648      * @parem {YAHOO.widget.Column} Column instance.
649      * @return {Array} Array including the Column itself and all descendants (if any).
650      */
651     getDescendants : function(oColumn) {
652         var oSelf = this;
653         var allDescendants = [];
654         var i;
655
656         // Recursive function to loop thru all children
657         var parse = function(oParent) {
658             allDescendants.push(oParent);
659             // This Column has children
660             if(oParent.children) {
661                 for(i=0; i<oParent.children.length; i++) {
662                     parse(oSelf.getColumn(oParent.children[i].key));
663                 }
664             }
665         };
666         parse(oColumn);
667
668         return allDescendants;
669     }
670 };
671
672 /****************************************************************************/
673 /****************************************************************************/
674 /****************************************************************************/
675
676 /**
677  * The Column class defines and manages attributes of DataTable Columns
678  *
679  * @namespace YAHOO.widget
680  * @class Column
681  * @constructor
682  * @param oConfigs {Object} Object literal of definitions.
683  */
684 YAHOO.widget.Column = function(oConfigs) {
685     this._sId = "yui-col" + YAHOO.widget.Column._nCount;
686     
687     // Object literal defines Column attributes
688     if(oConfigs && YAHOO.lang.isObject(oConfigs)) {
689         for(var sConfig in oConfigs) {
690             if(sConfig) {
691                 this[sConfig] = oConfigs[sConfig];
692             }
693         }
694     }
695
696     // Assign a key if not found
697     if(!YAHOO.lang.isValue(this.key)) {
698         this.key = "yui-dt-col" + YAHOO.widget.Column._nCount;
699     }
700     
701     // Assign a field if not found, defaults to key
702     if(!YAHOO.lang.isValue(this.field)) {
703         this.field = this.key;
704     }
705
706     // Increment counter
707     YAHOO.widget.Column._nCount++;
708
709     // Backward compatibility
710     if(this.width && !YAHOO.lang.isNumber(this.width)) {
711         this.width = null;
712         YAHOO.log("The Column property width must be a number", "warn", this.toString());
713     }
714     if(this.editor && YAHOO.lang.isString(this.editor)) {
715         this.editor = new YAHOO.widget.CellEditor(this.editor, this.editorOptions);
716         YAHOO.log("The Column property editor must be an instance of YAHOO.widget.CellEditor", "warn", this.toString());
717     }
718 };
719
720 /////////////////////////////////////////////////////////////////////////////
721 //
722 // Private member variables
723 //
724 /////////////////////////////////////////////////////////////////////////////
725
726 YAHOO.lang.augmentObject(YAHOO.widget.Column, {
727     /**
728      * Internal class variable to index multiple Column instances.
729      *
730      * @property Column._nCount
731      * @type Number
732      * @private
733      * @static
734      */
735     _nCount : 0,
736
737     formatCheckbox : function(elCell, oRecord, oColumn, oData) {
738         YAHOO.log("The method YAHOO.widget.Column.formatCheckbox() has been" +
739         " deprecated in favor of YAHOO.widget.DataTable.formatCheckbox()", "warn",
740         "YAHOO.widget.Column.formatCheckbox");
741         YAHOO.widget.DataTable.formatCheckbox(elCell, oRecord, oColumn, oData);
742     },
743
744     formatCurrency : function(elCell, oRecord, oColumn, oData) {
745         YAHOO.log("The method YAHOO.widget.Column.formatCurrency() has been" +
746         " deprecated in favor of YAHOO.widget.DataTable.formatCurrency()", "warn",
747         "YAHOO.widget.Column.formatCurrency");
748         YAHOO.widget.DataTable.formatCurrency(elCell, oRecord, oColumn, oData);
749     },
750
751     formatDate : function(elCell, oRecord, oColumn, oData) {
752         YAHOO.log("The method YAHOO.widget.Column.formatDate() has been" +
753         " deprecated in favor of YAHOO.widget.DataTable.formatDate()", "warn",
754         "YAHOO.widget.Column.formatDate");
755         YAHOO.widget.DataTable.formatDate(elCell, oRecord, oColumn, oData);
756     },
757
758     formatEmail : function(elCell, oRecord, oColumn, oData) {
759         YAHOO.log("The method YAHOO.widget.Column.formatEmail() has been" +
760         " deprecated in favor of YAHOO.widget.DataTable.formatEmail()", "warn",
761         "YAHOO.widget.Column.formatEmail");
762         YAHOO.widget.DataTable.formatEmail(elCell, oRecord, oColumn, oData);
763     },
764
765     formatLink : function(elCell, oRecord, oColumn, oData) {
766         YAHOO.log("The method YAHOO.widget.Column.formatLink() has been" +
767         " deprecated in favor of YAHOO.widget.DataTable.formatLink()", "warn",
768         "YAHOO.widget.Column.formatLink");
769         YAHOO.widget.DataTable.formatLink(elCell, oRecord, oColumn, oData);
770     },
771
772     formatNumber : function(elCell, oRecord, oColumn, oData) {
773         YAHOO.log("The method YAHOO.widget.Column.formatNumber() has been" +
774         " deprecated in favor of YAHOO.widget.DataTable.formatNumber()", "warn",
775         "YAHOO.widget.Column.formatNumber");
776         YAHOO.widget.DataTable.formatNumber(elCell, oRecord, oColumn, oData);
777     },
778
779     formatSelect : function(elCell, oRecord, oColumn, oData) {
780         YAHOO.log("The method YAHOO.widget.Column.formatSelect() has been" +
781         " deprecated in favor of YAHOO.widget.DataTable.formatDropdown()", "warn",
782         "YAHOO.widget.Column.formatSelect");
783         YAHOO.widget.DataTable.formatDropdown(elCell, oRecord, oColumn, oData);
784     }
785 });
786
787 YAHOO.widget.Column.prototype = {
788     /**
789      * Unique String identifier assigned at instantiation.
790      *
791      * @property _sId
792      * @type String
793      * @private
794      */
795     _sId : null,
796
797     /**
798      * Reference to Column's current position index within its ColumnSet's keys
799      * array, if applicable. This property only applies to non-nested and bottom-
800      * level child Columns.
801      *
802      * @property _nKeyIndex
803      * @type Number
804      * @private
805      */
806     _nKeyIndex : null,
807
808     /**
809      * Reference to Column's current position index within its ColumnSet's tree
810      * array, if applicable. This property only applies to non-nested and top-
811      * level parent Columns.
812      *
813      * @property _nTreeIndex
814      * @type Number
815      * @private
816      */
817     _nTreeIndex : null,
818
819     /**
820      * Number of table cells the Column spans.
821      *
822      * @property _nColspan
823      * @type Number
824      * @private
825      */
826     _nColspan : 1,
827
828     /**
829      * Number of table rows the Column spans.
830      *
831      * @property _nRowspan
832      * @type Number
833      * @private
834      */
835     _nRowspan : 1,
836
837     /**
838      * Column's parent Column instance, or null.
839      *
840      * @property _oParent
841      * @type YAHOO.widget.Column
842      * @private
843      */
844     _oParent : null,
845
846     /**
847      * The DOM reference to the associated TH element.
848      *
849      * @property _elTh
850      * @type HTMLElement
851      * @private
852      */
853     _elTh : null,
854
855     /**
856      * The DOM reference to the associated TH element's liner DIV element.
857      *
858      * @property _elThLiner
859      * @type HTMLElement
860      * @private
861      */
862     _elThLiner : null,
863
864     /**
865      * The DOM reference to the associated TH element's label SPAN element.
866      *
867      * @property _elThLabel
868      * @type HTMLElement
869      * @private
870      */
871     _elThLabel : null,
872
873     /**
874      * The DOM reference to the associated resizerelement (if any).
875      *
876      * @property _elResizer
877      * @type HTMLElement
878      * @private
879      */
880     _elResizer : null,
881
882     /**
883      * Internal width tracker.
884      *
885      * @property _nWidth
886      * @type Number
887      * @private
888      */
889     _nWidth : null,
890
891     /**
892      * For unreg() purposes, a reference to the Column's DragDrop instance.
893      *
894      * @property _dd
895      * @type YAHOO.util.DragDrop
896      * @private
897      */
898     _dd : null,
899
900     /**
901      * For unreg() purposes, a reference to the Column resizer's DragDrop instance.
902      *
903      * @property _ddResizer
904      * @type YAHOO.util.DragDrop
905      * @private
906      */
907     _ddResizer : null,
908
909     /////////////////////////////////////////////////////////////////////////////
910     //
911     // Public member variables
912     //
913     /////////////////////////////////////////////////////////////////////////////
914
915     /**
916      * Unique name, required.
917      *
918      * @property key
919      * @type String
920      */
921     key : null,
922
923     /**
924      * Associated database field, or null.
925      *
926      * @property field
927      * @type String
928      */
929     field : null,
930
931     /**
932      * Text or HTML for display as Column's label in the TH element.
933      *
934      * @property label
935      * @type String
936      */
937     label : null,
938
939     /**
940      * Column head cell ABBR for accessibility.
941      *
942      * @property abbr
943      * @type String
944      */
945     abbr : null,
946
947     /**
948      * Array of object literals that define children (nested headers) of a Column.
949      *
950      * @property children
951      * @type Object[]
952      */
953     children : null,
954
955     /**
956      * Column width (in pixels).
957      *
958      * @property width
959      * @type Number
960      */
961     width : null,
962
963     /**
964      * Minimum Column width (in pixels).
965      *
966      * @property minWidth
967      * @type Number
968      * @default null
969      */
970     minWidth : null,
971
972     /**
973      * When a width is not defined for a Column, maxAutoWidth defines an upper
974      * limit that the Column should be auto-sized to. If resizeable is enabled, 
975      * users may still resize to a greater width. Most useful for Columns intended
976      * to hold long unbroken, unwrapped Strings, such as URLs, to prevent very
977      * wide Columns from disrupting visual readability by inducing truncation.
978      *
979      * @property maxAutoWidth
980      * @type Number
981      * @default null
982      */
983     maxAutoWidth : null,
984
985     /**
986      * True if Column is in hidden state.
987      *
988      * @property hidden
989      * @type Boolean
990      * @default false     
991      */
992     hidden : false,
993
994     /**
995      * True if Column is in selected state.
996      *
997      * @property selected
998      * @type Boolean
999      * @default false     
1000      */
1001     selected : false,
1002
1003     /**
1004      * Custom CSS class or array of classes to be applied to every cell in the Column.
1005      *
1006      * @property className
1007      * @type String || String[]
1008      */
1009     className : null,
1010
1011     /**
1012      * Defines a format function.
1013      *
1014      * @property formatter
1015      * @type String || HTMLFunction
1016      */
1017     formatter : null,
1018     
1019     /**
1020      * Config passed to YAHOO.util.Number.format() by the 'currency' Column formatter.
1021      *
1022      * @property currencyOptions
1023      * @type Object
1024      * @default null
1025      */
1026     currencyOptions : null,
1027
1028     /**
1029      * Config passed to YAHOO.util.Date.format() by the 'date' Column formatter.
1030      *
1031      * @property dateOptions
1032      * @type Object
1033      * @default null
1034      */
1035     dateOptions : null,
1036
1037     /**
1038      * Array of dropdown values for formatter:"dropdown" cases. Can either be a simple array (e.g.,
1039      * ["Alabama","Alaska","Arizona","Arkansas"]) or a an array of objects (e.g.,
1040      * [{label:"Alabama", value:"AL"}, {label:"Alaska", value:"AK"},
1041      * {label:"Arizona", value:"AZ"}, {label:"Arkansas", value:"AR"}]).
1042      *
1043      * @property dropdownOptions
1044      * @type String[] | Object[]
1045      */
1046     dropdownOptions : null,
1047      
1048     /**
1049      * A CellEditor instance, otherwise Column is not editable.     
1050      *
1051      * @property editor
1052      * @type YAHOO.widget.CellEditor
1053      */
1054     editor : null,
1055
1056     /**
1057      * True if Column is resizeable, false otherwise. The Drag & Drop Utility is
1058      * required to enable this feature. Only bottom-level and non-nested Columns are
1059      * resizeble. 
1060      *
1061      * @property resizeable
1062      * @type Boolean
1063      * @default false
1064      */
1065     resizeable : false,
1066
1067     /**
1068      * True if Column is sortable, false otherwise.
1069      *
1070      * @property sortable
1071      * @type Boolean
1072      * @default false
1073      */
1074     sortable : false,
1075
1076     /**
1077      * @property sortOptions.defaultOrder
1078      * @deprecated Use sortOptions.defaultDir.
1079      */
1080     /**
1081      * Default sort direction for Column: YAHOO.widget.DataTable.CLASS_ASC or YAHOO.widget.DataTable.CLASS_DESC.
1082      *
1083      * @property sortOptions.defaultDir
1084      * @type String
1085      * @default null
1086      */
1087     /**
1088      * Custom field to sort on.
1089      *
1090      * @property sortOptions.field
1091      * @type String
1092      * @default null
1093      */
1094     /**
1095      * Custom sort handler. Signature: sortFunction(a, b, desc, field) where field is the sortOptions.field value
1096      *
1097      * @property sortOptions.sortFunction
1098      * @type Function
1099      * @default null
1100      */
1101     sortOptions : null,
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117     /////////////////////////////////////////////////////////////////////////////
1118     //
1119     // Public methods
1120     //
1121     /////////////////////////////////////////////////////////////////////////////
1122
1123     /**
1124      * Returns unique ID string.
1125      *
1126      * @method getId
1127      * @return {String} Unique ID string.
1128      */
1129     getId : function() {
1130         return this._sId;
1131     },
1132
1133     /**
1134      * Column instance name, for logging.
1135      *
1136      * @method toString
1137      * @return {String} Column's unique name.
1138      */
1139     toString : function() {
1140         return "Column instance " + this._sId;
1141     },
1142
1143     /**
1144      * Returns object literal definition.
1145      *
1146      * @method getDefinition
1147      * @return {Object} Object literal definition.
1148      */
1149     getDefinition : function() {
1150         var oDefinition = {};
1151         
1152         // Update the definition
1153         oDefinition.abbr = this.abbr;
1154         oDefinition.className = this.className;
1155         oDefinition.editor = this.editor;
1156         oDefinition.editorOptions = this.editorOptions; //TODO: deprecated
1157         oDefinition.field = this.field;
1158         oDefinition.formatter = this.formatter;
1159         oDefinition.hidden = this.hidden;
1160         oDefinition.key = this.key;
1161         oDefinition.label = this.label;
1162         oDefinition.minWidth = this.minWidth;
1163         oDefinition.maxAutoWidth = this.maxAutoWidth;
1164         oDefinition.resizeable = this.resizeable;
1165         oDefinition.selected = this.selected;
1166         oDefinition.sortable = this.sortable;
1167         oDefinition.sortOptions = this.sortOptions;
1168         oDefinition.width = this.width;
1169
1170         return oDefinition;
1171     },
1172
1173     /**
1174      * Returns unique Column key.
1175      *
1176      * @method getKey
1177      * @return {String} Column key.
1178      */
1179     getKey : function() {
1180         return this.key;
1181     },
1182     
1183     /**
1184      * Returns field.
1185      *
1186      * @method getField
1187      * @return {String} Column field.
1188      */
1189     getField : function() {
1190         return this.field;
1191     },
1192     
1193     /**
1194      * Returns Column key which has been sanitized for DOM (class and ID) usage
1195      * starts with letter, contains only letters, numbers, hyphen, or period.
1196      *
1197      * @method getSanitizedKey
1198      * @return {String} Sanitized Column key.
1199      */
1200     getSanitizedKey : function() {
1201         return this.getKey().replace(/[^\w\-]/g,"");
1202     },
1203
1204     /**
1205      * Public accessor returns Column's current position index within its
1206      * ColumnSet's keys array, if applicable. Only non-nested and bottom-level
1207      * child Columns will return a value.
1208      *
1209      * @method getKeyIndex
1210      * @return {Number} Position index, or null.
1211      */
1212     getKeyIndex : function() {
1213         return this._nKeyIndex;
1214     },
1215
1216     /**
1217      * Public accessor returns Column's current position index within its
1218      * ColumnSet's tree array, if applicable. Only non-nested and top-level parent
1219      * Columns will return a value;
1220      *
1221      * @method getTreeIndex
1222      * @return {Number} Position index, or null.
1223      */
1224     getTreeIndex : function() {
1225         return this._nTreeIndex;
1226     },
1227
1228     /**
1229      * Public accessor returns Column's parent instance if any, or null otherwise.
1230      *
1231      * @method getParent
1232      * @return {YAHOO.widget.Column} Column's parent instance.
1233      */
1234     getParent : function() {
1235         return this._oParent;
1236     },
1237
1238     /**
1239      * Public accessor returns Column's calculated COLSPAN value.
1240      *
1241      * @method getColspan
1242      * @return {Number} Column's COLSPAN value.
1243      */
1244     getColspan : function() {
1245         return this._nColspan;
1246     },
1247     // Backward compatibility
1248     getColSpan : function() {
1249         YAHOO.log("The method getColSpan() has been" +
1250         " deprecated in favor of getColspan()", "warn", this.toString());
1251         return this.getColspan();
1252     },
1253
1254     /**
1255      * Public accessor returns Column's calculated ROWSPAN value.
1256      *
1257      * @method getRowspan
1258      * @return {Number} Column's ROWSPAN value.
1259      */
1260     getRowspan : function() {
1261         return this._nRowspan;
1262     },
1263
1264     /**
1265      * Returns DOM reference to the key TH element.
1266      *
1267      * @method getThEl
1268      * @return {HTMLElement} TH element.
1269      */
1270     getThEl : function() {
1271         return this._elTh;
1272     },
1273
1274     /**
1275      * Returns DOM reference to the TH's liner DIV element. Introduced since
1276      * resizeable Columns may have an extra resizer liner, making the DIV liner
1277      * not reliably the TH element's first child.               
1278      *
1279      * @method getThLInerEl
1280      * @return {HTMLElement} TH element.
1281      */
1282     getThLinerEl : function() {
1283         return this._elThLiner;
1284     },
1285     
1286     /**
1287      * Returns DOM reference to the resizer element, or null.
1288      *
1289      * @method getResizerEl
1290      * @return {HTMLElement} DIV element.
1291      */
1292     getResizerEl : function() {
1293         return this._elResizer;
1294     },
1295
1296     // Backward compatibility
1297     /**
1298      * @method getColEl
1299      * @deprecated Use getThEl
1300      */
1301     getColEl : function() {
1302         YAHOO.log("The method getColEl() has been" +
1303         " deprecated in favor of getThEl()", "warn",
1304         this.toString());
1305         return this.getThEl();
1306     },
1307     getIndex : function() {
1308         YAHOO.log("The method getIndex() has been" +
1309         " deprecated in favor of getKeyIndex()", "warn",
1310         this.toString());
1311         return this.getKeyIndex();
1312     },
1313     format : function() {
1314         YAHOO.log("The method format() has been deprecated in favor of the " +
1315         "DataTable method formatCell()", "error", this.toString());
1316     }
1317 };
1318
1319 /****************************************************************************/
1320 /****************************************************************************/
1321 /****************************************************************************/
1322
1323 /**
1324  * Sort static utility to support Column sorting.
1325  *
1326  * @namespace YAHOO.util
1327  * @class Sort
1328  * @static
1329  */
1330 YAHOO.util.Sort = {
1331     /////////////////////////////////////////////////////////////////////////////
1332     //
1333     // Public methods
1334     //
1335     /////////////////////////////////////////////////////////////////////////////
1336
1337     /**
1338      * Comparator function for simple case-insensitive string sorting.
1339      *
1340      * @method compare
1341      * @param a {Object} First sort argument.
1342      * @param b {Object} Second sort argument.
1343      * @param desc {Boolean} True if sort direction is descending, false if
1344      * sort direction is ascending.
1345      */
1346     compare: function(a, b, desc) {
1347         if((a === null) || (typeof a == "undefined")) {
1348             if((b === null) || (typeof b == "undefined")) {
1349                 return 0;
1350             }
1351             else {
1352                 return 1;
1353             }
1354         }
1355         else if((b === null) || (typeof b == "undefined")) {
1356             return -1;
1357         }
1358
1359         if(a.constructor == String) {
1360             a = a.toLowerCase();
1361         }
1362         if(b.constructor == String) {
1363             b = b.toLowerCase();
1364         }
1365         if(a < b) {
1366             return (desc) ? 1 : -1;
1367         }
1368         else if (a > b) {
1369             return (desc) ? -1 : 1;
1370         }
1371         else {
1372             return 0;
1373         }
1374     }
1375 };
1376
1377 /****************************************************************************/
1378 /****************************************************************************/
1379 /****************************************************************************/
1380
1381 /**
1382  * ColumnDD subclasses DragDrop to support rearrangeable Columns.
1383  *
1384  * @namespace YAHOO.util
1385  * @class ColumnDD
1386  * @extends YAHOO.util.DDProxy
1387  * @constructor
1388  * @param oDataTable {YAHOO.widget.DataTable} DataTable instance.
1389  * @param oColumn {YAHOO.widget.Column} Column instance.
1390  * @param elTh {HTMLElement} TH element reference.
1391  * @param elTarget {HTMLElement} Drag target element.
1392  */
1393 YAHOO.widget.ColumnDD = function(oDataTable, oColumn, elTh, elTarget) {
1394     if(oDataTable && oColumn && elTh && elTarget) {
1395         this.datatable = oDataTable;
1396         this.table = oDataTable.getTableEl();
1397         this.column = oColumn;
1398         this.headCell = elTh;
1399         this.pointer = elTarget;
1400         this.newIndex = null;
1401         this.init(elTh);
1402         this.initFrame(); // Needed for DDProxy
1403         this.invalidHandleTypes = {};
1404
1405         // Set top/bottom padding to account for children of nested columns
1406         this.setPadding(10, 0, (this.datatable.getTheadEl().offsetHeight + 10) , 0);
1407
1408         YAHOO.util.Event.on(window, 'resize', function() {
1409             this.initConstraints();
1410         }, this, true);
1411     }
1412     else {
1413         YAHOO.log("Column dragdrop could not be created","warn",oDataTable.toString());
1414     }
1415 };
1416
1417 if(YAHOO.util.DDProxy) {
1418     YAHOO.extend(YAHOO.widget.ColumnDD, YAHOO.util.DDProxy, {
1419         initConstraints: function() {
1420             //Get the top, right, bottom and left positions
1421             var region = YAHOO.util.Dom.getRegion(this.table),
1422                 //Get the element we are working on
1423                 el = this.getEl(),
1424                 //Get the xy position of it
1425                 xy = YAHOO.util.Dom.getXY(el),
1426                 //Get the width and height
1427                 width = parseInt(YAHOO.util.Dom.getStyle(el, 'width'), 10),
1428                 height = parseInt(YAHOO.util.Dom.getStyle(el, 'height'), 10),
1429                 //Set left to x minus left
1430                 left = ((xy[0] - region.left) + 15), //Buffer of 15px
1431                 //Set right to right minus x minus width
1432                 right = ((region.right - xy[0] - width) + 15);
1433     
1434             //Set the constraints based on the above calculations
1435             this.setXConstraint(left, right);
1436             this.setYConstraint(10, 10);            
1437         },
1438         _resizeProxy: function() {
1439             YAHOO.widget.ColumnDD.superclass._resizeProxy.apply(this, arguments);
1440             var dragEl = this.getDragEl(),
1441                 el = this.getEl();
1442
1443             YAHOO.util.Dom.setStyle(this.pointer, 'height', (this.table.parentNode.offsetHeight + 10) + 'px');
1444             YAHOO.util.Dom.setStyle(this.pointer, 'display', 'block');
1445             var xy = YAHOO.util.Dom.getXY(el);
1446             YAHOO.util.Dom.setXY(this.pointer, [xy[0], (xy[1] - 5)]);
1447             
1448             YAHOO.util.Dom.setStyle(dragEl, 'height', this.datatable.getContainerEl().offsetHeight + "px");
1449             YAHOO.util.Dom.setStyle(dragEl, 'width', (parseInt(YAHOO.util.Dom.getStyle(dragEl, 'width'),10) + 4) + 'px');
1450             YAHOO.util.Dom.setXY(this.dragEl, xy);
1451         },
1452         onMouseDown: function() {
1453                 this.initConstraints();
1454                 this.resetConstraints();
1455         },
1456         clickValidator: function(e) {
1457             if(!this.column.hidden) {
1458                 var target = YAHOO.util.Event.getTarget(e);
1459                 return ( this.isValidHandleChild(target) &&
1460                             (this.id == this.handleElId ||
1461                                 this.DDM.handleWasClicked(target, this.id)) );
1462             }
1463         },
1464         onDragOver: function(ev, id) {
1465             // Validate target as a Column
1466             var target = this.datatable.getColumn(id);
1467             if(target) {                
1468                 // Validate target as a top-level parent
1469                 var targetIndex = target.getTreeIndex();
1470                 while((targetIndex === null) && target.getParent()) {
1471                     target = target.getParent();
1472                     targetIndex = target.getTreeIndex();
1473                 }
1474                 if(targetIndex !== null) {
1475                     // Are we placing to left or right of target?
1476                     var elTarget = target.getThEl();
1477                     var newIndex = targetIndex;
1478                     var mouseX = YAHOO.util.Event.getPageX(ev),
1479                         targetX = YAHOO.util.Dom.getX(elTarget),
1480                         midX = targetX + ((YAHOO.util.Dom.get(elTarget).offsetWidth)/2),
1481                         currentIndex =  this.column.getTreeIndex();
1482                     
1483                     if (mouseX < midX) {
1484                        YAHOO.util.Dom.setX(this.pointer, targetX);
1485                     } else {
1486                         var targetWidth = parseInt(elTarget.offsetWidth, 10);
1487                         YAHOO.util.Dom.setX(this.pointer, (targetX + targetWidth));
1488                         newIndex++;
1489                     }
1490                     if (targetIndex > currentIndex) {
1491                         newIndex--;
1492                     }
1493                     if(newIndex < 0) {
1494                         newIndex = 0;
1495                     }
1496                     else if(newIndex > this.datatable.getColumnSet().tree[0].length) {
1497                         newIndex = this.datatable.getColumnSet().tree[0].length;
1498                     }
1499                     this.newIndex = newIndex;
1500                 }
1501             }
1502         },
1503         onDragDrop: function() {
1504             this.datatable.reorderColumn(this.column, this.newIndex);
1505         },
1506         endDrag: function() {
1507             this.newIndex = null;
1508             YAHOO.util.Dom.setStyle(this.pointer, 'display', 'none');
1509         }
1510     });
1511 }
1512
1513 /****************************************************************************/
1514 /****************************************************************************/
1515 /****************************************************************************/
1516
1517 /**
1518  * ColumnResizer subclasses DragDrop to support resizeable Columns.
1519  *
1520  * @namespace YAHOO.util
1521  * @class ColumnResizer
1522  * @extends YAHOO.util.DDProxy
1523  * @constructor
1524  * @param oDataTable {YAHOO.widget.DataTable} DataTable instance.
1525  * @param oColumn {YAHOO.widget.Column} Column instance.
1526  * @param elTh {HTMLElement} TH element reference.
1527  * @param sHandleElId {String} DOM ID of the handle element that causes the resize.
1528  * @param elProxy {HTMLElement} Resizer proxy element.
1529  */
1530 YAHOO.util.ColumnResizer = function(oDataTable, oColumn, elTh, sHandleId, elProxy) {
1531     if(oDataTable && oColumn && elTh && sHandleId) {
1532         this.datatable = oDataTable;
1533         this.column = oColumn;
1534         this.headCell = elTh;
1535         this.headCellLiner = oColumn.getThLinerEl();
1536         this.resizerLiner = elTh.firstChild;
1537         this.init(sHandleId, sHandleId, {dragOnly:true, dragElId: elProxy.id});
1538         this.initFrame(); // Needed for proxy
1539         this.resetResizerEl(); // Needed when rowspan > 0
1540
1541         // Set right padding for bug 1858462
1542         this.setPadding(0, 1, 0, 0);
1543     }
1544     else {
1545         YAHOO.log("Column resizer could not be created","warn",oDataTable.toString());
1546     }
1547 };
1548
1549 if(YAHOO.util.DD) {
1550     YAHOO.extend(YAHOO.util.ColumnResizer, YAHOO.util.DDProxy, {
1551         /////////////////////////////////////////////////////////////////////////////
1552         //
1553         // Public methods
1554         //
1555         /////////////////////////////////////////////////////////////////////////////
1556         /**
1557          * Resets resizer element.
1558          *
1559          * @method resetResizerEl
1560          */
1561         resetResizerEl : function() {
1562             var resizerStyle = YAHOO.util.Dom.get(this.handleElId).style;
1563             resizerStyle.left = "auto";
1564             resizerStyle.right = 0;
1565             resizerStyle.top = "auto";
1566             resizerStyle.bottom = 0;
1567             resizerStyle.height = this.headCell.offsetHeight+"px";
1568         },
1569     
1570         /////////////////////////////////////////////////////////////////////////////
1571         //
1572         // Public DOM event handlers
1573         //
1574         /////////////////////////////////////////////////////////////////////////////
1575     
1576         /**
1577          * Handles mouseup events on the Column resizer.
1578          *
1579          * @method onMouseUp
1580          * @param e {string} The mouseup event
1581          */
1582         onMouseUp : function(e) {
1583             // Reset height of all resizer els in case TH's have changed height
1584             var allKeys = this.datatable.getColumnSet().keys,
1585                 col;
1586             for(var i=0, len=allKeys.length; i<len; i++) {
1587                 col = allKeys[i];
1588                 if(col._ddResizer) {
1589                     col._ddResizer.resetResizerEl();
1590                 }
1591             }
1592             this.resetResizerEl();
1593             
1594             var el = this.headCellLiner;
1595             var newWidth = el.offsetWidth -
1596                 (parseInt(YAHOO.util.Dom.getStyle(el,"paddingLeft"),10)|0) -
1597                 (parseInt(YAHOO.util.Dom.getStyle(el,"paddingRight"),10)|0);
1598
1599             this.datatable.fireEvent("columnResizeEvent", {column:this.column,target:this.headCell,width:newWidth});
1600         },
1601     
1602         /**
1603          * Handles mousedown events on the Column resizer.
1604          *
1605          * @method onMouseDown
1606          * @param e {string} The mousedown event
1607          */
1608         onMouseDown : function(e) {
1609             this.startWidth = this.headCellLiner.offsetWidth;
1610             this.startX = YAHOO.util.Event.getXY(e)[0];
1611             this.nLinerPadding = (parseInt(YAHOO.util.Dom.getStyle(this.headCellLiner,"paddingLeft"),10)|0) +
1612                     (parseInt(YAHOO.util.Dom.getStyle(this.headCellLiner,"paddingRight"),10)|0);
1613         },
1614     
1615         /**
1616          * Custom clickValidator to ensure Column is not in hidden state.
1617          *
1618          * @method clickValidator
1619          * @param {Event} e
1620          * @private
1621          */
1622         clickValidator : function(e) {
1623             if(!this.column.hidden) {
1624                 var target = YAHOO.util.Event.getTarget(e);
1625                 return ( this.isValidHandleChild(target) &&
1626                             (this.id == this.handleElId ||
1627                                 this.DDM.handleWasClicked(target, this.id)) );
1628             }
1629         },
1630     
1631         /**
1632          * Handles start drag on the Column resizer.
1633          *
1634          * @method startDrag
1635          * @param e {string} The drag event
1636          */
1637         startDrag : function() {
1638             // Shrinks height of all resizer els to not hold open TH els
1639             var allKeys = this.datatable.getColumnSet().keys,
1640                 thisKey = this.column.getKeyIndex(),
1641                 col;
1642             for(var i=0, len=allKeys.length; i<len; i++) {
1643                 col = allKeys[i];
1644                 if(col._ddResizer) {
1645                     YAHOO.util.Dom.get(col._ddResizer.handleElId).style.height = "1em";
1646                 }
1647             }
1648         },
1649
1650         /**
1651          * Handles drag events on the Column resizer.
1652          *
1653          * @method onDrag
1654          * @param e {string} The drag event
1655          */
1656         onDrag : function(e) {
1657             var newX = YAHOO.util.Event.getXY(e)[0];
1658             if(newX > YAHOO.util.Dom.getX(this.headCellLiner)) {
1659                 var offsetX = newX - this.startX;
1660                 var newWidth = this.startWidth + offsetX - this.nLinerPadding;
1661                 if(newWidth > 0) {
1662                     this.datatable.setColumnWidth(this.column, newWidth);
1663                 }
1664             }
1665         }
1666     });
1667 }
1668
1669 /////////////////////////////////////////////////////////////////////////////
1670 //
1671 // Deprecated
1672 //
1673 /////////////////////////////////////////////////////////////////////////////
1674
1675 /**
1676  * @property editorOptions
1677  * @deprecated Pass configs directly to CellEditor constructor. 
1678  */
1679
1680
1681 (function () {
1682
1683 var lang   = YAHOO.lang,
1684     util   = YAHOO.util,
1685     widget = YAHOO.widget,
1686     
1687     Dom    = util.Dom,
1688     Ev     = util.Event,
1689     DT     = widget.DataTable;
1690
1691 /****************************************************************************/
1692 /****************************************************************************/
1693 /****************************************************************************/
1694
1695 /**
1696  * A RecordSet defines and manages a set of Records.
1697  *
1698  * @namespace YAHOO.widget
1699  * @class RecordSet
1700  * @param data {Object || Object[]} An object literal or an array of data.
1701  * @constructor
1702  */
1703 YAHOO.widget.RecordSet = function(data) {
1704     // Internal variables
1705     this._sId = "yui-rs" + widget.RecordSet._nCount;
1706     widget.RecordSet._nCount++;
1707     this._records = [];
1708     //this._length = 0;
1709
1710     if(data) {
1711         if(lang.isArray(data)) {
1712             this.addRecords(data);
1713         }
1714         else if(lang.isObject(data)) {
1715             this.addRecord(data);
1716         }
1717     }
1718
1719     YAHOO.log("RecordSet initialized", "info", this.toString());
1720 };
1721
1722 var RS = widget.RecordSet;
1723
1724 /**
1725  * Internal class variable to name multiple Recordset instances.
1726  *
1727  * @property RecordSet._nCount
1728  * @type Number
1729  * @private
1730  * @static
1731  */
1732 RS._nCount = 0;
1733
1734 RS.prototype = {
1735
1736     /////////////////////////////////////////////////////////////////////////////
1737     //
1738     // Private member variables
1739     //
1740     /////////////////////////////////////////////////////////////////////////////
1741     /**
1742      * Unique String identifier assigned at instantiation.
1743      *
1744      * @property _sId
1745      * @type String
1746      * @private
1747      */
1748     _sId : null,
1749
1750     /**
1751      * Internal counter of how many Records are in the RecordSet.
1752      *
1753      * @property _length
1754      * @type Number
1755      * @private
1756      * @deprecated No longer used
1757      */
1758     //_length : null,
1759
1760     /////////////////////////////////////////////////////////////////////////////
1761     //
1762     // Private methods
1763     //
1764     /////////////////////////////////////////////////////////////////////////////
1765
1766     /**
1767      * Adds one Record to the RecordSet at the given index. If index is null,
1768      * then adds the Record to the end of the RecordSet.
1769      *
1770      * @method _addRecord
1771      * @param oData {Object} An object literal of data.
1772      * @param index {Number} (optional) Position index.
1773      * @return {YAHOO.widget.Record} A Record instance.
1774      * @private
1775      */
1776     _addRecord : function(oData, index) {
1777         var oRecord = new YAHOO.widget.Record(oData);
1778         
1779         if(YAHOO.lang.isNumber(index) && (index > -1)) {
1780             this._records.splice(index,0,oRecord);
1781         }
1782         else {
1783             //index = this.getLength();
1784             //this._records[index] = oRecord;
1785             this._records[this._records.length] = oRecord;
1786         }
1787         //this._length++;
1788         return oRecord;
1789     },
1790
1791     /**
1792      * Sets/replaces one Record to the RecordSet at the given index.  Existing
1793      * Records with higher indexes are not shifted.  If no index specified, the
1794      * Record is added to the end of the RecordSet.
1795      *
1796      * @method _setRecord
1797      * @param oData {Object} An object literal of data.
1798      * @param index {Number} (optional) Position index.
1799      * @return {YAHOO.widget.Record} A Record instance.
1800      * @private
1801      */
1802     _setRecord : function(oData, index) {
1803         if (!lang.isNumber(index) || index < 0) {
1804             index = this._records.length;
1805         }
1806         return (this._records[index] = new widget.Record(oData));
1807         /*
1808         if(lang.isNumber(index) && (index > -1)) {
1809             this._records[index] = oRecord;
1810             if((index+1) > this.getLength()) {
1811                 this._length = index+1;
1812             }
1813         }
1814         else {
1815             this._records[this.getLength()] = oRecord;
1816             this._length++;
1817         }
1818         return oRecord;
1819         */
1820     },
1821
1822     /**
1823      * Deletes Records from the RecordSet at the given index. If range is null,
1824      * then only one Record is deleted.
1825      *
1826      * @method _deleteRecord
1827      * @param index {Number} Position index.
1828      * @param range {Number} (optional) How many Records to delete
1829      * @private
1830      */
1831     _deleteRecord : function(index, range) {
1832         if(!lang.isNumber(range) || (range < 0)) {
1833             range = 1;
1834         }
1835         this._records.splice(index, range);
1836         //this._length = this._length - range;
1837     },
1838
1839     /////////////////////////////////////////////////////////////////////////////
1840     //
1841     // Public methods
1842     //
1843     /////////////////////////////////////////////////////////////////////////////
1844
1845     /**
1846      * Returns unique name of the RecordSet instance.
1847      *
1848      * @method getId
1849      * @return {String} Unique name of the RecordSet instance.
1850      */
1851     getId : function() {
1852         return this._sId;
1853     },
1854
1855     /**
1856      * Public accessor to the unique name of the RecordSet instance.
1857      *
1858      * @method toString
1859      * @return {String} Unique name of the RecordSet instance.
1860      */
1861     toString : function() {
1862         return "RecordSet instance " + this._sId;
1863     },
1864
1865     /**
1866      * Returns the number of Records held in the RecordSet.
1867      *
1868      * @method getLength
1869      * @return {Number} Number of records in the RecordSet.
1870      */
1871     getLength : function() {
1872             //return this._length;
1873             return this._records.length;
1874     },
1875
1876     /**
1877      * Returns Record by ID or RecordSet position index.
1878      *
1879      * @method getRecord
1880      * @param record {YAHOO.widget.Record | Number | String} Record instance,
1881      * RecordSet position index, or Record ID.
1882      * @return {YAHOO.widget.Record} Record object.
1883      */
1884     getRecord : function(record) {
1885         var i;
1886         if(record instanceof widget.Record) {
1887             for(i=0; i<this._records.length; i++) {
1888                 if(this._records[i] && (this._records[i]._sId === record._sId)) {
1889                     return record;
1890                 }
1891             }
1892         }
1893         else if(lang.isNumber(record)) {
1894             if((record > -1) && (record < this.getLength())) {
1895                 return this._records[record];
1896             }
1897         }
1898         else if(lang.isString(record)) {
1899             for(i=0; i<this._records.length; i++) {
1900                 if(this._records[i] && (this._records[i]._sId === record)) {
1901                     return this._records[i];
1902                 }
1903             }
1904         }
1905         // Not a valid Record for this RecordSet
1906         return null;
1907
1908     },
1909
1910     /**
1911      * Returns an array of Records from the RecordSet.
1912      *
1913      * @method getRecords
1914      * @param index {Number} (optional) Recordset position index of which Record to
1915      * start at.
1916      * @param range {Number} (optional) Number of Records to get.
1917      * @return {YAHOO.widget.Record[]} Array of Records starting at given index and
1918      * length equal to given range. If index is not given, all Records are returned.
1919      */
1920     getRecords : function(index, range) {
1921         if(!lang.isNumber(index)) {
1922             return this._records;
1923         }
1924         if(!lang.isNumber(range)) {
1925             return this._records.slice(index);
1926         }
1927         return this._records.slice(index, index+range);
1928     },
1929
1930     /**
1931      * Returns a boolean indicating whether Records exist in the RecordSet at the
1932      * specified index range.  Returns true if and only if a Record exists at each
1933      * index in the range.
1934      * @method hasRecords
1935      * @param index
1936      * @param range
1937      * @return {Boolean} true if all indices are populated in the RecordSet
1938      */
1939     hasRecords : function (index, range) {
1940         var recs = this.getRecords(index,range);
1941         for (var i = 0; i < range; ++i) {
1942             if (typeof recs[i] === 'undefined') {
1943                 return false;
1944             }
1945         }
1946         return true;
1947     },
1948
1949     /**
1950      * Returns current position index for the given Record.
1951      *
1952      * @method getRecordIndex
1953      * @param oRecord {YAHOO.widget.Record} Record instance.
1954      * @return {Number} Record's RecordSet position index.
1955      */
1956
1957     getRecordIndex : function(oRecord) {
1958         if(oRecord) {
1959             for(var i=this._records.length-1; i>-1; i--) {
1960                 if(this._records[i] && oRecord.getId() === this._records[i].getId()) {
1961                     return i;
1962                 }
1963             }
1964         }
1965         return null;
1966
1967     },
1968
1969     /**
1970      * Adds one Record to the RecordSet at the given index. If index is null,
1971      * then adds the Record to the end of the RecordSet.
1972      *
1973      * @method addRecord
1974      * @param oData {Object} An object literal of data.
1975      * @param index {Number} (optional) Position index.
1976      * @return {YAHOO.widget.Record} A Record instance.
1977      */
1978     addRecord : function(oData, index) {
1979         if(lang.isObject(oData)) {
1980             var oRecord = this._addRecord(oData, index);
1981             this.fireEvent("recordAddEvent",{record:oRecord,data:oData});
1982             YAHOO.log("Added Record at index " + index +
1983                     " with data " + lang.dump(oData), "info", this.toString());
1984             return oRecord;
1985         }
1986         else {
1987             YAHOO.log("Could not add Record with data" +
1988                     lang.dump(oData), "info", this.toString());
1989             return null;
1990         }
1991     },
1992
1993     /**
1994      * Adds multiple Records at once to the RecordSet at the given index with the
1995      * given object literal data. If index is null, then the new Records are
1996      * added to the end of the RecordSet.
1997      *
1998      * @method addRecords
1999      * @param aData {Object[]} An object literal data or an array of data object literals.
2000      * @param index {Number} (optional) Position index.
2001      * @return {YAHOO.widget.Record[]} An array of Record instances.
2002      */
2003     addRecords : function(aData, index) {
2004         if(lang.isArray(aData)) {
2005             var newRecords = [],
2006                 idx,i,len;
2007
2008             index = lang.isNumber(index) ? index : this._records.length;
2009             idx = index;
2010
2011             // Can't go backwards bc we need to preserve order
2012             for(i=0,len=aData.length; i<len; ++i) {
2013                 if(lang.isObject(aData[i])) {
2014                     var record = this._addRecord(aData[i], idx++);
2015                     newRecords.push(record);
2016                 }
2017            }
2018             this.fireEvent("recordsAddEvent",{records:newRecords,data:aData});
2019             YAHOO.log("Added " + newRecords.length + " Record(s) at index " + index +
2020                     " with data " + lang.dump(aData), "info", this.toString());
2021            return newRecords;
2022         }
2023         else if(lang.isObject(aData)) {
2024             var oRecord = this._addRecord(aData);
2025             this.fireEvent("recordsAddEvent",{records:[oRecord],data:aData});
2026             YAHOO.log("Added 1 Record at index " + index +
2027                     " with data " + lang.dump(aData), "info", this.toString());
2028             return oRecord;
2029         }
2030         else {
2031             YAHOO.log("Could not add Records with data " +
2032                     lang.dump(aData), "info", this.toString());
2033             return null;
2034         }
2035     },
2036
2037     /**
2038      * Sets or replaces one Record to the RecordSet at the given index. Unlike
2039      * addRecord, an existing Record at that index is not shifted to preserve it.
2040      * If no index is specified, it adds the Record to the end of the RecordSet.
2041      *
2042      * @method setRecord
2043      * @param oData {Object} An object literal of data.
2044      * @param index {Number} (optional) Position index.
2045      * @return {YAHOO.widget.Record} A Record instance.
2046      */
2047     setRecord : function(oData, index) {
2048         if(lang.isObject(oData)) {
2049             var oRecord = this._setRecord(oData, index);
2050             this.fireEvent("recordSetEvent",{record:oRecord,data:oData});
2051             YAHOO.log("Set Record at index " + index +
2052                     " with data " + lang.dump(oData), "info", this.toString());
2053             return oRecord;
2054         }
2055         else {
2056             YAHOO.log("Could not set Record with data" +
2057                     lang.dump(oData), "info", this.toString());
2058             return null;
2059         }
2060     },
2061
2062     /**
2063      * Sets or replaces multiple Records at once to the RecordSet with the given
2064      * data, starting at the given index. If index is not specified, then the new
2065      * Records are added to the end of the RecordSet.
2066      *
2067      * @method setRecords
2068      * @param aData {Object[]} An array of object literal data.
2069      * @param index {Number} (optional) Position index.
2070      * @return {YAHOO.widget.Record[]} An array of Record instances.
2071      */
2072     setRecords : function(aData, index) {
2073         var Rec   = widget.Record,
2074             a     = lang.isArray(aData) ? aData : [aData],
2075             added = [],
2076             i = 0, l = a.length, j = 0;
2077
2078         index = parseInt(index,10)|0;
2079
2080         for(; i < l; ++i) {
2081             if (typeof a[i] === 'object' && a[i]) {
2082                 added[j++] = this._records[index + i] = new Rec(a[i]);
2083             }
2084         }
2085
2086         this.fireEvent("recordsSetEvent",{records:added,data:aData});
2087         // Backward compatibility for bug 1918245
2088         this.fireEvent("recordsSet",{records:added,data:aData});
2089         YAHOO.log("Set "+j+" Record(s) at index "+index, "info",
2090                   this.toString());
2091
2092         if (a.length && !added.length) {
2093             YAHOO.log("Could not set Records with data " +
2094                     lang.dump(aData), "info", this.toString());
2095         }
2096
2097         return added.length > 1 ? added : added[0];
2098     },
2099
2100     /**
2101      * Updates given Record with given data.
2102      *
2103      * @method updateRecord
2104      * @param record {YAHOO.widget.Record | Number | String} A Record instance,
2105      * a RecordSet position index, or a Record ID.
2106      * @param oData {Object} Object literal of new data.
2107      * @return {YAHOO.widget.Record} Updated Record, or null.
2108      */
2109     updateRecord : function(record, oData) {
2110         var oRecord = this.getRecord(record);
2111         if(oRecord && lang.isObject(oData)) {
2112             // Copy data from the Record for the event that gets fired later
2113             var oldData = {};
2114             for(var key in oRecord._oData) {
2115                 if(lang.hasOwnProperty(oRecord._oData, key)) {
2116                     oldData[key] = oRecord._oData[key];
2117                 }
2118             }
2119             oRecord._oData = oData;
2120             this.fireEvent("recordUpdateEvent",{record:oRecord,newData:oData,oldData:oldData});
2121             YAHOO.log("Record at index " + this.getRecordIndex(oRecord) +
2122                     " updated with data " + lang.dump(oData), "info", this.toString());
2123             return oRecord;
2124         }
2125         else {
2126             YAHOO.log("Could not update Record " + record, "error", this.toString());
2127             return null;
2128         }
2129     },
2130
2131     /**
2132      * @method updateKey
2133      * @deprecated Use updateRecordValue
2134      */
2135     updateKey : function(record, sKey, oData) {
2136         this.updateRecordValue(record, sKey, oData);
2137     },
2138     /**
2139      * Sets given Record at given key to given data.
2140      *
2141      * @method updateRecordValue
2142      * @param record {YAHOO.widget.Record | Number | String} A Record instance,
2143      * a RecordSet position index, or a Record ID.
2144      * @param sKey {String} Key name.
2145      * @param oData {Object} New data.
2146      */
2147     updateRecordValue : function(record, sKey, oData) {
2148         var oRecord = this.getRecord(record);
2149         if(oRecord) {
2150             var oldData = null;
2151             var keyValue = oRecord._oData[sKey];
2152             // Copy data from the Record for the event that gets fired later
2153             if(keyValue && lang.isObject(keyValue)) {
2154                 oldData = {};
2155                 for(var key in keyValue)  {
2156                     if(lang.hasOwnProperty(keyValue, key)) {
2157                         oldData[key] = keyValue[key];
2158                     }
2159                 }
2160             }
2161             // Copy by value
2162             else {
2163                 oldData = keyValue;
2164             }
2165
2166             oRecord._oData[sKey] = oData;
2167             this.fireEvent("keyUpdateEvent",{record:oRecord,key:sKey,newData:oData,oldData:oldData});
2168             this.fireEvent("recordValueUpdateEvent",{record:oRecord,key:sKey,newData:oData,oldData:oldData});
2169             YAHOO.log("Key \"" + sKey +
2170                     "\" for Record at index " + this.getRecordIndex(oRecord) +
2171                     " updated to \"" + lang.dump(oData) + "\"", "info", this.toString());
2172         }
2173         else {
2174             YAHOO.log("Could not update key " + sKey + " for Record " + record, "error", this.toString());
2175         }
2176     },
2177
2178     /**
2179      * Replaces all Records in RecordSet with new object literal data.
2180      *
2181      * @method replaceRecords
2182      * @param data {Object || Object[]} An object literal of data or an array of
2183      * data object literals.
2184      * @return {YAHOO.widget.Record || YAHOO.widget.Record[]} A Record instance or
2185      * an array of Records.
2186      */
2187     replaceRecords : function(data) {
2188         this.reset();
2189         return this.addRecords(data);
2190     },
2191
2192     /**
2193      * Sorts all Records by given function. Records keep their unique IDs but will
2194      * have new RecordSet position indexes.
2195      *
2196      * @method sortRecords
2197      * @param fnSort {Function} Reference to a sort function.
2198      * @param desc {Boolean} True if sort direction is descending, false if sort
2199      * direction is ascending.
2200      * @param field {String} The field to sort by, from sortOptions.field
2201      * @return {YAHOO.widget.Record[]} Sorted array of Records.
2202      */
2203     sortRecords : function(fnSort, desc, field) {
2204         return this._records.sort(function(a, b) {return fnSort(a, b, desc, field);});
2205     },
2206
2207     /**
2208      * Reverses all Records, so ["one", "two", "three"] becomes ["three", "two", "one"].
2209      *
2210      * @method reverseRecords
2211      * @return {YAHOO.widget.Record[]} Reverse-sorted array of Records.
2212      */
2213     reverseRecords : function() {
2214         return this._records.reverse();
2215     },
2216
2217     /**
2218      * Removes the Record at the given position index from the RecordSet. If a range
2219      * is also provided, removes that many Records, starting from the index. Length
2220      * of RecordSet is correspondingly shortened.
2221      *
2222      * @method deleteRecord
2223      * @param index {Number} Record's RecordSet position index.
2224      * @param range {Number} (optional) How many Records to delete.
2225      * @return {Object} A copy of the data held by the deleted Record.
2226      */
2227     deleteRecord : function(index) {
2228         if(lang.isNumber(index) && (index > -1) && (index < this.getLength())) {
2229             // Copy data from the Record for the event that gets fired later
2230             var oData = widget.DataTable._cloneObject(this.getRecord(index).getData());
2231             
2232             this._deleteRecord(index);
2233             this.fireEvent("recordDeleteEvent",{data:oData,index:index});
2234             YAHOO.log("Record deleted at index " + index +
2235                     " and containing data " + lang.dump(oData), "info", this.toString());
2236             return oData;
2237         }
2238         else {
2239             YAHOO.log("Could not delete Record at index " + index, "error", this.toString());
2240             return null;
2241         }
2242     },
2243
2244     /**
2245      * Removes the Record at the given position index from the RecordSet. If a range
2246      * is also provided, removes that many Records, starting from the index. Length
2247      * of RecordSet is correspondingly shortened.
2248      *
2249      * @method deleteRecords
2250      * @param index {Number} Record's RecordSet position index.
2251      * @param range {Number} (optional) How many Records to delete.
2252      * @return {Object[]} An array of copies of the data held by the deleted Records.     
2253      */
2254     deleteRecords : function(index, range) {
2255         if(!lang.isNumber(range)) {
2256             range = 1;
2257         }
2258         if(lang.isNumber(index) && (index > -1) && (index < this.getLength())) {
2259             var recordsToDelete = this.getRecords(index, range);
2260             // Copy data from each Record for the event that gets fired later
2261             var deletedData = [];
2262             
2263             for(var i=0; i<recordsToDelete.length; i++) {
2264                 deletedData[deletedData.length] = widget.DataTable._cloneObject(recordsToDelete[i]);
2265             }
2266             this._deleteRecord(index, range);
2267
2268             this.fireEvent("recordsDeleteEvent",{data:deletedData,index:index});
2269             YAHOO.log(range + "Record(s) deleted at index " + index +
2270                     " and containing data " + lang.dump(deletedData), "info", this.toString());
2271
2272             return deletedData;
2273         }
2274         else {
2275             YAHOO.log("Could not delete Records at index " + index, "error", this.toString());
2276             return null;
2277         }
2278     },
2279
2280     /**
2281      * Deletes all Records from the RecordSet.
2282      *
2283      * @method reset
2284      */
2285     reset : function() {
2286         this._records = [];
2287         //this._length = 0;
2288         this.fireEvent("resetEvent");
2289         YAHOO.log("All Records deleted from RecordSet", "info", this.toString());
2290     }
2291 };
2292
2293 /////////////////////////////////////////////////////////////////////////////
2294 //
2295 // Custom Events
2296 //
2297 /////////////////////////////////////////////////////////////////////////////
2298
2299 // RecordSet uses EventProvider
2300 lang.augmentProto(RS, util.EventProvider);
2301
2302 /**
2303  * Fired when a new Record is added to the RecordSet.
2304  *
2305  * @event recordAddEvent
2306  * @param oArgs.record {YAHOO.widget.Record} The Record instance.
2307  * @param oArgs.data {Object} Data added.
2308  */
2309
2310 /**
2311  * Fired when multiple Records are added to the RecordSet at once.
2312  *
2313  * @event recordsAddEvent
2314  * @param oArgs.records {YAHOO.widget.Record[]} An array of Record instances.
2315  * @param oArgs.data {Object[]} Data added.
2316  */
2317
2318 /**
2319  * Fired when a Record is set in the RecordSet.
2320  *
2321  * @event recordSetEvent
2322  * @param oArgs.record {YAHOO.widget.Record} The Record instance.
2323  * @param oArgs.data {Object} Data added.
2324  */
2325
2326 /**
2327  * Fired when multiple Records are set in the RecordSet at once.
2328  *
2329  * @event recordsSetEvent
2330  * @param oArgs.records {YAHOO.widget.Record[]} An array of Record instances.
2331  * @param oArgs.data {Object[]} Data added.
2332  */
2333
2334 /**
2335  * Fired when a Record is updated with new data.
2336  *
2337  * @event recordUpdateEvent
2338  * @param oArgs.record {YAHOO.widget.Record} The Record instance.
2339  * @param oArgs.newData {Object} New data.
2340  * @param oArgs.oldData {Object} Old data.
2341  */
2342
2343 /**
2344  * Fired when a Record is deleted from the RecordSet.
2345  *
2346  * @event recordDeleteEvent
2347  * @param oArgs.data {Object} A copy of the data held by the Record,
2348  * or an array of data object literals if multiple Records were deleted at once.
2349  * @param oArgs.index {Object} Index of the deleted Record.
2350  */
2351
2352 /**
2353  * Fired when multiple Records are deleted from the RecordSet at once.
2354  *
2355  * @event recordsDeleteEvent
2356  * @param oArgs.data {Object[]} An array of data object literals copied
2357  * from the Records.
2358  * @param oArgs.index {Object} Index of the first deleted Record.
2359  */
2360
2361 /**
2362  * Fired when all Records are deleted from the RecordSet at once.
2363  *
2364  * @event resetEvent
2365  */
2366
2367 /**
2368  * @event keyUpdateEvent    
2369  * @deprecated Use recordValueUpdateEvent     
2370  */
2371
2372 /**
2373  * Fired when a Record value is updated with new data.
2374  *
2375  * @event recordValueUpdateEvent
2376  * @param oArgs.record {YAHOO.widget.Record} The Record instance.
2377  * @param oArgs.key {String} The updated key.
2378  * @param oArgs.newData {Object} New data.
2379  * @param oArgs.oldData {Object} Old data.
2380  *
2381  */
2382
2383
2384 /****************************************************************************/
2385 /****************************************************************************/
2386 /****************************************************************************/
2387
2388 /**
2389  * The Record class defines a DataTable record.
2390  *
2391  * @namespace YAHOO.widget
2392  * @class Record
2393  * @constructor
2394  * @param oConfigs {Object} (optional) Object literal of key/value pairs.
2395  */
2396 YAHOO.widget.Record = function(oLiteral) {
2397     this._nCount = widget.Record._nCount;
2398     this._sId = "yui-rec" + this._nCount;
2399     widget.Record._nCount++;
2400     this._oData = {};
2401     if(lang.isObject(oLiteral)) {
2402         for(var sKey in oLiteral) {
2403             if(lang.hasOwnProperty(oLiteral, sKey)) {
2404                 this._oData[sKey] = oLiteral[sKey];
2405             }
2406         }
2407     }
2408 };
2409
2410 /////////////////////////////////////////////////////////////////////////////
2411 //
2412 // Private member variables
2413 //
2414 /////////////////////////////////////////////////////////////////////////////
2415
2416 /**
2417  * Internal class variable to give unique IDs to Record instances.
2418  *
2419  * @property Record._nCount
2420  * @type Number
2421  * @private
2422  */
2423 YAHOO.widget.Record._nCount = 0;
2424
2425 YAHOO.widget.Record.prototype = {
2426     /**
2427      * Immutable unique count assigned at instantiation. Remains constant while a
2428      * Record's position index can change from sorting.
2429      *
2430      * @property _nCount
2431      * @type Number
2432      * @private
2433      */
2434     _nCount : null,
2435
2436     /**
2437      * Immutable unique ID assigned at instantiation. Remains constant while a
2438      * Record's position index can change from sorting.
2439      *
2440      * @property _sId
2441      * @type String
2442      * @private
2443      */
2444     _sId : null,
2445
2446     /**
2447      * Holds data for the Record in an object literal.
2448      *
2449      * @property _oData
2450      * @type Object
2451      * @private
2452      */
2453     _oData : null,
2454
2455     /////////////////////////////////////////////////////////////////////////////
2456     //
2457     // Public member variables
2458     //
2459     /////////////////////////////////////////////////////////////////////////////
2460
2461     /////////////////////////////////////////////////////////////////////////////
2462     //
2463     // Public methods
2464     //
2465     /////////////////////////////////////////////////////////////////////////////
2466
2467     /**
2468      * Returns unique count assigned at instantiation.
2469      *
2470      * @method getCount
2471      * @return Number
2472      */
2473     getCount : function() {
2474         return this._nCount;
2475     },
2476
2477     /**
2478      * Returns unique ID assigned at instantiation.
2479      *
2480      * @method getId
2481      * @return String
2482      */
2483     getId : function() {
2484         return this._sId;
2485     },
2486
2487     /**
2488      * Returns data for the Record for a field if given, or the entire object
2489      * literal otherwise.
2490      *
2491      * @method getData
2492      * @param sField {String} (Optional) The field from which to retrieve data value.
2493      * @return Object
2494      */
2495     getData : function(sField) {
2496         if(lang.isString(sField)) {
2497             return this._oData[sField];
2498         }
2499         else {
2500             return this._oData;
2501         }
2502     },
2503
2504     /**
2505      * Sets given data at the given key. Use the RecordSet method updateRecordValue to trigger
2506      * events. 
2507      *
2508      * @method setData
2509      * @param sKey {String} The key of the new value.
2510      * @param oData {MIXED} The new value.
2511      */
2512     setData : function(sKey, oData) {
2513         this._oData[sKey] = oData;
2514     }
2515 };
2516
2517 })();
2518
2519 (function () {
2520
2521 var lang   = YAHOO.lang,
2522     util   = YAHOO.util,
2523     widget = YAHOO.widget,
2524     ua     = YAHOO.env.ua,
2525     
2526     Dom    = util.Dom,
2527     Ev     = util.Event,
2528     DS     = util.DataSourceBase;
2529
2530 /**
2531  * The DataTable widget provides a progressively enhanced DHTML control for
2532  * displaying tabular data across A-grade browsers.
2533  *
2534  * @module datatable
2535  * @requires yahoo, dom, event, element, datasource
2536  * @optional dragdrop, dragdrop
2537  * @title DataTable Widget
2538  */
2539
2540 /****************************************************************************/
2541 /****************************************************************************/
2542 /****************************************************************************/
2543
2544 /**
2545  * DataTable class for the YUI DataTable widget.
2546  *
2547  * @namespace YAHOO.widget
2548  * @class DataTable
2549  * @extends YAHOO.util.Element
2550  * @constructor
2551  * @param elContainer {HTMLElement} Container element for the TABLE.
2552  * @param aColumnDefs {Object[]} Array of object literal Column definitions.
2553  * @param oDataSource {YAHOO.util.DataSource} DataSource instance.
2554  * @param oConfigs {object} (optional) Object literal of configuration values.
2555  */
2556 YAHOO.widget.DataTable = function(elContainer,aColumnDefs,oDataSource,oConfigs) {
2557     var DT = widget.DataTable;
2558     
2559     ////////////////////////////////////////////////////////////////////////////
2560     // Backward compatibility for SDT, but prevent infinite loops
2561     
2562     if(oConfigs && oConfigs.scrollable) {
2563         return new YAHOO.widget.ScrollingDataTable(elContainer,aColumnDefs,oDataSource,oConfigs);
2564     }
2565     
2566     ////////////////////////////////////////////////////////////////////////////
2567     // Initialization
2568
2569     // Internal vars
2570     this._nIndex = DT._nCount;
2571     this._sId = "yui-dt"+this._nIndex;
2572     this._oChainRender = new YAHOO.util.Chain();
2573     this._oChainRender.subscribe("end",this._onRenderChainEnd, this, true);
2574
2575     // Initialize configs
2576     this._initConfigs(oConfigs);
2577
2578     // Initialize DataSource
2579     this._initDataSource(oDataSource);
2580     if(!this._oDataSource) {
2581         YAHOO.log("Could not instantiate DataTable due to an invalid DataSource", "error", this.toString());
2582         return;
2583     }
2584
2585     // Initialize ColumnSet
2586     this._initColumnSet(aColumnDefs);
2587     if(!this._oColumnSet) {
2588         YAHOO.log("Could not instantiate DataTable due to an invalid ColumnSet", "error", this.toString());
2589         return;
2590     }
2591
2592     // Initialize RecordSet
2593     this._initRecordSet();
2594     if(!this._oRecordSet) {
2595     }
2596
2597     // Initialize Attributes
2598     DT.superclass.constructor.call(this, elContainer, this.configs);
2599
2600     // Initialize DOM elements
2601     var okDom = this._initDomElements(elContainer);
2602     if(!okDom) {
2603         YAHOO.log("Could not instantiate DataTable due to an invalid DOM element", "error", this.toString());
2604         return;
2605     }
2606             
2607     // Show message as soon as config is available
2608     this.showTableMessage(this.get("MSG_LOADING"), DT.CLASS_LOADING);
2609     
2610     ////////////////////////////////////////////////////////////////////////////
2611     // Once per instance
2612     this._initEvents();
2613
2614     DT._nCount++;
2615     DT._nCurrentCount++;
2616     
2617     ////////////////////////////////////////////////////////////////////////////
2618     // Data integration
2619
2620     // Send a simple initial request
2621     var oCallback = {
2622         success : this.onDataReturnSetRows,
2623         failure : this.onDataReturnSetRows,
2624         scope   : this,
2625         argument: this.getState()
2626     };
2627     
2628     var initialLoad = this.get("initialLoad");
2629     if(initialLoad === true) {
2630         this._oDataSource.sendRequest(this.get("initialRequest"), oCallback);
2631     }
2632     // Do not send an initial request at all
2633     else if(initialLoad === false) {
2634         this.showTableMessage(this.get("MSG_EMPTY"), DT.CLASS_EMPTY);
2635     }
2636     // Send an initial request with a custom payload
2637     else {
2638         var oCustom = initialLoad || {};
2639         oCallback.argument = oCustom.argument || {};
2640         this._oDataSource.sendRequest(oCustom.request, oCallback);
2641     }
2642 };
2643
2644 var DT = widget.DataTable;
2645
2646 /////////////////////////////////////////////////////////////////////////////
2647 //
2648 // Public constants
2649 //
2650 /////////////////////////////////////////////////////////////////////////////
2651
2652 lang.augmentObject(DT, {
2653
2654     /**
2655      * Class name assigned to outer DataTable container.
2656      *
2657      * @property DataTable.CLASS_DATATABLE
2658      * @type String
2659      * @static
2660      * @final
2661      * @default "yui-dt"
2662      */
2663     CLASS_DATATABLE : "yui-dt",
2664
2665     /**
2666      * Class name assigned to liner DIV elements.
2667      *
2668      * @property DataTable.CLASS_LINER
2669      * @type String
2670      * @static
2671      * @final
2672      * @default "yui-dt-liner"
2673      */
2674     CLASS_LINER : "yui-dt-liner",
2675
2676     /**
2677      * Class name assigned to display label elements.
2678      *
2679      * @property DataTable.CLASS_LABEL
2680      * @type String
2681      * @static
2682      * @final
2683      * @default "yui-dt-label"
2684      */
2685     CLASS_LABEL : "yui-dt-label",
2686
2687     /**
2688      * Class name assigned to messaging elements.
2689      *
2690      * @property DataTable.CLASS_MESSAGE
2691      * @type String
2692      * @static
2693      * @final
2694      * @default "yui-dt-message"
2695      */
2696     CLASS_MESSAGE : "yui-dt-message",
2697
2698     /**
2699      * Class name assigned to mask element when DataTable is disabled.
2700      *
2701      * @property DataTable.CLASS_MASK
2702      * @type String
2703      * @static
2704      * @final
2705      * @default "yui-dt-mask"
2706      */
2707     CLASS_MASK : "yui-dt-mask",
2708
2709     /**
2710      * Class name assigned to data elements.
2711      *
2712      * @property DataTable.CLASS_DATA
2713      * @type String
2714      * @static
2715      * @final
2716      * @default "yui-dt-data"
2717      */
2718     CLASS_DATA : "yui-dt-data",
2719
2720     /**
2721      * Class name assigned to Column drag target.
2722      *
2723      * @property DataTable.CLASS_COLTARGET
2724      * @type String
2725      * @static
2726      * @final
2727      * @default "yui-dt-coltarget"
2728      */
2729     CLASS_COLTARGET : "yui-dt-coltarget",
2730
2731     /**
2732      * Class name assigned to resizer handle elements.
2733      *
2734      * @property DataTable.CLASS_RESIZER
2735      * @type String
2736      * @static
2737      * @final
2738      * @default "yui-dt-resizer"
2739      */
2740     CLASS_RESIZER : "yui-dt-resizer",
2741
2742     /**
2743      * Class name assigned to resizer liner elements.
2744      *
2745      * @property DataTable.CLASS_RESIZERLINER
2746      * @type String
2747      * @static
2748      * @final
2749      * @default "yui-dt-resizerliner"
2750      */
2751     CLASS_RESIZERLINER : "yui-dt-resizerliner",
2752
2753     /**
2754      * Class name assigned to resizer proxy elements.
2755      *
2756      * @property DataTable.CLASS_RESIZERPROXY
2757      * @type String
2758      * @static
2759      * @final
2760      * @default "yui-dt-resizerproxy"
2761      */
2762     CLASS_RESIZERPROXY : "yui-dt-resizerproxy",
2763
2764     /**
2765      * Class name assigned to CellEditor container elements.
2766      *
2767      * @property DataTable.CLASS_EDITOR
2768      * @type String
2769      * @static
2770      * @final
2771      * @default "yui-dt-editor"
2772      */
2773     CLASS_EDITOR : "yui-dt-editor",
2774
2775     /**
2776      * Class name assigned to paginator container elements.
2777      *
2778      * @property DataTable.CLASS_PAGINATOR
2779      * @type String
2780      * @static
2781      * @final
2782      * @default "yui-dt-paginator"
2783      */
2784     CLASS_PAGINATOR : "yui-dt-paginator",
2785
2786     /**
2787      * Class name assigned to page number indicators.
2788      *
2789      * @property DataTable.CLASS_PAGE
2790      * @type String
2791      * @static
2792      * @final
2793      * @default "yui-dt-page"
2794      */
2795     CLASS_PAGE : "yui-dt-page",
2796
2797     /**
2798      * Class name assigned to default indicators.
2799      *
2800      * @property DataTable.CLASS_DEFAULT
2801      * @type String
2802      * @static
2803      * @final
2804      * @default "yui-dt-default"
2805      */
2806     CLASS_DEFAULT : "yui-dt-default",
2807
2808     /**
2809      * Class name assigned to previous indicators.
2810      *
2811      * @property DataTable.CLASS_PREVIOUS
2812      * @type String
2813      * @static
2814      * @final
2815      * @default "yui-dt-previous"
2816      */
2817     CLASS_PREVIOUS : "yui-dt-previous",
2818
2819     /**
2820      * Class name assigned next indicators.
2821      *
2822      * @property DataTable.CLASS_NEXT
2823      * @type String
2824      * @static
2825      * @final
2826      * @default "yui-dt-next"
2827      */
2828     CLASS_NEXT : "yui-dt-next",
2829
2830     /**
2831      * Class name assigned to first elements.
2832      *
2833      * @property DataTable.CLASS_FIRST
2834      * @type String
2835      * @static
2836      * @final
2837      * @default "yui-dt-first"
2838      */
2839     CLASS_FIRST : "yui-dt-first",
2840
2841     /**
2842      * Class name assigned to last elements.
2843      *
2844      * @property DataTable.CLASS_LAST
2845      * @type String
2846      * @static
2847      * @final
2848      * @default "yui-dt-last"
2849      */
2850     CLASS_LAST : "yui-dt-last",
2851
2852     /**
2853      * Class name assigned to even elements.
2854      *
2855      * @property DataTable.CLASS_EVEN
2856      * @type String
2857      * @static
2858      * @final
2859      * @default "yui-dt-even"
2860      */
2861     CLASS_EVEN : "yui-dt-even",
2862
2863     /**
2864      * Class name assigned to odd elements.
2865      *
2866      * @property DataTable.CLASS_ODD
2867      * @type String
2868      * @static
2869      * @final
2870      * @default "yui-dt-odd"
2871      */
2872     CLASS_ODD : "yui-dt-odd",
2873
2874     /**
2875      * Class name assigned to selected elements.
2876      *
2877      * @property DataTable.CLASS_SELECTED
2878      * @type String
2879      * @static
2880      * @final
2881      * @default "yui-dt-selected"
2882      */
2883     CLASS_SELECTED : "yui-dt-selected",
2884
2885     /**
2886      * Class name assigned to highlighted elements.
2887      *
2888      * @property DataTable.CLASS_HIGHLIGHTED
2889      * @type String
2890      * @static
2891      * @final
2892      * @default "yui-dt-highlighted"
2893      */
2894     CLASS_HIGHLIGHTED : "yui-dt-highlighted",
2895
2896     /**
2897      * Class name assigned to hidden elements.
2898      *
2899      * @property DataTable.CLASS_HIDDEN
2900      * @type String
2901      * @static
2902      * @final
2903      * @default "yui-dt-hidden"
2904      */
2905     CLASS_HIDDEN : "yui-dt-hidden",
2906
2907     /**
2908      * Class name assigned to disabled elements.
2909      *
2910      * @property DataTable.CLASS_DISABLED
2911      * @type String
2912      * @static
2913      * @final
2914      * @default "yui-dt-disabled"
2915      */
2916     CLASS_DISABLED : "yui-dt-disabled",
2917
2918     /**
2919      * Class name assigned to empty indicators.
2920      *
2921      * @property DataTable.CLASS_EMPTY
2922      * @type String
2923      * @static
2924      * @final
2925      * @default "yui-dt-empty"
2926      */
2927     CLASS_EMPTY : "yui-dt-empty",
2928
2929     /**
2930      * Class name assigned to loading indicatorx.
2931      *
2932      * @property DataTable.CLASS_LOADING
2933      * @type String
2934      * @static
2935      * @final
2936      * @default "yui-dt-loading"
2937      */
2938     CLASS_LOADING : "yui-dt-loading",
2939
2940     /**
2941      * Class name assigned to error indicators.
2942      *
2943      * @property DataTable.CLASS_ERROR
2944      * @type String
2945      * @static
2946      * @final
2947      * @default "yui-dt-error"
2948      */
2949     CLASS_ERROR : "yui-dt-error",
2950
2951     /**
2952      * Class name assigned to editable elements.
2953      *
2954      * @property DataTable.CLASS_EDITABLE
2955      * @type String
2956      * @static
2957      * @final
2958      * @default "yui-dt-editable"
2959      */
2960     CLASS_EDITABLE : "yui-dt-editable",
2961
2962     /**
2963      * Class name assigned to draggable elements.
2964      *
2965      * @property DataTable.CLASS_DRAGGABLE
2966      * @type String
2967      * @static
2968      * @final
2969      * @default "yui-dt-draggable"
2970      */
2971     CLASS_DRAGGABLE : "yui-dt-draggable",
2972
2973     /**
2974      * Class name assigned to resizeable elements.
2975      *
2976      * @property DataTable.CLASS_RESIZEABLE
2977      * @type String
2978      * @static
2979      * @final
2980      * @default "yui-dt-resizeable"
2981      */
2982     CLASS_RESIZEABLE : "yui-dt-resizeable",
2983
2984     /**
2985      * Class name assigned to scrollable elements.
2986      *
2987      * @property DataTable.CLASS_SCROLLABLE
2988      * @type String
2989      * @static
2990      * @final
2991      * @default "yui-dt-scrollable"
2992      */
2993     CLASS_SCROLLABLE : "yui-dt-scrollable",
2994
2995     /**
2996      * Class name assigned to sortable elements.
2997      *
2998      * @property DataTable.CLASS_SORTABLE
2999      * @type String
3000      * @static
3001      * @final
3002      * @default "yui-dt-sortable"
3003      */
3004     CLASS_SORTABLE : "yui-dt-sortable",
3005
3006     /**
3007      * Class name assigned to ascending elements.
3008      *
3009      * @property DataTable.CLASS_ASC
3010      * @type String
3011      * @static
3012      * @final
3013      * @default "yui-dt-asc"
3014      */
3015     CLASS_ASC : "yui-dt-asc",
3016
3017     /**
3018      * Class name assigned to descending elements.
3019      *
3020      * @property DataTable.CLASS_DESC
3021      * @type String
3022      * @static
3023      * @final
3024      * @default "yui-dt-desc"
3025      */
3026     CLASS_DESC : "yui-dt-desc",
3027
3028     /**
3029      * Class name assigned to BUTTON elements and/or container elements.
3030      *
3031      * @property DataTable.CLASS_BUTTON
3032      * @type String
3033      * @static
3034      * @final
3035      * @default "yui-dt-button"
3036      */
3037     CLASS_BUTTON : "yui-dt-button",
3038
3039     /**
3040      * Class name assigned to INPUT TYPE=CHECKBOX elements and/or container elements.
3041      *
3042      * @property DataTable.CLASS_CHECKBOX
3043      * @type String
3044      * @static
3045      * @final
3046      * @default "yui-dt-checkbox"
3047      */
3048     CLASS_CHECKBOX : "yui-dt-checkbox",
3049
3050     /**
3051      * Class name assigned to SELECT elements and/or container elements.
3052      *
3053      * @property DataTable.CLASS_DROPDOWN
3054      * @type String
3055      * @static
3056      * @final
3057      * @default "yui-dt-dropdown"
3058      */
3059     CLASS_DROPDOWN : "yui-dt-dropdown",
3060
3061     /**
3062      * Class name assigned to INPUT TYPE=RADIO elements and/or container elements.
3063      *
3064      * @property DataTable.CLASS_RADIO
3065      * @type String
3066      * @static
3067      * @final
3068      * @default "yui-dt-radio"
3069      */
3070     CLASS_RADIO : "yui-dt-radio",
3071
3072     /////////////////////////////////////////////////////////////////////////
3073     //
3074     // Private static properties
3075     //
3076     /////////////////////////////////////////////////////////////////////////
3077
3078     /**
3079      * Internal class variable for indexing multiple DataTable instances.
3080      *
3081      * @property DataTable._nCount
3082      * @type Number
3083      * @private
3084      * @static
3085      */
3086     _nCount : 0,
3087
3088     /**
3089      * Internal class variable tracking current number of DataTable instances,
3090      * so that certain class values can be reset when all instances are destroyed.          
3091      *
3092      * @property DataTable._nCurrentCount
3093      * @type Number
3094      * @private
3095      * @static
3096      */
3097     _nCurrentCount : 0,
3098
3099     /**
3100      * Reference to the STYLE node that is dynamically created and updated
3101      * in order to manage Column widths.
3102      *
3103      * @property DataTable._elDynStyleNode
3104      * @type HTMLElement
3105      * @private
3106      * @static     
3107      */
3108     _elDynStyleNode : null,
3109
3110     /**
3111      * Set to true if _elDynStyleNode cannot be populated due to browser incompatibility.
3112      *
3113      * @property DataTable._bDynStylesFallback
3114      * @type boolean
3115      * @private
3116      * @static     
3117      */
3118     _bDynStylesFallback : (ua.ie) ? true : false,
3119
3120     /**
3121      * Object literal hash of Columns and their dynamically create style rules.
3122      *
3123      * @property DataTable._oDynStyles
3124      * @type Object
3125      * @private
3126      * @static     
3127      */
3128     _oDynStyles : {},
3129
3130     /**
3131      * Element reference to shared Column drag target.
3132      *
3133      * @property DataTable._elColumnDragTarget
3134      * @type HTMLElement
3135      * @private
3136      * @static 
3137      */
3138     _elColumnDragTarget : null,
3139
3140     /**
3141      * Element reference to shared Column resizer proxy.
3142      *
3143      * @property DataTable._elColumnResizerProxy
3144      * @type HTMLElement
3145      * @private
3146      * @static 
3147      */
3148     _elColumnResizerProxy : null,
3149
3150     /////////////////////////////////////////////////////////////////////////
3151     //
3152     // Private static methods
3153     //
3154     /////////////////////////////////////////////////////////////////////////
3155
3156     /**
3157      * Clones object literal or array of object literals.
3158      *
3159      * @method DataTable._cloneObject
3160      * @param o {Object} Object.
3161      * @private
3162      * @static     
3163      */
3164     _cloneObject : function(o) {
3165         if(!lang.isValue(o)) {
3166             return o;
3167         }
3168         
3169         var copy = {};
3170         
3171         if(o instanceof YAHOO.widget.BaseCellEditor) {
3172             copy = o;
3173         }
3174         else if(lang.isFunction(o)) {
3175             copy = o;
3176         }
3177         else if(lang.isArray(o)) {
3178             var array = [];
3179             for(var i=0,len=o.length;i<len;i++) {
3180                 array[i] = DT._cloneObject(o[i]);
3181             }
3182             copy = array;
3183         }
3184         else if(lang.isObject(o)) { 
3185             for (var x in o){
3186                 if(lang.hasOwnProperty(o, x)) {
3187                     if(lang.isValue(o[x]) && lang.isObject(o[x]) || lang.isArray(o[x])) {
3188                         copy[x] = DT._cloneObject(o[x]);
3189                     }
3190                     else {
3191                         copy[x] = o[x];
3192                     }
3193                 }
3194             }
3195         }
3196         else {
3197             copy = o;
3198         }
3199     
3200         return copy;
3201     },
3202
3203     /**
3204      * Destroys shared Column drag target.
3205      *
3206      * @method DataTable._destroyColumnDragTargetEl
3207      * @private
3208      * @static 
3209      */
3210     _destroyColumnDragTargetEl : function() {
3211         if(DT._elColumnDragTarget) {
3212             var el = DT._elColumnDragTarget;
3213             YAHOO.util.Event.purgeElement(el);
3214             el.parentNode.removeChild(el);
3215             DT._elColumnDragTarget = null;
3216             
3217         }
3218     },
3219
3220     /**
3221      * Creates HTML markup for shared Column drag target.
3222      *
3223      * @method DataTable._initColumnDragTargetEl
3224      * @return {HTMLElement} Reference to Column drag target. 
3225      * @private
3226      * @static 
3227      */
3228     _initColumnDragTargetEl : function() {
3229         if(!DT._elColumnDragTarget) {
3230             // Attach Column drag target element as first child of body
3231             var elColumnDragTarget = document.createElement('div');
3232             elColumnDragTarget.className = DT.CLASS_COLTARGET;
3233             elColumnDragTarget.style.display = "none";
3234             document.body.insertBefore(elColumnDragTarget, document.body.firstChild);
3235
3236             // Internal tracker of Column drag target
3237             DT._elColumnDragTarget = elColumnDragTarget;
3238             
3239         }
3240         return DT._elColumnDragTarget;
3241     },
3242
3243     /**
3244      * Destroys shared Column resizer proxy.
3245      *
3246      * @method DataTable._destroyColumnResizerProxyEl
3247      * @return {HTMLElement} Reference to Column resizer proxy.
3248      * @private 
3249      * @static 
3250      */
3251     _destroyColumnResizerProxyEl : function() {
3252         if(DT._elColumnResizerProxy) {
3253             var el = DT._elColumnResizerProxy;
3254             YAHOO.util.Event.purgeElement(el);
3255             el.parentNode.removeChild(el);
3256             DT._elColumnResizerProxy = null;
3257         }
3258     },
3259
3260     /**
3261      * Creates HTML markup for shared Column resizer proxy.
3262      *
3263      * @method DataTable._initColumnResizerProxyEl
3264      * @return {HTMLElement} Reference to Column resizer proxy.
3265      * @private 
3266      * @static 
3267      */
3268     _initColumnResizerProxyEl : function() {
3269         if(!DT._elColumnResizerProxy) {
3270             // Attach Column resizer element as first child of body
3271             var elColumnResizerProxy = document.createElement("div");
3272             elColumnResizerProxy.id = "yui-dt-colresizerproxy"; // Needed for ColumnResizer
3273             elColumnResizerProxy.className = DT.CLASS_RESIZERPROXY;
3274             document.body.insertBefore(elColumnResizerProxy, document.body.firstChild);
3275
3276             // Internal tracker of Column resizer proxy
3277             DT._elColumnResizerProxy = elColumnResizerProxy;
3278         }
3279         return DT._elColumnResizerProxy;
3280     },
3281
3282     /**
3283      * Formats a BUTTON element.
3284      *
3285      * @method DataTable.formatButton
3286      * @param el {HTMLElement} The element to format with markup.
3287      * @param oRecord {YAHOO.widget.Record} Record instance.
3288      * @param oColumn {YAHOO.widget.Column} Column instance.
3289      * @param oData {Object | Boolean} Data value for the cell. By default, the value
3290      * is what gets written to the BUTTON.
3291      * @static
3292      */
3293     formatButton : function(el, oRecord, oColumn, oData) {
3294         var sValue = lang.isValue(oData) ? oData : "Click";
3295         //TODO: support YAHOO.widget.Button
3296         //if(YAHOO.widget.Button) {
3297
3298         //}
3299         //else {
3300             el.innerHTML = "<button type=\"button\" class=\""+
3301                     DT.CLASS_BUTTON + "\">" + sValue + "</button>";
3302         //}
3303     },
3304
3305     /**
3306      * Formats a CHECKBOX element.
3307      *
3308      * @method DataTable.formatCheckbox
3309      * @param el {HTMLElement} The element to format with markup.
3310      * @param oRecord {YAHOO.widget.Record} Record instance.
3311      * @param oColumn {YAHOO.widget.Column} Column instance.
3312      * @param oData {Object | Boolean} Data value for the cell. Can be a simple
3313      * Boolean to indicate whether checkbox is checked or not. Can be object literal
3314      * {checked:bBoolean, label:sLabel}. Other forms of oData require a custom
3315      * formatter.
3316      * @static
3317      */
3318     formatCheckbox : function(el, oRecord, oColumn, oData) {
3319         var bChecked = oData;
3320         bChecked = (bChecked) ? " checked=\"checked\"" : "";
3321         el.innerHTML = "<input type=\"checkbox\"" + bChecked +
3322                 " class=\"" + DT.CLASS_CHECKBOX + "\" />";
3323     },
3324
3325     /**
3326      * Formats currency. Default unit is USD.
3327      *
3328      * @method DataTable.formatCurrency
3329      * @param el {HTMLElement} The element to format with markup.
3330      * @param oRecord {YAHOO.widget.Record} Record instance.
3331      * @param oColumn {YAHOO.widget.Column} Column instance.
3332      * @param oData {Number} Data value for the cell.
3333      * @static
3334      */
3335     formatCurrency : function(el, oRecord, oColumn, oData) {
3336         el.innerHTML = util.Number.format(oData, oColumn.currencyOptions || this.get("currencyOptions"));
3337     },
3338
3339     /**
3340      * Formats JavaScript Dates.
3341      *
3342      * @method DataTable.formatDate
3343      * @param el {HTMLElement} The element to format with markup.
3344      * @param oRecord {YAHOO.widget.Record} Record instance.
3345      * @param oColumn {YAHOO.widget.Column} Column instance.
3346      * @param oData {Object} Data value for the cell, or null.
3347      * @static
3348      */
3349     formatDate : function(el, oRecord, oColumn, oData) {
3350         var oConfig = oColumn.dateOptions || this.get("dateOptions");
3351         el.innerHTML = util.Date.format(oData, oConfig, oConfig.locale);
3352     },
3353
3354     /**
3355      * Formats SELECT elements.
3356      *
3357      * @method DataTable.formatDropdown
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     formatDropdown : function(el, oRecord, oColumn, oData) {
3365         var selectedValue = (lang.isValue(oData)) ? oData : oRecord.getData(oColumn.field),
3366             options = (lang.isArray(oColumn.dropdownOptions)) ?
3367                 oColumn.dropdownOptions : null,
3368
3369             selectEl,
3370             collection = el.getElementsByTagName("select");
3371
3372         // Create the form element only once, so we can attach the onChange listener
3373         if(collection.length === 0) {
3374             // Create SELECT element
3375             selectEl = document.createElement("select");
3376             selectEl.className = DT.CLASS_DROPDOWN;
3377             selectEl = el.appendChild(selectEl);
3378
3379             // Add event listener
3380             Ev.addListener(selectEl,"change",this._onDropdownChange,this);
3381         }
3382
3383         selectEl = collection[0];
3384
3385         // Update the form element
3386         if(selectEl) {
3387             // Clear out previous options
3388             selectEl.innerHTML = "";
3389
3390             // We have options to populate
3391             if(options) {
3392                 // Create OPTION elements
3393                 for(var i=0; i<options.length; i++) {
3394                     var option = options[i];
3395                     var optionEl = document.createElement("option");
3396                     optionEl.value = (lang.isValue(option.value)) ?
3397                             option.value : option;
3398                     // Bug 2334323: Support legacy text, support label for consistency with DropdownCellEditor
3399                     optionEl.innerHTML = (lang.isValue(option.text)) ?
3400                             option.text : (lang.isValue(option.label)) ? option.label : option;
3401                     optionEl = selectEl.appendChild(optionEl);
3402                     if (optionEl.value == selectedValue) {
3403                         optionEl.selected = true;
3404                     }
3405                 }
3406             }
3407             // Selected value is our only option
3408             else {
3409                 selectEl.innerHTML = "<option selected value=\"" + selectedValue + "\">" + selectedValue + "</option>";
3410             }
3411         }
3412         else {
3413             el.innerHTML = lang.isValue(oData) ? oData : "";
3414         }
3415     },
3416
3417     /**
3418      * Formats emails.
3419      *
3420      * @method DataTable.formatEmail
3421      * @param el {HTMLElement} The element to format with markup.
3422      * @param oRecord {YAHOO.widget.Record} Record instance.
3423      * @param oColumn {YAHOO.widget.Column} Column instance.
3424      * @param oData {Object} Data value for the cell, or null.
3425      * @static
3426      */
3427     formatEmail : function(el, oRecord, oColumn, oData) {
3428         if(lang.isString(oData)) {
3429             el.innerHTML = "<a href=\"mailto:" + oData + "\">" + oData + "</a>";
3430         }
3431         else {
3432             el.innerHTML = lang.isValue(oData) ? oData : "";
3433         }
3434     },
3435
3436     /**
3437      * Formats links.
3438      *
3439      * @method DataTable.formatLink
3440      * @param el {HTMLElement} The element to format with markup.
3441      * @param oRecord {YAHOO.widget.Record} Record instance.
3442      * @param oColumn {YAHOO.widget.Column} Column instance.
3443      * @param oData {Object} Data value for the cell, or null.
3444      * @static
3445      */
3446     formatLink : function(el, oRecord, oColumn, oData) {
3447         if(lang.isString(oData)) {
3448             el.innerHTML = "<a href=\"" + oData + "\">" + oData + "</a>";
3449         }
3450         else {
3451             el.innerHTML = lang.isValue(oData) ? oData : "";
3452         }
3453     },
3454
3455     /**
3456      * Formats numbers.
3457      *
3458      * @method DataTable.formatNumber
3459      * @param el {HTMLElement} The element to format with markup.
3460      * @param oRecord {YAHOO.widget.Record} Record instance.
3461      * @param oColumn {YAHOO.widget.Column} Column instance.
3462      * @param oData {Object} Data value for the cell, or null.
3463      * @static
3464      */
3465     formatNumber : function(el, oRecord, oColumn, oData) {
3466         el.innerHTML = util.Number.format(oData, oColumn.numberOptions || this.get("numberOptions"));
3467     },
3468
3469     /**
3470      * Formats INPUT TYPE=RADIO elements.
3471      *
3472      * @method DataTable.formatRadio
3473      * @param el {HTMLElement} The element to format with markup.
3474      * @param oRecord {YAHOO.widget.Record} Record instance.
3475      * @param oColumn {YAHOO.widget.Column} Column instance.
3476      * @param oData {Object} (Optional) Data value for the cell.
3477      * @static
3478      */
3479     formatRadio : function(el, oRecord, oColumn, oData) {
3480         var bChecked = oData;
3481         bChecked = (bChecked) ? " checked=\"checked\"" : "";
3482         el.innerHTML = "<input type=\"radio\"" + bChecked +
3483                 " name=\""+this.getId()+"-col-" + oColumn.getSanitizedKey() + "\"" +
3484                 " class=\"" + DT.CLASS_RADIO+ "\" />";
3485     },
3486
3487     /**
3488      * Formats text strings.
3489      *
3490      * @method DataTable.formatText
3491      * @param el {HTMLElement} The element to format with markup.
3492      * @param oRecord {YAHOO.widget.Record} Record instance.
3493      * @param oColumn {YAHOO.widget.Column} Column instance.
3494      * @param oData {Object} (Optional) Data value for the cell.
3495      * @static
3496      */
3497     formatText : function(el, oRecord, oColumn, oData) {
3498         var value = (lang.isValue(oData)) ? oData : "";
3499         //TODO: move to util function
3500         el.innerHTML = value.toString().replace(/&/g, "&#38;").replace(/</g, "&#60;").replace(/>/g, "&#62;");
3501     },
3502
3503     /**
3504      * Formats TEXTAREA elements.
3505      *
3506      * @method DataTable.formatTextarea
3507      * @param el {HTMLElement} The element to format with markup.
3508      * @param oRecord {YAHOO.widget.Record} Record instance.
3509      * @param oColumn {YAHOO.widget.Column} Column instance.
3510      * @param oData {Object} (Optional) Data value for the cell.
3511      * @static
3512      */
3513     formatTextarea : function(el, oRecord, oColumn, oData) {
3514         var value = (lang.isValue(oData)) ? oData : "",
3515             markup = "<textarea>" + value + "</textarea>";
3516         el.innerHTML = markup;
3517     },
3518
3519     /**
3520      * Formats INPUT TYPE=TEXT elements.
3521      *
3522      * @method DataTable.formatTextbox
3523      * @param el {HTMLElement} The element to format with markup.
3524      * @param oRecord {YAHOO.widget.Record} Record instance.
3525      * @param oColumn {YAHOO.widget.Column} Column instance.
3526      * @param oData {Object} (Optional) Data value for the cell.
3527      * @static
3528      */
3529     formatTextbox : function(el, oRecord, oColumn, oData) {
3530         var value = (lang.isValue(oData)) ? oData : "",
3531             markup = "<input type=\"text\" value=\"" + value + "\" />";
3532         el.innerHTML = markup;
3533     },
3534
3535     /**
3536      * Default cell formatter
3537      *
3538      * @method DataTable.formatDefault
3539      * @param el {HTMLElement} The element to format with markup.
3540      * @param oRecord {YAHOO.widget.Record} Record instance.
3541      * @param oColumn {YAHOO.widget.Column} Column instance.
3542      * @param oData {Object} (Optional) Data value for the cell.
3543      * @static
3544      */
3545     formatDefault : function(el, oRecord, oColumn, oData) {
3546         el.innerHTML = oData === undefined ||
3547                        oData === null ||
3548                        (typeof oData === 'number' && isNaN(oData)) ?
3549                        "&#160;" : oData.toString();
3550     },
3551
3552     /**
3553      * Validates data value to type Number, doing type conversion as
3554      * necessary. A valid Number value is return, else null is returned
3555      * if input value does not validate.
3556      *
3557      *
3558      * @method DataTable.validateNumber
3559      * @param oData {Object} Data to validate.
3560      * @static
3561     */
3562     validateNumber : function(oData) {
3563         //Convert to number
3564         var number = oData * 1;
3565
3566         // Validate
3567         if(lang.isNumber(number)) {
3568             return number;
3569         }
3570         else {
3571             YAHOO.log("Could not validate data " + lang.dump(oData) + " to type Number", "warn", this.toString());
3572             return undefined;
3573         }
3574     }
3575 });
3576
3577 // Done in separate step so referenced functions are defined.
3578 /**
3579  * Cell formatting functions.
3580  * @property DataTable.Formatter
3581  * @type Object
3582  * @static
3583  */
3584 DT.Formatter = {
3585     button   : DT.formatButton,
3586     checkbox : DT.formatCheckbox,
3587     currency : DT.formatCurrency,
3588     "date"   : DT.formatDate,
3589     dropdown : DT.formatDropdown,
3590     email    : DT.formatEmail,
3591     link     : DT.formatLink,
3592     "number" : DT.formatNumber,
3593     radio    : DT.formatRadio,
3594     text     : DT.formatText,
3595     textarea : DT.formatTextarea,
3596     textbox  : DT.formatTextbox,
3597
3598     defaultFormatter : DT.formatDefault
3599 };
3600
3601 lang.extend(DT, util.Element, {
3602
3603 /////////////////////////////////////////////////////////////////////////////
3604 //
3605 // Superclass methods
3606 //
3607 /////////////////////////////////////////////////////////////////////////////
3608
3609 /**
3610  * Implementation of Element's abstract method. Sets up config values.
3611  *
3612  * @method initAttributes
3613  * @param oConfigs {Object} (Optional) Object literal definition of configuration values.
3614  * @private
3615  */
3616
3617 initAttributes : function(oConfigs) {
3618     oConfigs = oConfigs || {};
3619     DT.superclass.initAttributes.call(this, oConfigs);
3620
3621     /**
3622     * @attribute summary
3623     * @description Value for the SUMMARY attribute.
3624     * @type String
3625     * @default ""    
3626     */
3627     this.setAttributeConfig("summary", {
3628         value: "",
3629         validator: lang.isString,
3630         method: function(sSummary) {
3631             if(this._elTable) {
3632                 this._elTable.summary = sSummary;
3633             }
3634         }
3635     });
3636
3637     /**
3638     * @attribute selectionMode
3639     * @description Specifies row or cell selection mode. Accepts the following strings:
3640     *    <dl>
3641     *      <dt>"standard"</dt>
3642     *      <dd>Standard row selection with support for modifier keys to enable
3643     *      multiple selections.</dd>
3644     *
3645     *      <dt>"single"</dt>
3646     *      <dd>Row selection with modifier keys disabled to not allow
3647     *      multiple selections.</dd>
3648     *
3649     *      <dt>"singlecell"</dt>
3650     *      <dd>Cell selection with modifier keys disabled to not allow
3651     *      multiple selections.</dd>
3652     *
3653     *      <dt>"cellblock"</dt>
3654     *      <dd>Cell selection with support for modifier keys to enable multiple
3655     *      selections in a block-fashion, like a spreadsheet.</dd>
3656     *
3657     *      <dt>"cellrange"</dt>
3658     *      <dd>Cell selection with support for modifier keys to enable multiple
3659     *      selections in a range-fashion, like a calendar.</dd>
3660     *    </dl>
3661     *
3662     * @default "standard"
3663     * @type String
3664     */
3665     this.setAttributeConfig("selectionMode", {
3666         value: "standard",
3667         validator: lang.isString
3668     });
3669
3670     /**
3671     * @attribute sortedBy
3672     * @description Object literal provides metadata for initial sort values if
3673     * data will arrive pre-sorted:
3674     * <dl>
3675     *     <dt>sortedBy.key</dt>
3676     *     <dd>{String} Key of sorted Column</dd>
3677     *     <dt>sortedBy.dir</dt>
3678     *     <dd>{String} Initial sort direction, either YAHOO.widget.DataTable.CLASS_ASC or YAHOO.widget.DataTable.CLASS_DESC</dd>
3679     * </dl>
3680     * @type Object | null
3681     */
3682     this.setAttributeConfig("sortedBy", {
3683         value: null,
3684         // TODO: accepted array for nested sorts
3685         validator: function(oNewSortedBy) {
3686             if(oNewSortedBy) {
3687                 return (lang.isObject(oNewSortedBy) && oNewSortedBy.key);
3688             }
3689             else {
3690                 return (oNewSortedBy === null);
3691             }
3692         },
3693         method: function(oNewSortedBy) {
3694             // Stash the previous value
3695             var oOldSortedBy = this.get("sortedBy");
3696             
3697             // Workaround for bug 1827195
3698             this._configs.sortedBy.value = oNewSortedBy;
3699
3700             // Remove ASC/DESC from TH
3701             var oOldColumn,
3702                 nOldColumnKeyIndex,
3703                 oNewColumn,
3704                 nNewColumnKeyIndex;
3705                 
3706             if(this._elThead) {
3707                 if(oOldSortedBy && oOldSortedBy.key && oOldSortedBy.dir) {
3708                     oOldColumn = this._oColumnSet.getColumn(oOldSortedBy.key);
3709                     nOldColumnKeyIndex = oOldColumn.getKeyIndex();
3710                     
3711                     // Remove previous UI from THEAD
3712                     var elOldTh = oOldColumn.getThEl();
3713                     Dom.removeClass(elOldTh, oOldSortedBy.dir);
3714                     this.formatTheadCell(oOldColumn.getThLinerEl().firstChild, oOldColumn, oNewSortedBy);
3715                 }
3716                 if(oNewSortedBy) {
3717                     oNewColumn = (oNewSortedBy.column) ? oNewSortedBy.column : this._oColumnSet.getColumn(oNewSortedBy.key);
3718                     nNewColumnKeyIndex = oNewColumn.getKeyIndex();
3719     
3720                     // Update THEAD with new UI
3721                     var elNewTh = oNewColumn.getThEl();
3722                     // Backward compatibility
3723                     if(oNewSortedBy.dir && ((oNewSortedBy.dir == "asc") ||  (oNewSortedBy.dir == "desc"))) {
3724                         var newClass = (oNewSortedBy.dir == "desc") ?
3725                                 DT.CLASS_DESC :
3726                                 DT.CLASS_ASC;
3727                         Dom.addClass(elNewTh, newClass);
3728                     }
3729                     else {
3730                          var sortClass = oNewSortedBy.dir || DT.CLASS_ASC;
3731                          Dom.addClass(elNewTh, sortClass);
3732                     }
3733                     this.formatTheadCell(oNewColumn.getThLinerEl().firstChild, oNewColumn, oNewSortedBy);
3734                 }
3735             }
3736           
3737             if(this._elTbody) {
3738                 // Update TBODY UI
3739                 this._elTbody.style.display = "none";
3740                 var allRows = this._elTbody.rows,
3741                     allCells;
3742                 for(var i=allRows.length-1; i>-1; i--) {
3743                     allCells = allRows[i].childNodes;
3744                     if(allCells[nOldColumnKeyIndex]) {
3745                         Dom.removeClass(allCells[nOldColumnKeyIndex], oOldSortedBy.dir);
3746                     }
3747                     if(allCells[nNewColumnKeyIndex]) {
3748                         Dom.addClass(allCells[nNewColumnKeyIndex], oNewSortedBy.dir);
3749                     }
3750                 }
3751                 this._elTbody.style.display = "";
3752             }
3753                 
3754             this._clearTrTemplateEl();
3755         }
3756     });
3757     
3758     /**
3759     * @attribute paginator
3760     * @description An instance of YAHOO.widget.Paginator.
3761     * @default null
3762     * @type {Object|YAHOO.widget.Paginator}
3763     */
3764     this.setAttributeConfig("paginator", {
3765         value : null,
3766         validator : function (val) {
3767             return val === null || val instanceof widget.Paginator;
3768         },
3769         method : function () { this._updatePaginator.apply(this,arguments); }
3770     });
3771
3772     /**
3773     * @attribute caption
3774     * @description Value for the CAPTION element. NB: Not supported in
3775     * ScrollingDataTable.    
3776     * @type String
3777     */
3778     this.setAttributeConfig("caption", {
3779         value: null,
3780         validator: lang.isString,
3781         method: function(sCaption) {
3782             this._initCaptionEl(sCaption);
3783         }
3784     });
3785
3786     /**
3787     * @attribute draggableColumns
3788     * @description True if Columns are draggable to reorder, false otherwise.
3789     * The Drag & Drop Utility is required to enable this feature. Only top-level
3790     * and non-nested Columns are draggable. Write once.
3791     * @default false
3792     * @type Boolean
3793     */
3794     this.setAttributeConfig("draggableColumns", {
3795         value: false,
3796         validator: lang.isBoolean,
3797         method: function(oParam) {
3798             if(this._elThead) {
3799                 if(oParam) {
3800                     this._initDraggableColumns();
3801                 }
3802                 else {
3803                     this._destroyDraggableColumns();
3804                 }
3805             }
3806         }
3807     });
3808
3809     /**
3810     * @attribute renderLoopSize          
3811     * @description A value greater than 0 enables DOM rendering of rows to be
3812     * executed from a non-blocking timeout queue and sets how many rows to be
3813     * rendered per timeout. Recommended for very large data sets.     
3814     * @type Number       
3815     * @default 0         
3816     */   
3817      this.setAttributeConfig("renderLoopSize", {         
3818          value: 0,       
3819          validator: lang.isNumber        
3820      });         
3821
3822     /**
3823     * @attribute formatRow
3824     * @description A function that accepts a TR element and its associated Record
3825     * for custom formatting. The function must return TRUE in order to automatically
3826     * continue formatting of child TD elements, else TD elements will not be
3827     * automatically formatted.
3828     * @type function
3829     * @default null
3830     */
3831     this.setAttributeConfig("formatRow", {
3832         value: null,
3833         validator: lang.isFunction
3834     });
3835
3836     /**
3837     * @attribute generateRequest
3838     * @description A function that converts an object literal of desired DataTable
3839     * states into a request value which is then passed to the DataSource's
3840     * sendRequest method in order to retrieve data for those states. This
3841     * function is passed an object literal of state data and a reference to the
3842     * DataTable instance:
3843     *     
3844     * <dl>
3845     *   <dt>pagination<dt>
3846     *   <dd>        
3847     *         <dt>offsetRecord</dt>
3848     *         <dd>{Number} Index of the first Record of the desired page</dd>
3849     *         <dt>rowsPerPage</dt>
3850     *         <dd>{Number} Number of rows per page</dd>
3851     *   </dd>
3852     *   <dt>sortedBy</dt>
3853     *   <dd>                
3854     *         <dt>key</dt>
3855     *         <dd>{String} Key of sorted Column</dd>
3856     *         <dt>dir</dt>
3857     *         <dd>{String} Sort direction, either YAHOO.widget.DataTable.CLASS_ASC or YAHOO.widget.DataTable.CLASS_DESC</dd>
3858     *   </dd>
3859     *   <dt>self</dt>
3860     *   <dd>The DataTable instance</dd>
3861     * </dl>
3862     * 
3863     * and by default returns a String of syntax:
3864     * "sort={sortColumn}&dir={sortDir}&startIndex={pageStartIndex}&results={rowsPerPage}"
3865     * @type function
3866     * @default HTMLFunction
3867     */
3868     this.setAttributeConfig("generateRequest", {
3869         value: function(oState, oSelf) {
3870             // Set defaults
3871             oState = oState || {pagination:null, sortedBy:null};
3872             var sort = encodeURIComponent((oState.sortedBy) ? oState.sortedBy.key : oSelf.getColumnSet().keys[0].getKey());
3873             var dir = (oState.sortedBy && oState.sortedBy.dir === YAHOO.widget.DataTable.CLASS_DESC) ? "desc" : "asc";
3874             var startIndex = (oState.pagination) ? oState.pagination.recordOffset : 0;
3875             var results = (oState.pagination) ? oState.pagination.rowsPerPage : null;
3876             
3877             // Build the request
3878             return  "sort=" + sort +
3879                     "&dir=" + dir +
3880                     "&startIndex=" + startIndex +
3881                     ((results !== null) ? "&results=" + results : "");
3882         },
3883         validator: lang.isFunction
3884     });
3885
3886     /**
3887     * @attribute initialRequest
3888     * @description Defines the initial request that gets sent to the DataSource
3889     * during initialization. Value is ignored if initialLoad is set to any value
3890     * other than true.    
3891     * @type MIXED
3892     * @default null
3893     */
3894     this.setAttributeConfig("initialRequest", {
3895         value: null
3896     });
3897
3898     /**
3899     * @attribute initialLoad
3900     * @description Determines whether or not to load data at instantiation. By
3901     * default, will trigger a sendRequest() to the DataSource and pass in the
3902     * request defined by initialRequest. If set to false, data will not load
3903     * at instantiation. Alternatively, implementers who wish to work with a 
3904     * custom payload may pass in an object literal with the following values:
3905     *     
3906     *    <dl>
3907     *      <dt>request (MIXED)</dt>
3908     *      <dd>Request value.</dd>
3909     *
3910     *      <dt>argument (MIXED)</dt>
3911     *      <dd>Custom data that will be passed through to the callback function.</dd>
3912     *    </dl>
3913     *
3914     *                    
3915     * @type Boolean | Object
3916     * @default true
3917     */
3918     this.setAttributeConfig("initialLoad", {
3919         value: true
3920     });
3921     
3922     /**
3923     * @attribute dynamicData
3924     * @description If true, sorting and pagination are relegated to the DataSource
3925     * for handling, using the request returned by the "generateRequest" function.
3926     * Each new DataSource response blows away all previous Records. False by default, so 
3927     * sorting and pagination will be handled directly on the client side, without
3928     * causing any new requests for data from the DataSource.
3929     * @type Boolean
3930     * @default false
3931     */
3932     this.setAttributeConfig("dynamicData", {
3933         value: false,
3934         validator: lang.isBoolean
3935     });
3936
3937     /**
3938      * @attribute MSG_EMPTY      
3939      * @description Message to display if DataTable has no data.     
3940      * @type String      
3941      * @default "No records found."      
3942      */          
3943      this.setAttributeConfig("MSG_EMPTY", {      
3944          value: "No records found.",     
3945          validator: lang.isString        
3946      });         
3947
3948     /**
3949      * @attribute MSG_LOADING    
3950      * @description Message to display while DataTable is loading data.
3951      * @type String      
3952      * @default "Loading..."     
3953      */          
3954      this.setAttributeConfig("MSG_LOADING", {    
3955          value: "Loading...",    
3956          validator: lang.isString        
3957      });         
3958
3959     /**
3960      * @attribute MSG_ERROR      
3961      * @description Message to display while DataTable has data error.
3962      * @type String      
3963      * @default "Data error."    
3964      */          
3965      this.setAttributeConfig("MSG_ERROR", {      
3966          value: "Data error.",   
3967          validator: lang.isString        
3968      });         
3969
3970     /**
3971      * @attribute MSG_SORTASC 
3972      * @description Message to display in tooltip to sort Column in ascending order.
3973      * @type String      
3974      * @default "Click to sort ascending"        
3975      */          
3976      this.setAttributeConfig("MSG_SORTASC", {    
3977          value: "Click to sort ascending",       
3978          validator: lang.isString,
3979          method: function(sParam) {
3980             if(this._elThead) {
3981                 for(var i=0, allKeys=this.getColumnSet().keys, len=allKeys.length; i<len; i++) {
3982                     if(allKeys[i].sortable && this.getColumnSortDir(allKeys[i]) === DT.CLASS_ASC) {
3983                         allKeys[i]._elThLabel.firstChild.title = sParam;
3984                     }
3985                 }
3986             }      
3987          }
3988      });
3989
3990     /**
3991      * @attribute MSG_SORTDESC 
3992      * @description Message to display in tooltip to sort Column in descending order.
3993      * @type String      
3994      * @default "Click to sort descending"       
3995      */          
3996      this.setAttributeConfig("MSG_SORTDESC", {   
3997          value: "Click to sort descending",      
3998          validator: lang.isString,
3999          method: function(sParam) {
4000             if(this._elThead) {
4001                 for(var i=0, allKeys=this.getColumnSet().keys, len=allKeys.length; i<len; i++) {
4002                     if(allKeys[i].sortable && this.getColumnSortDir(allKeys[i]) === DT.CLASS_DESC) {
4003                         allKeys[i]._elThLabel.firstChild.title = sParam;
4004                     }
4005                 }
4006             }               
4007          }
4008      });
4009      
4010     /**
4011      * @attribute currencySymbol
4012      * @deprecated
4013      */
4014     this.setAttributeConfig("currencySymbol", {
4015         value: "$",
4016         validator: lang.isString
4017     });
4018     
4019     /**
4020      * Default config passed to YAHOO.util.Number.format() by the 'currency' Column formatter.
4021      * @attribute currencyOptions
4022      * @type Object
4023      * @default {prefix: $, decimalPlaces:2, decimalSeparator:".", thousandsSeparator:","}
4024      */
4025     this.setAttributeConfig("currencyOptions", {
4026         value: {
4027             prefix: this.get("currencySymbol"), // TODO: deprecate currencySymbol
4028             decimalPlaces:2,
4029             decimalSeparator:".",
4030             thousandsSeparator:","
4031         }
4032     });
4033     
4034     /**
4035      * Default config passed to YAHOO.util.Date.format() by the 'date' Column formatter.
4036      * @attribute dateOptions
4037      * @type Object
4038      * @default {format:"%m/%d/%Y", locale:"en"}
4039      */
4040     this.setAttributeConfig("dateOptions", {
4041         value: {format:"%m/%d/%Y", locale:"en"}
4042     });
4043     
4044     /**
4045      * Default config passed to YAHOO.util.Number.format() by the 'number' Column formatter.
4046      * @attribute numberOptions
4047      * @type Object
4048      * @default {decimalPlaces:0, thousandsSeparator:","}
4049      */
4050     this.setAttributeConfig("numberOptions", {
4051         value: {
4052             decimalPlaces:0,
4053             thousandsSeparator:","
4054         }
4055     });
4056
4057 },
4058
4059 /////////////////////////////////////////////////////////////////////////////
4060 //
4061 // Private member variables
4062 //
4063 /////////////////////////////////////////////////////////////////////////////
4064
4065 /**
4066  * True if instance is initialized, so as to fire the initEvent after render.
4067  *
4068  * @property _bInit
4069  * @type Boolean
4070  * @default true
4071  * @private
4072  */
4073 _bInit : true,
4074
4075 /**
4076  * Index assigned to instance.
4077  *
4078  * @property _nIndex
4079  * @type Number
4080  * @private
4081  */
4082 _nIndex : null,
4083
4084 /**
4085  * Counter for IDs assigned to TR elements.
4086  *
4087  * @property _nTrCount
4088  * @type Number
4089  * @private
4090  */
4091 _nTrCount : 0,
4092
4093 /**
4094  * Counter for IDs assigned to TD elements.
4095  *
4096  * @property _nTdCount
4097  * @type Number
4098  * @private
4099  */
4100 _nTdCount : 0,
4101
4102 /**
4103  * Unique id assigned to instance "yui-dtN", useful prefix for generating unique
4104  * DOM ID strings and log messages.
4105  *
4106  * @property _sId
4107  * @type String
4108  * @private
4109  */
4110 _sId : null,
4111
4112 /**
4113  * Render chain.
4114  *
4115  * @property _oChainRender
4116  * @type YAHOO.util.Chain
4117  * @private
4118  */
4119 _oChainRender : null,
4120
4121 /**
4122  * DOM reference to the container element for the DataTable instance into which
4123  * all other elements get created.
4124  *
4125  * @property _elContainer
4126  * @type HTMLElement
4127  * @private
4128  */
4129 _elContainer : null,
4130
4131 /**
4132  * DOM reference to the mask element for the DataTable instance which disables it.
4133  *
4134  * @property _elMask
4135  * @type HTMLElement
4136  * @private
4137  */
4138 _elMask : null,
4139
4140 /**
4141  * DOM reference to the TABLE element for the DataTable instance.
4142  *
4143  * @property _elTable
4144  * @type HTMLElement
4145  * @private
4146  */
4147 _elTable : null,
4148
4149 /**
4150  * DOM reference to the CAPTION element for the DataTable instance.
4151  *
4152  * @property _elCaption
4153  * @type HTMLElement
4154  * @private
4155  */
4156 _elCaption : null,
4157
4158 /**
4159  * DOM reference to the COLGROUP element for the DataTable instance.
4160  *
4161  * @property _elColgroup
4162  * @type HTMLElement
4163  * @private
4164  */
4165 _elColgroup : null,
4166
4167 /**
4168  * DOM reference to the THEAD element for the DataTable instance.
4169  *
4170  * @property _elThead
4171  * @type HTMLElement
4172  * @private
4173  */
4174 _elThead : null,
4175
4176 /**
4177  * DOM reference to the primary TBODY element for the DataTable instance.
4178  *
4179  * @property _elTbody
4180  * @type HTMLElement
4181  * @private
4182  */
4183 _elTbody : null,
4184
4185 /**
4186  * DOM reference to the secondary TBODY element used to display DataTable messages.
4187  *
4188  * @property _elMsgTbody
4189  * @type HTMLElement
4190  * @private
4191  */
4192 _elMsgTbody : null,
4193
4194 /**
4195  * DOM reference to the secondary TBODY element's single TR element used to display DataTable messages.
4196  *
4197  * @property _elMsgTr
4198  * @type HTMLElement
4199  * @private
4200  */
4201 _elMsgTr : null,
4202
4203 /**
4204  * DOM reference to the secondary TBODY element's single TD element used to display DataTable messages.
4205  *
4206  * @property _elMsgTd
4207  * @type HTMLElement
4208  * @private
4209  */
4210 _elMsgTd : null,
4211
4212 /**
4213  * DataSource instance for the DataTable instance.
4214  *
4215  * @property _oDataSource
4216  * @type YAHOO.util.DataSource
4217  * @private
4218  */
4219 _oDataSource : null,
4220
4221 /**
4222  * ColumnSet instance for the DataTable instance.
4223  *
4224  * @property _oColumnSet
4225  * @type YAHOO.widget.ColumnSet
4226  * @private
4227  */
4228 _oColumnSet : null,
4229
4230 /**
4231  * RecordSet instance for the DataTable instance.
4232  *
4233  * @property _oRecordSet
4234  * @type YAHOO.widget.RecordSet
4235  * @private
4236  */
4237 _oRecordSet : null,
4238
4239 /**
4240  * The active CellEditor instance for the DataTable instance.
4241  *
4242  * @property _oCellEditor
4243  * @type YAHOO.widget.CellEditor
4244  * @private
4245  */
4246 _oCellEditor : null,
4247
4248 /**
4249  * ID string of first TR element of the current DataTable page.
4250  *
4251  * @property _sFirstTrId
4252  * @type String
4253  * @private
4254  */
4255 _sFirstTrId : null,
4256
4257 /**
4258  * ID string of the last TR element of the current DataTable page.
4259  *
4260  * @property _sLastTrId
4261  * @type String
4262  * @private
4263  */
4264 _sLastTrId : null,
4265
4266 /**
4267  * Template row to create all new rows from.
4268  * @property _elTrTemplate
4269  * @type {HTMLElement}
4270  * @private 
4271  */
4272 _elTrTemplate : null,
4273
4274 /**
4275  * Sparse array of custom functions to set column widths for browsers that don't
4276  * support dynamic CSS rules.  Functions are added at the index representing
4277  * the number of rows they update.
4278  *
4279  * @property _aDynFunctions
4280  * @type Array
4281  * @private
4282  */
4283 _aDynFunctions : [],
4284
4285
4286
4287
4288
4289
4290
4291
4292
4293
4294
4295
4296
4297
4298
4299
4300
4301
4302
4303
4304
4305
4306
4307
4308
4309
4310
4311
4312
4313 /////////////////////////////////////////////////////////////////////////////
4314 //
4315 // Private methods
4316 //
4317 /////////////////////////////////////////////////////////////////////////////
4318
4319 /**
4320  * Clears browser text selection. Useful to call on rowSelectEvent or
4321  * cellSelectEvent to prevent clicks or dblclicks from selecting text in the
4322  * browser.
4323  *
4324  * @method clearTextSelection
4325  */
4326 clearTextSelection : function() {
4327     var sel;
4328     if(window.getSelection) {
4329         sel = window.getSelection();
4330     }
4331     else if(document.getSelection) {
4332         sel = document.getSelection();
4333     }
4334     else if(document.selection) {
4335         sel = document.selection;
4336     }
4337     if(sel) {
4338         if(sel.empty) {
4339             sel.empty();
4340         }
4341         else if (sel.removeAllRanges) {
4342             sel.removeAllRanges();
4343         }
4344         else if(sel.collapse) {
4345             sel.collapse();
4346         }
4347     }
4348 },
4349
4350 /**
4351  * Sets focus on the given element.
4352  *
4353  * @method _focusEl
4354  * @param el {HTMLElement} Element.
4355  * @private
4356  */
4357 _focusEl : function(el) {
4358     el = el || this._elTbody;
4359     // http://developer.mozilla.org/en/docs/index.php?title=Key-navigable_custom_DHTML_widgets
4360     // The timeout is necessary in both IE and Firefox 1.5, to prevent scripts from doing
4361     // strange unexpected things as the user clicks on buttons and other controls.
4362     setTimeout(function() {
4363         try {
4364             el.focus();
4365         }
4366         catch(e) {
4367         }
4368     },0);
4369 },
4370
4371 /**
4372  * Forces Gecko repaint.
4373  *
4374  * @method _repaintGecko
4375  * @el {HTMLElement} (Optional) Element to repaint, otherwise entire document body.
4376  * @private
4377  */
4378 _repaintGecko : (ua.gecko) ? 
4379     function(el) {
4380         el = el || this._elContainer;
4381         var parent = el.parentNode;
4382         var nextSibling = el.nextSibling;
4383         parent.insertBefore(parent.removeChild(el), nextSibling);
4384     } : function() {},
4385
4386 /**
4387  * Forces Opera repaint.
4388  *
4389  * @method _repaintOpera
4390  * @private 
4391  */
4392 _repaintOpera : (ua.opera) ? 
4393     function() {
4394         if(ua.opera) {
4395             document.documentElement.className += " ";
4396             document.documentElement.className = YAHOO.lang.trim(document.documentElement.className);
4397         }
4398     } : function() {} ,
4399
4400 /**
4401  * Forces Webkit repaint.
4402  *
4403  * @method _repaintWebkit
4404  * @el {HTMLElement} (Optional) Element to repaint, otherwise entire document body.
4405  * @private
4406  */
4407 _repaintWebkit : (ua.webkit) ? 
4408     function(el) {
4409         el = el || this._elContainer;
4410         var parent = el.parentNode;
4411         var nextSibling = el.nextSibling;
4412         parent.insertBefore(parent.removeChild(el), nextSibling);
4413     } : function() {},
4414
4415
4416
4417
4418
4419
4420
4421
4422
4423
4424
4425
4426
4427
4428
4429
4430
4431
4432
4433
4434
4435
4436 // INIT FUNCTIONS
4437
4438 /**
4439  * Initializes object literal of config values.
4440  *
4441  * @method _initConfigs
4442  * @param oConfig {Object} Object literal of config values.
4443  * @private
4444  */
4445 _initConfigs : function(oConfigs) {
4446     if(!oConfigs || !lang.isObject(oConfigs)) {
4447         oConfigs = {};
4448     }
4449     this.configs = oConfigs;
4450 },
4451
4452 /**
4453  * Initializes ColumnSet.
4454  *
4455  * @method _initColumnSet
4456  * @param aColumnDefs {Object[]} Array of object literal Column definitions.
4457  * @private
4458  */
4459 _initColumnSet : function(aColumnDefs) {
4460     var oColumn, i, len;
4461     
4462     if(this._oColumnSet) {
4463         // First clear _oDynStyles for existing ColumnSet and
4464         // uregister CellEditor Custom Events
4465         for(i=0, len=this._oColumnSet.keys.length; i<len; i++) {
4466             oColumn = this._oColumnSet.keys[i];
4467             DT._oDynStyles["."+this.getId()+"-col-"+oColumn.getSanitizedKey()+" ."+DT.CLASS_LINER] = undefined;
4468             if(oColumn.editor && oColumn.editor.unsubscribeAll) { // Backward compatibility
4469                 oColumn.editor.unsubscribeAll();
4470             }
4471         }
4472         
4473         this._oColumnSet = null;
4474         this._clearTrTemplateEl();
4475     }
4476     
4477     if(lang.isArray(aColumnDefs)) {
4478         this._oColumnSet =  new YAHOO.widget.ColumnSet(aColumnDefs);
4479     }
4480     // Backward compatibility
4481     else if(aColumnDefs instanceof YAHOO.widget.ColumnSet) {
4482         this._oColumnSet =  aColumnDefs;
4483         YAHOO.log("DataTable's constructor now requires an array" +
4484         " of object literal Column definitions instead of a ColumnSet instance",
4485         "warn", this.toString());
4486     }
4487
4488     // Register CellEditor Custom Events
4489     var allKeys = this._oColumnSet.keys;
4490     for(i=0, len=allKeys.length; i<len; i++) {
4491         oColumn = allKeys[i];
4492         if(oColumn.editor && oColumn.editor.subscribe) { // Backward incompatibility
4493             oColumn.editor.subscribe("showEvent", this._onEditorShowEvent, this, true);
4494             oColumn.editor.subscribe("keydownEvent", this._onEditorKeydownEvent, this, true);
4495             oColumn.editor.subscribe("revertEvent", this._onEditorRevertEvent, this, true);
4496             oColumn.editor.subscribe("saveEvent", this._onEditorSaveEvent, this, true);
4497             oColumn.editor.subscribe("cancelEvent", this._onEditorCancelEvent, this, true);
4498             oColumn.editor.subscribe("blurEvent", this._onEditorBlurEvent, this, true);
4499             oColumn.editor.subscribe("blockEvent", this._onEditorBlockEvent, this, true);
4500             oColumn.editor.subscribe("unblockEvent", this._onEditorUnblockEvent, this, true);
4501         }
4502     }
4503 },
4504
4505 /**
4506  * Initializes DataSource.
4507  *
4508  * @method _initDataSource
4509  * @param oDataSource {YAHOO.util.DataSource} DataSource instance.
4510  * @private
4511  */
4512 _initDataSource : function(oDataSource) {
4513     this._oDataSource = null;
4514     if(oDataSource && (lang.isFunction(oDataSource.sendRequest))) {
4515         this._oDataSource = oDataSource;
4516     }
4517     // Backward compatibility
4518     else {
4519         var tmpTable = null;
4520         var tmpContainer = this._elContainer;
4521         var i=0;
4522         //TODO: this will break if re-initing DS at runtime for SDT
4523         // Peek in container child nodes to see if TABLE already exists
4524         if(tmpContainer.hasChildNodes()) {
4525             var tmpChildren = tmpContainer.childNodes;
4526             for(i=0; i<tmpChildren.length; i++) {
4527                 if(tmpChildren[i].nodeName && tmpChildren[i].nodeName.toLowerCase() == "table") {
4528                     tmpTable = tmpChildren[i];
4529                     break;
4530                 }
4531             }
4532             if(tmpTable) {
4533                 var tmpFieldsArray = [];
4534                 for(; i<this._oColumnSet.keys.length; i++) {
4535                     tmpFieldsArray.push({key:this._oColumnSet.keys[i].key});
4536                 }
4537
4538                 this._oDataSource = new DS(tmpTable);
4539                 this._oDataSource.responseType = DS.TYPE_HTMLTABLE;
4540                 this._oDataSource.responseSchema = {fields: tmpFieldsArray};
4541                 YAHOO.log("Null DataSource for progressive enhancement from" +
4542                 " markup has been deprecated", "warn", this.toString());
4543             }
4544         }
4545     }
4546 },
4547
4548 /**
4549  * Initializes RecordSet.
4550  *
4551  * @method _initRecordSet
4552  * @private
4553  */
4554 _initRecordSet : function() {
4555     if(this._oRecordSet) {
4556         this._oRecordSet.reset();
4557     }
4558     else {
4559         this._oRecordSet = new YAHOO.widget.RecordSet();
4560     }
4561 },
4562
4563 /**
4564  * Initializes DOM elements.
4565  *
4566  * @method _initDomElements
4567  * @param elContainer {HTMLElement | String} HTML DIV element by reference or ID. 
4568  * return {Boolean} False in case of error, otherwise true 
4569  * @private
4570  */
4571 _initDomElements : function(elContainer) {
4572     // Outer container
4573     this._initContainerEl(elContainer);
4574     // TABLE
4575     this._initTableEl(this._elContainer);
4576     // COLGROUP
4577     this._initColgroupEl(this._elTable);
4578     // THEAD
4579     this._initTheadEl(this._elTable);
4580     
4581     // Message TBODY
4582     this._initMsgTbodyEl(this._elTable);  
4583
4584     // Primary TBODY
4585     this._initTbodyEl(this._elTable);
4586
4587     if(!this._elContainer || !this._elTable || !this._elColgroup ||  !this._elThead || !this._elTbody || !this._elMsgTbody) {
4588         return false;
4589     }
4590     else {
4591         return true;
4592     }
4593 },
4594
4595 /**
4596  * Destroy's the DataTable outer container element, if available.
4597  *
4598  * @method _destroyContainerEl
4599  * @param elContainer {HTMLElement} Reference to the container element. 
4600  * @private
4601  */
4602 _destroyContainerEl : function(elContainer) {
4603     Dom.removeClass(elContainer, DT.CLASS_DATATABLE);
4604     Ev.purgeElement(elContainer, true);
4605     elContainer.innerHTML = "";
4606     
4607     this._elContainer = null;
4608     this._elColgroup = null;
4609     this._elThead = null;
4610     this._elTbody = null;
4611 },
4612
4613 /**
4614  * Initializes the DataTable outer container element, including a mask.
4615  *
4616  * @method _initContainerEl
4617  * @param elContainer {HTMLElement | String} HTML DIV element by reference or ID.
4618  * @private
4619  */
4620 _initContainerEl : function(elContainer) {
4621     // Validate container
4622     elContainer = Dom.get(elContainer);
4623     
4624     if(elContainer && elContainer.nodeName && (elContainer.nodeName.toLowerCase() == "div")) {
4625         // Destroy previous
4626         this._destroyContainerEl(elContainer);
4627
4628         Dom.addClass(elContainer, DT.CLASS_DATATABLE);
4629         Ev.addListener(elContainer, "focus", this._onTableFocus, this);
4630         Ev.addListener(elContainer, "dblclick", this._onTableDblclick, this);
4631         this._elContainer = elContainer;
4632         
4633         var elMask = document.createElement("div");
4634         elMask.className = DT.CLASS_MASK;
4635         elMask.style.display = "none";
4636         this._elMask = elContainer.appendChild(elMask);
4637     }
4638 },
4639
4640 /**
4641  * Destroy's the DataTable TABLE element, if available.
4642  *
4643  * @method _destroyTableEl
4644  * @private
4645  */
4646 _destroyTableEl : function() {
4647     var elTable = this._elTable;
4648     if(elTable) {
4649         Ev.purgeElement(elTable, true);
4650         elTable.parentNode.removeChild(elTable);
4651         this._elCaption = null;
4652         this._elColgroup = null;
4653         this._elThead = null;
4654         this._elTbody = null;
4655     }
4656 },
4657
4658 /**
4659  * Creates HTML markup CAPTION element.
4660  *
4661  * @method _initCaptionEl
4662  * @param sCaption {String} Text for caption.
4663  * @private
4664  */
4665 _initCaptionEl : function(sCaption) {
4666     if(this._elTable && sCaption) {
4667         // Create CAPTION element
4668         if(!this._elCaption) { 
4669             this._elCaption = this._elTable.createCaption();
4670         }
4671         // Set CAPTION value
4672         this._elCaption.innerHTML = sCaption;
4673     }
4674     else if(this._elCaption) {
4675         this._elCaption.parentNode.removeChild(this._elCaption);
4676     }
4677 },
4678
4679 /**
4680  * Creates HTML markup for TABLE, COLGROUP, THEAD and TBODY elements in outer
4681  * container element.
4682  *
4683  * @method _initTableEl
4684  * @param elContainer {HTMLElement} Container element into which to create TABLE.
4685  * @private
4686  */
4687 _initTableEl : function(elContainer) {
4688     if(elContainer) {
4689         // Destroy previous
4690         this._destroyTableEl();
4691     
4692         // Create TABLE
4693         this._elTable = elContainer.appendChild(document.createElement("table"));  
4694          
4695         // Set SUMMARY attribute
4696         this._elTable.summary = this.get("summary");
4697         
4698         // Create CAPTION element
4699         if(this.get("caption")) {
4700             this._initCaptionEl(this.get("caption"));
4701         }
4702     } 
4703 },
4704
4705 /**
4706  * Destroy's the DataTable COLGROUP element, if available.
4707  *
4708  * @method _destroyColgroupEl
4709  * @private
4710  */
4711 _destroyColgroupEl : function() {
4712     var elColgroup = this._elColgroup;
4713     if(elColgroup) {
4714         var elTable = elColgroup.parentNode;
4715         Ev.purgeElement(elColgroup, true);
4716         elTable.removeChild(elColgroup);
4717         this._elColgroup = null;
4718     }
4719 },
4720
4721 /**
4722  * Initializes COLGROUP and COL elements for managing minWidth.
4723  *
4724  * @method _initColgroupEl
4725  * @param elTable {HTMLElement} TABLE element into which to create COLGROUP.
4726  * @private
4727  */
4728 _initColgroupEl : function(elTable) {
4729     if(elTable) {
4730         // Destroy previous
4731         this._destroyColgroupEl();
4732
4733         // Add COLs to DOCUMENT FRAGMENT
4734         var allCols = this._aColIds || [],
4735             allKeys = this._oColumnSet.keys,
4736             i = 0, len = allCols.length,
4737             elCol, oColumn,
4738             elFragment = document.createDocumentFragment(),
4739             elColTemplate = document.createElement("col");
4740     
4741         for(i=0,len=allKeys.length; i<len; i++) {
4742             oColumn = allKeys[i];
4743             elCol = elFragment.appendChild(elColTemplate.cloneNode(false));
4744         }
4745     
4746         // Create COLGROUP
4747         var elColgroup = elTable.insertBefore(document.createElement("colgroup"), elTable.firstChild);
4748         elColgroup.appendChild(elFragment);
4749         this._elColgroup = elColgroup;
4750     }
4751 },
4752
4753 /**
4754  * Adds a COL element to COLGROUP at given index.
4755  *
4756  * @method _insertColgroupColEl
4757  * @param index {Number} Index of new COL element.
4758  * @private
4759  */
4760 _insertColgroupColEl : function(index) {
4761     if(lang.isNumber(index)&& this._elColgroup) {
4762         var nextSibling = this._elColgroup.childNodes[index] || null;
4763         this._elColgroup.insertBefore(document.createElement("col"), nextSibling);
4764     }
4765 },
4766
4767 /**
4768  * Removes a COL element to COLGROUP at given index.
4769  *
4770  * @method _removeColgroupColEl
4771  * @param index {Number} Index of removed COL element.
4772  * @private
4773  */
4774 _removeColgroupColEl : function(index) {
4775     if(lang.isNumber(index) && this._elColgroup && this._elColgroup.childNodes[index]) {
4776         this._elColgroup.removeChild(this._elColgroup.childNodes[index]);
4777     }
4778 },
4779
4780 /**
4781  * Reorders a COL element from old index(es) to new index.
4782  *
4783  * @method _reorderColgroupColEl
4784  * @param aKeyIndexes {Number[]} Array of indexes of removed COL element.
4785  * @param newIndex {Number} New index. 
4786  * @private
4787  */
4788 _reorderColgroupColEl : function(aKeyIndexes, newIndex) {
4789     if(lang.isArray(aKeyIndexes) && lang.isNumber(newIndex) && this._elColgroup && (this._elColgroup.childNodes.length > aKeyIndexes[aKeyIndexes.length-1])) {
4790         var i,
4791             tmpCols = [];
4792         // Remove COL
4793         for(i=aKeyIndexes.length-1; i>-1; i--) {
4794             tmpCols.push(this._elColgroup.removeChild(this._elColgroup.childNodes[aKeyIndexes[i]]));
4795         }
4796         // Insert COL
4797         var nextSibling = this._elColgroup.childNodes[newIndex] || null;
4798         for(i=tmpCols.length-1; i>-1; i--) {
4799             this._elColgroup.insertBefore(tmpCols[i], nextSibling);
4800         }
4801     }
4802 },
4803
4804 /**
4805  * Destroy's the DataTable THEAD element, if available.
4806  *
4807  * @method _destroyTheadEl
4808  * @private
4809  */
4810 _destroyTheadEl : function() {
4811     var elThead = this._elThead;
4812     if(elThead) {
4813         var elTable = elThead.parentNode;
4814         Ev.purgeElement(elThead, true);
4815         this._destroyColumnHelpers();
4816         elTable.removeChild(elThead);
4817         this._elThead = null;
4818     }
4819 },
4820
4821 /**
4822  * Initializes THEAD element.
4823  *
4824  * @method _initTheadEl
4825  * @param elTable {HTMLElement} TABLE element into which to create COLGROUP.
4826  * @param {HTMLElement} Initialized THEAD element. 
4827  * @private
4828  */
4829 _initTheadEl : function(elTable) {
4830     elTable = elTable || this._elTable;
4831     
4832     if(elTable) {
4833         // Destroy previous
4834         this._destroyTheadEl();
4835     
4836         //TODO: append to DOM later for performance
4837         var elThead = (this._elColgroup) ?
4838             elTable.insertBefore(document.createElement("thead"), this._elColgroup.nextSibling) :
4839             elTable.appendChild(document.createElement("thead"));
4840     
4841         // Set up DOM events for THEAD
4842         Ev.addListener(elThead, "focus", this._onTheadFocus, this);
4843         Ev.addListener(elThead, "keydown", this._onTheadKeydown, this);
4844         Ev.addListener(elThead, "mouseover", this._onTableMouseover, this);
4845         Ev.addListener(elThead, "mouseout", this._onTableMouseout, this);
4846         Ev.addListener(elThead, "mousedown", this._onTableMousedown, this);
4847         Ev.addListener(elThead, "mouseup", this._onTableMouseup, this);
4848         Ev.addListener(elThead, "click", this._onTheadClick, this);
4849
4850         // Since we can't listen for click and dblclick on the same element...
4851         // Attach separately to THEAD and TBODY
4852         ///Ev.addListener(elThead, "dblclick", this._onTableDblclick, this);
4853         
4854        var oColumnSet = this._oColumnSet,
4855             oColumn, i,j, l;
4856         
4857         // Add TRs to the THEAD
4858         var colTree = oColumnSet.tree;
4859         var elTh;
4860         for(i=0; i<colTree.length; i++) {
4861             var elTheadTr = elThead.appendChild(document.createElement("tr"));
4862     
4863             // ...and create TH cells
4864             for(j=0; j<colTree[i].length; j++) {
4865                 oColumn = colTree[i][j];
4866                 elTh = elTheadTr.appendChild(document.createElement("th"));
4867                 this._initThEl(elTh,oColumn);
4868             }
4869     
4870                 // Set FIRST/LAST on THEAD rows
4871                 if(i === 0) {
4872                     Dom.addClass(elTheadTr, DT.CLASS_FIRST);
4873                 }
4874                 if(i === (colTree.length-1)) {
4875                     Dom.addClass(elTheadTr, DT.CLASS_LAST);
4876                 }
4877
4878         }
4879
4880         // Set FIRST/LAST on edge TH elements using the values in ColumnSet headers array
4881         var aFirstHeaders = oColumnSet.headers[0] || [];
4882         for(i=0; i<aFirstHeaders.length; i++) {
4883             Dom.addClass(Dom.get(this.getId() +"-th-"+aFirstHeaders[i]), DT.CLASS_FIRST);
4884         }
4885         var aLastHeaders = oColumnSet.headers[oColumnSet.headers.length-1] || [];
4886         for(i=0; i<aLastHeaders.length; i++) {
4887             Dom.addClass(Dom.get(this.getId() +"-th-"+aLastHeaders[i]), DT.CLASS_LAST);
4888         }
4889         
4890         YAHOO.log("TH cells for " + this._oColumnSet.keys.length + " keys created","info",this.toString());
4891
4892         ///TODO: try _repaintGecko(this._elContainer) instead
4893         // Bug 1806891
4894         if(ua.webkit && ua.webkit < 420) {
4895             var oSelf = this;
4896             setTimeout(function() {
4897                 elThead.style.display = "";
4898             },0);
4899             elThead.style.display = 'none';
4900         }
4901         
4902         this._elThead = elThead;
4903         
4904         // Column helpers needs _elThead to exist
4905         this._initColumnHelpers();  
4906     }
4907 },
4908
4909 /**
4910  * Populates TH element as defined by Column.
4911  *
4912  * @method _initThEl
4913  * @param elTh {HTMLElement} TH element reference.
4914  * @param oColumn {YAHOO.widget.Column} Column object.
4915  * @private
4916  */
4917 _initThEl : function(elTh, oColumn) {
4918     elTh.id = this.getId() + "-th-" + oColumn.getSanitizedKey(); // Needed for accessibility, getColumn by TH, and ColumnDD
4919     elTh.innerHTML = "";
4920     elTh.rowSpan = oColumn.getRowspan();
4921     elTh.colSpan = oColumn.getColspan();
4922     oColumn._elTh = elTh;
4923     
4924     var elThLiner = elTh.appendChild(document.createElement("div"));
4925     elThLiner.id = elTh.id + "-liner"; // Needed for resizer
4926     elThLiner.className = DT.CLASS_LINER;
4927     oColumn._elThLiner = elThLiner;
4928     
4929     var elThLabel = elThLiner.appendChild(document.createElement("span"));
4930     elThLabel.className = DT.CLASS_LABEL;    
4931
4932     // Assign abbr attribute
4933     if(oColumn.abbr) {
4934         elTh.abbr = oColumn.abbr;
4935     }
4936     // Clear minWidth on hidden Columns
4937     if(oColumn.hidden) {
4938         this._clearMinWidth(oColumn);
4939     }
4940         
4941     elTh.className = this._getColumnClassNames(oColumn);
4942             
4943     // Set Column width...
4944     if(oColumn.width) {
4945         // Validate minWidth
4946         var nWidth = (oColumn.minWidth && (oColumn.width < oColumn.minWidth)) ?
4947                 oColumn.minWidth : oColumn.width;
4948         // ...for fallback cases
4949         if(DT._bDynStylesFallback) {
4950             elTh.firstChild.style.overflow = 'hidden';
4951             elTh.firstChild.style.width = nWidth + 'px';        
4952         }
4953         // ...for non fallback cases
4954         else {
4955             this._setColumnWidthDynStyles(oColumn, nWidth + 'px', 'hidden');
4956         }
4957     }
4958
4959     this.formatTheadCell(elThLabel, oColumn, this.get("sortedBy"));
4960     oColumn._elThLabel = elThLabel;
4961 },
4962
4963 /**
4964  * Outputs markup into the given TH based on given Column.
4965  *
4966  * @method DataTable.formatTheadCell
4967  * @param elCellLabel {HTMLElement} The label SPAN element within the TH liner,
4968  * not the liner DIV element.     
4969  * @param oColumn {YAHOO.widget.Column} Column instance.
4970  * @param oSortedBy {Object} Sort state object literal.
4971 */
4972 formatTheadCell : function(elCellLabel, oColumn, oSortedBy) {
4973     var sKey = oColumn.getKey();
4974     var sLabel = lang.isValue(oColumn.label) ? oColumn.label : sKey;
4975
4976     // Add accessibility link for sortable Columns
4977     if(oColumn.sortable) {
4978         // Calculate the direction
4979         var sSortClass = this.getColumnSortDir(oColumn, oSortedBy);
4980         var bDesc = (sSortClass === DT.CLASS_DESC);
4981
4982         // This is the sorted Column
4983         if(oSortedBy && (oColumn.key === oSortedBy.key)) {
4984             bDesc = !(oSortedBy.dir === DT.CLASS_DESC);
4985         }
4986
4987         // Generate a unique HREF for visited status
4988         var sHref = this.getId() + "-href-" + oColumn.getSanitizedKey();
4989         
4990         // Generate a dynamic TITLE for sort status
4991         var sTitle = (bDesc) ? this.get("MSG_SORTDESC") : this.get("MSG_SORTASC");
4992         
4993         // Format the element
4994         elCellLabel.innerHTML = "<a href=\"" + sHref + "\" title=\"" + sTitle + "\" class=\"" + DT.CLASS_SORTABLE + "\">" + sLabel + "</a>";
4995     }
4996     // Just display the label for non-sortable Columns
4997     else {
4998         elCellLabel.innerHTML = sLabel;
4999     }
5000 },
5001
5002 /**
5003  * Disables DD from top-level Column TH elements.
5004  *
5005  * @method _destroyDraggableColumns
5006  * @private
5007  */
5008 _destroyDraggableColumns : function() {
5009     var oColumn, elTh;
5010     for(var i=0, len=this._oColumnSet.tree[0].length; i<len; i++) {
5011         oColumn = this._oColumnSet.tree[0][i];
5012         if(oColumn._dd) {
5013             oColumn._dd = oColumn._dd.unreg();
5014             Dom.removeClass(oColumn.getThEl(), DT.CLASS_DRAGGABLE);       
5015         }
5016     }
5017 },
5018
5019 /**
5020  * Initializes top-level Column TH elements into DD instances.
5021  *
5022  * @method _initDraggableColumns
5023  * @private
5024  */
5025 _initDraggableColumns : function() {
5026     this._destroyDraggableColumns();
5027     if(util.DD) {
5028         var oColumn, elTh, elDragTarget;
5029         for(var i=0, len=this._oColumnSet.tree[0].length; i<len; i++) {
5030             oColumn = this._oColumnSet.tree[0][i];
5031             elTh = oColumn.getThEl();
5032             Dom.addClass(elTh, DT.CLASS_DRAGGABLE);
5033             elDragTarget = DT._initColumnDragTargetEl();
5034             oColumn._dd = new YAHOO.widget.ColumnDD(this, oColumn, elTh, elDragTarget);
5035         }
5036     }
5037     else {
5038         YAHOO.log("Could not find DragDrop for draggable Columns", "warn", this.toString());
5039     }
5040 },
5041
5042 /**
5043  * Disables resizeability on key Column TH elements.
5044  *
5045  * @method _destroyResizeableColumns
5046  * @private
5047  */
5048 _destroyResizeableColumns : function() {
5049     var aKeys = this._oColumnSet.keys;
5050     for(var i=0, len=aKeys.length; i<len; i++) {
5051         if(aKeys[i]._ddResizer) {
5052             aKeys[i]._ddResizer = aKeys[i]._ddResizer.unreg();
5053             Dom.removeClass(aKeys[i].getThEl(), DT.CLASS_RESIZEABLE);
5054         }
5055     }
5056 },
5057
5058 /**
5059  * Initializes resizeability on key Column TH elements.
5060  *
5061  * @method _initResizeableColumns
5062  * @private
5063  */
5064 _initResizeableColumns : function() {
5065     this._destroyResizeableColumns();
5066     if(util.DD) {
5067         var oColumn, elTh, elThLiner, elThResizerLiner, elThResizer, elResizerProxy, cancelClick;
5068         for(var i=0, len=this._oColumnSet.keys.length; i<len; i++) {
5069             oColumn = this._oColumnSet.keys[i];
5070             if(oColumn.resizeable) {
5071                 elTh = oColumn.getThEl();
5072                 Dom.addClass(elTh, DT.CLASS_RESIZEABLE);
5073                 elThLiner = oColumn.getThLinerEl();
5074                 
5075                 // Bug 1915349: So resizer is as tall as TH when rowspan > 1
5076                 // Create a separate resizer liner with position:relative
5077                 elThResizerLiner = elTh.appendChild(document.createElement("div"));
5078                 elThResizerLiner.className = DT.CLASS_RESIZERLINER;
5079                 
5080                 // Move TH contents into the new resizer liner
5081                 elThResizerLiner.appendChild(elThLiner);
5082                 
5083                 // Create the resizer
5084                 elThResizer = elThResizerLiner.appendChild(document.createElement("div"));
5085                 elThResizer.id = elTh.id + "-resizer"; // Needed for ColumnResizer
5086                 elThResizer.className = DT.CLASS_RESIZER;
5087                 oColumn._elResizer = elThResizer;
5088
5089                 // Create the resizer proxy, once globally
5090                 elResizerProxy = DT._initColumnResizerProxyEl();
5091                 oColumn._ddResizer = new YAHOO.util.ColumnResizer(
5092                         this, oColumn, elTh, elThResizer, elResizerProxy);
5093                 cancelClick = function(e) {
5094                     Ev.stopPropagation(e);
5095                 };
5096                 Ev.addListener(elThResizer,"click",cancelClick);
5097             }
5098         }
5099     }
5100     else {
5101         YAHOO.log("Could not find DragDrop for resizeable Columns", "warn", this.toString());
5102     }
5103 },
5104
5105 /**
5106  * Destroys elements associated with Column functionality: ColumnDD and ColumnResizers.
5107  *
5108  * @method _destroyColumnHelpers
5109  * @private
5110  */
5111 _destroyColumnHelpers : function() {
5112     this._destroyDraggableColumns();
5113     this._destroyResizeableColumns();
5114 },
5115
5116 /**
5117  * Initializes elements associated with Column functionality: ColumnDD and ColumnResizers.
5118  *
5119  * @method _initColumnHelpers
5120  * @private
5121  */
5122 _initColumnHelpers : function() {
5123     if(this.get("draggableColumns")) {
5124         this._initDraggableColumns();
5125     }
5126     this._initResizeableColumns();
5127 },
5128
5129 /**
5130  * Destroy's the DataTable TBODY element, if available.
5131  *
5132  * @method _destroyTbodyEl
5133  * @private
5134  */
5135 _destroyTbodyEl : function() {
5136     var elTbody = this._elTbody;
5137     if(elTbody) {
5138         var elTable = elTbody.parentNode;
5139         Ev.purgeElement(elTbody, true);
5140         elTable.removeChild(elTbody);
5141         this._elTbody = null;
5142     }
5143 },
5144
5145 /**
5146  * Initializes TBODY element for data.
5147  *
5148  * @method _initTbodyEl
5149  * @param elTable {HTMLElement} TABLE element into which to create TBODY .
5150  * @private
5151  */
5152 _initTbodyEl : function(elTable) {
5153     if(elTable) {
5154         // Destroy previous
5155         this._destroyTbodyEl();
5156         
5157         // Create TBODY
5158         var elTbody = elTable.appendChild(document.createElement("tbody"));
5159         elTbody.tabIndex = 0;
5160         elTbody.className = DT.CLASS_DATA;
5161     
5162         // Set up DOM events for TBODY
5163         Ev.addListener(elTbody, "focus", this._onTbodyFocus, this);
5164         Ev.addListener(elTbody, "mouseover", this._onTableMouseover, this);
5165         Ev.addListener(elTbody, "mouseout", this._onTableMouseout, this);
5166         Ev.addListener(elTbody, "mousedown", this._onTableMousedown, this);
5167         Ev.addListener(elTbody, "mouseup", this._onTableMouseup, this);
5168         Ev.addListener(elTbody, "keydown", this._onTbodyKeydown, this);
5169         Ev.addListener(elTbody, "keypress", this._onTableKeypress, this);
5170         Ev.addListener(elTbody, "click", this._onTbodyClick, this);
5171         
5172         // Since we can't listen for click and dblclick on the same element...
5173         // Attach separately to THEAD and TBODY
5174         ///Ev.addListener(elTbody, "dblclick", this._onTableDblclick, this);
5175         
5176     
5177         // IE puts focus outline in the wrong place
5178         if(ua.ie) {
5179             elTbody.hideFocus=true;
5180         }
5181
5182         this._elTbody = elTbody;
5183     }
5184 },
5185
5186 /**
5187  * Destroy's the DataTable message TBODY element, if available.
5188  *
5189  * @method _destroyMsgTbodyEl
5190  * @private
5191  */
5192 _destroyMsgTbodyEl : function() {
5193     var elMsgTbody = this._elMsgTbody;
5194     if(elMsgTbody) {
5195         var elTable = elMsgTbody.parentNode;
5196         Ev.purgeElement(elMsgTbody, true);
5197         elTable.removeChild(elMsgTbody);
5198         this._elTbody = null;
5199     }
5200 },
5201
5202 /**
5203  * Initializes TBODY element for messaging.
5204  *
5205  * @method _initMsgTbodyEl
5206  * @param elTable {HTMLElement} TABLE element into which to create TBODY 
5207  * @private
5208  */
5209 _initMsgTbodyEl : function(elTable) {
5210     if(elTable) {
5211         var elMsgTbody = document.createElement("tbody");
5212         elMsgTbody.className = DT.CLASS_MESSAGE;
5213         var elMsgTr = elMsgTbody.appendChild(document.createElement("tr"));
5214         elMsgTr.className = DT.CLASS_FIRST + " " + DT.CLASS_LAST;
5215         this._elMsgTr = elMsgTr;
5216         var elMsgTd = elMsgTr.appendChild(document.createElement("td"));
5217         elMsgTd.colSpan = this._oColumnSet.keys.length || 1;
5218         elMsgTd.className = DT.CLASS_FIRST + " " + DT.CLASS_LAST;
5219         this._elMsgTd = elMsgTd;
5220         elMsgTbody = elTable.insertBefore(elMsgTbody, this._elTbody);
5221         var elMsgLiner = elMsgTd.appendChild(document.createElement("div"));
5222         elMsgLiner.className = DT.CLASS_LINER;
5223         this._elMsgTbody = elMsgTbody;
5224
5225         // Set up DOM events for TBODY
5226         Ev.addListener(elMsgTbody, "focus", this._onTbodyFocus, this);
5227         Ev.addListener(elMsgTbody, "mouseover", this._onTableMouseover, this);
5228         Ev.addListener(elMsgTbody, "mouseout", this._onTableMouseout, this);
5229         Ev.addListener(elMsgTbody, "mousedown", this._onTableMousedown, this);
5230         Ev.addListener(elMsgTbody, "mouseup", this._onTableMouseup, this);
5231         Ev.addListener(elMsgTbody, "keydown", this._onTbodyKeydown, this);
5232         Ev.addListener(elMsgTbody, "keypress", this._onTableKeypress, this);
5233         Ev.addListener(elMsgTbody, "click", this._onTbodyClick, this);
5234     }
5235 },
5236
5237 /**
5238  * Initialize internal event listeners
5239  *
5240  * @method _initEvents
5241  * @private
5242  */
5243 _initEvents : function () {
5244     // Initialize Column sort
5245     this._initColumnSort();
5246         
5247     // Add the document level click listener
5248     YAHOO.util.Event.addListener(document, "click", this._onDocumentClick, this);
5249
5250     // Paginator integration
5251     this.subscribe("paginatorChange",function () {
5252         this._handlePaginatorChange.apply(this,arguments);
5253     });
5254
5255     this.subscribe("initEvent",function () {
5256         this.renderPaginator();
5257     });
5258
5259     // Initialize CellEditor integration
5260     this._initCellEditing();
5261 },
5262
5263 /**      
5264   * Initializes Column sorting.          
5265   *      
5266   * @method _initColumnSort      
5267   * @private     
5268   */     
5269 _initColumnSort : function() {
5270     this.subscribe("theadCellClickEvent", this.onEventSortColumn);       
5271
5272     // Backward compatibility
5273     var oSortedBy = this.get("sortedBy");
5274     if(oSortedBy) {
5275         if(oSortedBy.dir == "desc") {
5276             this._configs.sortedBy.value.dir = DT.CLASS_DESC;
5277         }
5278         else if(oSortedBy.dir == "asc") {
5279             this._configs.sortedBy.value.dir = DT.CLASS_ASC;
5280         }
5281     }
5282 },
5283
5284 /**      
5285   * Initializes CellEditor integration.          
5286   *      
5287   * @method _initCellEditing     
5288   * @private     
5289   */     
5290 _initCellEditing : function() {
5291     this.subscribe("editorBlurEvent",function () {
5292         this.onEditorBlurEvent.apply(this,arguments);
5293     });
5294     this.subscribe("editorBlockEvent",function () {
5295         this.onEditorBlockEvent.apply(this,arguments);
5296     });
5297     this.subscribe("editorUnblockEvent",function () {
5298         this.onEditorUnblockEvent.apply(this,arguments);
5299     });
5300 },
5301
5302
5303
5304
5305
5306
5307
5308
5309
5310
5311
5312
5313
5314
5315
5316
5317
5318
5319
5320
5321
5322
5323
5324
5325
5326
5327
5328
5329
5330
5331
5332
5333
5334 // DOM MUTATION FUNCTIONS
5335
5336 /**
5337  * Retruns classnames to represent current Column states.
5338  * @method _getColumnClassnames 
5339  * @param oColumn {YAHOO.widget.Column} Column instance.
5340  * @param aAddClasses {String[]} An array of additional classnames to add to the
5341  * return value.  
5342  * @return {String} A String of classnames to be assigned to TH or TD elements
5343  * for given Column.  
5344  * @private 
5345  */
5346 _getColumnClassNames : function (oColumn, aAddClasses) {
5347     var allClasses;
5348     
5349     // Add CSS classes
5350     if(lang.isString(oColumn.className)) {
5351         // Single custom class
5352         allClasses = [oColumn.className];
5353     }
5354     else if(lang.isArray(oColumn.className)) {
5355         // Array of custom classes
5356         allClasses = oColumn.className;
5357     }
5358     else {
5359         // no custom classes
5360         allClasses = [];
5361     }
5362     
5363     // Hook for setting width with via dynamic style uses key since ID is too disposable
5364     allClasses[allClasses.length] = this.getId() + "-col-" +oColumn.getSanitizedKey();
5365
5366     // Column key - minus any chars other than "A-Z", "a-z", "0-9", "_", "-", ".", or ":"
5367     allClasses[allClasses.length] = "yui-dt-col-" +oColumn.getSanitizedKey();
5368
5369     var isSortedBy = this.get("sortedBy") || {};
5370     // Sorted
5371     if(oColumn.key === isSortedBy.key) {
5372         allClasses[allClasses.length] = isSortedBy.dir || '';
5373     }
5374     // Hidden
5375     if(oColumn.hidden) {
5376         allClasses[allClasses.length] = DT.CLASS_HIDDEN;
5377     }
5378     // Selected
5379     if(oColumn.selected) {
5380         allClasses[allClasses.length] = DT.CLASS_SELECTED;
5381     }
5382     // Sortable
5383     if(oColumn.sortable) {
5384         allClasses[allClasses.length] = DT.CLASS_SORTABLE;
5385     }
5386     // Resizeable
5387     if(oColumn.resizeable) {
5388         allClasses[allClasses.length] = DT.CLASS_RESIZEABLE;
5389     }
5390     // Editable
5391     if(oColumn.editor) {
5392         allClasses[allClasses.length] = DT.CLASS_EDITABLE;
5393     }
5394     
5395     // Addtnl classes, including First/Last
5396     if(aAddClasses) {
5397         allClasses = allClasses.concat(aAddClasses);
5398     }
5399     
5400     return allClasses.join(' ');  
5401 },
5402
5403 /**
5404  * Clears TR element template in response to any Column state change.
5405  * @method _clearTrTemplateEl
5406  * @private 
5407  */
5408 _clearTrTemplateEl : function () {
5409     this._elTrTemplate = null;
5410 },
5411
5412 /**
5413  * Returns a new TR element template with TD elements classed with current
5414  * Column states.
5415  * @method _getTrTemplateEl 
5416  * @return {HTMLElement} A TR element to be cloned and added to the DOM.
5417  * @private 
5418  */
5419 _getTrTemplateEl : function (oRecord, index) {
5420     // Template is already available
5421     if(this._elTrTemplate) {
5422         return this._elTrTemplate;
5423     }
5424     // Template needs to be created
5425     else {
5426         var d   = document,
5427             tr  = d.createElement('tr'),
5428             td  = d.createElement('td'),
5429             div = d.createElement('div');
5430     
5431         // Append the liner element
5432         td.appendChild(div);
5433
5434         // Create TD elements into DOCUMENT FRAGMENT
5435         var df = document.createDocumentFragment(),
5436             allKeys = this._oColumnSet.keys,
5437             elTd;
5438
5439         // Set state for each TD;
5440         var aAddClasses;
5441         for(var i=0, keysLen=allKeys.length; i<keysLen; i++) {
5442             // Clone the TD template
5443             elTd = td.cloneNode(true);
5444
5445             // Format the base TD
5446             elTd = this._formatTdEl(allKeys[i], elTd, i, (i===keysLen-1));
5447                         
5448             df.appendChild(elTd);
5449         }
5450         tr.appendChild(df);
5451         this._elTrTemplate = tr;
5452         return tr;
5453     }   
5454 },
5455
5456 /**
5457  * Formats a basic TD element.
5458  * @method _formatTdEl 
5459  * @param oColumn {YAHOO.widget.Column} Associated Column instance. 
5460  * @param elTd {HTMLElement} An unformatted TD element.
5461  * @param index {Number} Column key index. 
5462  * @param isLast {Boolean} True if Column is last key of the ColumnSet.
5463  * @return {HTMLElement} A formatted TD element.
5464  * @private 
5465  */
5466 _formatTdEl : function (oColumn, elTd, index, isLast) {
5467     var oColumnSet = this._oColumnSet;
5468     
5469     // Set the TD's accessibility headers
5470     var allHeaders = oColumnSet.headers,
5471         allColHeaders = allHeaders[index],
5472         sTdHeaders = "",
5473         sHeader;
5474     for(var j=0, headersLen=allColHeaders.length; j < headersLen; j++) {
5475         sHeader = this._sId + "-th-" + allColHeaders[j] + ' ';
5476         sTdHeaders += sHeader;
5477     }
5478     elTd.headers = sTdHeaders;
5479     
5480     // Class the TD element
5481     var aAddClasses = [];
5482     if(index === 0) {
5483         aAddClasses[aAddClasses.length] = DT.CLASS_FIRST;
5484     }
5485     if(isLast) {
5486         aAddClasses[aAddClasses.length] = DT.CLASS_LAST;
5487     }
5488     elTd.className = this._getColumnClassNames(oColumn, aAddClasses);
5489
5490     // Class the liner element
5491     elTd.firstChild.className = DT.CLASS_LINER;
5492
5493     // Set Column width for fallback cases
5494     if(oColumn.width && DT._bDynStylesFallback) {
5495         // Validate minWidth
5496         var nWidth = (oColumn.minWidth && (oColumn.width < oColumn.minWidth)) ?
5497                 oColumn.minWidth : oColumn.width;
5498         elTd.firstChild.style.overflow = 'hidden';
5499         elTd.firstChild.style.width = nWidth + 'px';
5500     }
5501     
5502     return elTd;
5503 },
5504
5505
5506 /**
5507  * Create a new TR element for a given Record and appends it with the correct
5508  * number of Column-state-classed TD elements. Striping is the responsibility of
5509  * the calling function, which may decide to stripe the single row, a subset of
5510  * rows, or all the rows.
5511  * @method _createTrEl
5512  * @param oRecord {YAHOO.widget.Record} Record instance
5513  * @return {HTMLElement} The new TR element.  This must be added to the DOM.
5514  * @private 
5515  */
5516 _addTrEl : function (oRecord) {
5517     var elTrTemplate = this._getTrTemplateEl();
5518     
5519     // Clone the TR template.
5520     var elTr = elTrTemplate.cloneNode(true);
5521     
5522     // Populate content
5523     return this._updateTrEl(elTr,oRecord);
5524 },
5525
5526 /**
5527  * Formats the contents of the given TR's TD elements with data from the given
5528  * Record. Only innerHTML should change, nothing structural.
5529  *
5530  * @method _updateTrEl
5531  * @param elTr {HTMLElement} The TR element to update.
5532  * @param oRecord {YAHOO.widget.Record} The associated Record instance.
5533  * @return {HTMLElement} DOM reference to the new TR element.
5534  * @private
5535  */
5536 _updateTrEl : function(elTr, oRecord) {
5537     var ok = this.get("formatRow") ? this.get("formatRow").call(this, elTr, oRecord) : true;
5538     if(ok) {
5539         // Hide the row to prevent constant reflows
5540         elTr.style.display = 'none';
5541         
5542         // Update TD elements with new data
5543         var allTds = elTr.childNodes,
5544             elTd;
5545         for(var i=0,len=allTds.length; i<len; ++i) {
5546             elTd = allTds[i];
5547             
5548             // Set the cell content
5549             this.formatCell(allTds[i].firstChild, oRecord, this._oColumnSet.keys[i]);
5550         }
5551         
5552         // Redisplay the row for reflow
5553         elTr.style.display = '';
5554     }
5555     
5556     elTr.id = oRecord.getId(); // Needed for Record association and tracking of FIRST/LAST
5557     return elTr;
5558 },
5559
5560
5561 /**
5562  * Deletes TR element by DOM reference or by DataTable page row index.
5563  *
5564  * @method _deleteTrEl
5565  * @param row {HTMLElement | Number} TR element reference or Datatable page row index.
5566  * @return {Boolean} Returns true if successful, else returns false.
5567  * @private
5568  */
5569 _deleteTrEl : function(row) {
5570     var rowIndex;
5571
5572     // Get page row index for the element
5573     if(!lang.isNumber(row)) {
5574         rowIndex = Dom.get(row).sectionRowIndex;
5575     }
5576     else {
5577         rowIndex = row;
5578     }
5579     if(lang.isNumber(rowIndex) && (rowIndex > -2) && (rowIndex < this._elTbody.rows.length)) {
5580         // Cannot use tbody.deleteRow due to IE6 instability
5581         //return this._elTbody.deleteRow(rowIndex);
5582         return this._elTbody.removeChild(this.getTrEl(row));
5583     }
5584     else {
5585         return null;
5586     }
5587 },
5588
5589
5590
5591
5592
5593
5594
5595
5596
5597
5598
5599
5600
5601
5602
5603
5604
5605
5606
5607
5608
5609
5610
5611
5612
5613
5614
5615 // CSS/STATE FUNCTIONS
5616
5617
5618
5619
5620 /**
5621  * Removes the class YAHOO.widget.DataTable.CLASS_FIRST from the first TR element
5622  * of the DataTable page and updates internal tracker.
5623  *
5624  * @method _unsetFirstRow
5625  * @private
5626  */
5627 _unsetFirstRow : function() {
5628     // Remove FIRST
5629     if(this._sFirstTrId) {
5630         Dom.removeClass(this._sFirstTrId, DT.CLASS_FIRST);
5631         this._sFirstTrId = null;
5632     }
5633 },
5634
5635 /**
5636  * Assigns the class YAHOO.widget.DataTable.CLASS_FIRST to the first TR element
5637  * of the DataTable page and updates internal tracker.
5638  *
5639  * @method _setFirstRow
5640  * @private
5641  */
5642 _setFirstRow : function() {
5643     this._unsetFirstRow();
5644     var elTr = this.getFirstTrEl();
5645     if(elTr) {
5646         // Set FIRST
5647         Dom.addClass(elTr, DT.CLASS_FIRST);
5648         this._sFirstTrId = elTr.id;
5649     }
5650 },
5651
5652 /**
5653  * Removes the class YAHOO.widget.DataTable.CLASS_LAST from the last TR element
5654  * of the DataTable page and updates internal tracker.
5655  *
5656  * @method _unsetLastRow
5657  * @private
5658  */
5659 _unsetLastRow : function() {
5660     // Unassign previous class
5661     if(this._sLastTrId) {
5662         Dom.removeClass(this._sLastTrId, DT.CLASS_LAST);
5663         this._sLastTrId = null;
5664     }   
5665 },
5666
5667 /**
5668  * Assigns the class YAHOO.widget.DataTable.CLASS_LAST to the last TR element
5669  * of the DataTable page and updates internal tracker.
5670  *
5671  * @method _setLastRow
5672  * @private
5673  */
5674 _setLastRow : function() {
5675     this._unsetLastRow();
5676     var elTr = this.getLastTrEl();
5677     if(elTr) {
5678         // Assign class
5679         Dom.addClass(elTr, DT.CLASS_LAST);
5680         this._sLastTrId = elTr.id;
5681     }
5682 },
5683
5684 /**
5685  * Assigns the classes DT.CLASS_EVEN and DT.CLASS_ODD to one, many, or all TR elements.
5686  *
5687  * @method _setRowStripes
5688  * @param row {HTMLElement | String | Number} (optional) HTML TR element reference
5689  * or string ID, or page row index of where to start striping.
5690  * @param range {Number} (optional) If given, how many rows to stripe, otherwise
5691  * stripe all the rows until the end.
5692  * @private
5693  */
5694 _setRowStripes : function(row, range) {
5695     // Default values stripe all rows
5696     var allRows = this._elTbody.rows,
5697         nStartIndex = 0,
5698         nEndIndex = allRows.length,
5699         aOdds = [], nOddIdx = 0,
5700         aEvens = [], nEvenIdx = 0;
5701
5702     // Stripe a subset
5703     if((row !== null) && (row !== undefined)) {
5704         // Validate given start row
5705         var elStartRow = this.getTrEl(row);
5706         if(elStartRow) {
5707             nStartIndex = elStartRow.sectionRowIndex;
5708
5709             // Validate given range
5710             if(lang.isNumber(range) && (range > 1)) {
5711                 nEndIndex = nStartIndex + range;
5712             }
5713         }
5714     }
5715
5716     for(var i=nStartIndex; i<nEndIndex; i++) {
5717         if(i%2) {
5718             aOdds[nOddIdx++] = allRows[i];
5719         } else {
5720             aEvens[nEvenIdx++] = allRows[i];
5721         }
5722     }
5723
5724     if (aOdds.length) {
5725         Dom.replaceClass(aOdds, DT.CLASS_EVEN, DT.CLASS_ODD);
5726     }
5727
5728     if (aEvens.length) {
5729         Dom.replaceClass(aEvens, DT.CLASS_ODD, DT.CLASS_EVEN);
5730     }
5731 },
5732
5733 /**
5734  * Assigns the class DT.CLASS_SELECTED to TR and TD elements.
5735  *
5736  * @method _setSelections
5737  * @private
5738  */
5739 _setSelections : function() {
5740     // Keep track of selected rows
5741     var allSelectedRows = this.getSelectedRows();
5742     // Keep track of selected cells
5743     var allSelectedCells = this.getSelectedCells();
5744     // Anything to select?
5745     if((allSelectedRows.length>0) || (allSelectedCells.length > 0)) {
5746         var oColumnSet = this._oColumnSet,
5747             el;
5748         // Loop over each row
5749         for(var i=0; i<allSelectedRows.length; i++) {
5750             el = Dom.get(allSelectedRows[i]);
5751             if(el) {
5752                 Dom.addClass(el, DT.CLASS_SELECTED);
5753             }
5754         }
5755         // Loop over each cell
5756         for(i=0; i<allSelectedCells.length; i++) {
5757             el = Dom.get(allSelectedCells[i].recordId);
5758             if(el) {
5759                 Dom.addClass(el.childNodes[oColumnSet.getColumn(allSelectedCells[i].columnKey).getKeyIndex()], DT.CLASS_SELECTED);
5760             }
5761         }
5762     }       
5763 },
5764
5765
5766
5767
5768
5769
5770
5771
5772
5773
5774
5775
5776
5777
5778
5779
5780
5781
5782
5783
5784
5785
5786
5787
5788
5789
5790
5791
5792
5793
5794
5795
5796
5797
5798
5799
5800
5801
5802
5803
5804
5805
5806
5807 /////////////////////////////////////////////////////////////////////////////
5808 //
5809 // Private DOM Event Handlers
5810 //
5811 /////////////////////////////////////////////////////////////////////////////
5812
5813 /**
5814  * Validates minWidths whenever the render chain ends.
5815  *
5816  * @method _onRenderChainEnd
5817  * @private
5818  */
5819 _onRenderChainEnd : function() {
5820     // Hide loading message
5821     this.hideTableMessage();
5822     
5823     // Show empty message
5824     if(this._elTbody.rows.length === 0) {
5825         this.showTableMessage(this.get("MSG_EMPTY"), DT.CLASS_EMPTY);        
5826     }
5827
5828     // Execute in timeout thread to give implementers a chance
5829     // to subscribe after the constructor
5830     var oSelf = this;
5831     setTimeout(function() {
5832         if((oSelf instanceof DT) && oSelf._sId) {        
5833             // Init event
5834             if(oSelf._bInit) {
5835                 oSelf._bInit = false;
5836                 oSelf.fireEvent("initEvent");
5837             }
5838     
5839             // Render event
5840             oSelf.fireEvent("renderEvent");
5841             // Backward compatibility
5842             oSelf.fireEvent("refreshEvent");
5843             YAHOO.log("DataTable rendered", "info", oSelf.toString());
5844     
5845             // Post-render routine
5846             oSelf.validateColumnWidths();
5847     
5848             // Post-render event
5849             oSelf.fireEvent("postRenderEvent");
5850             
5851             /*if(YAHOO.example.Performance.trialStart) {
5852                 YAHOO.log((new Date()).getTime() - YAHOO.example.Performance.trialStart.getTime() + " ms", "time");
5853                 YAHOO.example.Performance.trialStart = null;
5854             }*/
5855             
5856             YAHOO.log("Post-render routine executed", "info", oSelf.toString());
5857         }
5858     }, 0);
5859 },
5860
5861 /**
5862  * Handles click events on the DOCUMENT.
5863  *
5864  * @method _onDocumentClick
5865  * @param e {HTMLEvent} The click event.
5866  * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
5867  * @private
5868  */
5869 _onDocumentClick : function(e, oSelf) {
5870     var elTarget = Ev.getTarget(e);
5871     var elTag = elTarget.nodeName.toLowerCase();
5872
5873     if(!Dom.isAncestor(oSelf._elContainer, elTarget)) {
5874         oSelf.fireEvent("tableBlurEvent");
5875
5876         // Fires editorBlurEvent when click is not within the TABLE.
5877         // For cases when click is within the TABLE, due to timing issues,
5878         // the editorBlurEvent needs to get fired by the lower-level DOM click
5879         // handlers below rather than by the TABLE click handler directly.
5880         if(oSelf._oCellEditor) {
5881             if(oSelf._oCellEditor.getContainerEl) {
5882                 var elContainer = oSelf._oCellEditor.getContainerEl();
5883                 // Only if the click was not within the CellEditor container
5884                 if(!Dom.isAncestor(elContainer, elTarget) &&
5885                         (elContainer.id !== elTarget.id)) {
5886                     oSelf._oCellEditor.fireEvent("blurEvent", {editor: oSelf._oCellEditor});
5887                 }
5888             }
5889             // Backward Compatibility
5890             else if(oSelf._oCellEditor.isActive) {
5891                 // Only if the click was not within the Cell Editor container
5892                 if(!Dom.isAncestor(oSelf._oCellEditor.container, elTarget) &&
5893                         (oSelf._oCellEditor.container.id !== elTarget.id)) {
5894                     oSelf.fireEvent("editorBlurEvent", {editor:oSelf._oCellEditor});
5895                 }
5896             }
5897         }
5898     }
5899 },
5900
5901 /**
5902  * Handles focus events on the DataTable instance.
5903  *
5904  * @method _onTableFocus
5905  * @param e {HTMLEvent} The focus event.
5906  * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
5907  * @private
5908  */
5909 _onTableFocus : function(e, oSelf) {
5910     oSelf.fireEvent("tableFocusEvent");
5911 },
5912
5913 /**
5914  * Handles focus events on the THEAD element.
5915  *
5916  * @method _onTheadFocus
5917  * @param e {HTMLEvent} The focus event.
5918  * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
5919  * @private
5920  */
5921 _onTheadFocus : function(e, oSelf) {
5922     oSelf.fireEvent("theadFocusEvent");
5923     oSelf.fireEvent("tableFocusEvent");
5924 },
5925
5926 /**
5927  * Handles focus events on the TBODY element.
5928  *
5929  * @method _onTbodyFocus
5930  * @param e {HTMLEvent} The focus event.
5931  * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
5932  * @private
5933  */
5934 _onTbodyFocus : function(e, oSelf) {
5935     oSelf.fireEvent("tbodyFocusEvent");
5936     oSelf.fireEvent("tableFocusEvent");
5937 },
5938
5939 /**
5940  * Handles mouseover events on the DataTable instance.
5941  *
5942  * @method _onTableMouseover
5943  * @param e {HTMLEvent} The mouseover event.
5944  * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
5945  * @private
5946  */
5947 _onTableMouseover : function(e, oSelf) {
5948     var elTarget = Ev.getTarget(e);
5949         var elTag = elTarget.nodeName.toLowerCase();
5950         var bKeepBubbling = true;
5951         while(elTarget && (elTag != "table")) {
5952             switch(elTag) {
5953                 case "body":
5954                      return;
5955                 case "a":
5956                     break;
5957                 case "td":
5958                     bKeepBubbling = oSelf.fireEvent("cellMouseoverEvent",{target:elTarget,event:e});
5959                     break;
5960                 case "span":
5961                     if(Dom.hasClass(elTarget, DT.CLASS_LABEL)) {
5962                         bKeepBubbling = oSelf.fireEvent("theadLabelMouseoverEvent",{target:elTarget,event:e});
5963                         // Backward compatibility
5964                         bKeepBubbling = oSelf.fireEvent("headerLabelMouseoverEvent",{target:elTarget,event:e});
5965                     }
5966                     break;
5967                 case "th":
5968                     bKeepBubbling = oSelf.fireEvent("theadCellMouseoverEvent",{target:elTarget,event:e});
5969                     // Backward compatibility
5970                     bKeepBubbling = oSelf.fireEvent("headerCellMouseoverEvent",{target:elTarget,event:e});
5971                     break;
5972                 case "tr":
5973                     if(elTarget.parentNode.nodeName.toLowerCase() == "thead") {
5974                         bKeepBubbling = oSelf.fireEvent("theadRowMouseoverEvent",{target:elTarget,event:e});
5975                         // Backward compatibility
5976                         bKeepBubbling = oSelf.fireEvent("headerRowMouseoverEvent",{target:elTarget,event:e});
5977                     }
5978                     else {
5979                         bKeepBubbling = oSelf.fireEvent("rowMouseoverEvent",{target:elTarget,event:e});
5980                     }
5981                     break;
5982                 default:
5983                     break;
5984             }
5985             if(bKeepBubbling === false) {
5986                 return;
5987             }
5988             else {
5989                 elTarget = elTarget.parentNode;
5990                 if(elTarget) {
5991                     elTag = elTarget.nodeName.toLowerCase();
5992                 }
5993             }
5994         }
5995         oSelf.fireEvent("tableMouseoverEvent",{target:(elTarget || oSelf._elContainer),event:e});
5996 },
5997
5998 /**
5999  * Handles mouseout events on the DataTable instance.
6000  *
6001  * @method _onTableMouseout
6002  * @param e {HTMLEvent} The mouseout event.
6003  * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
6004  * @private
6005  */
6006 _onTableMouseout : function(e, oSelf) {
6007     var elTarget = Ev.getTarget(e);
6008     var elTag = elTarget.nodeName.toLowerCase();
6009     var bKeepBubbling = true;
6010     while(elTarget && (elTag != "table")) {
6011         switch(elTag) {
6012             case "body":
6013                 return;
6014             case "a":
6015                 break;
6016             case "td":
6017                 bKeepBubbling = oSelf.fireEvent("cellMouseoutEvent",{target:elTarget,event:e});
6018                 break;
6019             case "span":
6020                 if(Dom.hasClass(elTarget, DT.CLASS_LABEL)) {
6021                     bKeepBubbling = oSelf.fireEvent("theadLabelMouseoutEvent",{target:elTarget,event:e});
6022                     // Backward compatibility
6023                     bKeepBubbling = oSelf.fireEvent("headerLabelMouseoutEvent",{target:elTarget,event:e});
6024                 }
6025                 break;
6026             case "th":
6027                 bKeepBubbling = oSelf.fireEvent("theadCellMouseoutEvent",{target:elTarget,event:e});
6028                 // Backward compatibility
6029                 bKeepBubbling = oSelf.fireEvent("headerCellMouseoutEvent",{target:elTarget,event:e});
6030                 break;
6031             case "tr":
6032                 if(elTarget.parentNode.nodeName.toLowerCase() == "thead") {
6033                     bKeepBubbling = oSelf.fireEvent("theadRowMouseoutEvent",{target:elTarget,event:e});
6034                     // Backward compatibility
6035                     bKeepBubbling = oSelf.fireEvent("headerRowMouseoutEvent",{target:elTarget,event:e});
6036                 }
6037                 else {
6038                     bKeepBubbling = oSelf.fireEvent("rowMouseoutEvent",{target:elTarget,event:e});
6039                 }
6040                 break;
6041             default:
6042                 break;
6043         }
6044         if(bKeepBubbling === false) {
6045             return;
6046         }
6047         else {
6048             elTarget = elTarget.parentNode;
6049             if(elTarget) {
6050                 elTag = elTarget.nodeName.toLowerCase();
6051             }
6052         }
6053     }
6054     oSelf.fireEvent("tableMouseoutEvent",{target:(elTarget || oSelf._elContainer),event:e});
6055 },
6056
6057 /**
6058  * Handles mousedown events on the DataTable instance.
6059  *
6060  * @method _onTableMousedown
6061  * @param e {HTMLEvent} The mousedown event.
6062  * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
6063  * @private
6064  */
6065 _onTableMousedown : function(e, oSelf) {
6066     var elTarget = Ev.getTarget(e);
6067     var elTag = elTarget.nodeName.toLowerCase();
6068     var bKeepBubbling = true;
6069     while(elTarget && (elTag != "table")) {
6070         switch(elTag) {
6071             case "body":
6072                 return;
6073             case "a":
6074                 break;
6075             case "td":
6076                 bKeepBubbling = oSelf.fireEvent("cellMousedownEvent",{target:elTarget,event:e});
6077                 break;
6078             case "span":
6079                 if(Dom.hasClass(elTarget, DT.CLASS_LABEL)) {
6080                     bKeepBubbling = oSelf.fireEvent("theadLabelMousedownEvent",{target:elTarget,event:e});
6081                     // Backward compatibility
6082                     bKeepBubbling = oSelf.fireEvent("headerLabelMousedownEvent",{target:elTarget,event:e});
6083                 }
6084                 break;
6085             case "th":
6086                 bKeepBubbling = oSelf.fireEvent("theadCellMousedownEvent",{target:elTarget,event:e});
6087                 // Backward compatibility
6088                 bKeepBubbling = oSelf.fireEvent("headerCellMousedownEvent",{target:elTarget,event:e});
6089                 break;
6090             case "tr":
6091                 if(elTarget.parentNode.nodeName.toLowerCase() == "thead") {
6092                     bKeepBubbling = oSelf.fireEvent("theadRowMousedownEvent",{target:elTarget,event:e});
6093                     // Backward compatibility
6094                     bKeepBubbling = oSelf.fireEvent("headerRowMousedownEvent",{target:elTarget,event:e});
6095                 }
6096                 else {
6097                     bKeepBubbling = oSelf.fireEvent("rowMousedownEvent",{target:elTarget,event:e});
6098                 }
6099                 break;
6100             default:
6101                 break;
6102         }
6103         if(bKeepBubbling === false) {
6104             return;
6105         }
6106         else {
6107             elTarget = elTarget.parentNode;
6108             if(elTarget) {
6109                 elTag = elTarget.nodeName.toLowerCase();
6110             }
6111         }
6112     }
6113     oSelf.fireEvent("tableMousedownEvent",{target:(elTarget || oSelf._elContainer),event:e});
6114 },
6115
6116 /**
6117  * Handles mouseup events on the DataTable instance.
6118  *
6119  * @method _onTableMouseup
6120  * @param e {HTMLEvent} The mouseup event.
6121  * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
6122  * @private
6123  */
6124 _onTableMouseup : function(e, oSelf) {
6125     var elTarget = Ev.getTarget(e);
6126     var elTag = elTarget.nodeName.toLowerCase();
6127     var bKeepBubbling = true;
6128     while(elTarget && (elTag != "table")) {
6129         switch(elTag) {
6130             case "body":
6131                 return;
6132             case "a":
6133                 break;
6134             case "td":
6135                 bKeepBubbling = oSelf.fireEvent("cellMouseupEvent",{target:elTarget,event:e});
6136                 break;
6137             case "span":
6138                 if(Dom.hasClass(elTarget, DT.CLASS_LABEL)) {
6139                     bKeepBubbling = oSelf.fireEvent("theadLabelMouseupEvent",{target:elTarget,event:e});
6140                     // Backward compatibility
6141                     bKeepBubbling = oSelf.fireEvent("headerLabelMouseupEvent",{target:elTarget,event:e});
6142                 }
6143                 break;
6144             case "th":
6145                 bKeepBubbling = oSelf.fireEvent("theadCellMouseupEvent",{target:elTarget,event:e});
6146                 // Backward compatibility
6147                 bKeepBubbling = oSelf.fireEvent("headerCellMouseupEvent",{target:elTarget,event:e});
6148                 break;
6149             case "tr":
6150                 if(elTarget.parentNode.nodeName.toLowerCase() == "thead") {
6151                     bKeepBubbling = oSelf.fireEvent("theadRowMouseupEvent",{target:elTarget,event:e});
6152                     // Backward compatibility
6153                     bKeepBubbling = oSelf.fireEvent("headerRowMouseupEvent",{target:elTarget,event:e});
6154                 }
6155                 else {
6156                     bKeepBubbling = oSelf.fireEvent("rowMouseupEvent",{target:elTarget,event:e});
6157                 }
6158                 break;
6159             default:
6160                 break;
6161         }
6162         if(bKeepBubbling === false) {
6163             return;
6164         }
6165         else {
6166             elTarget = elTarget.parentNode;
6167             if(elTarget) {
6168                 elTag = elTarget.nodeName.toLowerCase();
6169             }
6170         }
6171     }
6172     oSelf.fireEvent("tableMouseupEvent",{target:(elTarget || oSelf._elContainer),event:e});
6173 },
6174
6175 /**
6176  * Handles dblclick events on the DataTable instance.
6177  *
6178  * @method _onTableDblclick
6179  * @param e {HTMLEvent} The dblclick event.
6180  * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
6181  * @private
6182  */
6183 _onTableDblclick : function(e, oSelf) {
6184     var elTarget = Ev.getTarget(e);
6185     var elTag = elTarget.nodeName.toLowerCase();
6186     var bKeepBubbling = true;
6187     while(elTarget && (elTag != "table")) {
6188         switch(elTag) {
6189             case "body":
6190                 return;
6191             case "td":
6192                 bKeepBubbling = oSelf.fireEvent("cellDblclickEvent",{target:elTarget,event:e});
6193                 break;
6194             case "span":
6195                 if(Dom.hasClass(elTarget, DT.CLASS_LABEL)) {
6196                     bKeepBubbling = oSelf.fireEvent("theadLabelDblclickEvent",{target:elTarget,event:e});
6197                     // Backward compatibility
6198                     bKeepBubbling = oSelf.fireEvent("headerLabelDblclickEvent",{target:elTarget,event:e});
6199                 }
6200                 break;
6201             case "th":
6202                 bKeepBubbling = oSelf.fireEvent("theadCellDblclickEvent",{target:elTarget,event:e});
6203                 // Backward compatibility
6204                 bKeepBubbling = oSelf.fireEvent("headerCellDblclickEvent",{target:elTarget,event:e});
6205                 break;
6206             case "tr":
6207                 if(elTarget.parentNode.nodeName.toLowerCase() == "thead") {
6208                     bKeepBubbling = oSelf.fireEvent("theadRowDblclickEvent",{target:elTarget,event:e});
6209                     // Backward compatibility
6210                     bKeepBubbling = oSelf.fireEvent("headerRowDblclickEvent",{target:elTarget,event:e});
6211                 }
6212                 else {
6213                     bKeepBubbling = oSelf.fireEvent("rowDblclickEvent",{target:elTarget,event:e});
6214                 }
6215                 break;
6216             default:
6217                 break;
6218         }
6219         if(bKeepBubbling === false) {
6220             return;
6221         }
6222         else {
6223             elTarget = elTarget.parentNode;
6224             if(elTarget) {
6225                 elTag = elTarget.nodeName.toLowerCase();
6226             }
6227         }
6228     }
6229     oSelf.fireEvent("tableDblclickEvent",{target:(elTarget || oSelf._elContainer),event:e});
6230 },
6231 /**
6232  * Handles keydown events on the THEAD element.
6233  *
6234  * @method _onTheadKeydown
6235  * @param e {HTMLEvent} The key event.
6236  * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
6237  * @private
6238  */
6239 _onTheadKeydown : function(e, oSelf) {
6240     var elTarget = Ev.getTarget(e);
6241     var elTag = elTarget.nodeName.toLowerCase();
6242     var bKeepBubbling = true;
6243     while(elTarget && (elTag != "table")) {
6244         switch(elTag) {
6245             case "body":
6246                 return;
6247             case "input":
6248             case "textarea":
6249                 // TODO: implement textareaKeyEvent
6250                 break;
6251             case "thead":
6252                 bKeepBubbling = oSelf.fireEvent("theadKeyEvent",{target:elTarget,event:e});
6253                 break;
6254             default:
6255                 break;
6256         }
6257         if(bKeepBubbling === false) {
6258             return;
6259         }
6260         else {
6261             elTarget = elTarget.parentNode;
6262             if(elTarget) {
6263                 elTag = elTarget.nodeName.toLowerCase();
6264             }
6265         }
6266     }
6267     oSelf.fireEvent("tableKeyEvent",{target:(elTarget || oSelf._elContainer),event:e});
6268 },
6269
6270 /**
6271  * Handles keydown events on the TBODY element. Handles selection behavior,
6272  * provides hooks for ENTER to edit functionality.
6273  *
6274  * @method _onTbodyKeydown
6275  * @param e {HTMLEvent} The key event.
6276  * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
6277  * @private
6278  */
6279 _onTbodyKeydown : function(e, oSelf) {
6280     var sMode = oSelf.get("selectionMode");
6281
6282     if(sMode == "standard") {
6283         oSelf._handleStandardSelectionByKey(e);
6284     }
6285     else if(sMode == "single") {
6286         oSelf._handleSingleSelectionByKey(e);
6287     }
6288     else if(sMode == "cellblock") {
6289         oSelf._handleCellBlockSelectionByKey(e);
6290     }
6291     else if(sMode == "cellrange") {
6292         oSelf._handleCellRangeSelectionByKey(e);
6293     }
6294     else if(sMode == "singlecell") {
6295         oSelf._handleSingleCellSelectionByKey(e);
6296     }
6297     
6298     if(oSelf._oCellEditor) {
6299         if(oSelf._oCellEditor.fireEvent) {
6300             oSelf._oCellEditor.fireEvent("blurEvent", {editor: oSelf._oCellEditor});
6301         }
6302         else if(oSelf._oCellEditor.isActive) {
6303             oSelf.fireEvent("editorBlurEvent", {editor:oSelf._oCellEditor});
6304         }
6305     }
6306
6307     var elTarget = Ev.getTarget(e);
6308     var elTag = elTarget.nodeName.toLowerCase();
6309     var bKeepBubbling = true;
6310     while(elTarget && (elTag != "table")) {
6311         switch(elTag) {
6312             case "body":
6313                 return;
6314             case "tbody":
6315                 bKeepBubbling = oSelf.fireEvent("tbodyKeyEvent",{target:elTarget,event:e});
6316                 break;
6317             default:
6318                 break;
6319         }
6320         if(bKeepBubbling === false) {
6321             return;
6322         }
6323         else {
6324             elTarget = elTarget.parentNode;
6325             if(elTarget) {
6326                 elTag = elTarget.nodeName.toLowerCase();
6327             }
6328         }
6329     }
6330     oSelf.fireEvent("tableKeyEvent",{target:(elTarget || oSelf._elContainer),event:e});
6331 },
6332
6333 /**
6334  * Handles keypress events on the TABLE. Mainly to support stopEvent on Mac.
6335  *
6336  * @method _onTableKeypress
6337  * @param e {HTMLEvent} The key event.
6338  * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
6339  * @private
6340  */
6341 _onTableKeypress : function(e, oSelf) {
6342     if(ua.opera || (navigator.userAgent.toLowerCase().indexOf("mac") !== -1) && (ua.webkit < 420)) {
6343         var nKey = Ev.getCharCode(e);
6344         // arrow down
6345         if(nKey == 40) {
6346             Ev.stopEvent(e);
6347         }
6348         // arrow up
6349         else if(nKey == 38) {
6350             Ev.stopEvent(e);
6351         }
6352     }
6353 },
6354
6355 /**
6356  * Handles click events on the THEAD element.
6357  *
6358  * @method _onTheadClick
6359  * @param e {HTMLEvent} The click event.
6360  * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
6361  * @private
6362  */
6363 _onTheadClick : function(e, oSelf) {
6364     // This blurs the CellEditor
6365     if(oSelf._oCellEditor) {
6366         if(oSelf._oCellEditor.fireEvent) {
6367             oSelf._oCellEditor.fireEvent("blurEvent", {editor: oSelf._oCellEditor});
6368         }
6369         // Backward compatibility
6370         else if(oSelf._oCellEditor.isActive) {
6371             oSelf.fireEvent("editorBlurEvent", {editor:oSelf._oCellEditor});
6372         }
6373     }
6374
6375     var elTarget = Ev.getTarget(e),
6376         elTag = elTarget.nodeName.toLowerCase(),
6377         bKeepBubbling = true;
6378     while(elTarget && (elTag != "table")) {
6379         switch(elTag) {
6380             case "body":
6381                 return;
6382             case "input":
6383                 var sType = elTarget.type.toLowerCase();
6384                 if(sType == "checkbox") {
6385                     bKeepBubbling = oSelf.fireEvent("theadCheckboxClickEvent",{target:elTarget,event:e});
6386                 }
6387                 else if(sType == "radio") {
6388                     bKeepBubbling = oSelf.fireEvent("theadRadioClickEvent",{target:elTarget,event:e});
6389                 }
6390                 else if((sType == "button") || (sType == "image") || (sType == "submit") || (sType == "reset")) {
6391                     bKeepBubbling = oSelf.fireEvent("theadButtonClickEvent",{target:elTarget,event:e});
6392                 }
6393                 break;
6394             case "a":
6395                 bKeepBubbling = oSelf.fireEvent("theadLinkClickEvent",{target:elTarget,event:e});
6396                 break;
6397             case "button":
6398                 bKeepBubbling = oSelf.fireEvent("theadButtonClickEvent",{target:elTarget,event:e});
6399                 break;
6400             case "span":
6401                 if(Dom.hasClass(elTarget, DT.CLASS_LABEL)) {
6402                     bKeepBubbling = oSelf.fireEvent("theadLabelClickEvent",{target:elTarget,event:e});
6403                     // Backward compatibility
6404                     bKeepBubbling = oSelf.fireEvent("headerLabelClickEvent",{target:elTarget,event:e});
6405                 }
6406                 break;
6407             case "th":
6408                 bKeepBubbling = oSelf.fireEvent("theadCellClickEvent",{target:elTarget,event:e});
6409                 // Backward compatibility
6410                 bKeepBubbling = oSelf.fireEvent("headerCellClickEvent",{target:elTarget,event:e});
6411                 break;
6412             case "tr":
6413                 bKeepBubbling = oSelf.fireEvent("theadRowClickEvent",{target:elTarget,event:e});
6414                 // Backward compatibility
6415                 bKeepBubbling = oSelf.fireEvent("headerRowClickEvent",{target:elTarget,event:e});
6416                 break;
6417             default:
6418                 break;
6419         }
6420         if(bKeepBubbling === false) {
6421             return;
6422         }
6423         else {
6424             elTarget = elTarget.parentNode;
6425             if(elTarget) {
6426                 elTag = elTarget.nodeName.toLowerCase();
6427             }
6428         }
6429     }
6430     oSelf.fireEvent("tableClickEvent",{target:(elTarget || oSelf._elContainer),event:e});
6431 },
6432
6433 /**
6434  * Handles click events on the primary TBODY element.
6435  *
6436  * @method _onTbodyClick
6437  * @param e {HTMLEvent} The click event.
6438  * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
6439  * @private
6440  */
6441 _onTbodyClick : function(e, oSelf) {
6442     // This blurs the CellEditor
6443     if(oSelf._oCellEditor) {
6444         if(oSelf._oCellEditor.fireEvent) {
6445             oSelf._oCellEditor.fireEvent("blurEvent", {editor: oSelf._oCellEditor});
6446         }
6447         else if(oSelf._oCellEditor.isActive) {
6448             oSelf.fireEvent("editorBlurEvent", {editor:oSelf._oCellEditor});
6449         }
6450     }
6451
6452     // Fire Custom Events
6453     var elTarget = Ev.getTarget(e),
6454         elTag = elTarget.nodeName.toLowerCase(),
6455         bKeepBubbling = true;
6456     while(elTarget && (elTag != "table")) {
6457         switch(elTag) {
6458             case "body":
6459                 return;
6460             case "input":
6461                 var sType = elTarget.type.toLowerCase();
6462                 if(sType == "checkbox") {
6463                     bKeepBubbling = oSelf.fireEvent("checkboxClickEvent",{target:elTarget,event:e});
6464                 }
6465                 else if(sType == "radio") {
6466                     bKeepBubbling = oSelf.fireEvent("radioClickEvent",{target:elTarget,event:e});
6467                 }
6468                 else if((sType == "button") || (sType == "image") || (sType == "submit") || (sType == "reset")) {
6469                     bKeepBubbling = oSelf.fireEvent("buttonClickEvent",{target:elTarget,event:e});
6470                 }
6471                 break;
6472             case "a":
6473                 bKeepBubbling = oSelf.fireEvent("linkClickEvent",{target:elTarget,event:e});
6474                 break;
6475             case "button":
6476                 bKeepBubbling = oSelf.fireEvent("buttonClickEvent",{target:elTarget,event:e});
6477                 break;
6478             case "td":
6479                 bKeepBubbling = oSelf.fireEvent("cellClickEvent",{target:elTarget,event:e});
6480                 break;
6481             case "tr":
6482                 bKeepBubbling = oSelf.fireEvent("rowClickEvent",{target:elTarget,event:e});
6483                 break;
6484             default:
6485                 break;
6486         }
6487         if(bKeepBubbling === false) {
6488             return;
6489         }
6490         else {
6491             elTarget = elTarget.parentNode;
6492             if(elTarget) {
6493                 elTag = elTarget.nodeName.toLowerCase();
6494             }
6495         }
6496     }
6497     oSelf.fireEvent("tableClickEvent",{target:(elTarget || oSelf._elContainer),event:e});
6498 },
6499
6500 /**
6501  * Handles change events on SELECT elements within DataTable.
6502  *
6503  * @method _onDropdownChange
6504  * @param e {HTMLEvent} The change event.
6505  * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
6506  * @private
6507  */
6508 _onDropdownChange : function(e, oSelf) {
6509     var elTarget = Ev.getTarget(e);
6510     oSelf.fireEvent("dropdownChangeEvent", {event:e, target:elTarget});
6511 },
6512
6513
6514
6515
6516
6517
6518
6519
6520
6521
6522
6523
6524
6525
6526
6527
6528
6529
6530
6531
6532
6533
6534
6535
6536
6537
6538
6539
6540
6541
6542
6543
6544 /////////////////////////////////////////////////////////////////////////////
6545 //
6546 // Public member variables
6547 //
6548 /////////////////////////////////////////////////////////////////////////////
6549 /**
6550  * Returns object literal of initial configs.
6551  *
6552  * @property configs
6553  * @type Object
6554  * @default {} 
6555  */
6556 configs: null,
6557
6558
6559 /////////////////////////////////////////////////////////////////////////////
6560 //
6561 // Public methods
6562 //
6563 /////////////////////////////////////////////////////////////////////////////
6564
6565 /**
6566  * Returns unique id assigned to instance, which is a useful prefix for
6567  * generating unique DOM ID strings.
6568  *
6569  * @method getId
6570  * @return {String} Unique ID of the DataSource instance.
6571  */
6572 getId : function() {
6573     return this._sId;
6574 },
6575
6576 /**
6577  * DataSource instance name, for logging.
6578  *
6579  * @method toString
6580  * @return {String} Unique name of the DataSource instance.
6581  */
6582
6583 toString : function() {
6584     return "DataTable instance " + this._sId;
6585 },
6586
6587 /**
6588  * Returns the DataTable instance's DataSource instance.
6589  *
6590  * @method getDataSource
6591  * @return {YAHOO.util.DataSource} DataSource instance.
6592  */
6593 getDataSource : function() {
6594     return this._oDataSource;
6595 },
6596
6597 /**
6598  * Returns the DataTable instance's ColumnSet instance.
6599  *
6600  * @method getColumnSet
6601  * @return {YAHOO.widget.ColumnSet} ColumnSet instance.
6602  */
6603 getColumnSet : function() {
6604     return this._oColumnSet;
6605 },
6606
6607 /**
6608  * Returns the DataTable instance's RecordSet instance.
6609  *
6610  * @method getRecordSet
6611  * @return {YAHOO.widget.RecordSet} RecordSet instance.
6612  */
6613 getRecordSet : function() {
6614     return this._oRecordSet;
6615 },
6616
6617 /**
6618  * Returns on object literal representing the DataTable instance's current
6619  * state with the following properties:
6620  * <dl>
6621  * <dt>pagination</dt>
6622  * <dd>Instance of YAHOO.widget.Paginator</dd>
6623  *
6624  * <dt>sortedBy</dt>
6625  * <dd>
6626  *     <dl>
6627  *         <dt>sortedBy.key</dt>
6628  *         <dd>{String} Key of sorted Column</dd>
6629  *         <dt>sortedBy.dir</dt>
6630  *         <dd>{String} Initial sort direction, either YAHOO.widget.DataTable.CLASS_ASC or YAHOO.widget.DataTable.CLASS_DESC</dd>
6631  *     </dl>
6632  * </dd>
6633  *
6634  * <dt>selectedRows</dt>
6635  * <dd>Array of selected rows by Record ID.</dd>
6636  *
6637  * <dt>selectedCells</dt>
6638  * <dd>Selected cells as an array of object literals:
6639  *     {recordId:sRecordId, columnKey:sColumnKey}</dd>
6640  * </dl>
6641  *  
6642  * @method getState
6643  * @return {Object} DataTable instance state object literal values.
6644  */
6645 getState : function() {
6646     return {
6647         totalRecords: this.get('paginator') ? this.get('paginator').get("totalRecords") : this._oRecordSet.getLength(),
6648         pagination: this.get("paginator") ? this.get("paginator").getState() : null,
6649         sortedBy: this.get("sortedBy"),
6650         selectedRows: this.getSelectedRows(),
6651         selectedCells: this.getSelectedCells()
6652     };
6653 },
6654
6655
6656
6657
6658
6659
6660
6661
6662
6663
6664
6665
6666
6667
6668
6669
6670
6671
6672
6673
6674
6675
6676
6677
6678
6679
6680
6681
6682
6683
6684
6685
6686
6687
6688
6689
6690
6691
6692
6693
6694
6695
6696
6697 // DOM ACCESSORS
6698
6699 /**
6700  * Returns DOM reference to the DataTable's container element.
6701  *
6702  * @method getContainerEl
6703  * @return {HTMLElement} Reference to DIV element.
6704  */
6705 getContainerEl : function() {
6706     return this._elContainer;
6707 },
6708
6709 /**
6710  * Returns DOM reference to the DataTable's TABLE element.
6711  *
6712  * @method getTableEl
6713  * @return {HTMLElement} Reference to TABLE element.
6714  */
6715 getTableEl : function() {
6716     return this._elTable;
6717 },
6718
6719 /**
6720  * Returns DOM reference to the DataTable's THEAD element.
6721  *
6722  * @method getTheadEl
6723  * @return {HTMLElement} Reference to THEAD element.
6724  */
6725 getTheadEl : function() {
6726     return this._elThead;
6727 },
6728
6729 /**
6730  * Returns DOM reference to the DataTable's primary TBODY element.
6731  *
6732  * @method getTbodyEl
6733  * @return {HTMLElement} Reference to TBODY element.
6734  */
6735 getTbodyEl : function() {
6736     return this._elTbody;
6737 },
6738
6739 /**
6740  * Returns DOM reference to the DataTable's secondary TBODY element that is
6741  * used to display messages.
6742  *
6743  * @method getMsgTbodyEl
6744  * @return {HTMLElement} Reference to TBODY element.
6745  */
6746 getMsgTbodyEl : function() {
6747     return this._elMsgTbody;
6748 },
6749
6750 /**
6751  * Returns DOM reference to the TD element within the secondary TBODY that is
6752  * used to display messages.
6753  *
6754  * @method getMsgTdEl
6755  * @return {HTMLElement} Reference to TD element.
6756  */
6757 getMsgTdEl : function() {
6758     return this._elMsgTd;
6759 },
6760
6761 /**
6762  * Returns the corresponding TR reference for a given DOM element, ID string or
6763  * directly page row index. If the given identifier is a child of a TR element,
6764  * then DOM tree is traversed until a parent TR element is returned, otherwise
6765  * null.
6766  *
6767  * @method getTrEl
6768  * @param row {HTMLElement | String | Number | YAHOO.widget.Record} Which row to
6769  * get: by element reference, ID string, page row index, or Record.
6770  * @return {HTMLElement} Reference to TR element, or null.
6771  */
6772 getTrEl : function(row) {
6773     // By Record
6774     if(row instanceof YAHOO.widget.Record) {
6775         return document.getElementById(row.getId());
6776     }
6777     // By page row index
6778     else if(lang.isNumber(row)) {
6779         var allRows = this._elTbody.rows;
6780         return ((row > -1) && (row < allRows.length)) ? allRows[row] : null;
6781     }
6782     // By ID string or element reference
6783     else {
6784         var elRow = (lang.isString(row)) ? document.getElementById(row) : row;
6785
6786         // Validate HTML element
6787         if(elRow && (elRow.ownerDocument == document)) {
6788             // Validate TR element
6789             if(elRow.nodeName.toLowerCase() != "tr") {
6790                 // Traverse up the DOM to find the corresponding TR element
6791                 elRow = Dom.getAncestorByTagName(elRow,"tr");
6792             }
6793
6794             return elRow;
6795         }
6796     }
6797
6798     return null;
6799 },
6800
6801 /**
6802  * Returns DOM reference to the first TR element in the DataTable page, or null.
6803  *
6804  * @method getFirstTrEl
6805  * @return {HTMLElement} Reference to TR element.
6806  */
6807 getFirstTrEl : function() {
6808     return this._elTbody.rows[0] || null;
6809 },
6810
6811 /**
6812  * Returns DOM reference to the last TR element in the DataTable page, or null.
6813  *
6814  * @method getLastTrEl
6815  * @return {HTMLElement} Reference to last TR element.
6816  */
6817 getLastTrEl : function() {
6818     var allRows = this._elTbody.rows;
6819         if(allRows.length > 0) {
6820             return allRows[allRows.length-1] || null;
6821         }
6822 },
6823
6824 /**
6825  * Returns DOM reference to the next TR element from the given TR element, or null.
6826  *
6827  * @method getNextTrEl
6828  * @param row {HTMLElement | String | Number | YAHOO.widget.Record} Element
6829  * reference, ID string, page row index, or Record from which to get next TR element.
6830  * @return {HTMLElement} Reference to next TR element.
6831  */
6832 getNextTrEl : function(row) {
6833     var nThisTrIndex = this.getTrIndex(row);
6834     if(nThisTrIndex !== null) {
6835         var allRows = this._elTbody.rows;
6836         if(nThisTrIndex < allRows.length-1) {
6837             return allRows[nThisTrIndex+1];
6838         }
6839     }
6840
6841     YAHOO.log("Could not get next TR element for row " + row, "info", this.toString());
6842     return null;
6843 },
6844
6845 /**
6846  * Returns DOM reference to the previous TR element from the given TR element, or null.
6847  *
6848  * @method getPreviousTrEl
6849  * @param row {HTMLElement | String | Number | YAHOO.widget.Record} Element
6850  * reference, ID string, page row index, or Record from which to get previous TR element.
6851  * @return {HTMLElement} Reference to previous TR element.
6852  */
6853 getPreviousTrEl : function(row) {
6854     var nThisTrIndex = this.getTrIndex(row);
6855     if(nThisTrIndex !== null) {
6856         var allRows = this._elTbody.rows;
6857         if(nThisTrIndex > 0) {
6858             return allRows[nThisTrIndex-1];
6859         }
6860     }
6861
6862     YAHOO.log("Could not get previous TR element for row " + row, "info", this.toString());
6863     return null;
6864 },
6865
6866 /**
6867  * Returns DOM reference to a TD liner element.
6868  *
6869  * @method getTdLinerEl
6870  * @param cell {HTMLElement | Object} TD element or child of a TD element, or
6871  * object literal of syntax {record:oRecord, column:oColumn}.
6872  * @return {HTMLElement} Reference to TD liner element.
6873  */
6874 getTdLinerEl : function(cell) {
6875     var elCell = this.getTdEl(cell);
6876     return elCell.firstChild || null;
6877 },
6878
6879 /**
6880  * Returns DOM reference to a TD element.
6881  *
6882  * @method getTdEl
6883  * @param cell {HTMLElement | String | Object} TD element or child of a TD element, or
6884  * object literal of syntax {record:oRecord, column:oColumn}.
6885  * @return {HTMLElement} Reference to TD element.
6886  */
6887 getTdEl : function(cell) {
6888     var elCell;
6889     var el = Dom.get(cell);
6890
6891     // Validate HTML element
6892     if(el && (el.ownerDocument == document)) {
6893         // Validate TD element
6894         if(el.nodeName.toLowerCase() != "td") {
6895             // Traverse up the DOM to find the corresponding TR element
6896             elCell = Dom.getAncestorByTagName(el, "td");
6897         }
6898         else {
6899             elCell = el;
6900         }
6901         
6902         // Make sure the TD is in this TBODY
6903         // Bug 2527707 and bug 2263558
6904         if(elCell && ((elCell.parentNode.parentNode == this._elTbody) || (elCell.parentNode.parentNode === null))) {
6905             // Now we can return the TD element
6906             return elCell;
6907         }
6908     }
6909     else if(cell) {
6910         var oRecord, nColKeyIndex;
6911
6912         if(lang.isString(cell.columnKey) && lang.isString(cell.recordId)) {
6913             oRecord = this.getRecord(cell.recordId);
6914             var oColumn = this.getColumn(cell.columnKey);
6915             if(oColumn) {
6916                 nColKeyIndex = oColumn.getKeyIndex();
6917             }
6918
6919         }
6920         if(cell.record && cell.column && cell.column.getKeyIndex) {
6921             oRecord = cell.record;
6922             nColKeyIndex = cell.column.getKeyIndex();
6923         }
6924         var elRow = this.getTrEl(oRecord);
6925         if((nColKeyIndex !== null) && elRow && elRow.cells && elRow.cells.length > 0) {
6926             return elRow.cells[nColKeyIndex] || null;
6927         }
6928     }
6929
6930     return null;
6931 },
6932
6933 /**
6934  * Returns DOM reference to the first TD element in the DataTable page (by default),
6935  * the first TD element of the optionally given row, or null.
6936  *
6937  * @method getFirstTdEl
6938  * @param row {HTMLElement} (optional) row from which to get first TD
6939  * @return {HTMLElement} Reference to TD element.
6940  */
6941 getFirstTdEl : function(row) {
6942     var elRow = this.getTrEl(row) || this.getFirstTrEl();
6943     if(elRow && (elRow.cells.length > 0)) {
6944         return elRow.cells[0];
6945     }
6946     YAHOO.log("Could not get first TD element for row " + elRow, "info", this.toString());
6947     return null;
6948 },
6949
6950 /**
6951  * Returns DOM reference to the last TD element in the DataTable page (by default),
6952  * the first TD element of the optionally given row, or null.
6953  *
6954  * @method getLastTdEl
6955  * @return {HTMLElement} Reference to last TD element.
6956  */
6957 getLastTdEl : function(row) {
6958     var elRow = this.getTrEl(row) || this.getLastTrEl();
6959     if(elRow && (elRow.cells.length > 0)) {
6960         return elRow.cells[elRow.cells.length-1];
6961     }
6962     YAHOO.log("Could not get last TD element for row " + elRow, "info", this.toString());
6963     return null;
6964 },
6965
6966 /**
6967  * Returns DOM reference to the next TD element from the given cell, or null.
6968  *
6969  * @method getNextTdEl
6970  * @param cell {HTMLElement | String | Object} DOM element reference or string ID, or
6971  * object literal of syntax {record:oRecord, column:oColumn} from which to get next TD element.
6972  * @return {HTMLElement} Reference to next TD element, or null.
6973  */
6974 getNextTdEl : function(cell) {
6975     var elCell = this.getTdEl(cell);
6976     if(elCell) {
6977         var nThisTdIndex = elCell.cellIndex;
6978         var elRow = this.getTrEl(elCell);
6979         if(nThisTdIndex < elRow.cells.length-1) {
6980             return elRow.cells[nThisTdIndex+1];
6981         }
6982         else {
6983             var elNextRow = this.getNextTrEl(elRow);
6984             if(elNextRow) {
6985                 return elNextRow.cells[0];
6986             }
6987         }
6988     }
6989     YAHOO.log("Could not get next TD element for cell " + cell, "info", this.toString());
6990     return null;
6991 },
6992
6993 /**
6994  * Returns DOM reference to the previous TD element from the given cell, or null.
6995  *
6996  * @method getPreviousTdEl
6997  * @param cell {HTMLElement | String | Object} DOM element reference or string ID, or
6998  * object literal of syntax {record:oRecord, column:oColumn} from which to get previous TD element.
6999  * @return {HTMLElement} Reference to previous TD element, or null.
7000  */
7001 getPreviousTdEl : function(cell) {
7002     var elCell = this.getTdEl(cell);
7003     if(elCell) {
7004         var nThisTdIndex = elCell.cellIndex;
7005         var elRow = this.getTrEl(elCell);
7006         if(nThisTdIndex > 0) {
7007             return elRow.cells[nThisTdIndex-1];
7008         }
7009         else {
7010             var elPreviousRow = this.getPreviousTrEl(elRow);
7011             if(elPreviousRow) {
7012                 return this.getLastTdEl(elPreviousRow);
7013             }
7014         }
7015     }
7016     YAHOO.log("Could not get next TD element for cell " + cell, "info", this.toString());
7017     return null;
7018 },
7019
7020 /**
7021  * Returns DOM reference to the above TD element from the given cell, or null.
7022  *
7023  * @method getAboveTdEl
7024  * @param cell {HTMLElement | String | Object} DOM element reference or string ID, or
7025  * object literal of syntax {record:oRecord, column:oColumn} from which to get next TD element.
7026  * @return {HTMLElement} Reference to next TD element, or null.
7027  */
7028 getAboveTdEl : function(cell) {
7029     var elCell = this.getTdEl(cell);
7030     if(elCell) {
7031         var elPreviousRow = this.getPreviousTrEl(elCell);
7032         if(elPreviousRow) {
7033             return elPreviousRow.cells[elCell.cellIndex];
7034         }
7035     }
7036     YAHOO.log("Could not get above TD element for cell " + cell, "info", this.toString());
7037     return null;
7038 },
7039
7040 /**
7041  * Returns DOM reference to the below TD element from the given cell, or null.
7042  *
7043  * @method getBelowTdEl
7044  * @param cell {HTMLElement | String | Object} DOM element reference or string ID, or
7045  * object literal of syntax {record:oRecord, column:oColumn} from which to get previous TD element.
7046  * @return {HTMLElement} Reference to previous TD element, or null.
7047  */
7048 getBelowTdEl : function(cell) {
7049     var elCell = this.getTdEl(cell);
7050     if(elCell) {
7051         var elNextRow = this.getNextTrEl(elCell);
7052         if(elNextRow) {
7053             return elNextRow.cells[elCell.cellIndex];
7054         }
7055     }
7056     YAHOO.log("Could not get below TD element for cell " + cell, "info", this.toString());
7057     return null;
7058 },
7059
7060 /**
7061  * Returns DOM reference to a TH liner element. Needed to normalize for resizeable 
7062  * Columns, which have an additional resizer liner DIV element between the TH
7063  * element and the liner DIV element. 
7064  *
7065  * @method getThLinerEl
7066  * @param theadCell {YAHOO.widget.Column | HTMLElement | String} Column instance,
7067  * DOM element reference, or string ID.
7068  * @return {HTMLElement} Reference to TH liner element.
7069  */
7070 getThLinerEl : function(theadCell) {
7071     var oColumn = this.getColumn(theadCell);
7072     return (oColumn) ? oColumn.getThLinerEl() : null;
7073 },
7074
7075 /**
7076  * Returns DOM reference to a TH element.
7077  *
7078  * @method getThEl
7079  * @param theadCell {YAHOO.widget.Column | HTMLElement | String} Column instance,
7080  * DOM element reference, or string ID.
7081  * @return {HTMLElement} Reference to TH element.
7082  */
7083 getThEl : function(theadCell) {
7084     var elTh;
7085
7086     // Validate Column instance
7087     if(theadCell instanceof YAHOO.widget.Column) {
7088         var oColumn = theadCell;
7089         elTh = oColumn.getThEl();
7090         if(elTh) {
7091             return elTh;
7092         }
7093     }
7094     // Validate HTML element
7095     else {
7096         var el = Dom.get(theadCell);
7097
7098         if(el && (el.ownerDocument == document)) {
7099             // Validate TH element
7100             if(el.nodeName.toLowerCase() != "th") {
7101                 // Traverse up the DOM to find the corresponding TR element
7102                 elTh = Dom.getAncestorByTagName(el,"th");
7103             }
7104             else {
7105                 elTh = el;
7106             }
7107
7108             return elTh;
7109         }
7110     }
7111
7112     return null;
7113 },
7114
7115 /**
7116  * Returns the page row index of given row. Returns null if the row is not on the
7117  * current DataTable page.
7118  *
7119  * @method getTrIndex
7120  * @param row {HTMLElement | String | YAHOO.widget.Record | Number} DOM or ID
7121  * string reference to an element within the DataTable page, a Record instance,
7122  * or a Record's RecordSet index.
7123  * @return {Number} Page row index, or null if row does not exist or is not on current page.
7124  */
7125 getTrIndex : function(row) {
7126     var nRecordIndex;
7127
7128     // By Record
7129     if(row instanceof YAHOO.widget.Record) {
7130         nRecordIndex = this._oRecordSet.getRecordIndex(row);
7131         if(nRecordIndex === null) {
7132             // Not a valid Record
7133             return null;
7134         }
7135     }
7136     // Calculate page row index from Record index
7137     else if(lang.isNumber(row)) {
7138         nRecordIndex = row;
7139     }
7140     if(lang.isNumber(nRecordIndex)) {
7141         // Validate the number
7142         if((nRecordIndex > -1) && (nRecordIndex < this._oRecordSet.getLength())) {
7143             // DataTable is paginated
7144             var oPaginator = this.get('paginator');
7145             if(oPaginator) {
7146                 // Check the record index is within the indices of the
7147                 // current page
7148                 var rng = oPaginator.getPageRecords();
7149                 if (rng && nRecordIndex >= rng[0] && nRecordIndex <= rng[1]) {
7150                     // This Record is on current page
7151                     return nRecordIndex - rng[0];
7152                 }
7153                 // This Record is not on current page
7154                 else {
7155                     return null;
7156                 }
7157             }
7158             // Not paginated, just return the Record index
7159             else {
7160                 return nRecordIndex;
7161             }
7162         }
7163         // RecordSet index is out of range
7164         else {
7165             return null;
7166         }
7167     }
7168     // By element reference or ID string
7169     else {
7170         // Validate TR element
7171         var elRow = this.getTrEl(row);
7172         if(elRow && (elRow.ownerDocument == document) &&
7173                 (elRow.parentNode == this._elTbody)) {
7174             return elRow.sectionRowIndex;
7175         }
7176     }
7177
7178     YAHOO.log("Could not get page row index for row " + row, "info", this.toString());
7179     return null;
7180 },
7181
7182
7183
7184
7185
7186
7187
7188
7189
7190
7191
7192
7193
7194
7195
7196
7197
7198
7199
7200
7201
7202
7203
7204
7205
7206
7207
7208
7209
7210
7211
7212
7213
7214
7215
7216
7217
7218
7219
7220
7221
7222
7223
7224
7225
7226
7227 // TABLE FUNCTIONS
7228
7229 /**
7230  * Resets a RecordSet with the given data and populates the page view
7231  * with the new data. Any previous data, and selection and sort states are
7232  * cleared. New data should be added as a separate step. 
7233  *
7234  * @method initializeTable
7235  */
7236 initializeTable : function() {
7237     // Reset init flag
7238     this._bInit = true;
7239     
7240     // Clear the RecordSet
7241     this._oRecordSet.reset();
7242
7243     // Clear the Paginator's totalRecords if paginating
7244     var pag = this.get('paginator');
7245     if (pag) {
7246         pag.set('totalRecords',0);
7247     }
7248
7249     // Clear selections
7250     this._unselectAllTrEls();
7251     this._unselectAllTdEls();
7252     this._aSelections = null;
7253     this._oAnchorRecord = null;
7254     this._oAnchorCell = null;
7255     
7256     // Clear sort
7257     this.set("sortedBy", null);
7258 },
7259
7260 /**
7261  * Internal wrapper calls run() on render Chain instance.
7262  *
7263  * @method _runRenderChain
7264  * @private 
7265  */
7266 _runRenderChain : function() {
7267     this._oChainRender.run();
7268 },
7269
7270 /**
7271  * Renders the view with existing Records from the RecordSet while
7272  * maintaining sort, pagination, and selection states. For performance, reuses
7273  * existing DOM elements when possible while deleting extraneous elements.
7274  *
7275  * @method render
7276  */
7277 render : function() {
7278 //YAHOO.example.Performance.trialStart = new Date();
7279
7280     this._oChainRender.stop();
7281
7282     this.fireEvent("beforeRenderEvent");
7283     YAHOO.log("DataTable rendering...", "info", this.toString());
7284
7285     var i, j, k, len, allRecords;
7286
7287     var oPaginator = this.get('paginator');
7288     // Paginator is enabled, show a subset of Records and update Paginator UI
7289     if(oPaginator) {
7290         allRecords = this._oRecordSet.getRecords(
7291                         oPaginator.getStartIndex(),
7292                         oPaginator.getRowsPerPage());
7293     }
7294     // Not paginated, show all records
7295     else {
7296         allRecords = this._oRecordSet.getRecords();
7297     }
7298
7299     // From the top, update in-place existing rows, so as to reuse DOM elements
7300     var elTbody = this._elTbody,
7301         loopN = this.get("renderLoopSize"),
7302         nRecordsLength = allRecords.length;
7303     
7304     // Table has rows
7305     if(nRecordsLength > 0) {                
7306         elTbody.style.display = "none";
7307         while(elTbody.lastChild) {
7308             elTbody.removeChild(elTbody.lastChild);
7309         }
7310         elTbody.style.display = "";
7311
7312         // Set up the loop Chain to render rows
7313         this._oChainRender.add({
7314             method: function(oArg) {
7315                 if((this instanceof DT) && this._sId) {
7316                     var i = oArg.nCurrentRecord,
7317                         endRecordIndex = ((oArg.nCurrentRecord+oArg.nLoopLength) > nRecordsLength) ?
7318                                 nRecordsLength : (oArg.nCurrentRecord+oArg.nLoopLength),
7319                         elRow, nextSibling;
7320
7321                     elTbody.style.display = "none";
7322                     
7323                     for(; i<endRecordIndex; i++) {
7324                         elRow = Dom.get(allRecords[i].getId());
7325                         elRow = elRow || this._addTrEl(allRecords[i]);
7326                         nextSibling = elTbody.childNodes[i] || null;
7327                         elTbody.insertBefore(elRow, nextSibling);
7328                     }
7329                     elTbody.style.display = "";
7330                     
7331                     // Set up for the next loop
7332                     oArg.nCurrentRecord = i;
7333                 }
7334             },
7335             scope: this,
7336             iterations: (loopN > 0) ? Math.ceil(nRecordsLength/loopN) : 1,
7337             argument: {
7338                 nCurrentRecord: 0,//nRecordsLength-1,  // Start at first Record
7339                 nLoopLength: (loopN > 0) ? loopN : nRecordsLength
7340             },
7341             timeout: (loopN > 0) ? 0 : -1
7342         });
7343         
7344         // Post-render tasks
7345         this._oChainRender.add({
7346             method: function(oArg) {
7347                 if((this instanceof DT) && this._sId) {
7348                     while(elTbody.rows.length > nRecordsLength) {
7349                         elTbody.removeChild(elTbody.lastChild);
7350                     }
7351                     this._setFirstRow();
7352                     this._setLastRow();
7353                     this._setRowStripes();
7354                     this._setSelections();
7355                 }
7356             },
7357             scope: this,
7358             timeout: (loopN > 0) ? 0 : -1
7359         });
7360      
7361     }
7362     // Table has no rows
7363     else {
7364         // Set up the loop Chain to delete rows
7365         var nTotal = elTbody.rows.length;
7366         if(nTotal > 0) {
7367             this._oChainRender.add({
7368                 method: function(oArg) {
7369                     if((this instanceof DT) && this._sId) {
7370                         var i = oArg.nCurrent,
7371                             loopN = oArg.nLoopLength,
7372                             nIterEnd = (i - loopN < 0) ? -1 : i - loopN;
7373     
7374                         elTbody.style.display = "none";
7375                         
7376                         for(; i>nIterEnd; i--) {
7377                             elTbody.deleteRow(-1);
7378                         }
7379                         elTbody.style.display = "";
7380                         
7381                         // Set up for the next loop
7382                         oArg.nCurrent = i;
7383                     }
7384                 },
7385                 scope: this,
7386                 iterations: (loopN > 0) ? Math.ceil(nTotal/loopN) : 1,
7387                 argument: {
7388                     nCurrent: nTotal, 
7389                     nLoopLength: (loopN > 0) ? loopN : nTotal
7390                 },
7391                 timeout: (loopN > 0) ? 0 : -1
7392             });
7393         }
7394     }
7395     this._runRenderChain();
7396 },
7397
7398 /**
7399  * Disables DataTable UI.
7400  *
7401  * @method disable
7402  */
7403 disable : function() {
7404     var elTable = this._elTable;
7405     var elMask = this._elMask;
7406     elMask.style.width = elTable.offsetWidth + "px";
7407     elMask.style.height = elTable.offsetHeight + "px";
7408     elMask.style.display = "";
7409     this.fireEvent("disableEvent");
7410 },
7411
7412 /**
7413  * Undisables DataTable UI.
7414  *
7415  * @method undisable
7416  */
7417 undisable : function() {
7418     this._elMask.style.display = "none";
7419     this.fireEvent("undisableEvent");
7420 },
7421
7422 /**
7423  * Nulls out the entire DataTable instance and related objects, removes attached
7424  * event listeners, and clears out DOM elements inside the container. After
7425  * calling this method, the instance reference should be expliclitly nulled by
7426  * implementer, as in myDataTable = null. Use with caution!
7427  *
7428  * @method destroy
7429  */
7430 destroy : function() {
7431     // Store for later
7432     var instanceName = this.toString();
7433
7434     this._oChainRender.stop();
7435     
7436     // Destroy static resizer proxy and column proxy
7437     DT._destroyColumnDragTargetEl();
7438     DT._destroyColumnResizerProxyEl();
7439     
7440     // Destroy ColumnDD and ColumnResizers
7441     this._destroyColumnHelpers();
7442     
7443     // Destroy all CellEditors
7444     var oCellEditor;
7445     for(var i=0, len=this._oColumnSet.flat.length; i<len; i++) {
7446         oCellEditor = this._oColumnSet.flat[i].editor;
7447         if(oCellEditor && oCellEditor.destroy) {
7448             oCellEditor.destroy();
7449             this._oColumnSet.flat[i].editor = null;
7450         }
7451     }
7452
7453     // Destroy Paginator
7454     this._destroyPaginator();
7455
7456     // Unhook custom events
7457     this._oRecordSet.unsubscribeAll();
7458     this.unsubscribeAll();
7459
7460     // Unhook DOM events
7461     Ev.removeListener(document, "click", this._onDocumentClick);
7462     
7463     // Clear out the container
7464     this._destroyContainerEl(this._elContainer);
7465
7466     // Null out objects
7467     for(var param in this) {
7468         if(lang.hasOwnProperty(this, param)) {
7469             this[param] = null;
7470         }
7471     }
7472     
7473     // Clean up static values
7474     DT._nCurrentCount--;
7475     
7476     if(DT._nCurrentCount < 1) {
7477         if(DT._elDynStyleNode) {
7478             document.getElementsByTagName('head')[0].removeChild(DT._elDynStyleNode);
7479             DT._elDynStyleNode = null;
7480         }
7481     }
7482
7483     YAHOO.log("DataTable instance destroyed: " + instanceName);
7484 },
7485
7486 /**
7487  * Displays message within secondary TBODY.
7488  *
7489  * @method showTableMessage
7490  * @param sHTML {String} (optional) Value for innerHTMlang.
7491  * @param sClassName {String} (optional) Classname.
7492  */
7493 showTableMessage : function(sHTML, sClassName) {
7494     var elCell = this._elMsgTd;
7495     if(lang.isString(sHTML)) {
7496         elCell.firstChild.innerHTML = sHTML;
7497     }
7498     if(lang.isString(sClassName)) {
7499         elCell.className = sClassName;
7500     }
7501
7502     this._elMsgTbody.style.display = "";
7503
7504     this.fireEvent("tableMsgShowEvent", {html:sHTML, className:sClassName});
7505     YAHOO.log("DataTable showing message: " + sHTML, "info", this.toString());
7506 },
7507
7508 /**
7509  * Hides secondary TBODY.
7510  *
7511  * @method hideTableMessage
7512  */
7513 hideTableMessage : function() {
7514     if(this._elMsgTbody.style.display != "none") {
7515         this._elMsgTbody.style.display = "none";
7516         this._elMsgTbody.parentNode.style.width = "";
7517         this.fireEvent("tableMsgHideEvent");
7518         YAHOO.log("DataTable message hidden", "info", this.toString());
7519     }
7520 },
7521
7522 /**
7523  * Brings focus to the TBODY element. Alias to focusTbodyEl.
7524  *
7525  * @method focus
7526  */
7527 focus : function() {
7528     this.focusTbodyEl();
7529 },
7530
7531 /**
7532  * Brings focus to the THEAD element.
7533  *
7534  * @method focusTheadEl
7535  */
7536 focusTheadEl : function() {
7537     this._focusEl(this._elThead);
7538 },
7539
7540 /**
7541  * Brings focus to the TBODY element.
7542  *
7543  * @method focusTbodyEl
7544  */
7545 focusTbodyEl : function() {
7546     this._focusEl(this._elTbody);
7547 },
7548
7549 /**
7550  * Setting display:none on DataTable or any parent may impact width validations.
7551  * After setting display back to "", implementers should call this method to 
7552  * manually perform those validations.
7553  *
7554  * @method onShow
7555  */
7556 onShow : function() {
7557     this.validateColumnWidths();
7558     
7559     for(var allKeys = this._oColumnSet.keys, i=0, len=allKeys.length, col; i<len; i++) {
7560         col = allKeys[i];
7561         if(col._ddResizer) {
7562             col._ddResizer.resetResizerEl();
7563         }
7564     }
7565 },
7566
7567
7568
7569
7570
7571
7572
7573
7574
7575
7576
7577
7578
7579
7580
7581
7582
7583
7584
7585
7586
7587
7588
7589
7590
7591
7592
7593
7594
7595
7596
7597
7598
7599
7600
7601
7602
7603
7604
7605
7606
7607
7608
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 // RECORDSET FUNCTIONS
7634
7635 /**
7636  * Returns Record index for given TR element or page row index.
7637  *
7638  * @method getRecordIndex
7639  * @param row {YAHOO.widget.Record | HTMLElement | Number} Record instance, TR
7640  * element reference or page row index.
7641  * @return {Number} Record's RecordSet index, or null.
7642  */
7643 getRecordIndex : function(row) {
7644     var nTrIndex;
7645
7646     if(!lang.isNumber(row)) {
7647         // By Record
7648         if(row instanceof YAHOO.widget.Record) {
7649             return this._oRecordSet.getRecordIndex(row);
7650         }
7651         // By element reference
7652         else {
7653             // Find the TR element
7654             var el = this.getTrEl(row);
7655             if(el) {
7656                 nTrIndex = el.sectionRowIndex;
7657             }
7658         }
7659     }
7660     // By page row index
7661     else {
7662         nTrIndex = row;
7663     }
7664
7665     if(lang.isNumber(nTrIndex)) {
7666         var oPaginator = this.get("paginator");
7667         if(oPaginator) {
7668             return oPaginator.get('recordOffset') + nTrIndex;
7669         }
7670         else {
7671             return nTrIndex;
7672         }
7673     }
7674
7675     YAHOO.log("Could not get Record index for row " + row, "info", this.toString());
7676     return null;
7677 },
7678
7679 /**
7680  * For the given identifier, returns the associated Record instance.
7681  *
7682  * @method getRecord
7683  * @param row {HTMLElement | Number | String} DOM reference to a TR element (or
7684  * child of a TR element), RecordSet position index, or Record ID.
7685  * @return {YAHOO.widget.Record} Record instance.
7686  */
7687 getRecord : function(row) {
7688     var oRecord = this._oRecordSet.getRecord(row);
7689
7690     if(!oRecord) {
7691         // Validate TR element
7692         var elRow = this.getTrEl(row);
7693         if(elRow) {
7694             oRecord = this._oRecordSet.getRecord(elRow.id);
7695         }
7696     }
7697
7698     if(oRecord instanceof YAHOO.widget.Record) {
7699         return this._oRecordSet.getRecord(oRecord);
7700     }
7701     else {
7702         YAHOO.log("Could not get Record for row at " + row, "info", this.toString());
7703         return null;
7704     }
7705 },
7706
7707
7708
7709
7710
7711
7712
7713
7714
7715
7716
7717
7718
7719
7720
7721
7722
7723
7724
7725
7726
7727
7728
7729
7730
7731
7732
7733
7734
7735
7736
7737
7738
7739
7740
7741
7742
7743
7744
7745
7746
7747
7748
7749
7750
7751
7752 // COLUMN FUNCTIONS
7753
7754 /**
7755  * For the given identifier, returns the associated Column instance. Note: For
7756  * getting Columns by Column ID string, please use the method getColumnById().
7757  *
7758  * @method getColumn
7759  * @param column {HTMLElement | String | Number} TH/TD element (or child of a
7760  * TH/TD element), a Column key, or a ColumnSet key index.
7761  * @return {YAHOO.widget.Column} Column instance.
7762  */
7763 getColumn : function(column) {
7764     var oColumn = this._oColumnSet.getColumn(column);
7765
7766     if(!oColumn) {
7767         // Validate TD element
7768         var elCell = this.getTdEl(column);
7769         if(elCell) {
7770             oColumn = this._oColumnSet.getColumn(elCell.cellIndex);
7771         }
7772         // Validate TH element
7773         else {
7774             elCell = this.getThEl(column);
7775             if(elCell) {
7776                 // Find by TH el ID
7777                 var allColumns = this._oColumnSet.flat;
7778                 for(var i=0, len=allColumns.length; i<len; i++) {
7779                     if(allColumns[i].getThEl().id === elCell.id) {
7780                         oColumn = allColumns[i];
7781                     } 
7782                 }
7783             }
7784         }
7785     }
7786     if(!oColumn) {
7787         YAHOO.log("Could not get Column for column at " + column, "info", this.toString());
7788     }
7789     return oColumn;
7790 },
7791
7792 /**
7793  * For the given Column ID, returns the associated Column instance. Note: For
7794  * getting Columns by key, please use the method getColumn().
7795  *
7796  * @method getColumnById
7797  * @param column {String} Column ID string.
7798  * @return {YAHOO.widget.Column} Column instance.
7799  */
7800 getColumnById : function(column) {
7801     return this._oColumnSet.getColumnById(column);
7802 },
7803
7804 /**
7805  * For the given Column instance, returns next direction to sort.
7806  *
7807  * @method getColumnSortDir
7808  * @param oColumn {YAHOO.widget.Column} Column instance.
7809  * @param oSortedBy {Object} (optional) Specify the state, or use current state. 
7810  * @return {String} YAHOO.widget.DataTable.CLASS_ASC or YAHOO.widget.DataTableCLASS_DESC.
7811  */
7812 getColumnSortDir : function(oColumn, oSortedBy) {
7813     // Backward compatibility
7814     if(oColumn.sortOptions && oColumn.sortOptions.defaultOrder) {
7815         if(oColumn.sortOptions.defaultOrder == "asc") {
7816             oColumn.sortOptions.defaultDir = DT.CLASS_ASC;
7817         }
7818         else if (oColumn.sortOptions.defaultOrder == "desc") {
7819             oColumn.sortOptions.defaultDir = DT.CLASS_DESC;
7820         }
7821     }
7822     
7823     // What is the Column's default sort direction?
7824     var sortDir = (oColumn.sortOptions && oColumn.sortOptions.defaultDir) ? oColumn.sortOptions.defaultDir : DT.CLASS_ASC;
7825
7826     // Is the Column currently sorted?
7827     var bSorted = false;
7828     oSortedBy = oSortedBy || this.get("sortedBy");
7829     if(oSortedBy && (oSortedBy.key === oColumn.key)) {
7830         bSorted = true;
7831         if(oSortedBy.dir) {
7832             sortDir = (oSortedBy.dir === DT.CLASS_ASC) ? DT.CLASS_DESC : DT.CLASS_ASC;
7833         }
7834         else {
7835             sortDir = (sortDir === DT.CLASS_ASC) ? DT.CLASS_DESC : DT.CLASS_ASC;
7836         }
7837     }
7838     return sortDir;
7839 },
7840
7841 /**
7842  * Overridable method gives implementers a hook to show loading message before
7843  * sorting Column.
7844  *
7845  * @method doBeforeSortColumn
7846  * @param oColumn {YAHOO.widget.Column} Column instance.
7847  * @param sSortDir {String} YAHOO.widget.DataTable.CLASS_ASC or
7848  * YAHOO.widget.DataTable.CLASS_DESC.
7849  * @return {Boolean} Return true to continue sorting Column.
7850  */
7851 doBeforeSortColumn : function(oColumn, sSortDir) {
7852     this.showTableMessage(this.get("MSG_LOADING"), DT.CLASS_LOADING);
7853     return true;
7854 },
7855
7856 /**
7857  * Sorts given Column. If "dynamicData" is true, current selections are purged before
7858  * a request is sent to the DataSource for data for the new state (using the
7859  * request returned by "generateRequest()").
7860  *
7861  * @method sortColumn
7862  * @param oColumn {YAHOO.widget.Column} Column instance.
7863  * @param sDir {String} (Optional) YAHOO.widget.DataTable.CLASS_ASC or
7864  * YAHOO.widget.DataTable.CLASS_DESC
7865  */
7866 sortColumn : function(oColumn, sDir) {
7867     if(oColumn && (oColumn instanceof YAHOO.widget.Column)) {
7868         if(!oColumn.sortable) {
7869             Dom.addClass(this.getThEl(oColumn), DT.CLASS_SORTABLE);
7870         }
7871         
7872         // Validate given direction
7873         if(sDir && (sDir !== DT.CLASS_ASC) && (sDir !== DT.CLASS_DESC)) {
7874             sDir = null;
7875         }
7876         
7877         // Get the sort dir
7878         var sSortDir = sDir || this.getColumnSortDir(oColumn);
7879
7880         // Is the Column currently sorted?
7881         var oSortedBy = this.get("sortedBy") || {};
7882         var bSorted = (oSortedBy.key === oColumn.key) ? true : false;
7883
7884         var ok = this.doBeforeSortColumn(oColumn, sSortDir);
7885         if(ok) {
7886             // Server-side sort
7887             if(this.get("dynamicData")) {
7888                 // Get current state
7889                 var oState = this.getState();
7890                 
7891                 // Reset record offset, if paginated
7892                 if(oState.pagination) {
7893                     oState.pagination.recordOffset = 0;
7894                 }
7895                 
7896                 // Update sortedBy to new values
7897                 oState.sortedBy = {
7898                     key: oColumn.key,
7899                     dir: sSortDir
7900                 };
7901                 
7902                 // Get the request for the new state
7903                 var request = this.get("generateRequest")(oState, this);
7904
7905                 // Purge selections
7906                 this.unselectAllRows();
7907                 this.unselectAllCells();
7908
7909                 // Send request for new data
7910                 var callback = {
7911                     success : this.onDataReturnSetRows,
7912                     failure : this.onDataReturnSetRows,
7913                     argument : oState, // Pass along the new state to the callback
7914                     scope : this
7915                 };
7916                 this._oDataSource.sendRequest(request, callback);            
7917             }
7918             // Client-side sort
7919             else {
7920                 // Is there a custom sort handler function defined?
7921                 var sortFnc = (oColumn.sortOptions && lang.isFunction(oColumn.sortOptions.sortFunction)) ?
7922                         // Custom sort function
7923                         oColumn.sortOptions.sortFunction : null;
7924                    
7925                 // Sort the Records
7926                 if(!bSorted || sDir || sortFnc) {
7927                     // Shortcut for the frequently-used compare method
7928                     var compare = YAHOO.util.Sort.compare;
7929
7930                     // Default sort function if necessary
7931                     sortFnc = sortFnc || 
7932                         function(a, b, desc, field) {
7933                             var sorted = compare(a.getData(field),b.getData(field), desc);
7934                             if(sorted === 0) {
7935                                 return compare(a.getCount(),b.getCount(), desc); // Bug 1932978
7936                             }
7937                             else {
7938                                 return sorted;
7939                             }
7940                         };
7941
7942                     // Get the field to sort
7943                     var sField = (oColumn.sortOptions && oColumn.sortOptions.field) ? oColumn.sortOptions.field : oColumn.field;
7944
7945                     // Sort the Records        
7946                     this._oRecordSet.sortRecords(sortFnc, ((sSortDir == DT.CLASS_DESC) ? true : false), sField);
7947                 }
7948                 // Just reverse the Records
7949                 else {
7950                     this._oRecordSet.reverseRecords();
7951                 }
7952         
7953                 // Reset to first page if paginated
7954                 var oPaginator = this.get('paginator');
7955                 if (oPaginator) {
7956                     // Set page silently, so as not to fire change event.
7957                     oPaginator.setPage(1,true);
7958                 }
7959         
7960                 // Update UI via sortedBy
7961                 this.render();
7962                 this.set("sortedBy", {key:oColumn.key, dir:sSortDir, column:oColumn}); 
7963             }       
7964             
7965             this.fireEvent("columnSortEvent",{column:oColumn,dir:sSortDir});
7966             YAHOO.log("Column \"" + oColumn.key + "\" sorted \"" + sSortDir + "\"", "info", this.toString());
7967             return;
7968         }
7969     }
7970     YAHOO.log("Could not sort Column \"" + oColumn.key + "\"", "warn", this.toString());
7971 },
7972
7973 /**
7974  * Sets given Column to given pixel width. If new width is less than minimum
7975  * width, sets to minimum width. Updates oColumn.width value.
7976  *
7977  * @method setColumnWidth
7978  * @param oColumn {YAHOO.widget.Column} Column instance.
7979  * @param nWidth {Number} New width in pixels. A null value auto-sizes Column,
7980  * subject to minWidth and maxAutoWidth validations. 
7981  */
7982 setColumnWidth : function(oColumn, nWidth) {
7983     if(!(oColumn instanceof YAHOO.widget.Column)) {
7984         oColumn = this.getColumn(oColumn);
7985     }
7986     if(oColumn) {
7987         // Validate new width against minimum width
7988         if(lang.isNumber(nWidth)) {
7989             // This is why we must require a Number... :-|
7990             nWidth = (nWidth > oColumn.minWidth) ? nWidth : oColumn.minWidth;
7991
7992             // Save state
7993             oColumn.width = nWidth;
7994             
7995             // Resize the DOM elements
7996             this._setColumnWidth(oColumn, nWidth+"px");
7997             
7998             this.fireEvent("columnSetWidthEvent",{column:oColumn,width:nWidth});
7999             YAHOO.log("Set width of Column " + oColumn + " to " + nWidth + "px", "info", this.toString());
8000         }
8001         // Unsets a width to auto-size
8002         else if(nWidth === null) {
8003             // Save state
8004             oColumn.width = nWidth;
8005             
8006             // Resize the DOM elements
8007             this._setColumnWidth(oColumn, "auto");
8008             this.validateColumnWidths(oColumn);
8009             this.fireEvent("columnUnsetWidthEvent",{column:oColumn});
8010             YAHOO.log("Column " + oColumn + " width unset", "info", this.toString());
8011         }
8012                 
8013         // Bug 2339454: resize then sort misaligment
8014         this._clearTrTemplateEl();
8015     }
8016     else {
8017         YAHOO.log("Could not set width of Column " + oColumn + " to " + nWidth + "px", "warn", this.toString());
8018     }
8019 },
8020
8021 /**
8022  * Sets liner DIV elements of given Column to given width. When value should be
8023  * auto-calculated to fit content overflow is set to visible, otherwise overflow
8024  * is set to hidden. No validations against minimum width and no updating
8025  * Column.width value.
8026  *
8027  * @method _setColumnWidth
8028  * @param oColumn {YAHOO.widget.Column} Column instance.
8029  * @param sWidth {String} New width value.
8030  * @param sOverflow {String} Should be "hidden" when Column width is explicitly
8031  * being set to a value, but should be "visible" when Column is meant to auto-fit content.  
8032  * @private
8033  */
8034 _setColumnWidth : function(oColumn, sWidth, sOverflow) {
8035     if(oColumn && (oColumn.getKeyIndex() !== null)) {
8036         sOverflow = sOverflow || (((sWidth === '') || (sWidth === 'auto')) ? 'visible' : 'hidden');
8037     
8038         // Dynamic style algorithm
8039         if(!DT._bDynStylesFallback) {
8040             this._setColumnWidthDynStyles(oColumn, sWidth, sOverflow);
8041         }
8042         // Dynamic function algorithm
8043         else {
8044             this._setColumnWidthDynFunction(oColumn, sWidth, sOverflow);
8045         }
8046     }
8047     else {
8048         YAHOO.log("Could not set width of unknown Column " + oColumn + " to " + sWidth, "warn", this.toString());
8049     }
8050 },
8051
8052 /**
8053  * Updates width of a Column's liner DIV elements by dynamically creating a
8054  * STYLE node and writing and updating CSS style rules to it. If this fails during
8055  * runtime, the fallback method _setColumnWidthDynFunction() will be called.
8056  * Notes: This technique is not performant in IE6. IE7 crashes if DataTable is
8057  * nested within another TABLE element. For these cases, it is recommended to
8058  * use the method _setColumnWidthDynFunction by setting _bDynStylesFallback to TRUE.
8059  *
8060  * @method _setColumnWidthDynStyles
8061  * @param oColumn {YAHOO.widget.Column} Column instance.
8062  * @param sWidth {String} New width value.
8063  * @private
8064  */
8065 _setColumnWidthDynStyles : function(oColumn, sWidth, sOverflow) {
8066     var s = DT._elDynStyleNode,
8067         rule;
8068     
8069     // Create a new STYLE node
8070     if(!s) {
8071         s = document.createElement('style');
8072         s.type = 'text/css';
8073         s = document.getElementsByTagName('head').item(0).appendChild(s);
8074         DT._elDynStyleNode = s;
8075     }
8076     
8077     // We have a STYLE node to update
8078     if(s) {
8079         // Use unique classname for this Column instance as a hook for resizing
8080         var sClassname = "." + this.getId() + "-col-" + oColumn.getSanitizedKey() + " ." + DT.CLASS_LINER;
8081         
8082         // Hide for performance
8083         if(this._elTbody) {
8084             this._elTbody.style.display = 'none';
8085         }
8086         
8087         rule = DT._oDynStyles[sClassname];
8088
8089         // The Column does not yet have a rule
8090         if(!rule) {
8091             if(s.styleSheet && s.styleSheet.addRule) {
8092                 s.styleSheet.addRule(sClassname,"overflow:"+sOverflow);
8093                 s.styleSheet.addRule(sClassname,'width:'+sWidth);
8094                 rule = s.styleSheet.rules[s.styleSheet.rules.length-1];
8095                 DT._oDynStyles[sClassname] = rule;
8096             }
8097             else if(s.sheet && s.sheet.insertRule) {
8098                 s.sheet.insertRule(sClassname+" {overflow:"+sOverflow+";width:"+sWidth+";}",s.sheet.cssRules.length);
8099                 rule = s.sheet.cssRules[s.sheet.cssRules.length-1];
8100                 DT._oDynStyles[sClassname] = rule;
8101             }
8102         }
8103         // We have a rule to update
8104         else {
8105             rule.style.overflow = sOverflow;
8106             rule.style.width = sWidth;
8107         } 
8108         
8109         // Unhide
8110         if(this._elTbody) {
8111             this._elTbody.style.display = '';
8112         }
8113     }
8114     
8115     // That was not a success, we must call the fallback routine
8116     if(!rule) {
8117         DT._bDynStylesFallback = true;
8118         this._setColumnWidthDynFunction(oColumn, sWidth);
8119     }
8120 },
8121
8122 /**
8123  * Updates width of a Column's liner DIV elements by dynamically creating a
8124  * function to update all element style properties in one pass. Note: This
8125  * technique is not supported in sandboxed environments that prohibit EVALs.    
8126  *
8127  * @method _setColumnWidthDynFunction
8128  * @param oColumn {YAHOO.widget.Column} Column instance.
8129  * @param sWidth {String} New width value.
8130  * @private
8131  */
8132 _setColumnWidthDynFunction : function(oColumn, sWidth, sOverflow) {
8133     // TODO: why is this here?
8134     if(sWidth == 'auto') {
8135         sWidth = ''; 
8136     }
8137     
8138     // Create one function for each value of rows.length
8139     var rowslen = this._elTbody ? this._elTbody.rows.length : 0;
8140     
8141     // Dynamically create the function
8142     if (!this._aDynFunctions[rowslen]) {
8143         
8144         //Compile a custom function to do all the liner div width
8145         //assignments at the same time.  A unique function is required
8146         //for each unique number of rows in _elTbody.  This will
8147         //result in a function declaration like:
8148         //function (oColumn,sWidth,sOverflow) {
8149         //    var colIdx = oColumn.getKeyIndex();
8150         //    oColumn.getThLinerEl().style.overflow =
8151         //    this._elTbody.rows[0].cells[colIdx].firstChild.style.overflow =
8152         //    this._elTbody.rows[1].cells[colIdx].firstChild.style.overflow =
8153         //    ... (for all row indices in this._elTbody.rows.length - 1)
8154         //    this._elTbody.rows[99].cells[colIdx].firstChild.style.overflow =
8155         //    sOverflow;
8156         //    oColumn.getThLinerEl().style.width =
8157         //    this._elTbody.rows[0].cells[colIdx].firstChild.style.width =
8158         //    this._elTbody.rows[1].cells[colIdx].firstChild.style.width =
8159         //    ... (for all row indices in this._elTbody.rows.length - 1)
8160         //    this._elTbody.rows[99].cells[colIdx].firstChild.style.width =
8161         //    sWidth;
8162         //}
8163         
8164         var i,j,k;
8165         var resizerDef = [
8166             'var colIdx=oColumn.getKeyIndex();',
8167             'oColumn.getThLinerEl().style.overflow='
8168         ];
8169         for (i=rowslen-1, j=2; i >= 0; --i) {
8170             resizerDef[j++] = 'this._elTbody.rows[';
8171             resizerDef[j++] = i;
8172             resizerDef[j++] = '].cells[colIdx].firstChild.style.overflow=';
8173         }
8174         resizerDef[j] = 'sOverflow;';
8175         resizerDef[j+1] = 'oColumn.getThLinerEl().style.width=';
8176         for (i=rowslen-1, k=j+2; i >= 0; --i) {
8177             resizerDef[k++] = 'this._elTbody.rows[';
8178             resizerDef[k++] = i;
8179             resizerDef[k++] = '].cells[colIdx].firstChild.style.width=';
8180         }
8181         resizerDef[k] = 'sWidth;';
8182         this._aDynFunctions[rowslen] =
8183             new Function('oColumn','sWidth','sOverflow',resizerDef.join(''));
8184     }
8185     
8186     // Get the function to execute
8187     var resizerFn = this._aDynFunctions[rowslen];
8188
8189     // TODO: Hide TBODY for performance in _setColumnWidthDynFunction?
8190     if (resizerFn) {
8191         resizerFn.call(this,oColumn,sWidth,sOverflow);
8192     }
8193 },
8194
8195 /**
8196  * For one or all Columns, when Column is not hidden, width is not set, and minWidth
8197  * and/or maxAutoWidth is set, validates auto-width against minWidth and maxAutoWidth.
8198  *
8199  * @method validateColumnWidths
8200  * @param oArg.column {YAHOO.widget.Column} (optional) One Column to validate. If null, all Columns' widths are validated.
8201  */
8202 validateColumnWidths : function(oColumn) {
8203     var elColgroup = this._elColgroup;
8204     var elColgroupClone = elColgroup.cloneNode(true);
8205     var bNeedsValidation = false;
8206     var allKeys = this._oColumnSet.keys;
8207     var elThLiner;
8208     // Validate just one Column's minWidth and/or maxAutoWidth
8209     if(oColumn && !oColumn.hidden && !oColumn.width && (oColumn.getKeyIndex() !== null)) {
8210             elThLiner = oColumn.getThLinerEl();
8211             if((oColumn.minWidth > 0) && (elThLiner.offsetWidth < oColumn.minWidth)) {
8212                 elColgroupClone.childNodes[oColumn.getKeyIndex()].style.width = 
8213                         oColumn.minWidth + 
8214                         (parseInt(Dom.getStyle(elThLiner,"paddingLeft"),10)|0) +
8215                         (parseInt(Dom.getStyle(elThLiner,"paddingRight"),10)|0) + "px";
8216                 bNeedsValidation = true;
8217             }
8218             else if((oColumn.maxAutoWidth > 0) && (elThLiner.offsetWidth > oColumn.maxAutoWidth)) {
8219                 this._setColumnWidth(oColumn, oColumn.maxAutoWidth+"px", "hidden");
8220             }
8221     }
8222     // Validate all Columns
8223     else {
8224         for(var i=0, len=allKeys.length; i<len; i++) {
8225             oColumn = allKeys[i];
8226             if(!oColumn.hidden && !oColumn.width) {
8227                 elThLiner = oColumn.getThLinerEl();
8228                 if((oColumn.minWidth > 0) && (elThLiner.offsetWidth < oColumn.minWidth)) {
8229                     elColgroupClone.childNodes[i].style.width = 
8230                             oColumn.minWidth + 
8231                             (parseInt(Dom.getStyle(elThLiner,"paddingLeft"),10)|0) +
8232                             (parseInt(Dom.getStyle(elThLiner,"paddingRight"),10)|0) + "px";
8233                     bNeedsValidation = true;
8234                 }
8235                 else if((oColumn.maxAutoWidth > 0) && (elThLiner.offsetWidth > oColumn.maxAutoWidth)) {
8236                     this._setColumnWidth(oColumn, oColumn.maxAutoWidth+"px", "hidden");
8237                 }
8238             }
8239         }
8240     }
8241     if(bNeedsValidation) {
8242         elColgroup.parentNode.replaceChild(elColgroupClone, elColgroup);
8243         this._elColgroup = elColgroupClone;
8244     }
8245 },
8246
8247 /**
8248  * Clears minWidth.
8249  *
8250  * @method _clearMinWidth
8251  * @param oColumn {YAHOO.widget.Column} Which Column.
8252  * @private
8253  */
8254 _clearMinWidth : function(oColumn) {
8255     if(oColumn.getKeyIndex() !== null) {
8256         this._elColgroup.childNodes[oColumn.getKeyIndex()].style.width = '';
8257     }
8258 },
8259
8260 /**
8261  * Restores minWidth.
8262  *
8263  * @method _restoreMinWidth
8264  * @param oColumn {YAHOO.widget.Column} Which Column.
8265  * @private
8266  */
8267 _restoreMinWidth : function(oColumn) {
8268     if(oColumn.minWidth && (oColumn.getKeyIndex() !== null)) {
8269         this._elColgroup.childNodes[oColumn.getKeyIndex()].style.width = oColumn.minWidth + 'px';
8270     }
8271 },
8272
8273 /**
8274  * Hides given Column. NOTE: You cannot hide/show nested Columns. You can only
8275  * hide/show non-nested Columns, and top-level parent Columns (which will
8276  * hide/show all children Columns).
8277  *
8278  * @method hideColumn
8279  * @param oColumn {YAHOO.widget.Column} Column instance.
8280  */
8281 hideColumn : function(oColumn) {
8282     if(!(oColumn instanceof YAHOO.widget.Column)) {
8283         oColumn = this.getColumn(oColumn);
8284     }
8285     // Only top-level Columns can get hidden due to issues in FF2 and SF3
8286     if(oColumn && !oColumn.hidden && oColumn.getTreeIndex() !== null) {
8287         
8288         var allrows = this.getTbodyEl().rows;
8289         var l = allrows.length;
8290         var allDescendants = this._oColumnSet.getDescendants(oColumn);
8291         
8292         // Hide each nested Column
8293         for(var i=0; i<allDescendants.length; i++) {
8294             var thisColumn = allDescendants[i];
8295             thisColumn.hidden = true;
8296
8297             // Style the head cell
8298             Dom.addClass(thisColumn.getThEl(), DT.CLASS_HIDDEN);
8299             
8300             // Does this Column have body cells?
8301             var thisKeyIndex = thisColumn.getKeyIndex();
8302             if(thisKeyIndex !== null) {                    
8303                 // Clear minWidth
8304                 this._clearMinWidth(oColumn);
8305                 
8306                 // Style the body cells
8307                 for(var j=0;j<l;j++) {
8308                     Dom.addClass(allrows[j].cells[thisKeyIndex],DT.CLASS_HIDDEN);
8309                 }
8310             }
8311             
8312             this.fireEvent("columnHideEvent",{column:thisColumn});
8313             YAHOO.log("Column \"" + oColumn.key + "\" hidden", "info", this.toString());
8314         }
8315       
8316         this._repaintOpera();
8317         this._clearTrTemplateEl();
8318     }
8319     else {
8320         YAHOO.log("Could not hide Column \"" + lang.dump(oColumn) + "\". Only non-nested Columns can be hidden", "warn", this.toString());
8321     }
8322 },
8323
8324 /**
8325  * Shows given Column. NOTE: You cannot hide/show nested Columns. You can only
8326  * hide/show non-nested Columns, and top-level parent Columns (which will
8327  * hide/show all children Columns).
8328  *
8329  * @method showColumn
8330  * @param oColumn {YAHOO.widget.Column} Column instance.
8331  */
8332 showColumn : function(oColumn) {
8333     if(!(oColumn instanceof YAHOO.widget.Column)) {
8334         oColumn = this.getColumn(oColumn);
8335     }
8336     // Only top-level Columns can get hidden
8337     if(oColumn && oColumn.hidden && (oColumn.getTreeIndex() !== null)) {
8338         var allrows = this.getTbodyEl().rows;
8339         var l = allrows.length;
8340         var allDescendants = this._oColumnSet.getDescendants(oColumn);
8341         
8342         // Show each nested Column
8343         for(var i=0; i<allDescendants.length; i++) {
8344             var thisColumn = allDescendants[i];
8345             thisColumn.hidden = false;
8346             
8347             // Unstyle the head cell
8348             Dom.removeClass(thisColumn.getThEl(), DT.CLASS_HIDDEN);
8349
8350             // Does this Column have body cells?
8351             var thisKeyIndex = thisColumn.getKeyIndex();
8352             if(thisKeyIndex !== null) {
8353                 // Restore minWidth
8354                 this._restoreMinWidth(oColumn);
8355                 
8356             
8357                 // Unstyle the body cells
8358                 for(var j=0;j<l;j++) {
8359                     Dom.removeClass(allrows[j].cells[thisKeyIndex],DT.CLASS_HIDDEN);
8360                 }
8361             }
8362
8363             this.fireEvent("columnShowEvent",{column:thisColumn});
8364             YAHOO.log("Column \"" + oColumn.key + "\" shown", "info", this.toString());
8365         }
8366         this._clearTrTemplateEl();
8367     }
8368     else {
8369         YAHOO.log("Could not show Column \"" + lang.dump(oColumn) + "\". Only non-nested Columns can be shown", "warn", this.toString());
8370     }
8371 },
8372
8373 /**
8374  * Removes given Column. NOTE: You cannot remove nested Columns. You can only remove
8375  * non-nested Columns, and top-level parent Columns (which will remove all
8376  * children Columns).
8377  *
8378  * @method removeColumn
8379  * @param oColumn {YAHOO.widget.Column} Column instance.
8380  * @return oColumn {YAHOO.widget.Column} Removed Column instance.
8381  */
8382 removeColumn : function(oColumn) {
8383     // Validate Column
8384     if(!(oColumn instanceof YAHOO.widget.Column)) {
8385         oColumn = this.getColumn(oColumn);
8386     }
8387     if(oColumn) {
8388         var nColTreeIndex = oColumn.getTreeIndex();
8389         if(nColTreeIndex !== null) {
8390             // Which key index(es)
8391             var i, len,
8392                 aKeyIndexes = oColumn.getKeyIndex();
8393             // Must be a parent Column
8394             if(aKeyIndexes === null) {
8395                 var descKeyIndexes = [];
8396                 var allDescendants = this._oColumnSet.getDescendants(oColumn);
8397                 for(i=0, len=allDescendants.length; i<len; i++) {
8398                     // Is this descendant a key Column?
8399                     var thisKey = allDescendants[i].getKeyIndex();
8400                     if(thisKey !== null) {
8401                         descKeyIndexes[descKeyIndexes.length] = thisKey;
8402                     }
8403                 }
8404                 if(descKeyIndexes.length > 0) {
8405                     aKeyIndexes = descKeyIndexes;
8406                 }
8407             }
8408             // Must be a key Column
8409             else {
8410                 aKeyIndexes = [aKeyIndexes];
8411             }
8412             
8413             if(aKeyIndexes !== null) {
8414                 // Sort the indexes so we can remove from the right
8415                 aKeyIndexes.sort(function(a, b) {return YAHOO.util.Sort.compare(a, b);});
8416                 
8417                 // Destroy previous THEAD
8418                 this._destroyTheadEl();
8419     
8420                 // Create new THEAD
8421                 var aOrigColumnDefs = this._oColumnSet.getDefinitions();
8422                 oColumn = aOrigColumnDefs.splice(nColTreeIndex,1)[0];
8423                 this._initColumnSet(aOrigColumnDefs);
8424                 this._initTheadEl();
8425                 
8426                 // Remove COL
8427                 for(i=aKeyIndexes.length-1; i>-1; i--) {
8428                     this._removeColgroupColEl(aKeyIndexes[i]);
8429                 }
8430                 
8431                 // Remove TD
8432                 var allRows = this._elTbody.rows;
8433                 if(allRows.length > 0) {
8434                     var loopN = this.get("renderLoopSize"),
8435                         loopEnd = allRows.length;
8436                     this._oChainRender.add({
8437                         method: function(oArg) {
8438                             if((this instanceof DT) && this._sId) {
8439                                 var i = oArg.nCurrentRow,
8440                                     len = loopN > 0 ? Math.min(i + loopN,allRows.length) : allRows.length,
8441                                     aIndexes = oArg.aIndexes,
8442                                     j;
8443                                 for(; i < len; ++i) {
8444                                     for(j = aIndexes.length-1; j>-1; j--) {
8445                                         allRows[i].removeChild(allRows[i].childNodes[aIndexes[j]]);
8446                                     }
8447                                 }
8448                                 oArg.nCurrentRow = i;
8449                             }
8450                         },
8451                         iterations: (loopN > 0) ? Math.ceil(loopEnd/loopN) : 1,
8452                         argument: {nCurrentRow:0, aIndexes:aKeyIndexes},
8453                         scope: this,
8454                         timeout: (loopN > 0) ? 0 : -1
8455                     });
8456                     this._runRenderChain();
8457                 }
8458         
8459                 this.fireEvent("columnRemoveEvent",{column:oColumn});
8460                 YAHOO.log("Column \"" + oColumn.key + "\" removed", "info", this.toString());
8461                 return oColumn;
8462             }
8463         }
8464     }
8465     YAHOO.log("Could not remove Column \"" + oColumn.key + "\". Only non-nested Columns can be removed", "warn", this.toString());
8466 },
8467
8468 /**
8469  * Inserts given Column at the index if given, otherwise at the end. NOTE: You
8470  * can only add non-nested Columns and top-level parent Columns. You cannot add
8471  * a nested Column to an existing parent.
8472  *
8473  * @method insertColumn
8474  * @param oColumn {Object | YAHOO.widget.Column} Object literal Column
8475  * definition or a Column instance.
8476  * @param index {Number} (optional) New tree index.
8477  * @return oColumn {YAHOO.widget.Column} Inserted Column instance. 
8478  */
8479 insertColumn : function(oColumn, index) {
8480     // Validate Column
8481     if(oColumn instanceof YAHOO.widget.Column) {
8482         oColumn = oColumn.getDefinition();
8483     }
8484     else if(oColumn.constructor !== Object) {
8485         YAHOO.log("Could not insert Column \"" + oColumn + "\" due to invalid argument", "warn", this.toString());
8486         return;
8487     }
8488     
8489     // Validate index or append new Column to the end of the ColumnSet
8490     var oColumnSet = this._oColumnSet;
8491     if(!lang.isValue(index) || !lang.isNumber(index)) {
8492         index = oColumnSet.tree[0].length;
8493     }
8494     
8495     // Destroy previous THEAD
8496     this._destroyTheadEl();
8497     
8498     // Create new THEAD
8499     var aNewColumnDefs = this._oColumnSet.getDefinitions();
8500     aNewColumnDefs.splice(index, 0, oColumn);
8501     this._initColumnSet(aNewColumnDefs);
8502     this._initTheadEl();
8503     
8504     // Need to refresh the reference
8505     oColumnSet = this._oColumnSet;
8506     var oNewColumn = oColumnSet.tree[0][index];
8507     
8508     // Get key index(es) for new Column
8509     var i, len,
8510         descKeyIndexes = [];
8511     var allDescendants = oColumnSet.getDescendants(oNewColumn);
8512     for(i=0, len=allDescendants.length; i<len; i++) {
8513         // Is this descendant a key Column?
8514         var thisKey = allDescendants[i].getKeyIndex();
8515         if(thisKey !== null) {
8516             descKeyIndexes[descKeyIndexes.length] = thisKey;
8517         }
8518     }
8519     
8520     if(descKeyIndexes.length > 0) {  
8521         // Sort the indexes
8522         var newIndex = descKeyIndexes.sort(function(a, b) {return YAHOO.util.Sort.compare(a, b);})[0];
8523         
8524         // Add COL
8525         for(i=descKeyIndexes.length-1; i>-1; i--) {
8526             this._insertColgroupColEl(descKeyIndexes[i]);
8527         }
8528             
8529         // Add TD
8530         var allRows = this._elTbody.rows;
8531         if(allRows.length > 0) {
8532             var loopN = this.get("renderLoopSize"),
8533                 loopEnd = allRows.length;
8534             
8535             // Get templates for each new TD
8536             var aTdTemplates = [],
8537                 elTdTemplate;
8538             for(i=0, len=descKeyIndexes.length; i<len; i++) {
8539                 var thisKeyIndex = descKeyIndexes[i];
8540                 elTdTemplate = this._getTrTemplateEl().childNodes[i].cloneNode(true);
8541                 elTdTemplate = this._formatTdEl(this._oColumnSet.keys[thisKeyIndex], elTdTemplate, thisKeyIndex, (thisKeyIndex===this._oColumnSet.keys.length-1));
8542                 aTdTemplates[thisKeyIndex] = elTdTemplate;
8543             }
8544             
8545             this._oChainRender.add({
8546                 method: function(oArg) {
8547                     if((this instanceof DT) && this._sId) {
8548                         var i = oArg.nCurrentRow, j,
8549                             descKeyIndexes = oArg.descKeyIndexes,
8550                             len = loopN > 0 ? Math.min(i + loopN,allRows.length) : allRows.length,
8551                             nextSibling;
8552                         for(; i < len; ++i) {
8553                             nextSibling = allRows[i].childNodes[newIndex] || null;
8554                             for(j=descKeyIndexes.length-1; j>-1; j--) {
8555                                 allRows[i].insertBefore(oArg.aTdTemplates[descKeyIndexes[j]].cloneNode(true), nextSibling);
8556                             }
8557                         }
8558                         oArg.nCurrentRow = i;
8559                     }
8560                 },
8561                 iterations: (loopN > 0) ? Math.ceil(loopEnd/loopN) : 1,
8562                 argument: {nCurrentRow:0,aTdTemplates:aTdTemplates,descKeyIndexes:descKeyIndexes},
8563                 scope: this,
8564                 timeout: (loopN > 0) ? 0 : -1
8565             });
8566             this._runRenderChain(); 
8567         }
8568
8569         this.fireEvent("columnInsertEvent",{column:oColumn,index:index});
8570         YAHOO.log("Column \"" + oColumn.key + "\" inserted into index " + index, "info", this.toString());
8571         return oNewColumn;
8572     }
8573 },
8574
8575 /**
8576  * Removes given Column and inserts into given tree index. NOTE: You
8577  * can only reorder non-nested Columns and top-level parent Columns. You cannot
8578  * reorder a nested Column to an existing parent.
8579  *
8580  * @method reorderColumn
8581  * @param oColumn {YAHOO.widget.Column} Column instance.
8582  * @param index {Number} New tree index.
8583  * @return oColumn {YAHOO.widget.Column} Reordered Column instance. 
8584  */
8585 reorderColumn : function(oColumn, index) {
8586     // Validate Column and new index
8587     if(!(oColumn instanceof YAHOO.widget.Column)) {
8588         oColumn = this.getColumn(oColumn);
8589     }
8590     if(oColumn && YAHOO.lang.isNumber(index)) {
8591         var nOrigTreeIndex = oColumn.getTreeIndex();
8592         if((nOrigTreeIndex !== null) && (nOrigTreeIndex !== index)) {
8593             // Which key index(es)
8594             var i, len,
8595                 aOrigKeyIndexes = oColumn.getKeyIndex(),
8596                 allDescendants,
8597                 descKeyIndexes = [],
8598                 thisKey;
8599             // Must be a parent Column...
8600             if(aOrigKeyIndexes === null) {
8601                 allDescendants = this._oColumnSet.getDescendants(oColumn);
8602                 for(i=0, len=allDescendants.length; i<len; i++) {
8603                     // Is this descendant a key Column?
8604                     thisKey = allDescendants[i].getKeyIndex();
8605                     if(thisKey !== null) {
8606                         descKeyIndexes[descKeyIndexes.length] = thisKey;
8607                     }
8608                 }
8609                 if(descKeyIndexes.length > 0) {
8610                     aOrigKeyIndexes = descKeyIndexes;
8611                 }
8612             }
8613             // ...or else must be a key Column
8614             else {
8615                 aOrigKeyIndexes = [aOrigKeyIndexes];
8616             }
8617             
8618             if(aOrigKeyIndexes !== null) {                   
8619                 // Sort the indexes
8620                 aOrigKeyIndexes.sort(function(a, b) {return YAHOO.util.Sort.compare(a, b);});
8621                 
8622                 // Destroy previous THEAD
8623                 this._destroyTheadEl();
8624     
8625                 // Create new THEAD
8626                 var aColumnDefs = this._oColumnSet.getDefinitions();
8627                 var oColumnDef = aColumnDefs.splice(nOrigTreeIndex,1)[0];
8628                 aColumnDefs.splice(index, 0, oColumnDef);
8629                 this._initColumnSet(aColumnDefs);
8630                 this._initTheadEl();
8631                 
8632                 // Need to refresh the reference
8633                 var oNewColumn = this._oColumnSet.tree[0][index];
8634
8635                 // What are new key index(es)
8636                 var aNewKeyIndexes = oNewColumn.getKeyIndex();
8637                 // Must be a parent Column
8638                 if(aNewKeyIndexes === null) {
8639                     descKeyIndexes = [];
8640                     allDescendants = this._oColumnSet.getDescendants(oNewColumn);
8641                     for(i=0, len=allDescendants.length; i<len; i++) {
8642                         // Is this descendant a key Column?
8643                         thisKey = allDescendants[i].getKeyIndex();
8644                         if(thisKey !== null) {
8645                             descKeyIndexes[descKeyIndexes.length] = thisKey;
8646                         }
8647                     }
8648                     if(descKeyIndexes.length > 0) {
8649                         aNewKeyIndexes = descKeyIndexes;
8650                     }
8651                 }
8652                 // Must be a key Column
8653                 else {
8654                     aNewKeyIndexes = [aNewKeyIndexes];
8655                 }
8656                 
8657                 // Sort the new indexes and grab the first one for the new location
8658                 var newIndex = aNewKeyIndexes.sort(function(a, b) {return YAHOO.util.Sort.compare(a, b);})[0];
8659
8660                 // Reorder COL
8661                 this._reorderColgroupColEl(aOrigKeyIndexes, newIndex);
8662                 
8663                 // Reorder TD
8664                 var allRows = this._elTbody.rows;
8665                 if(allRows.length > 0) {
8666                     var loopN = this.get("renderLoopSize"),
8667                         loopEnd = allRows.length;
8668                     this._oChainRender.add({
8669                         method: function(oArg) {
8670                             if((this instanceof DT) && this._sId) {
8671                                 var i = oArg.nCurrentRow, j, tmpTds, nextSibling,
8672                                     len = loopN > 0 ? Math.min(i + loopN,allRows.length) : allRows.length,
8673                                     aIndexes = oArg.aIndexes, thisTr;
8674                                 // For each row
8675                                 for(; i < len; ++i) {
8676                                     tmpTds = [];
8677                                     thisTr = allRows[i];
8678                                     
8679                                     // Remove each TD
8680                                     for(j=aIndexes.length-1; j>-1; j--) {
8681                                         tmpTds.push(thisTr.removeChild(thisTr.childNodes[aIndexes[j]]));
8682                                     }
8683                                     
8684                                     // Insert each TD
8685                                     nextSibling = thisTr.childNodes[newIndex] || null;
8686                                     for(j=tmpTds.length-1; j>-1; j--) {
8687                                         thisTr.insertBefore(tmpTds[j], nextSibling);
8688                                     }                                    
8689                                 }
8690                                 oArg.nCurrentRow = i;
8691                             }
8692                         },
8693                         iterations: (loopN > 0) ? Math.ceil(loopEnd/loopN) : 1,
8694                         argument: {nCurrentRow:0, aIndexes:aOrigKeyIndexes},
8695                         scope: this,
8696                         timeout: (loopN > 0) ? 0 : -1
8697                     });
8698                     this._runRenderChain();
8699                 }
8700         
8701                 this.fireEvent("columnReorderEvent",{column:oNewColumn});
8702                 YAHOO.log("Column \"" + oNewColumn.key + "\" reordered", "info", this.toString());
8703                 return oNewColumn;
8704             }
8705         }
8706     }
8707     YAHOO.log("Could not reorder Column \"" + oColumn.key + "\". Only non-nested Columns can be reordered", "warn", this.toString());
8708 },
8709
8710 /**
8711  * Selects given Column. NOTE: You cannot select/unselect nested Columns. You can only
8712  * select/unselect non-nested Columns, and bottom-level key Columns.
8713  *
8714  * @method selectColumn
8715  * @param column {HTMLElement | String | Number} DOM reference or ID string to a
8716  * TH/TD element (or child of a TH/TD element), a Column key, or a ColumnSet key index.
8717  */
8718 selectColumn : function(oColumn) {
8719     oColumn = this.getColumn(oColumn);
8720     if(oColumn && !oColumn.selected) {
8721         // Only bottom-level Columns can get hidden
8722         if(oColumn.getKeyIndex() !== null) {
8723             oColumn.selected = true;
8724             
8725             // Update head cell
8726             var elTh = oColumn.getThEl();
8727             Dom.addClass(elTh,DT.CLASS_SELECTED);
8728
8729             // Update body cells
8730             var allRows = this.getTbodyEl().rows;
8731             var oChainRender = this._oChainRender;
8732             oChainRender.add({
8733                 method: function(oArg) {
8734                     if((this instanceof DT) && this._sId && allRows[oArg.rowIndex] && allRows[oArg.rowIndex].cells[oArg.cellIndex]) {
8735                         Dom.addClass(allRows[oArg.rowIndex].cells[oArg.cellIndex],DT.CLASS_SELECTED);                    
8736                     }
8737                     oArg.rowIndex++;
8738                 },
8739                 scope: this,
8740                 iterations: allRows.length,
8741                 argument: {rowIndex:0,cellIndex:oColumn.getKeyIndex()}
8742             });
8743
8744             this._clearTrTemplateEl();
8745             
8746             this._elTbody.style.display = "none";
8747             this._runRenderChain();
8748             this._elTbody.style.display = "";      
8749             
8750             this.fireEvent("columnSelectEvent",{column:oColumn});
8751             YAHOO.log("Column \"" + oColumn.key + "\" selected", "info", this.toString());
8752         }
8753         else {
8754             YAHOO.log("Could not select Column \"" + oColumn.key + "\". Only non-nested Columns can be selected", "warn", this.toString());
8755         }
8756     }
8757 },
8758
8759 /**
8760  * Unselects given Column. NOTE: You cannot select/unselect nested Columns. You can only
8761  * select/unselect non-nested Columns, and bottom-level key Columns.
8762  *
8763  * @method unselectColumn
8764  * @param column {HTMLElement | String | Number} DOM reference or ID string to a
8765  * TH/TD element (or child of a TH/TD element), a Column key, or a ColumnSet key index.
8766  */
8767 unselectColumn : function(oColumn) {
8768     oColumn = this.getColumn(oColumn);
8769     if(oColumn && oColumn.selected) {
8770         // Only bottom-level Columns can get hidden
8771         if(oColumn.getKeyIndex() !== null) {
8772             oColumn.selected = false;
8773             
8774             // Update head cell
8775             var elTh = oColumn.getThEl();
8776             Dom.removeClass(elTh,DT.CLASS_SELECTED);
8777
8778             // Update body cells
8779             var allRows = this.getTbodyEl().rows;
8780             var oChainRender = this._oChainRender;
8781             oChainRender.add({
8782                 method: function(oArg) {
8783                     if((this instanceof DT) && this._sId && allRows[oArg.rowIndex] && allRows[oArg.rowIndex].cells[oArg.cellIndex]) {
8784                         Dom.removeClass(allRows[oArg.rowIndex].cells[oArg.cellIndex],DT.CLASS_SELECTED); 
8785                     }                   
8786                     oArg.rowIndex++;
8787                 },
8788                 scope: this,
8789                 iterations:allRows.length,
8790                 argument: {rowIndex:0,cellIndex:oColumn.getKeyIndex()}
8791             });
8792             
8793             this._clearTrTemplateEl();
8794
8795             this._elTbody.style.display = "none";
8796             this._runRenderChain();
8797             this._elTbody.style.display = "";      
8798             
8799             this.fireEvent("columnUnselectEvent",{column:oColumn});
8800             YAHOO.log("Column \"" + oColumn.key + "\" unselected", "info", this.toString());
8801         }
8802         else {
8803             YAHOO.log("Could not unselect Column \"" + oColumn.key + "\". Only non-nested Columns can be unselected", "warn", this.toString());
8804         }
8805     }
8806 },
8807
8808 /**
8809  * Returns an array selected Column instances.
8810  *
8811  * @method getSelectedColumns
8812  * @return {YAHOO.widget.Column[]} Array of Column instances.
8813  */
8814 getSelectedColumns : function(oColumn) {
8815     var selectedColumns = [];
8816     var aKeys = this._oColumnSet.keys;
8817     for(var i=0,len=aKeys.length; i<len; i++) {
8818         if(aKeys[i].selected) {
8819             selectedColumns[selectedColumns.length] = aKeys[i];
8820         }
8821     }
8822     return selectedColumns;
8823 },
8824
8825 /**
8826  * Assigns the class YAHOO.widget.DataTable.CLASS_HIGHLIGHTED to cells of the given Column.
8827  * NOTE: You cannot highlight/unhighlight nested Columns. You can only
8828  * highlight/unhighlight non-nested Columns, and bottom-level key Columns.
8829  *
8830  * @method highlightColumn
8831  * @param column {HTMLElement | String | Number} DOM reference or ID string to a
8832  * TH/TD element (or child of a TH/TD element), a Column key, or a ColumnSet key index.
8833  */
8834 highlightColumn : function(column) {
8835     var oColumn = this.getColumn(column);
8836     // Only bottom-level Columns can get highlighted
8837     if(oColumn && (oColumn.getKeyIndex() !== null)) {            
8838         // Update head cell
8839         var elTh = oColumn.getThEl();
8840         Dom.addClass(elTh,DT.CLASS_HIGHLIGHTED);
8841
8842         // Update body cells
8843         var allRows = this.getTbodyEl().rows;
8844         var oChainRender = this._oChainRender;
8845         oChainRender.add({
8846             method: function(oArg) {
8847                 if((this instanceof DT) && this._sId && allRows[oArg.rowIndex] && allRows[oArg.rowIndex].cells[oArg.cellIndex]) {
8848                     Dom.addClass(allRows[oArg.rowIndex].cells[oArg.cellIndex],DT.CLASS_HIGHLIGHTED);   
8849                 }                 
8850                 oArg.rowIndex++;
8851             },
8852             scope: this,
8853             iterations:allRows.length,
8854             argument: {rowIndex:0,cellIndex:oColumn.getKeyIndex()},
8855             timeout: -1
8856         });
8857         this._elTbody.style.display = "none";
8858         this._runRenderChain();
8859         this._elTbody.style.display = "";      
8860             
8861         this.fireEvent("columnHighlightEvent",{column:oColumn});
8862         YAHOO.log("Column \"" + oColumn.key + "\" highlighed", "info", this.toString());
8863     }
8864     else {
8865         YAHOO.log("Could not highlight Column \"" + oColumn.key + "\". Only non-nested Columns can be highlighted", "warn", this.toString());
8866     }
8867 },
8868
8869 /**
8870  * Removes the class YAHOO.widget.DataTable.CLASS_HIGHLIGHTED to cells of the given Column.
8871  * NOTE: You cannot highlight/unhighlight nested Columns. You can only
8872  * highlight/unhighlight non-nested Columns, and bottom-level key Columns.
8873  *
8874  * @method unhighlightColumn
8875  * @param column {HTMLElement | String | Number} DOM reference or ID string to a
8876  * TH/TD element (or child of a TH/TD element), a Column key, or a ColumnSet key index.
8877  */
8878 unhighlightColumn : function(column) {
8879     var oColumn = this.getColumn(column);
8880     // Only bottom-level Columns can get highlighted
8881     if(oColumn && (oColumn.getKeyIndex() !== null)) {
8882         // Update head cell
8883         var elTh = oColumn.getThEl();
8884         Dom.removeClass(elTh,DT.CLASS_HIGHLIGHTED);
8885
8886         // Update body cells
8887         var allRows = this.getTbodyEl().rows;
8888         var oChainRender = this._oChainRender;
8889         oChainRender.add({
8890             method: function(oArg) {
8891                 if((this instanceof DT) && this._sId && allRows[oArg.rowIndex] && allRows[oArg.rowIndex].cells[oArg.cellIndex]) {
8892                     Dom.removeClass(allRows[oArg.rowIndex].cells[oArg.cellIndex],DT.CLASS_HIGHLIGHTED);
8893                 }                 
8894                 oArg.rowIndex++;
8895             },
8896             scope: this,
8897             iterations:allRows.length,
8898             argument: {rowIndex:0,cellIndex:oColumn.getKeyIndex()},
8899             timeout: -1
8900         });
8901         this._elTbody.style.display = "none";
8902         this._runRenderChain();
8903         this._elTbody.style.display = "";     
8904             
8905         this.fireEvent("columnUnhighlightEvent",{column:oColumn});
8906         YAHOO.log("Column \"" + oColumn.key + "\" unhighlighted", "info", this.toString());
8907     }
8908     else {
8909         YAHOO.log("Could not unhighlight Column \"" + oColumn.key + "\". Only non-nested Columns can be unhighlighted", "warn", this.toString());
8910     }
8911 },
8912
8913
8914
8915
8916
8917
8918
8919
8920
8921
8922
8923
8924
8925
8926
8927
8928
8929
8930
8931
8932
8933
8934
8935
8936
8937
8938
8939
8940
8941
8942
8943
8944
8945
8946
8947
8948
8949
8950
8951
8952
8953
8954
8955
8956 // ROW FUNCTIONS
8957
8958 /**
8959  * Adds one new Record of data into the RecordSet at the index if given,
8960  * otherwise at the end. If the new Record is in page view, the
8961  * corresponding DOM elements are also updated.
8962  *
8963  * @method addRow
8964  * @param oData {Object} Object literal of data for the row.
8965  * @param index {Number} (optional) RecordSet position index at which to add data.
8966  */
8967 addRow : function(oData, index) {
8968     if(lang.isNumber(index) && (index < 0 || index > this._oRecordSet.getLength())) {
8969         YAHOO.log("Could not add row at index " + index + " with " + lang.dump(oData), "warn", this.toString());
8970         return;
8971     }
8972
8973     if(oData && lang.isObject(oData)) {
8974         var oRecord = this._oRecordSet.addRecord(oData, index);
8975         if(oRecord) {
8976             var recIndex;
8977             var oPaginator = this.get('paginator');
8978
8979             // Paginated
8980             if (oPaginator) {     
8981                 // Update the paginator's totalRecords
8982                 var totalRecords = oPaginator.get('totalRecords');
8983                 if (totalRecords !== widget.Paginator.VALUE_UNLIMITED) {
8984                     oPaginator.set('totalRecords',totalRecords + 1);
8985                 }
8986
8987                 recIndex = this.getRecordIndex(oRecord);
8988                 var endRecIndex = (oPaginator.getPageRecords())[1];
8989
8990                 // New record affects the view
8991                 if (recIndex <= endRecIndex) {
8992                     // Defer UI updates to the render method
8993                     this.render();
8994                 }
8995                 
8996                 this.fireEvent("rowAddEvent", {record:oRecord});
8997                 YAHOO.log("Added a row for Record " + YAHOO.lang.dump(oRecord) + " at RecordSet index " + recIndex, "info", this.toString()); 
8998                 return;
8999             }
9000             // Not paginated
9001             else {
9002                 recIndex = this.getTrIndex(oRecord);
9003                 if(lang.isNumber(recIndex)) {
9004                     // Add the TR element
9005                     this._oChainRender.add({
9006                         method: function(oArg) {
9007                             if((this instanceof DT) && this._sId) {
9008                                 var oRecord = oArg.record;
9009                                 var recIndex = oArg.recIndex;
9010                                 var elNewTr = this._addTrEl(oRecord);
9011                                 if(elNewTr) {
9012                                     var elNext = (this._elTbody.rows[recIndex]) ? this._elTbody.rows[recIndex] : null;
9013                                     this._elTbody.insertBefore(elNewTr, elNext);
9014
9015                                     // Set FIRST/LAST
9016                                     if(recIndex === 0) {
9017                                         this._setFirstRow();
9018                                     }
9019                                     if(elNext === null) {
9020                                         this._setLastRow();
9021                                     }
9022                                     // Set EVEN/ODD
9023                                     this._setRowStripes();                           
9024                                     
9025                                     this.hideTableMessage();
9026             
9027                                     this.fireEvent("rowAddEvent", {record:oRecord});
9028                                     YAHOO.log("Added a row for Record " + YAHOO.lang.dump(oRecord) + " at RecordSet index " + recIndex, "info", this.toString());
9029                                 }
9030                             }
9031                         },
9032                         argument: {record: oRecord, recIndex: recIndex},
9033                         scope: this,
9034                         timeout: (this.get("renderLoopSize") > 0) ? 0 : -1
9035                     });
9036                     this._runRenderChain();
9037                     return;
9038                 }
9039             }            
9040         }
9041     }
9042     YAHOO.log("Could not add row at index " + index + " with " + lang.dump(oData), "warn", this.toString());
9043 },
9044
9045 /**
9046  * Convenience method to add multiple rows.
9047  *
9048  * @method addRows
9049  * @param aData {Object[]} Array of object literal data for the rows.
9050  * @param index {Number} (optional) RecordSet position index at which to add data.
9051  */
9052 addRows : function(aData, index) {
9053     if(lang.isNumber(index) && (index < 0 || index > this._oRecordSet.getLength())) {
9054         YAHOO.log("Could not add rows at index " + index + " with " + lang.dump(aData), "warn", this.toString());    
9055         return;
9056     }
9057
9058     if(lang.isArray(aData)) {
9059         var aRecords = this._oRecordSet.addRecords(aData, index);
9060         if(aRecords) {
9061             var recIndex = this.getRecordIndex(aRecords[0]);
9062             
9063             // Paginated
9064             var oPaginator = this.get('paginator');
9065             if (oPaginator) {
9066                 // Update the paginator's totalRecords
9067                 var totalRecords = oPaginator.get('totalRecords');
9068                 if (totalRecords !== widget.Paginator.VALUE_UNLIMITED) {
9069                     oPaginator.set('totalRecords',totalRecords + aRecords.length);
9070                 }
9071     
9072                 var endRecIndex = (oPaginator.getPageRecords())[1];
9073
9074                 // At least one of the new records affects the view
9075                 if (recIndex <= endRecIndex) {
9076                     this.render();
9077                 }
9078                 
9079                 this.fireEvent("rowsAddEvent", {records:aRecords});
9080                 YAHOO.log("Added " + aRecords.length + 
9081                         " rows at index " + this._oRecordSet.getRecordIndex(aRecords[0]) +
9082                         " with data " + lang.dump(aData), "info", this.toString());
9083                 return;
9084             }
9085             // Not paginated
9086             else {
9087                 // Add the TR elements
9088                 var loopN = this.get("renderLoopSize");
9089                 var loopEnd = recIndex + aData.length;
9090                 var nRowsNeeded = (loopEnd - recIndex); // how many needed
9091                 var isLast = (recIndex >= this._elTbody.rows.length);
9092                 this._oChainRender.add({
9093                     method: function(oArg) {
9094                         if((this instanceof DT) && this._sId) {
9095                             var aRecords = oArg.aRecords,
9096                                 i = oArg.nCurrentRow,
9097                                 j = oArg.nCurrentRecord,
9098                                 len = loopN > 0 ? Math.min(i + loopN,loopEnd) : loopEnd,
9099                                 df = document.createDocumentFragment(),
9100                                 elNext = (this._elTbody.rows[i]) ? this._elTbody.rows[i] : null;
9101                             for(; i < len; i++, j++) {
9102                                 df.appendChild(this._addTrEl(aRecords[j]));
9103                             }
9104                             this._elTbody.insertBefore(df, elNext);
9105                             oArg.nCurrentRow = i;
9106                             oArg.nCurrentRecord = j;
9107                         }
9108                     },
9109                     iterations: (loopN > 0) ? Math.ceil(loopEnd/loopN) : 1,
9110                     argument: {nCurrentRow:recIndex,nCurrentRecord:0,aRecords:aRecords},
9111                     scope: this,
9112                     timeout: (loopN > 0) ? 0 : -1
9113                 });
9114                 this._oChainRender.add({
9115                     method: function(oArg) {
9116                         var recIndex = oArg.recIndex;
9117                         // Set FIRST/LAST
9118                         if(recIndex === 0) {
9119                             this._setFirstRow();
9120                         }
9121                         if(oArg.isLast) {
9122                             this._setLastRow();
9123                         }
9124                         // Set EVEN/ODD
9125                         this._setRowStripes();                           
9126
9127                         this.fireEvent("rowsAddEvent", {records:aRecords});
9128                         YAHOO.log("Added " + aRecords.length + 
9129                                 " rows at index " + this._oRecordSet.getRecordIndex(aRecords[0]) +
9130                                 " with data " + lang.dump(aData), "info", this.toString());
9131                     },
9132                     argument: {recIndex: recIndex, isLast: isLast},
9133                     scope: this,
9134                     timeout: -1 // Needs to run immediately after the DOM insertions above
9135                 });
9136                 this._runRenderChain();
9137                 this.hideTableMessage();                
9138                 return;
9139             }            
9140         }
9141     }
9142     YAHOO.log("Could not add rows at index " + index + " with " + lang.dump(aData), "warn", this.toString());    
9143 },
9144
9145 /**
9146  * For the given row, updates the associated Record with the given data. If the
9147  * row is on current page, the corresponding DOM elements are also updated.
9148  *
9149  * @method updateRow
9150  * @param row {YAHOO.widget.Record | Number | HTMLElement | String}
9151  * Which row to update: By Record instance, by Record's RecordSet
9152  * position index, by HTMLElement reference to the TR element, or by ID string
9153  * of the TR element.
9154  * @param oData {Object} Object literal of data for the row.
9155  */
9156 updateRow : function(row, oData) {
9157     var index = row;
9158     if (!lang.isNumber(index)) {
9159         index = this.getRecordIndex(row);
9160     }
9161
9162     // Update the Record
9163     if(lang.isNumber(index) && (index >= 0)) {
9164         var oRecordSet = this._oRecordSet,
9165             oldRecord = oRecordSet.getRecord(index);
9166             
9167         
9168         if(oldRecord) {
9169             var updatedRecord = this._oRecordSet.setRecord(oData, index),
9170                 elRow = this.getTrEl(oldRecord),
9171                 // Copy data from the Record for the event that gets fired later
9172                 oldData = oldRecord ? oldRecord.getData() : null;
9173                
9174             if(updatedRecord) {
9175                 // Update selected rows as necessary
9176                 var tracker = this._aSelections || [],
9177                 i=0,
9178                 oldId = oldRecord.getId(),
9179                 newId = updatedRecord.getId();
9180                 for(; i<tracker.length; i++) {
9181                     if((tracker[i] === oldId)) {
9182                         tracker[i] = newId;
9183                     }
9184                     else if(tracker[i].recordId === oldId) {
9185                         tracker[i].recordId = newId;
9186                     }
9187                 }
9188
9189                 // Update the TR only if row is on current page
9190                 this._oChainRender.add({
9191                     method: function() {
9192                         if((this instanceof DT) && this._sId) {
9193                             // Paginated
9194                             var oPaginator = this.get('paginator');
9195                             if (oPaginator) {
9196                                 var pageStartIndex = (oPaginator.getPageRecords())[0],
9197                                     pageLastIndex = (oPaginator.getPageRecords())[1];
9198         
9199                                 // At least one of the new records affects the view
9200                                 if ((index >= pageStartIndex) || (index <= pageLastIndex)) {
9201                                     this.render();
9202                                 }
9203                             }
9204                             else {
9205                                 if(elRow) {
9206                                     this._updateTrEl(elRow, updatedRecord);
9207                                 }
9208                                 else {
9209                                     this.getTbodyEl().appendChild(this._addTrEl(updatedRecord));
9210                                 }
9211                             }
9212                             this.fireEvent("rowUpdateEvent", {record:updatedRecord, oldData:oldData});
9213                             YAHOO.log("DataTable row updated: Record ID = " + updatedRecord.getId() +
9214                                     ", Record index = " + this.getRecordIndex(updatedRecord) +
9215                                     ", page row index = " + this.getTrIndex(updatedRecord), "info", this.toString());
9216                         }
9217                     },
9218                     scope: this,
9219                     timeout: (this.get("renderLoopSize") > 0) ? 0 : -1
9220                 });
9221                 this._runRenderChain();
9222                 return;
9223             }
9224         }
9225     }
9226     YAHOO.log("Could not update row " + row + " with the data : " + lang.dump(oData), "warn", this.toString());
9227     return;
9228 },
9229
9230 /**
9231  * Starting with the given row, updates associated Records with the given data.
9232  * The number of rows to update are determined by the array of data provided.
9233  * Undefined data (i.e., not an object literal) causes a row to be skipped. If
9234  * any of the rows are on current page, the corresponding DOM elements are also
9235  * updated.
9236  *
9237  * @method updateRows
9238  * @param startrow {YAHOO.widget.Record | Number | HTMLElement | String}
9239  * Starting row to update: By Record instance, by Record's RecordSet
9240  * position index, by HTMLElement reference to the TR element, or by ID string
9241  * of the TR element.
9242  * @param aData {Object[]} Array of object literal of data for the rows.
9243  */
9244 updateRows : function(startrow, aData) {
9245     if(lang.isArray(aData)) {
9246         var startIndex = startrow,
9247             oRecordSet = this._oRecordSet;
9248             
9249         if (!lang.isNumber(startrow)) {
9250             startIndex = this.getRecordIndex(startrow);
9251         }
9252             
9253         if(lang.isNumber(startIndex) && (startIndex >= 0) && (startIndex < oRecordSet.getLength())) {
9254             var lastIndex = startIndex + aData.length,
9255                 aOldRecords = oRecordSet.getRecords(startIndex, aData.length),
9256                 aNewRecords = oRecordSet.setRecords(aData, startIndex);
9257             if(aNewRecords) {
9258                 // Update selected rows as necessary
9259                 var tracker = this._aSelections || [],
9260                     i=0, j, newId, oldId;
9261                 for(; i<tracker.length; i++) {
9262                     for(j=0; j<aOldRecords.length; j++) {
9263                         oldId = aOldRecords[j].getId();
9264                         if((tracker[i] === oldId)) {
9265                             tracker[i] = aNewRecords[j].getId();
9266                         }
9267                         else if(tracker[i].recordId === oldId) {
9268                             tracker[i].recordId = aNewRecords[j].getId();
9269                         }
9270                     }
9271                 }
9272             
9273                 // Paginated
9274                 var oPaginator = this.get('paginator');
9275                 if (oPaginator) {
9276                     var pageStartIndex = (oPaginator.getPageRecords())[0],
9277                         pageLastIndex = (oPaginator.getPageRecords())[1];
9278     
9279                     // At least one of the new records affects the view
9280                     if ((startIndex >= pageStartIndex) || (lastIndex <= pageLastIndex)) {
9281                         this.render();
9282                     }
9283                     
9284                     this.fireEvent("rowsAddEvent", {newRecords:aNewRecords, oldRecords:aOldRecords});
9285                     YAHOO.log("Added " + aNewRecords.length + 
9286                             " rows starting at index " + startIndex +
9287                             " with data " + lang.dump(aData), "info", this.toString());
9288                     return;
9289                 }
9290                 // Not paginated
9291                 else {
9292                     // Update the TR elements
9293                     var loopN = this.get("renderLoopSize"),
9294                         rowCount = aData.length, // how many needed
9295                         lastRowIndex = this._elTbody.rows.length,
9296                         isLast = (lastIndex >= lastRowIndex),
9297                         isAdding = (lastIndex > lastRowIndex);
9298                                            
9299                     this._oChainRender.add({
9300                         method: function(oArg) {
9301                             if((this instanceof DT) && this._sId) {
9302                                 var aRecords = oArg.aRecords,
9303                                     i = oArg.nCurrentRow,
9304                                     j = oArg.nDataPointer,
9305                                     len = loopN > 0 ? Math.min(i+loopN, startIndex+aRecords.length) : startIndex+aRecords.length;
9306                                     
9307                                 for(; i < len; i++,j++) {
9308                                     if(isAdding && (i>=lastRowIndex)) {
9309                                         this._elTbody.appendChild(this._addTrEl(aRecords[j]));
9310                                     }
9311                                     else {
9312                                         this._updateTrEl(this._elTbody.rows[i], aRecords[j]);
9313                                     }
9314                                 }
9315                                 oArg.nCurrentRow = i;
9316                                 oArg.nDataPointer = j;
9317                             }
9318                         },
9319                         iterations: (loopN > 0) ? Math.ceil(rowCount/loopN) : 1,
9320                         argument: {nCurrentRow:startIndex,aRecords:aNewRecords,nDataPointer:0,isAdding:isAdding},
9321                         scope: this,
9322                         timeout: (loopN > 0) ? 0 : -1
9323                     });
9324                     this._oChainRender.add({
9325                         method: function(oArg) {
9326                             var recIndex = oArg.recIndex;
9327                             // Set FIRST/LAST
9328                             if(recIndex === 0) {
9329                                 this._setFirstRow();
9330                             }
9331                             if(oArg.isLast) {
9332                                 this._setLastRow();
9333                             }
9334                             // Set EVEN/ODD
9335                             this._setRowStripes();                           
9336     
9337                             this.fireEvent("rowsAddEvent", {newRecords:aNewRecords, oldRecords:aOldRecords});
9338                             YAHOO.log("Added " + aNewRecords.length + 
9339                                     " rows starting at index " + startIndex +
9340                                     " with data " + lang.dump(aData), "info", this.toString());
9341                         },
9342                         argument: {recIndex: startIndex, isLast: isLast},
9343                         scope: this,
9344                         timeout: -1 // Needs to run immediately after the DOM insertions above
9345                     });
9346                     this._runRenderChain();
9347                     this.hideTableMessage();                
9348                     return;
9349                 }            
9350             }
9351         }
9352     }
9353     YAHOO.log("Could not update rows at " + startrow + " with " + lang.dump(aData), "warn", this.toString());
9354 },
9355
9356 /**
9357  * Deletes the given row's Record from the RecordSet. If the row is on current page,
9358  * the corresponding DOM elements are also deleted.
9359  *
9360  * @method deleteRow
9361  * @param row {HTMLElement | String | Number} DOM element reference or ID string
9362  * to DataTable page element or RecordSet index.
9363  */
9364 deleteRow : function(row) {
9365     var nRecordIndex = (lang.isNumber(row)) ? row : this.getRecordIndex(row);
9366     if(lang.isNumber(nRecordIndex)) {
9367         var oRecord = this.getRecord(nRecordIndex);
9368         if(oRecord) {
9369             var nTrIndex = this.getTrIndex(nRecordIndex);
9370             
9371             // Remove from selection tracker if there
9372             var sRecordId = oRecord.getId();
9373             var tracker = this._aSelections || [];
9374             for(var j=tracker.length-1; j>-1; j--) {
9375                 if((lang.isString(tracker[j]) && (tracker[j] === sRecordId)) ||
9376                         (lang.isObject(tracker[j]) && (tracker[j].recordId === sRecordId))) {
9377                     tracker.splice(j,1);
9378                 }
9379             }
9380     
9381             // Delete Record from RecordSet
9382             var oData = this._oRecordSet.deleteRecord(nRecordIndex);
9383     
9384             // Update the UI
9385             if(oData) {
9386                 // If paginated and the deleted row was on this or a prior page, just
9387                 // re-render
9388                 var oPaginator = this.get('paginator');
9389                 if (oPaginator) {
9390                     // Update the paginator's totalRecords
9391                     var totalRecords = oPaginator.get('totalRecords'),
9392                         // must capture before the totalRecords change because
9393                         // Paginator shifts to previous page automatically
9394                         rng = oPaginator.getPageRecords();
9395
9396                     if (totalRecords !== widget.Paginator.VALUE_UNLIMITED) {
9397                         oPaginator.set('totalRecords',totalRecords - 1);
9398                     }
9399     
9400                     // The deleted record was on this or a prior page, re-render
9401                     if (!rng || nRecordIndex <= rng[1]) {
9402                         this.render();
9403                     }
9404
9405                     this._oChainRender.add({
9406                         method: function() {
9407                             if((this instanceof DT) && this._sId) {
9408                                 this.fireEvent("rowDeleteEvent", {recordIndex:nRecordIndex, oldData:oData, trElIndex:nTrIndex});
9409                                 YAHOO.log("Deleted row with data " + YAHOO.lang.dump(oData) + " at RecordSet index " + nRecordIndex + " and page row index " + nTrIndex, "info", this.toString());     
9410                             }
9411                         },
9412                         scope: this,
9413                         timeout: (this.get("renderLoopSize") > 0) ? 0 : -1
9414                     });
9415                     this._runRenderChain();
9416                 }
9417                 // Not paginated
9418                 else {
9419                     if(lang.isNumber(nTrIndex)) {
9420                         this._oChainRender.add({
9421                             method: function() {
9422                                 if((this instanceof DT) && this._sId) {
9423                                     var isLast = (nRecordIndex === this._oRecordSet.getLength());//(nTrIndex == this.getLastTrEl().sectionRowIndex);
9424                                     this._deleteTrEl(nTrIndex);
9425                     
9426                                     // Post-delete tasks
9427                                     if(this._elTbody.rows.length > 0) {
9428                                         // Set FIRST/LAST
9429                                         if(nTrIndex === 0) {
9430                                             this._setFirstRow();
9431                                         }
9432                                         if(isLast) {
9433                                             this._setLastRow();
9434                                         }
9435                                         // Set EVEN/ODD
9436                                         if(nTrIndex != this._elTbody.rows.length) {
9437                                             this._setRowStripes(nTrIndex);
9438                                         }                                
9439                                     }
9440                     
9441                                     this.fireEvent("rowDeleteEvent", {recordIndex:nRecordIndex,oldData:oData, trElIndex:nTrIndex});
9442                                     YAHOO.log("Deleted row with data " + YAHOO.lang.dump(oData) + " at RecordSet index " + nRecordIndex + " and page row index " + nTrIndex, "info", this.toString());     
9443                                 }
9444                             },
9445                             scope: this,
9446                             timeout: (this.get("renderLoopSize") > 0) ? 0 : -1
9447                         });
9448                         this._runRenderChain();
9449                         return;
9450                     }
9451                 }
9452             }
9453         }
9454     }
9455     YAHOO.log("Could not delete row: " + row, "warn", this.toString());
9456     return null;
9457 },
9458
9459 /**
9460  * Convenience method to delete multiple rows.
9461  *
9462  * @method deleteRows
9463  * @param row {HTMLElement | String | Number} DOM element reference or ID string
9464  * to DataTable page element or RecordSet index.
9465  * @param count {Number} (optional) How many rows to delete. A negative value
9466  * will delete towards the beginning.
9467  */
9468 deleteRows : function(row, count) {
9469     var nRecordIndex = (lang.isNumber(row)) ? row : this.getRecordIndex(row);
9470     if(lang.isNumber(nRecordIndex)) {
9471         var oRecord = this.getRecord(nRecordIndex);
9472         if(oRecord) {
9473             var nTrIndex = this.getTrIndex(nRecordIndex);
9474             
9475             // Remove from selection tracker if there
9476             var sRecordId = oRecord.getId();
9477             var tracker = this._aSelections || [];
9478             for(var j=tracker.length-1; j>-1; j--) {
9479                 if((lang.isString(tracker[j]) && (tracker[j] === sRecordId)) ||
9480                         (lang.isObject(tracker[j]) && (tracker[j].recordId === sRecordId))) {
9481                     tracker.splice(j,1);
9482                 }
9483             }
9484     
9485             // Delete Record from RecordSet
9486             var highIndex = nRecordIndex;
9487             var lowIndex = nRecordIndex;
9488         
9489             // Validate count and account for negative value
9490             if(count && lang.isNumber(count)) {
9491                 highIndex = (count > 0) ? nRecordIndex + count -1 : nRecordIndex;
9492                 lowIndex = (count > 0) ? nRecordIndex : nRecordIndex + count + 1;
9493                 count = (count > 0) ? count : count*-1;
9494                 if(lowIndex < 0) {
9495                     lowIndex = 0;
9496                     count = highIndex - lowIndex + 1;
9497                 }
9498             }
9499             else {
9500                 count = 1;
9501             }
9502             
9503             var aData = this._oRecordSet.deleteRecords(lowIndex, count);
9504     
9505             // Update the UI
9506             if(aData) {
9507                 var oPaginator = this.get('paginator'),
9508                     loopN = this.get("renderLoopSize");
9509                 // If paginated and the deleted row was on this or a prior page, just
9510                 // re-render
9511                 if (oPaginator) {
9512                     // Update the paginator's totalRecords
9513                     var totalRecords = oPaginator.get('totalRecords'),
9514                         // must capture before the totalRecords change because
9515                         // Paginator shifts to previous page automatically
9516                         rng = oPaginator.getPageRecords();
9517
9518                     if (totalRecords !== widget.Paginator.VALUE_UNLIMITED) {
9519                         oPaginator.set('totalRecords',totalRecords - aData.length);
9520                     }
9521     
9522                     // The records were on this or a prior page, re-render
9523                     if (!rng || lowIndex <= rng[1]) {
9524                         this.render();
9525                     }
9526
9527                     this._oChainRender.add({
9528                         method: function(oArg) {
9529                             if((this instanceof DT) && this._sId) {
9530                                 this.fireEvent("rowsDeleteEvent", {recordIndex:lowIndex, oldData:aData, count:count});
9531                                 YAHOO.log("DataTable " + count + " rows deleted starting at index " + lowIndex, "info", this.toString());
9532                             }
9533                         },
9534                         scope: this,
9535                         timeout: (loopN > 0) ? 0 : -1
9536                     });
9537                     this._runRenderChain();
9538                     return;
9539                 }
9540                 // Not paginated
9541                 else {
9542                     if(lang.isNumber(nTrIndex)) {
9543                         // Delete the TR elements starting with highest index
9544                         var loopEnd = lowIndex;
9545                         var nRowsNeeded = count; // how many needed
9546                         this._oChainRender.add({
9547                             method: function(oArg) {
9548                                 if((this instanceof DT) && this._sId) {
9549                                     var i = oArg.nCurrentRow,
9550                                         len = (loopN > 0) ? (Math.max(i - loopN,loopEnd)-1) : loopEnd-1;
9551                                     for(; i>len; --i) {
9552                                         this._deleteTrEl(i);
9553                                     }
9554                                     oArg.nCurrentRow = i;
9555                                 }
9556                             },
9557                             iterations: (loopN > 0) ? Math.ceil(count/loopN) : 1,
9558                             argument: {nCurrentRow:highIndex},
9559                             scope: this,
9560                             timeout: (loopN > 0) ? 0 : -1
9561                         });
9562                         this._oChainRender.add({
9563                             method: function() {    
9564                                 // Post-delete tasks
9565                                 if(this._elTbody.rows.length > 0) {
9566                                     this._setFirstRow();
9567                                     this._setLastRow();
9568                                     this._setRowStripes();
9569                                 }
9570                                 
9571                                 this.fireEvent("rowsDeleteEvent", {recordIndex:lowIndex, oldData:aData, count:count});
9572                                 YAHOO.log("DataTable " + count + " rows deleted starting at index " + lowIndex, "info", this.toString());
9573                             },
9574                             scope: this,
9575                             timeout: -1 // Needs to run immediately after the DOM deletions above
9576                         });
9577                         this._runRenderChain();
9578                         return;
9579                     }
9580                 }
9581             }
9582         }
9583     }
9584     YAHOO.log("Could not delete " + count + " rows at row " + row, "warn", this.toString());
9585     return null;
9586 },
9587
9588
9589
9590
9591
9592
9593
9594
9595
9596
9597
9598
9599
9600
9601
9602
9603
9604
9605
9606
9607
9608
9609
9610
9611
9612
9613
9614
9615
9616
9617
9618
9619
9620
9621
9622
9623
9624
9625
9626
9627
9628
9629
9630
9631
9632
9633 // CELL FUNCTIONS
9634
9635 /**
9636  * Outputs markup into the given TD based on given Record.
9637  *
9638  * @method formatCell
9639  * @param elLiner {HTMLElement} The liner DIV element within the TD.
9640  * @param oRecord {YAHOO.widget.Record} (Optional) Record instance.
9641  * @param oColumn {YAHOO.widget.Column} (Optional) Column instance.
9642  */
9643 formatCell : function(elLiner, oRecord, oColumn) {
9644     if(!oRecord) {
9645         oRecord = this.getRecord(elLiner);
9646     }
9647     if(!oColumn) {
9648         oColumn = this.getColumn(elLiner.parentNode.cellIndex);
9649     }
9650
9651     if(oRecord && oColumn) {
9652         var sField = oColumn.field;
9653         var oData = oRecord.getData(sField);
9654
9655         var fnFormatter = typeof oColumn.formatter === 'function' ?
9656                           oColumn.formatter :
9657                           DT.Formatter[oColumn.formatter+''] ||
9658                           DT.Formatter.defaultFormatter;
9659
9660         // Apply special formatter
9661         if(fnFormatter) {
9662             fnFormatter.call(this, elLiner, oRecord, oColumn, oData);
9663         }
9664         else {
9665             elLiner.innerHTML = oData;
9666         }
9667
9668         this.fireEvent("cellFormatEvent", {record:oRecord, column:oColumn, key:oColumn.key, el:elLiner});
9669     }
9670     else {
9671         YAHOO.log("Could not format cell " + elLiner, "error", this.toString());
9672     }
9673 },
9674
9675 /**
9676  * For the given row and column, updates the Record with the given data. If the
9677  * cell is on current page, the corresponding DOM elements are also updated.
9678  *
9679  * @method updateCell
9680  * @param oRecord {YAHOO.widget.Record} Record instance.
9681  * @param oColumn {YAHOO.widget.Column | String | Number} A Column key, or a ColumnSet key index.
9682  * @param oData {Object} New data value for the cell.
9683  */
9684 updateCell : function(oRecord, oColumn, oData) {    
9685     // Validate Column and Record
9686     oColumn = (oColumn instanceof YAHOO.widget.Column) ? oColumn : this.getColumn(oColumn);
9687     if(oColumn && oColumn.getField() && (oRecord instanceof YAHOO.widget.Record)) {
9688         var sKey = oColumn.getField(),
9689         
9690         // Copy data from the Record for the event that gets fired later
9691         //var oldData = YAHOO.widget.DataTable._cloneObject(oRecord.getData());
9692             oldData = oRecord.getData(sKey);
9693
9694         // Update Record with new data
9695         this._oRecordSet.updateRecordValue(oRecord, sKey, oData);
9696     
9697         // Update the TD only if row is on current page
9698         var elTd = this.getTdEl({record: oRecord, column: oColumn});
9699         if(elTd) {
9700             this._oChainRender.add({
9701                 method: function() {
9702                     if((this instanceof DT) && this._sId) {
9703                         this.formatCell(elTd.firstChild);
9704                         this.fireEvent("cellUpdateEvent", {record:oRecord, column: oColumn, oldData:oldData});
9705                         YAHOO.log("DataTable cell updated: Record ID = " + oRecord.getId() +
9706                                 ", Record index = " + this.getRecordIndex(oRecord) +
9707                                 ", page row index = " + this.getTrIndex(oRecord) +
9708                                 ", Column key = " + oColumn.getKey(), "info", this.toString());
9709                     }
9710                 },
9711                 scope: this,
9712                 timeout: (this.get("renderLoopSize") > 0) ? 0 : -1
9713             });
9714             this._runRenderChain();
9715         }
9716         else {
9717             this.fireEvent("cellUpdateEvent", {record:oRecord, column: oColumn, oldData:oldData});
9718             YAHOO.log("DataTable cell updated: Record ID = " + oRecord.getId() +
9719                     ", Record index = " + this.getRecordIndex(oRecord) +
9720                     ", page row index = " + this.getTrIndex(oRecord) +
9721                     ", Column key = " + oColumn.getKey(), "info", this.toString());   
9722         }
9723     }
9724 },
9725
9726
9727
9728
9729
9730
9731
9732
9733
9734
9735
9736
9737
9738
9739
9740
9741
9742
9743
9744
9745
9746
9747
9748
9749
9750
9751
9752
9753
9754
9755
9756
9757
9758
9759
9760
9761
9762
9763
9764
9765
9766
9767
9768
9769
9770
9771
9772
9773
9774
9775
9776 // PAGINATION
9777 /**
9778  * Method executed during set() operation for the "paginator" attribute.
9779  * Adds and/or severs event listeners between DataTable and Paginator
9780  *
9781  * @method _updatePaginator
9782  * @param newPag {Paginator} Paginator instance (or null) for DataTable to use
9783  * @private
9784  */
9785 _updatePaginator : function (newPag) {
9786     var oldPag = this.get('paginator');
9787     if (oldPag && newPag !== oldPag) {
9788         oldPag.unsubscribe('changeRequest', this.onPaginatorChangeRequest, this, true);
9789     }
9790     if (newPag) {
9791         newPag.subscribe('changeRequest', this.onPaginatorChangeRequest, this, true);
9792     }
9793 },
9794
9795 /**
9796  * Update the UI infrastructure in response to a "paginator" attribute change.
9797  *
9798  * @method _handlePaginatorChange
9799  * @param e {Object} Change event object containing keys 'type','newValue',
9800  *                   and 'prevValue'
9801  * @private
9802  */
9803 _handlePaginatorChange : function (e) {
9804     if (e.prevValue === e.newValue) { return; }
9805
9806     var newPag     = e.newValue,
9807         oldPag     = e.prevValue,
9808         containers = this._defaultPaginatorContainers();
9809
9810     if (oldPag) {
9811         if (oldPag.getContainerNodes()[0] == containers[0]) {
9812             oldPag.set('containers',[]);
9813         }
9814         oldPag.destroy();
9815
9816         // Convenience: share the default containers if possible.
9817         // Otherwise, remove the default containers from the DOM.
9818         if (containers[0]) {
9819             if (newPag && !newPag.getContainerNodes().length) {
9820                 newPag.set('containers',containers);
9821             } else {
9822                 // No new Paginator to use existing containers, OR new
9823                 // Paginator has configured containers.
9824                 for (var i = containers.length - 1; i >= 0; --i) {
9825                     if (containers[i]) {
9826                         containers[i].parentNode.removeChild(containers[i]);
9827                     }
9828                 }
9829             }
9830         }
9831     }
9832
9833     if (!this._bInit) {
9834         this.render();
9835
9836     }
9837
9838     if (newPag) {
9839         this.renderPaginator();
9840     }
9841
9842 },
9843
9844 /**
9845  * Returns the default containers used for Paginators.  If create param is
9846  * passed, the containers will be created and added to the DataTable container.
9847  *
9848  * @method _defaultPaginatorContainers
9849  * @param create {boolean} Create the default containers if not found
9850  * @private
9851  */
9852 _defaultPaginatorContainers : function (create) {
9853     var above_id = this._sId + '-paginator0',
9854         below_id = this._sId + '-paginator1',
9855         above    = Dom.get(above_id),
9856         below    = Dom.get(below_id);
9857
9858     if (create && (!above || !below)) {
9859         // One above and one below the table
9860         if (!above) {
9861             above    = document.createElement('div');
9862             above.id = above_id;
9863             Dom.addClass(above, DT.CLASS_PAGINATOR);
9864
9865             this._elContainer.insertBefore(above,this._elContainer.firstChild);
9866         }
9867
9868         if (!below) {
9869             below    = document.createElement('div');
9870             below.id = below_id;
9871             Dom.addClass(below, DT.CLASS_PAGINATOR);
9872
9873             this._elContainer.appendChild(below);
9874         }
9875     }
9876
9877     return [above,below];
9878 },
9879
9880 /**
9881  * Calls Paginator's destroy() method
9882  *
9883  * @method _destroyPaginator
9884  * @private
9885  */
9886 _destroyPaginator : function () {
9887     var oldPag = this.get('paginator');
9888     if (oldPag) {
9889         oldPag.destroy();
9890     }
9891 },
9892
9893 /**
9894  * Renders the Paginator to the DataTable UI
9895  *
9896  * @method renderPaginator
9897  */
9898 renderPaginator : function () {
9899     var pag = this.get("paginator");
9900     if (!pag) { return; }
9901
9902     // Add the containers if the Paginator is not configured with containers
9903     if (!pag.getContainerNodes().length) {
9904         pag.set('containers',this._defaultPaginatorContainers(true));
9905     }
9906
9907     pag.render();
9908 },
9909
9910 /**
9911  * Overridable method gives implementers a hook to show loading message before
9912  * changing Paginator value.
9913  *
9914  * @method doBeforePaginatorChange
9915  * @param oPaginatorState {Object} An object literal describing the proposed pagination state.
9916  * @return {Boolean} Return true to continue changing Paginator value.
9917  */
9918 doBeforePaginatorChange : function(oPaginatorState) {
9919     this.showTableMessage(this.get("MSG_LOADING"), DT.CLASS_LOADING);
9920     return true;
9921 },
9922
9923 /**
9924  * Responds to new Pagination states. By default, updates the UI to reflect the
9925  * new state. If "dynamicData" is true, current selections are purged before
9926  * a request is sent to the DataSource for data for the new state (using the
9927  * request returned by "generateRequest()").
9928  *  
9929  * @method onPaginatorChangeRequest
9930  * @param oPaginatorState {Object} An object literal describing the proposed pagination state.
9931  */
9932 onPaginatorChangeRequest : function (oPaginatorState) {
9933     var ok = this.doBeforePaginatorChange(oPaginatorState);
9934     if(ok) {
9935         // Server-side pagination
9936         if(this.get("dynamicData")) {
9937             // Get the current state
9938             var oState = this.getState();
9939             
9940             // Update pagination values
9941             oState.pagination = oPaginatorState;
9942     
9943             // Get the request for the new state
9944             var request = this.get("generateRequest")(oState, this);
9945             
9946             // Purge selections
9947             this.unselectAllRows();
9948             this.unselectAllCells();
9949             
9950             // Get the new data from the server
9951             var callback = {
9952                 success : this.onDataReturnSetRows,
9953                 failure : this.onDataReturnSetRows,
9954                 argument : oState, // Pass along the new state to the callback
9955                 scope : this
9956             };
9957             this._oDataSource.sendRequest(request, callback);
9958         }
9959         // Client-side pagination
9960         else {
9961             // Set the core pagination values silently (the second param)
9962             // to avoid looping back through the changeRequest mechanism
9963             oPaginatorState.paginator.setStartIndex(oPaginatorState.recordOffset,true);
9964             oPaginatorState.paginator.setRowsPerPage(oPaginatorState.rowsPerPage,true);
9965     
9966             // Update the UI
9967             this.render();
9968         }
9969     }
9970     else {
9971         YAHOO.log("Could not change Paginator value \"" + oPaginatorState + "\"", "warn", this.toString());
9972     }
9973 },
9974
9975
9976
9977
9978
9979
9980
9981
9982
9983
9984
9985
9986
9987
9988
9989
9990
9991
9992
9993
9994
9995
9996
9997
9998
9999
10000
10001
10002
10003
10004
10005
10006
10007
10008
10009
10010
10011
10012
10013
10014
10015
10016
10017
10018
10019
10020
10021
10022
10023
10024 // SELECTION/HIGHLIGHTING
10025
10026 /*
10027  * Reference to last highlighted cell element
10028  *
10029  * @property _elLastHighlightedTd
10030  * @type HTMLElement
10031  * @private
10032  */
10033 _elLastHighlightedTd : null,
10034
10035 /*
10036  * ID string of last highlighted row element
10037  *
10038  * @property _sLastHighlightedTrElId
10039  * @type String
10040  * @private
10041  */
10042 //_sLastHighlightedTrElId : null,
10043
10044 /**
10045  * Array to track row selections (by sRecordId) and/or cell selections
10046  * (by {recordId:sRecordId, columnKey:sColumnKey})
10047  *
10048  * @property _aSelections
10049  * @type Object[]
10050  * @private
10051  */
10052 _aSelections : null,
10053
10054 /**
10055  * Record instance of the row selection anchor.
10056  *
10057  * @property _oAnchorRecord
10058  * @type YAHOO.widget.Record
10059  * @private
10060  */
10061 _oAnchorRecord : null,
10062
10063 /**
10064  * Object literal representing cell selection anchor:
10065  * {recordId:sRecordId, columnKey:sColumnKey}.
10066  *
10067  * @property _oAnchorCell
10068  * @type Object
10069  * @private
10070  */
10071 _oAnchorCell : null,
10072
10073 /**
10074  * Convenience method to remove the class YAHOO.widget.DataTable.CLASS_SELECTED
10075  * from all TR elements on the page.
10076  *
10077  * @method _unselectAllTrEls
10078  * @private
10079  */
10080 _unselectAllTrEls : function() {
10081     var selectedRows = Dom.getElementsByClassName(DT.CLASS_SELECTED,"tr",this._elTbody);
10082     Dom.removeClass(selectedRows, DT.CLASS_SELECTED);
10083 },
10084
10085 /**
10086  * Returns object literal of values that represent the selection trigger. Used
10087  * to determine selection behavior resulting from a key event.
10088  *
10089  * @method _getSelectionTrigger
10090  * @private
10091  */
10092 _getSelectionTrigger : function() {
10093     var sMode = this.get("selectionMode");
10094     var oTrigger = {};
10095     var oTriggerCell, oTriggerRecord, nTriggerRecordIndex, elTriggerRow, nTriggerTrIndex;
10096
10097     // Cell mode
10098     if((sMode == "cellblock") || (sMode == "cellrange") || (sMode == "singlecell")) {
10099         oTriggerCell = this.getLastSelectedCell();
10100         // No selected cells found
10101         if(!oTriggerCell) {
10102             return null;
10103         }
10104         else {
10105             oTriggerRecord = this.getRecord(oTriggerCell.recordId);
10106             nTriggerRecordIndex = this.getRecordIndex(oTriggerRecord);
10107             elTriggerRow = this.getTrEl(oTriggerRecord);
10108             nTriggerTrIndex = this.getTrIndex(elTriggerRow);
10109
10110             // Selected cell not found on this page
10111             if(nTriggerTrIndex === null) {
10112                 return null;
10113             }
10114             else {
10115                 oTrigger.record = oTriggerRecord;
10116                 oTrigger.recordIndex = nTriggerRecordIndex;
10117                 oTrigger.el = this.getTdEl(oTriggerCell);
10118                 oTrigger.trIndex = nTriggerTrIndex;
10119                 oTrigger.column = this.getColumn(oTriggerCell.columnKey);
10120                 oTrigger.colKeyIndex = oTrigger.column.getKeyIndex();
10121                 oTrigger.cell = oTriggerCell;
10122                 return oTrigger;
10123             }
10124         }
10125     }
10126     // Row mode
10127     else {
10128         oTriggerRecord = this.getLastSelectedRecord();
10129         // No selected rows found
10130         if(!oTriggerRecord) {
10131                 return null;
10132         }
10133         else {
10134             // Selected row found, but is it on current page?
10135             oTriggerRecord = this.getRecord(oTriggerRecord);
10136             nTriggerRecordIndex = this.getRecordIndex(oTriggerRecord);
10137             elTriggerRow = this.getTrEl(oTriggerRecord);
10138             nTriggerTrIndex = this.getTrIndex(elTriggerRow);
10139
10140             // Selected row not found on this page
10141             if(nTriggerTrIndex === null) {
10142                 return null;
10143             }
10144             else {
10145                 oTrigger.record = oTriggerRecord;
10146                 oTrigger.recordIndex = nTriggerRecordIndex;
10147                 oTrigger.el = elTriggerRow;
10148                 oTrigger.trIndex = nTriggerTrIndex;
10149                 return oTrigger;
10150             }
10151         }
10152     }
10153 },
10154
10155 /**
10156  * Returns object literal of values that represent the selection anchor. Used
10157  * to determine selection behavior resulting from a user event.
10158  *
10159  * @method _getSelectionAnchor
10160  * @param oTrigger {Object} (Optional) Object literal of selection trigger values
10161  * (for key events).
10162  * @private
10163  */
10164 _getSelectionAnchor : function(oTrigger) {
10165     var sMode = this.get("selectionMode");
10166     var oAnchor = {};
10167     var oAnchorRecord, nAnchorRecordIndex, nAnchorTrIndex;
10168
10169     // Cell mode
10170     if((sMode == "cellblock") || (sMode == "cellrange") || (sMode == "singlecell")) {
10171         // Validate anchor cell
10172         var oAnchorCell = this._oAnchorCell;
10173         if(!oAnchorCell) {
10174             if(oTrigger) {
10175                 oAnchorCell = this._oAnchorCell = oTrigger.cell;
10176             }
10177             else {
10178                 return null;
10179             }
10180         }
10181         oAnchorRecord = this._oAnchorCell.record;
10182         nAnchorRecordIndex = this._oRecordSet.getRecordIndex(oAnchorRecord);
10183         nAnchorTrIndex = this.getTrIndex(oAnchorRecord);
10184         // If anchor cell is not on this page...
10185         if(nAnchorTrIndex === null) {
10186             // ...set TR index equal to top TR
10187             if(nAnchorRecordIndex < this.getRecordIndex(this.getFirstTrEl())) {
10188                 nAnchorTrIndex = 0;
10189             }
10190             // ...set TR index equal to bottom TR
10191             else {
10192                 nAnchorTrIndex = this.getRecordIndex(this.getLastTrEl());
10193             }
10194         }
10195
10196         oAnchor.record = oAnchorRecord;
10197         oAnchor.recordIndex = nAnchorRecordIndex;
10198         oAnchor.trIndex = nAnchorTrIndex;
10199         oAnchor.column = this._oAnchorCell.column;
10200         oAnchor.colKeyIndex = oAnchor.column.getKeyIndex();
10201         oAnchor.cell = oAnchorCell;
10202         return oAnchor;
10203     }
10204     // Row mode
10205     else {
10206         oAnchorRecord = this._oAnchorRecord;
10207         if(!oAnchorRecord) {
10208             if(oTrigger) {
10209                 oAnchorRecord = this._oAnchorRecord = oTrigger.record;
10210             }
10211             else {
10212                 return null;
10213             }
10214         }
10215
10216         nAnchorRecordIndex = this.getRecordIndex(oAnchorRecord);
10217         nAnchorTrIndex = this.getTrIndex(oAnchorRecord);
10218         // If anchor row is not on this page...
10219         if(nAnchorTrIndex === null) {
10220             // ...set TR index equal to top TR
10221             if(nAnchorRecordIndex < this.getRecordIndex(this.getFirstTrEl())) {
10222                 nAnchorTrIndex = 0;
10223             }
10224             // ...set TR index equal to bottom TR
10225             else {
10226                 nAnchorTrIndex = this.getRecordIndex(this.getLastTrEl());
10227             }
10228         }
10229
10230         oAnchor.record = oAnchorRecord;
10231         oAnchor.recordIndex = nAnchorRecordIndex;
10232         oAnchor.trIndex = nAnchorTrIndex;
10233         return oAnchor;
10234     }
10235 },
10236
10237 /**
10238  * Determines selection behavior resulting from a mouse event when selection mode
10239  * is set to "standard".
10240  *
10241  * @method _handleStandardSelectionByMouse
10242  * @param oArgs.event {HTMLEvent} Event object.
10243  * @param oArgs.target {HTMLElement} Target element.
10244  * @private
10245  */
10246 _handleStandardSelectionByMouse : function(oArgs) {
10247     var elTarget = oArgs.target;
10248
10249     // Validate target row
10250     var elTargetRow = this.getTrEl(elTarget);
10251     if(elTargetRow) {
10252         var e = oArgs.event;
10253         var bSHIFT = e.shiftKey;
10254         var bCTRL = e.ctrlKey || ((navigator.userAgent.toLowerCase().indexOf("mac") != -1) && e.metaKey);
10255
10256         var oTargetRecord = this.getRecord(elTargetRow);
10257         var nTargetRecordIndex = this._oRecordSet.getRecordIndex(oTargetRecord);
10258
10259         var oAnchor = this._getSelectionAnchor();
10260
10261         var i;
10262
10263         // Both SHIFT and CTRL
10264         if(bSHIFT && bCTRL) {
10265             // Validate anchor
10266             if(oAnchor) {
10267                 if(this.isSelected(oAnchor.record)) {
10268                     // Select all rows between anchor row and target row, including target row
10269                     if(oAnchor.recordIndex < nTargetRecordIndex) {
10270                         for(i=oAnchor.recordIndex+1; i<=nTargetRecordIndex; i++) {
10271                             if(!this.isSelected(i)) {
10272                                 this.selectRow(i);
10273                             }
10274                         }
10275                     }
10276                     // Select all rows between target row and anchor row, including target row
10277                     else {
10278                         for(i=oAnchor.recordIndex-1; i>=nTargetRecordIndex; i--) {
10279                             if(!this.isSelected(i)) {
10280                                 this.selectRow(i);
10281                             }
10282                         }
10283                     }
10284                 }
10285                 else {
10286                     // Unselect all rows between anchor row and target row
10287                     if(oAnchor.recordIndex < nTargetRecordIndex) {
10288                         for(i=oAnchor.recordIndex+1; i<=nTargetRecordIndex-1; i++) {
10289                             if(this.isSelected(i)) {
10290                                 this.unselectRow(i);
10291                             }
10292                         }
10293                     }
10294                     // Unselect all rows between target row and anchor row
10295                     else {
10296                         for(i=nTargetRecordIndex+1; i<=oAnchor.recordIndex-1; i++) {
10297                             if(this.isSelected(i)) {
10298                                 this.unselectRow(i);
10299                             }
10300                         }
10301                     }
10302                     // Select the target row
10303                     this.selectRow(oTargetRecord);
10304                 }
10305             }
10306             // Invalid anchor
10307             else {
10308                 // Set anchor
10309                 this._oAnchorRecord = oTargetRecord;
10310
10311                 // Toggle selection of target
10312                 if(this.isSelected(oTargetRecord)) {
10313                     this.unselectRow(oTargetRecord);
10314                 }
10315                 else {
10316                     this.selectRow(oTargetRecord);
10317                 }
10318             }
10319         }
10320          // Only SHIFT
10321         else if(bSHIFT) {
10322             this.unselectAllRows();
10323
10324             // Validate anchor
10325             if(oAnchor) {
10326                 // Select all rows between anchor row and target row,
10327                 // including the anchor row and target row
10328                 if(oAnchor.recordIndex < nTargetRecordIndex) {
10329                     for(i=oAnchor.recordIndex; i<=nTargetRecordIndex; i++) {
10330                         this.selectRow(i);
10331                     }
10332                 }
10333                 // Select all rows between target row and anchor row,
10334                 // including the target row and anchor row
10335                 else {
10336                     for(i=oAnchor.recordIndex; i>=nTargetRecordIndex; i--) {
10337                         this.selectRow(i);
10338                     }
10339                 }
10340             }
10341             // Invalid anchor
10342             else {
10343                 // Set anchor
10344                 this._oAnchorRecord = oTargetRecord;
10345
10346                 // Select target row only
10347                 this.selectRow(oTargetRecord);
10348             }
10349         }
10350         // Only CTRL
10351         else if(bCTRL) {
10352             // Set anchor
10353             this._oAnchorRecord = oTargetRecord;
10354
10355             // Toggle selection of target
10356             if(this.isSelected(oTargetRecord)) {
10357                 this.unselectRow(oTargetRecord);
10358             }
10359             else {
10360                 this.selectRow(oTargetRecord);
10361             }
10362         }
10363         // Neither SHIFT nor CTRL
10364         else {
10365             this._handleSingleSelectionByMouse(oArgs);
10366             return;
10367         }
10368     }
10369 },
10370
10371 /**
10372  * Determines selection behavior resulting from a key event when selection mode
10373  * is set to "standard".
10374  *
10375  * @method _handleStandardSelectionByKey
10376  * @param e {HTMLEvent} Event object.
10377  * @private
10378  */
10379 _handleStandardSelectionByKey : function(e) {
10380     var nKey = Ev.getCharCode(e);
10381
10382     if((nKey == 38) || (nKey == 40)) {
10383         var bSHIFT = e.shiftKey;
10384
10385         // Validate trigger
10386         var oTrigger = this._getSelectionTrigger();
10387         // Arrow selection only works if last selected row is on current page
10388         if(!oTrigger) {
10389             return null;
10390         }
10391
10392         Ev.stopEvent(e);
10393
10394         // Validate anchor
10395         var oAnchor = this._getSelectionAnchor(oTrigger);
10396
10397         // Determine which direction we're going to
10398         if(bSHIFT) {
10399             // Selecting down away from anchor row
10400             if((nKey == 40) && (oAnchor.recordIndex <= oTrigger.trIndex)) {
10401                 this.selectRow(this.getNextTrEl(oTrigger.el));
10402             }
10403             // Selecting up away from anchor row
10404             else if((nKey == 38) && (oAnchor.recordIndex >= oTrigger.trIndex)) {
10405                 this.selectRow(this.getPreviousTrEl(oTrigger.el));
10406             }
10407             // Unselect trigger
10408             else {
10409                 this.unselectRow(oTrigger.el);
10410             }
10411         }
10412         else {
10413             this._handleSingleSelectionByKey(e);
10414         }
10415     }
10416 },
10417
10418 /**
10419  * Determines selection behavior resulting from a mouse event when selection mode
10420  * is set to "single".
10421  *
10422  * @method _handleSingleSelectionByMouse
10423  * @param oArgs.event {HTMLEvent} Event object.
10424  * @param oArgs.target {HTMLElement} Target element.
10425  * @private
10426  */
10427 _handleSingleSelectionByMouse : function(oArgs) {
10428     var elTarget = oArgs.target;
10429
10430     // Validate target row
10431     var elTargetRow = this.getTrEl(elTarget);
10432     if(elTargetRow) {
10433         var oTargetRecord = this.getRecord(elTargetRow);
10434
10435         // Set anchor
10436         this._oAnchorRecord = oTargetRecord;
10437
10438         // Select only target
10439         this.unselectAllRows();
10440         this.selectRow(oTargetRecord);
10441     }
10442 },
10443
10444 /**
10445  * Determines selection behavior resulting from a key event when selection mode
10446  * is set to "single".
10447  *
10448  * @method _handleSingleSelectionByKey
10449  * @param e {HTMLEvent} Event object.
10450  * @private
10451  */
10452 _handleSingleSelectionByKey : function(e) {
10453     var nKey = Ev.getCharCode(e);
10454
10455     if((nKey == 38) || (nKey == 40)) {
10456         // Validate trigger
10457         var oTrigger = this._getSelectionTrigger();
10458         // Arrow selection only works if last selected row is on current page
10459         if(!oTrigger) {
10460             return null;
10461         }
10462
10463         Ev.stopEvent(e);
10464
10465         // Determine the new row to select
10466         var elNew;
10467         if(nKey == 38) { // arrow up
10468             elNew = this.getPreviousTrEl(oTrigger.el);
10469
10470             // Validate new row
10471             if(elNew === null) {
10472                 //TODO: wrap around to last tr on current page
10473                 //elNew = this.getLastTrEl();
10474
10475                 //TODO: wrap back to last tr of previous page
10476
10477                 // Top row selection is sticky
10478                 elNew = this.getFirstTrEl();
10479             }
10480         }
10481         else if(nKey == 40) { // arrow down
10482             elNew = this.getNextTrEl(oTrigger.el);
10483
10484             // Validate new row
10485             if(elNew === null) {
10486                 //TODO: wrap around to first tr on current page
10487                 //elNew = this.getFirstTrEl();
10488
10489                 //TODO: wrap forward to first tr of previous page
10490
10491                 // Bottom row selection is sticky
10492                 elNew = this.getLastTrEl();
10493             }
10494         }
10495
10496         // Unselect all rows
10497         this.unselectAllRows();
10498
10499         // Select the new row
10500         this.selectRow(elNew);
10501
10502         // Set new anchor
10503         this._oAnchorRecord = this.getRecord(elNew);
10504     }
10505 },
10506
10507 /**
10508  * Determines selection behavior resulting from a mouse event when selection mode
10509  * is set to "cellblock".
10510  *
10511  * @method _handleCellBlockSelectionByMouse
10512  * @param oArgs.event {HTMLEvent} Event object.
10513  * @param oArgs.target {HTMLElement} Target element.
10514  * @private
10515  */
10516 _handleCellBlockSelectionByMouse : function(oArgs) {
10517     var elTarget = oArgs.target;
10518
10519     // Validate target cell
10520     var elTargetCell = this.getTdEl(elTarget);
10521     if(elTargetCell) {
10522         var e = oArgs.event;
10523         var bSHIFT = e.shiftKey;
10524         var bCTRL = e.ctrlKey || ((navigator.userAgent.toLowerCase().indexOf("mac") != -1) && e.metaKey);
10525
10526         var elTargetRow = this.getTrEl(elTargetCell);
10527         var nTargetTrIndex = this.getTrIndex(elTargetRow);
10528         var oTargetColumn = this.getColumn(elTargetCell);
10529         var nTargetColKeyIndex = oTargetColumn.getKeyIndex();
10530         var oTargetRecord = this.getRecord(elTargetRow);
10531         var nTargetRecordIndex = this._oRecordSet.getRecordIndex(oTargetRecord);
10532         var oTargetCell = {record:oTargetRecord, column:oTargetColumn};
10533
10534         var oAnchor = this._getSelectionAnchor();
10535
10536         var allRows = this.getTbodyEl().rows;
10537         var startIndex, endIndex, currentRow, i, j;
10538
10539         // Both SHIFT and CTRL
10540         if(bSHIFT && bCTRL) {
10541
10542             // Validate anchor
10543             if(oAnchor) {
10544                 // Anchor is selected
10545                 if(this.isSelected(oAnchor.cell)) {
10546                     // All cells are on the same row
10547                     if(oAnchor.recordIndex === nTargetRecordIndex) {
10548                         // Select all cells between anchor cell and target cell, including target cell
10549                         if(oAnchor.colKeyIndex < nTargetColKeyIndex) {
10550                             for(i=oAnchor.colKeyIndex+1; i<=nTargetColKeyIndex; i++) {
10551                                 this.selectCell(elTargetRow.cells[i]);
10552                             }
10553                         }
10554                         // Select all cells between target cell and anchor cell, including target cell
10555                         else if(nTargetColKeyIndex < oAnchor.colKeyIndex) {
10556                             for(i=nTargetColKeyIndex; i<oAnchor.colKeyIndex; i++) {
10557                                 this.selectCell(elTargetRow.cells[i]);
10558                             }
10559                         }
10560                     }
10561                     // Anchor row is above target row
10562                     else if(oAnchor.recordIndex < nTargetRecordIndex) {
10563                         startIndex = Math.min(oAnchor.colKeyIndex, nTargetColKeyIndex);
10564                         endIndex = Math.max(oAnchor.colKeyIndex, nTargetColKeyIndex);
10565
10566                         // Select all cells from startIndex to endIndex on rows between anchor row and target row
10567                         for(i=oAnchor.trIndex; i<=nTargetTrIndex; i++) {
10568                             for(j=startIndex; j<=endIndex; j++) {
10569                                 this.selectCell(allRows[i].cells[j]);
10570                             }
10571                         }
10572                     }
10573                     // Anchor row is below target row
10574                     else {
10575                         startIndex = Math.min(oAnchor.trIndex, nTargetColKeyIndex);
10576                         endIndex = Math.max(oAnchor.trIndex, nTargetColKeyIndex);
10577
10578                         // Select all cells from startIndex to endIndex on rows between target row and anchor row
10579                         for(i=oAnchor.trIndex; i>=nTargetTrIndex; i--) {
10580                             for(j=endIndex; j>=startIndex; j--) {
10581                                 this.selectCell(allRows[i].cells[j]);
10582                             }
10583                         }
10584                     }
10585                 }
10586                 // Anchor cell is unselected
10587                 else {
10588                     // All cells are on the same row
10589                     if(oAnchor.recordIndex === nTargetRecordIndex) {
10590                         // Unselect all cells between anchor cell and target cell
10591                         if(oAnchor.colKeyIndex < nTargetColKeyIndex) {
10592                             for(i=oAnchor.colKeyIndex+1; i<nTargetColKeyIndex; i++) {
10593                                 this.unselectCell(elTargetRow.cells[i]);
10594                             }
10595                         }
10596                         // Select all cells between target cell and anchor cell
10597                         else if(nTargetColKeyIndex < oAnchor.colKeyIndex) {
10598                             for(i=nTargetColKeyIndex+1; i<oAnchor.colKeyIndex; i++) {
10599                                 this.unselectCell(elTargetRow.cells[i]);
10600                             }
10601                         }
10602                     }
10603                     // Anchor row is above target row
10604                     if(oAnchor.recordIndex < nTargetRecordIndex) {
10605                         // Unselect all cells from anchor cell to target cell
10606                         for(i=oAnchor.trIndex; i<=nTargetTrIndex; i++) {
10607                             currentRow = allRows[i];
10608                             for(j=0; j<currentRow.cells.length; j++) {
10609                                 // This is the anchor row, only unselect cells after the anchor cell
10610                                 if(currentRow.sectionRowIndex === oAnchor.trIndex) {
10611                                     if(j>oAnchor.colKeyIndex) {
10612                                         this.unselectCell(currentRow.cells[j]);
10613                                     }
10614                                 }
10615                                 // This is the target row, only unelect cells before the target cell
10616                                 else if(currentRow.sectionRowIndex === nTargetTrIndex) {
10617                                     if(j<nTargetColKeyIndex) {
10618                                         this.unselectCell(currentRow.cells[j]);
10619                                     }
10620                                 }
10621                                 // Unselect all cells on this row
10622                                 else {
10623                                     this.unselectCell(currentRow.cells[j]);
10624                                 }
10625                             }
10626                         }
10627                     }
10628                     // Anchor row is below target row
10629                     else {
10630                         // Unselect all cells from target cell to anchor cell
10631                         for(i=nTargetTrIndex; i<=oAnchor.trIndex; i++) {
10632                             currentRow = allRows[i];
10633                             for(j=0; j<currentRow.cells.length; j++) {
10634                                 // This is the target row, only unselect cells after the target cell
10635                                 if(currentRow.sectionRowIndex == nTargetTrIndex) {
10636                                     if(j>nTargetColKeyIndex) {
10637                                         this.unselectCell(currentRow.cells[j]);
10638                                     }
10639                                 }
10640                                 // This is the anchor row, only unselect cells before the anchor cell
10641                                 else if(currentRow.sectionRowIndex == oAnchor.trIndex) {
10642                                     if(j<oAnchor.colKeyIndex) {
10643                                         this.unselectCell(currentRow.cells[j]);
10644                                     }
10645                                 }
10646                                 // Unselect all cells on this row
10647                                 else {
10648                                     this.unselectCell(currentRow.cells[j]);
10649                                 }
10650                             }
10651                         }
10652                     }
10653
10654                     // Select the target cell
10655                     this.selectCell(elTargetCell);
10656                 }
10657             }
10658             // Invalid anchor
10659             else {
10660                 // Set anchor
10661                 this._oAnchorCell = oTargetCell;
10662
10663                 // Toggle selection of target
10664                 if(this.isSelected(oTargetCell)) {
10665                     this.unselectCell(oTargetCell);
10666                 }
10667                 else {
10668                     this.selectCell(oTargetCell);
10669                 }
10670             }
10671
10672         }
10673          // Only SHIFT
10674         else if(bSHIFT) {
10675             this.unselectAllCells();
10676
10677             // Validate anchor
10678             if(oAnchor) {
10679                 // All cells are on the same row
10680                 if(oAnchor.recordIndex === nTargetRecordIndex) {
10681                     // Select all cells between anchor cell and target cell,
10682                     // including the anchor cell and target cell
10683                     if(oAnchor.colKeyIndex < nTargetColKeyIndex) {
10684                         for(i=oAnchor.colKeyIndex; i<=nTargetColKeyIndex; i++) {
10685                             this.selectCell(elTargetRow.cells[i]);
10686                         }
10687                     }
10688                     // Select all cells between target cell and anchor cell
10689                     // including the target cell and anchor cell
10690                     else if(nTargetColKeyIndex < oAnchor.colKeyIndex) {
10691                         for(i=nTargetColKeyIndex; i<=oAnchor.colKeyIndex; i++) {
10692                             this.selectCell(elTargetRow.cells[i]);
10693                         }
10694                     }
10695                 }
10696                 // Anchor row is above target row
10697                 else if(oAnchor.recordIndex < nTargetRecordIndex) {
10698                     // Select the cellblock from anchor cell to target cell
10699                     // including the anchor cell and the target cell
10700                     startIndex = Math.min(oAnchor.colKeyIndex, nTargetColKeyIndex);
10701                     endIndex = Math.max(oAnchor.colKeyIndex, nTargetColKeyIndex);
10702
10703                     for(i=oAnchor.trIndex; i<=nTargetTrIndex; i++) {
10704                         for(j=startIndex; j<=endIndex; j++) {
10705                             this.selectCell(allRows[i].cells[j]);
10706                         }
10707                     }
10708                 }
10709                 // Anchor row is below target row
10710                 else {
10711                     // Select the cellblock from target cell to anchor cell
10712                     // including the target cell and the anchor cell
10713                     startIndex = Math.min(oAnchor.colKeyIndex, nTargetColKeyIndex);
10714                     endIndex = Math.max(oAnchor.colKeyIndex, nTargetColKeyIndex);
10715
10716                     for(i=nTargetTrIndex; i<=oAnchor.trIndex; i++) {
10717                         for(j=startIndex; j<=endIndex; j++) {
10718                             this.selectCell(allRows[i].cells[j]);
10719                         }
10720                     }
10721                 }
10722             }
10723             // Invalid anchor
10724             else {
10725                 // Set anchor
10726                 this._oAnchorCell = oTargetCell;
10727
10728                 // Select target only
10729                 this.selectCell(oTargetCell);
10730             }
10731         }
10732         // Only CTRL
10733         else if(bCTRL) {
10734
10735             // Set anchor
10736             this._oAnchorCell = oTargetCell;
10737
10738             // Toggle selection of target
10739             if(this.isSelected(oTargetCell)) {
10740                 this.unselectCell(oTargetCell);
10741             }
10742             else {
10743                 this.selectCell(oTargetCell);
10744             }
10745
10746         }
10747         // Neither SHIFT nor CTRL
10748         else {
10749             this._handleSingleCellSelectionByMouse(oArgs);
10750         }
10751     }
10752 },
10753
10754 /**
10755  * Determines selection behavior resulting from a key event when selection mode
10756  * is set to "cellblock".
10757  *
10758  * @method _handleCellBlockSelectionByKey
10759  * @param e {HTMLEvent} Event object.
10760  * @private
10761  */
10762 _handleCellBlockSelectionByKey : function(e) {
10763     var nKey = Ev.getCharCode(e);
10764     var bSHIFT = e.shiftKey;
10765     if((nKey == 9) || !bSHIFT) {
10766         this._handleSingleCellSelectionByKey(e);
10767         return;
10768     }
10769
10770     if((nKey > 36) && (nKey < 41)) {
10771         // Validate trigger
10772         var oTrigger = this._getSelectionTrigger();
10773         // Arrow selection only works if last selected row is on current page
10774         if(!oTrigger) {
10775             return null;
10776         }
10777
10778         Ev.stopEvent(e);
10779
10780         // Validate anchor
10781         var oAnchor = this._getSelectionAnchor(oTrigger);
10782
10783         var i, startIndex, endIndex, elNew, elNewRow;
10784         var allRows = this.getTbodyEl().rows;
10785         var elThisRow = oTrigger.el.parentNode;
10786
10787         // Determine which direction we're going to
10788
10789         if(nKey == 40) { // arrow down
10790             // Selecting away from anchor cell
10791             if(oAnchor.recordIndex <= oTrigger.recordIndex) {
10792                 // Select the horiz block on the next row...
10793                 // ...making sure there is room below the trigger row
10794                 elNewRow = this.getNextTrEl(oTrigger.el);
10795                 if(elNewRow) {
10796                     startIndex = oAnchor.colKeyIndex;
10797                     endIndex = oTrigger.colKeyIndex;
10798                     // ...going left
10799                     if(startIndex > endIndex) {
10800                         for(i=startIndex; i>=endIndex; i--) {
10801                             elNew = elNewRow.cells[i];
10802                             this.selectCell(elNew);
10803                         }
10804                     }
10805                     // ... going right
10806                     else {
10807                         for(i=startIndex; i<=endIndex; i++) {
10808                             elNew = elNewRow.cells[i];
10809                             this.selectCell(elNew);
10810                         }
10811                     }
10812                 }
10813             }
10814             // Unselecting towards anchor cell
10815             else {
10816                 startIndex = Math.min(oAnchor.colKeyIndex, oTrigger.colKeyIndex);
10817                 endIndex = Math.max(oAnchor.colKeyIndex, oTrigger.colKeyIndex);
10818                 // Unselect the horiz block on this row towards the next row
10819                 for(i=startIndex; i<=endIndex; i++) {
10820                     this.unselectCell(elThisRow.cells[i]);
10821                 }
10822             }
10823         }
10824         // Arrow up
10825         else if(nKey == 38) {
10826             // Selecting away from anchor cell
10827             if(oAnchor.recordIndex >= oTrigger.recordIndex) {
10828                 // Select the horiz block on the previous row...
10829                 // ...making sure there is room
10830                 elNewRow = this.getPreviousTrEl(oTrigger.el);
10831                 if(elNewRow) {
10832                     // Select in order from anchor to trigger...
10833                     startIndex = oAnchor.colKeyIndex;
10834                     endIndex = oTrigger.colKeyIndex;
10835                     // ...going left
10836                     if(startIndex > endIndex) {
10837                         for(i=startIndex; i>=endIndex; i--) {
10838                             elNew = elNewRow.cells[i];
10839                             this.selectCell(elNew);
10840                         }
10841                     }
10842                     // ... going right
10843                     else {
10844                         for(i=startIndex; i<=endIndex; i++) {
10845                             elNew = elNewRow.cells[i];
10846                             this.selectCell(elNew);
10847                         }
10848                     }
10849                 }
10850             }
10851             // Unselecting towards anchor cell
10852             else {
10853                 startIndex = Math.min(oAnchor.colKeyIndex, oTrigger.colKeyIndex);
10854                 endIndex = Math.max(oAnchor.colKeyIndex, oTrigger.colKeyIndex);
10855                 // Unselect the horiz block on this row towards the previous row
10856                 for(i=startIndex; i<=endIndex; i++) {
10857                     this.unselectCell(elThisRow.cells[i]);
10858                 }
10859             }
10860         }
10861         // Arrow right
10862         else if(nKey == 39) {
10863             // Selecting away from anchor cell
10864             if(oAnchor.colKeyIndex <= oTrigger.colKeyIndex) {
10865                 // Select the next vert block to the right...
10866                 // ...making sure there is room
10867                 if(oTrigger.colKeyIndex < elThisRow.cells.length-1) {
10868                     // Select in order from anchor to trigger...
10869                     startIndex = oAnchor.trIndex;
10870                     endIndex = oTrigger.trIndex;
10871                     // ...going up
10872                     if(startIndex > endIndex) {
10873                         for(i=startIndex; i>=endIndex; i--) {
10874                             elNew = allRows[i].cells[oTrigger.colKeyIndex+1];
10875                             this.selectCell(elNew);
10876                         }
10877                     }
10878                     // ... going down
10879                     else {
10880                         for(i=startIndex; i<=endIndex; i++) {
10881                             elNew = allRows[i].cells[oTrigger.colKeyIndex+1];
10882                             this.selectCell(elNew);
10883                         }
10884                     }
10885                 }
10886             }
10887             // Unselecting towards anchor cell
10888             else {
10889                 // Unselect the vert block on this column towards the right
10890                 startIndex = Math.min(oAnchor.trIndex, oTrigger.trIndex);
10891                 endIndex = Math.max(oAnchor.trIndex, oTrigger.trIndex);
10892                 for(i=startIndex; i<=endIndex; i++) {
10893                     this.unselectCell(allRows[i].cells[oTrigger.colKeyIndex]);
10894                 }
10895             }
10896         }
10897         // Arrow left
10898         else if(nKey == 37) {
10899             // Selecting away from anchor cell
10900             if(oAnchor.colKeyIndex >= oTrigger.colKeyIndex) {
10901                 //Select the previous vert block to the left
10902                 if(oTrigger.colKeyIndex > 0) {
10903                     // Select in order from anchor to trigger...
10904                     startIndex = oAnchor.trIndex;
10905                     endIndex = oTrigger.trIndex;
10906                     // ...going up
10907                     if(startIndex > endIndex) {
10908                         for(i=startIndex; i>=endIndex; i--) {
10909                             elNew = allRows[i].cells[oTrigger.colKeyIndex-1];
10910                             this.selectCell(elNew);
10911                         }
10912                     }
10913                     // ... going down
10914                     else {
10915                         for(i=startIndex; i<=endIndex; i++) {
10916                             elNew = allRows[i].cells[oTrigger.colKeyIndex-1];
10917                             this.selectCell(elNew);
10918                         }
10919                     }
10920                 }
10921             }
10922             // Unselecting towards anchor cell
10923             else {
10924                 // Unselect the vert block on this column towards the left
10925                 startIndex = Math.min(oAnchor.trIndex, oTrigger.trIndex);
10926                 endIndex = Math.max(oAnchor.trIndex, oTrigger.trIndex);
10927                 for(i=startIndex; i<=endIndex; i++) {
10928                     this.unselectCell(allRows[i].cells[oTrigger.colKeyIndex]);
10929                 }
10930             }
10931         }
10932     }
10933 },
10934
10935 /**
10936  * Determines selection behavior resulting from a mouse event when selection mode
10937  * is set to "cellrange".
10938  *
10939  * @method _handleCellRangeSelectionByMouse
10940  * @param oArgs.event {HTMLEvent} Event object.
10941  * @param oArgs.target {HTMLElement} Target element.
10942  * @private
10943  */
10944 _handleCellRangeSelectionByMouse : function(oArgs) {
10945     var elTarget = oArgs.target;
10946
10947     // Validate target cell
10948     var elTargetCell = this.getTdEl(elTarget);
10949     if(elTargetCell) {
10950         var e = oArgs.event;
10951         var bSHIFT = e.shiftKey;
10952         var bCTRL = e.ctrlKey || ((navigator.userAgent.toLowerCase().indexOf("mac") != -1) && e.metaKey);
10953
10954         var elTargetRow = this.getTrEl(elTargetCell);
10955         var nTargetTrIndex = this.getTrIndex(elTargetRow);
10956         var oTargetColumn = this.getColumn(elTargetCell);
10957         var nTargetColKeyIndex = oTargetColumn.getKeyIndex();
10958         var oTargetRecord = this.getRecord(elTargetRow);
10959         var nTargetRecordIndex = this._oRecordSet.getRecordIndex(oTargetRecord);
10960         var oTargetCell = {record:oTargetRecord, column:oTargetColumn};
10961
10962         var oAnchor = this._getSelectionAnchor();
10963
10964         var allRows = this.getTbodyEl().rows;
10965         var currentRow, i, j;
10966
10967         // Both SHIFT and CTRL
10968         if(bSHIFT && bCTRL) {
10969
10970             // Validate anchor
10971             if(oAnchor) {
10972                 // Anchor is selected
10973                 if(this.isSelected(oAnchor.cell)) {
10974                     // All cells are on the same row
10975                     if(oAnchor.recordIndex === nTargetRecordIndex) {
10976                         // Select all cells between anchor cell and target cell, including target cell
10977                         if(oAnchor.colKeyIndex < nTargetColKeyIndex) {
10978                             for(i=oAnchor.colKeyIndex+1; i<=nTargetColKeyIndex; i++) {
10979                                 this.selectCell(elTargetRow.cells[i]);
10980                             }
10981                         }
10982                         // Select all cells between target cell and anchor cell, including target cell
10983                         else if(nTargetColKeyIndex < oAnchor.colKeyIndex) {
10984                             for(i=nTargetColKeyIndex; i<oAnchor.colKeyIndex; i++) {
10985                                 this.selectCell(elTargetRow.cells[i]);
10986                             }
10987                         }
10988                     }
10989                     // Anchor row is above target row
10990                     else if(oAnchor.recordIndex < nTargetRecordIndex) {
10991                         // Select all cells on anchor row from anchor cell to the end of the row
10992                         for(i=oAnchor.colKeyIndex+1; i<elTargetRow.cells.length; i++) {
10993                             this.selectCell(elTargetRow.cells[i]);
10994                         }
10995
10996                         // Select all cells on all rows between anchor row and target row
10997                         for(i=oAnchor.trIndex+1; i<nTargetTrIndex; i++) {
10998                             for(j=0; j<allRows[i].cells.length; j++){
10999                                 this.selectCell(allRows[i].cells[j]);
11000                             }
11001                         }
11002
11003                         // Select all cells on target row from first cell to the target cell
11004                         for(i=0; i<=nTargetColKeyIndex; i++) {
11005                             this.selectCell(elTargetRow.cells[i]);
11006                         }
11007                     }
11008                     // Anchor row is below target row
11009                     else {
11010                         // Select all cells on target row from target cell to the end of the row
11011                         for(i=nTargetColKeyIndex; i<elTargetRow.cells.length; i++) {
11012                             this.selectCell(elTargetRow.cells[i]);
11013                         }
11014
11015                         // Select all cells on all rows between target row and anchor row
11016                         for(i=nTargetTrIndex+1; i<oAnchor.trIndex; i++) {
11017                             for(j=0; j<allRows[i].cells.length; j++){
11018                                 this.selectCell(allRows[i].cells[j]);
11019                             }
11020                         }
11021
11022                         // Select all cells on anchor row from first cell to the anchor cell
11023                         for(i=0; i<oAnchor.colKeyIndex; i++) {
11024                             this.selectCell(elTargetRow.cells[i]);
11025                         }
11026                     }
11027                 }
11028                 // Anchor cell is unselected
11029                 else {
11030                     // All cells are on the same row
11031                     if(oAnchor.recordIndex === nTargetRecordIndex) {
11032                         // Unselect all cells between anchor cell and target cell
11033                         if(oAnchor.colKeyIndex < nTargetColKeyIndex) {
11034                             for(i=oAnchor.colKeyIndex+1; i<nTargetColKeyIndex; i++) {
11035                                 this.unselectCell(elTargetRow.cells[i]);
11036                             }
11037                         }
11038                         // Select all cells between target cell and anchor cell
11039                         else if(nTargetColKeyIndex < oAnchor.colKeyIndex) {
11040                             for(i=nTargetColKeyIndex+1; i<oAnchor.colKeyIndex; i++) {
11041                                 this.unselectCell(elTargetRow.cells[i]);
11042                             }
11043                         }
11044                     }
11045                     // Anchor row is above target row
11046                     if(oAnchor.recordIndex < nTargetRecordIndex) {
11047                         // Unselect all cells from anchor cell to target cell
11048                         for(i=oAnchor.trIndex; i<=nTargetTrIndex; i++) {
11049                             currentRow = allRows[i];
11050                             for(j=0; j<currentRow.cells.length; j++) {
11051                                 // This is the anchor row, only unselect cells after the anchor cell
11052                                 if(currentRow.sectionRowIndex === oAnchor.trIndex) {
11053                                     if(j>oAnchor.colKeyIndex) {
11054                                         this.unselectCell(currentRow.cells[j]);
11055                                     }
11056                                 }
11057                                 // This is the target row, only unelect cells before the target cell
11058                                 else if(currentRow.sectionRowIndex === nTargetTrIndex) {
11059                                     if(j<nTargetColKeyIndex) {
11060                                         this.unselectCell(currentRow.cells[j]);
11061                                     }
11062                                 }
11063                                 // Unselect all cells on this row
11064                                 else {
11065                                     this.unselectCell(currentRow.cells[j]);
11066                                 }
11067                             }
11068                         }
11069                     }
11070                     // Anchor row is below target row
11071                     else {
11072                         // Unselect all cells from target cell to anchor cell
11073                         for(i=nTargetTrIndex; i<=oAnchor.trIndex; i++) {
11074                             currentRow = allRows[i];
11075                             for(j=0; j<currentRow.cells.length; j++) {
11076                                 // This is the target row, only unselect cells after the target cell
11077                                 if(currentRow.sectionRowIndex == nTargetTrIndex) {
11078                                     if(j>nTargetColKeyIndex) {
11079                                         this.unselectCell(currentRow.cells[j]);
11080                                     }
11081                                 }
11082                                 // This is the anchor row, only unselect cells before the anchor cell
11083                                 else if(currentRow.sectionRowIndex == oAnchor.trIndex) {
11084                                     if(j<oAnchor.colKeyIndex) {
11085                                         this.unselectCell(currentRow.cells[j]);
11086                                     }
11087                                 }
11088                                 // Unselect all cells on this row
11089                                 else {
11090                                     this.unselectCell(currentRow.cells[j]);
11091                                 }
11092                             }
11093                         }
11094                     }
11095
11096                     // Select the target cell
11097                     this.selectCell(elTargetCell);
11098                 }
11099             }
11100             // Invalid anchor
11101             else {
11102                 // Set anchor
11103                 this._oAnchorCell = oTargetCell;
11104
11105                 // Toggle selection of target
11106                 if(this.isSelected(oTargetCell)) {
11107                     this.unselectCell(oTargetCell);
11108                 }
11109                 else {
11110                     this.selectCell(oTargetCell);
11111                 }
11112             }
11113         }
11114          // Only SHIFT
11115         else if(bSHIFT) {
11116
11117             this.unselectAllCells();
11118
11119             // Validate anchor
11120             if(oAnchor) {
11121                 // All cells are on the same row
11122                 if(oAnchor.recordIndex === nTargetRecordIndex) {
11123                     // Select all cells between anchor cell and target cell,
11124                     // including the anchor cell and target cell
11125                     if(oAnchor.colKeyIndex < nTargetColKeyIndex) {
11126                         for(i=oAnchor.colKeyIndex; i<=nTargetColKeyIndex; i++) {
11127                             this.selectCell(elTargetRow.cells[i]);
11128                         }
11129                     }
11130                     // Select all cells between target cell and anchor cell
11131                     // including the target cell and anchor cell
11132                     else if(nTargetColKeyIndex < oAnchor.colKeyIndex) {
11133                         for(i=nTargetColKeyIndex; i<=oAnchor.colKeyIndex; i++) {
11134                             this.selectCell(elTargetRow.cells[i]);
11135                         }
11136                     }
11137                 }
11138                 // Anchor row is above target row
11139                 else if(oAnchor.recordIndex < nTargetRecordIndex) {
11140                     // Select all cells from anchor cell to target cell
11141                     // including the anchor cell and target cell
11142                     for(i=oAnchor.trIndex; i<=nTargetTrIndex; i++) {
11143                         currentRow = allRows[i];
11144                         for(j=0; j<currentRow.cells.length; j++) {
11145                             // This is the anchor row, only select the anchor cell and after
11146                             if(currentRow.sectionRowIndex == oAnchor.trIndex) {
11147                                 if(j>=oAnchor.colKeyIndex) {
11148                                     this.selectCell(currentRow.cells[j]);
11149                                 }
11150                             }
11151                             // This is the target row, only select the target cell and before
11152                             else if(currentRow.sectionRowIndex == nTargetTrIndex) {
11153                                 if(j<=nTargetColKeyIndex) {
11154                                     this.selectCell(currentRow.cells[j]);
11155                                 }
11156                             }
11157                             // Select all cells on this row
11158                             else {
11159                                 this.selectCell(currentRow.cells[j]);
11160                             }
11161                         }
11162                     }
11163                 }
11164                 // Anchor row is below target row
11165                 else {
11166                     // Select all cells from target cell to anchor cell,
11167                     // including the target cell and anchor cell
11168                     for(i=nTargetTrIndex; i<=oAnchor.trIndex; i++) {
11169                         currentRow = allRows[i];
11170                         for(j=0; j<currentRow.cells.length; j++) {
11171                             // This is the target row, only select the target cell and after
11172                             if(currentRow.sectionRowIndex == nTargetTrIndex) {
11173                                 if(j>=nTargetColKeyIndex) {
11174                                     this.selectCell(currentRow.cells[j]);
11175                                 }
11176                             }
11177                             // This is the anchor row, only select the anchor cell and before
11178                             else if(currentRow.sectionRowIndex == oAnchor.trIndex) {
11179                                 if(j<=oAnchor.colKeyIndex) {
11180                                     this.selectCell(currentRow.cells[j]);
11181                                 }
11182                             }
11183                             // Select all cells on this row
11184                             else {
11185                                 this.selectCell(currentRow.cells[j]);
11186                             }
11187                         }
11188                     }
11189                 }
11190             }
11191             // Invalid anchor
11192             else {
11193                 // Set anchor
11194                 this._oAnchorCell = oTargetCell;
11195
11196                 // Select target only
11197                 this.selectCell(oTargetCell);
11198             }
11199
11200
11201         }
11202         // Only CTRL
11203         else if(bCTRL) {
11204
11205             // Set anchor
11206             this._oAnchorCell = oTargetCell;
11207
11208             // Toggle selection of target
11209             if(this.isSelected(oTargetCell)) {
11210                 this.unselectCell(oTargetCell);
11211             }
11212             else {
11213                 this.selectCell(oTargetCell);
11214             }
11215
11216         }
11217         // Neither SHIFT nor CTRL
11218         else {
11219             this._handleSingleCellSelectionByMouse(oArgs);
11220         }
11221     }
11222 },
11223
11224 /**
11225  * Determines selection behavior resulting from a key event when selection mode
11226  * is set to "cellrange".
11227  *
11228  * @method _handleCellRangeSelectionByKey
11229  * @param e {HTMLEvent} Event object.
11230  * @private
11231  */
11232 _handleCellRangeSelectionByKey : function(e) {
11233     var nKey = Ev.getCharCode(e);
11234     var bSHIFT = e.shiftKey;
11235     if((nKey == 9) || !bSHIFT) {
11236         this._handleSingleCellSelectionByKey(e);
11237         return;
11238     }
11239
11240     if((nKey > 36) && (nKey < 41)) {
11241         // Validate trigger
11242         var oTrigger = this._getSelectionTrigger();
11243         // Arrow selection only works if last selected row is on current page
11244         if(!oTrigger) {
11245             return null;
11246         }
11247
11248         Ev.stopEvent(e);
11249
11250         // Validate anchor
11251         var oAnchor = this._getSelectionAnchor(oTrigger);
11252
11253         var i, elNewRow, elNew;
11254         var allRows = this.getTbodyEl().rows;
11255         var elThisRow = oTrigger.el.parentNode;
11256
11257         // Arrow down
11258         if(nKey == 40) {
11259             elNewRow = this.getNextTrEl(oTrigger.el);
11260
11261             // Selecting away from anchor cell
11262             if(oAnchor.recordIndex <= oTrigger.recordIndex) {
11263                 // Select all cells to the end of this row
11264                 for(i=oTrigger.colKeyIndex+1; i<elThisRow.cells.length; i++){
11265                     elNew = elThisRow.cells[i];
11266                     this.selectCell(elNew);
11267                 }
11268
11269                 // Select some of the cells on the next row down
11270                 if(elNewRow) {
11271                     for(i=0; i<=oTrigger.colKeyIndex; i++){
11272                         elNew = elNewRow.cells[i];
11273                         this.selectCell(elNew);
11274                     }
11275                 }
11276             }
11277             // Unselecting towards anchor cell
11278             else {
11279                 // Unselect all cells to the end of this row
11280                 for(i=oTrigger.colKeyIndex; i<elThisRow.cells.length; i++){
11281                     this.unselectCell(elThisRow.cells[i]);
11282                 }
11283
11284                 // Unselect some of the cells on the next row down
11285                 if(elNewRow) {
11286                     for(i=0; i<oTrigger.colKeyIndex; i++){
11287                         this.unselectCell(elNewRow.cells[i]);
11288                     }
11289                 }
11290             }
11291         }
11292         // Arrow up
11293         else if(nKey == 38) {
11294             elNewRow = this.getPreviousTrEl(oTrigger.el);
11295
11296             // Selecting away from anchor cell
11297             if(oAnchor.recordIndex >= oTrigger.recordIndex) {
11298                 // Select all the cells to the beginning of this row
11299                 for(i=oTrigger.colKeyIndex-1; i>-1; i--){
11300                     elNew = elThisRow.cells[i];
11301                     this.selectCell(elNew);
11302                 }
11303
11304                 // Select some of the cells from the end of the previous row
11305                 if(elNewRow) {
11306                     for(i=elThisRow.cells.length-1; i>=oTrigger.colKeyIndex; i--){
11307                         elNew = elNewRow.cells[i];
11308                         this.selectCell(elNew);
11309                     }
11310                 }
11311             }
11312             // Unselecting towards anchor cell
11313             else {
11314                 // Unselect all the cells to the beginning of this row
11315                 for(i=oTrigger.colKeyIndex; i>-1; i--){
11316                     this.unselectCell(elThisRow.cells[i]);
11317                 }
11318
11319                 // Unselect some of the cells from the end of the previous row
11320                 if(elNewRow) {
11321                     for(i=elThisRow.cells.length-1; i>oTrigger.colKeyIndex; i--){
11322                         this.unselectCell(elNewRow.cells[i]);
11323                     }
11324                 }
11325             }
11326         }
11327         // Arrow right
11328         else if(nKey == 39) {
11329             elNewRow = this.getNextTrEl(oTrigger.el);
11330
11331             // Selecting away from anchor cell
11332             if(oAnchor.recordIndex < oTrigger.recordIndex) {
11333                 // Select the next cell to the right
11334                 if(oTrigger.colKeyIndex < elThisRow.cells.length-1) {
11335                     elNew = elThisRow.cells[oTrigger.colKeyIndex+1];
11336                     this.selectCell(elNew);
11337                 }
11338                 // Select the first cell of the next row
11339                 else if(elNewRow) {
11340                     elNew = elNewRow.cells[0];
11341                     this.selectCell(elNew);
11342                 }
11343             }
11344             // Unselecting towards anchor cell
11345             else if(oAnchor.recordIndex > oTrigger.recordIndex) {
11346                 this.unselectCell(elThisRow.cells[oTrigger.colKeyIndex]);
11347
11348                 // Unselect this cell towards the right
11349                 if(oTrigger.colKeyIndex < elThisRow.cells.length-1) {
11350                 }
11351                 // Unselect this cells towards the first cell of the next row
11352                 else {
11353                 }
11354             }
11355             // Anchor is on this row
11356             else {
11357                 // Selecting away from anchor
11358                 if(oAnchor.colKeyIndex <= oTrigger.colKeyIndex) {
11359                     // Select the next cell to the right
11360                     if(oTrigger.colKeyIndex < elThisRow.cells.length-1) {
11361                         elNew = elThisRow.cells[oTrigger.colKeyIndex+1];
11362                         this.selectCell(elNew);
11363                     }
11364                     // Select the first cell on the next row
11365                     else if(oTrigger.trIndex < allRows.length-1){
11366                         elNew = elNewRow.cells[0];
11367                         this.selectCell(elNew);
11368                     }
11369                 }
11370                 // Unselecting towards anchor
11371                 else {
11372                     // Unselect this cell towards the right
11373                     this.unselectCell(elThisRow.cells[oTrigger.colKeyIndex]);
11374                 }
11375             }
11376         }
11377         // Arrow left
11378         else if(nKey == 37) {
11379             elNewRow = this.getPreviousTrEl(oTrigger.el);
11380
11381             // Unselecting towards the anchor
11382             if(oAnchor.recordIndex < oTrigger.recordIndex) {
11383                 this.unselectCell(elThisRow.cells[oTrigger.colKeyIndex]);
11384
11385                 // Unselect this cell towards the left
11386                 if(oTrigger.colKeyIndex > 0) {
11387                 }
11388                 // Unselect this cell towards the last cell of the previous row
11389                 else {
11390                 }
11391             }
11392             // Selecting towards the anchor
11393             else if(oAnchor.recordIndex > oTrigger.recordIndex) {
11394                 // Select the next cell to the left
11395                 if(oTrigger.colKeyIndex > 0) {
11396                     elNew = elThisRow.cells[oTrigger.colKeyIndex-1];
11397                     this.selectCell(elNew);
11398                 }
11399                 // Select the last cell of the previous row
11400                 else if(oTrigger.trIndex > 0){
11401                     elNew = elNewRow.cells[elNewRow.cells.length-1];
11402                     this.selectCell(elNew);
11403                 }
11404             }
11405             // Anchor is on this row
11406             else {
11407                 // Selecting away from anchor cell
11408                 if(oAnchor.colKeyIndex >= oTrigger.colKeyIndex) {
11409                     // Select the next cell to the left
11410                     if(oTrigger.colKeyIndex > 0) {
11411                         elNew = elThisRow.cells[oTrigger.colKeyIndex-1];
11412                         this.selectCell(elNew);
11413                     }
11414                     // Select the last cell of the previous row
11415                     else if(oTrigger.trIndex > 0){
11416                         elNew = elNewRow.cells[elNewRow.cells.length-1];
11417                         this.selectCell(elNew);
11418                     }
11419                 }
11420                 // Unselecting towards anchor cell
11421                 else {
11422                     this.unselectCell(elThisRow.cells[oTrigger.colKeyIndex]);
11423
11424                     // Unselect this cell towards the left
11425                     if(oTrigger.colKeyIndex > 0) {
11426                     }
11427                     // Unselect this cell towards the last cell of the previous row
11428                     else {
11429                     }
11430                 }
11431             }
11432         }
11433     }
11434 },
11435
11436 /**
11437  * Determines selection behavior resulting from a mouse event when selection mode
11438  * is set to "singlecell".
11439  *
11440  * @method _handleSingleCellSelectionByMouse
11441  * @param oArgs.event {HTMLEvent} Event object.
11442  * @param oArgs.target {HTMLElement} Target element.
11443  * @private
11444  */
11445 _handleSingleCellSelectionByMouse : function(oArgs) {
11446     var elTarget = oArgs.target;
11447
11448     // Validate target cell
11449     var elTargetCell = this.getTdEl(elTarget);
11450     if(elTargetCell) {
11451         var elTargetRow = this.getTrEl(elTargetCell);
11452         var oTargetRecord = this.getRecord(elTargetRow);
11453         var oTargetColumn = this.getColumn(elTargetCell);
11454         var oTargetCell = {record:oTargetRecord, column:oTargetColumn};
11455
11456         // Set anchor
11457         this._oAnchorCell = oTargetCell;
11458
11459         // Select only target
11460         this.unselectAllCells();
11461         this.selectCell(oTargetCell);
11462     }
11463 },
11464
11465 /**
11466  * Determines selection behavior resulting from a key event when selection mode
11467  * is set to "singlecell".
11468  *
11469  * @method _handleSingleCellSelectionByKey
11470  * @param e {HTMLEvent} Event object.
11471  * @private
11472  */
11473 _handleSingleCellSelectionByKey : function(e) {
11474     var nKey = Ev.getCharCode(e);
11475     if((nKey == 9) || ((nKey > 36) && (nKey < 41))) {
11476         var bSHIFT = e.shiftKey;
11477
11478         // Validate trigger
11479         var oTrigger = this._getSelectionTrigger();
11480         // Arrow selection only works if last selected row is on current page
11481         if(!oTrigger) {
11482             return null;
11483         }
11484
11485         // Determine the new cell to select
11486         var elNew;
11487         if(nKey == 40) { // Arrow down
11488             elNew = this.getBelowTdEl(oTrigger.el);
11489
11490             // Validate new cell
11491             if(elNew === null) {
11492                 //TODO: wrap around to first tr on current page
11493
11494                 //TODO: wrap forward to first tr of next page
11495
11496                 // Bottom selection is sticky
11497                 elNew = oTrigger.el;
11498             }
11499         }
11500         else if(nKey == 38) { // Arrow up
11501             elNew = this.getAboveTdEl(oTrigger.el);
11502
11503             // Validate new cell
11504             if(elNew === null) {
11505                 //TODO: wrap around to last tr on current page
11506
11507                 //TODO: wrap back to last tr of previous page
11508
11509                 // Top selection is sticky
11510                 elNew = oTrigger.el;
11511             }
11512         }
11513         else if((nKey == 39) || (!bSHIFT && (nKey == 9))) { // Arrow right or tab
11514             elNew = this.getNextTdEl(oTrigger.el);
11515
11516             // Validate new cell
11517             if(elNew === null) {
11518                 //TODO: wrap around to first td on current page
11519
11520                 //TODO: wrap forward to first td of next page
11521
11522                 // Top-left selection is sticky, and release TAB focus
11523                 //elNew = oTrigger.el;
11524                 return;
11525             }
11526         }
11527         else if((nKey == 37) || (bSHIFT && (nKey == 9))) { // Arrow left or shift-tab
11528             elNew = this.getPreviousTdEl(oTrigger.el);
11529
11530             // Validate new cell
11531             if(elNew === null) {
11532                 //TODO: wrap around to last td on current page
11533
11534                 //TODO: wrap back to last td of previous page
11535
11536                 // Bottom-right selection is sticky, and release TAB focus
11537                 //elNew = oTrigger.el;
11538                 return;
11539             }
11540         }
11541
11542         Ev.stopEvent(e);
11543         
11544         // Unselect all cells
11545         this.unselectAllCells();
11546
11547         // Select the new cell
11548         this.selectCell(elNew);
11549
11550         // Set new anchor
11551         this._oAnchorCell = {record:this.getRecord(elNew), column:this.getColumn(elNew)};
11552     }
11553 },
11554
11555 /**
11556  * Returns array of selected TR elements on the page.
11557  *
11558  * @method getSelectedTrEls
11559  * @return {HTMLElement[]} Array of selected TR elements.
11560  */
11561 getSelectedTrEls : function() {
11562     return Dom.getElementsByClassName(DT.CLASS_SELECTED,"tr",this._elTbody);
11563 },
11564
11565 /**
11566  * Sets given row to the selected state.
11567  *
11568  * @method selectRow
11569  * @param row {HTMLElement | String | YAHOO.widget.Record | Number} HTML element
11570  * reference or ID string, Record instance, or RecordSet position index.
11571  */
11572 selectRow : function(row) {
11573     var oRecord, elRow;
11574
11575     if(row instanceof YAHOO.widget.Record) {
11576         oRecord = this._oRecordSet.getRecord(row);
11577         elRow = this.getTrEl(oRecord);
11578     }
11579     else if(lang.isNumber(row)) {
11580         oRecord = this.getRecord(row);
11581         elRow = this.getTrEl(oRecord);
11582     }
11583     else {
11584         elRow = this.getTrEl(row);
11585         oRecord = this.getRecord(elRow);
11586     }
11587
11588     if(oRecord) {
11589         // Update selection trackers
11590         var tracker = this._aSelections || [];
11591         var sRecordId = oRecord.getId();
11592         var index = -1;
11593
11594         // Remove if already there:
11595         // Use Array.indexOf if available...
11596         /*if(tracker.indexOf && (tracker.indexOf(sRecordId) >  -1)) {
11597             tracker.splice(tracker.indexOf(sRecordId),1);
11598         }*/
11599         if(tracker.indexOf) {
11600             index = tracker.indexOf(sRecordId);
11601             
11602         }
11603         // ...or do it the old-fashioned way
11604         else {
11605             for(var j=tracker.length-1; j>-1; j--) {
11606                 if(tracker[j] === sRecordId){
11607                     index = j;
11608                     break;
11609                 }
11610             }
11611         }
11612         if(index > -1) {
11613             tracker.splice(index,1);
11614         }
11615         
11616         // Add to the end
11617         tracker.push(sRecordId);
11618         this._aSelections = tracker;
11619
11620         // Update trackers
11621         if(!this._oAnchorRecord) {
11622             this._oAnchorRecord = oRecord;
11623         }
11624
11625         // Update UI
11626         if(elRow) {
11627             Dom.addClass(elRow, DT.CLASS_SELECTED);
11628         }
11629
11630         this.fireEvent("rowSelectEvent", {record:oRecord, el:elRow});
11631         YAHOO.log("Selected " + elRow, "info", this.toString());
11632     }
11633     else {
11634         YAHOO.log("Could not select row " + row, "warn", this.toString());
11635     }
11636 },
11637
11638 /**
11639  * Sets given row to the unselected state.
11640  *
11641  * @method unselectRow
11642  * @param row {HTMLElement | String | YAHOO.widget.Record | Number} HTML element
11643  * reference or ID string, Record instance, or RecordSet position index.
11644  */
11645 unselectRow : function(row) {
11646     var elRow = this.getTrEl(row);
11647
11648     var oRecord;
11649     if(row instanceof YAHOO.widget.Record) {
11650         oRecord = this._oRecordSet.getRecord(row);
11651     }
11652     else if(lang.isNumber(row)) {
11653         oRecord = this.getRecord(row);
11654     }
11655     else {
11656         oRecord = this.getRecord(elRow);
11657     }
11658
11659     if(oRecord) {
11660         // Update selection trackers
11661         var tracker = this._aSelections || [];
11662         var sRecordId = oRecord.getId();
11663         var index = -1;
11664
11665         // Use Array.indexOf if available...
11666         if(tracker.indexOf) {
11667             index = tracker.indexOf(sRecordId);
11668         }
11669         // ...or do it the old-fashioned way
11670         else {
11671             for(var j=tracker.length-1; j>-1; j--) {
11672                 if(tracker[j] === sRecordId){
11673                     index = j;
11674                     break;
11675                 }
11676             }
11677         }
11678         if(index > -1) {
11679             // Update tracker
11680             tracker.splice(index,1);
11681             this._aSelections = tracker;
11682
11683             // Update the UI
11684             Dom.removeClass(elRow, DT.CLASS_SELECTED);
11685
11686             this.fireEvent("rowUnselectEvent", {record:oRecord, el:elRow});
11687             YAHOO.log("Unselected " + elRow, "info", this.toString());
11688
11689             return;
11690         }
11691     }
11692     YAHOO.log("Could not unselect row " + row, "warn", this.toString());
11693 },
11694
11695 /**
11696  * Clears out all row selections.
11697  *
11698  * @method unselectAllRows
11699  */
11700 unselectAllRows : function() {
11701     // Remove all rows from tracker
11702     var tracker = this._aSelections || [],
11703         recId,
11704         removed = [];
11705     for(var j=tracker.length-1; j>-1; j--) {
11706        if(lang.isString(tracker[j])){
11707             recId = tracker.splice(j,1);
11708             removed[removed.length] = this.getRecord(lang.isArray(recId) ? recId[0] : recId);
11709         }
11710     }
11711
11712     // Update tracker
11713     this._aSelections = tracker;
11714
11715     // Update UI
11716     this._unselectAllTrEls();
11717
11718     this.fireEvent("unselectAllRowsEvent", {records: removed});
11719     YAHOO.log("Unselected all rows", "info", this.toString());
11720 },
11721
11722 /**
11723  * Convenience method to remove the class YAHOO.widget.DataTable.CLASS_SELECTED
11724  * from all TD elements in the internal tracker.
11725  *
11726  * @method _unselectAllTdEls
11727  * @private
11728  */
11729 _unselectAllTdEls : function() {
11730     var selectedCells = Dom.getElementsByClassName(DT.CLASS_SELECTED,"td",this._elTbody);
11731     Dom.removeClass(selectedCells, DT.CLASS_SELECTED);
11732 },
11733
11734 /**
11735  * Returns array of selected TD elements on the page.
11736  *
11737  * @method getSelectedTdEls
11738  * @return {HTMLElement[]} Array of selected TD elements.
11739  */
11740 getSelectedTdEls : function() {
11741     return Dom.getElementsByClassName(DT.CLASS_SELECTED,"td",this._elTbody);
11742 },
11743
11744 /**
11745  * Sets given cell to the selected state.
11746  *
11747  * @method selectCell
11748  * @param cell {HTMLElement | String} DOM element reference or ID string
11749  * to DataTable page element or RecordSet index.
11750  */
11751 selectCell : function(cell) {
11752 //TODO: accept {record} in selectCell()
11753     var elCell = this.getTdEl(cell);
11754
11755     if(elCell) {
11756         var oRecord = this.getRecord(elCell);
11757         var sColumnKey = this.getColumn(elCell.cellIndex).getKey();
11758
11759         if(oRecord && sColumnKey) {
11760             // Get Record ID
11761             var tracker = this._aSelections || [];
11762             var sRecordId = oRecord.getId();
11763
11764             // Remove if there
11765             for(var j=tracker.length-1; j>-1; j--) {
11766                if((tracker[j].recordId === sRecordId) && (tracker[j].columnKey === sColumnKey)){
11767                     tracker.splice(j,1);
11768                     break;
11769                 }
11770             }
11771
11772             // Add to the end
11773             tracker.push({recordId:sRecordId, columnKey:sColumnKey});
11774
11775             // Update trackers
11776             this._aSelections = tracker;
11777             if(!this._oAnchorCell) {
11778                 this._oAnchorCell = {record:oRecord, column:this.getColumn(sColumnKey)};
11779             }
11780
11781             // Update the UI
11782             Dom.addClass(elCell, DT.CLASS_SELECTED);
11783
11784             this.fireEvent("cellSelectEvent", {record:oRecord, column:this.getColumn(elCell.cellIndex), key: this.getColumn(elCell.cellIndex).getKey(), el:elCell});
11785             YAHOO.log("Selected " + elCell, "info", this.toString());
11786             return;
11787         }
11788     }
11789     YAHOO.log("Could not select cell " + cell, "warn", this.toString());
11790 },
11791
11792 /**
11793  * Sets given cell to the unselected state.
11794  *
11795  * @method unselectCell
11796  * @param cell {HTMLElement | String} DOM element reference or ID string
11797  * to DataTable page element or RecordSet index.
11798  */
11799 unselectCell : function(cell) {
11800     var elCell = this.getTdEl(cell);
11801
11802     if(elCell) {
11803         var oRecord = this.getRecord(elCell);
11804         var sColumnKey = this.getColumn(elCell.cellIndex).getKey();
11805
11806         if(oRecord && sColumnKey) {
11807             // Get Record ID
11808             var tracker = this._aSelections || [];
11809             var id = oRecord.getId();
11810
11811             // Is it selected?
11812             for(var j=tracker.length-1; j>-1; j--) {
11813                 if((tracker[j].recordId === id) && (tracker[j].columnKey === sColumnKey)){
11814                     // Remove from tracker
11815                     tracker.splice(j,1);
11816
11817                     // Update tracker
11818                     this._aSelections = tracker;
11819
11820                     // Update the UI
11821                     Dom.removeClass(elCell, DT.CLASS_SELECTED);
11822
11823                     this.fireEvent("cellUnselectEvent", {record:oRecord, column: this.getColumn(elCell.cellIndex), key:this.getColumn(elCell.cellIndex).getKey(), el:elCell});
11824                     YAHOO.log("Unselected " + elCell, "info", this.toString());
11825                     return;
11826                 }
11827             }
11828         }
11829     }
11830     YAHOO.log("Could not unselect cell " + cell, "warn", this.toString());
11831 },
11832
11833 /**
11834  * Clears out all cell selections.
11835  *
11836  * @method unselectAllCells
11837  */
11838 unselectAllCells : function() {
11839     // Remove all cells from tracker
11840     var tracker = this._aSelections || [];
11841     for(var j=tracker.length-1; j>-1; j--) {
11842        if(lang.isObject(tracker[j])){
11843             tracker.splice(j,1);
11844         }
11845     }
11846
11847     // Update tracker
11848     this._aSelections = tracker;
11849
11850     // Update UI
11851     this._unselectAllTdEls();
11852
11853     //TODO: send data to unselectAllCellsEvent handler
11854     this.fireEvent("unselectAllCellsEvent");
11855     YAHOO.log("Unselected all cells", "info", this.toString());
11856 },
11857
11858 /**
11859  * Returns true if given item is selected, false otherwise.
11860  *
11861  * @method isSelected
11862  * @param o {String | HTMLElement | YAHOO.widget.Record | Number
11863  * {record:YAHOO.widget.Record, column:YAHOO.widget.Column} } TR or TD element by
11864  * reference or ID string, a Record instance, a RecordSet position index,
11865  * or an object literal representation
11866  * of a cell.
11867  * @return {Boolean} True if item is selected.
11868  */
11869 isSelected : function(o) {
11870     if(o && (o.ownerDocument == document)) {
11871         return (Dom.hasClass(this.getTdEl(o),DT.CLASS_SELECTED) || Dom.hasClass(this.getTrEl(o),DT.CLASS_SELECTED));
11872     }
11873     else {
11874         var oRecord, sRecordId, j;
11875         var tracker = this._aSelections;
11876         if(tracker && tracker.length > 0) {
11877             // Looking for a Record?
11878             if(o instanceof YAHOO.widget.Record) {
11879                 oRecord = o;
11880             }
11881             else if(lang.isNumber(o)) {
11882                 oRecord = this.getRecord(o);
11883             }
11884             if(oRecord) {
11885                 sRecordId = oRecord.getId();
11886
11887                 // Is it there?
11888                 // Use Array.indexOf if available...
11889                 if(tracker.indexOf) {
11890                     if(tracker.indexOf(sRecordId) >  -1) {
11891                         return true;
11892                     }
11893                 }
11894                 // ...or do it the old-fashioned way
11895                 else {
11896                     for(j=tracker.length-1; j>-1; j--) {
11897                        if(tracker[j] === sRecordId){
11898                         return true;
11899                        }
11900                     }
11901                 }
11902             }
11903             // Looking for a cell
11904             else if(o.record && o.column){
11905                 sRecordId = o.record.getId();
11906                 var sColumnKey = o.column.getKey();
11907
11908                 for(j=tracker.length-1; j>-1; j--) {
11909                     if((tracker[j].recordId === sRecordId) && (tracker[j].columnKey === sColumnKey)){
11910                         return true;
11911                     }
11912                 }
11913             }
11914         }
11915     }
11916     return false;
11917 },
11918
11919 /**
11920  * Returns selected rows as an array of Record IDs.
11921  *
11922  * @method getSelectedRows
11923  * @return {String[]} Array of selected rows by Record ID.
11924  */
11925 getSelectedRows : function() {
11926     var aSelectedRows = [];
11927     var tracker = this._aSelections || [];
11928     for(var j=0; j<tracker.length; j++) {
11929        if(lang.isString(tracker[j])){
11930             aSelectedRows.push(tracker[j]);
11931         }
11932     }
11933     return aSelectedRows;
11934 },
11935
11936 /**
11937  * Returns selected cells as an array of object literals:
11938  *     {recordId:sRecordId, columnKey:sColumnKey}.
11939  *
11940  * @method getSelectedCells
11941  * @return {Object[]} Array of selected cells by Record ID and Column ID.
11942  */
11943 getSelectedCells : function() {
11944     var aSelectedCells = [];
11945     var tracker = this._aSelections || [];
11946     for(var j=0; j<tracker.length; j++) {
11947        if(tracker[j] && lang.isObject(tracker[j])){
11948             aSelectedCells.push(tracker[j]);
11949         }
11950     }
11951     return aSelectedCells;
11952 },
11953
11954 /**
11955  * Returns last selected Record ID.
11956  *
11957  * @method getLastSelectedRecord
11958  * @return {String} Record ID of last selected row.
11959  */
11960 getLastSelectedRecord : function() {
11961     var tracker = this._aSelections;
11962     if(tracker && tracker.length > 0) {
11963         for(var i=tracker.length-1; i>-1; i--) {
11964            if(lang.isString(tracker[i])){
11965                 return tracker[i];
11966             }
11967         }
11968     }
11969 },
11970
11971 /**
11972  * Returns last selected cell as an object literal:
11973  *     {recordId:sRecordId, columnKey:sColumnKey}.
11974  *
11975  * @method getLastSelectedCell
11976  * @return {Object} Object literal representation of a cell.
11977  */
11978 getLastSelectedCell : function() {
11979     var tracker = this._aSelections;
11980     if(tracker && tracker.length > 0) {
11981         for(var i=tracker.length-1; i>-1; i--) {
11982            if(tracker[i].recordId && tracker[i].columnKey){
11983                 return tracker[i];
11984             }
11985         }
11986     }
11987 },
11988
11989 /**
11990  * Assigns the class YAHOO.widget.DataTable.CLASS_HIGHLIGHTED to the given row.
11991  *
11992  * @method highlightRow
11993  * @param row {HTMLElement | String} DOM element reference or ID string.
11994  */
11995 highlightRow : function(row) {
11996     var elRow = this.getTrEl(row);
11997
11998     if(elRow) {
11999         // Make sure previous row is unhighlighted
12000 /*        if(this._sLastHighlightedTrElId) {
12001             Dom.removeClass(this._sLastHighlightedTrElId,DT.CLASS_HIGHLIGHTED);
12002         }*/
12003         var oRecord = this.getRecord(elRow);
12004         Dom.addClass(elRow,DT.CLASS_HIGHLIGHTED);
12005         //this._sLastHighlightedTrElId = elRow.id;
12006         this.fireEvent("rowHighlightEvent", {record:oRecord, el:elRow});
12007         YAHOO.log("Highlighted " + elRow, "info", this.toString());
12008         return;
12009     }
12010     YAHOO.log("Could not highlight row " + row, "warn", this.toString());
12011 },
12012
12013 /**
12014  * Removes the class YAHOO.widget.DataTable.CLASS_HIGHLIGHTED from the given row.
12015  *
12016  * @method unhighlightRow
12017  * @param row {HTMLElement | String} DOM element reference or ID string.
12018  */
12019 unhighlightRow : function(row) {
12020     var elRow = this.getTrEl(row);
12021
12022     if(elRow) {
12023         var oRecord = this.getRecord(elRow);
12024         Dom.removeClass(elRow,DT.CLASS_HIGHLIGHTED);
12025         this.fireEvent("rowUnhighlightEvent", {record:oRecord, el:elRow});
12026         YAHOO.log("Unhighlighted " + elRow, "info", this.toString());
12027         return;
12028     }
12029     YAHOO.log("Could not unhighlight row " + row, "warn", this.toString());
12030 },
12031
12032 /**
12033  * Assigns the class YAHOO.widget.DataTable.CLASS_HIGHLIGHTED to the given cell.
12034  *
12035  * @method highlightCell
12036  * @param cell {HTMLElement | String} DOM element reference or ID string.
12037  */
12038 highlightCell : function(cell) {
12039     var elCell = this.getTdEl(cell);
12040
12041     if(elCell) {
12042         // Make sure previous cell is unhighlighted
12043         if(this._elLastHighlightedTd) {
12044             this.unhighlightCell(this._elLastHighlightedTd);
12045         }
12046
12047         var oRecord = this.getRecord(elCell);
12048         var sColumnKey = this.getColumn(elCell.cellIndex).getKey();
12049         Dom.addClass(elCell,DT.CLASS_HIGHLIGHTED);
12050         this._elLastHighlightedTd = elCell;
12051         this.fireEvent("cellHighlightEvent", {record:oRecord, column:this.getColumn(elCell.cellIndex), key:this.getColumn(elCell.cellIndex).getKey(), el:elCell});
12052         YAHOO.log("Highlighted " + elCell, "info", this.toString());
12053         return;
12054     }
12055     YAHOO.log("Could not highlight cell " + cell, "warn", this.toString());
12056 },
12057
12058 /**
12059  * Removes the class YAHOO.widget.DataTable.CLASS_HIGHLIGHTED from the given cell.
12060  *
12061  * @method unhighlightCell
12062  * @param cell {HTMLElement | String} DOM element reference or ID string.
12063  */
12064 unhighlightCell : function(cell) {
12065     var elCell = this.getTdEl(cell);
12066
12067     if(elCell) {
12068         var oRecord = this.getRecord(elCell);
12069         Dom.removeClass(elCell,DT.CLASS_HIGHLIGHTED);
12070         this._elLastHighlightedTd = null;
12071         this.fireEvent("cellUnhighlightEvent", {record:oRecord, column:this.getColumn(elCell.cellIndex), key:this.getColumn(elCell.cellIndex).getKey(), el:elCell});
12072         YAHOO.log("Unhighlighted " + elCell, "info", this.toString());
12073         return;
12074     }
12075     YAHOO.log("Could not unhighlight cell " + cell, "warn", this.toString());
12076 },
12077
12078
12079
12080
12081
12082
12083
12084
12085
12086
12087
12088
12089
12090
12091
12092
12093
12094
12095
12096
12097
12098
12099
12100
12101
12102
12103
12104
12105
12106
12107
12108
12109
12110
12111
12112
12113
12114
12115
12116
12117
12118
12119
12120
12121
12122 // INLINE EDITING
12123
12124 /**
12125  * Returns current CellEditor instance, or null.
12126  * @method getCellEditor
12127  * @return {YAHOO.widget.CellEditor} CellEditor instance.
12128  */
12129 getCellEditor : function() {
12130     return this._oCellEditor;
12131 },
12132
12133
12134 /**
12135  * Activates and shows CellEditor instance for the given cell while deactivating and
12136  * canceling previous CellEditor. It is baked into DataTable that only one CellEditor
12137  * can be active at any given time. 
12138  *
12139  * @method showCellEditor
12140  * @param elCell {HTMLElement | String} Cell to edit.
12141  */
12142 showCellEditor : function(elCell, oRecord, oColumn) {
12143     // Get a particular CellEditor
12144     elCell = this.getTdEl(elCell);
12145     if(elCell) {
12146         oColumn = this.getColumn(elCell);
12147         if(oColumn && oColumn.editor) {
12148             var oCellEditor = this._oCellEditor;
12149             // Clean up active CellEditor
12150             if(oCellEditor) {
12151                 if(this._oCellEditor.cancel) {
12152                     this._oCellEditor.cancel();
12153                 }
12154                 else if(oCellEditor.isActive) {
12155                     this.cancelCellEditor();
12156                 }
12157             }
12158             
12159             if(oColumn.editor instanceof YAHOO.widget.BaseCellEditor) {
12160                 // Get CellEditor
12161                 oCellEditor = oColumn.editor;
12162                 var ok = oCellEditor.attach(this, elCell);
12163                 if(ok) {
12164                     oCellEditor.move();
12165                     ok = this.doBeforeShowCellEditor(oCellEditor);
12166                     if(ok) {
12167                         oCellEditor.show();
12168                         this._oCellEditor = oCellEditor;
12169                     }
12170                 }
12171             }
12172             // Backward compatibility
12173             else {
12174                     if(!oRecord || !(oRecord instanceof YAHOO.widget.Record)) {
12175                         oRecord = this.getRecord(elCell);
12176                     }
12177                     if(!oColumn || !(oColumn instanceof YAHOO.widget.Column)) {
12178                         oColumn = this.getColumn(elCell);
12179                     }
12180                     if(oRecord && oColumn) {
12181                         if(!this._oCellEditor || this._oCellEditor.container) {
12182                             this._initCellEditorEl();
12183                         }
12184                         
12185                         // Update Editor values
12186                         oCellEditor = this._oCellEditor;
12187                         oCellEditor.cell = elCell;
12188                         oCellEditor.record = oRecord;
12189                         oCellEditor.column = oColumn;
12190                         oCellEditor.validator = (oColumn.editorOptions &&
12191                                 lang.isFunction(oColumn.editorOptions.validator)) ?
12192                                 oColumn.editorOptions.validator : null;
12193                         oCellEditor.value = oRecord.getData(oColumn.key);
12194                         oCellEditor.defaultValue = null;
12195             
12196                         // Move Editor
12197                         var elContainer = oCellEditor.container;
12198                         var x = Dom.getX(elCell);
12199                         var y = Dom.getY(elCell);
12200             
12201                         // SF doesn't get xy for cells in scrolling table
12202                         // when tbody display is set to block
12203                         if(isNaN(x) || isNaN(y)) {
12204                             x = elCell.offsetLeft + // cell pos relative to table
12205                                     Dom.getX(this._elTbody.parentNode) - // plus table pos relative to document
12206                                     this._elTbody.scrollLeft; // minus tbody scroll
12207                             y = elCell.offsetTop + // cell pos relative to table
12208                                     Dom.getY(this._elTbody.parentNode) - // plus table pos relative to document
12209                                     this._elTbody.scrollTop + // minus tbody scroll
12210                                     this._elThead.offsetHeight; // account for fixed THEAD cells
12211                         }
12212             
12213                         elContainer.style.left = x + "px";
12214                         elContainer.style.top = y + "px";
12215             
12216                         // Hook to customize the UI
12217                         this.doBeforeShowCellEditor(this._oCellEditor);
12218             
12219                         //TODO: This is temporarily up here due so elements can be focused
12220                         // Show Editor
12221                         elContainer.style.display = "";
12222             
12223                         // Handle ESC key
12224                         Ev.addListener(elContainer, "keydown", function(e, oSelf) {
12225                             // ESC hides Cell Editor
12226                             if((e.keyCode == 27)) {
12227                                 oSelf.cancelCellEditor();
12228                                 oSelf.focusTbodyEl();
12229                             }
12230                             else {
12231                                 oSelf.fireEvent("editorKeydownEvent", {editor:oSelf._oCellEditor, event:e});
12232                             }
12233                         }, this);
12234             
12235                         // Render Editor markup
12236                         var fnEditor;
12237                         if(lang.isString(oColumn.editor)) {
12238                             switch(oColumn.editor) {
12239                                 case "checkbox":
12240                                     fnEditor = DT.editCheckbox;
12241                                     break;
12242                                 case "date":
12243                                     fnEditor = DT.editDate;
12244                                     break;
12245                                 case "dropdown":
12246                                     fnEditor = DT.editDropdown;
12247                                     break;
12248                                 case "radio":
12249                                     fnEditor = DT.editRadio;
12250                                     break;
12251                                 case "textarea":
12252                                     fnEditor = DT.editTextarea;
12253                                     break;
12254                                 case "textbox":
12255                                     fnEditor = DT.editTextbox;
12256                                     break;
12257                                 default:
12258                                     fnEditor = null;
12259                             }
12260                         }
12261                         else if(lang.isFunction(oColumn.editor)) {
12262                             fnEditor = oColumn.editor;
12263                         }
12264             
12265                         if(fnEditor) {
12266                             // Create DOM input elements
12267                             fnEditor(this._oCellEditor, this);
12268             
12269                             // Show Save/Cancel buttons
12270                             if(!oColumn.editorOptions || !oColumn.editorOptions.disableBtns) {
12271                                 this.showCellEditorBtns(elContainer);
12272                             }
12273             
12274                             oCellEditor.isActive = true;
12275             
12276                             //TODO: verify which args to pass
12277                             this.fireEvent("editorShowEvent", {editor:oCellEditor});
12278                             YAHOO.log("Cell Editor shown for " + elCell, "info", this.toString());
12279                             return;
12280                         }
12281                     }
12282
12283
12284
12285             
12286             }
12287         }
12288     }
12289 },
12290
12291 /**
12292  * Backward compatibility.
12293  *
12294  * @method _initCellEditorEl
12295  * @private
12296  * @deprecated 
12297  */
12298 _initCellEditorEl : function() {
12299     // Attach Cell Editor container element as first child of body
12300     var elCellEditor = document.createElement("div");
12301     elCellEditor.id = this._sId + "-celleditor";
12302     elCellEditor.style.display = "none";
12303     elCellEditor.tabIndex = 0;
12304     Dom.addClass(elCellEditor, DT.CLASS_EDITOR);
12305     var elFirstChild = Dom.getFirstChild(document.body);
12306     if(elFirstChild) {
12307         elCellEditor = Dom.insertBefore(elCellEditor, elFirstChild);
12308     }
12309     else {
12310         elCellEditor = document.body.appendChild(elCellEditor);
12311     }
12312     
12313     // Internal tracker of Cell Editor values
12314     var oCellEditor = {};
12315     oCellEditor.container = elCellEditor;
12316     oCellEditor.value = null;
12317     oCellEditor.isActive = false;
12318     this._oCellEditor = oCellEditor;
12319 },
12320
12321 /**
12322  * Overridable abstract method to customize CellEditor before showing.
12323  *
12324  * @method doBeforeShowCellEditor
12325  * @param oCellEditor {YAHOO.widget.CellEditor} The CellEditor instance.
12326  * @return {Boolean} Return true to continue showing CellEditor.
12327  */
12328 doBeforeShowCellEditor : function(oCellEditor) {
12329     return true;
12330 },
12331
12332 /**
12333  * Saves active CellEditor input to Record and upates DOM UI.
12334  *
12335  * @method saveCellEditor
12336  */
12337 saveCellEditor : function() {
12338     if(this._oCellEditor) {
12339         if(this._oCellEditor.save) {
12340             this._oCellEditor.save();
12341         }
12342         // Backward compatibility
12343         else if(this._oCellEditor.isActive) {
12344             var newData = this._oCellEditor.value;
12345             // Copy the data to pass to the event
12346             //var oldData = YAHOO.widget.DataTable._cloneObject(this._oCellEditor.record.getData(this._oCellEditor.column.key));
12347             var oldData = this._oCellEditor.record.getData(this._oCellEditor.column.key);
12348     
12349             // Validate input data
12350             if(this._oCellEditor.validator) {
12351                 newData = this._oCellEditor.value = this._oCellEditor.validator.call(this, newData, oldData, this._oCellEditor);
12352                 if(newData === null ) {
12353                     this.resetCellEditor();
12354                     this.fireEvent("editorRevertEvent",
12355                             {editor:this._oCellEditor, oldData:oldData, newData:newData});
12356                     YAHOO.log("Could not save Cell Editor input due to invalid data " +
12357                             lang.dump(newData), "warn", this.toString());
12358                     return;
12359                 }
12360             }
12361             // Update the Record
12362             this._oRecordSet.updateRecordValue(this._oCellEditor.record, this._oCellEditor.column.key, this._oCellEditor.value);
12363             // Update the UI
12364             this.formatCell(this._oCellEditor.cell.firstChild);
12365             
12366             // Bug fix 1764044
12367             this._oChainRender.add({
12368                 method: function() {
12369                     this.validateColumnWidths();
12370                 },
12371                 scope: this
12372             });
12373             this._oChainRender.run();
12374             // Clear out the Cell Editor
12375             this.resetCellEditor();
12376     
12377             this.fireEvent("editorSaveEvent",
12378                     {editor:this._oCellEditor, oldData:oldData, newData:newData});
12379             YAHOO.log("Cell Editor input saved", "info", this.toString());
12380         }
12381     }   
12382 },
12383
12384 /**
12385  * Cancels active CellEditor.
12386  *
12387  * @method cancelCellEditor
12388  */
12389 cancelCellEditor : function() {
12390     if(this._oCellEditor) {
12391         if(this._oCellEditor.cancel) {
12392             this._oCellEditor.cancel();
12393         }
12394         // Backward compatibility
12395         else if(this._oCellEditor.isActive) {
12396             this.resetCellEditor();
12397             //TODO: preserve values for the event?
12398             this.fireEvent("editorCancelEvent", {editor:this._oCellEditor});
12399             YAHOO.log("Cell Editor input canceled", "info", this.toString());
12400         }
12401
12402         YAHOO.log("CellEditor input canceled", "info", this.toString());
12403     }
12404 },
12405
12406 /**
12407  * Destroys active CellEditor instance and UI.
12408  *
12409  * @method destroyCellEditor
12410  */
12411 destroyCellEditor : function() {
12412     if(this._oCellEditor) {
12413         this._oCellEditor.destroy();
12414         this._oCellEditor = null;
12415     }   
12416 },
12417
12418 /**
12419  * Passes through showEvent of the active CellEditor.
12420  *
12421  * @method _onEditorShowEvent
12422  * @param oArgs {Object}  Custom Event args.
12423  * @private 
12424  */
12425 _onEditorShowEvent : function(oArgs) {
12426     this.fireEvent("editorShowEvent", oArgs);
12427 },
12428
12429 /**
12430  * Passes through keydownEvent of the active CellEditor.
12431  * @param oArgs {Object}  Custom Event args. 
12432  *
12433  * @method _onEditorKeydownEvent
12434  * @private 
12435  */
12436 _onEditorKeydownEvent : function(oArgs) {
12437     this.fireEvent("editorKeydownEvent", oArgs);
12438 },
12439
12440 /**
12441  * Passes through revertEvent of the active CellEditor.
12442  *
12443  * @method _onEditorRevertEvent
12444  * @param oArgs {Object}  Custom Event args. 
12445  * @private  
12446  */
12447 _onEditorRevertEvent : function(oArgs) {
12448     this.fireEvent("editorRevertEvent", oArgs);
12449 },
12450
12451 /**
12452  * Passes through saveEvent of the active CellEditor.
12453  *
12454  * @method _onEditorSaveEvent
12455  * @param oArgs {Object}  Custom Event args.  
12456  * @private 
12457  */
12458 _onEditorSaveEvent : function(oArgs) {
12459     this.fireEvent("editorSaveEvent", oArgs);
12460 },
12461
12462 /**
12463  * Passes through cancelEvent of the active CellEditor.
12464  *
12465  * @method _onEditorCancelEvent
12466  * @param oArgs {Object}  Custom Event args.
12467  * @private   
12468  */
12469 _onEditorCancelEvent : function(oArgs) {
12470     this.fireEvent("editorCancelEvent", oArgs);
12471 },
12472
12473 /**
12474  * Passes through blurEvent of the active CellEditor.
12475  *
12476  * @method _onEditorBlurEvent
12477  * @param oArgs {Object}  Custom Event args. 
12478  * @private  
12479  */
12480 _onEditorBlurEvent : function(oArgs) {
12481     this.fireEvent("editorBlurEvent", oArgs);
12482 },
12483
12484 /**
12485  * Passes through blockEvent of the active CellEditor.
12486  *
12487  * @method _onEditorBlockEvent
12488  * @param oArgs {Object}  Custom Event args. 
12489  * @private  
12490  */
12491 _onEditorBlockEvent : function(oArgs) {
12492     this.fireEvent("editorBlockEvent", oArgs);
12493 },
12494
12495 /**
12496  * Passes through unblockEvent of the active CellEditor.
12497  *
12498  * @method _onEditorUnblockEvent
12499  * @param oArgs {Object}  Custom Event args. 
12500  * @private  
12501  */
12502 _onEditorUnblockEvent : function(oArgs) {
12503     this.fireEvent("editorUnblockEvent", oArgs);
12504 },
12505
12506 /**
12507  * Public handler of the editorBlurEvent. By default, saves on blur if
12508  * disableBtns is true, otherwise cancels on blur. 
12509  *
12510  * @method onEditorBlurEvent
12511  * @param oArgs {Object}  Custom Event args.  
12512  */
12513 onEditorBlurEvent : function(oArgs) {
12514     if(oArgs.editor.disableBtns) {
12515         // Save on blur
12516         if(oArgs.editor.save) { // Backward incompatible
12517             oArgs.editor.save();
12518         }
12519     }      
12520     else if(oArgs.editor.cancel) { // Backward incompatible
12521         // Cancel on blur
12522         oArgs.editor.cancel();
12523     }      
12524 },
12525
12526 /**
12527  * Public handler of the editorBlockEvent. By default, disables DataTable UI.
12528  *
12529  * @method onEditorBlockEvent
12530  * @param oArgs {Object}  Custom Event args.  
12531  */
12532 onEditorBlockEvent : function(oArgs) {
12533     this.disable();
12534 },
12535
12536 /**
12537  * Public handler of the editorUnblockEvent. By default, undisables DataTable UI.
12538  *
12539  * @method onEditorUnblockEvent
12540  * @param oArgs {Object}  Custom Event args.  
12541  */
12542 onEditorUnblockEvent : function(oArgs) {
12543     this.undisable();
12544 },
12545
12546
12547
12548
12549
12550
12551
12552
12553
12554
12555
12556
12557
12558
12559
12560
12561
12562
12563
12564
12565
12566
12567
12568
12569
12570
12571
12572
12573
12574
12575
12576
12577
12578
12579
12580
12581
12582
12583 // ABSTRACT METHODS
12584
12585 /**
12586  * Overridable method gives implementers a hook to access data before
12587  * it gets added to RecordSet and rendered to the TBODY.
12588  *
12589  * @method doBeforeLoadData
12590  * @param sRequest {String} Original request.
12591  * @param oResponse {Object} Response object.
12592  * @param oPayload {MIXED} additional arguments
12593  * @return {Boolean} Return true to continue loading data into RecordSet and
12594  * updating DataTable with new Records, false to cancel.
12595  */
12596 doBeforeLoadData : function(sRequest, oResponse, oPayload) {
12597     return true;
12598 },
12599
12600
12601
12602
12603
12604
12605
12606
12607
12608
12609
12610
12611
12612
12613
12614
12615
12616
12617
12618
12619
12620
12621
12622
12623
12624
12625
12626
12627
12628
12629
12630
12631
12632
12633
12634
12635
12636
12637
12638
12639
12640
12641
12642
12643
12644
12645
12646
12647
12648
12649
12650
12651
12652
12653
12654
12655
12656
12657
12658
12659
12660
12661
12662 /////////////////////////////////////////////////////////////////////////////
12663 //
12664 // Public Custom Event Handlers
12665 //
12666 /////////////////////////////////////////////////////////////////////////////
12667
12668 /**
12669  * Overridable custom event handler to sort Column.
12670  *
12671  * @method onEventSortColumn
12672  * @param oArgs.event {HTMLEvent} Event object.
12673  * @param oArgs.target {HTMLElement} Target element.
12674  */
12675 onEventSortColumn : function(oArgs) {
12676 //TODO: support form elements in sortable columns
12677     var evt = oArgs.event;
12678     var target = oArgs.target;
12679
12680     var el = this.getThEl(target) || this.getTdEl(target);
12681     if(el) {
12682         var oColumn = this.getColumn(el);
12683         if(oColumn.sortable) {
12684             Ev.stopEvent(evt);
12685             this.sortColumn(oColumn);
12686         }
12687     }
12688     else {
12689         YAHOO.log("Could not find Column for " + target, "warn", this.toString());
12690     }
12691 },
12692
12693 /**
12694  * Overridable custom event handler to select Column.
12695  *
12696  * @method onEventSelectColumn
12697  * @param oArgs.event {HTMLEvent} Event object.
12698  * @param oArgs.target {HTMLElement} Target element.
12699  */
12700 onEventSelectColumn : function(oArgs) {
12701     this.selectColumn(oArgs.target);
12702 },
12703
12704 /**
12705  * Overridable custom event handler to highlight Column. Accounts for spurious
12706  * caused-by-child events. 
12707  *
12708  * @method onEventHighlightColumn
12709  * @param oArgs.event {HTMLEvent} Event object.
12710  * @param oArgs.target {HTMLElement} Target element.
12711  */
12712 onEventHighlightColumn : function(oArgs) {
12713     //TODO: filter for all spurious events at a lower level
12714     if(!Dom.isAncestor(oArgs.target,Ev.getRelatedTarget(oArgs.event))) {
12715         this.highlightColumn(oArgs.target);
12716     }
12717 },
12718
12719 /**
12720  * Overridable custom event handler to unhighlight Column. Accounts for spurious
12721  * caused-by-child events. 
12722  *
12723  * @method onEventUnhighlightColumn
12724  * @param oArgs.event {HTMLEvent} Event object.
12725  * @param oArgs.target {HTMLElement} Target element.
12726  */
12727 onEventUnhighlightColumn : function(oArgs) {
12728     //TODO: filter for all spurious events at a lower level
12729     if(!Dom.isAncestor(oArgs.target,Ev.getRelatedTarget(oArgs.event))) {
12730         this.unhighlightColumn(oArgs.target);
12731     }
12732 },
12733
12734 /**
12735  * Overridable custom event handler to manage selection according to desktop paradigm.
12736  *
12737  * @method onEventSelectRow
12738  * @param oArgs.event {HTMLEvent} Event object.
12739  * @param oArgs.target {HTMLElement} Target element.
12740  */
12741 onEventSelectRow : function(oArgs) {
12742     var sMode = this.get("selectionMode");
12743     if(sMode == "single") {
12744         this._handleSingleSelectionByMouse(oArgs);
12745     }
12746     else {
12747         this._handleStandardSelectionByMouse(oArgs);
12748     }
12749 },
12750
12751 /**
12752  * Overridable custom event handler to select cell.
12753  *
12754  * @method onEventSelectCell
12755  * @param oArgs.event {HTMLEvent} Event object.
12756  * @param oArgs.target {HTMLElement} Target element.
12757  */
12758 onEventSelectCell : function(oArgs) {
12759     var sMode = this.get("selectionMode");
12760     if(sMode == "cellblock") {
12761         this._handleCellBlockSelectionByMouse(oArgs);
12762     }
12763     else if(sMode == "cellrange") {
12764         this._handleCellRangeSelectionByMouse(oArgs);
12765     }
12766     else {
12767         this._handleSingleCellSelectionByMouse(oArgs);
12768     }
12769 },
12770
12771 /**
12772  * Overridable custom event handler to highlight row. Accounts for spurious
12773  * caused-by-child events. 
12774  *
12775  * @method onEventHighlightRow
12776  * @param oArgs.event {HTMLEvent} Event object.
12777  * @param oArgs.target {HTMLElement} Target element.
12778  */
12779 onEventHighlightRow : function(oArgs) {
12780     //TODO: filter for all spurious events at a lower level
12781     if(!Dom.isAncestor(oArgs.target,Ev.getRelatedTarget(oArgs.event))) {
12782         this.highlightRow(oArgs.target);
12783     }
12784 },
12785
12786 /**
12787  * Overridable custom event handler to unhighlight row. Accounts for spurious
12788  * caused-by-child events. 
12789  *
12790  * @method onEventUnhighlightRow
12791  * @param oArgs.event {HTMLEvent} Event object.
12792  * @param oArgs.target {HTMLElement} Target element.
12793  */
12794 onEventUnhighlightRow : function(oArgs) {
12795     //TODO: filter for all spurious events at a lower level
12796     if(!Dom.isAncestor(oArgs.target,Ev.getRelatedTarget(oArgs.event))) {
12797         this.unhighlightRow(oArgs.target);
12798     }
12799 },
12800
12801 /**
12802  * Overridable custom event handler to highlight cell. Accounts for spurious
12803  * caused-by-child events. 
12804  *
12805  * @method onEventHighlightCell
12806  * @param oArgs.event {HTMLEvent} Event object.
12807  * @param oArgs.target {HTMLElement} Target element.
12808  */
12809 onEventHighlightCell : function(oArgs) {
12810     //TODO: filter for all spurious events at a lower level
12811     if(!Dom.isAncestor(oArgs.target,Ev.getRelatedTarget(oArgs.event))) {
12812         this.highlightCell(oArgs.target);
12813     }
12814 },
12815
12816 /**
12817  * Overridable custom event handler to unhighlight cell. Accounts for spurious
12818  * caused-by-child events. 
12819  *
12820  * @method onEventUnhighlightCell
12821  * @param oArgs.event {HTMLEvent} Event object.
12822  * @param oArgs.target {HTMLElement} Target element.
12823  */
12824 onEventUnhighlightCell : function(oArgs) {
12825     //TODO: filter for all spurious events at a lower level
12826     if(!Dom.isAncestor(oArgs.target,Ev.getRelatedTarget(oArgs.event))) {
12827         this.unhighlightCell(oArgs.target);
12828     }
12829 },
12830
12831 /**
12832  * Overridable custom event handler to format cell.
12833  *
12834  * @method onEventFormatCell
12835  * @param oArgs.event {HTMLEvent} Event object.
12836  * @param oArgs.target {HTMLElement} Target element.
12837  */
12838 onEventFormatCell : function(oArgs) {
12839     var target = oArgs.target;
12840
12841     var elCell = this.getTdEl(target);
12842     if(elCell) {
12843         var oColumn = this.getColumn(elCell.cellIndex);
12844         this.formatCell(elCell.firstChild, this.getRecord(elCell), oColumn);
12845     }
12846     else {
12847         YAHOO.log("Could not format cell " + target, "warn", this.toString());
12848     }
12849 },
12850
12851 /**
12852  * Overridable custom event handler to edit cell.
12853  *
12854  * @method onEventShowCellEditor
12855  * @param oArgs.event {HTMLEvent} Event object.
12856  * @param oArgs.target {HTMLElement} Target element.
12857  */
12858 onEventShowCellEditor : function(oArgs) {
12859     this.showCellEditor(oArgs.target);
12860 },
12861
12862 /**
12863  * Overridable custom event handler to save active CellEditor input.
12864  *
12865  * @method onEventSaveCellEditor
12866  */
12867 onEventSaveCellEditor : function(oArgs) {
12868     if(this._oCellEditor) {
12869         if(this._oCellEditor.save) {
12870             this._oCellEditor.save();
12871         }
12872         // Backward compatibility
12873         else {
12874             this.saveCellEditor();
12875         }
12876     }
12877 },
12878
12879 /**
12880  * Overridable custom event handler to cancel active CellEditor.
12881  *
12882  * @method onEventCancelCellEditor
12883  */
12884 onEventCancelCellEditor : function(oArgs) {
12885     if(this._oCellEditor) {
12886         if(this._oCellEditor.cancel) {
12887             this._oCellEditor.cancel();
12888         }
12889         // Backward compatibility
12890         else {
12891             this.cancelCellEditor();
12892         }
12893     }
12894 },
12895
12896 /**
12897  * Callback function receives data from DataSource and populates an entire
12898  * DataTable with Records and TR elements, clearing previous Records, if any.
12899  *
12900  * @method onDataReturnInitializeTable
12901  * @param sRequest {String} Original request.
12902  * @param oResponse {Object} Response object.
12903  * @param oPayload {MIXED} (optional) Additional argument(s)
12904  */
12905 onDataReturnInitializeTable : function(sRequest, oResponse, oPayload) {
12906     if((this instanceof DT) && this._sId) {
12907         this.initializeTable();
12908     
12909         this.onDataReturnSetRows(sRequest,oResponse,oPayload);
12910     }
12911 },
12912
12913 /**
12914  * Callback function receives reponse from DataSource, replaces all existing
12915  * Records in  RecordSet, updates TR elements with new data, and updates state
12916  * UI for pagination and sorting from payload data, if necessary. 
12917  *  
12918  * @method onDataReturnReplaceRows
12919  * @param oRequest {MIXED} Original generated request.
12920  * @param oResponse {Object} Response object.
12921  * @param oPayload {MIXED} (optional) Additional argument(s)
12922  */
12923 onDataReturnReplaceRows : function(oRequest, oResponse, oPayload) {
12924     if((this instanceof DT) && this._sId) {
12925         this.fireEvent("dataReturnEvent", {request:oRequest,response:oResponse,payload:oPayload});
12926     
12927         // Pass data through abstract method for any transformations
12928         var ok    = this.doBeforeLoadData(oRequest, oResponse, oPayload),
12929             pag   = this.get('paginator'),
12930             index = 0;
12931     
12932         // Data ok to set
12933         if(ok && oResponse && !oResponse.error && lang.isArray(oResponse.results)) {
12934             // Update Records
12935             this._oRecordSet.reset();
12936     
12937             if (this.get('dynamicData')) {
12938                 if (oPayload && oPayload.pagination &&
12939                     lang.isNumber(oPayload.pagination.recordOffset)) {
12940                     index = oPayload.pagination.recordOffset;
12941                 } else if (pag) {
12942                     index = pag.getStartIndex();
12943                 }
12944             }
12945     
12946             this._oRecordSet.setRecords(oResponse.results, index | 0);
12947             
12948             // Update state
12949             this._handleDataReturnPayload(oRequest, oResponse, oPayload);
12950             
12951             // Update UI
12952             this.render();    
12953         }
12954         // Error
12955         else if(ok && oResponse.error) {
12956             this.showTableMessage(this.get("MSG_ERROR"), DT.CLASS_ERROR);
12957         }
12958     }
12959 },
12960
12961 /**
12962  * Callback function receives data from DataSource and appends to an existing
12963  * DataTable new Records and, if applicable, creates or updates
12964  * corresponding TR elements.
12965  *
12966  * @method onDataReturnAppendRows
12967  * @param sRequest {String} Original request.
12968  * @param oResponse {Object} Response object.
12969  * @param oPayload {MIXED} (optional) Additional argument(s)
12970  */
12971 onDataReturnAppendRows : function(sRequest, oResponse, oPayload) {
12972     if((this instanceof DT) && this._sId) {
12973         this.fireEvent("dataReturnEvent", {request:sRequest,response:oResponse,payload:oPayload});
12974     
12975         // Pass data through abstract method for any transformations
12976         var ok = this.doBeforeLoadData(sRequest, oResponse, oPayload);
12977     
12978         // Data ok to append
12979         if(ok && oResponse && !oResponse.error && lang.isArray(oResponse.results)) {        
12980             // Append rows
12981             this.addRows(oResponse.results);
12982     
12983             // Update state
12984             this._handleDataReturnPayload(sRequest, oResponse, oPayload);
12985         }
12986         // Error
12987         else if(ok && oResponse.error) {
12988             this.showTableMessage(this.get("MSG_ERROR"), DT.CLASS_ERROR);
12989         }
12990     }
12991 },
12992
12993 /**
12994  * Callback function receives data from DataSource and inserts new records
12995  * starting at the index specified in oPayload.insertIndex. The value for
12996  * oPayload.insertIndex can be populated when sending the request to the DataSource,
12997  * or by accessing oPayload.insertIndex with the doBeforeLoadData() method at runtime.
12998  * If applicable, creates or updates corresponding TR elements.
12999  *
13000  * @method onDataReturnInsertRows
13001  * @param sRequest {String} Original request.
13002  * @param oResponse {Object} Response object.
13003  * @param oPayload {MIXED} Argument payload, looks in oPayload.insertIndex.
13004  */
13005 onDataReturnInsertRows : function(sRequest, oResponse, oPayload) {
13006     if((this instanceof DT) && this._sId) {
13007         this.fireEvent("dataReturnEvent", {request:sRequest,response:oResponse,payload:oPayload});
13008     
13009         // Pass data through abstract method for any transformations
13010         var ok = this.doBeforeLoadData(sRequest, oResponse, oPayload);
13011     
13012         // Data ok to append
13013         if(ok && oResponse && !oResponse.error && lang.isArray(oResponse.results)) {
13014             // Insert rows
13015             this.addRows(oResponse.results, (oPayload ? oPayload.insertIndex : 0));
13016     
13017             // Update state
13018             this._handleDataReturnPayload(sRequest, oResponse, oPayload);
13019         }
13020         // Error
13021         else if(ok && oResponse.error) {
13022             this.showTableMessage(this.get("MSG_ERROR"), DT.CLASS_ERROR);
13023         }
13024     }
13025 },
13026
13027 /**
13028  * Callback function receives data from DataSource and incrementally updates Records
13029  * starting at the index specified in oPayload.updateIndex. The value for
13030  * oPayload.updateIndex can be populated when sending the request to the DataSource,
13031  * or by accessing oPayload.updateIndex with the doBeforeLoadData() method at runtime.
13032  * If applicable, creates or updates corresponding TR elements.
13033  *
13034  * @method onDataReturnUpdateRows
13035  * @param sRequest {String} Original request.
13036  * @param oResponse {Object} Response object.
13037  * @param oPayload {MIXED} Argument payload, looks in oPayload.updateIndex.
13038  */
13039 onDataReturnUpdateRows : function(sRequest, oResponse, oPayload) {
13040     if((this instanceof DT) && this._sId) {
13041         this.fireEvent("dataReturnEvent", {request:sRequest,response:oResponse,payload:oPayload});
13042     
13043         // Pass data through abstract method for any transformations
13044         var ok = this.doBeforeLoadData(sRequest, oResponse, oPayload);
13045     
13046         // Data ok to append
13047         if(ok && oResponse && !oResponse.error && lang.isArray(oResponse.results)) {
13048             // Insert rows
13049             this.updateRows((oPayload ? oPayload.updateIndex : 0), oResponse.results);
13050     
13051             // Update state
13052             this._handleDataReturnPayload(sRequest, oResponse, oPayload);
13053         }
13054         // Error
13055         else if(ok && oResponse.error) {
13056             this.showTableMessage(this.get("MSG_ERROR"), DT.CLASS_ERROR);
13057         }
13058     }
13059 },
13060
13061 /**
13062  * Callback function receives reponse from DataSource and populates the
13063  * RecordSet with the results.
13064  *  
13065  * @method onDataReturnSetRows
13066  * @param oRequest {MIXED} Original generated request.
13067  * @param oResponse {Object} Response object.
13068  * @param oPayload {MIXED} (optional) Additional argument(s)
13069  */
13070 onDataReturnSetRows : function(oRequest, oResponse, oPayload) {
13071     if((this instanceof DT) && this._sId) {
13072         this.fireEvent("dataReturnEvent", {request:oRequest,response:oResponse,payload:oPayload});
13073     
13074         // Pass data through abstract method for any transformations
13075         var ok    = this.doBeforeLoadData(oRequest, oResponse, oPayload),
13076             pag   = this.get('paginator'),
13077             index = 0;
13078     
13079         // Data ok to set
13080         if(ok && oResponse && !oResponse.error && lang.isArray(oResponse.results)) {
13081             // Update Records
13082             if (this.get('dynamicData')) {
13083                 if (oPayload && oPayload.pagination &&
13084                     lang.isNumber(oPayload.pagination.recordOffset)) {
13085                     index = oPayload.pagination.recordOffset;
13086                 } else if (pag) {
13087                     index = pag.getStartIndex();
13088                 }
13089                 
13090                 this._oRecordSet.reset(); // Bug 2290604: dyanmic data shouldn't keep accumulating by default
13091             }
13092     
13093             this._oRecordSet.setRecords(oResponse.results, index | 0);
13094     
13095             // Update state
13096             this._handleDataReturnPayload(oRequest, oResponse, oPayload);
13097             
13098             // Update UI
13099             this.render();
13100         }
13101         // Error
13102         else if(ok && oResponse.error) {
13103             this.showTableMessage(this.get("MSG_ERROR"), DT.CLASS_ERROR);
13104         }
13105     }
13106     else {
13107         YAHOO.log("Instance destroyed before data returned.","info",this.toString());
13108     }
13109 },
13110
13111 /**
13112  * Hook to update oPayload before consumption.
13113  *  
13114  * @method handleDataReturnPayload
13115  * @param oRequest {MIXED} Original generated request.
13116  * @param oResponse {Object} Response object.
13117  * @param oPayload {MIXED} State values.
13118  * @return oPayload {MIXED} State values.
13119  */
13120 handleDataReturnPayload : function (oRequest, oResponse, oPayload) {
13121     return oPayload;
13122 },
13123
13124 /**
13125  * Updates the DataTable with state data sent in an onDataReturn* payload.
13126  *  
13127  * @method handleDataReturnPayload
13128  * @param oRequest {MIXED} Original generated request.
13129  * @param oResponse {Object} Response object.
13130  * @param oPayload {MIXED} State values
13131  */
13132 _handleDataReturnPayload : function (oRequest, oResponse, oPayload) {
13133     oPayload = this.handleDataReturnPayload(oRequest, oResponse, oPayload);
13134     if(oPayload) {
13135         // Update pagination
13136         var oPaginator = this.get('paginator');
13137         if (oPaginator) {
13138             // Update totalRecords
13139             if(this.get("dynamicData")) {
13140                 if (widget.Paginator.isNumeric(oPayload.totalRecords)) {
13141                     oPaginator.set('totalRecords',oPayload.totalRecords);
13142                 }
13143             }
13144             else {
13145                 oPaginator.set('totalRecords',this._oRecordSet.getLength());
13146             }
13147             // Update other paginator values
13148             if (lang.isObject(oPayload.pagination)) {
13149                 oPaginator.set('rowsPerPage',oPayload.pagination.rowsPerPage);
13150                 oPaginator.set('recordOffset',oPayload.pagination.recordOffset);
13151             }
13152         }
13153
13154         // Update sorting
13155         if (oPayload.sortedBy) {
13156             // Set the sorting values in preparation for refresh
13157             this.set('sortedBy', oPayload.sortedBy);
13158         }
13159         // Backwards compatibility for sorting
13160         else if (oPayload.sorting) {
13161             // Set the sorting values in preparation for refresh
13162             this.set('sortedBy', oPayload.sorting);
13163         }
13164     }
13165 },
13166
13167
13168
13169
13170
13171
13172
13173
13174
13175
13176
13177
13178
13179
13180
13181
13182
13183
13184
13185
13186
13187
13188
13189
13190
13191
13192
13193
13194
13195
13196
13197
13198
13199     /////////////////////////////////////////////////////////////////////////////
13200     //
13201     // Custom Events
13202     //
13203     /////////////////////////////////////////////////////////////////////////////
13204
13205     /**
13206      * Fired when the DataTable's rows are rendered from an initialized state.
13207      *
13208      * @event initEvent
13209      */
13210
13211     /**
13212      * Fired before the DataTable's DOM is rendered or modified.
13213      *
13214      * @event beforeRenderEvent
13215      */
13216
13217     /**
13218      * Fired when the DataTable's DOM is rendered or modified.
13219      *
13220      * @event renderEvent
13221      */
13222
13223     /**
13224      * Fired when the DataTable's post-render routine is complete, including
13225      * Column width validations.
13226      *
13227      * @event postRenderEvent
13228      */
13229
13230     /**
13231      * Fired when the DataTable is disabled.
13232      *
13233      * @event disableEvent
13234      */
13235
13236     /**
13237      * Fired when the DataTable is undisabled.
13238      *
13239      * @event undisableEvent
13240      */
13241
13242     /**
13243      * Fired when data is returned from DataSource but before it is consumed by
13244      * DataTable.
13245      *
13246      * @event dataReturnEvent
13247      * @param oArgs.request {String} Original request.
13248      * @param oArgs.response {Object} Response object.
13249      */
13250
13251     /**
13252      * Fired when the DataTable has a focus event.
13253      *
13254      * @event tableFocusEvent
13255      */
13256
13257     /**
13258      * Fired when the DataTable THEAD element has a focus event.
13259      *
13260      * @event theadFocusEvent
13261      */
13262
13263     /**
13264      * Fired when the DataTable TBODY element has a focus event.
13265      *
13266      * @event tbodyFocusEvent
13267      */
13268
13269     /**
13270      * Fired when the DataTable has a blur event.
13271      *
13272      * @event tableBlurEvent
13273      */
13274
13275     /*TODO implement theadBlurEvent
13276      * Fired when the DataTable THEAD element has a blur event.
13277      *
13278      * @event theadBlurEvent
13279      */
13280
13281     /*TODO: implement tbodyBlurEvent
13282      * Fired when the DataTable TBODY element has a blur event.
13283      *
13284      * @event tbodyBlurEvent
13285      */
13286
13287     /**
13288      * Fired when the DataTable has a key event.
13289      *
13290      * @event tableKeyEvent
13291      * @param oArgs.event {HTMLEvent} The event object.
13292      * @param oArgs.target {HTMLElement} The DataTable's TABLE element.
13293      */
13294
13295     /**
13296      * Fired when the DataTable THEAD element has a key event.
13297      *
13298      * @event theadKeyEvent
13299      * @param oArgs.event {HTMLEvent} The event object.
13300      * @param oArgs.target {HTMLElement} The DataTable's TABLE element.
13301      */
13302
13303     /**
13304      * Fired when the DataTable TBODY element has a key event.
13305      *
13306      * @event tbodyKeyEvent
13307      * @param oArgs.event {HTMLEvent} The event object.
13308      * @param oArgs.target {HTMLElement} The DataTable's TABLE element.
13309      */
13310
13311     /**
13312      * Fired when the DataTable has a mouseover.
13313      *
13314      * @event tableMouseoverEvent
13315      * @param oArgs.event {HTMLEvent} The event object.
13316      * @param oArgs.target {HTMLElement} The DataTable's TABLE element.
13317      *
13318      */
13319
13320     /**
13321      * Fired when the DataTable has a mouseout.
13322      *
13323      * @event tableMouseoutEvent
13324      * @param oArgs.event {HTMLEvent} The event object.
13325      * @param oArgs.target {HTMLElement} The DataTable's TABLE element.
13326      *
13327      */
13328
13329     /**
13330      * Fired when the DataTable has a mousedown.
13331      *
13332      * @event tableMousedownEvent
13333      * @param oArgs.event {HTMLEvent} The event object.
13334      * @param oArgs.target {HTMLElement} The DataTable's TABLE element.
13335      *
13336      */
13337
13338     /**
13339      * Fired when the DataTable has a mouseup.
13340      *
13341      * @event tableMouseupEvent
13342      * @param oArgs.event {HTMLEvent} The event object.
13343      * @param oArgs.target {HTMLElement} The DataTable's TABLE element.
13344      *
13345      */
13346
13347     /**
13348      * Fired when the DataTable has a click.
13349      *
13350      * @event tableClickEvent
13351      * @param oArgs.event {HTMLEvent} The event object.
13352      * @param oArgs.target {HTMLElement} The DataTable's TABLE element.
13353      *
13354      */
13355
13356     /**
13357      * Fired when the DataTable has a dblclick.
13358      *
13359      * @event tableDblclickEvent
13360      * @param oArgs.event {HTMLEvent} The event object.
13361      * @param oArgs.target {HTMLElement} The DataTable's TABLE element.
13362      *
13363      */
13364
13365     /**
13366      * Fired when a message is shown in the DataTable's message element.
13367      *
13368      * @event tableMsgShowEvent
13369      * @param oArgs.html {String} The HTML displayed.
13370      * @param oArgs.className {String} The className assigned.
13371      *
13372      */
13373
13374     /**
13375      * Fired when the DataTable's message element is hidden.
13376      *
13377      * @event tableMsgHideEvent
13378      */
13379
13380     /**
13381      * Fired when a THEAD row has a mouseover.
13382      *
13383      * @event theadRowMouseoverEvent
13384      * @param oArgs.event {HTMLEvent} The event object.
13385      * @param oArgs.target {HTMLElement} The TR element.
13386      */
13387
13388     /**
13389      * Fired when a THEAD row has a mouseout.
13390      *
13391      * @event theadRowMouseoutEvent
13392      * @param oArgs.event {HTMLEvent} The event object.
13393      * @param oArgs.target {HTMLElement} The TR element.
13394      */
13395
13396     /**
13397      * Fired when a THEAD row has a mousedown.
13398      *
13399      * @event theadRowMousedownEvent
13400      * @param oArgs.event {HTMLEvent} The event object.
13401      * @param oArgs.target {HTMLElement} The TR element.
13402      */
13403
13404     /**
13405      * Fired when a THEAD row has a mouseup.
13406      *
13407      * @event theadRowMouseupEvent
13408      * @param oArgs.event {HTMLEvent} The event object.
13409      * @param oArgs.target {HTMLElement} The TR element.
13410      */
13411
13412     /**
13413      * Fired when a THEAD row has a click.
13414      *
13415      * @event theadRowClickEvent
13416      * @param oArgs.event {HTMLEvent} The event object.
13417      * @param oArgs.target {HTMLElement} The TR element.
13418      */
13419
13420     /**
13421      * Fired when a THEAD row has a dblclick.
13422      *
13423      * @event theadRowDblclickEvent
13424      * @param oArgs.event {HTMLEvent} The event object.
13425      * @param oArgs.target {HTMLElement} The TR element.
13426      */
13427
13428     /**
13429      * Fired when a THEAD cell has a mouseover.
13430      *
13431      * @event theadCellMouseoverEvent
13432      * @param oArgs.event {HTMLEvent} The event object.
13433      * @param oArgs.target {HTMLElement} The TH element.
13434      *
13435      */
13436
13437     /**
13438      * Fired when a THEAD cell has a mouseout.
13439      *
13440      * @event theadCellMouseoutEvent
13441      * @param oArgs.event {HTMLEvent} The event object.
13442      * @param oArgs.target {HTMLElement} The TH element.
13443      *
13444      */
13445
13446     /**
13447      * Fired when a THEAD cell has a mousedown.
13448      *
13449      * @event theadCellMousedownEvent
13450      * @param oArgs.event {HTMLEvent} The event object.
13451      * @param oArgs.target {HTMLElement} The TH element.
13452      */
13453
13454     /**
13455      * Fired when a THEAD cell has a mouseup.
13456      *
13457      * @event theadCellMouseupEvent
13458      * @param oArgs.event {HTMLEvent} The event object.
13459      * @param oArgs.target {HTMLElement} The TH element.
13460      */
13461
13462     /**
13463      * Fired when a THEAD cell has a click.
13464      *
13465      * @event theadCellClickEvent
13466      * @param oArgs.event {HTMLEvent} The event object.
13467      * @param oArgs.target {HTMLElement} The TH element.
13468      */
13469
13470     /**
13471      * Fired when a THEAD cell has a dblclick.
13472      *
13473      * @event theadCellDblclickEvent
13474      * @param oArgs.event {HTMLEvent} The event object.
13475      * @param oArgs.target {HTMLElement} The TH element.
13476      */
13477
13478     /**
13479      * Fired when a THEAD label has a mouseover.
13480      *
13481      * @event theadLabelMouseoverEvent
13482      * @param oArgs.event {HTMLEvent} The event object.
13483      * @param oArgs.target {HTMLElement} The SPAN element.
13484      *
13485      */
13486
13487     /**
13488      * Fired when a THEAD label has a mouseout.
13489      *
13490      * @event theadLabelMouseoutEvent
13491      * @param oArgs.event {HTMLEvent} The event object.
13492      * @param oArgs.target {HTMLElement} The SPAN element.
13493      *
13494      */
13495
13496     /**
13497      * Fired when a THEAD label has a mousedown.
13498      *
13499      * @event theadLabelMousedownEvent
13500      * @param oArgs.event {HTMLEvent} The event object.
13501      * @param oArgs.target {HTMLElement} The SPAN element.
13502      */
13503
13504     /**
13505      * Fired when a THEAD label has a mouseup.
13506      *
13507      * @event theadLabelMouseupEvent
13508      * @param oArgs.event {HTMLEvent} The event object.
13509      * @param oArgs.target {HTMLElement} The SPAN element.
13510      */
13511
13512     /**
13513      * Fired when a THEAD label has a click.
13514      *
13515      * @event theadLabelClickEvent
13516      * @param oArgs.event {HTMLEvent} The event object.
13517      * @param oArgs.target {HTMLElement} The SPAN element.
13518      */
13519
13520     /**
13521      * Fired when a THEAD label has a dblclick.
13522      *
13523      * @event theadLabelDblclickEvent
13524      * @param oArgs.event {HTMLEvent} The event object.
13525      * @param oArgs.target {HTMLElement} The SPAN element.
13526      */
13527
13528     /**
13529      * Fired when a column is sorted.
13530      *
13531      * @event columnSortEvent
13532      * @param oArgs.column {YAHOO.widget.Column} The Column instance.
13533      * @param oArgs.dir {String} Sort direction: YAHOO.widget.DataTable.CLASS_ASC
13534      * or YAHOO.widget.DataTable.CLASS_DESC.
13535      */
13536
13537     /**
13538      * Fired when a column width is set.
13539      *
13540      * @event columnSetWidthEvent
13541      * @param oArgs.column {YAHOO.widget.Column} The Column instance.
13542      * @param oArgs.width {Number} The width in pixels.
13543      */
13544
13545     /**
13546      * Fired when a column width is unset.
13547      *
13548      * @event columnUnsetWidthEvent
13549      * @param oArgs.column {YAHOO.widget.Column} The Column instance.
13550      */
13551
13552     /**
13553      * Fired when a column is drag-resized.
13554      *
13555      * @event columnResizeEvent
13556      * @param oArgs.column {YAHOO.widget.Column} The Column instance.
13557      * @param oArgs.target {HTMLElement} The TH element.
13558      * @param oArgs.width {Number} Width in pixels.     
13559      */
13560
13561     /**
13562      * Fired when a Column is moved to a new index.
13563      *
13564      * @event columnReorderEvent
13565      * @param oArgs.column {YAHOO.widget.Column} The Column instance.
13566      * @param oArgs.oldIndex {Number} The previous index position.
13567      */
13568
13569     /**
13570      * Fired when a column is hidden.
13571      *
13572      * @event columnHideEvent
13573      * @param oArgs.column {YAHOO.widget.Column} The Column instance.
13574      */
13575
13576     /**
13577      * Fired when a column is shown.
13578      *
13579      * @event columnShowEvent
13580      * @param oArgs.column {YAHOO.widget.Column} The Column instance.
13581      */
13582
13583     /**
13584      * Fired when a column is selected.
13585      *
13586      * @event columnSelectEvent
13587      * @param oArgs.column {YAHOO.widget.Column} The Column instance.
13588      */
13589
13590     /**
13591      * Fired when a column is unselected.
13592      *
13593      * @event columnUnselectEvent
13594      * @param oArgs.column {YAHOO.widget.Column} The Column instance.
13595      */
13596     /**
13597      * Fired when a column is removed.
13598      *
13599      * @event columnRemoveEvent
13600      * @param oArgs.column {YAHOO.widget.Column} The Column instance.
13601      */
13602
13603     /**
13604      * Fired when a column is inserted.
13605      *
13606      * @event columnInsertEvent
13607      * @param oArgs.column {YAHOO.widget.Column} The Column instance.
13608      * @param oArgs.index {Number} The index position.
13609      */
13610
13611     /**
13612      * Fired when a column is highlighted.
13613      *
13614      * @event columnHighlightEvent
13615      * @param oArgs.column {YAHOO.widget.Column} The highlighted Column.
13616      */
13617
13618     /**
13619      * Fired when a column is unhighlighted.
13620      *
13621      * @event columnUnhighlightEvent
13622      * @param oArgs.column {YAHOO.widget.Column} The unhighlighted Column.
13623      */
13624
13625
13626     /**
13627      * Fired when a row has a mouseover.
13628      *
13629      * @event rowMouseoverEvent
13630      * @param oArgs.event {HTMLEvent} The event object.
13631      * @param oArgs.target {HTMLElement} The TR element.
13632      */
13633
13634     /**
13635      * Fired when a row has a mouseout.
13636      *
13637      * @event rowMouseoutEvent
13638      * @param oArgs.event {HTMLEvent} The event object.
13639      * @param oArgs.target {HTMLElement} The TR element.
13640      */
13641
13642     /**
13643      * Fired when a row has a mousedown.
13644      *
13645      * @event rowMousedownEvent
13646      * @param oArgs.event {HTMLEvent} The event object.
13647      * @param oArgs.target {HTMLElement} The TR element.
13648      */
13649
13650     /**
13651      * Fired when a row has a mouseup.
13652      *
13653      * @event rowMouseupEvent
13654      * @param oArgs.event {HTMLEvent} The event object.
13655      * @param oArgs.target {HTMLElement} The TR element.
13656      */
13657
13658     /**
13659      * Fired when a row has a click.
13660      *
13661      * @event rowClickEvent
13662      * @param oArgs.event {HTMLEvent} The event object.
13663      * @param oArgs.target {HTMLElement} The TR element.
13664      */
13665
13666     /**
13667      * Fired when a row has a dblclick.
13668      *
13669      * @event rowDblclickEvent
13670      * @param oArgs.event {HTMLEvent} The event object.
13671      * @param oArgs.target {HTMLElement} The TR element.
13672      */
13673
13674     /**
13675      * Fired when a row is added.
13676      *
13677      * @event rowAddEvent
13678      * @param oArgs.record {YAHOO.widget.Record} The added Record.
13679      */
13680      
13681     /**
13682      * Fired when rows are added.
13683      *
13684      * @event rowsAddEvent
13685      * @param oArgs.record {YAHOO.widget.Record[]} The added Records.
13686      */
13687
13688     /**
13689      * Fired when a row is updated.
13690      *
13691      * @event rowUpdateEvent
13692      * @param oArgs.record {YAHOO.widget.Record} The updated Record.
13693      * @param oArgs.oldData {Object} Object literal of the old data.
13694      */
13695
13696     /**
13697      * Fired when a row is deleted.
13698      *
13699      * @event rowDeleteEvent
13700      * @param oArgs.oldData {Object} Object literal of the deleted data.
13701      * @param oArgs.recordIndex {Number} Index of the deleted Record.
13702      * @param oArgs.trElIndex {Number} Index of the deleted TR element, if on current page.
13703      */
13704      
13705     /**
13706      * Fired when rows are deleted.
13707      *
13708      * @event rowsDeleteEvent
13709      * @param oArgs.oldData {Object[]} Array of object literals of the deleted data.
13710      * @param oArgs.recordIndex {Number} Index of the first deleted Record.
13711      * @param oArgs.count {Number} Number of deleted Records.
13712      */
13713
13714     /**
13715      * Fired when a row is selected.
13716      *
13717      * @event rowSelectEvent
13718      * @param oArgs.el {HTMLElement} The selected TR element, if applicable.
13719      * @param oArgs.record {YAHOO.widget.Record} The selected Record.
13720      */
13721
13722     /**
13723      * Fired when a row is unselected.
13724      *
13725      * @event rowUnselectEvent
13726      * @param oArgs.el {HTMLElement} The unselected TR element, if applicable.
13727      * @param oArgs.record {YAHOO.widget.Record} The unselected Record.
13728      */
13729
13730     /**
13731      * Fired when all row selections are cleared.
13732      *
13733      * @event unselectAllRowsEvent
13734      */
13735
13736     /**
13737      * Fired when a row is highlighted.
13738      *
13739      * @event rowHighlightEvent
13740      * @param oArgs.el {HTMLElement} The highlighted TR element.
13741      * @param oArgs.record {YAHOO.widget.Record} The highlighted Record.
13742      */
13743
13744     /**
13745      * Fired when a row is unhighlighted.
13746      *
13747      * @event rowUnhighlightEvent
13748      * @param oArgs.el {HTMLElement} The highlighted TR element.
13749      * @param oArgs.record {YAHOO.widget.Record} The highlighted Record.
13750      */
13751
13752     /**
13753      * Fired when a cell is updated.
13754      *
13755      * @event cellUpdateEvent
13756      * @param oArgs.record {YAHOO.widget.Record} The updated Record.
13757      * @param oArgs.column {YAHOO.widget.Column} The updated Column.
13758      * @param oArgs.oldData {Object} Original data value of the updated cell.
13759      */
13760
13761     /**
13762      * Fired when a cell has a mouseover.
13763      *
13764      * @event cellMouseoverEvent
13765      * @param oArgs.event {HTMLEvent} The event object.
13766      * @param oArgs.target {HTMLElement} The TD element.
13767      */
13768
13769     /**
13770      * Fired when a cell has a mouseout.
13771      *
13772      * @event cellMouseoutEvent
13773      * @param oArgs.event {HTMLEvent} The event object.
13774      * @param oArgs.target {HTMLElement} The TD element.
13775      */
13776
13777     /**
13778      * Fired when a cell has a mousedown.
13779      *
13780      * @event cellMousedownEvent
13781      * @param oArgs.event {HTMLEvent} The event object.
13782      * @param oArgs.target {HTMLElement} The TD element.
13783      */
13784
13785     /**
13786      * Fired when a cell has a mouseup.
13787      *
13788      * @event cellMouseupEvent
13789      * @param oArgs.event {HTMLEvent} The event object.
13790      * @param oArgs.target {HTMLElement} The TD element.
13791      */
13792
13793     /**
13794      * Fired when a cell has a click.
13795      *
13796      * @event cellClickEvent
13797      * @param oArgs.event {HTMLEvent} The event object.
13798      * @param oArgs.target {HTMLElement} The TD element.
13799      */
13800
13801     /**
13802      * Fired when a cell has a dblclick.
13803      *
13804      * @event cellDblclickEvent
13805      * @param oArgs.event {HTMLEvent} The event object.
13806      * @param oArgs.target {HTMLElement} The TD element.
13807      */
13808
13809     /**
13810      * Fired when a cell is formatted.
13811      *
13812      * @event cellFormatEvent
13813      * @param oArgs.el {HTMLElement} The formatted TD element.
13814      * @param oArgs.record {YAHOO.widget.Record} The associated Record instance.
13815      * @param oArgs.column {YAHOO.widget.Column} The associated Column instance.
13816      * @param oArgs.key {String} (deprecated) The key of the formatted cell.
13817      */
13818
13819     /**
13820      * Fired when a cell is selected.
13821      *
13822      * @event cellSelectEvent
13823      * @param oArgs.el {HTMLElement} The selected TD element.
13824      * @param oArgs.record {YAHOO.widget.Record} The associated Record instance.
13825      * @param oArgs.column {YAHOO.widget.Column} The associated Column instance.
13826      * @param oArgs.key {String} (deprecated) The key of the selected cell.
13827      */
13828
13829     /**
13830      * Fired when a cell is unselected.
13831      *
13832      * @event cellUnselectEvent
13833      * @param oArgs.el {HTMLElement} The unselected TD element.
13834      * @param oArgs.record {YAHOO.widget.Record} The associated Record.
13835      * @param oArgs.column {YAHOO.widget.Column} The associated Column instance.
13836      * @param oArgs.key {String} (deprecated) The key of the unselected cell.
13837
13838      */
13839
13840     /**
13841      * Fired when a cell is highlighted.
13842      *
13843      * @event cellHighlightEvent
13844      * @param oArgs.el {HTMLElement} The highlighted TD element.
13845      * @param oArgs.record {YAHOO.widget.Record} The associated Record instance.
13846      * @param oArgs.column {YAHOO.widget.Column} The associated Column instance.
13847      * @param oArgs.key {String} (deprecated) The key of the highlighted cell.
13848
13849      */
13850
13851     /**
13852      * Fired when a cell is unhighlighted.
13853      *
13854      * @event cellUnhighlightEvent
13855      * @param oArgs.el {HTMLElement} The unhighlighted TD element.
13856      * @param oArgs.record {YAHOO.widget.Record} The associated Record instance.
13857      * @param oArgs.column {YAHOO.widget.Column} The associated Column instance.
13858      * @param oArgs.key {String} (deprecated) The key of the unhighlighted cell.
13859
13860      */
13861
13862     /**
13863      * Fired when all cell selections are cleared.
13864      *
13865      * @event unselectAllCellsEvent
13866      */
13867
13868     /**
13869      * Fired when a CellEditor is shown.
13870      *
13871      * @event editorShowEvent
13872      * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
13873      */
13874
13875     /**
13876      * Fired when a CellEditor has a keydown.
13877      *
13878      * @event editorKeydownEvent
13879      * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
13880      * @param oArgs.event {HTMLEvent} The event object.
13881      */
13882
13883     /**
13884      * Fired when a CellEditor input is reverted.
13885      *
13886      * @event editorRevertEvent
13887      * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
13888      * @param oArgs.newData {Object} New data value from form input field.
13889      * @param oArgs.oldData {Object} Old data value.
13890      */
13891
13892     /**
13893      * Fired when a CellEditor input is saved.
13894      *
13895      * @event editorSaveEvent
13896      * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
13897      * @param oArgs.newData {Object} New data value from form input field.
13898      * @param oArgs.oldData {Object} Old data value.
13899      */
13900
13901     /**
13902      * Fired when a CellEditor input is canceled.
13903      *
13904      * @event editorCancelEvent
13905      * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
13906      */
13907
13908     /**
13909      * Fired when a CellEditor has a blur event.
13910      *
13911      * @event editorBlurEvent
13912      * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
13913      */
13914
13915     /**
13916      * Fired when a CellEditor is blocked.
13917      *
13918      * @event editorBlockEvent
13919      * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
13920      */
13921
13922     /**
13923      * Fired when a CellEditor is unblocked.
13924      *
13925      * @event editorUnblockEvent
13926      * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
13927      */
13928
13929
13930
13931
13932
13933     /**
13934      * Fired when a link is clicked.
13935      *
13936      * @event linkClickEvent
13937      * @param oArgs.event {HTMLEvent} The event object.
13938      * @param oArgs.target {HTMLElement} The A element.
13939      */
13940
13941     /**
13942      * Fired when a BUTTON element or INPUT element of type "button", "image",
13943      * "submit", "reset" is clicked.
13944      *
13945      * @event buttonClickEvent
13946      * @param oArgs.event {HTMLEvent} The event object.
13947      * @param oArgs.target {HTMLElement} The BUTTON element.
13948      */
13949
13950     /**
13951      * Fired when a CHECKBOX element is clicked.
13952      *
13953      * @event checkboxClickEvent
13954      * @param oArgs.event {HTMLEvent} The event object.
13955      * @param oArgs.target {HTMLElement} The CHECKBOX element.
13956      */
13957
13958     /**
13959      * Fired when a SELECT element is changed.
13960      *
13961      * @event dropdownChangeEvent
13962      * @param oArgs.event {HTMLEvent} The event object.
13963      * @param oArgs.target {HTMLElement} The SELECT element.
13964      */
13965
13966     /**
13967      * Fired when a RADIO element is clicked.
13968      *
13969      * @event radioClickEvent
13970      * @param oArgs.event {HTMLEvent} The event object.
13971      * @param oArgs.target {HTMLElement} The RADIO element.
13972      */
13973
13974
13975
13976
13977
13978
13979
13980
13981
13982
13983
13984
13985
13986
13987
13988
13989
13990
13991
13992
13993
13994
13995
13996
13997
13998
13999 /////////////////////////////////////////////////////////////////////////////
14000 //
14001 // Deprecated APIs
14002 //
14003 /////////////////////////////////////////////////////////////////////////////
14004   
14005 /*
14006  * @method showCellEditorBtns
14007  * @deprecated Use CellEditor.renderBtns() 
14008  */
14009 showCellEditorBtns : function(elContainer) {
14010     // Buttons
14011     var elBtnsDiv = elContainer.appendChild(document.createElement("div"));
14012     Dom.addClass(elBtnsDiv, DT.CLASS_BUTTON);
14013
14014     // Save button
14015     var elSaveBtn = elBtnsDiv.appendChild(document.createElement("button"));
14016     Dom.addClass(elSaveBtn, DT.CLASS_DEFAULT);
14017     elSaveBtn.innerHTML = "OK";
14018     Ev.addListener(elSaveBtn, "click", function(oArgs, oSelf) {
14019         oSelf.onEventSaveCellEditor(oArgs, oSelf);
14020         oSelf.focusTbodyEl();
14021     }, this, true);
14022
14023     // Cancel button
14024     var elCancelBtn = elBtnsDiv.appendChild(document.createElement("button"));
14025     elCancelBtn.innerHTML = "Cancel";
14026     Ev.addListener(elCancelBtn, "click", function(oArgs, oSelf) {
14027         oSelf.onEventCancelCellEditor(oArgs, oSelf);
14028         oSelf.focusTbodyEl();
14029     }, this, true);
14030
14031     YAHOO.log("The method showCellEditorBtns() has been deprecated." +
14032             " Please use the CellEditor class.", "warn", this.toString());
14033 },
14034
14035 /**
14036  * @method resetCellEditor
14037  * @deprecated Use destroyCellEditor 
14038  */
14039 resetCellEditor : function() {
14040     var elContainer = this._oCellEditor.container;
14041     elContainer.style.display = "none";
14042     Ev.purgeElement(elContainer, true);
14043     elContainer.innerHTML = "";
14044     this._oCellEditor.value = null;
14045     this._oCellEditor.isActive = false;
14046
14047     YAHOO.log("The method resetCellEditor() has been deprecated." +
14048             " Please use the CellEditor class.", "warn", this.toString());
14049 },
14050
14051 /**
14052  * @event editorUpdateEvent
14053  * @deprecated Use CellEditor class.
14054  */
14055
14056 /**
14057  * @method getBody
14058  * @deprecated Use getTbodyEl().
14059  */
14060 getBody : function() {
14061     // Backward compatibility
14062     YAHOO.log("The method getBody() has been deprecated" +
14063             " in favor of getTbodyEl()", "warn", this.toString());
14064     return this.getTbodyEl();
14065 },
14066
14067 /**
14068  * @method getCell
14069  * @deprecated Use getTdEl().
14070  */
14071 getCell : function(index) {
14072     // Backward compatibility
14073     YAHOO.log("The method getCell() has been deprecated" +
14074             " in favor of getTdEl()", "warn", this.toString());
14075     return this.getTdEl(index);
14076 },
14077
14078 /**
14079  * @method getRow
14080  * @deprecated Use getTrEl().
14081  */
14082 getRow : function(index) {
14083     // Backward compatibility
14084     YAHOO.log("The method getRow() has been deprecated" +
14085             " in favor of getTrEl()", "warn", this.toString());
14086     return this.getTrEl(index);
14087 },
14088
14089 /**
14090  * @method refreshView
14091  * @deprecated Use render.
14092  */
14093 refreshView : function() {
14094     // Backward compatibility
14095     YAHOO.log("The method refreshView() has been deprecated" +
14096             " in favor of render()", "warn", this.toString());
14097     this.render();
14098 },
14099
14100 /**
14101  * @method select
14102  * @deprecated Use selectRow.
14103  */
14104 select : function(els) {
14105     // Backward compatibility
14106     YAHOO.log("The method select() has been deprecated" +
14107             " in favor of selectRow()", "warn", this.toString());
14108     if(!lang.isArray(els)) {
14109         els = [els];
14110     }
14111     for(var i=0; i<els.length; i++) {
14112         this.selectRow(els[i]);
14113     }
14114 },
14115
14116 /**
14117  * @method onEventEditCell
14118  * @deprecated Use onEventShowCellEditor.
14119  */
14120 onEventEditCell : function(oArgs) {
14121     // Backward compatibility
14122     YAHOO.log("The method onEventEditCell() has been deprecated" +
14123         " in favor of onEventShowCellEditor()", "warn", this.toString());
14124     this.onEventShowCellEditor(oArgs);
14125 },
14126
14127 /**
14128  * @method _syncColWidths
14129  * @deprecated Use validateColumnWidths.
14130  */
14131 _syncColWidths : function() {
14132     // Backward compatibility
14133     YAHOO.log("The method _syncColWidths() has been deprecated" +
14134         " in favor of validateColumnWidths()", "warn", this.toString());
14135     this.validateColumnWidths();
14136 }
14137
14138 /**
14139  * @event headerRowMouseoverEvent
14140  * @deprecated Use theadRowMouseoverEvent.
14141  */
14142
14143 /**
14144  * @event headerRowMouseoutEvent
14145  * @deprecated Use theadRowMouseoutEvent.
14146  */
14147
14148 /**
14149  * @event headerRowMousedownEvent
14150  * @deprecated Use theadRowMousedownEvent.
14151  */
14152
14153 /**
14154  * @event headerRowClickEvent
14155  * @deprecated Use theadRowClickEvent.
14156  */
14157
14158 /**
14159  * @event headerRowDblclickEvent
14160  * @deprecated Use theadRowDblclickEvent.
14161  */
14162
14163 /**
14164  * @event headerCellMouseoverEvent
14165  * @deprecated Use theadCellMouseoverEvent.
14166  */
14167
14168 /**
14169  * @event headerCellMouseoutEvent
14170  * @deprecated Use theadCellMouseoutEvent.
14171  */
14172
14173 /**
14174  * @event headerCellMousedownEvent
14175  * @deprecated Use theadCellMousedownEvent.
14176  */
14177
14178 /**
14179  * @event headerCellClickEvent
14180  * @deprecated Use theadCellClickEvent.
14181  */
14182
14183 /**
14184  * @event headerCellDblclickEvent
14185  * @deprecated Use theadCellDblclickEvent.
14186  */
14187
14188 /**
14189  * @event headerLabelMouseoverEvent
14190  * @deprecated Use theadLabelMouseoverEvent.
14191  */
14192
14193 /**
14194  * @event headerLabelMouseoutEvent
14195  * @deprecated Use theadLabelMouseoutEvent.
14196  */
14197
14198 /**
14199  * @event headerLabelMousedownEvent
14200  * @deprecated Use theadLabelMousedownEvent.
14201  */
14202
14203 /**
14204  * @event headerLabelClickEvent
14205  * @deprecated Use theadLabelClickEvent.
14206  */
14207
14208 /**
14209  * @event headerLabelDbllickEvent
14210  * @deprecated Use theadLabelDblclickEvent.
14211  */
14212
14213 });
14214
14215 /**
14216  * Alias for onDataReturnSetRows for backward compatibility
14217  * @method onDataReturnSetRecords
14218  * @deprecated Use onDataReturnSetRows
14219  */
14220 DT.prototype.onDataReturnSetRecords = DT.prototype.onDataReturnSetRows;
14221
14222 /**
14223  * Alias for onPaginatorChange for backward compatibility
14224  * @method onPaginatorChange
14225  * @deprecated Use onPaginatorChangeRequest
14226  */
14227 DT.prototype.onPaginatorChange = DT.prototype.onPaginatorChangeRequest;
14228
14229 /////////////////////////////////////////////////////////////////////////////
14230 //
14231 // Deprecated static APIs
14232 //
14233 /////////////////////////////////////////////////////////////////////////////
14234 /**
14235  * @method DataTable.formatTheadCell
14236  * @deprecated  Use formatTheadCell.
14237  */
14238 DT.formatTheadCell = function() {};
14239
14240 /**
14241  * @method DataTable.editCheckbox
14242  * @deprecated  Use YAHOO.widget.CheckboxCellEditor.
14243  */
14244 DT.editCheckbox = function() {};
14245
14246 /**
14247  * @method DataTable.editDate
14248  * @deprecated Use YAHOO.widget.DateCellEditor.
14249  */
14250 DT.editDate = function() {};
14251
14252 /**
14253  * @method DataTable.editDropdown
14254  * @deprecated Use YAHOO.widget.DropdownCellEditor.
14255  */
14256 DT.editDropdown = function() {};
14257
14258 /**
14259  * @method DataTable.editRadio
14260  * @deprecated Use YAHOO.widget.RadioCellEditor.
14261  */
14262 DT.editRadio = function() {};
14263
14264 /**
14265  * @method DataTable.editTextarea
14266  * @deprecated Use YAHOO.widget.TextareaCellEditor
14267  */
14268 DT.editTextarea = function() {};
14269
14270 /**
14271  * @method DataTable.editTextbox
14272  * @deprecated Use YAHOO.widget.TextboxCellEditor
14273  */
14274 DT.editTextbox= function() {};
14275
14276 })();
14277
14278 (function () {
14279
14280 var lang   = YAHOO.lang,
14281     util   = YAHOO.util,
14282     widget = YAHOO.widget,
14283     ua     = YAHOO.env.ua,
14284     
14285     Dom    = util.Dom,
14286     Ev     = util.Event,
14287     DS     = util.DataSourceBase,
14288     DT     = widget.DataTable,
14289     Pag    = widget.Paginator;
14290     
14291 /**
14292  * The ScrollingDataTable class extends the DataTable class to provide
14293  * functionality for x-scrolling, y-scrolling, and xy-scrolling.
14294  *
14295  * @namespace YAHOO.widget
14296  * @class ScrollingDataTable
14297  * @extends YAHOO.widget.DataTable
14298  * @constructor
14299  * @param elContainer {HTMLElement} Container element for the TABLE.
14300  * @param aColumnDefs {Object[]} Array of object literal Column definitions.
14301  * @param oDataSource {YAHOO.util.DataSource} DataSource instance.
14302  * @param oConfigs {object} (optional) Object literal of configuration values.
14303  */
14304 widget.ScrollingDataTable = function(elContainer,aColumnDefs,oDataSource,oConfigs) {
14305     oConfigs = oConfigs || {};
14306     
14307     // Prevent infinite loop
14308     if(oConfigs.scrollable) {
14309         oConfigs.scrollable = false;
14310     }
14311
14312     widget.ScrollingDataTable.superclass.constructor.call(this, elContainer,aColumnDefs,oDataSource,oConfigs); 
14313
14314     // Once per instance
14315     this.subscribe("columnShowEvent", this._onColumnChange);
14316 };
14317
14318 var SDT = widget.ScrollingDataTable;
14319
14320 /////////////////////////////////////////////////////////////////////////////
14321 //
14322 // Public constants
14323 //
14324 /////////////////////////////////////////////////////////////////////////////
14325 lang.augmentObject(SDT, {
14326
14327     /**
14328      * Class name assigned to inner DataTable header container.
14329      *
14330      * @property DataTable.CLASS_HEADER
14331      * @type String
14332      * @static
14333      * @final
14334      * @default "yui-dt-hd"
14335      */
14336     CLASS_HEADER : "yui-dt-hd",
14337     
14338     /**
14339      * Class name assigned to inner DataTable body container.
14340      *
14341      * @property DataTable.CLASS_BODY
14342      * @type String
14343      * @static
14344      * @final
14345      * @default "yui-dt-bd"
14346      */
14347     CLASS_BODY : "yui-dt-bd"
14348 });
14349
14350 lang.extend(SDT, DT, {
14351
14352 /**
14353  * Container for fixed header TABLE element.
14354  *
14355  * @property _elHdContainer
14356  * @type HTMLElement
14357  * @private
14358  */
14359 _elHdContainer : null,
14360
14361 /**
14362  * Fixed header TABLE element.
14363  *
14364  * @property _elHdTable
14365  * @type HTMLElement
14366  * @private
14367  */
14368 _elHdTable : null,
14369
14370 /**
14371  * Container for scrolling body TABLE element.
14372  *
14373  * @property _elBdContainer
14374  * @type HTMLElement
14375  * @private
14376  */
14377 _elBdContainer : null,
14378
14379 /**
14380  * Body THEAD element.
14381  *
14382  * @property _elBdThead
14383  * @type HTMLElement
14384  * @private
14385  */
14386 _elBdThead : null,
14387
14388 /**
14389  * Offscreen container to temporarily clone SDT for auto-width calculation.
14390  *
14391  * @property _elTmpContainer
14392  * @type HTMLElement
14393  * @private
14394  */
14395 _elTmpContainer : null,
14396
14397 /**
14398  * Offscreen TABLE element for auto-width calculation.
14399  *
14400  * @property _elTmpTable
14401  * @type HTMLElement
14402  * @private
14403  */
14404 _elTmpTable : null,
14405
14406 /**
14407  * True if x-scrollbar is currently visible.
14408  * @property _bScrollbarX
14409  * @type Boolean
14410  * @private 
14411  */
14412 _bScrollbarX : null,
14413
14414
14415
14416
14417
14418
14419
14420
14421
14422
14423
14424
14425
14426
14427
14428 /////////////////////////////////////////////////////////////////////////////
14429 //
14430 // Superclass methods
14431 //
14432 /////////////////////////////////////////////////////////////////////////////
14433
14434 /**
14435  * Implementation of Element's abstract method. Sets up config values.
14436  *
14437  * @method initAttributes
14438  * @param oConfigs {Object} (Optional) Object literal definition of configuration values.
14439  * @private
14440  */
14441
14442 initAttributes : function(oConfigs) {
14443     oConfigs = oConfigs || {};
14444     SDT.superclass.initAttributes.call(this, oConfigs);
14445
14446     /**
14447     * @attribute width
14448     * @description Table width for scrollable tables (e.g., "40em").
14449     * @type String
14450     */
14451     this.setAttributeConfig("width", {
14452         value: null,
14453         validator: lang.isString,
14454         method: function(oParam) {
14455             if(this._elHdContainer && this._elBdContainer) {
14456                 this._elHdContainer.style.width = oParam;
14457                 this._elBdContainer.style.width = oParam;            
14458                 this._syncScrollX();      
14459                 this._syncScrollOverhang();
14460             }
14461         }
14462     });
14463
14464     /**
14465     * @attribute height
14466     * @description Table body height for scrollable tables, not including headers (e.g., "40em").
14467     * @type String
14468     */
14469     this.setAttributeConfig("height", {
14470         value: null,
14471         validator: lang.isString,
14472         method: function(oParam) {
14473             if(this._elHdContainer && this._elBdContainer) {
14474                 this._elBdContainer.style.height = oParam;    
14475                 this._syncScrollX();   
14476                 this._syncScrollY();
14477                 this._syncScrollOverhang();
14478             }
14479         }
14480     });
14481
14482     /**
14483     * @attribute COLOR_COLUMNFILLER
14484     * @description CSS color value assigned to header filler on scrollable tables.  
14485     * @type String
14486     * @default "#F2F2F2"
14487     */
14488     this.setAttributeConfig("COLOR_COLUMNFILLER", {
14489         value: "#F2F2F2",
14490         validator: lang.isString,
14491         method: function(oParam) {
14492             this._elHdContainer.style.backgroundColor = oParam;
14493         }
14494     });
14495 },
14496
14497 /**
14498  * Initializes DOM elements for a ScrollingDataTable, including creation of
14499  * two separate TABLE elements.
14500  *
14501  * @method _initDomElements
14502  * @param elContainer {HTMLElement | String} HTML DIV element by reference or ID. 
14503  * return {Boolean} False in case of error, otherwise true 
14504  * @private
14505  */
14506 _initDomElements : function(elContainer) {
14507     // Outer and inner containers
14508     this._initContainerEl(elContainer);
14509     if(this._elContainer && this._elHdContainer && this._elBdContainer) {
14510         // TABLEs
14511         this._initTableEl();
14512         
14513         if(this._elHdTable && this._elTable) {
14514             // COLGROUPs
14515             ///this._initColgroupEl(this._elHdTable, this._elTable);  
14516             this._initColgroupEl(this._elHdTable);        
14517             
14518             // THEADs
14519             this._initTheadEl(this._elHdTable, this._elTable);
14520             
14521             // Primary TBODY
14522             this._initTbodyEl(this._elTable);
14523             // Message TBODY
14524             this._initMsgTbodyEl(this._elTable);            
14525         }
14526     }
14527     if(!this._elContainer || !this._elTable || !this._elColgroup ||  !this._elThead || !this._elTbody || !this._elMsgTbody ||
14528             !this._elHdTable || !this._elBdThead) {
14529         YAHOO.log("Could not instantiate DataTable due to an invalid DOM elements", "error", this.toString());
14530         return false;
14531     }
14532     else {
14533         return true;
14534     }
14535 },
14536
14537 /**
14538  * Destroy's the DataTable outer and inner container elements, if available.
14539  *
14540  * @method _destroyContainerEl
14541  * @param elContainer {HTMLElement} Reference to the container element. 
14542  * @private
14543  */
14544 _destroyContainerEl : function(elContainer) {
14545     Dom.removeClass(elContainer, DT.CLASS_SCROLLABLE);
14546     SDT.superclass._destroyContainerEl.call(this, elContainer);
14547     this._elHdContainer = null;
14548     this._elBdContainer = null;
14549 },
14550
14551 /**
14552  * Initializes the DataTable outer container element and creates inner header
14553  * and body container elements.
14554  *
14555  * @method _initContainerEl
14556  * @param elContainer {HTMLElement | String} HTML DIV element by reference or ID.
14557  * @private
14558  */
14559 _initContainerEl : function(elContainer) {
14560     SDT.superclass._initContainerEl.call(this, elContainer);
14561     
14562     if(this._elContainer) {
14563         elContainer = this._elContainer; // was constructor input, now is DOM ref
14564         Dom.addClass(elContainer, DT.CLASS_SCROLLABLE);
14565         
14566         // Container for header TABLE
14567         var elHdContainer = document.createElement("div");
14568         elHdContainer.style.width = this.get("width") || "";
14569         elHdContainer.style.backgroundColor = this.get("COLOR_COLUMNFILLER");
14570         Dom.addClass(elHdContainer, SDT.CLASS_HEADER);
14571         this._elHdContainer = elHdContainer;
14572         elContainer.appendChild(elHdContainer);
14573     
14574         // Container for body TABLE
14575         var elBdContainer = document.createElement("div");
14576         elBdContainer.style.width = this.get("width") || "";
14577         elBdContainer.style.height = this.get("height") || "";
14578         Dom.addClass(elBdContainer, SDT.CLASS_BODY);
14579         Ev.addListener(elBdContainer, "scroll", this._onScroll, this); // to sync horiz scroll headers
14580         this._elBdContainer = elBdContainer;
14581         elContainer.appendChild(elBdContainer);
14582     }
14583 },
14584
14585 /**
14586  * Creates HTML markup CAPTION element.
14587  *
14588  * @method _initCaptionEl
14589  * @param sCaption {String} Text for caption.
14590  * @private
14591  */
14592 _initCaptionEl : function(sCaption) {
14593     // Not yet supported
14594     /*if(this._elHdTable && sCaption) {
14595         // Create CAPTION element
14596         if(!this._elCaption) { 
14597             this._elCaption = this._elHdTable.createCaption();
14598         }
14599         // Set CAPTION value
14600         this._elCaption.innerHTML = sCaption;
14601     }
14602     else if(this._elCaption) {
14603         this._elCaption.parentNode.removeChild(this._elCaption);
14604     }*/
14605 },
14606
14607 /**
14608  * Destroy's the DataTable head TABLE element, if available.
14609  *
14610  * @method _destroyHdTableEl
14611  * @private
14612  */
14613 _destroyHdTableEl : function() {
14614     var elTable = this._elHdTable;
14615     if(elTable) {
14616         Ev.purgeElement(elTable, true);
14617         elTable.parentNode.removeChild(elTable);
14618         
14619         // A little out of place, but where else can we null out these extra elements?
14620         ///this._elBdColgroup = null;
14621         this._elBdThead = null;
14622     }
14623 },
14624
14625 /**
14626  * Initializes ScrollingDataTable TABLE elements into the two inner containers.
14627  *
14628  * @method _initTableEl
14629  * @private
14630  */
14631 _initTableEl : function() {
14632     // Head TABLE
14633     if(this._elHdContainer) {
14634         this._destroyHdTableEl();
14635     
14636         // Create TABLE
14637         this._elHdTable = this._elHdContainer.appendChild(document.createElement("table"));   
14638     } 
14639     // Body TABLE
14640     SDT.superclass._initTableEl.call(this, this._elBdContainer);
14641 },
14642
14643 /**
14644  * Initializes ScrollingDataTable THEAD elements into the two inner containers.
14645  *
14646  * @method _initTheadEl
14647  * @param elHdTable {HTMLElement} (optional) Fixed header TABLE element reference.
14648  * @param elTable {HTMLElement} (optional) TABLE element reference.
14649  * @private
14650  */
14651 _initTheadEl : function(elHdTable, elTable) {
14652     elHdTable = elHdTable || this._elHdTable;
14653     elTable = elTable || this._elTable;
14654     
14655     // Scrolling body's THEAD
14656     this._initBdTheadEl(elTable);
14657     // Standard fixed head THEAD
14658     SDT.superclass._initTheadEl.call(this, elHdTable);
14659 },
14660
14661 /**
14662  * SDT changes ID so as not to duplicate the accessibility TH IDs.
14663  *
14664  * @method _initThEl
14665  * @param elTh {HTMLElement} TH element reference.
14666  * @param oColumn {YAHOO.widget.Column} Column object.
14667  * @private
14668  */
14669 _initThEl : function(elTh, oColumn) {
14670     SDT.superclass._initThEl.call(this, elTh, oColumn);
14671     elTh.id = this.getId() +"-fixedth-" + oColumn.getSanitizedKey(); // Needed for getColumn by TH and ColumnDD
14672 },
14673
14674 /**
14675  * Destroy's the DataTable body THEAD element, if available.
14676  *
14677  * @method _destroyBdTheadEl
14678  * @private
14679  */
14680 _destroyBdTheadEl : function() {
14681     var elBdThead = this._elBdThead;
14682     if(elBdThead) {
14683         var elTable = elBdThead.parentNode;
14684         Ev.purgeElement(elBdThead, true);
14685         elTable.removeChild(elBdThead);
14686         this._elBdThead = null;
14687
14688         this._destroyColumnHelpers();
14689     }
14690 },
14691
14692 /**
14693  * Initializes body THEAD element.
14694  *
14695  * @method _initBdTheadEl
14696  * @param elTable {HTMLElement} TABLE element into which to create THEAD.
14697  * @return {HTMLElement} Initialized THEAD element. 
14698  * @private
14699  */
14700 _initBdTheadEl : function(elTable) {
14701     if(elTable) {
14702         // Destroy previous
14703         this._destroyBdTheadEl();
14704
14705         var elThead = elTable.insertBefore(document.createElement("thead"), elTable.firstChild);
14706         
14707         // Add TRs to the THEAD;
14708         var oColumnSet = this._oColumnSet,
14709             colTree = oColumnSet.tree,
14710             elTh, elTheadTr, oColumn, i, j, k, len;
14711
14712         for(i=0, k=colTree.length; i<k; i++) {
14713             elTheadTr = elThead.appendChild(document.createElement("tr"));
14714     
14715             // ...and create TH cells
14716             for(j=0, len=colTree[i].length; j<len; j++) {
14717                 oColumn = colTree[i][j];
14718                 elTh = elTheadTr.appendChild(document.createElement("th"));
14719                 this._initBdThEl(elTh,oColumn,i,j);
14720             }
14721         }
14722         this._elBdThead = elThead;
14723         YAHOO.log("Accessibility TH cells for " + this._oColumnSet.keys.length + " keys created","info",this.toString());
14724     }
14725 },
14726
14727 /**
14728  * Populates TH element for the body THEAD element.
14729  *
14730  * @method _initBdThEl
14731  * @param elTh {HTMLElement} TH element reference.
14732  * @param oColumn {YAHOO.widget.Column} Column object.
14733  * @private
14734  */
14735 _initBdThEl : function(elTh, oColumn) {
14736     elTh.id = this.getId()+"-th-" + oColumn.getSanitizedKey(); // Needed for accessibility
14737     elTh.rowSpan = oColumn.getRowspan();
14738     elTh.colSpan = oColumn.getColspan();
14739     // Assign abbr attribute
14740     if(oColumn.abbr) {
14741         elTh.abbr = oColumn.abbr;
14742     }
14743
14744     // TODO: strip links and form elements
14745     var sKey = oColumn.getKey();
14746     var sLabel = lang.isValue(oColumn.label) ? oColumn.label : sKey;
14747     elTh.innerHTML = sLabel;
14748 },
14749
14750 /**
14751  * Initializes ScrollingDataTable TBODY element for data
14752  *
14753  * @method _initTbodyEl
14754  * @param elTable {HTMLElement} TABLE element into which to create TBODY .
14755  * @private
14756  */
14757 _initTbodyEl : function(elTable) {
14758     SDT.superclass._initTbodyEl.call(this, elTable);
14759     
14760     // Bug 2105534 - Safari 3 gap
14761     // Bug 2492591 - IE8 offsetTop
14762     elTable.style.marginTop = (this._elTbody.offsetTop > 0) ?
14763             "-"+this._elTbody.offsetTop+"px" : 0;
14764 },
14765
14766
14767
14768
14769
14770
14771
14772
14773
14774
14775
14776
14777
14778
14779
14780
14781
14782
14783
14784
14785
14786
14787
14788
14789
14790
14791
14792
14793
14794 /**
14795  * Sets focus on the given element.
14796  *
14797  * @method _focusEl
14798  * @param el {HTMLElement} Element.
14799  * @private
14800  */
14801 _focusEl : function(el) {
14802     el = el || this._elTbody;
14803     var oSelf = this;
14804     this._storeScrollPositions();
14805     // http://developer.mozilla.org/en/docs/index.php?title=Key-navigable_custom_DHTML_widgets
14806     // The timeout is necessary in both IE and Firefox 1.5, to prevent scripts from doing
14807     // strange unexpected things as the user clicks on buttons and other controls.
14808     
14809     // Bug 1921135: Wrap the whole thing in a setTimeout
14810     setTimeout(function() {
14811         setTimeout(function() {
14812             try {
14813                 el.focus();
14814                 oSelf._restoreScrollPositions();
14815             }
14816             catch(e) {
14817             }
14818         },0);
14819     }, 0);
14820 },
14821
14822
14823
14824
14825
14826
14827
14828
14829
14830
14831
14832
14833
14834
14835
14836
14837
14838
14839
14840 /**
14841  * Internal wrapper calls run() on render Chain instance.
14842  *
14843  * @method _runRenderChain
14844  * @private 
14845  */
14846 _runRenderChain : function() {
14847     this._storeScrollPositions();
14848     this._oChainRender.run();
14849 },
14850
14851 /**
14852  * Stores scroll positions so they can be restored after a render.
14853  *
14854  * @method _storeScrollPositions
14855  * @private
14856  */
14857  _storeScrollPositions : function() {
14858     this._nScrollTop = this._elBdContainer.scrollTop;
14859     this._nScrollLeft = this._elBdContainer.scrollLeft;
14860 },
14861
14862 /**
14863  * Clears stored scroll positions to interrupt the automatic restore mechanism.
14864  * Useful for setting scroll positions programmatically rather than as part of
14865  * the post-render cleanup process.
14866  *
14867  * @method clearScrollPositions
14868  * @private
14869  */
14870  clearScrollPositions : function() {
14871     this._nScrollTop = 0;
14872     this._nScrollLeft = 0;
14873 },
14874
14875 /**
14876  * Restores scroll positions to stored value. 
14877  *
14878  * @method _retoreScrollPositions
14879  * @private 
14880  */
14881  _restoreScrollPositions : function() {
14882     // Reset scroll positions
14883     if(this._nScrollTop) {
14884         this._elBdContainer.scrollTop = this._nScrollTop;
14885         this._nScrollTop = null;
14886     } 
14887     if(this._nScrollLeft) {
14888         this._elBdContainer.scrollLeft = this._nScrollLeft;
14889         this._nScrollLeft = null;
14890     } 
14891 },
14892
14893 /**
14894  * Helper function calculates and sets a validated width for a Column in a ScrollingDataTable.
14895  *
14896  * @method _validateColumnWidth
14897  * @param oColumn {YAHOO.widget.Column} Column instance.
14898  * @param elTd {HTMLElement} TD element to validate against.
14899  * @private
14900  */
14901 _validateColumnWidth : function(oColumn, elTd) {
14902     // Only Columns without widths that are not hidden
14903     if(!oColumn.width && !oColumn.hidden) {
14904         var elTh = oColumn.getThEl();
14905         // Unset a calculated auto-width
14906         if(oColumn._calculatedWidth) {
14907             this._setColumnWidth(oColumn, "auto", "visible");
14908         }
14909         // Compare auto-widths
14910         if(elTh.offsetWidth !== elTd.offsetWidth) {
14911             var elWider = (elTh.offsetWidth > elTd.offsetWidth) ?
14912                     oColumn.getThLinerEl() : elTd.firstChild;               
14913
14914             // Grab the wider liner width, unless the minWidth is wider
14915             var newWidth = Math.max(0,
14916                 (elWider.offsetWidth -(parseInt(Dom.getStyle(elWider,"paddingLeft"),10)|0) - (parseInt(Dom.getStyle(elWider,"paddingRight"),10)|0)),
14917                 oColumn.minWidth);
14918                 
14919             var sOverflow = 'visible';
14920             
14921             // Now validate against maxAutoWidth
14922             if((oColumn.maxAutoWidth > 0) && (newWidth > oColumn.maxAutoWidth)) {
14923                 newWidth = oColumn.maxAutoWidth;
14924                 sOverflow = "hidden";
14925             }
14926
14927             // Set to the wider auto-width
14928             this._elTbody.style.display = "none";
14929             this._setColumnWidth(oColumn, newWidth+'px', sOverflow);
14930             oColumn._calculatedWidth = newWidth;
14931             this._elTbody.style.display = "";
14932         }
14933     }
14934 },
14935
14936 /**
14937  * For one or all Columns of a ScrollingDataTable, when Column is not hidden,
14938  * and width is not set, syncs widths of header and body cells and 
14939  * validates that width against minWidth and/or maxAutoWidth as necessary.
14940  *
14941  * @method validateColumnWidths
14942  * @param oArg.column {YAHOO.widget.Column} (optional) One Column to validate. If null, all Columns' widths are validated.
14943  */
14944 validateColumnWidths : function(oColumn) {
14945     // Validate there is at least one TR with proper TDs
14946     var allKeys   = this._oColumnSet.keys,
14947         allKeysLength = allKeys.length,
14948         elRow     = this.getFirstTrEl();
14949
14950     // Reset overhang for IE
14951     if(ua.ie) {
14952         this._setOverhangValue(1);
14953     }
14954
14955     if(allKeys && elRow && (elRow.childNodes.length === allKeysLength)) {
14956         // Temporarily unsnap container since it causes inaccurate calculations
14957         var sWidth = this.get("width");
14958         if(sWidth) {
14959             this._elHdContainer.style.width = "";
14960             this._elBdContainer.style.width = "";
14961         }
14962         this._elContainer.style.width = "";
14963         
14964         //Validate just one Column
14965         if(oColumn && lang.isNumber(oColumn.getKeyIndex())) {
14966             this._validateColumnWidth(oColumn, elRow.childNodes[oColumn.getKeyIndex()]);
14967         }
14968         // Iterate through all Columns to unset calculated widths in one pass
14969         else {
14970             var elTd, todos = [], thisTodo, i, len;
14971             for(i=0; i<allKeysLength; i++) {
14972                 oColumn = allKeys[i];
14973                 // Only Columns without widths that are not hidden, unset a calculated auto-width
14974                 if(!oColumn.width && !oColumn.hidden && oColumn._calculatedWidth) {
14975                     todos[todos.length] = oColumn;      
14976                 }
14977             }
14978             
14979             this._elTbody.style.display = "none";
14980             for(i=0, len=todos.length; i<len; i++) {
14981                 this._setColumnWidth(todos[i], "auto", "visible");
14982             }
14983             this._elTbody.style.display = "";
14984             
14985             todos = [];
14986
14987             // Iterate through all Columns and make the store the adjustments to make in one pass
14988             for(i=0; i<allKeysLength; i++) {
14989                 oColumn = allKeys[i];
14990                 elTd = elRow.childNodes[i];
14991                 // Only Columns without widths that are not hidden
14992                 if(!oColumn.width && !oColumn.hidden) {
14993                     var elTh = oColumn.getThEl();
14994
14995                     // Compare auto-widths
14996                     if(elTh.offsetWidth !== elTd.offsetWidth) {
14997                         var elWider = (elTh.offsetWidth > elTd.offsetWidth) ?
14998                                 oColumn.getThLinerEl() : elTd.firstChild;               
14999                 
15000                         // Grab the wider liner width, unless the minWidth is wider
15001                         var newWidth = Math.max(0,
15002                             (elWider.offsetWidth -(parseInt(Dom.getStyle(elWider,"paddingLeft"),10)|0) - (parseInt(Dom.getStyle(elWider,"paddingRight"),10)|0)),
15003                             oColumn.minWidth);
15004                             
15005                         var sOverflow = 'visible';
15006                         
15007                         // Now validate against maxAutoWidth
15008                         if((oColumn.maxAutoWidth > 0) && (newWidth > oColumn.maxAutoWidth)) {
15009                             newWidth = oColumn.maxAutoWidth;
15010                             sOverflow = "hidden";
15011                         }
15012                 
15013                         todos[todos.length] = [oColumn, newWidth, sOverflow];
15014                     }
15015                 }
15016             }
15017             
15018             this._elTbody.style.display = "none";
15019             for(i=0, len=todos.length; i<len; i++) {
15020                 thisTodo = todos[i];
15021                 // Set to the wider auto-width
15022                 this._setColumnWidth(thisTodo[0], thisTodo[1]+"px", thisTodo[2]);
15023                 thisTodo[0]._calculatedWidth = thisTodo[1];
15024             }
15025             this._elTbody.style.display = "";
15026         }
15027     
15028         // Resnap unsnapped containers
15029         if(sWidth) {
15030             this._elHdContainer.style.width = sWidth;
15031             this._elBdContainer.style.width = sWidth;
15032         } 
15033     }
15034     
15035     this._syncScroll();
15036     this._restoreScrollPositions();
15037 },
15038
15039 /**
15040  * Syncs padding around scrollable tables, including Column header right-padding
15041  * and container width and height.
15042  *
15043  * @method _syncScroll
15044  * @private 
15045  */
15046 _syncScroll : function() {
15047     this._syncScrollX();
15048     this._syncScrollY();
15049     this._syncScrollOverhang();
15050     if(ua.opera) {
15051         // Bug 1925874
15052         this._elHdContainer.scrollLeft = this._elBdContainer.scrollLeft;
15053         if(!this.get("width")) {
15054             // Bug 1926125
15055             document.body.style += '';
15056         }
15057     }
15058  },
15059
15060 /**
15061  * Snaps container width for y-scrolling tables.
15062  *
15063  * @method _syncScrollY
15064  * @private
15065  */
15066 _syncScrollY : function() {
15067     var elTbody = this._elTbody,
15068         elBdContainer = this._elBdContainer;
15069     
15070     // X-scrolling not enabled
15071     if(!this.get("width")) {
15072         // Snap outer container width to content
15073         this._elContainer.style.width = 
15074                 (elBdContainer.scrollHeight > elBdContainer.clientHeight) ?
15075                 // but account for y-scrollbar since it is visible
15076                 (elTbody.parentNode.clientWidth + 19) + "px" :
15077                 // no y-scrollbar, just borders
15078                 (elTbody.parentNode.clientWidth + 2) + "px";
15079     }
15080 },
15081
15082 /**
15083  * Snaps container height for x-scrolling tables in IE. Syncs message TBODY width.
15084  *
15085  * @method _syncScrollX
15086  * @private
15087  */
15088 _syncScrollX : function() {
15089     var elTbody = this._elTbody,
15090         elBdContainer = this._elBdContainer;
15091     
15092     // IE 6 and 7 only when y-scrolling not enabled
15093     if(!this.get("height") && (ua.ie)) {
15094         // Snap outer container height to content
15095         elBdContainer.style.height = 
15096                 // but account for x-scrollbar if it is visible
15097                 (elBdContainer.scrollWidth > elBdContainer.offsetWidth ) ?
15098                 (elTbody.parentNode.offsetHeight + 18) + "px" : 
15099                 elTbody.parentNode.offsetHeight + "px";
15100     }
15101
15102     // Sync message tbody
15103     if(this._elTbody.rows.length === 0) {
15104         this._elMsgTbody.parentNode.style.width = this.getTheadEl().parentNode.offsetWidth + "px";
15105     }
15106     else {
15107         this._elMsgTbody.parentNode.style.width = "";
15108     }
15109 },
15110
15111 /**
15112  * Adds/removes Column header overhang as necesary.
15113  *
15114  * @method _syncScrollOverhang
15115  * @private
15116  */
15117 _syncScrollOverhang : function() {
15118     var elBdContainer = this._elBdContainer,
15119         // Overhang should be either 1 (default) or 18px, depending on the location of the right edge of the table
15120         nPadding = 1;
15121     
15122     // Y-scrollbar is visible, which is when the overhang needs to jut out
15123     if((elBdContainer.scrollHeight > elBdContainer.clientHeight) &&
15124         // X-scrollbar is also visible, which means the right is jagged, not flush with the Column
15125         (elBdContainer.scrollWidth > elBdContainer.clientWidth)) {
15126         nPadding = 18;
15127     }
15128     
15129     this._setOverhangValue(nPadding);
15130     
15131 },
15132
15133 /**
15134  * Sets Column header overhang to given width.
15135  *
15136  * @method _setOverhangValue
15137  * @param nBorderWidth {Number} Value of new border for overhang. 
15138  * @private
15139  */
15140 _setOverhangValue : function(nBorderWidth) {
15141     var aLastHeaders = this._oColumnSet.headers[this._oColumnSet.headers.length-1] || [],
15142         len = aLastHeaders.length,
15143         sPrefix = this._sId+"-fixedth-",
15144         sValue = nBorderWidth + "px solid " + this.get("COLOR_COLUMNFILLER");
15145
15146     this._elThead.style.display = "none";
15147     for(var i=0; i<len; i++) {
15148         Dom.get(sPrefix+aLastHeaders[i]).style.borderRight = sValue;
15149     }
15150     this._elThead.style.display = "";
15151 },
15152
15153
15154
15155
15156
15157
15158
15159
15160
15161
15162
15163
15164
15165
15166
15167
15168
15169
15170
15171
15172
15173
15174
15175
15176
15177
15178
15179
15180
15181
15182
15183
15184
15185
15186
15187
15188
15189
15190 /**
15191  * Returns DOM reference to the DataTable's fixed header container element.
15192  *
15193  * @method getHdContainerEl
15194  * @return {HTMLElement} Reference to DIV element.
15195  */
15196 getHdContainerEl : function() {
15197     return this._elHdContainer;
15198 },
15199
15200 /**
15201  * Returns DOM reference to the DataTable's scrolling body container element.
15202  *
15203  * @method getBdContainerEl
15204  * @return {HTMLElement} Reference to DIV element.
15205  */
15206 getBdContainerEl : function() {
15207     return this._elBdContainer;
15208 },
15209
15210 /**
15211  * Returns DOM reference to the DataTable's fixed header TABLE element.
15212  *
15213  * @method getHdTableEl
15214  * @return {HTMLElement} Reference to TABLE element.
15215  */
15216 getHdTableEl : function() {
15217     return this._elHdTable;
15218 },
15219
15220 /**
15221  * Returns DOM reference to the DataTable's scrolling body TABLE element.
15222  *
15223  * @method getBdTableEl
15224  * @return {HTMLElement} Reference to TABLE element.
15225  */
15226 getBdTableEl : function() {
15227     return this._elTable;
15228 },
15229
15230 /**
15231  * Disables ScrollingDataTable UI.
15232  *
15233  * @method disable
15234  */
15235 disable : function() {
15236     var elMask = this._elMask;
15237     elMask.style.width = this._elBdContainer.offsetWidth + "px";
15238     elMask.style.height = this._elHdContainer.offsetHeight + this._elBdContainer.offsetHeight + "px";
15239     elMask.style.display = "";
15240     this.fireEvent("disableEvent");
15241 },
15242
15243 /**
15244  * Removes given Column. NOTE: You cannot remove nested Columns. You can only remove
15245  * non-nested Columns, and top-level parent Columns (which will remove all
15246  * children Columns).
15247  *
15248  * @method removeColumn
15249  * @param oColumn {YAHOO.widget.Column} Column instance.
15250  * @return oColumn {YAHOO.widget.Column} Removed Column instance.
15251  */
15252 removeColumn : function(oColumn) {
15253     // Store scroll pos
15254     var hdPos = this._elHdContainer.scrollLeft;
15255     var bdPos = this._elBdContainer.scrollLeft;
15256     
15257     // Call superclass method
15258     oColumn = SDT.superclass.removeColumn.call(this, oColumn);
15259     
15260     // Restore scroll pos
15261     this._elHdContainer.scrollLeft = hdPos;
15262     this._elBdContainer.scrollLeft = bdPos;
15263     
15264     return oColumn;
15265 },
15266
15267 /**
15268  * Inserts given Column at the index if given, otherwise at the end. NOTE: You
15269  * can only add non-nested Columns and top-level parent Columns. You cannot add
15270  * a nested Column to an existing parent.
15271  *
15272  * @method insertColumn
15273  * @param oColumn {Object | YAHOO.widget.Column} Object literal Column
15274  * definition or a Column instance.
15275  * @param index {Number} (optional) New tree index.
15276  * @return oColumn {YAHOO.widget.Column} Inserted Column instance. 
15277  */
15278 insertColumn : function(oColumn, index) {
15279     // Store scroll pos
15280     var hdPos = this._elHdContainer.scrollLeft;
15281     var bdPos = this._elBdContainer.scrollLeft;
15282     
15283     // Call superclass method
15284     var oNewColumn = SDT.superclass.insertColumn.call(this, oColumn, index);
15285     
15286     // Restore scroll pos
15287     this._elHdContainer.scrollLeft = hdPos;
15288     this._elBdContainer.scrollLeft = bdPos;
15289     
15290     return oNewColumn;
15291 },
15292
15293 /**
15294  * Removes given Column and inserts into given tree index. NOTE: You
15295  * can only reorder non-nested Columns and top-level parent Columns. You cannot
15296  * reorder a nested Column to an existing parent.
15297  *
15298  * @method reorderColumn
15299  * @param oColumn {YAHOO.widget.Column} Column instance.
15300  * @param index {Number} New tree index.
15301  */
15302 reorderColumn : function(oColumn, index) {
15303     // Store scroll pos
15304     var hdPos = this._elHdContainer.scrollLeft;
15305     var bdPos = this._elBdContainer.scrollLeft;
15306     
15307     // Call superclass method
15308     var oNewColumn = SDT.superclass.reorderColumn.call(this, oColumn, index);
15309     
15310     // Restore scroll pos
15311     this._elHdContainer.scrollLeft = hdPos;
15312     this._elBdContainer.scrollLeft = bdPos;
15313
15314     return oNewColumn;
15315 },
15316
15317 /**
15318  * Sets given Column to given pixel width. If new width is less than minWidth
15319  * width, sets to minWidth. Updates oColumn.width value.
15320  *
15321  * @method setColumnWidth
15322  * @param oColumn {YAHOO.widget.Column} Column instance.
15323  * @param nWidth {Number} New width in pixels.
15324  */
15325 setColumnWidth : function(oColumn, nWidth) {
15326     oColumn = this.getColumn(oColumn);
15327     if(oColumn) {
15328         this._storeScrollPositions();
15329
15330         // Validate new width against minWidth
15331         if(lang.isNumber(nWidth)) {
15332             nWidth = (nWidth > oColumn.minWidth) ? nWidth : oColumn.minWidth;
15333
15334             // Save state
15335             oColumn.width = nWidth;
15336             
15337             // Resize the DOM elements
15338             this._setColumnWidth(oColumn, nWidth+"px");
15339             this._syncScroll();
15340             
15341             this.fireEvent("columnSetWidthEvent",{column:oColumn,width:nWidth});
15342             YAHOO.log("Set width of Column " + oColumn + " to " + nWidth + "px", "info", this.toString());
15343         }
15344         // Unsets a width to auto-size
15345         else if(nWidth === null) {
15346             // Save state
15347             oColumn.width = nWidth;
15348             
15349             // Resize the DOM elements
15350             this._setColumnWidth(oColumn, "auto");
15351             this.validateColumnWidths(oColumn);
15352             this.fireEvent("columnUnsetWidthEvent",{column:oColumn});
15353             YAHOO.log("Column " + oColumn + " width unset", "info", this.toString());
15354         }
15355         
15356         // Bug 2339454: resize then sort misaligment
15357         this._clearTrTemplateEl();
15358     }
15359     else {
15360         YAHOO.log("Could not set width of Column " + oColumn + " to " + nWidth + "px", "warn", this.toString());
15361     }
15362 },
15363
15364 /**
15365  * Scrolls to given row or cell
15366  *
15367  * @method scrollTo
15368  * @param to {YAHOO.widget.Record | HTMLElement } Itme to scroll to.
15369  */
15370 scrollTo : function(to) {
15371         var td = this.getTdEl(to);
15372         if(td) {
15373             this.clearScrollPositions();
15374             this.getBdContainerEl().scrollLeft = td.offsetLeft;
15375             this.getBdContainerEl().scrollTop = td.parentNode.offsetTop;
15376         }
15377         else {
15378             var tr = this.getTrEl(to);
15379             if(tr) {
15380                 this.clearScrollPositions();
15381                 this.getBdContainerEl().scrollTop = tr.offsetTop;
15382             }
15383         }
15384 },
15385
15386 /**
15387  * Displays message within secondary TBODY.
15388  *
15389  * @method showTableMessage
15390  * @param sHTML {String} (optional) Value for innerHTMlang.
15391  * @param sClassName {String} (optional) Classname.
15392  */
15393 showTableMessage : function(sHTML, sClassName) {
15394     var elCell = this._elMsgTd;
15395     if(lang.isString(sHTML)) {
15396         elCell.firstChild.innerHTML = sHTML;
15397     }
15398     if(lang.isString(sClassName)) {
15399         Dom.addClass(elCell.firstChild, sClassName);
15400     }
15401
15402     // Needed for SDT only
15403     var elThead = this.getTheadEl();
15404     var elTable = elThead.parentNode;
15405     var newWidth = elTable.offsetWidth;
15406     this._elMsgTbody.parentNode.style.width = this.getTheadEl().parentNode.offsetWidth + "px";
15407
15408     this._elMsgTbody.style.display = "";
15409
15410     this.fireEvent("tableMsgShowEvent", {html:sHTML, className:sClassName});
15411     YAHOO.log("DataTable showing message: " + sHTML, "info", this.toString());
15412 },
15413
15414
15415
15416
15417
15418
15419
15420
15421
15422
15423
15424
15425
15426 /////////////////////////////////////////////////////////////////////////////
15427 //
15428 // Private Custom Event Handlers
15429 //
15430 /////////////////////////////////////////////////////////////////////////////
15431
15432 /**
15433  * Handles Column mutations
15434  *
15435  * @method onColumnChange
15436  * @param oArgs {Object} Custom Event data.
15437  */
15438 _onColumnChange : function(oArg) {
15439     // Figure out which Column changed
15440     var oColumn = (oArg.column) ? oArg.column :
15441             (oArg.editor) ? oArg.editor.column : null;
15442     this._storeScrollPositions();
15443     this.validateColumnWidths(oColumn);
15444 },
15445
15446
15447
15448
15449
15450
15451
15452
15453
15454
15455
15456
15457
15458
15459
15460 /////////////////////////////////////////////////////////////////////////////
15461 //
15462 // Private DOM Event Handlers
15463 //
15464 /////////////////////////////////////////////////////////////////////////////
15465
15466 /**
15467  * Syncs scrolltop and scrollleft of all TABLEs.
15468  *
15469  * @method _onScroll
15470  * @param e {HTMLEvent} The scroll event.
15471  * @param oSelf {YAHOO.widget.ScrollingDataTable} ScrollingDataTable instance.
15472  * @private
15473  */
15474 _onScroll : function(e, oSelf) {
15475     oSelf._elHdContainer.scrollLeft = oSelf._elBdContainer.scrollLeft;
15476
15477     if(oSelf._oCellEditor && oSelf._oCellEditor.isActive) {
15478         oSelf.fireEvent("editorBlurEvent", {editor:oSelf._oCellEditor});
15479         oSelf.cancelCellEditor();
15480     }
15481
15482     var elTarget = Ev.getTarget(e);
15483     var elTag = elTarget.nodeName.toLowerCase();
15484     oSelf.fireEvent("tableScrollEvent", {event:e, target:elTarget});
15485 },
15486
15487 /**
15488  * Handles keydown events on the THEAD element.
15489  *
15490  * @method _onTheadKeydown
15491  * @param e {HTMLEvent} The key event.
15492  * @param oSelf {YAHOO.widget.ScrollingDataTable} ScrollingDataTable instance.
15493  * @private
15494  */
15495 _onTheadKeydown : function(e, oSelf) {
15496     // If tabbing to next TH label link causes THEAD to scroll,
15497     // need to sync scrollLeft with TBODY
15498     if(Ev.getCharCode(e) === 9) {
15499         setTimeout(function() {
15500             if((oSelf instanceof SDT) && oSelf._sId) {
15501                 oSelf._elBdContainer.scrollLeft = oSelf._elHdContainer.scrollLeft;
15502             }
15503         },0);
15504     }
15505     
15506     var elTarget = Ev.getTarget(e);
15507     var elTag = elTarget.nodeName.toLowerCase();
15508     var bKeepBubbling = true;
15509     while(elTarget && (elTag != "table")) {
15510         switch(elTag) {
15511             case "body":
15512                 return;
15513             case "input":
15514             case "textarea":
15515                 // TODO: implement textareaKeyEvent
15516                 break;
15517             case "thead":
15518                 bKeepBubbling = oSelf.fireEvent("theadKeyEvent",{target:elTarget,event:e});
15519                 break;
15520             default:
15521                 break;
15522         }
15523         if(bKeepBubbling === false) {
15524             return;
15525         }
15526         else {
15527             elTarget = elTarget.parentNode;
15528             if(elTarget) {
15529                 elTag = elTarget.nodeName.toLowerCase();
15530             }
15531         }
15532     }
15533     oSelf.fireEvent("tableKeyEvent",{target:(elTarget || oSelf._elContainer),event:e});
15534 }
15535
15536
15537
15538
15539 /**
15540  * Fired when a fixed scrolling DataTable has a scroll.
15541  *
15542  * @event tableScrollEvent
15543  * @param oArgs.event {HTMLEvent} The event object.
15544  * @param oArgs.target {HTMLElement} The DataTable's CONTAINER element (in IE)
15545  * or the DataTable's TBODY element (everyone else).
15546  *
15547  */
15548
15549
15550
15551
15552 });
15553
15554 })();
15555
15556 (function () {
15557
15558 var lang   = YAHOO.lang,
15559     util   = YAHOO.util,
15560     widget = YAHOO.widget,
15561     ua     = YAHOO.env.ua,
15562     
15563     Dom    = util.Dom,
15564     Ev     = util.Event,
15565     
15566     DT     = widget.DataTable;
15567 /****************************************************************************/
15568 /****************************************************************************/
15569 /****************************************************************************/
15570     
15571 /**
15572  * The BaseCellEditor class provides base functionality common to all inline cell
15573  * editors for a DataTable widget.
15574  *
15575  * @namespace YAHOO.widget
15576  * @class BaseCellEditor
15577  * @uses YAHOO.util.EventProvider 
15578  * @constructor
15579  * @param sType {String} Type indicator, to map to YAHOO.widget.DataTable.Editors.
15580  * @param oConfigs {Object} (Optional) Object literal of configs.
15581  */
15582 widget.BaseCellEditor = function(sType, oConfigs) {
15583     this._sId = this._sId || "yui-ceditor" + YAHOO.widget.BaseCellEditor._nCount++;
15584     this._sType = sType;
15585     
15586     // Validate inputs
15587     this._initConfigs(oConfigs); 
15588     
15589     // Create Custom Events
15590     this._initEvents();
15591              
15592     // Draw UI
15593     this.render();
15594 };
15595
15596 var BCE = widget.BaseCellEditor;
15597
15598 /////////////////////////////////////////////////////////////////////////////
15599 //
15600 // Static members
15601 //
15602 /////////////////////////////////////////////////////////////////////////////
15603 lang.augmentObject(BCE, {
15604
15605 /**
15606  * Global instance counter.
15607  *
15608  * @property CellEditor._nCount
15609  * @type Number
15610  * @static
15611  * @default 0
15612  * @private 
15613  */
15614 _nCount : 0,
15615
15616 /**
15617  * Class applied to CellEditor container.
15618  *
15619  * @property CellEditor.CLASS_CELLEDITOR
15620  * @type String
15621  * @static
15622  * @default "yui-ceditor"
15623  */
15624 CLASS_CELLEDITOR : "yui-ceditor"
15625
15626 });
15627
15628 BCE.prototype = {
15629 /////////////////////////////////////////////////////////////////////////////
15630 //
15631 // Private members
15632 //
15633 /////////////////////////////////////////////////////////////////////////////
15634 /**
15635  * Unique id assigned to instance "yui-ceditorN", useful prefix for generating unique
15636  * DOM ID strings and log messages.
15637  *
15638  * @property _sId
15639  * @type String
15640  * @private
15641  */
15642 _sId : null,
15643
15644 /**
15645  * Editor type.
15646  *
15647  * @property _sType
15648  * @type String
15649  * @private
15650  */
15651 _sType : null,
15652
15653 /**
15654  * DataTable instance.
15655  *
15656  * @property _oDataTable
15657  * @type YAHOO.widget.DataTable
15658  * @private 
15659  */
15660 _oDataTable : null,
15661
15662 /**
15663  * Column instance.
15664  *
15665  * @property _oColumn
15666  * @type YAHOO.widget.Column
15667  * @default null
15668  * @private 
15669  */
15670 _oColumn : null,
15671
15672 /**
15673  * Record instance.
15674  *
15675  * @property _oRecord
15676  * @type YAHOO.widget.Record
15677  * @default null
15678  * @private 
15679  */
15680 _oRecord : null,
15681
15682 /**
15683  * TD element.
15684  *
15685  * @property _elTd
15686  * @type HTMLElement
15687  * @default null
15688  * @private
15689  */
15690 _elTd : null,
15691
15692 /**
15693  * Container for inline editor.
15694  *
15695  * @property _elContainer
15696  * @type HTMLElement
15697  * @private 
15698  */
15699 _elContainer : null,
15700
15701 /**
15702  * Reference to Cancel button, if available.
15703  *
15704  * @property _elCancelBtn
15705  * @type HTMLElement
15706  * @default null
15707  * @private 
15708  */
15709 _elCancelBtn : null,
15710
15711 /**
15712  * Reference to Save button, if available.
15713  *
15714  * @property _elSaveBtn
15715  * @type HTMLElement
15716  * @default null
15717  * @private 
15718  */
15719 _elSaveBtn : null,
15720
15721
15722
15723
15724
15725
15726
15727
15728 /////////////////////////////////////////////////////////////////////////////
15729 //
15730 // Private methods
15731 //
15732 /////////////////////////////////////////////////////////////////////////////
15733
15734 /**
15735  * Initialize configs.
15736  *
15737  * @method _initConfigs
15738  * @private   
15739  */
15740 _initConfigs : function(oConfigs) {
15741     // Object literal defines CellEditor configs
15742     if(oConfigs && YAHOO.lang.isObject(oConfigs)) {
15743         for(var sConfig in oConfigs) {
15744             if(sConfig) {
15745                 this[sConfig] = oConfigs[sConfig];
15746             }
15747         }
15748     }
15749 },
15750
15751 /**
15752  * Initialize Custom Events.
15753  *
15754  * @method _initEvents
15755  * @private   
15756  */
15757 _initEvents : function() {
15758     this.createEvent("showEvent");
15759     this.createEvent("keydownEvent");
15760     this.createEvent("invalidDataEvent");
15761     this.createEvent("revertEvent");
15762     this.createEvent("saveEvent");
15763     this.createEvent("cancelEvent");
15764     this.createEvent("blurEvent");
15765     this.createEvent("blockEvent");
15766     this.createEvent("unblockEvent");
15767 },
15768
15769
15770
15771
15772
15773
15774
15775
15776
15777
15778
15779
15780
15781 /////////////////////////////////////////////////////////////////////////////
15782 //
15783 // Public properties
15784 //
15785 /////////////////////////////////////////////////////////////////////////////
15786 /**
15787  * Implementer defined function that can submit the input value to a server. This
15788  * function must accept the arguments fnCallback and oNewValue. When the submission
15789  * is complete, the function must also call fnCallback(bSuccess, oNewValue) to 
15790  * finish the save routine in the CellEditor. This function can also be used to 
15791  * perform extra validation or input value manipulation. 
15792  *
15793  * @property asyncSubmitter
15794  * @type HTMLFunction
15795  */
15796 asyncSubmitter : null,
15797
15798 /**
15799  * Current value.
15800  *
15801  * @property value
15802  * @type MIXED
15803  */
15804 value : null,
15805
15806 /**
15807  * Default value in case Record data is undefined. NB: Null values will not trigger
15808  * the default value.
15809  *
15810  * @property defaultValue
15811  * @type MIXED
15812  * @default null
15813  */
15814 defaultValue : null,
15815
15816 /**
15817  * Validator function for input data, called from the DataTable instance scope,
15818  * receives the arguments (inputValue, currentValue, editorInstance) and returns
15819  * either the validated (or type-converted) value or undefined.
15820  *
15821  * @property validator
15822  * @type HTMLFunction
15823  * @default null
15824  */
15825 validator : null,
15826
15827 /**
15828  * If validation is enabled, resets input field of invalid data.
15829  *
15830  * @property resetInvalidData
15831  * @type Boolean
15832  * @default true
15833  */
15834 resetInvalidData : true,
15835
15836 /**
15837  * True if currently active.
15838  *
15839  * @property isActive
15840  * @type Boolean
15841  */
15842 isActive : false,
15843
15844 /**
15845  * Text to display on Save button.
15846  *
15847  * @property LABEL_SAVE
15848  * @type String
15849  * @default "Save"
15850  */
15851 LABEL_SAVE : "Save",
15852
15853 /**
15854  * Text to display on Cancel button.
15855  *
15856  * @property LABEL_CANCEL
15857  * @type String
15858  * @default "Cancel"
15859  */
15860 LABEL_CANCEL : "Cancel",
15861
15862 /**
15863  * True if Save/Cancel buttons should not be displayed in the CellEditor.
15864  *
15865  * @property disableBtns
15866  * @type Boolean
15867  * @default false
15868  */
15869 disableBtns : false,
15870
15871
15872
15873
15874
15875
15876
15877 /////////////////////////////////////////////////////////////////////////////
15878 //
15879 // Public methods
15880 //
15881 /////////////////////////////////////////////////////////////////////////////
15882 /**
15883  * CellEditor instance name, for logging.
15884  *
15885  * @method toString
15886  * @return {String} Unique name of the CellEditor instance.
15887  */
15888
15889 toString : function() {
15890     return "CellEditor instance " + this._sId;
15891 },
15892
15893 /**
15894  * CellEditor unique ID.
15895  *
15896  * @method getId
15897  * @return {String} Unique ID of the CellEditor instance.
15898  */
15899
15900 getId : function() {
15901     return this._sId;
15902 },
15903
15904 /**
15905  * Returns reference to associated DataTable instance.
15906  *
15907  * @method getDataTable
15908  * @return {YAHOO.widget.DataTable} DataTable instance.
15909  */
15910
15911 getDataTable : function() {
15912     return this._oDataTable;
15913 },
15914
15915 /**
15916  * Returns reference to associated Column instance.
15917  *
15918  * @method getColumn
15919  * @return {YAHOO.widget.Column} Column instance.
15920  */
15921
15922 getColumn : function() {
15923     return this._oColumn;
15924 },
15925
15926 /**
15927  * Returns reference to associated Record instance.
15928  *
15929  * @method getRecord
15930  * @return {YAHOO.widget.Record} Record instance.
15931  */
15932
15933 getRecord : function() {
15934     return this._oRecord;
15935 },
15936
15937
15938
15939 /**
15940  * Returns reference to associated TD element.
15941  *
15942  * @method getTdEl
15943  * @return {HTMLElement} TD element.
15944  */
15945
15946 getTdEl : function() {
15947     return this._elTd;
15948 },
15949
15950 /**
15951  * Returns container element.
15952  *
15953  * @method getContainerEl
15954  * @return {HTMLElement} Reference to container element.
15955  */
15956
15957 getContainerEl : function() {
15958     return this._elContainer;
15959 },
15960
15961 /**
15962  * Nulls out the entire CellEditor instance and related objects, removes attached
15963  * event listeners, and clears out DOM elements inside the container, removes
15964  * container from the DOM.
15965  *
15966  * @method destroy
15967  */
15968 destroy : function() {
15969     this.unsubscribeAll();
15970     
15971     // Column is late-binding in attach()
15972     var oColumn = this.getColumn();
15973     if(oColumn) {
15974         oColumn.editor = null;
15975     }
15976     
15977     var elContainer = this.getContainerEl();
15978     Ev.purgeElement(elContainer, true);
15979     elContainer.parentNode.removeChild(elContainer);
15980 },
15981
15982 /**
15983  * Renders DOM elements and attaches event listeners.
15984  *
15985  * @method render
15986  */
15987 render : function() {
15988     if(this._elContainer) {
15989         YAHOO.util.Event.purgeElement(this._elContainer, true);
15990         this._elContainer.innerHTML = "";
15991     }
15992
15993     // Render Cell Editor container element as first child of body
15994     var elContainer = document.createElement("div");
15995     elContainer.id = this.getId() + "-container"; // Needed for tracking blur event
15996     elContainer.style.display = "none";
15997     elContainer.tabIndex = 0;
15998     elContainer.className = DT.CLASS_EDITOR;
15999     document.body.insertBefore(elContainer, document.body.firstChild);
16000     this._elContainer = elContainer;
16001     
16002     // Handle ESC key
16003     Ev.addListener(elContainer, "keydown", function(e, oSelf) {
16004         // ESC cancels Cell Editor
16005         if((e.keyCode == 27)) {
16006             var target = Ev.getTarget(e);
16007             // workaround for Mac FF3 bug that disabled clicks when ESC hit when
16008             // select is open. [bug 2273056]
16009             if (target.nodeName && target.nodeName.toLowerCase() === 'select') {
16010                 target.blur();
16011             }
16012             oSelf.cancel();
16013         }
16014         // Pass through event
16015         oSelf.fireEvent("keydownEvent", {editor:this, event:e});
16016     }, this);
16017     
16018     this.renderForm();
16019
16020     // Show Save/Cancel buttons
16021     if(!this.disableBtns) {
16022         this.renderBtns();
16023     }
16024     
16025     this.doAfterRender();
16026 },
16027
16028 /**
16029  * Renders Save/Cancel buttons.
16030  *
16031  * @method renderBtns
16032  */
16033 renderBtns : function() {
16034     // Buttons
16035     var elBtnsDiv = this.getContainerEl().appendChild(document.createElement("div"));
16036     elBtnsDiv.className = DT.CLASS_BUTTON;
16037
16038     // Save button
16039     var elSaveBtn = elBtnsDiv.appendChild(document.createElement("button"));
16040     elSaveBtn.className = DT.CLASS_DEFAULT;
16041     elSaveBtn.innerHTML = this.LABEL_SAVE;
16042     Ev.addListener(elSaveBtn, "click", function(oArgs) {
16043         this.save();
16044     }, this, true);
16045     this._elSaveBtn = elSaveBtn;
16046
16047     // Cancel button
16048     var elCancelBtn = elBtnsDiv.appendChild(document.createElement("button"));
16049     elCancelBtn.innerHTML = this.LABEL_CANCEL;
16050     Ev.addListener(elCancelBtn, "click", function(oArgs) {
16051         this.cancel();
16052     }, this, true);
16053     this._elCancelBtn = elCancelBtn;
16054 },
16055
16056 /**
16057  * Attach CellEditor for a new interaction.
16058  *
16059  * @method attach
16060  * @param oDataTable {YAHOO.widget.DataTable} Associated DataTable instance.
16061  * @param elCell {HTMLElement} Cell to edit.  
16062  */
16063 attach : function(oDataTable, elCell) {
16064     // Validate 
16065     if(oDataTable instanceof YAHOO.widget.DataTable) {
16066         this._oDataTable = oDataTable;
16067         
16068         // Validate cell
16069         elCell = oDataTable.getTdEl(elCell);
16070         if(elCell) {
16071             this._elTd = elCell;
16072
16073             // Validate Column
16074             var oColumn = oDataTable.getColumn(elCell);
16075             if(oColumn) {
16076                 this._oColumn = oColumn;
16077                 
16078                 // Validate Record
16079                 var oRecord = oDataTable.getRecord(elCell);
16080                 if(oRecord) {
16081                     this._oRecord = oRecord;
16082                     var value = oRecord.getData(this.getColumn().getField());
16083                     this.value = (value !== undefined) ? value : this.defaultValue;
16084                     return true;
16085                 }
16086             }            
16087         }
16088     }
16089     YAHOO.log("Could not attach CellEditor","error",this.toString());
16090     return false;
16091 },
16092
16093 /**
16094  * Moves container into position for display.
16095  *
16096  * @method move
16097  */
16098 move : function() {
16099     // Move Editor
16100     var elContainer = this.getContainerEl(),
16101         elTd = this.getTdEl(),
16102         x = Dom.getX(elTd),
16103         y = Dom.getY(elTd);
16104
16105     //TODO: remove scrolling logic
16106     // SF doesn't get xy for cells in scrolling table
16107     // when tbody display is set to block
16108     if(isNaN(x) || isNaN(y)) {
16109         var elTbody = this.getDataTable().getTbodyEl();
16110         x = elTd.offsetLeft + // cell pos relative to table
16111                 Dom.getX(elTbody.parentNode) - // plus table pos relative to document
16112                 elTbody.scrollLeft; // minus tbody scroll
16113         y = elTd.offsetTop + // cell pos relative to table
16114                 Dom.getY(elTbody.parentNode) - // plus table pos relative to document
16115                 elTbody.scrollTop + // minus tbody scroll
16116                 this.getDataTable().getTheadEl().offsetHeight; // account for fixed THEAD cells
16117     }
16118
16119     elContainer.style.left = x + "px";
16120     elContainer.style.top = y + "px";
16121 },
16122
16123 /**
16124  * Displays CellEditor UI in the correct position.
16125  *
16126  * @method show
16127  */
16128 show : function() {
16129     this.resetForm();
16130     this.isActive = true;
16131     this.getContainerEl().style.display = "";
16132     this.focus();
16133     this.fireEvent("showEvent", {editor:this});
16134     YAHOO.log("CellEditor shown", "info", this.toString()); 
16135 },
16136
16137 /**
16138  * Fires blockEvent
16139  *
16140  * @method block
16141  */
16142 block : function() {
16143     this.fireEvent("blockEvent", {editor:this});
16144     YAHOO.log("CellEditor blocked", "info", this.toString()); 
16145 },
16146
16147 /**
16148  * Fires unblockEvent
16149  *
16150  * @method unblock
16151  */
16152 unblock : function() {
16153     this.fireEvent("unblockEvent", {editor:this});
16154     YAHOO.log("CellEditor unblocked", "info", this.toString()); 
16155 },
16156
16157 /**
16158  * Saves value of CellEditor and hides UI.
16159  *
16160  * @method save
16161  */
16162 save : function() {
16163     // Get new value
16164     var inputValue = this.getInputValue();
16165     var validValue = inputValue;
16166     
16167     // Validate new value
16168     if(this.validator) {
16169         validValue = this.validator.call(this.getDataTable(), inputValue, this.value, this);
16170         if(validValue === undefined ) {
16171             if(this.resetInvalidData) {
16172                 this.resetForm();
16173             }
16174             this.fireEvent("invalidDataEvent",
16175                     {editor:this, oldData:this.value, newData:inputValue});
16176             YAHOO.log("Could not save Cell Editor input due to invalid data " +
16177                     lang.dump(inputValue), "warn", this.toString());
16178             return;
16179         }
16180     }
16181         
16182     var oSelf = this;
16183     var finishSave = function(bSuccess, oNewValue) {
16184         var oOrigValue = oSelf.value;
16185         if(bSuccess) {
16186             // Update new value
16187             oSelf.value = oNewValue;
16188             oSelf.getDataTable().updateCell(oSelf.getRecord(), oSelf.getColumn(), oNewValue);
16189             
16190             // Hide CellEditor
16191             oSelf.getContainerEl().style.display = "none";
16192             oSelf.isActive = false;
16193             oSelf.getDataTable()._oCellEditor =  null;
16194             
16195             oSelf.fireEvent("saveEvent",
16196                     {editor:oSelf, oldData:oOrigValue, newData:oSelf.value});
16197             YAHOO.log("Cell Editor input saved", "info", this.toString());
16198         }
16199         else {
16200             oSelf.resetForm();
16201             oSelf.fireEvent("revertEvent",
16202                     {editor:oSelf, oldData:oOrigValue, newData:oNewValue});
16203             YAHOO.log("Could not save Cell Editor input " +
16204                     lang.dump(oNewValue), "warn", oSelf.toString());
16205         }
16206         oSelf.unblock();
16207     };
16208     
16209     this.block();
16210     if(lang.isFunction(this.asyncSubmitter)) {
16211         this.asyncSubmitter.call(this, finishSave, validValue);
16212     } 
16213     else {   
16214         finishSave(true, validValue);
16215     }
16216 },
16217
16218 /**
16219  * Cancels CellEditor input and hides UI.
16220  *
16221  * @method cancel
16222  */
16223 cancel : function() {
16224     if(this.isActive) {
16225         this.getContainerEl().style.display = "none";
16226         this.isActive = false;
16227         this.getDataTable()._oCellEditor =  null;
16228         this.fireEvent("cancelEvent", {editor:this});
16229         YAHOO.log("CellEditor canceled", "info", this.toString());
16230     }
16231     else {
16232         YAHOO.log("Unable to cancel CellEditor", "warn", this.toString());
16233     }
16234 },
16235
16236 /**
16237  * Renders form elements.
16238  *
16239  * @method renderForm
16240  */
16241 renderForm : function() {
16242     // To be implemented by subclass
16243 },
16244
16245 /**
16246  * Access to add additional event listeners.
16247  *
16248  * @method doAfterRender
16249  */
16250 doAfterRender : function() {
16251     // To be implemented by subclass
16252 },
16253
16254
16255 /**
16256  * After rendering form, if disabledBtns is set to true, then sets up a mechanism
16257  * to save input without them. 
16258  *
16259  * @method handleDisabledBtns
16260  */
16261 handleDisabledBtns : function() {
16262     // To be implemented by subclass
16263 },
16264
16265 /**
16266  * Resets CellEditor UI to initial state.
16267  *
16268  * @method resetForm
16269  */
16270 resetForm : function() {
16271     // To be implemented by subclass
16272 },
16273
16274 /**
16275  * Sets focus in CellEditor.
16276  *
16277  * @method focus
16278  */
16279 focus : function() {
16280     // To be implemented by subclass
16281 },
16282
16283 /**
16284  * Retrieves input value from CellEditor.
16285  *
16286  * @method getInputValue
16287  */
16288 getInputValue : function() {
16289     // To be implemented by subclass
16290 }
16291
16292 };
16293
16294 lang.augmentProto(BCE, util.EventProvider);
16295
16296
16297 /////////////////////////////////////////////////////////////////////////////
16298 //
16299 // Custom Events
16300 //
16301 /////////////////////////////////////////////////////////////////////////////
16302
16303 /**
16304  * Fired when a CellEditor is shown.
16305  *
16306  * @event showEvent
16307  * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
16308  */
16309
16310 /**
16311  * Fired when a CellEditor has a keydown.
16312  *
16313  * @event keydownEvent
16314  * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance. 
16315  * @param oArgs.event {HTMLEvent} The event object.
16316  */
16317
16318 /**
16319  * Fired when a CellEditor input is reverted due to invalid data.
16320  *
16321  * @event invalidDataEvent
16322  * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance. 
16323  * @param oArgs.newData {Object} New data value from form input field.
16324  * @param oArgs.oldData {Object} Old data value.
16325  */
16326
16327 /**
16328  * Fired when a CellEditor input is reverted due to asyncSubmitter failure.
16329  *
16330  * @event revertEvent
16331  * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance. 
16332  * @param oArgs.newData {Object} New data value from form input field.
16333  * @param oArgs.oldData {Object} Old data value.
16334  */
16335
16336 /**
16337  * Fired when a CellEditor input is saved.
16338  *
16339  * @event saveEvent
16340  * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance. 
16341  * @param oArgs.newData {Object} New data value from form input field.
16342  * @param oArgs.oldData {Object} Old data value.
16343  */
16344
16345 /**
16346  * Fired when a CellEditor input is canceled.
16347  *
16348  * @event cancelEvent
16349  * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance. 
16350  */
16351
16352 /**
16353  * Fired when a CellEditor has a blur event.
16354  *
16355  * @event blurEvent
16356  * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance. 
16357  */
16358
16359
16360
16361
16362
16363
16364
16365
16366
16367
16368
16369
16370
16371
16372 /****************************************************************************/
16373 /****************************************************************************/
16374 /****************************************************************************/
16375     
16376 /**
16377  * The CheckboxCellEditor class provides functionality for inline editing
16378  * DataTable cell data with checkboxes.
16379  *
16380  * @namespace YAHOO.widget
16381  * @class CheckboxCellEditor
16382  * @extends YAHOO.widget.BaseCellEditor
16383  * @constructor
16384  * @param oConfigs {Object} (Optional) Object literal of configs.
16385  */
16386 widget.CheckboxCellEditor = function(oConfigs) {
16387     this._sId = "yui-checkboxceditor" + YAHOO.widget.BaseCellEditor._nCount++;
16388     widget.CheckboxCellEditor.superclass.constructor.call(this, "checkbox", oConfigs); 
16389 };
16390
16391 // CheckboxCellEditor extends BaseCellEditor
16392 lang.extend(widget.CheckboxCellEditor, BCE, {
16393
16394 /////////////////////////////////////////////////////////////////////////////
16395 //
16396 // CheckboxCellEditor public properties
16397 //
16398 /////////////////////////////////////////////////////////////////////////////
16399 /**
16400  * Array of checkbox values. Can either be a simple array (e.g., ["red","green","blue"])
16401  * or a an array of objects (e.g., [{label:"red", value:"#FF0000"},
16402  * {label:"green", value:"#00FF00"}, {label:"blue", value:"#0000FF"}]). 
16403  *
16404  * @property checkboxOptions
16405  * @type String[] | Object[]
16406  */
16407 checkboxOptions : null,
16408
16409 /**
16410  * Reference to the checkbox elements.
16411  *
16412  * @property checkboxes
16413  * @type HTMLElement[] 
16414  */
16415 checkboxes : null,
16416
16417 /**
16418  * Array of checked values
16419  *
16420  * @property value
16421  * @type String[] 
16422  */
16423 value : null,
16424
16425 /////////////////////////////////////////////////////////////////////////////
16426 //
16427 // CheckboxCellEditor public methods
16428 //
16429 /////////////////////////////////////////////////////////////////////////////
16430
16431 /**
16432  * Render a form with input(s) type=checkbox.
16433  *
16434  * @method renderForm
16435  */
16436 renderForm : function() {
16437     if(lang.isArray(this.checkboxOptions)) {
16438         var checkboxOption, checkboxValue, checkboxId, elLabel, j, len;
16439         
16440         // Create the checkbox buttons in an IE-friendly way...
16441         for(j=0,len=this.checkboxOptions.length; j<len; j++) {
16442             checkboxOption = this.checkboxOptions[j];
16443             checkboxValue = lang.isValue(checkboxOption.value) ?
16444                     checkboxOption.value : checkboxOption;
16445
16446             checkboxId = this.getId() + "-chk" + j;
16447             this.getContainerEl().innerHTML += "<input type=\"checkbox\"" +
16448                     " id=\"" + checkboxId + "\"" + // Needed for label
16449                     " value=\"" + checkboxValue + "\" />";
16450             
16451             // Create the labels in an IE-friendly way
16452             elLabel = this.getContainerEl().appendChild(document.createElement("label"));
16453             elLabel.htmlFor = checkboxId;
16454             elLabel.innerHTML = lang.isValue(checkboxOption.label) ?
16455                     checkboxOption.label : checkboxOption;
16456         }
16457         
16458         // Store the reference to the checkbox elements
16459         var allCheckboxes = [];
16460         for(j=0; j<len; j++) {
16461             allCheckboxes[allCheckboxes.length] = this.getContainerEl().childNodes[j*2];
16462         }
16463         this.checkboxes = allCheckboxes;
16464
16465         if(this.disableBtns) {
16466             this.handleDisabledBtns();
16467         }
16468     }
16469     else {
16470         YAHOO.log("Could not find checkboxOptions", "error", this.toString());
16471     }
16472 },
16473
16474 /**
16475  * After rendering form, if disabledBtns is set to true, then sets up a mechanism
16476  * to save input without them. 
16477  *
16478  * @method handleDisabledBtns
16479  */
16480 handleDisabledBtns : function() {
16481     Ev.addListener(this.getContainerEl(), "click", function(v){
16482         if(Ev.getTarget(v).tagName.toLowerCase() === "input") {
16483             // Save on blur
16484             this.save();
16485         }
16486     }, this, true);
16487 },
16488
16489 /**
16490  * Resets CheckboxCellEditor UI to initial state.
16491  *
16492  * @method resetForm
16493  */
16494 resetForm : function() {
16495     // Normalize to array
16496     var originalValues = lang.isArray(this.value) ? this.value : [this.value];
16497     
16498     // Match checks to value
16499     for(var i=0, j=this.checkboxes.length; i<j; i++) {
16500         this.checkboxes[i].checked = false;
16501         for(var k=0, len=originalValues.length; k<len; k++) {
16502             if(this.checkboxes[i].value === originalValues[k]) {
16503                 this.checkboxes[i].checked = true;
16504             }
16505         }
16506     }
16507 },
16508
16509 /**
16510  * Sets focus in CheckboxCellEditor.
16511  *
16512  * @method focus
16513  */
16514 focus : function() {
16515     this.checkboxes[0].focus();
16516 },
16517
16518 /**
16519  * Retrieves input value from CheckboxCellEditor.
16520  *
16521  * @method getInputValue
16522  */
16523 getInputValue : function() {
16524     var checkedValues = [];
16525     for(var i=0, j=this.checkboxes.length; i<j; i++) {
16526         if(this.checkboxes[i].checked) {
16527             checkedValues[checkedValues.length] = this.checkboxes[i].value;
16528         }
16529     }  
16530     return checkedValues;
16531 }
16532
16533 });
16534
16535 // Copy static members to CheckboxCellEditor class
16536 lang.augmentObject(widget.CheckboxCellEditor, BCE);
16537
16538
16539
16540
16541
16542
16543
16544
16545 /****************************************************************************/
16546 /****************************************************************************/
16547 /****************************************************************************/
16548     
16549 /**
16550  * The DataCellEditor class provides functionality for inline editing
16551  * DataTable cell data with a YUI Calendar.
16552  *
16553  * @namespace YAHOO.widget
16554  * @class DateCellEditor
16555  * @extends YAHOO.widget.BaseCellEditor 
16556  * @constructor
16557  * @param oConfigs {Object} (Optional) Object literal of configs.
16558  */
16559 widget.DateCellEditor = function(oConfigs) {
16560     this._sId = "yui-dateceditor" + YAHOO.widget.BaseCellEditor._nCount++;
16561     widget.DateCellEditor.superclass.constructor.call(this, "date", oConfigs); 
16562 };
16563
16564 // CheckboxCellEditor extends BaseCellEditor
16565 lang.extend(widget.DateCellEditor, BCE, {
16566
16567 /////////////////////////////////////////////////////////////////////////////
16568 //
16569 // DateCellEditor public properties
16570 //
16571 /////////////////////////////////////////////////////////////////////////////
16572 /**
16573  * Reference to Calendar instance.
16574  *
16575  * @property calendar
16576  * @type YAHOO.widget.Calendar
16577  */
16578 calendar : null,
16579
16580 /**
16581  * Configs for the calendar instance, to be passed to Calendar constructor.
16582  *
16583  * @property calendarOptions
16584  * @type Object
16585  */
16586 calendarOptions : null,
16587
16588 /**
16589  * Default value.
16590  *
16591  * @property defaultValue
16592  * @type Date
16593  * @default new Date()
16594  */
16595 defaultValue : new Date(),
16596
16597
16598 /////////////////////////////////////////////////////////////////////////////
16599 //
16600 // DateCellEditor public methods
16601 //
16602 /////////////////////////////////////////////////////////////////////////////
16603
16604 /**
16605  * Render a Calendar.
16606  *
16607  * @method renderForm
16608  */
16609 renderForm : function() {
16610     // Calendar widget
16611     if(YAHOO.widget.Calendar) {
16612         var calContainer = this.getContainerEl().appendChild(document.createElement("div"));
16613         calContainer.id = this.getId() + "-dateContainer"; // Needed for Calendar constructor
16614         var calendar =
16615                 new YAHOO.widget.Calendar(this.getId() + "-date",
16616                 calContainer.id, this.calendarOptions);
16617         calendar.render();
16618         calContainer.style.cssFloat = "none";
16619
16620         if(ua.ie) {
16621             var calFloatClearer = this.getContainerEl().appendChild(document.createElement("div"));
16622             calFloatClearer.style.clear = "both";
16623         }
16624         
16625         this.calendar = calendar;
16626
16627         if(this.disableBtns) {
16628             this.handleDisabledBtns();
16629         }
16630     }
16631     else {
16632         YAHOO.log("Could not find YUI Calendar", "error", this.toString());
16633     }
16634     
16635 },
16636
16637 /**
16638  * After rendering form, if disabledBtns is set to true, then sets up a mechanism
16639  * to save input without them. 
16640  *
16641  * @method handleDisabledBtns
16642  */
16643 handleDisabledBtns : function() {
16644     this.calendar.selectEvent.subscribe(function(v){
16645         // Save on select
16646         this.save();
16647     }, this, true);
16648 },
16649
16650 /**
16651  * Resets DateCellEditor UI to initial state.
16652  *
16653  * @method resetForm
16654  */
16655 resetForm : function() {
16656     var value = this.value;
16657     var selectedValue = (value.getMonth()+1)+"/"+value.getDate()+"/"+value.getFullYear();
16658     this.calendar.cfg.setProperty("selected",selectedValue,false);
16659         this.calendar.render();
16660 },
16661
16662 /**
16663  * Sets focus in DateCellEditor.
16664  *
16665  * @method focus
16666  */
16667 focus : function() {
16668     // To be impmlemented by subclass
16669 },
16670
16671 /**
16672  * Retrieves input value from DateCellEditor.
16673  *
16674  * @method getInputValue
16675  */
16676 getInputValue : function() {
16677     return this.calendar.getSelectedDates()[0];
16678 }
16679
16680 });
16681
16682 // Copy static members to DateCellEditor class
16683 lang.augmentObject(widget.DateCellEditor, BCE);
16684
16685
16686
16687
16688
16689
16690
16691
16692
16693 /****************************************************************************/
16694 /****************************************************************************/
16695 /****************************************************************************/
16696     
16697 /**
16698  * The DropdownCellEditor class provides functionality for inline editing
16699  * DataTable cell data a SELECT element.
16700  *
16701  * @namespace YAHOO.widget
16702  * @class DropdownCellEditor
16703  * @extends YAHOO.widget.BaseCellEditor 
16704  * @constructor
16705  * @param oConfigs {Object} (Optional) Object literal of configs.
16706  */
16707 widget.DropdownCellEditor = function(oConfigs) {
16708     this._sId = "yui-dropdownceditor" + YAHOO.widget.BaseCellEditor._nCount++;
16709     widget.DropdownCellEditor.superclass.constructor.call(this, "dropdown", oConfigs);
16710 };
16711
16712 // DropdownCellEditor extends BaseCellEditor
16713 lang.extend(widget.DropdownCellEditor, BCE, {
16714
16715 /////////////////////////////////////////////////////////////////////////////
16716 //
16717 // DropdownCellEditor public properties
16718 //
16719 /////////////////////////////////////////////////////////////////////////////
16720 /**
16721  * Array of dropdown values. Can either be a simple array (e.g.,
16722  * ["Alabama","Alaska","Arizona","Arkansas"]) or a an array of objects (e.g., 
16723  * [{label:"Alabama", value:"AL"}, {label:"Alaska", value:"AK"},
16724  * {label:"Arizona", value:"AZ"}, {label:"Arkansas", value:"AR"}]). 
16725  *
16726  * @property dropdownOptions
16727  * @type String[] | Object[]
16728  */
16729 dropdownOptions : null,
16730
16731 /**
16732  * Reference to Dropdown element.
16733  *
16734  * @property dropdown
16735  * @type HTMLElement
16736  */
16737 dropdown : null,
16738
16739 /**
16740  * Enables multi-select.
16741  *
16742  * @property multiple
16743  * @type Boolean
16744  */
16745 multiple : false,
16746
16747 /**
16748  * Specifies number of visible options.
16749  *
16750  * @property size
16751  * @type Number
16752  */
16753 size : null,
16754
16755 /////////////////////////////////////////////////////////////////////////////
16756 //
16757 // DropdownCellEditor public methods
16758 //
16759 /////////////////////////////////////////////////////////////////////////////
16760
16761 /**
16762  * Render a form with select element.
16763  *
16764  * @method renderForm
16765  */
16766 renderForm : function() {
16767     var elDropdown = this.getContainerEl().appendChild(document.createElement("select"));
16768     elDropdown.style.zoom = 1;
16769     if(this.multiple) {
16770         elDropdown.multiple = "multiple";
16771     }
16772     if(lang.isNumber(this.size)) {
16773         elDropdown.size = this.size;
16774     }
16775     this.dropdown = elDropdown;
16776     
16777     if(lang.isArray(this.dropdownOptions)) {
16778         var dropdownOption, elOption;
16779         for(var i=0, j=this.dropdownOptions.length; i<j; i++) {
16780             dropdownOption = this.dropdownOptions[i];
16781             elOption = document.createElement("option");
16782             elOption.value = (lang.isValue(dropdownOption.value)) ?
16783                     dropdownOption.value : dropdownOption;
16784             elOption.innerHTML = (lang.isValue(dropdownOption.label)) ?
16785                     dropdownOption.label : dropdownOption;
16786             elOption = elDropdown.appendChild(elOption);
16787         }
16788         
16789         if(this.disableBtns) {
16790             this.handleDisabledBtns();
16791         }
16792     }
16793 },
16794
16795 /**
16796  * After rendering form, if disabledBtns is set to true, then sets up a mechanism
16797  * to save input without them. 
16798  *
16799  * @method handleDisabledBtns
16800  */
16801 handleDisabledBtns : function() {
16802     // Save on blur for multi-select
16803     if(this.multiple) {
16804         Ev.addListener(this.dropdown, "blur", function(v){
16805             // Save on change
16806             this.save();
16807         }, this, true);
16808     }
16809     // Save on change for single-select
16810     else {
16811         Ev.addListener(this.dropdown, "change", function(v){
16812             // Save on change
16813             this.save();
16814         }, this, true);
16815     }
16816 },
16817
16818 /**
16819  * Resets DropdownCellEditor UI to initial state.
16820  *
16821  * @method resetForm
16822  */
16823 resetForm : function() {
16824     var allOptions = this.dropdown.options,
16825         i=0, j=allOptions.length;
16826
16827     // Look for multi-select selections
16828     if(lang.isArray(this.value)) {
16829         var allValues = this.value,
16830             m=0, n=allValues.length,
16831             hash = {};
16832         // Reset all selections and stash options in a value hash
16833         for(; i<j; i++) {
16834             allOptions[i].selected = false;
16835             hash[allOptions[i].value] = allOptions[i];
16836         }
16837         for(; m<n; m++) {
16838             if(hash[allValues[m]]) {
16839                 hash[allValues[m]].selected = true;
16840             }
16841         }
16842     }
16843     // Only need to look for a single selection
16844     else {
16845         for(; i<j; i++) {
16846             if(this.value === allOptions[i].value) {
16847                 allOptions[i].selected = true;
16848             }
16849         }
16850     }
16851 },
16852
16853 /**
16854  * Sets focus in DropdownCellEditor.
16855  *
16856  * @method focus
16857  */
16858 focus : function() {
16859     this.getDataTable()._focusEl(this.dropdown);
16860 },
16861
16862 /**
16863  * Retrieves input value from DropdownCellEditor.
16864  *
16865  * @method getInputValue
16866  */
16867 getInputValue : function() {
16868     var allOptions = this.dropdown.options;
16869     
16870     // Look for multiple selections
16871     if(this.multiple) {
16872         var values = [],
16873             i=0, j=allOptions.length;
16874         for(; i<j; i++) {
16875             if(allOptions[i].selected) {
16876                 values.push(allOptions[i].value);
16877             }
16878         }
16879         return values;
16880     }
16881     // Only need to look for single selection
16882     else {
16883         return allOptions[allOptions.selectedIndex].value;
16884     }
16885 }
16886
16887 });
16888
16889 // Copy static members to DropdownCellEditor class
16890 lang.augmentObject(widget.DropdownCellEditor, BCE);
16891
16892
16893
16894
16895
16896
16897 /****************************************************************************/
16898 /****************************************************************************/
16899 /****************************************************************************/
16900     
16901 /**
16902  * The RadioCellEditor class provides functionality for inline editing
16903  * DataTable cell data with radio buttons.
16904  *
16905  * @namespace YAHOO.widget
16906  * @class RadioCellEditor
16907  * @extends YAHOO.widget.BaseCellEditor 
16908  * @constructor
16909  * @param oConfigs {Object} (Optional) Object literal of configs.
16910  */
16911 widget.RadioCellEditor = function(oConfigs) {
16912     this._sId = "yui-radioceditor" + YAHOO.widget.BaseCellEditor._nCount++;
16913     widget.RadioCellEditor.superclass.constructor.call(this, "radio", oConfigs); 
16914 };
16915
16916 // RadioCellEditor extends BaseCellEditor
16917 lang.extend(widget.RadioCellEditor, BCE, {
16918
16919 /////////////////////////////////////////////////////////////////////////////
16920 //
16921 // RadioCellEditor public properties
16922 //
16923 /////////////////////////////////////////////////////////////////////////////
16924 /**
16925  * Reference to radio elements.
16926  *
16927  * @property radios
16928  * @type HTMLElement[]
16929  */
16930 radios : null,
16931
16932 /**
16933  * Array of radio values. Can either be a simple array (e.g., ["yes","no","maybe"])
16934  * or a an array of objects (e.g., [{label:"yes", value:1}, {label:"no", value:-1},
16935  * {label:"maybe", value:0}]). 
16936  *
16937  * @property radioOptions
16938  * @type String[] | Object[]
16939  */
16940 radioOptions : null,
16941
16942 /////////////////////////////////////////////////////////////////////////////
16943 //
16944 // RadioCellEditor public methods
16945 //
16946 /////////////////////////////////////////////////////////////////////////////
16947
16948 /**
16949  * Render a form with input(s) type=radio.
16950  *
16951  * @method renderForm
16952  */
16953 renderForm : function() {
16954     if(lang.isArray(this.radioOptions)) {
16955         var radioOption, radioValue, radioId, elLabel;
16956         
16957         // Create the radio buttons in an IE-friendly way
16958         for(var i=0, len=this.radioOptions.length; i<len; i++) {
16959             radioOption = this.radioOptions[i];
16960             radioValue = lang.isValue(radioOption.value) ?
16961                     radioOption.value : radioOption;
16962             radioId = this.getId() + "-radio" + i;
16963             this.getContainerEl().innerHTML += "<input type=\"radio\"" +
16964                     " name=\"" + this.getId() + "\"" +
16965                     " value=\"" + radioValue + "\"" +
16966                     " id=\"" +  radioId + "\" />"; // Needed for label
16967             
16968             // Create the labels in an IE-friendly way
16969             elLabel = this.getContainerEl().appendChild(document.createElement("label"));
16970             elLabel.htmlFor = radioId;
16971             elLabel.innerHTML = (lang.isValue(radioOption.label)) ?
16972                     radioOption.label : radioOption;
16973         }
16974         
16975         // Store the reference to the checkbox elements
16976         var allRadios = [],
16977             elRadio;
16978         for(var j=0; j<len; j++) {
16979             elRadio = this.getContainerEl().childNodes[j*2];
16980             allRadios[allRadios.length] = elRadio;
16981         }
16982         this.radios = allRadios;
16983
16984         if(this.disableBtns) {
16985             this.handleDisabledBtns();
16986         }
16987     }
16988     else {
16989         YAHOO.log("Could not find radioOptions", "error", this.toString());
16990     }
16991 },
16992
16993 /**
16994  * After rendering form, if disabledBtns is set to true, then sets up a mechanism
16995  * to save input without them. 
16996  *
16997  * @method handleDisabledBtns
16998  */
16999 handleDisabledBtns : function() {
17000     Ev.addListener(this.getContainerEl(), "click", function(v){
17001         if(Ev.getTarget(v).tagName.toLowerCase() === "input") {
17002             // Save on blur
17003             this.save();
17004         }
17005     }, this, true);
17006 },
17007
17008 /**
17009  * Resets RadioCellEditor UI to initial state.
17010  *
17011  * @method resetForm
17012  */
17013 resetForm : function() {
17014     for(var i=0, j=this.radios.length; i<j; i++) {
17015         var elRadio = this.radios[i];
17016         if(this.value === elRadio.value) {
17017             elRadio.checked = true;
17018             return;
17019         }
17020     }
17021 },
17022
17023 /**
17024  * Sets focus in RadioCellEditor.
17025  *
17026  * @method focus
17027  */
17028 focus : function() {
17029     for(var i=0, j=this.radios.length; i<j; i++) {
17030         if(this.radios[i].checked) {
17031             this.radios[i].focus();
17032             return;
17033         }
17034     }
17035 },
17036
17037 /**
17038  * Retrieves input value from RadioCellEditor.
17039  *
17040  * @method getInputValue
17041  */
17042 getInputValue : function() {
17043     for(var i=0, j=this.radios.length; i<j; i++) {
17044         if(this.radios[i].checked) {
17045             return this.radios[i].value;
17046         }
17047     }
17048 }
17049
17050 });
17051
17052 // Copy static members to RadioCellEditor class
17053 lang.augmentObject(widget.RadioCellEditor, BCE);
17054
17055
17056
17057
17058
17059
17060 /****************************************************************************/
17061 /****************************************************************************/
17062 /****************************************************************************/
17063     
17064 /**
17065  * The TextareaCellEditor class provides functionality for inline editing
17066  * DataTable cell data with a TEXTAREA element.
17067  *
17068  * @namespace YAHOO.widget
17069  * @class TextareaCellEditor
17070  * @extends YAHOO.widget.BaseCellEditor 
17071  * @constructor
17072  * @param oConfigs {Object} (Optional) Object literal of configs.
17073  */
17074 widget.TextareaCellEditor = function(oConfigs) {
17075     this._sId = "yui-textareaceditor" + YAHOO.widget.BaseCellEditor._nCount++;
17076     widget.TextareaCellEditor.superclass.constructor.call(this, "textarea", oConfigs); 
17077 };
17078
17079 // TextareaCellEditor extends BaseCellEditor
17080 lang.extend(widget.TextareaCellEditor, BCE, {
17081
17082 /////////////////////////////////////////////////////////////////////////////
17083 //
17084 // TextareaCellEditor public properties
17085 //
17086 /////////////////////////////////////////////////////////////////////////////
17087 /**
17088  * Reference to textarea element.
17089  *
17090  * @property textarea
17091  * @type HTMLElement
17092  */
17093 textarea : null,
17094
17095
17096 /////////////////////////////////////////////////////////////////////////////
17097 //
17098 // TextareaCellEditor public methods
17099 //
17100 /////////////////////////////////////////////////////////////////////////////
17101
17102 /**
17103  * Render a form with textarea.
17104  *
17105  * @method renderForm
17106  */
17107 renderForm : function() {
17108     var elTextarea = this.getContainerEl().appendChild(document.createElement("textarea"));
17109     this.textarea = elTextarea;
17110
17111     if(this.disableBtns) {
17112         this.handleDisabledBtns();
17113     }
17114 },
17115
17116 /**
17117  * After rendering form, if disabledBtns is set to true, then sets up a mechanism
17118  * to save input without them. 
17119  *
17120  * @method handleDisabledBtns
17121  */
17122 handleDisabledBtns : function() {
17123     Ev.addListener(this.textarea, "blur", function(v){
17124         // Save on blur
17125         this.save();
17126     }, this, true);        
17127 },
17128
17129 /**
17130  * Moves TextareaCellEditor UI to a cell.
17131  *
17132  * @method move
17133  */
17134 move : function() {
17135     this.textarea.style.width = this.getTdEl().offsetWidth + "px";
17136     this.textarea.style.height = "3em";
17137     YAHOO.widget.TextareaCellEditor.superclass.move.call(this);
17138 },
17139
17140 /**
17141  * Resets TextareaCellEditor UI to initial state.
17142  *
17143  * @method resetForm
17144  */
17145 resetForm : function() {
17146     this.textarea.value = this.value;
17147 },
17148
17149 /**
17150  * Sets focus in TextareaCellEditor.
17151  *
17152  * @method focus
17153  */
17154 focus : function() {
17155     // Bug 2303181, Bug 2263600
17156     this.getDataTable()._focusEl(this.textarea);
17157     this.textarea.select();
17158 },
17159
17160 /**
17161  * Retrieves input value from TextareaCellEditor.
17162  *
17163  * @method getInputValue
17164  */
17165 getInputValue : function() {
17166     return this.textarea.value;
17167 }
17168
17169 });
17170
17171 // Copy static members to TextareaCellEditor class
17172 lang.augmentObject(widget.TextareaCellEditor, BCE);
17173
17174
17175
17176
17177
17178
17179
17180
17181
17182 /****************************************************************************/
17183 /****************************************************************************/
17184 /****************************************************************************/
17185     
17186 /**
17187  * The TextboxCellEditor class provides functionality for inline editing
17188  * DataTable cell data with an INPUT TYPE=TEXT element.
17189  *
17190  * @namespace YAHOO.widget
17191  * @class TextboxCellEditor
17192  * @extends YAHOO.widget.BaseCellEditor 
17193  * @constructor
17194  * @param oConfigs {Object} (Optional) Object literal of configs.
17195  */
17196 widget.TextboxCellEditor = function(oConfigs) {
17197     this._sId = "yui-textboxceditor" + YAHOO.widget.BaseCellEditor._nCount++;
17198     widget.TextboxCellEditor.superclass.constructor.call(this, "textbox", oConfigs); 
17199 };
17200
17201 // TextboxCellEditor extends BaseCellEditor
17202 lang.extend(widget.TextboxCellEditor, BCE, {
17203
17204 /////////////////////////////////////////////////////////////////////////////
17205 //
17206 // TextboxCellEditor public properties
17207 //
17208 /////////////////////////////////////////////////////////////////////////////
17209 /**
17210  * Reference to the textbox element.
17211  *
17212  * @property textbox
17213  */
17214 textbox : null,
17215
17216 /////////////////////////////////////////////////////////////////////////////
17217 //
17218 // TextboxCellEditor public methods
17219 //
17220 /////////////////////////////////////////////////////////////////////////////
17221
17222 /**
17223  * Render a form with input type=text.
17224  *
17225  * @method renderForm
17226  */
17227 renderForm : function() {
17228     var elTextbox;
17229     // Bug 1802582: SF3/Mac needs a form element wrapping the input
17230     if(ua.webkit>420) {
17231         elTextbox = this.getContainerEl().appendChild(document.createElement("form")).appendChild(document.createElement("input"));
17232     }
17233     else {
17234         elTextbox = this.getContainerEl().appendChild(document.createElement("input"));
17235     }
17236     elTextbox.type = "text";
17237     this.textbox = elTextbox;
17238
17239     // Save on enter by default
17240     // Bug: 1802582 Set up a listener on each textbox to track on keypress
17241     // since SF/OP can't preventDefault on keydown
17242     Ev.addListener(elTextbox, "keypress", function(v){
17243         if((v.keyCode === 13)) {
17244             // Prevent form submit
17245             YAHOO.util.Event.preventDefault(v);
17246             this.save();
17247         }
17248     }, this, true);
17249
17250     if(this.disableBtns) {
17251         // By default this is no-op since enter saves by default
17252         this.handleDisabledBtns();
17253     }
17254 },
17255
17256 /**
17257  * Moves TextboxCellEditor UI to a cell.
17258  *
17259  * @method move
17260  */
17261 move : function() {
17262     this.textbox.style.width = this.getTdEl().offsetWidth + "px";
17263     widget.TextboxCellEditor.superclass.move.call(this);
17264 },
17265
17266 /**
17267  * Resets TextboxCellEditor UI to initial state.
17268  *
17269  * @method resetForm
17270  */
17271 resetForm : function() {
17272     this.textbox.value = lang.isValue(this.value) ? this.value.toString() : "";
17273 },
17274
17275 /**
17276  * Sets focus in TextboxCellEditor.
17277  *
17278  * @method focus
17279  */
17280 focus : function() {
17281     // Bug 2303181, Bug 2263600
17282     this.getDataTable()._focusEl(this.textbox);
17283     this.textbox.select();
17284 },
17285
17286 /**
17287  * Returns new value for TextboxCellEditor.
17288  *
17289  * @method getInputValue
17290  */
17291 getInputValue : function() {
17292     return this.textbox.value;
17293 }
17294
17295 });
17296
17297 // Copy static members to TextboxCellEditor class
17298 lang.augmentObject(widget.TextboxCellEditor, BCE);
17299
17300
17301
17302
17303
17304
17305
17306 /////////////////////////////////////////////////////////////////////////////
17307 //
17308 // DataTable extension
17309 //
17310 /////////////////////////////////////////////////////////////////////////////
17311
17312 /**
17313  * CellEditor subclasses.
17314  * @property DataTable.Editors
17315  * @type Object
17316  * @static
17317  */
17318 DT.Editors = {
17319     checkbox : widget.CheckboxCellEditor,
17320     "date"   : widget.DateCellEditor,
17321     dropdown : widget.DropdownCellEditor,
17322     radio    : widget.RadioCellEditor,
17323     textarea : widget.TextareaCellEditor,
17324     textbox  : widget.TextboxCellEditor
17325 };
17326
17327 /****************************************************************************/
17328 /****************************************************************************/
17329 /****************************************************************************/
17330     
17331 /**
17332  * Factory class for instantiating a BaseCellEditor subclass.
17333  *
17334  * @namespace YAHOO.widget
17335  * @class CellEditor
17336  * @extends YAHOO.widget.BaseCellEditor 
17337  * @constructor
17338  * @param sType {String} Type indicator, to map to YAHOO.widget.DataTable.Editors.
17339  * @param oConfigs {Object} (Optional) Object literal of configs.
17340  */
17341 widget.CellEditor = function(sType, oConfigs) {
17342     // Point to one of the subclasses
17343     if(sType && DT.Editors[sType]) {
17344         lang.augmentObject(BCE, DT.Editors[sType]);
17345         return new DT.Editors[sType](oConfigs);
17346     }
17347     else {
17348         return new BCE(null, oConfigs);
17349     }
17350 };
17351
17352 var CE = widget.CellEditor;
17353
17354 // Copy static members to CellEditor class
17355 lang.augmentObject(CE, BCE);
17356
17357
17358 })();
17359
17360 YAHOO.register("datatable", YAHOO.widget.DataTable, {version: "2.8.0r4", build: "2449"});