]> ToastFreeware Gitweb - philipp/winterrodeln/wradmin.git/blob - wradmin/public/yui/datatable/datatable-debug.js
Additional cleanup.
[philipp/winterrodeln/wradmin.git] / wradmin / public / 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.7.0
6 */
7 /**
8  * Mechanism to execute a series of callbacks in a non-blocking queue.  Each callback is executed via setTimout unless configured with a negative timeout, in which case it is run in blocking mode in the same execution thread as the previous callback.  Callbacks can be function references or object literals with the following keys:
9  * <ul>
10  *    <li><code>method</code> - {Function} REQUIRED the callback function.</li>
11  *    <li><code>scope</code> - {Object} the scope from which to execute the callback.  Default is the global window scope.</li>
12  *    <li><code>argument</code> - {Array} parameters to be passed to method as individual arguments.</li>
13  *    <li><code>timeout</code> - {number} millisecond delay to wait after previous callback completion before executing this callback.  Negative values cause immediate blocking execution.  Default 0.</li>
14  *    <li><code>until</code> - {Function} boolean function executed before each iteration.  Return true to indicate completion and proceed to the next callback.</li>
15  *    <li><code>iterations</code> - {Number} number of times to execute the callback before proceeding to the next callback in the chain. Incompatible with <code>until</code>.</li>
16  * </ul>
17  *
18  * @namespace YAHOO.util
19  * @class Chain
20  * @constructor
21  * @param callback* {Function|Object} Any number of callbacks to initialize the queue
22 */
23 YAHOO.util.Chain = function () {
24     /**
25      * The callback queue
26      * @property q
27      * @type {Array}
28      * @private
29      */
30     this.q = [].slice.call(arguments);
31
32     /**
33      * Event fired when the callback queue is emptied via execution (not via
34      * a call to chain.stop().
35      * @event end
36      */
37     this.createEvent('end');
38 };
39
40 YAHOO.util.Chain.prototype = {
41     /**
42      * Timeout id used to pause or stop execution and indicate the execution state of the Chain.  0 indicates paused or stopped, -1 indicates blocking execution, and any positive number indicates non-blocking execution.
43      * @property id
44      * @type {number}
45      * @private
46      */
47     id   : 0,
48
49     /**
50      * Begin executing the chain, or resume execution from the last paused position.
51      * @method run
52      * @return {Chain} the Chain instance
53      */
54     run : function () {
55         // Grab the first callback in the queue
56         var c  = this.q[0],
57             fn;
58
59         // If there is no callback in the queue or the Chain is currently
60         // in an execution mode, return
61         if (!c) {
62             this.fireEvent('end');
63             return this;
64         } else if (this.id) {
65             return this;
66         }
67
68         fn = c.method || c;
69
70         if (typeof fn === 'function') {
71             var o    = c.scope || {},
72                 args = c.argument || [],
73                 ms   = c.timeout || 0,
74                 me   = this;
75                 
76             if (!(args instanceof Array)) {
77                 args = [args];
78             }
79
80             // Execute immediately if the callback timeout is negative.
81             if (ms < 0) {
82                 this.id = ms;
83                 if (c.until) {
84                     for (;!c.until();) {
85                         // Execute the callback from scope, with argument
86                         fn.apply(o,args);
87                     }
88                 } else if (c.iterations) {
89                     for (;c.iterations-- > 0;) {
90                         fn.apply(o,args);
91                     }
92                 } else {
93                     fn.apply(o,args);
94                 }
95                 this.q.shift();
96                 this.id = 0;
97                 return this.run();
98             } else {
99                 // If the until condition is set, check if we're done
100                 if (c.until) {
101                     if (c.until()) {
102                         // Shift this callback from the queue and execute the next
103                         // callback
104                         this.q.shift();
105                         return this.run();
106                     }
107                 // Otherwise if either iterations is not set or we're
108                 // executing the last iteration, shift callback from the queue
109                 } else if (!c.iterations || !--c.iterations) {
110                     this.q.shift();
111                 }
112
113                 // Otherwise set to execute after the configured timeout
114                 this.id = setTimeout(function () {
115                     // Execute the callback from scope, with argument
116                     fn.apply(o,args);
117                     // Check if the Chain was not paused from inside the callback
118                     if (me.id) {
119                         // Indicate ready to run state
120                         me.id = 0;
121                         // Start the fun all over again
122                         me.run();
123                     }
124                 },ms);
125             }
126         }
127
128         return this;
129     },
130     
131     /**
132      * Add a callback to the end of the queue
133      * @method add
134      * @param c {Function|Object} the callback function ref or object literal
135      * @return {Chain} the Chain instance
136      */
137     add  : function (c) {
138         this.q.push(c);
139         return this;
140     },
141
142     /**
143      * Pause the execution of the Chain after the current execution of the
144      * current callback completes.  If called interstitially, clears the
145      * timeout for the pending callback. Paused Chains can be restarted with
146      * chain.run()
147      * @method pause
148      * @return {Chain} the Chain instance
149      */
150     pause: function () {
151         clearTimeout(this.id);
152         this.id = 0;
153         return this;
154     },
155
156     /**
157      * Stop and clear the Chain's queue after the current execution of the
158      * current callback completes.
159      * @method stop
160      * @return {Chain} the Chain instance
161      */
162     stop : function () { 
163         this.pause();
164         this.q = [];
165         return this;
166     }
167 };
168 YAHOO.lang.augmentProto(YAHOO.util.Chain,YAHOO.util.EventProvider);
169
170 /****************************************************************************/
171 /****************************************************************************/
172 /****************************************************************************/
173
174 /**
175  * The ColumnSet class defines and manages a DataTable's Columns,
176  * including nested hierarchies and access to individual Column instances.
177  *
178  * @namespace YAHOO.widget
179  * @class ColumnSet
180  * @uses YAHOO.util.EventProvider
181  * @constructor
182  * @param aDefinitions {Object[]} Array of object literals that define cells in
183  * the THEAD.
184  */
185 YAHOO.widget.ColumnSet = function(aDefinitions) {
186     this._sId = "yui-cs" + YAHOO.widget.ColumnSet._nCount;
187
188     // First clone the defs
189     aDefinitions = YAHOO.widget.DataTable._cloneObject(aDefinitions);
190     this._init(aDefinitions);
191
192     YAHOO.widget.ColumnSet._nCount++;
193     YAHOO.log("ColumnSet initialized", "info", this.toString());
194 };
195
196 /////////////////////////////////////////////////////////////////////////////
197 //
198 // Private member variables
199 //
200 /////////////////////////////////////////////////////////////////////////////
201
202 /**
203  * Internal class variable to index multiple ColumnSet instances.
204  *
205  * @property ColumnSet._nCount
206  * @type Number
207  * @private
208  * @static
209  */
210 YAHOO.widget.ColumnSet._nCount = 0;
211
212 YAHOO.widget.ColumnSet.prototype = {
213     /**
214      * Unique instance name.
215      *
216      * @property _sId
217      * @type String
218      * @private
219      */
220     _sId : null,
221
222     /**
223      * Array of object literal Column definitions passed to the constructor.
224      *
225      * @property _aDefinitions
226      * @type Object[]
227      * @private
228      */
229     _aDefinitions : null,
230
231     /////////////////////////////////////////////////////////////////////////////
232     //
233     // Public member variables
234     //
235     /////////////////////////////////////////////////////////////////////////////
236
237     /**
238      * Top-down tree representation of Column hierarchy.
239      *
240      * @property tree
241      * @type YAHOO.widget.Column[]
242      */
243     tree : null,
244
245     /**
246      * Flattened representation of all Columns.
247      *
248      * @property flat
249      * @type YAHOO.widget.Column[]
250      * @default []
251      */
252     flat : null,
253
254     /**
255      * Array of Columns that map one-to-one to a table column.
256      *
257      * @property keys
258      * @type YAHOO.widget.Column[]
259      * @default []
260      */
261     keys : null,
262
263     /**
264      * ID index of nested parent hierarchies for HEADERS accessibility attribute.
265      *
266      * @property headers
267      * @type String[]
268      * @default []
269      */
270     headers : null,
271
272     /////////////////////////////////////////////////////////////////////////////
273     //
274     // Private methods
275     //
276     /////////////////////////////////////////////////////////////////////////////
277
278     /**
279      * Initializes ColumnSet instance with data from Column definitions.
280      *
281      * @method _init
282      * @param aDefinitions {Object[]} Array of object literals that define cells in
283      * the THEAD .
284      * @private
285      */
286
287     _init : function(aDefinitions) {        
288         // DOM tree representation of all Columns
289         var tree = [];
290         // Flat representation of all Columns
291         var flat = [];
292         // Flat representation of only Columns that are meant to display data
293         var keys = [];
294         // Array of HEADERS attribute values for all keys in the "keys" array
295         var headers = [];
296
297         // Tracks current node list depth being tracked
298         var nodeDepth = -1;
299
300         // Internal recursive function to define Column instances
301         var parseColumns = function(nodeList, parent) {
302             // One level down
303             nodeDepth++;
304
305             // Create corresponding tree node if not already there for this depth
306             if(!tree[nodeDepth]) {
307                 tree[nodeDepth] = [];
308             }
309
310
311             // Parse each node at this depth for attributes and any children
312             for(var j=0; j<nodeList.length; j++) {
313                 var currentNode = nodeList[j];
314
315                 // Instantiate a new Column for each node
316                 var oColumn = new YAHOO.widget.Column(currentNode);
317                 
318                 // Cross-reference Column ID back to the original object literal definition
319                 currentNode.yuiColumnId = oColumn._sId;
320                 
321                 // Add the new Column to the flat list
322                 flat.push(oColumn);
323
324                 // Assign its parent as an attribute, if applicable
325                 if(parent) {
326                     oColumn._oParent = parent;
327                 }
328
329                 // The Column has descendants
330                 if(YAHOO.lang.isArray(currentNode.children)) {
331                     oColumn.children = currentNode.children;
332
333                     // Determine COLSPAN value for this Column
334                     var terminalChildNodes = 0;
335                     var countTerminalChildNodes = function(ancestor) {
336                         var descendants = ancestor.children;
337                         // Drill down each branch and count terminal nodes
338                         for(var k=0; k<descendants.length; k++) {
339                             // Keep drilling down
340                             if(YAHOO.lang.isArray(descendants[k].children)) {
341                                 countTerminalChildNodes(descendants[k]);
342                             }
343                             // Reached branch terminus
344                             else {
345                                 terminalChildNodes++;
346                             }
347                         }
348                     };
349                     countTerminalChildNodes(currentNode);
350                     oColumn._nColspan = terminalChildNodes;
351
352                     // Cascade certain properties to children if not defined on their own
353                     var currentChildren = currentNode.children;
354                     for(var k=0; k<currentChildren.length; k++) {
355                         var child = currentChildren[k];
356                         if(oColumn.className && (child.className === undefined)) {
357                             child.className = oColumn.className;
358                         }
359                         if(oColumn.editor && (child.editor === undefined)) {
360                             child.editor = oColumn.editor;
361                         }
362                         //TODO: Deprecated
363                         if(oColumn.editorOptions && (child.editorOptions === undefined)) {
364                             child.editorOptions = oColumn.editorOptions;
365                         }
366                         if(oColumn.formatter && (child.formatter === undefined)) {
367                             child.formatter = oColumn.formatter;
368                         }
369                         if(oColumn.resizeable && (child.resizeable === undefined)) {
370                             child.resizeable = oColumn.resizeable;
371                         }
372                         if(oColumn.sortable && (child.sortable === undefined)) {
373                             child.sortable = oColumn.sortable;
374                         }
375                         if(oColumn.hidden) {
376                             child.hidden = true;
377                         }
378                         if(oColumn.width && (child.width === undefined)) {
379                             child.width = oColumn.width;
380                         }
381                         if(oColumn.minWidth && (child.minWidth === undefined)) {
382                             child.minWidth = oColumn.minWidth;
383                         }
384                         if(oColumn.maxAutoWidth && (child.maxAutoWidth === undefined)) {
385                             child.maxAutoWidth = oColumn.maxAutoWidth;
386                         }
387                         // Backward compatibility
388                         if(oColumn.type && (child.type === undefined)) {
389                             child.type = oColumn.type;
390                         }
391                         if(oColumn.type && !oColumn.formatter) {
392                             YAHOO.log("The property type has been" +
393                             " deprecated in favor of formatter", "warn", oColumn.toString());
394                             oColumn.formatter = oColumn.type;
395                         }
396                         if(oColumn.text && !YAHOO.lang.isValue(oColumn.label)) {
397                             YAHOO.log("The property text has been" +
398                             " deprecated in favor of label", "warn", oColumn.toString());
399                             oColumn.label = oColumn.text;
400                         }
401                         if(oColumn.parser) {
402                             YAHOO.log("The property parser is no longer supported",
403                             "warn", this.toString());
404                         }
405                         if(oColumn.sortOptions && ((oColumn.sortOptions.ascFunction) ||
406                                 (oColumn.sortOptions.descFunction))) {
407                             YAHOO.log("The properties sortOptions.ascFunction and " +
408                             " sortOptions.descFunction have been deprecated in favor " +
409                             " of sortOptions.sortFunction", "warn", oColumn.toString());
410                         }
411                     }
412
413                     // The children themselves must also be parsed for Column instances
414                     if(!tree[nodeDepth+1]) {
415                         tree[nodeDepth+1] = [];
416                     }
417                     parseColumns(currentChildren, oColumn);
418                 }
419                 // This Column does not have any children
420                 else {
421                     oColumn._nKeyIndex = keys.length;
422                     oColumn._nColspan = 1;
423                     keys.push(oColumn);
424                 }
425
426                 // Add the Column to the top-down tree
427                 tree[nodeDepth].push(oColumn);
428             }
429             nodeDepth--;
430         };
431
432         // Parse out Column instances from the array of object literals
433         if(YAHOO.lang.isArray(aDefinitions)) {
434             parseColumns(aDefinitions);
435
436             // Store the array
437             this._aDefinitions = aDefinitions;
438         }
439         else {
440             YAHOO.log("Could not initialize ColumnSet due to invalid definitions","error");
441             return null;
442         }
443
444         var i;
445
446         // Determine ROWSPAN value for each Column in the tree
447         var parseTreeForRowspan = function(tree) {
448             var maxRowDepth = 1;
449             var currentRow;
450             var currentColumn;
451
452             // Calculate the max depth of descendants for this row
453             var countMaxRowDepth = function(row, tmpRowDepth) {
454                 tmpRowDepth = tmpRowDepth || 1;
455
456                 for(var n=0; n<row.length; n++) {
457                     var col = row[n];
458                     // Column has children, so keep counting
459                     if(YAHOO.lang.isArray(col.children)) {
460                         tmpRowDepth++;
461                         countMaxRowDepth(col.children, tmpRowDepth);
462                         tmpRowDepth--;
463                     }
464                     // No children, is it the max depth?
465                     else {
466                         if(tmpRowDepth > maxRowDepth) {
467                             maxRowDepth = tmpRowDepth;
468                         }
469                     }
470
471                 }
472             };
473
474             // Count max row depth for each row
475             for(var m=0; m<tree.length; m++) {
476                 currentRow = tree[m];
477                 countMaxRowDepth(currentRow);
478
479                 // Assign the right ROWSPAN values to each Column in the row
480                 for(var p=0; p<currentRow.length; p++) {
481                     currentColumn = currentRow[p];
482                     if(!YAHOO.lang.isArray(currentColumn.children)) {
483                         currentColumn._nRowspan = maxRowDepth;
484                     }
485                     else {
486                         currentColumn._nRowspan = 1;
487                     }
488                 }
489
490                 // Reset counter for next row
491                 maxRowDepth = 1;
492             }
493         };
494         parseTreeForRowspan(tree);
495
496         // Store tree index values
497         for(i=0; i<tree[0].length; i++) {
498             tree[0][i]._nTreeIndex = i;
499         }
500
501         // Store header relationships in an array for HEADERS attribute
502         var recurseAncestorsForHeaders = function(i, oColumn) {
503             headers[i].push(oColumn.getSanitizedKey());
504             if(oColumn._oParent) {
505                 recurseAncestorsForHeaders(i, oColumn._oParent);
506             }
507         };
508         for(i=0; i<keys.length; i++) {
509             headers[i] = [];
510             recurseAncestorsForHeaders(i, keys[i]);
511             headers[i] = headers[i].reverse();
512         }
513
514         // Save to the ColumnSet instance
515         this.tree = tree;
516         this.flat = flat;
517         this.keys = keys;
518         this.headers = headers;
519     },
520
521     /////////////////////////////////////////////////////////////////////////////
522     //
523     // Public methods
524     //
525     /////////////////////////////////////////////////////////////////////////////
526
527     /**
528      * Returns unique name of the ColumnSet instance.
529      *
530      * @method getId
531      * @return {String} Unique name of the ColumnSet instance.
532      */
533
534     getId : function() {
535         return this._sId;
536     },
537
538     /**
539      * ColumnSet instance name, for logging.
540      *
541      * @method toString
542      * @return {String} Unique name of the ColumnSet instance.
543      */
544
545     toString : function() {
546         return "ColumnSet instance " + this._sId;
547     },
548
549     /**
550      * Public accessor to the definitions array.
551      *
552      * @method getDefinitions
553      * @return {Object[]} Array of object literal Column definitions.
554      */
555
556     getDefinitions : function() {
557         var aDefinitions = this._aDefinitions;
558         
559         // Internal recursive function to define Column instances
560         var parseColumns = function(nodeList, oSelf) {
561             // Parse each node at this depth for attributes and any children
562             for(var j=0; j<nodeList.length; j++) {
563                 var currentNode = nodeList[j];
564                 
565                 // Get the Column for each node
566                 var oColumn = oSelf.getColumnById(currentNode.yuiColumnId);
567                 
568                 if(oColumn) {    
569                     // Update the current values
570                     var oDefinition = oColumn.getDefinition();
571                     for(var name in oDefinition) {
572                         if(YAHOO.lang.hasOwnProperty(oDefinition, name)) {
573                             currentNode[name] = oDefinition[name];
574                         }
575                     }
576                 }
577                             
578                 // The Column has descendants
579                 if(YAHOO.lang.isArray(currentNode.children)) {
580                     // The children themselves must also be parsed for Column instances
581                     parseColumns(currentNode.children, oSelf);
582                 }
583             }
584         };
585
586         parseColumns(aDefinitions, this);
587         this._aDefinitions = aDefinitions;
588         return aDefinitions;
589     },
590
591     /**
592      * Returns Column instance with given ID.
593      *
594      * @method getColumnById
595      * @param column {String} Column ID.
596      * @return {YAHOO.widget.Column} Column instance.
597      */
598
599     getColumnById : function(column) {
600         if(YAHOO.lang.isString(column)) {
601             var allColumns = this.flat;
602             for(var i=allColumns.length-1; i>-1; i--) {
603                 if(allColumns[i]._sId === column) {
604                     return allColumns[i];
605                 }
606             }
607         }
608         return null;
609     },
610
611     /**
612      * Returns Column instance with given key or ColumnSet key index.
613      *
614      * @method getColumn
615      * @param column {String | Number} Column key or ColumnSet key index.
616      * @return {YAHOO.widget.Column} Column instance.
617      */
618
619     getColumn : function(column) {
620         if(YAHOO.lang.isNumber(column) && this.keys[column]) {
621             return this.keys[column];
622         }
623         else if(YAHOO.lang.isString(column)) {
624             var allColumns = this.flat;
625             var aColumns = [];
626             for(var i=0; i<allColumns.length; i++) {
627                 if(allColumns[i].key === column) {
628                     aColumns.push(allColumns[i]);
629                 }
630             }
631             if(aColumns.length === 1) {
632                 return aColumns[0];
633             }
634             else if(aColumns.length > 1) {
635                 return aColumns;
636             }
637         }
638         return null;
639     },
640
641     /**
642      * Public accessor returns array of given Column's desendants (if any), including itself.
643      *
644      * @method getDescendants
645      * @parem {YAHOO.widget.Column} Column instance.
646      * @return {Array} Array including the Column itself and all descendants (if any).
647      */
648     getDescendants : function(oColumn) {
649         var oSelf = this;
650         var allDescendants = [];
651         var i;
652
653         // Recursive function to loop thru all children
654         var parse = function(oParent) {
655             allDescendants.push(oParent);
656             // This Column has children
657             if(oParent.children) {
658                 for(i=0; i<oParent.children.length; i++) {
659                     parse(oSelf.getColumn(oParent.children[i].key));
660                 }
661             }
662         };
663         parse(oColumn);
664
665         return allDescendants;
666     }
667 };
668
669 /****************************************************************************/
670 /****************************************************************************/
671 /****************************************************************************/
672
673 /**
674  * The Column class defines and manages attributes of DataTable Columns
675  *
676  * @namespace YAHOO.widget
677  * @class Column
678  * @constructor
679  * @param oConfigs {Object} Object literal of definitions.
680  */
681 YAHOO.widget.Column = function(oConfigs) {
682     this._sId = "yui-col" + YAHOO.widget.Column._nCount;
683     
684     // Object literal defines Column attributes
685     if(oConfigs && YAHOO.lang.isObject(oConfigs)) {
686         for(var sConfig in oConfigs) {
687             if(sConfig) {
688                 this[sConfig] = oConfigs[sConfig];
689             }
690         }
691     }
692
693     // Assign a key if not found
694     if(!YAHOO.lang.isValue(this.key)) {
695         this.key = "yui-dt-col" + YAHOO.widget.Column._nCount;
696     }
697     
698     // Assign a field if not found, defaults to key
699     if(!YAHOO.lang.isValue(this.field)) {
700         this.field = this.key;
701     }
702
703     // Increment counter
704     YAHOO.widget.Column._nCount++;
705
706     // Backward compatibility
707     if(this.width && !YAHOO.lang.isNumber(this.width)) {
708         this.width = null;
709         YAHOO.log("The Column property width must be a number", "warn", this.toString());
710     }
711     if(this.editor && YAHOO.lang.isString(this.editor)) {
712         this.editor = new YAHOO.widget.CellEditor(this.editor, this.editorOptions);
713         YAHOO.log("The Column property editor must be an instance of YAHOO.widget.CellEditor", "warn", this.toString());
714     }
715 };
716
717 /////////////////////////////////////////////////////////////////////////////
718 //
719 // Private member variables
720 //
721 /////////////////////////////////////////////////////////////////////////////
722
723 YAHOO.lang.augmentObject(YAHOO.widget.Column, {
724     /**
725      * Internal class variable to index multiple Column instances.
726      *
727      * @property Column._nCount
728      * @type Number
729      * @private
730      * @static
731      */
732     _nCount : 0,
733
734     formatCheckbox : function(elCell, oRecord, oColumn, oData) {
735         YAHOO.log("The method YAHOO.widget.Column.formatCheckbox() has been" +
736         " deprecated in favor of YAHOO.widget.DataTable.formatCheckbox()", "warn",
737         "YAHOO.widget.Column.formatCheckbox");
738         YAHOO.widget.DataTable.formatCheckbox(elCell, oRecord, oColumn, oData);
739     },
740
741     formatCurrency : function(elCell, oRecord, oColumn, oData) {
742         YAHOO.log("The method YAHOO.widget.Column.formatCurrency() has been" +
743         " deprecated in favor of YAHOO.widget.DataTable.formatCurrency()", "warn",
744         "YAHOO.widget.Column.formatCurrency");
745         YAHOO.widget.DataTable.formatCurrency(elCell, oRecord, oColumn, oData);
746     },
747
748     formatDate : function(elCell, oRecord, oColumn, oData) {
749         YAHOO.log("The method YAHOO.widget.Column.formatDate() has been" +
750         " deprecated in favor of YAHOO.widget.DataTable.formatDate()", "warn",
751         "YAHOO.widget.Column.formatDate");
752         YAHOO.widget.DataTable.formatDate(elCell, oRecord, oColumn, oData);
753     },
754
755     formatEmail : function(elCell, oRecord, oColumn, oData) {
756         YAHOO.log("The method YAHOO.widget.Column.formatEmail() has been" +
757         " deprecated in favor of YAHOO.widget.DataTable.formatEmail()", "warn",
758         "YAHOO.widget.Column.formatEmail");
759         YAHOO.widget.DataTable.formatEmail(elCell, oRecord, oColumn, oData);
760     },
761
762     formatLink : function(elCell, oRecord, oColumn, oData) {
763         YAHOO.log("The method YAHOO.widget.Column.formatLink() has been" +
764         " deprecated in favor of YAHOO.widget.DataTable.formatLink()", "warn",
765         "YAHOO.widget.Column.formatLink");
766         YAHOO.widget.DataTable.formatLink(elCell, oRecord, oColumn, oData);
767     },
768
769     formatNumber : function(elCell, oRecord, oColumn, oData) {
770         YAHOO.log("The method YAHOO.widget.Column.formatNumber() has been" +
771         " deprecated in favor of YAHOO.widget.DataTable.formatNumber()", "warn",
772         "YAHOO.widget.Column.formatNumber");
773         YAHOO.widget.DataTable.formatNumber(elCell, oRecord, oColumn, oData);
774     },
775
776     formatSelect : function(elCell, oRecord, oColumn, oData) {
777         YAHOO.log("The method YAHOO.widget.Column.formatSelect() has been" +
778         " deprecated in favor of YAHOO.widget.DataTable.formatDropdown()", "warn",
779         "YAHOO.widget.Column.formatSelect");
780         YAHOO.widget.DataTable.formatDropdown(elCell, oRecord, oColumn, oData);
781     }
782 });
783
784 YAHOO.widget.Column.prototype = {
785     /**
786      * Unique String identifier assigned at instantiation.
787      *
788      * @property _sId
789      * @type String
790      * @private
791      */
792     _sId : null,
793
794     /**
795      * Reference to Column's current position index within its ColumnSet's keys
796      * array, if applicable. This property only applies to non-nested and bottom-
797      * level child Columns.
798      *
799      * @property _nKeyIndex
800      * @type Number
801      * @private
802      */
803     _nKeyIndex : null,
804
805     /**
806      * Reference to Column's current position index within its ColumnSet's tree
807      * array, if applicable. This property only applies to non-nested and top-
808      * level parent Columns.
809      *
810      * @property _nTreeIndex
811      * @type Number
812      * @private
813      */
814     _nTreeIndex : null,
815
816     /**
817      * Number of table cells the Column spans.
818      *
819      * @property _nColspan
820      * @type Number
821      * @private
822      */
823     _nColspan : 1,
824
825     /**
826      * Number of table rows the Column spans.
827      *
828      * @property _nRowspan
829      * @type Number
830      * @private
831      */
832     _nRowspan : 1,
833
834     /**
835      * Column's parent Column instance, or null.
836      *
837      * @property _oParent
838      * @type YAHOO.widget.Column
839      * @private
840      */
841     _oParent : null,
842
843     /**
844      * The DOM reference to the associated TH element.
845      *
846      * @property _elTh
847      * @type HTMLElement
848      * @private
849      */
850     _elTh : null,
851
852     /**
853      * The DOM reference to the associated TH element's liner DIV element.
854      *
855      * @property _elThLiner
856      * @type HTMLElement
857      * @private
858      */
859     _elThLiner : null,
860
861     /**
862      * The DOM reference to the associated TH element's label SPAN element.
863      *
864      * @property _elThLabel
865      * @type HTMLElement
866      * @private
867      */
868     _elThLabel : null,
869
870     /**
871      * The DOM reference to the associated resizerelement (if any).
872      *
873      * @property _elResizer
874      * @type HTMLElement
875      * @private
876      */
877     _elResizer : null,
878
879     /**
880      * Internal width tracker.
881      *
882      * @property _nWidth
883      * @type Number
884      * @private
885      */
886     _nWidth : null,
887
888     /**
889      * For unreg() purposes, a reference to the Column's DragDrop instance.
890      *
891      * @property _dd
892      * @type YAHOO.util.DragDrop
893      * @private
894      */
895     _dd : null,
896
897     /**
898      * For unreg() purposes, a reference to the Column resizer's DragDrop instance.
899      *
900      * @property _ddResizer
901      * @type YAHOO.util.DragDrop
902      * @private
903      */
904     _ddResizer : null,
905
906     /////////////////////////////////////////////////////////////////////////////
907     //
908     // Public member variables
909     //
910     /////////////////////////////////////////////////////////////////////////////
911
912     /**
913      * Unique name, required.
914      *
915      * @property key
916      * @type String
917      */
918     key : null,
919
920     /**
921      * Associated database field, or null.
922      *
923      * @property field
924      * @type String
925      */
926     field : null,
927
928     /**
929      * Text or HTML for display as Column's label in the TH element.
930      *
931      * @property label
932      * @type String
933      */
934     label : null,
935
936     /**
937      * Column head cell ABBR for accessibility.
938      *
939      * @property abbr
940      * @type String
941      */
942     abbr : null,
943
944     /**
945      * Array of object literals that define children (nested headers) of a Column.
946      *
947      * @property children
948      * @type Object[]
949      */
950     children : null,
951
952     /**
953      * Column width (in pixels).
954      *
955      * @property width
956      * @type Number
957      */
958     width : null,
959
960     /**
961      * Minimum Column width (in pixels).
962      *
963      * @property minWidth
964      * @type Number
965      * @default null
966      */
967     minWidth : null,
968
969     /**
970      * When a width is not defined for a Column, maxAutoWidth defines an upper
971      * limit that the Column should be auto-sized to. If resizeable is enabled, 
972      * users may still resize to a greater width. Most useful for Columns intended
973      * to hold long unbroken, unwrapped Strings, such as URLs, to prevent very
974      * wide Columns from disrupting visual readability by inducing truncation.
975      *
976      * @property maxAutoWidth
977      * @type Number
978      * @default null
979      */
980     maxAutoWidth : null,
981
982     /**
983      * True if Column is in hidden state.
984      *
985      * @property hidden
986      * @type Boolean
987      * @default false     
988      */
989     hidden : false,
990
991     /**
992      * True if Column is in selected state.
993      *
994      * @property selected
995      * @type Boolean
996      * @default false     
997      */
998     selected : false,
999
1000     /**
1001      * Custom CSS class or array of classes to be applied to every cell in the Column.
1002      *
1003      * @property className
1004      * @type String || String[]
1005      */
1006     className : null,
1007
1008     /**
1009      * Defines a format function.
1010      *
1011      * @property formatter
1012      * @type String || HTMLFunction
1013      */
1014     formatter : null,
1015     
1016     /**
1017      * Config passed to YAHOO.util.Number.format() by the 'currency' Column formatter.
1018      *
1019      * @property currencyOptions
1020      * @type Object
1021      * @default null
1022      */
1023     currencyOptions : null,
1024
1025     /**
1026      * Config passed to YAHOO.util.Date.format() by the 'date' Column formatter.
1027      *
1028      * @property dateOptions
1029      * @type Object
1030      * @default null
1031      */
1032     dateOptions : null,
1033
1034     /**
1035      * A CellEditor instance, otherwise Column is not editable.     
1036      *
1037      * @property editor
1038      * @type YAHOO.widget.CellEditor
1039      */
1040     editor : null,
1041
1042     /**
1043      * True if Column is resizeable, false otherwise. The Drag & Drop Utility is
1044      * required to enable this feature. Only bottom-level and non-nested Columns are
1045      * resizeble. 
1046      *
1047      * @property resizeable
1048      * @type Boolean
1049      * @default false
1050      */
1051     resizeable : false,
1052
1053     /**
1054      * True if Column is sortable, false otherwise.
1055      *
1056      * @property sortable
1057      * @type Boolean
1058      * @default false
1059      */
1060     sortable : false,
1061
1062     /**
1063      * @property sortOptions.defaultOrder
1064      * @deprecated Use sortOptions.defaultDir.
1065      */
1066     /**
1067      * Default sort direction for Column: YAHOO.widget.DataTable.CLASS_ASC or YAHOO.widget.DataTable.CLASS_DESC.
1068      *
1069      * @property sortOptions.defaultDir
1070      * @type String
1071      * @default null
1072      */
1073     /**
1074      * Custom field to sort on.
1075      *
1076      * @property sortOptions.field
1077      * @type String
1078      * @default null
1079      */
1080     /**
1081      * Custom sort handler.
1082      *
1083      * @property sortOptions.sortFunction
1084      * @type Function
1085      * @default null
1086      */
1087     sortOptions : null,
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103     /////////////////////////////////////////////////////////////////////////////
1104     //
1105     // Public methods
1106     //
1107     /////////////////////////////////////////////////////////////////////////////
1108
1109     /**
1110      * Returns unique ID string.
1111      *
1112      * @method getId
1113      * @return {String} Unique ID string.
1114      */
1115     getId : function() {
1116         return this._sId;
1117     },
1118
1119     /**
1120      * Column instance name, for logging.
1121      *
1122      * @method toString
1123      * @return {String} Column's unique name.
1124      */
1125     toString : function() {
1126         return "Column instance " + this._sId;
1127     },
1128
1129     /**
1130      * Returns object literal definition.
1131      *
1132      * @method getDefinition
1133      * @return {Object} Object literal definition.
1134      */
1135     getDefinition : function() {
1136         var oDefinition = {};
1137         
1138         // Update the definition
1139         oDefinition.abbr = this.abbr;
1140         oDefinition.className = this.className;
1141         oDefinition.editor = this.editor;
1142         oDefinition.editorOptions = this.editorOptions; //TODO: deprecated
1143         oDefinition.field = this.field;
1144         oDefinition.formatter = this.formatter;
1145         oDefinition.hidden = this.hidden;
1146         oDefinition.key = this.key;
1147         oDefinition.label = this.label;
1148         oDefinition.minWidth = this.minWidth;
1149         oDefinition.maxAutoWidth = this.maxAutoWidth;
1150         oDefinition.resizeable = this.resizeable;
1151         oDefinition.selected = this.selected;
1152         oDefinition.sortable = this.sortable;
1153         oDefinition.sortOptions = this.sortOptions;
1154         oDefinition.width = this.width;
1155
1156         return oDefinition;
1157     },
1158
1159     /**
1160      * Returns unique Column key.
1161      *
1162      * @method getKey
1163      * @return {String} Column key.
1164      */
1165     getKey : function() {
1166         return this.key;
1167     },
1168     
1169     /**
1170      * Returns field.
1171      *
1172      * @method getField
1173      * @return {String} Column field.
1174      */
1175     getField : function() {
1176         return this.field;
1177     },
1178     
1179     /**
1180      * Returns Column key which has been sanitized for DOM (class and ID) usage
1181      * starts with letter, contains only letters, numbers, hyphen, or period.
1182      *
1183      * @method getSanitizedKey
1184      * @return {String} Sanitized Column key.
1185      */
1186     getSanitizedKey : function() {
1187         return this.getKey().replace(/[^\w\-]/g,"");
1188     },
1189
1190     /**
1191      * Public accessor returns Column's current position index within its
1192      * ColumnSet's keys array, if applicable. Only non-nested and bottom-level
1193      * child Columns will return a value.
1194      *
1195      * @method getKeyIndex
1196      * @return {Number} Position index, or null.
1197      */
1198     getKeyIndex : function() {
1199         return this._nKeyIndex;
1200     },
1201
1202     /**
1203      * Public accessor returns Column's current position index within its
1204      * ColumnSet's tree array, if applicable. Only non-nested and top-level parent
1205      * Columns will return a value;
1206      *
1207      * @method getTreeIndex
1208      * @return {Number} Position index, or null.
1209      */
1210     getTreeIndex : function() {
1211         return this._nTreeIndex;
1212     },
1213
1214     /**
1215      * Public accessor returns Column's parent instance if any, or null otherwise.
1216      *
1217      * @method getParent
1218      * @return {YAHOO.widget.Column} Column's parent instance.
1219      */
1220     getParent : function() {
1221         return this._oParent;
1222     },
1223
1224     /**
1225      * Public accessor returns Column's calculated COLSPAN value.
1226      *
1227      * @method getColspan
1228      * @return {Number} Column's COLSPAN value.
1229      */
1230     getColspan : function() {
1231         return this._nColspan;
1232     },
1233     // Backward compatibility
1234     getColSpan : function() {
1235         YAHOO.log("The method getColSpan() has been" +
1236         " deprecated in favor of getColspan()", "warn", this.toString());
1237         return this.getColspan();
1238     },
1239
1240     /**
1241      * Public accessor returns Column's calculated ROWSPAN value.
1242      *
1243      * @method getRowspan
1244      * @return {Number} Column's ROWSPAN value.
1245      */
1246     getRowspan : function() {
1247         return this._nRowspan;
1248     },
1249
1250     /**
1251      * Returns DOM reference to the key TH element.
1252      *
1253      * @method getThEl
1254      * @return {HTMLElement} TH element.
1255      */
1256     getThEl : function() {
1257         return this._elTh;
1258     },
1259
1260     /**
1261      * Returns DOM reference to the TH's liner DIV element. Introduced since
1262      * resizeable Columns may have an extra resizer liner, making the DIV liner
1263      * not reliably the TH element's first child.               
1264      *
1265      * @method getThLInerEl
1266      * @return {HTMLElement} TH element.
1267      */
1268     getThLinerEl : function() {
1269         return this._elThLiner;
1270     },
1271     
1272     /**
1273      * Returns DOM reference to the resizer element, or null.
1274      *
1275      * @method getResizerEl
1276      * @return {HTMLElement} DIV element.
1277      */
1278     getResizerEl : function() {
1279         return this._elResizer;
1280     },
1281
1282     // Backward compatibility
1283     /**
1284      * @method getColEl
1285      * @deprecated Use getThEl
1286      */
1287     getColEl : function() {
1288         YAHOO.log("The method getColEl() has been" +
1289         " deprecated in favor of getThEl()", "warn",
1290         this.toString());
1291         return this.getThEl();
1292     },
1293     getIndex : function() {
1294         YAHOO.log("The method getIndex() has been" +
1295         " deprecated in favor of getKeyIndex()", "warn",
1296         this.toString());
1297         return this.getKeyIndex();
1298     },
1299     format : function() {
1300         YAHOO.log("The method format() has been deprecated in favor of the " +
1301         "DataTable method formatCell()", "error", this.toString());
1302     }
1303 };
1304
1305 /****************************************************************************/
1306 /****************************************************************************/
1307 /****************************************************************************/
1308
1309 /**
1310  * Sort static utility to support Column sorting.
1311  *
1312  * @namespace YAHOO.util
1313  * @class Sort
1314  * @static
1315  */
1316 YAHOO.util.Sort = {
1317     /////////////////////////////////////////////////////////////////////////////
1318     //
1319     // Public methods
1320     //
1321     /////////////////////////////////////////////////////////////////////////////
1322
1323     /**
1324      * Comparator function for simple case-insensitive string sorting.
1325      *
1326      * @method compare
1327      * @param a {Object} First sort argument.
1328      * @param b {Object} Second sort argument.
1329      * @param desc {Boolean} True if sort direction is descending, false if
1330      * sort direction is ascending.
1331      */
1332     compare: function(a, b, desc) {
1333         if((a === null) || (typeof a == "undefined")) {
1334             if((b === null) || (typeof b == "undefined")) {
1335                 return 0;
1336             }
1337             else {
1338                 return 1;
1339             }
1340         }
1341         else if((b === null) || (typeof b == "undefined")) {
1342             return -1;
1343         }
1344
1345         if(a.constructor == String) {
1346             a = a.toLowerCase();
1347         }
1348         if(b.constructor == String) {
1349             b = b.toLowerCase();
1350         }
1351         if(a < b) {
1352             return (desc) ? 1 : -1;
1353         }
1354         else if (a > b) {
1355             return (desc) ? -1 : 1;
1356         }
1357         else {
1358             return 0;
1359         }
1360     }
1361 };
1362
1363 /****************************************************************************/
1364 /****************************************************************************/
1365 /****************************************************************************/
1366
1367 /**
1368  * ColumnDD subclasses DragDrop to support rearrangeable Columns.
1369  *
1370  * @namespace YAHOO.util
1371  * @class ColumnDD
1372  * @extends YAHOO.util.DDProxy
1373  * @constructor
1374  * @param oDataTable {YAHOO.widget.DataTable} DataTable instance.
1375  * @param oColumn {YAHOO.widget.Column} Column instance.
1376  * @param elTh {HTMLElement} TH element reference.
1377  * @param elTarget {HTMLElement} Drag target element.
1378  */
1379 YAHOO.widget.ColumnDD = function(oDataTable, oColumn, elTh, elTarget) {
1380     if(oDataTable && oColumn && elTh && elTarget) {
1381         this.datatable = oDataTable;
1382         this.table = oDataTable.getTableEl();
1383         this.column = oColumn;
1384         this.headCell = elTh;
1385         this.pointer = elTarget;
1386         this.newIndex = null;
1387         this.init(elTh);
1388         this.initFrame(); // Needed for DDProxy
1389         this.invalidHandleTypes = {};
1390
1391         // Set top/bottom padding to account for children of nested columns
1392         this.setPadding(10, 0, (this.datatable.getTheadEl().offsetHeight + 10) , 0);
1393
1394         YAHOO.util.Event.on(window, 'resize', function() {
1395             this.initConstraints();
1396         }, this, true);
1397     }
1398     else {
1399         YAHOO.log("Column dragdrop could not be created","warn",oDataTable.toString());
1400     }
1401 };
1402
1403 if(YAHOO.util.DDProxy) {
1404     YAHOO.extend(YAHOO.widget.ColumnDD, YAHOO.util.DDProxy, {
1405         initConstraints: function() {
1406             //Get the top, right, bottom and left positions
1407             var region = YAHOO.util.Dom.getRegion(this.table),
1408                 //Get the element we are working on
1409                 el = this.getEl(),
1410                 //Get the xy position of it
1411                 xy = YAHOO.util.Dom.getXY(el),
1412                 //Get the width and height
1413                 width = parseInt(YAHOO.util.Dom.getStyle(el, 'width'), 10),
1414                 height = parseInt(YAHOO.util.Dom.getStyle(el, 'height'), 10),
1415                 //Set left to x minus left
1416                 left = ((xy[0] - region.left) + 15), //Buffer of 15px
1417                 //Set right to right minus x minus width
1418                 right = ((region.right - xy[0] - width) + 15);
1419     
1420             //Set the constraints based on the above calculations
1421             this.setXConstraint(left, right);
1422             this.setYConstraint(10, 10);            
1423         },
1424         _resizeProxy: function() {
1425             this.constructor.superclass._resizeProxy.apply(this, arguments);
1426             var dragEl = this.getDragEl(),
1427                 el = this.getEl();
1428
1429             YAHOO.util.Dom.setStyle(this.pointer, 'height', (this.table.parentNode.offsetHeight + 10) + 'px');
1430             YAHOO.util.Dom.setStyle(this.pointer, 'display', 'block');
1431             var xy = YAHOO.util.Dom.getXY(el);
1432             YAHOO.util.Dom.setXY(this.pointer, [xy[0], (xy[1] - 5)]);
1433             
1434             YAHOO.util.Dom.setStyle(dragEl, 'height', this.datatable.getContainerEl().offsetHeight + "px");
1435             YAHOO.util.Dom.setStyle(dragEl, 'width', (parseInt(YAHOO.util.Dom.getStyle(dragEl, 'width'),10) + 4) + 'px');
1436             YAHOO.util.Dom.setXY(this.dragEl, xy);
1437         },
1438         onMouseDown: function() {
1439                 this.initConstraints();
1440                 this.resetConstraints();
1441         },
1442         clickValidator: function(e) {
1443             if(!this.column.hidden) {
1444                 var target = YAHOO.util.Event.getTarget(e);
1445                 return ( this.isValidHandleChild(target) &&
1446                             (this.id == this.handleElId ||
1447                                 this.DDM.handleWasClicked(target, this.id)) );
1448             }
1449         },
1450         onDragOver: function(ev, id) {
1451             // Validate target as a Column
1452             var target = this.datatable.getColumn(id);
1453             if(target) {                
1454                 // Validate target as a top-level parent
1455                 var targetIndex = target.getTreeIndex();
1456                 while((targetIndex === null) && target.getParent()) {
1457                     target = target.getParent();
1458                     targetIndex = target.getTreeIndex();
1459                 }
1460                 if(targetIndex !== null) {
1461                     // Are we placing to left or right of target?
1462                     var elTarget = target.getThEl();
1463                     var newIndex = targetIndex;
1464                     var mouseX = YAHOO.util.Event.getPageX(ev),
1465                         targetX = YAHOO.util.Dom.getX(elTarget),
1466                         midX = targetX + ((YAHOO.util.Dom.get(elTarget).offsetWidth)/2),
1467                         currentIndex =  this.column.getTreeIndex();
1468                     
1469                     if (mouseX < midX) {
1470                        YAHOO.util.Dom.setX(this.pointer, targetX);
1471                     } else {
1472                         var targetWidth = parseInt(elTarget.offsetWidth, 10);
1473                         YAHOO.util.Dom.setX(this.pointer, (targetX + targetWidth));
1474                         newIndex++;
1475                     }
1476                     if (targetIndex > currentIndex) {
1477                         newIndex--;
1478                     }
1479                     if(newIndex < 0) {
1480                         newIndex = 0;
1481                     }
1482                     else if(newIndex > this.datatable.getColumnSet().tree[0].length) {
1483                         newIndex = this.datatable.getColumnSet().tree[0].length;
1484                     }
1485                     this.newIndex = newIndex;
1486                 }
1487             }
1488         },
1489         onDragDrop: function() {
1490             this.datatable.reorderColumn(this.column, this.newIndex);
1491         },
1492         endDrag: function() {
1493             this.newIndex = null;
1494             YAHOO.util.Dom.setStyle(this.pointer, 'display', 'none');
1495         }
1496     });
1497 }
1498
1499 /****************************************************************************/
1500 /****************************************************************************/
1501 /****************************************************************************/
1502
1503 /**
1504  * ColumnResizer subclasses DragDrop to support resizeable Columns.
1505  *
1506  * @namespace YAHOO.util
1507  * @class ColumnResizer
1508  * @extends YAHOO.util.DDProxy
1509  * @constructor
1510  * @param oDataTable {YAHOO.widget.DataTable} DataTable instance.
1511  * @param oColumn {YAHOO.widget.Column} Column instance.
1512  * @param elTh {HTMLElement} TH element reference.
1513  * @param sHandleElId {String} DOM ID of the handle element that causes the resize.
1514  * @param elProxy {HTMLElement} Resizer proxy element.
1515  */
1516 YAHOO.util.ColumnResizer = function(oDataTable, oColumn, elTh, sHandleId, elProxy) {
1517     if(oDataTable && oColumn && elTh && sHandleId) {
1518         this.datatable = oDataTable;
1519         this.column = oColumn;
1520         this.headCell = elTh;
1521         this.headCellLiner = oColumn.getThLinerEl();
1522         this.resizerLiner = elTh.firstChild;
1523         this.init(sHandleId, sHandleId, {dragOnly:true, dragElId: elProxy.id});
1524         this.initFrame(); // Needed for proxy
1525         this.resetResizerEl(); // Needed when rowspan > 0
1526
1527         // Set right padding for bug 1858462
1528         this.setPadding(0, 1, 0, 0);
1529     }
1530     else {
1531         YAHOO.log("Column resizer could not be created","warn",oDataTable.toString());
1532     }
1533 };
1534
1535 if(YAHOO.util.DD) {
1536     YAHOO.extend(YAHOO.util.ColumnResizer, YAHOO.util.DDProxy, {
1537         /////////////////////////////////////////////////////////////////////////////
1538         //
1539         // Public methods
1540         //
1541         /////////////////////////////////////////////////////////////////////////////
1542         /**
1543          * Resets resizer element.
1544          *
1545          * @method resetResizerEl
1546          */
1547         resetResizerEl : function() {
1548             var resizerStyle = YAHOO.util.Dom.get(this.handleElId).style;
1549             resizerStyle.left = "auto";
1550             resizerStyle.right = 0;
1551             resizerStyle.top = "auto";
1552             resizerStyle.bottom = 0;
1553             resizerStyle.height = this.headCell.offsetHeight+"px";
1554         },
1555     
1556         /////////////////////////////////////////////////////////////////////////////
1557         //
1558         // Public DOM event handlers
1559         //
1560         /////////////////////////////////////////////////////////////////////////////
1561     
1562         /**
1563          * Handles mouseup events on the Column resizer.
1564          *
1565          * @method onMouseUp
1566          * @param e {string} The mouseup event
1567          */
1568         onMouseUp : function(e) {
1569             // Reset height of all resizer els in case TH's have changed height
1570             var allKeys = this.datatable.getColumnSet().keys,
1571                 col;
1572             for(var i=0, len=allKeys.length; i<len; i++) {
1573                 col = allKeys[i];
1574                 if(col._ddResizer) {
1575                     col._ddResizer.resetResizerEl();
1576                 }
1577             }
1578             this.resetResizerEl();
1579             
1580             var el = this.headCellLiner;
1581             var newWidth = el.offsetWidth -
1582                 (parseInt(YAHOO.util.Dom.getStyle(el,"paddingLeft"),10)|0) -
1583                 (parseInt(YAHOO.util.Dom.getStyle(el,"paddingRight"),10)|0);
1584
1585             this.datatable.fireEvent("columnResizeEvent", {column:this.column,target:this.headCell,width:newWidth});
1586         },
1587     
1588         /**
1589          * Handles mousedown events on the Column resizer.
1590          *
1591          * @method onMouseDown
1592          * @param e {string} The mousedown event
1593          */
1594         onMouseDown : function(e) {
1595             this.startWidth = this.headCellLiner.offsetWidth;
1596             this.startX = YAHOO.util.Event.getXY(e)[0];
1597             this.nLinerPadding = (parseInt(YAHOO.util.Dom.getStyle(this.headCellLiner,"paddingLeft"),10)|0) +
1598                     (parseInt(YAHOO.util.Dom.getStyle(this.headCellLiner,"paddingRight"),10)|0);
1599         },
1600     
1601         /**
1602          * Custom clickValidator to ensure Column is not in hidden state.
1603          *
1604          * @method clickValidator
1605          * @param {Event} e
1606          * @private
1607          */
1608         clickValidator : function(e) {
1609             if(!this.column.hidden) {
1610                 var target = YAHOO.util.Event.getTarget(e);
1611                 return ( this.isValidHandleChild(target) &&
1612                             (this.id == this.handleElId ||
1613                                 this.DDM.handleWasClicked(target, this.id)) );
1614             }
1615         },
1616     
1617         /**
1618          * Handles start drag on the Column resizer.
1619          *
1620          * @method startDrag
1621          * @param e {string} The drag event
1622          */
1623         startDrag : function() {
1624             // Shrinks height of all resizer els to not hold open TH els
1625             var allKeys = this.datatable.getColumnSet().keys,
1626                 thisKey = this.column.getKeyIndex(),
1627                 col;
1628             for(var i=0, len=allKeys.length; i<len; i++) {
1629                 col = allKeys[i];
1630                 if(col._ddResizer) {
1631                     YAHOO.util.Dom.get(col._ddResizer.handleElId).style.height = "1em";
1632                 }
1633             }
1634         },
1635
1636         /**
1637          * Handles drag events on the Column resizer.
1638          *
1639          * @method onDrag
1640          * @param e {string} The drag event
1641          */
1642         onDrag : function(e) {
1643             var newX = YAHOO.util.Event.getXY(e)[0];
1644             if(newX > YAHOO.util.Dom.getX(this.headCellLiner)) {
1645                 var offsetX = newX - this.startX;
1646                 var newWidth = this.startWidth + offsetX - this.nLinerPadding;
1647                 if(newWidth > 0) {
1648                     this.datatable.setColumnWidth(this.column, newWidth);
1649                 }
1650             }
1651         }
1652     });
1653 }
1654
1655 /////////////////////////////////////////////////////////////////////////////
1656 //
1657 // Deprecated
1658 //
1659 /////////////////////////////////////////////////////////////////////////////
1660
1661 /**
1662  * @property editorOptions
1663  * @deprecated Pass configs directly to CellEditor constructor. 
1664  */
1665
1666
1667 (function () {
1668
1669 var lang   = YAHOO.lang,
1670     util   = YAHOO.util,
1671     widget = YAHOO.widget,
1672     
1673     Dom    = util.Dom,
1674     Ev     = util.Event,
1675     DT     = widget.DataTable;
1676
1677 /****************************************************************************/
1678 /****************************************************************************/
1679 /****************************************************************************/
1680
1681 /**
1682  * A RecordSet defines and manages a set of Records.
1683  *
1684  * @namespace YAHOO.widget
1685  * @class RecordSet
1686  * @param data {Object || Object[]} An object literal or an array of data.
1687  * @constructor
1688  */
1689 YAHOO.widget.RecordSet = function(data) {
1690     // Internal variables
1691     this._sId = "yui-rs" + widget.RecordSet._nCount;
1692     widget.RecordSet._nCount++;
1693     this._records = [];
1694     //this._length = 0;
1695
1696     if(data) {
1697         if(lang.isArray(data)) {
1698             this.addRecords(data);
1699         }
1700         else if(lang.isObject(data)) {
1701             this.addRecord(data);
1702         }
1703     }
1704
1705     YAHOO.log("RecordSet initialized", "info", this.toString());
1706 };
1707
1708 var RS = widget.RecordSet;
1709
1710 /**
1711  * Internal class variable to name multiple Recordset instances.
1712  *
1713  * @property RecordSet._nCount
1714  * @type Number
1715  * @private
1716  * @static
1717  */
1718 RS._nCount = 0;
1719
1720 RS.prototype = {
1721
1722     /////////////////////////////////////////////////////////////////////////////
1723     //
1724     // Private member variables
1725     //
1726     /////////////////////////////////////////////////////////////////////////////
1727     /**
1728      * Unique String identifier assigned at instantiation.
1729      *
1730      * @property _sId
1731      * @type String
1732      * @private
1733      */
1734     _sId : null,
1735
1736     /**
1737      * Internal counter of how many Records are in the RecordSet.
1738      *
1739      * @property _length
1740      * @type Number
1741      * @private
1742      * @deprecated No longer used
1743      */
1744     //_length : null,
1745
1746     /////////////////////////////////////////////////////////////////////////////
1747     //
1748     // Private methods
1749     //
1750     /////////////////////////////////////////////////////////////////////////////
1751
1752     /**
1753      * Adds one Record to the RecordSet at the given index. If index is null,
1754      * then adds the Record to the end of the RecordSet.
1755      *
1756      * @method _addRecord
1757      * @param oData {Object} An object literal of data.
1758      * @param index {Number} (optional) Position index.
1759      * @return {YAHOO.widget.Record} A Record instance.
1760      * @private
1761      */
1762     _addRecord : function(oData, index) {
1763         var oRecord = new YAHOO.widget.Record(oData);
1764         
1765         if(YAHOO.lang.isNumber(index) && (index > -1)) {
1766             this._records.splice(index,0,oRecord);
1767         }
1768         else {
1769             //index = this.getLength();
1770             //this._records[index] = oRecord;
1771             this._records[this._records.length] = oRecord;
1772         }
1773         //this._length++;
1774         return oRecord;
1775     },
1776
1777     /**
1778      * Sets/replaces one Record to the RecordSet at the given index.  Existing
1779      * Records with higher indexes are not shifted.  If no index specified, the
1780      * Record is added to the end of the RecordSet.
1781      *
1782      * @method _setRecord
1783      * @param oData {Object} An object literal of data.
1784      * @param index {Number} (optional) Position index.
1785      * @return {YAHOO.widget.Record} A Record instance.
1786      * @private
1787      */
1788     _setRecord : function(oData, index) {
1789         if (!lang.isNumber(index) || index < 0) {
1790             index = this._records.length;
1791         }
1792         return (this._records[index] = new widget.Record(oData));
1793         /*
1794         if(lang.isNumber(index) && (index > -1)) {
1795             this._records[index] = oRecord;
1796             if((index+1) > this.getLength()) {
1797                 this._length = index+1;
1798             }
1799         }
1800         else {
1801             this._records[this.getLength()] = oRecord;
1802             this._length++;
1803         }
1804         return oRecord;
1805         */
1806     },
1807
1808     /**
1809      * Deletes Records from the RecordSet at the given index. If range is null,
1810      * then only one Record is deleted.
1811      *
1812      * @method _deleteRecord
1813      * @param index {Number} Position index.
1814      * @param range {Number} (optional) How many Records to delete
1815      * @private
1816      */
1817     _deleteRecord : function(index, range) {
1818         if(!lang.isNumber(range) || (range < 0)) {
1819             range = 1;
1820         }
1821         this._records.splice(index, range);
1822         //this._length = this._length - range;
1823     },
1824
1825     /////////////////////////////////////////////////////////////////////////////
1826     //
1827     // Public methods
1828     //
1829     /////////////////////////////////////////////////////////////////////////////
1830
1831     /**
1832      * Returns unique name of the RecordSet instance.
1833      *
1834      * @method getId
1835      * @return {String} Unique name of the RecordSet instance.
1836      */
1837     getId : function() {
1838         return this._sId;
1839     },
1840
1841     /**
1842      * Public accessor to the unique name of the RecordSet instance.
1843      *
1844      * @method toString
1845      * @return {String} Unique name of the RecordSet instance.
1846      */
1847     toString : function() {
1848         return "RecordSet instance " + this._sId;
1849     },
1850
1851     /**
1852      * Returns the number of Records held in the RecordSet.
1853      *
1854      * @method getLength
1855      * @return {Number} Number of records in the RecordSet.
1856      */
1857     getLength : function() {
1858             //return this._length;
1859             return this._records.length;
1860     },
1861
1862     /**
1863      * Returns Record by ID or RecordSet position index.
1864      *
1865      * @method getRecord
1866      * @param record {YAHOO.widget.Record | Number | String} Record instance,
1867      * RecordSet position index, or Record ID.
1868      * @return {YAHOO.widget.Record} Record object.
1869      */
1870     getRecord : function(record) {
1871         var i;
1872         if(record instanceof widget.Record) {
1873             for(i=0; i<this._records.length; i++) {
1874                 if(this._records[i] && (this._records[i]._sId === record._sId)) {
1875                     return record;
1876                 }
1877             }
1878         }
1879         else if(lang.isNumber(record)) {
1880             if((record > -1) && (record < this.getLength())) {
1881                 return this._records[record];
1882             }
1883         }
1884         else if(lang.isString(record)) {
1885             for(i=0; i<this._records.length; i++) {
1886                 if(this._records[i] && (this._records[i]._sId === record)) {
1887                     return this._records[i];
1888                 }
1889             }
1890         }
1891         // Not a valid Record for this RecordSet
1892         return null;
1893
1894     },
1895
1896     /**
1897      * Returns an array of Records from the RecordSet.
1898      *
1899      * @method getRecords
1900      * @param index {Number} (optional) Recordset position index of which Record to
1901      * start at.
1902      * @param range {Number} (optional) Number of Records to get.
1903      * @return {YAHOO.widget.Record[]} Array of Records starting at given index and
1904      * length equal to given range. If index is not given, all Records are returned.
1905      */
1906     getRecords : function(index, range) {
1907         if(!lang.isNumber(index)) {
1908             return this._records;
1909         }
1910         if(!lang.isNumber(range)) {
1911             return this._records.slice(index);
1912         }
1913         return this._records.slice(index, index+range);
1914     },
1915
1916     /**
1917      * Returns a boolean indicating whether Records exist in the RecordSet at the
1918      * specified index range.  Returns true if and only if a Record exists at each
1919      * index in the range.
1920      * @method hasRecords
1921      * @param index
1922      * @param range
1923      * @return {Boolean} true if all indices are populated in the RecordSet
1924      */
1925     hasRecords : function (index, range) {
1926         var recs = this.getRecords(index,range);
1927         for (var i = 0; i < range; ++i) {
1928             if (typeof recs[i] === 'undefined') {
1929                 return false;
1930             }
1931         }
1932         return true;
1933     },
1934
1935     /**
1936      * Returns current position index for the given Record.
1937      *
1938      * @method getRecordIndex
1939      * @param oRecord {YAHOO.widget.Record} Record instance.
1940      * @return {Number} Record's RecordSet position index.
1941      */
1942
1943     getRecordIndex : function(oRecord) {
1944         if(oRecord) {
1945             for(var i=this._records.length-1; i>-1; i--) {
1946                 if(this._records[i] && oRecord.getId() === this._records[i].getId()) {
1947                     return i;
1948                 }
1949             }
1950         }
1951         return null;
1952
1953     },
1954
1955     /**
1956      * Adds one Record to the RecordSet at the given index. If index is null,
1957      * then adds the Record to the end of the RecordSet.
1958      *
1959      * @method addRecord
1960      * @param oData {Object} An object literal of data.
1961      * @param index {Number} (optional) Position index.
1962      * @return {YAHOO.widget.Record} A Record instance.
1963      */
1964     addRecord : function(oData, index) {
1965         if(lang.isObject(oData)) {
1966             var oRecord = this._addRecord(oData, index);
1967             this.fireEvent("recordAddEvent",{record:oRecord,data:oData});
1968             YAHOO.log("Added Record at index " + index +
1969                     " with data " + lang.dump(oData), "info", this.toString());
1970             return oRecord;
1971         }
1972         else {
1973             YAHOO.log("Could not add Record with data" +
1974                     lang.dump(oData), "info", this.toString());
1975             return null;
1976         }
1977     },
1978
1979     /**
1980      * Adds multiple Records at once to the RecordSet at the given index with the
1981      * given object literal data. If index is null, then the new Records are
1982      * added to the end of the RecordSet.
1983      *
1984      * @method addRecords
1985      * @param aData {Object[]} An object literal data or an array of data object literals.
1986      * @param index {Number} (optional) Position index.
1987      * @return {YAHOO.widget.Record[]} An array of Record instances.
1988      */
1989     addRecords : function(aData, index) {
1990         if(lang.isArray(aData)) {
1991             var newRecords = [],
1992                 idx,i,len;
1993
1994             index = lang.isNumber(index) ? index : this._records.length;
1995             idx = index;
1996
1997             // Can't go backwards bc we need to preserve order
1998             for(i=0,len=aData.length; i<len; ++i) {
1999                 if(lang.isObject(aData[i])) {
2000                     var record = this._addRecord(aData[i], idx++);
2001                     newRecords.push(record);
2002                 }
2003            }
2004             this.fireEvent("recordsAddEvent",{records:newRecords,data:aData});
2005             YAHOO.log("Added " + newRecords.length + " Record(s) at index " + index +
2006                     " with data " + lang.dump(aData), "info", this.toString());
2007            return newRecords;
2008         }
2009         else if(lang.isObject(aData)) {
2010             var oRecord = this._addRecord(aData);
2011             this.fireEvent("recordsAddEvent",{records:[oRecord],data:aData});
2012             YAHOO.log("Added 1 Record at index " + index +
2013                     " with data " + lang.dump(aData), "info", this.toString());
2014             return oRecord;
2015         }
2016         else {
2017             YAHOO.log("Could not add Records with data " +
2018                     lang.dump(aData), "info", this.toString());
2019             return null;
2020         }
2021     },
2022
2023     /**
2024      * Sets or replaces one Record to the RecordSet at the given index. Unlike
2025      * addRecord, an existing Record at that index is not shifted to preserve it.
2026      * If no index is specified, it adds the Record to the end of the RecordSet.
2027      *
2028      * @method setRecord
2029      * @param oData {Object} An object literal of data.
2030      * @param index {Number} (optional) Position index.
2031      * @return {YAHOO.widget.Record} A Record instance.
2032      */
2033     setRecord : function(oData, index) {
2034         if(lang.isObject(oData)) {
2035             var oRecord = this._setRecord(oData, index);
2036             this.fireEvent("recordSetEvent",{record:oRecord,data:oData});
2037             YAHOO.log("Set Record at index " + index +
2038                     " with data " + lang.dump(oData), "info", this.toString());
2039             return oRecord;
2040         }
2041         else {
2042             YAHOO.log("Could not set Record with data" +
2043                     lang.dump(oData), "info", this.toString());
2044             return null;
2045         }
2046     },
2047
2048     /**
2049      * Sets or replaces multiple Records at once to the RecordSet with the given
2050      * data, starting at the given index. If index is not specified, then the new
2051      * Records are added to the end of the RecordSet.
2052      *
2053      * @method setRecords
2054      * @param aData {Object[]} An array of object literal data.
2055      * @param index {Number} (optional) Position index.
2056      * @return {YAHOO.widget.Record[]} An array of Record instances.
2057      */
2058     setRecords : function(aData, index) {
2059         var Rec   = widget.Record,
2060             a     = lang.isArray(aData) ? aData : [aData],
2061             added = [],
2062             i = 0, l = a.length, j = 0;
2063
2064         index = parseInt(index,10)|0;
2065
2066         for(; i < l; ++i) {
2067             if (typeof a[i] === 'object' && a[i]) {
2068                 added[j++] = this._records[index + i] = new Rec(a[i]);
2069             }
2070         }
2071
2072         this.fireEvent("recordsSetEvent",{records:added,data:aData});
2073         // Backward compatibility for bug 1918245
2074         this.fireEvent("recordsSet",{records:added,data:aData});
2075         YAHOO.log("Set "+j+" Record(s) at index "+index, "info",
2076                   this.toString());
2077
2078         if (a.length && !added.length) {
2079             YAHOO.log("Could not set Records with data " +
2080                     lang.dump(aData), "info", this.toString());
2081         }
2082
2083         return added.length > 1 ? added : added[0];
2084     },
2085
2086     /**
2087      * Updates given Record with given data.
2088      *
2089      * @method updateRecord
2090      * @param record {YAHOO.widget.Record | Number | String} A Record instance,
2091      * a RecordSet position index, or a Record ID.
2092      * @param oData {Object} Object literal of new data.
2093      * @return {YAHOO.widget.Record} Updated Record, or null.
2094      */
2095     updateRecord : function(record, oData) {
2096         var oRecord = this.getRecord(record);
2097         if(oRecord && lang.isObject(oData)) {
2098             // Copy data from the Record for the event that gets fired later
2099             var oldData = {};
2100             for(var key in oRecord._oData) {
2101                 if(lang.hasOwnProperty(oRecord._oData, key)) {
2102                     oldData[key] = oRecord._oData[key];
2103                 }
2104             }
2105             oRecord._oData = oData;
2106             this.fireEvent("recordUpdateEvent",{record:oRecord,newData:oData,oldData:oldData});
2107             YAHOO.log("Record at index " + this.getRecordIndex(oRecord) +
2108                     " updated with data " + lang.dump(oData), "info", this.toString());
2109             return oRecord;
2110         }
2111         else {
2112             YAHOO.log("Could not update Record " + record, "error", this.toString());
2113             return null;
2114         }
2115     },
2116
2117     /**
2118      * @method updateKey
2119      * @deprecated Use updateRecordValue
2120      */
2121     updateKey : function(record, sKey, oData) {
2122         this.updateRecordValue(record, sKey, oData);
2123     },
2124     /**
2125      * Sets given Record at given key to given data.
2126      *
2127      * @method updateRecordValue
2128      * @param record {YAHOO.widget.Record | Number | String} A Record instance,
2129      * a RecordSet position index, or a Record ID.
2130      * @param sKey {String} Key name.
2131      * @param oData {Object} New data.
2132      */
2133     updateRecordValue : function(record, sKey, oData) {
2134         var oRecord = this.getRecord(record);
2135         if(oRecord) {
2136             var oldData = null;
2137             var keyValue = oRecord._oData[sKey];
2138             // Copy data from the Record for the event that gets fired later
2139             if(keyValue && lang.isObject(keyValue)) {
2140                 oldData = {};
2141                 for(var key in keyValue)  {
2142                     if(lang.hasOwnProperty(keyValue, key)) {
2143                         oldData[key] = keyValue[key];
2144                     }
2145                 }
2146             }
2147             // Copy by value
2148             else {
2149                 oldData = keyValue;
2150             }
2151
2152             oRecord._oData[sKey] = oData;
2153             this.fireEvent("keyUpdateEvent",{record:oRecord,key:sKey,newData:oData,oldData:oldData});
2154             this.fireEvent("recordValueUpdateEvent",{record:oRecord,key:sKey,newData:oData,oldData:oldData});
2155             YAHOO.log("Key \"" + sKey +
2156                     "\" for Record at index " + this.getRecordIndex(oRecord) +
2157                     " updated to \"" + lang.dump(oData) + "\"", "info", this.toString());
2158         }
2159         else {
2160             YAHOO.log("Could not update key " + sKey + " for Record " + record, "error", this.toString());
2161         }
2162     },
2163
2164     /**
2165      * Replaces all Records in RecordSet with new object literal data.
2166      *
2167      * @method replaceRecords
2168      * @param data {Object || Object[]} An object literal of data or an array of
2169      * data object literals.
2170      * @return {YAHOO.widget.Record || YAHOO.widget.Record[]} A Record instance or
2171      * an array of Records.
2172      */
2173     replaceRecords : function(data) {
2174         this.reset();
2175         return this.addRecords(data);
2176     },
2177
2178     /**
2179      * Sorts all Records by given function. Records keep their unique IDs but will
2180      * have new RecordSet position indexes.
2181      *
2182      * @method sortRecords
2183      * @param fnSort {Function} Reference to a sort function.
2184      * @param desc {Boolean} True if sort direction is descending, false if sort
2185      * direction is ascending.
2186      * @return {YAHOO.widget.Record[]} Sorted array of Records.
2187      */
2188     sortRecords : function(fnSort, desc) {
2189         return this._records.sort(function(a, b) {return fnSort(a, b, desc);});
2190     },
2191
2192     /**
2193      * Reverses all Records, so ["one", "two", "three"] becomes ["three", "two", "one"].
2194      *
2195      * @method reverseRecords
2196      * @return {YAHOO.widget.Record[]} Reverse-sorted array of Records.
2197      */
2198     reverseRecords : function() {
2199         return this._records.reverse();
2200     },
2201
2202     /**
2203      * Removes the Record at the given position index from the RecordSet. If a range
2204      * is also provided, removes that many Records, starting from the index. Length
2205      * of RecordSet is correspondingly shortened.
2206      *
2207      * @method deleteRecord
2208      * @param index {Number} Record's RecordSet position index.
2209      * @param range {Number} (optional) How many Records to delete.
2210      * @return {Object} A copy of the data held by the deleted Record.
2211      */
2212     deleteRecord : function(index) {
2213         if(lang.isNumber(index) && (index > -1) && (index < this.getLength())) {
2214             // Copy data from the Record for the event that gets fired later
2215             var oData = widget.DataTable._cloneObject(this.getRecord(index).getData());
2216             
2217             this._deleteRecord(index);
2218             this.fireEvent("recordDeleteEvent",{data:oData,index:index});
2219             YAHOO.log("Record deleted at index " + index +
2220                     " and containing data " + lang.dump(oData), "info", this.toString());
2221             return oData;
2222         }
2223         else {
2224             YAHOO.log("Could not delete Record at index " + index, "error", this.toString());
2225             return null;
2226         }
2227     },
2228
2229     /**
2230      * Removes the Record at the given position index from the RecordSet. If a range
2231      * is also provided, removes that many Records, starting from the index. Length
2232      * of RecordSet is correspondingly shortened.
2233      *
2234      * @method deleteRecords
2235      * @param index {Number} Record's RecordSet position index.
2236      * @param range {Number} (optional) How many Records to delete.
2237      * @return {Object[]} An array of copies of the data held by the deleted Records.     
2238      */
2239     deleteRecords : function(index, range) {
2240         if(!lang.isNumber(range)) {
2241             range = 1;
2242         }
2243         if(lang.isNumber(index) && (index > -1) && (index < this.getLength())) {
2244             var recordsToDelete = this.getRecords(index, range);
2245             // Copy data from each Record for the event that gets fired later
2246             var deletedData = [];
2247             
2248             for(var i=0; i<recordsToDelete.length; i++) {
2249                 deletedData[deletedData.length] = widget.DataTable._cloneObject(recordsToDelete[i]);
2250             }
2251             this._deleteRecord(index, range);
2252
2253             this.fireEvent("recordsDeleteEvent",{data:deletedData,index:index});
2254             YAHOO.log(range + "Record(s) deleted at index " + index +
2255                     " and containing data " + lang.dump(deletedData), "info", this.toString());
2256
2257             return deletedData;
2258         }
2259         else {
2260             YAHOO.log("Could not delete Records at index " + index, "error", this.toString());
2261             return null;
2262         }
2263     },
2264
2265     /**
2266      * Deletes all Records from the RecordSet.
2267      *
2268      * @method reset
2269      */
2270     reset : function() {
2271         this._records = [];
2272         //this._length = 0;
2273         this.fireEvent("resetEvent");
2274         YAHOO.log("All Records deleted from RecordSet", "info", this.toString());
2275     }
2276 };
2277
2278 /////////////////////////////////////////////////////////////////////////////
2279 //
2280 // Custom Events
2281 //
2282 /////////////////////////////////////////////////////////////////////////////
2283
2284 // RecordSet uses EventProvider
2285 lang.augmentProto(RS, util.EventProvider);
2286
2287 /**
2288  * Fired when a new Record is added to the RecordSet.
2289  *
2290  * @event recordAddEvent
2291  * @param oArgs.record {YAHOO.widget.Record} The Record instance.
2292  * @param oArgs.data {Object} Data added.
2293  */
2294
2295 /**
2296  * Fired when multiple Records are added to the RecordSet at once.
2297  *
2298  * @event recordsAddEvent
2299  * @param oArgs.records {YAHOO.widget.Record[]} An array of Record instances.
2300  * @param oArgs.data {Object[]} Data added.
2301  */
2302
2303 /**
2304  * Fired when a Record is set in the RecordSet.
2305  *
2306  * @event recordSetEvent
2307  * @param oArgs.record {YAHOO.widget.Record} The Record instance.
2308  * @param oArgs.data {Object} Data added.
2309  */
2310
2311 /**
2312  * Fired when multiple Records are set in the RecordSet at once.
2313  *
2314  * @event recordsSetEvent
2315  * @param oArgs.records {YAHOO.widget.Record[]} An array of Record instances.
2316  * @param oArgs.data {Object[]} Data added.
2317  */
2318
2319 /**
2320  * Fired when a Record is updated with new data.
2321  *
2322  * @event recordUpdateEvent
2323  * @param oArgs.record {YAHOO.widget.Record} The Record instance.
2324  * @param oArgs.newData {Object} New data.
2325  * @param oArgs.oldData {Object} Old data.
2326  */
2327
2328 /**
2329  * Fired when a Record is deleted from the RecordSet.
2330  *
2331  * @event recordDeleteEvent
2332  * @param oArgs.data {Object} A copy of the data held by the Record,
2333  * or an array of data object literals if multiple Records were deleted at once.
2334  * @param oArgs.index {Object} Index of the deleted Record.
2335  */
2336
2337 /**
2338  * Fired when multiple Records are deleted from the RecordSet at once.
2339  *
2340  * @event recordsDeleteEvent
2341  * @param oArgs.data {Object[]} An array of data object literals copied
2342  * from the Records.
2343  * @param oArgs.index {Object} Index of the first deleted Record.
2344  */
2345
2346 /**
2347  * Fired when all Records are deleted from the RecordSet at once.
2348  *
2349  * @event resetEvent
2350  */
2351
2352 /**
2353  * @event keyUpdateEvent    
2354  * @deprecated Use recordValueUpdateEvent     
2355  */
2356
2357 /**
2358  * Fired when a Record value is updated with new data.
2359  *
2360  * @event recordValueUpdateEvent
2361  * @param oArgs.record {YAHOO.widget.Record} The Record instance.
2362  * @param oArgs.key {String} The updated key.
2363  * @param oArgs.newData {Object} New data.
2364  * @param oArgs.oldData {Object} Old data.
2365  *
2366  */
2367
2368
2369 /****************************************************************************/
2370 /****************************************************************************/
2371 /****************************************************************************/
2372
2373 /**
2374  * The Record class defines a DataTable record.
2375  *
2376  * @namespace YAHOO.widget
2377  * @class Record
2378  * @constructor
2379  * @param oConfigs {Object} (optional) Object literal of key/value pairs.
2380  */
2381 YAHOO.widget.Record = function(oLiteral) {
2382     this._nCount = widget.Record._nCount;
2383     this._sId = "yui-rec" + this._nCount;
2384     widget.Record._nCount++;
2385     this._oData = {};
2386     if(lang.isObject(oLiteral)) {
2387         for(var sKey in oLiteral) {
2388             if(lang.hasOwnProperty(oLiteral, sKey)) {
2389                 this._oData[sKey] = oLiteral[sKey];
2390             }
2391         }
2392     }
2393 };
2394
2395 /////////////////////////////////////////////////////////////////////////////
2396 //
2397 // Private member variables
2398 //
2399 /////////////////////////////////////////////////////////////////////////////
2400
2401 /**
2402  * Internal class variable to give unique IDs to Record instances.
2403  *
2404  * @property Record._nCount
2405  * @type Number
2406  * @private
2407  */
2408 YAHOO.widget.Record._nCount = 0;
2409
2410 YAHOO.widget.Record.prototype = {
2411     /**
2412      * Immutable unique count assigned at instantiation. Remains constant while a
2413      * Record's position index can change from sorting.
2414      *
2415      * @property _nCount
2416      * @type Number
2417      * @private
2418      */
2419     _nCount : null,
2420
2421     /**
2422      * Immutable unique ID assigned at instantiation. Remains constant while a
2423      * Record's position index can change from sorting.
2424      *
2425      * @property _sId
2426      * @type String
2427      * @private
2428      */
2429     _sId : null,
2430
2431     /**
2432      * Holds data for the Record in an object literal.
2433      *
2434      * @property _oData
2435      * @type Object
2436      * @private
2437      */
2438     _oData : null,
2439
2440     /////////////////////////////////////////////////////////////////////////////
2441     //
2442     // Public member variables
2443     //
2444     /////////////////////////////////////////////////////////////////////////////
2445
2446     /////////////////////////////////////////////////////////////////////////////
2447     //
2448     // Public methods
2449     //
2450     /////////////////////////////////////////////////////////////////////////////
2451
2452     /**
2453      * Returns unique count assigned at instantiation.
2454      *
2455      * @method getCount
2456      * @return Number
2457      */
2458     getCount : function() {
2459         return this._nCount;
2460     },
2461
2462     /**
2463      * Returns unique ID assigned at instantiation.
2464      *
2465      * @method getId
2466      * @return String
2467      */
2468     getId : function() {
2469         return this._sId;
2470     },
2471
2472     /**
2473      * Returns data for the Record for a field if given, or the entire object
2474      * literal otherwise.
2475      *
2476      * @method getData
2477      * @param sField {String} (Optional) The field from which to retrieve data value.
2478      * @return Object
2479      */
2480     getData : function(sField) {
2481         if(lang.isString(sField)) {
2482             return this._oData[sField];
2483         }
2484         else {
2485             return this._oData;
2486         }
2487     },
2488
2489     /**
2490      * Sets given data at the given key. Use the RecordSet method setValue to trigger
2491      * events. 
2492      *
2493      * @method setData
2494      * @param sKey {String} The key of the new value.
2495      * @param oData {MIXED} The new value.
2496      */
2497     setData : function(sKey, oData) {
2498         this._oData[sKey] = oData;
2499     }
2500 };
2501
2502 })();
2503
2504 (function () {
2505
2506 var lang   = YAHOO.lang,
2507     util   = YAHOO.util,
2508     widget = YAHOO.widget,
2509     ua     = YAHOO.env.ua,
2510     
2511     Dom    = util.Dom,
2512     Ev     = util.Event,
2513     DS     = util.DataSourceBase;
2514
2515 /**
2516  * The DataTable widget provides a progressively enhanced DHTML control for
2517  * displaying tabular data across A-grade browsers.
2518  *
2519  * @module datatable
2520  * @requires yahoo, dom, event, element, datasource
2521  * @optional dragdrop, dragdrop
2522  * @title DataTable Widget
2523  */
2524
2525 /****************************************************************************/
2526 /****************************************************************************/
2527 /****************************************************************************/
2528
2529 /**
2530  * DataTable class for the YUI DataTable widget.
2531  *
2532  * @namespace YAHOO.widget
2533  * @class DataTable
2534  * @extends YAHOO.util.Element
2535  * @constructor
2536  * @param elContainer {HTMLElement} Container element for the TABLE.
2537  * @param aColumnDefs {Object[]} Array of object literal Column definitions.
2538  * @param oDataSource {YAHOO.util.DataSource} DataSource instance.
2539  * @param oConfigs {object} (optional) Object literal of configuration values.
2540  */
2541 YAHOO.widget.DataTable = function(elContainer,aColumnDefs,oDataSource,oConfigs) {
2542     var DT = widget.DataTable;
2543     
2544     ////////////////////////////////////////////////////////////////////////////
2545     // Backward compatibility for SDT, but prevent infinite loops
2546     
2547     if(oConfigs && oConfigs.scrollable) {
2548         return new YAHOO.widget.ScrollingDataTable(elContainer,aColumnDefs,oDataSource,oConfigs);
2549     }
2550     
2551     ////////////////////////////////////////////////////////////////////////////
2552     // Initialization
2553
2554     // Internal vars
2555     this._nIndex = DT._nCount;
2556     this._sId = "yui-dt"+this._nIndex;
2557     this._oChainRender = new YAHOO.util.Chain();
2558     this._oChainRender.subscribe("end",this._onRenderChainEnd, this, true);
2559
2560     // Initialize configs
2561     this._initConfigs(oConfigs);
2562
2563     // Initialize DataSource
2564     this._initDataSource(oDataSource);
2565     if(!this._oDataSource) {
2566         YAHOO.log("Could not instantiate DataTable due to an invalid DataSource", "error", this.toString());
2567         return;
2568     }
2569
2570     // Initialize ColumnSet
2571     this._initColumnSet(aColumnDefs);
2572     if(!this._oColumnSet) {
2573         YAHOO.log("Could not instantiate DataTable due to an invalid ColumnSet", "error", this.toString());
2574         return;
2575     }
2576
2577     // Initialize RecordSet
2578     this._initRecordSet();
2579     if(!this._oRecordSet) {
2580     }
2581
2582     // Initialize Attributes
2583     DT.superclass.constructor.call(this, elContainer, this.configs);
2584
2585     // Initialize DOM elements
2586     var okDom = this._initDomElements(elContainer);
2587     if(!okDom) {
2588         YAHOO.log("Could not instantiate DataTable due to an invalid DOM elements", "error", this.toString());
2589         return;
2590     }
2591             
2592     // Show message as soon as config is available
2593     this.showTableMessage(this.get("MSG_LOADING"), DT.CLASS_LOADING);
2594     
2595     ////////////////////////////////////////////////////////////////////////////
2596     // Once per instance
2597     this._initEvents();
2598
2599     DT._nCount++;
2600     DT._nCurrentCount++;
2601     
2602     ////////////////////////////////////////////////////////////////////////////
2603     // Data integration
2604
2605     // Send a simple initial request
2606     var oCallback = {
2607         success : this.onDataReturnSetRows,
2608         failure : this.onDataReturnSetRows,
2609         scope   : this,
2610         argument: this.getState()
2611     };
2612     
2613     var initialLoad = this.get("initialLoad");
2614     if(initialLoad === true) {
2615         this._oDataSource.sendRequest(this.get("initialRequest"), oCallback);
2616     }
2617     // Do not send an initial request at all
2618     else if(initialLoad === false) {
2619         this.showTableMessage(this.get("MSG_EMPTY"), DT.CLASS_EMPTY);
2620     }
2621     // Send an initial request with a custom payload
2622     else {
2623         var oCustom = initialLoad || {};
2624         oCallback.argument = oCustom.argument || {};
2625         this._oDataSource.sendRequest(oCustom.request, oCallback);
2626     }
2627 };
2628
2629 var DT = widget.DataTable;
2630
2631 /////////////////////////////////////////////////////////////////////////////
2632 //
2633 // Public constants
2634 //
2635 /////////////////////////////////////////////////////////////////////////////
2636
2637 lang.augmentObject(DT, {
2638
2639     /**
2640      * Class name assigned to outer DataTable container.
2641      *
2642      * @property DataTable.CLASS_DATATABLE
2643      * @type String
2644      * @static
2645      * @final
2646      * @default "yui-dt"
2647      */
2648     CLASS_DATATABLE : "yui-dt",
2649
2650     /**
2651      * Class name assigned to liner DIV elements.
2652      *
2653      * @property DataTable.CLASS_LINER
2654      * @type String
2655      * @static
2656      * @final
2657      * @default "yui-dt-liner"
2658      */
2659     CLASS_LINER : "yui-dt-liner",
2660
2661     /**
2662      * Class name assigned to display label elements.
2663      *
2664      * @property DataTable.CLASS_LABEL
2665      * @type String
2666      * @static
2667      * @final
2668      * @default "yui-dt-label"
2669      */
2670     CLASS_LABEL : "yui-dt-label",
2671
2672     /**
2673      * Class name assigned to messaging elements.
2674      *
2675      * @property DataTable.CLASS_MESSAGE
2676      * @type String
2677      * @static
2678      * @final
2679      * @default "yui-dt-message"
2680      */
2681     CLASS_MESSAGE : "yui-dt-message",
2682
2683     /**
2684      * Class name assigned to mask element when DataTable is disabled.
2685      *
2686      * @property DataTable.CLASS_MASK
2687      * @type String
2688      * @static
2689      * @final
2690      * @default "yui-dt-mask"
2691      */
2692     CLASS_MASK : "yui-dt-mask",
2693
2694     /**
2695      * Class name assigned to data elements.
2696      *
2697      * @property DataTable.CLASS_DATA
2698      * @type String
2699      * @static
2700      * @final
2701      * @default "yui-dt-data"
2702      */
2703     CLASS_DATA : "yui-dt-data",
2704
2705     /**
2706      * Class name assigned to Column drag target.
2707      *
2708      * @property DataTable.CLASS_COLTARGET
2709      * @type String
2710      * @static
2711      * @final
2712      * @default "yui-dt-coltarget"
2713      */
2714     CLASS_COLTARGET : "yui-dt-coltarget",
2715
2716     /**
2717      * Class name assigned to resizer handle elements.
2718      *
2719      * @property DataTable.CLASS_RESIZER
2720      * @type String
2721      * @static
2722      * @final
2723      * @default "yui-dt-resizer"
2724      */
2725     CLASS_RESIZER : "yui-dt-resizer",
2726
2727     /**
2728      * Class name assigned to resizer liner elements.
2729      *
2730      * @property DataTable.CLASS_RESIZERLINER
2731      * @type String
2732      * @static
2733      * @final
2734      * @default "yui-dt-resizerliner"
2735      */
2736     CLASS_RESIZERLINER : "yui-dt-resizerliner",
2737
2738     /**
2739      * Class name assigned to resizer proxy elements.
2740      *
2741      * @property DataTable.CLASS_RESIZERPROXY
2742      * @type String
2743      * @static
2744      * @final
2745      * @default "yui-dt-resizerproxy"
2746      */
2747     CLASS_RESIZERPROXY : "yui-dt-resizerproxy",
2748
2749     /**
2750      * Class name assigned to CellEditor container elements.
2751      *
2752      * @property DataTable.CLASS_EDITOR
2753      * @type String
2754      * @static
2755      * @final
2756      * @default "yui-dt-editor"
2757      */
2758     CLASS_EDITOR : "yui-dt-editor",
2759
2760     /**
2761      * Class name assigned to paginator container elements.
2762      *
2763      * @property DataTable.CLASS_PAGINATOR
2764      * @type String
2765      * @static
2766      * @final
2767      * @default "yui-dt-paginator"
2768      */
2769     CLASS_PAGINATOR : "yui-dt-paginator",
2770
2771     /**
2772      * Class name assigned to page number indicators.
2773      *
2774      * @property DataTable.CLASS_PAGE
2775      * @type String
2776      * @static
2777      * @final
2778      * @default "yui-dt-page"
2779      */
2780     CLASS_PAGE : "yui-dt-page",
2781
2782     /**
2783      * Class name assigned to default indicators.
2784      *
2785      * @property DataTable.CLASS_DEFAULT
2786      * @type String
2787      * @static
2788      * @final
2789      * @default "yui-dt-default"
2790      */
2791     CLASS_DEFAULT : "yui-dt-default",
2792
2793     /**
2794      * Class name assigned to previous indicators.
2795      *
2796      * @property DataTable.CLASS_PREVIOUS
2797      * @type String
2798      * @static
2799      * @final
2800      * @default "yui-dt-previous"
2801      */
2802     CLASS_PREVIOUS : "yui-dt-previous",
2803
2804     /**
2805      * Class name assigned next indicators.
2806      *
2807      * @property DataTable.CLASS_NEXT
2808      * @type String
2809      * @static
2810      * @final
2811      * @default "yui-dt-next"
2812      */
2813     CLASS_NEXT : "yui-dt-next",
2814
2815     /**
2816      * Class name assigned to first elements.
2817      *
2818      * @property DataTable.CLASS_FIRST
2819      * @type String
2820      * @static
2821      * @final
2822      * @default "yui-dt-first"
2823      */
2824     CLASS_FIRST : "yui-dt-first",
2825
2826     /**
2827      * Class name assigned to last elements.
2828      *
2829      * @property DataTable.CLASS_LAST
2830      * @type String
2831      * @static
2832      * @final
2833      * @default "yui-dt-last"
2834      */
2835     CLASS_LAST : "yui-dt-last",
2836
2837     /**
2838      * Class name assigned to even elements.
2839      *
2840      * @property DataTable.CLASS_EVEN
2841      * @type String
2842      * @static
2843      * @final
2844      * @default "yui-dt-even"
2845      */
2846     CLASS_EVEN : "yui-dt-even",
2847
2848     /**
2849      * Class name assigned to odd elements.
2850      *
2851      * @property DataTable.CLASS_ODD
2852      * @type String
2853      * @static
2854      * @final
2855      * @default "yui-dt-odd"
2856      */
2857     CLASS_ODD : "yui-dt-odd",
2858
2859     /**
2860      * Class name assigned to selected elements.
2861      *
2862      * @property DataTable.CLASS_SELECTED
2863      * @type String
2864      * @static
2865      * @final
2866      * @default "yui-dt-selected"
2867      */
2868     CLASS_SELECTED : "yui-dt-selected",
2869
2870     /**
2871      * Class name assigned to highlighted elements.
2872      *
2873      * @property DataTable.CLASS_HIGHLIGHTED
2874      * @type String
2875      * @static
2876      * @final
2877      * @default "yui-dt-highlighted"
2878      */
2879     CLASS_HIGHLIGHTED : "yui-dt-highlighted",
2880
2881     /**
2882      * Class name assigned to hidden elements.
2883      *
2884      * @property DataTable.CLASS_HIDDEN
2885      * @type String
2886      * @static
2887      * @final
2888      * @default "yui-dt-hidden"
2889      */
2890     CLASS_HIDDEN : "yui-dt-hidden",
2891
2892     /**
2893      * Class name assigned to disabled elements.
2894      *
2895      * @property DataTable.CLASS_DISABLED
2896      * @type String
2897      * @static
2898      * @final
2899      * @default "yui-dt-disabled"
2900      */
2901     CLASS_DISABLED : "yui-dt-disabled",
2902
2903     /**
2904      * Class name assigned to empty indicators.
2905      *
2906      * @property DataTable.CLASS_EMPTY
2907      * @type String
2908      * @static
2909      * @final
2910      * @default "yui-dt-empty"
2911      */
2912     CLASS_EMPTY : "yui-dt-empty",
2913
2914     /**
2915      * Class name assigned to loading indicatorx.
2916      *
2917      * @property DataTable.CLASS_LOADING
2918      * @type String
2919      * @static
2920      * @final
2921      * @default "yui-dt-loading"
2922      */
2923     CLASS_LOADING : "yui-dt-loading",
2924
2925     /**
2926      * Class name assigned to error indicators.
2927      *
2928      * @property DataTable.CLASS_ERROR
2929      * @type String
2930      * @static
2931      * @final
2932      * @default "yui-dt-error"
2933      */
2934     CLASS_ERROR : "yui-dt-error",
2935
2936     /**
2937      * Class name assigned to editable elements.
2938      *
2939      * @property DataTable.CLASS_EDITABLE
2940      * @type String
2941      * @static
2942      * @final
2943      * @default "yui-dt-editable"
2944      */
2945     CLASS_EDITABLE : "yui-dt-editable",
2946
2947     /**
2948      * Class name assigned to draggable elements.
2949      *
2950      * @property DataTable.CLASS_DRAGGABLE
2951      * @type String
2952      * @static
2953      * @final
2954      * @default "yui-dt-draggable"
2955      */
2956     CLASS_DRAGGABLE : "yui-dt-draggable",
2957
2958     /**
2959      * Class name assigned to resizeable elements.
2960      *
2961      * @property DataTable.CLASS_RESIZEABLE
2962      * @type String
2963      * @static
2964      * @final
2965      * @default "yui-dt-resizeable"
2966      */
2967     CLASS_RESIZEABLE : "yui-dt-resizeable",
2968
2969     /**
2970      * Class name assigned to scrollable elements.
2971      *
2972      * @property DataTable.CLASS_SCROLLABLE
2973      * @type String
2974      * @static
2975      * @final
2976      * @default "yui-dt-scrollable"
2977      */
2978     CLASS_SCROLLABLE : "yui-dt-scrollable",
2979
2980     /**
2981      * Class name assigned to sortable elements.
2982      *
2983      * @property DataTable.CLASS_SORTABLE
2984      * @type String
2985      * @static
2986      * @final
2987      * @default "yui-dt-sortable"
2988      */
2989     CLASS_SORTABLE : "yui-dt-sortable",
2990
2991     /**
2992      * Class name assigned to ascending elements.
2993      *
2994      * @property DataTable.CLASS_ASC
2995      * @type String
2996      * @static
2997      * @final
2998      * @default "yui-dt-asc"
2999      */
3000     CLASS_ASC : "yui-dt-asc",
3001
3002     /**
3003      * Class name assigned to descending elements.
3004      *
3005      * @property DataTable.CLASS_DESC
3006      * @type String
3007      * @static
3008      * @final
3009      * @default "yui-dt-desc"
3010      */
3011     CLASS_DESC : "yui-dt-desc",
3012
3013     /**
3014      * Class name assigned to BUTTON elements and/or container elements.
3015      *
3016      * @property DataTable.CLASS_BUTTON
3017      * @type String
3018      * @static
3019      * @final
3020      * @default "yui-dt-button"
3021      */
3022     CLASS_BUTTON : "yui-dt-button",
3023
3024     /**
3025      * Class name assigned to INPUT TYPE=CHECKBOX elements and/or container elements.
3026      *
3027      * @property DataTable.CLASS_CHECKBOX
3028      * @type String
3029      * @static
3030      * @final
3031      * @default "yui-dt-checkbox"
3032      */
3033     CLASS_CHECKBOX : "yui-dt-checkbox",
3034
3035     /**
3036      * Class name assigned to SELECT elements and/or container elements.
3037      *
3038      * @property DataTable.CLASS_DROPDOWN
3039      * @type String
3040      * @static
3041      * @final
3042      * @default "yui-dt-dropdown"
3043      */
3044     CLASS_DROPDOWN : "yui-dt-dropdown",
3045
3046     /**
3047      * Class name assigned to INPUT TYPE=RADIO elements and/or container elements.
3048      *
3049      * @property DataTable.CLASS_RADIO
3050      * @type String
3051      * @static
3052      * @final
3053      * @default "yui-dt-radio"
3054      */
3055     CLASS_RADIO : "yui-dt-radio",
3056
3057     /////////////////////////////////////////////////////////////////////////
3058     //
3059     // Private static properties
3060     //
3061     /////////////////////////////////////////////////////////////////////////
3062
3063     /**
3064      * Internal class variable for indexing multiple DataTable instances.
3065      *
3066      * @property DataTable._nCount
3067      * @type Number
3068      * @private
3069      * @static
3070      */
3071     _nCount : 0,
3072
3073     /**
3074      * Internal class variable tracking current number of DataTable instances,
3075      * so that certain class values can be reset when all instances are destroyed.          
3076      *
3077      * @property DataTable._nCurrentCount
3078      * @type Number
3079      * @private
3080      * @static
3081      */
3082     _nCurrentCount : 0,
3083
3084     /**
3085      * Reference to the STYLE node that is dynamically created and updated
3086      * in order to manage Column widths.
3087      *
3088      * @property DataTable._elDynStyleNode
3089      * @type HTMLElement
3090      * @private
3091      * @static     
3092      */
3093     _elDynStyleNode : null,
3094
3095     /**
3096      * Set to true if _elDynStyleNode cannot be populated due to browser incompatibility.
3097      *
3098      * @property DataTable._bDynStylesFallback
3099      * @type boolean
3100      * @private
3101      * @static     
3102      */
3103     _bDynStylesFallback : (ua.ie && (ua.ie<7)) ? true : false,
3104
3105     /**
3106      * Object literal hash of Columns and their dynamically create style rules.
3107      *
3108      * @property DataTable._oDynStyles
3109      * @type Object
3110      * @private
3111      * @static     
3112      */
3113     _oDynStyles : {},
3114
3115     /**
3116      * Element reference to shared Column drag target.
3117      *
3118      * @property DataTable._elColumnDragTarget
3119      * @type HTMLElement
3120      * @private
3121      * @static 
3122      */
3123     _elColumnDragTarget : null,
3124
3125     /**
3126      * Element reference to shared Column resizer proxy.
3127      *
3128      * @property DataTable._elColumnResizerProxy
3129      * @type HTMLElement
3130      * @private
3131      * @static 
3132      */
3133     _elColumnResizerProxy : null,
3134
3135     /////////////////////////////////////////////////////////////////////////
3136     //
3137     // Private static methods
3138     //
3139     /////////////////////////////////////////////////////////////////////////
3140
3141     /**
3142      * Clones object literal or array of object literals.
3143      *
3144      * @method DataTable._cloneObject
3145      * @param o {Object} Object.
3146      * @private
3147      * @static     
3148      */
3149     _cloneObject : function(o) {
3150         if(!lang.isValue(o)) {
3151             return o;
3152         }
3153         
3154         var copy = {};
3155         
3156         if(o instanceof YAHOO.widget.BaseCellEditor) {
3157             copy = o;
3158         }
3159         else if(lang.isFunction(o)) {
3160             copy = o;
3161         }
3162         else if(lang.isArray(o)) {
3163             var array = [];
3164             for(var i=0,len=o.length;i<len;i++) {
3165                 array[i] = DT._cloneObject(o[i]);
3166             }
3167             copy = array;
3168         }
3169         else if(lang.isObject(o)) { 
3170             for (var x in o){
3171                 if(lang.hasOwnProperty(o, x)) {
3172                     if(lang.isValue(o[x]) && lang.isObject(o[x]) || lang.isArray(o[x])) {
3173                         copy[x] = DT._cloneObject(o[x]);
3174                     }
3175                     else {
3176                         copy[x] = o[x];
3177                     }
3178                 }
3179             }
3180         }
3181         else {
3182             copy = o;
3183         }
3184     
3185         return copy;
3186     },
3187
3188     /**
3189      * Destroys shared Column drag target.
3190      *
3191      * @method DataTable._destroyColumnDragTargetEl
3192      * @private
3193      * @static 
3194      */
3195     _destroyColumnDragTargetEl : function() {
3196         if(DT._elColumnDragTarget) {
3197             var el = DT._elColumnDragTarget;
3198             YAHOO.util.Event.purgeElement(el);
3199             el.parentNode.removeChild(el);
3200             DT._elColumnDragTarget = null;
3201             
3202         }
3203     },
3204
3205     /**
3206      * Creates HTML markup for shared Column drag target.
3207      *
3208      * @method DataTable._initColumnDragTargetEl
3209      * @return {HTMLElement} Reference to Column drag target. 
3210      * @private
3211      * @static 
3212      */
3213     _initColumnDragTargetEl : function() {
3214         if(!DT._elColumnDragTarget) {
3215             // Attach Column drag target element as first child of body
3216             var elColumnDragTarget = document.createElement('div');
3217             elColumnDragTarget.className = DT.CLASS_COLTARGET;
3218             elColumnDragTarget.style.display = "none";
3219             document.body.insertBefore(elColumnDragTarget, document.body.firstChild);
3220
3221             // Internal tracker of Column drag target
3222             DT._elColumnDragTarget = elColumnDragTarget;
3223             
3224         }
3225         return DT._elColumnDragTarget;
3226     },
3227
3228     /**
3229      * Destroys shared Column resizer proxy.
3230      *
3231      * @method DataTable._destroyColumnResizerProxyEl
3232      * @return {HTMLElement} Reference to Column resizer proxy.
3233      * @private 
3234      * @static 
3235      */
3236     _destroyColumnResizerProxyEl : function() {
3237         if(DT._elColumnResizerProxy) {
3238             var el = DT._elColumnResizerProxy;
3239             YAHOO.util.Event.purgeElement(el);
3240             el.parentNode.removeChild(el);
3241             DT._elColumnResizerProxy = null;
3242         }
3243     },
3244
3245     /**
3246      * Creates HTML markup for shared Column resizer proxy.
3247      *
3248      * @method DataTable._initColumnResizerProxyEl
3249      * @return {HTMLElement} Reference to Column resizer proxy.
3250      * @private 
3251      * @static 
3252      */
3253     _initColumnResizerProxyEl : function() {
3254         if(!DT._elColumnResizerProxy) {
3255             // Attach Column resizer element as first child of body
3256             var elColumnResizerProxy = document.createElement("div");
3257             elColumnResizerProxy.id = "yui-dt-colresizerproxy"; // Needed for ColumnResizer
3258             elColumnResizerProxy.className = DT.CLASS_RESIZERPROXY;
3259             document.body.insertBefore(elColumnResizerProxy, document.body.firstChild);
3260
3261             // Internal tracker of Column resizer proxy
3262             DT._elColumnResizerProxy = elColumnResizerProxy;
3263         }
3264         return DT._elColumnResizerProxy;
3265     },
3266
3267     /**
3268      * Formats a BUTTON element.
3269      *
3270      * @method DataTable.formatButton
3271      * @param el {HTMLElement} The element to format with markup.
3272      * @param oRecord {YAHOO.widget.Record} Record instance.
3273      * @param oColumn {YAHOO.widget.Column} Column instance.
3274      * @param oData {Object | Boolean} Data value for the cell. By default, the value
3275      * is what gets written to the BUTTON.
3276      * @static
3277      */
3278     formatButton : function(el, oRecord, oColumn, oData) {
3279         var sValue = lang.isValue(oData) ? oData : "Click";
3280         //TODO: support YAHOO.widget.Button
3281         //if(YAHOO.widget.Button) {
3282
3283         //}
3284         //else {
3285             el.innerHTML = "<button type=\"button\" class=\""+
3286                     DT.CLASS_BUTTON + "\">" + sValue + "</button>";
3287         //}
3288     },
3289
3290     /**
3291      * Formats a CHECKBOX element.
3292      *
3293      * @method DataTable.formatCheckbox
3294      * @param el {HTMLElement} The element to format with markup.
3295      * @param oRecord {YAHOO.widget.Record} Record instance.
3296      * @param oColumn {YAHOO.widget.Column} Column instance.
3297      * @param oData {Object | Boolean} Data value for the cell. Can be a simple
3298      * Boolean to indicate whether checkbox is checked or not. Can be object literal
3299      * {checked:bBoolean, label:sLabel}. Other forms of oData require a custom
3300      * formatter.
3301      * @static
3302      */
3303     formatCheckbox : function(el, oRecord, oColumn, oData) {
3304         var bChecked = oData;
3305         bChecked = (bChecked) ? " checked=\"checked\"" : "";
3306         el.innerHTML = "<input type=\"checkbox\"" + bChecked +
3307                 " class=\"" + DT.CLASS_CHECKBOX + "\" />";
3308     },
3309
3310     /**
3311      * Formats currency. Default unit is USD.
3312      *
3313      * @method DataTable.formatCurrency
3314      * @param el {HTMLElement} The element to format with markup.
3315      * @param oRecord {YAHOO.widget.Record} Record instance.
3316      * @param oColumn {YAHOO.widget.Column} Column instance.
3317      * @param oData {Number} Data value for the cell.
3318      * @static
3319      */
3320     formatCurrency : function(el, oRecord, oColumn, oData) {
3321         el.innerHTML = util.Number.format(oData, oColumn.currencyOptions || this.get("currencyOptions"));
3322     },
3323
3324     /**
3325      * Formats JavaScript Dates.
3326      *
3327      * @method DataTable.formatDate
3328      * @param el {HTMLElement} The element to format with markup.
3329      * @param oRecord {YAHOO.widget.Record} Record instance.
3330      * @param oColumn {YAHOO.widget.Column} Column instance.
3331      * @param oData {Object} Data value for the cell, or null.
3332      * @static
3333      */
3334     formatDate : function(el, oRecord, oColumn, oData) {
3335         var oConfig = oColumn.dateOptions || this.get("dateOptions");
3336         el.innerHTML = util.Date.format(oData, oConfig, oConfig.locale);
3337     },
3338
3339     /**
3340      * Formats SELECT elements.
3341      *
3342      * @method DataTable.formatDropdown
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     formatDropdown : function(el, oRecord, oColumn, oData) {
3350         var selectedValue = (lang.isValue(oData)) ? oData : oRecord.getData(oColumn.field),
3351             options = (lang.isArray(oColumn.dropdownOptions)) ?
3352                 oColumn.dropdownOptions : null,
3353
3354             selectEl,
3355             collection = el.getElementsByTagName("select");
3356
3357         // Create the form element only once, so we can attach the onChange listener
3358         if(collection.length === 0) {
3359             // Create SELECT element
3360             selectEl = document.createElement("select");
3361             selectEl.className = DT.CLASS_DROPDOWN;
3362             selectEl = el.appendChild(selectEl);
3363
3364             // Add event listener
3365             Ev.addListener(selectEl,"change",this._onDropdownChange,this);
3366         }
3367
3368         selectEl = collection[0];
3369
3370         // Update the form element
3371         if(selectEl) {
3372             // Clear out previous options
3373             selectEl.innerHTML = "";
3374
3375             // We have options to populate
3376             if(options) {
3377                 // Create OPTION elements
3378                 for(var i=0; i<options.length; i++) {
3379                     var option = options[i];
3380                     var optionEl = document.createElement("option");
3381                     optionEl.value = (lang.isValue(option.value)) ?
3382                             option.value : option;
3383                     // Bug 2334323: Support legacy text, support label for consistency with DropdownCellEditor
3384                     optionEl.innerHTML = (lang.isValue(option.text)) ?
3385                             option.text : (lang.isValue(option.label)) ? option.label : option;
3386                     optionEl = selectEl.appendChild(optionEl);
3387                     if (optionEl.value == selectedValue) {
3388                         optionEl.selected = true;
3389                     }
3390                 }
3391             }
3392             // Selected value is our only option
3393             else {
3394                 selectEl.innerHTML = "<option selected value=\"" + selectedValue + "\">" + selectedValue + "</option>";
3395             }
3396         }
3397         else {
3398             el.innerHTML = lang.isValue(oData) ? oData : "";
3399         }
3400     },
3401
3402     /**
3403      * Formats emails.
3404      *
3405      * @method DataTable.formatEmail
3406      * @param el {HTMLElement} The element to format with markup.
3407      * @param oRecord {YAHOO.widget.Record} Record instance.
3408      * @param oColumn {YAHOO.widget.Column} Column instance.
3409      * @param oData {Object} Data value for the cell, or null.
3410      * @static
3411      */
3412     formatEmail : function(el, oRecord, oColumn, oData) {
3413         if(lang.isString(oData)) {
3414             el.innerHTML = "<a href=\"mailto:" + oData + "\">" + oData + "</a>";
3415         }
3416         else {
3417             el.innerHTML = lang.isValue(oData) ? oData : "";
3418         }
3419     },
3420
3421     /**
3422      * Formats links.
3423      *
3424      * @method DataTable.formatLink
3425      * @param el {HTMLElement} The element to format with markup.
3426      * @param oRecord {YAHOO.widget.Record} Record instance.
3427      * @param oColumn {YAHOO.widget.Column} Column instance.
3428      * @param oData {Object} Data value for the cell, or null.
3429      * @static
3430      */
3431     formatLink : function(el, oRecord, oColumn, oData) {
3432         if(lang.isString(oData)) {
3433             el.innerHTML = "<a href=\"" + oData + "\">" + oData + "</a>";
3434         }
3435         else {
3436             el.innerHTML = lang.isValue(oData) ? oData : "";
3437         }
3438     },
3439
3440     /**
3441      * Formats numbers.
3442      *
3443      * @method DataTable.formatNumber
3444      * @param el {HTMLElement} The element to format with markup.
3445      * @param oRecord {YAHOO.widget.Record} Record instance.
3446      * @param oColumn {YAHOO.widget.Column} Column instance.
3447      * @param oData {Object} Data value for the cell, or null.
3448      * @static
3449      */
3450     formatNumber : function(el, oRecord, oColumn, oData) {
3451         el.innerHTML = util.Number.format(oData, oColumn.numberOptions || this.get("numberOptions"));
3452     },
3453
3454     /**
3455      * Formats INPUT TYPE=RADIO elements.
3456      *
3457      * @method DataTable.formatRadio
3458      * @param el {HTMLElement} The element to format with markup.
3459      * @param oRecord {YAHOO.widget.Record} Record instance.
3460      * @param oColumn {YAHOO.widget.Column} Column instance.
3461      * @param oData {Object} (Optional) Data value for the cell.
3462      * @static
3463      */
3464     formatRadio : function(el, oRecord, oColumn, oData) {
3465         var bChecked = oData;
3466         bChecked = (bChecked) ? " checked=\"checked\"" : "";
3467         el.innerHTML = "<input type=\"radio\"" + bChecked +
3468                 " name=\""+this.getId()+"-col-" + oColumn.getSanitizedKey() + "\"" +
3469                 " class=\"" + DT.CLASS_RADIO+ "\" />";
3470     },
3471
3472     /**
3473      * Formats text strings.
3474      *
3475      * @method DataTable.formatText
3476      * @param el {HTMLElement} The element to format with markup.
3477      * @param oRecord {YAHOO.widget.Record} Record instance.
3478      * @param oColumn {YAHOO.widget.Column} Column instance.
3479      * @param oData {Object} (Optional) Data value for the cell.
3480      * @static
3481      */
3482     formatText : function(el, oRecord, oColumn, oData) {
3483         var value = (lang.isValue(oData)) ? oData : "";
3484         //TODO: move to util function
3485         el.innerHTML = value.toString().replace(/&/g, "&#38;").replace(/</g, "&#60;").replace(/>/g, "&#62;");
3486     },
3487
3488     /**
3489      * Formats TEXTAREA elements.
3490      *
3491      * @method DataTable.formatTextarea
3492      * @param el {HTMLElement} The element to format with markup.
3493      * @param oRecord {YAHOO.widget.Record} Record instance.
3494      * @param oColumn {YAHOO.widget.Column} Column instance.
3495      * @param oData {Object} (Optional) Data value for the cell.
3496      * @static
3497      */
3498     formatTextarea : function(el, oRecord, oColumn, oData) {
3499         var value = (lang.isValue(oData)) ? oData : "",
3500             markup = "<textarea>" + value + "</textarea>";
3501         el.innerHTML = markup;
3502     },
3503
3504     /**
3505      * Formats INPUT TYPE=TEXT elements.
3506      *
3507      * @method DataTable.formatTextbox
3508      * @param el {HTMLElement} The element to format with markup.
3509      * @param oRecord {YAHOO.widget.Record} Record instance.
3510      * @param oColumn {YAHOO.widget.Column} Column instance.
3511      * @param oData {Object} (Optional) Data value for the cell.
3512      * @static
3513      */
3514     formatTextbox : function(el, oRecord, oColumn, oData) {
3515         var value = (lang.isValue(oData)) ? oData : "",
3516             markup = "<input type=\"text\" value=\"" + value + "\" />";
3517         el.innerHTML = markup;
3518     },
3519
3520     /**
3521      * Default cell formatter
3522      *
3523      * @method DataTable.formatDefault
3524      * @param el {HTMLElement} The element to format with markup.
3525      * @param oRecord {YAHOO.widget.Record} Record instance.
3526      * @param oColumn {YAHOO.widget.Column} Column instance.
3527      * @param oData {Object} (Optional) Data value for the cell.
3528      * @static
3529      */
3530     formatDefault : function(el, oRecord, oColumn, oData) {
3531         el.innerHTML = oData === undefined ||
3532                        oData === null ||
3533                        (typeof oData === 'number' && isNaN(oData)) ?
3534                        "&#160;" : oData.toString();
3535     },
3536
3537     /**
3538      * Validates data value to type Number, doing type conversion as
3539      * necessary. A valid Number value is return, else null is returned
3540      * if input value does not validate.
3541      *
3542      *
3543      * @method DataTable.validateNumber
3544      * @param oData {Object} Data to validate.
3545      * @static
3546     */
3547     validateNumber : function(oData) {
3548         //Convert to number
3549         var number = oData * 1;
3550
3551         // Validate
3552         if(lang.isNumber(number)) {
3553             return number;
3554         }
3555         else {
3556             YAHOO.log("Could not validate data " + lang.dump(oData) + " to type Number", "warn", this.toString());
3557             return undefined;
3558         }
3559     }
3560 });
3561
3562 // Done in separate step so referenced functions are defined.
3563 /**
3564  * Cell formatting functions.
3565  * @property DataTable.Formatter
3566  * @type Object
3567  * @static
3568  */
3569 DT.Formatter = {
3570     button   : DT.formatButton,
3571     checkbox : DT.formatCheckbox,
3572     currency : DT.formatCurrency,
3573     "date"   : DT.formatDate,
3574     dropdown : DT.formatDropdown,
3575     email    : DT.formatEmail,
3576     link     : DT.formatLink,
3577     "number" : DT.formatNumber,
3578     radio    : DT.formatRadio,
3579     text     : DT.formatText,
3580     textarea : DT.formatTextarea,
3581     textbox  : DT.formatTextbox,
3582
3583     defaultFormatter : DT.formatDefault
3584 };
3585
3586 lang.extend(DT, util.Element, {
3587
3588 /////////////////////////////////////////////////////////////////////////////
3589 //
3590 // Superclass methods
3591 //
3592 /////////////////////////////////////////////////////////////////////////////
3593
3594 /**
3595  * Implementation of Element's abstract method. Sets up config values.
3596  *
3597  * @method initAttributes
3598  * @param oConfigs {Object} (Optional) Object literal definition of configuration values.
3599  * @private
3600  */
3601
3602 initAttributes : function(oConfigs) {
3603     oConfigs = oConfigs || {};
3604     DT.superclass.initAttributes.call(this, oConfigs);
3605
3606     /**
3607     * @attribute summary
3608     * @description Value for the SUMMARY attribute.
3609     * @type String
3610     * @default ""    
3611     */
3612     this.setAttributeConfig("summary", {
3613         value: "",
3614         validator: lang.isString,
3615         method: function(sSummary) {
3616             if(this._elTable) {
3617                 this._elTable.summary = sSummary;
3618             }
3619         }
3620     });
3621
3622     /**
3623     * @attribute selectionMode
3624     * @description Specifies row or cell selection mode. Accepts the following strings:
3625     *    <dl>
3626     *      <dt>"standard"</dt>
3627     *      <dd>Standard row selection with support for modifier keys to enable
3628     *      multiple selections.</dd>
3629     *
3630     *      <dt>"single"</dt>
3631     *      <dd>Row selection with modifier keys disabled to not allow
3632     *      multiple selections.</dd>
3633     *
3634     *      <dt>"singlecell"</dt>
3635     *      <dd>Cell selection with modifier keys disabled to not allow
3636     *      multiple selections.</dd>
3637     *
3638     *      <dt>"cellblock"</dt>
3639     *      <dd>Cell selection with support for modifier keys to enable multiple
3640     *      selections in a block-fashion, like a spreadsheet.</dd>
3641     *
3642     *      <dt>"cellrange"</dt>
3643     *      <dd>Cell selection with support for modifier keys to enable multiple
3644     *      selections in a range-fashion, like a calendar.</dd>
3645     *    </dl>
3646     *
3647     * @default "standard"
3648     * @type String
3649     */
3650     this.setAttributeConfig("selectionMode", {
3651         value: "standard",
3652         validator: lang.isString
3653     });
3654
3655     /**
3656     * @attribute sortedBy
3657     * @description Object literal provides metadata for initial sort values if
3658     * data will arrive pre-sorted:
3659     * <dl>
3660     *     <dt>sortedBy.key</dt>
3661     *     <dd>{String} Key of sorted Column</dd>
3662     *     <dt>sortedBy.dir</dt>
3663     *     <dd>{String} Initial sort direction, either YAHOO.widget.DataTable.CLASS_ASC or YAHOO.widget.DataTable.CLASS_DESC</dd>
3664     * </dl>
3665     * @type Object | null
3666     */
3667     this.setAttributeConfig("sortedBy", {
3668         value: null,
3669         // TODO: accepted array for nested sorts
3670         validator: function(oNewSortedBy) {
3671             if(oNewSortedBy) {
3672                 return (lang.isObject(oNewSortedBy) && oNewSortedBy.key);
3673             }
3674             else {
3675                 return (oNewSortedBy === null);
3676             }
3677         },
3678         method: function(oNewSortedBy) {
3679             // Stash the previous value
3680             var oOldSortedBy = this.get("sortedBy");
3681             
3682             // Workaround for bug 1827195
3683             this._configs.sortedBy.value = oNewSortedBy;
3684
3685             // Remove ASC/DESC from TH
3686             var oOldColumn,
3687                 nOldColumnKeyIndex,
3688                 oNewColumn,
3689                 nNewColumnKeyIndex;
3690                 
3691             if(this._elThead) {
3692                 if(oOldSortedBy && oOldSortedBy.key && oOldSortedBy.dir) {
3693                     oOldColumn = this._oColumnSet.getColumn(oOldSortedBy.key);
3694                     nOldColumnKeyIndex = oOldColumn.getKeyIndex();
3695                     
3696                     // Remove previous UI from THEAD
3697                     var elOldTh = oOldColumn.getThEl();
3698                     Dom.removeClass(elOldTh, oOldSortedBy.dir);
3699                     this.formatTheadCell(oOldColumn.getThLinerEl().firstChild, oOldColumn, oNewSortedBy);
3700                 }
3701                 if(oNewSortedBy) {
3702                     oNewColumn = (oNewSortedBy.column) ? oNewSortedBy.column : this._oColumnSet.getColumn(oNewSortedBy.key);
3703                     nNewColumnKeyIndex = oNewColumn.getKeyIndex();
3704     
3705                     // Update THEAD with new UI
3706                     var elNewTh = oNewColumn.getThEl();
3707                     // Backward compatibility
3708                     if(oNewSortedBy.dir && ((oNewSortedBy.dir == "asc") ||  (oNewSortedBy.dir == "desc"))) {
3709                         var newClass = (oNewSortedBy.dir == "desc") ?
3710                                 DT.CLASS_DESC :
3711                                 DT.CLASS_ASC;
3712                         Dom.addClass(elNewTh, newClass);
3713                     }
3714                     else {
3715                          var sortClass = oNewSortedBy.dir || DT.CLASS_ASC;
3716                          Dom.addClass(elNewTh, sortClass);
3717                     }
3718                     this.formatTheadCell(oNewColumn.getThLinerEl().firstChild, oNewColumn, oNewSortedBy);
3719                 }
3720             }
3721           
3722             if(this._elTbody) {
3723                 // Update TBODY UI
3724                 this._elTbody.style.display = "none";
3725                 var allRows = this._elTbody.rows,
3726                     allCells;
3727                 for(var i=allRows.length-1; i>-1; i--) {
3728                     allCells = allRows[i].childNodes;
3729                     if(allCells[nOldColumnKeyIndex]) {
3730                         Dom.removeClass(allCells[nOldColumnKeyIndex], oOldSortedBy.dir);
3731                     }
3732                     if(allCells[nNewColumnKeyIndex]) {
3733                         Dom.addClass(allCells[nNewColumnKeyIndex], oNewSortedBy.dir);
3734                     }
3735                 }
3736                 this._elTbody.style.display = "";
3737             }
3738                 
3739             this._clearTrTemplateEl();
3740         }
3741     });
3742     
3743     /**
3744     * @attribute paginator
3745     * @description An instance of YAHOO.widget.Paginator.
3746     * @default null
3747     * @type {Object|YAHOO.widget.Paginator}
3748     */
3749     this.setAttributeConfig("paginator", {
3750         value : null,
3751         validator : function (val) {
3752             return val === null || val instanceof widget.Paginator;
3753         },
3754         method : function () { this._updatePaginator.apply(this,arguments); }
3755     });
3756
3757     /**
3758     * @attribute caption
3759     * @description Value for the CAPTION element. NB: Not supported in
3760     * ScrollingDataTable.    
3761     * @type String
3762     */
3763     this.setAttributeConfig("caption", {
3764         value: null,
3765         validator: lang.isString,
3766         method: function(sCaption) {
3767             this._initCaptionEl(sCaption);
3768         }
3769     });
3770
3771     /**
3772     * @attribute draggableColumns
3773     * @description True if Columns are draggable to reorder, false otherwise.
3774     * The Drag & Drop Utility is required to enable this feature. Only top-level
3775     * and non-nested Columns are draggable. Write once.
3776     * @default false
3777     * @type Boolean
3778     */
3779     this.setAttributeConfig("draggableColumns", {
3780         value: false,
3781         validator: lang.isBoolean,
3782         method: function(oParam) {
3783             if(this._elThead) {
3784                 if(oParam) {
3785                     this._initDraggableColumns();
3786                 }
3787                 else {
3788                     this._destroyDraggableColumns();
3789                 }
3790             }
3791         }
3792     });
3793
3794     /**
3795     * @attribute renderLoopSize          
3796     * @description A value greater than 0 enables DOM rendering of rows to be
3797     * executed from a non-blocking timeout queue and sets how many rows to be
3798     * rendered per timeout. Recommended for very large data sets.     
3799     * @type Number       
3800     * @default 0         
3801     */   
3802      this.setAttributeConfig("renderLoopSize", {         
3803          value: 0,       
3804          validator: lang.isNumber        
3805      });         
3806
3807     /**
3808     * @attribute formatRow
3809     * @description A function that accepts a TR element and its associated Record
3810     * for custom formatting. The function must return TRUE in order to automatically
3811     * continue formatting of child TD elements, else TD elements will not be
3812     * automatically formatted.
3813     * @type function
3814     * @default null
3815     */
3816     this.setAttributeConfig("formatRow", {
3817         value: null,
3818         validator: lang.isFunction
3819     });
3820
3821     /**
3822     * @attribute generateRequest
3823     * @description A function that converts an object literal of desired DataTable
3824     * states into a request value which is then passed to the DataSource's
3825     * sendRequest method in order to retrieve data for those states. This
3826     * function is passed an object literal of state data and a reference to the
3827     * DataTable instance:
3828     *     
3829     * <dl>
3830     *   <dt>pagination<dt>
3831     *   <dd>        
3832     *         <dt>offsetRecord</dt>
3833     *         <dd>{Number} Index of the first Record of the desired page</dd>
3834     *         <dt>rowsPerPage</dt>
3835     *         <dd>{Number} Number of rows per page</dd>
3836     *   </dd>
3837     *   <dt>sortedBy</dt>
3838     *   <dd>                
3839     *         <dt>key</dt>
3840     *         <dd>{String} Key of sorted Column</dd>
3841     *         <dt>dir</dt>
3842     *         <dd>{String} Sort direction, either YAHOO.widget.DataTable.CLASS_ASC or YAHOO.widget.DataTable.CLASS_DESC</dd>
3843     *   </dd>
3844     *   <dt>self</dt>
3845     *   <dd>The DataTable instance</dd>
3846     * </dl>
3847     * 
3848     * and by default returns a String of syntax:
3849     * "sort={sortColumn}&dir={sortDir}&startIndex={pageStartIndex}&results={rowsPerPage}"
3850     * @type function
3851     * @default HTMLFunction
3852     */
3853     this.setAttributeConfig("generateRequest", {
3854         value: function(oState, oSelf) {
3855             // Set defaults
3856             oState = oState || {pagination:null, sortedBy:null};
3857             var sort = (oState.sortedBy) ? oState.sortedBy.key : oSelf.getColumnSet().keys[0].getKey();
3858             var dir = (oState.sortedBy && oState.sortedBy.dir === YAHOO.widget.DataTable.CLASS_DESC) ? "desc" : "asc";
3859             var startIndex = (oState.pagination) ? oState.pagination.recordOffset : 0;
3860             var results = (oState.pagination) ? oState.pagination.rowsPerPage : null;
3861             
3862             // Build the request
3863             return  "sort=" + sort +
3864                     "&dir=" + dir +
3865                     "&startIndex=" + startIndex +
3866                     ((results !== null) ? "&results=" + results : "");
3867         },
3868         validator: lang.isFunction
3869     });
3870
3871     /**
3872     * @attribute initialRequest
3873     * @description Defines the initial request that gets sent to the DataSource
3874     * during initialization. Value is ignored if initialLoad is set to any value
3875     * other than true.    
3876     * @type MIXED
3877     * @default null
3878     */
3879     this.setAttributeConfig("initialRequest", {
3880         value: null
3881     });
3882
3883     /**
3884     * @attribute initialLoad
3885     * @description Determines whether or not to load data at instantiation. By
3886     * default, will trigger a sendRequest() to the DataSource and pass in the
3887     * request defined by initialRequest. If set to false, data will not load
3888     * at instantiation. Alternatively, implementers who wish to work with a 
3889     * custom payload may pass in an object literal with the following values:
3890     *     
3891     *    <dl>
3892     *      <dt>request (MIXED)</dt>
3893     *      <dd>Request value.</dd>
3894     *
3895     *      <dt>argument (MIXED)</dt>
3896     *      <dd>Custom data that will be passed through to the callback function.</dd>
3897     *    </dl>
3898     *
3899     *                    
3900     * @type Boolean | Object
3901     * @default true
3902     */
3903     this.setAttributeConfig("initialLoad", {
3904         value: true
3905     });
3906     
3907     /**
3908     * @attribute dynamicData
3909     * @description If true, sorting and pagination are relegated to the DataSource
3910     * for handling, using the request returned by the "generateRequest" function.
3911     * Each new DataSource response blows away all previous Records. False by default, so 
3912     * sorting and pagination will be handled directly on the client side, without
3913     * causing any new requests for data from the DataSource.
3914     * @type Boolean
3915     * @default false
3916     */
3917     this.setAttributeConfig("dynamicData", {
3918         value: false,
3919         validator: lang.isBoolean
3920     });
3921
3922     /**
3923      * @attribute MSG_EMPTY      
3924      * @description Message to display if DataTable has no data.     
3925      * @type String      
3926      * @default "No records found."      
3927      */          
3928      this.setAttributeConfig("MSG_EMPTY", {      
3929          value: "No records found.",     
3930          validator: lang.isString        
3931      });         
3932
3933     /**
3934      * @attribute MSG_LOADING    
3935      * @description Message to display while DataTable is loading data.
3936      * @type String      
3937      * @default "Loading..."     
3938      */          
3939      this.setAttributeConfig("MSG_LOADING", {    
3940          value: "Loading...",    
3941          validator: lang.isString        
3942      });         
3943
3944     /**
3945      * @attribute MSG_ERROR      
3946      * @description Message to display while DataTable has data error.
3947      * @type String      
3948      * @default "Data error."    
3949      */          
3950      this.setAttributeConfig("MSG_ERROR", {      
3951          value: "Data error.",   
3952          validator: lang.isString        
3953      });         
3954
3955     /**
3956      * @attribute MSG_SORTASC 
3957      * @description Message to display in tooltip to sort Column in ascending order.
3958      * @type String      
3959      * @default "Click to sort ascending"        
3960      */          
3961      this.setAttributeConfig("MSG_SORTASC", {    
3962          value: "Click to sort ascending",       
3963          validator: lang.isString,
3964          method: function(sParam) {
3965             if(this._elThead) {
3966                 for(var i=0, allKeys=this.getColumnSet().keys, len=allKeys.length; i<len; i++) {
3967                     if(allKeys[i].sortable && this.getColumnSortDir(allKeys[i]) === DT.CLASS_ASC) {
3968                         allKeys[i]._elThLabel.firstChild.title = sParam;
3969                     }
3970                 }
3971             }      
3972          }
3973      });
3974
3975     /**
3976      * @attribute MSG_SORTDESC 
3977      * @description Message to display in tooltip to sort Column in descending order.
3978      * @type String      
3979      * @default "Click to sort descending"       
3980      */          
3981      this.setAttributeConfig("MSG_SORTDESC", {   
3982          value: "Click to sort descending",      
3983          validator: lang.isString,
3984          method: function(sParam) {
3985             if(this._elThead) {
3986                 for(var i=0, allKeys=this.getColumnSet().keys, len=allKeys.length; i<len; i++) {
3987                     if(allKeys[i].sortable && this.getColumnSortDir(allKeys[i]) === DT.CLASS_DESC) {
3988                         allKeys[i]._elThLabel.firstChild.title = sParam;
3989                     }
3990                 }
3991             }               
3992          }
3993      });
3994      
3995     /**
3996      * @attribute currencySymbol
3997      * @deprecated
3998      */
3999     this.setAttributeConfig("currencySymbol", {
4000         value: "$",
4001         validator: lang.isString
4002     });
4003     
4004     /**
4005      * Default config passed to YAHOO.util.Number.format() by the 'currency' Column formatter.
4006      * @attribute currencyOptions
4007      * @type Object
4008      * @default {prefix: $, decimalPlaces:2, decimalSeparator:".", thousandsSeparator:","}
4009      */
4010     this.setAttributeConfig("currencyOptions", {
4011         value: {
4012             prefix: this.get("currencySymbol"), // TODO: deprecate currencySymbol
4013             decimalPlaces:2,
4014             decimalSeparator:".",
4015             thousandsSeparator:","
4016         }
4017     });
4018     
4019     /**
4020      * Default config passed to YAHOO.util.Date.format() by the 'date' Column formatter.
4021      * @attribute dateOptions
4022      * @type Object
4023      * @default {format:"%m/%d/%Y", locale:"en"}
4024      */
4025     this.setAttributeConfig("dateOptions", {
4026         value: {format:"%m/%d/%Y", locale:"en"}
4027     });
4028     
4029     /**
4030      * Default config passed to YAHOO.util.Number.format() by the 'number' Column formatter.
4031      * @attribute numberOptions
4032      * @type Object
4033      * @default {decimalPlaces:0, thousandsSeparator:","}
4034      */
4035     this.setAttributeConfig("numberOptions", {
4036         value: {
4037             decimalPlaces:0,
4038             thousandsSeparator:","
4039         }
4040     });
4041
4042 },
4043
4044 /////////////////////////////////////////////////////////////////////////////
4045 //
4046 // Private member variables
4047 //
4048 /////////////////////////////////////////////////////////////////////////////
4049
4050 /**
4051  * True if instance is initialized, so as to fire the initEvent after render.
4052  *
4053  * @property _bInit
4054  * @type Boolean
4055  * @default true
4056  * @private
4057  */
4058 _bInit : true,
4059
4060 /**
4061  * Index assigned to instance.
4062  *
4063  * @property _nIndex
4064  * @type Number
4065  * @private
4066  */
4067 _nIndex : null,
4068
4069 /**
4070  * Counter for IDs assigned to TR elements.
4071  *
4072  * @property _nTrCount
4073  * @type Number
4074  * @private
4075  */
4076 _nTrCount : 0,
4077
4078 /**
4079  * Counter for IDs assigned to TD elements.
4080  *
4081  * @property _nTdCount
4082  * @type Number
4083  * @private
4084  */
4085 _nTdCount : 0,
4086
4087 /**
4088  * Unique id assigned to instance "yui-dtN", useful prefix for generating unique
4089  * DOM ID strings and log messages.
4090  *
4091  * @property _sId
4092  * @type String
4093  * @private
4094  */
4095 _sId : null,
4096
4097 /**
4098  * Render chain.
4099  *
4100  * @property _oChainRender
4101  * @type YAHOO.util.Chain
4102  * @private
4103  */
4104 _oChainRender : null,
4105
4106 /**
4107  * DOM reference to the container element for the DataTable instance into which
4108  * all other elements get created.
4109  *
4110  * @property _elContainer
4111  * @type HTMLElement
4112  * @private
4113  */
4114 _elContainer : null,
4115
4116 /**
4117  * DOM reference to the mask element for the DataTable instance which disables it.
4118  *
4119  * @property _elMask
4120  * @type HTMLElement
4121  * @private
4122  */
4123 _elMask : null,
4124
4125 /**
4126  * DOM reference to the TABLE element for the DataTable instance.
4127  *
4128  * @property _elTable
4129  * @type HTMLElement
4130  * @private
4131  */
4132 _elTable : null,
4133
4134 /**
4135  * DOM reference to the CAPTION element for the DataTable instance.
4136  *
4137  * @property _elCaption
4138  * @type HTMLElement
4139  * @private
4140  */
4141 _elCaption : null,
4142
4143 /**
4144  * DOM reference to the COLGROUP element for the DataTable instance.
4145  *
4146  * @property _elColgroup
4147  * @type HTMLElement
4148  * @private
4149  */
4150 _elColgroup : null,
4151
4152 /**
4153  * DOM reference to the THEAD element for the DataTable instance.
4154  *
4155  * @property _elThead
4156  * @type HTMLElement
4157  * @private
4158  */
4159 _elThead : null,
4160
4161 /**
4162  * DOM reference to the primary TBODY element for the DataTable instance.
4163  *
4164  * @property _elTbody
4165  * @type HTMLElement
4166  * @private
4167  */
4168 _elTbody : null,
4169
4170 /**
4171  * DOM reference to the secondary TBODY element used to display DataTable messages.
4172  *
4173  * @property _elMsgTbody
4174  * @type HTMLElement
4175  * @private
4176  */
4177 _elMsgTbody : null,
4178
4179 /**
4180  * DOM reference to the secondary TBODY element's single TR element used to display DataTable messages.
4181  *
4182  * @property _elMsgTr
4183  * @type HTMLElement
4184  * @private
4185  */
4186 _elMsgTr : null,
4187
4188 /**
4189  * DOM reference to the secondary TBODY element's single TD element used to display DataTable messages.
4190  *
4191  * @property _elMsgTd
4192  * @type HTMLElement
4193  * @private
4194  */
4195 _elMsgTd : null,
4196
4197 /**
4198  * DataSource instance for the DataTable instance.
4199  *
4200  * @property _oDataSource
4201  * @type YAHOO.util.DataSource
4202  * @private
4203  */
4204 _oDataSource : null,
4205
4206 /**
4207  * ColumnSet instance for the DataTable instance.
4208  *
4209  * @property _oColumnSet
4210  * @type YAHOO.widget.ColumnSet
4211  * @private
4212  */
4213 _oColumnSet : null,
4214
4215 /**
4216  * RecordSet instance for the DataTable instance.
4217  *
4218  * @property _oRecordSet
4219  * @type YAHOO.widget.RecordSet
4220  * @private
4221  */
4222 _oRecordSet : null,
4223
4224 /**
4225  * The active CellEditor instance for the DataTable instance.
4226  *
4227  * @property _oCellEditor
4228  * @type YAHOO.widget.CellEditor
4229  * @private
4230  */
4231 _oCellEditor : null,
4232
4233 /**
4234  * ID string of first TR element of the current DataTable page.
4235  *
4236  * @property _sFirstTrId
4237  * @type String
4238  * @private
4239  */
4240 _sFirstTrId : null,
4241
4242 /**
4243  * ID string of the last TR element of the current DataTable page.
4244  *
4245  * @property _sLastTrId
4246  * @type String
4247  * @private
4248  */
4249 _sLastTrId : null,
4250
4251 /**
4252  * Template row to create all new rows from.
4253  * @property _elTrTemplate
4254  * @type {HTMLElement}
4255  * @private 
4256  */
4257 _elTrTemplate : null,
4258
4259 /**
4260  * Sparse array of custom functions to set column widths for browsers that don't
4261  * support dynamic CSS rules.  Functions are added at the index representing
4262  * the number of rows they update.
4263  *
4264  * @property _aDynFunctions
4265  * @type Array
4266  * @private
4267  */
4268 _aDynFunctions : [],
4269
4270
4271
4272
4273
4274
4275
4276
4277
4278
4279
4280
4281
4282
4283
4284
4285
4286
4287
4288
4289
4290
4291
4292
4293
4294
4295
4296
4297
4298 /////////////////////////////////////////////////////////////////////////////
4299 //
4300 // Private methods
4301 //
4302 /////////////////////////////////////////////////////////////////////////////
4303
4304 /**
4305  * Clears browser text selection. Useful to call on rowSelectEvent or
4306  * cellSelectEvent to prevent clicks or dblclicks from selecting text in the
4307  * browser.
4308  *
4309  * @method clearTextSelection
4310  */
4311 clearTextSelection : function() {
4312     var sel;
4313     if(window.getSelection) {
4314         sel = window.getSelection();
4315     }
4316     else if(document.getSelection) {
4317         sel = document.getSelection();
4318     }
4319     else if(document.selection) {
4320         sel = document.selection;
4321     }
4322     if(sel) {
4323         if(sel.empty) {
4324             sel.empty();
4325         }
4326         else if (sel.removeAllRanges) {
4327             sel.removeAllRanges();
4328         }
4329         else if(sel.collapse) {
4330             sel.collapse();
4331         }
4332     }
4333 },
4334
4335 /**
4336  * Sets focus on the given element.
4337  *
4338  * @method _focusEl
4339  * @param el {HTMLElement} Element.
4340  * @private
4341  */
4342 _focusEl : function(el) {
4343     el = el || this._elTbody;
4344     // http://developer.mozilla.org/en/docs/index.php?title=Key-navigable_custom_DHTML_widgets
4345     // The timeout is necessary in both IE and Firefox 1.5, to prevent scripts from doing
4346     // strange unexpected things as the user clicks on buttons and other controls.
4347     setTimeout(function() {
4348         try {
4349             el.focus();
4350         }
4351         catch(e) {
4352         }
4353     },0);
4354 },
4355
4356 /**
4357  * Forces Gecko repaint.
4358  *
4359  * @method _repaintGecko
4360  * @el {HTMLElement} (Optional) Element to repaint, otherwise entire document body.
4361  * @private
4362  */
4363 _repaintGecko : (ua.gecko) ? 
4364     function(el) {
4365         el = el || this._elContainer;
4366         var parent = el.parentNode;
4367         var nextSibling = el.nextSibling;
4368         parent.insertBefore(parent.removeChild(el), nextSibling);
4369     } : function() {},
4370
4371 /**
4372  * Forces Opera repaint.
4373  *
4374  * @method _repaintOpera
4375  * @private 
4376  */
4377 _repaintOpera : (ua.opera) ? 
4378     function() {
4379         if(ua.opera) {
4380             document.documentElement.className += " ";
4381             document.documentElement.className.trim();
4382         }
4383     } : function() {} ,
4384
4385 /**
4386  * Forces Webkit repaint.
4387  *
4388  * @method _repaintWebkit
4389  * @el {HTMLElement} (Optional) Element to repaint, otherwise entire document body.
4390  * @private
4391  */
4392 _repaintWebkit : (ua.webkit) ? 
4393     function(el) {
4394         el = el || this._elContainer;
4395         var parent = el.parentNode;
4396         var nextSibling = el.nextSibling;
4397         parent.insertBefore(parent.removeChild(el), nextSibling);
4398     } : function() {},
4399
4400
4401
4402
4403
4404
4405
4406
4407
4408
4409
4410
4411
4412
4413
4414
4415
4416
4417
4418
4419
4420
4421 // INIT FUNCTIONS
4422
4423 /**
4424  * Initializes object literal of config values.
4425  *
4426  * @method _initConfigs
4427  * @param oConfig {Object} Object literal of config values.
4428  * @private
4429  */
4430 _initConfigs : function(oConfigs) {
4431     if(!oConfigs || !lang.isObject(oConfigs)) {
4432         oConfigs = {};
4433     }
4434     this.configs = oConfigs;
4435 },
4436
4437 /**
4438  * Initializes ColumnSet.
4439  *
4440  * @method _initColumnSet
4441  * @param aColumnDefs {Object[]} Array of object literal Column definitions.
4442  * @private
4443  */
4444 _initColumnSet : function(aColumnDefs) {
4445     var oColumn, i, len;
4446     
4447     if(this._oColumnSet) {
4448         // First clear _oDynStyles for existing ColumnSet and
4449         // uregister CellEditor Custom Events
4450         for(i=0, len=this._oColumnSet.keys.length; i<len; i++) {
4451             oColumn = this._oColumnSet.keys[i];
4452             DT._oDynStyles["."+this.getId()+"-col-"+oColumn.getSanitizedKey()+" ."+DT.CLASS_LINER] = undefined;
4453             if(oColumn.editor && oColumn.editor.unsubscribeAll) { // Backward compatibility
4454                 oColumn.editor.unsubscribeAll();
4455             }
4456         }
4457         
4458         this._oColumnSet = null;
4459         this._clearTrTemplateEl();
4460     }
4461     
4462     if(lang.isArray(aColumnDefs)) {
4463         this._oColumnSet =  new YAHOO.widget.ColumnSet(aColumnDefs);
4464     }
4465     // Backward compatibility
4466     else if(aColumnDefs instanceof YAHOO.widget.ColumnSet) {
4467         this._oColumnSet =  aColumnDefs;
4468         YAHOO.log("DataTable's constructor now requires an array" +
4469         " of object literal Column definitions instead of a ColumnSet instance",
4470         "warn", this.toString());
4471     }
4472
4473     // Register CellEditor Custom Events
4474     var allKeys = this._oColumnSet.keys;
4475     for(i=0, len=allKeys.length; i<len; i++) {
4476         oColumn = allKeys[i];
4477         if(oColumn.editor && oColumn.editor.subscribe) { // Backward incompatibility
4478             oColumn.editor.subscribe("showEvent", this._onEditorShowEvent, this, true);
4479             oColumn.editor.subscribe("keydownEvent", this._onEditorKeydownEvent, this, true);
4480             oColumn.editor.subscribe("revertEvent", this._onEditorRevertEvent, this, true);
4481             oColumn.editor.subscribe("saveEvent", this._onEditorSaveEvent, this, true);
4482             oColumn.editor.subscribe("cancelEvent", this._onEditorCancelEvent, this, true);
4483             oColumn.editor.subscribe("blurEvent", this._onEditorBlurEvent, this, true);
4484             oColumn.editor.subscribe("blockEvent", this._onEditorBlockEvent, this, true);
4485             oColumn.editor.subscribe("unblockEvent", this._onEditorUnblockEvent, this, true);
4486         }
4487     }
4488 },
4489
4490 /**
4491  * Initializes DataSource.
4492  *
4493  * @method _initDataSource
4494  * @param oDataSource {YAHOO.util.DataSource} DataSource instance.
4495  * @private
4496  */
4497 _initDataSource : function(oDataSource) {
4498     this._oDataSource = null;
4499     if(oDataSource && (oDataSource instanceof DS)) {
4500         this._oDataSource = oDataSource;
4501     }
4502     // Backward compatibility
4503     else {
4504         var tmpTable = null;
4505         var tmpContainer = this._elContainer;
4506         var i=0;
4507         //TODO: this will break if re-initing DS at runtime for SDT
4508         // Peek in container child nodes to see if TABLE already exists
4509         if(tmpContainer.hasChildNodes()) {
4510             var tmpChildren = tmpContainer.childNodes;
4511             for(i=0; i<tmpChildren.length; i++) {
4512                 if(tmpChildren[i].nodeName && tmpChildren[i].nodeName.toLowerCase() == "table") {
4513                     tmpTable = tmpChildren[i];
4514                     break;
4515                 }
4516             }
4517             if(tmpTable) {
4518                 var tmpFieldsArray = [];
4519                 for(; i<this._oColumnSet.keys.length; i++) {
4520                     tmpFieldsArray.push({key:this._oColumnSet.keys[i].key});
4521                 }
4522
4523                 this._oDataSource = new DS(tmpTable);
4524                 this._oDataSource.responseType = DS.TYPE_HTMLTABLE;
4525                 this._oDataSource.responseSchema = {fields: tmpFieldsArray};
4526                 YAHOO.log("Null DataSource for progressive enhancement from" +
4527                 " markup has been deprecated", "warn", this.toString());
4528             }
4529         }
4530     }
4531 },
4532
4533 /**
4534  * Initializes RecordSet.
4535  *
4536  * @method _initRecordSet
4537  * @private
4538  */
4539 _initRecordSet : function() {
4540     if(this._oRecordSet) {
4541         this._oRecordSet.reset();
4542     }
4543     else {
4544         this._oRecordSet = new YAHOO.widget.RecordSet();
4545     }
4546 },
4547
4548 /**
4549  * Initializes DOM elements.
4550  *
4551  * @method _initDomElements
4552  * @param elContainer {HTMLElement | String} HTML DIV element by reference or ID. 
4553  * return {Boolean} False in case of error, otherwise true 
4554  * @private
4555  */
4556 _initDomElements : function(elContainer) {
4557     // Outer container
4558     this._initContainerEl(elContainer);
4559     // TABLE
4560     this._initTableEl(this._elContainer);
4561     // COLGROUP
4562     this._initColgroupEl(this._elTable);
4563     // THEAD
4564     this._initTheadEl(this._elTable);
4565     
4566     // Message TBODY
4567     this._initMsgTbodyEl(this._elTable);  
4568
4569     // Primary TBODY
4570     this._initTbodyEl(this._elTable);
4571
4572     if(!this._elContainer || !this._elTable || !this._elColgroup ||  !this._elThead || !this._elTbody || !this._elMsgTbody) {
4573         YAHOO.log("Could not instantiate DataTable due to an invalid DOM elements", "error", this.toString());
4574         return false;
4575     }
4576     else {
4577         return true;
4578     }
4579 },
4580
4581 /**
4582  * Destroy's the DataTable outer container element, if available.
4583  *
4584  * @method _destroyContainerEl
4585  * @param elContainer {HTMLElement} Reference to the container element. 
4586  * @private
4587  */
4588 _destroyContainerEl : function(elContainer) {
4589     Dom.removeClass(elContainer, DT.CLASS_DATATABLE);
4590     Ev.purgeElement(elContainer, true);
4591     elContainer.innerHTML = "";
4592     
4593     this._elContainer = null;
4594     this._elColgroup = null;
4595     this._elThead = null;
4596     this._elTbody = null;
4597 },
4598
4599 /**
4600  * Initializes the DataTable outer container element, including a mask.
4601  *
4602  * @method _initContainerEl
4603  * @param elContainer {HTMLElement | String} HTML DIV element by reference or ID.
4604  * @private
4605  */
4606 _initContainerEl : function(elContainer) {
4607     // Validate container
4608     elContainer = Dom.get(elContainer);
4609     
4610     if(elContainer && elContainer.nodeName && (elContainer.nodeName.toLowerCase() == "div")) {
4611         // Destroy previous
4612         this._destroyContainerEl(elContainer);
4613
4614         Dom.addClass(elContainer, DT.CLASS_DATATABLE);
4615         Ev.addListener(elContainer, "focus", this._onTableFocus, this);
4616         Ev.addListener(elContainer, "dblclick", this._onTableDblclick, this);
4617         this._elContainer = elContainer;
4618         
4619         var elMask = document.createElement("div");
4620         elMask.className = DT.CLASS_MASK;
4621         elMask.style.display = "none";
4622         this._elMask = elContainer.appendChild(elMask);
4623     }
4624 },
4625
4626 /**
4627  * Destroy's the DataTable TABLE element, if available.
4628  *
4629  * @method _destroyTableEl
4630  * @private
4631  */
4632 _destroyTableEl : function() {
4633     var elTable = this._elTable;
4634     if(elTable) {
4635         Ev.purgeElement(elTable, true);
4636         elTable.parentNode.removeChild(elTable);
4637         this._elCaption = null;
4638         this._elColgroup = null;
4639         this._elThead = null;
4640         this._elTbody = null;
4641     }
4642 },
4643
4644 /**
4645  * Creates HTML markup CAPTION element.
4646  *
4647  * @method _initCaptionEl
4648  * @param sCaption {String} Text for caption.
4649  * @private
4650  */
4651 _initCaptionEl : function(sCaption) {
4652     if(this._elTable && sCaption) {
4653         // Create CAPTION element
4654         if(!this._elCaption) { 
4655             this._elCaption = this._elTable.createCaption();
4656         }
4657         // Set CAPTION value
4658         this._elCaption.innerHTML = sCaption;
4659     }
4660     else if(this._elCaption) {
4661         this._elCaption.parentNode.removeChild(this._elCaption);
4662     }
4663 },
4664
4665 /**
4666  * Creates HTML markup for TABLE, COLGROUP, THEAD and TBODY elements in outer
4667  * container element.
4668  *
4669  * @method _initTableEl
4670  * @param elContainer {HTMLElement} Container element into which to create TABLE.
4671  * @private
4672  */
4673 _initTableEl : function(elContainer) {
4674     if(elContainer) {
4675         // Destroy previous
4676         this._destroyTableEl();
4677     
4678         // Create TABLE
4679         this._elTable = elContainer.appendChild(document.createElement("table"));  
4680          
4681         // Set SUMMARY attribute
4682         this._elTable.summary = this.get("summary");
4683         
4684         // Create CAPTION element
4685         if(this.get("caption")) {
4686             this._initCaptionEl(this.get("caption"));
4687         }
4688     } 
4689 },
4690
4691 /**
4692  * Destroy's the DataTable COLGROUP element, if available.
4693  *
4694  * @method _destroyColgroupEl
4695  * @private
4696  */
4697 _destroyColgroupEl : function() {
4698     var elColgroup = this._elColgroup;
4699     if(elColgroup) {
4700         var elTable = elColgroup.parentNode;
4701         Ev.purgeElement(elColgroup, true);
4702         elTable.removeChild(elColgroup);
4703         this._elColgroup = null;
4704     }
4705 },
4706
4707 /**
4708  * Initializes COLGROUP and COL elements for managing minWidth.
4709  *
4710  * @method _initColgroupEl
4711  * @param elTable {HTMLElement} TABLE element into which to create COLGROUP.
4712  * @private
4713  */
4714 _initColgroupEl : function(elTable) {
4715     if(elTable) {
4716         // Destroy previous
4717         this._destroyColgroupEl();
4718
4719         // Add COLs to DOCUMENT FRAGMENT
4720         var allCols = this._aColIds || [],
4721             allKeys = this._oColumnSet.keys,
4722             i = 0, len = allCols.length,
4723             elCol, oColumn,
4724             elFragment = document.createDocumentFragment(),
4725             elColTemplate = document.createElement("col");
4726     
4727         for(i=0,len=allKeys.length; i<len; i++) {
4728             oColumn = allKeys[i];
4729             elCol = elFragment.appendChild(elColTemplate.cloneNode(false));
4730         }
4731     
4732         // Create COLGROUP
4733         var elColgroup = elTable.insertBefore(document.createElement("colgroup"), elTable.firstChild);
4734         elColgroup.appendChild(elFragment);
4735         this._elColgroup = elColgroup;
4736     }
4737 },
4738
4739 /**
4740  * Adds a COL element to COLGROUP at given index.
4741  *
4742  * @method _insertColgroupColEl
4743  * @param index {Number} Index of new COL element.
4744  * @private
4745  */
4746 _insertColgroupColEl : function(index) {
4747     if(lang.isNumber(index)&& this._elColgroup) {
4748         var nextSibling = this._elColgroup.childNodes[index] || null;
4749         this._elColgroup.insertBefore(document.createElement("col"), nextSibling);
4750     }
4751 },
4752
4753 /**
4754  * Removes a COL element to COLGROUP at given index.
4755  *
4756  * @method _removeColgroupColEl
4757  * @param index {Number} Index of removed COL element.
4758  * @private
4759  */
4760 _removeColgroupColEl : function(index) {
4761     if(lang.isNumber(index) && this._elColgroup && this._elColgroup.childNodes[index]) {
4762         this._elColgroup.removeChild(this._elColgroup.childNodes[index]);
4763     }
4764 },
4765
4766 /**
4767  * Reorders a COL element from old index(es) to new index.
4768  *
4769  * @method _reorderColgroupColEl
4770  * @param aKeyIndexes {Number[]} Array of indexes of removed COL element.
4771  * @param newIndex {Number} New index. 
4772  * @private
4773  */
4774 _reorderColgroupColEl : function(aKeyIndexes, newIndex) {
4775     if(lang.isArray(aKeyIndexes) && lang.isNumber(newIndex) && this._elColgroup && (this._elColgroup.childNodes.length > aKeyIndexes[aKeyIndexes.length-1])) {
4776         var i,
4777             tmpCols = [];
4778         // Remove COL
4779         for(i=aKeyIndexes.length-1; i>-1; i--) {
4780             tmpCols.push(this._elColgroup.removeChild(this._elColgroup.childNodes[aKeyIndexes[i]]));
4781         }
4782         // Insert COL
4783         var nextSibling = this._elColgroup.childNodes[newIndex] || null;
4784         for(i=tmpCols.length-1; i>-1; i--) {
4785             this._elColgroup.insertBefore(tmpCols[i], nextSibling);
4786         }
4787     }
4788 },
4789
4790 /**
4791  * Destroy's the DataTable THEAD element, if available.
4792  *
4793  * @method _destroyTheadEl
4794  * @private
4795  */
4796 _destroyTheadEl : function() {
4797     var elThead = this._elThead;
4798     if(elThead) {
4799         var elTable = elThead.parentNode;
4800         Ev.purgeElement(elThead, true);
4801         this._destroyColumnHelpers();
4802         elTable.removeChild(elThead);
4803         this._elThead = null;
4804     }
4805 },
4806
4807 /**
4808  * Initializes THEAD element.
4809  *
4810  * @method _initTheadEl
4811  * @param elTable {HTMLElement} TABLE element into which to create COLGROUP.
4812  * @param {HTMLElement} Initialized THEAD element. 
4813  * @private
4814  */
4815 _initTheadEl : function(elTable) {
4816     elTable = elTable || this._elTable;
4817     
4818     if(elTable) {
4819         // Destroy previous
4820         this._destroyTheadEl();
4821     
4822         //TODO: append to DOM later for performance
4823         var elThead = (this._elColgroup) ?
4824             elTable.insertBefore(document.createElement("thead"), this._elColgroup.nextSibling) :
4825             elTable.appendChild(document.createElement("thead"));
4826     
4827         // Set up DOM events for THEAD
4828         Ev.addListener(elThead, "focus", this._onTheadFocus, this);
4829         Ev.addListener(elThead, "keydown", this._onTheadKeydown, this);
4830         Ev.addListener(elThead, "mouseover", this._onTableMouseover, this);
4831         Ev.addListener(elThead, "mouseout", this._onTableMouseout, this);
4832         Ev.addListener(elThead, "mousedown", this._onTableMousedown, this);
4833         Ev.addListener(elThead, "mouseup", this._onTableMouseup, this);
4834         Ev.addListener(elThead, "click", this._onTheadClick, this);
4835
4836         // Since we can't listen for click and dblclick on the same element...
4837         // Attach separately to THEAD and TBODY
4838         ///Ev.addListener(elThead, "dblclick", this._onTableDblclick, this);
4839         
4840        var oColumnSet = this._oColumnSet,
4841             oColumn, i,j, l;
4842         
4843         // Add TRs to the THEAD
4844         var colTree = oColumnSet.tree;
4845         var elTh;
4846         for(i=0; i<colTree.length; i++) {
4847             var elTheadTr = elThead.appendChild(document.createElement("tr"));
4848     
4849             // ...and create TH cells
4850             for(j=0; j<colTree[i].length; j++) {
4851                 oColumn = colTree[i][j];
4852                 elTh = elTheadTr.appendChild(document.createElement("th"));
4853                 this._initThEl(elTh,oColumn);
4854             }
4855     
4856                 // Set FIRST/LAST on THEAD rows
4857                 if(i === 0) {
4858                     Dom.addClass(elTheadTr, DT.CLASS_FIRST);
4859                 }
4860                 if(i === (colTree.length-1)) {
4861                     Dom.addClass(elTheadTr, DT.CLASS_LAST);
4862                 }
4863
4864         }
4865
4866         // Set FIRST/LAST on edge TH elements using the values in ColumnSet headers array
4867         var aFirstHeaders = oColumnSet.headers[0] || [];
4868         for(i=0; i<aFirstHeaders.length; i++) {
4869             Dom.addClass(Dom.get(this.getId() +"-th-"+aFirstHeaders[i]), DT.CLASS_FIRST);
4870         }
4871         var aLastHeaders = oColumnSet.headers[oColumnSet.headers.length-1] || [];
4872         for(i=0; i<aLastHeaders.length; i++) {
4873             Dom.addClass(Dom.get(this.getId() +"-th-"+aLastHeaders[i]), DT.CLASS_LAST);
4874         }
4875         
4876         YAHOO.log("TH cells for " + this._oColumnSet.keys.length + " keys created","info",this.toString());
4877
4878         ///TODO: try _repaintGecko(this._elContainer) instead
4879         // Bug 1806891
4880         if(ua.webkit && ua.webkit < 420) {
4881             var oSelf = this;
4882             setTimeout(function() {
4883                 elThead.style.display = "";
4884             },0);
4885             elThead.style.display = 'none';
4886         }
4887         
4888         this._elThead = elThead;
4889         
4890         // Column helpers needs _elThead to exist
4891         this._initColumnHelpers();  
4892     }
4893 },
4894
4895 /**
4896  * Populates TH element as defined by Column.
4897  *
4898  * @method _initThEl
4899  * @param elTh {HTMLElement} TH element reference.
4900  * @param oColumn {YAHOO.widget.Column} Column object.
4901  * @private
4902  */
4903 _initThEl : function(elTh, oColumn) {
4904     elTh.id = this.getId() + "-th-" + oColumn.getSanitizedKey(); // Needed for accessibility, getColumn by TH, and ColumnDD
4905     elTh.innerHTML = "";
4906     elTh.rowSpan = oColumn.getRowspan();
4907     elTh.colSpan = oColumn.getColspan();
4908     oColumn._elTh = elTh;
4909     
4910     var elThLiner = elTh.appendChild(document.createElement("div"));
4911     elThLiner.id = elTh.id + "-liner"; // Needed for resizer
4912     elThLiner.className = DT.CLASS_LINER;
4913     oColumn._elThLiner = elThLiner;
4914     
4915     var elThLabel = elThLiner.appendChild(document.createElement("span"));
4916     elThLabel.className = DT.CLASS_LABEL;    
4917
4918     // Assign abbr attribute
4919     if(oColumn.abbr) {
4920         elTh.abbr = oColumn.abbr;
4921     }
4922     // Clear minWidth on hidden Columns
4923     if(oColumn.hidden) {
4924         this._clearMinWidth(oColumn);
4925     }
4926         
4927     elTh.className = this._getColumnClassNames(oColumn);
4928             
4929     // Set Column width...
4930     if(oColumn.width) {
4931         // Validate minWidth
4932         var nWidth = (oColumn.minWidth && (oColumn.width < oColumn.minWidth)) ?
4933                 oColumn.minWidth : oColumn.width;
4934         // ...for fallback cases
4935         if(DT._bDynStylesFallback) {
4936             elTh.firstChild.style.overflow = 'hidden';
4937             elTh.firstChild.style.width = nWidth + 'px';        
4938         }
4939         // ...for non fallback cases
4940         else {
4941             this._setColumnWidthDynStyles(oColumn, nWidth + 'px', 'hidden');
4942         }
4943     }
4944
4945     this.formatTheadCell(elThLabel, oColumn, this.get("sortedBy"));
4946     oColumn._elThLabel = elThLabel;
4947 },
4948
4949 /**
4950  * Outputs markup into the given TH based on given Column.
4951  *
4952  * @method DataTable.formatTheadCell
4953  * @param elCellLabel {HTMLElement} The label SPAN element within the TH liner,
4954  * not the liner DIV element.     
4955  * @param oColumn {YAHOO.widget.Column} Column instance.
4956  * @param oSortedBy {Object} Sort state object literal.
4957 */
4958 formatTheadCell : function(elCellLabel, oColumn, oSortedBy) {
4959     var sKey = oColumn.getKey();
4960     var sLabel = lang.isValue(oColumn.label) ? oColumn.label : sKey;
4961
4962     // Add accessibility link for sortable Columns
4963     if(oColumn.sortable) {
4964         // Calculate the direction
4965         var sSortClass = this.getColumnSortDir(oColumn, oSortedBy);
4966         var bDesc = (sSortClass === DT.CLASS_DESC);
4967
4968         // This is the sorted Column
4969         if(oSortedBy && (oColumn.key === oSortedBy.key)) {
4970             bDesc = !(oSortedBy.dir === DT.CLASS_DESC);
4971         }
4972
4973         // Generate a unique HREF for visited status
4974         var sHref = this.getId() + "-href-" + oColumn.getSanitizedKey();
4975         
4976         // Generate a dynamic TITLE for sort status
4977         var sTitle = (bDesc) ? this.get("MSG_SORTDESC") : this.get("MSG_SORTASC");
4978         
4979         // Format the element
4980         elCellLabel.innerHTML = "<a href=\"" + sHref + "\" title=\"" + sTitle + "\" class=\"" + DT.CLASS_SORTABLE + "\">" + sLabel + "</a>";
4981     }
4982     // Just display the label for non-sortable Columns
4983     else {
4984         elCellLabel.innerHTML = sLabel;
4985     }
4986 },
4987
4988 /**
4989  * Disables DD from top-level Column TH elements.
4990  *
4991  * @method _destroyDraggableColumns
4992  * @private
4993  */
4994 _destroyDraggableColumns : function() {
4995     var oColumn, elTh;
4996     for(var i=0, len=this._oColumnSet.tree[0].length; i<len; i++) {
4997         oColumn = this._oColumnSet.tree[0][i];
4998         if(oColumn._dd) {
4999             oColumn._dd = oColumn._dd.unreg();
5000             Dom.removeClass(oColumn.getThEl(), DT.CLASS_DRAGGABLE);       
5001         }
5002     }
5003 },
5004
5005 /**
5006  * Initializes top-level Column TH elements into DD instances.
5007  *
5008  * @method _initDraggableColumns
5009  * @private
5010  */
5011 _initDraggableColumns : function() {
5012     this._destroyDraggableColumns();
5013     if(util.DD) {
5014         var oColumn, elTh, elDragTarget;
5015         for(var i=0, len=this._oColumnSet.tree[0].length; i<len; i++) {
5016             oColumn = this._oColumnSet.tree[0][i];
5017             elTh = oColumn.getThEl();
5018             Dom.addClass(elTh, DT.CLASS_DRAGGABLE);
5019             elDragTarget = DT._initColumnDragTargetEl();
5020             oColumn._dd = new YAHOO.widget.ColumnDD(this, oColumn, elTh, elDragTarget);
5021         }
5022     }
5023     else {
5024         YAHOO.log("Could not find DragDrop for draggable Columns", "warn", this.toString());
5025     }
5026 },
5027
5028 /**
5029  * Disables resizeability on key Column TH elements.
5030  *
5031  * @method _destroyResizeableColumns
5032  * @private
5033  */
5034 _destroyResizeableColumns : function() {
5035     var aKeys = this._oColumnSet.keys;
5036     for(var i=0, len=aKeys.length; i<len; i++) {
5037         if(aKeys[i]._ddResizer) {
5038             aKeys[i]._ddResizer = aKeys[i]._ddResizer.unreg();
5039             Dom.removeClass(aKeys[i].getThEl(), DT.CLASS_RESIZEABLE);
5040         }
5041     }
5042 },
5043
5044 /**
5045  * Initializes resizeability on key Column TH elements.
5046  *
5047  * @method _initResizeableColumns
5048  * @private
5049  */
5050 _initResizeableColumns : function() {
5051     this._destroyResizeableColumns();
5052     if(util.DD) {
5053         var oColumn, elTh, elThLiner, elThResizerLiner, elThResizer, elResizerProxy, cancelClick;
5054         for(var i=0, len=this._oColumnSet.keys.length; i<len; i++) {
5055             oColumn = this._oColumnSet.keys[i];
5056             if(oColumn.resizeable) {
5057                 elTh = oColumn.getThEl();
5058                 Dom.addClass(elTh, DT.CLASS_RESIZEABLE);
5059                 elThLiner = oColumn.getThLinerEl();
5060                 
5061                 // Bug 1915349: So resizer is as tall as TH when rowspan > 1
5062                 // Create a separate resizer liner with position:relative
5063                 elThResizerLiner = elTh.appendChild(document.createElement("div"));
5064                 elThResizerLiner.className = DT.CLASS_RESIZERLINER;
5065                 
5066                 // Move TH contents into the new resizer liner
5067                 elThResizerLiner.appendChild(elThLiner);
5068                 
5069                 // Create the resizer
5070                 elThResizer = elThResizerLiner.appendChild(document.createElement("div"));
5071                 elThResizer.id = elTh.id + "-resizer"; // Needed for ColumnResizer
5072                 elThResizer.className = DT.CLASS_RESIZER;
5073                 oColumn._elResizer = elThResizer;
5074
5075                 // Create the resizer proxy, once globally
5076                 elResizerProxy = DT._initColumnResizerProxyEl();
5077                 oColumn._ddResizer = new YAHOO.util.ColumnResizer(
5078                         this, oColumn, elTh, elThResizer, elResizerProxy);
5079                 cancelClick = function(e) {
5080                     Ev.stopPropagation(e);
5081                 };
5082                 Ev.addListener(elThResizer,"click",cancelClick);
5083             }
5084         }
5085     }
5086     else {
5087         YAHOO.log("Could not find DragDrop for resizeable Columns", "warn", this.toString());
5088     }
5089 },
5090
5091 /**
5092  * Destroys elements associated with Column functionality: ColumnDD and ColumnResizers.
5093  *
5094  * @method _destroyColumnHelpers
5095  * @private
5096  */
5097 _destroyColumnHelpers : function() {
5098     this._destroyDraggableColumns();
5099     this._destroyResizeableColumns();
5100 },
5101
5102 /**
5103  * Initializes elements associated with Column functionality: ColumnDD and ColumnResizers.
5104  *
5105  * @method _initColumnHelpers
5106  * @private
5107  */
5108 _initColumnHelpers : function() {
5109     if(this.get("draggableColumns")) {
5110         this._initDraggableColumns();
5111     }
5112     this._initResizeableColumns();
5113 },
5114
5115 /**
5116  * Destroy's the DataTable TBODY element, if available.
5117  *
5118  * @method _destroyTbodyEl
5119  * @private
5120  */
5121 _destroyTbodyEl : function() {
5122     var elTbody = this._elTbody;
5123     if(elTbody) {
5124         var elTable = elTbody.parentNode;
5125         Ev.purgeElement(elTbody, true);
5126         elTable.removeChild(elTbody);
5127         this._elTbody = null;
5128     }
5129 },
5130
5131 /**
5132  * Initializes TBODY element for data.
5133  *
5134  * @method _initTbodyEl
5135  * @param elTable {HTMLElement} TABLE element into which to create TBODY .
5136  * @private
5137  */
5138 _initTbodyEl : function(elTable) {
5139     if(elTable) {
5140         // Destroy previous
5141         this._destroyTbodyEl();
5142         
5143         // Create TBODY
5144         var elTbody = elTable.appendChild(document.createElement("tbody"));
5145         elTbody.tabIndex = 0;
5146         elTbody.className = DT.CLASS_DATA;
5147     
5148         // Set up DOM events for TBODY
5149         Ev.addListener(elTbody, "focus", this._onTbodyFocus, this);
5150         Ev.addListener(elTbody, "mouseover", this._onTableMouseover, this);
5151         Ev.addListener(elTbody, "mouseout", this._onTableMouseout, this);
5152         Ev.addListener(elTbody, "mousedown", this._onTableMousedown, this);
5153         Ev.addListener(elTbody, "mouseup", this._onTableMouseup, this);
5154         Ev.addListener(elTbody, "keydown", this._onTbodyKeydown, this);
5155         Ev.addListener(elTbody, "keypress", this._onTableKeypress, this);
5156         Ev.addListener(elTbody, "click", this._onTbodyClick, this);
5157         
5158         // Since we can't listen for click and dblclick on the same element...
5159         // Attach separately to THEAD and TBODY
5160         ///Ev.addListener(elTbody, "dblclick", this._onTableDblclick, this);
5161         
5162     
5163         // IE puts focus outline in the wrong place
5164         if(ua.ie) {
5165             elTbody.hideFocus=true;
5166         }
5167
5168         this._elTbody = elTbody;
5169     }
5170 },
5171
5172 /**
5173  * Destroy's the DataTable message TBODY element, if available.
5174  *
5175  * @method _destroyMsgTbodyEl
5176  * @private
5177  */
5178 _destroyMsgTbodyEl : function() {
5179     var elMsgTbody = this._elMsgTbody;
5180     if(elMsgTbody) {
5181         var elTable = elMsgTbody.parentNode;
5182         Ev.purgeElement(elMsgTbody, true);
5183         elTable.removeChild(elMsgTbody);
5184         this._elTbody = null;
5185     }
5186 },
5187
5188 /**
5189  * Initializes TBODY element for messaging.
5190  *
5191  * @method _initMsgTbodyEl
5192  * @param elTable {HTMLElement} TABLE element into which to create TBODY 
5193  * @private
5194  */
5195 _initMsgTbodyEl : function(elTable) {
5196     if(elTable) {
5197         var elMsgTbody = document.createElement("tbody");
5198         elMsgTbody.className = DT.CLASS_MESSAGE;
5199         var elMsgTr = elMsgTbody.appendChild(document.createElement("tr"));
5200         elMsgTr.className = DT.CLASS_FIRST + " " + DT.CLASS_LAST;
5201         this._elMsgTr = elMsgTr;
5202         var elMsgTd = elMsgTr.appendChild(document.createElement("td"));
5203         elMsgTd.colSpan = this._oColumnSet.keys.length || 1;
5204         elMsgTd.className = DT.CLASS_FIRST + " " + DT.CLASS_LAST;
5205         this._elMsgTd = elMsgTd;
5206         elMsgTbody = elTable.insertBefore(elMsgTbody, this._elTbody);
5207         var elMsgLiner = elMsgTd.appendChild(document.createElement("div"));
5208         elMsgLiner.className = DT.CLASS_LINER;
5209         this._elMsgTbody = elMsgTbody;
5210     }
5211 },
5212
5213 /**
5214  * Initialize internal event listeners
5215  *
5216  * @method _initEvents
5217  * @private
5218  */
5219 _initEvents : function () {
5220     // Initialize Column sort
5221     this._initColumnSort();
5222         
5223     // Add the document level click listener
5224     YAHOO.util.Event.addListener(document, "click", this._onDocumentClick, this);
5225
5226     // Paginator integration
5227     this.subscribe("paginatorChange",function () {
5228         this._handlePaginatorChange.apply(this,arguments);
5229     });
5230
5231     this.subscribe("initEvent",function () {
5232         this.renderPaginator();
5233     });
5234
5235     // Initialize CellEditor integration
5236     this._initCellEditing();
5237 },
5238
5239 /**      
5240   * Initializes Column sorting.          
5241   *      
5242   * @method _initColumnSort      
5243   * @private     
5244   */     
5245 _initColumnSort : function() {
5246     this.subscribe("theadCellClickEvent", this.onEventSortColumn);       
5247
5248     // Backward compatibility
5249     var oSortedBy = this.get("sortedBy");
5250     if(oSortedBy) {
5251         if(oSortedBy.dir == "desc") {
5252             this._configs.sortedBy.value.dir = DT.CLASS_DESC;
5253         }
5254         else if(oSortedBy.dir == "asc") {
5255             this._configs.sortedBy.value.dir = DT.CLASS_ASC;
5256         }
5257     }
5258 },
5259
5260 /**      
5261   * Initializes CellEditor integration.          
5262   *      
5263   * @method _initCellEditing     
5264   * @private     
5265   */     
5266 _initCellEditing : function() {
5267     this.subscribe("editorBlurEvent",function () {
5268         this.onEditorBlurEvent.apply(this,arguments);
5269     });
5270     this.subscribe("editorBlockEvent",function () {
5271         this.onEditorBlockEvent.apply(this,arguments);
5272     });
5273     this.subscribe("editorUnblockEvent",function () {
5274         this.onEditorUnblockEvent.apply(this,arguments);
5275     });
5276 },
5277
5278
5279
5280
5281
5282
5283
5284
5285
5286
5287
5288
5289
5290
5291
5292
5293
5294
5295
5296
5297
5298
5299
5300
5301
5302
5303
5304
5305
5306
5307
5308
5309
5310 // DOM MUTATION FUNCTIONS
5311
5312 /**
5313  * Retruns classnames to represent current Column states.
5314  * @method _getColumnClassnames 
5315  * @param oColumn {YAHOO.widget.Column} Column instance.
5316  * @param aAddClasses {String[]} An array of additional classnames to add to the
5317  * return value.  
5318  * @return {String} A String of classnames to be assigned to TH or TD elements
5319  * for given Column.  
5320  * @private 
5321  */
5322 _getColumnClassNames : function (oColumn, aAddClasses) {
5323     var allClasses;
5324     
5325     // Add CSS classes
5326     if(lang.isString(oColumn.className)) {
5327         // Single custom class
5328         allClasses = [oColumn.className];
5329     }
5330     else if(lang.isArray(oColumn.className)) {
5331         // Array of custom classes
5332         allClasses = oColumn.className;
5333     }
5334     else {
5335         // no custom classes
5336         allClasses = [];
5337     }
5338     
5339     // Hook for setting width with via dynamic style uses key since ID is too disposable
5340     allClasses[allClasses.length] = this.getId() + "-col-" +oColumn.getSanitizedKey();
5341
5342     // Column key - minus any chars other than "A-Z", "a-z", "0-9", "_", "-", ".", or ":"
5343     allClasses[allClasses.length] = "yui-dt-col-" +oColumn.getSanitizedKey();
5344
5345     var isSortedBy = this.get("sortedBy") || {};
5346     // Sorted
5347     if(oColumn.key === isSortedBy.key) {
5348         allClasses[allClasses.length] = isSortedBy.dir || '';
5349     }
5350     // Hidden
5351     if(oColumn.hidden) {
5352         allClasses[allClasses.length] = DT.CLASS_HIDDEN;
5353     }
5354     // Selected
5355     if(oColumn.selected) {
5356         allClasses[allClasses.length] = DT.CLASS_SELECTED;
5357     }
5358     // Sortable
5359     if(oColumn.sortable) {
5360         allClasses[allClasses.length] = DT.CLASS_SORTABLE;
5361     }
5362     // Resizeable
5363     if(oColumn.resizeable) {
5364         allClasses[allClasses.length] = DT.CLASS_RESIZEABLE;
5365     }
5366     // Editable
5367     if(oColumn.editor) {
5368         allClasses[allClasses.length] = DT.CLASS_EDITABLE;
5369     }
5370     
5371     // Addtnl classes, including First/Last
5372     if(aAddClasses) {
5373         allClasses = allClasses.concat(aAddClasses);
5374     }
5375     
5376     return allClasses.join(' ');  
5377 },
5378
5379 /**
5380  * Clears TR element template in response to any Column state change.
5381  * @method _clearTrTemplateEl
5382  * @private 
5383  */
5384 _clearTrTemplateEl : function () {
5385     this._elTrTemplate = null;
5386 },
5387
5388 /**
5389  * Returns a new TR element template with TD elements classed with current
5390  * Column states.
5391  * @method _getTrTemplateEl 
5392  * @return {HTMLElement} A TR element to be cloned and added to the DOM.
5393  * @private 
5394  */
5395 _getTrTemplateEl : function (oRecord, index) {
5396     // Template is already available
5397     if(this._elTrTemplate) {
5398         return this._elTrTemplate;
5399     }
5400     // Template needs to be created
5401     else {
5402         var d   = document,
5403             tr  = d.createElement('tr'),
5404             td  = d.createElement('td'),
5405             div = d.createElement('div');
5406     
5407         // Append the liner element
5408         td.appendChild(div);
5409
5410         // Create TD elements into DOCUMENT FRAGMENT
5411         var df = document.createDocumentFragment(),
5412             allKeys = this._oColumnSet.keys,
5413             elTd;
5414
5415         // Set state for each TD;
5416         var aAddClasses;
5417         for(var i=0, keysLen=allKeys.length; i<keysLen; i++) {
5418             // Clone the TD template
5419             elTd = td.cloneNode(true);
5420
5421             // Format the base TD
5422             elTd = this._formatTdEl(allKeys[i], elTd, i, (i===keysLen-1));
5423                         
5424             df.appendChild(elTd);
5425         }
5426         tr.appendChild(df);
5427         this._elTrTemplate = tr;
5428         return tr;
5429     }   
5430 },
5431
5432 /**
5433  * Formats a basic TD element.
5434  * @method _formatTdEl 
5435  * @param oColumn {YAHOO.widget.Column} Associated Column instance. 
5436  * @param elTd {HTMLElement} An unformatted TD element.
5437  * @param index {Number} Column key index. 
5438  * @param isLast {Boolean} True if Column is last key of the ColumnSet.
5439  * @return {HTMLElement} A formatted TD element.
5440  * @private 
5441  */
5442 _formatTdEl : function (oColumn, elTd, index, isLast) {
5443     var oColumnSet = this._oColumnSet;
5444     
5445     // Set the TD's accessibility headers
5446     var allHeaders = oColumnSet.headers,
5447         allColHeaders = allHeaders[index],
5448         sTdHeaders = "",
5449         sHeader;
5450     for(var j=0, headersLen=allColHeaders.length; j < headersLen; j++) {
5451         sHeader = this._sId + "-th-" + allColHeaders[j] + ' ';
5452         sTdHeaders += sHeader;
5453     }
5454     elTd.headers = sTdHeaders;
5455     
5456     // Class the TD element
5457     var aAddClasses = [];
5458     if(index === 0) {
5459         aAddClasses[aAddClasses.length] = DT.CLASS_FIRST;
5460     }
5461     if(isLast) {
5462         aAddClasses[aAddClasses.length] = DT.CLASS_LAST;
5463     }
5464     elTd.className = this._getColumnClassNames(oColumn, aAddClasses);
5465
5466     // Class the liner element
5467     elTd.firstChild.className = DT.CLASS_LINER;
5468
5469     // Set Column width for fallback cases
5470     if(oColumn.width && DT._bDynStylesFallback) {
5471         // Validate minWidth
5472         var nWidth = (oColumn.minWidth && (oColumn.width < oColumn.minWidth)) ?
5473                 oColumn.minWidth : oColumn.width;
5474         elTd.firstChild.style.overflow = 'hidden';
5475         elTd.firstChild.style.width = nWidth + 'px';
5476     }
5477     
5478     return elTd;
5479 },
5480
5481
5482 /**
5483  * Create a new TR element for a given Record and appends it with the correct
5484  * number of Column-state-classed TD elements. Striping is the responsibility of
5485  * the calling function, which may decide to stripe the single row, a subset of
5486  * rows, or all the rows.
5487  * @method _createTrEl
5488  * @param oRecord {YAHOO.widget.Record} Record instance
5489  * @return {HTMLElement} The new TR element.  This must be added to the DOM.
5490  * @private 
5491  */
5492 _addTrEl : function (oRecord) {
5493     var elTrTemplate = this._getTrTemplateEl();
5494     
5495     // Clone the TR template.
5496     var elTr = elTrTemplate.cloneNode(true);
5497     
5498     // Populate content
5499     return this._updateTrEl(elTr,oRecord);
5500 },
5501
5502 /**
5503  * Formats the contents of the given TR's TD elements with data from the given
5504  * Record. Only innerHTML should change, nothing structural.
5505  *
5506  * @method _updateTrEl
5507  * @param elTr {HTMLElement} The TR element to update.
5508  * @param oRecord {YAHOO.widget.Record} The associated Record instance.
5509  * @return {HTMLElement} DOM reference to the new TR element.
5510  * @private
5511  */
5512 _updateTrEl : function(elTr, oRecord) {
5513     var ok = this.get("formatRow") ? this.get("formatRow").call(this, elTr, oRecord) : true;
5514     if(ok) {
5515         // Hide the row to prevent constant reflows
5516         elTr.style.display = 'none';
5517         
5518         // Update TD elements with new data
5519         var allTds = elTr.childNodes,
5520             elTd;
5521         for(var i=0,len=allTds.length; i<len; ++i) {
5522             elTd = allTds[i];
5523             
5524             // Set the cell content
5525             this.formatCell(allTds[i].firstChild, oRecord, this._oColumnSet.keys[i]);
5526         }
5527         
5528         // Redisplay the row for reflow
5529         elTr.style.display = '';
5530     }
5531     
5532     elTr.id = oRecord.getId(); // Needed for Record association and tracking of FIRST/LAST
5533     return elTr;
5534 },
5535
5536
5537 /**
5538  * Deletes TR element by DOM reference or by DataTable page row index.
5539  *
5540  * @method _deleteTrEl
5541  * @param row {HTMLElement | Number} TR element reference or Datatable page row index.
5542  * @return {Boolean} Returns true if successful, else returns false.
5543  * @private
5544  */
5545 _deleteTrEl : function(row) {
5546     var rowIndex;
5547
5548     // Get page row index for the element
5549     if(!lang.isNumber(row)) {
5550         rowIndex = Dom.get(row).sectionRowIndex;
5551     }
5552     else {
5553         rowIndex = row;
5554     }
5555     if(lang.isNumber(rowIndex) && (rowIndex > -2) && (rowIndex < this._elTbody.rows.length)) {
5556         // Cannot use tbody.deleteRow due to IE6 instability
5557         //return this._elTbody.deleteRow(rowIndex);
5558         return this._elTbody.removeChild(this.getTrEl(row));
5559     }
5560     else {
5561         return null;
5562     }
5563 },
5564
5565
5566
5567
5568
5569
5570
5571
5572
5573
5574
5575
5576
5577
5578
5579
5580
5581
5582
5583
5584
5585
5586
5587
5588
5589
5590
5591 // CSS/STATE FUNCTIONS
5592
5593
5594
5595
5596 /**
5597  * Removes the class YAHOO.widget.DataTable.CLASS_FIRST from the first TR element
5598  * of the DataTable page and updates internal tracker.
5599  *
5600  * @method _unsetFirstRow
5601  * @private
5602  */
5603 _unsetFirstRow : function() {
5604     // Remove FIRST
5605     if(this._sFirstTrId) {
5606         Dom.removeClass(this._sFirstTrId, DT.CLASS_FIRST);
5607         this._sFirstTrId = null;
5608     }
5609 },
5610
5611 /**
5612  * Assigns the class YAHOO.widget.DataTable.CLASS_FIRST to the first TR element
5613  * of the DataTable page and updates internal tracker.
5614  *
5615  * @method _setFirstRow
5616  * @private
5617  */
5618 _setFirstRow : function() {
5619     this._unsetFirstRow();
5620     var elTr = this.getFirstTrEl();
5621     if(elTr) {
5622         // Set FIRST
5623         Dom.addClass(elTr, DT.CLASS_FIRST);
5624         this._sFirstTrId = elTr.id;
5625     }
5626 },
5627
5628 /**
5629  * Removes the class YAHOO.widget.DataTable.CLASS_LAST from the last TR element
5630  * of the DataTable page and updates internal tracker.
5631  *
5632  * @method _unsetLastRow
5633  * @private
5634  */
5635 _unsetLastRow : function() {
5636     // Unassign previous class
5637     if(this._sLastTrId) {
5638         Dom.removeClass(this._sLastTrId, DT.CLASS_LAST);
5639         this._sLastTrId = null;
5640     }   
5641 },
5642
5643 /**
5644  * Assigns the class YAHOO.widget.DataTable.CLASS_LAST to the last TR element
5645  * of the DataTable page and updates internal tracker.
5646  *
5647  * @method _setLastRow
5648  * @private
5649  */
5650 _setLastRow : function() {
5651     this._unsetLastRow();
5652     var elTr = this.getLastTrEl();
5653     if(elTr) {
5654         // Assign class
5655         Dom.addClass(elTr, DT.CLASS_LAST);
5656         this._sLastTrId = elTr.id;
5657     }
5658 },
5659
5660 /**
5661  * Assigns the classes DT.CLASS_EVEN and DT.CLASS_ODD to one, many, or all TR elements.
5662  *
5663  * @method _setRowStripes
5664  * @param row {HTMLElement | String | Number} (optional) HTML TR element reference
5665  * or string ID, or page row index of where to start striping.
5666  * @param range {Number} (optional) If given, how many rows to stripe, otherwise
5667  * stripe all the rows until the end.
5668  * @private
5669  */
5670 _setRowStripes : function(row, range) {
5671     // Default values stripe all rows
5672     var allRows = this._elTbody.rows,
5673         nStartIndex = 0,
5674         nEndIndex = allRows.length,
5675         aOdds = [], nOddIdx = 0,
5676         aEvens = [], nEvenIdx = 0;
5677
5678     // Stripe a subset
5679     if((row !== null) && (row !== undefined)) {
5680         // Validate given start row
5681         var elStartRow = this.getTrEl(row);
5682         if(elStartRow) {
5683             nStartIndex = elStartRow.sectionRowIndex;
5684
5685             // Validate given range
5686             if(lang.isNumber(range) && (range > 1)) {
5687                 nEndIndex = nStartIndex + range;
5688             }
5689         }
5690     }
5691
5692     for(var i=nStartIndex; i<nEndIndex; i++) {
5693         if(i%2) {
5694             aOdds[nOddIdx++] = allRows[i];
5695         } else {
5696             aEvens[nEvenIdx++] = allRows[i];
5697         }
5698     }
5699
5700     if (aOdds.length) {
5701         Dom.replaceClass(aOdds, DT.CLASS_EVEN, DT.CLASS_ODD);
5702     }
5703
5704     if (aEvens.length) {
5705         Dom.replaceClass(aEvens, DT.CLASS_ODD, DT.CLASS_EVEN);
5706     }
5707 },
5708
5709 /**
5710  * Assigns the class DT.CLASS_SELECTED to TR and TD elements.
5711  *
5712  * @method _setSelections
5713  * @private
5714  */
5715 _setSelections : function() {
5716     // Keep track of selected rows
5717     var allSelectedRows = this.getSelectedRows();
5718     // Keep track of selected cells
5719     var allSelectedCells = this.getSelectedCells();
5720     // Anything to select?
5721     if((allSelectedRows.length>0) || (allSelectedCells.length > 0)) {
5722         var oColumnSet = this._oColumnSet,
5723             el;
5724         // Loop over each row
5725         for(var i=0; i<allSelectedRows.length; i++) {
5726             el = Dom.get(allSelectedRows[i]);
5727             if(el) {
5728                 Dom.addClass(el, DT.CLASS_SELECTED);
5729             }
5730         }
5731         // Loop over each cell
5732         for(i=0; i<allSelectedCells.length; i++) {
5733             el = Dom.get(allSelectedCells[i].recordId);
5734             if(el) {
5735                 Dom.addClass(el.childNodes[oColumnSet.getColumn(allSelectedCells[i].columnKey).getKeyIndex()], DT.CLASS_SELECTED);
5736             }
5737         }
5738     }       
5739 },
5740
5741
5742
5743
5744
5745
5746
5747
5748
5749
5750
5751
5752
5753
5754
5755
5756
5757
5758
5759
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 // Private DOM Event Handlers
5786 //
5787 /////////////////////////////////////////////////////////////////////////////
5788
5789 /**
5790  * Validates minWidths whenever the render chain ends.
5791  *
5792  * @method _onRenderChainEnd
5793  * @private
5794  */
5795 _onRenderChainEnd : function() {
5796     // Hide loading message
5797     this.hideTableMessage();
5798     
5799     // Show empty message
5800     if(this._elTbody.rows.length === 0) {
5801         this.showTableMessage(this.get("MSG_EMPTY"), DT.CLASS_EMPTY);        
5802     }
5803
5804     // Execute in timeout thread to give implementers a chance
5805     // to subscribe after the constructor
5806     var oSelf = this;
5807     setTimeout(function() {
5808         if((oSelf instanceof DT) && oSelf._sId) {        
5809             // Init event
5810             if(oSelf._bInit) {
5811                 oSelf._bInit = false;
5812                 oSelf.fireEvent("initEvent");
5813             }
5814     
5815             // Render event
5816             oSelf.fireEvent("renderEvent");
5817             // Backward compatibility
5818             oSelf.fireEvent("refreshEvent");
5819             YAHOO.log("DataTable rendered", "info", oSelf.toString());
5820     
5821             // Post-render routine
5822             oSelf.validateColumnWidths();
5823     
5824             // Post-render event
5825             oSelf.fireEvent("postRenderEvent");
5826             
5827             /*if(YAHOO.example.Performance.trialStart) {
5828                 YAHOO.log((new Date()).getTime() - YAHOO.example.Performance.trialStart.getTime() + " ms", "time");
5829                 YAHOO.example.Performance.trialStart = null;
5830             }*/
5831             
5832             YAHOO.log("Post-render routine executed", "info", oSelf.toString());
5833         }
5834     }, 0);
5835 },
5836
5837 /**
5838  * Handles click events on the DOCUMENT.
5839  *
5840  * @method _onDocumentClick
5841  * @param e {HTMLEvent} The click event.
5842  * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
5843  * @private
5844  */
5845 _onDocumentClick : function(e, oSelf) {
5846     var elTarget = Ev.getTarget(e);
5847     var elTag = elTarget.nodeName.toLowerCase();
5848
5849     if(!Dom.isAncestor(oSelf._elContainer, elTarget)) {
5850         oSelf.fireEvent("tableBlurEvent");
5851
5852         // Fires editorBlurEvent when click is not within the TABLE.
5853         // For cases when click is within the TABLE, due to timing issues,
5854         // the editorBlurEvent needs to get fired by the lower-level DOM click
5855         // handlers below rather than by the TABLE click handler directly.
5856         if(oSelf._oCellEditor) {
5857             if(oSelf._oCellEditor.getContainerEl) {
5858                 var elContainer = oSelf._oCellEditor.getContainerEl();
5859                 // Only if the click was not within the CellEditor container
5860                 if(!Dom.isAncestor(elContainer, elTarget) &&
5861                         (elContainer.id !== elTarget.id)) {
5862                     oSelf._oCellEditor.fireEvent("blurEvent", {editor: oSelf._oCellEditor});
5863                 }
5864             }
5865             // Backward Compatibility
5866             else if(oSelf._oCellEditor.isActive) {
5867                 // Only if the click was not within the Cell Editor container
5868                 if(!Dom.isAncestor(oSelf._oCellEditor.container, elTarget) &&
5869                         (oSelf._oCellEditor.container.id !== elTarget.id)) {
5870                     oSelf.fireEvent("editorBlurEvent", {editor:oSelf._oCellEditor});
5871                 }
5872             }
5873         }
5874     }
5875 },
5876
5877 /**
5878  * Handles focus events on the DataTable instance.
5879  *
5880  * @method _onTableFocus
5881  * @param e {HTMLEvent} The focus event.
5882  * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
5883  * @private
5884  */
5885 _onTableFocus : function(e, oSelf) {
5886     oSelf.fireEvent("tableFocusEvent");
5887 },
5888
5889 /**
5890  * Handles focus events on the THEAD element.
5891  *
5892  * @method _onTheadFocus
5893  * @param e {HTMLEvent} The focus event.
5894  * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
5895  * @private
5896  */
5897 _onTheadFocus : function(e, oSelf) {
5898     oSelf.fireEvent("theadFocusEvent");
5899     oSelf.fireEvent("tableFocusEvent");
5900 },
5901
5902 /**
5903  * Handles focus events on the TBODY element.
5904  *
5905  * @method _onTbodyFocus
5906  * @param e {HTMLEvent} The focus event.
5907  * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
5908  * @private
5909  */
5910 _onTbodyFocus : function(e, oSelf) {
5911     oSelf.fireEvent("tbodyFocusEvent");
5912     oSelf.fireEvent("tableFocusEvent");
5913 },
5914
5915 /**
5916  * Handles mouseover events on the DataTable instance.
5917  *
5918  * @method _onTableMouseover
5919  * @param e {HTMLEvent} The mouseover event.
5920  * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
5921  * @private
5922  */
5923 _onTableMouseover : function(e, oSelf) {
5924     var elTarget = Ev.getTarget(e);
5925         var elTag = elTarget.nodeName.toLowerCase();
5926         var bKeepBubbling = true;
5927         while(elTarget && (elTag != "table")) {
5928             switch(elTag) {
5929                 case "body":
5930                      return;
5931                 case "a":
5932                     break;
5933                 case "td":
5934                     bKeepBubbling = oSelf.fireEvent("cellMouseoverEvent",{target:elTarget,event:e});
5935                     break;
5936                 case "span":
5937                     if(Dom.hasClass(elTarget, DT.CLASS_LABEL)) {
5938                         bKeepBubbling = oSelf.fireEvent("theadLabelMouseoverEvent",{target:elTarget,event:e});
5939                         // Backward compatibility
5940                         bKeepBubbling = oSelf.fireEvent("headerLabelMouseoverEvent",{target:elTarget,event:e});
5941                     }
5942                     break;
5943                 case "th":
5944                     bKeepBubbling = oSelf.fireEvent("theadCellMouseoverEvent",{target:elTarget,event:e});
5945                     // Backward compatibility
5946                     bKeepBubbling = oSelf.fireEvent("headerCellMouseoverEvent",{target:elTarget,event:e});
5947                     break;
5948                 case "tr":
5949                     if(elTarget.parentNode.nodeName.toLowerCase() == "thead") {
5950                         bKeepBubbling = oSelf.fireEvent("theadRowMouseoverEvent",{target:elTarget,event:e});
5951                         // Backward compatibility
5952                         bKeepBubbling = oSelf.fireEvent("headerRowMouseoverEvent",{target:elTarget,event:e});
5953                     }
5954                     else {
5955                         bKeepBubbling = oSelf.fireEvent("rowMouseoverEvent",{target:elTarget,event:e});
5956                     }
5957                     break;
5958                 default:
5959                     break;
5960             }
5961             if(bKeepBubbling === false) {
5962                 return;
5963             }
5964             else {
5965                 elTarget = elTarget.parentNode;
5966                 if(elTarget) {
5967                     elTag = elTarget.nodeName.toLowerCase();
5968                 }
5969             }
5970         }
5971         oSelf.fireEvent("tableMouseoverEvent",{target:(elTarget || oSelf._elContainer),event:e});
5972 },
5973
5974 /**
5975  * Handles mouseout events on the DataTable instance.
5976  *
5977  * @method _onTableMouseout
5978  * @param e {HTMLEvent} The mouseout event.
5979  * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
5980  * @private
5981  */
5982 _onTableMouseout : function(e, oSelf) {
5983     var elTarget = Ev.getTarget(e);
5984     var elTag = elTarget.nodeName.toLowerCase();
5985     var bKeepBubbling = true;
5986     while(elTarget && (elTag != "table")) {
5987         switch(elTag) {
5988             case "body":
5989                 return;
5990             case "a":
5991                 break;
5992             case "td":
5993                 bKeepBubbling = oSelf.fireEvent("cellMouseoutEvent",{target:elTarget,event:e});
5994                 break;
5995             case "span":
5996                 if(Dom.hasClass(elTarget, DT.CLASS_LABEL)) {
5997                     bKeepBubbling = oSelf.fireEvent("theadLabelMouseoutEvent",{target:elTarget,event:e});
5998                     // Backward compatibility
5999                     bKeepBubbling = oSelf.fireEvent("headerLabelMouseoutEvent",{target:elTarget,event:e});
6000                 }
6001                 break;
6002             case "th":
6003                 bKeepBubbling = oSelf.fireEvent("theadCellMouseoutEvent",{target:elTarget,event:e});
6004                 // Backward compatibility
6005                 bKeepBubbling = oSelf.fireEvent("headerCellMouseoutEvent",{target:elTarget,event:e});
6006                 break;
6007             case "tr":
6008                 if(elTarget.parentNode.nodeName.toLowerCase() == "thead") {
6009                     bKeepBubbling = oSelf.fireEvent("theadRowMouseoutEvent",{target:elTarget,event:e});
6010                     // Backward compatibility
6011                     bKeepBubbling = oSelf.fireEvent("headerRowMouseoutEvent",{target:elTarget,event:e});
6012                 }
6013                 else {
6014                     bKeepBubbling = oSelf.fireEvent("rowMouseoutEvent",{target:elTarget,event:e});
6015                 }
6016                 break;
6017             default:
6018                 break;
6019         }
6020         if(bKeepBubbling === false) {
6021             return;
6022         }
6023         else {
6024             elTarget = elTarget.parentNode;
6025             if(elTarget) {
6026                 elTag = elTarget.nodeName.toLowerCase();
6027             }
6028         }
6029     }
6030     oSelf.fireEvent("tableMouseoutEvent",{target:(elTarget || oSelf._elContainer),event:e});
6031 },
6032
6033 /**
6034  * Handles mousedown events on the DataTable instance.
6035  *
6036  * @method _onTableMousedown
6037  * @param e {HTMLEvent} The mousedown event.
6038  * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
6039  * @private
6040  */
6041 _onTableMousedown : function(e, oSelf) {
6042     var elTarget = Ev.getTarget(e);
6043     var elTag = elTarget.nodeName.toLowerCase();
6044     var bKeepBubbling = true;
6045     while(elTarget && (elTag != "table")) {
6046         switch(elTag) {
6047             case "body":
6048                 return;
6049             case "a":
6050                 break;
6051             case "td":
6052                 bKeepBubbling = oSelf.fireEvent("cellMousedownEvent",{target:elTarget,event:e});
6053                 break;
6054             case "span":
6055                 if(Dom.hasClass(elTarget, DT.CLASS_LABEL)) {
6056                     bKeepBubbling = oSelf.fireEvent("theadLabelMousedownEvent",{target:elTarget,event:e});
6057                     // Backward compatibility
6058                     bKeepBubbling = oSelf.fireEvent("headerLabelMousedownEvent",{target:elTarget,event:e});
6059                 }
6060                 break;
6061             case "th":
6062                 bKeepBubbling = oSelf.fireEvent("theadCellMousedownEvent",{target:elTarget,event:e});
6063                 // Backward compatibility
6064                 bKeepBubbling = oSelf.fireEvent("headerCellMousedownEvent",{target:elTarget,event:e});
6065                 break;
6066             case "tr":
6067                 if(elTarget.parentNode.nodeName.toLowerCase() == "thead") {
6068                     bKeepBubbling = oSelf.fireEvent("theadRowMousedownEvent",{target:elTarget,event:e});
6069                     // Backward compatibility
6070                     bKeepBubbling = oSelf.fireEvent("headerRowMousedownEvent",{target:elTarget,event:e});
6071                 }
6072                 else {
6073                     bKeepBubbling = oSelf.fireEvent("rowMousedownEvent",{target:elTarget,event:e});
6074                 }
6075                 break;
6076             default:
6077                 break;
6078         }
6079         if(bKeepBubbling === false) {
6080             return;
6081         }
6082         else {
6083             elTarget = elTarget.parentNode;
6084             if(elTarget) {
6085                 elTag = elTarget.nodeName.toLowerCase();
6086             }
6087         }
6088     }
6089     oSelf.fireEvent("tableMousedownEvent",{target:(elTarget || oSelf._elContainer),event:e});
6090 },
6091
6092 /**
6093  * Handles mouseup events on the DataTable instance.
6094  *
6095  * @method _onTableMouseup
6096  * @param e {HTMLEvent} The mouseup event.
6097  * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
6098  * @private
6099  */
6100 _onTableMouseup : function(e, oSelf) {
6101     var elTarget = Ev.getTarget(e);
6102     var elTag = elTarget.nodeName.toLowerCase();
6103     var bKeepBubbling = true;
6104     while(elTarget && (elTag != "table")) {
6105         switch(elTag) {
6106             case "body":
6107                 return;
6108             case "a":
6109                 break;
6110             case "td":
6111                 bKeepBubbling = oSelf.fireEvent("cellMouseupEvent",{target:elTarget,event:e});
6112                 break;
6113             case "span":
6114                 if(Dom.hasClass(elTarget, DT.CLASS_LABEL)) {
6115                     bKeepBubbling = oSelf.fireEvent("theadLabelMouseupEvent",{target:elTarget,event:e});
6116                     // Backward compatibility
6117                     bKeepBubbling = oSelf.fireEvent("headerLabelMouseupEvent",{target:elTarget,event:e});
6118                 }
6119                 break;
6120             case "th":
6121                 bKeepBubbling = oSelf.fireEvent("theadCellMouseupEvent",{target:elTarget,event:e});
6122                 // Backward compatibility
6123                 bKeepBubbling = oSelf.fireEvent("headerCellMouseupEvent",{target:elTarget,event:e});
6124                 break;
6125             case "tr":
6126                 if(elTarget.parentNode.nodeName.toLowerCase() == "thead") {
6127                     bKeepBubbling = oSelf.fireEvent("theadRowMouseupEvent",{target:elTarget,event:e});
6128                     // Backward compatibility
6129                     bKeepBubbling = oSelf.fireEvent("headerRowMouseupEvent",{target:elTarget,event:e});
6130                 }
6131                 else {
6132                     bKeepBubbling = oSelf.fireEvent("rowMouseupEvent",{target:elTarget,event:e});
6133                 }
6134                 break;
6135             default:
6136                 break;
6137         }
6138         if(bKeepBubbling === false) {
6139             return;
6140         }
6141         else {
6142             elTarget = elTarget.parentNode;
6143             if(elTarget) {
6144                 elTag = elTarget.nodeName.toLowerCase();
6145             }
6146         }
6147     }
6148     oSelf.fireEvent("tableMouseupEvent",{target:(elTarget || oSelf._elContainer),event:e});
6149 },
6150
6151 /**
6152  * Handles dblclick events on the DataTable instance.
6153  *
6154  * @method _onTableDblclick
6155  * @param e {HTMLEvent} The dblclick event.
6156  * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
6157  * @private
6158  */
6159 _onTableDblclick : function(e, oSelf) {
6160     var elTarget = Ev.getTarget(e);
6161     var elTag = elTarget.nodeName.toLowerCase();
6162     var bKeepBubbling = true;
6163     while(elTarget && (elTag != "table")) {
6164         switch(elTag) {
6165             case "body":
6166                 return;
6167             case "td":
6168                 bKeepBubbling = oSelf.fireEvent("cellDblclickEvent",{target:elTarget,event:e});
6169                 break;
6170             case "span":
6171                 if(Dom.hasClass(elTarget, DT.CLASS_LABEL)) {
6172                     bKeepBubbling = oSelf.fireEvent("theadLabelDblclickEvent",{target:elTarget,event:e});
6173                     // Backward compatibility
6174                     bKeepBubbling = oSelf.fireEvent("headerLabelDblclickEvent",{target:elTarget,event:e});
6175                 }
6176                 break;
6177             case "th":
6178                 bKeepBubbling = oSelf.fireEvent("theadCellDblclickEvent",{target:elTarget,event:e});
6179                 // Backward compatibility
6180                 bKeepBubbling = oSelf.fireEvent("headerCellDblclickEvent",{target:elTarget,event:e});
6181                 break;
6182             case "tr":
6183                 if(elTarget.parentNode.nodeName.toLowerCase() == "thead") {
6184                     bKeepBubbling = oSelf.fireEvent("theadRowDblclickEvent",{target:elTarget,event:e});
6185                     // Backward compatibility
6186                     bKeepBubbling = oSelf.fireEvent("headerRowDblclickEvent",{target:elTarget,event:e});
6187                 }
6188                 else {
6189                     bKeepBubbling = oSelf.fireEvent("rowDblclickEvent",{target:elTarget,event:e});
6190                 }
6191                 break;
6192             default:
6193                 break;
6194         }
6195         if(bKeepBubbling === false) {
6196             return;
6197         }
6198         else {
6199             elTarget = elTarget.parentNode;
6200             if(elTarget) {
6201                 elTag = elTarget.nodeName.toLowerCase();
6202             }
6203         }
6204     }
6205     oSelf.fireEvent("tableDblclickEvent",{target:(elTarget || oSelf._elContainer),event:e});
6206 },
6207 /**
6208  * Handles keydown events on the THEAD element.
6209  *
6210  * @method _onTheadKeydown
6211  * @param e {HTMLEvent} The key event.
6212  * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
6213  * @private
6214  */
6215 _onTheadKeydown : function(e, oSelf) {
6216     var elTarget = Ev.getTarget(e);
6217     var elTag = elTarget.nodeName.toLowerCase();
6218     var bKeepBubbling = true;
6219     while(elTarget && (elTag != "table")) {
6220         switch(elTag) {
6221             case "body":
6222                 return;
6223             case "input":
6224             case "textarea":
6225                 // TODO: implement textareaKeyEvent
6226                 break;
6227             case "thead":
6228                 bKeepBubbling = oSelf.fireEvent("theadKeyEvent",{target:elTarget,event:e});
6229                 break;
6230             default:
6231                 break;
6232         }
6233         if(bKeepBubbling === false) {
6234             return;
6235         }
6236         else {
6237             elTarget = elTarget.parentNode;
6238             if(elTarget) {
6239                 elTag = elTarget.nodeName.toLowerCase();
6240             }
6241         }
6242     }
6243     oSelf.fireEvent("tableKeyEvent",{target:(elTarget || oSelf._elContainer),event:e});
6244 },
6245
6246 /**
6247  * Handles keydown events on the TBODY element. Handles selection behavior,
6248  * provides hooks for ENTER to edit functionality.
6249  *
6250  * @method _onTbodyKeydown
6251  * @param e {HTMLEvent} The key event.
6252  * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
6253  * @private
6254  */
6255 _onTbodyKeydown : function(e, oSelf) {
6256     var sMode = oSelf.get("selectionMode");
6257
6258     if(sMode == "standard") {
6259         oSelf._handleStandardSelectionByKey(e);
6260     }
6261     else if(sMode == "single") {
6262         oSelf._handleSingleSelectionByKey(e);
6263     }
6264     else if(sMode == "cellblock") {
6265         oSelf._handleCellBlockSelectionByKey(e);
6266     }
6267     else if(sMode == "cellrange") {
6268         oSelf._handleCellRangeSelectionByKey(e);
6269     }
6270     else if(sMode == "singlecell") {
6271         oSelf._handleSingleCellSelectionByKey(e);
6272     }
6273     
6274     if(oSelf._oCellEditor) {
6275         if(oSelf._oCellEditor.fireEvent) {
6276             oSelf._oCellEditor.fireEvent("blurEvent", {editor: oSelf._oCellEditor});
6277         }
6278         else if(oSelf._oCellEditor.isActive) {
6279             oSelf.fireEvent("editorBlurEvent", {editor:oSelf._oCellEditor});
6280         }
6281     }
6282
6283     var elTarget = Ev.getTarget(e);
6284     var elTag = elTarget.nodeName.toLowerCase();
6285     var bKeepBubbling = true;
6286     while(elTarget && (elTag != "table")) {
6287         switch(elTag) {
6288             case "body":
6289                 return;
6290             case "tbody":
6291                 bKeepBubbling = oSelf.fireEvent("tbodyKeyEvent",{target:elTarget,event:e});
6292                 break;
6293             default:
6294                 break;
6295         }
6296         if(bKeepBubbling === false) {
6297             return;
6298         }
6299         else {
6300             elTarget = elTarget.parentNode;
6301             if(elTarget) {
6302                 elTag = elTarget.nodeName.toLowerCase();
6303             }
6304         }
6305     }
6306     oSelf.fireEvent("tableKeyEvent",{target:(elTarget || oSelf._elContainer),event:e});
6307 },
6308
6309 /**
6310  * Handles keypress events on the TABLE. Mainly to support stopEvent on Mac.
6311  *
6312  * @method _onTableKeypress
6313  * @param e {HTMLEvent} The key event.
6314  * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
6315  * @private
6316  */
6317 _onTableKeypress : function(e, oSelf) {
6318     if(ua.opera || (navigator.userAgent.toLowerCase().indexOf("mac") !== -1) && (ua.webkit < 420)) {
6319         var nKey = Ev.getCharCode(e);
6320         // arrow down
6321         if(nKey == 40) {
6322             Ev.stopEvent(e);
6323         }
6324         // arrow up
6325         else if(nKey == 38) {
6326             Ev.stopEvent(e);
6327         }
6328     }
6329 },
6330
6331 /**
6332  * Handles click events on the THEAD element.
6333  *
6334  * @method _onTheadClick
6335  * @param e {HTMLEvent} The click event.
6336  * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
6337  * @private
6338  */
6339 _onTheadClick : function(e, oSelf) {
6340     // This blurs the CellEditor
6341     if(oSelf._oCellEditor) {
6342         if(oSelf._oCellEditor.fireEvent) {
6343             oSelf._oCellEditor.fireEvent("blurEvent", {editor: oSelf._oCellEditor});
6344         }
6345         // Backward compatibility
6346         else if(oSelf._oCellEditor.isActive) {
6347             oSelf.fireEvent("editorBlurEvent", {editor:oSelf._oCellEditor});
6348         }
6349     }
6350
6351     var elTarget = Ev.getTarget(e),
6352         elTag = elTarget.nodeName.toLowerCase(),
6353         bKeepBubbling = true;
6354     while(elTarget && (elTag != "table")) {
6355         switch(elTag) {
6356             case "body":
6357                 return;
6358             case "input":
6359                 var sType = elTarget.type.toLowerCase();
6360                 if(sType == "checkbox") {
6361                     bKeepBubbling = oSelf.fireEvent("theadCheckboxClickEvent",{target:elTarget,event:e});
6362                 }
6363                 else if(sType == "radio") {
6364                     bKeepBubbling = oSelf.fireEvent("theadRadioClickEvent",{target:elTarget,event:e});
6365                 }
6366                 else if((sType == "button") || (sType == "image") || (sType == "submit") || (sType == "reset")) {
6367                     bKeepBubbling = oSelf.fireEvent("theadButtonClickEvent",{target:elTarget,event:e});
6368                 }
6369                 break;
6370             case "a":
6371                 bKeepBubbling = oSelf.fireEvent("theadLinkClickEvent",{target:elTarget,event:e});
6372                 break;
6373             case "button":
6374                 bKeepBubbling = oSelf.fireEvent("theadButtonClickEvent",{target:elTarget,event:e});
6375                 break;
6376             case "span":
6377                 if(Dom.hasClass(elTarget, DT.CLASS_LABEL)) {
6378                     bKeepBubbling = oSelf.fireEvent("theadLabelClickEvent",{target:elTarget,event:e});
6379                     // Backward compatibility
6380                     bKeepBubbling = oSelf.fireEvent("headerLabelClickEvent",{target:elTarget,event:e});
6381                 }
6382                 break;
6383             case "th":
6384                 bKeepBubbling = oSelf.fireEvent("theadCellClickEvent",{target:elTarget,event:e});
6385                 // Backward compatibility
6386                 bKeepBubbling = oSelf.fireEvent("headerCellClickEvent",{target:elTarget,event:e});
6387                 break;
6388             case "tr":
6389                 bKeepBubbling = oSelf.fireEvent("theadRowClickEvent",{target:elTarget,event:e});
6390                 // Backward compatibility
6391                 bKeepBubbling = oSelf.fireEvent("headerRowClickEvent",{target:elTarget,event:e});
6392                 break;
6393             default:
6394                 break;
6395         }
6396         if(bKeepBubbling === false) {
6397             return;
6398         }
6399         else {
6400             elTarget = elTarget.parentNode;
6401             if(elTarget) {
6402                 elTag = elTarget.nodeName.toLowerCase();
6403             }
6404         }
6405     }
6406     oSelf.fireEvent("tableClickEvent",{target:(elTarget || oSelf._elContainer),event:e});
6407 },
6408
6409 /**
6410  * Handles click events on the primary TBODY element.
6411  *
6412  * @method _onTbodyClick
6413  * @param e {HTMLEvent} The click event.
6414  * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
6415  * @private
6416  */
6417 _onTbodyClick : function(e, oSelf) {
6418     // This blurs the CellEditor
6419     if(oSelf._oCellEditor) {
6420         if(oSelf._oCellEditor.fireEvent) {
6421             oSelf._oCellEditor.fireEvent("blurEvent", {editor: oSelf._oCellEditor});
6422         }
6423         else if(oSelf._oCellEditor.isActive) {
6424             oSelf.fireEvent("editorBlurEvent", {editor:oSelf._oCellEditor});
6425         }
6426     }
6427
6428     // Fire Custom Events
6429     var elTarget = Ev.getTarget(e),
6430         elTag = elTarget.nodeName.toLowerCase(),
6431         bKeepBubbling = true;
6432     while(elTarget && (elTag != "table")) {
6433         switch(elTag) {
6434             case "body":
6435                 return;
6436             case "input":
6437                 var sType = elTarget.type.toLowerCase();
6438                 if(sType == "checkbox") {
6439                     bKeepBubbling = oSelf.fireEvent("checkboxClickEvent",{target:elTarget,event:e});
6440                 }
6441                 else if(sType == "radio") {
6442                     bKeepBubbling = oSelf.fireEvent("radioClickEvent",{target:elTarget,event:e});
6443                 }
6444                 else if((sType == "button") || (sType == "image") || (sType == "submit") || (sType == "reset")) {
6445                     bKeepBubbling = oSelf.fireEvent("buttonClickEvent",{target:elTarget,event:e});
6446                 }
6447                 break;
6448             case "a":
6449                 bKeepBubbling = oSelf.fireEvent("linkClickEvent",{target:elTarget,event:e});
6450                 break;
6451             case "button":
6452                 bKeepBubbling = oSelf.fireEvent("buttonClickEvent",{target:elTarget,event:e});
6453                 break;
6454             case "td":
6455                 bKeepBubbling = oSelf.fireEvent("cellClickEvent",{target:elTarget,event:e});
6456                 break;
6457             case "tr":
6458                 bKeepBubbling = oSelf.fireEvent("rowClickEvent",{target:elTarget,event:e});
6459                 break;
6460             default:
6461                 break;
6462         }
6463         if(bKeepBubbling === false) {
6464             return;
6465         }
6466         else {
6467             elTarget = elTarget.parentNode;
6468             if(elTarget) {
6469                 elTag = elTarget.nodeName.toLowerCase();
6470             }
6471         }
6472     }
6473     oSelf.fireEvent("tableClickEvent",{target:(elTarget || oSelf._elContainer),event:e});
6474 },
6475
6476 /**
6477  * Handles change events on SELECT elements within DataTable.
6478  *
6479  * @method _onDropdownChange
6480  * @param e {HTMLEvent} The change event.
6481  * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
6482  * @private
6483  */
6484 _onDropdownChange : function(e, oSelf) {
6485     var elTarget = Ev.getTarget(e);
6486     oSelf.fireEvent("dropdownChangeEvent", {event:e, target:elTarget});
6487 },
6488
6489
6490
6491
6492
6493
6494
6495
6496
6497
6498
6499
6500
6501
6502
6503
6504
6505
6506
6507
6508
6509
6510
6511
6512
6513
6514
6515
6516
6517
6518
6519
6520 /////////////////////////////////////////////////////////////////////////////
6521 //
6522 // Public member variables
6523 //
6524 /////////////////////////////////////////////////////////////////////////////
6525 /**
6526  * Returns object literal of initial configs.
6527  *
6528  * @property configs
6529  * @type Object
6530  * @default {} 
6531  */
6532 configs: null,
6533
6534
6535 /////////////////////////////////////////////////////////////////////////////
6536 //
6537 // Public methods
6538 //
6539 /////////////////////////////////////////////////////////////////////////////
6540
6541 /**
6542  * Returns unique id assigned to instance, which is a useful prefix for
6543  * generating unique DOM ID strings.
6544  *
6545  * @method getId
6546  * @return {String} Unique ID of the DataSource instance.
6547  */
6548 getId : function() {
6549     return this._sId;
6550 },
6551
6552 /**
6553  * DataSource instance name, for logging.
6554  *
6555  * @method toString
6556  * @return {String} Unique name of the DataSource instance.
6557  */
6558
6559 toString : function() {
6560     return "DataTable instance " + this._sId;
6561 },
6562
6563 /**
6564  * Returns the DataTable instance's DataSource instance.
6565  *
6566  * @method getDataSource
6567  * @return {YAHOO.util.DataSource} DataSource instance.
6568  */
6569 getDataSource : function() {
6570     return this._oDataSource;
6571 },
6572
6573 /**
6574  * Returns the DataTable instance's ColumnSet instance.
6575  *
6576  * @method getColumnSet
6577  * @return {YAHOO.widget.ColumnSet} ColumnSet instance.
6578  */
6579 getColumnSet : function() {
6580     return this._oColumnSet;
6581 },
6582
6583 /**
6584  * Returns the DataTable instance's RecordSet instance.
6585  *
6586  * @method getRecordSet
6587  * @return {YAHOO.widget.RecordSet} RecordSet instance.
6588  */
6589 getRecordSet : function() {
6590     return this._oRecordSet;
6591 },
6592
6593 /**
6594  * Returns on object literal representing the DataTable instance's current
6595  * state with the following properties:
6596  * <dl>
6597  * <dt>pagination</dt>
6598  * <dd>Instance of YAHOO.widget.Paginator</dd>
6599  *
6600  * <dt>sortedBy</dt>
6601  * <dd>
6602  *     <dl>
6603  *         <dt>sortedBy.key</dt>
6604  *         <dd>{String} Key of sorted Column</dd>
6605  *         <dt>sortedBy.dir</dt>
6606  *         <dd>{String} Initial sort direction, either YAHOO.widget.DataTable.CLASS_ASC or YAHOO.widget.DataTable.CLASS_DESC</dd>
6607  *     </dl>
6608  * </dd>
6609  *
6610  * <dt>selectedRows</dt>
6611  * <dd>Array of selected rows by Record ID.</dd>
6612  *
6613  * <dt>selectedCells</dt>
6614  * <dd>Selected cells as an array of object literals:
6615  *     {recordId:sRecordId, columnKey:sColumnKey}</dd>
6616  * </dl>
6617  *  
6618  * @method getState
6619  * @return {Object} DataTable instance state object literal values.
6620  */
6621 getState : function() {
6622     return {
6623         totalRecords: this.get('paginator') ? this.get('paginator').get("totalRecords") : this._oRecordSet.getLength(),
6624         pagination: this.get("paginator") ? this.get("paginator").getState() : null,
6625         sortedBy: this.get("sortedBy"),
6626         selectedRows: this.getSelectedRows(),
6627         selectedCells: this.getSelectedCells()
6628     };
6629 },
6630
6631
6632
6633
6634
6635
6636
6637
6638
6639
6640
6641
6642
6643
6644
6645
6646
6647
6648
6649
6650
6651
6652
6653
6654
6655
6656
6657
6658
6659
6660
6661
6662
6663
6664
6665
6666
6667
6668
6669
6670
6671
6672
6673 // DOM ACCESSORS
6674
6675 /**
6676  * Returns DOM reference to the DataTable's container element.
6677  *
6678  * @method getContainerEl
6679  * @return {HTMLElement} Reference to DIV element.
6680  */
6681 getContainerEl : function() {
6682     return this._elContainer;
6683 },
6684
6685 /**
6686  * Returns DOM reference to the DataTable's TABLE element.
6687  *
6688  * @method getTableEl
6689  * @return {HTMLElement} Reference to TABLE element.
6690  */
6691 getTableEl : function() {
6692     return this._elTable;
6693 },
6694
6695 /**
6696  * Returns DOM reference to the DataTable's THEAD element.
6697  *
6698  * @method getTheadEl
6699  * @return {HTMLElement} Reference to THEAD element.
6700  */
6701 getTheadEl : function() {
6702     return this._elThead;
6703 },
6704
6705 /**
6706  * Returns DOM reference to the DataTable's primary TBODY element.
6707  *
6708  * @method getTbodyEl
6709  * @return {HTMLElement} Reference to TBODY element.
6710  */
6711 getTbodyEl : function() {
6712     return this._elTbody;
6713 },
6714
6715 /**
6716  * Returns DOM reference to the DataTable's secondary TBODY element that is
6717  * used to display messages.
6718  *
6719  * @method getMsgTbodyEl
6720  * @return {HTMLElement} Reference to TBODY element.
6721  */
6722 getMsgTbodyEl : function() {
6723     return this._elMsgTbody;
6724 },
6725
6726 /**
6727  * Returns DOM reference to the TD element within the secondary TBODY that is
6728  * used to display messages.
6729  *
6730  * @method getMsgTdEl
6731  * @return {HTMLElement} Reference to TD element.
6732  */
6733 getMsgTdEl : function() {
6734     return this._elMsgTd;
6735 },
6736
6737 /**
6738  * Returns the corresponding TR reference for a given DOM element, ID string or
6739  * directly page row index. If the given identifier is a child of a TR element,
6740  * then DOM tree is traversed until a parent TR element is returned, otherwise
6741  * null.
6742  *
6743  * @method getTrEl
6744  * @param row {HTMLElement | String | Number | YAHOO.widget.Record} Which row to
6745  * get: by element reference, ID string, page row index, or Record.
6746  * @return {HTMLElement} Reference to TR element, or null.
6747  */
6748 getTrEl : function(row) {
6749     // By Record
6750     if(row instanceof YAHOO.widget.Record) {
6751         return document.getElementById(row.getId());
6752     }
6753     // By page row index
6754     else if(lang.isNumber(row)) {
6755         var allRows = this._elTbody.rows;
6756         return ((row > -1) && (row < allRows.length)) ? allRows[row] : null;
6757     }
6758     // By ID string or element reference
6759     else {
6760         var elRow = (lang.isString(row)) ? document.getElementById(row) : row;
6761
6762         // Validate HTML element
6763         if(elRow && (elRow.ownerDocument == document)) {
6764             // Validate TR element
6765             if(elRow.nodeName.toLowerCase() != "tr") {
6766                 // Traverse up the DOM to find the corresponding TR element
6767                 elRow = Dom.getAncestorByTagName(elRow,"tr");
6768             }
6769
6770             return elRow;
6771         }
6772     }
6773
6774     return null;
6775 },
6776
6777 /**
6778  * Returns DOM reference to the first TR element in the DataTable page, or null.
6779  *
6780  * @method getFirstTrEl
6781  * @return {HTMLElement} Reference to TR element.
6782  */
6783 getFirstTrEl : function() {
6784     return this._elTbody.rows[0] || null;
6785 },
6786
6787 /**
6788  * Returns DOM reference to the last TR element in the DataTable page, or null.
6789  *
6790  * @method getLastTrEl
6791  * @return {HTMLElement} Reference to last TR element.
6792  */
6793 getLastTrEl : function() {
6794     var allRows = this._elTbody.rows;
6795         if(allRows.length > 0) {
6796             return allRows[allRows.length-1] || null;
6797         }
6798 },
6799
6800 /**
6801  * Returns DOM reference to the next TR element from the given TR element, or null.
6802  *
6803  * @method getNextTrEl
6804  * @param row {HTMLElement | String | Number | YAHOO.widget.Record} Element
6805  * reference, ID string, page row index, or Record from which to get next TR element.
6806  * @return {HTMLElement} Reference to next TR element.
6807  */
6808 getNextTrEl : function(row) {
6809     var nThisTrIndex = this.getTrIndex(row);
6810     if(nThisTrIndex !== null) {
6811         var allRows = this._elTbody.rows;
6812         if(nThisTrIndex < allRows.length-1) {
6813             return allRows[nThisTrIndex+1];
6814         }
6815     }
6816
6817     YAHOO.log("Could not get next TR element for row " + row, "info", this.toString());
6818     return null;
6819 },
6820
6821 /**
6822  * Returns DOM reference to the previous TR element from the given TR element, or null.
6823  *
6824  * @method getPreviousTrEl
6825  * @param row {HTMLElement | String | Number | YAHOO.widget.Record} Element
6826  * reference, ID string, page row index, or Record from which to get previous TR element.
6827  * @return {HTMLElement} Reference to previous TR element.
6828  */
6829 getPreviousTrEl : function(row) {
6830     var nThisTrIndex = this.getTrIndex(row);
6831     if(nThisTrIndex !== null) {
6832         var allRows = this._elTbody.rows;
6833         if(nThisTrIndex > 0) {
6834             return allRows[nThisTrIndex-1];
6835         }
6836     }
6837
6838     YAHOO.log("Could not get previous TR element for row " + row, "info", this.toString());
6839     return null;
6840 },
6841
6842 /**
6843  * Returns DOM reference to a TD liner element.
6844  *
6845  * @method getTdLinerEl
6846  * @param cell {HTMLElement | Object} TD element or child of a TD element, or
6847  * object literal of syntax {record:oRecord, column:oColumn}.
6848  * @return {HTMLElement} Reference to TD liner element.
6849  */
6850 getTdLinerEl : function(cell) {
6851     var elCell = this.getTdEl(cell);
6852     return elCell.firstChild || null;
6853 },
6854
6855 /**
6856  * Returns DOM reference to a TD element.
6857  *
6858  * @method getTdEl
6859  * @param cell {HTMLElement | String | Object} TD element or child of a TD element, or
6860  * object literal of syntax {record:oRecord, column:oColumn}.
6861  * @return {HTMLElement} Reference to TD element.
6862  */
6863 getTdEl : function(cell) {
6864     var elCell;
6865     var el = Dom.get(cell);
6866
6867     // Validate HTML element
6868     if(el && (el.ownerDocument == document)) {
6869         // Validate TD element
6870         if(el.nodeName.toLowerCase() != "td") {
6871             // Traverse up the DOM to find the corresponding TR element
6872             elCell = Dom.getAncestorByTagName(el, "td");
6873         }
6874         else {
6875             elCell = el;
6876         }
6877
6878         return elCell;
6879     }
6880     else if(cell) {
6881         var oRecord, nColKeyIndex;
6882
6883         if(lang.isString(cell.columnKey) && lang.isString(cell.recordId)) {
6884             oRecord = this.getRecord(cell.recordId);
6885             var oColumn = this.getColumn(cell.columnKey);
6886             if(oColumn) {
6887                 nColKeyIndex = oColumn.getKeyIndex();
6888             }
6889
6890         }
6891         if(cell.record && cell.column && cell.column.getKeyIndex) {
6892             oRecord = cell.record;
6893             nColKeyIndex = cell.column.getKeyIndex();
6894         }
6895         var elRow = this.getTrEl(oRecord);
6896         if((nColKeyIndex !== null) && elRow && elRow.cells && elRow.cells.length > 0) {
6897             return elRow.cells[nColKeyIndex] || null;
6898         }
6899     }
6900
6901     return null;
6902 },
6903
6904 /**
6905  * Returns DOM reference to the first TD element in the DataTable page (by default),
6906  * the first TD element of the optionally given row, or null.
6907  *
6908  * @method getFirstTdEl
6909  * @param row {HTMLElement} (optional) row from which to get first TD
6910  * @return {HTMLElement} Reference to TD element.
6911  */
6912 getFirstTdEl : function(row) {
6913     var elRow = this.getTrEl(row) || this.getFirstTrEl();
6914     if(elRow && (elRow.cells.length > 0)) {
6915         return elRow.cells[0];
6916     }
6917     YAHOO.log("Could not get first TD element for row " + elRow, "info", this.toString());
6918     return null;
6919 },
6920
6921 /**
6922  * Returns DOM reference to the last TD element in the DataTable page (by default),
6923  * the first TD element of the optionally given row, or null.
6924  *
6925  * @method getLastTdEl
6926  * @return {HTMLElement} Reference to last TD element.
6927  */
6928 getLastTdEl : function(row) {
6929     var elRow = this.getTrEl(row) || this.getLastTrEl();
6930     if(elRow && (elRow.cells.length > 0)) {
6931         return elRow.cells[elRow.cells.length-1];
6932     }
6933     YAHOO.log("Could not get last TD element for row " + elRow, "info", this.toString());
6934     return null;
6935 },
6936
6937 /**
6938  * Returns DOM reference to the next TD element from the given cell, or null.
6939  *
6940  * @method getNextTdEl
6941  * @param cell {HTMLElement | String | Object} DOM element reference or string ID, or
6942  * object literal of syntax {record:oRecord, column:oColumn} from which to get next TD element.
6943  * @return {HTMLElement} Reference to next TD element, or null.
6944  */
6945 getNextTdEl : function(cell) {
6946     var elCell = this.getTdEl(cell);
6947     if(elCell) {
6948         var nThisTdIndex = elCell.cellIndex;
6949         var elRow = this.getTrEl(elCell);
6950         if(nThisTdIndex < elRow.cells.length-1) {
6951             return elRow.cells[nThisTdIndex+1];
6952         }
6953         else {
6954             var elNextRow = this.getNextTrEl(elRow);
6955             if(elNextRow) {
6956                 return elNextRow.cells[0];
6957             }
6958         }
6959     }
6960     YAHOO.log("Could not get next TD element for cell " + cell, "info", this.toString());
6961     return null;
6962 },
6963
6964 /**
6965  * Returns DOM reference to the previous TD element from the given cell, or null.
6966  *
6967  * @method getPreviousTdEl
6968  * @param cell {HTMLElement | String | Object} DOM element reference or string ID, or
6969  * object literal of syntax {record:oRecord, column:oColumn} from which to get previous TD element.
6970  * @return {HTMLElement} Reference to previous TD element, or null.
6971  */
6972 getPreviousTdEl : function(cell) {
6973     var elCell = this.getTdEl(cell);
6974     if(elCell) {
6975         var nThisTdIndex = elCell.cellIndex;
6976         var elRow = this.getTrEl(elCell);
6977         if(nThisTdIndex > 0) {
6978             return elRow.cells[nThisTdIndex-1];
6979         }
6980         else {
6981             var elPreviousRow = this.getPreviousTrEl(elRow);
6982             if(elPreviousRow) {
6983                 return this.getLastTdEl(elPreviousRow);
6984             }
6985         }
6986     }
6987     YAHOO.log("Could not get next TD element for cell " + cell, "info", this.toString());
6988     return null;
6989 },
6990
6991 /**
6992  * Returns DOM reference to the above TD element from the given cell, or null.
6993  *
6994  * @method getAboveTdEl
6995  * @param cell {HTMLElement | String | Object} DOM element reference or string ID, or
6996  * object literal of syntax {record:oRecord, column:oColumn} from which to get next TD element.
6997  * @return {HTMLElement} Reference to next TD element, or null.
6998  */
6999 getAboveTdEl : function(cell) {
7000     var elCell = this.getTdEl(cell);
7001     if(elCell) {
7002         var elPreviousRow = this.getPreviousTrEl(elCell);
7003         if(elPreviousRow) {
7004             return elPreviousRow.cells[elCell.cellIndex];
7005         }
7006     }
7007     YAHOO.log("Could not get above TD element for cell " + cell, "info", this.toString());
7008     return null;
7009 },
7010
7011 /**
7012  * Returns DOM reference to the below TD element from the given cell, or null.
7013  *
7014  * @method getBelowTdEl
7015  * @param cell {HTMLElement | String | Object} DOM element reference or string ID, or
7016  * object literal of syntax {record:oRecord, column:oColumn} from which to get previous TD element.
7017  * @return {HTMLElement} Reference to previous TD element, or null.
7018  */
7019 getBelowTdEl : function(cell) {
7020     var elCell = this.getTdEl(cell);
7021     if(elCell) {
7022         var elNextRow = this.getNextTrEl(elCell);
7023         if(elNextRow) {
7024             return elNextRow.cells[elCell.cellIndex];
7025         }
7026     }
7027     YAHOO.log("Could not get below TD element for cell " + cell, "info", this.toString());
7028     return null;
7029 },
7030
7031 /**
7032  * Returns DOM reference to a TH liner element. Needed to normalize for resizeable 
7033  * Columns, which have an additional resizer liner DIV element between the TH
7034  * element and the liner DIV element. 
7035  *
7036  * @method getThLinerEl
7037  * @param theadCell {YAHOO.widget.Column | HTMLElement | String} Column instance,
7038  * DOM element reference, or string ID.
7039  * @return {HTMLElement} Reference to TH liner element.
7040  */
7041 getThLinerEl : function(theadCell) {
7042     var oColumn = this.getColumn(theadCell);
7043     return (oColumn) ? oColumn.getThLinerEl() : null;
7044 },
7045
7046 /**
7047  * Returns DOM reference to a TH element.
7048  *
7049  * @method getThEl
7050  * @param theadCell {YAHOO.widget.Column | HTMLElement | String} Column instance,
7051  * DOM element reference, or string ID.
7052  * @return {HTMLElement} Reference to TH element.
7053  */
7054 getThEl : function(theadCell) {
7055     var elTh;
7056
7057     // Validate Column instance
7058     if(theadCell instanceof YAHOO.widget.Column) {
7059         var oColumn = theadCell;
7060         elTh = oColumn.getThEl();
7061         if(elTh) {
7062             return elTh;
7063         }
7064     }
7065     // Validate HTML element
7066     else {
7067         var el = Dom.get(theadCell);
7068
7069         if(el && (el.ownerDocument == document)) {
7070             // Validate TH element
7071             if(el.nodeName.toLowerCase() != "th") {
7072                 // Traverse up the DOM to find the corresponding TR element
7073                 elTh = Dom.getAncestorByTagName(el,"th");
7074             }
7075             else {
7076                 elTh = el;
7077             }
7078
7079             return elTh;
7080         }
7081     }
7082
7083     return null;
7084 },
7085
7086 /**
7087  * Returns the page row index of given row. Returns null if the row is not on the
7088  * current DataTable page.
7089  *
7090  * @method getTrIndex
7091  * @param row {HTMLElement | String | YAHOO.widget.Record | Number} DOM or ID
7092  * string reference to an element within the DataTable page, a Record instance,
7093  * or a Record's RecordSet index.
7094  * @return {Number} Page row index, or null if row does not exist or is not on current page.
7095  */
7096 getTrIndex : function(row) {
7097     var nRecordIndex;
7098
7099     // By Record
7100     if(row instanceof YAHOO.widget.Record) {
7101         nRecordIndex = this._oRecordSet.getRecordIndex(row);
7102         if(nRecordIndex === null) {
7103             // Not a valid Record
7104             return null;
7105         }
7106     }
7107     // Calculate page row index from Record index
7108     else if(lang.isNumber(row)) {
7109         nRecordIndex = row;
7110     }
7111     if(lang.isNumber(nRecordIndex)) {
7112         // Validate the number
7113         if((nRecordIndex > -1) && (nRecordIndex < this._oRecordSet.getLength())) {
7114             // DataTable is paginated
7115             var oPaginator = this.get('paginator');
7116             if(oPaginator) {
7117                 // Check the record index is within the indices of the
7118                 // current page
7119                 var rng = oPaginator.getPageRecords();
7120                 if (rng && nRecordIndex >= rng[0] && nRecordIndex <= rng[1]) {
7121                     // This Record is on current page
7122                     return nRecordIndex - rng[0];
7123                 }
7124                 // This Record is not on current page
7125                 else {
7126                     return null;
7127                 }
7128             }
7129             // Not paginated, just return the Record index
7130             else {
7131                 return nRecordIndex;
7132             }
7133         }
7134         // RecordSet index is out of range
7135         else {
7136             return null;
7137         }
7138     }
7139     // By element reference or ID string
7140     else {
7141         // Validate TR element
7142         var elRow = this.getTrEl(row);
7143         if(elRow && (elRow.ownerDocument == document) &&
7144                 (elRow.parentNode == this._elTbody)) {
7145             return elRow.sectionRowIndex;
7146         }
7147     }
7148
7149     YAHOO.log("Could not get page row index for row " + row, "info", this.toString());
7150     return null;
7151 },
7152
7153
7154
7155
7156
7157
7158
7159
7160
7161
7162
7163
7164
7165
7166
7167
7168
7169
7170
7171
7172
7173
7174
7175
7176
7177
7178
7179
7180
7181
7182
7183
7184
7185
7186
7187
7188
7189
7190
7191
7192
7193
7194
7195
7196
7197
7198 // TABLE FUNCTIONS
7199
7200 /**
7201  * Resets a RecordSet with the given data and populates the page view
7202  * with the new data. Any previous data, and selection and sort states are
7203  * cleared. New data should be added as a separate step. 
7204  *
7205  * @method initializeTable
7206  */
7207 initializeTable : function() {
7208     // Reset init flag
7209     this._bInit = true;
7210     
7211     // Clear the RecordSet
7212     this._oRecordSet.reset();
7213
7214     // Clear the Paginator's totalRecords if paginating
7215     var pag = this.get('paginator');
7216     if (pag) {
7217         pag.set('totalRecords',0);
7218     }
7219
7220     // Clear selections
7221     this._unselectAllTrEls();
7222     this._unselectAllTdEls();
7223     this._aSelections = null;
7224     this._oAnchorRecord = null;
7225     this._oAnchorCell = null;
7226     
7227     // Clear sort
7228     this.set("sortedBy", null);
7229 },
7230
7231 /**
7232  * Internal wrapper calls run() on render Chain instance.
7233  *
7234  * @method _runRenderChain
7235  * @private 
7236  */
7237 _runRenderChain : function() {
7238     this._oChainRender.run();
7239 },
7240
7241 /**
7242  * Renders the view with existing Records from the RecordSet while
7243  * maintaining sort, pagination, and selection states. For performance, reuses
7244  * existing DOM elements when possible while deleting extraneous elements.
7245  *
7246  * @method render
7247  */
7248 render : function() {
7249 //YAHOO.example.Performance.trialStart = new Date();
7250
7251     this._oChainRender.stop();
7252     YAHOO.log("DataTable rendering...", "info", this.toString());
7253
7254     var i, j, k, len, allRecords;
7255
7256     var oPaginator = this.get('paginator');
7257     // Paginator is enabled, show a subset of Records and update Paginator UI
7258     if(oPaginator) {
7259         allRecords = this._oRecordSet.getRecords(
7260                         oPaginator.getStartIndex(),
7261                         oPaginator.getRowsPerPage());
7262     }
7263     // Not paginated, show all records
7264     else {
7265         allRecords = this._oRecordSet.getRecords();
7266     }
7267
7268     // From the top, update in-place existing rows, so as to reuse DOM elements
7269     var elTbody = this._elTbody,
7270         loopN = this.get("renderLoopSize"),
7271         nRecordsLength = allRecords.length;
7272     
7273     // Table has rows
7274     if(nRecordsLength > 0) {                
7275         elTbody.style.display = "none";
7276         while(elTbody.lastChild) {
7277             elTbody.removeChild(elTbody.lastChild);
7278         }
7279         elTbody.style.display = "";
7280
7281         // Set up the loop Chain to render rows
7282         this._oChainRender.add({
7283             method: function(oArg) {
7284                 if((this instanceof DT) && this._sId) {
7285                     var i = oArg.nCurrentRecord,
7286                         endRecordIndex = ((oArg.nCurrentRecord+oArg.nLoopLength) > nRecordsLength) ?
7287                                 nRecordsLength : (oArg.nCurrentRecord+oArg.nLoopLength),
7288                         elRow, nextSibling;
7289
7290                     elTbody.style.display = "none";
7291                     
7292                     for(; i<endRecordIndex; i++) {
7293                         elRow = Dom.get(allRecords[i].getId());
7294                         elRow = elRow || this._addTrEl(allRecords[i]);
7295                         nextSibling = elTbody.childNodes[i] || null;
7296                         elTbody.insertBefore(elRow, nextSibling);
7297                     }
7298                     elTbody.style.display = "";
7299                     
7300                     // Set up for the next loop
7301                     oArg.nCurrentRecord = i;
7302                 }
7303             },
7304             scope: this,
7305             iterations: (loopN > 0) ? Math.ceil(nRecordsLength/loopN) : 1,
7306             argument: {
7307                 nCurrentRecord: 0,//nRecordsLength-1,  // Start at first Record
7308                 nLoopLength: (loopN > 0) ? loopN : nRecordsLength
7309             },
7310             timeout: (loopN > 0) ? 0 : -1
7311         });
7312         
7313         // Post-render tasks
7314         this._oChainRender.add({
7315             method: function(oArg) {
7316                 if((this instanceof DT) && this._sId) {
7317                     while(elTbody.rows.length > nRecordsLength) {
7318                         elTbody.removeChild(elTbody.lastChild);
7319                     }
7320                     this._setFirstRow();
7321                     this._setLastRow();
7322                     this._setRowStripes();
7323                     this._setSelections();
7324                 }
7325             },
7326             scope: this,
7327             timeout: (loopN > 0) ? 0 : -1
7328         });
7329      
7330     }
7331     // Table has no rows
7332     else {
7333         // Set up the loop Chain to delete rows
7334         var nTotal = elTbody.rows.length;
7335         if(nTotal > 0) {
7336             this._oChainRender.add({
7337                 method: function(oArg) {
7338                     if((this instanceof DT) && this._sId) {
7339                         var i = oArg.nCurrent,
7340                             loopN = oArg.nLoopLength,
7341                             nIterEnd = (i - loopN < 0) ? -1 : i - loopN;
7342     
7343                         elTbody.style.display = "none";
7344                         
7345                         for(; i>nIterEnd; i--) {
7346                             elTbody.deleteRow(-1);
7347                         }
7348                         elTbody.style.display = "";
7349                         
7350                         // Set up for the next loop
7351                         oArg.nCurrent = i;
7352                     }
7353                 },
7354                 scope: this,
7355                 iterations: (loopN > 0) ? Math.ceil(nTotal/loopN) : 1,
7356                 argument: {
7357                     nCurrent: nTotal, 
7358                     nLoopLength: (loopN > 0) ? loopN : nTotal
7359                 },
7360                 timeout: (loopN > 0) ? 0 : -1
7361             });
7362         }
7363     }
7364     this._runRenderChain();
7365 },
7366
7367 /**
7368  * Disables DataTable UI.
7369  *
7370  * @method disable
7371  */
7372 disable : function() {
7373     var elTable = this._elTable;
7374     var elMask = this._elMask;
7375     elMask.style.width = elTable.offsetWidth + "px";
7376     elMask.style.height = elTable.offsetHeight + "px";
7377     elMask.style.display = "";
7378     this.fireEvent("disableEvent");
7379 },
7380
7381 /**
7382  * Undisables DataTable UI.
7383  *
7384  * @method undisable
7385  */
7386 undisable : function() {
7387     this._elMask.style.display = "none";
7388     this.fireEvent("undisableEvent");
7389 },
7390
7391 /**
7392  * Nulls out the entire DataTable instance and related objects, removes attached
7393  * event listeners, and clears out DOM elements inside the container. After
7394  * calling this method, the instance reference should be expliclitly nulled by
7395  * implementer, as in myDataTable = null. Use with caution!
7396  *
7397  * @method destroy
7398  */
7399 destroy : function() {
7400     // Store for later
7401     var instanceName = this.toString();
7402
7403     this._oChainRender.stop();
7404     
7405     // Destroy static resizer proxy and column proxy
7406     DT._destroyColumnDragTargetEl();
7407     DT._destroyColumnResizerProxyEl();
7408     
7409     // Destroy ColumnDD and ColumnResizers
7410     this._destroyColumnHelpers();
7411     
7412     // Destroy all CellEditors
7413     var oCellEditor;
7414     for(var i=0, len=this._oColumnSet.flat.length; i<len; i++) {
7415         oCellEditor = this._oColumnSet.flat[i].editor;
7416         if(oCellEditor && oCellEditor.destroy) {
7417             oCellEditor.destroy();
7418             this._oColumnSet.flat[i].editor = null;
7419         }
7420     }
7421
7422     // Unhook custom events
7423     this._oRecordSet.unsubscribeAll();
7424     this.unsubscribeAll();
7425
7426     // Unhook DOM events
7427     Ev.removeListener(document, "click", this._onDocumentClick);
7428     
7429     // Clear out the container
7430     this._destroyContainerEl(this._elContainer);
7431
7432     // Null out objects
7433     for(var param in this) {
7434         if(lang.hasOwnProperty(this, param)) {
7435             this[param] = null;
7436         }
7437     }
7438     
7439     // Clean up static values
7440     DT._nCurrentCount--;
7441     
7442     if(DT._nCurrentCount < 1) {
7443         if(DT._elDynStyleNode) {
7444             document.getElementsByTagName('head')[0].removeChild(DT._elDynStyleNode);
7445             DT._elDynStyleNode = null;
7446         }
7447     }
7448
7449     YAHOO.log("DataTable instance destroyed: " + instanceName);
7450 },
7451
7452 /**
7453  * Displays message within secondary TBODY.
7454  *
7455  * @method showTableMessage
7456  * @param sHTML {String} (optional) Value for innerHTMlang.
7457  * @param sClassName {String} (optional) Classname.
7458  */
7459 showTableMessage : function(sHTML, sClassName) {
7460     var elCell = this._elMsgTd;
7461     if(lang.isString(sHTML)) {
7462         elCell.firstChild.innerHTML = sHTML;
7463     }
7464     if(lang.isString(sClassName)) {
7465         elCell.className = sClassName;
7466     }
7467
7468     this._elMsgTbody.style.display = "";
7469
7470     this.fireEvent("tableMsgShowEvent", {html:sHTML, className:sClassName});
7471     YAHOO.log("DataTable showing message: " + sHTML, "info", this.toString());
7472 },
7473
7474 /**
7475  * Hides secondary TBODY.
7476  *
7477  * @method hideTableMessage
7478  */
7479 hideTableMessage : function() {
7480     if(this._elMsgTbody.style.display != "none") {
7481         this._elMsgTbody.style.display = "none";
7482         this._elMsgTbody.parentNode.style.width = "";
7483         this.fireEvent("tableMsgHideEvent");
7484         YAHOO.log("DataTable message hidden", "info", this.toString());
7485     }
7486 },
7487
7488 /**
7489  * Brings focus to the TBODY element. Alias to focusTbodyEl.
7490  *
7491  * @method focus
7492  */
7493 focus : function() {
7494     this.focusTbodyEl();
7495 },
7496
7497 /**
7498  * Brings focus to the THEAD element.
7499  *
7500  * @method focusTheadEl
7501  */
7502 focusTheadEl : function() {
7503     this._focusEl(this._elThead);
7504 },
7505
7506 /**
7507  * Brings focus to the TBODY element.
7508  *
7509  * @method focusTbodyEl
7510  */
7511 focusTbodyEl : function() {
7512     this._focusEl(this._elTbody);
7513 },
7514
7515 /**
7516  * Setting display:none on DataTable or any parent may impact width validations.
7517  * After setting display back to "", implementers should call this method to 
7518  * manually perform those validations.
7519  *
7520  * @method onShow
7521  */
7522 onShow : function() {
7523     this.validateColumnWidths();
7524     
7525     for(var allKeys = this._oColumnSet.keys, i=0, len=allKeys.length, col; i<len; i++) {
7526         col = allKeys[i];
7527         if(col._ddResizer) {
7528             col._ddResizer.resetResizerEl();
7529         }
7530     }
7531 },
7532
7533
7534
7535
7536
7537
7538
7539
7540
7541
7542
7543
7544
7545
7546
7547
7548
7549
7550
7551
7552
7553
7554
7555
7556
7557
7558
7559
7560
7561
7562
7563
7564
7565
7566
7567
7568
7569
7570
7571
7572
7573
7574
7575
7576
7577
7578
7579
7580
7581
7582
7583
7584
7585
7586
7587
7588
7589
7590
7591
7592
7593
7594
7595
7596
7597
7598
7599 // RECORDSET FUNCTIONS
7600
7601 /**
7602  * Returns Record index for given TR element or page row index.
7603  *
7604  * @method getRecordIndex
7605  * @param row {YAHOO.widget.Record | HTMLElement | Number} Record instance, TR
7606  * element reference or page row index.
7607  * @return {Number} Record's RecordSet index, or null.
7608  */
7609 getRecordIndex : function(row) {
7610     var nTrIndex;
7611
7612     if(!lang.isNumber(row)) {
7613         // By Record
7614         if(row instanceof YAHOO.widget.Record) {
7615             return this._oRecordSet.getRecordIndex(row);
7616         }
7617         // By element reference
7618         else {
7619             // Find the TR element
7620             var el = this.getTrEl(row);
7621             if(el) {
7622                 nTrIndex = el.sectionRowIndex;
7623             }
7624         }
7625     }
7626     // By page row index
7627     else {
7628         nTrIndex = row;
7629     }
7630
7631     if(lang.isNumber(nTrIndex)) {
7632         var oPaginator = this.get("paginator");
7633         if(oPaginator) {
7634             return oPaginator.get('recordOffset') + nTrIndex;
7635         }
7636         else {
7637             return nTrIndex;
7638         }
7639     }
7640
7641     YAHOO.log("Could not get Record index for row " + row, "info", this.toString());
7642     return null;
7643 },
7644
7645 /**
7646  * For the given identifier, returns the associated Record instance.
7647  *
7648  * @method getRecord
7649  * @param row {HTMLElement | Number | String} DOM reference to a TR element (or
7650  * child of a TR element), RecordSet position index, or Record ID.
7651  * @return {YAHOO.widget.Record} Record instance.
7652  */
7653 getRecord : function(row) {
7654     var oRecord = this._oRecordSet.getRecord(row);
7655
7656     if(!oRecord) {
7657         // Validate TR element
7658         var elRow = this.getTrEl(row);
7659         if(elRow) {
7660             oRecord = this._oRecordSet.getRecord(this.getRecordIndex(elRow.sectionRowIndex));
7661         }
7662     }
7663
7664     if(oRecord instanceof YAHOO.widget.Record) {
7665         return this._oRecordSet.getRecord(oRecord);
7666     }
7667     else {
7668         YAHOO.log("Could not get Record for row at " + row, "info", this.toString());
7669         return null;
7670     }
7671 },
7672
7673
7674
7675
7676
7677
7678
7679
7680
7681
7682
7683
7684
7685
7686
7687
7688
7689
7690
7691
7692
7693
7694
7695
7696
7697
7698
7699
7700
7701
7702
7703
7704
7705
7706
7707
7708
7709
7710
7711
7712
7713
7714
7715
7716
7717
7718 // COLUMN FUNCTIONS
7719
7720 /**
7721  * For the given identifier, returns the associated Column instance. Note: For
7722  * getting Columns by Column ID string, please use the method getColumnById().
7723  *
7724  * @method getColumn
7725  * @param column {HTMLElement | String | Number} TH/TD element (or child of a
7726  * TH/TD element), a Column key, or a ColumnSet key index.
7727  * @return {YAHOO.widget.Column} Column instance.
7728  */
7729 getColumn : function(column) {
7730     var oColumn = this._oColumnSet.getColumn(column);
7731
7732     if(!oColumn) {
7733         // Validate TD element
7734         var elCell = this.getTdEl(column);
7735         if(elCell) {
7736             oColumn = this._oColumnSet.getColumn(elCell.cellIndex);
7737         }
7738         // Validate TH element
7739         else {
7740             elCell = this.getThEl(column);
7741             if(elCell) {
7742                 // Find by TH el ID
7743                 var allColumns = this._oColumnSet.flat;
7744                 for(var i=0, len=allColumns.length; i<len; i++) {
7745                     if(allColumns[i].getThEl().id === elCell.id) {
7746                         oColumn = allColumns[i];
7747                     } 
7748                 }
7749             }
7750         }
7751     }
7752     if(!oColumn) {
7753         YAHOO.log("Could not get Column for column at " + column, "info", this.toString());
7754     }
7755     return oColumn;
7756 },
7757
7758 /**
7759  * For the given Column ID, returns the associated Column instance. Note: For
7760  * getting Columns by key, please use the method getColumn().
7761  *
7762  * @method getColumnById
7763  * @param column {String} Column ID string.
7764  * @return {YAHOO.widget.Column} Column instance.
7765  */
7766 getColumnById : function(column) {
7767     return this._oColumnSet.getColumnById(column);
7768 },
7769
7770 /**
7771  * For the given Column instance, returns next direction to sort.
7772  *
7773  * @method getColumnSortDir
7774  * @param oColumn {YAHOO.widget.Column} Column instance.
7775  * @param oSortedBy {Object} (optional) Specify the state, or use current state. 
7776  * @return {String} YAHOO.widget.DataTable.CLASS_ASC or YAHOO.widget.DataTableCLASS_DESC.
7777  */
7778 getColumnSortDir : function(oColumn, oSortedBy) {
7779     // Backward compatibility
7780     if(oColumn.sortOptions && oColumn.sortOptions.defaultOrder) {
7781         if(oColumn.sortOptions.defaultOrder == "asc") {
7782             oColumn.sortOptions.defaultDir = DT.CLASS_ASC;
7783         }
7784         else if (oColumn.sortOptions.defaultOrder == "desc") {
7785             oColumn.sortOptions.defaultDir = DT.CLASS_DESC;
7786         }
7787     }
7788     
7789     // What is the Column's default sort direction?
7790     var sortDir = (oColumn.sortOptions && oColumn.sortOptions.defaultDir) ? oColumn.sortOptions.defaultDir : DT.CLASS_ASC;
7791
7792     // Is the Column currently sorted?
7793     var bSorted = false;
7794     oSortedBy = oSortedBy || this.get("sortedBy");
7795     if(oSortedBy && (oSortedBy.key === oColumn.key)) {
7796         bSorted = true;
7797         if(oSortedBy.dir) {
7798             sortDir = (oSortedBy.dir === DT.CLASS_ASC) ? DT.CLASS_DESC : DT.CLASS_ASC;
7799         }
7800         else {
7801             sortDir = (sortDir === DT.CLASS_ASC) ? DT.CLASS_DESC : DT.CLASS_ASC;
7802         }
7803     }
7804     return sortDir;
7805 },
7806
7807 /**
7808  * Overridable method gives implementers a hook to show loading message before
7809  * sorting Column.
7810  *
7811  * @method doBeforeSortColumn
7812  * @param oColumn {YAHOO.widget.Column} Column instance.
7813  * @param sSortDir {String} YAHOO.widget.DataTable.CLASS_ASC or
7814  * YAHOO.widget.DataTable.CLASS_DESC.
7815  * @return {Boolean} Return true to continue sorting Column.
7816  */
7817 doBeforeSortColumn : function(oColumn, sSortDir) {
7818     this.showTableMessage(this.get("MSG_LOADING"), DT.CLASS_LOADING);
7819     return true;
7820 },
7821
7822 /**
7823  * Sorts given Column. If "dynamicData" is true, current selections are purged before
7824  * a request is sent to the DataSource for data for the new state (using the
7825  * request returned by "generateRequest()").
7826  *
7827  * @method sortColumn
7828  * @param oColumn {YAHOO.widget.Column} Column instance.
7829  * @param sDir {String} (Optional) YAHOO.widget.DataTable.CLASS_ASC or
7830  * YAHOO.widget.DataTable.CLASS_DESC
7831  */
7832 sortColumn : function(oColumn, sDir) {
7833     if(oColumn && (oColumn instanceof YAHOO.widget.Column)) {
7834         if(!oColumn.sortable) {
7835             Dom.addClass(this.getThEl(oColumn), DT.CLASS_SORTABLE);
7836         }
7837         
7838         // Validate given direction
7839         if(sDir && (sDir !== DT.CLASS_ASC) && (sDir !== DT.CLASS_DESC)) {
7840             sDir = null;
7841         }
7842         
7843         // Get the sort dir
7844         var sSortDir = sDir || this.getColumnSortDir(oColumn);
7845
7846         // Is the Column currently sorted?
7847         var oSortedBy = this.get("sortedBy") || {};
7848         var bSorted = (oSortedBy.key === oColumn.key) ? true : false;
7849
7850         var ok = this.doBeforeSortColumn(oColumn, sSortDir);
7851         if(ok) {
7852             // Server-side sort
7853             if(this.get("dynamicData")) {
7854                 // Get current state
7855                 var oState = this.getState();
7856                 
7857                 // Reset record offset, if paginated
7858                 if(oState.pagination) {
7859                     oState.pagination.recordOffset = 0;
7860                 }
7861                 
7862                 // Update sortedBy to new values
7863                 oState.sortedBy = {
7864                     key: oColumn.key,
7865                     dir: sSortDir
7866                 };
7867                 
7868                 // Get the request for the new state
7869                 var request = this.get("generateRequest")(oState, this);
7870
7871                 // Purge selections
7872                 this.unselectAllRows();
7873                 this.unselectAllCells();
7874
7875                 // Send request for new data
7876                 var callback = {
7877                     success : this.onDataReturnSetRows,
7878                     failure : this.onDataReturnSetRows,
7879                     argument : oState, // Pass along the new state to the callback
7880                     scope : this
7881                 };
7882                 this._oDataSource.sendRequest(request, callback);            
7883             }
7884             // Client-side sort
7885             else {
7886                 // Is there a custom sort handler function defined?
7887                 var sortFnc = (oColumn.sortOptions && lang.isFunction(oColumn.sortOptions.sortFunction)) ?
7888                         // Custom sort function
7889                         oColumn.sortOptions.sortFunction : null;
7890                    
7891                 // Sort the Records
7892                 if(!bSorted || sDir || sortFnc) {
7893                     // Get the field to sort
7894                     var sField = (oColumn.sortOptions && oColumn.sortOptions.field) ? oColumn.sortOptions.field : oColumn.field;
7895
7896                     // Default sort function if necessary
7897                     sortFnc = sortFnc || 
7898                         function(a, b, desc) {
7899                             var sorted = YAHOO.util.Sort.compare(a.getData(sField),b.getData(sField), desc);
7900                             if(sorted === 0) {
7901                                 return YAHOO.util.Sort.compare(a.getCount(),b.getCount(), desc); // Bug 1932978
7902                             }
7903                             else {
7904                                 return sorted;
7905                             }
7906                         };
7907                     // Sort the Records        
7908                     this._oRecordSet.sortRecords(sortFnc, ((sSortDir == DT.CLASS_DESC) ? true : false));
7909                 }
7910                 // Just reverse the Records
7911                 else {
7912                     this._oRecordSet.reverseRecords();
7913                 }
7914         
7915                 // Reset to first page if paginated
7916                 var oPaginator = this.get('paginator');
7917                 if (oPaginator) {
7918                     // Set page silently, so as not to fire change event.
7919                     oPaginator.setPage(1,true);
7920                 }
7921         
7922                 // Update UI via sortedBy
7923                 this.render();
7924                 this.set("sortedBy", {key:oColumn.key, dir:sSortDir, column:oColumn}); 
7925             }       
7926             
7927             this.fireEvent("columnSortEvent",{column:oColumn,dir:sSortDir});
7928             YAHOO.log("Column \"" + oColumn.key + "\" sorted \"" + sSortDir + "\"", "info", this.toString());
7929             return;
7930         }
7931     }
7932     YAHOO.log("Could not sort Column \"" + oColumn.key + "\"", "warn", this.toString());
7933 },
7934
7935 /**
7936  * Sets given Column to given pixel width. If new width is less than minimum
7937  * width, sets to minimum width. Updates oColumn.width value.
7938  *
7939  * @method setColumnWidth
7940  * @param oColumn {YAHOO.widget.Column} Column instance.
7941  * @param nWidth {Number} New width in pixels. A null value auto-sizes Column,
7942  * subject to minWidth and maxAutoWidth validations. 
7943  */
7944 setColumnWidth : function(oColumn, nWidth) {
7945     if(!(oColumn instanceof YAHOO.widget.Column)) {
7946         oColumn = this.getColumn(oColumn);
7947     }
7948     if(oColumn) {
7949         // Validate new width against minimum width
7950         if(lang.isNumber(nWidth)) {
7951             // This is why we must require a Number... :-|
7952             nWidth = (nWidth > oColumn.minWidth) ? nWidth : oColumn.minWidth;
7953
7954             // Save state
7955             oColumn.width = nWidth;
7956             
7957             // Resize the DOM elements
7958             this._setColumnWidth(oColumn, nWidth+"px");
7959             
7960             this.fireEvent("columnSetWidthEvent",{column:oColumn,width:nWidth});
7961             YAHOO.log("Set width of Column " + oColumn + " to " + nWidth + "px", "info", this.toString());
7962         }
7963         // Unsets a width to auto-size
7964         else if(nWidth === null) {
7965             // Save state
7966             oColumn.width = nWidth;
7967             
7968             // Resize the DOM elements
7969             this._setColumnWidth(oColumn, "auto");
7970             this.validateColumnWidths(oColumn);
7971             this.fireEvent("columnUnsetWidthEvent",{column:oColumn});
7972             YAHOO.log("Column " + oColumn + " width unset", "info", this.toString());
7973         }
7974                 
7975         // Bug 2339454: resize then sort misaligment
7976         this._clearTrTemplateEl();
7977     }
7978     else {
7979         YAHOO.log("Could not set width of Column " + oColumn + " to " + nWidth + "px", "warn", this.toString());
7980     }
7981 },
7982
7983 /**
7984  * Sets liner DIV elements of given Column to given width. When value should be
7985  * auto-calculated to fit content overflow is set to visible, otherwise overflow
7986  * is set to hidden. No validations against minimum width and no updating
7987  * Column.width value.
7988  *
7989  * @method _setColumnWidth
7990  * @param oColumn {YAHOO.widget.Column} Column instance.
7991  * @param sWidth {String} New width value.
7992  * @param sOverflow {String} Should be "hidden" when Column width is explicitly
7993  * being set to a value, but should be "visible" when Column is meant to auto-fit content.  
7994  * @private
7995  */
7996 _setColumnWidth : function(oColumn, sWidth, sOverflow) {
7997     if(oColumn && (oColumn.getKeyIndex() !== null)) {
7998         sOverflow = sOverflow || (((sWidth === '') || (sWidth === 'auto')) ? 'visible' : 'hidden');
7999     
8000         // Dynamic style algorithm
8001         if(!DT._bDynStylesFallback) {
8002             this._setColumnWidthDynStyles(oColumn, sWidth, sOverflow);
8003         }
8004         // Dynamic function algorithm
8005         else {
8006             this._setColumnWidthDynFunction(oColumn, sWidth, sOverflow);
8007         }
8008     }
8009     else {
8010         YAHOO.log("Could not set width of unknown Column " + oColumn + " to " + sWidth, "warn", this.toString());
8011     }
8012 },
8013
8014 /**
8015  * Updates width of a Column's liner DIV elements by dynamically creating a
8016  * STYLE node and writing and updating CSS style rules to it. If this fails during
8017  * runtime, the fallback method _setColumnWidthDynFunction() will be called.
8018  * Notes: This technique is not performant in IE6. IE7 crashes if DataTable is
8019  * nested within another TABLE element. For these cases, it is recommended to
8020  * use the method _setColumnWidthDynFunction by setting _bDynStylesFallback to TRUE.
8021  *
8022  * @method _setColumnWidthDynStyles
8023  * @param oColumn {YAHOO.widget.Column} Column instance.
8024  * @param sWidth {String} New width value.
8025  * @private
8026  */
8027 _setColumnWidthDynStyles : function(oColumn, sWidth, sOverflow) {
8028     var s = DT._elDynStyleNode,
8029         rule;
8030     
8031     // Create a new STYLE node
8032     if(!s) {
8033         s = document.createElement('style');
8034         s.type = 'text/css';
8035         s = document.getElementsByTagName('head').item(0).appendChild(s);
8036         DT._elDynStyleNode = s;
8037     }
8038     
8039     // We have a STYLE node to update
8040     if(s) {
8041         // Use unique classname for this Column instance as a hook for resizing
8042         var sClassname = "." + this.getId() + "-col-" + oColumn.getSanitizedKey() + " ." + DT.CLASS_LINER;
8043         
8044         // Hide for performance
8045         if(this._elTbody) {
8046             this._elTbody.style.display = 'none';
8047         }
8048         
8049         rule = DT._oDynStyles[sClassname];
8050
8051         // The Column does not yet have a rule
8052         if(!rule) {
8053             if(s.styleSheet && s.styleSheet.addRule) {
8054                 s.styleSheet.addRule(sClassname,"overflow:"+sOverflow);
8055                 s.styleSheet.addRule(sClassname,'width:'+sWidth);
8056                 rule = s.styleSheet.rules[s.styleSheet.rules.length-1];
8057                 DT._oDynStyles[sClassname] = rule;
8058             }
8059             else if(s.sheet && s.sheet.insertRule) {
8060                 s.sheet.insertRule(sClassname+" {overflow:"+sOverflow+";width:"+sWidth+";}",s.sheet.cssRules.length);
8061                 rule = s.sheet.cssRules[s.sheet.cssRules.length-1];
8062                 DT._oDynStyles[sClassname] = rule;
8063             }
8064         }
8065         // We have a rule to update
8066         else {
8067             rule.style.overflow = sOverflow;
8068             rule.style.width = sWidth;
8069         } 
8070         
8071         // Unhide
8072         if(this._elTbody) {
8073             this._elTbody.style.display = '';
8074         }
8075     }
8076     
8077     // That was not a success, we must call the fallback routine
8078     if(!rule) {
8079         DT._bDynStylesFallback = true;
8080         this._setColumnWidthDynFunction(oColumn, sWidth);
8081     }
8082 },
8083
8084 /**
8085  * Updates width of a Column's liner DIV elements by dynamically creating a
8086  * function to update all element style properties in one pass. Note: This
8087  * technique is not supported in sandboxed environments that prohibit EVALs.    
8088  *
8089  * @method _setColumnWidthDynFunction
8090  * @param oColumn {YAHOO.widget.Column} Column instance.
8091  * @param sWidth {String} New width value.
8092  * @private
8093  */
8094 _setColumnWidthDynFunction : function(oColumn, sWidth, sOverflow) {
8095     // TODO: why is this here?
8096     if(sWidth == 'auto') {
8097         sWidth = ''; 
8098     }
8099     
8100     // Create one function for each value of rows.length
8101     var rowslen = this._elTbody ? this._elTbody.rows.length : 0;
8102     
8103     // Dynamically create the function
8104     if (!this._aDynFunctions[rowslen]) {
8105         
8106         //Compile a custom function to do all the liner div width
8107         //assignments at the same time.  A unique function is required
8108         //for each unique number of rows in _elTbody.  This will
8109         //result in a function declaration like:
8110         //function (oColumn,sWidth,sOverflow) {
8111         //    var colIdx = oColumn.getKeyIndex();
8112         //    oColumn.getThLinerEl().style.overflow =
8113         //    this._elTbody.rows[0].cells[colIdx].firstChild.style.overflow =
8114         //    this._elTbody.rows[1].cells[colIdx].firstChild.style.overflow =
8115         //    ... (for all row indices in this._elTbody.rows.length - 1)
8116         //    this._elTbody.rows[99].cells[colIdx].firstChild.style.overflow =
8117         //    sOverflow;
8118         //    oColumn.getThLinerEl().style.width =
8119         //    this._elTbody.rows[0].cells[colIdx].firstChild.style.width =
8120         //    this._elTbody.rows[1].cells[colIdx].firstChild.style.width =
8121         //    ... (for all row indices in this._elTbody.rows.length - 1)
8122         //    this._elTbody.rows[99].cells[colIdx].firstChild.style.width =
8123         //    sWidth;
8124         //}
8125         
8126         var i,j,k;
8127         var resizerDef = [
8128             'var colIdx=oColumn.getKeyIndex();',
8129             'oColumn.getThLinerEl().style.overflow='
8130         ];
8131         for (i=rowslen-1, j=2; i >= 0; --i) {
8132             resizerDef[j++] = 'this._elTbody.rows[';
8133             resizerDef[j++] = i;
8134             resizerDef[j++] = '].cells[colIdx].firstChild.style.overflow=';
8135         }
8136         resizerDef[j] = 'sOverflow;';
8137         resizerDef[j+1] = 'oColumn.getThLinerEl().style.width=';
8138         for (i=rowslen-1, k=j+2; i >= 0; --i) {
8139             resizerDef[k++] = 'this._elTbody.rows[';
8140             resizerDef[k++] = i;
8141             resizerDef[k++] = '].cells[colIdx].firstChild.style.width=';
8142         }
8143         resizerDef[k] = 'sWidth;';
8144         this._aDynFunctions[rowslen] =
8145             new Function('oColumn','sWidth','sOverflow',resizerDef.join(''));
8146     }
8147     
8148     // Get the function to execute
8149     var resizerFn = this._aDynFunctions[rowslen];
8150
8151     // TODO: Hide TBODY for performance in _setColumnWidthDynFunction?
8152     if (resizerFn) {
8153         resizerFn.call(this,oColumn,sWidth,sOverflow);
8154     }
8155 },
8156
8157 /**
8158  * For one or all Columns, when Column is not hidden, width is not set, and minWidth
8159  * and/or maxAutoWidth is set, validates auto-width against minWidth and maxAutoWidth.
8160  *
8161  * @method validateColumnWidths
8162  * @param oArg.column {YAHOO.widget.Column} (optional) One Column to validate. If null, all Columns' widths are validated.
8163  */
8164 validateColumnWidths : function(oColumn) {
8165     var elColgroup = this._elColgroup;
8166     var elColgroupClone = elColgroup.cloneNode(true);
8167     var bNeedsValidation = false;
8168     var allKeys = this._oColumnSet.keys;
8169     var elThLiner;
8170     // Validate just one Column's minWidth and/or maxAutoWidth
8171     if(oColumn && !oColumn.hidden && !oColumn.width && (oColumn.getKeyIndex() !== null)) {
8172             elThLiner = oColumn.getThLinerEl();
8173             if((oColumn.minWidth > 0) && (elThLiner.offsetWidth < oColumn.minWidth)) {
8174                 elColgroupClone.childNodes[oColumn.getKeyIndex()].style.width = 
8175                         oColumn.minWidth + 
8176                         (parseInt(Dom.getStyle(elThLiner,"paddingLeft"),10)|0) +
8177                         (parseInt(Dom.getStyle(elThLiner,"paddingRight"),10)|0) + "px";
8178                 bNeedsValidation = true;
8179             }
8180             else if((oColumn.maxAutoWidth > 0) && (elThLiner.offsetWidth > oColumn.maxAutoWidth)) {
8181                 this._setColumnWidth(oColumn, oColumn.maxAutoWidth+"px", "hidden");
8182             }
8183     }
8184     // Validate all Columns
8185     else {
8186         for(var i=0, len=allKeys.length; i<len; i++) {
8187             oColumn = allKeys[i];
8188             if(!oColumn.hidden && !oColumn.width) {
8189                 elThLiner = oColumn.getThLinerEl();
8190                 if((oColumn.minWidth > 0) && (elThLiner.offsetWidth < oColumn.minWidth)) {
8191                     elColgroupClone.childNodes[i].style.width = 
8192                             oColumn.minWidth + 
8193                             (parseInt(Dom.getStyle(elThLiner,"paddingLeft"),10)|0) +
8194                             (parseInt(Dom.getStyle(elThLiner,"paddingRight"),10)|0) + "px";
8195                     bNeedsValidation = true;
8196                 }
8197                 else if((oColumn.maxAutoWidth > 0) && (elThLiner.offsetWidth > oColumn.maxAutoWidth)) {
8198                     this._setColumnWidth(oColumn, oColumn.maxAutoWidth+"px", "hidden");
8199                 }
8200             }
8201         }
8202     }
8203     if(bNeedsValidation) {
8204         elColgroup.parentNode.replaceChild(elColgroupClone, elColgroup);
8205         this._elColgroup = elColgroupClone;
8206     }
8207 },
8208
8209 /**
8210  * Clears minWidth.
8211  *
8212  * @method _clearMinWidth
8213  * @param oColumn {YAHOO.widget.Column} Which Column.
8214  * @private
8215  */
8216 _clearMinWidth : function(oColumn) {
8217     if(oColumn.getKeyIndex() !== null) {
8218         this._elColgroup.childNodes[oColumn.getKeyIndex()].style.width = '';
8219     }
8220 },
8221
8222 /**
8223  * Restores minWidth.
8224  *
8225  * @method _restoreMinWidth
8226  * @param oColumn {YAHOO.widget.Column} Which Column.
8227  * @private
8228  */
8229 _restoreMinWidth : function(oColumn) {
8230     if(oColumn.minWidth && (oColumn.getKeyIndex() !== null)) {
8231         this._elColgroup.childNodes[oColumn.getKeyIndex()].style.width = oColumn.minWidth + 'px';
8232     }
8233 },
8234
8235 /**
8236  * Hides given Column. NOTE: You cannot hide/show nested Columns. You can only
8237  * hide/show non-nested Columns, and top-level parent Columns (which will
8238  * hide/show all children Columns).
8239  *
8240  * @method hideColumn
8241  * @param oColumn {YAHOO.widget.Column} Column instance.
8242  */
8243 hideColumn : function(oColumn) {
8244     if(!(oColumn instanceof YAHOO.widget.Column)) {
8245         oColumn = this.getColumn(oColumn);
8246     }
8247     // Only top-level Columns can get hidden due to issues in FF2 and SF3
8248     if(oColumn && !oColumn.hidden && oColumn.getTreeIndex() !== null) {
8249         
8250         var allrows = this.getTbodyEl().rows;
8251         var l = allrows.length;
8252         var allDescendants = this._oColumnSet.getDescendants(oColumn);
8253         
8254         // Hide each nested Column
8255         for(var i=0; i<allDescendants.length; i++) {
8256             var thisColumn = allDescendants[i];
8257             thisColumn.hidden = true;
8258
8259             // Style the head cell
8260             Dom.addClass(thisColumn.getThEl(), DT.CLASS_HIDDEN);
8261             
8262             // Does this Column have body cells?
8263             var thisKeyIndex = thisColumn.getKeyIndex();
8264             if(thisKeyIndex !== null) {                    
8265                 // Clear minWidth
8266                 this._clearMinWidth(oColumn);
8267                 
8268                 // Style the body cells
8269                 for(var j=0;j<l;j++) {
8270                     Dom.addClass(allrows[j].cells[thisKeyIndex],DT.CLASS_HIDDEN);
8271                 }
8272             }
8273             
8274             this.fireEvent("columnHideEvent",{column:thisColumn});
8275             YAHOO.log("Column \"" + oColumn.key + "\" hidden", "info", this.toString());
8276         }
8277       
8278         this._repaintOpera();
8279         this._clearTrTemplateEl();
8280     }
8281     else {
8282         YAHOO.log("Could not hide Column \"" + lang.dump(oColumn) + "\". Only non-nested Columns can be hidden", "warn", this.toString());
8283     }
8284 },
8285
8286 /**
8287  * Shows given Column. NOTE: You cannot hide/show nested Columns. You can only
8288  * hide/show non-nested Columns, and top-level parent Columns (which will
8289  * hide/show all children Columns).
8290  *
8291  * @method showColumn
8292  * @param oColumn {YAHOO.widget.Column} Column instance.
8293  */
8294 showColumn : function(oColumn) {
8295     if(!(oColumn instanceof YAHOO.widget.Column)) {
8296         oColumn = this.getColumn(oColumn);
8297     }
8298     // Only top-level Columns can get hidden
8299     if(oColumn && oColumn.hidden && (oColumn.getTreeIndex() !== null)) {
8300         var allrows = this.getTbodyEl().rows;
8301         var l = allrows.length;
8302         var allDescendants = this._oColumnSet.getDescendants(oColumn);
8303         
8304         // Show each nested Column
8305         for(var i=0; i<allDescendants.length; i++) {
8306             var thisColumn = allDescendants[i];
8307             thisColumn.hidden = false;
8308             
8309             // Unstyle the head cell
8310             Dom.removeClass(thisColumn.getThEl(), DT.CLASS_HIDDEN);
8311
8312             // Does this Column have body cells?
8313             var thisKeyIndex = thisColumn.getKeyIndex();
8314             if(thisKeyIndex !== null) {
8315                 // Restore minWidth
8316                 this._restoreMinWidth(oColumn);
8317                 
8318             
8319                 // Unstyle the body cells
8320                 for(var j=0;j<l;j++) {
8321                     Dom.removeClass(allrows[j].cells[thisKeyIndex],DT.CLASS_HIDDEN);
8322                 }
8323             }
8324
8325             this.fireEvent("columnShowEvent",{column:thisColumn});
8326             YAHOO.log("Column \"" + oColumn.key + "\" shown", "info", this.toString());
8327         }
8328         this._clearTrTemplateEl();
8329     }
8330     else {
8331         YAHOO.log("Could not show Column \"" + lang.dump(oColumn) + "\". Only non-nested Columns can be shown", "warn", this.toString());
8332     }
8333 },
8334
8335 /**
8336  * Removes given Column. NOTE: You cannot remove nested Columns. You can only remove
8337  * non-nested Columns, and top-level parent Columns (which will remove all
8338  * children Columns).
8339  *
8340  * @method removeColumn
8341  * @param oColumn {YAHOO.widget.Column} Column instance.
8342  * @return oColumn {YAHOO.widget.Column} Removed Column instance.
8343  */
8344 removeColumn : function(oColumn) {
8345     // Validate Column
8346     if(!(oColumn instanceof YAHOO.widget.Column)) {
8347         oColumn = this.getColumn(oColumn);
8348     }
8349     if(oColumn) {
8350         var nColTreeIndex = oColumn.getTreeIndex();
8351         if(nColTreeIndex !== null) {
8352             // Which key index(es)
8353             var i, len,
8354                 aKeyIndexes = oColumn.getKeyIndex();
8355             // Must be a parent Column
8356             if(aKeyIndexes === null) {
8357                 var descKeyIndexes = [];
8358                 var allDescendants = this._oColumnSet.getDescendants(oColumn);
8359                 for(i=0, len=allDescendants.length; i<len; i++) {
8360                     // Is this descendant a key Column?
8361                     var thisKey = allDescendants[i].getKeyIndex();
8362                     if(thisKey !== null) {
8363                         descKeyIndexes[descKeyIndexes.length] = thisKey;
8364                     }
8365                 }
8366                 if(descKeyIndexes.length > 0) {
8367                     aKeyIndexes = descKeyIndexes;
8368                 }
8369             }
8370             // Must be a key Column
8371             else {
8372                 aKeyIndexes = [aKeyIndexes];
8373             }
8374             
8375             if(aKeyIndexes !== null) {
8376                 // Sort the indexes so we can remove from the right
8377                 aKeyIndexes.sort(function(a, b) {return YAHOO.util.Sort.compare(a, b);});
8378                 
8379                 // Destroy previous THEAD
8380                 this._destroyTheadEl();
8381     
8382                 // Create new THEAD
8383                 var aOrigColumnDefs = this._oColumnSet.getDefinitions();
8384                 oColumn = aOrigColumnDefs.splice(nColTreeIndex,1)[0];
8385                 this._initColumnSet(aOrigColumnDefs);
8386                 this._initTheadEl();
8387                 
8388                 // Remove COL
8389                 for(i=aKeyIndexes.length-1; i>-1; i--) {
8390                     this._removeColgroupColEl(aKeyIndexes[i]);
8391                 }
8392                 
8393                 // Remove TD
8394                 var allRows = this._elTbody.rows;
8395                 if(allRows.length > 0) {
8396                     var loopN = this.get("renderLoopSize"),
8397                         loopEnd = allRows.length;
8398                     this._oChainRender.add({
8399                         method: function(oArg) {
8400                             if((this instanceof DT) && this._sId) {
8401                                 var i = oArg.nCurrentRow,
8402                                     len = loopN > 0 ? Math.min(i + loopN,allRows.length) : allRows.length,
8403                                     aIndexes = oArg.aIndexes,
8404                                     j;
8405                                 for(; i < len; ++i) {
8406                                     for(j = aIndexes.length-1; j>-1; j--) {
8407                                         allRows[i].removeChild(allRows[i].childNodes[aIndexes[j]]);
8408                                     }
8409                                 }
8410                                 oArg.nCurrentRow = i;
8411                             }
8412                         },
8413                         iterations: (loopN > 0) ? Math.ceil(loopEnd/loopN) : 1,
8414                         argument: {nCurrentRow:0, aIndexes:aKeyIndexes},
8415                         scope: this,
8416                         timeout: (loopN > 0) ? 0 : -1
8417                     });
8418                     this._runRenderChain();
8419                 }
8420         
8421                 this.fireEvent("columnRemoveEvent",{column:oColumn});
8422                 YAHOO.log("Column \"" + oColumn.key + "\" removed", "info", this.toString());
8423                 return oColumn;
8424             }
8425         }
8426     }
8427     YAHOO.log("Could not remove Column \"" + oColumn.key + "\". Only non-nested Columns can be removed", "warn", this.toString());
8428 },
8429
8430 /**
8431  * Inserts given Column at the index if given, otherwise at the end. NOTE: You
8432  * can only add non-nested Columns and top-level parent Columns. You cannot add
8433  * a nested Column to an existing parent.
8434  *
8435  * @method insertColumn
8436  * @param oColumn {Object | YAHOO.widget.Column} Object literal Column
8437  * definition or a Column instance.
8438  * @param index {Number} (optional) New tree index.
8439  * @return oColumn {YAHOO.widget.Column} Inserted Column instance. 
8440  */
8441 insertColumn : function(oColumn, index) {
8442     // Validate Column
8443     if(oColumn instanceof YAHOO.widget.Column) {
8444         oColumn = oColumn.getDefinition();
8445     }
8446     else if(oColumn.constructor !== Object) {
8447         YAHOO.log("Could not insert Column \"" + oColumn + "\" due to invalid argument", "warn", this.toString());
8448         return;
8449     }
8450     
8451     // Validate index or append new Column to the end of the ColumnSet
8452     var oColumnSet = this._oColumnSet;
8453     if(!lang.isValue(index) || !lang.isNumber(index)) {
8454         index = oColumnSet.tree[0].length;
8455     }
8456     
8457     // Destroy previous THEAD
8458     this._destroyTheadEl();
8459     
8460     // Create new THEAD
8461     var aNewColumnDefs = this._oColumnSet.getDefinitions();
8462     aNewColumnDefs.splice(index, 0, oColumn);
8463     this._initColumnSet(aNewColumnDefs);
8464     this._initTheadEl();
8465     
8466     // Need to refresh the reference
8467     oColumnSet = this._oColumnSet;
8468     var oNewColumn = oColumnSet.tree[0][index];
8469     
8470     // Get key index(es) for new Column
8471     var i, len,
8472         descKeyIndexes = [];
8473     var allDescendants = oColumnSet.getDescendants(oNewColumn);
8474     for(i=0, len=allDescendants.length; i<len; i++) {
8475         // Is this descendant a key Column?
8476         var thisKey = allDescendants[i].getKeyIndex();
8477         if(thisKey !== null) {
8478             descKeyIndexes[descKeyIndexes.length] = thisKey;
8479         }
8480     }
8481     
8482     if(descKeyIndexes.length > 0) {  
8483         // Sort the indexes
8484         var newIndex = descKeyIndexes.sort(function(a, b) {return YAHOO.util.Sort.compare(a, b);})[0];
8485         
8486         // Add COL
8487         for(i=descKeyIndexes.length-1; i>-1; i--) {
8488             this._insertColgroupColEl(descKeyIndexes[i]);
8489         }
8490             
8491         // Add TD
8492         var allRows = this._elTbody.rows;
8493         if(allRows.length > 0) {
8494             var loopN = this.get("renderLoopSize"),
8495                 loopEnd = allRows.length;
8496             
8497             // Get templates for each new TD
8498             var aTdTemplates = [],
8499                 elTdTemplate;
8500             for(i=0, len=descKeyIndexes.length; i<len; i++) {
8501                 var thisKeyIndex = descKeyIndexes[i];
8502                 elTdTemplate = this._getTrTemplateEl().childNodes[i].cloneNode(true);
8503                 elTdTemplate = this._formatTdEl(this._oColumnSet.keys[thisKeyIndex], elTdTemplate, thisKeyIndex, (thisKeyIndex===this._oColumnSet.keys.length-1));
8504                 aTdTemplates[thisKeyIndex] = elTdTemplate;
8505             }
8506             
8507             this._oChainRender.add({
8508                 method: function(oArg) {
8509                     if((this instanceof DT) && this._sId) {
8510                         var i = oArg.nCurrentRow, j,
8511                             descKeyIndexes = oArg.descKeyIndexes,
8512                             len = loopN > 0 ? Math.min(i + loopN,allRows.length) : allRows.length,
8513                             nextSibling;
8514                         for(; i < len; ++i) {
8515                             nextSibling = allRows[i].childNodes[newIndex] || null;
8516                             for(j=descKeyIndexes.length-1; j>-1; j--) {
8517                                 allRows[i].insertBefore(oArg.aTdTemplates[descKeyIndexes[j]].cloneNode(true), nextSibling);
8518                             }
8519                         }
8520                         oArg.nCurrentRow = i;
8521                     }
8522                 },
8523                 iterations: (loopN > 0) ? Math.ceil(loopEnd/loopN) : 1,
8524                 argument: {nCurrentRow:0,aTdTemplates:aTdTemplates,descKeyIndexes:descKeyIndexes},
8525                 scope: this,
8526                 timeout: (loopN > 0) ? 0 : -1
8527             });
8528             this._runRenderChain(); 
8529         }
8530
8531         this.fireEvent("columnInsertEvent",{column:oColumn,index:index});
8532         YAHOO.log("Column \"" + oColumn.key + "\" inserted into index " + index, "info", this.toString());
8533         return oNewColumn;
8534     }
8535 },
8536
8537 /**
8538  * Removes given Column and inserts into given tree index. NOTE: You
8539  * can only reorder non-nested Columns and top-level parent Columns. You cannot
8540  * reorder a nested Column to an existing parent.
8541  *
8542  * @method reorderColumn
8543  * @param oColumn {YAHOO.widget.Column} Column instance.
8544  * @param index {Number} New tree index.
8545  * @return oColumn {YAHOO.widget.Column} Reordered Column instance. 
8546  */
8547 reorderColumn : function(oColumn, index) {
8548     // Validate Column and new index
8549     if(!(oColumn instanceof YAHOO.widget.Column)) {
8550         oColumn = this.getColumn(oColumn);
8551     }
8552     if(oColumn && YAHOO.lang.isNumber(index)) {
8553         var nOrigTreeIndex = oColumn.getTreeIndex();
8554         if((nOrigTreeIndex !== null) && (nOrigTreeIndex !== index)) {
8555             // Which key index(es)
8556             var i, len,
8557                 aOrigKeyIndexes = oColumn.getKeyIndex(),
8558                 allDescendants,
8559                 descKeyIndexes = [],
8560                 thisKey;
8561             // Must be a parent Column...
8562             if(aOrigKeyIndexes === null) {
8563                 allDescendants = this._oColumnSet.getDescendants(oColumn);
8564                 for(i=0, len=allDescendants.length; i<len; i++) {
8565                     // Is this descendant a key Column?
8566                     thisKey = allDescendants[i].getKeyIndex();
8567                     if(thisKey !== null) {
8568                         descKeyIndexes[descKeyIndexes.length] = thisKey;
8569                     }
8570                 }
8571                 if(descKeyIndexes.length > 0) {
8572                     aOrigKeyIndexes = descKeyIndexes;
8573                 }
8574             }
8575             // ...or else must be a key Column
8576             else {
8577                 aOrigKeyIndexes = [aOrigKeyIndexes];
8578             }
8579             
8580             if(aOrigKeyIndexes !== null) {                   
8581                 // Sort the indexes
8582                 aOrigKeyIndexes.sort(function(a, b) {return YAHOO.util.Sort.compare(a, b);});
8583                 
8584                 // Destroy previous THEAD
8585                 this._destroyTheadEl();
8586     
8587                 // Create new THEAD
8588                 var aColumnDefs = this._oColumnSet.getDefinitions();
8589                 var oColumnDef = aColumnDefs.splice(nOrigTreeIndex,1)[0];
8590                 aColumnDefs.splice(index, 0, oColumnDef);
8591                 this._initColumnSet(aColumnDefs);
8592                 this._initTheadEl();
8593                 
8594                 // Need to refresh the reference
8595                 var oNewColumn = this._oColumnSet.tree[0][index];
8596
8597                 // What are new key index(es)
8598                 var aNewKeyIndexes = oNewColumn.getKeyIndex();
8599                 // Must be a parent Column
8600                 if(aNewKeyIndexes === null) {
8601                     descKeyIndexes = [];
8602                     allDescendants = this._oColumnSet.getDescendants(oNewColumn);
8603                     for(i=0, len=allDescendants.length; i<len; i++) {
8604                         // Is this descendant a key Column?
8605                         thisKey = allDescendants[i].getKeyIndex();
8606                         if(thisKey !== null) {
8607                             descKeyIndexes[descKeyIndexes.length] = thisKey;
8608                         }
8609                     }
8610                     if(descKeyIndexes.length > 0) {
8611                         aNewKeyIndexes = descKeyIndexes;
8612                     }
8613                 }
8614                 // Must be a key Column
8615                 else {
8616                     aNewKeyIndexes = [aNewKeyIndexes];
8617                 }
8618                 
8619                 // Sort the new indexes and grab the first one for the new location
8620                 var newIndex = aNewKeyIndexes.sort(function(a, b) {return YAHOO.util.Sort.compare(a, b);})[0];
8621
8622                 // Reorder COL
8623                 this._reorderColgroupColEl(aOrigKeyIndexes, newIndex);
8624                 
8625                 // Reorder TD
8626                 var allRows = this._elTbody.rows;
8627                 if(allRows.length > 0) {
8628                     var loopN = this.get("renderLoopSize"),
8629                         loopEnd = allRows.length;
8630                     this._oChainRender.add({
8631                         method: function(oArg) {
8632                             if((this instanceof DT) && this._sId) {
8633                                 var i = oArg.nCurrentRow, j, tmpTds, nextSibling,
8634                                     len = loopN > 0 ? Math.min(i + loopN,allRows.length) : allRows.length,
8635                                     aIndexes = oArg.aIndexes, thisTr;
8636                                 // For each row
8637                                 for(; i < len; ++i) {
8638                                     tmpTds = [];
8639                                     thisTr = allRows[i];
8640                                     
8641                                     // Remove each TD
8642                                     for(j=aIndexes.length-1; j>-1; j--) {
8643                                         tmpTds.push(thisTr.removeChild(thisTr.childNodes[aIndexes[j]]));
8644                                     }
8645                                     
8646                                     // Insert each TD
8647                                     nextSibling = thisTr.childNodes[newIndex] || null;
8648                                     for(j=tmpTds.length-1; j>-1; j--) {
8649                                         thisTr.insertBefore(tmpTds[j], nextSibling);
8650                                     }                                    
8651                                 }
8652                                 oArg.nCurrentRow = i;
8653                             }
8654                         },
8655                         iterations: (loopN > 0) ? Math.ceil(loopEnd/loopN) : 1,
8656                         argument: {nCurrentRow:0, aIndexes:aOrigKeyIndexes},
8657                         scope: this,
8658                         timeout: (loopN > 0) ? 0 : -1
8659                     });
8660                     this._runRenderChain();
8661                 }
8662         
8663                 this.fireEvent("columnReorderEvent",{column:oNewColumn});
8664                 YAHOO.log("Column \"" + oNewColumn.key + "\" reordered", "info", this.toString());
8665                 return oNewColumn;
8666             }
8667         }
8668     }
8669     YAHOO.log("Could not reorder Column \"" + oColumn.key + "\". Only non-nested Columns can be reordered", "warn", this.toString());
8670 },
8671
8672 /**
8673  * Selects given Column. NOTE: You cannot select/unselect nested Columns. You can only
8674  * select/unselect non-nested Columns, and bottom-level key Columns.
8675  *
8676  * @method selectColumn
8677  * @param column {HTMLElement | String | Number} DOM reference or ID string to a
8678  * TH/TD element (or child of a TH/TD element), a Column key, or a ColumnSet key index.
8679  */
8680 selectColumn : function(oColumn) {
8681     oColumn = this.getColumn(oColumn);
8682     if(oColumn && !oColumn.selected) {
8683         // Only bottom-level Columns can get hidden
8684         if(oColumn.getKeyIndex() !== null) {
8685             oColumn.selected = true;
8686             
8687             // Update head cell
8688             var elTh = oColumn.getThEl();
8689             Dom.addClass(elTh,DT.CLASS_SELECTED);
8690
8691             // Update body cells
8692             var allRows = this.getTbodyEl().rows;
8693             var oChainRender = this._oChainRender;
8694             oChainRender.add({
8695                 method: function(oArg) {
8696                     if((this instanceof DT) && this._sId && allRows[oArg.rowIndex] && allRows[oArg.rowIndex].cells[oArg.cellIndex]) {
8697                         Dom.addClass(allRows[oArg.rowIndex].cells[oArg.cellIndex],DT.CLASS_SELECTED);                    
8698                     }
8699                     oArg.rowIndex++;
8700                 },
8701                 scope: this,
8702                 iterations: allRows.length,
8703                 argument: {rowIndex:0,cellIndex:oColumn.getKeyIndex()}
8704             });
8705
8706             this._clearTrTemplateEl();
8707             
8708             this._elTbody.style.display = "none";
8709             this._runRenderChain();
8710             this._elTbody.style.display = "";      
8711             
8712             this.fireEvent("columnSelectEvent",{column:oColumn});
8713             YAHOO.log("Column \"" + oColumn.key + "\" selected", "info", this.toString());
8714         }
8715         else {
8716             YAHOO.log("Could not select Column \"" + oColumn.key + "\". Only non-nested Columns can be selected", "warn", this.toString());
8717         }
8718     }
8719 },
8720
8721 /**
8722  * Unselects given Column. NOTE: You cannot select/unselect nested Columns. You can only
8723  * select/unselect non-nested Columns, and bottom-level key Columns.
8724  *
8725  * @method unselectColumn
8726  * @param column {HTMLElement | String | Number} DOM reference or ID string to a
8727  * TH/TD element (or child of a TH/TD element), a Column key, or a ColumnSet key index.
8728  */
8729 unselectColumn : function(oColumn) {
8730     oColumn = this.getColumn(oColumn);
8731     if(oColumn && oColumn.selected) {
8732         // Only bottom-level Columns can get hidden
8733         if(oColumn.getKeyIndex() !== null) {
8734             oColumn.selected = false;
8735             
8736             // Update head cell
8737             var elTh = oColumn.getThEl();
8738             Dom.removeClass(elTh,DT.CLASS_SELECTED);
8739
8740             // Update body cells
8741             var allRows = this.getTbodyEl().rows;
8742             var oChainRender = this._oChainRender;
8743             oChainRender.add({
8744                 method: function(oArg) {
8745                     if((this instanceof DT) && this._sId && allRows[oArg.rowIndex] && allRows[oArg.rowIndex].cells[oArg.cellIndex]) {
8746                         Dom.removeClass(allRows[oArg.rowIndex].cells[oArg.cellIndex],DT.CLASS_SELECTED); 
8747                     }                   
8748                     oArg.rowIndex++;
8749                 },
8750                 scope: this,
8751                 iterations:allRows.length,
8752                 argument: {rowIndex:0,cellIndex:oColumn.getKeyIndex()}
8753             });
8754             
8755             this._clearTrTemplateEl();
8756
8757             this._elTbody.style.display = "none";
8758             this._runRenderChain();
8759             this._elTbody.style.display = "";      
8760             
8761             this.fireEvent("columnUnselectEvent",{column:oColumn});
8762             YAHOO.log("Column \"" + oColumn.key + "\" unselected", "info", this.toString());
8763         }
8764         else {
8765             YAHOO.log("Could not unselect Column \"" + oColumn.key + "\". Only non-nested Columns can be unselected", "warn", this.toString());
8766         }
8767     }
8768 },
8769
8770 /**
8771  * Returns an array selected Column instances.
8772  *
8773  * @method getSelectedColumns
8774  * @return {YAHOO.widget.Column[]} Array of Column instances.
8775  */
8776 getSelectedColumns : function(oColumn) {
8777     var selectedColumns = [];
8778     var aKeys = this._oColumnSet.keys;
8779     for(var i=0,len=aKeys.length; i<len; i++) {
8780         if(aKeys[i].selected) {
8781             selectedColumns[selectedColumns.length] = aKeys[i];
8782         }
8783     }
8784     return selectedColumns;
8785 },
8786
8787 /**
8788  * Assigns the class YAHOO.widget.DataTable.CLASS_HIGHLIGHTED to cells of the given Column.
8789  * NOTE: You cannot highlight/unhighlight nested Columns. You can only
8790  * highlight/unhighlight non-nested Columns, and bottom-level key Columns.
8791  *
8792  * @method highlightColumn
8793  * @param column {HTMLElement | String | Number} DOM reference or ID string to a
8794  * TH/TD element (or child of a TH/TD element), a Column key, or a ColumnSet key index.
8795  */
8796 highlightColumn : function(column) {
8797     var oColumn = this.getColumn(column);
8798     // Only bottom-level Columns can get highlighted
8799     if(oColumn && (oColumn.getKeyIndex() !== null)) {            
8800         // Update head cell
8801         var elTh = oColumn.getThEl();
8802         Dom.addClass(elTh,DT.CLASS_HIGHLIGHTED);
8803
8804         // Update body cells
8805         var allRows = this.getTbodyEl().rows;
8806         var oChainRender = this._oChainRender;
8807         oChainRender.add({
8808             method: function(oArg) {
8809                 if((this instanceof DT) && this._sId && allRows[oArg.rowIndex] && allRows[oArg.rowIndex].cells[oArg.cellIndex]) {
8810                     Dom.addClass(allRows[oArg.rowIndex].cells[oArg.cellIndex],DT.CLASS_HIGHLIGHTED);   
8811                 }                 
8812                 oArg.rowIndex++;
8813             },
8814             scope: this,
8815             iterations:allRows.length,
8816             argument: {rowIndex:0,cellIndex:oColumn.getKeyIndex()},
8817             timeout: -1
8818         });
8819         this._elTbody.style.display = "none";
8820         this._runRenderChain();
8821         this._elTbody.style.display = "";      
8822             
8823         this.fireEvent("columnHighlightEvent",{column:oColumn});
8824         YAHOO.log("Column \"" + oColumn.key + "\" highlighed", "info", this.toString());
8825     }
8826     else {
8827         YAHOO.log("Could not highlight Column \"" + oColumn.key + "\". Only non-nested Columns can be highlighted", "warn", this.toString());
8828     }
8829 },
8830
8831 /**
8832  * Removes the class YAHOO.widget.DataTable.CLASS_HIGHLIGHTED to cells of the given Column.
8833  * NOTE: You cannot highlight/unhighlight nested Columns. You can only
8834  * highlight/unhighlight non-nested Columns, and bottom-level key Columns.
8835  *
8836  * @method unhighlightColumn
8837  * @param column {HTMLElement | String | Number} DOM reference or ID string to a
8838  * TH/TD element (or child of a TH/TD element), a Column key, or a ColumnSet key index.
8839  */
8840 unhighlightColumn : function(column) {
8841     var oColumn = this.getColumn(column);
8842     // Only bottom-level Columns can get highlighted
8843     if(oColumn && (oColumn.getKeyIndex() !== null)) {
8844         // Update head cell
8845         var elTh = oColumn.getThEl();
8846         Dom.removeClass(elTh,DT.CLASS_HIGHLIGHTED);
8847
8848         // Update body cells
8849         var allRows = this.getTbodyEl().rows;
8850         var oChainRender = this._oChainRender;
8851         oChainRender.add({
8852             method: function(oArg) {
8853                 if((this instanceof DT) && this._sId && allRows[oArg.rowIndex] && allRows[oArg.rowIndex].cells[oArg.cellIndex]) {
8854                     Dom.removeClass(allRows[oArg.rowIndex].cells[oArg.cellIndex],DT.CLASS_HIGHLIGHTED);
8855                 }                 
8856                 oArg.rowIndex++;
8857             },
8858             scope: this,
8859             iterations:allRows.length,
8860             argument: {rowIndex:0,cellIndex:oColumn.getKeyIndex()},
8861             timeout: -1
8862         });
8863         this._elTbody.style.display = "none";
8864         this._runRenderChain();
8865         this._elTbody.style.display = "";     
8866             
8867         this.fireEvent("columnUnhighlightEvent",{column:oColumn});
8868         YAHOO.log("Column \"" + oColumn.key + "\" unhighlighted", "info", this.toString());
8869     }
8870     else {
8871         YAHOO.log("Could not unhighlight Column \"" + oColumn.key + "\". Only non-nested Columns can be unhighlighted", "warn", this.toString());
8872     }
8873 },
8874
8875
8876
8877
8878
8879
8880
8881
8882
8883
8884
8885
8886
8887
8888
8889
8890
8891
8892
8893
8894
8895
8896
8897
8898
8899
8900
8901
8902
8903
8904
8905
8906
8907
8908
8909
8910
8911
8912
8913
8914
8915
8916
8917
8918 // ROW FUNCTIONS
8919
8920 /**
8921  * Adds one new Record of data into the RecordSet at the index if given,
8922  * otherwise at the end. If the new Record is in page view, the
8923  * corresponding DOM elements are also updated.
8924  *
8925  * @method addRow
8926  * @param oData {Object} Object literal of data for the row.
8927  * @param index {Number} (optional) RecordSet position index at which to add data.
8928  */
8929 addRow : function(oData, index) {
8930     if(lang.isNumber(index) && (index < 0 || index > this._oRecordSet.getLength())) {
8931         YAHOO.log("Could not add row at index " + index + " with " + lang.dump(oData), "warn", this.toString());
8932         return;
8933     }
8934
8935     if(oData && lang.isObject(oData)) {
8936         var oRecord = this._oRecordSet.addRecord(oData, index);
8937         if(oRecord) {
8938             var recIndex;
8939             var oPaginator = this.get('paginator');
8940
8941             // Paginated
8942             if (oPaginator) {     
8943                 // Update the paginator's totalRecords
8944                 var totalRecords = oPaginator.get('totalRecords');
8945                 if (totalRecords !== widget.Paginator.VALUE_UNLIMITED) {
8946                     oPaginator.set('totalRecords',totalRecords + 1);
8947                 }
8948
8949                 recIndex = this.getRecordIndex(oRecord);
8950                 var endRecIndex = (oPaginator.getPageRecords())[1];
8951
8952                 // New record affects the view
8953                 if (recIndex <= endRecIndex) {
8954                     // Defer UI updates to the render method
8955                     this.render();
8956                 }
8957                 
8958                 this.fireEvent("rowAddEvent", {record:oRecord});
8959                 YAHOO.log("Added a row for Record " + YAHOO.lang.dump(oRecord) + " at RecordSet index " + recIndex, "info", this.toString()); 
8960                 return;
8961             }
8962             // Not paginated
8963             else {
8964                 recIndex = this.getTrIndex(oRecord);
8965                 if(lang.isNumber(recIndex)) {
8966                     // Add the TR element
8967                     this._oChainRender.add({
8968                         method: function(oArg) {
8969                             if((this instanceof DT) && this._sId) {
8970                                 var oRecord = oArg.record;
8971                                 var recIndex = oArg.recIndex;
8972                                 var elNewTr = this._addTrEl(oRecord);
8973                                 if(elNewTr) {
8974                                     var elNext = (this._elTbody.rows[recIndex]) ? this._elTbody.rows[recIndex] : null;
8975                                     this._elTbody.insertBefore(elNewTr, elNext);
8976
8977                                     // Set FIRST/LAST
8978                                     if(recIndex === 0) {
8979                                         this._setFirstRow();
8980                                     }
8981                                     if(elNext === null) {
8982                                         this._setLastRow();
8983                                     }
8984                                     // Set EVEN/ODD
8985                                     this._setRowStripes();                           
8986                                     
8987                                     this.hideTableMessage();
8988             
8989                                     this.fireEvent("rowAddEvent", {record:oRecord});
8990                                     YAHOO.log("Added a row for Record " + YAHOO.lang.dump(oRecord) + " at RecordSet index " + recIndex, "info", this.toString());
8991                                 }
8992                             }
8993                         },
8994                         argument: {record: oRecord, recIndex: recIndex},
8995                         scope: this,
8996                         timeout: (this.get("renderLoopSize") > 0) ? 0 : -1
8997                     });
8998                     this._runRenderChain();
8999                     return;
9000                 }
9001             }            
9002         }
9003     }
9004     YAHOO.log("Could not add row at index " + index + " with " + lang.dump(oData), "warn", this.toString());
9005 },
9006
9007 /**
9008  * Convenience method to add multiple rows.
9009  *
9010  * @method addRows
9011  * @param aData {Object[]} Array of object literal data for the rows.
9012  * @param index {Number} (optional) RecordSet position index at which to add data.
9013  */
9014 addRows : function(aData, index) {
9015     if(lang.isNumber(index) && (index < 0 || index > this._oRecordSet.getLength())) {
9016         YAHOO.log("Could not add rows at index " + index + " with " + lang.dump(aData), "warn", this.toString());    
9017         return;
9018     }
9019
9020     if(lang.isArray(aData)) {
9021         var aRecords = this._oRecordSet.addRecords(aData, index);
9022         if(aRecords) {
9023             var recIndex = this.getRecordIndex(aRecords[0]);
9024             
9025             // Paginated
9026             var oPaginator = this.get('paginator');
9027             if (oPaginator) {
9028                 // Update the paginator's totalRecords
9029                 var totalRecords = oPaginator.get('totalRecords');
9030                 if (totalRecords !== widget.Paginator.VALUE_UNLIMITED) {
9031                     oPaginator.set('totalRecords',totalRecords + aRecords.length);
9032                 }
9033     
9034                 var endRecIndex = (oPaginator.getPageRecords())[1];
9035
9036                 // At least one of the new records affects the view
9037                 if (recIndex <= endRecIndex) {
9038                     this.render();
9039                 }
9040                 
9041                 this.fireEvent("rowsAddEvent", {records:aRecords});
9042                 YAHOO.log("Added " + aRecords.length + 
9043                         " rows at index " + this._oRecordSet.getRecordIndex(aRecords[0]) +
9044                         " with data " + lang.dump(aData), "info", this.toString());
9045                 return;
9046             }
9047             // Not paginated
9048             else {
9049                 // Add the TR elements
9050                 var loopN = this.get("renderLoopSize");
9051                 var loopEnd = recIndex + aData.length;
9052                 var nRowsNeeded = (loopEnd - recIndex); // how many needed
9053                 var isLast = (recIndex >= this._elTbody.rows.length);
9054                 this._oChainRender.add({
9055                     method: function(oArg) {
9056                         if((this instanceof DT) && this._sId) {
9057                             var aRecords = oArg.aRecords,
9058                                 i = oArg.nCurrentRow,
9059                                 j = oArg.nCurrentRecord,
9060                                 len = loopN > 0 ? Math.min(i + loopN,loopEnd) : loopEnd,
9061                                 df = document.createDocumentFragment(),
9062                                 elNext = (this._elTbody.rows[i]) ? this._elTbody.rows[i] : null;
9063                             for(; i < len; i++, j++) {
9064                                 df.appendChild(this._addTrEl(aRecords[j]));
9065                             }
9066                             this._elTbody.insertBefore(df, elNext);
9067                             oArg.nCurrentRow = i;
9068                             oArg.nCurrentRecord = j;
9069                         }
9070                     },
9071                     iterations: (loopN > 0) ? Math.ceil(loopEnd/loopN) : 1,
9072                     argument: {nCurrentRow:recIndex,nCurrentRecord:0,aRecords:aRecords},
9073                     scope: this,
9074                     timeout: (loopN > 0) ? 0 : -1
9075                 });
9076                 this._oChainRender.add({
9077                     method: function(oArg) {
9078                         var recIndex = oArg.recIndex;
9079                         // Set FIRST/LAST
9080                         if(recIndex === 0) {
9081                             this._setFirstRow();
9082                         }
9083                         if(oArg.isLast) {
9084                             this._setLastRow();
9085                         }
9086                         // Set EVEN/ODD
9087                         this._setRowStripes();                           
9088
9089                         this.fireEvent("rowsAddEvent", {records:aRecords});
9090                         YAHOO.log("Added " + aRecords.length + 
9091                                 " rows at index " + this._oRecordSet.getRecordIndex(aRecords[0]) +
9092                                 " with data " + lang.dump(aData), "info", this.toString());
9093                     },
9094                     argument: {recIndex: recIndex, isLast: isLast},
9095                     scope: this,
9096                     timeout: -1 // Needs to run immediately after the DOM insertions above
9097                 });
9098                 this._runRenderChain();
9099                 this.hideTableMessage();                
9100                 return;
9101             }            
9102         }
9103     }
9104     YAHOO.log("Could not add rows at index " + index + " with " + lang.dump(aData), "warn", this.toString());    
9105 },
9106
9107 /**
9108  * For the given row, updates the associated Record with the given data. If the
9109  * row is on current page, the corresponding DOM elements are also updated.
9110  *
9111  * @method updateRow
9112  * @param row {YAHOO.widget.Record | Number | HTMLElement | String}
9113  * Which row to update: By Record instance, by Record's RecordSet
9114  * position index, by HTMLElement reference to the TR element, or by ID string
9115  * of the TR element.
9116  * @param oData {Object} Object literal of data for the row.
9117  */
9118 updateRow : function(row, oData) {
9119     var index = row;
9120     if (!lang.isNumber(index)) {
9121         index = this.getRecordIndex(row);
9122     }
9123
9124     // Update the Record
9125     if(lang.isNumber(index) && (index >= 0)) {
9126         var oRecordSet = this._oRecordSet,
9127             oldRecord = oRecordSet.getRecord(index);
9128             
9129         
9130         if(oldRecord) {
9131             var updatedRecord = this._oRecordSet.setRecord(oData, index),
9132                 elRow = this.getTrEl(oldRecord),
9133                 // Copy data from the Record for the event that gets fired later
9134                 oldData = oldRecord ? oldRecord.getData() : null;
9135                
9136             if(updatedRecord) {
9137                 // Update selected rows as necessary
9138                 var tracker = this._aSelections || [],
9139                 i=0,
9140                 oldId = oldRecord.getId(),
9141                 newId = updatedRecord.getId();
9142                 for(; i<tracker.length; i++) {
9143                     if((tracker[i] === oldId)) {
9144                         tracker[i] = newId;
9145                     }
9146                     else if(tracker[i].recordId === oldId) {
9147                         tracker[i].recordId = newId;
9148                     }
9149                 }
9150
9151                 // Update the TR only if row is on current page
9152                 this._oChainRender.add({
9153                     method: function() {
9154                         if((this instanceof DT) && this._sId) {
9155                             // Paginated
9156                             var oPaginator = this.get('paginator');
9157                             if (oPaginator) {
9158                                 var pageStartIndex = (oPaginator.getPageRecords())[0],
9159                                     pageLastIndex = (oPaginator.getPageRecords())[1];
9160         
9161                                 // At least one of the new records affects the view
9162                                 if ((index >= pageStartIndex) || (index <= pageLastIndex)) {
9163                                     this.render();
9164                                 }
9165                             }
9166                             else {
9167                                 if(elRow) {
9168                                     this._updateTrEl(elRow, updatedRecord);
9169                                 }
9170                                 else {
9171                                     this.getTbodyEl().appendChild(this._addTrEl(updatedRecord));
9172                                 }
9173                             }
9174                             this.fireEvent("rowUpdateEvent", {record:updatedRecord, oldData:oldData});
9175                             YAHOO.log("DataTable row updated: Record ID = " + updatedRecord.getId() +
9176                                     ", Record index = " + this.getRecordIndex(updatedRecord) +
9177                                     ", page row index = " + this.getTrIndex(updatedRecord), "info", this.toString());
9178                         }
9179                     },
9180                     scope: this,
9181                     timeout: (this.get("renderLoopSize") > 0) ? 0 : -1
9182                 });
9183                 this._runRenderChain();
9184                 return;
9185             }
9186         }
9187     }
9188     YAHOO.log("Could not update row " + row + " with the data : " + lang.dump(oData), "warn", this.toString());
9189     return;
9190 },
9191
9192 /**
9193  * Starting with the given row, updates associated Records with the given data.
9194  * The number of rows to update are determined by the array of data provided.
9195  * Undefined data (i.e., not an object literal) causes a row to be skipped. If
9196  * any of the rows are on current page, the corresponding DOM elements are also
9197  * updated.
9198  *
9199  * @method updateRows
9200  * @param startrow {YAHOO.widget.Record | Number | HTMLElement | String}
9201  * Starting row to update: By Record instance, by Record's RecordSet
9202  * position index, by HTMLElement reference to the TR element, or by ID string
9203  * of the TR element.
9204  * @param aData {Object[]} Array of object literal of data for the rows.
9205  */
9206 updateRows : function(startrow, aData) {
9207     if(lang.isArray(aData)) {
9208         var startIndex = startrow,
9209             oRecordSet = this._oRecordSet;
9210             
9211         if (!lang.isNumber(startrow)) {
9212             startIndex = this.getRecordIndex(startrow);
9213         }
9214             
9215         if(lang.isNumber(startIndex) && (startIndex >= 0) && (startIndex < oRecordSet.getLength())) {
9216             var lastIndex = startIndex + aData.length,
9217                 aOldRecords = oRecordSet.getRecords(startIndex, aData.length),
9218                 aNewRecords = oRecordSet.setRecords(aData, startIndex);
9219             if(aNewRecords) {
9220                 // Update selected rows as necessary
9221                 var tracker = this._aSelections || [],
9222                     i=0, j, newId, oldId;
9223                 for(; i<tracker.length; i++) {
9224                     for(j=0; j<aOldRecords.length; j++) {
9225                         oldId = aOldRecords[j].getId();
9226                         if((tracker[i] === oldId)) {
9227                             tracker[i] = aNewRecords[j].getId();
9228                         }
9229                         else if(tracker[i].recordId === oldId) {
9230                             tracker[i].recordId = aNewRecords[j].getId();
9231                         }
9232                     }
9233                 }
9234             
9235                 // Paginated
9236                 var oPaginator = this.get('paginator');
9237                 if (oPaginator) {
9238                     var pageStartIndex = (oPaginator.getPageRecords())[0],
9239                         pageLastIndex = (oPaginator.getPageRecords())[1];
9240     
9241                     // At least one of the new records affects the view
9242                     if ((startIndex >= pageStartIndex) || (lastIndex <= pageLastIndex)) {
9243                         this.render();
9244                     }
9245                     
9246                     this.fireEvent("rowsAddEvent", {newRecords:aNewRecords, oldRecords:aOldRecords});
9247                     YAHOO.log("Added " + aNewRecords.length + 
9248                             " rows starting at index " + startIndex +
9249                             " with data " + lang.dump(aData), "info", this.toString());
9250                     return;
9251                 }
9252                 // Not paginated
9253                 else {
9254                     // Update the TR elements
9255                     var loopN = this.get("renderLoopSize"),
9256                         rowCount = aData.length, // how many needed
9257                         lastRowIndex = this._elTbody.rows.length,
9258                         isLast = (lastIndex >= lastRowIndex),
9259                         isAdding = (lastIndex > lastRowIndex);
9260                                            
9261                     this._oChainRender.add({
9262                         method: function(oArg) {
9263                             if((this instanceof DT) && this._sId) {
9264                                 var aRecords = oArg.aRecords,
9265                                     i = oArg.nCurrentRow,
9266                                     j = oArg.nDataPointer,
9267                                     len = loopN > 0 ? Math.min(i+loopN, startIndex+aRecords.length) : startIndex+aRecords.length;
9268                                     
9269                                 for(; i < len; i++,j++) {
9270                                     if(isAdding && (i>=lastRowIndex)) {
9271                                         this._elTbody.appendChild(this._addTrEl(aRecords[j]));
9272                                     }
9273                                     else {
9274                                         this._updateTrEl(this._elTbody.rows[i], aRecords[j]);
9275                                     }
9276                                 }
9277                                 oArg.nCurrentRow = i;
9278                                 oArg.nDataPointer = j;
9279                             }
9280                         },
9281                         iterations: (loopN > 0) ? Math.ceil(rowCount/loopN) : 1,
9282                         argument: {nCurrentRow:startIndex,aRecords:aNewRecords,nDataPointer:0,isAdding:isAdding},
9283                         scope: this,
9284                         timeout: (loopN > 0) ? 0 : -1
9285                     });
9286                     this._oChainRender.add({
9287                         method: function(oArg) {
9288                             var recIndex = oArg.recIndex;
9289                             // Set FIRST/LAST
9290                             if(recIndex === 0) {
9291                                 this._setFirstRow();
9292                             }
9293                             if(oArg.isLast) {
9294                                 this._setLastRow();
9295                             }
9296                             // Set EVEN/ODD
9297                             this._setRowStripes();                           
9298     
9299                             this.fireEvent("rowsAddEvent", {newRecords:aNewRecords, oldRecords:aOldRecords});
9300                             YAHOO.log("Added " + aNewRecords.length + 
9301                                     " rows starting at index " + startIndex +
9302                                     " with data " + lang.dump(aData), "info", this.toString());
9303                         },
9304                         argument: {recIndex: startIndex, isLast: isLast},
9305                         scope: this,
9306                         timeout: -1 // Needs to run immediately after the DOM insertions above
9307                     });
9308                     this._runRenderChain();
9309                     this.hideTableMessage();                
9310                     return;
9311                 }            
9312             }
9313         }
9314     }
9315     YAHOO.log("Could not update rows at " + startrow + " with " + lang.dump(aData), "warn", this.toString());
9316 },
9317
9318 /**
9319  * Deletes the given row's Record from the RecordSet. If the row is on current page,
9320  * the corresponding DOM elements are also deleted.
9321  *
9322  * @method deleteRow
9323  * @param row {HTMLElement | String | Number} DOM element reference or ID string
9324  * to DataTable page element or RecordSet index.
9325  */
9326 deleteRow : function(row) {
9327     var nRecordIndex = (lang.isNumber(row)) ? row : this.getRecordIndex(row);
9328     if(lang.isNumber(nRecordIndex)) {
9329         var oRecord = this.getRecord(nRecordIndex);
9330         if(oRecord) {
9331             var nTrIndex = this.getTrIndex(nRecordIndex);
9332             
9333             // Remove from selection tracker if there
9334             var sRecordId = oRecord.getId();
9335             var tracker = this._aSelections || [];
9336             for(var j=tracker.length-1; j>-1; j--) {
9337                 if((lang.isString(tracker[j]) && (tracker[j] === sRecordId)) ||
9338                         (lang.isObject(tracker[j]) && (tracker[j].recordId === sRecordId))) {
9339                     tracker.splice(j,1);
9340                 }
9341             }
9342     
9343             // Delete Record from RecordSet
9344             var oData = this._oRecordSet.deleteRecord(nRecordIndex);
9345     
9346             // Update the UI
9347             if(oData) {
9348                 // If paginated and the deleted row was on this or a prior page, just
9349                 // re-render
9350                 var oPaginator = this.get('paginator');
9351                 if (oPaginator) {
9352                     // Update the paginator's totalRecords
9353                     var totalRecords = oPaginator.get('totalRecords'),
9354                         // must capture before the totalRecords change because
9355                         // Paginator shifts to previous page automatically
9356                         rng = oPaginator.getPageRecords();
9357
9358                     if (totalRecords !== widget.Paginator.VALUE_UNLIMITED) {
9359                         oPaginator.set('totalRecords',totalRecords - 1);
9360                     }
9361     
9362                     // The deleted record was on this or a prior page, re-render
9363                     if (!rng || nRecordIndex <= rng[1]) {
9364                         this.render();
9365                     }
9366
9367                     this._oChainRender.add({
9368                         method: function() {
9369                             if((this instanceof DT) && this._sId) {
9370                                 this.fireEvent("rowDeleteEvent", {recordIndex:nRecordIndex, oldData:oData, trElIndex:nTrIndex});
9371                                 YAHOO.log("Deleted row with data " + YAHOO.lang.dump(oData) + " at RecordSet index " + nRecordIndex + " and page row index " + nTrIndex, "info", this.toString());     
9372                             }
9373                         },
9374                         scope: this,
9375                         timeout: (this.get("renderLoopSize") > 0) ? 0 : -1
9376                     });
9377                     this._runRenderChain();
9378                 }
9379                 // Not paginated
9380                 else {
9381                     if(lang.isNumber(nTrIndex)) {
9382                         this._oChainRender.add({
9383                             method: function() {
9384                                 if((this instanceof DT) && this._sId) {
9385                                     var isLast = (nTrIndex == this.getLastTrEl().sectionRowIndex);
9386                                     this._deleteTrEl(nTrIndex);
9387                     
9388                                     // Post-delete tasks
9389                                     if(this._elTbody.rows.length > 0) {
9390                                         // Set FIRST/LAST
9391                                         if(nTrIndex === 0) {
9392                                             this._setFirstRow();
9393                                         }
9394                                         if(isLast) {
9395                                             this._setLastRow();
9396                                         }
9397                                         // Set EVEN/ODD
9398                                         if(nTrIndex != this._elTbody.rows.length) {
9399                                             this._setRowStripes(nTrIndex);
9400                                         }                                
9401                                     }
9402                     
9403                                     this.fireEvent("rowDeleteEvent", {recordIndex:nRecordIndex,oldData:oData, trElIndex:nTrIndex});
9404                                     YAHOO.log("Deleted row with data " + YAHOO.lang.dump(oData) + " at RecordSet index " + nRecordIndex + " and page row index " + nTrIndex, "info", this.toString());     
9405                                 }
9406                             },
9407                             scope: this,
9408                             timeout: (this.get("renderLoopSize") > 0) ? 0 : -1
9409                         });
9410                         this._runRenderChain();
9411                         return;
9412                     }
9413                 }
9414             }
9415         }
9416     }
9417     YAHOO.log("Could not delete row: " + row, "warn", this.toString());
9418     return null;
9419 },
9420
9421 /**
9422  * Convenience method to delete multiple rows.
9423  *
9424  * @method deleteRows
9425  * @param row {HTMLElement | String | Number} DOM element reference or ID string
9426  * to DataTable page element or RecordSet index.
9427  * @param count {Number} (optional) How many rows to delete. A negative value
9428  * will delete towards the beginning.
9429  */
9430 deleteRows : function(row, count) {
9431     var nRecordIndex = (lang.isNumber(row)) ? row : this.getRecordIndex(row);
9432     if(lang.isNumber(nRecordIndex)) {
9433         var oRecord = this.getRecord(nRecordIndex);
9434         if(oRecord) {
9435             var nTrIndex = this.getTrIndex(nRecordIndex);
9436             
9437             // Remove from selection tracker if there
9438             var sRecordId = oRecord.getId();
9439             var tracker = this._aSelections || [];
9440             for(var j=tracker.length-1; j>-1; j--) {
9441                 if((lang.isString(tracker[j]) && (tracker[j] === sRecordId)) ||
9442                         (lang.isObject(tracker[j]) && (tracker[j].recordId === sRecordId))) {
9443                     tracker.splice(j,1);
9444                 }
9445             }
9446     
9447             // Delete Record from RecordSet
9448             var highIndex = nRecordIndex;
9449             var lowIndex = nRecordIndex;
9450         
9451             // Validate count and account for negative value
9452             if(count && lang.isNumber(count)) {
9453                 highIndex = (count > 0) ? nRecordIndex + count -1 : nRecordIndex;
9454                 lowIndex = (count > 0) ? nRecordIndex : nRecordIndex + count + 1;
9455                 count = (count > 0) ? count : count*-1;
9456                 if(lowIndex < 0) {
9457                     lowIndex = 0;
9458                     count = highIndex - lowIndex + 1;
9459                 }
9460             }
9461             else {
9462                 count = 1;
9463             }
9464             
9465             var aData = this._oRecordSet.deleteRecords(lowIndex, count);
9466     
9467             // Update the UI
9468             if(aData) {
9469                 var oPaginator = this.get('paginator'),
9470                     loopN = this.get("renderLoopSize");
9471                 // If paginated and the deleted row was on this or a prior page, just
9472                 // re-render
9473                 if (oPaginator) {
9474                     // Update the paginator's totalRecords
9475                     var totalRecords = oPaginator.get('totalRecords'),
9476                         // must capture before the totalRecords change because
9477                         // Paginator shifts to previous page automatically
9478                         rng = oPaginator.getPageRecords();
9479
9480                     if (totalRecords !== widget.Paginator.VALUE_UNLIMITED) {
9481                         oPaginator.set('totalRecords',totalRecords - aData.length);
9482                     }
9483     
9484                     // The records were on this or a prior page, re-render
9485                     if (!rng || lowIndex <= rng[1]) {
9486                         this.render();
9487                     }
9488
9489                     this._oChainRender.add({
9490                         method: function(oArg) {
9491                             if((this instanceof DT) && this._sId) {
9492                                 this.fireEvent("rowsDeleteEvent", {recordIndex:lowIndex, oldData:aData, count:count});
9493                                 YAHOO.log("DataTable " + count + " rows deleted starting at index " + lowIndex, "info", this.toString());
9494                             }
9495                         },
9496                         scope: this,
9497                         timeout: (loopN > 0) ? 0 : -1
9498                     });
9499                     this._runRenderChain();
9500                     return;
9501                 }
9502                 // Not paginated
9503                 else {
9504                     if(lang.isNumber(nTrIndex)) {
9505                         // Delete the TR elements starting with highest index
9506                         var loopEnd = lowIndex;
9507                         var nRowsNeeded = count; // how many needed
9508                         this._oChainRender.add({
9509                             method: function(oArg) {
9510                                 if((this instanceof DT) && this._sId) {
9511                                     var i = oArg.nCurrentRow,
9512                                         len = (loopN > 0) ? (Math.max(i - loopN,loopEnd)-1) : loopEnd-1;
9513                                     for(; i>len; --i) {
9514                                         this._deleteTrEl(i);
9515                                     }
9516                                     oArg.nCurrentRow = i;
9517                                 }
9518                             },
9519                             iterations: (loopN > 0) ? Math.ceil(count/loopN) : 1,
9520                             argument: {nCurrentRow:highIndex},
9521                             scope: this,
9522                             timeout: (loopN > 0) ? 0 : -1
9523                         });
9524                         this._oChainRender.add({
9525                             method: function() {    
9526                                 // Post-delete tasks
9527                                 if(this._elTbody.rows.length > 0) {
9528                                     this._setFirstRow();
9529                                     this._setLastRow();
9530                                     this._setRowStripes();
9531                                 }
9532                                 
9533                                 this.fireEvent("rowsDeleteEvent", {recordIndex:lowIndex, oldData:aData, count:count});
9534                                 YAHOO.log("DataTable " + count + " rows deleted starting at index " + lowIndex, "info", this.toString());
9535                             },
9536                             scope: this,
9537                             timeout: -1 // Needs to run immediately after the DOM deletions above
9538                         });
9539                         this._runRenderChain();
9540                         return;
9541                     }
9542                 }
9543             }
9544         }
9545     }
9546     YAHOO.log("Could not delete " + count + " rows at row " + row, "warn", this.toString());
9547     return null;
9548 },
9549
9550
9551
9552
9553
9554
9555
9556
9557
9558
9559
9560
9561
9562
9563
9564
9565
9566
9567
9568
9569
9570
9571
9572
9573
9574
9575
9576
9577
9578
9579
9580
9581
9582
9583
9584
9585
9586
9587
9588
9589
9590
9591
9592
9593
9594
9595 // CELL FUNCTIONS
9596
9597 /**
9598  * Outputs markup into the given TD based on given Record.
9599  *
9600  * @method formatCell
9601  * @param elCell {HTMLElement} The liner DIV element within the TD.
9602  * @param oRecord {YAHOO.widget.Record} (Optional) Record instance.
9603  * @param oColumn {YAHOO.widget.Column} (Optional) Column instance.
9604  */
9605 formatCell : function(elCell, oRecord, oColumn) {
9606     if(!oRecord) {
9607         oRecord = this.getRecord(elCell);
9608     }
9609     if(!oColumn) {
9610         oColumn = this.getColumn(elCell.parentNode.cellIndex);
9611     }
9612
9613     if(oRecord && oColumn) {
9614         var sField = oColumn.field;
9615         var oData = oRecord.getData(sField);
9616
9617         var fnFormatter = typeof oColumn.formatter === 'function' ?
9618                           oColumn.formatter :
9619                           DT.Formatter[oColumn.formatter+''] ||
9620                           DT.Formatter.defaultFormatter;
9621
9622         // Apply special formatter
9623         if(fnFormatter) {
9624             fnFormatter.call(this, elCell, oRecord, oColumn, oData);
9625         }
9626         else {
9627             elCell.innerHTML = oData;
9628         }
9629
9630         this.fireEvent("cellFormatEvent", {record:oRecord, column:oColumn, key:oColumn.key, el:elCell});
9631     }
9632     else {
9633         YAHOO.log("Could not format cell " + elCell, "error", this.toString());
9634     }
9635 },
9636
9637 /**
9638  * For the given row and column, updates the Record with the given data. If the
9639  * cell is on current page, the corresponding DOM elements are also updated.
9640  *
9641  * @method updateCell
9642  * @param oRecord {YAHOO.widget.Record} Record instance.
9643  * @param oColumn {YAHOO.widget.Column | String | Number} A Column key, or a ColumnSet key index.
9644  * @param oData {Object} New data value for the cell.
9645  */
9646 updateCell : function(oRecord, oColumn, oData) {    
9647     // Validate Column and Record
9648     oColumn = (oColumn instanceof YAHOO.widget.Column) ? oColumn : this.getColumn(oColumn);
9649     if(oColumn && oColumn.getKey() && (oRecord instanceof YAHOO.widget.Record)) {
9650         var sKey = oColumn.getKey(),
9651         
9652         // Copy data from the Record for the event that gets fired later
9653         //var oldData = YAHOO.widget.DataTable._cloneObject(oRecord.getData());
9654             oldData = oRecord.getData(sKey);
9655
9656         // Update Record with new data
9657         this._oRecordSet.updateRecordValue(oRecord, sKey, oData);
9658     
9659         // Update the TD only if row is on current page
9660         var elTd = this.getTdEl({record: oRecord, column: oColumn});
9661         if(elTd) {
9662             this._oChainRender.add({
9663                 method: function() {
9664                     if((this instanceof DT) && this._sId) {
9665                         this.formatCell(elTd.firstChild);
9666                         this.fireEvent("cellUpdateEvent", {record:oRecord, column: oColumn, oldData:oldData});
9667                         YAHOO.log("DataTable cell updated: Record ID = " + oRecord.getId() +
9668                                 ", Record index = " + this.getRecordIndex(oRecord) +
9669                                 ", page row index = " + this.getTrIndex(oRecord) +
9670                                 ", Column key = " + oColumn.getKey(), "info", this.toString());
9671                     }
9672                 },
9673                 scope: this,
9674                 timeout: (this.get("renderLoopSize") > 0) ? 0 : -1
9675             });
9676             this._runRenderChain();
9677         }
9678         else {
9679             this.fireEvent("cellUpdateEvent", {record:oRecord, column: oColumn, oldData:oldData});
9680             YAHOO.log("DataTable cell updated: Record ID = " + oRecord.getId() +
9681                     ", Record index = " + this.getRecordIndex(oRecord) +
9682                     ", page row index = " + this.getTrIndex(oRecord) +
9683                     ", Column key = " + oColumn.getKey(), "info", this.toString());   
9684         }
9685     }
9686 },
9687
9688
9689
9690
9691
9692
9693
9694
9695
9696
9697
9698
9699
9700
9701
9702
9703
9704
9705
9706
9707
9708
9709
9710
9711
9712
9713
9714
9715
9716
9717
9718
9719
9720
9721
9722
9723
9724
9725
9726
9727
9728
9729
9730
9731
9732
9733
9734
9735
9736
9737
9738 // PAGINATION
9739 /**
9740  * Method executed during set() operation for the "paginator" attribute.
9741  * Adds and/or severs event listeners between DataTable and Paginator
9742  *
9743  * @method _updatePaginator
9744  * @param newPag {Paginator} Paginator instance (or null) for DataTable to use
9745  * @private
9746  */
9747 _updatePaginator : function (newPag) {
9748     var oldPag = this.get('paginator');
9749     if (oldPag && newPag !== oldPag) {
9750         oldPag.unsubscribe('changeRequest', this.onPaginatorChangeRequest, this, true);
9751     }
9752     if (newPag) {
9753         newPag.subscribe('changeRequest', this.onPaginatorChangeRequest, this, true);
9754     }
9755 },
9756
9757 /**
9758  * Update the UI infrastructure in response to a "paginator" attribute change.
9759  *
9760  * @method _handlePaginatorChange
9761  * @param e {Object} Change event object containing keys 'type','newValue',
9762  *                   and 'prevValue'
9763  * @private
9764  */
9765 _handlePaginatorChange : function (e) {
9766     if (e.prevValue === e.newValue) { return; }
9767
9768     var newPag     = e.newValue,
9769         oldPag     = e.prevValue,
9770         containers = this._defaultPaginatorContainers();
9771
9772     if (oldPag) {
9773         if (oldPag.getContainerNodes()[0] == containers[0]) {
9774             oldPag.set('containers',[]);
9775         }
9776         oldPag.destroy();
9777
9778         // Convenience: share the default containers if possible.
9779         // Otherwise, remove the default containers from the DOM.
9780         if (containers[0]) {
9781             if (newPag && !newPag.getContainerNodes().length) {
9782                 newPag.set('containers',containers);
9783             } else {
9784                 // No new Paginator to use existing containers, OR new
9785                 // Paginator has configured containers.
9786                 for (var i = containers.length - 1; i >= 0; --i) {
9787                     if (containers[i]) {
9788                         containers[i].parentNode.removeChild(containers[i]);
9789                     }
9790                 }
9791             }
9792         }
9793     }
9794
9795     if (!this._bInit) {
9796         this.render();
9797
9798     }
9799
9800     if (newPag) {
9801         this.renderPaginator();
9802     }
9803
9804 },
9805
9806 /**
9807  * Returns the default containers used for Paginators.  If create param is
9808  * passed, the containers will be created and added to the DataTable container.
9809  *
9810  * @method _defaultPaginatorContainers
9811  * @param create {boolean} Create the default containers if not found
9812  * @private
9813  */
9814 _defaultPaginatorContainers : function (create) {
9815     var above_id = this._sId + '-paginator0',
9816         below_id = this._sId + '-paginator1',
9817         above    = Dom.get(above_id),
9818         below    = Dom.get(below_id);
9819
9820     if (create && (!above || !below)) {
9821         // One above and one below the table
9822         if (!above) {
9823             above    = document.createElement('div');
9824             above.id = above_id;
9825             Dom.addClass(above, DT.CLASS_PAGINATOR);
9826
9827             this._elContainer.insertBefore(above,this._elContainer.firstChild);
9828         }
9829
9830         if (!below) {
9831             below    = document.createElement('div');
9832             below.id = below_id;
9833             Dom.addClass(below, DT.CLASS_PAGINATOR);
9834
9835             this._elContainer.appendChild(below);
9836         }
9837     }
9838
9839     return [above,below];
9840 },
9841
9842 /**
9843  * Renders the Paginator to the DataTable UI
9844  *
9845  * @method renderPaginator
9846  */
9847 renderPaginator : function () {
9848     var pag = this.get("paginator");
9849     if (!pag) { return; }
9850
9851     // Add the containers if the Paginator is not configured with containers
9852     if (!pag.getContainerNodes().length) {
9853         pag.set('containers',this._defaultPaginatorContainers(true));
9854     }
9855
9856     pag.render();
9857 },
9858
9859 /**
9860  * Overridable method gives implementers a hook to show loading message before
9861  * changing Paginator value.
9862  *
9863  * @method doBeforePaginatorChange
9864  * @param oPaginatorState {Object} An object literal describing the proposed pagination state.
9865  * @return {Boolean} Return true to continue changing Paginator value.
9866  */
9867 doBeforePaginatorChange : function(oPaginatorState) {
9868     this.showTableMessage(this.get("MSG_LOADING"), DT.CLASS_LOADING);
9869     return true;
9870 },
9871
9872 /**
9873  * Responds to new Pagination states. By default, updates the UI to reflect the
9874  * new state. If "dynamicData" is true, current selections are purged before
9875  * a request is sent to the DataSource for data for the new state (using the
9876  * request returned by "generateRequest()").
9877  *  
9878  * @method onPaginatorChangeRequest
9879  * @param oPaginatorState {Object} An object literal describing the proposed pagination state.
9880  */
9881 onPaginatorChangeRequest : function (oPaginatorState) {
9882     var ok = this.doBeforePaginatorChange(oPaginatorState);
9883     if(ok) {
9884         // Server-side pagination
9885         if(this.get("dynamicData")) {
9886             // Get the current state
9887             var oState = this.getState();
9888             
9889             // Update pagination values
9890             oState.pagination = oPaginatorState;
9891     
9892             // Get the request for the new state
9893             var request = this.get("generateRequest")(oState, this);
9894             
9895             // Purge selections
9896             this.unselectAllRows();
9897             this.unselectAllCells();
9898             
9899             // Get the new data from the server
9900             var callback = {
9901                 success : this.onDataReturnSetRows,
9902                 failure : this.onDataReturnSetRows,
9903                 argument : oState, // Pass along the new state to the callback
9904                 scope : this
9905             };
9906             this._oDataSource.sendRequest(request, callback);
9907         }
9908         // Client-side pagination
9909         else {
9910             // Set the core pagination values silently (the second param)
9911             // to avoid looping back through the changeRequest mechanism
9912             oPaginatorState.paginator.setStartIndex(oPaginatorState.recordOffset,true);
9913             oPaginatorState.paginator.setRowsPerPage(oPaginatorState.rowsPerPage,true);
9914     
9915             // Update the UI
9916             this.render();
9917         }
9918     }
9919     else {
9920         YAHOO.log("Could not change Paginator value \"" + oPaginatorState + "\"", "warn", this.toString());
9921     }
9922 },
9923
9924
9925
9926
9927
9928
9929
9930
9931
9932
9933
9934
9935
9936
9937
9938
9939
9940
9941
9942
9943
9944
9945
9946
9947
9948
9949
9950
9951
9952
9953
9954
9955
9956
9957
9958
9959
9960
9961
9962
9963
9964
9965
9966
9967
9968
9969
9970
9971
9972
9973 // SELECTION/HIGHLIGHTING
9974
9975 /*
9976  * Reference to last highlighted cell element
9977  *
9978  * @property _elLastHighlightedTd
9979  * @type HTMLElement
9980  * @private
9981  */
9982 _elLastHighlightedTd : null,
9983
9984 /*
9985  * ID string of last highlighted row element
9986  *
9987  * @property _sLastHighlightedTrElId
9988  * @type String
9989  * @private
9990  */
9991 //_sLastHighlightedTrElId : null,
9992
9993 /**
9994  * Array to track row selections (by sRecordId) and/or cell selections
9995  * (by {recordId:sRecordId, columnKey:sColumnKey})
9996  *
9997  * @property _aSelections
9998  * @type Object[]
9999  * @private
10000  */
10001 _aSelections : null,
10002
10003 /**
10004  * Record instance of the row selection anchor.
10005  *
10006  * @property _oAnchorRecord
10007  * @type YAHOO.widget.Record
10008  * @private
10009  */
10010 _oAnchorRecord : null,
10011
10012 /**
10013  * Object literal representing cell selection anchor:
10014  * {recordId:sRecordId, columnKey:sColumnKey}.
10015  *
10016  * @property _oAnchorCell
10017  * @type Object
10018  * @private
10019  */
10020 _oAnchorCell : null,
10021
10022 /**
10023  * Convenience method to remove the class YAHOO.widget.DataTable.CLASS_SELECTED
10024  * from all TR elements on the page.
10025  *
10026  * @method _unselectAllTrEls
10027  * @private
10028  */
10029 _unselectAllTrEls : function() {
10030     var selectedRows = Dom.getElementsByClassName(DT.CLASS_SELECTED,"tr",this._elTbody);
10031     Dom.removeClass(selectedRows, DT.CLASS_SELECTED);
10032 },
10033
10034 /**
10035  * Returns object literal of values that represent the selection trigger. Used
10036  * to determine selection behavior resulting from a key event.
10037  *
10038  * @method _getSelectionTrigger
10039  * @private
10040  */
10041 _getSelectionTrigger : function() {
10042     var sMode = this.get("selectionMode");
10043     var oTrigger = {};
10044     var oTriggerCell, oTriggerRecord, nTriggerRecordIndex, elTriggerRow, nTriggerTrIndex;
10045
10046     // Cell mode
10047     if((sMode == "cellblock") || (sMode == "cellrange") || (sMode == "singlecell")) {
10048         oTriggerCell = this.getLastSelectedCell();
10049         // No selected cells found
10050         if(!oTriggerCell) {
10051             return null;
10052         }
10053         else {
10054             oTriggerRecord = this.getRecord(oTriggerCell.recordId);
10055             nTriggerRecordIndex = this.getRecordIndex(oTriggerRecord);
10056             elTriggerRow = this.getTrEl(oTriggerRecord);
10057             nTriggerTrIndex = this.getTrIndex(elTriggerRow);
10058
10059             // Selected cell not found on this page
10060             if(nTriggerTrIndex === null) {
10061                 return null;
10062             }
10063             else {
10064                 oTrigger.record = oTriggerRecord;
10065                 oTrigger.recordIndex = nTriggerRecordIndex;
10066                 oTrigger.el = this.getTdEl(oTriggerCell);
10067                 oTrigger.trIndex = nTriggerTrIndex;
10068                 oTrigger.column = this.getColumn(oTriggerCell.columnKey);
10069                 oTrigger.colKeyIndex = oTrigger.column.getKeyIndex();
10070                 oTrigger.cell = oTriggerCell;
10071                 return oTrigger;
10072             }
10073         }
10074     }
10075     // Row mode
10076     else {
10077         oTriggerRecord = this.getLastSelectedRecord();
10078         // No selected rows found
10079         if(!oTriggerRecord) {
10080                 return null;
10081         }
10082         else {
10083             // Selected row found, but is it on current page?
10084             oTriggerRecord = this.getRecord(oTriggerRecord);
10085             nTriggerRecordIndex = this.getRecordIndex(oTriggerRecord);
10086             elTriggerRow = this.getTrEl(oTriggerRecord);
10087             nTriggerTrIndex = this.getTrIndex(elTriggerRow);
10088
10089             // Selected row not found on this page
10090             if(nTriggerTrIndex === null) {
10091                 return null;
10092             }
10093             else {
10094                 oTrigger.record = oTriggerRecord;
10095                 oTrigger.recordIndex = nTriggerRecordIndex;
10096                 oTrigger.el = elTriggerRow;
10097                 oTrigger.trIndex = nTriggerTrIndex;
10098                 return oTrigger;
10099             }
10100         }
10101     }
10102 },
10103
10104 /**
10105  * Returns object literal of values that represent the selection anchor. Used
10106  * to determine selection behavior resulting from a user event.
10107  *
10108  * @method _getSelectionAnchor
10109  * @param oTrigger {Object} (Optional) Object literal of selection trigger values
10110  * (for key events).
10111  * @private
10112  */
10113 _getSelectionAnchor : function(oTrigger) {
10114     var sMode = this.get("selectionMode");
10115     var oAnchor = {};
10116     var oAnchorRecord, nAnchorRecordIndex, nAnchorTrIndex;
10117
10118     // Cell mode
10119     if((sMode == "cellblock") || (sMode == "cellrange") || (sMode == "singlecell")) {
10120         // Validate anchor cell
10121         var oAnchorCell = this._oAnchorCell;
10122         if(!oAnchorCell) {
10123             if(oTrigger) {
10124                 oAnchorCell = this._oAnchorCell = oTrigger.cell;
10125             }
10126             else {
10127                 return null;
10128             }
10129         }
10130         oAnchorRecord = this._oAnchorCell.record;
10131         nAnchorRecordIndex = this._oRecordSet.getRecordIndex(oAnchorRecord);
10132         nAnchorTrIndex = this.getTrIndex(oAnchorRecord);
10133         // If anchor cell is not on this page...
10134         if(nAnchorTrIndex === null) {
10135             // ...set TR index equal to top TR
10136             if(nAnchorRecordIndex < this.getRecordIndex(this.getFirstTrEl())) {
10137                 nAnchorTrIndex = 0;
10138             }
10139             // ...set TR index equal to bottom TR
10140             else {
10141                 nAnchorTrIndex = this.getRecordIndex(this.getLastTrEl());
10142             }
10143         }
10144
10145         oAnchor.record = oAnchorRecord;
10146         oAnchor.recordIndex = nAnchorRecordIndex;
10147         oAnchor.trIndex = nAnchorTrIndex;
10148         oAnchor.column = this._oAnchorCell.column;
10149         oAnchor.colKeyIndex = oAnchor.column.getKeyIndex();
10150         oAnchor.cell = oAnchorCell;
10151         return oAnchor;
10152     }
10153     // Row mode
10154     else {
10155         oAnchorRecord = this._oAnchorRecord;
10156         if(!oAnchorRecord) {
10157             if(oTrigger) {
10158                 oAnchorRecord = this._oAnchorRecord = oTrigger.record;
10159             }
10160             else {
10161                 return null;
10162             }
10163         }
10164
10165         nAnchorRecordIndex = this.getRecordIndex(oAnchorRecord);
10166         nAnchorTrIndex = this.getTrIndex(oAnchorRecord);
10167         // If anchor row is not on this page...
10168         if(nAnchorTrIndex === null) {
10169             // ...set TR index equal to top TR
10170             if(nAnchorRecordIndex < this.getRecordIndex(this.getFirstTrEl())) {
10171                 nAnchorTrIndex = 0;
10172             }
10173             // ...set TR index equal to bottom TR
10174             else {
10175                 nAnchorTrIndex = this.getRecordIndex(this.getLastTrEl());
10176             }
10177         }
10178
10179         oAnchor.record = oAnchorRecord;
10180         oAnchor.recordIndex = nAnchorRecordIndex;
10181         oAnchor.trIndex = nAnchorTrIndex;
10182         return oAnchor;
10183     }
10184 },
10185
10186 /**
10187  * Determines selection behavior resulting from a mouse event when selection mode
10188  * is set to "standard".
10189  *
10190  * @method _handleStandardSelectionByMouse
10191  * @param oArgs.event {HTMLEvent} Event object.
10192  * @param oArgs.target {HTMLElement} Target element.
10193  * @private
10194  */
10195 _handleStandardSelectionByMouse : function(oArgs) {
10196     var elTarget = oArgs.target;
10197
10198     // Validate target row
10199     var elTargetRow = this.getTrEl(elTarget);
10200     if(elTargetRow) {
10201         var e = oArgs.event;
10202         var bSHIFT = e.shiftKey;
10203         var bCTRL = e.ctrlKey || ((navigator.userAgent.toLowerCase().indexOf("mac") != -1) && e.metaKey);
10204
10205         var oTargetRecord = this.getRecord(elTargetRow);
10206         var nTargetRecordIndex = this._oRecordSet.getRecordIndex(oTargetRecord);
10207
10208         var oAnchor = this._getSelectionAnchor();
10209
10210         var i;
10211
10212         // Both SHIFT and CTRL
10213         if(bSHIFT && bCTRL) {
10214             // Validate anchor
10215             if(oAnchor) {
10216                 if(this.isSelected(oAnchor.record)) {
10217                     // Select all rows between anchor row and target row, including target row
10218                     if(oAnchor.recordIndex < nTargetRecordIndex) {
10219                         for(i=oAnchor.recordIndex+1; i<=nTargetRecordIndex; i++) {
10220                             if(!this.isSelected(i)) {
10221                                 this.selectRow(i);
10222                             }
10223                         }
10224                     }
10225                     // Select all rows between target row and anchor row, including target row
10226                     else {
10227                         for(i=oAnchor.recordIndex-1; i>=nTargetRecordIndex; i--) {
10228                             if(!this.isSelected(i)) {
10229                                 this.selectRow(i);
10230                             }
10231                         }
10232                     }
10233                 }
10234                 else {
10235                     // Unselect all rows between anchor row and target row
10236                     if(oAnchor.recordIndex < nTargetRecordIndex) {
10237                         for(i=oAnchor.recordIndex+1; i<=nTargetRecordIndex-1; i++) {
10238                             if(this.isSelected(i)) {
10239                                 this.unselectRow(i);
10240                             }
10241                         }
10242                     }
10243                     // Unselect all rows between target row and anchor row
10244                     else {
10245                         for(i=nTargetRecordIndex+1; i<=oAnchor.recordIndex-1; i++) {
10246                             if(this.isSelected(i)) {
10247                                 this.unselectRow(i);
10248                             }
10249                         }
10250                     }
10251                     // Select the target row
10252                     this.selectRow(oTargetRecord);
10253                 }
10254             }
10255             // Invalid anchor
10256             else {
10257                 // Set anchor
10258                 this._oAnchorRecord = oTargetRecord;
10259
10260                 // Toggle selection of target
10261                 if(this.isSelected(oTargetRecord)) {
10262                     this.unselectRow(oTargetRecord);
10263                 }
10264                 else {
10265                     this.selectRow(oTargetRecord);
10266                 }
10267             }
10268         }
10269          // Only SHIFT
10270         else if(bSHIFT) {
10271             this.unselectAllRows();
10272
10273             // Validate anchor
10274             if(oAnchor) {
10275                 // Select all rows between anchor row and target row,
10276                 // including the anchor row and target row
10277                 if(oAnchor.recordIndex < nTargetRecordIndex) {
10278                     for(i=oAnchor.recordIndex; i<=nTargetRecordIndex; i++) {
10279                         this.selectRow(i);
10280                     }
10281                 }
10282                 // Select all rows between target row and anchor row,
10283                 // including the target row and anchor row
10284                 else {
10285                     for(i=oAnchor.recordIndex; i>=nTargetRecordIndex; i--) {
10286                         this.selectRow(i);
10287                     }
10288                 }
10289             }
10290             // Invalid anchor
10291             else {
10292                 // Set anchor
10293                 this._oAnchorRecord = oTargetRecord;
10294
10295                 // Select target row only
10296                 this.selectRow(oTargetRecord);
10297             }
10298         }
10299         // Only CTRL
10300         else if(bCTRL) {
10301             // Set anchor
10302             this._oAnchorRecord = oTargetRecord;
10303
10304             // Toggle selection of target
10305             if(this.isSelected(oTargetRecord)) {
10306                 this.unselectRow(oTargetRecord);
10307             }
10308             else {
10309                 this.selectRow(oTargetRecord);
10310             }
10311         }
10312         // Neither SHIFT nor CTRL
10313         else {
10314             this._handleSingleSelectionByMouse(oArgs);
10315             return;
10316         }
10317     }
10318 },
10319
10320 /**
10321  * Determines selection behavior resulting from a key event when selection mode
10322  * is set to "standard".
10323  *
10324  * @method _handleStandardSelectionByKey
10325  * @param e {HTMLEvent} Event object.
10326  * @private
10327  */
10328 _handleStandardSelectionByKey : function(e) {
10329     var nKey = Ev.getCharCode(e);
10330
10331     if((nKey == 38) || (nKey == 40)) {
10332         var bSHIFT = e.shiftKey;
10333
10334         // Validate trigger
10335         var oTrigger = this._getSelectionTrigger();
10336         // Arrow selection only works if last selected row is on current page
10337         if(!oTrigger) {
10338             return null;
10339         }
10340
10341         Ev.stopEvent(e);
10342
10343         // Validate anchor
10344         var oAnchor = this._getSelectionAnchor(oTrigger);
10345
10346         // Determine which direction we're going to
10347         if(bSHIFT) {
10348             // Selecting down away from anchor row
10349             if((nKey == 40) && (oAnchor.recordIndex <= oTrigger.trIndex)) {
10350                 this.selectRow(this.getNextTrEl(oTrigger.el));
10351             }
10352             // Selecting up away from anchor row
10353             else if((nKey == 38) && (oAnchor.recordIndex >= oTrigger.trIndex)) {
10354                 this.selectRow(this.getPreviousTrEl(oTrigger.el));
10355             }
10356             // Unselect trigger
10357             else {
10358                 this.unselectRow(oTrigger.el);
10359             }
10360         }
10361         else {
10362             this._handleSingleSelectionByKey(e);
10363         }
10364     }
10365 },
10366
10367 /**
10368  * Determines selection behavior resulting from a mouse event when selection mode
10369  * is set to "single".
10370  *
10371  * @method _handleSingleSelectionByMouse
10372  * @param oArgs.event {HTMLEvent} Event object.
10373  * @param oArgs.target {HTMLElement} Target element.
10374  * @private
10375  */
10376 _handleSingleSelectionByMouse : function(oArgs) {
10377     var elTarget = oArgs.target;
10378
10379     // Validate target row
10380     var elTargetRow = this.getTrEl(elTarget);
10381     if(elTargetRow) {
10382         var oTargetRecord = this.getRecord(elTargetRow);
10383
10384         // Set anchor
10385         this._oAnchorRecord = oTargetRecord;
10386
10387         // Select only target
10388         this.unselectAllRows();
10389         this.selectRow(oTargetRecord);
10390     }
10391 },
10392
10393 /**
10394  * Determines selection behavior resulting from a key event when selection mode
10395  * is set to "single".
10396  *
10397  * @method _handleSingleSelectionByKey
10398  * @param e {HTMLEvent} Event object.
10399  * @private
10400  */
10401 _handleSingleSelectionByKey : function(e) {
10402     var nKey = Ev.getCharCode(e);
10403
10404     if((nKey == 38) || (nKey == 40)) {
10405         // Validate trigger
10406         var oTrigger = this._getSelectionTrigger();
10407         // Arrow selection only works if last selected row is on current page
10408         if(!oTrigger) {
10409             return null;
10410         }
10411
10412         Ev.stopEvent(e);
10413
10414         // Determine the new row to select
10415         var elNew;
10416         if(nKey == 38) { // arrow up
10417             elNew = this.getPreviousTrEl(oTrigger.el);
10418
10419             // Validate new row
10420             if(elNew === null) {
10421                 //TODO: wrap around to last tr on current page
10422                 //elNew = this.getLastTrEl();
10423
10424                 //TODO: wrap back to last tr of previous page
10425
10426                 // Top row selection is sticky
10427                 elNew = this.getFirstTrEl();
10428             }
10429         }
10430         else if(nKey == 40) { // arrow down
10431             elNew = this.getNextTrEl(oTrigger.el);
10432
10433             // Validate new row
10434             if(elNew === null) {
10435                 //TODO: wrap around to first tr on current page
10436                 //elNew = this.getFirstTrEl();
10437
10438                 //TODO: wrap forward to first tr of previous page
10439
10440                 // Bottom row selection is sticky
10441                 elNew = this.getLastTrEl();
10442             }
10443         }
10444
10445         // Unselect all rows
10446         this.unselectAllRows();
10447
10448         // Select the new row
10449         this.selectRow(elNew);
10450
10451         // Set new anchor
10452         this._oAnchorRecord = this.getRecord(elNew);
10453     }
10454 },
10455
10456 /**
10457  * Determines selection behavior resulting from a mouse event when selection mode
10458  * is set to "cellblock".
10459  *
10460  * @method _handleCellBlockSelectionByMouse
10461  * @param oArgs.event {HTMLEvent} Event object.
10462  * @param oArgs.target {HTMLElement} Target element.
10463  * @private
10464  */
10465 _handleCellBlockSelectionByMouse : function(oArgs) {
10466     var elTarget = oArgs.target;
10467
10468     // Validate target cell
10469     var elTargetCell = this.getTdEl(elTarget);
10470     if(elTargetCell) {
10471         var e = oArgs.event;
10472         var bSHIFT = e.shiftKey;
10473         var bCTRL = e.ctrlKey || ((navigator.userAgent.toLowerCase().indexOf("mac") != -1) && e.metaKey);
10474
10475         var elTargetRow = this.getTrEl(elTargetCell);
10476         var nTargetTrIndex = this.getTrIndex(elTargetRow);
10477         var oTargetColumn = this.getColumn(elTargetCell);
10478         var nTargetColKeyIndex = oTargetColumn.getKeyIndex();
10479         var oTargetRecord = this.getRecord(elTargetRow);
10480         var nTargetRecordIndex = this._oRecordSet.getRecordIndex(oTargetRecord);
10481         var oTargetCell = {record:oTargetRecord, column:oTargetColumn};
10482
10483         var oAnchor = this._getSelectionAnchor();
10484
10485         var allRows = this.getTbodyEl().rows;
10486         var startIndex, endIndex, currentRow, i, j;
10487
10488         // Both SHIFT and CTRL
10489         if(bSHIFT && bCTRL) {
10490
10491             // Validate anchor
10492             if(oAnchor) {
10493                 // Anchor is selected
10494                 if(this.isSelected(oAnchor.cell)) {
10495                     // All cells are on the same row
10496                     if(oAnchor.recordIndex === nTargetRecordIndex) {
10497                         // Select all cells between anchor cell and target cell, including target cell
10498                         if(oAnchor.colKeyIndex < nTargetColKeyIndex) {
10499                             for(i=oAnchor.colKeyIndex+1; i<=nTargetColKeyIndex; i++) {
10500                                 this.selectCell(elTargetRow.cells[i]);
10501                             }
10502                         }
10503                         // Select all cells between target cell and anchor cell, including target cell
10504                         else if(nTargetColKeyIndex < oAnchor.colKeyIndex) {
10505                             for(i=nTargetColKeyIndex; i<oAnchor.colKeyIndex; i++) {
10506                                 this.selectCell(elTargetRow.cells[i]);
10507                             }
10508                         }
10509                     }
10510                     // Anchor row is above target row
10511                     else if(oAnchor.recordIndex < nTargetRecordIndex) {
10512                         startIndex = Math.min(oAnchor.colKeyIndex, nTargetColKeyIndex);
10513                         endIndex = Math.max(oAnchor.colKeyIndex, nTargetColKeyIndex);
10514
10515                         // Select all cells from startIndex to endIndex on rows between anchor row and target row
10516                         for(i=oAnchor.trIndex; i<=nTargetTrIndex; i++) {
10517                             for(j=startIndex; j<=endIndex; j++) {
10518                                 this.selectCell(allRows[i].cells[j]);
10519                             }
10520                         }
10521                     }
10522                     // Anchor row is below target row
10523                     else {
10524                         startIndex = Math.min(oAnchor.trIndex, nTargetColKeyIndex);
10525                         endIndex = Math.max(oAnchor.trIndex, nTargetColKeyIndex);
10526
10527                         // Select all cells from startIndex to endIndex on rows between target row and anchor row
10528                         for(i=oAnchor.trIndex; i>=nTargetTrIndex; i--) {
10529                             for(j=endIndex; j>=startIndex; j--) {
10530                                 this.selectCell(allRows[i].cells[j]);
10531                             }
10532                         }
10533                     }
10534                 }
10535                 // Anchor cell is unselected
10536                 else {
10537                     // All cells are on the same row
10538                     if(oAnchor.recordIndex === nTargetRecordIndex) {
10539                         // Unselect all cells between anchor cell and target cell
10540                         if(oAnchor.colKeyIndex < nTargetColKeyIndex) {
10541                             for(i=oAnchor.colKeyIndex+1; i<nTargetColKeyIndex; i++) {
10542                                 this.unselectCell(elTargetRow.cells[i]);
10543                             }
10544                         }
10545                         // Select all cells between target cell and anchor cell
10546                         else if(nTargetColKeyIndex < oAnchor.colKeyIndex) {
10547                             for(i=nTargetColKeyIndex+1; i<oAnchor.colKeyIndex; i++) {
10548                                 this.unselectCell(elTargetRow.cells[i]);
10549                             }
10550                         }
10551                     }
10552                     // Anchor row is above target row
10553                     if(oAnchor.recordIndex < nTargetRecordIndex) {
10554                         // Unselect all cells from anchor cell to target cell
10555                         for(i=oAnchor.trIndex; i<=nTargetTrIndex; i++) {
10556                             currentRow = allRows[i];
10557                             for(j=0; j<currentRow.cells.length; j++) {
10558                                 // This is the anchor row, only unselect cells after the anchor cell
10559                                 if(currentRow.sectionRowIndex === oAnchor.trIndex) {
10560                                     if(j>oAnchor.colKeyIndex) {
10561                                         this.unselectCell(currentRow.cells[j]);
10562                                     }
10563                                 }
10564                                 // This is the target row, only unelect cells before the target cell
10565                                 else if(currentRow.sectionRowIndex === nTargetTrIndex) {
10566                                     if(j<nTargetColKeyIndex) {
10567                                         this.unselectCell(currentRow.cells[j]);
10568                                     }
10569                                 }
10570                                 // Unselect all cells on this row
10571                                 else {
10572                                     this.unselectCell(currentRow.cells[j]);
10573                                 }
10574                             }
10575                         }
10576                     }
10577                     // Anchor row is below target row
10578                     else {
10579                         // Unselect all cells from target cell to anchor cell
10580                         for(i=nTargetTrIndex; i<=oAnchor.trIndex; i++) {
10581                             currentRow = allRows[i];
10582                             for(j=0; j<currentRow.cells.length; j++) {
10583                                 // This is the target row, only unselect cells after the target cell
10584                                 if(currentRow.sectionRowIndex == nTargetTrIndex) {
10585                                     if(j>nTargetColKeyIndex) {
10586                                         this.unselectCell(currentRow.cells[j]);
10587                                     }
10588                                 }
10589                                 // This is the anchor row, only unselect cells before the anchor cell
10590                                 else if(currentRow.sectionRowIndex == oAnchor.trIndex) {
10591                                     if(j<oAnchor.colKeyIndex) {
10592                                         this.unselectCell(currentRow.cells[j]);
10593                                     }
10594                                 }
10595                                 // Unselect all cells on this row
10596                                 else {
10597                                     this.unselectCell(currentRow.cells[j]);
10598                                 }
10599                             }
10600                         }
10601                     }
10602
10603                     // Select the target cell
10604                     this.selectCell(elTargetCell);
10605                 }
10606             }
10607             // Invalid anchor
10608             else {
10609                 // Set anchor
10610                 this._oAnchorCell = oTargetCell;
10611
10612                 // Toggle selection of target
10613                 if(this.isSelected(oTargetCell)) {
10614                     this.unselectCell(oTargetCell);
10615                 }
10616                 else {
10617                     this.selectCell(oTargetCell);
10618                 }
10619             }
10620
10621         }
10622          // Only SHIFT
10623         else if(bSHIFT) {
10624             this.unselectAllCells();
10625
10626             // Validate anchor
10627             if(oAnchor) {
10628                 // All cells are on the same row
10629                 if(oAnchor.recordIndex === nTargetRecordIndex) {
10630                     // Select all cells between anchor cell and target cell,
10631                     // including the anchor cell and target cell
10632                     if(oAnchor.colKeyIndex < nTargetColKeyIndex) {
10633                         for(i=oAnchor.colKeyIndex; i<=nTargetColKeyIndex; i++) {
10634                             this.selectCell(elTargetRow.cells[i]);
10635                         }
10636                     }
10637                     // Select all cells between target cell and anchor cell
10638                     // including the target cell and anchor cell
10639                     else if(nTargetColKeyIndex < oAnchor.colKeyIndex) {
10640                         for(i=nTargetColKeyIndex; i<=oAnchor.colKeyIndex; i++) {
10641                             this.selectCell(elTargetRow.cells[i]);
10642                         }
10643                     }
10644                 }
10645                 // Anchor row is above target row
10646                 else if(oAnchor.recordIndex < nTargetRecordIndex) {
10647                     // Select the cellblock from anchor cell to target cell
10648                     // including the anchor cell and the target cell
10649                     startIndex = Math.min(oAnchor.colKeyIndex, nTargetColKeyIndex);
10650                     endIndex = Math.max(oAnchor.colKeyIndex, nTargetColKeyIndex);
10651
10652                     for(i=oAnchor.trIndex; i<=nTargetTrIndex; i++) {
10653                         for(j=startIndex; j<=endIndex; j++) {
10654                             this.selectCell(allRows[i].cells[j]);
10655                         }
10656                     }
10657                 }
10658                 // Anchor row is below target row
10659                 else {
10660                     // Select the cellblock from target cell to anchor cell
10661                     // including the target cell and the anchor cell
10662                     startIndex = Math.min(oAnchor.colKeyIndex, nTargetColKeyIndex);
10663                     endIndex = Math.max(oAnchor.colKeyIndex, nTargetColKeyIndex);
10664
10665                     for(i=nTargetTrIndex; i<=oAnchor.trIndex; i++) {
10666                         for(j=startIndex; j<=endIndex; j++) {
10667                             this.selectCell(allRows[i].cells[j]);
10668                         }
10669                     }
10670                 }
10671             }
10672             // Invalid anchor
10673             else {
10674                 // Set anchor
10675                 this._oAnchorCell = oTargetCell;
10676
10677                 // Select target only
10678                 this.selectCell(oTargetCell);
10679             }
10680         }
10681         // Only CTRL
10682         else if(bCTRL) {
10683
10684             // Set anchor
10685             this._oAnchorCell = oTargetCell;
10686
10687             // Toggle selection of target
10688             if(this.isSelected(oTargetCell)) {
10689                 this.unselectCell(oTargetCell);
10690             }
10691             else {
10692                 this.selectCell(oTargetCell);
10693             }
10694
10695         }
10696         // Neither SHIFT nor CTRL
10697         else {
10698             this._handleSingleCellSelectionByMouse(oArgs);
10699         }
10700     }
10701 },
10702
10703 /**
10704  * Determines selection behavior resulting from a key event when selection mode
10705  * is set to "cellblock".
10706  *
10707  * @method _handleCellBlockSelectionByKey
10708  * @param e {HTMLEvent} Event object.
10709  * @private
10710  */
10711 _handleCellBlockSelectionByKey : function(e) {
10712     var nKey = Ev.getCharCode(e);
10713     var bSHIFT = e.shiftKey;
10714     if((nKey == 9) || !bSHIFT) {
10715         this._handleSingleCellSelectionByKey(e);
10716         return;
10717     }
10718
10719     if((nKey > 36) && (nKey < 41)) {
10720         // Validate trigger
10721         var oTrigger = this._getSelectionTrigger();
10722         // Arrow selection only works if last selected row is on current page
10723         if(!oTrigger) {
10724             return null;
10725         }
10726
10727         Ev.stopEvent(e);
10728
10729         // Validate anchor
10730         var oAnchor = this._getSelectionAnchor(oTrigger);
10731
10732         var i, startIndex, endIndex, elNew, elNewRow;
10733         var allRows = this.getTbodyEl().rows;
10734         var elThisRow = oTrigger.el.parentNode;
10735
10736         // Determine which direction we're going to
10737
10738         if(nKey == 40) { // arrow down
10739             // Selecting away from anchor cell
10740             if(oAnchor.recordIndex <= oTrigger.recordIndex) {
10741                 // Select the horiz block on the next row...
10742                 // ...making sure there is room below the trigger row
10743                 elNewRow = this.getNextTrEl(oTrigger.el);
10744                 if(elNewRow) {
10745                     startIndex = oAnchor.colKeyIndex;
10746                     endIndex = oTrigger.colKeyIndex;
10747                     // ...going left
10748                     if(startIndex > endIndex) {
10749                         for(i=startIndex; i>=endIndex; i--) {
10750                             elNew = elNewRow.cells[i];
10751                             this.selectCell(elNew);
10752                         }
10753                     }
10754                     // ... going right
10755                     else {
10756                         for(i=startIndex; i<=endIndex; i++) {
10757                             elNew = elNewRow.cells[i];
10758                             this.selectCell(elNew);
10759                         }
10760                     }
10761                 }
10762             }
10763             // Unselecting towards anchor cell
10764             else {
10765                 startIndex = Math.min(oAnchor.colKeyIndex, oTrigger.colKeyIndex);
10766                 endIndex = Math.max(oAnchor.colKeyIndex, oTrigger.colKeyIndex);
10767                 // Unselect the horiz block on this row towards the next row
10768                 for(i=startIndex; i<=endIndex; i++) {
10769                     this.unselectCell(elThisRow.cells[i]);
10770                 }
10771             }
10772         }
10773         // Arrow up
10774         else if(nKey == 38) {
10775             // Selecting away from anchor cell
10776             if(oAnchor.recordIndex >= oTrigger.recordIndex) {
10777                 // Select the horiz block on the previous row...
10778                 // ...making sure there is room
10779                 elNewRow = this.getPreviousTrEl(oTrigger.el);
10780                 if(elNewRow) {
10781                     // Select in order from anchor to trigger...
10782                     startIndex = oAnchor.colKeyIndex;
10783                     endIndex = oTrigger.colKeyIndex;
10784                     // ...going left
10785                     if(startIndex > endIndex) {
10786                         for(i=startIndex; i>=endIndex; i--) {
10787                             elNew = elNewRow.cells[i];
10788                             this.selectCell(elNew);
10789                         }
10790                     }
10791                     // ... going right
10792                     else {
10793                         for(i=startIndex; i<=endIndex; i++) {
10794                             elNew = elNewRow.cells[i];
10795                             this.selectCell(elNew);
10796                         }
10797                     }
10798                 }
10799             }
10800             // Unselecting towards anchor cell
10801             else {
10802                 startIndex = Math.min(oAnchor.colKeyIndex, oTrigger.colKeyIndex);
10803                 endIndex = Math.max(oAnchor.colKeyIndex, oTrigger.colKeyIndex);
10804                 // Unselect the horiz block on this row towards the previous row
10805                 for(i=startIndex; i<=endIndex; i++) {
10806                     this.unselectCell(elThisRow.cells[i]);
10807                 }
10808             }
10809         }
10810         // Arrow right
10811         else if(nKey == 39) {
10812             // Selecting away from anchor cell
10813             if(oAnchor.colKeyIndex <= oTrigger.colKeyIndex) {
10814                 // Select the next vert block to the right...
10815                 // ...making sure there is room
10816                 if(oTrigger.colKeyIndex < elThisRow.cells.length-1) {
10817                     // Select in order from anchor to trigger...
10818                     startIndex = oAnchor.trIndex;
10819                     endIndex = oTrigger.trIndex;
10820                     // ...going up
10821                     if(startIndex > endIndex) {
10822                         for(i=startIndex; i>=endIndex; i--) {
10823                             elNew = allRows[i].cells[oTrigger.colKeyIndex+1];
10824                             this.selectCell(elNew);
10825                         }
10826                     }
10827                     // ... going down
10828                     else {
10829                         for(i=startIndex; i<=endIndex; i++) {
10830                             elNew = allRows[i].cells[oTrigger.colKeyIndex+1];
10831                             this.selectCell(elNew);
10832                         }
10833                     }
10834                 }
10835             }
10836             // Unselecting towards anchor cell
10837             else {
10838                 // Unselect the vert block on this column towards the right
10839                 startIndex = Math.min(oAnchor.trIndex, oTrigger.trIndex);
10840                 endIndex = Math.max(oAnchor.trIndex, oTrigger.trIndex);
10841                 for(i=startIndex; i<=endIndex; i++) {
10842                     this.unselectCell(allRows[i].cells[oTrigger.colKeyIndex]);
10843                 }
10844             }
10845         }
10846         // Arrow left
10847         else if(nKey == 37) {
10848             // Selecting away from anchor cell
10849             if(oAnchor.colKeyIndex >= oTrigger.colKeyIndex) {
10850                 //Select the previous vert block to the left
10851                 if(oTrigger.colKeyIndex > 0) {
10852                     // Select in order from anchor to trigger...
10853                     startIndex = oAnchor.trIndex;
10854                     endIndex = oTrigger.trIndex;
10855                     // ...going up
10856                     if(startIndex > endIndex) {
10857                         for(i=startIndex; i>=endIndex; i--) {
10858                             elNew = allRows[i].cells[oTrigger.colKeyIndex-1];
10859                             this.selectCell(elNew);
10860                         }
10861                     }
10862                     // ... going down
10863                     else {
10864                         for(i=startIndex; i<=endIndex; i++) {
10865                             elNew = allRows[i].cells[oTrigger.colKeyIndex-1];
10866                             this.selectCell(elNew);
10867                         }
10868                     }
10869                 }
10870             }
10871             // Unselecting towards anchor cell
10872             else {
10873                 // Unselect the vert block on this column towards the left
10874                 startIndex = Math.min(oAnchor.trIndex, oTrigger.trIndex);
10875                 endIndex = Math.max(oAnchor.trIndex, oTrigger.trIndex);
10876                 for(i=startIndex; i<=endIndex; i++) {
10877                     this.unselectCell(allRows[i].cells[oTrigger.colKeyIndex]);
10878                 }
10879             }
10880         }
10881     }
10882 },
10883
10884 /**
10885  * Determines selection behavior resulting from a mouse event when selection mode
10886  * is set to "cellrange".
10887  *
10888  * @method _handleCellRangeSelectionByMouse
10889  * @param oArgs.event {HTMLEvent} Event object.
10890  * @param oArgs.target {HTMLElement} Target element.
10891  * @private
10892  */
10893 _handleCellRangeSelectionByMouse : function(oArgs) {
10894     var elTarget = oArgs.target;
10895
10896     // Validate target cell
10897     var elTargetCell = this.getTdEl(elTarget);
10898     if(elTargetCell) {
10899         var e = oArgs.event;
10900         var bSHIFT = e.shiftKey;
10901         var bCTRL = e.ctrlKey || ((navigator.userAgent.toLowerCase().indexOf("mac") != -1) && e.metaKey);
10902
10903         var elTargetRow = this.getTrEl(elTargetCell);
10904         var nTargetTrIndex = this.getTrIndex(elTargetRow);
10905         var oTargetColumn = this.getColumn(elTargetCell);
10906         var nTargetColKeyIndex = oTargetColumn.getKeyIndex();
10907         var oTargetRecord = this.getRecord(elTargetRow);
10908         var nTargetRecordIndex = this._oRecordSet.getRecordIndex(oTargetRecord);
10909         var oTargetCell = {record:oTargetRecord, column:oTargetColumn};
10910
10911         var oAnchor = this._getSelectionAnchor();
10912
10913         var allRows = this.getTbodyEl().rows;
10914         var currentRow, i, j;
10915
10916         // Both SHIFT and CTRL
10917         if(bSHIFT && bCTRL) {
10918
10919             // Validate anchor
10920             if(oAnchor) {
10921                 // Anchor is selected
10922                 if(this.isSelected(oAnchor.cell)) {
10923                     // All cells are on the same row
10924                     if(oAnchor.recordIndex === nTargetRecordIndex) {
10925                         // Select all cells between anchor cell and target cell, including target cell
10926                         if(oAnchor.colKeyIndex < nTargetColKeyIndex) {
10927                             for(i=oAnchor.colKeyIndex+1; i<=nTargetColKeyIndex; i++) {
10928                                 this.selectCell(elTargetRow.cells[i]);
10929                             }
10930                         }
10931                         // Select all cells between target cell and anchor cell, including target cell
10932                         else if(nTargetColKeyIndex < oAnchor.colKeyIndex) {
10933                             for(i=nTargetColKeyIndex; i<oAnchor.colKeyIndex; i++) {
10934                                 this.selectCell(elTargetRow.cells[i]);
10935                             }
10936                         }
10937                     }
10938                     // Anchor row is above target row
10939                     else if(oAnchor.recordIndex < nTargetRecordIndex) {
10940                         // Select all cells on anchor row from anchor cell to the end of the row
10941                         for(i=oAnchor.colKeyIndex+1; i<elTargetRow.cells.length; i++) {
10942                             this.selectCell(elTargetRow.cells[i]);
10943                         }
10944
10945                         // Select all cells on all rows between anchor row and target row
10946                         for(i=oAnchor.trIndex+1; i<nTargetTrIndex; i++) {
10947                             for(j=0; j<allRows[i].cells.length; j++){
10948                                 this.selectCell(allRows[i].cells[j]);
10949                             }
10950                         }
10951
10952                         // Select all cells on target row from first cell to the target cell
10953                         for(i=0; i<=nTargetColKeyIndex; i++) {
10954                             this.selectCell(elTargetRow.cells[i]);
10955                         }
10956                     }
10957                     // Anchor row is below target row
10958                     else {
10959                         // Select all cells on target row from target cell to the end of the row
10960                         for(i=nTargetColKeyIndex; i<elTargetRow.cells.length; i++) {
10961                             this.selectCell(elTargetRow.cells[i]);
10962                         }
10963
10964                         // Select all cells on all rows between target row and anchor row
10965                         for(i=nTargetTrIndex+1; i<oAnchor.trIndex; i++) {
10966                             for(j=0; j<allRows[i].cells.length; j++){
10967                                 this.selectCell(allRows[i].cells[j]);
10968                             }
10969                         }
10970
10971                         // Select all cells on anchor row from first cell to the anchor cell
10972                         for(i=0; i<oAnchor.colKeyIndex; i++) {
10973                             this.selectCell(elTargetRow.cells[i]);
10974                         }
10975                     }
10976                 }
10977                 // Anchor cell is unselected
10978                 else {
10979                     // All cells are on the same row
10980                     if(oAnchor.recordIndex === nTargetRecordIndex) {
10981                         // Unselect all cells between anchor cell and target cell
10982                         if(oAnchor.colKeyIndex < nTargetColKeyIndex) {
10983                             for(i=oAnchor.colKeyIndex+1; i<nTargetColKeyIndex; i++) {
10984                                 this.unselectCell(elTargetRow.cells[i]);
10985                             }
10986                         }
10987                         // Select all cells between target cell and anchor cell
10988                         else if(nTargetColKeyIndex < oAnchor.colKeyIndex) {
10989                             for(i=nTargetColKeyIndex+1; i<oAnchor.colKeyIndex; i++) {
10990                                 this.unselectCell(elTargetRow.cells[i]);
10991                             }
10992                         }
10993                     }
10994                     // Anchor row is above target row
10995                     if(oAnchor.recordIndex < nTargetRecordIndex) {
10996                         // Unselect all cells from anchor cell to target cell
10997                         for(i=oAnchor.trIndex; i<=nTargetTrIndex; i++) {
10998                             currentRow = allRows[i];
10999                             for(j=0; j<currentRow.cells.length; j++) {
11000                                 // This is the anchor row, only unselect cells after the anchor cell
11001                                 if(currentRow.sectionRowIndex === oAnchor.trIndex) {
11002                                     if(j>oAnchor.colKeyIndex) {
11003                                         this.unselectCell(currentRow.cells[j]);
11004                                     }
11005                                 }
11006                                 // This is the target row, only unelect cells before the target cell
11007                                 else if(currentRow.sectionRowIndex === nTargetTrIndex) {
11008                                     if(j<nTargetColKeyIndex) {
11009                                         this.unselectCell(currentRow.cells[j]);
11010                                     }
11011                                 }
11012                                 // Unselect all cells on this row
11013                                 else {
11014                                     this.unselectCell(currentRow.cells[j]);
11015                                 }
11016                             }
11017                         }
11018                     }
11019                     // Anchor row is below target row
11020                     else {
11021                         // Unselect all cells from target cell to anchor cell
11022                         for(i=nTargetTrIndex; i<=oAnchor.trIndex; i++) {
11023                             currentRow = allRows[i];
11024                             for(j=0; j<currentRow.cells.length; j++) {
11025                                 // This is the target row, only unselect cells after the target cell
11026                                 if(currentRow.sectionRowIndex == nTargetTrIndex) {
11027                                     if(j>nTargetColKeyIndex) {
11028                                         this.unselectCell(currentRow.cells[j]);
11029                                     }
11030                                 }
11031                                 // This is the anchor row, only unselect cells before the anchor cell
11032                                 else if(currentRow.sectionRowIndex == oAnchor.trIndex) {
11033                                     if(j<oAnchor.colKeyIndex) {
11034                                         this.unselectCell(currentRow.cells[j]);
11035                                     }
11036                                 }
11037                                 // Unselect all cells on this row
11038                                 else {
11039                                     this.unselectCell(currentRow.cells[j]);
11040                                 }
11041                             }
11042                         }
11043                     }
11044
11045                     // Select the target cell
11046                     this.selectCell(elTargetCell);
11047                 }
11048             }
11049             // Invalid anchor
11050             else {
11051                 // Set anchor
11052                 this._oAnchorCell = oTargetCell;
11053
11054                 // Toggle selection of target
11055                 if(this.isSelected(oTargetCell)) {
11056                     this.unselectCell(oTargetCell);
11057                 }
11058                 else {
11059                     this.selectCell(oTargetCell);
11060                 }
11061             }
11062         }
11063          // Only SHIFT
11064         else if(bSHIFT) {
11065
11066             this.unselectAllCells();
11067
11068             // Validate anchor
11069             if(oAnchor) {
11070                 // All cells are on the same row
11071                 if(oAnchor.recordIndex === nTargetRecordIndex) {
11072                     // Select all cells between anchor cell and target cell,
11073                     // including the anchor cell and target cell
11074                     if(oAnchor.colKeyIndex < nTargetColKeyIndex) {
11075                         for(i=oAnchor.colKeyIndex; i<=nTargetColKeyIndex; i++) {
11076                             this.selectCell(elTargetRow.cells[i]);
11077                         }
11078                     }
11079                     // Select all cells between target cell and anchor cell
11080                     // including the target cell and anchor cell
11081                     else if(nTargetColKeyIndex < oAnchor.colKeyIndex) {
11082                         for(i=nTargetColKeyIndex; i<=oAnchor.colKeyIndex; i++) {
11083                             this.selectCell(elTargetRow.cells[i]);
11084                         }
11085                     }
11086                 }
11087                 // Anchor row is above target row
11088                 else if(oAnchor.recordIndex < nTargetRecordIndex) {
11089                     // Select all cells from anchor cell to target cell
11090                     // including the anchor cell and target cell
11091                     for(i=oAnchor.trIndex; i<=nTargetTrIndex; i++) {
11092                         currentRow = allRows[i];
11093                         for(j=0; j<currentRow.cells.length; j++) {
11094                             // This is the anchor row, only select the anchor cell and after
11095                             if(currentRow.sectionRowIndex == oAnchor.trIndex) {
11096                                 if(j>=oAnchor.colKeyIndex) {
11097                                     this.selectCell(currentRow.cells[j]);
11098                                 }
11099                             }
11100                             // This is the target row, only select the target cell and before
11101                             else if(currentRow.sectionRowIndex == nTargetTrIndex) {
11102                                 if(j<=nTargetColKeyIndex) {
11103                                     this.selectCell(currentRow.cells[j]);
11104                                 }
11105                             }
11106                             // Select all cells on this row
11107                             else {
11108                                 this.selectCell(currentRow.cells[j]);
11109                             }
11110                         }
11111                     }
11112                 }
11113                 // Anchor row is below target row
11114                 else {
11115                     // Select all cells from target cell to anchor cell,
11116                     // including the target cell and anchor cell
11117                     for(i=nTargetTrIndex; i<=oAnchor.trIndex; i++) {
11118                         currentRow = allRows[i];
11119                         for(j=0; j<currentRow.cells.length; j++) {
11120                             // This is the target row, only select the target cell and after
11121                             if(currentRow.sectionRowIndex == nTargetTrIndex) {
11122                                 if(j>=nTargetColKeyIndex) {
11123                                     this.selectCell(currentRow.cells[j]);
11124                                 }
11125                             }
11126                             // This is the anchor row, only select the anchor cell and before
11127                             else if(currentRow.sectionRowIndex == oAnchor.trIndex) {
11128                                 if(j<=oAnchor.colKeyIndex) {
11129                                     this.selectCell(currentRow.cells[j]);
11130                                 }
11131                             }
11132                             // Select all cells on this row
11133                             else {
11134                                 this.selectCell(currentRow.cells[j]);
11135                             }
11136                         }
11137                     }
11138                 }
11139             }
11140             // Invalid anchor
11141             else {
11142                 // Set anchor
11143                 this._oAnchorCell = oTargetCell;
11144
11145                 // Select target only
11146                 this.selectCell(oTargetCell);
11147             }
11148
11149
11150         }
11151         // Only CTRL
11152         else if(bCTRL) {
11153
11154             // Set anchor
11155             this._oAnchorCell = oTargetCell;
11156
11157             // Toggle selection of target
11158             if(this.isSelected(oTargetCell)) {
11159                 this.unselectCell(oTargetCell);
11160             }
11161             else {
11162                 this.selectCell(oTargetCell);
11163             }
11164
11165         }
11166         // Neither SHIFT nor CTRL
11167         else {
11168             this._handleSingleCellSelectionByMouse(oArgs);
11169         }
11170     }
11171 },
11172
11173 /**
11174  * Determines selection behavior resulting from a key event when selection mode
11175  * is set to "cellrange".
11176  *
11177  * @method _handleCellRangeSelectionByKey
11178  * @param e {HTMLEvent} Event object.
11179  * @private
11180  */
11181 _handleCellRangeSelectionByKey : function(e) {
11182     var nKey = Ev.getCharCode(e);
11183     var bSHIFT = e.shiftKey;
11184     if((nKey == 9) || !bSHIFT) {
11185         this._handleSingleCellSelectionByKey(e);
11186         return;
11187     }
11188
11189     if((nKey > 36) && (nKey < 41)) {
11190         // Validate trigger
11191         var oTrigger = this._getSelectionTrigger();
11192         // Arrow selection only works if last selected row is on current page
11193         if(!oTrigger) {
11194             return null;
11195         }
11196
11197         Ev.stopEvent(e);
11198
11199         // Validate anchor
11200         var oAnchor = this._getSelectionAnchor(oTrigger);
11201
11202         var i, elNewRow, elNew;
11203         var allRows = this.getTbodyEl().rows;
11204         var elThisRow = oTrigger.el.parentNode;
11205
11206         // Arrow down
11207         if(nKey == 40) {
11208             elNewRow = this.getNextTrEl(oTrigger.el);
11209
11210             // Selecting away from anchor cell
11211             if(oAnchor.recordIndex <= oTrigger.recordIndex) {
11212                 // Select all cells to the end of this row
11213                 for(i=oTrigger.colKeyIndex+1; i<elThisRow.cells.length; i++){
11214                     elNew = elThisRow.cells[i];
11215                     this.selectCell(elNew);
11216                 }
11217
11218                 // Select some of the cells on the next row down
11219                 if(elNewRow) {
11220                     for(i=0; i<=oTrigger.colKeyIndex; i++){
11221                         elNew = elNewRow.cells[i];
11222                         this.selectCell(elNew);
11223                     }
11224                 }
11225             }
11226             // Unselecting towards anchor cell
11227             else {
11228                 // Unselect all cells to the end of this row
11229                 for(i=oTrigger.colKeyIndex; i<elThisRow.cells.length; i++){
11230                     this.unselectCell(elThisRow.cells[i]);
11231                 }
11232
11233                 // Unselect some of the cells on the next row down
11234                 if(elNewRow) {
11235                     for(i=0; i<oTrigger.colKeyIndex; i++){
11236                         this.unselectCell(elNewRow.cells[i]);
11237                     }
11238                 }
11239             }
11240         }
11241         // Arrow up
11242         else if(nKey == 38) {
11243             elNewRow = this.getPreviousTrEl(oTrigger.el);
11244
11245             // Selecting away from anchor cell
11246             if(oAnchor.recordIndex >= oTrigger.recordIndex) {
11247                 // Select all the cells to the beginning of this row
11248                 for(i=oTrigger.colKeyIndex-1; i>-1; i--){
11249                     elNew = elThisRow.cells[i];
11250                     this.selectCell(elNew);
11251                 }
11252
11253                 // Select some of the cells from the end of the previous row
11254                 if(elNewRow) {
11255                     for(i=elThisRow.cells.length-1; i>=oTrigger.colKeyIndex; i--){
11256                         elNew = elNewRow.cells[i];
11257                         this.selectCell(elNew);
11258                     }
11259                 }
11260             }
11261             // Unselecting towards anchor cell
11262             else {
11263                 // Unselect all the cells to the beginning of this row
11264                 for(i=oTrigger.colKeyIndex; i>-1; i--){
11265                     this.unselectCell(elThisRow.cells[i]);
11266                 }
11267
11268                 // Unselect some of the cells from the end of the previous row
11269                 if(elNewRow) {
11270                     for(i=elThisRow.cells.length-1; i>oTrigger.colKeyIndex; i--){
11271                         this.unselectCell(elNewRow.cells[i]);
11272                     }
11273                 }
11274             }
11275         }
11276         // Arrow right
11277         else if(nKey == 39) {
11278             elNewRow = this.getNextTrEl(oTrigger.el);
11279
11280             // Selecting away from anchor cell
11281             if(oAnchor.recordIndex < oTrigger.recordIndex) {
11282                 // Select the next cell to the right
11283                 if(oTrigger.colKeyIndex < elThisRow.cells.length-1) {
11284                     elNew = elThisRow.cells[oTrigger.colKeyIndex+1];
11285                     this.selectCell(elNew);
11286                 }
11287                 // Select the first cell of the next row
11288                 else if(elNewRow) {
11289                     elNew = elNewRow.cells[0];
11290                     this.selectCell(elNew);
11291                 }
11292             }
11293             // Unselecting towards anchor cell
11294             else if(oAnchor.recordIndex > oTrigger.recordIndex) {
11295                 this.unselectCell(elThisRow.cells[oTrigger.colKeyIndex]);
11296
11297                 // Unselect this cell towards the right
11298                 if(oTrigger.colKeyIndex < elThisRow.cells.length-1) {
11299                 }
11300                 // Unselect this cells towards the first cell of the next row
11301                 else {
11302                 }
11303             }
11304             // Anchor is on this row
11305             else {
11306                 // Selecting away from anchor
11307                 if(oAnchor.colKeyIndex <= oTrigger.colKeyIndex) {
11308                     // Select the next cell to the right
11309                     if(oTrigger.colKeyIndex < elThisRow.cells.length-1) {
11310                         elNew = elThisRow.cells[oTrigger.colKeyIndex+1];
11311                         this.selectCell(elNew);
11312                     }
11313                     // Select the first cell on the next row
11314                     else if(oTrigger.trIndex < allRows.length-1){
11315                         elNew = elNewRow.cells[0];
11316                         this.selectCell(elNew);
11317                     }
11318                 }
11319                 // Unselecting towards anchor
11320                 else {
11321                     // Unselect this cell towards the right
11322                     this.unselectCell(elThisRow.cells[oTrigger.colKeyIndex]);
11323                 }
11324             }
11325         }
11326         // Arrow left
11327         else if(nKey == 37) {
11328             elNewRow = this.getPreviousTrEl(oTrigger.el);
11329
11330             // Unselecting towards the anchor
11331             if(oAnchor.recordIndex < oTrigger.recordIndex) {
11332                 this.unselectCell(elThisRow.cells[oTrigger.colKeyIndex]);
11333
11334                 // Unselect this cell towards the left
11335                 if(oTrigger.colKeyIndex > 0) {
11336                 }
11337                 // Unselect this cell towards the last cell of the previous row
11338                 else {
11339                 }
11340             }
11341             // Selecting towards the anchor
11342             else if(oAnchor.recordIndex > oTrigger.recordIndex) {
11343                 // Select the next cell to the left
11344                 if(oTrigger.colKeyIndex > 0) {
11345                     elNew = elThisRow.cells[oTrigger.colKeyIndex-1];
11346                     this.selectCell(elNew);
11347                 }
11348                 // Select the last cell of the previous row
11349                 else if(oTrigger.trIndex > 0){
11350                     elNew = elNewRow.cells[elNewRow.cells.length-1];
11351                     this.selectCell(elNew);
11352                 }
11353             }
11354             // Anchor is on this row
11355             else {
11356                 // Selecting away from anchor cell
11357                 if(oAnchor.colKeyIndex >= oTrigger.colKeyIndex) {
11358                     // Select the next cell to the left
11359                     if(oTrigger.colKeyIndex > 0) {
11360                         elNew = elThisRow.cells[oTrigger.colKeyIndex-1];
11361                         this.selectCell(elNew);
11362                     }
11363                     // Select the last cell of the previous row
11364                     else if(oTrigger.trIndex > 0){
11365                         elNew = elNewRow.cells[elNewRow.cells.length-1];
11366                         this.selectCell(elNew);
11367                     }
11368                 }
11369                 // Unselecting towards anchor cell
11370                 else {
11371                     this.unselectCell(elThisRow.cells[oTrigger.colKeyIndex]);
11372
11373                     // Unselect this cell towards the left
11374                     if(oTrigger.colKeyIndex > 0) {
11375                     }
11376                     // Unselect this cell towards the last cell of the previous row
11377                     else {
11378                     }
11379                 }
11380             }
11381         }
11382     }
11383 },
11384
11385 /**
11386  * Determines selection behavior resulting from a mouse event when selection mode
11387  * is set to "singlecell".
11388  *
11389  * @method _handleSingleCellSelectionByMouse
11390  * @param oArgs.event {HTMLEvent} Event object.
11391  * @param oArgs.target {HTMLElement} Target element.
11392  * @private
11393  */
11394 _handleSingleCellSelectionByMouse : function(oArgs) {
11395     var elTarget = oArgs.target;
11396
11397     // Validate target cell
11398     var elTargetCell = this.getTdEl(elTarget);
11399     if(elTargetCell) {
11400         var elTargetRow = this.getTrEl(elTargetCell);
11401         var oTargetRecord = this.getRecord(elTargetRow);
11402         var oTargetColumn = this.getColumn(elTargetCell);
11403         var oTargetCell = {record:oTargetRecord, column:oTargetColumn};
11404
11405         // Set anchor
11406         this._oAnchorCell = oTargetCell;
11407
11408         // Select only target
11409         this.unselectAllCells();
11410         this.selectCell(oTargetCell);
11411     }
11412 },
11413
11414 /**
11415  * Determines selection behavior resulting from a key event when selection mode
11416  * is set to "singlecell".
11417  *
11418  * @method _handleSingleCellSelectionByKey
11419  * @param e {HTMLEvent} Event object.
11420  * @private
11421  */
11422 _handleSingleCellSelectionByKey : function(e) {
11423     var nKey = Ev.getCharCode(e);
11424     if((nKey == 9) || ((nKey > 36) && (nKey < 41))) {
11425         var bSHIFT = e.shiftKey;
11426
11427         // Validate trigger
11428         var oTrigger = this._getSelectionTrigger();
11429         // Arrow selection only works if last selected row is on current page
11430         if(!oTrigger) {
11431             return null;
11432         }
11433
11434         // Determine the new cell to select
11435         var elNew;
11436         if(nKey == 40) { // Arrow down
11437             elNew = this.getBelowTdEl(oTrigger.el);
11438
11439             // Validate new cell
11440             if(elNew === null) {
11441                 //TODO: wrap around to first tr on current page
11442
11443                 //TODO: wrap forward to first tr of next page
11444
11445                 // Bottom selection is sticky
11446                 elNew = oTrigger.el;
11447             }
11448         }
11449         else if(nKey == 38) { // Arrow up
11450             elNew = this.getAboveTdEl(oTrigger.el);
11451
11452             // Validate new cell
11453             if(elNew === null) {
11454                 //TODO: wrap around to last tr on current page
11455
11456                 //TODO: wrap back to last tr of previous page
11457
11458                 // Top selection is sticky
11459                 elNew = oTrigger.el;
11460             }
11461         }
11462         else if((nKey == 39) || (!bSHIFT && (nKey == 9))) { // Arrow right or tab
11463             elNew = this.getNextTdEl(oTrigger.el);
11464
11465             // Validate new cell
11466             if(elNew === null) {
11467                 //TODO: wrap around to first td on current page
11468
11469                 //TODO: wrap forward to first td of next page
11470
11471                 // Top-left selection is sticky, and release TAB focus
11472                 //elNew = oTrigger.el;
11473                 return;
11474             }
11475         }
11476         else if((nKey == 37) || (bSHIFT && (nKey == 9))) { // Arrow left or shift-tab
11477             elNew = this.getPreviousTdEl(oTrigger.el);
11478
11479             // Validate new cell
11480             if(elNew === null) {
11481                 //TODO: wrap around to last td on current page
11482
11483                 //TODO: wrap back to last td of previous page
11484
11485                 // Bottom-right selection is sticky, and release TAB focus
11486                 //elNew = oTrigger.el;
11487                 return;
11488             }
11489         }
11490
11491         Ev.stopEvent(e);
11492         
11493         // Unselect all cells
11494         this.unselectAllCells();
11495
11496         // Select the new cell
11497         this.selectCell(elNew);
11498
11499         // Set new anchor
11500         this._oAnchorCell = {record:this.getRecord(elNew), column:this.getColumn(elNew)};
11501     }
11502 },
11503
11504 /**
11505  * Returns array of selected TR elements on the page.
11506  *
11507  * @method getSelectedTrEls
11508  * @return {HTMLElement[]} Array of selected TR elements.
11509  */
11510 getSelectedTrEls : function() {
11511     return Dom.getElementsByClassName(DT.CLASS_SELECTED,"tr",this._elTbody);
11512 },
11513
11514 /**
11515  * Sets given row to the selected state.
11516  *
11517  * @method selectRow
11518  * @param row {HTMLElement | String | YAHOO.widget.Record | Number} HTML element
11519  * reference or ID string, Record instance, or RecordSet position index.
11520  */
11521 selectRow : function(row) {
11522     var oRecord, elRow;
11523
11524     if(row instanceof YAHOO.widget.Record) {
11525         oRecord = this._oRecordSet.getRecord(row);
11526         elRow = this.getTrEl(oRecord);
11527     }
11528     else if(lang.isNumber(row)) {
11529         oRecord = this.getRecord(row);
11530         elRow = this.getTrEl(oRecord);
11531     }
11532     else {
11533         elRow = this.getTrEl(row);
11534         oRecord = this.getRecord(elRow);
11535     }
11536
11537     if(oRecord) {
11538         // Update selection trackers
11539         var tracker = this._aSelections || [];
11540         var sRecordId = oRecord.getId();
11541         var index = -1;
11542
11543         // Remove if already there:
11544         // Use Array.indexOf if available...
11545         /*if(tracker.indexOf && (tracker.indexOf(sRecordId) >  -1)) {
11546             tracker.splice(tracker.indexOf(sRecordId),1);
11547         }*/
11548         if(tracker.indexOf) {
11549             index = tracker.indexOf(sRecordId);
11550             
11551         }
11552         // ...or do it the old-fashioned way
11553         else {
11554             for(var j=tracker.length-1; j>-1; j--) {
11555                 if(tracker[j] === sRecordId){
11556                     index = j;
11557                     break;
11558                 }
11559             }
11560         }
11561         if(index > -1) {
11562             tracker.splice(index,1);
11563         }
11564         
11565         // Add to the end
11566         tracker.push(sRecordId);
11567         this._aSelections = tracker;
11568
11569         // Update trackers
11570         if(!this._oAnchorRecord) {
11571             this._oAnchorRecord = oRecord;
11572         }
11573
11574         // Update UI
11575         if(elRow) {
11576             Dom.addClass(elRow, DT.CLASS_SELECTED);
11577         }
11578
11579         this.fireEvent("rowSelectEvent", {record:oRecord, el:elRow});
11580         YAHOO.log("Selected " + elRow, "info", this.toString());
11581     }
11582     else {
11583         YAHOO.log("Could not select row " + row, "warn", this.toString());
11584     }
11585 },
11586
11587 /**
11588  * Sets given row to the unselected state.
11589  *
11590  * @method unselectRow
11591  * @param row {HTMLElement | String | YAHOO.widget.Record | Number} HTML element
11592  * reference or ID string, Record instance, or RecordSet position index.
11593  */
11594 unselectRow : function(row) {
11595     var elRow = this.getTrEl(row);
11596
11597     var oRecord;
11598     if(row instanceof YAHOO.widget.Record) {
11599         oRecord = this._oRecordSet.getRecord(row);
11600     }
11601     else if(lang.isNumber(row)) {
11602         oRecord = this.getRecord(row);
11603     }
11604     else {
11605         oRecord = this.getRecord(elRow);
11606     }
11607
11608     if(oRecord) {
11609         // Update selection trackers
11610         var tracker = this._aSelections || [];
11611         var sRecordId = oRecord.getId();
11612         var index = -1;
11613
11614         // Use Array.indexOf if available...
11615         if(tracker.indexOf) {
11616             index = tracker.indexOf(sRecordId);
11617         }
11618         // ...or do it the old-fashioned way
11619         else {
11620             for(var j=tracker.length-1; j>-1; j--) {
11621                 if(tracker[j] === sRecordId){
11622                     index = j;
11623                     break;
11624                 }
11625             }
11626         }
11627         if(index > -1) {
11628             // Update tracker
11629             tracker.splice(index,1);
11630             this._aSelections = tracker;
11631
11632             // Update the UI
11633             Dom.removeClass(elRow, DT.CLASS_SELECTED);
11634
11635             this.fireEvent("rowUnselectEvent", {record:oRecord, el:elRow});
11636             YAHOO.log("Unselected " + elRow, "info", this.toString());
11637
11638             return;
11639         }
11640     }
11641     YAHOO.log("Could not unselect row " + row, "warn", this.toString());
11642 },
11643
11644 /**
11645  * Clears out all row selections.
11646  *
11647  * @method unselectAllRows
11648  */
11649 unselectAllRows : function() {
11650     // Remove all rows from tracker
11651     var tracker = this._aSelections || [],
11652         recId,
11653         removed = [];
11654     for(var j=tracker.length-1; j>-1; j--) {
11655        if(lang.isString(tracker[j])){
11656             recId = tracker.splice(j,1);
11657             removed[removed.length] = this.getRecord(lang.isArray(recId) ? recId[0] : recId);
11658         }
11659     }
11660
11661     // Update tracker
11662     this._aSelections = tracker;
11663
11664     // Update UI
11665     this._unselectAllTrEls();
11666
11667     this.fireEvent("unselectAllRowsEvent", {records: removed});
11668     YAHOO.log("Unselected all rows", "info", this.toString());
11669 },
11670
11671 /**
11672  * Convenience method to remove the class YAHOO.widget.DataTable.CLASS_SELECTED
11673  * from all TD elements in the internal tracker.
11674  *
11675  * @method _unselectAllTdEls
11676  * @private
11677  */
11678 _unselectAllTdEls : function() {
11679     var selectedCells = Dom.getElementsByClassName(DT.CLASS_SELECTED,"td",this._elTbody);
11680     Dom.removeClass(selectedCells, DT.CLASS_SELECTED);
11681 },
11682
11683 /**
11684  * Returns array of selected TD elements on the page.
11685  *
11686  * @method getSelectedTdEls
11687  * @return {HTMLElement[]} Array of selected TD elements.
11688  */
11689 getSelectedTdEls : function() {
11690     return Dom.getElementsByClassName(DT.CLASS_SELECTED,"td",this._elTbody);
11691 },
11692
11693 /**
11694  * Sets given cell to the selected state.
11695  *
11696  * @method selectCell
11697  * @param cell {HTMLElement | String} DOM element reference or ID string
11698  * to DataTable page element or RecordSet index.
11699  */
11700 selectCell : function(cell) {
11701 //TODO: accept {record} in selectCell()
11702     var elCell = this.getTdEl(cell);
11703
11704     if(elCell) {
11705         var oRecord = this.getRecord(elCell);
11706         var sColumnKey = this.getColumn(elCell.cellIndex).getKey();
11707
11708         if(oRecord && sColumnKey) {
11709             // Get Record ID
11710             var tracker = this._aSelections || [];
11711             var sRecordId = oRecord.getId();
11712
11713             // Remove if there
11714             for(var j=tracker.length-1; j>-1; j--) {
11715                if((tracker[j].recordId === sRecordId) && (tracker[j].columnKey === sColumnKey)){
11716                     tracker.splice(j,1);
11717                     break;
11718                 }
11719             }
11720
11721             // Add to the end
11722             tracker.push({recordId:sRecordId, columnKey:sColumnKey});
11723
11724             // Update trackers
11725             this._aSelections = tracker;
11726             if(!this._oAnchorCell) {
11727                 this._oAnchorCell = {record:oRecord, column:this.getColumn(sColumnKey)};
11728             }
11729
11730             // Update the UI
11731             Dom.addClass(elCell, DT.CLASS_SELECTED);
11732
11733             this.fireEvent("cellSelectEvent", {record:oRecord, column:this.getColumn(elCell.cellIndex), key: this.getColumn(elCell.cellIndex).getKey(), el:elCell});
11734             YAHOO.log("Selected " + elCell, "info", this.toString());
11735             return;
11736         }
11737     }
11738     YAHOO.log("Could not select cell " + cell, "warn", this.toString());
11739 },
11740
11741 /**
11742  * Sets given cell to the unselected state.
11743  *
11744  * @method unselectCell
11745  * @param cell {HTMLElement | String} DOM element reference or ID string
11746  * to DataTable page element or RecordSet index.
11747  */
11748 unselectCell : function(cell) {
11749     var elCell = this.getTdEl(cell);
11750
11751     if(elCell) {
11752         var oRecord = this.getRecord(elCell);
11753         var sColumnKey = this.getColumn(elCell.cellIndex).getKey();
11754
11755         if(oRecord && sColumnKey) {
11756             // Get Record ID
11757             var tracker = this._aSelections || [];
11758             var id = oRecord.getId();
11759
11760             // Is it selected?
11761             for(var j=tracker.length-1; j>-1; j--) {
11762                 if((tracker[j].recordId === id) && (tracker[j].columnKey === sColumnKey)){
11763                     // Remove from tracker
11764                     tracker.splice(j,1);
11765
11766                     // Update tracker
11767                     this._aSelections = tracker;
11768
11769                     // Update the UI
11770                     Dom.removeClass(elCell, DT.CLASS_SELECTED);
11771
11772                     this.fireEvent("cellUnselectEvent", {record:oRecord, column: this.getColumn(elCell.cellIndex), key:this.getColumn(elCell.cellIndex).getKey(), el:elCell});
11773                     YAHOO.log("Unselected " + elCell, "info", this.toString());
11774                     return;
11775                 }
11776             }
11777         }
11778     }
11779     YAHOO.log("Could not unselect cell " + cell, "warn", this.toString());
11780 },
11781
11782 /**
11783  * Clears out all cell selections.
11784  *
11785  * @method unselectAllCells
11786  */
11787 unselectAllCells : function() {
11788     // Remove all cells from tracker
11789     var tracker = this._aSelections || [];
11790     for(var j=tracker.length-1; j>-1; j--) {
11791        if(lang.isObject(tracker[j])){
11792             tracker.splice(j,1);
11793         }
11794     }
11795
11796     // Update tracker
11797     this._aSelections = tracker;
11798
11799     // Update UI
11800     this._unselectAllTdEls();
11801
11802     //TODO: send data to unselectAllCellsEvent handler
11803     this.fireEvent("unselectAllCellsEvent");
11804     YAHOO.log("Unselected all cells", "info", this.toString());
11805 },
11806
11807 /**
11808  * Returns true if given item is selected, false otherwise.
11809  *
11810  * @method isSelected
11811  * @param o {String | HTMLElement | YAHOO.widget.Record | Number
11812  * {record:YAHOO.widget.Record, column:YAHOO.widget.Column} } TR or TD element by
11813  * reference or ID string, a Record instance, a RecordSet position index,
11814  * or an object literal representation
11815  * of a cell.
11816  * @return {Boolean} True if item is selected.
11817  */
11818 isSelected : function(o) {
11819     if(o && (o.ownerDocument == document)) {
11820         return (Dom.hasClass(this.getTdEl(o),DT.CLASS_SELECTED) || Dom.hasClass(this.getTrEl(o),DT.CLASS_SELECTED));
11821     }
11822     else {
11823         var oRecord, sRecordId, j;
11824         var tracker = this._aSelections;
11825         if(tracker && tracker.length > 0) {
11826             // Looking for a Record?
11827             if(o instanceof YAHOO.widget.Record) {
11828                 oRecord = o;
11829             }
11830             else if(lang.isNumber(o)) {
11831                 oRecord = this.getRecord(o);
11832             }
11833             if(oRecord) {
11834                 sRecordId = oRecord.getId();
11835
11836                 // Is it there?
11837                 // Use Array.indexOf if available...
11838                 if(tracker.indexOf) {
11839                     if(tracker.indexOf(sRecordId) >  -1) {
11840                         return true;
11841                     }
11842                 }
11843                 // ...or do it the old-fashioned way
11844                 else {
11845                     for(j=tracker.length-1; j>-1; j--) {
11846                        if(tracker[j] === sRecordId){
11847                         return true;
11848                        }
11849                     }
11850                 }
11851             }
11852             // Looking for a cell
11853             else if(o.record && o.column){
11854                 sRecordId = o.record.getId();
11855                 var sColumnKey = o.column.getKey();
11856
11857                 for(j=tracker.length-1; j>-1; j--) {
11858                     if((tracker[j].recordId === sRecordId) && (tracker[j].columnKey === sColumnKey)){
11859                         return true;
11860                     }
11861                 }
11862             }
11863         }
11864     }
11865     return false;
11866 },
11867
11868 /**
11869  * Returns selected rows as an array of Record IDs.
11870  *
11871  * @method getSelectedRows
11872  * @return {String[]} Array of selected rows by Record ID.
11873  */
11874 getSelectedRows : function() {
11875     var aSelectedRows = [];
11876     var tracker = this._aSelections || [];
11877     for(var j=0; j<tracker.length; j++) {
11878        if(lang.isString(tracker[j])){
11879             aSelectedRows.push(tracker[j]);
11880         }
11881     }
11882     return aSelectedRows;
11883 },
11884
11885 /**
11886  * Returns selected cells as an array of object literals:
11887  *     {recordId:sRecordId, columnKey:sColumnKey}.
11888  *
11889  * @method getSelectedCells
11890  * @return {Object[]} Array of selected cells by Record ID and Column ID.
11891  */
11892 getSelectedCells : function() {
11893     var aSelectedCells = [];
11894     var tracker = this._aSelections || [];
11895     for(var j=0; j<tracker.length; j++) {
11896        if(tracker[j] && lang.isObject(tracker[j])){
11897             aSelectedCells.push(tracker[j]);
11898         }
11899     }
11900     return aSelectedCells;
11901 },
11902
11903 /**
11904  * Returns last selected Record ID.
11905  *
11906  * @method getLastSelectedRecord
11907  * @return {String} Record ID of last selected row.
11908  */
11909 getLastSelectedRecord : function() {
11910     var tracker = this._aSelections;
11911     if(tracker && tracker.length > 0) {
11912         for(var i=tracker.length-1; i>-1; i--) {
11913            if(lang.isString(tracker[i])){
11914                 return tracker[i];
11915             }
11916         }
11917     }
11918 },
11919
11920 /**
11921  * Returns last selected cell as an object literal:
11922  *     {recordId:sRecordId, columnKey:sColumnKey}.
11923  *
11924  * @method getLastSelectedCell
11925  * @return {Object} Object literal representation of a cell.
11926  */
11927 getLastSelectedCell : function() {
11928     var tracker = this._aSelections;
11929     if(tracker && tracker.length > 0) {
11930         for(var i=tracker.length-1; i>-1; i--) {
11931            if(tracker[i].recordId && tracker[i].columnKey){
11932                 return tracker[i];
11933             }
11934         }
11935     }
11936 },
11937
11938 /**
11939  * Assigns the class YAHOO.widget.DataTable.CLASS_HIGHLIGHTED to the given row.
11940  *
11941  * @method highlightRow
11942  * @param row {HTMLElement | String} DOM element reference or ID string.
11943  */
11944 highlightRow : function(row) {
11945     var elRow = this.getTrEl(row);
11946
11947     if(elRow) {
11948         // Make sure previous row is unhighlighted
11949 /*        if(this._sLastHighlightedTrElId) {
11950             Dom.removeClass(this._sLastHighlightedTrElId,DT.CLASS_HIGHLIGHTED);
11951         }*/
11952         var oRecord = this.getRecord(elRow);
11953         Dom.addClass(elRow,DT.CLASS_HIGHLIGHTED);
11954         //this._sLastHighlightedTrElId = elRow.id;
11955         this.fireEvent("rowHighlightEvent", {record:oRecord, el:elRow});
11956         YAHOO.log("Highlighted " + elRow, "info", this.toString());
11957         return;
11958     }
11959     YAHOO.log("Could not highlight row " + row, "warn", this.toString());
11960 },
11961
11962 /**
11963  * Removes the class YAHOO.widget.DataTable.CLASS_HIGHLIGHTED from the given row.
11964  *
11965  * @method unhighlightRow
11966  * @param row {HTMLElement | String} DOM element reference or ID string.
11967  */
11968 unhighlightRow : function(row) {
11969     var elRow = this.getTrEl(row);
11970
11971     if(elRow) {
11972         var oRecord = this.getRecord(elRow);
11973         Dom.removeClass(elRow,DT.CLASS_HIGHLIGHTED);
11974         this.fireEvent("rowUnhighlightEvent", {record:oRecord, el:elRow});
11975         YAHOO.log("Unhighlighted " + elRow, "info", this.toString());
11976         return;
11977     }
11978     YAHOO.log("Could not unhighlight row " + row, "warn", this.toString());
11979 },
11980
11981 /**
11982  * Assigns the class YAHOO.widget.DataTable.CLASS_HIGHLIGHTED to the given cell.
11983  *
11984  * @method highlightCell
11985  * @param cell {HTMLElement | String} DOM element reference or ID string.
11986  */
11987 highlightCell : function(cell) {
11988     var elCell = this.getTdEl(cell);
11989
11990     if(elCell) {
11991         // Make sure previous cell is unhighlighted
11992         if(this._elLastHighlightedTd) {
11993             this.unhighlightCell(this._elLastHighlightedTd);
11994         }
11995
11996         var oRecord = this.getRecord(elCell);
11997         var sColumnKey = this.getColumn(elCell.cellIndex).getKey();
11998         Dom.addClass(elCell,DT.CLASS_HIGHLIGHTED);
11999         this._elLastHighlightedTd = elCell;
12000         this.fireEvent("cellHighlightEvent", {record:oRecord, column:this.getColumn(elCell.cellIndex), key:this.getColumn(elCell.cellIndex).getKey(), el:elCell});
12001         YAHOO.log("Highlighted " + elCell, "info", this.toString());
12002         return;
12003     }
12004     YAHOO.log("Could not highlight cell " + cell, "warn", this.toString());
12005 },
12006
12007 /**
12008  * Removes the class YAHOO.widget.DataTable.CLASS_HIGHLIGHTED from the given cell.
12009  *
12010  * @method unhighlightCell
12011  * @param cell {HTMLElement | String} DOM element reference or ID string.
12012  */
12013 unhighlightCell : function(cell) {
12014     var elCell = this.getTdEl(cell);
12015
12016     if(elCell) {
12017         var oRecord = this.getRecord(elCell);
12018         Dom.removeClass(elCell,DT.CLASS_HIGHLIGHTED);
12019         this._elLastHighlightedTd = null;
12020         this.fireEvent("cellUnhighlightEvent", {record:oRecord, column:this.getColumn(elCell.cellIndex), key:this.getColumn(elCell.cellIndex).getKey(), el:elCell});
12021         YAHOO.log("Unhighlighted " + elCell, "info", this.toString());
12022         return;
12023     }
12024     YAHOO.log("Could not unhighlight cell " + cell, "warn", this.toString());
12025 },
12026
12027
12028
12029
12030
12031
12032
12033
12034
12035
12036
12037
12038
12039
12040
12041
12042
12043
12044
12045
12046
12047
12048
12049
12050
12051
12052
12053
12054
12055
12056
12057
12058
12059
12060
12061
12062
12063
12064
12065
12066
12067
12068
12069
12070
12071 // INLINE EDITING
12072
12073 /**
12074  * Returns current CellEditor instance, or null.
12075  * @method getCellEditor
12076  * @return {YAHOO.widget.CellEditor} CellEditor instance.
12077  */
12078 getCellEditor : function() {
12079     return this._oCellEditor;
12080 },
12081
12082
12083 /**
12084  * Activates and shows CellEditor instance for the given cell while deactivating and
12085  * canceling previous CellEditor. It is baked into DataTable that only one CellEditor
12086  * can be active at any given time. 
12087  *
12088  * @method showCellEditor
12089  * @param elCell {HTMLElement | String} Cell to edit.
12090  */
12091 showCellEditor : function(elCell, oRecord, oColumn) {
12092     // Get a particular CellEditor
12093     elCell = this.getTdEl(elCell);
12094     if(elCell) {
12095         oColumn = this.getColumn(elCell);
12096         if(oColumn && oColumn.editor) {
12097             var oCellEditor = this._oCellEditor;
12098             // Clean up active CellEditor
12099             if(oCellEditor) {
12100                 if(this._oCellEditor.cancel) {
12101                     this._oCellEditor.cancel();
12102                 }
12103                 else if(oCellEditor.isActive) {
12104                     this.cancelCellEditor();
12105                 }
12106             }
12107             
12108             if(oColumn.editor instanceof YAHOO.widget.BaseCellEditor) {
12109                 // Get CellEditor
12110                 oCellEditor = oColumn.editor;
12111                 var ok = oCellEditor.attach(this, elCell);
12112                 if(ok) {
12113                     oCellEditor.move();
12114                     ok = this.doBeforeShowCellEditor(oCellEditor);
12115                     if(ok) {
12116                         oCellEditor.show();
12117                         this._oCellEditor = oCellEditor;
12118                     }
12119                 }
12120             }
12121             // Backward compatibility
12122             else {
12123                     if(!oRecord || !(oRecord instanceof YAHOO.widget.Record)) {
12124                         oRecord = this.getRecord(elCell);
12125                     }
12126                     if(!oColumn || !(oColumn instanceof YAHOO.widget.Column)) {
12127                         oColumn = this.getColumn(elCell);
12128                     }
12129                     if(oRecord && oColumn) {
12130                         if(!this._oCellEditor || this._oCellEditor.container) {
12131                             this._initCellEditorEl();
12132                         }
12133                         
12134                         // Update Editor values
12135                         oCellEditor = this._oCellEditor;
12136                         oCellEditor.cell = elCell;
12137                         oCellEditor.record = oRecord;
12138                         oCellEditor.column = oColumn;
12139                         oCellEditor.validator = (oColumn.editorOptions &&
12140                                 lang.isFunction(oColumn.editorOptions.validator)) ?
12141                                 oColumn.editorOptions.validator : null;
12142                         oCellEditor.value = oRecord.getData(oColumn.key);
12143                         oCellEditor.defaultValue = null;
12144             
12145                         // Move Editor
12146                         var elContainer = oCellEditor.container;
12147                         var x = Dom.getX(elCell);
12148                         var y = Dom.getY(elCell);
12149             
12150                         // SF doesn't get xy for cells in scrolling table
12151                         // when tbody display is set to block
12152                         if(isNaN(x) || isNaN(y)) {
12153                             x = elCell.offsetLeft + // cell pos relative to table
12154                                     Dom.getX(this._elTbody.parentNode) - // plus table pos relative to document
12155                                     this._elTbody.scrollLeft; // minus tbody scroll
12156                             y = elCell.offsetTop + // cell pos relative to table
12157                                     Dom.getY(this._elTbody.parentNode) - // plus table pos relative to document
12158                                     this._elTbody.scrollTop + // minus tbody scroll
12159                                     this._elThead.offsetHeight; // account for fixed THEAD cells
12160                         }
12161             
12162                         elContainer.style.left = x + "px";
12163                         elContainer.style.top = y + "px";
12164             
12165                         // Hook to customize the UI
12166                         this.doBeforeShowCellEditor(this._oCellEditor);
12167             
12168                         //TODO: This is temporarily up here due so elements can be focused
12169                         // Show Editor
12170                         elContainer.style.display = "";
12171             
12172                         // Handle ESC key
12173                         Ev.addListener(elContainer, "keydown", function(e, oSelf) {
12174                             // ESC hides Cell Editor
12175                             if((e.keyCode == 27)) {
12176                                 oSelf.cancelCellEditor();
12177                                 oSelf.focusTbodyEl();
12178                             }
12179                             else {
12180                                 oSelf.fireEvent("editorKeydownEvent", {editor:oSelf._oCellEditor, event:e});
12181                             }
12182                         }, this);
12183             
12184                         // Render Editor markup
12185                         var fnEditor;
12186                         if(lang.isString(oColumn.editor)) {
12187                             switch(oColumn.editor) {
12188                                 case "checkbox":
12189                                     fnEditor = DT.editCheckbox;
12190                                     break;
12191                                 case "date":
12192                                     fnEditor = DT.editDate;
12193                                     break;
12194                                 case "dropdown":
12195                                     fnEditor = DT.editDropdown;
12196                                     break;
12197                                 case "radio":
12198                                     fnEditor = DT.editRadio;
12199                                     break;
12200                                 case "textarea":
12201                                     fnEditor = DT.editTextarea;
12202                                     break;
12203                                 case "textbox":
12204                                     fnEditor = DT.editTextbox;
12205                                     break;
12206                                 default:
12207                                     fnEditor = null;
12208                             }
12209                         }
12210                         else if(lang.isFunction(oColumn.editor)) {
12211                             fnEditor = oColumn.editor;
12212                         }
12213             
12214                         if(fnEditor) {
12215                             // Create DOM input elements
12216                             fnEditor(this._oCellEditor, this);
12217             
12218                             // Show Save/Cancel buttons
12219                             if(!oColumn.editorOptions || !oColumn.editorOptions.disableBtns) {
12220                                 this.showCellEditorBtns(elContainer);
12221                             }
12222             
12223                             oCellEditor.isActive = true;
12224             
12225                             //TODO: verify which args to pass
12226                             this.fireEvent("editorShowEvent", {editor:oCellEditor});
12227                             YAHOO.log("Cell Editor shown for " + elCell, "info", this.toString());
12228                             return;
12229                         }
12230                     }
12231
12232
12233
12234             
12235             }
12236         }
12237     }
12238 },
12239
12240 /**
12241  * Backward compatibility.
12242  *
12243  * @method _initCellEditorEl
12244  * @private
12245  * @deprecated 
12246  */
12247 _initCellEditorEl : function() {
12248     // Attach Cell Editor container element as first child of body
12249     var elCellEditor = document.createElement("div");
12250     elCellEditor.id = this._sId + "-celleditor";
12251     elCellEditor.style.display = "none";
12252     elCellEditor.tabIndex = 0;
12253     Dom.addClass(elCellEditor, DT.CLASS_EDITOR);
12254     var elFirstChild = Dom.getFirstChild(document.body);
12255     if(elFirstChild) {
12256         elCellEditor = Dom.insertBefore(elCellEditor, elFirstChild);
12257     }
12258     else {
12259         elCellEditor = document.body.appendChild(elCellEditor);
12260     }
12261     
12262     // Internal tracker of Cell Editor values
12263     var oCellEditor = {};
12264     oCellEditor.container = elCellEditor;
12265     oCellEditor.value = null;
12266     oCellEditor.isActive = false;
12267     this._oCellEditor = oCellEditor;
12268 },
12269
12270 /**
12271  * Overridable abstract method to customize CellEditor before showing.
12272  *
12273  * @method doBeforeShowCellEditor
12274  * @param oCellEditor {YAHOO.widget.CellEditor} The CellEditor instance.
12275  * @return {Boolean} Return true to continue showing CellEditor.
12276  */
12277 doBeforeShowCellEditor : function(oCellEditor) {
12278     return true;
12279 },
12280
12281 /**
12282  * Saves active CellEditor input to Record and upates DOM UI.
12283  *
12284  * @method saveCellEditor
12285  */
12286 saveCellEditor : function() {
12287     if(this._oCellEditor) {
12288         if(this._oCellEditor.save) {
12289             this._oCellEditor.save();
12290         }
12291         // Backward compatibility
12292         else if(this._oCellEditor.isActive) {
12293             var newData = this._oCellEditor.value;
12294             // Copy the data to pass to the event
12295             //var oldData = YAHOO.widget.DataTable._cloneObject(this._oCellEditor.record.getData(this._oCellEditor.column.key));
12296             var oldData = this._oCellEditor.record.getData(this._oCellEditor.column.key);
12297     
12298             // Validate input data
12299             if(this._oCellEditor.validator) {
12300                 newData = this._oCellEditor.value = this._oCellEditor.validator.call(this, newData, oldData, this._oCellEditor);
12301                 if(newData === null ) {
12302                     this.resetCellEditor();
12303                     this.fireEvent("editorRevertEvent",
12304                             {editor:this._oCellEditor, oldData:oldData, newData:newData});
12305                     YAHOO.log("Could not save Cell Editor input due to invalid data " +
12306                             lang.dump(newData), "warn", this.toString());
12307                     return;
12308                 }
12309             }
12310             // Update the Record
12311             this._oRecordSet.updateRecordValue(this._oCellEditor.record, this._oCellEditor.column.key, this._oCellEditor.value);
12312             // Update the UI
12313             this.formatCell(this._oCellEditor.cell.firstChild);
12314             
12315             // Bug fix 1764044
12316             this._oChainRender.add({
12317                 method: function() {
12318                     this.validateColumnWidths();
12319                 },
12320                 scope: this
12321             });
12322             this._oChainRender.run();
12323             // Clear out the Cell Editor
12324             this.resetCellEditor();
12325     
12326             this.fireEvent("editorSaveEvent",
12327                     {editor:this._oCellEditor, oldData:oldData, newData:newData});
12328             YAHOO.log("Cell Editor input saved", "info", this.toString());
12329         }
12330     }   
12331 },
12332
12333 /**
12334  * Cancels active CellEditor.
12335  *
12336  * @method cancelCellEditor
12337  */
12338 cancelCellEditor : function() {
12339     if(this._oCellEditor) {
12340         if(this._oCellEditor.cancel) {
12341             this._oCellEditor.cancel();
12342         }
12343         // Backward compatibility
12344         else if(this._oCellEditor.isActive) {
12345             this.resetCellEditor();
12346             //TODO: preserve values for the event?
12347             this.fireEvent("editorCancelEvent", {editor:this._oCellEditor});
12348             YAHOO.log("Cell Editor input canceled", "info", this.toString());
12349         }
12350
12351         YAHOO.log("CellEditor input canceled", "info", this.toString());
12352     }
12353 },
12354
12355 /**
12356  * Destroys active CellEditor instance and UI.
12357  *
12358  * @method destroyCellEditor
12359  */
12360 destroyCellEditor : function() {
12361     if(this._oCellEditor) {
12362         this._oCellEditor.destroy();
12363         this._oCellEditor = null;
12364     }   
12365 },
12366
12367 /**
12368  * Passes through showEvent of the active CellEditor.
12369  *
12370  * @method _onEditorShowEvent
12371  * @param oArgs {Object}  Custom Event args.
12372  * @private 
12373  */
12374 _onEditorShowEvent : function(oArgs) {
12375     this.fireEvent("editorShowEvent", oArgs);
12376 },
12377
12378 /**
12379  * Passes through keydownEvent of the active CellEditor.
12380  * @param oArgs {Object}  Custom Event args. 
12381  *
12382  * @method _onEditorKeydownEvent
12383  * @private 
12384  */
12385 _onEditorKeydownEvent : function(oArgs) {
12386     this.fireEvent("editorKeydownEvent", oArgs);
12387 },
12388
12389 /**
12390  * Passes through revertEvent of the active CellEditor.
12391  *
12392  * @method _onEditorRevertEvent
12393  * @param oArgs {Object}  Custom Event args. 
12394  * @private  
12395  */
12396 _onEditorRevertEvent : function(oArgs) {
12397     this.fireEvent("editorRevertEvent", oArgs);
12398 },
12399
12400 /**
12401  * Passes through saveEvent of the active CellEditor.
12402  *
12403  * @method _onEditorSaveEvent
12404  * @param oArgs {Object}  Custom Event args.  
12405  * @private 
12406  */
12407 _onEditorSaveEvent : function(oArgs) {
12408     this.fireEvent("editorSaveEvent", oArgs);
12409 },
12410
12411 /**
12412  * Passes through cancelEvent of the active CellEditor.
12413  *
12414  * @method _onEditorCancelEvent
12415  * @param oArgs {Object}  Custom Event args.
12416  * @private   
12417  */
12418 _onEditorCancelEvent : function(oArgs) {
12419     this.fireEvent("editorCancelEvent", oArgs);
12420 },
12421
12422 /**
12423  * Passes through blurEvent of the active CellEditor.
12424  *
12425  * @method _onEditorBlurEvent
12426  * @param oArgs {Object}  Custom Event args. 
12427  * @private  
12428  */
12429 _onEditorBlurEvent : function(oArgs) {
12430     this.fireEvent("editorBlurEvent", oArgs);
12431 },
12432
12433 /**
12434  * Passes through blockEvent of the active CellEditor.
12435  *
12436  * @method _onEditorBlockEvent
12437  * @param oArgs {Object}  Custom Event args. 
12438  * @private  
12439  */
12440 _onEditorBlockEvent : function(oArgs) {
12441     this.fireEvent("editorBlockEvent", oArgs);
12442 },
12443
12444 /**
12445  * Passes through unblockEvent of the active CellEditor.
12446  *
12447  * @method _onEditorUnblockEvent
12448  * @param oArgs {Object}  Custom Event args. 
12449  * @private  
12450  */
12451 _onEditorUnblockEvent : function(oArgs) {
12452     this.fireEvent("editorUnblockEvent", oArgs);
12453 },
12454
12455 /**
12456  * Public handler of the editorBlurEvent. By default, saves on blur if
12457  * disableBtns is true, otherwise cancels on blur. 
12458  *
12459  * @method onEditorBlurEvent
12460  * @param oArgs {Object}  Custom Event args.  
12461  */
12462 onEditorBlurEvent : function(oArgs) {
12463     if(oArgs.editor.disableBtns) {
12464         // Save on blur
12465         if(oArgs.editor.save) { // Backward incompatible
12466             oArgs.editor.save();
12467         }
12468     }      
12469     else if(oArgs.editor.cancel) { // Backward incompatible
12470         // Cancel on blur
12471         oArgs.editor.cancel();
12472     }      
12473 },
12474
12475 /**
12476  * Public handler of the editorBlockEvent. By default, disables DataTable UI.
12477  *
12478  * @method onEditorBlockEvent
12479  * @param oArgs {Object}  Custom Event args.  
12480  */
12481 onEditorBlockEvent : function(oArgs) {
12482     this.disable();
12483 },
12484
12485 /**
12486  * Public handler of the editorUnblockEvent. By default, undisables DataTable UI.
12487  *
12488  * @method onEditorUnblockEvent
12489  * @param oArgs {Object}  Custom Event args.  
12490  */
12491 onEditorUnblockEvent : function(oArgs) {
12492     this.undisable();
12493 },
12494
12495
12496
12497
12498
12499
12500
12501
12502
12503
12504
12505
12506
12507
12508
12509
12510
12511
12512
12513
12514
12515
12516
12517
12518
12519
12520
12521
12522
12523
12524
12525
12526
12527
12528
12529
12530
12531
12532 // ABSTRACT METHODS
12533
12534 /**
12535  * Overridable method gives implementers a hook to access data before
12536  * it gets added to RecordSet and rendered to the TBODY.
12537  *
12538  * @method doBeforeLoadData
12539  * @param sRequest {String} Original request.
12540  * @param oResponse {Object} Response object.
12541  * @param oPayload {MIXED} additional arguments
12542  * @return {Boolean} Return true to continue loading data into RecordSet and
12543  * updating DataTable with new Records, false to cancel.
12544  */
12545 doBeforeLoadData : function(sRequest, oResponse, oPayload) {
12546     return true;
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
12584
12585
12586
12587
12588
12589
12590
12591
12592
12593
12594
12595
12596
12597
12598
12599
12600
12601
12602
12603
12604
12605
12606
12607
12608
12609
12610
12611 /////////////////////////////////////////////////////////////////////////////
12612 //
12613 // Public Custom Event Handlers
12614 //
12615 /////////////////////////////////////////////////////////////////////////////
12616
12617 /**
12618  * Overridable custom event handler to sort Column.
12619  *
12620  * @method onEventSortColumn
12621  * @param oArgs.event {HTMLEvent} Event object.
12622  * @param oArgs.target {HTMLElement} Target element.
12623  */
12624 onEventSortColumn : function(oArgs) {
12625 //TODO: support form elements in sortable columns
12626     var evt = oArgs.event;
12627     var target = oArgs.target;
12628
12629     var el = this.getThEl(target) || this.getTdEl(target);
12630     if(el) {
12631         var oColumn = this.getColumn(el);
12632         if(oColumn.sortable) {
12633             Ev.stopEvent(evt);
12634             this.sortColumn(oColumn);
12635         }
12636     }
12637     else {
12638         YAHOO.log("Could not find Column for " + target, "warn", this.toString());
12639     }
12640 },
12641
12642 /**
12643  * Overridable custom event handler to select Column.
12644  *
12645  * @method onEventSelectColumn
12646  * @param oArgs.event {HTMLEvent} Event object.
12647  * @param oArgs.target {HTMLElement} Target element.
12648  */
12649 onEventSelectColumn : function(oArgs) {
12650     this.selectColumn(oArgs.target);
12651 },
12652
12653 /**
12654  * Overridable custom event handler to highlight Column. Accounts for spurious
12655  * caused-by-child events. 
12656  *
12657  * @method onEventHighlightColumn
12658  * @param oArgs.event {HTMLEvent} Event object.
12659  * @param oArgs.target {HTMLElement} Target element.
12660  */
12661 onEventHighlightColumn : function(oArgs) {
12662     //TODO: filter for all spurious events at a lower level
12663     if(!Dom.isAncestor(oArgs.target,Ev.getRelatedTarget(oArgs.event))) {
12664         this.highlightColumn(oArgs.target);
12665     }
12666 },
12667
12668 /**
12669  * Overridable custom event handler to unhighlight Column. Accounts for spurious
12670  * caused-by-child events. 
12671  *
12672  * @method onEventUnhighlightColumn
12673  * @param oArgs.event {HTMLEvent} Event object.
12674  * @param oArgs.target {HTMLElement} Target element.
12675  */
12676 onEventUnhighlightColumn : function(oArgs) {
12677     //TODO: filter for all spurious events at a lower level
12678     if(!Dom.isAncestor(oArgs.target,Ev.getRelatedTarget(oArgs.event))) {
12679         this.unhighlightColumn(oArgs.target);
12680     }
12681 },
12682
12683 /**
12684  * Overridable custom event handler to manage selection according to desktop paradigm.
12685  *
12686  * @method onEventSelectRow
12687  * @param oArgs.event {HTMLEvent} Event object.
12688  * @param oArgs.target {HTMLElement} Target element.
12689  */
12690 onEventSelectRow : function(oArgs) {
12691     var sMode = this.get("selectionMode");
12692     if(sMode == "single") {
12693         this._handleSingleSelectionByMouse(oArgs);
12694     }
12695     else {
12696         this._handleStandardSelectionByMouse(oArgs);
12697     }
12698 },
12699
12700 /**
12701  * Overridable custom event handler to select cell.
12702  *
12703  * @method onEventSelectCell
12704  * @param oArgs.event {HTMLEvent} Event object.
12705  * @param oArgs.target {HTMLElement} Target element.
12706  */
12707 onEventSelectCell : function(oArgs) {
12708     var sMode = this.get("selectionMode");
12709     if(sMode == "cellblock") {
12710         this._handleCellBlockSelectionByMouse(oArgs);
12711     }
12712     else if(sMode == "cellrange") {
12713         this._handleCellRangeSelectionByMouse(oArgs);
12714     }
12715     else {
12716         this._handleSingleCellSelectionByMouse(oArgs);
12717     }
12718 },
12719
12720 /**
12721  * Overridable custom event handler to highlight row. Accounts for spurious
12722  * caused-by-child events. 
12723  *
12724  * @method onEventHighlightRow
12725  * @param oArgs.event {HTMLEvent} Event object.
12726  * @param oArgs.target {HTMLElement} Target element.
12727  */
12728 onEventHighlightRow : function(oArgs) {
12729     //TODO: filter for all spurious events at a lower level
12730     if(!Dom.isAncestor(oArgs.target,Ev.getRelatedTarget(oArgs.event))) {
12731         this.highlightRow(oArgs.target);
12732     }
12733 },
12734
12735 /**
12736  * Overridable custom event handler to unhighlight row. Accounts for spurious
12737  * caused-by-child events. 
12738  *
12739  * @method onEventUnhighlightRow
12740  * @param oArgs.event {HTMLEvent} Event object.
12741  * @param oArgs.target {HTMLElement} Target element.
12742  */
12743 onEventUnhighlightRow : function(oArgs) {
12744     //TODO: filter for all spurious events at a lower level
12745     if(!Dom.isAncestor(oArgs.target,Ev.getRelatedTarget(oArgs.event))) {
12746         this.unhighlightRow(oArgs.target);
12747     }
12748 },
12749
12750 /**
12751  * Overridable custom event handler to highlight cell. Accounts for spurious
12752  * caused-by-child events. 
12753  *
12754  * @method onEventHighlightCell
12755  * @param oArgs.event {HTMLEvent} Event object.
12756  * @param oArgs.target {HTMLElement} Target element.
12757  */
12758 onEventHighlightCell : function(oArgs) {
12759     //TODO: filter for all spurious events at a lower level
12760     if(!Dom.isAncestor(oArgs.target,Ev.getRelatedTarget(oArgs.event))) {
12761         this.highlightCell(oArgs.target);
12762     }
12763 },
12764
12765 /**
12766  * Overridable custom event handler to unhighlight cell. Accounts for spurious
12767  * caused-by-child events. 
12768  *
12769  * @method onEventUnhighlightCell
12770  * @param oArgs.event {HTMLEvent} Event object.
12771  * @param oArgs.target {HTMLElement} Target element.
12772  */
12773 onEventUnhighlightCell : function(oArgs) {
12774     //TODO: filter for all spurious events at a lower level
12775     if(!Dom.isAncestor(oArgs.target,Ev.getRelatedTarget(oArgs.event))) {
12776         this.unhighlightCell(oArgs.target);
12777     }
12778 },
12779
12780 /**
12781  * Overridable custom event handler to format cell.
12782  *
12783  * @method onEventFormatCell
12784  * @param oArgs.event {HTMLEvent} Event object.
12785  * @param oArgs.target {HTMLElement} Target element.
12786  */
12787 onEventFormatCell : function(oArgs) {
12788     var target = oArgs.target;
12789
12790     var elCell = this.getTdEl(target);
12791     if(elCell) {
12792         var oColumn = this.getColumn(elCell.cellIndex);
12793         this.formatCell(elCell.firstChild, this.getRecord(elCell), oColumn);
12794     }
12795     else {
12796         YAHOO.log("Could not format cell " + target, "warn", this.toString());
12797     }
12798 },
12799
12800 /**
12801  * Overridable custom event handler to edit cell.
12802  *
12803  * @method onEventShowCellEditor
12804  * @param oArgs.event {HTMLEvent} Event object.
12805  * @param oArgs.target {HTMLElement} Target element.
12806  */
12807 onEventShowCellEditor : function(oArgs) {
12808     this.showCellEditor(oArgs.target);
12809 },
12810
12811 /**
12812  * Overridable custom event handler to save active CellEditor input.
12813  *
12814  * @method onEventSaveCellEditor
12815  */
12816 onEventSaveCellEditor : function(oArgs) {
12817     if(this._oCellEditor) {
12818         if(this._oCellEditor.save) {
12819             this._oCellEditor.save();
12820         }
12821         // Backward compatibility
12822         else {
12823             this.saveCellEditor();
12824         }
12825     }
12826 },
12827
12828 /**
12829  * Overridable custom event handler to cancel active CellEditor.
12830  *
12831  * @method onEventCancelCellEditor
12832  */
12833 onEventCancelCellEditor : function(oArgs) {
12834     if(this._oCellEditor) {
12835         if(this._oCellEditor.cancel) {
12836             this._oCellEditor.cancel();
12837         }
12838         // Backward compatibility
12839         else {
12840             this.cancelCellEditor();
12841         }
12842     }
12843 },
12844
12845 /**
12846  * Callback function receives data from DataSource and populates an entire
12847  * DataTable with Records and TR elements, clearing previous Records, if any.
12848  *
12849  * @method onDataReturnInitializeTable
12850  * @param sRequest {String} Original request.
12851  * @param oResponse {Object} Response object.
12852  * @param oPayload {MIXED} (optional) Additional argument(s)
12853  */
12854 onDataReturnInitializeTable : function(sRequest, oResponse, oPayload) {
12855     if((this instanceof DT) && this._sId) {
12856         this.initializeTable();
12857     
12858         this.onDataReturnSetRows(sRequest,oResponse,oPayload);
12859     }
12860 },
12861
12862 /**
12863  * Callback function receives reponse from DataSource, replaces all existing
12864  * Records in  RecordSet, updates TR elements with new data, and updates state
12865  * UI for pagination and sorting from payload data, if necessary. 
12866  *  
12867  * @method onDataReturnReplaceRows
12868  * @param oRequest {MIXED} Original generated request.
12869  * @param oResponse {Object} Response object.
12870  * @param oPayload {MIXED} (optional) Additional argument(s)
12871  */
12872 onDataReturnReplaceRows : function(oRequest, oResponse, oPayload) {
12873     if((this instanceof DT) && this._sId) {
12874         this.fireEvent("dataReturnEvent", {request:oRequest,response:oResponse,payload:oPayload});
12875     
12876         // Pass data through abstract method for any transformations
12877         var ok    = this.doBeforeLoadData(oRequest, oResponse, oPayload),
12878             pag   = this.get('paginator'),
12879             index = 0;
12880     
12881         // Data ok to set
12882         if(ok && oResponse && !oResponse.error && lang.isArray(oResponse.results)) {
12883             // Update Records
12884             this._oRecordSet.reset();
12885     
12886             if (this.get('dynamicData')) {
12887                 if (oPayload && oPayload.pagination &&
12888                     lang.isNumber(oPayload.pagination.recordOffset)) {
12889                     index = oPayload.pagination.recordOffset;
12890                 } else if (pag) {
12891                     index = pag.getStartIndex();
12892                 }
12893             }
12894     
12895             this._oRecordSet.setRecords(oResponse.results, index | 0);
12896             
12897             // Update state
12898             this._handleDataReturnPayload(oRequest, oResponse, oPayload);
12899             
12900             // Update UI
12901             this.render();    
12902         }
12903         // Error
12904         else if(ok && oResponse.error) {
12905             this.showTableMessage(this.get("MSG_ERROR"), DT.CLASS_ERROR);
12906         }
12907     }
12908 },
12909
12910 /**
12911  * Callback function receives data from DataSource and appends to an existing
12912  * DataTable new Records and, if applicable, creates or updates
12913  * corresponding TR elements.
12914  *
12915  * @method onDataReturnAppendRows
12916  * @param sRequest {String} Original request.
12917  * @param oResponse {Object} Response object.
12918  * @param oPayload {MIXED} (optional) Additional argument(s)
12919  */
12920 onDataReturnAppendRows : function(sRequest, oResponse, oPayload) {
12921     if((this instanceof DT) && this._sId) {
12922         this.fireEvent("dataReturnEvent", {request:sRequest,response:oResponse,payload:oPayload});
12923     
12924         // Pass data through abstract method for any transformations
12925         var ok = this.doBeforeLoadData(sRequest, oResponse, oPayload);
12926     
12927         // Data ok to append
12928         if(ok && oResponse && !oResponse.error && lang.isArray(oResponse.results)) {        
12929             // Append rows
12930             this.addRows(oResponse.results);
12931     
12932             // Update state
12933             this._handleDataReturnPayload(sRequest, oResponse, oPayload);
12934         }
12935         // Error
12936         else if(ok && oResponse.error) {
12937             this.showTableMessage(this.get("MSG_ERROR"), DT.CLASS_ERROR);
12938         }
12939     }
12940 },
12941
12942 /**
12943  * Callback function receives data from DataSource and inserts new records
12944  * starting at the index specified in oPayload.insertIndex. The value for
12945  * oPayload.insertIndex can be populated when sending the request to the DataSource,
12946  * or by accessing oPayload.insertIndex with the doBeforeLoadData() method at runtime.
12947  * If applicable, creates or updates corresponding TR elements.
12948  *
12949  * @method onDataReturnInsertRows
12950  * @param sRequest {String} Original request.
12951  * @param oResponse {Object} Response object.
12952  * @param oPayload {MIXED} Argument payload, looks in oPayload.insertIndex.
12953  */
12954 onDataReturnInsertRows : function(sRequest, oResponse, oPayload) {
12955     if((this instanceof DT) && this._sId) {
12956         this.fireEvent("dataReturnEvent", {request:sRequest,response:oResponse,payload:oPayload});
12957     
12958         // Pass data through abstract method for any transformations
12959         var ok = this.doBeforeLoadData(sRequest, oResponse, oPayload);
12960     
12961         // Data ok to append
12962         if(ok && oResponse && !oResponse.error && lang.isArray(oResponse.results)) {
12963             // Insert rows
12964             this.addRows(oResponse.results, (oPayload ? oPayload.insertIndex : 0));
12965     
12966             // Update state
12967             this._handleDataReturnPayload(sRequest, oResponse, oPayload);
12968         }
12969         // Error
12970         else if(ok && oResponse.error) {
12971             this.showTableMessage(this.get("MSG_ERROR"), DT.CLASS_ERROR);
12972         }
12973     }
12974 },
12975
12976 /**
12977  * Callback function receives data from DataSource and incrementally updates Records
12978  * starting at the index specified in oPayload.updateIndex. The value for
12979  * oPayload.updateIndex can be populated when sending the request to the DataSource,
12980  * or by accessing oPayload.updateIndex with the doBeforeLoadData() method at runtime.
12981  * If applicable, creates or updates corresponding TR elements.
12982  *
12983  * @method onDataReturnUpdateRows
12984  * @param sRequest {String} Original request.
12985  * @param oResponse {Object} Response object.
12986  * @param oPayload {MIXED} Argument payload, looks in oPayload.updateIndex.
12987  */
12988 onDataReturnUpdateRows : function(sRequest, oResponse, oPayload) {
12989     if((this instanceof DT) && this._sId) {
12990         this.fireEvent("dataReturnEvent", {request:sRequest,response:oResponse,payload:oPayload});
12991     
12992         // Pass data through abstract method for any transformations
12993         var ok = this.doBeforeLoadData(sRequest, oResponse, oPayload);
12994     
12995         // Data ok to append
12996         if(ok && oResponse && !oResponse.error && lang.isArray(oResponse.results)) {
12997             // Insert rows
12998             this.updateRows((oPayload ? oPayload.updateIndex : 0), oResponse.results);
12999     
13000             // Update state
13001             this._handleDataReturnPayload(sRequest, oResponse, oPayload);
13002         }
13003         // Error
13004         else if(ok && oResponse.error) {
13005             this.showTableMessage(this.get("MSG_ERROR"), DT.CLASS_ERROR);
13006         }
13007     }
13008 },
13009
13010 /**
13011  * Callback function receives reponse from DataSource and populates the
13012  * RecordSet with the results.
13013  *  
13014  * @method onDataReturnSetRows
13015  * @param oRequest {MIXED} Original generated request.
13016  * @param oResponse {Object} Response object.
13017  * @param oPayload {MIXED} (optional) Additional argument(s)
13018  */
13019 onDataReturnSetRows : function(oRequest, oResponse, oPayload) {
13020     if((this instanceof DT) && this._sId) {
13021         this.fireEvent("dataReturnEvent", {request:oRequest,response:oResponse,payload:oPayload});
13022     
13023         // Pass data through abstract method for any transformations
13024         var ok    = this.doBeforeLoadData(oRequest, oResponse, oPayload),
13025             pag   = this.get('paginator'),
13026             index = 0;
13027     
13028         // Data ok to set
13029         if(ok && oResponse && !oResponse.error && lang.isArray(oResponse.results)) {
13030             // Update Records
13031             if (this.get('dynamicData')) {
13032                 if (oPayload && oPayload.pagination &&
13033                     lang.isNumber(oPayload.pagination.recordOffset)) {
13034                     index = oPayload.pagination.recordOffset;
13035                 } else if (pag) {
13036                     index = pag.getStartIndex();
13037                 }
13038                 
13039                 this._oRecordSet.reset(); // Bug 2290604: dyanmic data shouldn't keep accumulating by default
13040             }
13041     
13042             this._oRecordSet.setRecords(oResponse.results, index | 0);
13043     
13044             // Update state
13045             this._handleDataReturnPayload(oRequest, oResponse, oPayload);
13046             
13047             // Update UI
13048             this.render();
13049         }
13050         // Error
13051         else if(ok && oResponse.error) {
13052             this.showTableMessage(this.get("MSG_ERROR"), DT.CLASS_ERROR);
13053         }
13054     }
13055     else {
13056         YAHOO.log("Instance destroyed before data returned.","info",this.toString());
13057     }
13058 },
13059
13060 /**
13061  * Hook to update oPayload before consumption.
13062  *  
13063  * @method handleDataReturnPayload
13064  * @param oRequest {MIXED} Original generated request.
13065  * @param oResponse {Object} Response object.
13066  * @param oPayload {MIXED} State values.
13067  * @return oPayload {MIXED} State values.
13068  */
13069 handleDataReturnPayload : function (oRequest, oResponse, oPayload) {
13070     return oPayload;
13071 },
13072
13073 /**
13074  * Updates the DataTable with state data sent in an onDataReturn* payload.
13075  *  
13076  * @method handleDataReturnPayload
13077  * @param oRequest {MIXED} Original generated request.
13078  * @param oResponse {Object} Response object.
13079  * @param oPayload {MIXED} State values
13080  */
13081 _handleDataReturnPayload : function (oRequest, oResponse, oPayload) {
13082     oPayload = this.handleDataReturnPayload(oRequest, oResponse, oPayload);
13083     if(oPayload) {
13084         // Update pagination
13085         var oPaginator = this.get('paginator');
13086         if (oPaginator) {
13087             // Update totalRecords
13088             if(this.get("dynamicData")) {
13089                 if (widget.Paginator.isNumeric(oPayload.totalRecords)) {
13090                     oPaginator.set('totalRecords',oPayload.totalRecords);
13091                 }
13092             }
13093             else {
13094                 oPaginator.set('totalRecords',this._oRecordSet.getLength());
13095             }
13096             // Update other paginator values
13097             if (lang.isObject(oPayload.pagination)) {
13098                 oPaginator.set('rowsPerPage',oPayload.pagination.rowsPerPage);
13099                 oPaginator.set('recordOffset',oPayload.pagination.recordOffset);
13100             }
13101         }
13102
13103         // Update sorting
13104         if (oPayload.sortedBy) {
13105             // Set the sorting values in preparation for refresh
13106             this.set('sortedBy', oPayload.sortedBy);
13107         }
13108         // Backwards compatibility for sorting
13109         else if (oPayload.sorting) {
13110             // Set the sorting values in preparation for refresh
13111             this.set('sortedBy', oPayload.sorting);
13112         }
13113     }
13114 },
13115
13116
13117
13118
13119
13120
13121
13122
13123
13124
13125
13126
13127
13128
13129
13130
13131
13132
13133
13134
13135
13136
13137
13138
13139
13140
13141
13142
13143
13144
13145
13146
13147
13148     /////////////////////////////////////////////////////////////////////////////
13149     //
13150     // Custom Events
13151     //
13152     /////////////////////////////////////////////////////////////////////////////
13153
13154     /**
13155      * Fired when the DataTable's rows are rendered from an initialized state.
13156      *
13157      * @event initEvent
13158      */
13159
13160     /**
13161      * Fired when the DataTable's DOM is rendered or modified.
13162      *
13163      * @event renderEvent
13164      */
13165
13166     /**
13167      * Fired when the DataTable's post-render routine is complete, including
13168      * Column width validations.
13169      *
13170      * @event postRenderEvent
13171      */
13172
13173     /**
13174      * Fired when the DataTable is disabled.
13175      *
13176      * @event disableEvent
13177      */
13178
13179     /**
13180      * Fired when the DataTable is undisabled.
13181      *
13182      * @event undisableEvent
13183      */
13184
13185     /**
13186      * Fired when data is returned from DataSource but before it is consumed by
13187      * DataTable.
13188      *
13189      * @event dataReturnEvent
13190      * @param oArgs.request {String} Original request.
13191      * @param oArgs.response {Object} Response object.
13192      */
13193
13194     /**
13195      * Fired when the DataTable has a focus event.
13196      *
13197      * @event tableFocusEvent
13198      */
13199
13200     /**
13201      * Fired when the DataTable THEAD element has a focus event.
13202      *
13203      * @event theadFocusEvent
13204      */
13205
13206     /**
13207      * Fired when the DataTable TBODY element has a focus event.
13208      *
13209      * @event tbodyFocusEvent
13210      */
13211
13212     /**
13213      * Fired when the DataTable has a blur event.
13214      *
13215      * @event tableBlurEvent
13216      */
13217
13218     /*TODO implement theadBlurEvent
13219      * Fired when the DataTable THEAD element has a blur event.
13220      *
13221      * @event theadBlurEvent
13222      */
13223
13224     /*TODO: implement tbodyBlurEvent
13225      * Fired when the DataTable TBODY element has a blur event.
13226      *
13227      * @event tbodyBlurEvent
13228      */
13229
13230     /**
13231      * Fired when the DataTable has a key event.
13232      *
13233      * @event tableKeyEvent
13234      * @param oArgs.event {HTMLEvent} The event object.
13235      * @param oArgs.target {HTMLElement} The DataTable's TABLE element.
13236      */
13237
13238     /**
13239      * Fired when the DataTable THEAD element has a key event.
13240      *
13241      * @event theadKeyEvent
13242      * @param oArgs.event {HTMLEvent} The event object.
13243      * @param oArgs.target {HTMLElement} The DataTable's TABLE element.
13244      */
13245
13246     /**
13247      * Fired when the DataTable TBODY element has a key event.
13248      *
13249      * @event tbodyKeyEvent
13250      * @param oArgs.event {HTMLEvent} The event object.
13251      * @param oArgs.target {HTMLElement} The DataTable's TABLE element.
13252      */
13253
13254     /**
13255      * Fired when the DataTable has a mouseover.
13256      *
13257      * @event tableMouseoverEvent
13258      * @param oArgs.event {HTMLEvent} The event object.
13259      * @param oArgs.target {HTMLElement} The DataTable's TABLE element.
13260      *
13261      */
13262
13263     /**
13264      * Fired when the DataTable has a mouseout.
13265      *
13266      * @event tableMouseoutEvent
13267      * @param oArgs.event {HTMLEvent} The event object.
13268      * @param oArgs.target {HTMLElement} The DataTable's TABLE element.
13269      *
13270      */
13271
13272     /**
13273      * Fired when the DataTable has a mousedown.
13274      *
13275      * @event tableMousedownEvent
13276      * @param oArgs.event {HTMLEvent} The event object.
13277      * @param oArgs.target {HTMLElement} The DataTable's TABLE element.
13278      *
13279      */
13280
13281     /**
13282      * Fired when the DataTable has a mouseup.
13283      *
13284      * @event tableMouseupEvent
13285      * @param oArgs.event {HTMLEvent} The event object.
13286      * @param oArgs.target {HTMLElement} The DataTable's TABLE element.
13287      *
13288      */
13289
13290     /**
13291      * Fired when the DataTable has a click.
13292      *
13293      * @event tableClickEvent
13294      * @param oArgs.event {HTMLEvent} The event object.
13295      * @param oArgs.target {HTMLElement} The DataTable's TABLE element.
13296      *
13297      */
13298
13299     /**
13300      * Fired when the DataTable has a dblclick.
13301      *
13302      * @event tableDblclickEvent
13303      * @param oArgs.event {HTMLEvent} The event object.
13304      * @param oArgs.target {HTMLElement} The DataTable's TABLE element.
13305      *
13306      */
13307
13308     /**
13309      * Fired when a message is shown in the DataTable's message element.
13310      *
13311      * @event tableMsgShowEvent
13312      * @param oArgs.html {String} The HTML displayed.
13313      * @param oArgs.className {String} The className assigned.
13314      *
13315      */
13316
13317     /**
13318      * Fired when the DataTable's message element is hidden.
13319      *
13320      * @event tableMsgHideEvent
13321      */
13322
13323     /**
13324      * Fired when a THEAD row has a mouseover.
13325      *
13326      * @event theadRowMouseoverEvent
13327      * @param oArgs.event {HTMLEvent} The event object.
13328      * @param oArgs.target {HTMLElement} The TR element.
13329      */
13330
13331     /**
13332      * Fired when a THEAD row has a mouseout.
13333      *
13334      * @event theadRowMouseoutEvent
13335      * @param oArgs.event {HTMLEvent} The event object.
13336      * @param oArgs.target {HTMLElement} The TR element.
13337      */
13338
13339     /**
13340      * Fired when a THEAD row has a mousedown.
13341      *
13342      * @event theadRowMousedownEvent
13343      * @param oArgs.event {HTMLEvent} The event object.
13344      * @param oArgs.target {HTMLElement} The TR element.
13345      */
13346
13347     /**
13348      * Fired when a THEAD row has a mouseup.
13349      *
13350      * @event theadRowMouseupEvent
13351      * @param oArgs.event {HTMLEvent} The event object.
13352      * @param oArgs.target {HTMLElement} The TR element.
13353      */
13354
13355     /**
13356      * Fired when a THEAD row has a click.
13357      *
13358      * @event theadRowClickEvent
13359      * @param oArgs.event {HTMLEvent} The event object.
13360      * @param oArgs.target {HTMLElement} The TR element.
13361      */
13362
13363     /**
13364      * Fired when a THEAD row has a dblclick.
13365      *
13366      * @event theadRowDblclickEvent
13367      * @param oArgs.event {HTMLEvent} The event object.
13368      * @param oArgs.target {HTMLElement} The TR element.
13369      */
13370
13371     /**
13372      * Fired when a THEAD cell has a mouseover.
13373      *
13374      * @event theadCellMouseoverEvent
13375      * @param oArgs.event {HTMLEvent} The event object.
13376      * @param oArgs.target {HTMLElement} The TH element.
13377      *
13378      */
13379
13380     /**
13381      * Fired when a THEAD cell has a mouseout.
13382      *
13383      * @event theadCellMouseoutEvent
13384      * @param oArgs.event {HTMLEvent} The event object.
13385      * @param oArgs.target {HTMLElement} The TH element.
13386      *
13387      */
13388
13389     /**
13390      * Fired when a THEAD cell has a mousedown.
13391      *
13392      * @event theadCellMousedownEvent
13393      * @param oArgs.event {HTMLEvent} The event object.
13394      * @param oArgs.target {HTMLElement} The TH element.
13395      */
13396
13397     /**
13398      * Fired when a THEAD cell has a mouseup.
13399      *
13400      * @event theadCellMouseupEvent
13401      * @param oArgs.event {HTMLEvent} The event object.
13402      * @param oArgs.target {HTMLElement} The TH element.
13403      */
13404
13405     /**
13406      * Fired when a THEAD cell has a click.
13407      *
13408      * @event theadCellClickEvent
13409      * @param oArgs.event {HTMLEvent} The event object.
13410      * @param oArgs.target {HTMLElement} The TH element.
13411      */
13412
13413     /**
13414      * Fired when a THEAD cell has a dblclick.
13415      *
13416      * @event theadCellDblclickEvent
13417      * @param oArgs.event {HTMLEvent} The event object.
13418      * @param oArgs.target {HTMLElement} The TH element.
13419      */
13420
13421     /**
13422      * Fired when a THEAD label has a mouseover.
13423      *
13424      * @event theadLabelMouseoverEvent
13425      * @param oArgs.event {HTMLEvent} The event object.
13426      * @param oArgs.target {HTMLElement} The SPAN element.
13427      *
13428      */
13429
13430     /**
13431      * Fired when a THEAD label has a mouseout.
13432      *
13433      * @event theadLabelMouseoutEvent
13434      * @param oArgs.event {HTMLEvent} The event object.
13435      * @param oArgs.target {HTMLElement} The SPAN element.
13436      *
13437      */
13438
13439     /**
13440      * Fired when a THEAD label has a mousedown.
13441      *
13442      * @event theadLabelMousedownEvent
13443      * @param oArgs.event {HTMLEvent} The event object.
13444      * @param oArgs.target {HTMLElement} The SPAN element.
13445      */
13446
13447     /**
13448      * Fired when a THEAD label has a mouseup.
13449      *
13450      * @event theadLabelMouseupEvent
13451      * @param oArgs.event {HTMLEvent} The event object.
13452      * @param oArgs.target {HTMLElement} The SPAN element.
13453      */
13454
13455     /**
13456      * Fired when a THEAD label has a click.
13457      *
13458      * @event theadLabelClickEvent
13459      * @param oArgs.event {HTMLEvent} The event object.
13460      * @param oArgs.target {HTMLElement} The SPAN element.
13461      */
13462
13463     /**
13464      * Fired when a THEAD label has a dblclick.
13465      *
13466      * @event theadLabelDblclickEvent
13467      * @param oArgs.event {HTMLEvent} The event object.
13468      * @param oArgs.target {HTMLElement} The SPAN element.
13469      */
13470
13471     /**
13472      * Fired when a column is sorted.
13473      *
13474      * @event columnSortEvent
13475      * @param oArgs.column {YAHOO.widget.Column} The Column instance.
13476      * @param oArgs.dir {String} Sort direction: YAHOO.widget.DataTable.CLASS_ASC
13477      * or YAHOO.widget.DataTable.CLASS_DESC.
13478      */
13479
13480     /**
13481      * Fired when a column width is set.
13482      *
13483      * @event columnSetWidthEvent
13484      * @param oArgs.column {YAHOO.widget.Column} The Column instance.
13485      * @param oArgs.width {Number} The width in pixels.
13486      */
13487
13488     /**
13489      * Fired when a column width is unset.
13490      *
13491      * @event columnUnsetWidthEvent
13492      * @param oArgs.column {YAHOO.widget.Column} The Column instance.
13493      */
13494
13495     /**
13496      * Fired when a column is drag-resized.
13497      *
13498      * @event columnResizeEvent
13499      * @param oArgs.column {YAHOO.widget.Column} The Column instance.
13500      * @param oArgs.target {HTMLElement} The TH element.
13501      * @param oArgs.width {Number} Width in pixels.     
13502      */
13503
13504     /**
13505      * Fired when a Column is moved to a new index.
13506      *
13507      * @event columnReorderEvent
13508      * @param oArgs.column {YAHOO.widget.Column} The Column instance.
13509      * @param oArgs.oldIndex {Number} The previous index position.
13510      */
13511
13512     /**
13513      * Fired when a column is hidden.
13514      *
13515      * @event columnHideEvent
13516      * @param oArgs.column {YAHOO.widget.Column} The Column instance.
13517      */
13518
13519     /**
13520      * Fired when a column is shown.
13521      *
13522      * @event columnShowEvent
13523      * @param oArgs.column {YAHOO.widget.Column} The Column instance.
13524      */
13525
13526     /**
13527      * Fired when a column is selected.
13528      *
13529      * @event columnSelectEvent
13530      * @param oArgs.column {YAHOO.widget.Column} The Column instance.
13531      */
13532
13533     /**
13534      * Fired when a column is unselected.
13535      *
13536      * @event columnUnselectEvent
13537      * @param oArgs.column {YAHOO.widget.Column} The Column instance.
13538      */
13539     /**
13540      * Fired when a column is removed.
13541      *
13542      * @event columnRemoveEvent
13543      * @param oArgs.column {YAHOO.widget.Column} The Column instance.
13544      */
13545
13546     /**
13547      * Fired when a column is inserted.
13548      *
13549      * @event columnInsertEvent
13550      * @param oArgs.column {YAHOO.widget.Column} The Column instance.
13551      * @param oArgs.index {Number} The index position.
13552      */
13553
13554     /**
13555      * Fired when a column is highlighted.
13556      *
13557      * @event columnHighlightEvent
13558      * @param oArgs.column {YAHOO.widget.Column} The highlighted Column.
13559      */
13560
13561     /**
13562      * Fired when a column is unhighlighted.
13563      *
13564      * @event columnUnhighlightEvent
13565      * @param oArgs.column {YAHOO.widget.Column} The unhighlighted Column.
13566      */
13567
13568
13569     /**
13570      * Fired when a row has a mouseover.
13571      *
13572      * @event rowMouseoverEvent
13573      * @param oArgs.event {HTMLEvent} The event object.
13574      * @param oArgs.target {HTMLElement} The TR element.
13575      */
13576
13577     /**
13578      * Fired when a row has a mouseout.
13579      *
13580      * @event rowMouseoutEvent
13581      * @param oArgs.event {HTMLEvent} The event object.
13582      * @param oArgs.target {HTMLElement} The TR element.
13583      */
13584
13585     /**
13586      * Fired when a row has a mousedown.
13587      *
13588      * @event rowMousedownEvent
13589      * @param oArgs.event {HTMLEvent} The event object.
13590      * @param oArgs.target {HTMLElement} The TR element.
13591      */
13592
13593     /**
13594      * Fired when a row has a mouseup.
13595      *
13596      * @event rowMouseupEvent
13597      * @param oArgs.event {HTMLEvent} The event object.
13598      * @param oArgs.target {HTMLElement} The TR element.
13599      */
13600
13601     /**
13602      * Fired when a row has a click.
13603      *
13604      * @event rowClickEvent
13605      * @param oArgs.event {HTMLEvent} The event object.
13606      * @param oArgs.target {HTMLElement} The TR element.
13607      */
13608
13609     /**
13610      * Fired when a row has a dblclick.
13611      *
13612      * @event rowDblclickEvent
13613      * @param oArgs.event {HTMLEvent} The event object.
13614      * @param oArgs.target {HTMLElement} The TR element.
13615      */
13616
13617     /**
13618      * Fired when a row is added.
13619      *
13620      * @event rowAddEvent
13621      * @param oArgs.record {YAHOO.widget.Record} The added Record.
13622      */
13623      
13624     /**
13625      * Fired when rows are added.
13626      *
13627      * @event rowsAddEvent
13628      * @param oArgs.record {YAHOO.widget.Record[]} The added Records.
13629      */
13630
13631     /**
13632      * Fired when a row is updated.
13633      *
13634      * @event rowUpdateEvent
13635      * @param oArgs.record {YAHOO.widget.Record} The updated Record.
13636      * @param oArgs.oldData {Object} Object literal of the old data.
13637      */
13638
13639     /**
13640      * Fired when a row is deleted.
13641      *
13642      * @event rowDeleteEvent
13643      * @param oArgs.oldData {Object} Object literal of the deleted data.
13644      * @param oArgs.recordIndex {Number} Index of the deleted Record.
13645      * @param oArgs.trElIndex {Number} Index of the deleted TR element, if on current page.
13646      */
13647      
13648     /**
13649      * Fired when rows are deleted.
13650      *
13651      * @event rowsDeleteEvent
13652      * @param oArgs.oldData {Object[]} Array of object literals of the deleted data.
13653      * @param oArgs.recordIndex {Number} Index of the first deleted Record.
13654      * @param oArgs.count {Number} Number of deleted Records.
13655      */
13656
13657     /**
13658      * Fired when a row is selected.
13659      *
13660      * @event rowSelectEvent
13661      * @param oArgs.el {HTMLElement} The selected TR element, if applicable.
13662      * @param oArgs.record {YAHOO.widget.Record} The selected Record.
13663      */
13664
13665     /**
13666      * Fired when a row is unselected.
13667      *
13668      * @event rowUnselectEvent
13669      * @param oArgs.el {HTMLElement} The unselected TR element, if applicable.
13670      * @param oArgs.record {YAHOO.widget.Record} The unselected Record.
13671      */
13672
13673     /**
13674      * Fired when all row selections are cleared.
13675      *
13676      * @event unselectAllRowsEvent
13677      */
13678
13679     /**
13680      * Fired when a row is highlighted.
13681      *
13682      * @event rowHighlightEvent
13683      * @param oArgs.el {HTMLElement} The highlighted TR element.
13684      * @param oArgs.record {YAHOO.widget.Record} The highlighted Record.
13685      */
13686
13687     /**
13688      * Fired when a row is unhighlighted.
13689      *
13690      * @event rowUnhighlightEvent
13691      * @param oArgs.el {HTMLElement} The highlighted TR element.
13692      * @param oArgs.record {YAHOO.widget.Record} The highlighted Record.
13693      */
13694
13695     /**
13696      * Fired when a cell is updated.
13697      *
13698      * @event cellUpdateEvent
13699      * @param oArgs.record {YAHOO.widget.Record} The updated Record.
13700      * @param oArgs.column {YAHOO.widget.Column} The updated Column.
13701      * @param oArgs.oldData {Object} Original data value of the updated cell.
13702      */
13703
13704     /**
13705      * Fired when a cell has a mouseover.
13706      *
13707      * @event cellMouseoverEvent
13708      * @param oArgs.event {HTMLEvent} The event object.
13709      * @param oArgs.target {HTMLElement} The TD element.
13710      */
13711
13712     /**
13713      * Fired when a cell has a mouseout.
13714      *
13715      * @event cellMouseoutEvent
13716      * @param oArgs.event {HTMLEvent} The event object.
13717      * @param oArgs.target {HTMLElement} The TD element.
13718      */
13719
13720     /**
13721      * Fired when a cell has a mousedown.
13722      *
13723      * @event cellMousedownEvent
13724      * @param oArgs.event {HTMLEvent} The event object.
13725      * @param oArgs.target {HTMLElement} The TD element.
13726      */
13727
13728     /**
13729      * Fired when a cell has a mouseup.
13730      *
13731      * @event cellMouseupEvent
13732      * @param oArgs.event {HTMLEvent} The event object.
13733      * @param oArgs.target {HTMLElement} The TD element.
13734      */
13735
13736     /**
13737      * Fired when a cell has a click.
13738      *
13739      * @event cellClickEvent
13740      * @param oArgs.event {HTMLEvent} The event object.
13741      * @param oArgs.target {HTMLElement} The TD element.
13742      */
13743
13744     /**
13745      * Fired when a cell has a dblclick.
13746      *
13747      * @event cellDblclickEvent
13748      * @param oArgs.event {HTMLEvent} The event object.
13749      * @param oArgs.target {HTMLElement} The TD element.
13750      */
13751
13752     /**
13753      * Fired when a cell is formatted.
13754      *
13755      * @event cellFormatEvent
13756      * @param oArgs.el {HTMLElement} The formatted TD element.
13757      * @param oArgs.record {YAHOO.widget.Record} The associated Record instance.
13758      * @param oArgs.column {YAHOO.widget.Column} The associated Column instance.
13759      * @param oArgs.key {String} (deprecated) The key of the formatted cell.
13760      */
13761
13762     /**
13763      * Fired when a cell is selected.
13764      *
13765      * @event cellSelectEvent
13766      * @param oArgs.el {HTMLElement} The selected TD element.
13767      * @param oArgs.record {YAHOO.widget.Record} The associated Record instance.
13768      * @param oArgs.column {YAHOO.widget.Column} The associated Column instance.
13769      * @param oArgs.key {String} (deprecated) The key of the selected cell.
13770      */
13771
13772     /**
13773      * Fired when a cell is unselected.
13774      *
13775      * @event cellUnselectEvent
13776      * @param oArgs.el {HTMLElement} The unselected TD element.
13777      * @param oArgs.record {YAHOO.widget.Record} The associated Record.
13778      * @param oArgs.column {YAHOO.widget.Column} The associated Column instance.
13779      * @param oArgs.key {String} (deprecated) The key of the unselected cell.
13780
13781      */
13782
13783     /**
13784      * Fired when a cell is highlighted.
13785      *
13786      * @event cellHighlightEvent
13787      * @param oArgs.el {HTMLElement} The highlighted TD element.
13788      * @param oArgs.record {YAHOO.widget.Record} The associated Record instance.
13789      * @param oArgs.column {YAHOO.widget.Column} The associated Column instance.
13790      * @param oArgs.key {String} (deprecated) The key of the highlighted cell.
13791
13792      */
13793
13794     /**
13795      * Fired when a cell is unhighlighted.
13796      *
13797      * @event cellUnhighlightEvent
13798      * @param oArgs.el {HTMLElement} The unhighlighted TD element.
13799      * @param oArgs.record {YAHOO.widget.Record} The associated Record instance.
13800      * @param oArgs.column {YAHOO.widget.Column} The associated Column instance.
13801      * @param oArgs.key {String} (deprecated) The key of the unhighlighted cell.
13802
13803      */
13804
13805     /**
13806      * Fired when all cell selections are cleared.
13807      *
13808      * @event unselectAllCellsEvent
13809      */
13810
13811     /**
13812      * Fired when a CellEditor is shown.
13813      *
13814      * @event editorShowEvent
13815      * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
13816      */
13817
13818     /**
13819      * Fired when a CellEditor has a keydown.
13820      *
13821      * @event editorKeydownEvent
13822      * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
13823      * @param oArgs.event {HTMLEvent} The event object.
13824      */
13825
13826     /**
13827      * Fired when a CellEditor input is reverted.
13828      *
13829      * @event editorRevertEvent
13830      * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
13831      * @param oArgs.newData {Object} New data value from form input field.
13832      * @param oArgs.oldData {Object} Old data value.
13833      */
13834
13835     /**
13836      * Fired when a CellEditor input is saved.
13837      *
13838      * @event editorSaveEvent
13839      * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
13840      * @param oArgs.newData {Object} New data value from form input field.
13841      * @param oArgs.oldData {Object} Old data value.
13842      */
13843
13844     /**
13845      * Fired when a CellEditor input is canceled.
13846      *
13847      * @event editorCancelEvent
13848      * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
13849      */
13850
13851     /**
13852      * Fired when a CellEditor has a blur event.
13853      *
13854      * @event editorBlurEvent
13855      * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
13856      */
13857
13858     /**
13859      * Fired when a CellEditor is blocked.
13860      *
13861      * @event editorBlockEvent
13862      * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
13863      */
13864
13865     /**
13866      * Fired when a CellEditor is unblocked.
13867      *
13868      * @event editorUnblockEvent
13869      * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
13870      */
13871
13872
13873
13874
13875
13876     /**
13877      * Fired when a link is clicked.
13878      *
13879      * @event linkClickEvent
13880      * @param oArgs.event {HTMLEvent} The event object.
13881      * @param oArgs.target {HTMLElement} The A element.
13882      */
13883
13884     /**
13885      * Fired when a BUTTON element or INPUT element of type "button", "image",
13886      * "submit", "reset" is clicked.
13887      *
13888      * @event buttonClickEvent
13889      * @param oArgs.event {HTMLEvent} The event object.
13890      * @param oArgs.target {HTMLElement} The BUTTON element.
13891      */
13892
13893     /**
13894      * Fired when a CHECKBOX element is clicked.
13895      *
13896      * @event checkboxClickEvent
13897      * @param oArgs.event {HTMLEvent} The event object.
13898      * @param oArgs.target {HTMLElement} The CHECKBOX element.
13899      */
13900
13901     /**
13902      * Fired when a SELECT element is changed.
13903      *
13904      * @event dropdownChangeEvent
13905      * @param oArgs.event {HTMLEvent} The event object.
13906      * @param oArgs.target {HTMLElement} The SELECT element.
13907      */
13908
13909     /**
13910      * Fired when a RADIO element is clicked.
13911      *
13912      * @event radioClickEvent
13913      * @param oArgs.event {HTMLEvent} The event object.
13914      * @param oArgs.target {HTMLElement} The RADIO element.
13915      */
13916
13917
13918
13919
13920
13921
13922
13923
13924
13925
13926
13927
13928
13929
13930
13931
13932
13933
13934
13935
13936
13937
13938
13939
13940
13941
13942 /////////////////////////////////////////////////////////////////////////////
13943 //
13944 // Deprecated APIs
13945 //
13946 /////////////////////////////////////////////////////////////////////////////
13947   
13948 /*
13949  * @method showCellEditorBtns
13950  * @deprecated Use CellEditor.renderBtns() 
13951  */
13952 showCellEditorBtns : function(elContainer) {
13953     // Buttons
13954     var elBtnsDiv = elContainer.appendChild(document.createElement("div"));
13955     Dom.addClass(elBtnsDiv, DT.CLASS_BUTTON);
13956
13957     // Save button
13958     var elSaveBtn = elBtnsDiv.appendChild(document.createElement("button"));
13959     Dom.addClass(elSaveBtn, DT.CLASS_DEFAULT);
13960     elSaveBtn.innerHTML = "OK";
13961     Ev.addListener(elSaveBtn, "click", function(oArgs, oSelf) {
13962         oSelf.onEventSaveCellEditor(oArgs, oSelf);
13963         oSelf.focusTbodyEl();
13964     }, this, true);
13965
13966     // Cancel button
13967     var elCancelBtn = elBtnsDiv.appendChild(document.createElement("button"));
13968     elCancelBtn.innerHTML = "Cancel";
13969     Ev.addListener(elCancelBtn, "click", function(oArgs, oSelf) {
13970         oSelf.onEventCancelCellEditor(oArgs, oSelf);
13971         oSelf.focusTbodyEl();
13972     }, this, true);
13973
13974     YAHOO.log("The method showCellEditorBtns() has been deprecated." +
13975             " Please use the CellEditor class.", "warn", this.toString());
13976 },
13977
13978 /**
13979  * @method resetCellEditor
13980  * @deprecated Use destroyCellEditor 
13981  */
13982 resetCellEditor : function() {
13983     var elContainer = this._oCellEditor.container;
13984     elContainer.style.display = "none";
13985     Ev.purgeElement(elContainer, true);
13986     elContainer.innerHTML = "";
13987     this._oCellEditor.value = null;
13988     this._oCellEditor.isActive = false;
13989
13990     YAHOO.log("The method resetCellEditor() has been deprecated." +
13991             " Please use the CellEditor class.", "warn", this.toString());
13992 },
13993
13994 /**
13995  * @event editorUpdateEvent
13996  * @deprecated Use CellEditor class.
13997  */
13998
13999 /**
14000  * @method getBody
14001  * @deprecated Use getTbodyEl().
14002  */
14003 getBody : function() {
14004     // Backward compatibility
14005     YAHOO.log("The method getBody() has been deprecated" +
14006             " in favor of getTbodyEl()", "warn", this.toString());
14007     return this.getTbodyEl();
14008 },
14009
14010 /**
14011  * @method getCell
14012  * @deprecated Use getTdEl().
14013  */
14014 getCell : function(index) {
14015     // Backward compatibility
14016     YAHOO.log("The method getCell() has been deprecated" +
14017             " in favor of getTdEl()", "warn", this.toString());
14018     return this.getTdEl(index);
14019 },
14020
14021 /**
14022  * @method getRow
14023  * @deprecated Use getTrEl().
14024  */
14025 getRow : function(index) {
14026     // Backward compatibility
14027     YAHOO.log("The method getRow() has been deprecated" +
14028             " in favor of getTrEl()", "warn", this.toString());
14029     return this.getTrEl(index);
14030 },
14031
14032 /**
14033  * @method refreshView
14034  * @deprecated Use render.
14035  */
14036 refreshView : function() {
14037     // Backward compatibility
14038     YAHOO.log("The method refreshView() has been deprecated" +
14039             " in favor of render()", "warn", this.toString());
14040     this.render();
14041 },
14042
14043 /**
14044  * @method select
14045  * @deprecated Use selectRow.
14046  */
14047 select : function(els) {
14048     // Backward compatibility
14049     YAHOO.log("The method select() has been deprecated" +
14050             " in favor of selectRow()", "warn", this.toString());
14051     if(!lang.isArray(els)) {
14052         els = [els];
14053     }
14054     for(var i=0; i<els.length; i++) {
14055         this.selectRow(els[i]);
14056     }
14057 },
14058
14059 /**
14060  * @method onEventEditCell
14061  * @deprecated Use onEventShowCellEditor.
14062  */
14063 onEventEditCell : function(oArgs) {
14064     // Backward compatibility
14065     YAHOO.log("The method onEventEditCell() has been deprecated" +
14066         " in favor of onEventShowCellEditor()", "warn", this.toString());
14067     this.onEventShowCellEditor(oArgs);
14068 },
14069
14070 /**
14071  * @method _syncColWidths
14072  * @deprecated Use validateColumnWidths.
14073  */
14074 _syncColWidths : function() {
14075     // Backward compatibility
14076     YAHOO.log("The method _syncColWidths() has been deprecated" +
14077         " in favor of validateColumnWidths()", "warn", this.toString());
14078     this.validateColumnWidths();
14079 }
14080
14081 /**
14082  * @event headerRowMouseoverEvent
14083  * @deprecated Use theadRowMouseoverEvent.
14084  */
14085
14086 /**
14087  * @event headerRowMouseoutEvent
14088  * @deprecated Use theadRowMouseoutEvent.
14089  */
14090
14091 /**
14092  * @event headerRowMousedownEvent
14093  * @deprecated Use theadRowMousedownEvent.
14094  */
14095
14096 /**
14097  * @event headerRowClickEvent
14098  * @deprecated Use theadRowClickEvent.
14099  */
14100
14101 /**
14102  * @event headerRowDblclickEvent
14103  * @deprecated Use theadRowDblclickEvent.
14104  */
14105
14106 /**
14107  * @event headerCellMouseoverEvent
14108  * @deprecated Use theadCellMouseoverEvent.
14109  */
14110
14111 /**
14112  * @event headerCellMouseoutEvent
14113  * @deprecated Use theadCellMouseoutEvent.
14114  */
14115
14116 /**
14117  * @event headerCellMousedownEvent
14118  * @deprecated Use theadCellMousedownEvent.
14119  */
14120
14121 /**
14122  * @event headerCellClickEvent
14123  * @deprecated Use theadCellClickEvent.
14124  */
14125
14126 /**
14127  * @event headerCellDblclickEvent
14128  * @deprecated Use theadCellDblclickEvent.
14129  */
14130
14131 /**
14132  * @event headerLabelMouseoverEvent
14133  * @deprecated Use theadLabelMouseoverEvent.
14134  */
14135
14136 /**
14137  * @event headerLabelMouseoutEvent
14138  * @deprecated Use theadLabelMouseoutEvent.
14139  */
14140
14141 /**
14142  * @event headerLabelMousedownEvent
14143  * @deprecated Use theadLabelMousedownEvent.
14144  */
14145
14146 /**
14147  * @event headerLabelClickEvent
14148  * @deprecated Use theadLabelClickEvent.
14149  */
14150
14151 /**
14152  * @event headerLabelDbllickEvent
14153  * @deprecated Use theadLabelDblclickEvent.
14154  */
14155
14156 });
14157
14158 /**
14159  * Alias for onDataReturnSetRows for backward compatibility
14160  * @method onDataReturnSetRecords
14161  * @deprecated Use onDataReturnSetRows
14162  */
14163 DT.prototype.onDataReturnSetRecords = DT.prototype.onDataReturnSetRows;
14164
14165 /**
14166  * Alias for onPaginatorChange for backward compatibility
14167  * @method onPaginatorChange
14168  * @deprecated Use onPaginatorChangeRequest
14169  */
14170 DT.prototype.onPaginatorChange = DT.prototype.onPaginatorChangeRequest;
14171
14172 /////////////////////////////////////////////////////////////////////////////
14173 //
14174 // Deprecated static APIs
14175 //
14176 /////////////////////////////////////////////////////////////////////////////
14177 /**
14178  * @method DataTable.formatTheadCell
14179  * @deprecated  Use formatTheadCell.
14180  */
14181 DT.formatTheadCell = function() {};
14182
14183 /**
14184  * @method DataTable.editCheckbox
14185  * @deprecated  Use YAHOO.widget.CheckboxCellEditor.
14186  */
14187 DT.editCheckbox = function() {};
14188
14189 /**
14190  * @method DataTable.editDate
14191  * @deprecated Use YAHOO.widget.DateCellEditor.
14192  */
14193 DT.editDate = function() {};
14194
14195 /**
14196  * @method DataTable.editDropdown
14197  * @deprecated Use YAHOO.widget.DropdownCellEditor.
14198  */
14199 DT.editDropdown = function() {};
14200
14201 /**
14202  * @method DataTable.editRadio
14203  * @deprecated Use YAHOO.widget.RadioCellEditor.
14204  */
14205 DT.editRadio = function() {};
14206
14207 /**
14208  * @method DataTable.editTextarea
14209  * @deprecated Use YAHOO.widget.TextareaCellEditor
14210  */
14211 DT.editTextarea = function() {};
14212
14213 /**
14214  * @method DataTable.editTextbox
14215  * @deprecated Use YAHOO.widget.TextboxCellEditor
14216  */
14217 DT.editTextbox= function() {};
14218
14219 })();
14220
14221 (function () {
14222
14223 var lang   = YAHOO.lang,
14224     util   = YAHOO.util,
14225     widget = YAHOO.widget,
14226     ua     = YAHOO.env.ua,
14227     
14228     Dom    = util.Dom,
14229     Ev     = util.Event,
14230     DS     = util.DataSourceBase,
14231     DT     = widget.DataTable,
14232     Pag    = widget.Paginator;
14233     
14234 /**
14235  * The ScrollingDataTable class extends the DataTable class to provide
14236  * functionality for x-scrolling, y-scrolling, and xy-scrolling.
14237  *
14238  * @namespace YAHOO.widget
14239  * @class ScrollingDataTable
14240  * @extends YAHOO.widget.DataTable
14241  * @constructor
14242  * @param elContainer {HTMLElement} Container element for the TABLE.
14243  * @param aColumnDefs {Object[]} Array of object literal Column definitions.
14244  * @param oDataSource {YAHOO.util.DataSource} DataSource instance.
14245  * @param oConfigs {object} (optional) Object literal of configuration values.
14246  */
14247 widget.ScrollingDataTable = function(elContainer,aColumnDefs,oDataSource,oConfigs) {
14248     oConfigs = oConfigs || {};
14249     
14250     // Prevent infinite loop
14251     if(oConfigs.scrollable) {
14252         oConfigs.scrollable = false;
14253     }
14254
14255     widget.ScrollingDataTable.superclass.constructor.call(this, elContainer,aColumnDefs,oDataSource,oConfigs); 
14256
14257     // Once per instance
14258     this.subscribe("columnShowEvent", this._onColumnChange);
14259 };
14260
14261 var SDT = widget.ScrollingDataTable;
14262
14263 /////////////////////////////////////////////////////////////////////////////
14264 //
14265 // Public constants
14266 //
14267 /////////////////////////////////////////////////////////////////////////////
14268 lang.augmentObject(SDT, {
14269
14270     /**
14271      * Class name assigned to inner DataTable header container.
14272      *
14273      * @property DataTable.CLASS_HEADER
14274      * @type String
14275      * @static
14276      * @final
14277      * @default "yui-dt-hd"
14278      */
14279     CLASS_HEADER : "yui-dt-hd",
14280     
14281     /**
14282      * Class name assigned to inner DataTable body container.
14283      *
14284      * @property DataTable.CLASS_BODY
14285      * @type String
14286      * @static
14287      * @final
14288      * @default "yui-dt-bd"
14289      */
14290     CLASS_BODY : "yui-dt-bd"
14291 });
14292
14293 lang.extend(SDT, DT, {
14294
14295 /**
14296  * Container for fixed header TABLE element.
14297  *
14298  * @property _elHdContainer
14299  * @type HTMLElement
14300  * @private
14301  */
14302 _elHdContainer : null,
14303
14304 /**
14305  * Fixed header TABLE element.
14306  *
14307  * @property _elHdTable
14308  * @type HTMLElement
14309  * @private
14310  */
14311 _elHdTable : null,
14312
14313 /**
14314  * Container for scrolling body TABLE element.
14315  *
14316  * @property _elBdContainer
14317  * @type HTMLElement
14318  * @private
14319  */
14320 _elBdContainer : null,
14321
14322 /**
14323  * Body THEAD element.
14324  *
14325  * @property _elBdThead
14326  * @type HTMLElement
14327  * @private
14328  */
14329 _elBdThead : null,
14330
14331 /**
14332  * Offscreen container to temporarily clone SDT for auto-width calculation.
14333  *
14334  * @property _elTmpContainer
14335  * @type HTMLElement
14336  * @private
14337  */
14338 _elTmpContainer : null,
14339
14340 /**
14341  * Offscreen TABLE element for auto-width calculation.
14342  *
14343  * @property _elTmpTable
14344  * @type HTMLElement
14345  * @private
14346  */
14347 _elTmpTable : null,
14348
14349 /**
14350  * True if x-scrollbar is currently visible.
14351  * @property _bScrollbarX
14352  * @type Boolean
14353  * @private 
14354  */
14355 _bScrollbarX : null,
14356
14357
14358
14359
14360
14361
14362
14363
14364
14365
14366
14367
14368
14369
14370
14371 /////////////////////////////////////////////////////////////////////////////
14372 //
14373 // Superclass methods
14374 //
14375 /////////////////////////////////////////////////////////////////////////////
14376
14377 /**
14378  * Implementation of Element's abstract method. Sets up config values.
14379  *
14380  * @method initAttributes
14381  * @param oConfigs {Object} (Optional) Object literal definition of configuration values.
14382  * @private
14383  */
14384
14385 initAttributes : function(oConfigs) {
14386     oConfigs = oConfigs || {};
14387     SDT.superclass.initAttributes.call(this, oConfigs);
14388
14389     /**
14390     * @attribute width
14391     * @description Table width for scrollable tables (e.g., "40em").
14392     * @type String
14393     */
14394     this.setAttributeConfig("width", {
14395         value: null,
14396         validator: lang.isString,
14397         method: function(oParam) {
14398             if(this._elHdContainer && this._elBdContainer) {
14399                 this._elHdContainer.style.width = oParam;
14400                 this._elBdContainer.style.width = oParam;            
14401                 this._syncScrollX();      
14402                 this._syncScrollOverhang();
14403             }
14404         }
14405     });
14406
14407     /**
14408     * @attribute height
14409     * @description Table body height for scrollable tables, not including headers (e.g., "40em").
14410     * @type String
14411     */
14412     this.setAttributeConfig("height", {
14413         value: null,
14414         validator: lang.isString,
14415         method: function(oParam) {
14416             if(this._elHdContainer && this._elBdContainer) {
14417                 this._elBdContainer.style.height = oParam;    
14418                 this._syncScrollX();   
14419                 this._syncScrollY();
14420                 this._syncScrollOverhang();
14421             }
14422         }
14423     });
14424
14425     /**
14426     * @attribute COLOR_COLUMNFILLER
14427     * @description CSS color value assigned to header filler on scrollable tables.  
14428     * @type String
14429     * @default "#F2F2F2"
14430     */
14431     this.setAttributeConfig("COLOR_COLUMNFILLER", {
14432         value: "#F2F2F2",
14433         validator: lang.isString,
14434         method: function(oParam) {
14435             this._elHdContainer.style.backgroundColor = oParam;
14436         }
14437     });
14438 },
14439
14440 /**
14441  * Initializes DOM elements for a ScrollingDataTable, including creation of
14442  * two separate TABLE elements.
14443  *
14444  * @method _initDomElements
14445  * @param elContainer {HTMLElement | String} HTML DIV element by reference or ID. 
14446  * return {Boolean} False in case of error, otherwise true 
14447  * @private
14448  */
14449 _initDomElements : function(elContainer) {
14450     // Outer and inner containers
14451     this._initContainerEl(elContainer);
14452     if(this._elContainer && this._elHdContainer && this._elBdContainer) {
14453         // TABLEs
14454         this._initTableEl();
14455         
14456         if(this._elHdTable && this._elTable) {
14457             // COLGROUPs
14458             ///this._initColgroupEl(this._elHdTable, this._elTable);  
14459             this._initColgroupEl(this._elHdTable);        
14460             
14461             // THEADs
14462             this._initTheadEl(this._elHdTable, this._elTable);
14463             
14464             // Primary TBODY
14465             this._initTbodyEl(this._elTable);
14466             // Message TBODY
14467             this._initMsgTbodyEl(this._elTable);            
14468         }
14469     }
14470     if(!this._elContainer || !this._elTable || !this._elColgroup ||  !this._elThead || !this._elTbody || !this._elMsgTbody ||
14471             !this._elHdTable || !this._elBdThead) {
14472         YAHOO.log("Could not instantiate DataTable due to an invalid DOM elements", "error", this.toString());
14473         return false;
14474     }
14475     else {
14476         return true;
14477     }
14478 },
14479
14480 /**
14481  * Destroy's the DataTable outer and inner container elements, if available.
14482  *
14483  * @method _destroyContainerEl
14484  * @param elContainer {HTMLElement} Reference to the container element. 
14485  * @private
14486  */
14487 _destroyContainerEl : function(elContainer) {
14488     Dom.removeClass(elContainer, DT.CLASS_SCROLLABLE);
14489     SDT.superclass._destroyContainerEl.call(this, elContainer);
14490     this._elHdContainer = null;
14491     this._elBdContainer = null;
14492 },
14493
14494 /**
14495  * Initializes the DataTable outer container element and creates inner header
14496  * and body container elements.
14497  *
14498  * @method _initContainerEl
14499  * @param elContainer {HTMLElement | String} HTML DIV element by reference or ID.
14500  * @private
14501  */
14502 _initContainerEl : function(elContainer) {
14503     SDT.superclass._initContainerEl.call(this, elContainer);
14504     
14505     if(this._elContainer) {
14506         elContainer = this._elContainer; // was constructor input, now is DOM ref
14507         Dom.addClass(elContainer, DT.CLASS_SCROLLABLE);
14508         
14509         // Container for header TABLE
14510         var elHdContainer = document.createElement("div");
14511         elHdContainer.style.width = this.get("width") || "";
14512         elHdContainer.style.backgroundColor = this.get("COLOR_COLUMNFILLER");
14513         Dom.addClass(elHdContainer, SDT.CLASS_HEADER);
14514         this._elHdContainer = elHdContainer;
14515         elContainer.appendChild(elHdContainer);
14516     
14517         // Container for body TABLE
14518         var elBdContainer = document.createElement("div");
14519         elBdContainer.style.width = this.get("width") || "";
14520         elBdContainer.style.height = this.get("height") || "";
14521         Dom.addClass(elBdContainer, SDT.CLASS_BODY);
14522         Ev.addListener(elBdContainer, "scroll", this._onScroll, this); // to sync horiz scroll headers
14523         this._elBdContainer = elBdContainer;
14524         elContainer.appendChild(elBdContainer);
14525     }
14526 },
14527
14528 /**
14529  * Creates HTML markup CAPTION element.
14530  *
14531  * @method _initCaptionEl
14532  * @param sCaption {String} Text for caption.
14533  * @private
14534  */
14535 _initCaptionEl : function(sCaption) {
14536     // Not yet supported
14537     /*if(this._elHdTable && sCaption) {
14538         // Create CAPTION element
14539         if(!this._elCaption) { 
14540             this._elCaption = this._elHdTable.createCaption();
14541         }
14542         // Set CAPTION value
14543         this._elCaption.innerHTML = sCaption;
14544     }
14545     else if(this._elCaption) {
14546         this._elCaption.parentNode.removeChild(this._elCaption);
14547     }*/
14548 },
14549
14550 /**
14551  * Destroy's the DataTable head TABLE element, if available.
14552  *
14553  * @method _destroyHdTableEl
14554  * @private
14555  */
14556 _destroyHdTableEl : function() {
14557     var elTable = this._elHdTable;
14558     if(elTable) {
14559         Ev.purgeElement(elTable, true);
14560         elTable.parentNode.removeChild(elTable);
14561         
14562         // A little out of place, but where else can we null out these extra elements?
14563         ///this._elBdColgroup = null;
14564         this._elBdThead = null;
14565     }
14566 },
14567
14568 /**
14569  * Initializes ScrollingDataTable TABLE elements into the two inner containers.
14570  *
14571  * @method _initTableEl
14572  * @private
14573  */
14574 _initTableEl : function() {
14575     // Head TABLE
14576     if(this._elHdContainer) {
14577         this._destroyHdTableEl();
14578     
14579         // Create TABLE
14580         this._elHdTable = this._elHdContainer.appendChild(document.createElement("table"));   
14581     } 
14582     // Body TABLE
14583     SDT.superclass._initTableEl.call(this, this._elBdContainer);
14584 },
14585
14586 /**
14587  * Initializes ScrollingDataTable THEAD elements into the two inner containers.
14588  *
14589  * @method _initTheadEl
14590  * @param elHdTable {HTMLElement} (optional) Fixed header TABLE element reference.
14591  * @param elTable {HTMLElement} (optional) TABLE element reference.
14592  * @private
14593  */
14594 _initTheadEl : function(elHdTable, elTable) {
14595     elHdTable = elHdTable || this._elHdTable;
14596     elTable = elTable || this._elTable;
14597     
14598     // Scrolling body's THEAD
14599     this._initBdTheadEl(elTable);
14600     // Standard fixed head THEAD
14601     SDT.superclass._initTheadEl.call(this, elHdTable);
14602 },
14603
14604 /**
14605  * SDT changes ID so as not to duplicate the accessibility TH IDs.
14606  *
14607  * @method _initThEl
14608  * @param elTh {HTMLElement} TH element reference.
14609  * @param oColumn {YAHOO.widget.Column} Column object.
14610  * @private
14611  */
14612 _initThEl : function(elTh, oColumn) {
14613     SDT.superclass._initThEl.call(this, elTh, oColumn);
14614     elTh.id = this.getId() +"-fixedth-" + oColumn.getSanitizedKey(); // Needed for getColumn by TH and ColumnDD
14615 },
14616
14617 /**
14618  * Destroy's the DataTable body THEAD element, if available.
14619  *
14620  * @method _destroyBdTheadEl
14621  * @private
14622  */
14623 _destroyBdTheadEl : function() {
14624     var elBdThead = this._elBdThead;
14625     if(elBdThead) {
14626         var elTable = elBdThead.parentNode;
14627         Ev.purgeElement(elBdThead, true);
14628         elTable.removeChild(elBdThead);
14629         this._elBdThead = null;
14630
14631         this._destroyColumnHelpers();
14632     }
14633 },
14634
14635 /**
14636  * Initializes body THEAD element.
14637  *
14638  * @method _initBdTheadEl
14639  * @param elTable {HTMLElement} TABLE element into which to create THEAD.
14640  * @return {HTMLElement} Initialized THEAD element. 
14641  * @private
14642  */
14643 _initBdTheadEl : function(elTable) {
14644     if(elTable) {
14645         // Destroy previous
14646         this._destroyBdTheadEl();
14647
14648         var elThead = elTable.insertBefore(document.createElement("thead"), elTable.firstChild);
14649         
14650         // Add TRs to the THEAD;
14651         var oColumnSet = this._oColumnSet,
14652             colTree = oColumnSet.tree,
14653             elTh, elTheadTr, oColumn, i, j, k, len;
14654
14655         for(i=0, k=colTree.length; i<k; i++) {
14656             elTheadTr = elThead.appendChild(document.createElement("tr"));
14657     
14658             // ...and create TH cells
14659             for(j=0, len=colTree[i].length; j<len; j++) {
14660                 oColumn = colTree[i][j];
14661                 elTh = elTheadTr.appendChild(document.createElement("th"));
14662                 this._initBdThEl(elTh,oColumn,i,j);
14663             }
14664         }
14665         this._elBdThead = elThead;
14666         YAHOO.log("Accessibility TH cells for " + this._oColumnSet.keys.length + " keys created","info",this.toString());
14667     }
14668 },
14669
14670 /**
14671  * Populates TH element for the body THEAD element.
14672  *
14673  * @method _initBdThEl
14674  * @param elTh {HTMLElement} TH element reference.
14675  * @param oColumn {YAHOO.widget.Column} Column object.
14676  * @private
14677  */
14678 _initBdThEl : function(elTh, oColumn) {
14679     elTh.id = this.getId()+"-th-" + oColumn.getSanitizedKey(); // Needed for accessibility
14680     elTh.rowSpan = oColumn.getRowspan();
14681     elTh.colSpan = oColumn.getColspan();
14682     // Assign abbr attribute
14683     if(oColumn.abbr) {
14684         elTh.abbr = oColumn.abbr;
14685     }
14686
14687     // TODO: strip links and form elements
14688     var sKey = oColumn.getKey();
14689     var sLabel = lang.isValue(oColumn.label) ? oColumn.label : sKey;
14690     elTh.innerHTML = sLabel;
14691 },
14692
14693 /**
14694  * Initializes ScrollingDataTable TBODY element for data
14695  *
14696  * @method _initTbodyEl
14697  * @param elTable {HTMLElement} TABLE element into which to create TBODY .
14698  * @private
14699  */
14700 _initTbodyEl : function(elTable) {
14701     SDT.superclass._initTbodyEl.call(this, elTable);
14702     
14703     // Bug 2105534 - Safari 3 gap
14704     // Bug 2492591 - IE8 offsetTop
14705     elTable.style.marginTop = (this._elTbody.offsetTop > 0) ?
14706             "-"+this._elTbody.offsetTop+"px" : 0;
14707 },
14708
14709
14710
14711
14712
14713
14714
14715
14716
14717
14718
14719
14720
14721
14722
14723
14724
14725
14726
14727
14728
14729
14730
14731
14732
14733
14734
14735
14736
14737 /**
14738  * Sets focus on the given element.
14739  *
14740  * @method _focusEl
14741  * @param el {HTMLElement} Element.
14742  * @private
14743  */
14744 _focusEl : function(el) {
14745     el = el || this._elTbody;
14746     var oSelf = this;
14747     this._storeScrollPositions();
14748     // http://developer.mozilla.org/en/docs/index.php?title=Key-navigable_custom_DHTML_widgets
14749     // The timeout is necessary in both IE and Firefox 1.5, to prevent scripts from doing
14750     // strange unexpected things as the user clicks on buttons and other controls.
14751     
14752     // Bug 1921135: Wrap the whole thing in a setTimeout
14753     setTimeout(function() {
14754         setTimeout(function() {
14755             try {
14756                 el.focus();
14757                 oSelf._restoreScrollPositions();
14758             }
14759             catch(e) {
14760             }
14761         },0);
14762     }, 0);
14763 },
14764
14765
14766
14767
14768
14769
14770
14771
14772
14773
14774
14775
14776
14777
14778
14779
14780
14781
14782
14783 /**
14784  * Internal wrapper calls run() on render Chain instance.
14785  *
14786  * @method _runRenderChain
14787  * @private 
14788  */
14789 _runRenderChain : function() {
14790     this._storeScrollPositions();
14791     this._oChainRender.run();
14792 },
14793
14794 /**
14795  * Stores scroll positions so they can be restored after a render. 
14796  *
14797  * @method _storeScrollPositions
14798  * @private 
14799  */
14800  _storeScrollPositions : function() {
14801     this._nScrollTop = this._elBdContainer.scrollTop;
14802     this._nScrollLeft = this._elBdContainer.scrollLeft;
14803 },
14804
14805 /**
14806  * Restores scroll positions to stored value. 
14807  *
14808  * @method _retoreScrollPositions
14809  * @private 
14810  */
14811  _restoreScrollPositions : function() {
14812     // Reset scroll positions
14813     if(this._nScrollTop) {
14814         this._elBdContainer.scrollTop = this._nScrollTop;
14815         this._nScrollTop = null;
14816     } 
14817     if(this._nScrollLeft) {
14818         this._elBdContainer.scrollLeft = this._nScrollLeft;
14819         this._nScrollLeft = null;
14820     } 
14821 },
14822
14823 /**
14824  * Helper function calculates and sets a validated width for a Column in a ScrollingDataTable.
14825  *
14826  * @method _validateColumnWidth
14827  * @param oColumn {YAHOO.widget.Column} Column instance.
14828  * @param elTd {HTMLElement} TD element to validate against.
14829  * @private
14830  */
14831 _validateColumnWidth : function(oColumn, elTd) {
14832     // Only Columns without widths that are not hidden
14833     if(!oColumn.width && !oColumn.hidden) {
14834         var elTh = oColumn.getThEl();
14835         // Unset a calculated auto-width
14836         if(oColumn._calculatedWidth) {
14837             this._setColumnWidth(oColumn, "auto", "visible");
14838         }
14839         // Compare auto-widths
14840         if(elTh.offsetWidth !== elTd.offsetWidth) {
14841             var elWider = (elTh.offsetWidth > elTd.offsetWidth) ?
14842                     oColumn.getThLinerEl() : elTd.firstChild;               
14843
14844             // Grab the wider liner width, unless the minWidth is wider
14845             var newWidth = Math.max(0,
14846                 (elWider.offsetWidth -(parseInt(Dom.getStyle(elWider,"paddingLeft"),10)|0) - (parseInt(Dom.getStyle(elWider,"paddingRight"),10)|0)),
14847                 oColumn.minWidth);
14848                 
14849             var sOverflow = 'visible';
14850             
14851             // Now validate against maxAutoWidth
14852             if((oColumn.maxAutoWidth > 0) && (newWidth > oColumn.maxAutoWidth)) {
14853                 newWidth = oColumn.maxAutoWidth;
14854                 sOverflow = "hidden";
14855             }
14856
14857             // Set to the wider auto-width
14858             this._elTbody.style.display = "none";
14859             this._setColumnWidth(oColumn, newWidth+'px', sOverflow);
14860             oColumn._calculatedWidth = newWidth;
14861             this._elTbody.style.display = "";
14862         }
14863     }
14864 },
14865
14866 /**
14867  * For one or all Columns of a ScrollingDataTable, when Column is not hidden,
14868  * and width is not set, syncs widths of header and body cells and 
14869  * validates that width against minWidth and/or maxAutoWidth as necessary.
14870  *
14871  * @method validateColumnWidths
14872  * @param oArg.column {YAHOO.widget.Column} (optional) One Column to validate. If null, all Columns' widths are validated.
14873  */
14874 validateColumnWidths : function(oColumn) {
14875     // Validate there is at least one TR with proper TDs
14876     var allKeys   = this._oColumnSet.keys,
14877         allKeysLength = allKeys.length,
14878         elRow     = this.getFirstTrEl();
14879
14880     // Reset overhang for IE
14881     if(ua.ie) {
14882         this._setOverhangValue(1);
14883     }
14884
14885     if(allKeys && elRow && (elRow.childNodes.length === allKeysLength)) {
14886         // Temporarily unsnap container since it causes inaccurate calculations
14887         var sWidth = this.get("width");
14888         if(sWidth) {
14889             this._elHdContainer.style.width = "";
14890             this._elBdContainer.style.width = "";
14891         }
14892         this._elContainer.style.width = "";
14893         
14894         //Validate just one Column
14895         if(oColumn && lang.isNumber(oColumn.getKeyIndex())) {
14896             this._validateColumnWidth(oColumn, elRow.childNodes[oColumn.getKeyIndex()]);
14897         }
14898         // Iterate through all Columns to unset calculated widths in one pass
14899         else {
14900             var elTd, todos = [], thisTodo, i, len;
14901             for(i=0; i<allKeysLength; i++) {
14902                 oColumn = allKeys[i];
14903                 // Only Columns without widths that are not hidden, unset a calculated auto-width
14904                 if(!oColumn.width && !oColumn.hidden && oColumn._calculatedWidth) {
14905                     todos[todos.length] = oColumn;      
14906                 }
14907             }
14908             
14909             this._elTbody.style.display = "none";
14910             for(i=0, len=todos.length; i<len; i++) {
14911                 this._setColumnWidth(todos[i], "auto", "visible");
14912             }
14913             this._elTbody.style.display = "";
14914             
14915             todos = [];
14916
14917             // Iterate through all Columns and make the store the adjustments to make in one pass
14918             for(i=0; i<allKeysLength; i++) {
14919                 oColumn = allKeys[i];
14920                 elTd = elRow.childNodes[i];
14921                 // Only Columns without widths that are not hidden
14922                 if(!oColumn.width && !oColumn.hidden) {
14923                     var elTh = oColumn.getThEl();
14924
14925                     // Compare auto-widths
14926                     if(elTh.offsetWidth !== elTd.offsetWidth) {
14927                         var elWider = (elTh.offsetWidth > elTd.offsetWidth) ?
14928                                 oColumn.getThLinerEl() : elTd.firstChild;               
14929                 
14930                         // Grab the wider liner width, unless the minWidth is wider
14931                         var newWidth = Math.max(0,
14932                             (elWider.offsetWidth -(parseInt(Dom.getStyle(elWider,"paddingLeft"),10)|0) - (parseInt(Dom.getStyle(elWider,"paddingRight"),10)|0)),
14933                             oColumn.minWidth);
14934                             
14935                         var sOverflow = 'visible';
14936                         
14937                         // Now validate against maxAutoWidth
14938                         if((oColumn.maxAutoWidth > 0) && (newWidth > oColumn.maxAutoWidth)) {
14939                             newWidth = oColumn.maxAutoWidth;
14940                             sOverflow = "hidden";
14941                         }
14942                 
14943                         todos[todos.length] = [oColumn, newWidth, sOverflow];
14944                     }
14945                 }
14946             }
14947             
14948             this._elTbody.style.display = "none";
14949             for(i=0, len=todos.length; i<len; i++) {
14950                 thisTodo = todos[i];
14951                 // Set to the wider auto-width
14952                 this._setColumnWidth(thisTodo[0], thisTodo[1]+"px", thisTodo[2]);
14953                 thisTodo[0]._calculatedWidth = thisTodo[1];
14954             }
14955             this._elTbody.style.display = "";
14956         }
14957     
14958         // Resnap unsnapped containers
14959         if(sWidth) {
14960             this._elHdContainer.style.width = sWidth;
14961             this._elBdContainer.style.width = sWidth;
14962         } 
14963     }
14964     
14965     this._syncScroll();
14966     this._restoreScrollPositions();
14967 },
14968
14969 /**
14970  * Syncs padding around scrollable tables, including Column header right-padding
14971  * and container width and height.
14972  *
14973  * @method _syncScroll
14974  * @private 
14975  */
14976 _syncScroll : function() {
14977     this._syncScrollX();
14978     this._syncScrollY();
14979     this._syncScrollOverhang();
14980     if(ua.opera) {
14981         // Bug 1925874
14982         this._elHdContainer.scrollLeft = this._elBdContainer.scrollLeft;
14983         if(!this.get("width")) {
14984             // Bug 1926125
14985             document.body.style += '';
14986         }
14987     }
14988  },
14989
14990 /**
14991  * Snaps container width for y-scrolling tables.
14992  *
14993  * @method _syncScrollY
14994  * @private
14995  */
14996 _syncScrollY : function() {
14997     var elTbody = this._elTbody,
14998         elBdContainer = this._elBdContainer;
14999     
15000     // X-scrolling not enabled
15001     if(!this.get("width")) {
15002         // Snap outer container width to content
15003         this._elContainer.style.width = 
15004                 (elBdContainer.scrollHeight > elBdContainer.clientHeight) ?
15005                 // but account for y-scrollbar since it is visible
15006                 (elTbody.parentNode.clientWidth + 19) + "px" :
15007                 // no y-scrollbar, just borders
15008                 (elTbody.parentNode.clientWidth + 2) + "px";
15009     }
15010 },
15011
15012 /**
15013  * Snaps container height for x-scrolling tables in IE. Syncs message TBODY width.
15014  *
15015  * @method _syncScrollX
15016  * @private
15017  */
15018 _syncScrollX : function() {
15019     var elTbody = this._elTbody,
15020         elBdContainer = this._elBdContainer;
15021     
15022     // IE 6 and 7 only when y-scrolling not enabled
15023     if(!this.get("height") && (ua.ie)) {
15024         // Snap outer container height to content
15025         elBdContainer.style.height = 
15026                 // but account for x-scrollbar if it is visible
15027                 (elBdContainer.scrollWidth > elBdContainer.offsetWidth ) ?
15028                 (elTbody.parentNode.offsetHeight + 18) + "px" : 
15029                 elTbody.parentNode.offsetHeight + "px";
15030     }
15031
15032     // Sync message tbody
15033     if(this._elTbody.rows.length === 0) {
15034         this._elMsgTbody.parentNode.style.width = this.getTheadEl().parentNode.offsetWidth + "px";
15035     }
15036     else {
15037         this._elMsgTbody.parentNode.style.width = "";
15038     }
15039 },
15040
15041 /**
15042  * Adds/removes Column header overhang as necesary.
15043  *
15044  * @method _syncScrollOverhang
15045  * @private
15046  */
15047 _syncScrollOverhang : function() {
15048     var elBdContainer = this._elBdContainer,
15049         // Overhang should be either 1 (default) or 18px, depending on the location of the right edge of the table
15050         nPadding = 1;
15051     
15052     // Y-scrollbar is visible, which is when the overhang needs to jut out
15053     if((elBdContainer.scrollHeight > elBdContainer.clientHeight) &&
15054         // X-scrollbar is also visible, which means the right is jagged, not flush with the Column
15055         (elBdContainer.scrollWidth > elBdContainer.clientWidth)) {
15056         nPadding = 18;
15057     }
15058     
15059     this._setOverhangValue(nPadding);
15060     
15061 },
15062
15063 /**
15064  * Sets Column header overhang to given width.
15065  *
15066  * @method _setOverhangValue
15067  * @param nBorderWidth {Number} Value of new border for overhang. 
15068  * @private
15069  */
15070 _setOverhangValue : function(nBorderWidth) {
15071     var aLastHeaders = this._oColumnSet.headers[this._oColumnSet.headers.length-1] || [],
15072         len = aLastHeaders.length,
15073         sPrefix = this._sId+"-fixedth-",
15074         sValue = nBorderWidth + "px solid " + this.get("COLOR_COLUMNFILLER");
15075
15076     this._elThead.style.display = "none";
15077     for(var i=0; i<len; i++) {
15078         Dom.get(sPrefix+aLastHeaders[i]).style.borderRight = sValue;
15079     }
15080     this._elThead.style.display = "";
15081 },
15082
15083
15084
15085
15086
15087
15088
15089
15090
15091
15092
15093
15094
15095
15096
15097
15098
15099
15100
15101
15102
15103
15104
15105
15106
15107
15108
15109
15110
15111
15112
15113
15114
15115
15116
15117
15118
15119
15120 /**
15121  * Returns DOM reference to the DataTable's fixed header container element.
15122  *
15123  * @method getHdContainerEl
15124  * @return {HTMLElement} Reference to DIV element.
15125  */
15126 getHdContainerEl : function() {
15127     return this._elHdContainer;
15128 },
15129
15130 /**
15131  * Returns DOM reference to the DataTable's scrolling body container element.
15132  *
15133  * @method getBdContainerEl
15134  * @return {HTMLElement} Reference to DIV element.
15135  */
15136 getBdContainerEl : function() {
15137     return this._elBdContainer;
15138 },
15139
15140 /**
15141  * Returns DOM reference to the DataTable's fixed header TABLE element.
15142  *
15143  * @method getHdTableEl
15144  * @return {HTMLElement} Reference to TABLE element.
15145  */
15146 getHdTableEl : function() {
15147     return this._elHdTable;
15148 },
15149
15150 /**
15151  * Returns DOM reference to the DataTable's scrolling body TABLE element.
15152  *
15153  * @method getBdTableEl
15154  * @return {HTMLElement} Reference to TABLE element.
15155  */
15156 getBdTableEl : function() {
15157     return this._elTable;
15158 },
15159
15160 /**
15161  * Disables ScrollingDataTable UI.
15162  *
15163  * @method disable
15164  */
15165 disable : function() {
15166     var elMask = this._elMask;
15167     elMask.style.width = this._elBdContainer.offsetWidth + "px";
15168     elMask.style.height = this._elHdContainer.offsetHeight + this._elBdContainer.offsetHeight + "px";
15169     elMask.style.display = "";
15170     this.fireEvent("disableEvent");
15171 },
15172
15173 /**
15174  * Removes given Column. NOTE: You cannot remove nested Columns. You can only remove
15175  * non-nested Columns, and top-level parent Columns (which will remove all
15176  * children Columns).
15177  *
15178  * @method removeColumn
15179  * @param oColumn {YAHOO.widget.Column} Column instance.
15180  * @return oColumn {YAHOO.widget.Column} Removed Column instance.
15181  */
15182 removeColumn : function(oColumn) {
15183     // Store scroll pos
15184     var hdPos = this._elHdContainer.scrollLeft;
15185     var bdPos = this._elBdContainer.scrollLeft;
15186     
15187     // Call superclass method
15188     oColumn = SDT.superclass.removeColumn.call(this, oColumn);
15189     
15190     // Restore scroll pos
15191     this._elHdContainer.scrollLeft = hdPos;
15192     this._elBdContainer.scrollLeft = bdPos;
15193     
15194     return oColumn;
15195 },
15196
15197 /**
15198  * Inserts given Column at the index if given, otherwise at the end. NOTE: You
15199  * can only add non-nested Columns and top-level parent Columns. You cannot add
15200  * a nested Column to an existing parent.
15201  *
15202  * @method insertColumn
15203  * @param oColumn {Object | YAHOO.widget.Column} Object literal Column
15204  * definition or a Column instance.
15205  * @param index {Number} (optional) New tree index.
15206  * @return oColumn {YAHOO.widget.Column} Inserted Column instance. 
15207  */
15208 insertColumn : function(oColumn, index) {
15209     // Store scroll pos
15210     var hdPos = this._elHdContainer.scrollLeft;
15211     var bdPos = this._elBdContainer.scrollLeft;
15212     
15213     // Call superclass method
15214     var oNewColumn = SDT.superclass.insertColumn.call(this, oColumn, index);
15215     
15216     // Restore scroll pos
15217     this._elHdContainer.scrollLeft = hdPos;
15218     this._elBdContainer.scrollLeft = bdPos;
15219     
15220     return oNewColumn;
15221 },
15222
15223 /**
15224  * Removes given Column and inserts into given tree index. NOTE: You
15225  * can only reorder non-nested Columns and top-level parent Columns. You cannot
15226  * reorder a nested Column to an existing parent.
15227  *
15228  * @method reorderColumn
15229  * @param oColumn {YAHOO.widget.Column} Column instance.
15230  * @param index {Number} New tree index.
15231  */
15232 reorderColumn : function(oColumn, index) {
15233     // Store scroll pos
15234     var hdPos = this._elHdContainer.scrollLeft;
15235     var bdPos = this._elBdContainer.scrollLeft;
15236     
15237     // Call superclass method
15238     var oNewColumn = SDT.superclass.reorderColumn.call(this, oColumn, index);
15239     
15240     // Restore scroll pos
15241     this._elHdContainer.scrollLeft = hdPos;
15242     this._elBdContainer.scrollLeft = bdPos;
15243
15244     return oNewColumn;
15245 },
15246
15247 /**
15248  * Sets given Column to given pixel width. If new width is less than minWidth
15249  * width, sets to minWidth. Updates oColumn.width value.
15250  *
15251  * @method setColumnWidth
15252  * @param oColumn {YAHOO.widget.Column} Column instance.
15253  * @param nWidth {Number} New width in pixels.
15254  */
15255 setColumnWidth : function(oColumn, nWidth) {
15256     oColumn = this.getColumn(oColumn);
15257     if(oColumn) {
15258         // Validate new width against minWidth
15259         if(lang.isNumber(nWidth)) {
15260             nWidth = (nWidth > oColumn.minWidth) ? nWidth : oColumn.minWidth;
15261
15262             // Save state
15263             oColumn.width = nWidth;
15264             
15265             // Resize the DOM elements
15266             this._setColumnWidth(oColumn, nWidth+"px");
15267             this._syncScroll();
15268             
15269             this.fireEvent("columnSetWidthEvent",{column:oColumn,width:nWidth});
15270             YAHOO.log("Set width of Column " + oColumn + " to " + nWidth + "px", "info", this.toString());
15271         }
15272         // Unsets a width to auto-size
15273         else if(nWidth === null) {
15274             // Save state
15275             oColumn.width = nWidth;
15276             
15277             // Resize the DOM elements
15278             this._setColumnWidth(oColumn, "auto");
15279             this.validateColumnWidths(oColumn);
15280             this.fireEvent("columnUnsetWidthEvent",{column:oColumn});
15281             YAHOO.log("Column " + oColumn + " width unset", "info", this.toString());
15282         }
15283         
15284         // Bug 2339454: resize then sort misaligment
15285         this._clearTrTemplateEl();
15286     }
15287     else {
15288         YAHOO.log("Could not set width of Column " + oColumn + " to " + nWidth + "px", "warn", this.toString());
15289     }
15290 },
15291
15292 /**
15293  * Displays message within secondary TBODY.
15294  *
15295  * @method showTableMessage
15296  * @param sHTML {String} (optional) Value for innerHTMlang.
15297  * @param sClassName {String} (optional) Classname.
15298  */
15299 showTableMessage : function(sHTML, sClassName) {
15300     var elCell = this._elMsgTd;
15301     if(lang.isString(sHTML)) {
15302         elCell.firstChild.innerHTML = sHTML;
15303     }
15304     if(lang.isString(sClassName)) {
15305         Dom.addClass(elCell.firstChild, sClassName);
15306     }
15307
15308     // Needed for SDT only
15309     var elThead = this.getTheadEl();
15310     var elTable = elThead.parentNode;
15311     var newWidth = elTable.offsetWidth;
15312     this._elMsgTbody.parentNode.style.width = this.getTheadEl().parentNode.offsetWidth + "px";
15313
15314     this._elMsgTbody.style.display = "";
15315
15316     this.fireEvent("tableMsgShowEvent", {html:sHTML, className:sClassName});
15317     YAHOO.log("DataTable showing message: " + sHTML, "info", this.toString());
15318 },
15319
15320
15321
15322
15323
15324
15325
15326
15327
15328
15329
15330
15331
15332 /////////////////////////////////////////////////////////////////////////////
15333 //
15334 // Private Custom Event Handlers
15335 //
15336 /////////////////////////////////////////////////////////////////////////////
15337
15338 /**
15339  * Handles Column mutations
15340  *
15341  * @method onColumnChange
15342  * @param oArgs {Object} Custom Event data.
15343  */
15344 _onColumnChange : function(oArg) {
15345     // Figure out which Column changed
15346     var oColumn = (oArg.column) ? oArg.column :
15347             (oArg.editor) ? oArg.editor.column : null;
15348     this._storeScrollPositions();
15349     this.validateColumnWidths(oColumn);
15350 },
15351
15352
15353
15354
15355
15356
15357
15358
15359
15360
15361
15362
15363
15364
15365
15366 /////////////////////////////////////////////////////////////////////////////
15367 //
15368 // Private DOM Event Handlers
15369 //
15370 /////////////////////////////////////////////////////////////////////////////
15371
15372 /**
15373  * Syncs scrolltop and scrollleft of all TABLEs.
15374  *
15375  * @method _onScroll
15376  * @param e {HTMLEvent} The scroll event.
15377  * @param oSelf {YAHOO.widget.ScrollingDataTable} ScrollingDataTable instance.
15378  * @private
15379  */
15380 _onScroll : function(e, oSelf) {
15381     oSelf._elHdContainer.scrollLeft = oSelf._elBdContainer.scrollLeft;
15382
15383     if(oSelf._oCellEditor && oSelf._oCellEditor.isActive) {
15384         oSelf.fireEvent("editorBlurEvent", {editor:oSelf._oCellEditor});
15385         oSelf.cancelCellEditor();
15386     }
15387
15388     var elTarget = Ev.getTarget(e);
15389     var elTag = elTarget.nodeName.toLowerCase();
15390     oSelf.fireEvent("tableScrollEvent", {event:e, target:elTarget});
15391 },
15392
15393 /**
15394  * Handles keydown events on the THEAD element.
15395  *
15396  * @method _onTheadKeydown
15397  * @param e {HTMLEvent} The key event.
15398  * @param oSelf {YAHOO.widget.ScrollingDataTable} ScrollingDataTable instance.
15399  * @private
15400  */
15401 _onTheadKeydown : function(e, oSelf) {
15402     // If tabbing to next TH label link causes THEAD to scroll,
15403     // need to sync scrollLeft with TBODY
15404     if(Ev.getCharCode(e) === 9) {
15405         setTimeout(function() {
15406             if((oSelf instanceof SDT) && oSelf._sId) {
15407                 oSelf._elBdContainer.scrollLeft = oSelf._elHdContainer.scrollLeft;
15408             }
15409         },0);
15410     }
15411     
15412     var elTarget = Ev.getTarget(e);
15413     var elTag = elTarget.nodeName.toLowerCase();
15414     var bKeepBubbling = true;
15415     while(elTarget && (elTag != "table")) {
15416         switch(elTag) {
15417             case "body":
15418                 return;
15419             case "input":
15420             case "textarea":
15421                 // TODO: implement textareaKeyEvent
15422                 break;
15423             case "thead":
15424                 bKeepBubbling = oSelf.fireEvent("theadKeyEvent",{target:elTarget,event:e});
15425                 break;
15426             default:
15427                 break;
15428         }
15429         if(bKeepBubbling === false) {
15430             return;
15431         }
15432         else {
15433             elTarget = elTarget.parentNode;
15434             if(elTarget) {
15435                 elTag = elTarget.nodeName.toLowerCase();
15436             }
15437         }
15438     }
15439     oSelf.fireEvent("tableKeyEvent",{target:(elTarget || oSelf._elContainer),event:e});
15440 }
15441
15442
15443
15444
15445 /**
15446  * Fired when a fixed scrolling DataTable has a scroll.
15447  *
15448  * @event tableScrollEvent
15449  * @param oArgs.event {HTMLEvent} The event object.
15450  * @param oArgs.target {HTMLElement} The DataTable's CONTAINER element (in IE)
15451  * or the DataTable's TBODY element (everyone else).
15452  *
15453  */
15454
15455
15456
15457
15458 });
15459
15460 })();
15461
15462 (function () {
15463
15464 var lang   = YAHOO.lang,
15465     util   = YAHOO.util,
15466     widget = YAHOO.widget,
15467     ua     = YAHOO.env.ua,
15468     
15469     Dom    = util.Dom,
15470     Ev     = util.Event,
15471     
15472     DT     = widget.DataTable;
15473 /****************************************************************************/
15474 /****************************************************************************/
15475 /****************************************************************************/
15476     
15477 /**
15478  * The BaseCellEditor class provides base functionality common to all inline cell
15479  * editors for a DataTable widget.
15480  *
15481  * @namespace YAHOO.widget
15482  * @class BaseCellEditor
15483  * @uses YAHOO.util.EventProvider 
15484  * @constructor
15485  * @param sType {String} Type indicator, to map to YAHOO.widget.DataTable.Editors.
15486  * @param oConfigs {Object} (Optional) Object literal of configs.
15487  */
15488 widget.BaseCellEditor = function(sType, oConfigs) {
15489     this._sId = this._sId || "yui-ceditor" + YAHOO.widget.BaseCellEditor._nCount++;
15490     this._sType = sType;
15491     
15492     // Validate inputs
15493     this._initConfigs(oConfigs); 
15494     
15495     // Create Custom Events
15496     this._initEvents();
15497              
15498     // Draw UI
15499     this.render();
15500 };
15501
15502 var BCE = widget.BaseCellEditor;
15503
15504 /////////////////////////////////////////////////////////////////////////////
15505 //
15506 // Static members
15507 //
15508 /////////////////////////////////////////////////////////////////////////////
15509 lang.augmentObject(BCE, {
15510
15511 /**
15512  * Global instance counter.
15513  *
15514  * @property CellEditor._nCount
15515  * @type Number
15516  * @static
15517  * @default 0
15518  * @private 
15519  */
15520 _nCount : 0,
15521
15522 /**
15523  * Class applied to CellEditor container.
15524  *
15525  * @property CellEditor.CLASS_CELLEDITOR
15526  * @type String
15527  * @static
15528  * @default "yui-ceditor"
15529  */
15530 CLASS_CELLEDITOR : "yui-ceditor"
15531
15532 });
15533
15534 BCE.prototype = {
15535 /////////////////////////////////////////////////////////////////////////////
15536 //
15537 // Private members
15538 //
15539 /////////////////////////////////////////////////////////////////////////////
15540 /**
15541  * Unique id assigned to instance "yui-ceditorN", useful prefix for generating unique
15542  * DOM ID strings and log messages.
15543  *
15544  * @property _sId
15545  * @type String
15546  * @private
15547  */
15548 _sId : null,
15549
15550 /**
15551  * Editor type.
15552  *
15553  * @property _sType
15554  * @type String
15555  * @private
15556  */
15557 _sType : null,
15558
15559 /**
15560  * DataTable instance.
15561  *
15562  * @property _oDataTable
15563  * @type YAHOO.widget.DataTable
15564  * @private 
15565  */
15566 _oDataTable : null,
15567
15568 /**
15569  * Column instance.
15570  *
15571  * @property _oColumn
15572  * @type YAHOO.widget.Column
15573  * @default null
15574  */
15575 _oColumn : null,
15576
15577 /**
15578  * Record instance.
15579  *
15580  * @property _oRecord
15581  * @type YAHOO.widget.Record
15582  * @default null
15583  * @private 
15584  */
15585 _oRecord : null,
15586
15587 /**
15588  * TD element.
15589  *
15590  * @property _elTd
15591  * @type HTMLElement
15592  * @default null
15593  * @private
15594  */
15595 _elTd : null,
15596
15597 /**
15598  * Container for inline editor.
15599  *
15600  * @property _elContainer
15601  * @type HTMLElement
15602  * @private 
15603  */
15604 _elContainer : null,
15605
15606 /**
15607  * Reference to Cancel button, if available.
15608  *
15609  * @property _elCancelBtn
15610  * @type HTMLElement
15611  * @default null
15612  * @private 
15613  */
15614 _elCancelBtn : null,
15615
15616 /**
15617  * Reference to Save button, if available.
15618  *
15619  * @property _elSaveBtn
15620  * @type HTMLElement
15621  * @default null
15622  * @private 
15623  */
15624 _elSaveBtn : null,
15625
15626
15627
15628
15629
15630
15631
15632
15633 /////////////////////////////////////////////////////////////////////////////
15634 //
15635 // Private methods
15636 //
15637 /////////////////////////////////////////////////////////////////////////////
15638
15639 /**
15640  * Initialize configs.
15641  *
15642  * @method _initConfigs
15643  * @private   
15644  */
15645 _initConfigs : function(oConfigs) {
15646     // Object literal defines CellEditor configs
15647     if(oConfigs && YAHOO.lang.isObject(oConfigs)) {
15648         for(var sConfig in oConfigs) {
15649             if(sConfig) {
15650                 this[sConfig] = oConfigs[sConfig];
15651             }
15652         }
15653     }
15654 },
15655
15656 /**
15657  * Initialize Custom Events.
15658  *
15659  * @method _initEvents
15660  * @private   
15661  */
15662 _initEvents : function() {
15663     this.createEvent("showEvent");
15664     this.createEvent("keydownEvent");
15665     this.createEvent("invalidDataEvent");
15666     this.createEvent("revertEvent");
15667     this.createEvent("saveEvent");
15668     this.createEvent("cancelEvent");
15669     this.createEvent("blurEvent");
15670     this.createEvent("blockEvent");
15671     this.createEvent("unblockEvent");
15672 },
15673
15674
15675
15676
15677
15678
15679
15680
15681
15682
15683
15684
15685
15686 /////////////////////////////////////////////////////////////////////////////
15687 //
15688 // Public properties
15689 //
15690 /////////////////////////////////////////////////////////////////////////////
15691 /**
15692  * Implementer defined function that can submit the input value to a server. This
15693  * function must accept the arguments fnCallback and oNewValue. When the submission
15694  * is complete, the function must also call fnCallback(bSuccess, oNewValue) to 
15695  * finish the save routine in the CellEditor. This function can also be used to 
15696  * perform extra validation or input value manipulation. 
15697  *
15698  * @property asyncSubmitter
15699  * @type HTMLFunction
15700  */
15701 asyncSubmitter : null,
15702
15703 /**
15704  * Current value.
15705  *
15706  * @property value
15707  * @type MIXED
15708  */
15709 value : null,
15710
15711 /**
15712  * Default value in case Record data is undefined. NB: Null values will not trigger
15713  * the default value.
15714  *
15715  * @property defaultValue
15716  * @type MIXED
15717  * @default null
15718  */
15719 defaultValue : null,
15720
15721 /**
15722  * Validator function for input data, called from the DataTable instance scope,
15723  * receives the arguments (inputValue, currentValue, editorInstance) and returns
15724  * either the validated (or type-converted) value or undefined.
15725  *
15726  * @property validator
15727  * @type HTMLFunction
15728  * @default null
15729  */
15730 validator : null,
15731
15732 /**
15733  * If validation is enabled, resets input field of invalid data.
15734  *
15735  * @property resetInvalidData
15736  * @type Boolean
15737  * @default true
15738  */
15739 resetInvalidData : true,
15740
15741 /**
15742  * True if currently active.
15743  *
15744  * @property isActive
15745  * @type Boolean
15746  */
15747 isActive : false,
15748
15749 /**
15750  * Text to display on Save button.
15751  *
15752  * @property LABEL_SAVE
15753  * @type String
15754  * @default "Save"
15755  */
15756 LABEL_SAVE : "Save",
15757
15758 /**
15759  * Text to display on Cancel button.
15760  *
15761  * @property LABEL_CANCEL
15762  * @type String
15763  * @default "Cancel"
15764  */
15765 LABEL_CANCEL : "Cancel",
15766
15767 /**
15768  * True if Save/Cancel buttons should not be displayed in the CellEditor.
15769  *
15770  * @property disableBtns
15771  * @type Boolean
15772  * @default false
15773  */
15774 disableBtns : false,
15775
15776
15777
15778
15779
15780
15781
15782 /////////////////////////////////////////////////////////////////////////////
15783 //
15784 // Public methods
15785 //
15786 /////////////////////////////////////////////////////////////////////////////
15787 /**
15788  * CellEditor instance name, for logging.
15789  *
15790  * @method toString
15791  * @return {String} Unique name of the CellEditor instance.
15792  */
15793
15794 toString : function() {
15795     return "CellEditor instance " + this._sId;
15796 },
15797
15798 /**
15799  * CellEditor unique ID.
15800  *
15801  * @method getId
15802  * @return {String} Unique ID of the CellEditor instance.
15803  */
15804
15805 getId : function() {
15806     return this._sId;
15807 },
15808
15809 /**
15810  * Returns reference to associated DataTable instance.
15811  *
15812  * @method getDataTable
15813  * @return {YAHOO.widget.DataTable} DataTable instance.
15814  */
15815
15816 getDataTable : function() {
15817     return this._oDataTable;
15818 },
15819
15820 /**
15821  * Returns reference to associated Column instance.
15822  *
15823  * @method getColumn
15824  * @return {YAHOO.widget.Column} Column instance.
15825  */
15826
15827 getColumn : function() {
15828     return this._oColumn;
15829 },
15830
15831 /**
15832  * Returns reference to associated Record instance.
15833  *
15834  * @method getRecord
15835  * @return {YAHOO.widget.Record} Record instance.
15836  */
15837
15838 getRecord : function() {
15839     return this._oRecord;
15840 },
15841
15842
15843
15844 /**
15845  * Returns reference to associated TD element.
15846  *
15847  * @method getTdEl
15848  * @return {HTMLElement} TD element.
15849  */
15850
15851 getTdEl : function() {
15852     return this._elTd;
15853 },
15854
15855 /**
15856  * Returns container element.
15857  *
15858  * @method getContainerEl
15859  * @return {HTMLElement} Reference to container element.
15860  */
15861
15862 getContainerEl : function() {
15863     return this._elContainer;
15864 },
15865
15866 /**
15867  * Nulls out the entire CellEditor instance and related objects, removes attached
15868  * event listeners, and clears out DOM elements inside the container, removes
15869  * container from the DOM.
15870  *
15871  * @method destroy
15872  */
15873 destroy : function() {
15874     this.unsubscribeAll();
15875     
15876     // Column is late-binding in attach()
15877     var oColumn = this.getColumn();
15878     if(oColumn) {
15879         oColumn.editor = null;
15880     }
15881     
15882     var elContainer = this.getContainerEl();
15883     Ev.purgeElement(elContainer, true);
15884     elContainer.parentNode.removeChild(elContainer);
15885 },
15886
15887 /**
15888  * Renders DOM elements and attaches event listeners.
15889  *
15890  * @method render
15891  */
15892 render : function() {
15893     if(this._elContainer) {
15894         YAHOO.util.Event.purgeElement(this._elContainer, true);
15895         this._elContainer.innerHTML = "";
15896     }
15897
15898     // Render Cell Editor container element as first child of body
15899     var elContainer = document.createElement("div");
15900     elContainer.id = this.getId() + "-container"; // Needed for tracking blur event
15901     elContainer.style.display = "none";
15902     elContainer.tabIndex = 0;
15903     elContainer.className = DT.CLASS_EDITOR;
15904     document.body.insertBefore(elContainer, document.body.firstChild);
15905     this._elContainer = elContainer;
15906     
15907     // Handle ESC key
15908     Ev.addListener(elContainer, "keydown", function(e, oSelf) {
15909         // ESC cancels Cell Editor
15910         if((e.keyCode == 27)) {
15911             var target = Ev.getTarget(e);
15912             // workaround for Mac FF3 bug that disabled clicks when ESC hit when
15913             // select is open. [bug 2273056]
15914             if (target.nodeName && target.nodeName.toLowerCase() === 'select') {
15915                 target.blur();
15916             }
15917             oSelf.cancel();
15918         }
15919         // Pass through event
15920         oSelf.fireEvent("keydownEvent", {editor:this, event:e});
15921     }, this);
15922     
15923     this.renderForm();
15924
15925     // Show Save/Cancel buttons
15926     if(!this.disableBtns) {
15927         this.renderBtns();
15928     }
15929     
15930     this.doAfterRender();
15931 },
15932
15933 /**
15934  * Renders Save/Cancel buttons.
15935  *
15936  * @method renderBtns
15937  */
15938 renderBtns : function() {
15939     // Buttons
15940     var elBtnsDiv = this.getContainerEl().appendChild(document.createElement("div"));
15941     elBtnsDiv.className = DT.CLASS_BUTTON;
15942
15943     // Save button
15944     var elSaveBtn = elBtnsDiv.appendChild(document.createElement("button"));
15945     elSaveBtn.className = DT.CLASS_DEFAULT;
15946     elSaveBtn.innerHTML = this.LABEL_SAVE;
15947     Ev.addListener(elSaveBtn, "click", function(oArgs) {
15948         this.save();
15949     }, this, true);
15950     this._elSaveBtn = elSaveBtn;
15951
15952     // Cancel button
15953     var elCancelBtn = elBtnsDiv.appendChild(document.createElement("button"));
15954     elCancelBtn.innerHTML = this.LABEL_CANCEL;
15955     Ev.addListener(elCancelBtn, "click", function(oArgs) {
15956         this.cancel();
15957     }, this, true);
15958     this._elCancelBtn = elCancelBtn;
15959 },
15960
15961 /**
15962  * Attach CellEditor for a new interaction.
15963  *
15964  * @method attach
15965  * @param oDataTable {YAHOO.widget.DataTable} Associated DataTable instance.
15966  * @param elCell {HTMLElement} Cell to edit.  
15967  */
15968 attach : function(oDataTable, elCell) {
15969     // Validate 
15970     if(oDataTable instanceof YAHOO.widget.DataTable) {
15971         this._oDataTable = oDataTable;
15972         
15973         // Validate cell
15974         elCell = oDataTable.getTdEl(elCell);
15975         if(elCell) {
15976             this._elTd = elCell;
15977
15978             // Validate Column
15979             var oColumn = oDataTable.getColumn(elCell);
15980             if(oColumn) {
15981                 this._oColumn = oColumn;
15982                 
15983                 // Validate Record
15984                 var oRecord = oDataTable.getRecord(elCell);
15985                 if(oRecord) {
15986                     this._oRecord = oRecord;
15987                     var value = oRecord.getData(this.getColumn().getKey());
15988                     this.value = (value !== undefined) ? value : this.defaultValue;
15989                     return true;
15990                 }
15991             }            
15992         }
15993     }
15994     YAHOO.log("Could not attach CellEditor","error",this.toString());
15995     return false;
15996 },
15997
15998 /**
15999  * Moves container into position for display.
16000  *
16001  * @method move
16002  */
16003 move : function() {
16004     // Move Editor
16005     var elContainer = this.getContainerEl(),
16006         elTd = this.getTdEl(),
16007         x = Dom.getX(elTd),
16008         y = Dom.getY(elTd);
16009
16010     //TODO: remove scrolling logic
16011     // SF doesn't get xy for cells in scrolling table
16012     // when tbody display is set to block
16013     if(isNaN(x) || isNaN(y)) {
16014         var elTbody = this.getDataTable().getTbodyEl();
16015         x = elTd.offsetLeft + // cell pos relative to table
16016                 Dom.getX(elTbody.parentNode) - // plus table pos relative to document
16017                 elTbody.scrollLeft; // minus tbody scroll
16018         y = elTd.offsetTop + // cell pos relative to table
16019                 Dom.getY(elTbody.parentNode) - // plus table pos relative to document
16020                 elTbody.scrollTop + // minus tbody scroll
16021                 this.getDataTable().getTheadEl().offsetHeight; // account for fixed THEAD cells
16022     }
16023
16024     elContainer.style.left = x + "px";
16025     elContainer.style.top = y + "px";
16026 },
16027
16028 /**
16029  * Displays CellEditor UI in the correct position.
16030  *
16031  * @method show
16032  */
16033 show : function() {
16034     this.resetForm();
16035     this.isActive = true;
16036     this.getContainerEl().style.display = "";
16037     this.focus();
16038     this.fireEvent("showEvent", {editor:this});
16039     YAHOO.log("CellEditor shown", "info", this.toString()); 
16040 },
16041
16042 /**
16043  * Fires blockEvent
16044  *
16045  * @method block
16046  */
16047 block : function() {
16048     this.fireEvent("blockEvent", {editor:this});
16049     YAHOO.log("CellEditor blocked", "info", this.toString()); 
16050 },
16051
16052 /**
16053  * Fires unblockEvent
16054  *
16055  * @method unblock
16056  */
16057 unblock : function() {
16058     this.fireEvent("unblockEvent", {editor:this});
16059     YAHOO.log("CellEditor unblocked", "info", this.toString()); 
16060 },
16061
16062 /**
16063  * Saves value of CellEditor and hides UI.
16064  *
16065  * @method save
16066  */
16067 save : function() {
16068     // Get new value
16069     var inputValue = this.getInputValue();
16070     var validValue = inputValue;
16071     
16072     // Validate new value
16073     if(this.validator) {
16074         validValue = this.validator.call(this.getDataTable(), inputValue, this.value, this);
16075         if(validValue === undefined ) {
16076             if(this.resetInvalidData) {
16077                 this.resetForm();
16078             }
16079             this.fireEvent("invalidDataEvent",
16080                     {editor:this, oldData:this.value, newData:inputValue});
16081             YAHOO.log("Could not save Cell Editor input due to invalid data " +
16082                     lang.dump(inputValue), "warn", this.toString());
16083             return;
16084         }
16085     }
16086         
16087     var oSelf = this;
16088     var finishSave = function(bSuccess, oNewValue) {
16089         var oOrigValue = oSelf.value;
16090         if(bSuccess) {
16091             // Update new value
16092             oSelf.value = oNewValue;
16093             oSelf.getDataTable().updateCell(oSelf.getRecord(), oSelf.getColumn(), oNewValue);
16094             
16095             // Hide CellEditor
16096             oSelf.getContainerEl().style.display = "none";
16097             oSelf.isActive = false;
16098             oSelf.getDataTable()._oCellEditor =  null;
16099             
16100             oSelf.fireEvent("saveEvent",
16101                     {editor:oSelf, oldData:oOrigValue, newData:oSelf.value});
16102             YAHOO.log("Cell Editor input saved", "info", this.toString());
16103         }
16104         else {
16105             oSelf.resetForm();
16106             oSelf.fireEvent("revertEvent",
16107                     {editor:oSelf, oldData:oOrigValue, newData:oNewValue});
16108             YAHOO.log("Could not save Cell Editor input " +
16109                     lang.dump(oNewValue), "warn", oSelf.toString());
16110         }
16111         oSelf.unblock();
16112     };
16113     
16114     this.block();
16115     if(lang.isFunction(this.asyncSubmitter)) {
16116         this.asyncSubmitter.call(this, finishSave, validValue);
16117     } 
16118     else {   
16119         finishSave(true, validValue);
16120     }
16121 },
16122
16123 /**
16124  * Cancels CellEditor input and hides UI.
16125  *
16126  * @method cancel
16127  */
16128 cancel : function() {
16129     if(this.isActive) {
16130         this.getContainerEl().style.display = "none";
16131         this.isActive = false;
16132         this.getDataTable()._oCellEditor =  null;
16133         this.fireEvent("cancelEvent", {editor:this});
16134         YAHOO.log("CellEditor canceled", "info", this.toString());
16135     }
16136     else {
16137         YAHOO.log("Unable to cancel CellEditor", "warn", this.toString());
16138     }
16139 },
16140
16141 /**
16142  * Renders form elements.
16143  *
16144  * @method renderForm
16145  */
16146 renderForm : function() {
16147     // To be implemented by subclass
16148 },
16149
16150 /**
16151  * Access to add additional event listeners.
16152  *
16153  * @method doAfterRender
16154  */
16155 doAfterRender : function() {
16156     // To be implemented by subclass
16157 },
16158
16159
16160 /**
16161  * After rendering form, if disabledBtns is set to true, then sets up a mechanism
16162  * to save input without them. 
16163  *
16164  * @method handleDisabledBtns
16165  */
16166 handleDisabledBtns : function() {
16167     // To be implemented by subclass
16168 },
16169
16170 /**
16171  * Resets CellEditor UI to initial state.
16172  *
16173  * @method resetForm
16174  */
16175 resetForm : function() {
16176     // To be implemented by subclass
16177 },
16178
16179 /**
16180  * Sets focus in CellEditor.
16181  *
16182  * @method focus
16183  */
16184 focus : function() {
16185     // To be implemented by subclass
16186 },
16187
16188 /**
16189  * Retrieves input value from CellEditor.
16190  *
16191  * @method getInputValue
16192  */
16193 getInputValue : function() {
16194     // To be implemented by subclass
16195 }
16196
16197 };
16198
16199 lang.augmentProto(BCE, util.EventProvider);
16200
16201
16202 /////////////////////////////////////////////////////////////////////////////
16203 //
16204 // Custom Events
16205 //
16206 /////////////////////////////////////////////////////////////////////////////
16207
16208 /**
16209  * Fired when a CellEditor is shown.
16210  *
16211  * @event showEvent
16212  * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
16213  */
16214
16215 /**
16216  * Fired when a CellEditor has a keydown.
16217  *
16218  * @event keydownEvent
16219  * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance. 
16220  * @param oArgs.event {HTMLEvent} The event object.
16221  */
16222
16223 /**
16224  * Fired when a CellEditor input is reverted due to invalid data.
16225  *
16226  * @event invalidDataEvent
16227  * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance. 
16228  * @param oArgs.newData {Object} New data value from form input field.
16229  * @param oArgs.oldData {Object} Old data value.
16230  */
16231
16232 /**
16233  * Fired when a CellEditor input is reverted due to asyncSubmitter failure.
16234  *
16235  * @event revertEvent
16236  * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance. 
16237  * @param oArgs.newData {Object} New data value from form input field.
16238  * @param oArgs.oldData {Object} Old data value.
16239  */
16240
16241 /**
16242  * Fired when a CellEditor input is saved.
16243  *
16244  * @event saveEvent
16245  * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance. 
16246  * @param oArgs.newData {Object} New data value from form input field.
16247  * @param oArgs.oldData {Object} Old data value.
16248  */
16249
16250 /**
16251  * Fired when a CellEditor input is canceled.
16252  *
16253  * @event cancelEvent
16254  * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance. 
16255  */
16256
16257 /**
16258  * Fired when a CellEditor has a blur event.
16259  *
16260  * @event blurEvent
16261  * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance. 
16262  */
16263
16264
16265
16266
16267
16268
16269
16270
16271
16272
16273
16274
16275
16276
16277 /****************************************************************************/
16278 /****************************************************************************/
16279 /****************************************************************************/
16280     
16281 /**
16282  * The CheckboxCellEditor class provides functionality for inline editing
16283  * DataTable cell data with checkboxes.
16284  *
16285  * @namespace YAHOO.widget
16286  * @class CheckboxCellEditor
16287  * @extends YAHOO.widget.BaseCellEditor
16288  * @constructor
16289  * @param oConfigs {Object} (Optional) Object literal of configs.
16290  */
16291 widget.CheckboxCellEditor = function(oConfigs) {
16292     this._sId = "yui-checkboxceditor" + YAHOO.widget.BaseCellEditor._nCount++;
16293     widget.CheckboxCellEditor.superclass.constructor.call(this, "checkbox", oConfigs); 
16294 };
16295
16296 // CheckboxCellEditor extends BaseCellEditor
16297 lang.extend(widget.CheckboxCellEditor, BCE, {
16298
16299 /////////////////////////////////////////////////////////////////////////////
16300 //
16301 // CheckboxCellEditor public properties
16302 //
16303 /////////////////////////////////////////////////////////////////////////////
16304 /**
16305  * Array of checkbox values. Can either be a simple array (e.g., ["red","green","blue"])
16306  * or a an array of objects (e.g., [{label:"red", value:"#FF0000"},
16307  * {label:"green", value:"#00FF00"}, {label:"blue", value:"#0000FF"}]). 
16308  *
16309  * @property checkboxOptions
16310  * @type String[] | Object[]
16311  */
16312 checkboxOptions : null,
16313
16314 /**
16315  * Reference to the checkbox elements.
16316  *
16317  * @property checkboxes
16318  * @type HTMLElement[] 
16319  */
16320 checkboxes : null,
16321
16322 /**
16323  * Array of checked values
16324  *
16325  * @property value
16326  * @type String[] 
16327  */
16328 value : null,
16329
16330 /////////////////////////////////////////////////////////////////////////////
16331 //
16332 // CheckboxCellEditor public methods
16333 //
16334 /////////////////////////////////////////////////////////////////////////////
16335
16336 /**
16337  * Render a form with input(s) type=checkbox.
16338  *
16339  * @method renderForm
16340  */
16341 renderForm : function() {
16342     if(lang.isArray(this.checkboxOptions)) {
16343         var checkboxOption, checkboxValue, checkboxId, elLabel, j, len;
16344         
16345         // Create the checkbox buttons in an IE-friendly way...
16346         for(j=0,len=this.checkboxOptions.length; j<len; j++) {
16347             checkboxOption = this.checkboxOptions[j];
16348             checkboxValue = lang.isValue(checkboxOption.value) ?
16349                     checkboxOption.value : checkboxOption;
16350
16351             checkboxId = this.getId() + "-chk" + j;
16352             this.getContainerEl().innerHTML += "<input type=\"checkbox\"" +
16353                     " id=\"" + checkboxId + "\"" + // Needed for label
16354                     " value=\"" + checkboxValue + "\" />";
16355             
16356             // Create the labels in an IE-friendly way
16357             elLabel = this.getContainerEl().appendChild(document.createElement("label"));
16358             elLabel.htmlFor = checkboxId;
16359             elLabel.innerHTML = lang.isValue(checkboxOption.label) ?
16360                     checkboxOption.label : checkboxOption;
16361         }
16362         
16363         // Store the reference to the checkbox elements
16364         var allCheckboxes = [];
16365         for(j=0; j<len; j++) {
16366             allCheckboxes[allCheckboxes.length] = this.getContainerEl().childNodes[j*2];
16367         }
16368         this.checkboxes = allCheckboxes;
16369
16370         if(this.disableBtns) {
16371             this.handleDisabledBtns();
16372         }
16373     }
16374     else {
16375         YAHOO.log("Could not find checkboxOptions", "error", this.toString());
16376     }
16377 },
16378
16379 /**
16380  * After rendering form, if disabledBtns is set to true, then sets up a mechanism
16381  * to save input without them. 
16382  *
16383  * @method handleDisabledBtns
16384  */
16385 handleDisabledBtns : function() {
16386     Ev.addListener(this.getContainerEl(), "click", function(v){
16387         if(Ev.getTarget(v).tagName.toLowerCase() === "input") {
16388             // Save on blur
16389             this.save();
16390         }
16391     }, this, true);
16392 },
16393
16394 /**
16395  * Resets CheckboxCellEditor UI to initial state.
16396  *
16397  * @method resetForm
16398  */
16399 resetForm : function() {
16400     // Normalize to array
16401     var originalValues = lang.isArray(this.value) ? this.value : [this.value];
16402     
16403     // Match checks to value
16404     for(var i=0, j=this.checkboxes.length; i<j; i++) {
16405         this.checkboxes[i].checked = false;
16406         for(var k=0, len=originalValues.length; k<len; k++) {
16407             if(this.checkboxes[i].value === originalValues[k]) {
16408                 this.checkboxes[i].checked = true;
16409             }
16410         }
16411     }
16412 },
16413
16414 /**
16415  * Sets focus in CheckboxCellEditor.
16416  *
16417  * @method focus
16418  */
16419 focus : function() {
16420     this.checkboxes[0].focus();
16421 },
16422
16423 /**
16424  * Retrieves input value from CheckboxCellEditor.
16425  *
16426  * @method getInputValue
16427  */
16428 getInputValue : function() {
16429     var checkedValues = [];
16430     for(var i=0, j=this.checkboxes.length; i<j; i++) {
16431         if(this.checkboxes[i].checked) {
16432             checkedValues[checkedValues.length] = this.checkboxes[i].value;
16433         }
16434     }  
16435     return checkedValues;
16436 }
16437
16438 });
16439
16440 // Copy static members to CheckboxCellEditor class
16441 lang.augmentObject(widget.CheckboxCellEditor, BCE);
16442
16443
16444
16445
16446
16447
16448
16449
16450 /****************************************************************************/
16451 /****************************************************************************/
16452 /****************************************************************************/
16453     
16454 /**
16455  * The DataCellEditor class provides functionality for inline editing
16456  * DataTable cell data with a YUI Calendar.
16457  *
16458  * @namespace YAHOO.widget
16459  * @class DateCellEditor
16460  * @extends YAHOO.widget.BaseCellEditor 
16461  * @constructor
16462  * @param oConfigs {Object} (Optional) Object literal of configs.
16463  */
16464 widget.DateCellEditor = function(oConfigs) {
16465     this._sId = "yui-dateceditor" + YAHOO.widget.BaseCellEditor._nCount++;
16466     widget.DateCellEditor.superclass.constructor.call(this, "date", oConfigs); 
16467 };
16468
16469 // CheckboxCellEditor extends BaseCellEditor
16470 lang.extend(widget.DateCellEditor, BCE, {
16471
16472 /////////////////////////////////////////////////////////////////////////////
16473 //
16474 // DateCellEditor public properties
16475 //
16476 /////////////////////////////////////////////////////////////////////////////
16477 /**
16478  * Reference to Calendar instance.
16479  *
16480  * @property calendar
16481  * @type YAHOO.widget.Calendar
16482  */
16483 calendar : null,
16484
16485 /**
16486  * Configs for the calendar instance, to be passed to Calendar constructor.
16487  *
16488  * @property calendarOptions
16489  * @type Object
16490  */
16491 calendarOptions : null,
16492
16493 /**
16494  * Default value.
16495  *
16496  * @property defaultValue
16497  * @type Date
16498  * @default new Date()
16499  */
16500 defaultValue : new Date(),
16501
16502
16503 /////////////////////////////////////////////////////////////////////////////
16504 //
16505 // DateCellEditor public methods
16506 //
16507 /////////////////////////////////////////////////////////////////////////////
16508
16509 /**
16510  * Render a Calendar.
16511  *
16512  * @method renderForm
16513  */
16514 renderForm : function() {
16515     // Calendar widget
16516     if(YAHOO.widget.Calendar) {
16517         var calContainer = this.getContainerEl().appendChild(document.createElement("div"));
16518         calContainer.id = this.getId() + "-dateContainer"; // Needed for Calendar constructor
16519         var calendar =
16520                 new YAHOO.widget.Calendar(this.getId() + "-date",
16521                 calContainer.id, this.calendarOptions);
16522         calendar.render();
16523         calContainer.style.cssFloat = "none";
16524
16525         if(ua.ie) {
16526             var calFloatClearer = this.getContainerEl().appendChild(document.createElement("div"));
16527             calFloatClearer.style.clear = "both";
16528         }
16529         
16530         this.calendar = calendar;
16531
16532         if(this.disableBtns) {
16533             this.handleDisabledBtns();
16534         }
16535     }
16536     else {
16537         YAHOO.log("Could not find YUI Calendar", "error", this.toString());
16538     }
16539     
16540 },
16541
16542 /**
16543  * After rendering form, if disabledBtns is set to true, then sets up a mechanism
16544  * to save input without them. 
16545  *
16546  * @method handleDisabledBtns
16547  */
16548 handleDisabledBtns : function() {
16549     this.calendar.selectEvent.subscribe(function(v){
16550         // Save on select
16551         this.save();
16552     }, this, true);
16553 },
16554
16555 /**
16556  * Resets DateCellEditor UI to initial state.
16557  *
16558  * @method resetForm
16559  */
16560 resetForm : function() {
16561     var value = this.value;
16562     var selectedValue = (value.getMonth()+1)+"/"+value.getDate()+"/"+value.getFullYear();
16563     this.calendar.cfg.setProperty("selected",selectedValue,false);
16564         this.calendar.render();
16565 },
16566
16567 /**
16568  * Sets focus in DateCellEditor.
16569  *
16570  * @method focus
16571  */
16572 focus : function() {
16573     // To be impmlemented by subclass
16574 },
16575
16576 /**
16577  * Retrieves input value from DateCellEditor.
16578  *
16579  * @method getInputValue
16580  */
16581 getInputValue : function() {
16582     return this.calendar.getSelectedDates()[0];
16583 }
16584
16585 });
16586
16587 // Copy static members to DateCellEditor class
16588 lang.augmentObject(widget.DateCellEditor, BCE);
16589
16590
16591
16592
16593
16594
16595
16596
16597
16598 /****************************************************************************/
16599 /****************************************************************************/
16600 /****************************************************************************/
16601     
16602 /**
16603  * The DropdownCellEditor class provides functionality for inline editing
16604  * DataTable cell data a SELECT element.
16605  *
16606  * @namespace YAHOO.widget
16607  * @class DropdownCellEditor
16608  * @extends YAHOO.widget.BaseCellEditor 
16609  * @constructor
16610  * @param oConfigs {Object} (Optional) Object literal of configs.
16611  */
16612 widget.DropdownCellEditor = function(oConfigs) {
16613     this._sId = "yui-dropdownceditor" + YAHOO.widget.BaseCellEditor._nCount++;
16614     widget.DropdownCellEditor.superclass.constructor.call(this, "dropdown", oConfigs); 
16615 };
16616
16617 // DropdownCellEditor extends BaseCellEditor
16618 lang.extend(widget.DropdownCellEditor, BCE, {
16619
16620 /////////////////////////////////////////////////////////////////////////////
16621 //
16622 // DropdownCellEditor public properties
16623 //
16624 /////////////////////////////////////////////////////////////////////////////
16625 /**
16626  * Array of dropdown values. Can either be a simple array (e.g., 
16627  * ["Alabama","Alaska","Arizona","Arkansas"]) or a an array of objects (e.g., 
16628  * [{label:"Alabama", value:"AL"}, {label:"Alaska", value:"AK"},
16629  * {label:"Arizona", value:"AZ"}, {label:"Arkansas", value:"AR"}]). 
16630  *
16631  * @property dropdownOptions
16632  * @type String[] | Object[]
16633  */
16634 dropdownOptions : null,
16635
16636 /**
16637  * Reference to Dropdown element.
16638  *
16639  * @property dropdown
16640  * @type HTMLElement
16641  */
16642 dropdown : null,
16643
16644
16645 /////////////////////////////////////////////////////////////////////////////
16646 //
16647 // DropdownCellEditor public methods
16648 //
16649 /////////////////////////////////////////////////////////////////////////////
16650
16651 /**
16652  * Render a form with select element.
16653  *
16654  * @method renderForm
16655  */
16656 renderForm : function() {
16657     var elDropdown = this.getContainerEl().appendChild(document.createElement("select"));
16658     elDropdown.style.zoom = 1;
16659     this.dropdown = elDropdown;
16660     
16661     if(lang.isArray(this.dropdownOptions)) {
16662         var dropdownOption, elOption;
16663         for(var i=0, j=this.dropdownOptions.length; i<j; i++) {
16664             dropdownOption = this.dropdownOptions[i];
16665             elOption = document.createElement("option");
16666             elOption.value = (lang.isValue(dropdownOption.value)) ?
16667                     dropdownOption.value : dropdownOption;
16668             elOption.innerHTML = (lang.isValue(dropdownOption.label)) ?
16669                     dropdownOption.label : dropdownOption;
16670             elOption = elDropdown.appendChild(elOption);
16671         }
16672         
16673         if(this.disableBtns) {
16674             this.handleDisabledBtns();
16675         }
16676     }
16677 },
16678
16679 /**
16680  * After rendering form, if disabledBtns is set to true, then sets up a mechanism
16681  * to save input without them. 
16682  *
16683  * @method handleDisabledBtns
16684  */
16685 handleDisabledBtns : function() {
16686     Ev.addListener(this.dropdown, "change", function(v){
16687         // Save on change
16688         this.save();
16689     }, this, true);        
16690 },
16691
16692 /**
16693  * Resets DropdownCellEditor UI to initial state.
16694  *
16695  * @method resetForm
16696  */
16697 resetForm : function() {
16698     for(var i=0, j=this.dropdown.options.length; i<j; i++) {
16699         if(this.value === this.dropdown.options[i].value) {
16700             this.dropdown.options[i].selected = true;
16701         }
16702     }    
16703 },
16704
16705 /**
16706  * Sets focus in DropdownCellEditor.
16707  *
16708  * @method focus
16709  */
16710 focus : function() {
16711     this.getDataTable()._focusEl(this.dropdown);
16712 },
16713
16714 /**
16715  * Retrieves input value from DropdownCellEditor.
16716  *
16717  * @method getInputValue
16718  */
16719 getInputValue : function() {
16720     return this.dropdown.options[this.dropdown.options.selectedIndex].value;
16721 }
16722
16723 });
16724
16725 // Copy static members to DropdownCellEditor class
16726 lang.augmentObject(widget.DropdownCellEditor, BCE);
16727
16728
16729
16730
16731
16732
16733 /****************************************************************************/
16734 /****************************************************************************/
16735 /****************************************************************************/
16736     
16737 /**
16738  * The RadioCellEditor class provides functionality for inline editing
16739  * DataTable cell data with radio buttons.
16740  *
16741  * @namespace YAHOO.widget
16742  * @class RadioCellEditor
16743  * @extends YAHOO.widget.BaseCellEditor 
16744  * @constructor
16745  * @param oConfigs {Object} (Optional) Object literal of configs.
16746  */
16747 widget.RadioCellEditor = function(oConfigs) {
16748     this._sId = "yui-radioceditor" + YAHOO.widget.BaseCellEditor._nCount++;
16749     widget.RadioCellEditor.superclass.constructor.call(this, "radio", oConfigs); 
16750 };
16751
16752 // RadioCellEditor extends BaseCellEditor
16753 lang.extend(widget.RadioCellEditor, BCE, {
16754
16755 /////////////////////////////////////////////////////////////////////////////
16756 //
16757 // RadioCellEditor public properties
16758 //
16759 /////////////////////////////////////////////////////////////////////////////
16760 /**
16761  * Reference to radio elements.
16762  *
16763  * @property radios
16764  * @type HTMLElement[]
16765  */
16766 radios : null,
16767
16768 /**
16769  * Array of radio values. Can either be a simple array (e.g., ["yes","no","maybe"])
16770  * or a an array of objects (e.g., [{label:"yes", value:1}, {label:"no", value:-1},
16771  * {label:"maybe", value:0}]). 
16772  *
16773  * @property radioOptions
16774  * @type String[] | Object[]
16775  */
16776 radioOptions : null,
16777
16778 /////////////////////////////////////////////////////////////////////////////
16779 //
16780 // RadioCellEditor public methods
16781 //
16782 /////////////////////////////////////////////////////////////////////////////
16783
16784 /**
16785  * Render a form with input(s) type=radio.
16786  *
16787  * @method renderForm
16788  */
16789 renderForm : function() {
16790     if(lang.isArray(this.radioOptions)) {
16791         var radioOption, radioValue, radioId, elLabel;
16792         
16793         // Create the radio buttons in an IE-friendly way
16794         for(var i=0, len=this.radioOptions.length; i<len; i++) {
16795             radioOption = this.radioOptions[i];
16796             radioValue = lang.isValue(radioOption.value) ?
16797                     radioOption.value : radioOption;
16798             radioId = this.getId() + "-radio" + i;
16799             this.getContainerEl().innerHTML += "<input type=\"radio\"" +
16800                     " name=\"" + this.getId() + "\"" +
16801                     " value=\"" + radioValue + "\"" +
16802                     " id=\"" +  radioId + "\" />"; // Needed for label
16803             
16804             // Create the labels in an IE-friendly way
16805             elLabel = this.getContainerEl().appendChild(document.createElement("label"));
16806             elLabel.htmlFor = radioId;
16807             elLabel.innerHTML = (lang.isValue(radioOption.label)) ?
16808                     radioOption.label : radioOption;
16809         }
16810         
16811         // Store the reference to the checkbox elements
16812         var allRadios = [],
16813             elRadio;
16814         for(var j=0; j<len; j++) {
16815             elRadio = this.getContainerEl().childNodes[j*2];
16816             allRadios[allRadios.length] = elRadio;
16817         }
16818         this.radios = allRadios;
16819
16820         if(this.disableBtns) {
16821             this.handleDisabledBtns();
16822         }
16823     }
16824     else {
16825         YAHOO.log("Could not find radioOptions", "error", this.toString());
16826     }
16827 },
16828
16829 /**
16830  * After rendering form, if disabledBtns is set to true, then sets up a mechanism
16831  * to save input without them. 
16832  *
16833  * @method handleDisabledBtns
16834  */
16835 handleDisabledBtns : function() {
16836     Ev.addListener(this.getContainerEl(), "click", function(v){
16837         if(Ev.getTarget(v).tagName.toLowerCase() === "input") {
16838             // Save on blur
16839             this.save();
16840         }
16841     }, this, true);
16842 },
16843
16844 /**
16845  * Resets RadioCellEditor UI to initial state.
16846  *
16847  * @method resetForm
16848  */
16849 resetForm : function() {
16850     for(var i=0, j=this.radios.length; i<j; i++) {
16851         var elRadio = this.radios[i];
16852         if(this.value === elRadio.value) {
16853             elRadio.checked = true;
16854             return;
16855         }
16856     }
16857 },
16858
16859 /**
16860  * Sets focus in RadioCellEditor.
16861  *
16862  * @method focus
16863  */
16864 focus : function() {
16865     for(var i=0, j=this.radios.length; i<j; i++) {
16866         if(this.radios[i].checked) {
16867             this.radios[i].focus();
16868             return;
16869         }
16870     }
16871 },
16872
16873 /**
16874  * Retrieves input value from RadioCellEditor.
16875  *
16876  * @method getInputValue
16877  */
16878 getInputValue : function() {
16879     for(var i=0, j=this.radios.length; i<j; i++) {
16880         if(this.radios[i].checked) {
16881             return this.radios[i].value;
16882         }
16883     }
16884 }
16885
16886 });
16887
16888 // Copy static members to RadioCellEditor class
16889 lang.augmentObject(widget.RadioCellEditor, BCE);
16890
16891
16892
16893
16894
16895
16896 /****************************************************************************/
16897 /****************************************************************************/
16898 /****************************************************************************/
16899     
16900 /**
16901  * The TextareaCellEditor class provides functionality for inline editing
16902  * DataTable cell data with a TEXTAREA element.
16903  *
16904  * @namespace YAHOO.widget
16905  * @class TextareaCellEditor
16906  * @extends YAHOO.widget.BaseCellEditor 
16907  * @constructor
16908  * @param oConfigs {Object} (Optional) Object literal of configs.
16909  */
16910 widget.TextareaCellEditor = function(oConfigs) {
16911     this._sId = "yui-textareaceditor" + YAHOO.widget.BaseCellEditor._nCount++;
16912     widget.TextareaCellEditor.superclass.constructor.call(this, "textarea", oConfigs); 
16913 };
16914
16915 // TextareaCellEditor extends BaseCellEditor
16916 lang.extend(widget.TextareaCellEditor, BCE, {
16917
16918 /////////////////////////////////////////////////////////////////////////////
16919 //
16920 // TextareaCellEditor public properties
16921 //
16922 /////////////////////////////////////////////////////////////////////////////
16923 /**
16924  * Reference to textarea element.
16925  *
16926  * @property textarea
16927  * @type HTMLElement
16928  */
16929 textarea : null,
16930
16931
16932 /////////////////////////////////////////////////////////////////////////////
16933 //
16934 // TextareaCellEditor public methods
16935 //
16936 /////////////////////////////////////////////////////////////////////////////
16937
16938 /**
16939  * Render a form with textarea.
16940  *
16941  * @method renderForm
16942  */
16943 renderForm : function() {
16944     var elTextarea = this.getContainerEl().appendChild(document.createElement("textarea"));
16945     this.textarea = elTextarea;
16946
16947     if(this.disableBtns) {
16948         this.handleDisabledBtns();
16949     }
16950 },
16951
16952 /**
16953  * After rendering form, if disabledBtns is set to true, then sets up a mechanism
16954  * to save input without them. 
16955  *
16956  * @method handleDisabledBtns
16957  */
16958 handleDisabledBtns : function() {
16959     Ev.addListener(this.textarea, "blur", function(v){
16960         // Save on blur
16961         this.save();
16962     }, this, true);        
16963 },
16964
16965 /**
16966  * Moves TextareaCellEditor UI to a cell.
16967  *
16968  * @method move
16969  */
16970 move : function() {
16971     this.textarea.style.width = this.getTdEl().offsetWidth + "px";
16972     this.textarea.style.height = "3em";
16973     YAHOO.widget.TextareaCellEditor.superclass.move.call(this);
16974 },
16975
16976 /**
16977  * Resets TextareaCellEditor UI to initial state.
16978  *
16979  * @method resetForm
16980  */
16981 resetForm : function() {
16982     this.textarea.value = this.value;
16983 },
16984
16985 /**
16986  * Sets focus in TextareaCellEditor.
16987  *
16988  * @method focus
16989  */
16990 focus : function() {
16991     // Bug 2303181, Bug 2263600
16992     this.getDataTable()._focusEl(this.textarea);
16993     this.textarea.select();
16994 },
16995
16996 /**
16997  * Retrieves input value from TextareaCellEditor.
16998  *
16999  * @method getInputValue
17000  */
17001 getInputValue : function() {
17002     return this.textarea.value;
17003 }
17004
17005 });
17006
17007 // Copy static members to TextareaCellEditor class
17008 lang.augmentObject(widget.TextareaCellEditor, BCE);
17009
17010
17011
17012
17013
17014
17015
17016
17017
17018 /****************************************************************************/
17019 /****************************************************************************/
17020 /****************************************************************************/
17021     
17022 /**
17023  * The TextboxCellEditor class provides functionality for inline editing
17024  * DataTable cell data with an INPUT TYPE=TEXT element.
17025  *
17026  * @namespace YAHOO.widget
17027  * @class TextboxCellEditor
17028  * @extends YAHOO.widget.BaseCellEditor 
17029  * @constructor
17030  * @param oConfigs {Object} (Optional) Object literal of configs.
17031  */
17032 widget.TextboxCellEditor = function(oConfigs) {
17033     this._sId = "yui-textboxceditor" + YAHOO.widget.BaseCellEditor._nCount++;
17034     widget.TextboxCellEditor.superclass.constructor.call(this, "textbox", oConfigs); 
17035 };
17036
17037 // TextboxCellEditor extends BaseCellEditor
17038 lang.extend(widget.TextboxCellEditor, BCE, {
17039
17040 /////////////////////////////////////////////////////////////////////////////
17041 //
17042 // TextboxCellEditor public properties
17043 //
17044 /////////////////////////////////////////////////////////////////////////////
17045 /**
17046  * Reference to the textbox element.
17047  *
17048  * @property textbox
17049  */
17050 textbox : null,
17051
17052 /////////////////////////////////////////////////////////////////////////////
17053 //
17054 // TextboxCellEditor public methods
17055 //
17056 /////////////////////////////////////////////////////////////////////////////
17057
17058 /**
17059  * Render a form with input type=text.
17060  *
17061  * @method renderForm
17062  */
17063 renderForm : function() {
17064     var elTextbox;
17065     // Bug 1802582: SF3/Mac needs a form element wrapping the input
17066     if(ua.webkit>420) {
17067         elTextbox = this.getContainerEl().appendChild(document.createElement("form")).appendChild(document.createElement("input"));
17068     }
17069     else {
17070         elTextbox = this.getContainerEl().appendChild(document.createElement("input"));
17071     }
17072     elTextbox.type = "text";
17073     this.textbox = elTextbox;
17074
17075     // Save on enter by default
17076     // Bug: 1802582 Set up a listener on each textbox to track on keypress
17077     // since SF/OP can't preventDefault on keydown
17078     Ev.addListener(elTextbox, "keypress", function(v){
17079         if((v.keyCode === 13)) {
17080             // Prevent form submit
17081             YAHOO.util.Event.preventDefault(v);
17082             this.save();
17083         }
17084     }, this, true);
17085
17086     if(this.disableBtns) {
17087         // By default this is no-op since enter saves by default
17088         this.handleDisabledBtns();
17089     }
17090 },
17091
17092 /**
17093  * Moves TextboxCellEditor UI to a cell.
17094  *
17095  * @method move
17096  */
17097 move : function() {
17098     this.textbox.style.width = this.getTdEl().offsetWidth + "px";
17099     widget.TextboxCellEditor.superclass.move.call(this);
17100 },
17101
17102 /**
17103  * Resets TextboxCellEditor UI to initial state.
17104  *
17105  * @method resetForm
17106  */
17107 resetForm : function() {
17108     this.textbox.value = lang.isValue(this.value) ? this.value.toString() : "";
17109 },
17110
17111 /**
17112  * Sets focus in TextboxCellEditor.
17113  *
17114  * @method focus
17115  */
17116 focus : function() {
17117     // Bug 2303181, Bug 2263600
17118     this.getDataTable()._focusEl(this.textbox);
17119     this.textbox.select();
17120 },
17121
17122 /**
17123  * Returns new value for TextboxCellEditor.
17124  *
17125  * @method getInputValue
17126  */
17127 getInputValue : function() {
17128     return this.textbox.value;
17129 }
17130
17131 });
17132
17133 // Copy static members to TextboxCellEditor class
17134 lang.augmentObject(widget.TextboxCellEditor, BCE);
17135
17136
17137
17138
17139
17140
17141
17142 /////////////////////////////////////////////////////////////////////////////
17143 //
17144 // DataTable extension
17145 //
17146 /////////////////////////////////////////////////////////////////////////////
17147
17148 /**
17149  * CellEditor subclasses.
17150  * @property DataTable.Editors
17151  * @type Object
17152  * @static
17153  */
17154 DT.Editors = {
17155     checkbox : widget.CheckboxCellEditor,
17156     "date"   : widget.DateCellEditor,
17157     dropdown : widget.DropdownCellEditor,
17158     radio    : widget.RadioCellEditor,
17159     textarea : widget.TextareaCellEditor,
17160     textbox  : widget.TextboxCellEditor
17161 };
17162
17163 /****************************************************************************/
17164 /****************************************************************************/
17165 /****************************************************************************/
17166     
17167 /**
17168  * Factory class for instantiating a BaseCellEditor subclass.
17169  *
17170  * @namespace YAHOO.widget
17171  * @class CellEditor
17172  * @extends YAHOO.widget.BaseCellEditor 
17173  * @constructor
17174  * @param sType {String} Type indicator, to map to YAHOO.widget.DataTable.Editors.
17175  * @param oConfigs {Object} (Optional) Object literal of configs.
17176  */
17177 widget.CellEditor = function(sType, oConfigs) {
17178     // Point to one of the subclasses
17179     if(sType && DT.Editors[sType]) {
17180         lang.augmentObject(BCE, DT.Editors[sType]);
17181         return new DT.Editors[sType](oConfigs);
17182     }
17183     else {
17184         return new BCE(null, oConfigs);
17185     }
17186 };
17187
17188 var CE = widget.CellEditor;
17189
17190 // Copy static members to CellEditor class
17191 lang.augmentObject(CE, BCE);
17192
17193
17194 })();
17195
17196 YAHOO.register("datatable", YAHOO.widget.DataTable, {version: "2.7.0", build: "1799"});