]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - include/javascript/yui/build/datatable/datatable.js
Release 6.5.0
[Github/sugarcrm.git] / include / javascript / yui / build / datatable / datatable.js
1 /*
2 Copyright (c) 2011, Yahoo! Inc. All rights reserved.
3 Code licensed under the BSD License:
4 http://developer.yahoo.com/yui/license.html
5 version: 2.9.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         // Conditional added for Caja compatibility
152         if (this.id > 0) {
153             clearTimeout(this.id);
154         }
155         this.id = 0;
156         return this;
157     },
158
159     /**
160      * Stop and clear the Chain's queue after the current execution of the
161      * current callback completes.
162      * @method stop
163      * @return {Chain} the Chain instance
164      */
165     stop : function () { 
166         this.pause();
167         this.q = [];
168         return this;
169     }
170 };
171 YAHOO.lang.augmentProto(YAHOO.util.Chain,YAHOO.util.EventProvider);
172
173 /**
174  * Augments the Event Utility with a <code>delegate</code> method that 
175  * facilitates easy creation of delegated event listeners.  (Note: Using CSS 
176  * selectors as the filtering criteria for delegated event listeners requires 
177  * inclusion of the Selector Utility.)
178  *
179  * @module event-delegate
180  * @title Event Utility Event Delegation Module
181  * @namespace YAHOO.util
182  * @requires event
183  */
184
185 (function () {
186
187     var Event = YAHOO.util.Event,
188         Lang = YAHOO.lang,
189         delegates = [],
190
191
192         getMatch = function(el, selector, container) {
193         
194             var returnVal;
195         
196             if (!el || el === container) {
197                 returnVal = false;
198             }
199             else {
200                 returnVal = YAHOO.util.Selector.test(el, selector) ? el: getMatch(el.parentNode, selector, container);
201             }
202         
203             return returnVal;
204         
205         };
206
207
208     Lang.augmentObject(Event, {
209
210         /**
211          * Creates a delegate function used to call event listeners specified 
212          * via the <code>YAHOO.util.Event.delegate</code> method.
213          *
214          * @method _createDelegate
215          *
216          * @param {Function} fn        The method (event listener) to call.
217          * @param {Function|string} filter Function or CSS selector used to 
218          * determine for what element(s) the event listener should be called.        
219          * @param {Object}   obj    An arbitrary object that will be 
220          *                             passed as a parameter to the listener.
221          * @param {Boolean|object}  overrideContext  If true, the value of the 
222          *                             obj parameter becomes the execution context
223          *                          of the listener. If an object, this object
224          *                          becomes the execution context.
225          * @return {Function} Function that will call the event listener 
226          * specified by the <code>YAHOO.util.Event.delegate</code> method.
227          * @private
228          * @for Event
229          * @static
230          */
231         _createDelegate: function (fn, filter, obj, overrideContext) {
232
233             return function (event) {
234
235                 var container = this,
236                     target = Event.getTarget(event),
237                     selector = filter,
238
239                     //    The user might have specified the document object 
240                     //    as the delegation container, in which case it is not 
241                     //    nessary to scope the provided CSS selector(s) to the 
242                     //    delegation container
243                     bDocument = (container.nodeType === 9),
244
245                     matchedEl,
246                     context,
247                     sID,
248                     sIDSelector;
249
250
251                 if (Lang.isFunction(filter)) {
252                     matchedEl = filter(target);
253                 }
254                 else if (Lang.isString(filter)) {
255
256                     if (!bDocument) {
257
258                         sID = container.id;
259
260                         if (!sID) {
261                             sID = Event.generateId(container);
262                         }                        
263
264                         //    Scope all selectors to the container
265                         sIDSelector = ("#" + sID + " ");
266                         selector = (sIDSelector + filter).replace(/,/gi, ("," + sIDSelector));
267
268                     }
269
270
271                     if (YAHOO.util.Selector.test(target, selector)) {
272                         matchedEl = target;
273                     }
274                     else if (YAHOO.util.Selector.test(target, ((selector.replace(/,/gi, " *,")) + " *"))) {
275
276                         //    The target is a descendant of an element matching 
277                         //    the selector, so crawl up to find the ancestor that 
278                         //    matches the selector
279
280                         matchedEl = getMatch(target, selector, container);
281
282                     }
283
284                 }
285
286
287                 if (matchedEl) {
288
289                     //    The default context for delegated listeners is the 
290                     //    element that matched the filter.
291
292                     context = matchedEl;
293
294                     if (overrideContext) {
295                         if (overrideContext === true) {
296                             context = obj;
297                         } else {
298                             context = overrideContext;
299                         }
300                     }
301
302                     //    Call the listener passing in the container and the 
303                     //    element that matched the filter in case the user 
304                     //    needs those.
305
306                     return fn.call(context, event, matchedEl, container, obj);
307
308                 }
309
310             };
311
312         },
313
314
315         /**
316          * Appends a delegated event listener.  Delegated event listeners 
317          * receive three arguments by default: the DOM event, the element  
318          * specified by the filtering function or CSS selector, and the 
319          * container element (the element to which the event listener is 
320          * bound).  (Note: Using the delegate method requires the event-delegate 
321          * module.  Using CSS selectors as the filtering criteria for delegated 
322          * event listeners requires inclusion of the Selector Utility.)
323          *
324          * @method delegate
325          *
326          * @param {String|HTMLElement|Array|NodeList} container An id, an element 
327          *  reference, or a collection of ids and/or elements to assign the 
328          *  listener to.
329          * @param {String}   type     The type of event listener to append
330          * @param {Function} fn        The method the event invokes
331          * @param {Function|string} filter Function or CSS selector used to 
332          * determine for what element(s) the event listener should be called. 
333          * When a function is specified, the function should return an 
334          * HTML element.  Using a CSS Selector requires the inclusion of the 
335          * CSS Selector Utility.
336          * @param {Object}   obj    An arbitrary object that will be 
337          *                             passed as a parameter to the listener
338          * @param {Boolean|object}  overrideContext  If true, the value of the obj parameter becomes
339          *                             the execution context of the listener. If an
340          *                             object, this object becomes the execution
341          *                             context.
342          * @return {Boolean} Returns true if the action was successful or defered,
343          *                   false if one or more of the elements 
344          *                   could not have the listener attached,
345          *                   or if the operation throws an exception.
346          * @static
347          * @for Event
348          */
349         delegate: function (container, type, fn, filter, obj, overrideContext) {
350
351             var sType = type,
352                 fnMouseDelegate,
353                 fnDelegate;
354
355
356             if (Lang.isString(filter) && !YAHOO.util.Selector) {
357                 return false;
358             }
359
360
361             if (type == "mouseenter" || type == "mouseleave") {
362
363                 if (!Event._createMouseDelegate) {
364                     return false;
365                 }
366
367                 //    Look up the real event--either mouseover or mouseout
368                 sType = Event._getType(type);
369
370                 fnMouseDelegate = Event._createMouseDelegate(fn, obj, overrideContext);
371
372                 fnDelegate = Event._createDelegate(function (event, matchedEl, container) {
373
374                     return fnMouseDelegate.call(matchedEl, event, container);
375
376                 }, filter, obj, overrideContext);
377
378             }
379             else {
380
381                 fnDelegate = Event._createDelegate(fn, filter, obj, overrideContext);
382
383             }
384
385             delegates.push([container, sType, fn, fnDelegate]);
386             
387             return Event.on(container, sType, fnDelegate);
388
389         },
390
391
392         /**
393          * Removes a delegated event listener.
394          *
395          * @method removeDelegate
396          *
397          * @param {String|HTMLElement|Array|NodeList} container An id, an element 
398          *  reference, or a collection of ids and/or elements to remove
399          *  the listener from.
400          * @param {String} type The type of event to remove.
401          * @param {Function} fn The method the event invokes.  If fn is
402          *  undefined, then all event listeners for the type of event are 
403          *  removed.
404          * @return {boolean} Returns true if the unbind was successful, false 
405          *  otherwise.
406          * @static
407          * @for Event
408          */
409         removeDelegate: function (container, type, fn) {
410
411             var sType = type,
412                 returnVal = false,
413                 index,
414                 cacheItem;
415
416             //    Look up the real event--either mouseover or mouseout
417             if (type == "mouseenter" || type == "mouseleave") {
418                 sType = Event._getType(type);
419             }
420
421             index = Event._getCacheIndex(delegates, container, sType, fn);
422
423             if (index >= 0) {
424                 cacheItem = delegates[index];
425             }
426
427
428             if (container && cacheItem) {
429
430                 returnVal = Event.removeListener(cacheItem[0], cacheItem[1], cacheItem[3]);
431
432                 if (returnVal) {
433                     delete delegates[index][2];
434                     delete delegates[index][3];
435                     delegates.splice(index, 1);
436                 }        
437         
438             }
439
440             return returnVal;
441
442         }
443         
444     });
445
446 }());
447
448
449 /**
450  * Augments the Event Utility with support for the mouseenter and mouseleave
451  * events:  A mouseenter event fires the first time the mouse enters an
452  * element; a mouseleave event first the first time the mouse leaves an
453  * element.
454  *
455  * @module event-mouseenter
456  * @title Event Utility mouseenter and mouseout Module
457  * @namespace YAHOO.util
458  * @requires event
459  */
460
461 (function () {
462
463     var Event = YAHOO.util.Event,
464         Lang = YAHOO.lang,
465
466         addListener = Event.addListener,
467         removeListener = Event.removeListener,
468         getListeners = Event.getListeners,
469
470         delegates = [],
471
472         specialTypes = {
473             mouseenter: "mouseover",
474             mouseleave: "mouseout"
475         },
476
477         remove = function(el, type, fn) {
478
479             var index = Event._getCacheIndex(delegates, el, type, fn),
480                 cacheItem,
481                 returnVal;
482
483             if (index >= 0) {
484                 cacheItem = delegates[index];
485             }
486
487             if (el && cacheItem) {
488
489                 //    removeListener will translate the value of type
490                 returnVal = removeListener.call(Event, cacheItem[0], type, cacheItem[3]);
491
492                 if (returnVal) {
493                     delete delegates[index][2];
494                     delete delegates[index][3];
495                     delegates.splice(index, 1);
496                 }
497
498             }
499
500             return returnVal;
501
502         };
503
504
505     Lang.augmentObject(Event._specialTypes, specialTypes);
506
507     Lang.augmentObject(Event, {
508
509         /**
510          * Creates a delegate function used to call mouseover and mouseleave
511          * event listeners specified via the
512          * <code>YAHOO.util.Event.addListener</code>
513          * or <code>YAHOO.util.Event.on</code> method.
514          *
515          * @method _createMouseDelegate
516          *
517          * @param {Function} fn        The method (event listener) to call
518          * @param {Object}   obj    An arbitrary object that will be
519          *                             passed as a parameter to the listener
520          * @param {Boolean|object}  overrideContext  If true, the value of the
521          *                             obj parameter becomes the execution context
522          *                          of the listener. If an object, this object
523          *                          becomes the execution context.
524          * @return {Function} Function that will call the event listener
525          * specified by either the <code>YAHOO.util.Event.addListener</code>
526          * or <code>YAHOO.util.Event.on</code> method.
527          * @private
528          * @static
529          * @for Event
530          */
531         _createMouseDelegate: function (fn, obj, overrideContext) {
532
533             return function (event, container) {
534
535                 var el = this,
536                     relatedTarget = Event.getRelatedTarget(event),
537                     context,
538                     args;
539
540                 if (el != relatedTarget && !YAHOO.util.Dom.isAncestor(el, relatedTarget)) {
541
542                     context = el;
543
544                     if (overrideContext) {
545                         if (overrideContext === true) {
546                             context = obj;
547                         } else {
548                             context = overrideContext;
549                         }
550                     }
551
552                     // The default args passed back to a mouseenter or
553                     // mouseleave listener are: the event, and any object
554                     // the user passed when subscribing
555
556                     args = [event, obj];
557
558                     // Add the element and delegation container as arguments
559                     // when delegating mouseenter and mouseleave
560
561                     if (container) {
562                         args.splice(1, 0, el, container);
563                     }
564
565                     return fn.apply(context, args);
566
567                 }
568
569             };
570
571         },
572
573         addListener: function (el, type, fn, obj, overrideContext) {
574
575             var fnDelegate,
576                 returnVal;
577
578             if (specialTypes[type]) {
579
580                 fnDelegate = Event._createMouseDelegate(fn, obj, overrideContext);
581
582                 fnDelegate.mouseDelegate = true;
583
584                 delegates.push([el, type, fn, fnDelegate]);
585
586                 //    addListener will translate the value of type
587                 returnVal = addListener.call(Event, el, type, fnDelegate);
588
589             }
590             else {
591                 returnVal = addListener.apply(Event, arguments);
592             }
593
594             return returnVal;
595
596         },
597
598         removeListener: function (el, type, fn) {
599
600             var returnVal;
601
602             if (specialTypes[type]) {
603                 returnVal = remove.apply(Event, arguments);
604             }
605             else {
606                 returnVal = removeListener.apply(Event, arguments);
607             }
608
609             return returnVal;
610
611         },
612
613         getListeners: function (el, type) {
614
615             //    If the user specified the type as mouseover or mouseout,
616             //    need to filter out those used by mouseenter and mouseleave.
617             //    If the user specified the type as mouseenter or mouseleave,
618             //    need to filter out the true mouseover and mouseout listeners.
619
620             var listeners = [],
621                 elListeners,
622                 bMouseOverOrOut = (type === "mouseover" || type === "mouseout"),
623                 bMouseDelegate,
624                 i,
625                 l;
626
627             if (type && (bMouseOverOrOut || specialTypes[type])) {
628
629                 elListeners = getListeners.call(Event, el, this._getType(type));
630
631                 if (elListeners) {
632
633                     for (i=elListeners.length-1; i>-1; i--) {
634
635                         l = elListeners[i];
636                         bMouseDelegate = l.fn.mouseDelegate;
637
638                         if ((specialTypes[type] && bMouseDelegate) || (bMouseOverOrOut && !bMouseDelegate)) {
639                             listeners.push(l);
640                         }
641
642                     }
643
644                 }
645
646             }
647             else {
648                 listeners = getListeners.apply(Event, arguments);
649             }
650
651             return (listeners && listeners.length) ? listeners : null;
652
653         }
654
655     }, true);
656
657     Event.on = Event.addListener;
658
659 }());
660 YAHOO.register("event-mouseenter", YAHOO.util.Event, {version: "2.9.0", build: "2800"});
661
662 var Y = YAHOO,
663     Y_DOM = YAHOO.util.Dom,
664     EMPTY_ARRAY = [],
665     Y_UA = Y.env.ua,
666     Y_Lang = Y.lang,
667     Y_DOC = document,
668     Y_DOCUMENT_ELEMENT = Y_DOC.documentElement,
669
670     Y_DOM_inDoc = Y_DOM.inDocument,
671     Y_mix = Y_Lang.augmentObject,
672     Y_guid = Y_DOM.generateId,
673
674     Y_getDoc = function(element) {
675         var doc = Y_DOC;
676         if (element) {
677             doc = (element.nodeType === 9) ? element : // element === document
678                 element.ownerDocument || // element === DOM node
679                 element.document || // element === window
680                 Y_DOC; // default
681         }
682
683         return doc;
684     },
685
686     Y_Array = function(o, startIdx) {
687         var l, a, start = startIdx || 0;
688
689         // IE errors when trying to slice HTMLElement collections
690         try {
691             return Array.prototype.slice.call(o, start);
692         } catch (e) {
693             a = [];
694             l = o.length;
695             for (; start < l; start++) {
696                 a.push(o[start]);
697             }
698             return a;
699         }
700     },
701
702     Y_DOM_allById = function(id, root) {
703         root = root || Y_DOC;
704         var nodes = [],
705             ret = [],
706             i,
707             node;
708
709         if (root.querySelectorAll) {
710             ret = root.querySelectorAll('[id="' + id + '"]');
711         } else if (root.all) {
712             nodes = root.all(id);
713
714             if (nodes) {
715                 // root.all may return HTMLElement or HTMLCollection.
716                 // some elements are also HTMLCollection (FORM, SELECT).
717                 if (nodes.nodeName) {
718                     if (nodes.id === id) { // avoid false positive on name
719                         ret.push(nodes);
720                         nodes = EMPTY_ARRAY; // done, no need to filter
721                     } else { //  prep for filtering
722                         nodes = [nodes];
723                     }
724                 }
725
726                 if (nodes.length) {
727                     // filter out matches on node.name
728                     // and element.id as reference to element with id === 'id'
729                     for (i = 0; node = nodes[i++];) {
730                         if (node.id === id  ||
731                                 (node.attributes && node.attributes.id &&
732                                 node.attributes.id.value === id)) {
733                             ret.push(node);
734                         }
735                     }
736                 }
737             }
738         } else {
739             ret = [Y_getDoc(root).getElementById(id)];
740         }
741
742         return ret;
743     };
744
745 /**
746  * The selector-native module provides support for native querySelector
747  * @module dom
748  * @submodule selector-native
749  * @for Selector
750  */
751
752 /**
753  * Provides support for using CSS selectors to query the DOM
754  * @class Selector
755  * @static
756  * @for Selector
757  */
758
759 var COMPARE_DOCUMENT_POSITION = 'compareDocumentPosition',
760     OWNER_DOCUMENT = 'ownerDocument',
761
762 Selector = {
763     _foundCache: [],
764
765     useNative: true,
766
767     _compare: ('sourceIndex' in Y_DOCUMENT_ELEMENT) ?
768         function(nodeA, nodeB) {
769             var a = nodeA.sourceIndex,
770                 b = nodeB.sourceIndex;
771
772             if (a === b) {
773                 return 0;
774             } else if (a > b) {
775                 return 1;
776             }
777
778             return -1;
779
780         } : (Y_DOCUMENT_ELEMENT[COMPARE_DOCUMENT_POSITION] ?
781         function(nodeA, nodeB) {
782             if (nodeA[COMPARE_DOCUMENT_POSITION](nodeB) & 4) {
783                 return -1;
784             } else {
785                 return 1;
786             }
787         } :
788         function(nodeA, nodeB) {
789             var rangeA, rangeB, compare;
790             if (nodeA && nodeB) {
791                 rangeA = nodeA[OWNER_DOCUMENT].createRange();
792                 rangeA.setStart(nodeA, 0);
793                 rangeB = nodeB[OWNER_DOCUMENT].createRange();
794                 rangeB.setStart(nodeB, 0);
795                 compare = rangeA.compareBoundaryPoints(1, rangeB); // 1 === Range.START_TO_END
796             }
797
798             return compare;
799
800     }),
801
802     _sort: function(nodes) {
803         if (nodes) {
804             nodes = Y_Array(nodes, 0, true);
805             if (nodes.sort) {
806                 nodes.sort(Selector._compare);
807             }
808         }
809
810         return nodes;
811     },
812
813     _deDupe: function(nodes) {
814         var ret = [],
815             i, node;
816
817         for (i = 0; (node = nodes[i++]);) {
818             if (!node._found) {
819                 ret[ret.length] = node;
820                 node._found = true;
821             }
822         }
823
824         for (i = 0; (node = ret[i++]);) {
825             node._found = null;
826             node.removeAttribute('_found');
827         }
828
829         return ret;
830     },
831
832     /**
833      * Retrieves a set of nodes based on a given CSS selector.
834      * @method query
835      *
836      * @param {string} selector The CSS Selector to test the node against.
837      * @param {HTMLElement} root optional An HTMLElement to start the query from. Defaults to Y.config.doc
838      * @param {Boolean} firstOnly optional Whether or not to return only the first match.
839      * @return {Array} An array of nodes that match the given selector.
840      * @static
841      */
842     query: function(selector, root, firstOnly, skipNative) {
843         if (typeof root == 'string') {
844             root = Y_DOM.get(root);
845             if (!root) {
846                 return (firstOnly) ? null : [];
847             }
848         } else {
849             root = root || Y_DOC;
850         }
851
852         var ret = [],
853             useNative = (Selector.useNative && Y_DOC.querySelector && !skipNative),
854             queries = [[selector, root]],
855             query,
856             result,
857             i,
858             fn = (useNative) ? Selector._nativeQuery : Selector._bruteQuery;
859
860         if (selector && fn) {
861             // split group into seperate queries
862             if (!skipNative && // already done if skipping
863                     (!useNative || root.tagName)) { // split native when element scoping is needed
864                 queries = Selector._splitQueries(selector, root);
865             }
866
867             for (i = 0; (query = queries[i++]);) {
868                 result = fn(query[0], query[1], firstOnly);
869                 if (!firstOnly) { // coerce DOM Collection to Array
870                     result = Y_Array(result, 0, true);
871                 }
872                 if (result) {
873                     ret = ret.concat(result);
874                 }
875             }
876
877             if (queries.length > 1) { // remove dupes and sort by doc order
878                 ret = Selector._sort(Selector._deDupe(ret));
879             }
880         }
881
882         Y.log('query: ' + selector + ' returning: ' + ret.length, 'info', 'Selector');
883         return (firstOnly) ? (ret[0] || null) : ret;
884
885     },
886
887     // allows element scoped queries to begin with combinator
888     // e.g. query('> p', document.body) === query('body > p')
889     _splitQueries: function(selector, node) {
890         var groups = selector.split(','),
891             queries = [],
892             prefix = '',
893             i, len;
894
895         if (node) {
896             // enforce for element scoping
897             if (node.tagName) {
898                 node.id = node.id || Y_guid();
899                 prefix = '[id="' + node.id + '"] ';
900             }
901
902             for (i = 0, len = groups.length; i < len; ++i) {
903                 selector =  prefix + groups[i];
904                 queries.push([selector, node]);
905             }
906         }
907
908         return queries;
909     },
910
911     _nativeQuery: function(selector, root, one) {
912         if (Y_UA.webkit && selector.indexOf(':checked') > -1 &&
913                 (Selector.pseudos && Selector.pseudos.checked)) { // webkit (chrome, safari) fails to find "selected"
914             return Selector.query(selector, root, one, true); // redo with skipNative true to try brute query
915         }
916         try {
917             //Y.log('trying native query with: ' + selector, 'info', 'selector-native');
918             return root['querySelector' + (one ? '' : 'All')](selector);
919         } catch(e) { // fallback to brute if available
920             //Y.log('native query error; reverting to brute query with: ' + selector, 'info', 'selector-native');
921             return Selector.query(selector, root, one, true); // redo with skipNative true
922         }
923     },
924
925     filter: function(nodes, selector) {
926         var ret = [],
927             i, node;
928
929         if (nodes && selector) {
930             for (i = 0; (node = nodes[i++]);) {
931                 if (Selector.test(node, selector)) {
932                     ret[ret.length] = node;
933                 }
934             }
935         } else {
936             Y.log('invalid filter input (nodes: ' + nodes +
937                     ', selector: ' + selector + ')', 'warn', 'Selector');
938         }
939
940         return ret;
941     },
942
943     test: function(node, selector, root) {
944         var ret = false,
945             groups = selector.split(','),
946             useFrag = false,
947             parent,
948             item,
949             items,
950             frag,
951             i, j, group;
952
953         if (node && node.tagName) { // only test HTMLElements
954
955             // we need a root if off-doc
956             if (!root && !Y_DOM_inDoc(node)) {
957                 parent = node.parentNode;
958                 if (parent) {
959                     root = parent;
960                 } else { // only use frag when no parent to query
961                     frag = node[OWNER_DOCUMENT].createDocumentFragment();
962                     frag.appendChild(node);
963                     root = frag;
964                     useFrag = true;
965                 }
966             }
967             root = root || node[OWNER_DOCUMENT];
968
969             if (!node.id) {
970                 node.id = Y_guid();
971             }
972             for (i = 0; (group = groups[i++]);) { // TODO: off-dom test
973                 group += '[id="' + node.id + '"]';
974                 items = Selector.query(group, root);
975
976                 for (j = 0; item = items[j++];) {
977                     if (item === node) {
978                         ret = true;
979                         break;
980                     }
981                 }
982                 if (ret) {
983                     break;
984                 }
985             }
986
987             if (useFrag) { // cleanup
988                 frag.removeChild(node);
989             }
990         }
991
992         return ret;
993     }
994
995 };
996
997 YAHOO.util.Selector = Selector;
998 /**
999  * The selector module provides helper methods allowing CSS2 Selectors to be used with DOM elements.
1000  * @module dom
1001  * @submodule selector-css2
1002  * @for Selector
1003  */
1004
1005 /**
1006  * Provides helper methods for collecting and filtering DOM elements.
1007  */
1008
1009 var PARENT_NODE = 'parentNode',
1010     TAG_NAME = 'tagName',
1011     ATTRIBUTES = 'attributes',
1012     COMBINATOR = 'combinator',
1013     PSEUDOS = 'pseudos',
1014
1015     SelectorCSS2 = {
1016         _reRegExpTokens: /([\^\$\?\[\]\*\+\-\.\(\)\|\\])/, // TODO: move?
1017         SORT_RESULTS: true,
1018         _children: function(node, tag) {
1019             var ret = node.children,
1020                 i,
1021                 children = [],
1022                 childNodes,
1023                 child;
1024
1025             if (node.children && tag && node.children.tags) {
1026                 children = node.children.tags(tag);
1027             } else if ((!ret && node[TAG_NAME]) || (ret && tag)) { // only HTMLElements have children
1028                 childNodes = ret || node.childNodes;
1029                 ret = [];
1030                 for (i = 0; (child = childNodes[i++]);) {
1031                     if (child.tagName) {
1032                         if (!tag || tag === child.tagName) {
1033                             ret.push(child);
1034                         }
1035                     }
1036                 }
1037             }
1038
1039             return ret || [];
1040         },
1041
1042         _re: {
1043             //attr: /(\[.*\])/g,
1044             attr: /(\[[^\]]*\])/g,
1045             //esc: /\\[:\[][\w\d\]]*/gi,
1046             esc: /\\[:\[\]\(\)#\.\'\>+~"]/gi,
1047             //pseudos: /:([\-\w]+(?:\(?:['"]?(.+)['"]?\))*)/i
1048             pseudos: /(\([^\)]*\))/g
1049         },
1050
1051         /**
1052          * Mapping of shorthand tokens to corresponding attribute selector
1053          * @property shorthand
1054          * @type object
1055          */
1056         shorthand: {
1057             //'\\#([^\\s\\\\(\\[:]*)': '[id=$1]',
1058             '\\#(-?[_a-z]+[-\\w\\uE000]*)': '[id=$1]',
1059             //'\\#([^\\s\\\.:\\[\\]]*)': '[id=$1]',
1060             //'\\.([^\\s\\\\(\\[:]*)': '[className=$1]'
1061             '\\.(-?[_a-z]+[-\\w\\uE000]*)': '[className~=$1]'
1062         },
1063
1064         /**
1065          * List of operators and corresponding boolean functions.
1066          * These functions are passed the attribute and the current node's value of the attribute.
1067          * @property operators
1068          * @type object
1069          */
1070         operators: {
1071             '': function(node, attr) { return !!node.getAttribute(attr); }, // Just test for existence of attribute
1072             //'': '.+',
1073             //'=': '^{val}$', // equality
1074             '~=': '(?:^|\\s+){val}(?:\\s+|$)', // space-delimited
1075             '|=': '^{val}(?:-|$)' // optional hyphen-delimited
1076         },
1077
1078         pseudos: {
1079            'first-child': function(node) {
1080                 return Selector._children(node[PARENT_NODE])[0] === node;
1081             }
1082         },
1083
1084         _bruteQuery: function(selector, root, firstOnly) {
1085             var ret = [],
1086                 nodes = [],
1087                 tokens = Selector._tokenize(selector),
1088                 token = tokens[tokens.length - 1],
1089                 rootDoc = Y_getDoc(root),
1090                 child,
1091                 id,
1092                 className,
1093                 tagName;
1094
1095
1096             // if we have an initial ID, set to root when in document
1097             /*
1098             if (tokens[0] && rootDoc === root &&
1099                     (id = tokens[0].id) &&
1100                     rootDoc.getElementById(id)) {
1101                 root = rootDoc.getElementById(id);
1102             }
1103             */
1104
1105             if (token) {
1106                 // prefilter nodes
1107                 id = token.id;
1108                 className = token.className;
1109                 tagName = token.tagName || '*';
1110
1111                 if (root.getElementsByTagName) { // non-IE lacks DOM api on doc frags
1112                     // try ID first, unless no root.all && root not in document
1113                     // (root.all works off document, but not getElementById)
1114                     // TODO: move to allById?
1115                     if (id && (root.all || (root.nodeType === 9 || Y_DOM_inDoc(root)))) {
1116                         nodes = Y_DOM_allById(id, root);
1117                     // try className
1118                     } else if (className) {
1119                         nodes = root.getElementsByClassName(className);
1120                     } else { // default to tagName
1121                         nodes = root.getElementsByTagName(tagName);
1122                     }
1123
1124                 } else { // brute getElementsByTagName('*')
1125                     child = root.firstChild;
1126                     while (child) {
1127                         if (child.tagName) { // only collect HTMLElements
1128                             nodes.push(child);
1129                         }
1130                         child = child.nextSilbing || child.firstChild;
1131                     }
1132                 }
1133                 if (nodes.length) {
1134                     ret = Selector._filterNodes(nodes, tokens, firstOnly);
1135                 }
1136             }
1137
1138             return ret;
1139         },
1140
1141         _filterNodes: function(nodes, tokens, firstOnly) {
1142             var i = 0,
1143                 j,
1144                 len = tokens.length,
1145                 n = len - 1,
1146                 result = [],
1147                 node = nodes[0],
1148                 tmpNode = node,
1149                 getters = Selector.getters,
1150                 operator,
1151                 combinator,
1152                 token,
1153                 path,
1154                 pass,
1155                 //FUNCTION = 'function',
1156                 value,
1157                 tests,
1158                 test;
1159
1160             //do {
1161             for (i = 0; (tmpNode = node = nodes[i++]);) {
1162                 n = len - 1;
1163                 path = null;
1164
1165                 testLoop:
1166                 while (tmpNode && tmpNode.tagName) {
1167                     token = tokens[n];
1168                     tests = token.tests;
1169                     j = tests.length;
1170                     if (j && !pass) {
1171                         while ((test = tests[--j])) {
1172                             operator = test[1];
1173                             if (getters[test[0]]) {
1174                                 value = getters[test[0]](tmpNode, test[0]);
1175                             } else {
1176                                 value = tmpNode[test[0]];
1177                                 // use getAttribute for non-standard attributes
1178                                 if (value === undefined && tmpNode.getAttribute) {
1179                                     value = tmpNode.getAttribute(test[0]);
1180                                 }
1181                             }
1182
1183                             if ((operator === '=' && value !== test[2]) ||  // fast path for equality
1184                                 (typeof operator !== 'string' && // protect against String.test monkey-patch (Moo)
1185                                 operator.test && !operator.test(value)) ||  // regex test
1186                                 (!operator.test && // protect against RegExp as function (webkit)
1187                                         typeof operator === 'function' && !operator(tmpNode, test[0], test[2]))) { // function test
1188
1189                                 // skip non element nodes or non-matching tags
1190                                 if ((tmpNode = tmpNode[path])) {
1191                                     while (tmpNode &&
1192                                         (!tmpNode.tagName ||
1193                                             (token.tagName && token.tagName !== tmpNode.tagName))
1194                                     ) {
1195                                         tmpNode = tmpNode[path];
1196                                     }
1197                                 }
1198                                 continue testLoop;
1199                             }
1200                         }
1201                     }
1202
1203                     n--; // move to next token
1204                     // now that we've passed the test, move up the tree by combinator
1205                     if (!pass && (combinator = token.combinator)) {
1206                         path = combinator.axis;
1207                         tmpNode = tmpNode[path];
1208
1209                         // skip non element nodes
1210                         while (tmpNode && !tmpNode.tagName) {
1211                             tmpNode = tmpNode[path];
1212                         }
1213
1214                         if (combinator.direct) { // one pass only
1215                             path = null;
1216                         }
1217
1218                     } else { // success if we made it this far
1219                         result.push(node);
1220                         if (firstOnly) {
1221                             return result;
1222                         }
1223                         break;
1224                     }
1225                 }
1226             }// while (tmpNode = node = nodes[++i]);
1227             node = tmpNode = null;
1228             return result;
1229         },
1230
1231         combinators: {
1232             ' ': {
1233                 axis: 'parentNode'
1234             },
1235
1236             '>': {
1237                 axis: 'parentNode',
1238                 direct: true
1239             },
1240
1241
1242             '+': {
1243                 axis: 'previousSibling',
1244                 direct: true
1245             }
1246         },
1247
1248         _parsers: [
1249             {
1250                 name: ATTRIBUTES,
1251                 //re: /^\[(-?[a-z]+[\w\-]*)+([~\|\^\$\*!=]=?)?['"]?([^\]]*?)['"]?\]/i,
1252                 re: /^\uE003(-?[a-z]+[\w\-]*)+([~\|\^\$\*!=]=?)?['"]?([^\uE004'"]*)['"]?\uE004/i,
1253                 fn: function(match, token) {
1254                     var operator = match[2] || '',
1255                         operators = Selector.operators,
1256                         escVal = (match[3]) ? match[3].replace(/\\/g, '') : '',
1257                         test;
1258
1259                     // add prefiltering for ID and CLASS
1260                     if ((match[1] === 'id' && operator === '=') ||
1261                             (match[1] === 'className' &&
1262                             Y_DOCUMENT_ELEMENT.getElementsByClassName &&
1263                             (operator === '~=' || operator === '='))) {
1264                         token.prefilter = match[1];
1265
1266
1267                         match[3] = escVal;
1268
1269                         // escape all but ID for prefilter, which may run through QSA (via Dom.allById)
1270                         token[match[1]] = (match[1] === 'id') ? match[3] : escVal;
1271
1272                     }
1273
1274                     // add tests
1275                     if (operator in operators) {
1276                         test = operators[operator];
1277                         if (typeof test === 'string') {
1278                             match[3] = escVal.replace(Selector._reRegExpTokens, '\\$1');
1279                             test = new RegExp(test.replace('{val}', match[3]));
1280                         }
1281                         match[2] = test;
1282                     }
1283                     if (!token.last || token.prefilter !== match[1]) {
1284                         return match.slice(1);
1285                     }
1286                 }
1287
1288             },
1289             {
1290                 name: TAG_NAME,
1291                 re: /^((?:-?[_a-z]+[\w-]*)|\*)/i,
1292                 fn: function(match, token) {
1293                     var tag = match[1].toUpperCase();
1294                     token.tagName = tag;
1295
1296                     if (tag !== '*' && (!token.last || token.prefilter)) {
1297                         return [TAG_NAME, '=', tag];
1298                     }
1299                     if (!token.prefilter) {
1300                         token.prefilter = 'tagName';
1301                     }
1302                 }
1303             },
1304             {
1305                 name: COMBINATOR,
1306                 re: /^\s*([>+~]|\s)\s*/,
1307                 fn: function(match, token) {
1308                 }
1309             },
1310             {
1311                 name: PSEUDOS,
1312                 re: /^:([\-\w]+)(?:\uE005['"]?([^\uE005]*)['"]?\uE006)*/i,
1313                 fn: function(match, token) {
1314                     var test = Selector[PSEUDOS][match[1]];
1315                     if (test) { // reorder match array and unescape special chars for tests
1316                         if (match[2]) {
1317                             match[2] = match[2].replace(/\\/g, '');
1318                         }
1319                         return [match[2], test];
1320                     } else { // selector token not supported (possibly missing CSS3 module)
1321                         return false;
1322                     }
1323                 }
1324             }
1325             ],
1326
1327         _getToken: function(token) {
1328             return {
1329                 tagName: null,
1330                 id: null,
1331                 className: null,
1332                 attributes: {},
1333                 combinator: null,
1334                 tests: []
1335             };
1336         },
1337
1338         /**
1339             Break selector into token units per simple selector.
1340             Combinator is attached to the previous token.
1341          */
1342         _tokenize: function(selector) {
1343             selector = selector || '';
1344             selector = Selector._replaceShorthand(Y_Lang.trim(selector));
1345             var token = Selector._getToken(),     // one token per simple selector (left selector holds combinator)
1346                 query = selector, // original query for debug report
1347                 tokens = [],    // array of tokens
1348                 found = false,  // whether or not any matches were found this pass
1349                 match,         // the regex match
1350                 test,
1351                 i, parser;
1352
1353             /*
1354                 Search for selector patterns, store, and strip them from the selector string
1355                 until no patterns match (invalid selector) or we run out of chars.
1356
1357                 Multiple attributes and pseudos are allowed, in any order.
1358                 for example:
1359                     'form:first-child[type=button]:not(button)[lang|=en]'
1360             */
1361
1362             outer:
1363             do {
1364                 found = false; // reset after full pass
1365
1366                 for (i = 0; (parser = Selector._parsers[i++]);) {
1367                     if ( (match = parser.re.exec(selector)) ) { // note assignment
1368                         if (parser.name !== COMBINATOR ) {
1369                             token.selector = selector;
1370                         }
1371                         selector = selector.replace(match[0], ''); // strip current match from selector
1372                         if (!selector.length) {
1373                             token.last = true;
1374                         }
1375
1376                         if (Selector._attrFilters[match[1]]) { // convert class to className, etc.
1377                             match[1] = Selector._attrFilters[match[1]];
1378                         }
1379
1380                         test = parser.fn(match, token);
1381                         if (test === false) { // selector not supported
1382                             found = false;
1383                             break outer;
1384                         } else if (test) {
1385                             token.tests.push(test);
1386                         }
1387
1388                         if (!selector.length || parser.name === COMBINATOR) {
1389                             tokens.push(token);
1390                             token = Selector._getToken(token);
1391                             if (parser.name === COMBINATOR) {
1392                                 token.combinator = Selector.combinators[match[1]];
1393                             }
1394                         }
1395                         found = true;
1396
1397
1398                     }
1399                 }
1400             } while (found && selector.length);
1401
1402             if (!found || selector.length) { // not fully parsed
1403                 Y.log('query: ' + query + ' contains unsupported token in: ' + selector, 'warn', 'Selector');
1404                 tokens = [];
1405             }
1406             return tokens;
1407         },
1408
1409         _replaceShorthand: function(selector) {
1410             var shorthand = Selector.shorthand,
1411                 esc = selector.match(Selector._re.esc), // pull escaped colon, brackets, etc.
1412                 attrs,
1413                 pseudos,
1414                 re, i, len;
1415
1416             if (esc) {
1417                 selector = selector.replace(Selector._re.esc, '\uE000');
1418             }
1419
1420             attrs = selector.match(Selector._re.attr);
1421             pseudos = selector.match(Selector._re.pseudos);
1422
1423             if (attrs) {
1424                 selector = selector.replace(Selector._re.attr, '\uE001');
1425             }
1426
1427             if (pseudos) {
1428                 selector = selector.replace(Selector._re.pseudos, '\uE002');
1429             }
1430
1431
1432             for (re in shorthand) {
1433                 if (shorthand.hasOwnProperty(re)) {
1434                     selector = selector.replace(new RegExp(re, 'gi'), shorthand[re]);
1435                 }
1436             }
1437
1438             if (attrs) {
1439                 for (i = 0, len = attrs.length; i < len; ++i) {
1440                     selector = selector.replace(/\uE001/, attrs[i]);
1441                 }
1442             }
1443
1444             if (pseudos) {
1445                 for (i = 0, len = pseudos.length; i < len; ++i) {
1446                     selector = selector.replace(/\uE002/, pseudos[i]);
1447                 }
1448             }
1449
1450             selector = selector.replace(/\[/g, '\uE003');
1451             selector = selector.replace(/\]/g, '\uE004');
1452
1453             selector = selector.replace(/\(/g, '\uE005');
1454             selector = selector.replace(/\)/g, '\uE006');
1455
1456             if (esc) {
1457                 for (i = 0, len = esc.length; i < len; ++i) {
1458                     selector = selector.replace('\uE000', esc[i]);
1459                 }
1460             }
1461
1462             return selector;
1463         },
1464
1465         _attrFilters: {
1466             'class': 'className',
1467             'for': 'htmlFor'
1468         },
1469
1470         getters: {
1471             href: function(node, attr) {
1472                 return Y_DOM.getAttribute(node, attr);
1473             }
1474         }
1475     };
1476
1477 Y_mix(Selector, SelectorCSS2, true);
1478 Selector.getters.src = Selector.getters.rel = Selector.getters.href;
1479
1480 // IE wants class with native queries
1481 if (Selector.useNative && Y_DOC.querySelector) {
1482     Selector.shorthand['\\.([^\\s\\\\(\\[:]*)'] = '[class~=$1]';
1483 }
1484
1485 /**
1486  * The selector css3 module provides support for css3 selectors.
1487  * @module dom
1488  * @submodule selector-css3
1489  * @for Selector
1490  */
1491
1492 /*
1493     an+b = get every _a_th node starting at the _b_th
1494     0n+b = no repeat ("0" and "n" may both be omitted (together) , e.g. "0n+1" or "1", not "0+1"), return only the _b_th element
1495     1n+b =  get every element starting from b ("1" may may be omitted, e.g. "1n+0" or "n+0" or "n")
1496     an+0 = get every _a_th element, "0" may be omitted
1497 */
1498
1499 Selector._reNth = /^(?:([\-]?\d*)(n){1}|(odd|even)$)*([\-+]?\d*)$/;
1500
1501 Selector._getNth = function(node, expr, tag, reverse) {
1502     Selector._reNth.test(expr);
1503     var a = parseInt(RegExp.$1, 10), // include every _a_ elements (zero means no repeat, just first _a_)
1504         n = RegExp.$2, // "n"
1505         oddeven = RegExp.$3, // "odd" or "even"
1506         b = parseInt(RegExp.$4, 10) || 0, // start scan from element _b_
1507         result = [],
1508         siblings = Selector._children(node.parentNode, tag),
1509         op;
1510
1511     if (oddeven) {
1512         a = 2; // always every other
1513         op = '+';
1514         n = 'n';
1515         b = (oddeven === 'odd') ? 1 : 0;
1516     } else if ( isNaN(a) ) {
1517         a = (n) ? 1 : 0; // start from the first or no repeat
1518     }
1519
1520     if (a === 0) { // just the first
1521         if (reverse) {
1522             b = siblings.length - b + 1;
1523         }
1524
1525         if (siblings[b - 1] === node) {
1526             return true;
1527         } else {
1528             return false;
1529         }
1530
1531     } else if (a < 0) {
1532         reverse = !!reverse;
1533         a = Math.abs(a);
1534     }
1535
1536     if (!reverse) {
1537         for (var i = b - 1, len = siblings.length; i < len; i += a) {
1538             if ( i >= 0 && siblings[i] === node ) {
1539                 return true;
1540             }
1541         }
1542     } else {
1543         for (var i = siblings.length - b, len = siblings.length; i >= 0; i -= a) {
1544             if ( i < len && siblings[i] === node ) {
1545                 return true;
1546             }
1547         }
1548     }
1549     return false;
1550 };
1551
1552 Y_mix(Selector.pseudos, {
1553     'root': function(node) {
1554         return node === node.ownerDocument.documentElement;
1555     },
1556
1557     'nth-child': function(node, expr) {
1558         return Selector._getNth(node, expr);
1559     },
1560
1561     'nth-last-child': function(node, expr) {
1562         return Selector._getNth(node, expr, null, true);
1563     },
1564
1565     'nth-of-type': function(node, expr) {
1566         return Selector._getNth(node, expr, node.tagName);
1567     },
1568
1569     'nth-last-of-type': function(node, expr) {
1570         return Selector._getNth(node, expr, node.tagName, true);
1571     },
1572
1573     'last-child': function(node) {
1574         var children = Selector._children(node.parentNode);
1575         return children[children.length - 1] === node;
1576     },
1577
1578     'first-of-type': function(node) {
1579         return Selector._children(node.parentNode, node.tagName)[0] === node;
1580     },
1581
1582     'last-of-type': function(node) {
1583         var children = Selector._children(node.parentNode, node.tagName);
1584         return children[children.length - 1] === node;
1585     },
1586
1587     'only-child': function(node) {
1588         var children = Selector._children(node.parentNode);
1589         return children.length === 1 && children[0] === node;
1590     },
1591
1592     'only-of-type': function(node) {
1593         var children = Selector._children(node.parentNode, node.tagName);
1594         return children.length === 1 && children[0] === node;
1595     },
1596
1597     'empty': function(node) {
1598         return node.childNodes.length === 0;
1599     },
1600
1601     'not': function(node, expr) {
1602         return !Selector.test(node, expr);
1603     },
1604
1605     'contains': function(node, expr) {
1606         var text = node.innerText || node.textContent || '';
1607         return text.indexOf(expr) > -1;
1608     },
1609
1610     'checked': function(node) {
1611         return (node.checked === true || node.selected === true);
1612     },
1613
1614     enabled: function(node) {
1615         return (node.disabled !== undefined && !node.disabled);
1616     },
1617
1618     disabled: function(node) {
1619         return (node.disabled);
1620     }
1621 });
1622
1623 Y_mix(Selector.operators, {
1624     '^=': '^{val}', // Match starts with value
1625     '!=': function(node, attr, val) { return node[attr] !== val; }, // Match starts with value
1626     '$=': '{val}$', // Match ends with value
1627     '*=': '{val}' // Match contains value as substring
1628 });
1629
1630 Selector.combinators['~'] = {
1631     axis: 'previousSibling'
1632 };
1633 YAHOO.register("selector", YAHOO.util.Selector, {version: "2.9.0", build: "2800"});
1634
1635
1636
1637 /****************************************************************************/
1638 /****************************************************************************/
1639 /****************************************************************************/
1640
1641 var Dom = YAHOO.util.Dom;
1642
1643 /**
1644  * The ColumnSet class defines and manages a DataTable's Columns,
1645  * including nested hierarchies and access to individual Column instances.
1646  *
1647  * @namespace YAHOO.widget
1648  * @class ColumnSet
1649  * @uses YAHOO.util.EventProvider
1650  * @constructor
1651  * @param aDefinitions {Object[]} Array of object literals that define cells in
1652  * the THEAD.
1653  */
1654 YAHOO.widget.ColumnSet = function(aDefinitions) {
1655     this._sId = Dom.generateId(null, "yui-cs"); // "yui-cs" + YAHOO.widget.ColumnSet._nCount;
1656
1657     // First clone the defs
1658     aDefinitions = YAHOO.widget.DataTable._cloneObject(aDefinitions);
1659     this._init(aDefinitions);
1660
1661     YAHOO.widget.ColumnSet._nCount++;
1662 };
1663
1664 /////////////////////////////////////////////////////////////////////////////
1665 //
1666 // Private member variables
1667 //
1668 /////////////////////////////////////////////////////////////////////////////
1669
1670 /**
1671  * Internal class variable to index multiple ColumnSet instances.
1672  *
1673  * @property ColumnSet._nCount
1674  * @type Number
1675  * @private
1676  * @static
1677  */
1678 YAHOO.widget.ColumnSet._nCount = 0;
1679
1680 YAHOO.widget.ColumnSet.prototype = {
1681     /**
1682      * Unique instance name.
1683      *
1684      * @property _sId
1685      * @type String
1686      * @private
1687      */
1688     _sId : null,
1689
1690     /**
1691      * Array of object literal Column definitions passed to the constructor.
1692      *
1693      * @property _aDefinitions
1694      * @type Object[]
1695      * @private
1696      */
1697     _aDefinitions : null,
1698
1699     /////////////////////////////////////////////////////////////////////////////
1700     //
1701     // Public member variables
1702     //
1703     /////////////////////////////////////////////////////////////////////////////
1704
1705     /**
1706      * Top-down tree representation of Column hierarchy.
1707      *
1708      * @property tree
1709      * @type YAHOO.widget.Column[]
1710      */
1711     tree : null,
1712
1713     /**
1714      * Flattened representation of all Columns.
1715      *
1716      * @property flat
1717      * @type YAHOO.widget.Column[]
1718      * @default []
1719      */
1720     flat : null,
1721
1722     /**
1723      * Array of Columns that map one-to-one to a table column.
1724      *
1725      * @property keys
1726      * @type YAHOO.widget.Column[]
1727      * @default []
1728      */
1729     keys : null,
1730
1731     /**
1732      * ID index of nested parent hierarchies for HEADERS accessibility attribute.
1733      *
1734      * @property headers
1735      * @type String[]
1736      * @default []
1737      */
1738     headers : null,
1739
1740     /////////////////////////////////////////////////////////////////////////////
1741     //
1742     // Private methods
1743     //
1744     /////////////////////////////////////////////////////////////////////////////
1745
1746     /**
1747      * Initializes ColumnSet instance with data from Column definitions.
1748      *
1749      * @method _init
1750      * @param aDefinitions {Object[]} Array of object literals that define cells in
1751      * the THEAD .
1752      * @private
1753      */
1754
1755     _init : function(aDefinitions) {        
1756         // DOM tree representation of all Columns
1757         var tree = [];
1758         // Flat representation of all Columns
1759         var flat = [];
1760         // Flat representation of only Columns that are meant to display data
1761         var keys = [];
1762         // Array of HEADERS attribute values for all keys in the "keys" array
1763         var headers = [];
1764
1765         // Tracks current node list depth being tracked
1766         var nodeDepth = -1;
1767
1768         // Internal recursive function to define Column instances
1769         var parseColumns = function(nodeList, parent) {
1770             // One level down
1771             nodeDepth++;
1772
1773             // Create corresponding tree node if not already there for this depth
1774             if(!tree[nodeDepth]) {
1775                 tree[nodeDepth] = [];
1776             }
1777
1778
1779             // Parse each node at this depth for attributes and any children
1780             for(var j=0; j<nodeList.length; j++) {
1781                 var currentNode = nodeList[j];
1782
1783                 // Instantiate a new Column for each node
1784                 var oColumn = new YAHOO.widget.Column(currentNode);
1785                 
1786                 // Cross-reference Column ID back to the original object literal definition
1787                 currentNode.yuiColumnId = oColumn._sId;
1788                 
1789                 // Add the new Column to the flat list
1790                 flat.push(oColumn);
1791
1792                 // Assign its parent as an attribute, if applicable
1793                 if(parent) {
1794                     oColumn._oParent = parent;
1795                 }
1796
1797                 // The Column has descendants
1798                 if(YAHOO.lang.isArray(currentNode.children)) {
1799                     oColumn.children = currentNode.children;
1800
1801                     // Determine COLSPAN value for this Column
1802                     var terminalChildNodes = 0;
1803                     var countTerminalChildNodes = function(ancestor) {
1804                         var descendants = ancestor.children;
1805                         // Drill down each branch and count terminal nodes
1806                         for(var k=0; k<descendants.length; k++) {
1807                             // Keep drilling down
1808                             if(YAHOO.lang.isArray(descendants[k].children)) {
1809                                 countTerminalChildNodes(descendants[k]);
1810                             }
1811                             // Reached branch terminus
1812                             else {
1813                                 terminalChildNodes++;
1814                             }
1815                         }
1816                     };
1817                     countTerminalChildNodes(currentNode);
1818                     oColumn._nColspan = terminalChildNodes;
1819
1820                     // Cascade certain properties to children if not defined on their own
1821                     var currentChildren = currentNode.children;
1822                     for(var k=0; k<currentChildren.length; k++) {
1823                         var child = currentChildren[k];
1824                         if(oColumn.className && (child.className === undefined)) {
1825                             child.className = oColumn.className;
1826                         }
1827                         if(oColumn.editor && (child.editor === undefined)) {
1828                             child.editor = oColumn.editor;
1829                         }
1830                         //TODO: Deprecated
1831                         if(oColumn.editorOptions && (child.editorOptions === undefined)) {
1832                             child.editorOptions = oColumn.editorOptions;
1833                         }
1834                         if(oColumn.formatter && (child.formatter === undefined)) {
1835                             child.formatter = oColumn.formatter;
1836                         }
1837                         if(oColumn.resizeable && (child.resizeable === undefined)) {
1838                             child.resizeable = oColumn.resizeable;
1839                         }
1840                         if(oColumn.sortable && (child.sortable === undefined)) {
1841                             child.sortable = oColumn.sortable;
1842                         }
1843                         if(oColumn.hidden) {
1844                             child.hidden = true;
1845                         }
1846                         if(oColumn.width && (child.width === undefined)) {
1847                             child.width = oColumn.width;
1848                         }
1849                         if(oColumn.minWidth && (child.minWidth === undefined)) {
1850                             child.minWidth = oColumn.minWidth;
1851                         }
1852                         if(oColumn.maxAutoWidth && (child.maxAutoWidth === undefined)) {
1853                             child.maxAutoWidth = oColumn.maxAutoWidth;
1854                         }
1855                         // Backward compatibility
1856                         if(oColumn.type && (child.type === undefined)) {
1857                             child.type = oColumn.type;
1858                         }
1859                         if(oColumn.type && !oColumn.formatter) {
1860                             oColumn.formatter = oColumn.type;
1861                         }
1862                         if(oColumn.text && !YAHOO.lang.isValue(oColumn.label)) {
1863                             oColumn.label = oColumn.text;
1864                         }
1865                         if(oColumn.parser) {
1866                         }
1867                         if(oColumn.sortOptions && ((oColumn.sortOptions.ascFunction) ||
1868                                 (oColumn.sortOptions.descFunction))) {
1869                         }
1870                     }
1871
1872                     // The children themselves must also be parsed for Column instances
1873                     if(!tree[nodeDepth+1]) {
1874                         tree[nodeDepth+1] = [];
1875                     }
1876                     parseColumns(currentChildren, oColumn);
1877                 }
1878                 // This Column does not have any children
1879                 else {
1880                     oColumn._nKeyIndex = keys.length;
1881                     oColumn._nColspan = 1;
1882                     keys.push(oColumn);
1883                 }
1884
1885                 // Add the Column to the top-down tree
1886                 tree[nodeDepth].push(oColumn);
1887             }
1888             nodeDepth--;
1889         };
1890
1891         // Parse out Column instances from the array of object literals
1892         if(YAHOO.lang.isArray(aDefinitions)) {
1893             parseColumns(aDefinitions);
1894
1895             // Store the array
1896             this._aDefinitions = aDefinitions;
1897         }
1898         else {
1899             return null;
1900         }
1901
1902         var i;
1903
1904         // Determine ROWSPAN value for each Column in the tree
1905         var parseTreeForRowspan = function(tree) {
1906             var maxRowDepth = 1;
1907             var currentRow;
1908             var currentColumn;
1909
1910             // Calculate the max depth of descendants for this row
1911             var countMaxRowDepth = function(row, tmpRowDepth) {
1912                 tmpRowDepth = tmpRowDepth || 1;
1913
1914                 for(var n=0; n<row.length; n++) {
1915                     var col = row[n];
1916                     // Column has children, so keep counting
1917                     if(YAHOO.lang.isArray(col.children)) {
1918                         tmpRowDepth++;
1919                         countMaxRowDepth(col.children, tmpRowDepth);
1920                         tmpRowDepth--;
1921                     }
1922                     // No children, is it the max depth?
1923                     else {
1924                         if(tmpRowDepth > maxRowDepth) {
1925                             maxRowDepth = tmpRowDepth;
1926                         }
1927                     }
1928
1929                 }
1930             };
1931
1932             // Count max row depth for each row
1933             for(var m=0; m<tree.length; m++) {
1934                 currentRow = tree[m];
1935                 countMaxRowDepth(currentRow);
1936
1937                 // Assign the right ROWSPAN values to each Column in the row
1938                 for(var p=0; p<currentRow.length; p++) {
1939                     currentColumn = currentRow[p];
1940                     if(!YAHOO.lang.isArray(currentColumn.children)) {
1941                         currentColumn._nRowspan = maxRowDepth;
1942                     }
1943                     else {
1944                         currentColumn._nRowspan = 1;
1945                     }
1946                 }
1947
1948                 // Reset counter for next row
1949                 maxRowDepth = 1;
1950             }
1951         };
1952         parseTreeForRowspan(tree);
1953
1954         // Store tree index values
1955         for(i=0; i<tree[0].length; i++) {
1956             tree[0][i]._nTreeIndex = i;
1957         }
1958
1959         // Store header relationships in an array for HEADERS attribute
1960         var recurseAncestorsForHeaders = function(i, oColumn) {
1961             headers[i].push(oColumn.getSanitizedKey());
1962             if(oColumn._oParent) {
1963                 recurseAncestorsForHeaders(i, oColumn._oParent);
1964             }
1965         };
1966         for(i=0; i<keys.length; i++) {
1967             headers[i] = [];
1968             recurseAncestorsForHeaders(i, keys[i]);
1969             headers[i] = headers[i].reverse();
1970         }
1971
1972         // Save to the ColumnSet instance
1973         this.tree = tree;
1974         this.flat = flat;
1975         this.keys = keys;
1976         this.headers = headers;
1977     },
1978
1979     /////////////////////////////////////////////////////////////////////////////
1980     //
1981     // Public methods
1982     //
1983     /////////////////////////////////////////////////////////////////////////////
1984
1985     /**
1986      * Returns unique name of the ColumnSet instance.
1987      *
1988      * @method getId
1989      * @return {String} Unique name of the ColumnSet instance.
1990      */
1991
1992     getId : function() {
1993         return this._sId;
1994     },
1995
1996     /**
1997      * ColumnSet instance name, for logging.
1998      *
1999      * @method toString
2000      * @return {String} Unique name of the ColumnSet instance.
2001      */
2002
2003     toString : function() {
2004         return "ColumnSet instance " + this._sId;
2005     },
2006
2007     /**
2008      * Public accessor to the definitions array.
2009      *
2010      * @method getDefinitions
2011      * @return {Object[]} Array of object literal Column definitions.
2012      */
2013
2014     getDefinitions : function() {
2015         var aDefinitions = this._aDefinitions;
2016         
2017         // Internal recursive function to define Column instances
2018         var parseColumns = function(nodeList, oSelf) {
2019             // Parse each node at this depth for attributes and any children
2020             for(var j=0; j<nodeList.length; j++) {
2021                 var currentNode = nodeList[j];
2022                 
2023                 // Get the Column for each node
2024                 var oColumn = oSelf.getColumnById(currentNode.yuiColumnId);
2025                 
2026                 if(oColumn) {    
2027                     // Update the current values
2028                     var oDefinition = oColumn.getDefinition();
2029                     for(var name in oDefinition) {
2030                         if(YAHOO.lang.hasOwnProperty(oDefinition, name)) {
2031                             currentNode[name] = oDefinition[name];
2032                         }
2033                     }
2034                 }
2035                             
2036                 // The Column has descendants
2037                 if(YAHOO.lang.isArray(currentNode.children)) {
2038                     // The children themselves must also be parsed for Column instances
2039                     parseColumns(currentNode.children, oSelf);
2040                 }
2041             }
2042         };
2043
2044         parseColumns(aDefinitions, this);
2045         this._aDefinitions = aDefinitions;
2046         return aDefinitions;
2047     },
2048
2049     /**
2050      * Returns Column instance with given ID.
2051      *
2052      * @method getColumnById
2053      * @param column {String} Column ID.
2054      * @return {YAHOO.widget.Column} Column instance.
2055      */
2056
2057     getColumnById : function(column) {
2058         if(YAHOO.lang.isString(column)) {
2059             var allColumns = this.flat;
2060             for(var i=allColumns.length-1; i>-1; i--) {
2061                 if(allColumns[i]._sId === column) {
2062                     return allColumns[i];
2063                 }
2064             }
2065         }
2066         return null;
2067     },
2068
2069     /**
2070      * Returns Column instance with given key or ColumnSet key index.
2071      *
2072      * @method getColumn
2073      * @param column {String | Number} Column key or ColumnSet key index.
2074      * @return {YAHOO.widget.Column} Column instance.
2075      */
2076
2077     getColumn : function(column) {
2078         if(YAHOO.lang.isNumber(column) && this.keys[column]) {
2079             return this.keys[column];
2080         }
2081         else if(YAHOO.lang.isString(column)) {
2082             var allColumns = this.flat;
2083             var aColumns = [];
2084             for(var i=0; i<allColumns.length; i++) {
2085                 if(allColumns[i].key === column) {
2086                     aColumns.push(allColumns[i]);
2087                 }
2088             }
2089             if(aColumns.length === 1) {
2090                 return aColumns[0];
2091             }
2092             else if(aColumns.length > 1) {
2093                 return aColumns;
2094             }
2095         }
2096         return null;
2097     },
2098
2099     /**
2100      * Public accessor returns array of given Column's desendants (if any), including itself.
2101      *
2102      * @method getDescendants
2103      * @parem {YAHOO.widget.Column} Column instance.
2104      * @return {Array} Array including the Column itself and all descendants (if any).
2105      */
2106     getDescendants : function(oColumn) {
2107         var oSelf = this;
2108         var allDescendants = [];
2109         var i;
2110
2111         // Recursive function to loop thru all children
2112         var parse = function(oParent) {
2113             allDescendants.push(oParent);
2114             // This Column has children
2115             if(oParent.children) {
2116                 for(i=0; i<oParent.children.length; i++) {
2117                     parse(oSelf.getColumn(oParent.children[i].key));
2118                 }
2119             }
2120         };
2121         parse(oColumn);
2122
2123         return allDescendants;
2124     }
2125 };
2126
2127 /****************************************************************************/
2128 /****************************************************************************/
2129 /****************************************************************************/
2130
2131 /**
2132  * The Column class defines and manages attributes of DataTable Columns
2133  *
2134  * @namespace YAHOO.widget
2135  * @class Column
2136  * @constructor
2137  * @param oConfigs {Object} Object literal of definitions.
2138  */
2139 YAHOO.widget.Column = function(oConfigs) {
2140     this._sId = Dom.generateId(null, "yui-col"); // "yui-col" + YAHOO.widget.Column._nCount;
2141     
2142     // Object literal defines Column attributes
2143     if(oConfigs && YAHOO.lang.isObject(oConfigs)) {
2144         for(var sConfig in oConfigs) {
2145             if(sConfig) {
2146                 this[sConfig] = oConfigs[sConfig];
2147             }
2148         }
2149     }
2150
2151     // Assign a key if not found
2152     if(!YAHOO.lang.isValue(this.key)) {
2153         this.key = Dom.generateId(null, "yui-dt-col"); //"yui-dt-col" + YAHOO.widget.Column._nCount;
2154     }
2155     
2156     // Assign a field if not found, defaults to key
2157     if(!YAHOO.lang.isValue(this.field)) {
2158         this.field = this.key;
2159     }
2160
2161     // Increment counter
2162     YAHOO.widget.Column._nCount++;
2163
2164     // Backward compatibility
2165     if(this.width && !YAHOO.lang.isNumber(this.width)) {
2166         this.width = null;
2167     }
2168     if(this.editor && YAHOO.lang.isString(this.editor)) {
2169         this.editor = new YAHOO.widget.CellEditor(this.editor, this.editorOptions);
2170     }
2171 };
2172
2173 /////////////////////////////////////////////////////////////////////////////
2174 //
2175 // Private member variables
2176 //
2177 /////////////////////////////////////////////////////////////////////////////
2178
2179 YAHOO.lang.augmentObject(YAHOO.widget.Column, {
2180     /**
2181      * Internal class variable to index multiple Column instances.
2182      *
2183      * @property Column._nCount
2184      * @type Number
2185      * @private
2186      * @static
2187      */
2188     _nCount : 0,
2189
2190     formatCheckbox : function(elCell, oRecord, oColumn, oData) {
2191         YAHOO.widget.DataTable.formatCheckbox(elCell, oRecord, oColumn, oData);
2192     },
2193
2194     formatCurrency : function(elCell, oRecord, oColumn, oData) {
2195         YAHOO.widget.DataTable.formatCurrency(elCell, oRecord, oColumn, oData);
2196     },
2197
2198     formatDate : function(elCell, oRecord, oColumn, oData) {
2199         YAHOO.widget.DataTable.formatDate(elCell, oRecord, oColumn, oData);
2200     },
2201
2202     formatEmail : function(elCell, oRecord, oColumn, oData) {
2203         YAHOO.widget.DataTable.formatEmail(elCell, oRecord, oColumn, oData);
2204     },
2205
2206     formatLink : function(elCell, oRecord, oColumn, oData) {
2207         YAHOO.widget.DataTable.formatLink(elCell, oRecord, oColumn, oData);
2208     },
2209
2210     formatNumber : function(elCell, oRecord, oColumn, oData) {
2211         YAHOO.widget.DataTable.formatNumber(elCell, oRecord, oColumn, oData);
2212     },
2213
2214     formatSelect : function(elCell, oRecord, oColumn, oData) {
2215         YAHOO.widget.DataTable.formatDropdown(elCell, oRecord, oColumn, oData);
2216     }
2217 });
2218
2219 YAHOO.widget.Column.prototype = {
2220     /**
2221      * Unique String identifier assigned at instantiation.
2222      *
2223      * @property _sId
2224      * @type String
2225      * @private
2226      */
2227     _sId : null,
2228
2229     /**
2230      * Reference to Column's current position index within its ColumnSet's keys
2231      * array, if applicable. This property only applies to non-nested and bottom-
2232      * level child Columns.
2233      *
2234      * @property _nKeyIndex
2235      * @type Number
2236      * @private
2237      */
2238     _nKeyIndex : null,
2239
2240     /**
2241      * Reference to Column's current position index within its ColumnSet's tree
2242      * array, if applicable. This property only applies to non-nested and top-
2243      * level parent Columns.
2244      *
2245      * @property _nTreeIndex
2246      * @type Number
2247      * @private
2248      */
2249     _nTreeIndex : null,
2250
2251     /**
2252      * Number of table cells the Column spans.
2253      *
2254      * @property _nColspan
2255      * @type Number
2256      * @private
2257      */
2258     _nColspan : 1,
2259
2260     /**
2261      * Number of table rows the Column spans.
2262      *
2263      * @property _nRowspan
2264      * @type Number
2265      * @private
2266      */
2267     _nRowspan : 1,
2268
2269     /**
2270      * Column's parent Column instance, or null.
2271      *
2272      * @property _oParent
2273      * @type YAHOO.widget.Column
2274      * @private
2275      */
2276     _oParent : null,
2277
2278     /**
2279      * The DOM reference to the associated TH element.
2280      *
2281      * @property _elTh
2282      * @type HTMLElement
2283      * @private
2284      */
2285     _elTh : null,
2286
2287     /**
2288      * The DOM reference to the associated TH element's liner DIV element.
2289      *
2290      * @property _elThLiner
2291      * @type HTMLElement
2292      * @private
2293      */
2294     _elThLiner : null,
2295
2296     /**
2297      * The DOM reference to the associated TH element's label SPAN element.
2298      *
2299      * @property _elThLabel
2300      * @type HTMLElement
2301      * @private
2302      */
2303     _elThLabel : null,
2304
2305     /**
2306      * The DOM reference to the associated resizerelement (if any).
2307      *
2308      * @property _elResizer
2309      * @type HTMLElement
2310      * @private
2311      */
2312     _elResizer : null,
2313
2314     /**
2315      * Internal width tracker.
2316      *
2317      * @property _nWidth
2318      * @type Number
2319      * @private
2320      */
2321     _nWidth : null,
2322
2323     /**
2324      * For unreg() purposes, a reference to the Column's DragDrop instance.
2325      *
2326      * @property _dd
2327      * @type YAHOO.util.DragDrop
2328      * @private
2329      */
2330     _dd : null,
2331
2332     /**
2333      * For unreg() purposes, a reference to the Column resizer's DragDrop instance.
2334      *
2335      * @property _ddResizer
2336      * @type YAHOO.util.DragDrop
2337      * @private
2338      */
2339     _ddResizer : null,
2340
2341     /////////////////////////////////////////////////////////////////////////////
2342     //
2343     // Public member variables
2344     //
2345     /////////////////////////////////////////////////////////////////////////////
2346
2347     /**
2348      * Unique name, required. If "label" property is not provided, the "key"
2349      * value will be treated as markup and inserted into the DOM as innerHTML.
2350      *
2351      * @property key
2352      * @type String|HTML
2353      */
2354     key : null,
2355
2356     /**
2357      * Associated database field, or null.
2358      *
2359      * @property field
2360      * @type String
2361      */
2362     field : null,
2363
2364     /**
2365      * Value displayed as Column header in the TH element. String value is
2366      * treated as markup and inserted into the DOM as innerHTML.
2367      *
2368      * @property label
2369      * @type HTML
2370      */
2371     label : null,
2372
2373     /**
2374      * Column head cell ABBR for accessibility.
2375      *
2376      * @property abbr
2377      * @type String
2378      */
2379     abbr : null,
2380
2381     /**
2382      * Array of object literals that define children (nested headers) of a Column.
2383      *
2384      * @property children
2385      * @type Object[]
2386      */
2387     children : null,
2388
2389     /**
2390      * Column width (in pixels).
2391      *
2392      * @property width
2393      * @type Number
2394      */
2395     width : null,
2396
2397     /**
2398      * Minimum Column width (in pixels).
2399      *
2400      * @property minWidth
2401      * @type Number
2402      * @default null
2403      */
2404     minWidth : null,
2405
2406     /**
2407      * When a width is not defined for a Column, maxAutoWidth defines an upper
2408      * limit that the Column should be auto-sized to. If resizeable is enabled, 
2409      * users may still resize to a greater width. Most useful for Columns intended
2410      * to hold long unbroken, unwrapped Strings, such as URLs, to prevent very
2411      * wide Columns from disrupting visual readability by inducing truncation.
2412      *
2413      * @property maxAutoWidth
2414      * @type Number
2415      * @default null
2416      */
2417     maxAutoWidth : null,
2418
2419     /**
2420      * True if Column is in hidden state.
2421      *
2422      * @property hidden
2423      * @type Boolean
2424      * @default false     
2425      */
2426     hidden : false,
2427
2428     /**
2429      * True if Column is in selected state.
2430      *
2431      * @property selected
2432      * @type Boolean
2433      * @default false     
2434      */
2435     selected : false,
2436
2437     /**
2438      * Custom CSS class or array of classes to be applied to every cell in the Column.
2439      *
2440      * @property className
2441      * @type String || String[]
2442      */
2443     className : null,
2444
2445     /**
2446      * Cell formatter function, or a shortcut pointer to a function in the
2447      * DataTable.Formatter object. The function, called from the DataTable's
2448      * formatCell method, renders markup into the cell liner
2449      * element and accepts the following arguments:
2450      * <dl>
2451      *    <dt>elLiner</dt>
2452      *    <dd>The element to write innerHTML to.</dd>
2453      *    <dt>oRecord</dt>
2454      *    <dd>The associated Record for the row.</dd>
2455      *    <dt>oColumn</dt>
2456      *    <dd>The Column instance for the cell.</dd>
2457      *    <dt>oData</dt>
2458      *    <dd>The data value for the cell.</dd>
2459      * </dl>
2460      *
2461      * @property formatter
2462      * @type String || HTMLFunction
2463      */
2464     formatter : null,
2465     
2466     /**
2467      * Config passed to YAHOO.util.Number.format() by the 'currency' Column formatter.
2468      *
2469      * @property currencyOptions
2470      * @type Object
2471      * @default null
2472      */
2473     currencyOptions : null,
2474
2475     /**
2476      * Config passed to YAHOO.util.Date.format() by the 'date' Column formatter.
2477      *
2478      * @property dateOptions
2479      * @type Object
2480      * @default null
2481      */
2482     dateOptions : null,
2483
2484     /**
2485      * Array of dropdown values for formatter:"dropdown" cases. Can either be a
2486      * simple array (e.g., ["Alabama","Alaska","Arizona","Arkansas"]) or a an
2487      * array of objects (e.g., [{label:"Alabama", value:"AL"},
2488      * {label:"Alaska", value:"AK"}, {label:"Arizona", value:"AZ"},
2489      * {label:"Arkansas", value:"AR"}]). String values are treated as markup and
2490      * inserted into the DOM as innerHTML.
2491      *
2492      * @property dropdownOptions
2493      * @type HTML[] | Object[]
2494      */
2495     dropdownOptions : null,
2496      
2497     /**
2498      * A CellEditor instance, otherwise Column is not editable.     
2499      *
2500      * @property editor
2501      * @type YAHOO.widget.CellEditor
2502      */
2503     editor : null,
2504
2505     /**
2506      * True if Column is resizeable, false otherwise. The Drag & Drop Utility is
2507      * required to enable this feature. Only bottom-level and non-nested Columns are
2508      * resizeble. 
2509      *
2510      * @property resizeable
2511      * @type Boolean
2512      * @default false
2513      */
2514     resizeable : false,
2515
2516     /**
2517      * True if Column is sortable, false otherwise.
2518      *
2519      * @property sortable
2520      * @type Boolean
2521      * @default false
2522      */
2523     sortable : false,
2524
2525     /**
2526      * @property sortOptions.defaultOrder
2527      * @deprecated Use sortOptions.defaultDir.
2528      */
2529     /**
2530      * Default sort direction for Column: YAHOO.widget.DataTable.CLASS_ASC or YAHOO.widget.DataTable.CLASS_DESC.
2531      *
2532      * @property sortOptions.defaultDir
2533      * @type String
2534      * @default null
2535      */
2536     /**
2537      * Custom field to sort on.
2538      *
2539      * @property sortOptions.field
2540      * @type String
2541      * @default null
2542      */
2543     /**
2544      * Custom sort handler. Signature: sortFunction(a, b, desc, field) where field is the sortOptions.field value
2545      *
2546      * @property sortOptions.sortFunction
2547      * @type Function
2548      * @default null
2549      */
2550     sortOptions : null,
2551
2552
2553
2554
2555
2556
2557
2558
2559
2560
2561
2562
2563
2564
2565
2566     /////////////////////////////////////////////////////////////////////////////
2567     //
2568     // Public methods
2569     //
2570     /////////////////////////////////////////////////////////////////////////////
2571
2572     /**
2573      * Returns unique ID string.
2574      *
2575      * @method getId
2576      * @return {String} Unique ID string.
2577      */
2578     getId : function() {
2579         return this._sId;
2580     },
2581
2582     /**
2583      * Column instance name, for logging.
2584      *
2585      * @method toString
2586      * @return {String} Column's unique name.
2587      */
2588     toString : function() {
2589         return "Column instance " + this._sId;
2590     },
2591
2592     /**
2593      * Returns object literal definition.
2594      *
2595      * @method getDefinition
2596      * @return {Object} Object literal definition.
2597      */
2598     getDefinition : function() {
2599         var oDefinition = {};
2600         
2601         // Update the definition
2602         oDefinition.abbr = this.abbr;
2603         oDefinition.className = this.className;
2604         oDefinition.editor = this.editor;
2605         oDefinition.editorOptions = this.editorOptions; //TODO: deprecated
2606         oDefinition.field = this.field;
2607         oDefinition.formatter = this.formatter;
2608         oDefinition.hidden = this.hidden;
2609         oDefinition.key = this.key;
2610         oDefinition.label = this.label;
2611         oDefinition.minWidth = this.minWidth;
2612         oDefinition.maxAutoWidth = this.maxAutoWidth;
2613         oDefinition.resizeable = this.resizeable;
2614         oDefinition.selected = this.selected;
2615         oDefinition.sortable = this.sortable;
2616         oDefinition.sortOptions = this.sortOptions;
2617         oDefinition.width = this.width;
2618         
2619         // Bug 2529147
2620         oDefinition._calculatedWidth = this._calculatedWidth;
2621
2622         return oDefinition;
2623     },
2624
2625     /**
2626      * Returns unique Column key.
2627      *
2628      * @method getKey
2629      * @return {String} Column key.
2630      */
2631     getKey : function() {
2632         return this.key;
2633     },
2634     
2635     /**
2636      * Returns field.
2637      *
2638      * @method getField
2639      * @return {String} Column field.
2640      */
2641     getField : function() {
2642         return this.field;
2643     },
2644     
2645     /**
2646      * Returns Column key which has been sanitized for DOM (class and ID) usage
2647      * starts with letter, contains only letters, numbers, hyphen, or period.
2648      *
2649      * @method getSanitizedKey
2650      * @return {String} Sanitized Column key.
2651      */
2652     getSanitizedKey : function() {
2653         return this.getKey().replace(/[^\w\-]/g,"");
2654     },
2655
2656     /**
2657      * Public accessor returns Column's current position index within its
2658      * ColumnSet's keys array, if applicable. Only non-nested and bottom-level
2659      * child Columns will return a value.
2660      *
2661      * @method getKeyIndex
2662      * @return {Number} Position index, or null.
2663      */
2664     getKeyIndex : function() {
2665         return this._nKeyIndex;
2666     },
2667
2668     /**
2669      * Public accessor returns Column's current position index within its
2670      * ColumnSet's tree array, if applicable. Only non-nested and top-level parent
2671      * Columns will return a value;
2672      *
2673      * @method getTreeIndex
2674      * @return {Number} Position index, or null.
2675      */
2676     getTreeIndex : function() {
2677         return this._nTreeIndex;
2678     },
2679
2680     /**
2681      * Public accessor returns Column's parent instance if any, or null otherwise.
2682      *
2683      * @method getParent
2684      * @return {YAHOO.widget.Column} Column's parent instance.
2685      */
2686     getParent : function() {
2687         return this._oParent;
2688     },
2689
2690     /**
2691      * Public accessor returns Column's calculated COLSPAN value.
2692      *
2693      * @method getColspan
2694      * @return {Number} Column's COLSPAN value.
2695      */
2696     getColspan : function() {
2697         return this._nColspan;
2698     },
2699     // Backward compatibility
2700     getColSpan : function() {
2701         return this.getColspan();
2702     },
2703
2704     /**
2705      * Public accessor returns Column's calculated ROWSPAN value.
2706      *
2707      * @method getRowspan
2708      * @return {Number} Column's ROWSPAN value.
2709      */
2710     getRowspan : function() {
2711         return this._nRowspan;
2712     },
2713
2714     /**
2715      * Returns DOM reference to the key TH element.
2716      *
2717      * @method getThEl
2718      * @return {HTMLElement} TH element.
2719      */
2720     getThEl : function() {
2721         return this._elTh;
2722     },
2723
2724     /**
2725      * Returns DOM reference to the TH's liner DIV element. Introduced since
2726      * resizeable Columns may have an extra resizer liner, making the DIV liner
2727      * not reliably the TH element's first child.               
2728      *
2729      * @method getThLInerEl
2730      * @return {HTMLElement} TH element.
2731      */
2732     getThLinerEl : function() {
2733         return this._elThLiner;
2734     },
2735     
2736     /**
2737      * Returns DOM reference to the resizer element, or null.
2738      *
2739      * @method getResizerEl
2740      * @return {HTMLElement} DIV element.
2741      */
2742     getResizerEl : function() {
2743         return this._elResizer;
2744     },
2745
2746     // Backward compatibility
2747     /**
2748      * @method getColEl
2749      * @deprecated Use getThEl
2750      */
2751     getColEl : function() {
2752         return this.getThEl();
2753     },
2754     getIndex : function() {
2755         return this.getKeyIndex();
2756     },
2757     format : function() {
2758     }
2759 };
2760
2761 /****************************************************************************/
2762 /****************************************************************************/
2763 /****************************************************************************/
2764
2765 /**
2766  * Sort static utility to support Column sorting.
2767  *
2768  * @namespace YAHOO.util
2769  * @class Sort
2770  * @static
2771  */
2772 YAHOO.util.Sort = {
2773     /////////////////////////////////////////////////////////////////////////////
2774     //
2775     // Public methods
2776     //
2777     /////////////////////////////////////////////////////////////////////////////
2778
2779     /**
2780      * Comparator function for simple case-insensitive string sorting.
2781      *
2782      * @method compare
2783      * @param a {Object} First sort argument.
2784      * @param b {Object} Second sort argument.
2785      * @param desc {Boolean} True if sort direction is descending, false if
2786      * sort direction is ascending.
2787      * @return {Boolean} Return -1 when a < b. Return 0 when a = b.
2788      * Return 1 when a > b.
2789      */
2790     compare: function(a, b, desc) {
2791         if((a === null) || (typeof a == "undefined")) {
2792             if((b === null) || (typeof b == "undefined")) {
2793                 return 0;
2794             }
2795             else {
2796                 return 1;
2797             }
2798         }
2799         else if((b === null) || (typeof b == "undefined")) {
2800             return -1;
2801         }
2802
2803         if(a.constructor == String) {
2804             a = a.toLowerCase();
2805         }
2806         if(b.constructor == String) {
2807             b = b.toLowerCase();
2808         }
2809         if(a < b) {
2810             return (desc) ? 1 : -1;
2811         }
2812         else if (a > b) {
2813             return (desc) ? -1 : 1;
2814         }
2815         else {
2816             return 0;
2817         }
2818     }
2819 };
2820
2821 /****************************************************************************/
2822 /****************************************************************************/
2823 /****************************************************************************/
2824
2825 /**
2826  * ColumnDD subclasses DragDrop to support rearrangeable Columns.
2827  *
2828  * @namespace YAHOO.util
2829  * @class ColumnDD
2830  * @extends YAHOO.util.DDProxy
2831  * @constructor
2832  * @param oDataTable {YAHOO.widget.DataTable} DataTable instance.
2833  * @param oColumn {YAHOO.widget.Column} Column instance.
2834  * @param elTh {HTMLElement} TH element reference.
2835  * @param elTarget {HTMLElement} Drag target element.
2836  */
2837 YAHOO.widget.ColumnDD = function(oDataTable, oColumn, elTh, elTarget) {
2838     if(oDataTable && oColumn && elTh && elTarget) {
2839         this.datatable = oDataTable;
2840         this.table = oDataTable.getTableEl();
2841         this.column = oColumn;
2842         this.headCell = elTh;
2843         this.pointer = elTarget;
2844         this.newIndex = null;
2845         this.init(elTh);
2846         this.initFrame(); // Needed for DDProxy
2847         this.invalidHandleTypes = {};
2848
2849         // Set top/bottom padding to account for children of nested columns
2850         this.setPadding(10, 0, (this.datatable.getTheadEl().offsetHeight + 10) , 0);
2851
2852         YAHOO.util.Event.on(window, 'resize', function() {
2853             this.initConstraints();
2854         }, this, true);
2855     }
2856     else {
2857     }
2858 };
2859
2860 if(YAHOO.util.DDProxy) {
2861     YAHOO.extend(YAHOO.widget.ColumnDD, YAHOO.util.DDProxy, {
2862         initConstraints: function() {
2863             //Get the top, right, bottom and left positions
2864             var region = YAHOO.util.Dom.getRegion(this.table),
2865                 //Get the element we are working on
2866                 el = this.getEl(),
2867                 //Get the xy position of it
2868                 xy = YAHOO.util.Dom.getXY(el),
2869                 //Get the width and height
2870                 width = parseInt(YAHOO.util.Dom.getStyle(el, 'width'), 10),
2871                 height = parseInt(YAHOO.util.Dom.getStyle(el, 'height'), 10),
2872                 //Set left to x minus left
2873                 left = ((xy[0] - region.left) + 15), //Buffer of 15px
2874                 //Set right to right minus x minus width
2875                 right = ((region.right - xy[0] - width) + 15);
2876     
2877             //Set the constraints based on the above calculations
2878             this.setXConstraint(left, right);
2879             this.setYConstraint(10, 10);            
2880         },
2881         _resizeProxy: function() {
2882             YAHOO.widget.ColumnDD.superclass._resizeProxy.apply(this, arguments);
2883             var dragEl = this.getDragEl(),
2884                 el = this.getEl();
2885
2886             YAHOO.util.Dom.setStyle(this.pointer, 'height', (this.table.parentNode.offsetHeight + 10) + 'px');
2887             YAHOO.util.Dom.setStyle(this.pointer, 'display', 'block');
2888             var xy = YAHOO.util.Dom.getXY(el);
2889             YAHOO.util.Dom.setXY(this.pointer, [xy[0], (xy[1] - 5)]);
2890             
2891             YAHOO.util.Dom.setStyle(dragEl, 'height', this.datatable.getContainerEl().offsetHeight + "px");
2892             YAHOO.util.Dom.setStyle(dragEl, 'width', (parseInt(YAHOO.util.Dom.getStyle(dragEl, 'width'),10) + 4) + 'px');
2893             YAHOO.util.Dom.setXY(this.dragEl, xy);
2894         },
2895         onMouseDown: function() {
2896                 this.initConstraints();
2897                 this.resetConstraints();
2898         },
2899         clickValidator: function(e) {
2900             if(!this.column.hidden) {
2901                 var target = YAHOO.util.Event.getTarget(e);
2902                 return ( this.isValidHandleChild(target) &&
2903                             (this.id == this.handleElId ||
2904                                 this.DDM.handleWasClicked(target, this.id)) );
2905             }
2906         },
2907         onDragOver: function(ev, id) {
2908             // Validate target as a Column
2909             var target = this.datatable.getColumn(id);
2910             if(target) {                
2911                 // Validate target as a top-level parent
2912                 var targetIndex = target.getTreeIndex();
2913                 while((targetIndex === null) && target.getParent()) {
2914                     target = target.getParent();
2915                     targetIndex = target.getTreeIndex();
2916                 }
2917                 if(targetIndex !== null) {
2918                     // Are we placing to left or right of target?
2919                     var elTarget = target.getThEl();
2920                     var newIndex = targetIndex;
2921                     var mouseX = YAHOO.util.Event.getPageX(ev),
2922                         targetX = YAHOO.util.Dom.getX(elTarget),
2923                         midX = targetX + ((YAHOO.util.Dom.get(elTarget).offsetWidth)/2),
2924                         currentIndex =  this.column.getTreeIndex();
2925                     
2926                     if (mouseX < midX) {
2927                        YAHOO.util.Dom.setX(this.pointer, targetX);
2928                     } else {
2929                         var targetWidth = parseInt(elTarget.offsetWidth, 10);
2930                         YAHOO.util.Dom.setX(this.pointer, (targetX + targetWidth));
2931                         newIndex++;
2932                     }
2933                     if (targetIndex > currentIndex) {
2934                         newIndex--;
2935                     }
2936                     if(newIndex < 0) {
2937                         newIndex = 0;
2938                     }
2939                     else if(newIndex > this.datatable.getColumnSet().tree[0].length) {
2940                         newIndex = this.datatable.getColumnSet().tree[0].length;
2941                     }
2942                     this.newIndex = newIndex;
2943                 }
2944             }
2945         },
2946         onDragDrop: function() {
2947             this.datatable.reorderColumn(this.column, this.newIndex);
2948         },
2949         endDrag: function() {
2950             this.newIndex = null;
2951             YAHOO.util.Dom.setStyle(this.pointer, 'display', 'none');
2952         }
2953     });
2954 }
2955
2956 /****************************************************************************/
2957 /****************************************************************************/
2958 /****************************************************************************/
2959
2960 /**
2961  * ColumnResizer subclasses DragDrop to support resizeable Columns.
2962  *
2963  * @namespace YAHOO.util
2964  * @class ColumnResizer
2965  * @extends YAHOO.util.DDProxy
2966  * @constructor
2967  * @param oDataTable {YAHOO.widget.DataTable} DataTable instance.
2968  * @param oColumn {YAHOO.widget.Column} Column instance.
2969  * @param elTh {HTMLElement} TH element reference.
2970  * @param sHandleElId {String} DOM ID of the handle element that causes the resize.
2971  * @param elProxy {HTMLElement} Resizer proxy element.
2972  */
2973 YAHOO.util.ColumnResizer = function(oDataTable, oColumn, elTh, sHandleId, elProxy) {
2974     if(oDataTable && oColumn && elTh && sHandleId) {
2975         this.datatable = oDataTable;
2976         this.column = oColumn;
2977         this.headCell = elTh;
2978         this.headCellLiner = oColumn.getThLinerEl();
2979         this.resizerLiner = elTh.firstChild;
2980         this.init(sHandleId, sHandleId, {dragOnly:true, dragElId: elProxy.id});
2981         this.initFrame(); // Needed for proxy
2982         this.resetResizerEl(); // Needed when rowspan > 0
2983
2984         // Set right padding for bug 1858462
2985         this.setPadding(0, 1, 0, 0);
2986     }
2987     else {
2988     }
2989 };
2990
2991 if(YAHOO.util.DD) {
2992     YAHOO.extend(YAHOO.util.ColumnResizer, YAHOO.util.DDProxy, {
2993         /////////////////////////////////////////////////////////////////////////////
2994         //
2995         // Public methods
2996         //
2997         /////////////////////////////////////////////////////////////////////////////
2998         /**
2999          * Resets resizer element.
3000          *
3001          * @method resetResizerEl
3002          */
3003         resetResizerEl : function() {
3004             var resizerStyle = YAHOO.util.Dom.get(this.handleElId).style;
3005             resizerStyle.left = "auto";
3006             resizerStyle.right = 0;
3007             resizerStyle.top = "auto";
3008             resizerStyle.bottom = 0;
3009             resizerStyle.height = this.headCell.offsetHeight+"px";
3010         },
3011     
3012         /////////////////////////////////////////////////////////////////////////////
3013         //
3014         // Public DOM event handlers
3015         //
3016         /////////////////////////////////////////////////////////////////////////////
3017     
3018         /**
3019          * Handles mouseup events on the Column resizer.
3020          *
3021          * @method onMouseUp
3022          * @param e {string} The mouseup event
3023          */
3024         onMouseUp : function(e) {
3025             // Reset height of all resizer els in case TH's have changed height
3026             var allKeys = this.datatable.getColumnSet().keys,
3027                 col;
3028             for(var i=0, len=allKeys.length; i<len; i++) {
3029                 col = allKeys[i];
3030                 if(col._ddResizer) {
3031                     col._ddResizer.resetResizerEl();
3032                 }
3033             }
3034             this.resetResizerEl();
3035             
3036             var el = this.headCellLiner;
3037             var newWidth = el.offsetWidth -
3038                 (parseInt(YAHOO.util.Dom.getStyle(el,"paddingLeft"),10)|0) -
3039                 (parseInt(YAHOO.util.Dom.getStyle(el,"paddingRight"),10)|0);
3040
3041             this.datatable.fireEvent("columnResizeEvent", {column:this.column,target:this.headCell,width:newWidth});
3042         },
3043     
3044         /**
3045          * Handles mousedown events on the Column resizer.
3046          *
3047          * @method onMouseDown
3048          * @param e {string} The mousedown event
3049          */
3050         onMouseDown : function(e) {
3051             this.startWidth = this.headCellLiner.offsetWidth;
3052             this.startX = YAHOO.util.Event.getXY(e)[0];
3053             this.nLinerPadding = (parseInt(YAHOO.util.Dom.getStyle(this.headCellLiner,"paddingLeft"),10)|0) +
3054                     (parseInt(YAHOO.util.Dom.getStyle(this.headCellLiner,"paddingRight"),10)|0);
3055         },
3056     
3057         /**
3058          * Custom clickValidator to ensure Column is not in hidden state.
3059          *
3060          * @method clickValidator
3061          * @param {Event} e
3062          * @private
3063          */
3064         clickValidator : function(e) {
3065             if(!this.column.hidden) {
3066                 var target = YAHOO.util.Event.getTarget(e);
3067                 return ( this.isValidHandleChild(target) &&
3068                             (this.id == this.handleElId ||
3069                                 this.DDM.handleWasClicked(target, this.id)) );
3070             }
3071         },
3072     
3073         /**
3074          * Handles start drag on the Column resizer.
3075          *
3076          * @method startDrag
3077          * @param e {string} The drag event
3078          */
3079         startDrag : function() {
3080             // Shrinks height of all resizer els to not hold open TH els
3081             var allKeys = this.datatable.getColumnSet().keys,
3082                 thisKey = this.column.getKeyIndex(),
3083                 col;
3084             for(var i=0, len=allKeys.length; i<len; i++) {
3085                 col = allKeys[i];
3086                 if(col._ddResizer) {
3087                     YAHOO.util.Dom.get(col._ddResizer.handleElId).style.height = "1em";
3088                 }
3089             }
3090         },
3091
3092         /**
3093          * Handles drag events on the Column resizer.
3094          *
3095          * @method onDrag
3096          * @param e {string} The drag event
3097          */
3098         onDrag : function(e) {
3099             var newX = YAHOO.util.Event.getXY(e)[0];
3100             if(newX > YAHOO.util.Dom.getX(this.headCellLiner)) {
3101                 var offsetX = newX - this.startX;
3102                 var newWidth = this.startWidth + offsetX - this.nLinerPadding;
3103                 if(newWidth > 0) {
3104                     this.datatable.setColumnWidth(this.column, newWidth);
3105                 }
3106             }
3107         }
3108     });
3109 }
3110
3111 /////////////////////////////////////////////////////////////////////////////
3112 //
3113 // Deprecated
3114 //
3115 /////////////////////////////////////////////////////////////////////////////
3116
3117 /**
3118  * @property editorOptions
3119  * @deprecated Pass configs directly to CellEditor constructor. 
3120  */
3121
3122
3123 (function () {
3124
3125 var lang   = YAHOO.lang,
3126     util   = YAHOO.util,
3127     widget = YAHOO.widget,
3128     
3129     Dom    = util.Dom,
3130     Ev     = util.Event,
3131     DT     = widget.DataTable;
3132
3133 /****************************************************************************/
3134 /****************************************************************************/
3135 /****************************************************************************/
3136
3137 /**
3138  * A RecordSet defines and manages a set of Records.
3139  *
3140  * @namespace YAHOO.widget
3141  * @class RecordSet
3142  * @param data {Object || Object[]} An object literal or an array of data.
3143  * @constructor
3144  */
3145 YAHOO.widget.RecordSet = function(data) {
3146     this._init(data);
3147 };
3148
3149 var RS = widget.RecordSet;
3150
3151 /**
3152  * Internal class variable to name multiple Recordset instances.
3153  *
3154  * @property RecordSet._nCount
3155  * @type Number
3156  * @private
3157  * @static
3158  */
3159 RS._nCount = 0;
3160
3161 RS.prototype = {
3162
3163     /////////////////////////////////////////////////////////////////////////////
3164     //
3165     // Private member variables
3166     //
3167     /////////////////////////////////////////////////////////////////////////////
3168     /**
3169      * Unique String identifier assigned at instantiation.
3170      *
3171      * @property _sId
3172      * @type String
3173      * @private
3174      */
3175     _sId : null,
3176
3177     /**
3178      * Internal counter of how many Records are in the RecordSet.
3179      *
3180      * @property _length
3181      * @type Number
3182      * @private
3183      * @deprecated No longer used
3184      */
3185     //_length : null,
3186
3187     /////////////////////////////////////////////////////////////////////////////
3188     //
3189     // Private methods
3190     //
3191     /////////////////////////////////////////////////////////////////////////////
3192     
3193     /**
3194      * Initializer.
3195      *
3196      * @method _init
3197      * @param data {Object || Object[]} An object literal or an array of data.
3198      * @private
3199      */
3200     _init : function(data) {
3201         // Internal variables
3202         this._sId = Dom.generateId(null, "yui-rs");// "yui-rs" + widget.RecordSet._nCount;
3203         widget.RecordSet._nCount++;
3204         this._records = [];
3205         //this._length = 0;
3206
3207         this._initEvents();
3208
3209         if(data) {
3210             if(lang.isArray(data)) {
3211                 this.addRecords(data);
3212             }
3213             else if(lang.isObject(data)) {
3214                 this.addRecord(data);
3215             }
3216         }
3217
3218     },
3219     
3220     /**
3221      * Initializes custom events.
3222      *
3223      * @method _initEvents
3224      * @private
3225      */
3226     _initEvents : function() {
3227         this.createEvent("recordAddEvent");
3228         this.createEvent("recordsAddEvent");
3229         this.createEvent("recordSetEvent");
3230         this.createEvent("recordsSetEvent");
3231         this.createEvent("recordUpdateEvent");
3232         this.createEvent("recordDeleteEvent");
3233         this.createEvent("recordsDeleteEvent");
3234         this.createEvent("resetEvent");
3235         this.createEvent("recordValueUpdateEvent");
3236     },
3237
3238     /**
3239      * Adds one Record to the RecordSet at the given index. If index is null,
3240      * then adds the Record to the end of the RecordSet.
3241      *
3242      * @method _addRecord
3243      * @param oData {Object} An object literal of data.
3244      * @param index {Number} (optional) Position index.
3245      * @return {YAHOO.widget.Record} A Record instance.
3246      * @private
3247      */
3248     _addRecord : function(oData, index) {
3249         var oRecord = new YAHOO.widget.Record(oData);
3250         
3251         if(YAHOO.lang.isNumber(index) && (index > -1)) {
3252             this._records.splice(index,0,oRecord);
3253         }
3254         else {
3255             //index = this.getLength();
3256             //this._records[index] = oRecord;
3257             this._records[this._records.length] = oRecord;
3258         }
3259         //this._length++;
3260         return oRecord;
3261     },
3262
3263     /**
3264      * Sets/replaces one Record to the RecordSet at the given index.  Existing
3265      * Records with higher indexes are not shifted.  If no index specified, the
3266      * Record is added to the end of the RecordSet.
3267      *
3268      * @method _setRecord
3269      * @param oData {Object} An object literal of data.
3270      * @param index {Number} (optional) Position index.
3271      * @return {YAHOO.widget.Record} A Record instance.
3272      * @private
3273      */
3274     _setRecord : function(oData, index) {
3275         if (!lang.isNumber(index) || index < 0) {
3276             index = this._records.length;
3277         }
3278         return (this._records[index] = new widget.Record(oData));
3279         /*
3280         if(lang.isNumber(index) && (index > -1)) {
3281             this._records[index] = oRecord;
3282             if((index+1) > this.getLength()) {
3283                 this._length = index+1;
3284             }
3285         }
3286         else {
3287             this._records[this.getLength()] = oRecord;
3288             this._length++;
3289         }
3290         return oRecord;
3291         */
3292     },
3293
3294     /**
3295      * Deletes Records from the RecordSet at the given index. If range is null,
3296      * then only one Record is deleted.
3297      *
3298      * @method _deleteRecord
3299      * @param index {Number} Position index.
3300      * @param range {Number} (optional) How many Records to delete
3301      * @private
3302      */
3303     _deleteRecord : function(index, range) {
3304         if(!lang.isNumber(range) || (range < 0)) {
3305             range = 1;
3306         }
3307         this._records.splice(index, range);
3308         //this._length = this._length - range;
3309     },
3310
3311     /////////////////////////////////////////////////////////////////////////////
3312     //
3313     // Public methods
3314     //
3315     /////////////////////////////////////////////////////////////////////////////
3316
3317     /**
3318      * Returns unique name of the RecordSet instance.
3319      *
3320      * @method getId
3321      * @return {String} Unique name of the RecordSet instance.
3322      */
3323     getId : function() {
3324         return this._sId;
3325     },
3326
3327     /**
3328      * Public accessor to the unique name of the RecordSet instance.
3329      *
3330      * @method toString
3331      * @return {String} Unique name of the RecordSet instance.
3332      */
3333     toString : function() {
3334         return "RecordSet instance " + this._sId;
3335     },
3336
3337     /**
3338      * Returns the number of Records held in the RecordSet.
3339      *
3340      * @method getLength
3341      * @return {Number} Number of records in the RecordSet.
3342      */
3343     getLength : function() {
3344             //return this._length;
3345             return this._records.length;
3346     },
3347
3348     /**
3349      * Returns Record by ID or RecordSet position index.
3350      *
3351      * @method getRecord
3352      * @param record {YAHOO.widget.Record | Number | String} Record instance,
3353      * RecordSet position index, or Record ID.
3354      * @return {YAHOO.widget.Record} Record object.
3355      */
3356     getRecord : function(record) {
3357         var i;
3358         if(record instanceof widget.Record) {
3359             for(i=0; i<this._records.length; i++) {
3360                 if(this._records[i] && (this._records[i]._sId === record._sId)) {
3361                     return record;
3362                 }
3363             }
3364         }
3365         else if(lang.isNumber(record)) {
3366             if((record > -1) && (record < this.getLength())) {
3367                 return this._records[record];
3368             }
3369         }
3370         else if(lang.isString(record)) {
3371             for(i=0; i<this._records.length; i++) {
3372                 if(this._records[i] && (this._records[i]._sId === record)) {
3373                     return this._records[i];
3374                 }
3375             }
3376         }
3377         // Not a valid Record for this RecordSet
3378         return null;
3379
3380     },
3381
3382     /**
3383      * Returns an array of Records from the RecordSet.
3384      *
3385      * @method getRecords
3386      * @param index {Number} (optional) Recordset position index of which Record to
3387      * start at.
3388      * @param range {Number} (optional) Number of Records to get.
3389      * @return {YAHOO.widget.Record[]} Array of Records starting at given index and
3390      * length equal to given range. If index is not given, all Records are returned.
3391      */
3392     getRecords : function(index, range) {
3393         if(!lang.isNumber(index)) {
3394             return this._records;
3395         }
3396         if(!lang.isNumber(range)) {
3397             return this._records.slice(index);
3398         }
3399         return this._records.slice(index, index+range);
3400     },
3401
3402     /**
3403      * Returns a boolean indicating whether Records exist in the RecordSet at the
3404      * specified index range.  Returns true if and only if a Record exists at each
3405      * index in the range.
3406      * @method hasRecords
3407      * @param index
3408      * @param range
3409      * @return {Boolean} true if all indices are populated in the RecordSet
3410      */
3411     hasRecords : function (index, range) {
3412         var recs = this.getRecords(index,range);
3413         for (var i = 0; i < range; ++i) {
3414             if (typeof recs[i] === 'undefined') {
3415                 return false;
3416             }
3417         }
3418         return true;
3419     },
3420
3421     /**
3422      * Returns current position index for the given Record.
3423      *
3424      * @method getRecordIndex
3425      * @param oRecord {YAHOO.widget.Record} Record instance.
3426      * @return {Number} Record's RecordSet position index.
3427      */
3428
3429     getRecordIndex : function(oRecord) {
3430         if(oRecord) {
3431             for(var i=this._records.length-1; i>-1; i--) {
3432                 if(this._records[i] && oRecord.getId() === this._records[i].getId()) {
3433                     return i;
3434                 }
3435             }
3436         }
3437         return null;
3438
3439     },
3440
3441     /**
3442      * Adds one Record to the RecordSet at the given index. If index is null,
3443      * then adds the Record to the end of the RecordSet.
3444      *
3445      * @method addRecord
3446      * @param oData {Object} An object literal of data.
3447      * @param index {Number} (optional) Position index.
3448      * @return {YAHOO.widget.Record} A Record instance.
3449      */
3450     addRecord : function(oData, index) {
3451         if(lang.isObject(oData)) {
3452             var oRecord = this._addRecord(oData, index);
3453             this.fireEvent("recordAddEvent",{record:oRecord,data:oData});
3454             return oRecord;
3455         }
3456         else {
3457             return null;
3458         }
3459     },
3460
3461     /**
3462      * Adds multiple Records at once to the RecordSet at the given index with the
3463      * given object literal data. If index is null, then the new Records are
3464      * added to the end of the RecordSet.
3465      *
3466      * @method addRecords
3467      * @param aData {Object[]} An object literal data or an array of data object literals.
3468      * @param index {Number} (optional) Position index.
3469      * @return {YAHOO.widget.Record[]} An array of Record instances.
3470      */
3471     addRecords : function(aData, index) {
3472         if(lang.isArray(aData)) {
3473             var newRecords = [],
3474                 idx,i,len;
3475
3476             index = lang.isNumber(index) ? index : this._records.length;
3477             idx = index;
3478
3479             // Can't go backwards bc we need to preserve order
3480             for(i=0,len=aData.length; i<len; ++i) {
3481                 if(lang.isObject(aData[i])) {
3482                     var record = this._addRecord(aData[i], idx++);
3483                     newRecords.push(record);
3484                 }
3485            }
3486             this.fireEvent("recordsAddEvent",{records:newRecords,data:aData});
3487            return newRecords;
3488         }
3489         else if(lang.isObject(aData)) {
3490             var oRecord = this._addRecord(aData);
3491             this.fireEvent("recordsAddEvent",{records:[oRecord],data:aData});
3492             return oRecord;
3493         }
3494         else {
3495             return null;
3496         }
3497     },
3498
3499     /**
3500      * Sets or replaces one Record to the RecordSet at the given index. Unlike
3501      * addRecord, an existing Record at that index is not shifted to preserve it.
3502      * If no index is specified, it adds the Record to the end of the RecordSet.
3503      *
3504      * @method setRecord
3505      * @param oData {Object} An object literal of data.
3506      * @param index {Number} (optional) Position index.
3507      * @return {YAHOO.widget.Record} A Record instance.
3508      */
3509     setRecord : function(oData, index) {
3510         if(lang.isObject(oData)) {
3511             var oRecord = this._setRecord(oData, index);
3512             this.fireEvent("recordSetEvent",{record:oRecord,data:oData});
3513             return oRecord;
3514         }
3515         else {
3516             return null;
3517         }
3518     },
3519
3520     /**
3521      * Sets or replaces multiple Records at once to the RecordSet with the given
3522      * data, starting at the given index. If index is not specified, then the new
3523      * Records are added to the end of the RecordSet.
3524      *
3525      * @method setRecords
3526      * @param aData {Object[]} An array of object literal data.
3527      * @param index {Number} (optional) Position index.
3528      * @return {YAHOO.widget.Record[]} An array of Record instances.
3529      */
3530     setRecords : function(aData, index) {
3531         var Rec   = widget.Record,
3532             a     = lang.isArray(aData) ? aData : [aData],
3533             added = [],
3534             i = 0, l = a.length, j = 0;
3535
3536         index = parseInt(index,10)|0;
3537
3538         for(; i < l; ++i) {
3539             if (typeof a[i] === 'object' && a[i]) {
3540                 added[j++] = this._records[index + i] = new Rec(a[i]);
3541             }
3542         }
3543
3544         this.fireEvent("recordsSetEvent",{records:added,data:aData});
3545         // Backward compatibility for bug 1918245
3546         this.fireEvent("recordsSet",{records:added,data:aData});
3547
3548         if (a.length && !added.length) {
3549         }
3550
3551         return added;
3552     },
3553
3554     /**
3555      * Updates given Record with given data.
3556      *
3557      * @method updateRecord
3558      * @param record {YAHOO.widget.Record | Number | String} A Record instance,
3559      * a RecordSet position index, or a Record ID.
3560      * @param oData {Object} Object literal of new data.
3561      * @return {YAHOO.widget.Record} Updated Record, or null.
3562      */
3563     updateRecord : function(record, oData) {
3564         var oRecord = this.getRecord(record);
3565         if(oRecord && lang.isObject(oData)) {
3566             // Copy data from the Record for the event that gets fired later
3567             var oldData = {};
3568             for(var key in oRecord._oData) {
3569                 if(lang.hasOwnProperty(oRecord._oData, key)) {
3570                     oldData[key] = oRecord._oData[key];
3571                 }
3572             }
3573             oRecord._oData = oData;
3574             this.fireEvent("recordUpdateEvent",{record:oRecord,newData:oData,oldData:oldData});
3575             return oRecord;
3576         }
3577         else {
3578             return null;
3579         }
3580     },
3581
3582     /**
3583      * @method updateKey
3584      * @deprecated Use updateRecordValue
3585      */
3586     updateKey : function(record, sKey, oData) {
3587         this.updateRecordValue(record, sKey, oData);
3588     },
3589     /**
3590      * Sets given Record at given key to given data.
3591      *
3592      * @method updateRecordValue
3593      * @param record {YAHOO.widget.Record | Number | String} A Record instance,
3594      * a RecordSet position index, or a Record ID.
3595      * @param sKey {String} Key name.
3596      * @param oData {Object} New data.
3597      */
3598     updateRecordValue : function(record, sKey, oData) {
3599         var oRecord = this.getRecord(record);
3600         if(oRecord) {
3601             var oldData = null;
3602             var keyValue = oRecord._oData[sKey];
3603             // Copy data from the Record for the event that gets fired later
3604             if(keyValue && lang.isObject(keyValue)) {
3605                 oldData = {};
3606                 for(var key in keyValue)  {
3607                     if(lang.hasOwnProperty(keyValue, key)) {
3608                         oldData[key] = keyValue[key];
3609                     }
3610                 }
3611             }
3612             // Copy by value
3613             else {
3614                 oldData = keyValue;
3615             }
3616
3617             oRecord._oData[sKey] = oData;
3618             this.fireEvent("keyUpdateEvent",{record:oRecord,key:sKey,newData:oData,oldData:oldData});
3619             this.fireEvent("recordValueUpdateEvent",{record:oRecord,key:sKey,newData:oData,oldData:oldData});
3620         }
3621         else {
3622         }
3623     },
3624
3625     /**
3626      * Replaces all Records in RecordSet with new object literal data.
3627      *
3628      * @method replaceRecords
3629      * @param data {Object || Object[]} An object literal of data or an array of
3630      * data object literals.
3631      * @return {YAHOO.widget.Record || YAHOO.widget.Record[]} A Record instance or
3632      * an array of Records.
3633      */
3634     replaceRecords : function(data) {
3635         this.reset();
3636         return this.addRecords(data);
3637     },
3638
3639     /**
3640      * Sorts all Records by given function. Records keep their unique IDs but will
3641      * have new RecordSet position indexes.
3642      *
3643      * @method sortRecords
3644      * @param fnSort {Function} Reference to a sort function.
3645      * @param desc {Boolean} True if sort direction is descending, false if sort
3646      * direction is ascending.
3647      * @param field {String} The field to sort by, from sortOptions.field
3648      * @return {YAHOO.widget.Record[]} Sorted array of Records.
3649      */
3650     sortRecords : function(fnSort, desc, field) {
3651         return this._records.sort(function(a, b) {return fnSort(a, b, desc, field);});
3652     },
3653
3654     /**
3655      * Reverses all Records, so ["one", "two", "three"] becomes ["three", "two", "one"].
3656      *
3657      * @method reverseRecords
3658      * @return {YAHOO.widget.Record[]} Reverse-sorted array of Records.
3659      */
3660     reverseRecords : function() {
3661         return this._records.reverse();
3662     },
3663
3664     /**
3665      * Removes the Record at the given position index from the RecordSet. If a range
3666      * is also provided, removes that many Records, starting from the index. Length
3667      * of RecordSet is correspondingly shortened.
3668      *
3669      * @method deleteRecord
3670      * @param index {Number} Record's RecordSet position index.
3671      * @return {Object} A copy of the data held by the deleted Record.
3672      */
3673     deleteRecord : function(index) {
3674         if(lang.isNumber(index) && (index > -1) && (index < this.getLength())) {
3675             var oData = this.getRecord(index).getData();
3676             
3677             this._deleteRecord(index);
3678             this.fireEvent("recordDeleteEvent",{data:oData,index:index});
3679             return oData;
3680         }
3681         else {
3682             return null;
3683         }
3684     },
3685
3686     /**
3687      * Removes the Record at the given position index from the RecordSet. If a range
3688      * is also provided, removes that many Records, starting from the index. Length
3689      * of RecordSet is correspondingly shortened.
3690      *
3691      * @method deleteRecords
3692      * @param index {Number} Record's RecordSet position index.
3693      * @param range {Number} (optional) How many Records to delete.
3694      * @return {Object[]} An array of copies of the data held by the deleted Records.     
3695      */
3696     deleteRecords : function(index, range) {
3697         if(!lang.isNumber(range)) {
3698             range = 1;
3699         }
3700         if(lang.isNumber(index) && (index > -1) && (index < this.getLength())) {
3701             var recordsToDelete = this.getRecords(index, range);
3702             var deletedData = [], // this mistakenly held Records, not data
3703                 deletedObjects = []; // this passes data only
3704             
3705             for(var i=0; i<recordsToDelete.length; i++) {
3706                 deletedData[deletedData.length] = recordsToDelete[i]; // backward compatibility
3707                 deletedObjects[deletedObjects.length] = recordsToDelete[i].getData();
3708             }
3709             this._deleteRecord(index, range);
3710
3711             this.fireEvent("recordsDeleteEvent",{data:deletedData,deletedData:deletedObjects,index:index});
3712
3713             return deletedData;
3714         }
3715         else {
3716             return null;
3717         }
3718     },
3719
3720     /**
3721      * Deletes all Records from the RecordSet.
3722      *
3723      * @method reset
3724      */
3725     reset : function() {
3726         this._records = [];
3727         //this._length = 0;
3728         this.fireEvent("resetEvent");
3729     }
3730 };
3731
3732 /////////////////////////////////////////////////////////////////////////////
3733 //
3734 // Custom Events
3735 //
3736 /////////////////////////////////////////////////////////////////////////////
3737
3738 // RecordSet uses EventProvider
3739 lang.augmentProto(RS, util.EventProvider);
3740
3741 /**
3742  * Fired when a new Record is added to the RecordSet.
3743  *
3744  * @event recordAddEvent
3745  * @param oArgs.record {YAHOO.widget.Record} The Record instance.
3746  * @param oArgs.data {Object} Data added.
3747  */
3748
3749 /**
3750  * Fired when multiple Records are added to the RecordSet at once.
3751  *
3752  * @event recordsAddEvent
3753  * @param oArgs.records {YAHOO.widget.Record[]} An array of Record instances.
3754  * @param oArgs.data {Object[]} Data added.
3755  */
3756
3757 /**
3758  * Fired when a Record is set in the RecordSet.
3759  *
3760  * @event recordSetEvent
3761  * @param oArgs.record {YAHOO.widget.Record} The Record instance.
3762  * @param oArgs.data {Object} Data added.
3763  */
3764
3765 /**
3766  * Fired when multiple Records are set in the RecordSet at once.
3767  *
3768  * @event recordsSetEvent
3769  * @param oArgs.records {YAHOO.widget.Record[]} An array of Record instances.
3770  * @param oArgs.data {Object[]} Data added.
3771  */
3772
3773 /**
3774  * Fired when a Record is updated with new data.
3775  *
3776  * @event recordUpdateEvent
3777  * @param oArgs.record {YAHOO.widget.Record} The Record instance.
3778  * @param oArgs.newData {Object} New data.
3779  * @param oArgs.oldData {Object} Old data.
3780  */
3781
3782 /**
3783  * Fired when a Record is deleted from the RecordSet.
3784  *
3785  * @event recordDeleteEvent
3786  * @param oArgs.data {Object} The data held by the deleted Record,
3787  * or an array of data object literals if multiple Records were deleted at once.
3788  * @param oArgs.index {Object} Index of the deleted Record.
3789  */
3790
3791 /**
3792  * Fired when multiple Records are deleted from the RecordSet at once.
3793  *
3794  * @event recordsDeleteEvent
3795  * @param oArgs.data {Object[]} An array of deleted Records.
3796  * @param oArgs.deletedData {Object[]} An array of deleted data.
3797  * @param oArgs.index {Object} Index of the first deleted Record.
3798  */
3799
3800 /**
3801  * Fired when all Records are deleted from the RecordSet at once.
3802  *
3803  * @event resetEvent
3804  */
3805
3806 /**
3807  * @event keyUpdateEvent    
3808  * @deprecated Use recordValueUpdateEvent     
3809  */
3810
3811 /**
3812  * Fired when a Record value is updated with new data.
3813  *
3814  * @event recordValueUpdateEvent
3815  * @param oArgs.record {YAHOO.widget.Record} The Record instance.
3816  * @param oArgs.key {String} The updated key.
3817  * @param oArgs.newData {Object} New data.
3818  * @param oArgs.oldData {Object} Old data.
3819  *
3820  */
3821
3822
3823 /****************************************************************************/
3824 /****************************************************************************/
3825 /****************************************************************************/
3826
3827 /**
3828  * The Record class defines a DataTable record.
3829  *
3830  * @namespace YAHOO.widget
3831  * @class Record
3832  * @constructor
3833  * @param oConfigs {Object} (optional) Object literal of key/value pairs.
3834  */
3835 YAHOO.widget.Record = function(oLiteral) {
3836     this._nCount = widget.Record._nCount;
3837     this._sId = Dom.generateId(null, "yui-rec");//"yui-rec" + this._nCount;
3838     widget.Record._nCount++;
3839     this._oData = {};
3840     if(lang.isObject(oLiteral)) {
3841         for(var sKey in oLiteral) {
3842             if(lang.hasOwnProperty(oLiteral, sKey)) {
3843                 this._oData[sKey] = oLiteral[sKey];
3844             }
3845         }
3846     }
3847 };
3848
3849 /////////////////////////////////////////////////////////////////////////////
3850 //
3851 // Private member variables
3852 //
3853 /////////////////////////////////////////////////////////////////////////////
3854
3855 /**
3856  * Internal class variable to give unique IDs to Record instances.
3857  *
3858  * @property Record._nCount
3859  * @type Number
3860  * @private
3861  */
3862 YAHOO.widget.Record._nCount = 0;
3863
3864 YAHOO.widget.Record.prototype = {
3865     /**
3866      * Immutable unique count assigned at instantiation. Remains constant while a
3867      * Record's position index can change from sorting.
3868      *
3869      * @property _nCount
3870      * @type Number
3871      * @private
3872      */
3873     _nCount : null,
3874
3875     /**
3876      * Immutable unique ID assigned at instantiation. Remains constant while a
3877      * Record's position index can change from sorting.
3878      *
3879      * @property _sId
3880      * @type String
3881      * @private
3882      */
3883     _sId : null,
3884
3885     /**
3886      * Holds data for the Record in an object literal.
3887      *
3888      * @property _oData
3889      * @type Object
3890      * @private
3891      */
3892     _oData : null,
3893
3894     /////////////////////////////////////////////////////////////////////////////
3895     //
3896     // Public member variables
3897     //
3898     /////////////////////////////////////////////////////////////////////////////
3899
3900     /////////////////////////////////////////////////////////////////////////////
3901     //
3902     // Public methods
3903     //
3904     /////////////////////////////////////////////////////////////////////////////
3905
3906     /**
3907      * Returns unique count assigned at instantiation.
3908      *
3909      * @method getCount
3910      * @return Number
3911      */
3912     getCount : function() {
3913         return this._nCount;
3914     },
3915
3916     /**
3917      * Returns unique ID assigned at instantiation.
3918      *
3919      * @method getId
3920      * @return String
3921      */
3922     getId : function() {
3923         return this._sId;
3924     },
3925
3926     /**
3927      * Returns data for the Record for a field if given, or the entire object
3928      * literal otherwise.
3929      *
3930      * @method getData
3931      * @param sField {String} (Optional) The field from which to retrieve data value.
3932      * @return Object
3933      */
3934     getData : function(sField) {
3935         if(lang.isString(sField)) {
3936             return this._oData[sField];
3937         }
3938         else {
3939             return this._oData;
3940         }
3941     },
3942
3943     /**
3944      * Sets given data at the given key. Use the RecordSet method updateRecordValue to trigger
3945      * events. 
3946      *
3947      * @method setData
3948      * @param sKey {String} The key of the new value.
3949      * @param oData {MIXED} The new value.
3950      */
3951     setData : function(sKey, oData) {
3952         this._oData[sKey] = oData;
3953     }
3954 };
3955
3956 })();
3957
3958 (function () {
3959
3960 var lang   = YAHOO.lang,
3961     util   = YAHOO.util,
3962     widget = YAHOO.widget,
3963     ua     = YAHOO.env.ua,
3964     
3965     Dom    = util.Dom,
3966     Ev     = util.Event,
3967     DS     = util.DataSourceBase;
3968
3969 /**
3970  * The DataTable widget provides a progressively enhanced DHTML control for
3971  * displaying tabular data across A-grade browsers.
3972  *
3973  * @module datatable
3974  * @requires yahoo, dom, event, element, datasource
3975  * @optional dragdrop, dragdrop
3976  * @title DataTable Widget
3977  */
3978
3979 /****************************************************************************/
3980 /****************************************************************************/
3981 /****************************************************************************/
3982
3983 /**
3984  * DataTable class for the YUI DataTable widget.
3985  *
3986  * @namespace YAHOO.widget
3987  * @class DataTable
3988  * @extends YAHOO.util.Element
3989  * @constructor
3990  * @param elContainer {HTMLElement} Container element for the TABLE.
3991  * @param aColumnDefs {Object[]} Array of object literal Column definitions.
3992  * @param oDataSource {YAHOO.util.DataSource} DataSource instance.
3993  * @param oConfigs {object} (optional) Object literal of configuration values.
3994  */
3995 YAHOO.widget.DataTable = function(elContainer,aColumnDefs,oDataSource,oConfigs) {
3996     var DT = widget.DataTable;
3997     
3998     ////////////////////////////////////////////////////////////////////////////
3999     // Backward compatibility for SDT, but prevent infinite loops
4000     
4001     if(oConfigs && oConfigs.scrollable) {
4002         return new YAHOO.widget.ScrollingDataTable(elContainer,aColumnDefs,oDataSource,oConfigs);
4003     }
4004     
4005     ////////////////////////////////////////////////////////////////////////////
4006     // Initialization
4007
4008     // Internal vars
4009     this._nIndex = DT._nCount;
4010     this._sId = Dom.generateId(null, "yui-dt");// "yui-dt"+this._nIndex;
4011     this._oChainRender = new YAHOO.util.Chain();
4012     this._oChainRender.subscribe("end",this._onRenderChainEnd, this, true);
4013
4014     // Initialize configs
4015     this._initConfigs(oConfigs);
4016
4017     // Initialize DataSource
4018     this._initDataSource(oDataSource);
4019     if(!this._oDataSource) {
4020         return;
4021     }
4022
4023     // Initialize ColumnSet
4024     this._initColumnSet(aColumnDefs);
4025     if(!this._oColumnSet) {
4026         return;
4027     }
4028
4029     // Initialize RecordSet
4030     this._initRecordSet();
4031     if(!this._oRecordSet) {
4032     }
4033
4034     // Initialize Attributes
4035     DT.superclass.constructor.call(this, elContainer, this.configs);
4036
4037     // Initialize DOM elements
4038     var okDom = this._initDomElements(elContainer);
4039     if(!okDom) {
4040         return;
4041     }
4042             
4043     // Show message as soon as config is available
4044     this.showTableMessage(this.get("MSG_LOADING"), DT.CLASS_LOADING);
4045     
4046     ////////////////////////////////////////////////////////////////////////////
4047     // Once per instance
4048     this._initEvents();
4049
4050     DT._nCount++;
4051     DT._nCurrentCount++;
4052     
4053     ////////////////////////////////////////////////////////////////////////////
4054     // Data integration
4055
4056     // Send a simple initial request
4057     var oCallback = {
4058         success : this.onDataReturnSetRows,
4059         failure : this.onDataReturnSetRows,
4060         scope   : this,
4061         argument: this.getState()
4062     };
4063     
4064     var initialLoad = this.get("initialLoad");
4065     if(initialLoad === true) {
4066         this._oDataSource.sendRequest(this.get("initialRequest"), oCallback);
4067     }
4068     // Do not send an initial request at all
4069     else if(initialLoad === false) {
4070         this.showTableMessage(this.get("MSG_EMPTY"), DT.CLASS_EMPTY);
4071     }
4072     // Send an initial request with a custom payload
4073     else {
4074         var oCustom = initialLoad || {};
4075         oCallback.argument = oCustom.argument || {};
4076         this._oDataSource.sendRequest(oCustom.request, oCallback);
4077     }
4078 };
4079
4080 var DT = widget.DataTable;
4081
4082 /////////////////////////////////////////////////////////////////////////////
4083 //
4084 // Public constants
4085 //
4086 /////////////////////////////////////////////////////////////////////////////
4087
4088 lang.augmentObject(DT, {
4089
4090     /**
4091      * Class name assigned to outer DataTable container.
4092      *
4093      * @property DataTable.CLASS_DATATABLE
4094      * @type String
4095      * @static
4096      * @final
4097      * @default "yui-dt"
4098      */
4099     CLASS_DATATABLE : "yui-dt",
4100
4101     /**
4102      * Class name assigned to liner DIV elements.
4103      *
4104      * @property DataTable.CLASS_LINER
4105      * @type String
4106      * @static
4107      * @final
4108      * @default "yui-dt-liner"
4109      */
4110     CLASS_LINER : "yui-dt-liner",
4111
4112     /**
4113      * Class name assigned to display label elements.
4114      *
4115      * @property DataTable.CLASS_LABEL
4116      * @type String
4117      * @static
4118      * @final
4119      * @default "yui-dt-label"
4120      */
4121     CLASS_LABEL : "yui-dt-label",
4122
4123     /**
4124      * Class name assigned to messaging elements.
4125      *
4126      * @property DataTable.CLASS_MESSAGE
4127      * @type String
4128      * @static
4129      * @final
4130      * @default "yui-dt-message"
4131      */
4132     CLASS_MESSAGE : "yui-dt-message",
4133
4134     /**
4135      * Class name assigned to mask element when DataTable is disabled.
4136      *
4137      * @property DataTable.CLASS_MASK
4138      * @type String
4139      * @static
4140      * @final
4141      * @default "yui-dt-mask"
4142      */
4143     CLASS_MASK : "yui-dt-mask",
4144
4145     /**
4146      * Class name assigned to data elements.
4147      *
4148      * @property DataTable.CLASS_DATA
4149      * @type String
4150      * @static
4151      * @final
4152      * @default "yui-dt-data"
4153      */
4154     CLASS_DATA : "yui-dt-data",
4155
4156     /**
4157      * Class name assigned to Column drag target.
4158      *
4159      * @property DataTable.CLASS_COLTARGET
4160      * @type String
4161      * @static
4162      * @final
4163      * @default "yui-dt-coltarget"
4164      */
4165     CLASS_COLTARGET : "yui-dt-coltarget",
4166
4167     /**
4168      * Class name assigned to resizer handle elements.
4169      *
4170      * @property DataTable.CLASS_RESIZER
4171      * @type String
4172      * @static
4173      * @final
4174      * @default "yui-dt-resizer"
4175      */
4176     CLASS_RESIZER : "yui-dt-resizer",
4177
4178     /**
4179      * Class name assigned to resizer liner elements.
4180      *
4181      * @property DataTable.CLASS_RESIZERLINER
4182      * @type String
4183      * @static
4184      * @final
4185      * @default "yui-dt-resizerliner"
4186      */
4187     CLASS_RESIZERLINER : "yui-dt-resizerliner",
4188
4189     /**
4190      * Class name assigned to resizer proxy elements.
4191      *
4192      * @property DataTable.CLASS_RESIZERPROXY
4193      * @type String
4194      * @static
4195      * @final
4196      * @default "yui-dt-resizerproxy"
4197      */
4198     CLASS_RESIZERPROXY : "yui-dt-resizerproxy",
4199
4200     /**
4201      * Class name assigned to CellEditor container elements.
4202      *
4203      * @property DataTable.CLASS_EDITOR
4204      * @type String
4205      * @static
4206      * @final
4207      * @default "yui-dt-editor"
4208      */
4209     CLASS_EDITOR : "yui-dt-editor",
4210
4211     /**
4212      * Class name assigned to CellEditor container shim.
4213      *
4214      * @property DataTable.CLASS_EDITOR_SHIM
4215      * @type String
4216      * @static
4217      * @final
4218      * @default "yui-dt-editor-shim"
4219      */
4220     CLASS_EDITOR_SHIM : "yui-dt-editor-shim",
4221
4222     /**
4223      * Class name assigned to paginator container elements.
4224      *
4225      * @property DataTable.CLASS_PAGINATOR
4226      * @type String
4227      * @static
4228      * @final
4229      * @default "yui-dt-paginator"
4230      */
4231     CLASS_PAGINATOR : "yui-dt-paginator",
4232
4233     /**
4234      * Class name assigned to page number indicators.
4235      *
4236      * @property DataTable.CLASS_PAGE
4237      * @type String
4238      * @static
4239      * @final
4240      * @default "yui-dt-page"
4241      */
4242     CLASS_PAGE : "yui-dt-page",
4243
4244     /**
4245      * Class name assigned to default indicators.
4246      *
4247      * @property DataTable.CLASS_DEFAULT
4248      * @type String
4249      * @static
4250      * @final
4251      * @default "yui-dt-default"
4252      */
4253     CLASS_DEFAULT : "yui-dt-default",
4254
4255     /**
4256      * Class name assigned to previous indicators.
4257      *
4258      * @property DataTable.CLASS_PREVIOUS
4259      * @type String
4260      * @static
4261      * @final
4262      * @default "yui-dt-previous"
4263      */
4264     CLASS_PREVIOUS : "yui-dt-previous",
4265
4266     /**
4267      * Class name assigned next indicators.
4268      *
4269      * @property DataTable.CLASS_NEXT
4270      * @type String
4271      * @static
4272      * @final
4273      * @default "yui-dt-next"
4274      */
4275     CLASS_NEXT : "yui-dt-next",
4276
4277     /**
4278      * Class name assigned to first elements.
4279      *
4280      * @property DataTable.CLASS_FIRST
4281      * @type String
4282      * @static
4283      * @final
4284      * @default "yui-dt-first"
4285      */
4286     CLASS_FIRST : "yui-dt-first",
4287
4288     /**
4289      * Class name assigned to last elements.
4290      *
4291      * @property DataTable.CLASS_LAST
4292      * @type String
4293      * @static
4294      * @final
4295      * @default "yui-dt-last"
4296      */
4297     CLASS_LAST : "yui-dt-last",
4298
4299     /**
4300      * Class name assigned to Record elements.
4301      *
4302      * @property DataTable.CLASS_REC
4303      * @type String
4304      * @static
4305      * @final
4306      * @default "yui-dt-rec"
4307      */
4308     CLASS_REC : "yui-dt-rec",
4309
4310     /**
4311      * Class name assigned to even elements.
4312      *
4313      * @property DataTable.CLASS_EVEN
4314      * @type String
4315      * @static
4316      * @final
4317      * @default "yui-dt-even"
4318      */
4319     CLASS_EVEN : "yui-dt-even",
4320
4321     /**
4322      * Class name assigned to odd elements.
4323      *
4324      * @property DataTable.CLASS_ODD
4325      * @type String
4326      * @static
4327      * @final
4328      * @default "yui-dt-odd"
4329      */
4330     CLASS_ODD : "yui-dt-odd",
4331
4332     /**
4333      * Class name assigned to selected elements.
4334      *
4335      * @property DataTable.CLASS_SELECTED
4336      * @type String
4337      * @static
4338      * @final
4339      * @default "yui-dt-selected"
4340      */
4341     CLASS_SELECTED : "yui-dt-selected",
4342
4343     /**
4344      * Class name assigned to highlighted elements.
4345      *
4346      * @property DataTable.CLASS_HIGHLIGHTED
4347      * @type String
4348      * @static
4349      * @final
4350      * @default "yui-dt-highlighted"
4351      */
4352     CLASS_HIGHLIGHTED : "yui-dt-highlighted",
4353
4354     /**
4355      * Class name assigned to hidden elements.
4356      *
4357      * @property DataTable.CLASS_HIDDEN
4358      * @type String
4359      * @static
4360      * @final
4361      * @default "yui-dt-hidden"
4362      */
4363     CLASS_HIDDEN : "yui-dt-hidden",
4364
4365     /**
4366      * Class name assigned to disabled elements.
4367      *
4368      * @property DataTable.CLASS_DISABLED
4369      * @type String
4370      * @static
4371      * @final
4372      * @default "yui-dt-disabled"
4373      */
4374     CLASS_DISABLED : "yui-dt-disabled",
4375
4376     /**
4377      * Class name assigned to empty indicators.
4378      *
4379      * @property DataTable.CLASS_EMPTY
4380      * @type String
4381      * @static
4382      * @final
4383      * @default "yui-dt-empty"
4384      */
4385     CLASS_EMPTY : "yui-dt-empty",
4386
4387     /**
4388      * Class name assigned to loading indicatorx.
4389      *
4390      * @property DataTable.CLASS_LOADING
4391      * @type String
4392      * @static
4393      * @final
4394      * @default "yui-dt-loading"
4395      */
4396     CLASS_LOADING : "yui-dt-loading",
4397
4398     /**
4399      * Class name assigned to error indicators.
4400      *
4401      * @property DataTable.CLASS_ERROR
4402      * @type String
4403      * @static
4404      * @final
4405      * @default "yui-dt-error"
4406      */
4407     CLASS_ERROR : "yui-dt-error",
4408
4409     /**
4410      * Class name assigned to editable elements.
4411      *
4412      * @property DataTable.CLASS_EDITABLE
4413      * @type String
4414      * @static
4415      * @final
4416      * @default "yui-dt-editable"
4417      */
4418     CLASS_EDITABLE : "yui-dt-editable",
4419
4420     /**
4421      * Class name assigned to draggable elements.
4422      *
4423      * @property DataTable.CLASS_DRAGGABLE
4424      * @type String
4425      * @static
4426      * @final
4427      * @default "yui-dt-draggable"
4428      */
4429     CLASS_DRAGGABLE : "yui-dt-draggable",
4430
4431     /**
4432      * Class name assigned to resizeable elements.
4433      *
4434      * @property DataTable.CLASS_RESIZEABLE
4435      * @type String
4436      * @static
4437      * @final
4438      * @default "yui-dt-resizeable"
4439      */
4440     CLASS_RESIZEABLE : "yui-dt-resizeable",
4441
4442     /**
4443      * Class name assigned to scrollable elements.
4444      *
4445      * @property DataTable.CLASS_SCROLLABLE
4446      * @type String
4447      * @static
4448      * @final
4449      * @default "yui-dt-scrollable"
4450      */
4451     CLASS_SCROLLABLE : "yui-dt-scrollable",
4452
4453     /**
4454      * Class name assigned to sortable elements.
4455      *
4456      * @property DataTable.CLASS_SORTABLE
4457      * @type String
4458      * @static
4459      * @final
4460      * @default "yui-dt-sortable"
4461      */
4462     CLASS_SORTABLE : "yui-dt-sortable",
4463
4464     /**
4465      * Class name assigned to ascending elements.
4466      *
4467      * @property DataTable.CLASS_ASC
4468      * @type String
4469      * @static
4470      * @final
4471      * @default "yui-dt-asc"
4472      */
4473     CLASS_ASC : "yui-dt-asc",
4474
4475     /**
4476      * Class name assigned to descending elements.
4477      *
4478      * @property DataTable.CLASS_DESC
4479      * @type String
4480      * @static
4481      * @final
4482      * @default "yui-dt-desc"
4483      */
4484     CLASS_DESC : "yui-dt-desc",
4485
4486     /**
4487      * Class name assigned to BUTTON elements and/or container elements.
4488      *
4489      * @property DataTable.CLASS_BUTTON
4490      * @type String
4491      * @static
4492      * @final
4493      * @default "yui-dt-button"
4494      */
4495     CLASS_BUTTON : "yui-dt-button",
4496
4497     /**
4498      * Class name assigned to INPUT TYPE=CHECKBOX elements and/or container elements.
4499      *
4500      * @property DataTable.CLASS_CHECKBOX
4501      * @type String
4502      * @static
4503      * @final
4504      * @default "yui-dt-checkbox"
4505      */
4506     CLASS_CHECKBOX : "yui-dt-checkbox",
4507
4508     /**
4509      * Class name assigned to SELECT elements and/or container elements.
4510      *
4511      * @property DataTable.CLASS_DROPDOWN
4512      * @type String
4513      * @static
4514      * @final
4515      * @default "yui-dt-dropdown"
4516      */
4517     CLASS_DROPDOWN : "yui-dt-dropdown",
4518
4519     /**
4520      * Class name assigned to INPUT TYPE=RADIO elements and/or container elements.
4521      *
4522      * @property DataTable.CLASS_RADIO
4523      * @type String
4524      * @static
4525      * @final
4526      * @default "yui-dt-radio"
4527      */
4528     CLASS_RADIO : "yui-dt-radio",
4529
4530     /////////////////////////////////////////////////////////////////////////
4531     //
4532     // Private static properties
4533     //
4534     /////////////////////////////////////////////////////////////////////////
4535
4536     /**
4537      * Internal class variable for indexing multiple DataTable instances.
4538      *
4539      * @property DataTable._nCount
4540      * @type Number
4541      * @private
4542      * @static
4543      */
4544     _nCount : 0,
4545
4546     /**
4547      * Internal class variable tracking current number of DataTable instances,
4548      * so that certain class values can be reset when all instances are destroyed.          
4549      *
4550      * @property DataTable._nCurrentCount
4551      * @type Number
4552      * @private
4553      * @static
4554      */
4555     _nCurrentCount : 0,
4556
4557     /**
4558      * Reference to the STYLE node that is dynamically created and updated
4559      * in order to manage Column widths.
4560      *
4561      * @property DataTable._elDynStyleNode
4562      * @type HTMLElement
4563      * @private
4564      * @static     
4565      */
4566     _elDynStyleNode : null,
4567
4568     /**
4569      * Set to true if _elDynStyleNode cannot be populated due to browser incompatibility.
4570      *
4571      * @property DataTable._bDynStylesFallback
4572      * @type boolean
4573      * @private
4574      * @static     
4575      */
4576     _bDynStylesFallback : (ua.ie) ? true : false,
4577
4578     /**
4579      * Object literal hash of Columns and their dynamically create style rules.
4580      *
4581      * @property DataTable._oDynStyles
4582      * @type Object
4583      * @private
4584      * @static     
4585      */
4586     _oDynStyles : {},
4587
4588     /////////////////////////////////////////////////////////////////////////
4589     //
4590     // Private static methods
4591     //
4592     /////////////////////////////////////////////////////////////////////////
4593
4594     /**
4595      * Clones object literal or array of object literals.
4596      *
4597      * @method DataTable._cloneObject
4598      * @param o {Object} Object.
4599      * @private
4600      * @static     
4601      */
4602     _cloneObject: function(o) {
4603         if(!lang.isValue(o)) {
4604             return o;
4605         }
4606
4607         var copy = {};
4608
4609         if(o instanceof YAHOO.widget.BaseCellEditor) {
4610             copy = o;
4611         }
4612         else if(Object.prototype.toString.apply(o) === "[object RegExp]") {
4613             copy = o;
4614         }
4615         else if(lang.isFunction(o)) {
4616             copy = o;
4617         }
4618         else if(lang.isArray(o)) {
4619             var array = [];
4620             for(var i=0,len=o.length;i<len;i++) {
4621                 array[i] = DT._cloneObject(o[i]);
4622             }
4623             copy = array;
4624         }
4625         else if(lang.isObject(o)) {
4626             for (var x in o){
4627                 if(lang.hasOwnProperty(o, x)) {
4628                     if(lang.isValue(o[x]) && lang.isObject(o[x]) || lang.isArray(o[x])) {
4629                         copy[x] = DT._cloneObject(o[x]);
4630                     }
4631                     else {
4632                         copy[x] = o[x];
4633                     }
4634                 }
4635             }
4636         }
4637         else {
4638             copy = o;
4639         }
4640
4641         return copy;
4642     },
4643
4644     /**
4645      * Formats a BUTTON element.
4646      *
4647      * @method DataTable.formatButton
4648      * @param el {HTMLElement} The element to format with markup.
4649      * @param oRecord {YAHOO.widget.Record} Record instance.
4650      * @param oColumn {YAHOO.widget.Column} Column instance.
4651      * @param oData {HTML} Data value for the cell. By default, the value
4652      * is what gets written to the BUTTON. String values are treated as markup
4653      * and inserted into the DOM with innerHTML.
4654      * @param oDataTable {YAHOO.widget.DataTable} DataTable instance.
4655      * @static
4656      */
4657     formatButton : function(el, oRecord, oColumn, oData, oDataTable) {
4658         var sValue = lang.isValue(oData) ? oData : "Click";
4659         //TODO: support YAHOO.widget.Button
4660         //if(YAHOO.widget.Button) {
4661
4662         //}
4663         //else {
4664             el.innerHTML = "<button type=\"button\" class=\""+
4665                     DT.CLASS_BUTTON + "\">" + sValue + "</button>";
4666         //}
4667     },
4668
4669     /**
4670      * Formats a CHECKBOX element.
4671      *
4672      * @method DataTable.formatCheckbox
4673      * @param el {HTMLElement} The element to format with markup.
4674      * @param oRecord {YAHOO.widget.Record} Record instance.
4675      * @param oColumn {YAHOO.widget.Column} Column instance.
4676      * @param oData {Object | Boolean | HTML} Data value for the cell. Can be a simple
4677      * Boolean to indicate whether checkbox is checked or not. Can be object literal
4678      * {checked:bBoolean, label:sLabel}. String values are treated as markup
4679      * and inserted into the DOM with innerHTML.
4680      * @param oDataTable {YAHOO.widget.DataTable} DataTable instance.
4681      * @static
4682      */
4683     formatCheckbox : function(el, oRecord, oColumn, oData, oDataTable) {
4684         var bChecked = oData;
4685         bChecked = (bChecked) ? " checked=\"checked\"" : "";
4686         el.innerHTML = "<input type=\"checkbox\"" + bChecked +
4687                 " class=\"" + DT.CLASS_CHECKBOX + "\" />";
4688     },
4689
4690     /**
4691      * Formats currency. Default unit is USD.
4692      *
4693      * @method DataTable.formatCurrency
4694      * @param el {HTMLElement} The element to format with markup.
4695      * @param oRecord {YAHOO.widget.Record} Record instance.
4696      * @param oColumn {YAHOO.widget.Column} Column instance.
4697      * @param oData {Number} Data value for the cell.
4698      * @param oDataTable {YAHOO.widget.DataTable} DataTable instance.
4699      * @static
4700      */
4701     formatCurrency : function(el, oRecord, oColumn, oData, oDataTable) {
4702         var oDT = oDataTable || this;
4703         el.innerHTML = util.Number.format(oData, oColumn.currencyOptions || oDT.get("currencyOptions"));
4704     },
4705
4706     /**
4707      * Formats JavaScript Dates.
4708      *
4709      * @method DataTable.formatDate
4710      * @param el {HTMLElement} The element to format with markup.
4711      * @param oRecord {YAHOO.widget.Record} Record instance.
4712      * @param oColumn {YAHOO.widget.Column} Column instance.
4713      * @param oData {Object} Data value for the cell, or null. String values are
4714      * treated as markup and inserted into the DOM with innerHTML.
4715      * @param oDataTable {YAHOO.widget.DataTable} DataTable instance.
4716      * @static
4717      */
4718     formatDate : function(el, oRecord, oColumn, oData, oDataTable) {
4719         var oDT = oDataTable || this,
4720             oConfig = oColumn.dateOptions || oDT.get("dateOptions");
4721         el.innerHTML = util.Date.format(oData, oConfig, oConfig.locale);
4722     },
4723
4724     /**
4725      * Formats SELECT elements.
4726      *
4727      * @method DataTable.formatDropdown
4728      * @param el {HTMLElement} The element to format with markup.
4729      * @param oRecord {YAHOO.widget.Record} Record instance.
4730      * @param oColumn {YAHOO.widget.Column} Column instance.
4731      * @param oData {Object} Data value for the cell, or null. String values may
4732      * be treated as markup and inserted into the DOM with innerHTML as element
4733      * label.
4734      * @param oDataTable {YAHOO.widget.DataTable} DataTable instance.
4735      * @static
4736      */
4737     formatDropdown : function(el, oRecord, oColumn, oData, oDataTable) {
4738         var oDT = oDataTable || this,
4739             selectedValue = (lang.isValue(oData)) ? oData : oRecord.getData(oColumn.field),
4740             options = (lang.isArray(oColumn.dropdownOptions)) ?
4741                 oColumn.dropdownOptions : null,
4742
4743             selectEl,
4744             collection = el.getElementsByTagName("select");
4745
4746         // Create the form element only once, so we can attach the onChange listener
4747         if(collection.length === 0) {
4748             // Create SELECT element
4749             selectEl = document.createElement("select");
4750             selectEl.className = DT.CLASS_DROPDOWN;
4751             selectEl = el.appendChild(selectEl);
4752
4753             // Add event listener
4754             Ev.addListener(selectEl,"change",oDT._onDropdownChange,oDT);
4755         }
4756
4757         selectEl = collection[0];
4758
4759         // Update the form element
4760         if(selectEl) {
4761             // Clear out previous options
4762             selectEl.innerHTML = "";
4763
4764             // We have options to populate
4765             if(options) {
4766                 // Create OPTION elements
4767                 for(var i=0; i<options.length; i++) {
4768                     var option = options[i];
4769                     var optionEl = document.createElement("option");
4770                     optionEl.value = (lang.isValue(option.value)) ?
4771                             option.value : option;
4772                     // Bug 2334323: Support legacy text, support label for consistency with DropdownCellEditor
4773                     optionEl.innerHTML = (lang.isValue(option.text)) ?
4774                             option.text : (lang.isValue(option.label)) ? option.label : option;
4775                     optionEl = selectEl.appendChild(optionEl);
4776                     if (optionEl.value == selectedValue) {
4777                         optionEl.selected = true;
4778                     }
4779                 }
4780             }
4781             // Selected value is our only option
4782             else {
4783                 selectEl.innerHTML = "<option selected value=\"" + selectedValue + "\">" + selectedValue + "</option>";
4784             }
4785         }
4786         else {
4787             el.innerHTML = lang.isValue(oData) ? oData : "";
4788         }
4789     },
4790
4791     /**
4792      * Formats emails.
4793      *
4794      * @method DataTable.formatEmail
4795      * @param el {HTMLElement} The element to format with markup.
4796      * @param oRecord {YAHOO.widget.Record} Record instance.
4797      * @param oColumn {YAHOO.widget.Column} Column instance.
4798      * @param oData {String} Data value for the cell, or null. Values are
4799      * HTML-escaped.
4800      * @param oDataTable {YAHOO.widget.DataTable} DataTable instance.
4801      * @static
4802      */
4803     formatEmail : function(el, oRecord, oColumn, oData, oDataTable) {
4804         if(lang.isString(oData)) {
4805             oData = lang.escapeHTML(oData);
4806             el.innerHTML = "<a href=\"mailto:" + oData + "\">" + oData + "</a>";
4807         }
4808         else {
4809             el.innerHTML = lang.isValue(oData) ? lang.escapeHTML(oData.toString()) : "";
4810         }
4811     },
4812
4813     /**
4814      * Formats links.
4815      *
4816      * @method DataTable.formatLink
4817      * @param el {HTMLElement} The element to format with markup.
4818      * @param oRecord {YAHOO.widget.Record} Record instance.
4819      * @param oColumn {YAHOO.widget.Column} Column instance.
4820      * @param oData {String} Data value for the cell, or null. Values are
4821      * HTML-escaped
4822      * @param oDataTable {YAHOO.widget.DataTable} DataTable instance.
4823      * @static
4824      */
4825     formatLink : function(el, oRecord, oColumn, oData, oDataTable) {
4826         if(lang.isString(oData)) {
4827             oData = lang.escapeHTML(oData);
4828             el.innerHTML = "<a href=\"" + oData + "\">" + oData + "</a>";
4829         }
4830         else {
4831             el.innerHTML = lang.isValue(oData) ? lang.escapeHTML(oData.toString()) : "";
4832         }
4833     },
4834
4835     /**
4836      * Formats numbers.
4837      *
4838      * @method DataTable.formatNumber
4839      * @param el {HTMLElement} The element to format with markup.
4840      * @param oRecord {YAHOO.widget.Record} Record instance.
4841      * @param oColumn {YAHOO.widget.Column} Column instance.
4842      * @param oData {Object} Data value for the cell, or null.
4843      * @param oDataTable {YAHOO.widget.DataTable} DataTable instance.
4844      * @static
4845      */
4846     formatNumber : function(el, oRecord, oColumn, oData, oDataTable) {
4847         var oDT = oDataTable || this;
4848         el.innerHTML = util.Number.format(oData, oColumn.numberOptions || oDT.get("numberOptions"));
4849     },
4850
4851     /**
4852      * Formats INPUT TYPE=RADIO elements.
4853      *
4854      * @method DataTable.formatRadio
4855      * @param el {HTMLElement} The element to format with markup.
4856      * @param oRecord {YAHOO.widget.Record} Record instance.
4857      * @param oColumn {YAHOO.widget.Column} Column instance.
4858      * @param oData {Object} (Optional) Data value for the cell.
4859      * @param oDataTable {YAHOO.widget.DataTable} DataTable instance.
4860      * @static
4861      */
4862     formatRadio : function(el, oRecord, oColumn, oData, oDataTable) {
4863         var oDT = oDataTable || this,
4864             bChecked = oData;
4865         bChecked = (bChecked) ? " checked=\"checked\"" : "";
4866         el.innerHTML = "<input type=\"radio\"" + bChecked +
4867                 " name=\""+oDT.getId()+"-col-" + oColumn.getSanitizedKey() + "\"" +
4868                 " class=\"" + DT.CLASS_RADIO+ "\" />";
4869     },
4870
4871     /**
4872      * Formats text strings.
4873      *
4874      * @method DataTable.formatText
4875      * @param el {HTMLElement} The element to format with markup.
4876      * @param oRecord {YAHOO.widget.Record} Record instance.
4877      * @param oColumn {YAHOO.widget.Column} Column instance.
4878      * @param oData {String} (Optional) Data value for the cell. Values are
4879      * HTML-escaped.
4880      * @param oDataTable {YAHOO.widget.DataTable} DataTable instance.
4881      * @static
4882      */
4883     formatText : function(el, oRecord, oColumn, oData, oDataTable) {
4884         var value = (lang.isValue(oData)) ? oData : "";
4885         el.innerHTML = lang.escapeHTML(value.toString());
4886     },
4887
4888     /**
4889      * Formats TEXTAREA elements.
4890      *
4891      * @method DataTable.formatTextarea
4892      * @param el {HTMLElement} The element to format with markup.
4893      * @param oRecord {YAHOO.widget.Record} Record instance.
4894      * @param oColumn {YAHOO.widget.Column} Column instance.
4895      * @param oData {Object} (Optional) Data value for the cell. Values are
4896      * HTML-escaped.
4897      * @param oDataTable {YAHOO.widget.DataTable} DataTable instance.
4898      * @static
4899      */
4900     formatTextarea : function(el, oRecord, oColumn, oData, oDataTable) {
4901         var value = (lang.isValue(oData)) ? lang.escapeHTML(oData.toString()) : "",
4902             markup = "<textarea>" + value + "</textarea>";
4903         el.innerHTML = markup;
4904     },
4905
4906     /**
4907      * Formats INPUT TYPE=TEXT elements.
4908      *
4909      * @method DataTable.formatTextbox
4910      * @param el {HTMLElement} The element to format with markup.
4911      * @param oRecord {YAHOO.widget.Record} Record instance.
4912      * @param oColumn {YAHOO.widget.Column} Column instance.
4913      * @param oData {Object} (Optional) Data value for the cell. Values are
4914      * HTML-escaped.
4915      * @param oDataTable {YAHOO.widget.DataTable} DataTable instance.
4916      * @static
4917      */
4918     formatTextbox : function(el, oRecord, oColumn, oData, oDataTable) {
4919         var value = (lang.isValue(oData)) ? lang.escapeHTML(oData.toString()) : "",
4920             markup = "<input type=\"text\" value=\"" + value + "\" />";
4921         el.innerHTML = markup;
4922     },
4923
4924     /**
4925      * Default cell formatter
4926      *
4927      * @method DataTable.formatDefault
4928      * @param el {HTMLElement} The element to format with markup.
4929      * @param oRecord {YAHOO.widget.Record} Record instance.
4930      * @param oColumn {YAHOO.widget.Column} Column instance.
4931      * @param oData {HTML} (Optional) Data value for the cell. String values are
4932      * treated as markup and inserted into the DOM with innerHTML.
4933      * @param oDataTable {YAHOO.widget.DataTable} DataTable instance.
4934      * @static
4935      */
4936     formatDefault : function(el, oRecord, oColumn, oData, oDataTable) {
4937         el.innerHTML = (lang.isValue(oData) && oData !== "") ? oData.toString() : "&#160;";
4938     },
4939
4940     /**
4941      * Validates data value to type Number, doing type conversion as
4942      * necessary. A valid Number value is return, else null is returned
4943      * if input value does not validate.
4944      *
4945      *
4946      * @method DataTable.validateNumber
4947      * @param oData {Object} Data to validate.
4948      * @static
4949     */
4950     validateNumber : function(oData) {
4951         //Convert to number
4952         var number = oData * 1;
4953
4954         // Validate
4955         if(lang.isNumber(number)) {
4956             return number;
4957         }
4958         else {
4959             return undefined;
4960         }
4961     }
4962 });
4963
4964 // Done in separate step so referenced functions are defined.
4965 /**
4966  * Registry of cell formatting functions, enables shortcut pointers in Column
4967  * definition formatter value (i.e., {key:"myColumn", formatter:"date"}).
4968  * @property DataTable.Formatter
4969  * @type Object
4970  * @static
4971  */
4972 DT.Formatter = {
4973     button   : DT.formatButton,
4974     checkbox : DT.formatCheckbox,
4975     currency : DT.formatCurrency,
4976     "date"   : DT.formatDate,
4977     dropdown : DT.formatDropdown,
4978     email    : DT.formatEmail,
4979     link     : DT.formatLink,
4980     "number" : DT.formatNumber,
4981     radio    : DT.formatRadio,
4982     text     : DT.formatText,
4983     textarea : DT.formatTextarea,
4984     textbox  : DT.formatTextbox,
4985
4986     defaultFormatter : DT.formatDefault
4987 };
4988
4989 lang.extend(DT, util.Element, {
4990
4991 /////////////////////////////////////////////////////////////////////////////
4992 //
4993 // Superclass methods
4994 //
4995 /////////////////////////////////////////////////////////////////////////////
4996
4997 /**
4998  * Implementation of Element's abstract method. Sets up config values.
4999  *
5000  * @method initAttributes
5001  * @param oConfigs {Object} (Optional) Object literal definition of configuration values.
5002  * @private
5003  */
5004
5005 initAttributes : function(oConfigs) {
5006     oConfigs = oConfigs || {};
5007     DT.superclass.initAttributes.call(this, oConfigs);
5008
5009     /**
5010     * @attribute summary
5011     * @description String value for the SUMMARY attribute.
5012     * @type String
5013     * @default ""    
5014     */
5015     this.setAttributeConfig("summary", {
5016         value: "",
5017         validator: lang.isString,
5018         method: function(sSummary) {
5019             if(this._elTable) {
5020                 this._elTable.summary = sSummary;
5021             }
5022         }
5023     });
5024
5025     /**
5026     * @attribute selectionMode
5027     * @description Specifies row or cell selection mode. Accepts the following strings:
5028     *    <dl>
5029     *      <dt>"standard"</dt>
5030     *      <dd>Standard row selection with support for modifier keys to enable
5031     *      multiple selections.</dd>
5032     *
5033     *      <dt>"single"</dt>
5034     *      <dd>Row selection with modifier keys disabled to not allow
5035     *      multiple selections.</dd>
5036     *
5037     *      <dt>"singlecell"</dt>
5038     *      <dd>Cell selection with modifier keys disabled to not allow
5039     *      multiple selections.</dd>
5040     *
5041     *      <dt>"cellblock"</dt>
5042     *      <dd>Cell selection with support for modifier keys to enable multiple
5043     *      selections in a block-fashion, like a spreadsheet.</dd>
5044     *
5045     *      <dt>"cellrange"</dt>
5046     *      <dd>Cell selection with support for modifier keys to enable multiple
5047     *      selections in a range-fashion, like a calendar.</dd>
5048     *    </dl>
5049     *
5050     * @default "standard"
5051     * @type String
5052     */
5053     this.setAttributeConfig("selectionMode", {
5054         value: "standard",
5055         validator: lang.isString
5056     });
5057
5058     /**
5059     * @attribute sortedBy
5060     * @description Object literal provides metadata for initial sort values if
5061     * data will arrive pre-sorted:
5062     * <dl>
5063     *     <dt>sortedBy.key</dt>
5064     *     <dd>{String} Key of sorted Column</dd>
5065     *     <dt>sortedBy.dir</dt>
5066     *     <dd>{String} Initial sort direction, either YAHOO.widget.DataTable.CLASS_ASC or YAHOO.widget.DataTable.CLASS_DESC</dd>
5067     * </dl>
5068     * @type Object | null
5069     */
5070     this.setAttributeConfig("sortedBy", {
5071         value: null,
5072         // TODO: accepted array for nested sorts
5073         validator: function(oNewSortedBy) {
5074             if(oNewSortedBy) {
5075                 return (lang.isObject(oNewSortedBy) && oNewSortedBy.key);
5076             }
5077             else {
5078                 return (oNewSortedBy === null);
5079             }
5080         },
5081         method: function(oNewSortedBy) {
5082             // Stash the previous value
5083             var oOldSortedBy = this.get("sortedBy");
5084             
5085             // Workaround for bug 1827195
5086             this._configs.sortedBy.value = oNewSortedBy;
5087
5088             // Remove ASC/DESC from TH
5089             var oOldColumn,
5090                 nOldColumnKeyIndex,
5091                 oNewColumn,
5092                 nNewColumnKeyIndex;
5093                 
5094             if(this._elThead) {
5095                 if(oOldSortedBy && oOldSortedBy.key && oOldSortedBy.dir) {
5096                     oOldColumn = this._oColumnSet.getColumn(oOldSortedBy.key);
5097                     nOldColumnKeyIndex = oOldColumn.getKeyIndex();
5098                     
5099                     // Remove previous UI from THEAD
5100                     var elOldTh = oOldColumn.getThEl();
5101                     Dom.removeClass(elOldTh, oOldSortedBy.dir);
5102                     this.formatTheadCell(oOldColumn.getThLinerEl().firstChild, oOldColumn, oNewSortedBy);
5103                 }
5104                 if(oNewSortedBy) {
5105                     oNewColumn = (oNewSortedBy.column) ? oNewSortedBy.column : this._oColumnSet.getColumn(oNewSortedBy.key);
5106                     nNewColumnKeyIndex = oNewColumn.getKeyIndex();
5107     
5108                     // Update THEAD with new UI
5109                     var elNewTh = oNewColumn.getThEl();
5110                     // Backward compatibility
5111                     if(oNewSortedBy.dir && ((oNewSortedBy.dir == "asc") ||  (oNewSortedBy.dir == "desc"))) {
5112                         var newClass = (oNewSortedBy.dir == "desc") ?
5113                                 DT.CLASS_DESC :
5114                                 DT.CLASS_ASC;
5115                         Dom.addClass(elNewTh, newClass);
5116                     }
5117                     else {
5118                          var sortClass = oNewSortedBy.dir || DT.CLASS_ASC;
5119                          Dom.addClass(elNewTh, sortClass);
5120                     }
5121                     this.formatTheadCell(oNewColumn.getThLinerEl().firstChild, oNewColumn, oNewSortedBy);
5122                 }
5123             }
5124           
5125             if(this._elTbody) {
5126                 // Update TBODY UI
5127                 this._elTbody.style.display = "none";
5128                 var allRows = this._elTbody.rows,
5129                     allCells;
5130                 for(var i=allRows.length-1; i>-1; i--) {
5131                     allCells = allRows[i].childNodes;
5132                     if(allCells[nOldColumnKeyIndex]) {
5133                         Dom.removeClass(allCells[nOldColumnKeyIndex], oOldSortedBy.dir);
5134                     }
5135                     if(allCells[nNewColumnKeyIndex]) {
5136                         Dom.addClass(allCells[nNewColumnKeyIndex], oNewSortedBy.dir);
5137                     }
5138                 }
5139                 this._elTbody.style.display = "";
5140             }
5141                 
5142             this._clearTrTemplateEl();
5143         }
5144     });
5145     
5146     /**
5147     * @attribute paginator
5148     * @description An instance of YAHOO.widget.Paginator.
5149     * @default null
5150     * @type {Object|YAHOO.widget.Paginator}
5151     */
5152     this.setAttributeConfig("paginator", {
5153         value : null,
5154         validator : function (val) {
5155             return val === null || val instanceof widget.Paginator;
5156         },
5157         method : function () { this._updatePaginator.apply(this,arguments); }
5158     });
5159
5160     /**
5161     * @attribute caption
5162     * @description Value for the CAPTION element. String values are treated as
5163     * markup and inserted into the DOM with innerHTML. NB: Not supported in
5164     * ScrollingDataTable.    
5165     * @type HTML
5166     */
5167     this.setAttributeConfig("caption", {
5168         value: null,
5169         validator: lang.isString,
5170         method: function(sCaption) {
5171             this._initCaptionEl(sCaption);
5172         }
5173     });
5174
5175     /**
5176     * @attribute draggableColumns
5177     * @description True if Columns are draggable to reorder, false otherwise.
5178     * The Drag & Drop Utility is required to enable this feature. Only top-level
5179     * and non-nested Columns are draggable. Write once.
5180     * @default false
5181     * @type Boolean
5182     */
5183     this.setAttributeConfig("draggableColumns", {
5184         value: false,
5185         validator: lang.isBoolean,
5186         method: function(oParam) {
5187             if(this._elThead) {
5188                 if(oParam) {
5189                     this._initDraggableColumns();
5190                 }
5191                 else {
5192                     this._destroyDraggableColumns();
5193                 }
5194             }
5195         }
5196     });
5197
5198     /**
5199     * @attribute renderLoopSize      
5200     * @description A value greater than 0 enables DOM rendering of rows to be
5201     * executed from a non-blocking timeout queue and sets how many rows to be
5202     * rendered per timeout. Recommended for very large data sets.     
5203     * @type Number      
5204     * @default 0      
5205     */      
5206      this.setAttributeConfig("renderLoopSize", {
5207          value: 0,
5208          validator: lang.isNumber
5209      });
5210
5211     /**
5212     * @attribute sortFunction
5213     * @description Default Column sort function, receives the following args:
5214     *    <dl>
5215     *      <dt>a {Object}</dt>
5216     *      <dd>First sort argument.</dd>
5217     *      <dt>b {Object}</dt>
5218     *      <dd>Second sort argument.</dd>
5219
5220     *      <dt>desc {Boolean}</dt>
5221     *      <dd>True if sort direction is descending, false if
5222     * sort direction is ascending.</dd>
5223     *      <dt>field {String}</dt>
5224     *      <dd>The field to sort by, from sortOptions.field</dd>
5225     *   </dl>
5226     * @type function
5227     */
5228     this.setAttributeConfig("sortFunction", {
5229         value: function(a, b, desc, field) {
5230             var compare = YAHOO.util.Sort.compare,
5231                 sorted = compare(a.getData(field),b.getData(field), desc);
5232             if(sorted === 0) {
5233                 return compare(a.getCount(),b.getCount(), desc); // Bug 1932978
5234             }
5235             else {
5236                 return sorted;
5237             }
5238         }
5239     });
5240
5241     /**
5242     * @attribute formatRow
5243     * @description A function that accepts a TR element and its associated Record
5244     * for custom formatting. The function must return TRUE in order to automatically
5245     * continue formatting of child TD elements, else TD elements will not be
5246     * automatically formatted.
5247     * @type function
5248     * @default null
5249     */
5250     this.setAttributeConfig("formatRow", {
5251         value: null,
5252         validator: lang.isFunction
5253     });
5254
5255     /**
5256     * @attribute generateRequest
5257     * @description A function that converts an object literal of desired DataTable
5258     * states into a request value which is then passed to the DataSource's
5259     * sendRequest method in order to retrieve data for those states. This
5260     * function is passed an object literal of state data and a reference to the
5261     * DataTable instance:
5262     *     
5263     * <dl>
5264     *   <dt>pagination<dt>
5265     *   <dd>        
5266     *         <dt>offsetRecord</dt>
5267     *         <dd>{Number} Index of the first Record of the desired page</dd>
5268     *         <dt>rowsPerPage</dt>
5269     *         <dd>{Number} Number of rows per page</dd>
5270     *   </dd>
5271     *   <dt>sortedBy</dt>
5272     *   <dd>                
5273     *         <dt>key</dt>
5274     *         <dd>{String} Key of sorted Column</dd>
5275     *         <dt>dir</dt>
5276     *         <dd>{String} Sort direction, either YAHOO.widget.DataTable.CLASS_ASC or YAHOO.widget.DataTable.CLASS_DESC</dd>
5277     *   </dd>
5278     *   <dt>self</dt>
5279     *   <dd>The DataTable instance</dd>
5280     * </dl>
5281     * 
5282     * and by default returns a String of syntax:
5283     * "sort={sortColumn}&dir={sortDir}&startIndex={pageStartIndex}&results={rowsPerPage}"
5284     * @type function
5285     * @default HTMLFunction
5286     */
5287     this.setAttributeConfig("generateRequest", {
5288         value: function(oState, oSelf) {
5289             // Set defaults
5290             oState = oState || {pagination:null, sortedBy:null};
5291             var sort = encodeURIComponent((oState.sortedBy) ? oState.sortedBy.key : oSelf.getColumnSet().keys[0].getKey());
5292             var dir = (oState.sortedBy && oState.sortedBy.dir === YAHOO.widget.DataTable.CLASS_DESC) ? "desc" : "asc";
5293             var startIndex = (oState.pagination) ? oState.pagination.recordOffset : 0;
5294             var results = (oState.pagination) ? oState.pagination.rowsPerPage : null;
5295             
5296             // Build the request
5297             return  "sort=" + sort +
5298                     "&dir=" + dir +
5299                     "&startIndex=" + startIndex +
5300                     ((results !== null) ? "&results=" + results : "");
5301         },
5302         validator: lang.isFunction
5303     });
5304
5305     /**
5306     * @attribute initialRequest
5307     * @description Defines the initial request that gets sent to the DataSource
5308     * during initialization. Value is ignored if initialLoad is set to any value
5309     * other than true.    
5310     * @type MIXED
5311     * @default null
5312     */
5313     this.setAttributeConfig("initialRequest", {
5314         value: null
5315     });
5316
5317     /**
5318     * @attribute initialLoad
5319     * @description Determines whether or not to load data at instantiation. By
5320     * default, will trigger a sendRequest() to the DataSource and pass in the
5321     * request defined by initialRequest. If set to false, data will not load
5322     * at instantiation. Alternatively, implementers who wish to work with a 
5323     * custom payload may pass in an object literal with the following values:
5324     *     
5325     *    <dl>
5326     *      <dt>request (MIXED)</dt>
5327     *      <dd>Request value.</dd>
5328     *
5329     *      <dt>argument (MIXED)</dt>
5330     *      <dd>Custom data that will be passed through to the callback function.</dd>
5331     *    </dl>
5332     *
5333     *                    
5334     * @type Boolean | Object
5335     * @default true
5336     */
5337     this.setAttributeConfig("initialLoad", {
5338         value: true
5339     });
5340     
5341     /**
5342     * @attribute dynamicData
5343     * @description If true, sorting and pagination are relegated to the DataSource
5344     * for handling, using the request returned by the "generateRequest" function.
5345     * Each new DataSource response blows away all previous Records. False by default, so 
5346     * sorting and pagination will be handled directly on the client side, without
5347     * causing any new requests for data from the DataSource.
5348     * @type Boolean
5349     * @default false
5350     */
5351     this.setAttributeConfig("dynamicData", {
5352         value: false,
5353         validator: lang.isBoolean
5354     });
5355
5356     /**
5357      * @attribute MSG_EMPTY
5358      * @description Message to display if DataTable has no data. String
5359      * values are treated as markup and inserted into the DOM with innerHTML.
5360      * @type HTML
5361      * @default "No records found."
5362      */
5363      this.setAttributeConfig("MSG_EMPTY", {
5364          value: "No records found.",
5365          validator: lang.isString
5366      });      
5367
5368     /**
5369      * @attribute MSG_LOADING
5370      * @description Message to display while DataTable is loading data. String
5371      * values are treated as markup and inserted into the DOM with innerHTML.
5372      * @type HTML
5373      * @default "Loading..."
5374      */      
5375      this.setAttributeConfig("MSG_LOADING", {
5376          value: "Loading...",
5377          validator: lang.isString
5378      });      
5379
5380     /**
5381      * @attribute MSG_ERROR
5382      * @description Message to display while DataTable has data error. String
5383      * values are treated as markup and inserted into the DOM with innerHTML.
5384      * @type HTML
5385      * @default "Data error."
5386      */      
5387      this.setAttributeConfig("MSG_ERROR", {
5388          value: "Data error.",
5389          validator: lang.isString
5390      });
5391
5392     /**
5393      * @attribute MSG_SORTASC
5394      * @description Message to display in tooltip to sort Column in ascending
5395      * order. String values are treated as markup and inserted into the DOM as
5396      * innerHTML.
5397      * @type HTML
5398      * @default "Click to sort ascending"
5399      */      
5400      this.setAttributeConfig("MSG_SORTASC", {      
5401          value: "Click to sort ascending",      
5402          validator: lang.isString,
5403          method: function(sParam) {
5404             if(this._elThead) {
5405                 for(var i=0, allKeys=this.getColumnSet().keys, len=allKeys.length; i<len; i++) {
5406                     if(allKeys[i].sortable && this.getColumnSortDir(allKeys[i]) === DT.CLASS_ASC) {
5407                         allKeys[i]._elThLabel.firstChild.title = sParam;
5408                     }
5409                 }
5410             }      
5411          }
5412      });
5413
5414     /**
5415      * @attribute MSG_SORTDESC
5416      * @description Message to display in tooltip to sort Column in descending
5417      * order. String values are treated as markup and inserted into the DOM as
5418      * innerHTML.
5419      * @type HTML
5420      * @default "Click to sort descending"
5421      */      
5422      this.setAttributeConfig("MSG_SORTDESC", {      
5423          value: "Click to sort descending",      
5424          validator: lang.isString,
5425          method: function(sParam) {
5426             if(this._elThead) {
5427                 for(var i=0, allKeys=this.getColumnSet().keys, len=allKeys.length; i<len; i++) {
5428                     if(allKeys[i].sortable && this.getColumnSortDir(allKeys[i]) === DT.CLASS_DESC) {
5429                         allKeys[i]._elThLabel.firstChild.title = sParam;
5430                     }
5431                 }
5432             }               
5433          }
5434      });
5435      
5436     /**
5437      * @attribute currencySymbol
5438      * @deprecated Use currencyOptions.
5439      */
5440     this.setAttributeConfig("currencySymbol", {
5441         value: "$",
5442         validator: lang.isString
5443     });
5444     
5445     /**
5446      * Default config passed to YAHOO.util.Number.format() by the 'currency' Column formatter.
5447      * @attribute currencyOptions
5448      * @type Object
5449      * @default {prefix: $, decimalPlaces:2, decimalSeparator:".", thousandsSeparator:","}
5450      */
5451     this.setAttributeConfig("currencyOptions", {
5452         value: {
5453             prefix: this.get("currencySymbol"), // TODO: deprecate currencySymbol
5454             decimalPlaces:2,
5455             decimalSeparator:".",
5456             thousandsSeparator:","
5457         }
5458     });
5459     
5460     /**
5461      * Default config passed to YAHOO.util.Date.format() by the 'date' Column formatter.
5462      * @attribute dateOptions
5463      * @type Object
5464      * @default {format:"%m/%d/%Y", locale:"en"}
5465      */
5466     this.setAttributeConfig("dateOptions", {
5467         value: {format:"%m/%d/%Y", locale:"en"}
5468     });
5469     
5470     /**
5471      * Default config passed to YAHOO.util.Number.format() by the 'number' Column formatter.
5472      * @attribute numberOptions
5473      * @type Object
5474      * @default {decimalPlaces:0, thousandsSeparator:","}
5475      */
5476     this.setAttributeConfig("numberOptions", {
5477         value: {
5478             decimalPlaces:0,
5479             thousandsSeparator:","
5480         }
5481     });
5482
5483 },
5484
5485 /////////////////////////////////////////////////////////////////////////////
5486 //
5487 // Private member variables
5488 //
5489 /////////////////////////////////////////////////////////////////////////////
5490
5491 /**
5492  * True if instance is initialized, so as to fire the initEvent after render.
5493  *
5494  * @property _bInit
5495  * @type Boolean
5496  * @default true
5497  * @private
5498  */
5499 _bInit : true,
5500
5501 /**
5502  * Index assigned to instance.
5503  *
5504  * @property _nIndex
5505  * @type Number
5506  * @private
5507  */
5508 _nIndex : null,
5509
5510 /**
5511  * Counter for IDs assigned to TR elements.
5512  *
5513  * @property _nTrCount
5514  * @type Number
5515  * @private
5516  */
5517 _nTrCount : 0,
5518
5519 /**
5520  * Counter for IDs assigned to TD elements.
5521  *
5522  * @property _nTdCount
5523  * @type Number
5524  * @private
5525  */
5526 _nTdCount : 0,
5527
5528 /**
5529  * Unique id assigned to instance "yui-dtN", useful prefix for generating unique
5530  * DOM ID strings and log messages.
5531  *
5532  * @property _sId
5533  * @type String
5534  * @private
5535  */
5536 _sId : null,
5537
5538 /**
5539  * Render chain.
5540  *
5541  * @property _oChainRender
5542  * @type YAHOO.util.Chain
5543  * @private
5544  */
5545 _oChainRender : null,
5546
5547 /**
5548  * DOM reference to the container element for the DataTable instance into which
5549  * all other elements get created.
5550  *
5551  * @property _elContainer
5552  * @type HTMLElement
5553  * @private
5554  */
5555 _elContainer : null,
5556
5557 /**
5558  * DOM reference to the mask element for the DataTable instance which disables it.
5559  *
5560  * @property _elMask
5561  * @type HTMLElement
5562  * @private
5563  */
5564 _elMask : null,
5565
5566 /**
5567  * DOM reference to the TABLE element for the DataTable instance.
5568  *
5569  * @property _elTable
5570  * @type HTMLElement
5571  * @private
5572  */
5573 _elTable : null,
5574
5575 /**
5576  * DOM reference to the CAPTION element for the DataTable instance.
5577  *
5578  * @property _elCaption
5579  * @type HTMLElement
5580  * @private
5581  */
5582 _elCaption : null,
5583
5584 /**
5585  * DOM reference to the COLGROUP element for the DataTable instance.
5586  *
5587  * @property _elColgroup
5588  * @type HTMLElement
5589  * @private
5590  */
5591 _elColgroup : null,
5592
5593 /**
5594  * DOM reference to the THEAD element for the DataTable instance.
5595  *
5596  * @property _elThead
5597  * @type HTMLElement
5598  * @private
5599  */
5600 _elThead : null,
5601
5602 /**
5603  * DOM reference to the primary TBODY element for the DataTable instance.
5604  *
5605  * @property _elTbody
5606  * @type HTMLElement
5607  * @private
5608  */
5609 _elTbody : null,
5610
5611 /**
5612  * DOM reference to the secondary TBODY element used to display DataTable messages.
5613  *
5614  * @property _elMsgTbody
5615  * @type HTMLElement
5616  * @private
5617  */
5618 _elMsgTbody : null,
5619
5620 /**
5621  * DOM reference to the secondary TBODY element's single TR element used to display DataTable messages.
5622  *
5623  * @property _elMsgTr
5624  * @type HTMLElement
5625  * @private
5626  */
5627 _elMsgTr : null,
5628
5629 /**
5630  * DOM reference to the secondary TBODY element's single TD element used to display DataTable messages.
5631  *
5632  * @property _elMsgTd
5633  * @type HTMLElement
5634  * @private
5635  */
5636 _elMsgTd : null,
5637
5638 /**
5639  * Element reference to shared Column drag target.
5640  *
5641  * @property _elColumnDragTarget
5642  * @type HTMLElement
5643  * @private
5644  */
5645 _elColumnDragTarget : null,
5646
5647 /**
5648  * Element reference to shared Column resizer proxy.
5649  *
5650  * @property _elColumnResizerProxy
5651  * @type HTMLElement
5652  * @private
5653  */
5654 _elColumnResizerProxy : null,
5655
5656 /**
5657  * DataSource instance for the DataTable instance.
5658  *
5659  * @property _oDataSource
5660  * @type YAHOO.util.DataSource
5661  * @private
5662  */
5663 _oDataSource : null,
5664
5665 /**
5666  * ColumnSet instance for the DataTable instance.
5667  *
5668  * @property _oColumnSet
5669  * @type YAHOO.widget.ColumnSet
5670  * @private
5671  */
5672 _oColumnSet : null,
5673
5674 /**
5675  * RecordSet instance for the DataTable instance.
5676  *
5677  * @property _oRecordSet
5678  * @type YAHOO.widget.RecordSet
5679  * @private
5680  */
5681 _oRecordSet : null,
5682
5683 /**
5684  * The active CellEditor instance for the DataTable instance.
5685  *
5686  * @property _oCellEditor
5687  * @type YAHOO.widget.CellEditor
5688  * @private
5689  */
5690 _oCellEditor : null,
5691
5692 /**
5693  * ID string of first TR element of the current DataTable page.
5694  *
5695  * @property _sFirstTrId
5696  * @type String
5697  * @private
5698  */
5699 _sFirstTrId : null,
5700
5701 /**
5702  * ID string of the last TR element of the current DataTable page.
5703  *
5704  * @property _sLastTrId
5705  * @type String
5706  * @private
5707  */
5708 _sLastTrId : null,
5709
5710 /**
5711  * Template row to create all new rows from.
5712  * @property _elTrTemplate
5713  * @type {HTMLElement}
5714  * @private 
5715  */
5716 _elTrTemplate : null,
5717
5718 /**
5719  * Sparse array of custom functions to set column widths for browsers that don't
5720  * support dynamic CSS rules.  Functions are added at the index representing
5721  * the number of rows they update.
5722  *
5723  * @property _aDynFunctions
5724  * @type Array
5725  * @private
5726  */
5727 _aDynFunctions : [],
5728
5729 /**
5730  * Disabled state.
5731  *
5732  * @property _disabled
5733  * @type Boolean
5734  * @private
5735  */
5736 _disabled : false,
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 // Private methods
5768 //
5769 /////////////////////////////////////////////////////////////////////////////
5770
5771 /**
5772  * Clears browser text selection. Useful to call on rowSelectEvent or
5773  * cellSelectEvent to prevent clicks or dblclicks from selecting text in the
5774  * browser.
5775  *
5776  * @method clearTextSelection
5777  */
5778 clearTextSelection : function() {
5779     var sel;
5780     if(window.getSelection) {
5781         sel = window.getSelection();
5782     }
5783     else if(document.getSelection) {
5784         sel = document.getSelection();
5785     }
5786     else if(document.selection) {
5787         sel = document.selection;
5788     }
5789     if(sel) {
5790         if(sel.empty) {
5791             sel.empty();
5792         }
5793         else if (sel.removeAllRanges) {
5794             sel.removeAllRanges();
5795         }
5796         else if(sel.collapse) {
5797             sel.collapse();
5798         }
5799     }
5800 },
5801
5802 /**
5803  * Sets focus on the given element.
5804  *
5805  * @method _focusEl
5806  * @param el {HTMLElement} Element.
5807  * @private
5808  */
5809 _focusEl : function(el) {
5810     el = el || this._elTbody;
5811     // http://developer.mozilla.org/en/docs/index.php?title=Key-navigable_custom_DHTML_widgets
5812     // The timeout is necessary in both IE and Firefox 1.5, to prevent scripts from doing
5813     // strange unexpected things as the user clicks on buttons and other controls.
5814     setTimeout(function() {
5815         try {
5816             el.focus();
5817         }
5818         catch(e) {
5819         }
5820     },0);
5821 },
5822
5823 /**
5824  * Forces Gecko repaint.
5825  *
5826  * @method _repaintGecko
5827  * @el {HTMLElement} (Optional) Element to repaint, otherwise entire document body.
5828  * @private
5829  */
5830 _repaintGecko : (ua.gecko) ? 
5831     function(el) {
5832         el = el || this._elContainer;
5833         var parent = el.parentNode;
5834         var nextSibling = el.nextSibling;
5835         parent.insertBefore(parent.removeChild(el), nextSibling);
5836     } : function() {},
5837
5838 /**
5839  * Forces Opera repaint.
5840  *
5841  * @method _repaintOpera
5842  * @private 
5843  */
5844 _repaintOpera : (ua.opera) ? 
5845     function() {
5846         if(ua.opera) {
5847             document.documentElement.className += " ";
5848             document.documentElement.className = YAHOO.lang.trim(document.documentElement.className);
5849         }
5850     } : function() {} ,
5851
5852 /**
5853  * Forces Webkit repaint.
5854  *
5855  * @method _repaintWebkit
5856  * @el {HTMLElement} (Optional) Element to repaint, otherwise entire document body.
5857  * @private
5858  */
5859 _repaintWebkit : (ua.webkit) ? 
5860     function(el) {
5861         el = el || this._elContainer;
5862         var parent = el.parentNode;
5863         var nextSibling = el.nextSibling;
5864         parent.insertBefore(parent.removeChild(el), nextSibling);
5865     } : function() {},
5866
5867
5868
5869
5870
5871
5872
5873
5874
5875
5876
5877
5878
5879
5880
5881
5882
5883
5884
5885
5886
5887
5888 // INIT FUNCTIONS
5889
5890 /**
5891  * Initializes object literal of config values.
5892  *
5893  * @method _initConfigs
5894  * @param oConfig {Object} Object literal of config values.
5895  * @private
5896  */
5897 _initConfigs : function(oConfigs) {
5898     if(!oConfigs || !lang.isObject(oConfigs)) {
5899         oConfigs = {};
5900     }
5901     this.configs = oConfigs;
5902 },
5903
5904 /**
5905  * Initializes ColumnSet.
5906  *
5907  * @method _initColumnSet
5908  * @param aColumnDefs {Object[]} Array of object literal Column definitions.
5909  * @private
5910  */
5911 _initColumnSet : function(aColumnDefs) {
5912     var oColumn, i, len;
5913     
5914     if(this._oColumnSet) {
5915         // First clear _oDynStyles for existing ColumnSet and
5916         // uregister CellEditor Custom Events
5917         for(i=0, len=this._oColumnSet.keys.length; i<len; i++) {
5918             oColumn = this._oColumnSet.keys[i];
5919             DT._oDynStyles["."+this.getId()+"-col-"+oColumn.getSanitizedKey()+" ."+DT.CLASS_LINER] = undefined;
5920             if(oColumn.editor && oColumn.editor.unsubscribeAll) { // Backward compatibility
5921                 oColumn.editor.unsubscribeAll();
5922             }
5923         }
5924         
5925         this._oColumnSet = null;
5926         this._clearTrTemplateEl();
5927     }
5928     
5929     if(lang.isArray(aColumnDefs)) {
5930         this._oColumnSet =  new YAHOO.widget.ColumnSet(aColumnDefs);
5931     }
5932     // Backward compatibility
5933     else if(aColumnDefs instanceof YAHOO.widget.ColumnSet) {
5934         this._oColumnSet =  aColumnDefs;
5935     }
5936
5937     // Register CellEditor Custom Events
5938     var allKeys = this._oColumnSet.keys;
5939     for(i=0, len=allKeys.length; i<len; i++) {
5940         oColumn = allKeys[i];
5941         if(oColumn.editor && oColumn.editor.subscribe) { // Backward incompatibility
5942             oColumn.editor.subscribe("showEvent", this._onEditorShowEvent, this, true);
5943             oColumn.editor.subscribe("keydownEvent", this._onEditorKeydownEvent, this, true);
5944             oColumn.editor.subscribe("revertEvent", this._onEditorRevertEvent, this, true);
5945             oColumn.editor.subscribe("saveEvent", this._onEditorSaveEvent, this, true);
5946             oColumn.editor.subscribe("cancelEvent", this._onEditorCancelEvent, this, true);
5947             oColumn.editor.subscribe("blurEvent", this._onEditorBlurEvent, this, true);
5948             oColumn.editor.subscribe("blockEvent", this._onEditorBlockEvent, this, true);
5949             oColumn.editor.subscribe("unblockEvent", this._onEditorUnblockEvent, this, true);
5950         }
5951     }
5952 },
5953
5954 /**
5955  * Initializes DataSource.
5956  *
5957  * @method _initDataSource
5958  * @param oDataSource {YAHOO.util.DataSource} DataSource instance.
5959  * @private
5960  */
5961 _initDataSource : function(oDataSource) {
5962     this._oDataSource = null;
5963     if(oDataSource && (lang.isFunction(oDataSource.sendRequest))) {
5964         this._oDataSource = oDataSource;
5965     }
5966     // Backward compatibility
5967     else {
5968         var tmpTable = null;
5969         var tmpContainer = this._elContainer;
5970         var i=0;
5971         //TODO: this will break if re-initing DS at runtime for SDT
5972         // Peek in container child nodes to see if TABLE already exists
5973         if(tmpContainer.hasChildNodes()) {
5974             var tmpChildren = tmpContainer.childNodes;
5975             for(i=0; i<tmpChildren.length; i++) {
5976                 if(tmpChildren[i].nodeName && tmpChildren[i].nodeName.toLowerCase() == "table") {
5977                     tmpTable = tmpChildren[i];
5978                     break;
5979                 }
5980             }
5981             if(tmpTable) {
5982                 var tmpFieldsArray = [];
5983                 for(; i<this._oColumnSet.keys.length; i++) {
5984                     tmpFieldsArray.push({key:this._oColumnSet.keys[i].key});
5985                 }
5986
5987                 this._oDataSource = new DS(tmpTable);
5988                 this._oDataSource.responseType = DS.TYPE_HTMLTABLE;
5989                 this._oDataSource.responseSchema = {fields: tmpFieldsArray};
5990             }
5991         }
5992     }
5993 },
5994
5995 /**
5996  * Initializes RecordSet.
5997  *
5998  * @method _initRecordSet
5999  * @private
6000  */
6001 _initRecordSet : function() {
6002     if(this._oRecordSet) {
6003         this._oRecordSet.reset();
6004     }
6005     else {
6006         this._oRecordSet = new YAHOO.widget.RecordSet();
6007     }
6008 },
6009
6010 /**
6011  * Initializes DOM elements.
6012  *
6013  * @method _initDomElements
6014  * @param elContainer {HTMLElement | String} HTML DIV element by reference or ID. 
6015  * return {Boolean} False in case of error, otherwise true 
6016  * @private
6017  */
6018 _initDomElements : function(elContainer) {
6019     // Outer container
6020     this._initContainerEl(elContainer);
6021     // TABLE
6022     this._initTableEl(this._elContainer);
6023     // COLGROUP
6024     this._initColgroupEl(this._elTable);
6025     // THEAD
6026     this._initTheadEl(this._elTable);
6027     
6028     // Message TBODY
6029     this._initMsgTbodyEl(this._elTable);  
6030
6031     // Primary TBODY
6032     this._initTbodyEl(this._elTable);
6033
6034     if(!this._elContainer || !this._elTable || !this._elColgroup ||  !this._elThead || !this._elTbody || !this._elMsgTbody) {
6035         return false;
6036     }
6037     else {
6038         return true;
6039     }
6040 },
6041
6042 /**
6043  * Destroy's the DataTable outer container element, if available.
6044  *
6045  * @method _destroyContainerEl
6046  * @param elContainer {HTMLElement} Reference to the container element. 
6047  * @private
6048  */
6049 _destroyContainerEl : function(elContainer) {
6050         var columns = this._oColumnSet.keys,
6051         elements, i;
6052
6053         Dom.removeClass(elContainer, DT.CLASS_DATATABLE);
6054
6055     // Bug 2528783
6056     Ev.purgeElement( elContainer );
6057     Ev.purgeElement( this._elThead, true ); // recursive to get resize handles
6058     Ev.purgeElement( this._elTbody );
6059     Ev.purgeElement( this._elMsgTbody );
6060
6061     // because change doesn't bubble, each select (via formatDropdown) gets
6062     // its own subscription
6063     elements = elContainer.getElementsByTagName( 'select' );
6064
6065     if ( elements.length ) {
6066         Ev.detachListener( elements, 'change' );
6067     }
6068
6069     for ( i = columns.length - 1; i >= 0; --i ) {
6070         if ( columns[i].editor ) {
6071             Ev.purgeElement( columns[i].editor._elContainer );
6072         }
6073     }
6074
6075     elContainer.innerHTML = "";
6076     
6077     this._elContainer = null;
6078     this._elColgroup = null;
6079     this._elThead = null;
6080     this._elTbody = null;
6081 },
6082
6083 /**
6084  * Initializes the DataTable outer container element, including a mask.
6085  *
6086  * @method _initContainerEl
6087  * @param elContainer {HTMLElement | String} HTML DIV element by reference or ID.
6088  * @private
6089  */
6090 _initContainerEl : function(elContainer) {
6091     // Validate container
6092     elContainer = Dom.get(elContainer);
6093     
6094     if(elContainer && elContainer.nodeName && (elContainer.nodeName.toLowerCase() == "div")) {
6095         // Destroy previous
6096         this._destroyContainerEl(elContainer);
6097
6098         Dom.addClass(elContainer, DT.CLASS_DATATABLE);
6099         Ev.addListener(elContainer, "focus", this._onTableFocus, this);
6100         Ev.addListener(elContainer, "dblclick", this._onTableDblclick, this);
6101         this._elContainer = elContainer;
6102         
6103         var elMask = document.createElement("div");
6104         elMask.className = DT.CLASS_MASK;
6105         elMask.style.display = "none";
6106         this._elMask = elContainer.appendChild(elMask);
6107     }
6108 },
6109
6110 /**
6111  * Destroy's the DataTable TABLE element, if available.
6112  *
6113  * @method _destroyTableEl
6114  * @private
6115  */
6116 _destroyTableEl : function() {
6117     var elTable = this._elTable;
6118     if(elTable) {
6119         Ev.purgeElement(elTable, true);
6120         elTable.parentNode.removeChild(elTable);
6121         this._elCaption = null;
6122         this._elColgroup = null;
6123         this._elThead = null;
6124         this._elTbody = null;
6125     }
6126 },
6127
6128 /**
6129  * Creates HTML markup CAPTION element.
6130  *
6131  * @method _initCaptionEl
6132  * @param sCaption {HTML} Caption value. String values are treated as markup and
6133  * inserted into the DOM with innerHTML.
6134  * @private
6135  */
6136 _initCaptionEl : function(sCaption) {
6137     if(this._elTable && sCaption) {
6138         // Create CAPTION element
6139         if(!this._elCaption) { 
6140             this._elCaption = this._elTable.createCaption();
6141         }
6142         // Set CAPTION value
6143         this._elCaption.innerHTML = sCaption;
6144     }
6145     else if(this._elCaption) {
6146         this._elCaption.parentNode.removeChild(this._elCaption);
6147     }
6148 },
6149
6150 /**
6151  * Creates HTML markup for TABLE, COLGROUP, THEAD and TBODY elements in outer
6152  * container element.
6153  *
6154  * @method _initTableEl
6155  * @param elContainer {HTMLElement} Container element into which to create TABLE.
6156  * @private
6157  */
6158 _initTableEl : function(elContainer) {
6159     if(elContainer) {
6160         // Destroy previous
6161         this._destroyTableEl();
6162     
6163         // Create TABLE
6164         this._elTable = elContainer.appendChild(document.createElement("table"));  
6165          
6166         // Set SUMMARY attribute
6167         this._elTable.summary = this.get("summary");
6168         
6169         // Create CAPTION element
6170         if(this.get("caption")) {
6171             this._initCaptionEl(this.get("caption"));
6172         }
6173
6174         // Set up mouseover/mouseout events via mouseenter/mouseleave delegation
6175         Ev.delegate(this._elTable, "mouseenter", this._onTableMouseover, "thead ."+DT.CLASS_LABEL, this);
6176         Ev.delegate(this._elTable, "mouseleave", this._onTableMouseout, "thead ."+DT.CLASS_LABEL, this);
6177         Ev.delegate(this._elTable, "mouseenter", this._onTableMouseover, "tbody.yui-dt-data>tr>td", this);
6178         Ev.delegate(this._elTable, "mouseleave", this._onTableMouseout, "tbody.yui-dt-data>tr>td", this);
6179         Ev.delegate(this._elTable, "mouseenter", this._onTableMouseover, "tbody.yui-dt-message>tr>td", this);
6180         Ev.delegate(this._elTable, "mouseleave", this._onTableMouseout, "tbody.yui-dt-message>tr>td", this);
6181     }
6182 },
6183
6184 /**
6185  * Destroy's the DataTable COLGROUP element, if available.
6186  *
6187  * @method _destroyColgroupEl
6188  * @private
6189  */
6190 _destroyColgroupEl : function() {
6191     var elColgroup = this._elColgroup;
6192     if(elColgroup) {
6193         var elTable = elColgroup.parentNode;
6194         Ev.purgeElement(elColgroup, true);
6195         elTable.removeChild(elColgroup);
6196         this._elColgroup = null;
6197     }
6198 },
6199
6200 /**
6201  * Initializes COLGROUP and COL elements for managing minWidth.
6202  *
6203  * @method _initColgroupEl
6204  * @param elTable {HTMLElement} TABLE element into which to create COLGROUP.
6205  * @private
6206  */
6207 _initColgroupEl : function(elTable) {
6208     if(elTable) {
6209         // Destroy previous
6210         this._destroyColgroupEl();
6211
6212         // Add COLs to DOCUMENT FRAGMENT
6213         var allCols = this._aColIds || [],
6214             allKeys = this._oColumnSet.keys,
6215             i = 0, len = allCols.length,
6216             elCol, oColumn,
6217             elFragment = document.createDocumentFragment(),
6218             elColTemplate = document.createElement("col");
6219     
6220         for(i=0,len=allKeys.length; i<len; i++) {
6221             oColumn = allKeys[i];
6222             elCol = elFragment.appendChild(elColTemplate.cloneNode(false));
6223         }
6224     
6225         // Create COLGROUP
6226         var elColgroup = elTable.insertBefore(document.createElement("colgroup"), elTable.firstChild);
6227         elColgroup.appendChild(elFragment);
6228         this._elColgroup = elColgroup;
6229     }
6230 },
6231
6232 /**
6233  * Adds a COL element to COLGROUP at given index.
6234  *
6235  * @method _insertColgroupColEl
6236  * @param index {Number} Index of new COL element.
6237  * @private
6238  */
6239 _insertColgroupColEl : function(index) {
6240     if(lang.isNumber(index)&& this._elColgroup) {
6241         var nextSibling = this._elColgroup.childNodes[index] || null;
6242         this._elColgroup.insertBefore(document.createElement("col"), nextSibling);
6243     }
6244 },
6245
6246 /**
6247  * Removes a COL element to COLGROUP at given index.
6248  *
6249  * @method _removeColgroupColEl
6250  * @param index {Number} Index of removed COL element.
6251  * @private
6252  */
6253 _removeColgroupColEl : function(index) {
6254     if(lang.isNumber(index) && this._elColgroup && this._elColgroup.childNodes[index]) {
6255         this._elColgroup.removeChild(this._elColgroup.childNodes[index]);
6256     }
6257 },
6258
6259 /**
6260  * Reorders a COL element from old index(es) to new index.
6261  *
6262  * @method _reorderColgroupColEl
6263  * @param aKeyIndexes {Number[]} Array of indexes of removed COL element.
6264  * @param newIndex {Number} New index. 
6265  * @private
6266  */
6267 _reorderColgroupColEl : function(aKeyIndexes, newIndex) {
6268     if(lang.isArray(aKeyIndexes) && lang.isNumber(newIndex) && this._elColgroup && (this._elColgroup.childNodes.length > aKeyIndexes[aKeyIndexes.length-1])) {
6269         var i,
6270             tmpCols = [];
6271         // Remove COL
6272         for(i=aKeyIndexes.length-1; i>-1; i--) {
6273             tmpCols.push(this._elColgroup.removeChild(this._elColgroup.childNodes[aKeyIndexes[i]]));
6274         }
6275         // Insert COL
6276         var nextSibling = this._elColgroup.childNodes[newIndex] || null;
6277         for(i=tmpCols.length-1; i>-1; i--) {
6278             this._elColgroup.insertBefore(tmpCols[i], nextSibling);
6279         }
6280     }
6281 },
6282
6283 /**
6284  * Destroy's the DataTable THEAD element, if available.
6285  *
6286  * @method _destroyTheadEl
6287  * @private
6288  */
6289 _destroyTheadEl : function() {
6290     var elThead = this._elThead;
6291     if(elThead) {
6292         var elTable = elThead.parentNode;
6293         Ev.purgeElement(elThead, true);
6294         this._destroyColumnHelpers();
6295         elTable.removeChild(elThead);
6296         this._elThead = null;
6297     }
6298 },
6299
6300 /**
6301  * Initializes THEAD element.
6302  *
6303  * @method _initTheadEl
6304  * @param elTable {HTMLElement} TABLE element into which to create COLGROUP.
6305  * @param {HTMLElement} Initialized THEAD element. 
6306  * @private
6307  */
6308 _initTheadEl : function(elTable) {
6309     elTable = elTable || this._elTable;
6310     
6311     if(elTable) {
6312         // Destroy previous
6313         this._destroyTheadEl();
6314     
6315         //TODO: append to DOM later for performance
6316         var elThead = (this._elColgroup) ?
6317             elTable.insertBefore(document.createElement("thead"), this._elColgroup.nextSibling) :
6318             elTable.appendChild(document.createElement("thead"));
6319     
6320         // Set up DOM events for THEAD
6321         Ev.addListener(elThead, "focus", this._onTheadFocus, this);
6322         Ev.addListener(elThead, "keydown", this._onTheadKeydown, this);
6323         Ev.addListener(elThead, "mousedown", this._onTableMousedown, this);
6324         Ev.addListener(elThead, "mouseup", this._onTableMouseup, this);
6325         Ev.addListener(elThead, "click", this._onTheadClick, this);
6326         
6327         // Bug 2528073: mouseover/mouseout handled via mouseenter/mouseleave
6328         // delegation at the TABLE level
6329
6330         // Since we can't listen for click and dblclick on the same element...
6331         // Attach separately to THEAD and TBODY
6332         ///Ev.addListener(elThead, "dblclick", this._onTableDblclick, this);
6333         
6334        var oColumnSet = this._oColumnSet,
6335             oColumn, i,j, l;
6336         
6337         // Add TRs to the THEAD
6338         var colTree = oColumnSet.tree;
6339         var elTh;
6340         for(i=0; i<colTree.length; i++) {
6341             var elTheadTr = elThead.appendChild(document.createElement("tr"));
6342     
6343             // ...and create TH cells
6344             for(j=0; j<colTree[i].length; j++) {
6345                 oColumn = colTree[i][j];
6346                 elTh = elTheadTr.appendChild(document.createElement("th"));
6347                 this._initThEl(elTh,oColumn);
6348             }
6349     
6350                 // Set FIRST/LAST on THEAD rows
6351                 if(i === 0) {
6352                     Dom.addClass(elTheadTr, DT.CLASS_FIRST);
6353                 }
6354                 if(i === (colTree.length-1)) {
6355                     Dom.addClass(elTheadTr, DT.CLASS_LAST);
6356                 }
6357
6358         }
6359
6360         // Set FIRST/LAST on edge TH elements using the values in ColumnSet headers array
6361         var aFirstHeaders = oColumnSet.headers[0] || [];
6362         for(i=0; i<aFirstHeaders.length; i++) {
6363             Dom.addClass(Dom.get(this.getId() +"-th-"+aFirstHeaders[i]), DT.CLASS_FIRST);
6364         }
6365         var aLastHeaders = oColumnSet.headers[oColumnSet.headers.length-1] || [];
6366         for(i=0; i<aLastHeaders.length; i++) {
6367             Dom.addClass(Dom.get(this.getId() +"-th-"+aLastHeaders[i]), DT.CLASS_LAST);
6368         }
6369         
6370
6371         ///TODO: try _repaintGecko(this._elContainer) instead
6372         // Bug 1806891
6373         if(ua.webkit && ua.webkit < 420) {
6374             var oSelf = this;
6375             setTimeout(function() {
6376                 elThead.style.display = "";
6377             },0);
6378             elThead.style.display = 'none';
6379         }
6380         
6381         this._elThead = elThead;
6382         
6383         // Column helpers needs _elThead to exist
6384         this._initColumnHelpers();  
6385     }
6386 },
6387
6388 /**
6389  * Populates TH element as defined by Column.
6390  *
6391  * @method _initThEl
6392  * @param elTh {HTMLElement} TH element reference.
6393  * @param oColumn {YAHOO.widget.Column} Column object.
6394  * @private
6395  */
6396 _initThEl : function(elTh, oColumn) {
6397     elTh.id = this.getId() + "-th-" + oColumn.getSanitizedKey(); // Needed for accessibility, getColumn by TH, and ColumnDD
6398     elTh.innerHTML = "";
6399     elTh.rowSpan = oColumn.getRowspan();
6400     elTh.colSpan = oColumn.getColspan();
6401     oColumn._elTh = elTh;
6402     
6403     var elThLiner = elTh.appendChild(document.createElement("div"));
6404     elThLiner.id = elTh.id + "-liner"; // Needed for resizer
6405     elThLiner.className = DT.CLASS_LINER;
6406     oColumn._elThLiner = elThLiner;
6407     
6408     var elThLabel = elThLiner.appendChild(document.createElement("span"));
6409     elThLabel.className = DT.CLASS_LABEL;    
6410
6411     // Assign abbr attribute
6412     if(oColumn.abbr) {
6413         elTh.abbr = oColumn.abbr;
6414     }
6415     // Clear minWidth on hidden Columns
6416     if(oColumn.hidden) {
6417         this._clearMinWidth(oColumn);
6418     }
6419         
6420     elTh.className = this._getColumnClassNames(oColumn);
6421             
6422     // Set Column width...
6423     if(oColumn.width) {
6424         // Validate minWidth
6425         var nWidth = (oColumn.minWidth && (oColumn.width < oColumn.minWidth)) ?
6426                 oColumn.minWidth : oColumn.width;
6427         // ...for fallback cases
6428         if(DT._bDynStylesFallback) {
6429             elTh.firstChild.style.overflow = 'hidden';
6430             elTh.firstChild.style.width = nWidth + 'px';        
6431         }
6432         // ...for non fallback cases
6433         else {
6434             this._setColumnWidthDynStyles(oColumn, nWidth + 'px', 'hidden');
6435         }
6436     }
6437
6438     this.formatTheadCell(elThLabel, oColumn, this.get("sortedBy"));
6439     oColumn._elThLabel = elThLabel;
6440 },
6441
6442 /**
6443  * Outputs markup into the given TH based on given Column.
6444  *
6445  * @method formatTheadCell
6446  * @param elCellLabel {HTMLElement} The label SPAN element within the TH liner,
6447  * not the liner DIV element.     
6448  * @param oColumn {YAHOO.widget.Column} Column instance.
6449  * @param oSortedBy {Object} Sort state object literal.
6450 */
6451 formatTheadCell : function(elCellLabel, oColumn, oSortedBy) {
6452     var sKey = oColumn.getKey();
6453     var sLabel = lang.isValue(oColumn.label) ? oColumn.label : sKey;
6454
6455     // Add accessibility link for sortable Columns
6456     if(oColumn.sortable) {
6457         // Calculate the direction
6458         var sSortClass = this.getColumnSortDir(oColumn, oSortedBy);
6459         var bDesc = (sSortClass === DT.CLASS_DESC);
6460
6461         // This is the sorted Column
6462         if(oSortedBy && (oColumn.key === oSortedBy.key)) {
6463             bDesc = !(oSortedBy.dir === DT.CLASS_DESC);
6464         }
6465
6466         // Generate a unique HREF for visited status
6467         var sHref = this.getId() + "-href-" + oColumn.getSanitizedKey();
6468         
6469         // Generate a dynamic TITLE for sort status
6470         var sTitle = (bDesc) ? this.get("MSG_SORTDESC") : this.get("MSG_SORTASC");
6471         
6472         // Format the element
6473         elCellLabel.innerHTML = "<a href=\"" + sHref + "\" title=\"" + sTitle + "\" class=\"" + DT.CLASS_SORTABLE + "\">" + sLabel + "</a>";
6474     }
6475     // Just display the label for non-sortable Columns
6476     else {
6477         elCellLabel.innerHTML = sLabel;
6478     }
6479 },
6480
6481 /**
6482  * Disables DD from top-level Column TH elements.
6483  *
6484  * @method _destroyDraggableColumns
6485  * @private
6486  */
6487 _destroyDraggableColumns : function() {
6488     var oColumn, elTh;
6489     for(var i=0, len=this._oColumnSet.tree[0].length; i<len; i++) {
6490         oColumn = this._oColumnSet.tree[0][i];
6491         if(oColumn._dd) {
6492             oColumn._dd = oColumn._dd.unreg();
6493             Dom.removeClass(oColumn.getThEl(), DT.CLASS_DRAGGABLE);       
6494         }
6495     }
6496     
6497     // Destroy column drag proxy
6498     this._destroyColumnDragTargetEl();
6499 },
6500
6501 /**
6502  * Initializes top-level Column TH elements into DD instances.
6503  *
6504  * @method _initDraggableColumns
6505  * @private
6506  */
6507 _initDraggableColumns : function() {
6508     this._destroyDraggableColumns();
6509     if(util.DD) {
6510         var oColumn, elTh, elDragTarget;
6511         for(var i=0, len=this._oColumnSet.tree[0].length; i<len; i++) {
6512             oColumn = this._oColumnSet.tree[0][i];
6513             elTh = oColumn.getThEl();
6514             Dom.addClass(elTh, DT.CLASS_DRAGGABLE);
6515             elDragTarget = this._initColumnDragTargetEl();
6516             oColumn._dd = new YAHOO.widget.ColumnDD(this, oColumn, elTh, elDragTarget);
6517         }
6518     }
6519     else {
6520     }
6521 },
6522
6523 /**
6524  * Destroys shared Column drag target.
6525  *
6526  * @method _destroyColumnDragTargetEl
6527  * @private
6528  */
6529 _destroyColumnDragTargetEl : function() {
6530     if(this._elColumnDragTarget) {
6531         var el = this._elColumnDragTarget;
6532         YAHOO.util.Event.purgeElement(el);
6533         el.parentNode.removeChild(el);
6534         this._elColumnDragTarget = null;
6535     }
6536 },
6537
6538 /**
6539  * Creates HTML markup for shared Column drag target.
6540  *
6541  * @method _initColumnDragTargetEl
6542  * @return {HTMLElement} Reference to Column drag target.
6543  * @private
6544  */
6545 _initColumnDragTargetEl : function() {
6546     if(!this._elColumnDragTarget) {
6547         // Attach Column drag target element as first child of body
6548         var elColumnDragTarget = document.createElement('div');
6549         elColumnDragTarget.id = this.getId() + "-coltarget";
6550         elColumnDragTarget.className = DT.CLASS_COLTARGET;
6551         elColumnDragTarget.style.display = "none";
6552         document.body.insertBefore(elColumnDragTarget, document.body.firstChild);
6553
6554         // Internal tracker of Column drag target
6555         this._elColumnDragTarget = elColumnDragTarget;
6556
6557     }
6558     return this._elColumnDragTarget;
6559 },
6560
6561 /**
6562  * Disables resizeability on key Column TH elements.
6563  *
6564  * @method _destroyResizeableColumns
6565  * @private
6566  */
6567 _destroyResizeableColumns : function() {
6568     var aKeys = this._oColumnSet.keys;
6569     for(var i=0, len=aKeys.length; i<len; i++) {
6570         if(aKeys[i]._ddResizer) {
6571             aKeys[i]._ddResizer = aKeys[i]._ddResizer.unreg();
6572             Dom.removeClass(aKeys[i].getThEl(), DT.CLASS_RESIZEABLE);
6573         }
6574     }
6575
6576     // Destroy resizer proxy
6577     this._destroyColumnResizerProxyEl();
6578 },
6579
6580 /**
6581  * Initializes resizeability on key Column TH elements.
6582  *
6583  * @method _initResizeableColumns
6584  * @private
6585  */
6586 _initResizeableColumns : function() {
6587     this._destroyResizeableColumns();
6588     if(util.DD) {
6589         var oColumn, elTh, elThLiner, elThResizerLiner, elThResizer, elResizerProxy, cancelClick;
6590         for(var i=0, len=this._oColumnSet.keys.length; i<len; i++) {
6591             oColumn = this._oColumnSet.keys[i];
6592             if(oColumn.resizeable) {
6593                 elTh = oColumn.getThEl();
6594                 Dom.addClass(elTh, DT.CLASS_RESIZEABLE);
6595                 elThLiner = oColumn.getThLinerEl();
6596                 
6597                 // Bug 1915349: So resizer is as tall as TH when rowspan > 1
6598                 // Create a separate resizer liner with position:relative
6599                 elThResizerLiner = elTh.appendChild(document.createElement("div"));
6600                 elThResizerLiner.className = DT.CLASS_RESIZERLINER;
6601                 
6602                 // Move TH contents into the new resizer liner
6603                 elThResizerLiner.appendChild(elThLiner);
6604                 
6605                 // Create the resizer
6606                 elThResizer = elThResizerLiner.appendChild(document.createElement("div"));
6607                 elThResizer.id = elTh.id + "-resizer"; // Needed for ColumnResizer
6608                 elThResizer.className = DT.CLASS_RESIZER;
6609                 oColumn._elResizer = elThResizer;
6610
6611                 // Create the resizer proxy, once per instance
6612                 elResizerProxy = this._initColumnResizerProxyEl();
6613                 oColumn._ddResizer = new YAHOO.util.ColumnResizer(
6614                         this, oColumn, elTh, elThResizer, elResizerProxy);
6615                 cancelClick = function(e) {
6616                     Ev.stopPropagation(e);
6617                 };
6618                 Ev.addListener(elThResizer,"click",cancelClick);
6619             }
6620         }
6621     }
6622     else {
6623     }
6624 },
6625
6626 /**
6627  * Destroys shared Column resizer proxy.
6628  *
6629  * @method _destroyColumnResizerProxyEl
6630  * @return {HTMLElement} Reference to Column resizer proxy.
6631  * @private
6632  */
6633 _destroyColumnResizerProxyEl : function() {
6634     if(this._elColumnResizerProxy) {
6635         var el = this._elColumnResizerProxy;
6636         YAHOO.util.Event.purgeElement(el);
6637         el.parentNode.removeChild(el);
6638         this._elColumnResizerProxy = null;
6639     }
6640 },
6641
6642 /**
6643  * Creates HTML markup for shared Column resizer proxy.
6644  *
6645  * @method _initColumnResizerProxyEl
6646  * @return {HTMLElement} Reference to Column resizer proxy.
6647  * @private
6648  */
6649 _initColumnResizerProxyEl : function() {
6650     if(!this._elColumnResizerProxy) {
6651         // Attach Column resizer element as first child of body
6652         var elColumnResizerProxy = document.createElement("div");
6653         elColumnResizerProxy.id = this.getId() + "-colresizerproxy"; // Needed for ColumnResizer
6654         elColumnResizerProxy.className = DT.CLASS_RESIZERPROXY;
6655         document.body.insertBefore(elColumnResizerProxy, document.body.firstChild);
6656
6657         // Internal tracker of Column resizer proxy
6658         this._elColumnResizerProxy = elColumnResizerProxy;
6659     }
6660     return this._elColumnResizerProxy;
6661 },
6662
6663 /**
6664  * Destroys elements associated with Column functionality: ColumnDD and ColumnResizers.
6665  *
6666  * @method _destroyColumnHelpers
6667  * @private
6668  */
6669 _destroyColumnHelpers : function() {
6670     this._destroyDraggableColumns();
6671     this._destroyResizeableColumns();
6672 },
6673
6674 /**
6675  * Initializes elements associated with Column functionality: ColumnDD and ColumnResizers.
6676  *
6677  * @method _initColumnHelpers
6678  * @private
6679  */
6680 _initColumnHelpers : function() {
6681     if(this.get("draggableColumns")) {
6682         this._initDraggableColumns();
6683     }
6684     this._initResizeableColumns();
6685 },
6686
6687 /**
6688  * Destroy's the DataTable TBODY element, if available.
6689  *
6690  * @method _destroyTbodyEl
6691  * @private
6692  */
6693 _destroyTbodyEl : function() {
6694     var elTbody = this._elTbody;
6695     if(elTbody) {
6696         var elTable = elTbody.parentNode;
6697         Ev.purgeElement(elTbody, true);
6698         elTable.removeChild(elTbody);
6699         this._elTbody = null;
6700     }
6701 },
6702
6703 /**
6704  * Initializes TBODY element for data.
6705  *
6706  * @method _initTbodyEl
6707  * @param elTable {HTMLElement} TABLE element into which to create TBODY .
6708  * @private
6709  */
6710 _initTbodyEl : function(elTable) {
6711     if(elTable) {
6712         // Destroy previous
6713         this._destroyTbodyEl();
6714         
6715         // Create TBODY
6716         var elTbody = elTable.appendChild(document.createElement("tbody"));
6717         elTbody.tabIndex = 0;
6718         elTbody.className = DT.CLASS_DATA;
6719     
6720         // Set up DOM events for TBODY
6721         Ev.addListener(elTbody, "focus", this._onTbodyFocus, this);
6722         Ev.addListener(elTbody, "mousedown", this._onTableMousedown, this);
6723         Ev.addListener(elTbody, "mouseup", this._onTableMouseup, this);
6724         Ev.addListener(elTbody, "keydown", this._onTbodyKeydown, this);
6725         Ev.addListener(elTbody, "click", this._onTbodyClick, this);
6726
6727         // Bug 2528073: mouseover/mouseout handled via mouseenter/mouseleave
6728         // delegation at the TABLE level
6729
6730         // Since we can't listen for click and dblclick on the same element...
6731         // Attach separately to THEAD and TBODY
6732         ///Ev.addListener(elTbody, "dblclick", this._onTableDblclick, this);
6733         
6734     
6735         // IE puts focus outline in the wrong place
6736         if(ua.ie) {
6737             elTbody.hideFocus=true;
6738         }
6739
6740         this._elTbody = elTbody;
6741     }
6742 },
6743
6744 /**
6745  * Destroy's the DataTable message TBODY element, if available.
6746  *
6747  * @method _destroyMsgTbodyEl
6748  * @private
6749  */
6750 _destroyMsgTbodyEl : function() {
6751     var elMsgTbody = this._elMsgTbody;
6752     if(elMsgTbody) {
6753         var elTable = elMsgTbody.parentNode;
6754         Ev.purgeElement(elMsgTbody, true);
6755         elTable.removeChild(elMsgTbody);
6756         this._elTbody = null;
6757     }
6758 },
6759
6760 /**
6761  * Initializes TBODY element for messaging.
6762  *
6763  * @method _initMsgTbodyEl
6764  * @param elTable {HTMLElement} TABLE element into which to create TBODY 
6765  * @private
6766  */
6767 _initMsgTbodyEl : function(elTable) {
6768     if(elTable) {
6769         var elMsgTbody = document.createElement("tbody");
6770         elMsgTbody.className = DT.CLASS_MESSAGE;
6771         var elMsgTr = elMsgTbody.appendChild(document.createElement("tr"));
6772         elMsgTr.className = DT.CLASS_FIRST + " " + DT.CLASS_LAST;
6773         this._elMsgTr = elMsgTr;
6774         var elMsgTd = elMsgTr.appendChild(document.createElement("td"));
6775         elMsgTd.colSpan = this._oColumnSet.keys.length || 1;
6776         elMsgTd.className = DT.CLASS_FIRST + " " + DT.CLASS_LAST;
6777         this._elMsgTd = elMsgTd;
6778         elMsgTbody = elTable.insertBefore(elMsgTbody, this._elTbody);
6779         var elMsgLiner = elMsgTd.appendChild(document.createElement("div"));
6780         elMsgLiner.className = DT.CLASS_LINER;
6781         this._elMsgTbody = elMsgTbody;
6782
6783         // Set up DOM events for TBODY
6784         Ev.addListener(elMsgTbody, "focus", this._onTbodyFocus, this);
6785         Ev.addListener(elMsgTbody, "mousedown", this._onTableMousedown, this);
6786         Ev.addListener(elMsgTbody, "mouseup", this._onTableMouseup, this);
6787         Ev.addListener(elMsgTbody, "keydown", this._onTbodyKeydown, this);
6788         Ev.addListener(elMsgTbody, "click", this._onTbodyClick, this);
6789
6790         // Bug 2528073: mouseover/mouseout handled via mouseenter/mouseleave
6791         // delegation at the TABLE level
6792     }
6793 },
6794
6795 /**
6796  * Initialize internal event listeners
6797  *
6798  * @method _initEvents
6799  * @private
6800  */
6801 _initEvents : function () {
6802     // Initialize Column sort
6803     this._initColumnSort();
6804         
6805     // Add the document level click listener
6806     YAHOO.util.Event.addListener(document, "click", this._onDocumentClick, this);
6807
6808     // Paginator integration
6809     this.subscribe("paginatorChange",function () {
6810         this._handlePaginatorChange.apply(this,arguments);
6811     });
6812
6813     this.subscribe("initEvent",function () {
6814         this.renderPaginator();
6815     });
6816
6817     // Initialize CellEditor integration
6818     this._initCellEditing();
6819 },
6820
6821 /**      
6822   * Initializes Column sorting.      
6823   *      
6824   * @method _initColumnSort      
6825   * @private      
6826   */      
6827 _initColumnSort : function() {
6828     this.subscribe("theadCellClickEvent", this.onEventSortColumn);      
6829
6830     // Backward compatibility
6831     var oSortedBy = this.get("sortedBy");
6832     if(oSortedBy) {
6833         if(oSortedBy.dir == "desc") {
6834             this._configs.sortedBy.value.dir = DT.CLASS_DESC;
6835         }
6836         else if(oSortedBy.dir == "asc") {
6837             this._configs.sortedBy.value.dir = DT.CLASS_ASC;
6838         }
6839     }
6840 },
6841
6842 /**      
6843   * Initializes CellEditor integration.      
6844   *      
6845   * @method _initCellEditing      
6846   * @private      
6847   */      
6848 _initCellEditing : function() {
6849     this.subscribe("editorBlurEvent",function () {
6850         this.onEditorBlurEvent.apply(this,arguments);
6851     });
6852     this.subscribe("editorBlockEvent",function () {
6853         this.onEditorBlockEvent.apply(this,arguments);
6854     });
6855     this.subscribe("editorUnblockEvent",function () {
6856         this.onEditorUnblockEvent.apply(this,arguments);
6857     });
6858 },
6859
6860
6861
6862
6863
6864
6865
6866
6867
6868
6869
6870
6871
6872
6873
6874
6875
6876
6877
6878
6879
6880
6881
6882
6883
6884
6885
6886
6887
6888
6889
6890
6891
6892 // DOM MUTATION FUNCTIONS
6893
6894 /**
6895  * Retruns classnames to represent current Column states.
6896  * @method _getColumnClassnames 
6897  * @param oColumn {YAHOO.widget.Column} Column instance.
6898  * @param aAddClasses {String[]} An array of additional classnames to add to the
6899  * return value.  
6900  * @return {String} A String of classnames to be assigned to TH or TD elements
6901  * for given Column.  
6902  * @private 
6903  */
6904 _getColumnClassNames : function (oColumn, aAddClasses) {
6905     var allClasses;
6906     
6907     // Add CSS classes
6908     if(lang.isString(oColumn.className)) {
6909         // Single custom class
6910         allClasses = [oColumn.className];
6911     }
6912     else if(lang.isArray(oColumn.className)) {
6913         // Array of custom classes
6914         allClasses = oColumn.className;
6915     }
6916     else {
6917         // no custom classes
6918         allClasses = [];
6919     }
6920     
6921     // Hook for setting width with via dynamic style uses key since ID is too disposable
6922     allClasses[allClasses.length] = this.getId() + "-col-" +oColumn.getSanitizedKey();
6923
6924     // Column key - minus any chars other than "A-Z", "a-z", "0-9", "_", "-", ".", or ":"
6925     allClasses[allClasses.length] = "yui-dt-col-" +oColumn.getSanitizedKey();
6926
6927     var isSortedBy = this.get("sortedBy") || {};
6928     // Sorted
6929     if(oColumn.key === isSortedBy.key) {
6930         allClasses[allClasses.length] = isSortedBy.dir || '';
6931     }
6932     // Hidden
6933     if(oColumn.hidden) {
6934         allClasses[allClasses.length] = DT.CLASS_HIDDEN;
6935     }
6936     // Selected
6937     if(oColumn.selected) {
6938         allClasses[allClasses.length] = DT.CLASS_SELECTED;
6939     }
6940     // Sortable
6941     if(oColumn.sortable) {
6942         allClasses[allClasses.length] = DT.CLASS_SORTABLE;
6943     }
6944     // Resizeable
6945     if(oColumn.resizeable) {
6946         allClasses[allClasses.length] = DT.CLASS_RESIZEABLE;
6947     }
6948     // Editable
6949     if(oColumn.editor) {
6950         allClasses[allClasses.length] = DT.CLASS_EDITABLE;
6951     }
6952     
6953     // Addtnl classes, including First/Last
6954     if(aAddClasses) {
6955         allClasses = allClasses.concat(aAddClasses);
6956     }
6957     
6958     return allClasses.join(' ');  
6959 },
6960
6961 /**
6962  * Clears TR element template in response to any Column state change.
6963  * @method _clearTrTemplateEl
6964  * @private 
6965  */
6966 _clearTrTemplateEl : function () {
6967     this._elTrTemplate = null;
6968 },
6969
6970 /**
6971  * Returns a new TR element template with TD elements classed with current
6972  * Column states.
6973  * @method _getTrTemplateEl 
6974  * @return {HTMLElement} A TR element to be cloned and added to the DOM.
6975  * @private 
6976  */
6977 _getTrTemplateEl : function (oRecord, index) {
6978     // Template is already available
6979     if(this._elTrTemplate) {
6980         return this._elTrTemplate;
6981     }
6982     // Template needs to be created
6983     else {
6984         var d   = document,
6985             tr  = d.createElement('tr'),
6986             td  = d.createElement('td'),
6987             div = d.createElement('div');
6988     
6989         // Append the liner element
6990         td.appendChild(div);
6991
6992         // Create TD elements into DOCUMENT FRAGMENT
6993         var df = document.createDocumentFragment(),
6994             allKeys = this._oColumnSet.keys,
6995             elTd;
6996
6997         // Set state for each TD;
6998         var aAddClasses;
6999         for(var i=0, keysLen=allKeys.length; i<keysLen; i++) {
7000             // Clone the TD template
7001             elTd = td.cloneNode(true);
7002
7003             // Format the base TD
7004             elTd = this._formatTdEl(allKeys[i], elTd, i, (i===keysLen-1));
7005                         
7006             df.appendChild(elTd);
7007         }
7008         tr.appendChild(df);
7009         tr.className = DT.CLASS_REC;
7010         this._elTrTemplate = tr;
7011         return tr;
7012     }   
7013 },
7014
7015 /**
7016  * Formats a basic TD element.
7017  * @method _formatTdEl 
7018  * @param oColumn {YAHOO.widget.Column} Associated Column instance. 
7019  * @param elTd {HTMLElement} An unformatted TD element.
7020  * @param index {Number} Column key index. 
7021  * @param isLast {Boolean} True if Column is last key of the ColumnSet.
7022  * @return {HTMLElement} A formatted TD element.
7023  * @private 
7024  */
7025 _formatTdEl : function (oColumn, elTd, index, isLast) {
7026     var oColumnSet = this._oColumnSet;
7027     
7028     // Set the TD's accessibility headers
7029     var allHeaders = oColumnSet.headers,
7030         allColHeaders = allHeaders[index],
7031         sTdHeaders = "",
7032         sHeader;
7033     for(var j=0, headersLen=allColHeaders.length; j < headersLen; j++) {
7034         sHeader = this._sId + "-th-" + allColHeaders[j] + ' ';
7035         sTdHeaders += sHeader;
7036     }
7037     elTd.headers = sTdHeaders;
7038     
7039     // Class the TD element
7040     var aAddClasses = [];
7041     if(index === 0) {
7042         aAddClasses[aAddClasses.length] = DT.CLASS_FIRST;
7043     }
7044     if(isLast) {
7045         aAddClasses[aAddClasses.length] = DT.CLASS_LAST;
7046     }
7047     elTd.className = this._getColumnClassNames(oColumn, aAddClasses);
7048
7049     // Class the liner element
7050     elTd.firstChild.className = DT.CLASS_LINER;
7051
7052     // Set Column width for fallback cases
7053     if(oColumn.width && DT._bDynStylesFallback) {
7054         // Validate minWidth
7055         var nWidth = (oColumn.minWidth && (oColumn.width < oColumn.minWidth)) ?
7056                 oColumn.minWidth : oColumn.width;
7057         elTd.firstChild.style.overflow = 'hidden';
7058         elTd.firstChild.style.width = nWidth + 'px';
7059     }
7060     
7061     return elTd;
7062 },
7063
7064
7065 /**
7066  * Create a new TR element for a given Record and appends it with the correct
7067  * number of Column-state-classed TD elements. Striping is the responsibility of
7068  * the calling function, which may decide to stripe the single row, a subset of
7069  * rows, or all the rows.
7070  * @method _createTrEl
7071  * @param oRecord {YAHOO.widget.Record} Record instance
7072  * @return {HTMLElement} The new TR element.  This must be added to the DOM.
7073  * @private 
7074  */
7075 _addTrEl : function (oRecord) {
7076     var elTrTemplate = this._getTrTemplateEl();
7077     
7078     // Clone the TR template.
7079     var elTr = elTrTemplate.cloneNode(true);
7080     
7081     // Populate content
7082     return this._updateTrEl(elTr,oRecord);
7083 },
7084
7085 /**
7086  * Formats the contents of the given TR's TD elements with data from the given
7087  * Record. Only innerHTML should change, nothing structural.
7088  *
7089  * @method _updateTrEl
7090  * @param elTr {HTMLElement} The TR element to update.
7091  * @param oRecord {YAHOO.widget.Record} The associated Record instance.
7092  * @return {HTMLElement} DOM reference to the new TR element.
7093  * @private
7094  */
7095 _updateTrEl : function(elTr, oRecord) {
7096     var ok = this.get("formatRow") ? this.get("formatRow").call(this, elTr, oRecord) : true;
7097     if(ok) {
7098         // Hide the row to prevent constant reflows
7099         elTr.style.display = 'none';
7100         
7101         // Update TD elements with new data
7102         var allTds = elTr.childNodes,
7103             elTd;
7104         for(var i=0,len=allTds.length; i<len; ++i) {
7105             elTd = allTds[i];
7106             
7107             // Set the cell content
7108             this.formatCell(allTds[i].firstChild, oRecord, this._oColumnSet.keys[i]);
7109         }
7110         
7111         // Redisplay the row for reflow
7112         elTr.style.display = '';
7113     }
7114     
7115      // Record-to-TR association and tracking of FIRST/LAST
7116     var oldId = elTr.id,
7117         newId = oRecord.getId();
7118     if(this._sFirstTrId === oldId) {
7119         this._sFirstTrId = newId;
7120     }
7121     if(this._sLastTrId === oldId) {
7122         this._sLastTrId = newId;
7123     }
7124     elTr.id = newId;
7125     return elTr;
7126 },
7127
7128
7129 /**
7130  * Deletes TR element by DOM reference or by DataTable page row index.
7131  *
7132  * @method _deleteTrEl
7133  * @param row {HTMLElement | Number} TR element reference or Datatable page row index.
7134  * @return {Boolean} Returns true if successful, else returns false.
7135  * @private
7136  */
7137 _deleteTrEl : function(row) {
7138     var rowIndex;
7139
7140     // Get page row index for the element
7141     if(!lang.isNumber(row)) {
7142         rowIndex = Dom.get(row).sectionRowIndex;
7143     }
7144     else {
7145         rowIndex = row;
7146     }
7147     if(lang.isNumber(rowIndex) && (rowIndex > -2) && (rowIndex < this._elTbody.rows.length)) {
7148         // Cannot use tbody.deleteRow due to IE6 instability
7149         //return this._elTbody.deleteRow(rowIndex);
7150         return this._elTbody.removeChild(this._elTbody.rows[row]);
7151     }
7152     else {
7153         return null;
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 // CSS/STATE FUNCTIONS
7184
7185
7186
7187
7188 /**
7189  * Removes the class YAHOO.widget.DataTable.CLASS_FIRST from the first TR element
7190  * of the DataTable page and updates internal tracker.
7191  *
7192  * @method _unsetFirstRow
7193  * @private
7194  */
7195 _unsetFirstRow : function() {
7196     // Remove FIRST
7197     if(this._sFirstTrId) {
7198         Dom.removeClass(this._sFirstTrId, DT.CLASS_FIRST);
7199         this._sFirstTrId = null;
7200     }
7201 },
7202
7203 /**
7204  * Assigns the class YAHOO.widget.DataTable.CLASS_FIRST to the first TR element
7205  * of the DataTable page and updates internal tracker.
7206  *
7207  * @method _setFirstRow
7208  * @private
7209  */
7210 _setFirstRow : function() {
7211     this._unsetFirstRow();
7212     var elTr = this.getFirstTrEl();
7213     if(elTr) {
7214         // Set FIRST
7215         Dom.addClass(elTr, DT.CLASS_FIRST);
7216         this._sFirstTrId = elTr.id;
7217     }
7218 },
7219
7220 /**
7221  * Removes the class YAHOO.widget.DataTable.CLASS_LAST from the last TR element
7222  * of the DataTable page and updates internal tracker.
7223  *
7224  * @method _unsetLastRow
7225  * @private
7226  */
7227 _unsetLastRow : function() {
7228     // Unassign previous class
7229     if(this._sLastTrId) {
7230         Dom.removeClass(this._sLastTrId, DT.CLASS_LAST);
7231         this._sLastTrId = null;
7232     }   
7233 },
7234
7235 /**
7236  * Assigns the class YAHOO.widget.DataTable.CLASS_LAST to the last TR element
7237  * of the DataTable page and updates internal tracker.
7238  *
7239  * @method _setLastRow
7240  * @private
7241  */
7242 _setLastRow : function() {
7243     this._unsetLastRow();
7244     var elTr = this.getLastTrEl();
7245     if(elTr) {
7246         // Assign class
7247         Dom.addClass(elTr, DT.CLASS_LAST);
7248         this._sLastTrId = elTr.id;
7249     }
7250 },
7251
7252 /**
7253  * Assigns the classes DT.CLASS_EVEN and DT.CLASS_ODD to one, many, or all TR elements.
7254  *
7255  * @method _setRowStripes
7256  * @param row {HTMLElement | String | Number} (optional) HTML TR element reference
7257  * or string ID, or page row index of where to start striping.
7258  * @param range {Number} (optional) If given, how many rows to stripe, otherwise
7259  * stripe all the rows until the end.
7260  * @private
7261  */
7262 _setRowStripes : function(row, range) {
7263     // Default values stripe all rows
7264     var allRows = this._elTbody.rows,
7265         nStartIndex = 0,
7266         nEndIndex = allRows.length,
7267         aOdds = [], nOddIdx = 0,
7268         aEvens = [], nEvenIdx = 0;
7269
7270     // Stripe a subset
7271     if((row !== null) && (row !== undefined)) {
7272         // Validate given start row
7273         var elStartRow = this.getTrEl(row);
7274         if(elStartRow) {
7275             nStartIndex = elStartRow.sectionRowIndex;
7276
7277             // Validate given range
7278             if(lang.isNumber(range) && (range > 1)) {
7279                 nEndIndex = nStartIndex + range;
7280             }
7281         }
7282     }
7283
7284     for(var i=nStartIndex; i<nEndIndex; i++) {
7285         if(i%2) {
7286             aOdds[nOddIdx++] = allRows[i];
7287         } else {
7288             aEvens[nEvenIdx++] = allRows[i];
7289         }
7290     }
7291
7292     if (aOdds.length) {
7293         Dom.replaceClass(aOdds, DT.CLASS_EVEN, DT.CLASS_ODD);
7294     }
7295
7296     if (aEvens.length) {
7297         Dom.replaceClass(aEvens, DT.CLASS_ODD, DT.CLASS_EVEN);
7298     }
7299 },
7300
7301 /**
7302  * Assigns the class DT.CLASS_SELECTED to TR and TD elements.
7303  *
7304  * @method _setSelections
7305  * @private
7306  */
7307 _setSelections : function() {
7308     // Keep track of selected rows
7309     var allSelectedRows = this.getSelectedRows();
7310     // Keep track of selected cells
7311     var allSelectedCells = this.getSelectedCells();
7312     // Anything to select?
7313     if((allSelectedRows.length>0) || (allSelectedCells.length > 0)) {
7314         var oColumnSet = this._oColumnSet,
7315             el;
7316         // Loop over each row
7317         for(var i=0; i<allSelectedRows.length; i++) {
7318             el = Dom.get(allSelectedRows[i]);
7319             if(el) {
7320                 Dom.addClass(el, DT.CLASS_SELECTED);
7321             }
7322         }
7323         // Loop over each cell
7324         for(i=0; i<allSelectedCells.length; i++) {
7325             el = Dom.get(allSelectedCells[i].recordId);
7326             if(el) {
7327                 Dom.addClass(el.childNodes[oColumnSet.getColumn(allSelectedCells[i].columnKey).getKeyIndex()], DT.CLASS_SELECTED);
7328             }
7329         }
7330     }       
7331 },
7332
7333
7334
7335
7336
7337
7338
7339
7340
7341
7342
7343
7344
7345
7346
7347
7348
7349
7350
7351
7352
7353
7354
7355
7356
7357
7358
7359
7360
7361
7362
7363
7364
7365
7366
7367
7368
7369
7370
7371
7372
7373
7374
7375 /////////////////////////////////////////////////////////////////////////////
7376 //
7377 // Private DOM Event Handlers
7378 //
7379 /////////////////////////////////////////////////////////////////////////////
7380
7381 /**
7382  * Validates minWidths whenever the render chain ends.
7383  *
7384  * @method _onRenderChainEnd
7385  * @private
7386  */
7387 _onRenderChainEnd : function() {
7388     // Hide loading message
7389     this.hideTableMessage();
7390     
7391     // Show empty message
7392     if(this._elTbody.rows.length === 0) {
7393         this.showTableMessage(this.get("MSG_EMPTY"), DT.CLASS_EMPTY);        
7394     }
7395
7396     // Execute in timeout thread to give implementers a chance
7397     // to subscribe after the constructor
7398     var oSelf = this;
7399     setTimeout(function() {
7400         if((oSelf instanceof DT) && oSelf._sId) {        
7401             // Init event
7402             if(oSelf._bInit) {
7403                 oSelf._bInit = false;
7404                 oSelf.fireEvent("initEvent");
7405             }
7406     
7407             // Render event
7408             oSelf.fireEvent("renderEvent");
7409             // Backward compatibility
7410             oSelf.fireEvent("refreshEvent");
7411     
7412             // Post-render routine
7413             oSelf.validateColumnWidths();
7414     
7415             // Post-render event
7416             oSelf.fireEvent("postRenderEvent");
7417             
7418             /*if(YAHOO.example.Performance.trialStart) {
7419                 YAHOO.example.Performance.trialStart = null;
7420             }*/
7421             
7422         }
7423     }, 0);
7424 },
7425
7426 /**
7427  * Handles click events on the DOCUMENT.
7428  *
7429  * @method _onDocumentClick
7430  * @param e {HTMLEvent} The click event.
7431  * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
7432  * @private
7433  */
7434 _onDocumentClick : function(e, oSelf) {
7435     var elTarget = Ev.getTarget(e);
7436     var elTag = elTarget.nodeName.toLowerCase();
7437
7438     if(!Dom.isAncestor(oSelf._elContainer, elTarget)) {
7439         oSelf.fireEvent("tableBlurEvent");
7440
7441         // Fires editorBlurEvent when click is not within the TABLE.
7442         // For cases when click is within the TABLE, due to timing issues,
7443         // the editorBlurEvent needs to get fired by the lower-level DOM click
7444         // handlers below rather than by the TABLE click handler directly.
7445         if(oSelf._oCellEditor) {
7446             if(oSelf._oCellEditor.getContainerEl) {
7447                 var elContainer = oSelf._oCellEditor.getContainerEl();
7448                 // Only if the click was not within the CellEditor container
7449                 if(!Dom.isAncestor(elContainer, elTarget) &&
7450                         (elContainer.id !== elTarget.id)) {
7451                     oSelf._oCellEditor.fireEvent("blurEvent", {editor: oSelf._oCellEditor});
7452                 }
7453             }
7454             // Backward Compatibility
7455             else if(oSelf._oCellEditor.isActive) {
7456                 // Only if the click was not within the Cell Editor container
7457                 if(!Dom.isAncestor(oSelf._oCellEditor.container, elTarget) &&
7458                         (oSelf._oCellEditor.container.id !== elTarget.id)) {
7459                     oSelf.fireEvent("editorBlurEvent", {editor:oSelf._oCellEditor});
7460                 }
7461             }
7462         }
7463     }
7464 },
7465
7466 /**
7467  * Handles focus events on the DataTable instance.
7468  *
7469  * @method _onTableFocus
7470  * @param e {HTMLEvent} The focus event.
7471  * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
7472  * @private
7473  */
7474 _onTableFocus : function(e, oSelf) {
7475     oSelf.fireEvent("tableFocusEvent");
7476 },
7477
7478 /**
7479  * Handles focus events on the THEAD element.
7480  *
7481  * @method _onTheadFocus
7482  * @param e {HTMLEvent} The focus event.
7483  * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
7484  * @private
7485  */
7486 _onTheadFocus : function(e, oSelf) {
7487     oSelf.fireEvent("theadFocusEvent");
7488     oSelf.fireEvent("tableFocusEvent");
7489 },
7490
7491 /**
7492  * Handles focus events on the TBODY element.
7493  *
7494  * @method _onTbodyFocus
7495  * @param e {HTMLEvent} The focus event.
7496  * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
7497  * @private
7498  */
7499 _onTbodyFocus : function(e, oSelf) {
7500     oSelf.fireEvent("tbodyFocusEvent");
7501     oSelf.fireEvent("tableFocusEvent");
7502 },
7503
7504 /**
7505  * Handles mouseover events on the DataTable instance.
7506  *
7507  * @method _onTableMouseover
7508  * @param e {HTMLEvent} The mouseover event.
7509  * @param origTarget {HTMLElement} The mouseenter delegated element.
7510  * @param container {HTMLElement} The mouseenter delegation container.
7511  * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
7512  * @private
7513  */
7514 _onTableMouseover : function(e, origTarget, container, oSelf) {
7515     var elTarget = origTarget;
7516     var elTag = elTarget.nodeName && elTarget.nodeName.toLowerCase();
7517     var bKeepBubbling = true;
7518     while(elTarget && (elTag != "table")) {
7519         switch(elTag) {
7520             case "body":
7521                  return;
7522             case "a":
7523                 break;
7524             case "td":
7525                 bKeepBubbling = oSelf.fireEvent("cellMouseoverEvent",{target:elTarget,event:e});
7526                 break;
7527             case "span":
7528                 if(Dom.hasClass(elTarget, DT.CLASS_LABEL)) {
7529                     bKeepBubbling = oSelf.fireEvent("theadLabelMouseoverEvent",{target:elTarget,event:e});
7530                     // Backward compatibility
7531                     bKeepBubbling = oSelf.fireEvent("headerLabelMouseoverEvent",{target:elTarget,event:e});
7532                 }
7533                 break;
7534             case "th":
7535                 bKeepBubbling = oSelf.fireEvent("theadCellMouseoverEvent",{target:elTarget,event:e});
7536                 // Backward compatibility
7537                 bKeepBubbling = oSelf.fireEvent("headerCellMouseoverEvent",{target:elTarget,event:e});
7538                 break;
7539             case "tr":
7540                 if(elTarget.parentNode.nodeName.toLowerCase() == "thead") {
7541                     bKeepBubbling = oSelf.fireEvent("theadRowMouseoverEvent",{target:elTarget,event:e});
7542                     // Backward compatibility
7543                     bKeepBubbling = oSelf.fireEvent("headerRowMouseoverEvent",{target:elTarget,event:e});
7544                 }
7545                 else {
7546                     bKeepBubbling = oSelf.fireEvent("rowMouseoverEvent",{target:elTarget,event:e});
7547                 }
7548                 break;
7549             default:
7550                 break;
7551         }
7552         if(bKeepBubbling === false) {
7553             return;
7554         }
7555         else {
7556             elTarget = elTarget.parentNode;
7557             if(elTarget) {
7558                 elTag = elTarget.nodeName.toLowerCase();
7559             }
7560         }
7561     }
7562     oSelf.fireEvent("tableMouseoverEvent",{target:(elTarget || oSelf._elContainer),event:e});
7563 },
7564
7565 /**
7566  * Handles mouseout events on the DataTable instance.
7567  *
7568  * @method _onTableMouseout
7569  * @param e {HTMLEvent} The mouseout event.
7570  * @param origTarget {HTMLElement} The mouseleave delegated element.
7571  * @param container {HTMLElement} The mouseleave delegation container.
7572  * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
7573  * @private
7574  */
7575 _onTableMouseout : function(e, origTarget, container, oSelf) {
7576     var elTarget = origTarget;
7577     var elTag = elTarget.nodeName && elTarget.nodeName.toLowerCase();
7578     var bKeepBubbling = true;
7579     while(elTarget && (elTag != "table")) {
7580         switch(elTag) {
7581             case "body":
7582                 return;
7583             case "a":
7584                 break;
7585             case "td":
7586                 bKeepBubbling = oSelf.fireEvent("cellMouseoutEvent",{target:elTarget,event:e});
7587                 break;
7588             case "span":
7589                 if(Dom.hasClass(elTarget, DT.CLASS_LABEL)) {
7590                     bKeepBubbling = oSelf.fireEvent("theadLabelMouseoutEvent",{target:elTarget,event:e});
7591                     // Backward compatibility
7592                     bKeepBubbling = oSelf.fireEvent("headerLabelMouseoutEvent",{target:elTarget,event:e});
7593                 }
7594                 break;
7595             case "th":
7596                 bKeepBubbling = oSelf.fireEvent("theadCellMouseoutEvent",{target:elTarget,event:e});
7597                 // Backward compatibility
7598                 bKeepBubbling = oSelf.fireEvent("headerCellMouseoutEvent",{target:elTarget,event:e});
7599                 break;
7600             case "tr":
7601                 if(elTarget.parentNode.nodeName.toLowerCase() == "thead") {
7602                     bKeepBubbling = oSelf.fireEvent("theadRowMouseoutEvent",{target:elTarget,event:e});
7603                     // Backward compatibility
7604                     bKeepBubbling = oSelf.fireEvent("headerRowMouseoutEvent",{target:elTarget,event:e});
7605                 }
7606                 else {
7607                     bKeepBubbling = oSelf.fireEvent("rowMouseoutEvent",{target:elTarget,event:e});
7608                 }
7609                 break;
7610             default:
7611                 break;
7612         }
7613         if(bKeepBubbling === false) {
7614             return;
7615         }
7616         else {
7617             elTarget = elTarget.parentNode;
7618             if(elTarget) {
7619                 elTag = elTarget.nodeName.toLowerCase();
7620             }
7621         }
7622     }
7623     oSelf.fireEvent("tableMouseoutEvent",{target:(elTarget || oSelf._elContainer),event:e});
7624 },
7625
7626 /**
7627  * Handles mousedown events on the DataTable instance.
7628  *
7629  * @method _onTableMousedown
7630  * @param e {HTMLEvent} The mousedown event.
7631  * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
7632  * @private
7633  */
7634 _onTableMousedown : function(e, oSelf) {
7635     var elTarget = Ev.getTarget(e);
7636     var elTag = elTarget.nodeName && elTarget.nodeName.toLowerCase();
7637     var bKeepBubbling = true;
7638     while(elTarget && (elTag != "table")) {
7639         switch(elTag) {
7640             case "body":
7641                 return;
7642             case "a":
7643                 break;
7644             case "td":
7645                 bKeepBubbling = oSelf.fireEvent("cellMousedownEvent",{target:elTarget,event:e});
7646                 break;
7647             case "span":
7648                 if(Dom.hasClass(elTarget, DT.CLASS_LABEL)) {
7649                     bKeepBubbling = oSelf.fireEvent("theadLabelMousedownEvent",{target:elTarget,event:e});
7650                     // Backward compatibility
7651                     bKeepBubbling = oSelf.fireEvent("headerLabelMousedownEvent",{target:elTarget,event:e});
7652                 }
7653                 break;
7654             case "th":
7655                 bKeepBubbling = oSelf.fireEvent("theadCellMousedownEvent",{target:elTarget,event:e});
7656                 // Backward compatibility
7657                 bKeepBubbling = oSelf.fireEvent("headerCellMousedownEvent",{target:elTarget,event:e});
7658                 break;
7659             case "tr":
7660                 if(elTarget.parentNode.nodeName.toLowerCase() == "thead") {
7661                     bKeepBubbling = oSelf.fireEvent("theadRowMousedownEvent",{target:elTarget,event:e});
7662                     // Backward compatibility
7663                     bKeepBubbling = oSelf.fireEvent("headerRowMousedownEvent",{target:elTarget,event:e});
7664                 }
7665                 else {
7666                     bKeepBubbling = oSelf.fireEvent("rowMousedownEvent",{target:elTarget,event:e});
7667                 }
7668                 break;
7669             default:
7670                 break;
7671         }
7672         if(bKeepBubbling === false) {
7673             return;
7674         }
7675         else {
7676             elTarget = elTarget.parentNode;
7677             if(elTarget) {
7678                 elTag = elTarget.nodeName.toLowerCase();
7679             }
7680         }
7681     }
7682     oSelf.fireEvent("tableMousedownEvent",{target:(elTarget || oSelf._elContainer),event:e});
7683 },
7684
7685 /**
7686  * Handles mouseup events on the DataTable instance.
7687  *
7688  * @method _onTableMouseup
7689  * @param e {HTMLEvent} The mouseup event.
7690  * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
7691  * @private
7692  */
7693 _onTableMouseup : function(e, oSelf) {
7694     var elTarget = Ev.getTarget(e);
7695     var elTag = elTarget.nodeName && elTarget.nodeName.toLowerCase();
7696     var bKeepBubbling = true;
7697     while(elTarget && (elTag != "table")) {
7698         switch(elTag) {
7699             case "body":
7700                 return;
7701             case "a":
7702                 break;
7703             case "td":
7704                 bKeepBubbling = oSelf.fireEvent("cellMouseupEvent",{target:elTarget,event:e});
7705                 break;
7706             case "span":
7707                 if(Dom.hasClass(elTarget, DT.CLASS_LABEL)) {
7708                     bKeepBubbling = oSelf.fireEvent("theadLabelMouseupEvent",{target:elTarget,event:e});
7709                     // Backward compatibility
7710                     bKeepBubbling = oSelf.fireEvent("headerLabelMouseupEvent",{target:elTarget,event:e});
7711                 }
7712                 break;
7713             case "th":
7714                 bKeepBubbling = oSelf.fireEvent("theadCellMouseupEvent",{target:elTarget,event:e});
7715                 // Backward compatibility
7716                 bKeepBubbling = oSelf.fireEvent("headerCellMouseupEvent",{target:elTarget,event:e});
7717                 break;
7718             case "tr":
7719                 if(elTarget.parentNode.nodeName.toLowerCase() == "thead") {
7720                     bKeepBubbling = oSelf.fireEvent("theadRowMouseupEvent",{target:elTarget,event:e});
7721                     // Backward compatibility
7722                     bKeepBubbling = oSelf.fireEvent("headerRowMouseupEvent",{target:elTarget,event:e});
7723                 }
7724                 else {
7725                     bKeepBubbling = oSelf.fireEvent("rowMouseupEvent",{target:elTarget,event:e});
7726                 }
7727                 break;
7728             default:
7729                 break;
7730         }
7731         if(bKeepBubbling === false) {
7732             return;
7733         }
7734         else {
7735             elTarget = elTarget.parentNode;
7736             if(elTarget) {
7737                 elTag = elTarget.nodeName.toLowerCase();
7738             }
7739         }
7740     }
7741     oSelf.fireEvent("tableMouseupEvent",{target:(elTarget || oSelf._elContainer),event:e});
7742 },
7743
7744 /**
7745  * Handles dblclick events on the DataTable instance.
7746  *
7747  * @method _onTableDblclick
7748  * @param e {HTMLEvent} The dblclick event.
7749  * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
7750  * @private
7751  */
7752 _onTableDblclick : function(e, oSelf) {
7753     var elTarget = Ev.getTarget(e);
7754     var elTag = elTarget.nodeName && elTarget.nodeName.toLowerCase();
7755     var bKeepBubbling = true;
7756     while(elTarget && (elTag != "table")) {
7757         switch(elTag) {
7758             case "body":
7759                 return;
7760             case "td":
7761                 bKeepBubbling = oSelf.fireEvent("cellDblclickEvent",{target:elTarget,event:e});
7762                 break;
7763             case "span":
7764                 if(Dom.hasClass(elTarget, DT.CLASS_LABEL)) {
7765                     bKeepBubbling = oSelf.fireEvent("theadLabelDblclickEvent",{target:elTarget,event:e});
7766                     // Backward compatibility
7767                     bKeepBubbling = oSelf.fireEvent("headerLabelDblclickEvent",{target:elTarget,event:e});
7768                 }
7769                 break;
7770             case "th":
7771                 bKeepBubbling = oSelf.fireEvent("theadCellDblclickEvent",{target:elTarget,event:e});
7772                 // Backward compatibility
7773                 bKeepBubbling = oSelf.fireEvent("headerCellDblclickEvent",{target:elTarget,event:e});
7774                 break;
7775             case "tr":
7776                 if(elTarget.parentNode.nodeName.toLowerCase() == "thead") {
7777                     bKeepBubbling = oSelf.fireEvent("theadRowDblclickEvent",{target:elTarget,event:e});
7778                     // Backward compatibility
7779                     bKeepBubbling = oSelf.fireEvent("headerRowDblclickEvent",{target:elTarget,event:e});
7780                 }
7781                 else {
7782                     bKeepBubbling = oSelf.fireEvent("rowDblclickEvent",{target:elTarget,event:e});
7783                 }
7784                 break;
7785             default:
7786                 break;
7787         }
7788         if(bKeepBubbling === false) {
7789             return;
7790         }
7791         else {
7792             elTarget = elTarget.parentNode;
7793             if(elTarget) {
7794                 elTag = elTarget.nodeName.toLowerCase();
7795             }
7796         }
7797     }
7798     oSelf.fireEvent("tableDblclickEvent",{target:(elTarget || oSelf._elContainer),event:e});
7799 },
7800 /**
7801  * Handles keydown events on the THEAD element.
7802  *
7803  * @method _onTheadKeydown
7804  * @param e {HTMLEvent} The key event.
7805  * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
7806  * @private
7807  */
7808 _onTheadKeydown : function(e, oSelf) {
7809     var elTarget = Ev.getTarget(e);
7810     var elTag = elTarget.nodeName && elTarget.nodeName.toLowerCase();
7811     var bKeepBubbling = true;
7812     while(elTarget && (elTag != "table")) {
7813         switch(elTag) {
7814             case "body":
7815                 return;
7816             case "input":
7817             case "textarea":
7818                 // TODO: implement textareaKeyEvent
7819                 break;
7820             case "thead":
7821                 bKeepBubbling = oSelf.fireEvent("theadKeyEvent",{target:elTarget,event:e});
7822                 break;
7823             default:
7824                 break;
7825         }
7826         if(bKeepBubbling === false) {
7827             return;
7828         }
7829         else {
7830             elTarget = elTarget.parentNode;
7831             if(elTarget) {
7832                 elTag = elTarget.nodeName.toLowerCase();
7833             }
7834         }
7835     }
7836     oSelf.fireEvent("tableKeyEvent",{target:(elTarget || oSelf._elContainer),event:e});
7837 },
7838
7839 /**
7840  * Handles keydown events on the TBODY element. Handles selection behavior,
7841  * provides hooks for ENTER to edit functionality.
7842  *
7843  * @method _onTbodyKeydown
7844  * @param e {HTMLEvent} The key event.
7845  * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
7846  * @private
7847  */
7848 _onTbodyKeydown : function(e, oSelf) {
7849     var sMode = oSelf.get("selectionMode");
7850
7851     if(sMode == "standard") {
7852         oSelf._handleStandardSelectionByKey(e);
7853     }
7854     else if(sMode == "single") {
7855         oSelf._handleSingleSelectionByKey(e);
7856     }
7857     else if(sMode == "cellblock") {
7858         oSelf._handleCellBlockSelectionByKey(e);
7859     }
7860     else if(sMode == "cellrange") {
7861         oSelf._handleCellRangeSelectionByKey(e);
7862     }
7863     else if(sMode == "singlecell") {
7864         oSelf._handleSingleCellSelectionByKey(e);
7865     }
7866     
7867     if(oSelf._oCellEditor) {
7868         if(oSelf._oCellEditor.fireEvent) {
7869             oSelf._oCellEditor.fireEvent("blurEvent", {editor: oSelf._oCellEditor});
7870         }
7871         else if(oSelf._oCellEditor.isActive) {
7872             oSelf.fireEvent("editorBlurEvent", {editor:oSelf._oCellEditor});
7873         }
7874     }
7875
7876     var elTarget = Ev.getTarget(e);
7877     var elTag = elTarget.nodeName && elTarget.nodeName.toLowerCase();
7878     var bKeepBubbling = true;
7879     while(elTarget && (elTag != "table")) {
7880         switch(elTag) {
7881             case "body":
7882                 return;
7883             case "tbody":
7884                 bKeepBubbling = oSelf.fireEvent("tbodyKeyEvent",{target:elTarget,event:e});
7885                 break;
7886             default:
7887                 break;
7888         }
7889         if(bKeepBubbling === false) {
7890             return;
7891         }
7892         else {
7893             elTarget = elTarget.parentNode;
7894             if(elTarget) {
7895                 elTag = elTarget.nodeName.toLowerCase();
7896             }
7897         }
7898     }
7899     oSelf.fireEvent("tableKeyEvent",{target:(elTarget || oSelf._elContainer),event:e});
7900 },
7901
7902 /**
7903  * Handles click events on the THEAD element.
7904  *
7905  * @method _onTheadClick
7906  * @param e {HTMLEvent} The click event.
7907  * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
7908  * @private
7909  */
7910 _onTheadClick : function(e, oSelf) {
7911     // This blurs the CellEditor
7912     if(oSelf._oCellEditor) {
7913         if(oSelf._oCellEditor.fireEvent) {
7914             oSelf._oCellEditor.fireEvent("blurEvent", {editor: oSelf._oCellEditor});
7915         }
7916         // Backward compatibility
7917         else if(oSelf._oCellEditor.isActive) {
7918             oSelf.fireEvent("editorBlurEvent", {editor:oSelf._oCellEditor});
7919         }
7920     }
7921
7922     var elTarget = Ev.getTarget(e),
7923         elTag = elTarget.nodeName && elTarget.nodeName.toLowerCase(),
7924         bKeepBubbling = true;
7925     while(elTarget && (elTag != "table")) {
7926         switch(elTag) {
7927             case "body":
7928                 return;
7929             case "input":
7930                 var sType = elTarget.type.toLowerCase();
7931                 if(sType == "checkbox") {
7932                     bKeepBubbling = oSelf.fireEvent("theadCheckboxClickEvent",{target:elTarget,event:e});
7933                 }
7934                 else if(sType == "radio") {
7935                     bKeepBubbling = oSelf.fireEvent("theadRadioClickEvent",{target:elTarget,event:e});
7936                 }
7937                 else if((sType == "button") || (sType == "image") || (sType == "submit") || (sType == "reset")) {
7938                     if(!elTarget.disabled) {
7939                         bKeepBubbling = oSelf.fireEvent("theadButtonClickEvent",{target:elTarget,event:e});
7940                     }
7941                     else {
7942                         bKeepBubbling = false;
7943                     }
7944                 }
7945                 else if (elTarget.disabled){
7946                     bKeepBubbling = false;
7947                 }
7948                 break;
7949             case "a":
7950                 bKeepBubbling = oSelf.fireEvent("theadLinkClickEvent",{target:elTarget,event:e});
7951                 break;
7952             case "button":
7953                 if(!elTarget.disabled) {
7954                     bKeepBubbling = oSelf.fireEvent("theadButtonClickEvent",{target:elTarget,event:e});
7955                 }
7956                 else {
7957                     bKeepBubbling = false;
7958                 }
7959                 break;
7960             case "span":
7961                 if(Dom.hasClass(elTarget, DT.CLASS_LABEL)) {
7962                     bKeepBubbling = oSelf.fireEvent("theadLabelClickEvent",{target:elTarget,event:e});
7963                     // Backward compatibility
7964                     bKeepBubbling = oSelf.fireEvent("headerLabelClickEvent",{target:elTarget,event:e});
7965                 }
7966                 break;
7967             case "th":
7968                 bKeepBubbling = oSelf.fireEvent("theadCellClickEvent",{target:elTarget,event:e});
7969                 // Backward compatibility
7970                 bKeepBubbling = oSelf.fireEvent("headerCellClickEvent",{target:elTarget,event:e});
7971                 break;
7972             case "tr":
7973                 bKeepBubbling = oSelf.fireEvent("theadRowClickEvent",{target:elTarget,event:e});
7974                 // Backward compatibility
7975                 bKeepBubbling = oSelf.fireEvent("headerRowClickEvent",{target:elTarget,event:e});
7976                 break;
7977             default:
7978                 break;
7979         }
7980         if(bKeepBubbling === false) {
7981             return;
7982         }
7983         else {
7984             elTarget = elTarget.parentNode;
7985             if(elTarget) {
7986                 elTag = elTarget.nodeName.toLowerCase();
7987             }
7988         }
7989     }
7990     oSelf.fireEvent("tableClickEvent",{target:(elTarget || oSelf._elContainer),event:e});
7991 },
7992
7993 /**
7994  * Handles click events on the primary TBODY element.
7995  *
7996  * @method _onTbodyClick
7997  * @param e {HTMLEvent} The click event.
7998  * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
7999  * @private
8000  */
8001 _onTbodyClick : function(e, oSelf) {
8002     // This blurs the CellEditor
8003     if(oSelf._oCellEditor) {
8004         if(oSelf._oCellEditor.fireEvent) {
8005             oSelf._oCellEditor.fireEvent("blurEvent", {editor: oSelf._oCellEditor});
8006         }
8007         else if(oSelf._oCellEditor.isActive) {
8008             oSelf.fireEvent("editorBlurEvent", {editor:oSelf._oCellEditor});
8009         }
8010     }
8011
8012     // Fire Custom Events
8013     var elTarget = Ev.getTarget(e),
8014         elTag = elTarget.nodeName && elTarget.nodeName.toLowerCase(),
8015         bKeepBubbling = true;
8016     while(elTarget && (elTag != "table")) {
8017         switch(elTag) {
8018             case "body":
8019                 return;
8020             case "input":
8021                 var sType = elTarget.type.toLowerCase();
8022                 if(sType == "checkbox") {
8023                     bKeepBubbling = oSelf.fireEvent("checkboxClickEvent",{target:elTarget,event:e});
8024                 }
8025                 else if(sType == "radio") {
8026                     bKeepBubbling = oSelf.fireEvent("radioClickEvent",{target:elTarget,event:e});
8027                 }
8028                 else if((sType == "button") || (sType == "image") || (sType == "submit") || (sType == "reset")) {
8029                     if(!elTarget.disabled) {
8030                         bKeepBubbling = oSelf.fireEvent("buttonClickEvent",{target:elTarget,event:e});
8031                     }
8032                     else {
8033                         bKeepBubbling = false;
8034                     }
8035                 }
8036                 else if (elTarget.disabled){
8037                     bKeepBubbling = false;
8038                 }
8039                 break;
8040             case "a":
8041                 bKeepBubbling = oSelf.fireEvent("linkClickEvent",{target:elTarget,event:e});
8042                 break;
8043             case "button":
8044                 if(!elTarget.disabled) {
8045                     bKeepBubbling = oSelf.fireEvent("buttonClickEvent",{target:elTarget,event:e});
8046                 }
8047                 else {
8048                     bKeepBubbling = false;
8049                 }
8050                 break;
8051             case "td":
8052                 bKeepBubbling = oSelf.fireEvent("cellClickEvent",{target:elTarget,event:e});
8053                 break;
8054             case "tr":
8055                 bKeepBubbling = oSelf.fireEvent("rowClickEvent",{target:elTarget,event:e});
8056                 break;
8057             default:
8058                 break;
8059         }
8060         if(bKeepBubbling === false) {
8061             return;
8062         }
8063         else {
8064             elTarget = elTarget.parentNode;
8065             if(elTarget) {
8066                 elTag = elTarget.nodeName.toLowerCase();
8067             }
8068         }
8069     }
8070     oSelf.fireEvent("tableClickEvent",{target:(elTarget || oSelf._elContainer),event:e});
8071 },
8072
8073 /**
8074  * Handles change events on SELECT elements within DataTable.
8075  *
8076  * @method _onDropdownChange
8077  * @param e {HTMLEvent} The change event.
8078  * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
8079  * @private
8080  */
8081 _onDropdownChange : function(e, oSelf) {
8082     var elTarget = Ev.getTarget(e);
8083     oSelf.fireEvent("dropdownChangeEvent", {event:e, target:elTarget});
8084 },
8085
8086
8087
8088
8089
8090
8091
8092
8093
8094
8095
8096
8097
8098
8099
8100
8101
8102
8103
8104
8105
8106
8107
8108
8109
8110
8111
8112
8113
8114
8115
8116
8117 /////////////////////////////////////////////////////////////////////////////
8118 //
8119 // Public member variables
8120 //
8121 /////////////////////////////////////////////////////////////////////////////
8122 /**
8123  * Returns object literal of initial configs.
8124  *
8125  * @property configs
8126  * @type Object
8127  * @default {} 
8128  */
8129 configs: null,
8130
8131
8132 /////////////////////////////////////////////////////////////////////////////
8133 //
8134 // Public methods
8135 //
8136 /////////////////////////////////////////////////////////////////////////////
8137
8138 /**
8139  * Returns unique id assigned to instance, which is a useful prefix for
8140  * generating unique DOM ID strings.
8141  *
8142  * @method getId
8143  * @return {String} Unique ID of the DataSource instance.
8144  */
8145 getId : function() {
8146     return this._sId;
8147 },
8148
8149 /**
8150  * DataSource instance name, for logging.
8151  *
8152  * @method toString
8153  * @return {String} Unique name of the DataSource instance.
8154  */
8155
8156 toString : function() {
8157     return "DataTable instance " + this._sId;
8158 },
8159
8160 /**
8161  * Returns the DataTable instance's DataSource instance.
8162  *
8163  * @method getDataSource
8164  * @return {YAHOO.util.DataSource} DataSource instance.
8165  */
8166 getDataSource : function() {
8167     return this._oDataSource;
8168 },
8169
8170 /**
8171  * Returns the DataTable instance's ColumnSet instance.
8172  *
8173  * @method getColumnSet
8174  * @return {YAHOO.widget.ColumnSet} ColumnSet instance.
8175  */
8176 getColumnSet : function() {
8177     return this._oColumnSet;
8178 },
8179
8180 /**
8181  * Returns the DataTable instance's RecordSet instance.
8182  *
8183  * @method getRecordSet
8184  * @return {YAHOO.widget.RecordSet} RecordSet instance.
8185  */
8186 getRecordSet : function() {
8187     return this._oRecordSet;
8188 },
8189
8190 /**
8191  * Returns on object literal representing the DataTable instance's current
8192  * state with the following properties:
8193  * <dl>
8194  * <dt>pagination</dt>
8195  * <dd>Instance of YAHOO.widget.Paginator</dd>
8196  *
8197  * <dt>sortedBy</dt>
8198  * <dd>
8199  *     <dl>
8200  *         <dt>sortedBy.key</dt>
8201  *         <dd>{String} Key of sorted Column</dd>
8202  *         <dt>sortedBy.dir</dt>
8203  *         <dd>{String} Initial sort direction, either YAHOO.widget.DataTable.CLASS_ASC or YAHOO.widget.DataTable.CLASS_DESC</dd>
8204  *     </dl>
8205  * </dd>
8206  *
8207  * <dt>selectedRows</dt>
8208  * <dd>Array of selected rows by Record ID.</dd>
8209  *
8210  * <dt>selectedCells</dt>
8211  * <dd>Selected cells as an array of object literals:
8212  *     {recordId:sRecordId, columnKey:sColumnKey}</dd>
8213  * </dl>
8214  *  
8215  * @method getState
8216  * @return {Object} DataTable instance state object literal values.
8217  */
8218 getState : function() {
8219     return {
8220         totalRecords: this.get('paginator') ? this.get('paginator').get("totalRecords") : this._oRecordSet.getLength(),
8221         pagination: this.get("paginator") ? this.get("paginator").getState() : null,
8222         sortedBy: this.get("sortedBy"),
8223         selectedRows: this.getSelectedRows(),
8224         selectedCells: this.getSelectedCells()
8225     };
8226 },
8227
8228
8229
8230
8231
8232
8233
8234
8235
8236
8237
8238
8239
8240
8241
8242
8243
8244
8245
8246
8247
8248
8249
8250
8251
8252
8253
8254
8255
8256
8257
8258
8259
8260
8261
8262
8263
8264
8265
8266
8267
8268
8269
8270 // DOM ACCESSORS
8271
8272 /**
8273  * Returns DOM reference to the DataTable's container element.
8274  *
8275  * @method getContainerEl
8276  * @return {HTMLElement} Reference to DIV element.
8277  */
8278 getContainerEl : function() {
8279     return this._elContainer;
8280 },
8281
8282 /**
8283  * Returns DOM reference to the DataTable's TABLE element.
8284  *
8285  * @method getTableEl
8286  * @return {HTMLElement} Reference to TABLE element.
8287  */
8288 getTableEl : function() {
8289     return this._elTable;
8290 },
8291
8292 /**
8293  * Returns DOM reference to the DataTable's THEAD element.
8294  *
8295  * @method getTheadEl
8296  * @return {HTMLElement} Reference to THEAD element.
8297  */
8298 getTheadEl : function() {
8299     return this._elThead;
8300 },
8301
8302 /**
8303  * Returns DOM reference to the DataTable's primary TBODY element.
8304  *
8305  * @method getTbodyEl
8306  * @return {HTMLElement} Reference to TBODY element.
8307  */
8308 getTbodyEl : function() {
8309     return this._elTbody;
8310 },
8311
8312 /**
8313  * Returns DOM reference to the DataTable's secondary TBODY element that is
8314  * used to display messages.
8315  *
8316  * @method getMsgTbodyEl
8317  * @return {HTMLElement} Reference to TBODY element.
8318  */
8319 getMsgTbodyEl : function() {
8320     return this._elMsgTbody;
8321 },
8322
8323 /**
8324  * Returns DOM reference to the TD element within the secondary TBODY that is
8325  * used to display messages.
8326  *
8327  * @method getMsgTdEl
8328  * @return {HTMLElement} Reference to TD element.
8329  */
8330 getMsgTdEl : function() {
8331     return this._elMsgTd;
8332 },
8333
8334 /**
8335  * Returns the corresponding TR reference for a given DOM element, ID string or
8336  * page row index. If the given identifier is a child of a TR element,
8337  * then DOM tree is traversed until a parent TR element is returned, otherwise
8338  * null. Returns null if the row is not considered a primary row (i.e., row
8339  * extensions).
8340  *
8341  * @method getTrEl
8342  * @param row {HTMLElement | String | Number | YAHOO.widget.Record} Which row to
8343  * get: by element reference, ID string, page row index, or Record.
8344  * @return {HTMLElement} Reference to TR element, or null.
8345  */
8346 getTrEl : function(row) {
8347     // By Record
8348     if(row instanceof YAHOO.widget.Record) {
8349         return document.getElementById(row.getId());
8350     }
8351     // By page row index
8352     else if(lang.isNumber(row)) {
8353         var dataRows = Dom.getElementsByClassName(DT.CLASS_REC, "tr", this._elTbody);
8354         return dataRows && dataRows[row] ? dataRows[row] : null;
8355     }
8356     // By ID string or element reference
8357     else if(row) {
8358         var elRow = (lang.isString(row)) ? document.getElementById(row) : row;
8359
8360         // Validate HTML element
8361         if(elRow && elRow.ownerDocument == document) {
8362             // Validate TR element
8363             if(elRow.nodeName.toLowerCase() != "tr") {
8364                 // Traverse up the DOM to find the corresponding TR element
8365                 elRow = Dom.getAncestorByTagName(elRow,"tr");
8366             }
8367
8368             return elRow;
8369         }
8370     }
8371
8372     return null;
8373 },
8374
8375 /**
8376  * Returns DOM reference to the first primary TR element in the DataTable page, or null.
8377  *
8378  * @method getFirstTrEl
8379  * @return {HTMLElement} Reference to TR element.
8380  */
8381 getFirstTrEl : function() {
8382     var allRows = this._elTbody.rows,
8383         i=0;
8384     while(allRows[i]) {
8385         if(this.getRecord(allRows[i])) {
8386             return allRows[i];
8387         }
8388         i++;
8389     }
8390     return null;
8391
8392 },
8393
8394 /**
8395  * Returns DOM reference to the last primary TR element in the DataTable page, or null.
8396  *
8397  * @method getLastTrEl
8398  * @return {HTMLElement} Reference to last TR element.
8399  */
8400 getLastTrEl : function() {
8401     var allRows = this._elTbody.rows,
8402         i=allRows.length-1;
8403     while(i>-1) {
8404         if(this.getRecord(allRows[i])) {
8405             return allRows[i];
8406         }
8407         i--;
8408     }
8409     return null;
8410 },
8411
8412 /**
8413  * Returns DOM reference to the next TR element from the given primary TR element, or null.
8414  *
8415  * @method getNextTrEl
8416  * @param row {HTMLElement | String | Number | YAHOO.widget.Record} Element
8417  * reference, ID string, page row index, or Record from which to get next TR element.
8418  * @param forcePrimary {Boolean} (optional) If true, will only return TR elements
8419  * that correspond to Records. Non-primary rows (such as row expansions)
8420  * will be skipped.
8421  * @return {HTMLElement} Reference to next TR element.
8422  */
8423 getNextTrEl : function(row, forcePrimary) {
8424     var nThisTrIndex = this.getTrIndex(row);
8425     if(nThisTrIndex !== null) {
8426         var allRows = this._elTbody.rows;
8427         if(forcePrimary) {
8428             while(nThisTrIndex < allRows.length-1) {
8429                 row = allRows[nThisTrIndex+1];
8430                 if(this.getRecord(row)) {
8431                     return row;
8432                 }
8433                 nThisTrIndex++;
8434             }
8435         }
8436         else {
8437             if(nThisTrIndex < allRows.length-1) {
8438                 return allRows[nThisTrIndex+1];
8439             }
8440         }
8441     }
8442
8443     return null;
8444 },
8445
8446 /**
8447  * Returns DOM reference to the previous TR element from the given primary TR element, or null.
8448  *
8449  * @method getPreviousTrEl
8450  * @param row {HTMLElement | String | Number | YAHOO.widget.Record} Element
8451  * reference, ID string, page row index, or Record from which to get previous TR element.
8452  * @param forcePrimary {Boolean} (optional) If true, will only return TR elements
8453  * from rothat correspond to Records. Non-primary rows (such as row expansions)
8454  * will be skipped.
8455  * @return {HTMLElement} Reference to previous TR element.
8456  */
8457 getPreviousTrEl : function(row, forcePrimary) {
8458     var nThisTrIndex = this.getTrIndex(row);
8459     if(nThisTrIndex !== null) {
8460         var allRows = this._elTbody.rows;
8461
8462         if(forcePrimary) {
8463             while(nThisTrIndex > 0) {
8464                 row = allRows[nThisTrIndex-1];
8465                 if(this.getRecord(row)) {
8466                     return row;
8467                 }
8468                 nThisTrIndex--;
8469             }
8470         }
8471         else {
8472             if(nThisTrIndex > 0) {
8473                 return allRows[nThisTrIndex-1];
8474             }
8475         }
8476     }
8477
8478     return null;
8479 },
8480
8481
8482 /**
8483  * Workaround for IE bug where hidden or not-in-dom elements cause cellIndex
8484  * value to be incorrect.
8485  *
8486  * @method getCellIndex
8487  * @param cell {HTMLElement | Object} TD element or child of a TD element, or
8488  * object literal of syntax {record:oRecord, column:oColumn}.
8489  * @return {Number} TD.cellIndex value.
8490  */
8491 getCellIndex : function(cell) {
8492     cell = this.getTdEl(cell);
8493     if(cell) {
8494         if(ua.ie > 0) {
8495             var i=0,
8496                 tr = cell.parentNode,
8497                 allCells = tr.childNodes,
8498                 len = allCells.length;
8499             for(; i<len; i++) {
8500                 if(allCells[i] == cell) {
8501                     return i;
8502                 }
8503             }
8504         }
8505         else {
8506             return cell.cellIndex;
8507         }
8508     }
8509 },
8510
8511 /**
8512  * Returns DOM reference to a TD liner element.
8513  *
8514  * @method getTdLinerEl
8515  * @param cell {HTMLElement | Object} TD element or child of a TD element, or
8516  * object literal of syntax {record:oRecord, column:oColumn}.
8517  * @return {HTMLElement} Reference to TD liner element.
8518  */
8519 getTdLinerEl : function(cell) {
8520     var elCell = this.getTdEl(cell);
8521     return elCell.firstChild || null;
8522 },
8523
8524 /**
8525  * Returns DOM reference to a TD element. Returns null if the row is not
8526  * considered a primary row (i.e., row extensions).
8527  *
8528  * @method getTdEl
8529  * @param cell {HTMLElement | String | Object} TD element or child of a TD element, or
8530  * object literal of syntax {record:oRecord, column:oColumn}.
8531  * @return {HTMLElement} Reference to TD element.
8532  */
8533 getTdEl : function(cell) {
8534     var elCell;
8535     var el = Dom.get(cell);
8536
8537     // Validate HTML element
8538     if(el && (el.ownerDocument == document)) {
8539         // Validate TD element
8540         if(el.nodeName.toLowerCase() != "td") {
8541             // Traverse up the DOM to find the corresponding TR element
8542             elCell = Dom.getAncestorByTagName(el, "td");
8543         }
8544         else {
8545             elCell = el;
8546         }
8547         
8548         // Make sure the TD is in this TBODY or is not in DOM
8549         // Bug 2527707 and bug 2263558
8550         if(elCell && ((elCell.parentNode.parentNode == this._elTbody) ||
8551             (elCell.parentNode.parentNode === null) ||
8552             (elCell.parentNode.parentNode.nodeType === 11))) {
8553             // Now we can return the TD element
8554             return elCell;
8555         }
8556     }
8557     else if(cell) {
8558         var oRecord, nColKeyIndex;
8559
8560         if(lang.isString(cell.columnKey) && lang.isString(cell.recordId)) {
8561             oRecord = this.getRecord(cell.recordId);
8562             var oColumn = this.getColumn(cell.columnKey);
8563             if(oColumn) {
8564                 nColKeyIndex = oColumn.getKeyIndex();
8565             }
8566
8567         }
8568         if(cell.record && cell.column && cell.column.getKeyIndex) {
8569             oRecord = cell.record;
8570             nColKeyIndex = cell.column.getKeyIndex();
8571         }
8572         var elRow = this.getTrEl(oRecord);
8573         if((nColKeyIndex !== null) && elRow && elRow.cells && elRow.cells.length > 0) {
8574             return elRow.cells[nColKeyIndex] || null;
8575         }
8576     }
8577
8578     return null;
8579 },
8580
8581 /**
8582  * Returns DOM reference to the first primary TD element in the DataTable page (by default),
8583  * the first TD element of the optionally given row, or null.
8584  *
8585  * @method getFirstTdEl
8586  * @param row {HTMLElement} (optional) row from which to get first TD
8587  * @return {HTMLElement} Reference to TD element.
8588  */
8589 getFirstTdEl : function(row) {
8590     var elRow = lang.isValue(row) ? this.getTrEl(row) : this.getFirstTrEl();
8591     if(elRow) {
8592         if(elRow.cells && elRow.cells.length > 0) {
8593             return elRow.cells[0];
8594         }
8595         else if(elRow.childNodes && elRow.childNodes.length > 0) {
8596             return elRow.childNodes[0];
8597         }
8598     }
8599     return null;
8600 },
8601
8602 /**
8603  * Returns DOM reference to the last primary TD element in the DataTable page (by default),
8604  * the first TD element of the optionally given row, or null.
8605  *
8606  * @method getLastTdEl
8607  * @param row {HTMLElement} (optional) row from which to get first TD
8608  * @return {HTMLElement} Reference to last TD element.
8609  */
8610 getLastTdEl : function(row) {
8611     var elRow = lang.isValue(row) ? this.getTrEl(row) : this.getLastTrEl();
8612     if(elRow) {
8613         if(elRow.cells && elRow.cells.length > 0) {
8614             return elRow.cells[elRow.cells.length-1];
8615         }
8616         else if(elRow.childNodes && elRow.childNodes.length > 0) {
8617             return elRow.childNodes[elRow.childNodes.length-1];
8618         }
8619     }
8620     return null;
8621 },
8622
8623 /**
8624  * Returns DOM reference to the next TD element from the given cell, or null.
8625  *
8626  * @method getNextTdEl
8627  * @param cell {HTMLElement | String | Object} DOM element reference or string ID, or
8628  * object literal of syntax {record:oRecord, column:oColumn} from which to get next TD element.
8629  * @return {HTMLElement} Reference to next TD element, or null.
8630  */
8631 getNextTdEl : function(cell) {
8632     var elCell = this.getTdEl(cell);
8633     if(elCell) {
8634         var nThisTdIndex = this.getCellIndex(elCell);
8635         var elRow = this.getTrEl(elCell);
8636         if(elRow.cells && (elRow.cells.length) > 0 && (nThisTdIndex < elRow.cells.length-1)) {
8637             return elRow.cells[nThisTdIndex+1];
8638         }
8639         else if(elRow.childNodes && (elRow.childNodes.length) > 0 && (nThisTdIndex < elRow.childNodes.length-1)) {
8640             return elRow.childNodes[nThisTdIndex+1];
8641         }
8642         else {
8643             var elNextRow = this.getNextTrEl(elRow);
8644             if(elNextRow) {
8645                 return elNextRow.cells[0];
8646             }
8647         }
8648     }
8649     return null;
8650 },
8651
8652 /**
8653  * Returns DOM reference to the previous TD element from the given cell, or null.
8654  *
8655  * @method getPreviousTdEl
8656  * @param cell {HTMLElement | String | Object} DOM element reference or string ID, or
8657  * object literal of syntax {record:oRecord, column:oColumn} from which to get previous TD element.
8658  * @return {HTMLElement} Reference to previous TD element, or null.
8659  */
8660 getPreviousTdEl : function(cell) {
8661     var elCell = this.getTdEl(cell);
8662     if(elCell) {
8663         var nThisTdIndex = this.getCellIndex(elCell);
8664         var elRow = this.getTrEl(elCell);
8665         if(nThisTdIndex > 0) {
8666             if(elRow.cells && elRow.cells.length > 0) {
8667                 return elRow.cells[nThisTdIndex-1];
8668             }
8669             else if(elRow.childNodes && elRow.childNodes.length > 0) {
8670                 return elRow.childNodes[nThisTdIndex-1];
8671             }
8672         }
8673         else {
8674             var elPreviousRow = this.getPreviousTrEl(elRow);
8675             if(elPreviousRow) {
8676                 return this.getLastTdEl(elPreviousRow);
8677             }
8678         }
8679     }
8680     return null;
8681 },
8682
8683 /**
8684  * Returns DOM reference to the above TD element from the given cell, or null.
8685  *
8686  * @method getAboveTdEl
8687  * @param cell {HTMLElement | String | Object} DOM element reference or string ID, or
8688  * object literal of syntax {record:oRecord, column:oColumn} from which to get next TD element.
8689  * @param forcePrimary {Boolean} (optional) If true, will only return TD elements
8690  * from rows that correspond to Records. Non-primary rows (such as row expansions)
8691  * will be skipped.
8692  * @return {HTMLElement} Reference to above TD element, or null.
8693  */
8694 getAboveTdEl : function(cell, forcePrimary) {
8695     var elCell = this.getTdEl(cell);
8696     if(elCell) {
8697         var elPreviousRow = this.getPreviousTrEl(elCell, forcePrimary);
8698         if(elPreviousRow ) {
8699             var cellIndex = this.getCellIndex(elCell);
8700             if(elPreviousRow.cells && elPreviousRow.cells.length > 0) {
8701                 return elPreviousRow.cells[cellIndex] ? elPreviousRow.cells[cellIndex] : null;
8702             }
8703             else if(elPreviousRow.childNodes && elPreviousRow.childNodes.length > 0) {
8704                 return elPreviousRow.childNodes[cellIndex] ? elPreviousRow.childNodes[cellIndex] : null;
8705             }
8706         }
8707     }
8708     return null;
8709 },
8710
8711 /**
8712  * Returns DOM reference to the below TD element from the given cell, or null.
8713  *
8714  * @method getBelowTdEl
8715  * @param cell {HTMLElement | String | Object} DOM element reference or string ID, or
8716  * object literal of syntax {record:oRecord, column:oColumn} from which to get previous TD element.
8717  * @param forcePrimary {Boolean} (optional) If true, will only return TD elements
8718  * from rows that correspond to Records. Non-primary rows (such as row expansions)
8719  * will be skipped.
8720  * @return {HTMLElement} Reference to below TD element, or null.
8721  */
8722 getBelowTdEl : function(cell, forcePrimary) {
8723     var elCell = this.getTdEl(cell);
8724     if(elCell) {
8725         var elNextRow = this.getNextTrEl(elCell, forcePrimary);
8726         if(elNextRow) {
8727             var cellIndex = this.getCellIndex(elCell);
8728             if(elNextRow.cells && elNextRow.cells.length > 0) {
8729                 return elNextRow.cells[cellIndex] ? elNextRow.cells[cellIndex] : null;
8730             }
8731             else if(elNextRow.childNodes && elNextRow.childNodes.length > 0) {
8732                 return elNextRow.childNodes[cellIndex] ? elNextRow.childNodes[cellIndex] : null;
8733             }
8734         }
8735     }
8736     return null;
8737 },
8738
8739 /**
8740  * Returns DOM reference to a TH liner element. Needed to normalize for resizeable 
8741  * Columns, which have an additional resizer liner DIV element between the TH
8742  * element and the liner DIV element. 
8743  *
8744  * @method getThLinerEl
8745  * @param theadCell {YAHOO.widget.Column | HTMLElement | String} Column instance,
8746  * DOM element reference, or string ID.
8747  * @return {HTMLElement} Reference to TH liner element.
8748  */
8749 getThLinerEl : function(theadCell) {
8750     var oColumn = this.getColumn(theadCell);
8751     return (oColumn) ? oColumn.getThLinerEl() : null;
8752 },
8753
8754 /**
8755  * Returns DOM reference to a TH element.
8756  *
8757  * @method getThEl
8758  * @param theadCell {YAHOO.widget.Column | HTMLElement | String} Column instance,
8759  * DOM element reference, or string ID.
8760  * @return {HTMLElement} Reference to TH element.
8761  */
8762 getThEl : function(theadCell) {
8763     var elTh;
8764
8765     // Validate Column instance
8766     if(theadCell instanceof YAHOO.widget.Column) {
8767         var oColumn = theadCell;
8768         elTh = oColumn.getThEl();
8769         if(elTh) {
8770             return elTh;
8771         }
8772     }
8773     // Validate HTML element
8774     else {
8775         var el = Dom.get(theadCell);
8776
8777         if(el && (el.ownerDocument == document)) {
8778             // Validate TH element
8779             if(el.nodeName.toLowerCase() != "th") {
8780                 // Traverse up the DOM to find the corresponding TR element
8781                 elTh = Dom.getAncestorByTagName(el,"th");
8782             }
8783             else {
8784                 elTh = el;
8785             }
8786
8787             return elTh;
8788         }
8789     }
8790
8791     return null;
8792 },
8793
8794 /**
8795  * Returns the page row index of given primary row. Returns null if the row is not on the
8796  * current DataTable page, or if row is not considered a primary row (i.e., row
8797  * extensions).
8798  *
8799  * @method getTrIndex
8800  * @param row {HTMLElement | String | YAHOO.widget.Record | Number} DOM or ID
8801  * string reference to an element within the DataTable page, a Record instance,
8802  * or a Record's RecordSet index.
8803  * @return {Number} Page row index, or null if data row does not exist or is not on current page.
8804  */
8805 getTrIndex : function(row) {
8806     var record = this.getRecord(row),
8807         index = this.getRecordIndex(record),
8808         tr;
8809     if(record) {
8810         tr = this.getTrEl(record);
8811         if(tr) {
8812             return tr.sectionRowIndex;
8813         }
8814         else {
8815             var oPaginator = this.get("paginator");
8816             if(oPaginator) {
8817                 return oPaginator.get('recordOffset') + index;
8818             }
8819             else {
8820                 return index;
8821             }
8822         }
8823     }
8824     return null;
8825 },
8826
8827
8828
8829
8830
8831
8832
8833
8834
8835
8836
8837
8838
8839
8840
8841
8842
8843
8844
8845
8846
8847
8848
8849
8850
8851
8852
8853
8854
8855
8856
8857
8858
8859
8860
8861
8862
8863
8864
8865
8866
8867
8868
8869
8870
8871
8872 // TABLE FUNCTIONS
8873
8874 /**
8875  * Loads new data. Convenience method that calls DataSource's sendRequest()
8876  * method under the hood.
8877  *
8878  * @method load
8879  * @param oConfig {object} Optional configuration parameters:
8880  *
8881  * <dl>
8882  * <dt>request</dt><dd>Pass in a new request, or initialRequest is used.</dd>
8883  * <dt>callback</dt><dd>Pass in DataSource sendRequest() callback object, or the following is used:
8884  *    <dl>
8885  *      <dt>success</dt><dd>datatable.onDataReturnInitializeTable</dd>
8886  *      <dt>failure</dt><dd>datatable.onDataReturnInitializeTable</dd>
8887  *      <dt>scope</dt><dd>datatable</dd>
8888  *      <dt>argument</dt><dd>datatable.getState()</dd>
8889  *    </dl>
8890  * </dd>
8891  * <dt>datasource</dt><dd>Pass in a new DataSource instance to override the current DataSource for this transaction.</dd>
8892  * </dl>
8893  */
8894 load : function(oConfig) {
8895     oConfig = oConfig || {};
8896
8897     (oConfig.datasource || this._oDataSource).sendRequest(oConfig.request || this.get("initialRequest"), oConfig.callback || {
8898         success: this.onDataReturnInitializeTable,
8899         failure: this.onDataReturnInitializeTable,
8900         scope: this,
8901         argument: this.getState()
8902     });
8903 },
8904
8905 /**
8906  * Resets a RecordSet with the given data and populates the page view
8907  * with the new data. Any previous data, and selection and sort states are
8908  * cleared. New data should be added as a separate step. 
8909  *
8910  * @method initializeTable
8911  */
8912 initializeTable : function() {
8913     // Reset init flag
8914     this._bInit = true;
8915     
8916     // Clear the RecordSet
8917     this._oRecordSet.reset();
8918
8919     // Clear the Paginator's totalRecords if paginating
8920     var pag = this.get('paginator');
8921     if (pag) {
8922         pag.set('totalRecords',0);
8923     }
8924
8925     // Clear selections
8926     this._unselectAllTrEls();
8927     this._unselectAllTdEls();
8928     this._aSelections = null;
8929     this._oAnchorRecord = null;
8930     this._oAnchorCell = null;
8931     
8932     // Clear sort
8933     this.set("sortedBy", null);
8934 },
8935
8936 /**
8937  * Internal wrapper calls run() on render Chain instance.
8938  *
8939  * @method _runRenderChain
8940  * @private 
8941  */
8942 _runRenderChain : function() {
8943     this._oChainRender.run();
8944 },
8945
8946 /**
8947  * Returns array of Records for current view. For example, if paginated, it
8948  * returns the subset of Records for current page.
8949  *
8950  * @method _getViewRecords
8951  * @protected
8952  * @return {Array} Array of Records to display in current view.
8953  */
8954 _getViewRecords : function() {
8955     // Paginator is enabled, show a subset of Records
8956     var oPaginator = this.get('paginator');
8957     if(oPaginator) {
8958         return this._oRecordSet.getRecords(
8959                         oPaginator.getStartIndex(),
8960                         oPaginator.getRowsPerPage());
8961     }
8962     // Not paginated, show all records
8963     else {
8964         return this._oRecordSet.getRecords();
8965     }
8966
8967 },
8968
8969 /**
8970  * Renders the view with existing Records from the RecordSet while
8971  * maintaining sort, pagination, and selection states. For performance, reuses
8972  * existing DOM elements when possible while deleting extraneous elements.
8973  *
8974  * @method render
8975  */
8976 render : function() {
8977 //YAHOO.example.Performance.trialStart = new Date();
8978
8979     this._oChainRender.stop();
8980
8981     this.fireEvent("beforeRenderEvent");
8982
8983     var i, j, k, len,
8984         allRecords = this._getViewRecords();
8985
8986
8987     // From the top, update in-place existing rows, so as to reuse DOM elements
8988     var elTbody = this._elTbody,
8989         loopN = this.get("renderLoopSize"),
8990         nRecordsLength = allRecords.length;
8991     
8992     // Table has rows
8993     if(nRecordsLength > 0) {                
8994         elTbody.style.display = "none";
8995         while(elTbody.lastChild) {
8996             elTbody.removeChild(elTbody.lastChild);
8997         }
8998         elTbody.style.display = "";
8999
9000         // Set up the loop Chain to render rows
9001         this._oChainRender.add({
9002             method: function(oArg) {
9003                 if((this instanceof DT) && this._sId) {
9004                     var i = oArg.nCurrentRecord,
9005                         endRecordIndex = ((oArg.nCurrentRecord+oArg.nLoopLength) > nRecordsLength) ?
9006                                 nRecordsLength : (oArg.nCurrentRecord+oArg.nLoopLength),
9007                         elRow, nextSibling;
9008
9009                     elTbody.style.display = "none";
9010                     
9011                     for(; i<endRecordIndex; i++) {
9012                         elRow = Dom.get(allRecords[i].getId());
9013                         elRow = elRow || this._addTrEl(allRecords[i]);
9014                         nextSibling = elTbody.childNodes[i] || null;
9015                         elTbody.insertBefore(elRow, nextSibling);
9016                     }
9017                     elTbody.style.display = "";
9018                     
9019                     // Set up for the next loop
9020                     oArg.nCurrentRecord = i;
9021                 }
9022             },
9023             scope: this,
9024             iterations: (loopN > 0) ? Math.ceil(nRecordsLength/loopN) : 1,
9025             argument: {
9026                 nCurrentRecord: 0,//nRecordsLength-1,  // Start at first Record
9027                 nLoopLength: (loopN > 0) ? loopN : nRecordsLength
9028             },
9029             timeout: (loopN > 0) ? 0 : -1
9030         });
9031         
9032         // Post-render tasks
9033         this._oChainRender.add({
9034             method: function(oArg) {
9035                 if((this instanceof DT) && this._sId) {
9036                     while(elTbody.rows.length > nRecordsLength) {
9037                         elTbody.removeChild(elTbody.lastChild);
9038                     }
9039                     this._setFirstRow();
9040                     this._setLastRow();
9041                     this._setRowStripes();
9042                     this._setSelections();
9043                 }
9044             },
9045             scope: this,
9046             timeout: (loopN > 0) ? 0 : -1
9047         });
9048      
9049     }
9050     // Table has no rows
9051     else {
9052         // Set up the loop Chain to delete rows
9053         var nTotal = elTbody.rows.length;
9054         if(nTotal > 0) {
9055             this._oChainRender.add({
9056                 method: function(oArg) {
9057                     if((this instanceof DT) && this._sId) {
9058                         var i = oArg.nCurrent,
9059                             loopN = oArg.nLoopLength,
9060                             nIterEnd = (i - loopN < 0) ? 0 : i - loopN;
9061     
9062                         elTbody.style.display = "none";
9063                         
9064                         for(; i>nIterEnd; i--) {
9065                             elTbody.deleteRow(-1);
9066                         }
9067                         elTbody.style.display = "";
9068                         
9069                         // Set up for the next loop
9070                         oArg.nCurrent = i;
9071                     }
9072                 },
9073                 scope: this,
9074                 iterations: (loopN > 0) ? Math.ceil(nTotal/loopN) : 1,
9075                 argument: {
9076                     nCurrent: nTotal, 
9077                     nLoopLength: (loopN > 0) ? loopN : nTotal
9078                 },
9079                 timeout: (loopN > 0) ? 0 : -1
9080             });
9081         }
9082     }
9083     this._runRenderChain();
9084 },
9085
9086 /**
9087  * Disables DataTable UI.
9088  *
9089  * @method disable
9090  */
9091 disable : function() {
9092     this._disabled = true;
9093     var elTable = this._elTable;
9094     var elMask = this._elMask;
9095     elMask.style.width = elTable.offsetWidth + "px";
9096     elMask.style.height = elTable.offsetHeight + "px";
9097     elMask.style.left = elTable.offsetLeft + "px";
9098     elMask.style.display = "";
9099     this.fireEvent("disableEvent");
9100 },
9101
9102 /**
9103  * Undisables DataTable UI.
9104  *
9105  * @method undisable
9106  */
9107 undisable : function() {
9108     this._disabled = false;
9109     this._elMask.style.display = "none";
9110     this.fireEvent("undisableEvent");
9111 },
9112
9113  /**
9114  * Returns disabled state.
9115  *
9116  * @method isDisabled
9117  * @return {Boolean} True if UI is disabled, otherwise false
9118  */
9119 isDisabled : function() {
9120     return this._disabled;
9121 },
9122
9123 /**
9124  * Nulls out the entire DataTable instance and related objects, removes attached
9125  * event listeners, and clears out DOM elements inside the container. After
9126  * calling this method, the instance reference should be expliclitly nulled by
9127  * implementer, as in myDataTable = null. Use with caution!
9128  *
9129  * @method destroy
9130  */
9131 destroy : function() {
9132     // Store for later
9133     var instanceName = this.toString();
9134
9135     this._oChainRender.stop();
9136     
9137     // Destroy ColumnDD and ColumnResizers
9138     this._destroyColumnHelpers();
9139     
9140     // Destroy all CellEditors
9141     var oCellEditor;
9142     for(var i=0, len=this._oColumnSet.flat.length; i<len; i++) {
9143         oCellEditor = this._oColumnSet.flat[i].editor;
9144         if(oCellEditor && oCellEditor.destroy) {
9145             oCellEditor.destroy();
9146             this._oColumnSet.flat[i].editor = null;
9147         }
9148     }
9149
9150     // Destroy Paginator
9151     this._destroyPaginator();
9152
9153     // Unhook custom events
9154     this._oRecordSet.unsubscribeAll();
9155     this.unsubscribeAll();
9156
9157     // Unhook DOM events
9158     Ev.removeListener(document, "click", this._onDocumentClick);
9159     
9160     // Clear out the container
9161     this._destroyContainerEl(this._elContainer);
9162
9163     // Null out objects
9164     for(var param in this) {
9165         if(lang.hasOwnProperty(this, param)) {
9166             this[param] = null;
9167         }
9168     }
9169     
9170     // Clean up static values
9171     DT._nCurrentCount--;
9172     
9173     if(DT._nCurrentCount < 1) {
9174         if(DT._elDynStyleNode) {
9175             document.getElementsByTagName('head')[0].removeChild(DT._elDynStyleNode);
9176             DT._elDynStyleNode = null;
9177         }
9178     }
9179
9180 },
9181
9182 /**
9183  * Displays message within secondary TBODY.
9184  *
9185  * @method showTableMessage
9186  * @param sHTML {HTML} (optional) Value for innerHTML.
9187  * @param sClassName {String} (optional) Classname.
9188  */
9189 showTableMessage : function(sHTML, sClassName) {
9190     var elCell = this._elMsgTd;
9191     if(lang.isString(sHTML)) {
9192         elCell.firstChild.innerHTML = sHTML;
9193     }
9194     if(lang.isString(sClassName)) {
9195         elCell.className = sClassName;
9196     }
9197
9198     this._elMsgTbody.style.display = "";
9199
9200     this.fireEvent("tableMsgShowEvent", {html:sHTML, className:sClassName});
9201 },
9202
9203 /**
9204  * Hides secondary TBODY.
9205  *
9206  * @method hideTableMessage
9207  */
9208 hideTableMessage : function() {
9209     if(this._elMsgTbody.style.display != "none") {
9210         this._elMsgTbody.style.display = "none";
9211         this._elMsgTbody.parentNode.style.width = "";
9212         this.fireEvent("tableMsgHideEvent");
9213     }
9214 },
9215
9216 /**
9217  * Brings focus to the TBODY element. Alias to focusTbodyEl.
9218  *
9219  * @method focus
9220  */
9221 focus : function() {
9222     this.focusTbodyEl();
9223 },
9224
9225 /**
9226  * Brings focus to the THEAD element.
9227  *
9228  * @method focusTheadEl
9229  */
9230 focusTheadEl : function() {
9231     this._focusEl(this._elThead);
9232 },
9233
9234 /**
9235  * Brings focus to the TBODY element.
9236  *
9237  * @method focusTbodyEl
9238  */
9239 focusTbodyEl : function() {
9240     this._focusEl(this._elTbody);
9241 },
9242
9243 /**
9244  * Setting display:none on DataTable or any parent may impact width validations.
9245  * After setting display back to "", implementers should call this method to 
9246  * manually perform those validations.
9247  *
9248  * @method onShow
9249  */
9250 onShow : function() {
9251     this.validateColumnWidths();
9252     
9253     for(var allKeys = this._oColumnSet.keys, i=0, len=allKeys.length, col; i<len; i++) {
9254         col = allKeys[i];
9255         if(col._ddResizer) {
9256             col._ddResizer.resetResizerEl();
9257         }
9258     }
9259 },
9260
9261
9262
9263
9264
9265
9266
9267
9268
9269
9270
9271
9272
9273
9274
9275
9276
9277
9278
9279
9280
9281
9282
9283
9284
9285
9286
9287
9288
9289
9290
9291
9292
9293
9294
9295
9296
9297
9298
9299
9300
9301
9302
9303
9304
9305
9306
9307
9308
9309
9310
9311
9312
9313
9314
9315
9316
9317
9318
9319
9320
9321
9322
9323
9324
9325
9326
9327 // RECORDSET FUNCTIONS
9328
9329 /**
9330  * Returns Record index for given TR element or page row index.
9331  *
9332  * @method getRecordIndex
9333  * @param row {YAHOO.widget.Record | HTMLElement | Number} Record instance, TR
9334  * element reference or page row index.
9335  * @return {Number} Record's RecordSet index, or null.
9336  */
9337 getRecordIndex : function(row) {
9338     var nTrIndex;
9339
9340     if(!lang.isNumber(row)) {
9341         // By Record
9342         if(row instanceof YAHOO.widget.Record) {
9343             return this._oRecordSet.getRecordIndex(row);
9344         }
9345         // By element reference
9346         else {
9347             // Find the TR element
9348             var el = this.getTrEl(row);
9349             if(el) {
9350                 nTrIndex = el.sectionRowIndex;
9351             }
9352         }
9353     }
9354     // By page row index
9355     else {
9356         nTrIndex = row;
9357     }
9358
9359     if(lang.isNumber(nTrIndex)) {
9360         var oPaginator = this.get("paginator");
9361         if(oPaginator) {
9362             return oPaginator.get('recordOffset') + nTrIndex;
9363         }
9364         else {
9365             return nTrIndex;
9366         }
9367     }
9368
9369     return null;
9370 },
9371
9372 /**
9373  * For the given identifier, returns the associated Record instance.
9374  *
9375  * @method getRecord
9376  * @param row {HTMLElement | Number | String} DOM reference to a TR element (or
9377  * child of a TR element), RecordSet position index, or Record ID.
9378  * @return {YAHOO.widget.Record} Record instance.
9379  */
9380 getRecord : function(row) {
9381     var oRecord = this._oRecordSet.getRecord(row);
9382
9383     if(!oRecord) {
9384         // Validate TR element
9385         var elRow = this.getTrEl(row);
9386         if(elRow) {
9387             oRecord = this._oRecordSet.getRecord(elRow.id);
9388         }
9389     }
9390
9391     if(oRecord instanceof YAHOO.widget.Record) {
9392         return this._oRecordSet.getRecord(oRecord);
9393     }
9394     else {
9395         return null;
9396     }
9397 },
9398
9399
9400
9401
9402
9403
9404
9405
9406
9407
9408
9409
9410
9411
9412
9413
9414
9415
9416
9417
9418
9419
9420
9421
9422
9423
9424
9425
9426
9427
9428
9429
9430
9431
9432
9433
9434
9435
9436
9437
9438
9439
9440
9441
9442
9443
9444 // COLUMN FUNCTIONS
9445
9446 /**
9447  * For the given identifier, returns the associated Column instance. Note: For
9448  * getting Columns by Column ID string, please use the method getColumnById().
9449  *
9450  * @method getColumn
9451  * @param column {HTMLElement | String | Number} TH/TD element (or child of a
9452  * TH/TD element), a Column key, or a ColumnSet key index.
9453  * @return {YAHOO.widget.Column} Column instance.
9454  */
9455 getColumn : function(column) {
9456     var oColumn = this._oColumnSet.getColumn(column);
9457
9458     if(!oColumn) {
9459         // Validate TD element
9460         var elCell = this.getTdEl(column);
9461         if(elCell) {
9462             oColumn = this._oColumnSet.getColumn(this.getCellIndex(elCell));
9463         }
9464         // Validate TH element
9465         else {
9466             elCell = this.getThEl(column);
9467             if(elCell) {
9468                 // Find by TH el ID
9469                 var allColumns = this._oColumnSet.flat;
9470                 for(var i=0, len=allColumns.length; i<len; i++) {
9471                     if(allColumns[i].getThEl().id === elCell.id) {
9472                         oColumn = allColumns[i];
9473                     } 
9474                 }
9475             }
9476         }
9477     }
9478     if(!oColumn) {
9479     }
9480     return oColumn;
9481 },
9482
9483 /**
9484  * For the given Column ID, returns the associated Column instance. Note: For
9485  * getting Columns by key, please use the method getColumn().
9486  *
9487  * @method getColumnById
9488  * @param column {String} Column ID string.
9489  * @return {YAHOO.widget.Column} Column instance.
9490  */
9491 getColumnById : function(column) {
9492     return this._oColumnSet.getColumnById(column);
9493 },
9494
9495 /**
9496  * For the given Column instance, returns next direction to sort.
9497  *
9498  * @method getColumnSortDir
9499  * @param oColumn {YAHOO.widget.Column} Column instance.
9500  * @param oSortedBy {Object} (optional) Specify the state, or use current state. 
9501  * @return {String} YAHOO.widget.DataTable.CLASS_ASC or YAHOO.widget.DataTableCLASS_DESC.
9502  */
9503 getColumnSortDir : function(oColumn, oSortedBy) {
9504     // Backward compatibility
9505     if(oColumn.sortOptions && oColumn.sortOptions.defaultDir) {
9506         if(oColumn.sortOptions.defaultDir == "asc") {
9507             oColumn.sortOptions.defaultDir = DT.CLASS_ASC;
9508         }
9509         else if (oColumn.sortOptions.defaultDir == "desc") {
9510             oColumn.sortOptions.defaultDir = DT.CLASS_DESC;
9511         }
9512     }
9513     
9514     // What is the Column's default sort direction?
9515     var sortDir = (oColumn.sortOptions && oColumn.sortOptions.defaultDir) ? oColumn.sortOptions.defaultDir : DT.CLASS_ASC;
9516
9517     // Is the Column currently sorted?
9518     var bSorted = false;
9519     oSortedBy = oSortedBy || this.get("sortedBy");
9520     if(oSortedBy && (oSortedBy.key === oColumn.key)) {
9521         bSorted = true;
9522         if(oSortedBy.dir) {
9523             sortDir = (oSortedBy.dir === DT.CLASS_ASC) ? DT.CLASS_DESC : DT.CLASS_ASC;
9524         }
9525         else {
9526             sortDir = (sortDir === DT.CLASS_ASC) ? DT.CLASS_DESC : DT.CLASS_ASC;
9527         }
9528     }
9529     return sortDir;
9530 },
9531
9532 /**
9533  * Overridable method gives implementers a hook to show loading message before
9534  * sorting Column.
9535  *
9536  * @method doBeforeSortColumn
9537  * @param oColumn {YAHOO.widget.Column} Column instance.
9538  * @param sSortDir {String} YAHOO.widget.DataTable.CLASS_ASC or
9539  * YAHOO.widget.DataTable.CLASS_DESC.
9540  * @return {Boolean} Return true to continue sorting Column.
9541  */
9542 doBeforeSortColumn : function(oColumn, sSortDir) {
9543     this.showTableMessage(this.get("MSG_LOADING"), DT.CLASS_LOADING);
9544     return true;
9545 },
9546
9547 /**
9548  * Sorts given Column. If "dynamicData" is true, current selections are purged before
9549  * a request is sent to the DataSource for data for the new state (using the
9550  * request returned by "generateRequest()").
9551  *
9552  * @method sortColumn
9553  * @param oColumn {YAHOO.widget.Column} Column instance.
9554  * @param sDir {String} (Optional) YAHOO.widget.DataTable.CLASS_ASC or
9555  * YAHOO.widget.DataTable.CLASS_DESC
9556  */
9557 sortColumn : function(oColumn, sDir) {
9558     if(oColumn && (oColumn instanceof YAHOO.widget.Column)) {
9559         if(!oColumn.sortable) {
9560             Dom.addClass(this.getThEl(oColumn), DT.CLASS_SORTABLE);
9561         }
9562         
9563         // Validate given direction
9564         if(sDir && (sDir !== DT.CLASS_ASC) && (sDir !== DT.CLASS_DESC)) {
9565             sDir = null;
9566         }
9567         
9568         // Get the sort dir
9569         var sSortDir = sDir || this.getColumnSortDir(oColumn);
9570
9571         // Is the Column currently sorted?
9572         var oSortedBy = this.get("sortedBy") || {};
9573         var bSorted = (oSortedBy.key === oColumn.key) ? true : false;
9574
9575         var ok = this.doBeforeSortColumn(oColumn, sSortDir);
9576         if(ok) {
9577             // Server-side sort
9578             if(this.get("dynamicData")) {
9579                 // Get current state
9580                 var oState = this.getState();
9581                 
9582                 // Reset record offset, if paginated
9583                 if(oState.pagination) {
9584                     oState.pagination.recordOffset = 0;
9585                 }
9586                 
9587                 // Update sortedBy to new values
9588                 oState.sortedBy = {
9589                     key: oColumn.key,
9590                     dir: sSortDir
9591                 };
9592                 
9593                 // Get the request for the new state
9594                 var request = this.get("generateRequest")(oState, this);
9595
9596                 // Purge selections
9597                 this.unselectAllRows();
9598                 this.unselectAllCells();
9599
9600                 // Send request for new data
9601                 var callback = {
9602                     success : this.onDataReturnSetRows,
9603                     failure : this.onDataReturnSetRows,
9604                     argument : oState, // Pass along the new state to the callback
9605                     scope : this
9606                 };
9607                 this._oDataSource.sendRequest(request, callback);            
9608             }
9609             // Client-side sort
9610             else {
9611                 // Is there a custom sort handler function defined?
9612                 var sortFnc = (oColumn.sortOptions && lang.isFunction(oColumn.sortOptions.sortFunction)) ?
9613                         // Custom sort function
9614                         oColumn.sortOptions.sortFunction : null;
9615                    
9616                 // Sort the Records
9617                 if(!bSorted || sDir || sortFnc) {
9618                     // Default sort function if necessary
9619                     sortFnc = sortFnc || this.get("sortFunction");
9620                     // Get the field to sort
9621                     var sField = (oColumn.sortOptions && oColumn.sortOptions.field) ? oColumn.sortOptions.field : oColumn.field;
9622
9623                     // Sort the Records        
9624                     this._oRecordSet.sortRecords(sortFnc, ((sSortDir == DT.CLASS_DESC) ? true : false), sField);
9625                 }
9626                 // Just reverse the Records
9627                 else {
9628                     this._oRecordSet.reverseRecords();
9629                 }
9630         
9631                 // Reset to first page if paginated
9632                 var oPaginator = this.get('paginator');
9633                 if (oPaginator) {
9634                     // Set page silently, so as not to fire change event.
9635                     oPaginator.setPage(1,true);
9636                 }
9637         
9638                 // Update UI via sortedBy
9639                 this.render();
9640                 this.set("sortedBy", {key:oColumn.key, dir:sSortDir, column:oColumn}); 
9641             }       
9642             
9643             this.fireEvent("columnSortEvent",{column:oColumn,dir:sSortDir});
9644             return;
9645         }
9646     }
9647 },
9648
9649 /**
9650  * Sets given Column to given pixel width. If new width is less than minimum
9651  * width, sets to minimum width. Updates oColumn.width value.
9652  *
9653  * @method setColumnWidth
9654  * @param oColumn {YAHOO.widget.Column} Column instance.
9655  * @param nWidth {Number} New width in pixels. A null value auto-sizes Column,
9656  * subject to minWidth and maxAutoWidth validations. 
9657  */
9658 setColumnWidth : function(oColumn, nWidth) {
9659     if(!(oColumn instanceof YAHOO.widget.Column)) {
9660         oColumn = this.getColumn(oColumn);
9661     }
9662     if(oColumn) {
9663         // Validate new width against minimum width
9664         if(lang.isNumber(nWidth)) {
9665             // This is why we must require a Number... :-|
9666             nWidth = (nWidth > oColumn.minWidth) ? nWidth : oColumn.minWidth;
9667
9668             // Save state
9669             oColumn.width = nWidth;
9670             
9671             // Resize the DOM elements
9672             this._setColumnWidth(oColumn, nWidth+"px");
9673             
9674             this.fireEvent("columnSetWidthEvent",{column:oColumn,width:nWidth});
9675         }
9676         // Unsets a width to auto-size
9677         else if(nWidth === null) {
9678             // Save state
9679             oColumn.width = nWidth;
9680             
9681             // Resize the DOM elements
9682             this._setColumnWidth(oColumn, "auto");
9683             this.validateColumnWidths(oColumn);
9684             this.fireEvent("columnUnsetWidthEvent",{column:oColumn});
9685         }
9686                 
9687         // Bug 2339454: resize then sort misaligment
9688         this._clearTrTemplateEl();
9689     }
9690     else {
9691     }
9692 },
9693
9694 /**
9695  * Sets liner DIV elements of given Column to given width. When value should be
9696  * auto-calculated to fit content overflow is set to visible, otherwise overflow
9697  * is set to hidden. No validations against minimum width and no updating
9698  * Column.width value.
9699  *
9700  * @method _setColumnWidth
9701  * @param oColumn {YAHOO.widget.Column} Column instance.
9702  * @param sWidth {String} New width value.
9703  * @param sOverflow {String} Should be "hidden" when Column width is explicitly
9704  * being set to a value, but should be "visible" when Column is meant to auto-fit content.  
9705  * @private
9706  */
9707 _setColumnWidth : function(oColumn, sWidth, sOverflow) {
9708     if(oColumn && (oColumn.getKeyIndex() !== null)) {
9709         sOverflow = sOverflow || (((sWidth === '') || (sWidth === 'auto')) ? 'visible' : 'hidden');
9710     
9711         // Dynamic style algorithm
9712         if(!DT._bDynStylesFallback) {
9713             this._setColumnWidthDynStyles(oColumn, sWidth, sOverflow);
9714         }
9715         // Dynamic function algorithm
9716         else {
9717             this._setColumnWidthDynFunction(oColumn, sWidth, sOverflow);
9718         }
9719     }
9720     else {
9721     }
9722 },
9723
9724 /**
9725  * Updates width of a Column's liner DIV elements by dynamically creating a
9726  * STYLE node and writing and updating CSS style rules to it. If this fails during
9727  * runtime, the fallback method _setColumnWidthDynFunction() will be called.
9728  * Notes: This technique is not performant in IE6. IE7 crashes if DataTable is
9729  * nested within another TABLE element. For these cases, it is recommended to
9730  * use the method _setColumnWidthDynFunction by setting _bDynStylesFallback to TRUE.
9731  *
9732  * @method _setColumnWidthDynStyles
9733  * @param oColumn {YAHOO.widget.Column} Column instance.
9734  * @param sWidth {String} New width value.
9735  * @private
9736  */
9737 _setColumnWidthDynStyles : function(oColumn, sWidth, sOverflow) {
9738     var s = DT._elDynStyleNode,
9739         rule;
9740     
9741     // Create a new STYLE node
9742     if(!s) {
9743         s = document.createElement('style');
9744         s.type = 'text/css';
9745         s = document.getElementsByTagName('head').item(0).appendChild(s);
9746         DT._elDynStyleNode = s;
9747     }
9748     
9749     // We have a STYLE node to update
9750     if(s) {
9751         // Use unique classname for this Column instance as a hook for resizing
9752         var sClassname = "." + this.getId() + "-col-" + oColumn.getSanitizedKey() + " ." + DT.CLASS_LINER;
9753         
9754         // Hide for performance
9755         if(this._elTbody) {
9756             this._elTbody.style.display = 'none';
9757         }
9758         
9759         rule = DT._oDynStyles[sClassname];
9760
9761         // The Column does not yet have a rule
9762         if(!rule) {
9763             if(s.styleSheet && s.styleSheet.addRule) {
9764                 s.styleSheet.addRule(sClassname,"overflow:"+sOverflow);
9765                 s.styleSheet.addRule(sClassname,'width:'+sWidth);
9766                 rule = s.styleSheet.rules[s.styleSheet.rules.length-1];
9767                 DT._oDynStyles[sClassname] = rule;
9768             }
9769             else if(s.sheet && s.sheet.insertRule) {
9770                 s.sheet.insertRule(sClassname+" {overflow:"+sOverflow+";width:"+sWidth+";}",s.sheet.cssRules.length);
9771                 rule = s.sheet.cssRules[s.sheet.cssRules.length-1];
9772                 DT._oDynStyles[sClassname] = rule;
9773             }
9774         }
9775         // We have a rule to update
9776         else {
9777             rule.style.overflow = sOverflow;
9778             rule.style.width = sWidth;
9779         } 
9780         
9781         // Unhide
9782         if(this._elTbody) {
9783             this._elTbody.style.display = '';
9784         }
9785     }
9786     
9787     // That was not a success, we must call the fallback routine
9788     if(!rule) {
9789         DT._bDynStylesFallback = true;
9790         this._setColumnWidthDynFunction(oColumn, sWidth);
9791     }
9792 },
9793
9794 /**
9795  * Updates width of a Column's liner DIV elements by dynamically creating a
9796  * function to update all element style properties in one pass. Note: This
9797  * technique is not supported in sandboxed environments that prohibit EVALs.    
9798  *
9799  * @method _setColumnWidthDynFunction
9800  * @param oColumn {YAHOO.widget.Column} Column instance.
9801  * @param sWidth {String} New width value.
9802  * @private
9803  */
9804 _setColumnWidthDynFunction : function(oColumn, sWidth, sOverflow) {
9805     // TODO: why is this here?
9806     if(sWidth == 'auto') {
9807         sWidth = ''; 
9808     }
9809     
9810     // Create one function for each value of rows.length
9811     var rowslen = this._elTbody ? this._elTbody.rows.length : 0;
9812     
9813     // Dynamically create the function
9814     if (!this._aDynFunctions[rowslen]) {
9815         
9816         //Compile a custom function to do all the liner div width
9817         //assignments at the same time.  A unique function is required
9818         //for each unique number of rows in _elTbody.  This will
9819         //result in a function declaration like:
9820         //function (oColumn,sWidth,sOverflow) {
9821         //    var colIdx = oColumn.getKeyIndex();
9822         //    oColumn.getThLinerEl().style.overflow =
9823         //    this._elTbody.rows[0].cells[colIdx].firstChild.style.overflow =
9824         //    this._elTbody.rows[1].cells[colIdx].firstChild.style.overflow =
9825         //    ... (for all row indices in this._elTbody.rows.length - 1)
9826         //    this._elTbody.rows[99].cells[colIdx].firstChild.style.overflow =
9827         //    sOverflow;
9828         //    oColumn.getThLinerEl().style.width =
9829         //    this._elTbody.rows[0].cells[colIdx].firstChild.style.width =
9830         //    this._elTbody.rows[1].cells[colIdx].firstChild.style.width =
9831         //    ... (for all row indices in this._elTbody.rows.length - 1)
9832         //    this._elTbody.rows[99].cells[colIdx].firstChild.style.width =
9833         //    sWidth;
9834         //}
9835         
9836         var i,j,k;
9837         var resizerDef = [
9838             'var colIdx=oColumn.getKeyIndex();',
9839             'oColumn.getThLinerEl().style.overflow='
9840         ];
9841         for (i=rowslen-1, j=2; i >= 0; --i) {
9842             resizerDef[j++] = 'this._elTbody.rows[';
9843             resizerDef[j++] = i;
9844             resizerDef[j++] = '].cells[colIdx].firstChild.style.overflow=';
9845         }
9846         resizerDef[j] = 'sOverflow;';
9847         resizerDef[j+1] = 'oColumn.getThLinerEl().style.width=';
9848         for (i=rowslen-1, k=j+2; i >= 0; --i) {
9849             resizerDef[k++] = 'this._elTbody.rows[';
9850             resizerDef[k++] = i;
9851             resizerDef[k++] = '].cells[colIdx].firstChild.style.width=';
9852         }
9853         resizerDef[k] = 'sWidth;';
9854         this._aDynFunctions[rowslen] =
9855             new Function('oColumn','sWidth','sOverflow',resizerDef.join(''));
9856     }
9857     
9858     // Get the function to execute
9859     var resizerFn = this._aDynFunctions[rowslen];
9860
9861     // TODO: Hide TBODY for performance in _setColumnWidthDynFunction?
9862     if (resizerFn) {
9863         resizerFn.call(this,oColumn,sWidth,sOverflow);
9864     }
9865 },
9866
9867 /**
9868  * For one or all Columns, when Column is not hidden, width is not set, and minWidth
9869  * and/or maxAutoWidth is set, validates auto-width against minWidth and maxAutoWidth.
9870  *
9871  * @method validateColumnWidths
9872  * @param oArg.column {YAHOO.widget.Column} (optional) One Column to validate. If null, all Columns' widths are validated.
9873  */
9874 validateColumnWidths : function(oColumn) {
9875     var elColgroup = this._elColgroup;
9876     var elColgroupClone = elColgroup.cloneNode(true);
9877     var bNeedsValidation = false;
9878     var allKeys = this._oColumnSet.keys;
9879     var elThLiner;
9880     // Validate just one Column's minWidth and/or maxAutoWidth
9881     if(oColumn && !oColumn.hidden && !oColumn.width && (oColumn.getKeyIndex() !== null)) {
9882             elThLiner = oColumn.getThLinerEl();
9883             if((oColumn.minWidth > 0) && (elThLiner.offsetWidth < oColumn.minWidth)) {
9884                 elColgroupClone.childNodes[oColumn.getKeyIndex()].style.width = 
9885                         oColumn.minWidth + 
9886                         (parseInt(Dom.getStyle(elThLiner,"paddingLeft"),10)|0) +
9887                         (parseInt(Dom.getStyle(elThLiner,"paddingRight"),10)|0) + "px";
9888                 bNeedsValidation = true;
9889             }
9890             else if((oColumn.maxAutoWidth > 0) && (elThLiner.offsetWidth > oColumn.maxAutoWidth)) {
9891                 this._setColumnWidth(oColumn, oColumn.maxAutoWidth+"px", "hidden");
9892             }
9893     }
9894     // Validate all Columns
9895     else {
9896         for(var i=0, len=allKeys.length; i<len; i++) {
9897             oColumn = allKeys[i];
9898             if(!oColumn.hidden && !oColumn.width) {
9899                 elThLiner = oColumn.getThLinerEl();
9900                 if((oColumn.minWidth > 0) && (elThLiner.offsetWidth < oColumn.minWidth)) {
9901                     elColgroupClone.childNodes[i].style.width = 
9902                             oColumn.minWidth + 
9903                             (parseInt(Dom.getStyle(elThLiner,"paddingLeft"),10)|0) +
9904                             (parseInt(Dom.getStyle(elThLiner,"paddingRight"),10)|0) + "px";
9905                     bNeedsValidation = true;
9906                 }
9907                 else if((oColumn.maxAutoWidth > 0) && (elThLiner.offsetWidth > oColumn.maxAutoWidth)) {
9908                     this._setColumnWidth(oColumn, oColumn.maxAutoWidth+"px", "hidden");
9909                 }
9910             }
9911         }
9912     }
9913     if(bNeedsValidation) {
9914         elColgroup.parentNode.replaceChild(elColgroupClone, elColgroup);
9915         this._elColgroup = elColgroupClone;
9916     }
9917 },
9918
9919 /**
9920  * Clears minWidth.
9921  *
9922  * @method _clearMinWidth
9923  * @param oColumn {YAHOO.widget.Column} Which Column.
9924  * @private
9925  */
9926 _clearMinWidth : function(oColumn) {
9927     if(oColumn.getKeyIndex() !== null) {
9928         this._elColgroup.childNodes[oColumn.getKeyIndex()].style.width = '';
9929     }
9930 },
9931
9932 /**
9933  * Restores minWidth.
9934  *
9935  * @method _restoreMinWidth
9936  * @param oColumn {YAHOO.widget.Column} Which Column.
9937  * @private
9938  */
9939 _restoreMinWidth : function(oColumn) {
9940     if(oColumn.minWidth && (oColumn.getKeyIndex() !== null)) {
9941         this._elColgroup.childNodes[oColumn.getKeyIndex()].style.width = oColumn.minWidth + 'px';
9942     }
9943 },
9944
9945 /**
9946  * Hides given Column. NOTE: You cannot hide/show nested Columns. You can only
9947  * hide/show non-nested Columns, and top-level parent Columns (which will
9948  * hide/show all children Columns).
9949  *
9950  * @method hideColumn
9951  * @param oColumn {YAHOO.widget.Column | HTMLElement | String | Number} Column
9952  * instance, TH/TD element (or child of a TH/TD element), a Column key, or a
9953  * ColumnSet key index.
9954  */
9955 hideColumn : function(oColumn) {
9956     if(!(oColumn instanceof YAHOO.widget.Column)) {
9957         oColumn = this.getColumn(oColumn);
9958     }
9959     // Only top-level Columns can get hidden due to issues in FF2 and SF3
9960     if(oColumn && !oColumn.hidden && oColumn.getTreeIndex() !== null) {
9961         
9962         var allrows = this.getTbodyEl().rows;
9963         var l = allrows.length;
9964         var allDescendants = this._oColumnSet.getDescendants(oColumn);
9965         
9966         // Hide each nested Column
9967         for(var i=0, len=allDescendants.length; i<len; i++) {
9968             var thisColumn = allDescendants[i];
9969             thisColumn.hidden = true;
9970
9971             // Style the head cell
9972             Dom.addClass(thisColumn.getThEl(), DT.CLASS_HIDDEN);
9973             
9974             // Does this Column have body cells?
9975             var thisKeyIndex = thisColumn.getKeyIndex();
9976             if(thisKeyIndex !== null) {                    
9977                 // Clear minWidth
9978                 this._clearMinWidth(oColumn);
9979                 
9980                 // Style the body cells
9981                 for(var j=0;j<l;j++) {
9982                     Dom.addClass(allrows[j].cells[thisKeyIndex],DT.CLASS_HIDDEN);
9983                 }
9984             }
9985             
9986             this.fireEvent("columnHideEvent",{column:thisColumn});
9987         }
9988       
9989         this._repaintOpera();
9990         this._clearTrTemplateEl();
9991     }
9992     else {
9993     }
9994 },
9995
9996 /**
9997  * Shows given Column. NOTE: You cannot hide/show nested Columns. You can only
9998  * hide/show non-nested Columns, and top-level parent Columns (which will
9999  * hide/show all children Columns).
10000  *
10001  * @method showColumn
10002  * @param oColumn {YAHOO.widget.Column | HTMLElement | String | Number} Column
10003  * instance, TH/TD element (or child of a TH/TD element), a Column key, or a
10004  * ColumnSet key index.
10005  */
10006 showColumn : function(oColumn) {
10007     if(!(oColumn instanceof YAHOO.widget.Column)) {
10008         oColumn = this.getColumn(oColumn);
10009     }
10010     // Only top-level Columns can get hidden
10011     if(oColumn && oColumn.hidden && (oColumn.getTreeIndex() !== null)) {
10012         var allrows = this.getTbodyEl().rows;
10013         var l = allrows.length;
10014         var allDescendants = this._oColumnSet.getDescendants(oColumn);
10015         
10016         // Show each nested Column
10017         for(var i=0, len=allDescendants.length; i<len; i++) {
10018             var thisColumn = allDescendants[i];
10019             thisColumn.hidden = false;
10020             
10021             // Unstyle the head cell
10022             Dom.removeClass(thisColumn.getThEl(), DT.CLASS_HIDDEN);
10023
10024             // Does this Column have body cells?
10025             var thisKeyIndex = thisColumn.getKeyIndex();
10026             if(thisKeyIndex !== null) {
10027                 // Restore minWidth
10028                 this._restoreMinWidth(oColumn);
10029                 
10030             
10031                 // Unstyle the body cells
10032                 for(var j=0;j<l;j++) {
10033                     Dom.removeClass(allrows[j].cells[thisKeyIndex],DT.CLASS_HIDDEN);
10034                 }
10035             }
10036
10037             this.fireEvent("columnShowEvent",{column:thisColumn});
10038         }
10039         this._clearTrTemplateEl();
10040     }
10041     else {
10042     }
10043 },
10044
10045 /**
10046  * Removes given Column. NOTE: You cannot remove nested Columns. You can only remove
10047  * non-nested Columns, and top-level parent Columns (which will remove all
10048  * children Columns).
10049  *
10050  * @method removeColumn
10051  * @param oColumn {YAHOO.widget.Column} Column instance.
10052  * @return oColumn {YAHOO.widget.Column} Removed Column instance.
10053  */
10054 removeColumn : function(oColumn) {
10055     // Validate Column
10056     if(!(oColumn instanceof YAHOO.widget.Column)) {
10057         oColumn = this.getColumn(oColumn);
10058     }
10059     if(oColumn) {
10060         var nColTreeIndex = oColumn.getTreeIndex();
10061         if(nColTreeIndex !== null) {
10062             // Which key index(es)
10063             var i, len,
10064                 aKeyIndexes = oColumn.getKeyIndex();
10065             // Must be a parent Column
10066             if(aKeyIndexes === null) {
10067                 var descKeyIndexes = [];
10068                 var allDescendants = this._oColumnSet.getDescendants(oColumn);
10069                 for(i=0, len=allDescendants.length; i<len; i++) {
10070                     // Is this descendant a key Column?
10071                     var thisKey = allDescendants[i].getKeyIndex();
10072                     if(thisKey !== null) {
10073                         descKeyIndexes[descKeyIndexes.length] = thisKey;
10074                     }
10075                 }
10076                 if(descKeyIndexes.length > 0) {
10077                     aKeyIndexes = descKeyIndexes;
10078                 }
10079             }
10080             // Must be a key Column
10081             else {
10082                 aKeyIndexes = [aKeyIndexes];
10083             }
10084             
10085             if(aKeyIndexes !== null) {
10086                 // Sort the indexes so we can remove from the right
10087                 aKeyIndexes.sort(function(a, b) {return YAHOO.util.Sort.compare(a, b);});
10088                 
10089                 // Destroy previous THEAD
10090                 this._destroyTheadEl();
10091     
10092                 // Create new THEAD
10093                 var aOrigColumnDefs = this._oColumnSet.getDefinitions();
10094                 oColumn = aOrigColumnDefs.splice(nColTreeIndex,1)[0];
10095                 this._initColumnSet(aOrigColumnDefs);
10096                 this._initTheadEl();
10097                 
10098                 // Remove COL
10099                 for(i=aKeyIndexes.length-1; i>-1; i--) {
10100                     this._removeColgroupColEl(aKeyIndexes[i]);
10101                 }
10102                 
10103                 // Remove TD
10104                 var allRows = this._elTbody.rows;
10105                 if(allRows.length > 0) {
10106                     var loopN = this.get("renderLoopSize"),
10107                         loopEnd = allRows.length;
10108                     this._oChainRender.add({
10109                         method: function(oArg) {
10110                             if((this instanceof DT) && this._sId) {
10111                                 var i = oArg.nCurrentRow,
10112                                     len = loopN > 0 ? Math.min(i + loopN,allRows.length) : allRows.length,
10113                                     aIndexes = oArg.aIndexes,
10114                                     j;
10115                                 for(; i < len; ++i) {
10116                                     for(j = aIndexes.length-1; j>-1; j--) {
10117                                         allRows[i].removeChild(allRows[i].childNodes[aIndexes[j]]);
10118                                     }
10119                                 }
10120                                 oArg.nCurrentRow = i;
10121                             }
10122                         },
10123                         iterations: (loopN > 0) ? Math.ceil(loopEnd/loopN) : 1,
10124                         argument: {nCurrentRow:0, aIndexes:aKeyIndexes},
10125                         scope: this,
10126                         timeout: (loopN > 0) ? 0 : -1
10127                     });
10128                     this._runRenderChain();
10129                 }
10130         
10131                 this.fireEvent("columnRemoveEvent",{column:oColumn});
10132                 return oColumn;
10133             }
10134         }
10135     }
10136 },
10137
10138 /**
10139  * Inserts given Column at the index if given, otherwise at the end. NOTE: You
10140  * can only add non-nested Columns and top-level parent Columns. You cannot add
10141  * a nested Column to an existing parent.
10142  *
10143  * @method insertColumn
10144  * @param oColumn {Object | YAHOO.widget.Column} Object literal Column
10145  * definition or a Column instance.
10146  * @param index {Number} (optional) New tree index.
10147  * @return oColumn {YAHOO.widget.Column} Inserted Column instance. 
10148  */
10149 insertColumn : function(oColumn, index) {
10150     // Validate Column
10151     if(oColumn instanceof YAHOO.widget.Column) {
10152         oColumn = oColumn.getDefinition();
10153     }
10154     else if(oColumn.constructor !== Object) {
10155         return;
10156     }
10157     
10158     // Validate index or append new Column to the end of the ColumnSet
10159     var oColumnSet = this._oColumnSet;
10160     if(!lang.isValue(index) || !lang.isNumber(index)) {
10161         index = oColumnSet.tree[0].length;
10162     }
10163     
10164     // Destroy previous THEAD
10165     this._destroyTheadEl();
10166     
10167     // Create new THEAD
10168     var aNewColumnDefs = this._oColumnSet.getDefinitions();
10169     aNewColumnDefs.splice(index, 0, oColumn);
10170     this._initColumnSet(aNewColumnDefs);
10171     this._initTheadEl();
10172     
10173     // Need to refresh the reference
10174     oColumnSet = this._oColumnSet;
10175     var oNewColumn = oColumnSet.tree[0][index];
10176     
10177     // Get key index(es) for new Column
10178     var i, len,
10179         descKeyIndexes = [];
10180     var allDescendants = oColumnSet.getDescendants(oNewColumn);
10181     for(i=0, len=allDescendants.length; i<len; i++) {
10182         // Is this descendant a key Column?
10183         var thisKey = allDescendants[i].getKeyIndex();
10184         if(thisKey !== null) {
10185             descKeyIndexes[descKeyIndexes.length] = thisKey;
10186         }
10187     }
10188     
10189     if(descKeyIndexes.length > 0) {  
10190         // Sort the indexes
10191         var newIndex = descKeyIndexes.sort(function(a, b) {return YAHOO.util.Sort.compare(a, b);})[0];
10192         
10193         // Add COL
10194         for(i=descKeyIndexes.length-1; i>-1; i--) {
10195             this._insertColgroupColEl(descKeyIndexes[i]);
10196         }
10197             
10198         // Add TD
10199         var allRows = this._elTbody.rows;
10200         if(allRows.length > 0) {
10201             var loopN = this.get("renderLoopSize"),
10202                 loopEnd = allRows.length;
10203             
10204             // Get templates for each new TD
10205             var aTdTemplates = [],
10206                 elTdTemplate;
10207             for(i=0, len=descKeyIndexes.length; i<len; i++) {
10208                 var thisKeyIndex = descKeyIndexes[i];
10209                 elTdTemplate = this._getTrTemplateEl().childNodes[i].cloneNode(true);
10210                 elTdTemplate = this._formatTdEl(this._oColumnSet.keys[thisKeyIndex], elTdTemplate, thisKeyIndex, (thisKeyIndex===this._oColumnSet.keys.length-1));
10211                 aTdTemplates[thisKeyIndex] = elTdTemplate;
10212             }
10213             
10214             this._oChainRender.add({
10215                 method: function(oArg) {
10216                     if((this instanceof DT) && this._sId) {
10217                         var i = oArg.nCurrentRow, j,
10218                             descKeyIndexes = oArg.descKeyIndexes,
10219                             len = loopN > 0 ? Math.min(i + loopN,allRows.length) : allRows.length,
10220                             nextSibling;
10221                         for(; i < len; ++i) {
10222                             nextSibling = allRows[i].childNodes[newIndex] || null;
10223                             for(j=descKeyIndexes.length-1; j>-1; j--) {
10224                                 allRows[i].insertBefore(oArg.aTdTemplates[descKeyIndexes[j]].cloneNode(true), nextSibling);
10225                             }
10226                         }
10227                         oArg.nCurrentRow = i;
10228                     }
10229                 },
10230                 iterations: (loopN > 0) ? Math.ceil(loopEnd/loopN) : 1,
10231                 argument: {nCurrentRow:0,aTdTemplates:aTdTemplates,descKeyIndexes:descKeyIndexes},
10232                 scope: this,
10233                 timeout: (loopN > 0) ? 0 : -1
10234             });
10235             this._runRenderChain(); 
10236         }
10237
10238         this.fireEvent("columnInsertEvent",{column:oColumn,index:index});
10239         return oNewColumn;
10240     }
10241 },
10242
10243 /**
10244  * Removes given Column and inserts into given tree index. NOTE: You
10245  * can only reorder non-nested Columns and top-level parent Columns. You cannot
10246  * reorder a nested Column to an existing parent.
10247  *
10248  * @method reorderColumn
10249  * @param oColumn {YAHOO.widget.Column} Column instance.
10250  * @param index {Number} New tree index.
10251  * @return oColumn {YAHOO.widget.Column} Reordered Column instance. 
10252  */
10253 reorderColumn : function(oColumn, index) {
10254     // Validate Column and new index
10255     if(!(oColumn instanceof YAHOO.widget.Column)) {
10256         oColumn = this.getColumn(oColumn);
10257     }
10258     if(oColumn && YAHOO.lang.isNumber(index)) {
10259         var nOrigTreeIndex = oColumn.getTreeIndex();
10260         if((nOrigTreeIndex !== null) && (nOrigTreeIndex !== index)) {
10261             // Which key index(es)
10262             var i, len,
10263                 aOrigKeyIndexes = oColumn.getKeyIndex(),
10264                 allDescendants,
10265                 descKeyIndexes = [],
10266                 thisKey;
10267             // Must be a parent Column...
10268             if(aOrigKeyIndexes === null) {
10269                 allDescendants = this._oColumnSet.getDescendants(oColumn);
10270                 for(i=0, len=allDescendants.length; i<len; i++) {
10271                     // Is this descendant a key Column?
10272                     thisKey = allDescendants[i].getKeyIndex();
10273                     if(thisKey !== null) {
10274                         descKeyIndexes[descKeyIndexes.length] = thisKey;
10275                     }
10276                 }
10277                 if(descKeyIndexes.length > 0) {
10278                     aOrigKeyIndexes = descKeyIndexes;
10279                 }
10280             }
10281             // ...or else must be a key Column
10282             else {
10283                 aOrigKeyIndexes = [aOrigKeyIndexes];
10284             }
10285             
10286             if(aOrigKeyIndexes !== null) {                   
10287                 // Sort the indexes
10288                 aOrigKeyIndexes.sort(function(a, b) {return YAHOO.util.Sort.compare(a, b);});
10289                 
10290                 // Destroy previous THEAD
10291                 this._destroyTheadEl();
10292     
10293                 // Create new THEAD
10294                 var aColumnDefs = this._oColumnSet.getDefinitions();
10295                 var oColumnDef = aColumnDefs.splice(nOrigTreeIndex,1)[0];
10296                 aColumnDefs.splice(index, 0, oColumnDef);
10297                 this._initColumnSet(aColumnDefs);
10298                 this._initTheadEl();
10299                 
10300                 // Need to refresh the reference
10301                 var oNewColumn = this._oColumnSet.tree[0][index];
10302
10303                 // What are new key index(es)
10304                 var aNewKeyIndexes = oNewColumn.getKeyIndex();
10305                 // Must be a parent Column
10306                 if(aNewKeyIndexes === null) {
10307                     descKeyIndexes = [];
10308                     allDescendants = this._oColumnSet.getDescendants(oNewColumn);
10309                     for(i=0, len=allDescendants.length; i<len; i++) {
10310                         // Is this descendant a key Column?
10311                         thisKey = allDescendants[i].getKeyIndex();
10312                         if(thisKey !== null) {
10313                             descKeyIndexes[descKeyIndexes.length] = thisKey;
10314                         }
10315                     }
10316                     if(descKeyIndexes.length > 0) {
10317                         aNewKeyIndexes = descKeyIndexes;
10318                     }
10319                 }
10320                 // Must be a key Column
10321                 else {
10322                     aNewKeyIndexes = [aNewKeyIndexes];
10323                 }
10324                 
10325                 // Sort the new indexes and grab the first one for the new location
10326                 var newIndex = aNewKeyIndexes.sort(function(a, b) {return YAHOO.util.Sort.compare(a, b);})[0];
10327
10328                 // Reorder COL
10329                 this._reorderColgroupColEl(aOrigKeyIndexes, newIndex);
10330                 
10331                 // Reorder TD
10332                 var allRows = this._elTbody.rows;
10333                 if(allRows.length > 0) {
10334                     var loopN = this.get("renderLoopSize"),
10335                         loopEnd = allRows.length;
10336                     this._oChainRender.add({
10337                         method: function(oArg) {
10338                             if((this instanceof DT) && this._sId) {
10339                                 var i = oArg.nCurrentRow, j, tmpTds, nextSibling,
10340                                     len = loopN > 0 ? Math.min(i + loopN,allRows.length) : allRows.length,
10341                                     aIndexes = oArg.aIndexes, thisTr;
10342                                 // For each row
10343                                 for(; i < len; ++i) {
10344                                     tmpTds = [];
10345                                     thisTr = allRows[i];
10346                                     
10347                                     // Remove each TD
10348                                     for(j=aIndexes.length-1; j>-1; j--) {
10349                                         tmpTds.push(thisTr.removeChild(thisTr.childNodes[aIndexes[j]]));
10350                                     }
10351                                     
10352                                     // Insert each TD
10353                                     nextSibling = thisTr.childNodes[newIndex] || null;
10354                                     for(j=tmpTds.length-1; j>-1; j--) {
10355                                         thisTr.insertBefore(tmpTds[j], nextSibling);
10356                                     }                                    
10357                                 }
10358                                 oArg.nCurrentRow = i;
10359                             }
10360                         },
10361                         iterations: (loopN > 0) ? Math.ceil(loopEnd/loopN) : 1,
10362                         argument: {nCurrentRow:0, aIndexes:aOrigKeyIndexes},
10363                         scope: this,
10364                         timeout: (loopN > 0) ? 0 : -1
10365                     });
10366                     this._runRenderChain();
10367                 }
10368         
10369                 this.fireEvent("columnReorderEvent",{column:oNewColumn, oldIndex:nOrigTreeIndex});
10370                 return oNewColumn;
10371             }
10372         }
10373     }
10374 },
10375
10376 /**
10377  * Selects given Column. NOTE: You cannot select/unselect nested Columns. You can only
10378  * select/unselect non-nested Columns, and bottom-level key Columns.
10379  *
10380  * @method selectColumn
10381  * @param column {HTMLElement | String | Number} DOM reference or ID string to a
10382  * TH/TD element (or child of a TH/TD element), a Column key, or a ColumnSet key index.
10383  */
10384 selectColumn : function(oColumn) {
10385     oColumn = this.getColumn(oColumn);
10386     if(oColumn && !oColumn.selected) {
10387         // Only bottom-level Columns can get hidden
10388         if(oColumn.getKeyIndex() !== null) {
10389             oColumn.selected = true;
10390             
10391             // Update head cell
10392             var elTh = oColumn.getThEl();
10393             Dom.addClass(elTh,DT.CLASS_SELECTED);
10394
10395             // Update body cells
10396             var allRows = this.getTbodyEl().rows;
10397             var oChainRender = this._oChainRender;
10398             oChainRender.add({
10399                 method: function(oArg) {
10400                     if((this instanceof DT) && this._sId && allRows[oArg.rowIndex] && allRows[oArg.rowIndex].cells[oArg.cellIndex]) {
10401                         Dom.addClass(allRows[oArg.rowIndex].cells[oArg.cellIndex],DT.CLASS_SELECTED);                    
10402                     }
10403                     oArg.rowIndex++;
10404                 },
10405                 scope: this,
10406                 iterations: allRows.length,
10407                 argument: {rowIndex:0,cellIndex:oColumn.getKeyIndex()}
10408             });
10409
10410             this._clearTrTemplateEl();
10411             
10412             this._elTbody.style.display = "none";
10413             this._runRenderChain();
10414             this._elTbody.style.display = "";      
10415             
10416             this.fireEvent("columnSelectEvent",{column:oColumn});
10417         }
10418         else {
10419         }
10420     }
10421 },
10422
10423 /**
10424  * Unselects given Column. NOTE: You cannot select/unselect nested Columns. You can only
10425  * select/unselect non-nested Columns, and bottom-level key Columns.
10426  *
10427  * @method unselectColumn
10428  * @param column {HTMLElement | String | Number} DOM reference or ID string to a
10429  * TH/TD element (or child of a TH/TD element), a Column key, or a ColumnSet key index.
10430  */
10431 unselectColumn : function(oColumn) {
10432     oColumn = this.getColumn(oColumn);
10433     if(oColumn && oColumn.selected) {
10434         // Only bottom-level Columns can get hidden
10435         if(oColumn.getKeyIndex() !== null) {
10436             oColumn.selected = false;
10437             
10438             // Update head cell
10439             var elTh = oColumn.getThEl();
10440             Dom.removeClass(elTh,DT.CLASS_SELECTED);
10441
10442             // Update body cells
10443             var allRows = this.getTbodyEl().rows;
10444             var oChainRender = this._oChainRender;
10445             oChainRender.add({
10446                 method: function(oArg) {
10447                     if((this instanceof DT) && this._sId && allRows[oArg.rowIndex] && allRows[oArg.rowIndex].cells[oArg.cellIndex]) {
10448                         Dom.removeClass(allRows[oArg.rowIndex].cells[oArg.cellIndex],DT.CLASS_SELECTED); 
10449                     }                   
10450                     oArg.rowIndex++;
10451                 },
10452                 scope: this,
10453                 iterations:allRows.length,
10454                 argument: {rowIndex:0,cellIndex:oColumn.getKeyIndex()}
10455             });
10456             
10457             this._clearTrTemplateEl();
10458
10459             this._elTbody.style.display = "none";
10460             this._runRenderChain();
10461             this._elTbody.style.display = "";      
10462             
10463             this.fireEvent("columnUnselectEvent",{column:oColumn});
10464         }
10465         else {
10466         }
10467     }
10468 },
10469
10470 /**
10471  * Returns an array selected Column instances.
10472  *
10473  * @method getSelectedColumns
10474  * @return {YAHOO.widget.Column[]} Array of Column instances.
10475  */
10476 getSelectedColumns : function(oColumn) {
10477     var selectedColumns = [];
10478     var aKeys = this._oColumnSet.keys;
10479     for(var i=0,len=aKeys.length; i<len; i++) {
10480         if(aKeys[i].selected) {
10481             selectedColumns[selectedColumns.length] = aKeys[i];
10482         }
10483     }
10484     return selectedColumns;
10485 },
10486
10487 /**
10488  * Assigns the class YAHOO.widget.DataTable.CLASS_HIGHLIGHTED to cells of the given Column.
10489  * NOTE: You cannot highlight/unhighlight nested Columns. You can only
10490  * highlight/unhighlight non-nested Columns, and bottom-level key Columns.
10491  *
10492  * @method highlightColumn
10493  * @param column {HTMLElement | String | Number} DOM reference or ID string to a
10494  * TH/TD element (or child of a TH/TD element), a Column key, or a ColumnSet key index.
10495  */
10496 highlightColumn : function(column) {
10497     var oColumn = this.getColumn(column);
10498     // Only bottom-level Columns can get highlighted
10499     if(oColumn && (oColumn.getKeyIndex() !== null)) {            
10500         // Update head cell
10501         var elTh = oColumn.getThEl();
10502         Dom.addClass(elTh,DT.CLASS_HIGHLIGHTED);
10503
10504         // Update body cells
10505         var allRows = this.getTbodyEl().rows;
10506         var oChainRender = this._oChainRender;
10507         oChainRender.add({
10508             method: function(oArg) {
10509                 if((this instanceof DT) && this._sId && allRows[oArg.rowIndex] && allRows[oArg.rowIndex].cells[oArg.cellIndex]) {
10510                     Dom.addClass(allRows[oArg.rowIndex].cells[oArg.cellIndex],DT.CLASS_HIGHLIGHTED);   
10511                 }                 
10512                 oArg.rowIndex++;
10513             },
10514             scope: this,
10515             iterations:allRows.length,
10516             argument: {rowIndex:0,cellIndex:oColumn.getKeyIndex()},
10517             timeout: -1
10518         });
10519         this._elTbody.style.display = "none";
10520         this._runRenderChain();
10521         this._elTbody.style.display = "";      
10522             
10523         this.fireEvent("columnHighlightEvent",{column:oColumn});
10524     }
10525     else {
10526     }
10527 },
10528
10529 /**
10530  * Removes the class YAHOO.widget.DataTable.CLASS_HIGHLIGHTED to cells of the given Column.
10531  * NOTE: You cannot highlight/unhighlight nested Columns. You can only
10532  * highlight/unhighlight non-nested Columns, and bottom-level key Columns.
10533  *
10534  * @method unhighlightColumn
10535  * @param column {HTMLElement | String | Number} DOM reference or ID string to a
10536  * TH/TD element (or child of a TH/TD element), a Column key, or a ColumnSet key index.
10537  */
10538 unhighlightColumn : function(column) {
10539     var oColumn = this.getColumn(column);
10540     // Only bottom-level Columns can get highlighted
10541     if(oColumn && (oColumn.getKeyIndex() !== null)) {
10542         // Update head cell
10543         var elTh = oColumn.getThEl();
10544         Dom.removeClass(elTh,DT.CLASS_HIGHLIGHTED);
10545
10546         // Update body cells
10547         var allRows = this.getTbodyEl().rows;
10548         var oChainRender = this._oChainRender;
10549         oChainRender.add({
10550             method: function(oArg) {
10551                 if((this instanceof DT) && this._sId && allRows[oArg.rowIndex] && allRows[oArg.rowIndex].cells[oArg.cellIndex]) {
10552                     Dom.removeClass(allRows[oArg.rowIndex].cells[oArg.cellIndex],DT.CLASS_HIGHLIGHTED);
10553                 }                 
10554                 oArg.rowIndex++;
10555             },
10556             scope: this,
10557             iterations:allRows.length,
10558             argument: {rowIndex:0,cellIndex:oColumn.getKeyIndex()},
10559             timeout: -1
10560         });
10561         this._elTbody.style.display = "none";
10562         this._runRenderChain();
10563         this._elTbody.style.display = "";     
10564             
10565         this.fireEvent("columnUnhighlightEvent",{column:oColumn});
10566     }
10567     else {
10568     }
10569 },
10570
10571
10572
10573
10574
10575
10576
10577
10578
10579
10580
10581
10582
10583
10584
10585
10586
10587
10588
10589
10590
10591
10592
10593
10594
10595
10596
10597
10598
10599
10600
10601
10602
10603
10604
10605
10606
10607
10608
10609
10610
10611
10612
10613
10614 // ROW FUNCTIONS
10615
10616 /**
10617  * Adds one new Record of data into the RecordSet at the index if given,
10618  * otherwise at the end. If the new Record is in page view, the
10619  * corresponding DOM elements are also updated.
10620  *
10621  * @method addRow
10622  * @param oData {Object} Object literal of data for the row.
10623  * @param index {Number} (optional) RecordSet position index at which to add data.
10624  */
10625 addRow : function(oData, index) {
10626     if(lang.isNumber(index) && (index < 0 || index > this._oRecordSet.getLength())) {
10627         return;
10628     }
10629
10630     if(oData && lang.isObject(oData)) {
10631         var oRecord = this._oRecordSet.addRecord(oData, index);
10632         if(oRecord) {
10633             var recIndex;
10634             var oPaginator = this.get('paginator');
10635
10636             // Paginated
10637             if (oPaginator) {     
10638                 // Update the paginator's totalRecords
10639                 var totalRecords = oPaginator.get('totalRecords');
10640                 if (totalRecords !== widget.Paginator.VALUE_UNLIMITED) {
10641                     oPaginator.set('totalRecords',totalRecords + 1);
10642                 }
10643
10644                 recIndex = this.getRecordIndex(oRecord);
10645                 var endRecIndex = (oPaginator.getPageRecords())[1];
10646
10647                 // New record affects the view
10648                 if (recIndex <= endRecIndex) {
10649                     // Defer UI updates to the render method
10650                     this.render();
10651                 }
10652                 
10653                 this.fireEvent("rowAddEvent", {record:oRecord});
10654                 return;
10655             }
10656             // Not paginated
10657             else {
10658                 recIndex = this.getRecordIndex(oRecord);
10659                 if(lang.isNumber(recIndex)) {
10660                     // Add the TR element
10661                     this._oChainRender.add({
10662                         method: function(oArg) {
10663                             if((this instanceof DT) && this._sId) {
10664                                 var oRecord = oArg.record;
10665                                 var recIndex = oArg.recIndex;
10666                                 var elNewTr = this._addTrEl(oRecord);
10667                                 if(elNewTr) {
10668                                     var elNext = (this._elTbody.rows[recIndex]) ? this._elTbody.rows[recIndex] : null;
10669                                     this._elTbody.insertBefore(elNewTr, elNext);
10670
10671                                     // Set FIRST/LAST
10672                                     if(recIndex === 0) {
10673                                         this._setFirstRow();
10674                                     }
10675                                     if(elNext === null) {
10676                                         this._setLastRow();
10677                                     }
10678                                     // Set EVEN/ODD
10679                                     this._setRowStripes();                           
10680                                     
10681                                     this.hideTableMessage();
10682             
10683                                     this.fireEvent("rowAddEvent", {record:oRecord});
10684                                 }
10685                             }
10686                         },
10687                         argument: {record: oRecord, recIndex: recIndex},
10688                         scope: this,
10689                         timeout: (this.get("renderLoopSize") > 0) ? 0 : -1
10690                     });
10691                     this._runRenderChain();
10692                     return;
10693                 }
10694             }            
10695         }
10696     }
10697 },
10698
10699 /**
10700  * Convenience method to add multiple rows.
10701  *
10702  * @method addRows
10703  * @param aData {Object[]} Array of object literal data for the rows.
10704  * @param index {Number} (optional) RecordSet position index at which to add data.
10705  */
10706 addRows : function(aData, index) {
10707     if(lang.isNumber(index) && (index < 0 || index > this._oRecordSet.getLength())) {
10708         return;
10709     }
10710
10711     if(lang.isArray(aData)) {
10712         var aRecords = this._oRecordSet.addRecords(aData, index);
10713         if(aRecords) {
10714             var recIndex = this.getRecordIndex(aRecords[0]);
10715             
10716             // Paginated
10717             var oPaginator = this.get('paginator');
10718             if (oPaginator) {
10719                 // Update the paginator's totalRecords
10720                 var totalRecords = oPaginator.get('totalRecords');
10721                 if (totalRecords !== widget.Paginator.VALUE_UNLIMITED) {
10722                     oPaginator.set('totalRecords',totalRecords + aRecords.length);
10723                 }
10724     
10725                 var endRecIndex = (oPaginator.getPageRecords())[1];
10726
10727                 // At least one of the new records affects the view
10728                 if (recIndex <= endRecIndex) {
10729                     this.render();
10730                 }
10731                 
10732                 this.fireEvent("rowsAddEvent", {records:aRecords});
10733                 return;
10734             }
10735             // Not paginated
10736             else {
10737                 // Add the TR elements
10738                 var loopN = this.get("renderLoopSize");
10739                 var loopEnd = recIndex + aData.length;
10740                 var nRowsNeeded = (loopEnd - recIndex); // how many needed
10741                 var isLast = (recIndex >= this._elTbody.rows.length);
10742                 this._oChainRender.add({
10743                     method: function(oArg) {
10744                         if((this instanceof DT) && this._sId) {
10745                             var aRecords = oArg.aRecords,
10746                                 i = oArg.nCurrentRow,
10747                                 j = oArg.nCurrentRecord,
10748                                 len = loopN > 0 ? Math.min(i + loopN,loopEnd) : loopEnd,
10749                                 df = document.createDocumentFragment(),
10750                                 elNext = (this._elTbody.rows[i]) ? this._elTbody.rows[i] : null;
10751                             for(; i < len; i++, j++) {
10752                                 df.appendChild(this._addTrEl(aRecords[j]));
10753                             }
10754                             this._elTbody.insertBefore(df, elNext);
10755                             oArg.nCurrentRow = i;
10756                             oArg.nCurrentRecord = j;
10757                         }
10758                     },
10759                     iterations: (loopN > 0) ? Math.ceil(loopEnd/loopN) : 1,
10760                     argument: {nCurrentRow:recIndex,nCurrentRecord:0,aRecords:aRecords},
10761                     scope: this,
10762                     timeout: (loopN > 0) ? 0 : -1
10763                 });
10764                 this._oChainRender.add({
10765                     method: function(oArg) {
10766                         var recIndex = oArg.recIndex;
10767                         // Set FIRST/LAST
10768                         if(recIndex === 0) {
10769                             this._setFirstRow();
10770                         }
10771                         if(oArg.isLast) {
10772                             this._setLastRow();
10773                         }
10774                         // Set EVEN/ODD
10775                         this._setRowStripes();                           
10776
10777                         this.fireEvent("rowsAddEvent", {records:aRecords});
10778                     },
10779                     argument: {recIndex: recIndex, isLast: isLast},
10780                     scope: this,
10781                     timeout: -1 // Needs to run immediately after the DOM insertions above
10782                 });
10783                 this._runRenderChain();
10784                 this.hideTableMessage();                
10785                 return;
10786             }            
10787         }
10788     }
10789 },
10790
10791 /**
10792  * For the given row, updates the associated Record with the given data. If the
10793  * row is on current page, the corresponding DOM elements are also updated.
10794  *
10795  * @method updateRow
10796  * @param row {YAHOO.widget.Record | Number | HTMLElement | String}
10797  * Which row to update: By Record instance, by Record's RecordSet
10798  * position index, by HTMLElement reference to the TR element, or by ID string
10799  * of the TR element.
10800  * @param oData {Object} Object literal of data for the row.
10801  */
10802 updateRow : function(row, oData) {
10803     var index = row;
10804     if (!lang.isNumber(index)) {
10805         index = this.getRecordIndex(row);
10806     }
10807
10808     // Update the Record
10809     if(lang.isNumber(index) && (index >= 0)) {
10810         var oRecordSet = this._oRecordSet,
10811             oldRecord = oRecordSet.getRecord(index);
10812
10813         if(oldRecord) {
10814             var updatedRecord = this._oRecordSet.setRecord(oData, index),
10815                 elRow = this.getTrEl(oldRecord),
10816                 // Copy data from the Record for the event that gets fired later
10817                 oldData = oldRecord ? oldRecord.getData() : null;
10818
10819             if(updatedRecord) {
10820                 // Update selected rows as necessary
10821                 var tracker = this._aSelections || [],
10822                 i=0,
10823                 oldId = oldRecord.getId(),
10824                 newId = updatedRecord.getId();
10825                 for(; i<tracker.length; i++) {
10826                     if((tracker[i] === oldId)) {
10827                         tracker[i] = newId;
10828                     }
10829                     else if(tracker[i].recordId === oldId) {
10830                         tracker[i].recordId = newId;
10831                     }
10832                 }
10833
10834                 // Update anchors as necessary
10835                 if(this._oAnchorRecord && this._oAnchorRecord.getId() === oldId) {
10836                     this._oAnchorRecord = updatedRecord;
10837                 }
10838                 if(this._oAnchorCell && this._oAnchorCell.record.getId() === oldId) {
10839                     this._oAnchorCell.record = updatedRecord;
10840                 }
10841
10842                 // Update the TR only if row is on current page
10843                 this._oChainRender.add({
10844                     method: function() {
10845                         if((this instanceof DT) && this._sId) {
10846                             // Paginated
10847                             var oPaginator = this.get('paginator');
10848                             if (oPaginator) {
10849                                 var pageStartIndex = (oPaginator.getPageRecords())[0],
10850                                     pageLastIndex = (oPaginator.getPageRecords())[1];
10851
10852                                 // At least one of the new records affects the view
10853                                 if ((index >= pageStartIndex) || (index <= pageLastIndex)) {
10854                                     this.render();
10855                                 }
10856                             }
10857                             else {
10858                                 if(elRow) {
10859                                     this._updateTrEl(elRow, updatedRecord);
10860                                 }
10861                                 else {
10862                                     this.getTbodyEl().appendChild(this._addTrEl(updatedRecord));
10863                                 }
10864                             }
10865                             this.fireEvent("rowUpdateEvent", {record:updatedRecord, oldData:oldData});
10866                         }
10867                     },
10868                     scope: this,
10869                     timeout: (this.get("renderLoopSize") > 0) ? 0 : -1
10870                 });
10871                 this._runRenderChain();
10872                 return;
10873             }
10874         }
10875     }
10876     return;
10877 },
10878
10879 /**
10880  * Starting with the given row, updates associated Records with the given data.
10881  * The number of rows to update are determined by the array of data provided.
10882  * Undefined data (i.e., not an object literal) causes a row to be skipped. If
10883  * any of the rows are on current page, the corresponding DOM elements are also
10884  * updated.
10885  *
10886  * @method updateRows
10887  * @param startrow {YAHOO.widget.Record | Number | HTMLElement | String}
10888  * Starting row to update: By Record instance, by Record's RecordSet
10889  * position index, by HTMLElement reference to the TR element, or by ID string
10890  * of the TR element.
10891  * @param aData {Object[]} Array of object literal of data for the rows.
10892  */
10893 updateRows : function(startrow, aData) {
10894     if(lang.isArray(aData)) {
10895         var startIndex = startrow,
10896             oRecordSet = this._oRecordSet,
10897             lastRowIndex = oRecordSet.getLength();
10898
10899         if (!lang.isNumber(startrow)) {
10900             startIndex = this.getRecordIndex(startrow);
10901         }
10902             
10903         if(lang.isNumber(startIndex) && (startIndex >= 0) && (startIndex < oRecordSet.getLength())) {
10904             var lastIndex = startIndex + aData.length,
10905                 aOldRecords = oRecordSet.getRecords(startIndex, aData.length),
10906                 aNewRecords = oRecordSet.setRecords(aData, startIndex);
10907             if(aNewRecords) {
10908                 var tracker = this._aSelections || [],
10909                     i=0, j, newRecord, newId, oldId,
10910                     anchorRecord = this._oAnchorRecord ? this._oAnchorRecord.getId() : null,
10911                     anchorCell = this._oAnchorCell ? this._oAnchorCell.record.getId() : null;
10912                 for(; i<aOldRecords.length; i++) {
10913                     oldId = aOldRecords[i].getId();
10914                     newRecord = aNewRecords[i];
10915                     newId = newRecord.getId();
10916
10917                     // Update selected rows as necessary
10918                     for(j=0; j<tracker.length; j++) {
10919                         if((tracker[j] === oldId)) {
10920                             tracker[j] = newId;
10921                         }
10922                         else if(tracker[j].recordId === oldId) {
10923                             tracker[j].recordId = newId;
10924                         }
10925                     }
10926
10927                     // Update anchors as necessary
10928                     if(anchorRecord && anchorRecord === oldId) {
10929                         this._oAnchorRecord = newRecord;
10930                     }
10931                     if(anchorCell && anchorCell === oldId) {
10932                         this._oAnchorCell.record = newRecord;
10933                     }
10934                }
10935
10936                 // Paginated
10937                 var oPaginator = this.get('paginator');
10938                 if (oPaginator) {
10939                     var pageStartIndex = (oPaginator.getPageRecords())[0],
10940                         pageLastIndex = (oPaginator.getPageRecords())[1];
10941     
10942                     // At least one of the new records affects the view
10943                     if ((startIndex >= pageStartIndex) || (lastIndex <= pageLastIndex)) {
10944                         this.render();
10945                     }
10946
10947                     this.fireEvent("rowsAddEvent", {newRecords:aNewRecords, oldRecords:aOldRecords});
10948                     return;
10949                 }
10950                 // Not paginated
10951                 else {
10952                     // Update the TR elements
10953                     var loopN = this.get("renderLoopSize"),
10954                         rowCount = aData.length, // how many needed
10955                         isLast = (lastIndex >= lastRowIndex),
10956                         isAdding = (lastIndex > lastRowIndex);
10957                                            
10958                     this._oChainRender.add({
10959                         method: function(oArg) {
10960                             if((this instanceof DT) && this._sId) {
10961                                 var aRecords = oArg.aRecords,
10962                                     i = oArg.nCurrentRow,
10963                                     j = oArg.nDataPointer,
10964                                     len = loopN > 0 ? Math.min(i+loopN, startIndex+aRecords.length) : startIndex+aRecords.length;
10965                                     
10966                                 for(; i < len; i++,j++) {
10967                                     if(isAdding && (i>=lastRowIndex)) {
10968                                         this._elTbody.appendChild(this._addTrEl(aRecords[j]));
10969                                     }
10970                                     else {
10971                                         this._updateTrEl(this._elTbody.rows[i], aRecords[j]);
10972                                     }
10973                                 }
10974                                 oArg.nCurrentRow = i;
10975                                 oArg.nDataPointer = j;
10976                             }
10977                         },
10978                         iterations: (loopN > 0) ? Math.ceil(rowCount/loopN) : 1,
10979                         argument: {nCurrentRow:startIndex,aRecords:aNewRecords,nDataPointer:0,isAdding:isAdding},
10980                         scope: this,
10981                         timeout: (loopN > 0) ? 0 : -1
10982                     });
10983                     this._oChainRender.add({
10984                         method: function(oArg) {
10985                             var recIndex = oArg.recIndex;
10986                             // Set FIRST/LAST
10987                             if(recIndex === 0) {
10988                                 this._setFirstRow();
10989                             }
10990                             if(oArg.isLast) {
10991                                 this._setLastRow();
10992                             }
10993                             // Set EVEN/ODD
10994                             this._setRowStripes();                           
10995     
10996                             this.fireEvent("rowsAddEvent", {newRecords:aNewRecords, oldRecords:aOldRecords});
10997                         },
10998                         argument: {recIndex: startIndex, isLast: isLast},
10999                         scope: this,
11000                         timeout: -1 // Needs to run immediately after the DOM insertions above
11001                     });
11002                     this._runRenderChain();
11003                     this.hideTableMessage();                
11004                     return;
11005                 }            
11006             }
11007         }
11008     }
11009 },
11010
11011 /**
11012  * Deletes the given row's Record from the RecordSet. If the row is on current page,
11013  * the corresponding DOM elements are also deleted.
11014  *
11015  * @method deleteRow
11016  * @param row {HTMLElement | String | Number} DOM element reference or ID string
11017  * to DataTable page element or RecordSet index.
11018  */
11019 deleteRow : function(row) {
11020     var nRecordIndex = (lang.isNumber(row)) ? row : this.getRecordIndex(row);
11021     if(lang.isNumber(nRecordIndex)) {
11022         var oRecord = this.getRecord(nRecordIndex);
11023         if(oRecord) {
11024             var nTrIndex = this.getTrIndex(nRecordIndex);
11025             
11026             // Remove from selection tracker if there
11027             var sRecordId = oRecord.getId();
11028             var tracker = this._aSelections || [];
11029             for(var j=tracker.length-1; j>-1; j--) {
11030                 if((lang.isString(tracker[j]) && (tracker[j] === sRecordId)) ||
11031                         (lang.isObject(tracker[j]) && (tracker[j].recordId === sRecordId))) {
11032                     tracker.splice(j,1);
11033                 }
11034             }
11035     
11036             // Delete Record from RecordSet
11037             var oData = this._oRecordSet.deleteRecord(nRecordIndex);
11038     
11039             // Update the UI
11040             if(oData) {
11041                 // If paginated and the deleted row was on this or a prior page, just
11042                 // re-render
11043                 var oPaginator = this.get('paginator');
11044                 if (oPaginator) {
11045                     // Update the paginator's totalRecords
11046                     var totalRecords = oPaginator.get('totalRecords'),
11047                         // must capture before the totalRecords change because
11048                         // Paginator shifts to previous page automatically
11049                         rng = oPaginator.getPageRecords();
11050
11051                     if (totalRecords !== widget.Paginator.VALUE_UNLIMITED) {
11052                         oPaginator.set('totalRecords',totalRecords - 1);
11053                     }
11054     
11055                     // The deleted record was on this or a prior page, re-render
11056                     if (!rng || nRecordIndex <= rng[1]) {
11057                         this.render();
11058                     }
11059
11060                     this._oChainRender.add({
11061                         method: function() {
11062                             if((this instanceof DT) && this._sId) {
11063                                 this.fireEvent("rowDeleteEvent", {recordIndex:nRecordIndex, oldData:oData, trElIndex:nTrIndex});
11064                             }
11065                         },
11066                         scope: this,
11067                         timeout: (this.get("renderLoopSize") > 0) ? 0 : -1
11068                     });
11069                     this._runRenderChain();
11070                 }
11071                 // Not paginated
11072                 else {
11073                     if(lang.isNumber(nTrIndex)) {
11074                         this._oChainRender.add({
11075                             method: function() {
11076                                 if((this instanceof DT) && this._sId) {
11077                                     var isLast = (nRecordIndex === this._oRecordSet.getLength());//(nTrIndex == this.getLastTrEl().sectionRowIndex);
11078                                     this._deleteTrEl(nTrIndex);
11079                     
11080                                     // Post-delete tasks
11081                                     if(this._elTbody.rows.length > 0) {
11082                                         // Set FIRST/LAST
11083                                         if(nTrIndex === 0) {
11084                                             this._setFirstRow();
11085                                         }
11086                                         if(isLast) {
11087                                             this._setLastRow();
11088                                         }
11089                                         // Set EVEN/ODD
11090                                         if(nTrIndex != this._elTbody.rows.length) {
11091                                             this._setRowStripes(nTrIndex);
11092                                         }                                
11093                                     }
11094                     
11095                                     this.fireEvent("rowDeleteEvent", {recordIndex:nRecordIndex,oldData:oData, trElIndex:nTrIndex});
11096                                 }
11097                             },
11098                             scope: this,
11099                             timeout: (this.get("renderLoopSize") > 0) ? 0 : -1
11100                         });
11101                         this._runRenderChain();
11102                         return;
11103                     }
11104                 }
11105             }
11106         }
11107     }
11108     return null;
11109 },
11110
11111 /**
11112  * Convenience method to delete multiple rows.
11113  *
11114  * @method deleteRows
11115  * @param row {HTMLElement | String | Number} DOM element reference or ID string
11116  * to DataTable page element or RecordSet index.
11117  * @param count {Number} (optional) How many rows to delete. A negative value
11118  * will delete towards the beginning.
11119  */
11120 deleteRows : function(row, count) {
11121     var nRecordIndex = (lang.isNumber(row)) ? row : this.getRecordIndex(row);
11122     if(lang.isNumber(nRecordIndex)) {
11123         var oRecord = this.getRecord(nRecordIndex);
11124         if(oRecord) {
11125             var nTrIndex = this.getTrIndex(nRecordIndex);
11126             
11127             // Remove from selection tracker if there
11128             var sRecordId = oRecord.getId();
11129             var tracker = this._aSelections || [];
11130             for(var j=tracker.length-1; j>-1; j--) {
11131                 if((lang.isString(tracker[j]) && (tracker[j] === sRecordId)) ||
11132                         (lang.isObject(tracker[j]) && (tracker[j].recordId === sRecordId))) {
11133                     tracker.splice(j,1);
11134                 }
11135             }
11136     
11137             // Delete Record from RecordSet
11138             var highIndex = nRecordIndex;
11139             var lowIndex = nRecordIndex;
11140         
11141             // Validate count and account for negative value
11142             if(count && lang.isNumber(count)) {
11143                 highIndex = (count > 0) ? nRecordIndex + count -1 : nRecordIndex;
11144                 lowIndex = (count > 0) ? nRecordIndex : nRecordIndex + count + 1;
11145                 count = (count > 0) ? count : count*-1;
11146                 if(lowIndex < 0) {
11147                     lowIndex = 0;
11148                     count = highIndex - lowIndex + 1;
11149                 }
11150             }
11151             else {
11152                 count = 1;
11153             }
11154             
11155             var aData = this._oRecordSet.deleteRecords(lowIndex, count);
11156     
11157             // Update the UI
11158             if(aData) {
11159                 var oPaginator = this.get('paginator'),
11160                     loopN = this.get("renderLoopSize");
11161                 // If paginated and the deleted row was on this or a prior page, just
11162                 // re-render
11163                 if (oPaginator) {
11164                     // Update the paginator's totalRecords
11165                     var totalRecords = oPaginator.get('totalRecords'),
11166                         // must capture before the totalRecords change because
11167                         // Paginator shifts to previous page automatically
11168                         rng = oPaginator.getPageRecords();
11169
11170                     if (totalRecords !== widget.Paginator.VALUE_UNLIMITED) {
11171                         oPaginator.set('totalRecords',totalRecords - aData.length);
11172                     }
11173     
11174                     // The records were on this or a prior page, re-render
11175                     if (!rng || lowIndex <= rng[1]) {
11176                         this.render();
11177                     }
11178
11179                     this._oChainRender.add({
11180                         method: function(oArg) {
11181                             if((this instanceof DT) && this._sId) {
11182                                 this.fireEvent("rowsDeleteEvent", {recordIndex:lowIndex, oldData:aData, count:count});
11183                             }
11184                         },
11185                         scope: this,
11186                         timeout: (loopN > 0) ? 0 : -1
11187                     });
11188                     this._runRenderChain();
11189                     return;
11190                 }
11191                 // Not paginated
11192                 else {
11193                     if(lang.isNumber(nTrIndex)) {
11194                         // Delete the TR elements starting with highest index
11195                         var loopEnd = lowIndex;
11196                         var nRowsNeeded = count; // how many needed
11197                         this._oChainRender.add({
11198                             method: function(oArg) {
11199                                 if((this instanceof DT) && this._sId) {
11200                                     var i = oArg.nCurrentRow,
11201                                         len = (loopN > 0) ? (Math.max(i - loopN,loopEnd)-1) : loopEnd-1;
11202                                     for(; i>len; --i) {
11203                                         this._deleteTrEl(i);
11204                                     }
11205                                     oArg.nCurrentRow = i;
11206                                 }
11207                             },
11208                             iterations: (loopN > 0) ? Math.ceil(count/loopN) : 1,
11209                             argument: {nCurrentRow:highIndex},
11210                             scope: this,
11211                             timeout: (loopN > 0) ? 0 : -1
11212                         });
11213                         this._oChainRender.add({
11214                             method: function() {    
11215                                 // Post-delete tasks
11216                                 if(this._elTbody.rows.length > 0) {
11217                                     this._setFirstRow();
11218                                     this._setLastRow();
11219                                     this._setRowStripes();
11220                                 }
11221                                 
11222                                 this.fireEvent("rowsDeleteEvent", {recordIndex:lowIndex, oldData:aData, count:count});
11223                             },
11224                             scope: this,
11225                             timeout: -1 // Needs to run immediately after the DOM deletions above
11226                         });
11227                         this._runRenderChain();
11228                         return;
11229                     }
11230                 }
11231             }
11232         }
11233     }
11234     return null;
11235 },
11236
11237
11238
11239
11240
11241
11242
11243
11244
11245
11246
11247
11248
11249
11250
11251
11252
11253
11254
11255
11256
11257
11258
11259
11260
11261
11262
11263
11264
11265
11266
11267
11268
11269
11270
11271
11272
11273
11274
11275
11276
11277
11278
11279
11280
11281
11282 // CELL FUNCTIONS
11283
11284 /**
11285  * Outputs markup into the given TD based on given Record.
11286  *
11287  * @method formatCell
11288  * @param elLiner {HTMLElement} The liner DIV element within the TD.
11289  * @param oRecord {YAHOO.widget.Record} (Optional) Record instance.
11290  * @param oColumn {YAHOO.widget.Column} (Optional) Column instance.
11291  */
11292 formatCell : function(elLiner, oRecord, oColumn) {
11293     if(!oRecord) {
11294         oRecord = this.getRecord(elLiner);
11295     }
11296     if(!oColumn) {
11297         oColumn = this.getColumn(this.getCellIndex(elLiner.parentNode));
11298     }
11299
11300     if(oRecord && oColumn) {
11301         var sField = oColumn.field;
11302         var oData = oRecord.getData(sField);
11303
11304         var fnFormatter = typeof oColumn.formatter === 'function' ?
11305                           oColumn.formatter :
11306                           DT.Formatter[oColumn.formatter+''] ||
11307                           DT.Formatter.defaultFormatter;
11308
11309         // Apply special formatter
11310         if(fnFormatter) {
11311             fnFormatter.call(this, elLiner, oRecord, oColumn, oData);
11312         }
11313         else {
11314             elLiner.innerHTML = oData;
11315         }
11316
11317         this.fireEvent("cellFormatEvent", {record:oRecord, column:oColumn, key:oColumn.key, el:elLiner});
11318     }
11319     else {
11320     }
11321 },
11322
11323 /**
11324  * For the given row and column, updates the Record with the given data. If the
11325  * cell is on current page, the corresponding DOM elements are also updated.
11326  *
11327  * @method updateCell
11328  * @param oRecord {YAHOO.widget.Record} Record instance.
11329  * @param oColumn {YAHOO.widget.Column | String | Number} A Column key, or a ColumnSet key index.
11330  * @param oData {Object} New data value for the cell.
11331  * @param skipRender {Boolean} Skips render step. Editors that update multiple
11332  * cells in ScrollingDataTable should render only on the last call to updateCell().
11333  */
11334 updateCell : function(oRecord, oColumn, oData, skipRender) {
11335     // Validate Column and Record
11336     oColumn = (oColumn instanceof YAHOO.widget.Column) ? oColumn : this.getColumn(oColumn);
11337     if(oColumn && oColumn.getField() && (oRecord instanceof YAHOO.widget.Record)) {
11338         var sKey = oColumn.getField(),
11339         
11340         // Copy data from the Record for the event that gets fired later
11341         //var oldData = YAHOO.widget.DataTable._cloneObject(oRecord.getData());
11342             oldData = oRecord.getData(sKey);
11343
11344         // Update Record with new data
11345         this._oRecordSet.updateRecordValue(oRecord, sKey, oData);
11346     
11347         // Update the TD only if row is on current page
11348         var elTd = this.getTdEl({record: oRecord, column: oColumn});
11349         if(elTd) {
11350             this._oChainRender.add({
11351                 method: function() {
11352                     if((this instanceof DT) && this._sId) {
11353                         this.formatCell(elTd.firstChild, oRecord, oColumn);
11354                         this.fireEvent("cellUpdateEvent", {record:oRecord, column: oColumn, oldData:oldData});
11355                     }
11356                 },
11357                 scope: this,
11358                 timeout: (this.get("renderLoopSize") > 0) ? 0 : -1
11359             });
11360             // Bug 2529024
11361             if(!skipRender) {
11362                 this._runRenderChain();
11363             }
11364         }
11365         else {
11366             this.fireEvent("cellUpdateEvent", {record:oRecord, column: oColumn, oldData:oldData});
11367         }
11368     }
11369 },
11370
11371
11372
11373
11374
11375
11376
11377
11378
11379
11380
11381
11382
11383
11384
11385
11386
11387
11388
11389
11390
11391
11392
11393
11394
11395
11396
11397
11398
11399
11400
11401
11402
11403
11404
11405
11406
11407
11408
11409
11410
11411
11412
11413
11414
11415
11416
11417
11418
11419
11420
11421 // PAGINATION
11422 /**
11423  * Method executed during set() operation for the "paginator" attribute.
11424  * Adds and/or severs event listeners between DataTable and Paginator
11425  *
11426  * @method _updatePaginator
11427  * @param newPag {Paginator} Paginator instance (or null) for DataTable to use
11428  * @private
11429  */
11430 _updatePaginator : function (newPag) {
11431     var oldPag = this.get('paginator');
11432     if (oldPag && newPag !== oldPag) {
11433         oldPag.unsubscribe('changeRequest', this.onPaginatorChangeRequest, this, true);
11434     }
11435     if (newPag) {
11436         newPag.subscribe('changeRequest', this.onPaginatorChangeRequest, this, true);
11437     }
11438 },
11439
11440 /**
11441  * Update the UI infrastructure in response to a "paginator" attribute change.
11442  *
11443  * @method _handlePaginatorChange
11444  * @param e {Object} Change event object containing keys 'type','newValue',
11445  *                   and 'prevValue'
11446  * @private
11447  */
11448 _handlePaginatorChange : function (e) {
11449     if (e.prevValue === e.newValue) { return; }
11450
11451     var newPag     = e.newValue,
11452         oldPag     = e.prevValue,
11453         containers = this._defaultPaginatorContainers();
11454
11455     if (oldPag) {
11456         if (oldPag.getContainerNodes()[0] == containers[0]) {
11457             oldPag.set('containers',[]);
11458         }
11459         oldPag.destroy();
11460
11461         // Convenience: share the default containers if possible.
11462         // Otherwise, remove the default containers from the DOM.
11463         if (containers[0]) {
11464             if (newPag && !newPag.getContainerNodes().length) {
11465                 newPag.set('containers',containers);
11466             } else {
11467                 // No new Paginator to use existing containers, OR new
11468                 // Paginator has configured containers.
11469                 for (var i = containers.length - 1; i >= 0; --i) {
11470                     if (containers[i]) {
11471                         containers[i].parentNode.removeChild(containers[i]);
11472                     }
11473                 }
11474             }
11475         }
11476     }
11477
11478     if (!this._bInit) {
11479         this.render();
11480
11481     }
11482
11483     if (newPag) {
11484         this.renderPaginator();
11485     }
11486
11487 },
11488
11489 /**
11490  * Returns the default containers used for Paginators.  If create param is
11491  * passed, the containers will be created and added to the DataTable container.
11492  *
11493  * @method _defaultPaginatorContainers
11494  * @param create {boolean} Create the default containers if not found
11495  * @private
11496  */
11497 _defaultPaginatorContainers : function (create) {
11498     var above_id = this._sId + '-paginator0',
11499         below_id = this._sId + '-paginator1',
11500         above    = Dom.get(above_id),
11501         below    = Dom.get(below_id);
11502
11503     if (create && (!above || !below)) {
11504         // One above and one below the table
11505         if (!above) {
11506             above    = document.createElement('div');
11507             above.id = above_id;
11508             Dom.addClass(above, DT.CLASS_PAGINATOR);
11509
11510             this._elContainer.insertBefore(above,this._elContainer.firstChild);
11511         }
11512
11513         if (!below) {
11514             below    = document.createElement('div');
11515             below.id = below_id;
11516             Dom.addClass(below, DT.CLASS_PAGINATOR);
11517
11518             this._elContainer.appendChild(below);
11519         }
11520     }
11521
11522     return [above,below];
11523 },
11524
11525 /**
11526  * Calls Paginator's destroy() method
11527  *
11528  * @method _destroyPaginator
11529  * @private
11530  */
11531 _destroyPaginator : function () {
11532     var oldPag = this.get('paginator');
11533     if (oldPag) {
11534         oldPag.destroy();
11535     }
11536 },
11537
11538 /**
11539  * Renders the Paginator to the DataTable UI
11540  *
11541  * @method renderPaginator
11542  */
11543 renderPaginator : function () {
11544     var pag = this.get("paginator");
11545     if (!pag) { return; }
11546
11547     // Add the containers if the Paginator is not configured with containers
11548     if (!pag.getContainerNodes().length) {
11549         pag.set('containers',this._defaultPaginatorContainers(true));
11550     }
11551
11552     pag.render();
11553 },
11554
11555 /**
11556  * Overridable method gives implementers a hook to show loading message before
11557  * changing Paginator value.
11558  *
11559  * @method doBeforePaginatorChange
11560  * @param oPaginatorState {Object} An object literal describing the proposed pagination state.
11561  * @return {Boolean} Return true to continue changing Paginator value.
11562  */
11563 doBeforePaginatorChange : function(oPaginatorState) {
11564     this.showTableMessage(this.get("MSG_LOADING"), DT.CLASS_LOADING);
11565     return true;
11566 },
11567
11568 /**
11569  * Responds to new Pagination states. By default, updates the UI to reflect the
11570  * new state. If "dynamicData" is true, current selections are purged before
11571  * a request is sent to the DataSource for data for the new state (using the
11572  * request returned by "generateRequest()").
11573  *  
11574  * @method onPaginatorChangeRequest
11575  * @param oPaginatorState {Object} An object literal describing the proposed pagination state.
11576  */
11577 onPaginatorChangeRequest : function (oPaginatorState) {
11578     var ok = this.doBeforePaginatorChange(oPaginatorState);
11579     if(ok) {
11580         // Server-side pagination
11581         if(this.get("dynamicData")) {
11582             // Get the current state
11583             var oState = this.getState();
11584             
11585             // Update pagination values
11586             oState.pagination = oPaginatorState;
11587     
11588             // Get the request for the new state
11589             var request = this.get("generateRequest")(oState, this);
11590             
11591             // Purge selections
11592             this.unselectAllRows();
11593             this.unselectAllCells();
11594             
11595             // Get the new data from the server
11596             var callback = {
11597                 success : this.onDataReturnSetRows,
11598                 failure : this.onDataReturnSetRows,
11599                 argument : oState, // Pass along the new state to the callback
11600                 scope : this
11601             };
11602             this._oDataSource.sendRequest(request, callback);
11603         }
11604         // Client-side pagination
11605         else {
11606             // Set the core pagination values silently (the second param)
11607             // to avoid looping back through the changeRequest mechanism
11608             oPaginatorState.paginator.setStartIndex(oPaginatorState.recordOffset,true);
11609             oPaginatorState.paginator.setRowsPerPage(oPaginatorState.rowsPerPage,true);
11610     
11611             // Update the UI
11612             this.render();
11613         }
11614     }
11615     else {
11616     }
11617 },
11618
11619
11620
11621
11622
11623
11624
11625
11626
11627
11628
11629
11630
11631
11632
11633
11634
11635
11636
11637
11638
11639
11640
11641
11642
11643
11644
11645
11646
11647
11648
11649
11650
11651
11652
11653
11654
11655
11656
11657
11658
11659
11660
11661
11662
11663
11664
11665
11666
11667
11668 // SELECTION/HIGHLIGHTING
11669
11670 /*
11671  * Reference to last highlighted cell element
11672  *
11673  * @property _elLastHighlightedTd
11674  * @type HTMLElement
11675  * @private
11676  */
11677 _elLastHighlightedTd : null,
11678
11679 /*
11680  * ID string of last highlighted row element
11681  *
11682  * @property _sLastHighlightedTrElId
11683  * @type String
11684  * @private
11685  */
11686 //_sLastHighlightedTrElId : null,
11687
11688 /**
11689  * Array to track row selections (by sRecordId) and/or cell selections
11690  * (by {recordId:sRecordId, columnKey:sColumnKey})
11691  *
11692  * @property _aSelections
11693  * @type Object[]
11694  * @private
11695  */
11696 _aSelections : null,
11697
11698 /**
11699  * Record instance of the row selection anchor.
11700  *
11701  * @property _oAnchorRecord
11702  * @type YAHOO.widget.Record
11703  * @private
11704  */
11705 _oAnchorRecord : null,
11706
11707 /**
11708  * Object literal representing cell selection anchor:
11709  * {recordId:sRecordId, columnKey:sColumnKey}.
11710  *
11711  * @property _oAnchorCell
11712  * @type Object
11713  * @private
11714  */
11715 _oAnchorCell : null,
11716
11717 /**
11718  * Convenience method to remove the class YAHOO.widget.DataTable.CLASS_SELECTED
11719  * from all TR elements on the page.
11720  *
11721  * @method _unselectAllTrEls
11722  * @private
11723  */
11724 _unselectAllTrEls : function() {
11725     var selectedRows = Dom.getElementsByClassName(DT.CLASS_SELECTED,"tr",this._elTbody);
11726     Dom.removeClass(selectedRows, DT.CLASS_SELECTED);
11727 },
11728
11729 /**
11730  * Returns object literal of values that represent the selection trigger. Used
11731  * to determine selection behavior resulting from a key event.
11732  *
11733  * @method _getSelectionTrigger
11734  * @private
11735  */
11736 _getSelectionTrigger : function() {
11737     var sMode = this.get("selectionMode");
11738     var oTrigger = {};
11739     var oTriggerCell, oTriggerRecord, nTriggerRecordIndex, elTriggerRow, nTriggerTrIndex;
11740
11741     // Cell mode
11742     if((sMode == "cellblock") || (sMode == "cellrange") || (sMode == "singlecell")) {
11743         oTriggerCell = this.getLastSelectedCell();
11744         // No selected cells found
11745         if(!oTriggerCell) {
11746             return null;
11747         }
11748         else {
11749             oTriggerRecord = this.getRecord(oTriggerCell.recordId);
11750             nTriggerRecordIndex = this.getRecordIndex(oTriggerRecord);
11751             elTriggerRow = this.getTrEl(oTriggerRecord);
11752             nTriggerTrIndex = this.getTrIndex(elTriggerRow);
11753
11754             // Selected cell not found on this page
11755             if(nTriggerTrIndex === null) {
11756                 return null;
11757             }
11758             else {
11759                 oTrigger.record = oTriggerRecord;
11760                 oTrigger.recordIndex = nTriggerRecordIndex;
11761                 oTrigger.el = this.getTdEl(oTriggerCell);
11762                 oTrigger.trIndex = nTriggerTrIndex;
11763                 oTrigger.column = this.getColumn(oTriggerCell.columnKey);
11764                 oTrigger.colKeyIndex = oTrigger.column.getKeyIndex();
11765                 oTrigger.cell = oTriggerCell;
11766                 return oTrigger;
11767             }
11768         }
11769     }
11770     // Row mode
11771     else {
11772         oTriggerRecord = this.getLastSelectedRecord();
11773         // No selected rows found
11774         if(!oTriggerRecord) {
11775                 return null;
11776         }
11777         else {
11778             // Selected row found, but is it on current page?
11779             oTriggerRecord = this.getRecord(oTriggerRecord);
11780             nTriggerRecordIndex = this.getRecordIndex(oTriggerRecord);
11781             elTriggerRow = this.getTrEl(oTriggerRecord);
11782             nTriggerTrIndex = this.getTrIndex(elTriggerRow);
11783
11784             // Selected row not found on this page
11785             if(nTriggerTrIndex === null) {
11786                 return null;
11787             }
11788             else {
11789                 oTrigger.record = oTriggerRecord;
11790                 oTrigger.recordIndex = nTriggerRecordIndex;
11791                 oTrigger.el = elTriggerRow;
11792                 oTrigger.trIndex = nTriggerTrIndex;
11793                 return oTrigger;
11794             }
11795         }
11796     }
11797 },
11798
11799 /**
11800  * Returns object literal of values that represent the selection anchor. Used
11801  * to determine selection behavior resulting from a user event.
11802  *
11803  * @method _getSelectionAnchor
11804  * @param oTrigger {Object} (Optional) Object literal of selection trigger values
11805  * (for key events).
11806  * @private
11807  */
11808 _getSelectionAnchor : function(oTrigger) {
11809     var sMode = this.get("selectionMode");
11810     var oAnchor = {};
11811     var oAnchorRecord, nAnchorRecordIndex, nAnchorTrIndex;
11812
11813     // Cell mode
11814     if((sMode == "cellblock") || (sMode == "cellrange") || (sMode == "singlecell")) {
11815         // Validate anchor cell
11816         var oAnchorCell = this._oAnchorCell;
11817         if(!oAnchorCell) {
11818             if(oTrigger) {
11819                 oAnchorCell = this._oAnchorCell = oTrigger.cell;
11820             }
11821             else {
11822                 return null;
11823             }
11824         }
11825         oAnchorRecord = this._oAnchorCell.record;
11826         nAnchorRecordIndex = this._oRecordSet.getRecordIndex(oAnchorRecord);
11827         nAnchorTrIndex = this.getTrIndex(oAnchorRecord);
11828         // If anchor cell is not on this page...
11829         if(nAnchorTrIndex === null) {
11830             // ...set TR index equal to top TR
11831             if(nAnchorRecordIndex < this.getRecordIndex(this.getFirstTrEl())) {
11832                 nAnchorTrIndex = 0;
11833             }
11834             // ...set TR index equal to bottom TR
11835             else {
11836                 nAnchorTrIndex = this.getRecordIndex(this.getLastTrEl());
11837             }
11838         }
11839
11840         oAnchor.record = oAnchorRecord;
11841         oAnchor.recordIndex = nAnchorRecordIndex;
11842         oAnchor.trIndex = nAnchorTrIndex;
11843         oAnchor.column = this._oAnchorCell.column;
11844         oAnchor.colKeyIndex = oAnchor.column.getKeyIndex();
11845         oAnchor.cell = oAnchorCell;
11846         return oAnchor;
11847     }
11848     // Row mode
11849     else {
11850         oAnchorRecord = this._oAnchorRecord;
11851         if(!oAnchorRecord) {
11852             if(oTrigger) {
11853                 oAnchorRecord = this._oAnchorRecord = oTrigger.record;
11854             }
11855             else {
11856                 return null;
11857             }
11858         }
11859
11860         nAnchorRecordIndex = this.getRecordIndex(oAnchorRecord);
11861         nAnchorTrIndex = this.getTrIndex(oAnchorRecord);
11862         // If anchor row is not on this page...
11863         if(nAnchorTrIndex === null) {
11864             // ...set TR index equal to top TR
11865             if(nAnchorRecordIndex < this.getRecordIndex(this.getFirstTrEl())) {
11866                 nAnchorTrIndex = 0;
11867             }
11868             // ...set TR index equal to bottom TR
11869             else {
11870                 nAnchorTrIndex = this.getRecordIndex(this.getLastTrEl());
11871             }
11872         }
11873
11874         oAnchor.record = oAnchorRecord;
11875         oAnchor.recordIndex = nAnchorRecordIndex;
11876         oAnchor.trIndex = nAnchorTrIndex;
11877         return oAnchor;
11878     }
11879 },
11880
11881 /**
11882  * Determines selection behavior resulting from a mouse event when selection mode
11883  * is set to "standard".
11884  *
11885  * @method _handleStandardSelectionByMouse
11886  * @param oArgs.event {HTMLEvent} Event object.
11887  * @param oArgs.target {HTMLElement} Target element.
11888  * @private
11889  */
11890 _handleStandardSelectionByMouse : function(oArgs) {
11891     var elTarget = oArgs.target;
11892
11893     // Validate target row
11894     var elTargetRow = this.getTrEl(elTarget);
11895     if(elTargetRow) {
11896         var e = oArgs.event;
11897         var bSHIFT = e.shiftKey;
11898         var bCTRL = e.ctrlKey || ((navigator.userAgent.toLowerCase().indexOf("mac") != -1) && e.metaKey);
11899
11900         var oTargetRecord = this.getRecord(elTargetRow);
11901         var nTargetRecordIndex = this._oRecordSet.getRecordIndex(oTargetRecord);
11902
11903         var oAnchor = this._getSelectionAnchor();
11904
11905         var i;
11906
11907         // Both SHIFT and CTRL
11908         if(bSHIFT && bCTRL) {
11909             // Validate anchor
11910             if(oAnchor) {
11911                 if(this.isSelected(oAnchor.record)) {
11912                     // Select all rows between anchor row and target row, including target row
11913                     if(oAnchor.recordIndex < nTargetRecordIndex) {
11914                         for(i=oAnchor.recordIndex+1; i<=nTargetRecordIndex; i++) {
11915                             if(!this.isSelected(i)) {
11916                                 this.selectRow(i);
11917                             }
11918                         }
11919                     }
11920                     // Select all rows between target row and anchor row, including target row
11921                     else {
11922                         for(i=oAnchor.recordIndex-1; i>=nTargetRecordIndex; i--) {
11923                             if(!this.isSelected(i)) {
11924                                 this.selectRow(i);
11925                             }
11926                         }
11927                     }
11928                 }
11929                 else {
11930                     // Unselect all rows between anchor row and target row
11931                     if(oAnchor.recordIndex < nTargetRecordIndex) {
11932                         for(i=oAnchor.recordIndex+1; i<=nTargetRecordIndex-1; i++) {
11933                             if(this.isSelected(i)) {
11934                                 this.unselectRow(i);
11935                             }
11936                         }
11937                     }
11938                     // Unselect all rows between target row and anchor row
11939                     else {
11940                         for(i=nTargetRecordIndex+1; i<=oAnchor.recordIndex-1; i++) {
11941                             if(this.isSelected(i)) {
11942                                 this.unselectRow(i);
11943                             }
11944                         }
11945                     }
11946                     // Select the target row
11947                     this.selectRow(oTargetRecord);
11948                 }
11949             }
11950             // Invalid anchor
11951             else {
11952                 // Set anchor
11953                 this._oAnchorRecord = oTargetRecord;
11954
11955                 // Toggle selection of target
11956                 if(this.isSelected(oTargetRecord)) {
11957                     this.unselectRow(oTargetRecord);
11958                 }
11959                 else {
11960                     this.selectRow(oTargetRecord);
11961                 }
11962             }
11963         }
11964          // Only SHIFT
11965         else if(bSHIFT) {
11966             this.unselectAllRows();
11967
11968             // Validate anchor
11969             if(oAnchor) {
11970                 // Select all rows between anchor row and target row,
11971                 // including the anchor row and target row
11972                 if(oAnchor.recordIndex < nTargetRecordIndex) {
11973                     for(i=oAnchor.recordIndex; i<=nTargetRecordIndex; i++) {
11974                         this.selectRow(i);
11975                     }
11976                 }
11977                 // Select all rows between target row and anchor row,
11978                 // including the target row and anchor row
11979                 else {
11980                     for(i=oAnchor.recordIndex; i>=nTargetRecordIndex; i--) {
11981                         this.selectRow(i);
11982                     }
11983                 }
11984             }
11985             // Invalid anchor
11986             else {
11987                 // Set anchor
11988                 this._oAnchorRecord = oTargetRecord;
11989
11990                 // Select target row only
11991                 this.selectRow(oTargetRecord);
11992             }
11993         }
11994         // Only CTRL
11995         else if(bCTRL) {
11996             // Set anchor
11997             this._oAnchorRecord = oTargetRecord;
11998
11999             // Toggle selection of target
12000             if(this.isSelected(oTargetRecord)) {
12001                 this.unselectRow(oTargetRecord);
12002             }
12003             else {
12004                 this.selectRow(oTargetRecord);
12005             }
12006         }
12007         // Neither SHIFT nor CTRL
12008         else {
12009             this._handleSingleSelectionByMouse(oArgs);
12010             return;
12011         }
12012     }
12013 },
12014
12015 /**
12016  * Determines selection behavior resulting from a key event when selection mode
12017  * is set to "standard".
12018  *
12019  * @method _handleStandardSelectionByKey
12020  * @param e {HTMLEvent} Event object.
12021  * @private
12022  */
12023 _handleStandardSelectionByKey : function(e) {
12024     var nKey = Ev.getCharCode(e);
12025
12026     if((nKey == 38) || (nKey == 40)) {
12027         var bSHIFT = e.shiftKey;
12028
12029         // Validate trigger
12030         var oTrigger = this._getSelectionTrigger();
12031         // Arrow selection only works if last selected row is on current page
12032         if(!oTrigger) {
12033             return null;
12034         }
12035
12036         Ev.stopEvent(e);
12037
12038         // Validate anchor
12039         var oAnchor = this._getSelectionAnchor(oTrigger);
12040
12041         // Determine which direction we're going to
12042         if(bSHIFT) {
12043             // Selecting down away from anchor row
12044             if((nKey == 40) && (oAnchor.recordIndex <= oTrigger.trIndex)) {
12045                 this.selectRow(this.getNextTrEl(oTrigger.el));
12046             }
12047             // Selecting up away from anchor row
12048             else if((nKey == 38) && (oAnchor.recordIndex >= oTrigger.trIndex)) {
12049                 this.selectRow(this.getPreviousTrEl(oTrigger.el));
12050             }
12051             // Unselect trigger
12052             else {
12053                 this.unselectRow(oTrigger.el);
12054             }
12055         }
12056         else {
12057             this._handleSingleSelectionByKey(e);
12058         }
12059     }
12060 },
12061
12062 /**
12063  * Determines selection behavior resulting from a mouse event when selection mode
12064  * is set to "single".
12065  *
12066  * @method _handleSingleSelectionByMouse
12067  * @param oArgs.event {HTMLEvent} Event object.
12068  * @param oArgs.target {HTMLElement} Target element.
12069  * @private
12070  */
12071 _handleSingleSelectionByMouse : function(oArgs) {
12072     var elTarget = oArgs.target;
12073
12074     // Validate target row
12075     var elTargetRow = this.getTrEl(elTarget);
12076     if(elTargetRow) {
12077         var oTargetRecord = this.getRecord(elTargetRow);
12078
12079         // Set anchor
12080         this._oAnchorRecord = oTargetRecord;
12081
12082         // Select only target
12083         this.unselectAllRows();
12084         this.selectRow(oTargetRecord);
12085     }
12086 },
12087
12088 /**
12089  * Determines selection behavior resulting from a key event when selection mode
12090  * is set to "single".
12091  *
12092  * @method _handleSingleSelectionByKey
12093  * @param e {HTMLEvent} Event object.
12094  * @private
12095  */
12096 _handleSingleSelectionByKey : function(e) {
12097     var nKey = Ev.getCharCode(e);
12098
12099     if((nKey == 38) || (nKey == 40)) {
12100         // Validate trigger
12101         var oTrigger = this._getSelectionTrigger();
12102         // Arrow selection only works if last selected row is on current page
12103         if(!oTrigger) {
12104             return null;
12105         }
12106
12107         Ev.stopEvent(e);
12108
12109         // Determine the new row to select
12110         var elNew;
12111         if(nKey == 38) { // arrow up
12112             elNew = this.getPreviousTrEl(oTrigger.el);
12113
12114             // Validate new row
12115             if(elNew === null) {
12116                 //TODO: wrap around to last tr on current page
12117                 //elNew = this.getLastTrEl();
12118
12119                 //TODO: wrap back to last tr of previous page
12120
12121                 // Top row selection is sticky
12122                 elNew = this.getFirstTrEl();
12123             }
12124         }
12125         else if(nKey == 40) { // arrow down
12126             elNew = this.getNextTrEl(oTrigger.el);
12127
12128             // Validate new row
12129             if(elNew === null) {
12130                 //TODO: wrap around to first tr on current page
12131                 //elNew = this.getFirstTrEl();
12132
12133                 //TODO: wrap forward to first tr of previous page
12134
12135                 // Bottom row selection is sticky
12136                 elNew = this.getLastTrEl();
12137             }
12138         }
12139
12140         // Unselect all rows
12141         this.unselectAllRows();
12142
12143         // Select the new row
12144         this.selectRow(elNew);
12145
12146         // Set new anchor
12147         this._oAnchorRecord = this.getRecord(elNew);
12148     }
12149 },
12150
12151 /**
12152  * Determines selection behavior resulting from a mouse event when selection mode
12153  * is set to "cellblock".
12154  *
12155  * @method _handleCellBlockSelectionByMouse
12156  * @param oArgs.event {HTMLEvent} Event object.
12157  * @param oArgs.target {HTMLElement} Target element.
12158  * @private
12159  */
12160 _handleCellBlockSelectionByMouse : function(oArgs) {
12161     var elTarget = oArgs.target;
12162
12163     // Validate target cell
12164     var elTargetCell = this.getTdEl(elTarget);
12165     if(elTargetCell) {
12166         var e = oArgs.event;
12167         var bSHIFT = e.shiftKey;
12168         var bCTRL = e.ctrlKey || ((navigator.userAgent.toLowerCase().indexOf("mac") != -1) && e.metaKey);
12169
12170         var elTargetRow = this.getTrEl(elTargetCell);
12171         var nTargetTrIndex = this.getTrIndex(elTargetRow);
12172         var oTargetColumn = this.getColumn(elTargetCell);
12173         var nTargetColKeyIndex = oTargetColumn.getKeyIndex();
12174         var oTargetRecord = this.getRecord(elTargetRow);
12175         var nTargetRecordIndex = this._oRecordSet.getRecordIndex(oTargetRecord);
12176         var oTargetCell = {record:oTargetRecord, column:oTargetColumn};
12177
12178         var oAnchor = this._getSelectionAnchor();
12179
12180         var allRows = this.getTbodyEl().rows;
12181         var startIndex, endIndex, currentRow, i, j;
12182
12183         // Both SHIFT and CTRL
12184         if(bSHIFT && bCTRL) {
12185
12186             // Validate anchor
12187             if(oAnchor) {
12188                 // Anchor is selected
12189                 if(this.isSelected(oAnchor.cell)) {
12190                     // All cells are on the same row
12191                     if(oAnchor.recordIndex === nTargetRecordIndex) {
12192                         // Select all cells between anchor cell and target cell, including target cell
12193                         if(oAnchor.colKeyIndex < nTargetColKeyIndex) {
12194                             for(i=oAnchor.colKeyIndex+1; i<=nTargetColKeyIndex; i++) {
12195                                 this.selectCell(elTargetRow.cells[i]);
12196                             }
12197                         }
12198                         // Select all cells between target cell and anchor cell, including target cell
12199                         else if(nTargetColKeyIndex < oAnchor.colKeyIndex) {
12200                             for(i=nTargetColKeyIndex; i<oAnchor.colKeyIndex; i++) {
12201                                 this.selectCell(elTargetRow.cells[i]);
12202                             }
12203                         }
12204                     }
12205                     // Anchor row is above target row
12206                     else if(oAnchor.recordIndex < nTargetRecordIndex) {
12207                         startIndex = Math.min(oAnchor.colKeyIndex, nTargetColKeyIndex);
12208                         endIndex = Math.max(oAnchor.colKeyIndex, nTargetColKeyIndex);
12209
12210                         // Select all cells from startIndex to endIndex on rows between anchor row and target row
12211                         for(i=oAnchor.trIndex; i<=nTargetTrIndex; i++) {
12212                             for(j=startIndex; j<=endIndex; j++) {
12213                                 this.selectCell(allRows[i].cells[j]);
12214                             }
12215                         }
12216                     }
12217                     // Anchor row is below target row
12218                     else {
12219                         startIndex = Math.min(oAnchor.trIndex, nTargetColKeyIndex);
12220                         endIndex = Math.max(oAnchor.trIndex, nTargetColKeyIndex);
12221
12222                         // Select all cells from startIndex to endIndex on rows between target row and anchor row
12223                         for(i=oAnchor.trIndex; i>=nTargetTrIndex; i--) {
12224                             for(j=endIndex; j>=startIndex; j--) {
12225                                 this.selectCell(allRows[i].cells[j]);
12226                             }
12227                         }
12228                     }
12229                 }
12230                 // Anchor cell is unselected
12231                 else {
12232                     // All cells are on the same row
12233                     if(oAnchor.recordIndex === nTargetRecordIndex) {
12234                         // Unselect all cells between anchor cell and target cell
12235                         if(oAnchor.colKeyIndex < nTargetColKeyIndex) {
12236                             for(i=oAnchor.colKeyIndex+1; i<nTargetColKeyIndex; i++) {
12237                                 this.unselectCell(elTargetRow.cells[i]);
12238                             }
12239                         }
12240                         // Select all cells between target cell and anchor cell
12241                         else if(nTargetColKeyIndex < oAnchor.colKeyIndex) {
12242                             for(i=nTargetColKeyIndex+1; i<oAnchor.colKeyIndex; i++) {
12243                                 this.unselectCell(elTargetRow.cells[i]);
12244                             }
12245                         }
12246                     }
12247                     // Anchor row is above target row
12248                     if(oAnchor.recordIndex < nTargetRecordIndex) {
12249                         // Unselect all cells from anchor cell to target cell
12250                         for(i=oAnchor.trIndex; i<=nTargetTrIndex; i++) {
12251                             currentRow = allRows[i];
12252                             for(j=0; j<currentRow.cells.length; j++) {
12253                                 // This is the anchor row, only unselect cells after the anchor cell
12254                                 if(currentRow.sectionRowIndex === oAnchor.trIndex) {
12255                                     if(j>oAnchor.colKeyIndex) {
12256                                         this.unselectCell(currentRow.cells[j]);
12257                                     }
12258                                 }
12259                                 // This is the target row, only unelect cells before the target cell
12260                                 else if(currentRow.sectionRowIndex === nTargetTrIndex) {
12261                                     if(j<nTargetColKeyIndex) {
12262                                         this.unselectCell(currentRow.cells[j]);
12263                                     }
12264                                 }
12265                                 // Unselect all cells on this row
12266                                 else {
12267                                     this.unselectCell(currentRow.cells[j]);
12268                                 }
12269                             }
12270                         }
12271                     }
12272                     // Anchor row is below target row
12273                     else {
12274                         // Unselect all cells from target cell to anchor cell
12275                         for(i=nTargetTrIndex; i<=oAnchor.trIndex; i++) {
12276                             currentRow = allRows[i];
12277                             for(j=0; j<currentRow.cells.length; j++) {
12278                                 // This is the target row, only unselect cells after the target cell
12279                                 if(currentRow.sectionRowIndex == nTargetTrIndex) {
12280                                     if(j>nTargetColKeyIndex) {
12281                                         this.unselectCell(currentRow.cells[j]);
12282                                     }
12283                                 }
12284                                 // This is the anchor row, only unselect cells before the anchor cell
12285                                 else if(currentRow.sectionRowIndex == oAnchor.trIndex) {
12286                                     if(j<oAnchor.colKeyIndex) {
12287                                         this.unselectCell(currentRow.cells[j]);
12288                                     }
12289                                 }
12290                                 // Unselect all cells on this row
12291                                 else {
12292                                     this.unselectCell(currentRow.cells[j]);
12293                                 }
12294                             }
12295                         }
12296                     }
12297
12298                     // Select the target cell
12299                     this.selectCell(elTargetCell);
12300                 }
12301             }
12302             // Invalid anchor
12303             else {
12304                 // Set anchor
12305                 this._oAnchorCell = oTargetCell;
12306
12307                 // Toggle selection of target
12308                 if(this.isSelected(oTargetCell)) {
12309                     this.unselectCell(oTargetCell);
12310                 }
12311                 else {
12312                     this.selectCell(oTargetCell);
12313                 }
12314             }
12315
12316         }
12317          // Only SHIFT
12318         else if(bSHIFT) {
12319             this.unselectAllCells();
12320
12321             // Validate anchor
12322             if(oAnchor) {
12323                 // All cells are on the same row
12324                 if(oAnchor.recordIndex === nTargetRecordIndex) {
12325                     // Select all cells between anchor cell and target cell,
12326                     // including the anchor cell and target cell
12327                     if(oAnchor.colKeyIndex < nTargetColKeyIndex) {
12328                         for(i=oAnchor.colKeyIndex; i<=nTargetColKeyIndex; i++) {
12329                             this.selectCell(elTargetRow.cells[i]);
12330                         }
12331                     }
12332                     // Select all cells between target cell and anchor cell
12333                     // including the target cell and anchor cell
12334                     else if(nTargetColKeyIndex < oAnchor.colKeyIndex) {
12335                         for(i=nTargetColKeyIndex; i<=oAnchor.colKeyIndex; i++) {
12336                             this.selectCell(elTargetRow.cells[i]);
12337                         }
12338                     }
12339                 }
12340                 // Anchor row is above target row
12341                 else if(oAnchor.recordIndex < nTargetRecordIndex) {
12342                     // Select the cellblock from anchor cell to target cell
12343                     // including the anchor cell and the target cell
12344                     startIndex = Math.min(oAnchor.colKeyIndex, nTargetColKeyIndex);
12345                     endIndex = Math.max(oAnchor.colKeyIndex, nTargetColKeyIndex);
12346
12347                     for(i=oAnchor.trIndex; i<=nTargetTrIndex; i++) {
12348                         for(j=startIndex; j<=endIndex; j++) {
12349                             this.selectCell(allRows[i].cells[j]);
12350                         }
12351                     }
12352                 }
12353                 // Anchor row is below target row
12354                 else {
12355                     // Select the cellblock from target cell to anchor cell
12356                     // including the target cell and the anchor cell
12357                     startIndex = Math.min(oAnchor.colKeyIndex, nTargetColKeyIndex);
12358                     endIndex = Math.max(oAnchor.colKeyIndex, nTargetColKeyIndex);
12359
12360                     for(i=nTargetTrIndex; i<=oAnchor.trIndex; i++) {
12361                         for(j=startIndex; j<=endIndex; j++) {
12362                             this.selectCell(allRows[i].cells[j]);
12363                         }
12364                     }
12365                 }
12366             }
12367             // Invalid anchor
12368             else {
12369                 // Set anchor
12370                 this._oAnchorCell = oTargetCell;
12371
12372                 // Select target only
12373                 this.selectCell(oTargetCell);
12374             }
12375         }
12376         // Only CTRL
12377         else if(bCTRL) {
12378
12379             // Set anchor
12380             this._oAnchorCell = oTargetCell;
12381
12382             // Toggle selection of target
12383             if(this.isSelected(oTargetCell)) {
12384                 this.unselectCell(oTargetCell);
12385             }
12386             else {
12387                 this.selectCell(oTargetCell);
12388             }
12389
12390         }
12391         // Neither SHIFT nor CTRL
12392         else {
12393             this._handleSingleCellSelectionByMouse(oArgs);
12394         }
12395     }
12396 },
12397
12398 /**
12399  * Determines selection behavior resulting from a key event when selection mode
12400  * is set to "cellblock".
12401  *
12402  * @method _handleCellBlockSelectionByKey
12403  * @param e {HTMLEvent} Event object.
12404  * @private
12405  */
12406 _handleCellBlockSelectionByKey : function(e) {
12407     var nKey = Ev.getCharCode(e);
12408     var bSHIFT = e.shiftKey;
12409     if((nKey == 9) || !bSHIFT) {
12410         this._handleSingleCellSelectionByKey(e);
12411         return;
12412     }
12413
12414     if((nKey > 36) && (nKey < 41)) {
12415         // Validate trigger
12416         var oTrigger = this._getSelectionTrigger();
12417         // Arrow selection only works if last selected row is on current page
12418         if(!oTrigger) {
12419             return null;
12420         }
12421
12422         Ev.stopEvent(e);
12423
12424         // Validate anchor
12425         var oAnchor = this._getSelectionAnchor(oTrigger);
12426
12427         var i, startIndex, endIndex, elNew, elNewRow;
12428         var allRows = this.getTbodyEl().rows;
12429         var elThisRow = oTrigger.el.parentNode;
12430
12431         // Determine which direction we're going to
12432
12433         if(nKey == 40) { // arrow down
12434             // Selecting away from anchor cell
12435             if(oAnchor.recordIndex <= oTrigger.recordIndex) {
12436                 // Select the horiz block on the next row...
12437                 // ...making sure there is room below the trigger row
12438                 elNewRow = this.getNextTrEl(oTrigger.el);
12439                 if(elNewRow) {
12440                     startIndex = oAnchor.colKeyIndex;
12441                     endIndex = oTrigger.colKeyIndex;
12442                     // ...going left
12443                     if(startIndex > endIndex) {
12444                         for(i=startIndex; i>=endIndex; i--) {
12445                             elNew = elNewRow.cells[i];
12446                             this.selectCell(elNew);
12447                         }
12448                     }
12449                     // ... going right
12450                     else {
12451                         for(i=startIndex; i<=endIndex; i++) {
12452                             elNew = elNewRow.cells[i];
12453                             this.selectCell(elNew);
12454                         }
12455                     }
12456                 }
12457             }
12458             // Unselecting towards anchor cell
12459             else {
12460                 startIndex = Math.min(oAnchor.colKeyIndex, oTrigger.colKeyIndex);
12461                 endIndex = Math.max(oAnchor.colKeyIndex, oTrigger.colKeyIndex);
12462                 // Unselect the horiz block on this row towards the next row
12463                 for(i=startIndex; i<=endIndex; i++) {
12464                     this.unselectCell(elThisRow.cells[i]);
12465                 }
12466             }
12467         }
12468         // Arrow up
12469         else if(nKey == 38) {
12470             // Selecting away from anchor cell
12471             if(oAnchor.recordIndex >= oTrigger.recordIndex) {
12472                 // Select the horiz block on the previous row...
12473                 // ...making sure there is room
12474                 elNewRow = this.getPreviousTrEl(oTrigger.el);
12475                 if(elNewRow) {
12476                     // Select in order from anchor to trigger...
12477                     startIndex = oAnchor.colKeyIndex;
12478                     endIndex = oTrigger.colKeyIndex;
12479                     // ...going left
12480                     if(startIndex > endIndex) {
12481                         for(i=startIndex; i>=endIndex; i--) {
12482                             elNew = elNewRow.cells[i];
12483                             this.selectCell(elNew);
12484                         }
12485                     }
12486                     // ... going right
12487                     else {
12488                         for(i=startIndex; i<=endIndex; i++) {
12489                             elNew = elNewRow.cells[i];
12490                             this.selectCell(elNew);
12491                         }
12492                     }
12493                 }
12494             }
12495             // Unselecting towards anchor cell
12496             else {
12497                 startIndex = Math.min(oAnchor.colKeyIndex, oTrigger.colKeyIndex);
12498                 endIndex = Math.max(oAnchor.colKeyIndex, oTrigger.colKeyIndex);
12499                 // Unselect the horiz block on this row towards the previous row
12500                 for(i=startIndex; i<=endIndex; i++) {
12501                     this.unselectCell(elThisRow.cells[i]);
12502                 }
12503             }
12504         }
12505         // Arrow right
12506         else if(nKey == 39) {
12507             // Selecting away from anchor cell
12508             if(oAnchor.colKeyIndex <= oTrigger.colKeyIndex) {
12509                 // Select the next vert block to the right...
12510                 // ...making sure there is room
12511                 if(oTrigger.colKeyIndex < elThisRow.cells.length-1) {
12512                     // Select in order from anchor to trigger...
12513                     startIndex = oAnchor.trIndex;
12514                     endIndex = oTrigger.trIndex;
12515                     // ...going up
12516                     if(startIndex > endIndex) {
12517                         for(i=startIndex; i>=endIndex; i--) {
12518                             elNew = allRows[i].cells[oTrigger.colKeyIndex+1];
12519                             this.selectCell(elNew);
12520                         }
12521                     }
12522                     // ... going down
12523                     else {
12524                         for(i=startIndex; i<=endIndex; i++) {
12525                             elNew = allRows[i].cells[oTrigger.colKeyIndex+1];
12526                             this.selectCell(elNew);
12527                         }
12528                     }
12529                 }
12530             }
12531             // Unselecting towards anchor cell
12532             else {
12533                 // Unselect the vert block on this column towards the right
12534                 startIndex = Math.min(oAnchor.trIndex, oTrigger.trIndex);
12535                 endIndex = Math.max(oAnchor.trIndex, oTrigger.trIndex);
12536                 for(i=startIndex; i<=endIndex; i++) {
12537                     this.unselectCell(allRows[i].cells[oTrigger.colKeyIndex]);
12538                 }
12539             }
12540         }
12541         // Arrow left
12542         else if(nKey == 37) {
12543             // Selecting away from anchor cell
12544             if(oAnchor.colKeyIndex >= oTrigger.colKeyIndex) {
12545                 //Select the previous vert block to the left
12546                 if(oTrigger.colKeyIndex > 0) {
12547                     // Select in order from anchor to trigger...
12548                     startIndex = oAnchor.trIndex;
12549                     endIndex = oTrigger.trIndex;
12550                     // ...going up
12551                     if(startIndex > endIndex) {
12552                         for(i=startIndex; i>=endIndex; i--) {
12553                             elNew = allRows[i].cells[oTrigger.colKeyIndex-1];
12554                             this.selectCell(elNew);
12555                         }
12556                     }
12557                     // ... going down
12558                     else {
12559                         for(i=startIndex; i<=endIndex; i++) {
12560                             elNew = allRows[i].cells[oTrigger.colKeyIndex-1];
12561                             this.selectCell(elNew);
12562                         }
12563                     }
12564                 }
12565             }
12566             // Unselecting towards anchor cell
12567             else {
12568                 // Unselect the vert block on this column towards the left
12569                 startIndex = Math.min(oAnchor.trIndex, oTrigger.trIndex);
12570                 endIndex = Math.max(oAnchor.trIndex, oTrigger.trIndex);
12571                 for(i=startIndex; i<=endIndex; i++) {
12572                     this.unselectCell(allRows[i].cells[oTrigger.colKeyIndex]);
12573                 }
12574             }
12575         }
12576     }
12577 },
12578
12579 /**
12580  * Determines selection behavior resulting from a mouse event when selection mode
12581  * is set to "cellrange".
12582  *
12583  * @method _handleCellRangeSelectionByMouse
12584  * @param oArgs.event {HTMLEvent} Event object.
12585  * @param oArgs.target {HTMLElement} Target element.
12586  * @private
12587  */
12588 _handleCellRangeSelectionByMouse : function(oArgs) {
12589     var elTarget = oArgs.target;
12590
12591     // Validate target cell
12592     var elTargetCell = this.getTdEl(elTarget);
12593     if(elTargetCell) {
12594         var e = oArgs.event;
12595         var bSHIFT = e.shiftKey;
12596         var bCTRL = e.ctrlKey || ((navigator.userAgent.toLowerCase().indexOf("mac") != -1) && e.metaKey);
12597
12598         var elTargetRow = this.getTrEl(elTargetCell);
12599         var nTargetTrIndex = this.getTrIndex(elTargetRow);
12600         var oTargetColumn = this.getColumn(elTargetCell);
12601         var nTargetColKeyIndex = oTargetColumn.getKeyIndex();
12602         var oTargetRecord = this.getRecord(elTargetRow);
12603         var nTargetRecordIndex = this._oRecordSet.getRecordIndex(oTargetRecord);
12604         var oTargetCell = {record:oTargetRecord, column:oTargetColumn};
12605
12606         var oAnchor = this._getSelectionAnchor();
12607
12608         var allRows = this.getTbodyEl().rows;
12609         var currentRow, i, j;
12610
12611         // Both SHIFT and CTRL
12612         if(bSHIFT && bCTRL) {
12613
12614             // Validate anchor
12615             if(oAnchor) {
12616                 // Anchor is selected
12617                 if(this.isSelected(oAnchor.cell)) {
12618                     // All cells are on the same row
12619                     if(oAnchor.recordIndex === nTargetRecordIndex) {
12620                         // Select all cells between anchor cell and target cell, including target cell
12621                         if(oAnchor.colKeyIndex < nTargetColKeyIndex) {
12622                             for(i=oAnchor.colKeyIndex+1; i<=nTargetColKeyIndex; i++) {
12623                                 this.selectCell(elTargetRow.cells[i]);
12624                             }
12625                         }
12626                         // Select all cells between target cell and anchor cell, including target cell
12627                         else if(nTargetColKeyIndex < oAnchor.colKeyIndex) {
12628                             for(i=nTargetColKeyIndex; i<oAnchor.colKeyIndex; i++) {
12629                                 this.selectCell(elTargetRow.cells[i]);
12630                             }
12631                         }
12632                     }
12633                     // Anchor row is above target row
12634                     else if(oAnchor.recordIndex < nTargetRecordIndex) {
12635                         // Select all cells on anchor row from anchor cell to the end of the row
12636                         for(i=oAnchor.colKeyIndex+1; i<elTargetRow.cells.length; i++) {
12637                             this.selectCell(elTargetRow.cells[i]);
12638                         }
12639
12640                         // Select all cells on all rows between anchor row and target row
12641                         for(i=oAnchor.trIndex+1; i<nTargetTrIndex; i++) {
12642                             for(j=0; j<allRows[i].cells.length; j++){
12643                                 this.selectCell(allRows[i].cells[j]);
12644                             }
12645                         }
12646
12647                         // Select all cells on target row from first cell to the target cell
12648                         for(i=0; i<=nTargetColKeyIndex; i++) {
12649                             this.selectCell(elTargetRow.cells[i]);
12650                         }
12651                     }
12652                     // Anchor row is below target row
12653                     else {
12654                         // Select all cells on target row from target cell to the end of the row
12655                         for(i=nTargetColKeyIndex; i<elTargetRow.cells.length; i++) {
12656                             this.selectCell(elTargetRow.cells[i]);
12657                         }
12658
12659                         // Select all cells on all rows between target row and anchor row
12660                         for(i=nTargetTrIndex+1; i<oAnchor.trIndex; i++) {
12661                             for(j=0; j<allRows[i].cells.length; j++){
12662                                 this.selectCell(allRows[i].cells[j]);
12663                             }
12664                         }
12665
12666                         // Select all cells on anchor row from first cell to the anchor cell
12667                         for(i=0; i<oAnchor.colKeyIndex; i++) {
12668                             this.selectCell(elTargetRow.cells[i]);
12669                         }
12670                     }
12671                 }
12672                 // Anchor cell is unselected
12673                 else {
12674                     // All cells are on the same row
12675                     if(oAnchor.recordIndex === nTargetRecordIndex) {
12676                         // Unselect all cells between anchor cell and target cell
12677                         if(oAnchor.colKeyIndex < nTargetColKeyIndex) {
12678                             for(i=oAnchor.colKeyIndex+1; i<nTargetColKeyIndex; i++) {
12679                                 this.unselectCell(elTargetRow.cells[i]);
12680                             }
12681                         }
12682                         // Select all cells between target cell and anchor cell
12683                         else if(nTargetColKeyIndex < oAnchor.colKeyIndex) {
12684                             for(i=nTargetColKeyIndex+1; i<oAnchor.colKeyIndex; i++) {
12685                                 this.unselectCell(elTargetRow.cells[i]);
12686                             }
12687                         }
12688                     }
12689                     // Anchor row is above target row
12690                     if(oAnchor.recordIndex < nTargetRecordIndex) {
12691                         // Unselect all cells from anchor cell to target cell
12692                         for(i=oAnchor.trIndex; i<=nTargetTrIndex; i++) {
12693                             currentRow = allRows[i];
12694                             for(j=0; j<currentRow.cells.length; j++) {
12695                                 // This is the anchor row, only unselect cells after the anchor cell
12696                                 if(currentRow.sectionRowIndex === oAnchor.trIndex) {
12697                                     if(j>oAnchor.colKeyIndex) {
12698                                         this.unselectCell(currentRow.cells[j]);
12699                                     }
12700                                 }
12701                                 // This is the target row, only unelect cells before the target cell
12702                                 else if(currentRow.sectionRowIndex === nTargetTrIndex) {
12703                                     if(j<nTargetColKeyIndex) {
12704                                         this.unselectCell(currentRow.cells[j]);
12705                                     }
12706                                 }
12707                                 // Unselect all cells on this row
12708                                 else {
12709                                     this.unselectCell(currentRow.cells[j]);
12710                                 }
12711                             }
12712                         }
12713                     }
12714                     // Anchor row is below target row
12715                     else {
12716                         // Unselect all cells from target cell to anchor cell
12717                         for(i=nTargetTrIndex; i<=oAnchor.trIndex; i++) {
12718                             currentRow = allRows[i];
12719                             for(j=0; j<currentRow.cells.length; j++) {
12720                                 // This is the target row, only unselect cells after the target cell
12721                                 if(currentRow.sectionRowIndex == nTargetTrIndex) {
12722                                     if(j>nTargetColKeyIndex) {
12723                                         this.unselectCell(currentRow.cells[j]);
12724                                     }
12725                                 }
12726                                 // This is the anchor row, only unselect cells before the anchor cell
12727                                 else if(currentRow.sectionRowIndex == oAnchor.trIndex) {
12728                                     if(j<oAnchor.colKeyIndex) {
12729                                         this.unselectCell(currentRow.cells[j]);
12730                                     }
12731                                 }
12732                                 // Unselect all cells on this row
12733                                 else {
12734                                     this.unselectCell(currentRow.cells[j]);
12735                                 }
12736                             }
12737                         }
12738                     }
12739
12740                     // Select the target cell
12741                     this.selectCell(elTargetCell);
12742                 }
12743             }
12744             // Invalid anchor
12745             else {
12746                 // Set anchor
12747                 this._oAnchorCell = oTargetCell;
12748
12749                 // Toggle selection of target
12750                 if(this.isSelected(oTargetCell)) {
12751                     this.unselectCell(oTargetCell);
12752                 }
12753                 else {
12754                     this.selectCell(oTargetCell);
12755                 }
12756             }
12757         }
12758          // Only SHIFT
12759         else if(bSHIFT) {
12760
12761             this.unselectAllCells();
12762
12763             // Validate anchor
12764             if(oAnchor) {
12765                 // All cells are on the same row
12766                 if(oAnchor.recordIndex === nTargetRecordIndex) {
12767                     // Select all cells between anchor cell and target cell,
12768                     // including the anchor cell and target cell
12769                     if(oAnchor.colKeyIndex < nTargetColKeyIndex) {
12770                         for(i=oAnchor.colKeyIndex; i<=nTargetColKeyIndex; i++) {
12771                             this.selectCell(elTargetRow.cells[i]);
12772                         }
12773                     }
12774                     // Select all cells between target cell and anchor cell
12775                     // including the target cell and anchor cell
12776                     else if(nTargetColKeyIndex < oAnchor.colKeyIndex) {
12777                         for(i=nTargetColKeyIndex; i<=oAnchor.colKeyIndex; i++) {
12778                             this.selectCell(elTargetRow.cells[i]);
12779                         }
12780                     }
12781                 }
12782                 // Anchor row is above target row
12783                 else if(oAnchor.recordIndex < nTargetRecordIndex) {
12784                     // Select all cells from anchor cell to target cell
12785                     // including the anchor cell and target cell
12786                     for(i=oAnchor.trIndex; i<=nTargetTrIndex; i++) {
12787                         currentRow = allRows[i];
12788                         for(j=0; j<currentRow.cells.length; j++) {
12789                             // This is the anchor row, only select the anchor cell and after
12790                             if(currentRow.sectionRowIndex == oAnchor.trIndex) {
12791                                 if(j>=oAnchor.colKeyIndex) {
12792                                     this.selectCell(currentRow.cells[j]);
12793                                 }
12794                             }
12795                             // This is the target row, only select the target cell and before
12796                             else if(currentRow.sectionRowIndex == nTargetTrIndex) {
12797                                 if(j<=nTargetColKeyIndex) {
12798                                     this.selectCell(currentRow.cells[j]);
12799                                 }
12800                             }
12801                             // Select all cells on this row
12802                             else {
12803                                 this.selectCell(currentRow.cells[j]);
12804                             }
12805                         }
12806                     }
12807                 }
12808                 // Anchor row is below target row
12809                 else {
12810                     // Select all cells from target cell to anchor cell,
12811                     // including the target cell and anchor cell
12812                     for(i=nTargetTrIndex; i<=oAnchor.trIndex; i++) {
12813                         currentRow = allRows[i];
12814                         for(j=0; j<currentRow.cells.length; j++) {
12815                             // This is the target row, only select the target cell and after
12816                             if(currentRow.sectionRowIndex == nTargetTrIndex) {
12817                                 if(j>=nTargetColKeyIndex) {
12818                                     this.selectCell(currentRow.cells[j]);
12819                                 }
12820                             }
12821                             // This is the anchor row, only select the anchor cell and before
12822                             else if(currentRow.sectionRowIndex == oAnchor.trIndex) {
12823                                 if(j<=oAnchor.colKeyIndex) {
12824                                     this.selectCell(currentRow.cells[j]);
12825                                 }
12826                             }
12827                             // Select all cells on this row
12828                             else {
12829                                 this.selectCell(currentRow.cells[j]);
12830                             }
12831                         }
12832                     }
12833                 }
12834             }
12835             // Invalid anchor
12836             else {
12837                 // Set anchor
12838                 this._oAnchorCell = oTargetCell;
12839
12840                 // Select target only
12841                 this.selectCell(oTargetCell);
12842             }
12843
12844
12845         }
12846         // Only CTRL
12847         else if(bCTRL) {
12848
12849             // Set anchor
12850             this._oAnchorCell = oTargetCell;
12851
12852             // Toggle selection of target
12853             if(this.isSelected(oTargetCell)) {
12854                 this.unselectCell(oTargetCell);
12855             }
12856             else {
12857                 this.selectCell(oTargetCell);
12858             }
12859
12860         }
12861         // Neither SHIFT nor CTRL
12862         else {
12863             this._handleSingleCellSelectionByMouse(oArgs);
12864         }
12865     }
12866 },
12867
12868 /**
12869  * Determines selection behavior resulting from a key event when selection mode
12870  * is set to "cellrange".
12871  *
12872  * @method _handleCellRangeSelectionByKey
12873  * @param e {HTMLEvent} Event object.
12874  * @private
12875  */
12876 _handleCellRangeSelectionByKey : function(e) {
12877     var nKey = Ev.getCharCode(e);
12878     var bSHIFT = e.shiftKey;
12879     if((nKey == 9) || !bSHIFT) {
12880         this._handleSingleCellSelectionByKey(e);
12881         return;
12882     }
12883
12884     if((nKey > 36) && (nKey < 41)) {
12885         // Validate trigger
12886         var oTrigger = this._getSelectionTrigger();
12887         // Arrow selection only works if last selected row is on current page
12888         if(!oTrigger) {
12889             return null;
12890         }
12891
12892         Ev.stopEvent(e);
12893
12894         // Validate anchor
12895         var oAnchor = this._getSelectionAnchor(oTrigger);
12896
12897         var i, elNewRow, elNew;
12898         var allRows = this.getTbodyEl().rows;
12899         var elThisRow = oTrigger.el.parentNode;
12900
12901         // Arrow down
12902         if(nKey == 40) {
12903             elNewRow = this.getNextTrEl(oTrigger.el);
12904
12905             // Selecting away from anchor cell
12906             if(oAnchor.recordIndex <= oTrigger.recordIndex) {
12907                 // Select all cells to the end of this row
12908                 for(i=oTrigger.colKeyIndex+1; i<elThisRow.cells.length; i++){
12909                     elNew = elThisRow.cells[i];
12910                     this.selectCell(elNew);
12911                 }
12912
12913                 // Select some of the cells on the next row down
12914                 if(elNewRow) {
12915                     for(i=0; i<=oTrigger.colKeyIndex; i++){
12916                         elNew = elNewRow.cells[i];
12917                         this.selectCell(elNew);
12918                     }
12919                 }
12920             }
12921             // Unselecting towards anchor cell
12922             else {
12923                 // Unselect all cells to the end of this row
12924                 for(i=oTrigger.colKeyIndex; i<elThisRow.cells.length; i++){
12925                     this.unselectCell(elThisRow.cells[i]);
12926                 }
12927
12928                 // Unselect some of the cells on the next row down
12929                 if(elNewRow) {
12930                     for(i=0; i<oTrigger.colKeyIndex; i++){
12931                         this.unselectCell(elNewRow.cells[i]);
12932                     }
12933                 }
12934             }
12935         }
12936         // Arrow up
12937         else if(nKey == 38) {
12938             elNewRow = this.getPreviousTrEl(oTrigger.el);
12939
12940             // Selecting away from anchor cell
12941             if(oAnchor.recordIndex >= oTrigger.recordIndex) {
12942                 // Select all the cells to the beginning of this row
12943                 for(i=oTrigger.colKeyIndex-1; i>-1; i--){
12944                     elNew = elThisRow.cells[i];
12945                     this.selectCell(elNew);
12946                 }
12947
12948                 // Select some of the cells from the end of the previous row
12949                 if(elNewRow) {
12950                     for(i=elThisRow.cells.length-1; i>=oTrigger.colKeyIndex; i--){
12951                         elNew = elNewRow.cells[i];
12952                         this.selectCell(elNew);
12953                     }
12954                 }
12955             }
12956             // Unselecting towards anchor cell
12957             else {
12958                 // Unselect all the cells to the beginning of this row
12959                 for(i=oTrigger.colKeyIndex; i>-1; i--){
12960                     this.unselectCell(elThisRow.cells[i]);
12961                 }
12962
12963                 // Unselect some of the cells from the end of the previous row
12964                 if(elNewRow) {
12965                     for(i=elThisRow.cells.length-1; i>oTrigger.colKeyIndex; i--){
12966                         this.unselectCell(elNewRow.cells[i]);
12967                     }
12968                 }
12969             }
12970         }
12971         // Arrow right
12972         else if(nKey == 39) {
12973             elNewRow = this.getNextTrEl(oTrigger.el);
12974
12975             // Selecting away from anchor cell
12976             if(oAnchor.recordIndex < oTrigger.recordIndex) {
12977                 // Select the next cell to the right
12978                 if(oTrigger.colKeyIndex < elThisRow.cells.length-1) {
12979                     elNew = elThisRow.cells[oTrigger.colKeyIndex+1];
12980                     this.selectCell(elNew);
12981                 }
12982                 // Select the first cell of the next row
12983                 else if(elNewRow) {
12984                     elNew = elNewRow.cells[0];
12985                     this.selectCell(elNew);
12986                 }
12987             }
12988             // Unselecting towards anchor cell
12989             else if(oAnchor.recordIndex > oTrigger.recordIndex) {
12990                 this.unselectCell(elThisRow.cells[oTrigger.colKeyIndex]);
12991
12992                 // Unselect this cell towards the right
12993                 if(oTrigger.colKeyIndex < elThisRow.cells.length-1) {
12994                 }
12995                 // Unselect this cells towards the first cell of the next row
12996                 else {
12997                 }
12998             }
12999             // Anchor is on this row
13000             else {
13001                 // Selecting away from anchor
13002                 if(oAnchor.colKeyIndex <= oTrigger.colKeyIndex) {
13003                     // Select the next cell to the right
13004                     if(oTrigger.colKeyIndex < elThisRow.cells.length-1) {
13005                         elNew = elThisRow.cells[oTrigger.colKeyIndex+1];
13006                         this.selectCell(elNew);
13007                     }
13008                     // Select the first cell on the next row
13009                     else if(oTrigger.trIndex < allRows.length-1){
13010                         elNew = elNewRow.cells[0];
13011                         this.selectCell(elNew);
13012                     }
13013                 }
13014                 // Unselecting towards anchor
13015                 else {
13016                     // Unselect this cell towards the right
13017                     this.unselectCell(elThisRow.cells[oTrigger.colKeyIndex]);
13018                 }
13019             }
13020         }
13021         // Arrow left
13022         else if(nKey == 37) {
13023             elNewRow = this.getPreviousTrEl(oTrigger.el);
13024
13025             // Unselecting towards the anchor
13026             if(oAnchor.recordIndex < oTrigger.recordIndex) {
13027                 this.unselectCell(elThisRow.cells[oTrigger.colKeyIndex]);
13028
13029                 // Unselect this cell towards the left
13030                 if(oTrigger.colKeyIndex > 0) {
13031                 }
13032                 // Unselect this cell towards the last cell of the previous row
13033                 else {
13034                 }
13035             }
13036             // Selecting towards the anchor
13037             else if(oAnchor.recordIndex > oTrigger.recordIndex) {
13038                 // Select the next cell to the left
13039                 if(oTrigger.colKeyIndex > 0) {
13040                     elNew = elThisRow.cells[oTrigger.colKeyIndex-1];
13041                     this.selectCell(elNew);
13042                 }
13043                 // Select the last cell of the previous row
13044                 else if(oTrigger.trIndex > 0){
13045                     elNew = elNewRow.cells[elNewRow.cells.length-1];
13046                     this.selectCell(elNew);
13047                 }
13048             }
13049             // Anchor is on this row
13050             else {
13051                 // Selecting away from anchor cell
13052                 if(oAnchor.colKeyIndex >= oTrigger.colKeyIndex) {
13053                     // Select the next cell to the left
13054                     if(oTrigger.colKeyIndex > 0) {
13055                         elNew = elThisRow.cells[oTrigger.colKeyIndex-1];
13056                         this.selectCell(elNew);
13057                     }
13058                     // Select the last cell of the previous row
13059                     else if(oTrigger.trIndex > 0){
13060                         elNew = elNewRow.cells[elNewRow.cells.length-1];
13061                         this.selectCell(elNew);
13062                     }
13063                 }
13064                 // Unselecting towards anchor cell
13065                 else {
13066                     this.unselectCell(elThisRow.cells[oTrigger.colKeyIndex]);
13067
13068                     // Unselect this cell towards the left
13069                     if(oTrigger.colKeyIndex > 0) {
13070                     }
13071                     // Unselect this cell towards the last cell of the previous row
13072                     else {
13073                     }
13074                 }
13075             }
13076         }
13077     }
13078 },
13079
13080 /**
13081  * Determines selection behavior resulting from a mouse event when selection mode
13082  * is set to "singlecell".
13083  *
13084  * @method _handleSingleCellSelectionByMouse
13085  * @param oArgs.event {HTMLEvent} Event object.
13086  * @param oArgs.target {HTMLElement} Target element.
13087  * @private
13088  */
13089 _handleSingleCellSelectionByMouse : function(oArgs) {
13090     var elTarget = oArgs.target;
13091
13092     // Validate target cell
13093     var elTargetCell = this.getTdEl(elTarget);
13094     if(elTargetCell) {
13095         var elTargetRow = this.getTrEl(elTargetCell);
13096         var oTargetRecord = this.getRecord(elTargetRow);
13097         var oTargetColumn = this.getColumn(elTargetCell);
13098         var oTargetCell = {record:oTargetRecord, column:oTargetColumn};
13099
13100         // Set anchor
13101         this._oAnchorCell = oTargetCell;
13102
13103         // Select only target
13104         this.unselectAllCells();
13105         this.selectCell(oTargetCell);
13106     }
13107 },
13108
13109 /**
13110  * Determines selection behavior resulting from a key event when selection mode
13111  * is set to "singlecell".
13112  *
13113  * @method _handleSingleCellSelectionByKey
13114  * @param e {HTMLEvent} Event object.
13115  * @private
13116  */
13117 _handleSingleCellSelectionByKey : function(e) {
13118     var nKey = Ev.getCharCode(e);
13119     if((nKey == 9) || ((nKey > 36) && (nKey < 41))) {
13120         var bSHIFT = e.shiftKey;
13121
13122         // Validate trigger
13123         var oTrigger = this._getSelectionTrigger();
13124         // Arrow selection only works if last selected row is on current page
13125         if(!oTrigger) {
13126             return null;
13127         }
13128
13129         // Determine the new cell to select
13130         var elNew;
13131         if(nKey == 40) { // Arrow down
13132             elNew = this.getBelowTdEl(oTrigger.el);
13133
13134             // Validate new cell
13135             if(elNew === null) {
13136                 //TODO: wrap around to first tr on current page
13137
13138                 //TODO: wrap forward to first tr of next page
13139
13140                 // Bottom selection is sticky
13141                 elNew = oTrigger.el;
13142             }
13143         }
13144         else if(nKey == 38) { // Arrow up
13145             elNew = this.getAboveTdEl(oTrigger.el);
13146
13147             // Validate new cell
13148             if(elNew === null) {
13149                 //TODO: wrap around to last tr on current page
13150
13151                 //TODO: wrap back to last tr of previous page
13152
13153                 // Top selection is sticky
13154                 elNew = oTrigger.el;
13155             }
13156         }
13157         else if((nKey == 39) || (!bSHIFT && (nKey == 9))) { // Arrow right or tab
13158             elNew = this.getNextTdEl(oTrigger.el);
13159
13160             // Validate new cell
13161             if(elNew === null) {
13162                 //TODO: wrap around to first td on current page
13163
13164                 //TODO: wrap forward to first td of next page
13165
13166                 // Top-left selection is sticky, and release TAB focus
13167                 //elNew = oTrigger.el;
13168                 return;
13169             }
13170         }
13171         else if((nKey == 37) || (bSHIFT && (nKey == 9))) { // Arrow left or shift-tab
13172             elNew = this.getPreviousTdEl(oTrigger.el);
13173
13174             // Validate new cell
13175             if(elNew === null) {
13176                 //TODO: wrap around to last td on current page
13177
13178                 //TODO: wrap back to last td of previous page
13179
13180                 // Bottom-right selection is sticky, and release TAB focus
13181                 //elNew = oTrigger.el;
13182                 return;
13183             }
13184         }
13185
13186         Ev.stopEvent(e);
13187         
13188         // Unselect all cells
13189         this.unselectAllCells();
13190
13191         // Select the new cell
13192         this.selectCell(elNew);
13193
13194         // Set new anchor
13195         this._oAnchorCell = {record:this.getRecord(elNew), column:this.getColumn(elNew)};
13196     }
13197 },
13198
13199 /**
13200  * Returns array of selected TR elements on the page.
13201  *
13202  * @method getSelectedTrEls
13203  * @return {HTMLElement[]} Array of selected TR elements.
13204  */
13205 getSelectedTrEls : function() {
13206     return Dom.getElementsByClassName(DT.CLASS_SELECTED,"tr",this._elTbody);
13207 },
13208
13209 /**
13210  * Sets given row to the selected state.
13211  *
13212  * @method selectRow
13213  * @param row {HTMLElement | String | YAHOO.widget.Record | Number} HTML element
13214  * reference or ID string, Record instance, or RecordSet position index.
13215  */
13216 selectRow : function(row) {
13217     var oRecord, elRow;
13218
13219     if(row instanceof YAHOO.widget.Record) {
13220         oRecord = this._oRecordSet.getRecord(row);
13221         elRow = this.getTrEl(oRecord);
13222     }
13223     else if(lang.isNumber(row)) {
13224         oRecord = this.getRecord(row);
13225         elRow = this.getTrEl(oRecord);
13226     }
13227     else {
13228         elRow = this.getTrEl(row);
13229         oRecord = this.getRecord(elRow);
13230     }
13231
13232     if(oRecord) {
13233         // Update selection trackers
13234         var tracker = this._aSelections || [];
13235         var sRecordId = oRecord.getId();
13236         var index = -1;
13237
13238         // Remove if already there:
13239         // Use Array.indexOf if available...
13240         /*if(tracker.indexOf && (tracker.indexOf(sRecordId) >  -1)) {
13241             tracker.splice(tracker.indexOf(sRecordId),1);
13242         }*/
13243         if(tracker.indexOf) {
13244             index = tracker.indexOf(sRecordId);
13245             
13246         }
13247         // ...or do it the old-fashioned way
13248         else {
13249             for(var j=tracker.length-1; j>-1; j--) {
13250                 if(tracker[j] === sRecordId){
13251                     index = j;
13252                     break;
13253                 }
13254             }
13255         }
13256         if(index > -1) {
13257             tracker.splice(index,1);
13258         }
13259         
13260         // Add to the end
13261         tracker.push(sRecordId);
13262         this._aSelections = tracker;
13263
13264         // Update trackers
13265         if(!this._oAnchorRecord) {
13266             this._oAnchorRecord = oRecord;
13267         }
13268
13269         // Update UI
13270         if(elRow) {
13271             Dom.addClass(elRow, DT.CLASS_SELECTED);
13272         }
13273
13274         this.fireEvent("rowSelectEvent", {record:oRecord, el:elRow});
13275     }
13276     else {
13277     }
13278 },
13279
13280 /**
13281  * Sets given row to the unselected state.
13282  *
13283  * @method unselectRow
13284  * @param row {HTMLElement | String | YAHOO.widget.Record | Number} HTML element
13285  * reference or ID string, Record instance, or RecordSet position index.
13286  */
13287 unselectRow : function(row) {
13288     var elRow = this.getTrEl(row);
13289
13290     var oRecord;
13291     if(row instanceof YAHOO.widget.Record) {
13292         oRecord = this._oRecordSet.getRecord(row);
13293     }
13294     else if(lang.isNumber(row)) {
13295         oRecord = this.getRecord(row);
13296     }
13297     else {
13298         oRecord = this.getRecord(elRow);
13299     }
13300
13301     if(oRecord) {
13302         // Update selection trackers
13303         var tracker = this._aSelections || [];
13304         var sRecordId = oRecord.getId();
13305         var index = -1;
13306
13307         // Use Array.indexOf if available...
13308         if(tracker.indexOf) {
13309             index = tracker.indexOf(sRecordId);
13310         }
13311         // ...or do it the old-fashioned way
13312         else {
13313             for(var j=tracker.length-1; j>-1; j--) {
13314                 if(tracker[j] === sRecordId){
13315                     index = j;
13316                     break;
13317                 }
13318             }
13319         }
13320         if(index > -1) {
13321             // Update tracker
13322             tracker.splice(index,1);
13323             this._aSelections = tracker;
13324
13325             // Update the UI
13326             Dom.removeClass(elRow, DT.CLASS_SELECTED);
13327
13328             this.fireEvent("rowUnselectEvent", {record:oRecord, el:elRow});
13329
13330             return;
13331         }
13332     }
13333 },
13334
13335 /**
13336  * Clears out all row selections.
13337  *
13338  * @method unselectAllRows
13339  */
13340 unselectAllRows : function() {
13341     // Remove all rows from tracker
13342     var tracker = this._aSelections || [],
13343         recId,
13344         removed = [];
13345     for(var j=tracker.length-1; j>-1; j--) {
13346        if(lang.isString(tracker[j])){
13347             recId = tracker.splice(j,1);
13348             removed[removed.length] = this.getRecord(lang.isArray(recId) ? recId[0] : recId);
13349         }
13350     }
13351
13352     // Update tracker
13353     this._aSelections = tracker;
13354
13355     // Update UI
13356     this._unselectAllTrEls();
13357
13358     this.fireEvent("unselectAllRowsEvent", {records: removed});
13359 },
13360
13361 /**
13362  * Convenience method to remove the class YAHOO.widget.DataTable.CLASS_SELECTED
13363  * from all TD elements in the internal tracker.
13364  *
13365  * @method _unselectAllTdEls
13366  * @private
13367  */
13368 _unselectAllTdEls : function() {
13369     var selectedCells = Dom.getElementsByClassName(DT.CLASS_SELECTED,"td",this._elTbody);
13370     Dom.removeClass(selectedCells, DT.CLASS_SELECTED);
13371 },
13372
13373 /**
13374  * Returns array of selected TD elements on the page.
13375  *
13376  * @method getSelectedTdEls
13377  * @return {HTMLElement[]} Array of selected TD elements.
13378  */
13379 getSelectedTdEls : function() {
13380     return Dom.getElementsByClassName(DT.CLASS_SELECTED,"td",this._elTbody);
13381 },
13382
13383 /**
13384  * Sets given cell to the selected state.
13385  *
13386  * @method selectCell
13387  * @param cell {HTMLElement | String | Object} TD element or child of a TD element, or
13388  * object literal of syntax {record:oRecord, column:oColumn}.
13389  */
13390 selectCell : function(cell) {
13391 //TODO: accept {record} in selectCell()
13392     var elCell = this.getTdEl(cell);
13393
13394     if(elCell) {
13395         var oRecord = this.getRecord(elCell);
13396         var oColumn = this.getColumn(this.getCellIndex(elCell));
13397         var sColumnKey = oColumn.getKey();
13398
13399         if(oRecord && sColumnKey) {
13400             // Get Record ID
13401             var tracker = this._aSelections || [];
13402             var sRecordId = oRecord.getId();
13403
13404             // Remove if there
13405             for(var j=tracker.length-1; j>-1; j--) {
13406                if((tracker[j].recordId === sRecordId) && (tracker[j].columnKey === sColumnKey)){
13407                     tracker.splice(j,1);
13408                     break;
13409                 }
13410             }
13411
13412             // Add to the end
13413             tracker.push({recordId:sRecordId, columnKey:sColumnKey});
13414
13415             // Update trackers
13416             this._aSelections = tracker;
13417             if(!this._oAnchorCell) {
13418                 this._oAnchorCell = {record:oRecord, column:oColumn};
13419             }
13420
13421             // Update the UI
13422             Dom.addClass(elCell, DT.CLASS_SELECTED);
13423
13424             this.fireEvent("cellSelectEvent", {record:oRecord, column:oColumn, key: sColumnKey, el:elCell});
13425             return;
13426         }
13427     }
13428 },
13429
13430 /**
13431  * Sets given cell to the unselected state.
13432  *
13433  * @method unselectCell
13434  * @param cell {HTMLElement | String | Object} TD element or child of a TD element, or
13435  * object literal of syntax {record:oRecord, column:oColumn}.
13436  * @param cell {HTMLElement | String} DOM element reference or ID string
13437  * to DataTable page element or RecordSet index.
13438  */
13439 unselectCell : function(cell) {
13440     var elCell = this.getTdEl(cell);
13441
13442     if(elCell) {
13443         var oRecord = this.getRecord(elCell);
13444         var oColumn = this.getColumn(this.getCellIndex(elCell));
13445         var sColumnKey = oColumn.getKey();
13446
13447         if(oRecord && sColumnKey) {
13448             // Get Record ID
13449             var tracker = this._aSelections || [];
13450             var id = oRecord.getId();
13451
13452             // Is it selected?
13453             for(var j=tracker.length-1; j>-1; j--) {
13454                 if((tracker[j].recordId === id) && (tracker[j].columnKey === sColumnKey)){
13455                     // Remove from tracker
13456                     tracker.splice(j,1);
13457
13458                     // Update tracker
13459                     this._aSelections = tracker;
13460
13461                     // Update the UI
13462                     Dom.removeClass(elCell, DT.CLASS_SELECTED);
13463
13464                     this.fireEvent("cellUnselectEvent", {record:oRecord, column: oColumn, key:sColumnKey, el:elCell});
13465                     return;
13466                 }
13467             }
13468         }
13469     }
13470 },
13471
13472 /**
13473  * Clears out all cell selections.
13474  *
13475  * @method unselectAllCells
13476  */
13477 unselectAllCells : function() {
13478     // Remove all cells from tracker
13479     var tracker = this._aSelections || [];
13480     for(var j=tracker.length-1; j>-1; j--) {
13481        if(lang.isObject(tracker[j])){
13482             tracker.splice(j,1);
13483         }
13484     }
13485
13486     // Update tracker
13487     this._aSelections = tracker;
13488
13489     // Update UI
13490     this._unselectAllTdEls();
13491
13492     //TODO: send data to unselectAllCellsEvent handler
13493     this.fireEvent("unselectAllCellsEvent");
13494 },
13495
13496 /**
13497  * Returns true if given item is selected, false otherwise.
13498  *
13499  * @method isSelected
13500  * @param o {String | HTMLElement | YAHOO.widget.Record | Number
13501  * {record:YAHOO.widget.Record, column:YAHOO.widget.Column} } TR or TD element by
13502  * reference or ID string, a Record instance, a RecordSet position index,
13503  * or an object literal representation
13504  * of a cell.
13505  * @return {Boolean} True if item is selected.
13506  */
13507 isSelected : function(o) {
13508     if(o && (o.ownerDocument == document)) {
13509         return (Dom.hasClass(this.getTdEl(o),DT.CLASS_SELECTED) || Dom.hasClass(this.getTrEl(o),DT.CLASS_SELECTED));
13510     }
13511     else {
13512         var oRecord, sRecordId, j;
13513         var tracker = this._aSelections;
13514         if(tracker && tracker.length > 0) {
13515             // Looking for a Record?
13516             if(o instanceof YAHOO.widget.Record) {
13517                 oRecord = o;
13518             }
13519             else if(lang.isNumber(o)) {
13520                 oRecord = this.getRecord(o);
13521             }
13522             if(oRecord) {
13523                 sRecordId = oRecord.getId();
13524
13525                 // Is it there?
13526                 // Use Array.indexOf if available...
13527                 if(tracker.indexOf) {
13528                     if(tracker.indexOf(sRecordId) >  -1) {
13529                         return true;
13530                     }
13531                 }
13532                 // ...or do it the old-fashioned way
13533                 else {
13534                     for(j=tracker.length-1; j>-1; j--) {
13535                        if(tracker[j] === sRecordId){
13536                         return true;
13537                        }
13538                     }
13539                 }
13540             }
13541             // Looking for a cell
13542             else if(o.record && o.column){
13543                 sRecordId = o.record.getId();
13544                 var sColumnKey = o.column.getKey();
13545
13546                 for(j=tracker.length-1; j>-1; j--) {
13547                     if((tracker[j].recordId === sRecordId) && (tracker[j].columnKey === sColumnKey)){
13548                         return true;
13549                     }
13550                 }
13551             }
13552         }
13553     }
13554     return false;
13555 },
13556
13557 /**
13558  * Returns selected rows as an array of Record IDs.
13559  *
13560  * @method getSelectedRows
13561  * @return {String[]} Array of selected rows by Record ID.
13562  */
13563 getSelectedRows : function() {
13564     var aSelectedRows = [];
13565     var tracker = this._aSelections || [];
13566     for(var j=0; j<tracker.length; j++) {
13567        if(lang.isString(tracker[j])){
13568             aSelectedRows.push(tracker[j]);
13569         }
13570     }
13571     return aSelectedRows;
13572 },
13573
13574 /**
13575  * Returns selected cells as an array of object literals:
13576  *     {recordId:sRecordId, columnKey:sColumnKey}.
13577  *
13578  * @method getSelectedCells
13579  * @return {Object[]} Array of selected cells by Record ID and Column ID.
13580  */
13581 getSelectedCells : function() {
13582     var aSelectedCells = [];
13583     var tracker = this._aSelections || [];
13584     for(var j=0; j<tracker.length; j++) {
13585        if(tracker[j] && lang.isObject(tracker[j])){
13586             aSelectedCells.push(tracker[j]);
13587         }
13588     }
13589     return aSelectedCells;
13590 },
13591
13592 /**
13593  * Returns last selected Record ID.
13594  *
13595  * @method getLastSelectedRecord
13596  * @return {String} Record ID of last selected row.
13597  */
13598 getLastSelectedRecord : function() {
13599     var tracker = this._aSelections;
13600     if(tracker && tracker.length > 0) {
13601         for(var i=tracker.length-1; i>-1; i--) {
13602            if(lang.isString(tracker[i])){
13603                 return tracker[i];
13604             }
13605         }
13606     }
13607 },
13608
13609 /**
13610  * Returns last selected cell as an object literal:
13611  *     {recordId:sRecordId, columnKey:sColumnKey}.
13612  *
13613  * @method getLastSelectedCell
13614  * @return {Object} Object literal representation of a cell.
13615  */
13616 getLastSelectedCell : function() {
13617     var tracker = this._aSelections;
13618     if(tracker && tracker.length > 0) {
13619         for(var i=tracker.length-1; i>-1; i--) {
13620            if(tracker[i].recordId && tracker[i].columnKey){
13621                 return tracker[i];
13622             }
13623         }
13624     }
13625 },
13626
13627 /**
13628  * Assigns the class YAHOO.widget.DataTable.CLASS_HIGHLIGHTED to the given row.
13629  *
13630  * @method highlightRow
13631  * @param row {HTMLElement | String} DOM element reference or ID string.
13632  */
13633 highlightRow : function(row) {
13634     var elRow = this.getTrEl(row);
13635
13636     if(elRow) {
13637         // Make sure previous row is unhighlighted
13638 /*        if(this._sLastHighlightedTrElId) {
13639             Dom.removeClass(this._sLastHighlightedTrElId,DT.CLASS_HIGHLIGHTED);
13640         }*/
13641         var oRecord = this.getRecord(elRow);
13642         Dom.addClass(elRow,DT.CLASS_HIGHLIGHTED);
13643         //this._sLastHighlightedTrElId = elRow.id;
13644         this.fireEvent("rowHighlightEvent", {record:oRecord, el:elRow});
13645         return;
13646     }
13647 },
13648
13649 /**
13650  * Removes the class YAHOO.widget.DataTable.CLASS_HIGHLIGHTED from the given row.
13651  *
13652  * @method unhighlightRow
13653  * @param row {HTMLElement | String} DOM element reference or ID string.
13654  */
13655 unhighlightRow : function(row) {
13656     var elRow = this.getTrEl(row);
13657
13658     if(elRow) {
13659         var oRecord = this.getRecord(elRow);
13660         Dom.removeClass(elRow,DT.CLASS_HIGHLIGHTED);
13661         this.fireEvent("rowUnhighlightEvent", {record:oRecord, el:elRow});
13662         return;
13663     }
13664 },
13665
13666 /**
13667  * Assigns the class YAHOO.widget.DataTable.CLASS_HIGHLIGHTED to the given cell.
13668  *
13669  * @method highlightCell
13670  * @param cell {HTMLElement | String} DOM element reference or ID string.
13671  */
13672 highlightCell : function(cell) {
13673     var elCell = this.getTdEl(cell);
13674
13675     if(elCell) {
13676         // Make sure previous cell is unhighlighted
13677         if(this._elLastHighlightedTd) {
13678             this.unhighlightCell(this._elLastHighlightedTd);
13679         }
13680
13681         var oRecord = this.getRecord(elCell);
13682         var oColumn = this.getColumn(this.getCellIndex(elCell));
13683         var sColumnKey = oColumn.getKey();
13684         Dom.addClass(elCell,DT.CLASS_HIGHLIGHTED);
13685         this._elLastHighlightedTd = elCell;
13686         this.fireEvent("cellHighlightEvent", {record:oRecord, column:oColumn, key:sColumnKey, el:elCell});
13687         return;
13688     }
13689 },
13690
13691 /**
13692  * Removes the class YAHOO.widget.DataTable.CLASS_HIGHLIGHTED from the given cell.
13693  *
13694  * @method unhighlightCell
13695  * @param cell {HTMLElement | String} DOM element reference or ID string.
13696  */
13697 unhighlightCell : function(cell) {
13698     var elCell = this.getTdEl(cell);
13699
13700     if(elCell) {
13701         var oRecord = this.getRecord(elCell);
13702         Dom.removeClass(elCell,DT.CLASS_HIGHLIGHTED);
13703         this._elLastHighlightedTd = null;
13704         this.fireEvent("cellUnhighlightEvent", {record:oRecord, column:this.getColumn(this.getCellIndex(elCell)), key:this.getColumn(this.getCellIndex(elCell)).getKey(), el:elCell});
13705         return;
13706     }
13707 },
13708
13709
13710
13711
13712
13713
13714
13715
13716
13717
13718
13719
13720
13721
13722
13723
13724
13725
13726
13727
13728
13729
13730
13731
13732
13733
13734
13735
13736
13737
13738
13739
13740
13741
13742
13743
13744
13745
13746
13747
13748
13749
13750
13751
13752
13753 // INLINE EDITING
13754
13755 /**
13756  * Assigns CellEditor instance to existing Column.
13757  * @method addCellEditor
13758  * @param oColumn {YAHOO.widget.Column} Column instance.
13759  * @param oEditor {YAHOO.wdiget.CellEditor} CellEditor instance.
13760  */
13761 addCellEditor : function(oColumn, oEditor) {
13762     oColumn.editor = oEditor;
13763     oColumn.editor.subscribe("showEvent", this._onEditorShowEvent, this, true);
13764     oColumn.editor.subscribe("keydownEvent", this._onEditorKeydownEvent, this, true);
13765     oColumn.editor.subscribe("revertEvent", this._onEditorRevertEvent, this, true);
13766     oColumn.editor.subscribe("saveEvent", this._onEditorSaveEvent, this, true);
13767     oColumn.editor.subscribe("cancelEvent", this._onEditorCancelEvent, this, true);
13768     oColumn.editor.subscribe("blurEvent", this._onEditorBlurEvent, this, true);
13769     oColumn.editor.subscribe("blockEvent", this._onEditorBlockEvent, this, true);
13770     oColumn.editor.subscribe("unblockEvent", this._onEditorUnblockEvent, this, true);
13771 },
13772
13773 /**
13774  * Returns current CellEditor instance, or null.
13775  * @method getCellEditor
13776  * @return {YAHOO.widget.CellEditor} CellEditor instance.
13777  */
13778 getCellEditor : function() {
13779     return this._oCellEditor;
13780 },
13781
13782
13783 /**
13784  * Activates and shows CellEditor instance for the given cell while deactivating and
13785  * canceling previous CellEditor. It is baked into DataTable that only one CellEditor
13786  * can be active at any given time. 
13787  *
13788  * @method showCellEditor
13789  * @param elCell {HTMLElement | String} Cell to edit.
13790  */
13791 showCellEditor : function(elCell, oRecord, oColumn) {
13792     // Get a particular CellEditor
13793     elCell = this.getTdEl(elCell);
13794     if(elCell) {
13795         oColumn = this.getColumn(elCell);
13796         if(oColumn && oColumn.editor) {
13797             var oCellEditor = this._oCellEditor;
13798             // Clean up active CellEditor
13799             if(oCellEditor) {
13800                 if(this._oCellEditor.cancel) {
13801                     this._oCellEditor.cancel();
13802                 }
13803                 else if(oCellEditor.isActive) {
13804                     this.cancelCellEditor();
13805                 }
13806             }
13807             
13808             if(oColumn.editor instanceof YAHOO.widget.BaseCellEditor) {
13809                 // Get CellEditor
13810                 oCellEditor = oColumn.editor;
13811                 var ok = oCellEditor.attach(this, elCell);
13812                 if(ok) {
13813                     oCellEditor.render();
13814                     oCellEditor.move();
13815                     ok = this.doBeforeShowCellEditor(oCellEditor);
13816                     if(ok) {
13817                         oCellEditor.show();
13818                         this._oCellEditor = oCellEditor;
13819                     }
13820                 }
13821             }
13822             // Backward compatibility
13823             else {
13824                     if(!oRecord || !(oRecord instanceof YAHOO.widget.Record)) {
13825                         oRecord = this.getRecord(elCell);
13826                     }
13827                     if(!oColumn || !(oColumn instanceof YAHOO.widget.Column)) {
13828                         oColumn = this.getColumn(elCell);
13829                     }
13830                     if(oRecord && oColumn) {
13831                         if(!this._oCellEditor || this._oCellEditor.container) {
13832                             this._initCellEditorEl();
13833                         }
13834                         
13835                         // Update Editor values
13836                         oCellEditor = this._oCellEditor;
13837                         oCellEditor.cell = elCell;
13838                         oCellEditor.record = oRecord;
13839                         oCellEditor.column = oColumn;
13840                         oCellEditor.validator = (oColumn.editorOptions &&
13841                                 lang.isFunction(oColumn.editorOptions.validator)) ?
13842                                 oColumn.editorOptions.validator : null;
13843                         oCellEditor.value = oRecord.getData(oColumn.key);
13844                         oCellEditor.defaultValue = null;
13845             
13846                         // Move Editor
13847                         var elContainer = oCellEditor.container;
13848                         var x = Dom.getX(elCell);
13849                         var y = Dom.getY(elCell);
13850             
13851                         // SF doesn't get xy for cells in scrolling table
13852                         // when tbody display is set to block
13853                         if(isNaN(x) || isNaN(y)) {
13854                             x = elCell.offsetLeft + // cell pos relative to table
13855                                     Dom.getX(this._elTbody.parentNode) - // plus table pos relative to document
13856                                     this._elTbody.scrollLeft; // minus tbody scroll
13857                             y = elCell.offsetTop + // cell pos relative to table
13858                                     Dom.getY(this._elTbody.parentNode) - // plus table pos relative to document
13859                                     this._elTbody.scrollTop + // minus tbody scroll
13860                                     this._elThead.offsetHeight; // account for fixed THEAD cells
13861                         }
13862             
13863                         elContainer.style.left = x + "px";
13864                         elContainer.style.top = y + "px";
13865             
13866                         // Hook to customize the UI
13867                         this.doBeforeShowCellEditor(this._oCellEditor);
13868             
13869                         //TODO: This is temporarily up here due so elements can be focused
13870                         // Show Editor
13871                         elContainer.style.display = "";
13872             
13873                         // Handle ESC key
13874                         Ev.addListener(elContainer, "keydown", function(e, oSelf) {
13875                             // ESC hides Cell Editor
13876                             if((e.keyCode == 27)) {
13877                                 oSelf.cancelCellEditor();
13878                                 oSelf.focusTbodyEl();
13879                             }
13880                             else {
13881                                 oSelf.fireEvent("editorKeydownEvent", {editor:oSelf._oCellEditor, event:e});
13882                             }
13883                         }, this);
13884             
13885                         // Render Editor markup
13886                         var fnEditor;
13887                         if(lang.isString(oColumn.editor)) {
13888                             switch(oColumn.editor) {
13889                                 case "checkbox":
13890                                     fnEditor = DT.editCheckbox;
13891                                     break;
13892                                 case "date":
13893                                     fnEditor = DT.editDate;
13894                                     break;
13895                                 case "dropdown":
13896                                     fnEditor = DT.editDropdown;
13897                                     break;
13898                                 case "radio":
13899                                     fnEditor = DT.editRadio;
13900                                     break;
13901                                 case "textarea":
13902                                     fnEditor = DT.editTextarea;
13903                                     break;
13904                                 case "textbox":
13905                                     fnEditor = DT.editTextbox;
13906                                     break;
13907                                 default:
13908                                     fnEditor = null;
13909                             }
13910                         }
13911                         else if(lang.isFunction(oColumn.editor)) {
13912                             fnEditor = oColumn.editor;
13913                         }
13914             
13915                         if(fnEditor) {
13916                             // Create DOM input elements
13917                             fnEditor(this._oCellEditor, this);
13918             
13919                             // Show Save/Cancel buttons
13920                             if(!oColumn.editorOptions || !oColumn.editorOptions.disableBtns) {
13921                                 this.showCellEditorBtns(elContainer);
13922                             }
13923             
13924                             oCellEditor.isActive = true;
13925             
13926                             //TODO: verify which args to pass
13927                             this.fireEvent("editorShowEvent", {editor:oCellEditor});
13928                             return;
13929                         }
13930                     }
13931
13932
13933
13934             
13935             }
13936         }
13937     }
13938 },
13939
13940 /**
13941  * Backward compatibility.
13942  *
13943  * @method _initCellEditorEl
13944  * @private
13945  * @deprecated Use BaseCellEditor class.
13946  */
13947 _initCellEditorEl : function() {
13948     // Attach Cell Editor container element as first child of body
13949     var elCellEditor = document.createElement("div");
13950     elCellEditor.id = this._sId + "-celleditor";
13951     elCellEditor.style.display = "none";
13952     elCellEditor.tabIndex = 0;
13953     Dom.addClass(elCellEditor, DT.CLASS_EDITOR);
13954     var elFirstChild = Dom.getFirstChild(document.body);
13955     if(elFirstChild) {
13956         elCellEditor = Dom.insertBefore(elCellEditor, elFirstChild);
13957     }
13958     else {
13959         elCellEditor = document.body.appendChild(elCellEditor);
13960     }
13961     
13962     // Internal tracker of Cell Editor values
13963     var oCellEditor = {};
13964     oCellEditor.container = elCellEditor;
13965     oCellEditor.value = null;
13966     oCellEditor.isActive = false;
13967     this._oCellEditor = oCellEditor;
13968 },
13969
13970 /**
13971  * Overridable abstract method to customize CellEditor before showing.
13972  *
13973  * @method doBeforeShowCellEditor
13974  * @param oCellEditor {YAHOO.widget.CellEditor} The CellEditor instance.
13975  * @return {Boolean} Return true to continue showing CellEditor.
13976  */
13977 doBeforeShowCellEditor : function(oCellEditor) {
13978     return true;
13979 },
13980
13981 /**
13982  * Saves active CellEditor input to Record and upates DOM UI.
13983  *
13984  * @method saveCellEditor
13985  */
13986 saveCellEditor : function() {
13987     if(this._oCellEditor) {
13988         if(this._oCellEditor.save) {
13989             this._oCellEditor.save();
13990         }
13991         // Backward compatibility
13992         else if(this._oCellEditor.isActive) {
13993             var newData = this._oCellEditor.value;
13994             // Copy the data to pass to the event
13995             //var oldData = YAHOO.widget.DataTable._cloneObject(this._oCellEditor.record.getData(this._oCellEditor.column.key));
13996             var oldData = this._oCellEditor.record.getData(this._oCellEditor.column.key);
13997     
13998             // Validate input data
13999             if(this._oCellEditor.validator) {
14000                 newData = this._oCellEditor.value = this._oCellEditor.validator.call(this, newData, oldData, this._oCellEditor);
14001                 if(newData === null ) {
14002                     this.resetCellEditor();
14003                     this.fireEvent("editorRevertEvent",
14004                             {editor:this._oCellEditor, oldData:oldData, newData:newData});
14005                     return;
14006                 }
14007             }
14008             // Update the Record
14009             this._oRecordSet.updateRecordValue(this._oCellEditor.record, this._oCellEditor.column.key, this._oCellEditor.value);
14010             // Update the UI
14011             this.formatCell(this._oCellEditor.cell.firstChild, this._oCellEditor.record, this._oCellEditor.column);
14012             
14013             // Bug fix 1764044
14014             this._oChainRender.add({
14015                 method: function() {
14016                     this.validateColumnWidths();
14017                 },
14018                 scope: this
14019             });
14020             this._oChainRender.run();
14021             // Clear out the Cell Editor
14022             this.resetCellEditor();
14023     
14024             this.fireEvent("editorSaveEvent",
14025                     {editor:this._oCellEditor, oldData:oldData, newData:newData});
14026         }
14027     }   
14028 },
14029
14030 /**
14031  * Cancels active CellEditor.
14032  *
14033  * @method cancelCellEditor
14034  */
14035 cancelCellEditor : function() {
14036     if(this._oCellEditor) {
14037         if(this._oCellEditor.cancel) {
14038             this._oCellEditor.cancel();
14039         }
14040         // Backward compatibility
14041         else if(this._oCellEditor.isActive) {
14042             this.resetCellEditor();
14043             //TODO: preserve values for the event?
14044             this.fireEvent("editorCancelEvent", {editor:this._oCellEditor});
14045         }
14046
14047     }
14048 },
14049
14050 /**
14051  * Destroys active CellEditor instance and UI.
14052  *
14053  * @method destroyCellEditor
14054  */
14055 destroyCellEditor : function() {
14056     if(this._oCellEditor) {
14057         this._oCellEditor.destroy();
14058         this._oCellEditor = null;
14059     }   
14060 },
14061
14062 /**
14063  * Passes through showEvent of the active CellEditor.
14064  *
14065  * @method _onEditorShowEvent
14066  * @param oArgs {Object}  Custom Event args.
14067  * @private 
14068  */
14069 _onEditorShowEvent : function(oArgs) {
14070     this.fireEvent("editorShowEvent", oArgs);
14071 },
14072
14073 /**
14074  * Passes through keydownEvent of the active CellEditor.
14075  * @param oArgs {Object}  Custom Event args. 
14076  *
14077  * @method _onEditorKeydownEvent
14078  * @private 
14079  */
14080 _onEditorKeydownEvent : function(oArgs) {
14081     this.fireEvent("editorKeydownEvent", oArgs);
14082 },
14083
14084 /**
14085  * Passes through revertEvent of the active CellEditor.
14086  *
14087  * @method _onEditorRevertEvent
14088  * @param oArgs {Object}  Custom Event args. 
14089  * @private  
14090  */
14091 _onEditorRevertEvent : function(oArgs) {
14092     this.fireEvent("editorRevertEvent", oArgs);
14093 },
14094
14095 /**
14096  * Passes through saveEvent of the active CellEditor.
14097  *
14098  * @method _onEditorSaveEvent
14099  * @param oArgs {Object}  Custom Event args.  
14100  * @private 
14101  */
14102 _onEditorSaveEvent : function(oArgs) {
14103     this.fireEvent("editorSaveEvent", oArgs);
14104 },
14105
14106 /**
14107  * Passes through cancelEvent of the active CellEditor.
14108  *
14109  * @method _onEditorCancelEvent
14110  * @param oArgs {Object}  Custom Event args.
14111  * @private   
14112  */
14113 _onEditorCancelEvent : function(oArgs) {
14114     this.fireEvent("editorCancelEvent", oArgs);
14115 },
14116
14117 /**
14118  * Passes through blurEvent of the active CellEditor.
14119  *
14120  * @method _onEditorBlurEvent
14121  * @param oArgs {Object}  Custom Event args. 
14122  * @private  
14123  */
14124 _onEditorBlurEvent : function(oArgs) {
14125     this.fireEvent("editorBlurEvent", oArgs);
14126 },
14127
14128 /**
14129  * Passes through blockEvent of the active CellEditor.
14130  *
14131  * @method _onEditorBlockEvent
14132  * @param oArgs {Object}  Custom Event args. 
14133  * @private  
14134  */
14135 _onEditorBlockEvent : function(oArgs) {
14136     this.fireEvent("editorBlockEvent", oArgs);
14137 },
14138
14139 /**
14140  * Passes through unblockEvent of the active CellEditor.
14141  *
14142  * @method _onEditorUnblockEvent
14143  * @param oArgs {Object}  Custom Event args. 
14144  * @private  
14145  */
14146 _onEditorUnblockEvent : function(oArgs) {
14147     this.fireEvent("editorUnblockEvent", oArgs);
14148 },
14149
14150 /**
14151  * Public handler of the editorBlurEvent. By default, saves on blur if
14152  * disableBtns is true, otherwise cancels on blur. 
14153  *
14154  * @method onEditorBlurEvent
14155  * @param oArgs {Object}  Custom Event args.  
14156  */
14157 onEditorBlurEvent : function(oArgs) {
14158     if(oArgs.editor.disableBtns) {
14159         // Save on blur
14160         if(oArgs.editor.save) { // Backward incompatible
14161             oArgs.editor.save();
14162         }
14163     }      
14164     else if(oArgs.editor.cancel) { // Backward incompatible
14165         // Cancel on blur
14166         oArgs.editor.cancel();
14167     }      
14168 },
14169
14170 /**
14171  * Public handler of the editorBlockEvent. By default, disables DataTable UI.
14172  *
14173  * @method onEditorBlockEvent
14174  * @param oArgs {Object}  Custom Event args.  
14175  */
14176 onEditorBlockEvent : function(oArgs) {
14177     this.disable();
14178 },
14179
14180 /**
14181  * Public handler of the editorUnblockEvent. By default, undisables DataTable UI.
14182  *
14183  * @method onEditorUnblockEvent
14184  * @param oArgs {Object}  Custom Event args.  
14185  */
14186 onEditorUnblockEvent : function(oArgs) {
14187     this.undisable();
14188 },
14189
14190
14191
14192
14193
14194
14195
14196
14197
14198
14199
14200
14201
14202
14203
14204
14205
14206
14207
14208
14209
14210
14211
14212
14213
14214
14215
14216
14217
14218
14219
14220
14221
14222
14223
14224
14225
14226
14227 // ABSTRACT METHODS
14228
14229 /**
14230  * Overridable method gives implementers a hook to access data before
14231  * it gets added to RecordSet and rendered to the TBODY.
14232  *
14233  * @method doBeforeLoadData
14234  * @param sRequest {String} Original request.
14235  * @param oResponse {Object} <a href="http://developer.yahoo.com/yui/datasource/#ds_oParsedResponse">Response object</a>.
14236  * @param oPayload {MIXED} additional arguments
14237  * @return {Boolean} Return true to continue loading data into RecordSet and
14238  * updating DataTable with new Records, false to cancel.
14239  */
14240 doBeforeLoadData : function(sRequest, oResponse, oPayload) {
14241     return true;
14242 },
14243
14244
14245
14246
14247
14248
14249
14250
14251
14252
14253
14254
14255
14256
14257
14258
14259
14260
14261
14262
14263
14264
14265
14266
14267
14268
14269
14270
14271
14272
14273
14274
14275
14276
14277
14278
14279
14280
14281
14282
14283
14284
14285
14286
14287
14288
14289
14290
14291
14292
14293
14294
14295
14296
14297
14298
14299
14300
14301
14302
14303
14304
14305
14306 /////////////////////////////////////////////////////////////////////////////
14307 //
14308 // Public Custom Event Handlers
14309 //
14310 /////////////////////////////////////////////////////////////////////////////
14311
14312 /**
14313  * Custom event handler to sort Column.
14314  *
14315  * @method onEventSortColumn
14316  * @param oArgs.event {HTMLEvent} Event object.
14317  * @param oArgs.target {HTMLElement} Target element.
14318  */
14319 onEventSortColumn : function(oArgs) {
14320 //TODO: support form elements in sortable columns
14321     var evt = oArgs.event;
14322     var target = oArgs.target;
14323
14324     var el = this.getThEl(target) || this.getTdEl(target);
14325     if(el) {
14326         var oColumn = this.getColumn(el);
14327         if(oColumn.sortable) {
14328             Ev.stopEvent(evt);
14329             this.sortColumn(oColumn);
14330         }
14331     }
14332     else {
14333     }
14334 },
14335
14336 /**
14337  * Custom event handler to select Column.
14338  *
14339  * @method onEventSelectColumn
14340  * @param oArgs.event {HTMLEvent} Event object.
14341  * @param oArgs.target {HTMLElement} Target element.
14342  */
14343 onEventSelectColumn : function(oArgs) {
14344     this.selectColumn(oArgs.target);
14345 },
14346
14347 /**
14348  * Custom event handler to highlight Column. Accounts for spurious
14349  * caused-by-child events. 
14350  *
14351  * @method onEventHighlightColumn
14352  * @param oArgs.event {HTMLEvent} Event object.
14353  * @param oArgs.target {HTMLElement} Target element.
14354  */
14355 onEventHighlightColumn : function(oArgs) {
14356     this.highlightColumn(oArgs.target);
14357 },
14358
14359 /**
14360  * Custom event handler to unhighlight Column. Accounts for spurious
14361  * caused-by-child events. 
14362  *
14363  * @method onEventUnhighlightColumn
14364  * @param oArgs.event {HTMLEvent} Event object.
14365  * @param oArgs.target {HTMLElement} Target element.
14366  */
14367 onEventUnhighlightColumn : function(oArgs) {
14368     this.unhighlightColumn(oArgs.target);
14369 },
14370
14371 /**
14372  * Custom event handler to manage selection according to desktop paradigm.
14373  *
14374  * @method onEventSelectRow
14375  * @param oArgs.event {HTMLEvent} Event object.
14376  * @param oArgs.target {HTMLElement} Target element.
14377  */
14378 onEventSelectRow : function(oArgs) {
14379     var sMode = this.get("selectionMode");
14380     if(sMode == "single") {
14381         this._handleSingleSelectionByMouse(oArgs);
14382     }
14383     else {
14384         this._handleStandardSelectionByMouse(oArgs);
14385     }
14386 },
14387
14388 /**
14389  * Custom event handler to select cell.
14390  *
14391  * @method onEventSelectCell
14392  * @param oArgs.event {HTMLEvent} Event object.
14393  * @param oArgs.target {HTMLElement} Target element.
14394  */
14395 onEventSelectCell : function(oArgs) {
14396     var sMode = this.get("selectionMode");
14397     if(sMode == "cellblock") {
14398         this._handleCellBlockSelectionByMouse(oArgs);
14399     }
14400     else if(sMode == "cellrange") {
14401         this._handleCellRangeSelectionByMouse(oArgs);
14402     }
14403     else {
14404         this._handleSingleCellSelectionByMouse(oArgs);
14405     }
14406 },
14407
14408 /**
14409  * Custom event handler to highlight row. Accounts for spurious
14410  * caused-by-child events. 
14411  *
14412  * @method onEventHighlightRow
14413  * @param oArgs.event {HTMLEvent} Event object.
14414  * @param oArgs.target {HTMLElement} Target element.
14415  */
14416 onEventHighlightRow : function(oArgs) {
14417     this.highlightRow(oArgs.target);
14418 },
14419
14420 /**
14421  * Custom event handler to unhighlight row. Accounts for spurious
14422  * caused-by-child events. 
14423  *
14424  * @method onEventUnhighlightRow
14425  * @param oArgs.event {HTMLEvent} Event object.
14426  * @param oArgs.target {HTMLElement} Target element.
14427  */
14428 onEventUnhighlightRow : function(oArgs) {
14429     this.unhighlightRow(oArgs.target);
14430 },
14431
14432 /**
14433  * Custom event handler to highlight cell. Accounts for spurious
14434  * caused-by-child events. 
14435  *
14436  * @method onEventHighlightCell
14437  * @param oArgs.event {HTMLEvent} Event object.
14438  * @param oArgs.target {HTMLElement} Target element.
14439  */
14440 onEventHighlightCell : function(oArgs) {
14441     this.highlightCell(oArgs.target);
14442 },
14443
14444 /**
14445  * Custom event handler to unhighlight cell. Accounts for spurious
14446  * caused-by-child events. 
14447  *
14448  * @method onEventUnhighlightCell
14449  * @param oArgs.event {HTMLEvent} Event object.
14450  * @param oArgs.target {HTMLElement} Target element.
14451  */
14452 onEventUnhighlightCell : function(oArgs) {
14453     this.unhighlightCell(oArgs.target);
14454 },
14455
14456 /**
14457  * Custom event handler to format cell.
14458  *
14459  * @method onEventFormatCell
14460  * @param oArgs.event {HTMLEvent} Event object.
14461  * @param oArgs.target {HTMLElement} Target element.
14462  */
14463 onEventFormatCell : function(oArgs) {
14464     var target = oArgs.target;
14465
14466     var elCell = this.getTdEl(target);
14467     if(elCell) {
14468         var oColumn = this.getColumn(this.getCellIndex(elCell));
14469         this.formatCell(elCell.firstChild, this.getRecord(elCell), oColumn);
14470     }
14471     else {
14472     }
14473 },
14474
14475 /**
14476  * Custom event handler to edit cell.
14477  *
14478  * @method onEventShowCellEditor
14479  * @param oArgs.event {HTMLEvent} Event object.
14480  * @param oArgs.target {HTMLElement} Target element.
14481  */
14482 onEventShowCellEditor : function(oArgs) {
14483     if(!this.isDisabled()) {
14484         this.showCellEditor(oArgs.target);
14485     }
14486 },
14487
14488 /**
14489  * Custom event handler to save active CellEditor input.
14490  *
14491  * @method onEventSaveCellEditor
14492  */
14493 onEventSaveCellEditor : function(oArgs) {
14494     if(this._oCellEditor) {
14495         if(this._oCellEditor.save) {
14496             this._oCellEditor.save();
14497         }
14498         // Backward compatibility
14499         else {
14500             this.saveCellEditor();
14501         }
14502     }
14503 },
14504
14505 /**
14506  * Custom event handler to cancel active CellEditor.
14507  *
14508  * @method onEventCancelCellEditor
14509  */
14510 onEventCancelCellEditor : function(oArgs) {
14511     if(this._oCellEditor) {
14512         if(this._oCellEditor.cancel) {
14513             this._oCellEditor.cancel();
14514         }
14515         // Backward compatibility
14516         else {
14517             this.cancelCellEditor();
14518         }
14519     }
14520 },
14521
14522 /**
14523  * Callback function receives data from DataSource and populates an entire
14524  * DataTable with Records and TR elements, clearing previous Records, if any.
14525  *
14526  * @method onDataReturnInitializeTable
14527  * @param sRequest {String} Original request.
14528  * @param oResponse {Object} <a href="http://developer.yahoo.com/yui/datasource/#ds_oParsedResponse">Response object</a>.
14529  * @param oPayload {MIXED} (optional) Additional argument(s)
14530  */
14531 onDataReturnInitializeTable : function(sRequest, oResponse, oPayload) {
14532     if((this instanceof DT) && this._sId) {
14533         this.initializeTable();
14534     
14535         this.onDataReturnSetRows(sRequest,oResponse,oPayload);
14536     }
14537 },
14538
14539 /**
14540  * Callback function receives reponse from DataSource, replaces all existing
14541  * Records in  RecordSet, updates TR elements with new data, and updates state
14542  * UI for pagination and sorting from payload data, if necessary. 
14543  *  
14544  * @method onDataReturnReplaceRows
14545  * @param oRequest {MIXED} Original generated request.
14546  * @param oResponse {Object} <a href="http://developer.yahoo.com/yui/datasource/#ds_oParsedResponse">Response object</a>.
14547  * @param oPayload {MIXED} (optional) Additional argument(s)
14548  */
14549 onDataReturnReplaceRows : function(oRequest, oResponse, oPayload) {
14550     if((this instanceof DT) && this._sId) {
14551         this.fireEvent("dataReturnEvent", {request:oRequest,response:oResponse,payload:oPayload});
14552     
14553         // Pass data through abstract method for any transformations
14554         var ok    = this.doBeforeLoadData(oRequest, oResponse, oPayload),
14555             pag   = this.get('paginator'),
14556             index = 0;
14557     
14558         // Data ok to set
14559         if(ok && oResponse && !oResponse.error && lang.isArray(oResponse.results)) {
14560             // Update Records
14561             this._oRecordSet.reset();
14562     
14563             if (this.get('dynamicData')) {
14564                 if (oPayload && oPayload.pagination &&
14565                     lang.isNumber(oPayload.pagination.recordOffset)) {
14566                     index = oPayload.pagination.recordOffset;
14567                 } else if (pag) {
14568                     index = pag.getStartIndex();
14569                 }
14570             }
14571     
14572             this._oRecordSet.setRecords(oResponse.results, index | 0);
14573             
14574             // Update state
14575             this._handleDataReturnPayload(oRequest, oResponse, oPayload);
14576             
14577             // Update UI
14578             this.render();    
14579         }
14580         // Error
14581         else if(ok && oResponse.error) {
14582             this.showTableMessage(this.get("MSG_ERROR"), DT.CLASS_ERROR);
14583         }
14584     }
14585 },
14586
14587 /**
14588  * Callback function receives data from DataSource and appends to an existing
14589  * DataTable new Records and, if applicable, creates or updates
14590  * corresponding TR elements.
14591  *
14592  * @method onDataReturnAppendRows
14593  * @param sRequest {String} Original request.
14594  * @param oResponse {Object} <a href="http://developer.yahoo.com/yui/datasource/#ds_oParsedResponse">Response object</a>.
14595  * @param oPayload {MIXED} (optional) Additional argument(s)
14596  */
14597 onDataReturnAppendRows : function(sRequest, oResponse, oPayload) {
14598     if((this instanceof DT) && this._sId) {
14599         this.fireEvent("dataReturnEvent", {request:sRequest,response:oResponse,payload:oPayload});
14600     
14601         // Pass data through abstract method for any transformations
14602         var ok = this.doBeforeLoadData(sRequest, oResponse, oPayload);
14603     
14604         // Data ok to append
14605         if(ok && oResponse && !oResponse.error && lang.isArray(oResponse.results)) {        
14606             // Append rows
14607             this.addRows(oResponse.results);
14608     
14609             // Update state
14610             this._handleDataReturnPayload(sRequest, oResponse, oPayload);
14611         }
14612         // Error
14613         else if(ok && oResponse.error) {
14614             this.showTableMessage(this.get("MSG_ERROR"), DT.CLASS_ERROR);
14615         }
14616     }
14617 },
14618
14619 /**
14620  * Callback function receives data from DataSource and inserts new records
14621  * starting at the index specified in oPayload.insertIndex. The value for
14622  * oPayload.insertIndex can be populated when sending the request to the DataSource,
14623  * or by accessing oPayload.insertIndex with the doBeforeLoadData() method at runtime.
14624  * If applicable, creates or updates corresponding TR elements.
14625  *
14626  * @method onDataReturnInsertRows
14627  * @param sRequest {String} Original request.
14628  * @param oResponse {Object} <a href="http://developer.yahoo.com/yui/datasource/#ds_oParsedResponse">Response object</a>.
14629  * @param oPayload {MIXED} Argument payload, looks in oPayload.insertIndex.
14630  */
14631 onDataReturnInsertRows : function(sRequest, oResponse, oPayload) {
14632     if((this instanceof DT) && this._sId) {
14633         this.fireEvent("dataReturnEvent", {request:sRequest,response:oResponse,payload:oPayload});
14634     
14635         // Pass data through abstract method for any transformations
14636         var ok = this.doBeforeLoadData(sRequest, oResponse, oPayload);
14637     
14638         // Data ok to append
14639         if(ok && oResponse && !oResponse.error && lang.isArray(oResponse.results)) {
14640             // Insert rows
14641             this.addRows(oResponse.results, (oPayload ? oPayload.insertIndex : 0));
14642     
14643             // Update state
14644             this._handleDataReturnPayload(sRequest, oResponse, oPayload);
14645         }
14646         // Error
14647         else if(ok && oResponse.error) {
14648             this.showTableMessage(this.get("MSG_ERROR"), DT.CLASS_ERROR);
14649         }
14650     }
14651 },
14652
14653 /**
14654  * Callback function receives data from DataSource and incrementally updates Records
14655  * starting at the index specified in oPayload.updateIndex. The value for
14656  * oPayload.updateIndex can be populated when sending the request to the DataSource,
14657  * or by accessing oPayload.updateIndex with the doBeforeLoadData() method at runtime.
14658  * If applicable, creates or updates corresponding TR elements.
14659  *
14660  * @method onDataReturnUpdateRows
14661  * @param sRequest {String} Original request.
14662  * @param oResponse {Object} <a href="http://developer.yahoo.com/yui/datasource/#ds_oParsedResponse">Response object</a>.
14663  * @param oPayload {MIXED} Argument payload, looks in oPayload.updateIndex.
14664  */
14665 onDataReturnUpdateRows : function(sRequest, oResponse, oPayload) {
14666     if((this instanceof DT) && this._sId) {
14667         this.fireEvent("dataReturnEvent", {request:sRequest,response:oResponse,payload:oPayload});
14668     
14669         // Pass data through abstract method for any transformations
14670         var ok = this.doBeforeLoadData(sRequest, oResponse, oPayload);
14671     
14672         // Data ok to append
14673         if(ok && oResponse && !oResponse.error && lang.isArray(oResponse.results)) {
14674             // Insert rows
14675             this.updateRows((oPayload ? oPayload.updateIndex : 0), oResponse.results);
14676     
14677             // Update state
14678             this._handleDataReturnPayload(sRequest, oResponse, oPayload);
14679         }
14680         // Error
14681         else if(ok && oResponse.error) {
14682             this.showTableMessage(this.get("MSG_ERROR"), DT.CLASS_ERROR);
14683         }
14684     }
14685 },
14686
14687 /**
14688  * Callback function receives reponse from DataSource and populates the
14689  * RecordSet with the results.
14690  *  
14691  * @method onDataReturnSetRows
14692  * @param oRequest {MIXED} Original generated request.
14693  * @param oResponse {Object} <a href="http://developer.yahoo.com/yui/datasource/#ds_oParsedResponse">Response object</a>.
14694  * @param oPayload {MIXED} (optional) Additional argument(s)
14695  */
14696 onDataReturnSetRows : function(oRequest, oResponse, oPayload) {
14697     if((this instanceof DT) && this._sId) {
14698         this.fireEvent("dataReturnEvent", {request:oRequest,response:oResponse,payload:oPayload});
14699     
14700         // Pass data through abstract method for any transformations
14701         var ok    = this.doBeforeLoadData(oRequest, oResponse, oPayload),
14702             pag   = this.get('paginator'),
14703             index = 0;
14704     
14705         // Data ok to set
14706         if(ok && oResponse && !oResponse.error && lang.isArray(oResponse.results)) {
14707             // Update Records
14708             if (this.get('dynamicData')) {
14709                 if (oPayload && oPayload.pagination &&
14710                     lang.isNumber(oPayload.pagination.recordOffset)) {
14711                     index = oPayload.pagination.recordOffset;
14712                 } else if (pag) {
14713                     index = pag.getStartIndex();
14714                 }
14715                 
14716                 this._oRecordSet.reset(); // Bug 2290604: dyanmic data shouldn't keep accumulating by default
14717             }
14718     
14719             this._oRecordSet.setRecords(oResponse.results, index | 0);
14720     
14721             // Update state
14722             this._handleDataReturnPayload(oRequest, oResponse, oPayload);
14723             
14724             // Update UI
14725             this.render();
14726         }
14727         // Error
14728         else if(ok && oResponse.error) {
14729             this.showTableMessage(this.get("MSG_ERROR"), DT.CLASS_ERROR);
14730         }
14731     }
14732     else {
14733     }
14734 },
14735
14736 /**
14737  * Hook to update oPayload before consumption.
14738  *  
14739  * @method handleDataReturnPayload
14740  * @param oRequest {MIXED} Original generated request.
14741  * @param oResponse {Object} <a href="http://developer.yahoo.com/yui/datasource/#ds_oParsedResponse">Response object</a>.
14742  * @param oPayload {MIXED} State values.
14743  * @return oPayload {MIXED} State values.
14744  */
14745 handleDataReturnPayload : function (oRequest, oResponse, oPayload) {
14746     return oPayload || {};
14747 },
14748
14749 /**
14750  * Updates the DataTable with state data sent in an onDataReturn* payload.
14751  *  
14752  * @method _handleDataReturnPayload
14753  * @param oRequest {MIXED} Original generated request.
14754  * @param oResponse {Object} <a href="http://developer.yahoo.com/yui/datasource/#ds_oParsedResponse">Response object</a>.
14755  * @param oPayload {MIXED} State values
14756  * @private
14757  */
14758 _handleDataReturnPayload : function (oRequest, oResponse, oPayload) {
14759     oPayload = this.handleDataReturnPayload(oRequest, oResponse, oPayload);
14760     if(oPayload) {
14761         // Update pagination
14762         var oPaginator = this.get('paginator');
14763         if (oPaginator) {
14764             // Update totalRecords
14765             if(this.get("dynamicData")) {
14766                 if (widget.Paginator.isNumeric(oPayload.totalRecords)) {
14767                     oPaginator.set('totalRecords',oPayload.totalRecords);
14768                 }
14769             }
14770             else {
14771                 oPaginator.set('totalRecords',this._oRecordSet.getLength());
14772             }
14773             // Update other paginator values
14774             if (lang.isObject(oPayload.pagination)) {
14775                 oPaginator.set('rowsPerPage',oPayload.pagination.rowsPerPage);
14776                 oPaginator.set('recordOffset',oPayload.pagination.recordOffset);
14777             }
14778         }
14779
14780         // Update sorting
14781         if (oPayload.sortedBy) {
14782             // Set the sorting values in preparation for refresh
14783             this.set('sortedBy', oPayload.sortedBy);
14784         }
14785         // Backwards compatibility for sorting
14786         else if (oPayload.sorting) {
14787             // Set the sorting values in preparation for refresh
14788             this.set('sortedBy', oPayload.sorting);
14789         }
14790     }
14791 },
14792
14793
14794
14795
14796
14797
14798
14799
14800
14801
14802
14803
14804
14805
14806
14807
14808
14809
14810
14811
14812
14813
14814
14815
14816
14817
14818
14819
14820
14821
14822
14823
14824
14825     /////////////////////////////////////////////////////////////////////////////
14826     //
14827     // Custom Events
14828     //
14829     /////////////////////////////////////////////////////////////////////////////
14830
14831     /**
14832      * Fired when the DataTable's rows are rendered from an initialized state.
14833      *
14834      * @event initEvent
14835      */
14836
14837     /**
14838      * Fired before the DataTable's DOM is rendered or modified.
14839      *
14840      * @event beforeRenderEvent
14841      */
14842
14843     /**
14844      * Fired when the DataTable's DOM is rendered or modified.
14845      *
14846      * @event renderEvent
14847      */
14848
14849     /**
14850      * Fired when the DataTable's post-render routine is complete, including
14851      * Column width validations.
14852      *
14853      * @event postRenderEvent
14854      */
14855
14856     /**
14857      * Fired when the DataTable is disabled.
14858      *
14859      * @event disableEvent
14860      */
14861
14862     /**
14863      * Fired when the DataTable is undisabled.
14864      *
14865      * @event undisableEvent
14866      */
14867
14868     /**
14869      * Fired when data is returned from DataSource but before it is consumed by
14870      * DataTable.
14871      *
14872      * @event dataReturnEvent
14873      * @param oArgs.request {String} Original request.
14874      * @param oArgs.response {Object} Response object.
14875      */
14876
14877     /**
14878      * Fired when the DataTable has a focus event.
14879      *
14880      * @event tableFocusEvent
14881      */
14882
14883     /**
14884      * Fired when the DataTable THEAD element has a focus event.
14885      *
14886      * @event theadFocusEvent
14887      */
14888
14889     /**
14890      * Fired when the DataTable TBODY element has a focus event.
14891      *
14892      * @event tbodyFocusEvent
14893      */
14894
14895     /**
14896      * Fired when the DataTable has a blur event.
14897      *
14898      * @event tableBlurEvent
14899      */
14900
14901     /*TODO implement theadBlurEvent
14902      * Fired when the DataTable THEAD element has a blur event.
14903      *
14904      * @event theadBlurEvent
14905      */
14906
14907     /*TODO: implement tbodyBlurEvent
14908      * Fired when the DataTable TBODY element has a blur event.
14909      *
14910      * @event tbodyBlurEvent
14911      */
14912
14913     /**
14914      * Fired when the DataTable has a key event.
14915      *
14916      * @event tableKeyEvent
14917      * @param oArgs.event {HTMLEvent} The event object.
14918      * @param oArgs.target {HTMLElement} The DataTable's TABLE element.
14919      */
14920
14921     /**
14922      * Fired when the DataTable THEAD element has a key event.
14923      *
14924      * @event theadKeyEvent
14925      * @param oArgs.event {HTMLEvent} The event object.
14926      * @param oArgs.target {HTMLElement} The DataTable's TABLE element.
14927      */
14928
14929     /**
14930      * Fired when the DataTable TBODY element has a key event.
14931      *
14932      * @event tbodyKeyEvent
14933      * @param oArgs.event {HTMLEvent} The event object.
14934      * @param oArgs.target {HTMLElement} The DataTable's TABLE element.
14935      */
14936
14937     /**
14938      * Fired when the DataTable has a mouseover.
14939      *
14940      * @event tableMouseoverEvent
14941      * @param oArgs.event {HTMLEvent} The event object.
14942      * @param oArgs.target {HTMLElement} The DataTable's TABLE element.
14943      *
14944      */
14945
14946     /**
14947      * Fired when the DataTable has a mouseout.
14948      *
14949      * @event tableMouseoutEvent
14950      * @param oArgs.event {HTMLEvent} The event object.
14951      * @param oArgs.target {HTMLElement} The DataTable's TABLE element.
14952      *
14953      */
14954
14955     /**
14956      * Fired when the DataTable has a mousedown.
14957      *
14958      * @event tableMousedownEvent
14959      * @param oArgs.event {HTMLEvent} The event object.
14960      * @param oArgs.target {HTMLElement} The DataTable's TABLE element.
14961      *
14962      */
14963
14964     /**
14965      * Fired when the DataTable has a mouseup.
14966      *
14967      * @event tableMouseupEvent
14968      * @param oArgs.event {HTMLEvent} The event object.
14969      * @param oArgs.target {HTMLElement} The DataTable's TABLE element.
14970      *
14971      */
14972
14973     /**
14974      * Fired when the DataTable has a click.
14975      *
14976      * @event tableClickEvent
14977      * @param oArgs.event {HTMLEvent} The event object.
14978      * @param oArgs.target {HTMLElement} The DataTable's TABLE element.
14979      *
14980      */
14981
14982     /**
14983      * Fired when the DataTable has a dblclick.
14984      *
14985      * @event tableDblclickEvent
14986      * @param oArgs.event {HTMLEvent} The event object.
14987      * @param oArgs.target {HTMLElement} The DataTable's TABLE element.
14988      *
14989      */
14990
14991     /**
14992      * Fired when a message is shown in the DataTable's message element.
14993      *
14994      * @event tableMsgShowEvent
14995      * @param oArgs.html {HTML} The HTML displayed.
14996      * @param oArgs.className {String} The className assigned.
14997      *
14998      */
14999
15000     /**
15001      * Fired when the DataTable's message element is hidden.
15002      *
15003      * @event tableMsgHideEvent
15004      */
15005
15006     /**
15007      * Fired when a THEAD row has a mouseover.
15008      *
15009      * @event theadRowMouseoverEvent
15010      * @param oArgs.event {HTMLEvent} The event object.
15011      * @param oArgs.target {HTMLElement} The TR element.
15012      */
15013
15014     /**
15015      * Fired when a THEAD row has a mouseout.
15016      *
15017      * @event theadRowMouseoutEvent
15018      * @param oArgs.event {HTMLEvent} The event object.
15019      * @param oArgs.target {HTMLElement} The TR element.
15020      */
15021
15022     /**
15023      * Fired when a THEAD row has a mousedown.
15024      *
15025      * @event theadRowMousedownEvent
15026      * @param oArgs.event {HTMLEvent} The event object.
15027      * @param oArgs.target {HTMLElement} The TR element.
15028      */
15029
15030     /**
15031      * Fired when a THEAD row has a mouseup.
15032      *
15033      * @event theadRowMouseupEvent
15034      * @param oArgs.event {HTMLEvent} The event object.
15035      * @param oArgs.target {HTMLElement} The TR element.
15036      */
15037
15038     /**
15039      * Fired when a THEAD row has a click.
15040      *
15041      * @event theadRowClickEvent
15042      * @param oArgs.event {HTMLEvent} The event object.
15043      * @param oArgs.target {HTMLElement} The TR element.
15044      */
15045
15046     /**
15047      * Fired when a THEAD row has a dblclick.
15048      *
15049      * @event theadRowDblclickEvent
15050      * @param oArgs.event {HTMLEvent} The event object.
15051      * @param oArgs.target {HTMLElement} The TR element.
15052      */
15053
15054     /**
15055      * Fired when a THEAD cell has a mouseover.
15056      *
15057      * @event theadCellMouseoverEvent
15058      * @param oArgs.event {HTMLEvent} The event object.
15059      * @param oArgs.target {HTMLElement} The TH element.
15060      *
15061      */
15062
15063     /**
15064      * Fired when a THEAD cell has a mouseout.
15065      *
15066      * @event theadCellMouseoutEvent
15067      * @param oArgs.event {HTMLEvent} The event object.
15068      * @param oArgs.target {HTMLElement} The TH element.
15069      *
15070      */
15071
15072     /**
15073      * Fired when a THEAD cell has a mousedown.
15074      *
15075      * @event theadCellMousedownEvent
15076      * @param oArgs.event {HTMLEvent} The event object.
15077      * @param oArgs.target {HTMLElement} The TH element.
15078      */
15079
15080     /**
15081      * Fired when a THEAD cell has a mouseup.
15082      *
15083      * @event theadCellMouseupEvent
15084      * @param oArgs.event {HTMLEvent} The event object.
15085      * @param oArgs.target {HTMLElement} The TH element.
15086      */
15087
15088     /**
15089      * Fired when a THEAD cell has a click.
15090      *
15091      * @event theadCellClickEvent
15092      * @param oArgs.event {HTMLEvent} The event object.
15093      * @param oArgs.target {HTMLElement} The TH element.
15094      */
15095
15096     /**
15097      * Fired when a THEAD cell has a dblclick.
15098      *
15099      * @event theadCellDblclickEvent
15100      * @param oArgs.event {HTMLEvent} The event object.
15101      * @param oArgs.target {HTMLElement} The TH element.
15102      */
15103
15104     /**
15105      * Fired when a THEAD label has a mouseover.
15106      *
15107      * @event theadLabelMouseoverEvent
15108      * @param oArgs.event {HTMLEvent} The event object.
15109      * @param oArgs.target {HTMLElement} The SPAN element.
15110      *
15111      */
15112
15113     /**
15114      * Fired when a THEAD label has a mouseout.
15115      *
15116      * @event theadLabelMouseoutEvent
15117      * @param oArgs.event {HTMLEvent} The event object.
15118      * @param oArgs.target {HTMLElement} The SPAN element.
15119      *
15120      */
15121
15122     /**
15123      * Fired when a THEAD label has a mousedown.
15124      *
15125      * @event theadLabelMousedownEvent
15126      * @param oArgs.event {HTMLEvent} The event object.
15127      * @param oArgs.target {HTMLElement} The SPAN element.
15128      */
15129
15130     /**
15131      * Fired when a THEAD label has a mouseup.
15132      *
15133      * @event theadLabelMouseupEvent
15134      * @param oArgs.event {HTMLEvent} The event object.
15135      * @param oArgs.target {HTMLElement} The SPAN element.
15136      */
15137
15138     /**
15139      * Fired when a THEAD label has a click.
15140      *
15141      * @event theadLabelClickEvent
15142      * @param oArgs.event {HTMLEvent} The event object.
15143      * @param oArgs.target {HTMLElement} The SPAN element.
15144      */
15145
15146     /**
15147      * Fired when a THEAD label has a dblclick.
15148      *
15149      * @event theadLabelDblclickEvent
15150      * @param oArgs.event {HTMLEvent} The event object.
15151      * @param oArgs.target {HTMLElement} The SPAN element.
15152      */
15153
15154     /**
15155      * Fired when a column is sorted.
15156      *
15157      * @event columnSortEvent
15158      * @param oArgs.column {YAHOO.widget.Column} The Column instance.
15159      * @param oArgs.dir {String} Sort direction: YAHOO.widget.DataTable.CLASS_ASC
15160      * or YAHOO.widget.DataTable.CLASS_DESC.
15161      */
15162
15163     /**
15164      * Fired when a column width is set.
15165      *
15166      * @event columnSetWidthEvent
15167      * @param oArgs.column {YAHOO.widget.Column} The Column instance.
15168      * @param oArgs.width {Number} The width in pixels.
15169      */
15170
15171     /**
15172      * Fired when a column width is unset.
15173      *
15174      * @event columnUnsetWidthEvent
15175      * @param oArgs.column {YAHOO.widget.Column} The Column instance.
15176      */
15177
15178     /**
15179      * Fired when a column is drag-resized.
15180      *
15181      * @event columnResizeEvent
15182      * @param oArgs.column {YAHOO.widget.Column} The Column instance.
15183      * @param oArgs.target {HTMLElement} The TH element.
15184      * @param oArgs.width {Number} Width in pixels.     
15185      */
15186
15187     /**
15188      * Fired when a Column is moved to a new index.
15189      *
15190      * @event columnReorderEvent
15191      * @param oArgs.column {YAHOO.widget.Column} The Column instance.
15192      * @param oArgs.oldIndex {Number} The previous tree index position.
15193      */
15194
15195     /**
15196      * Fired when a column is hidden.
15197      *
15198      * @event columnHideEvent
15199      * @param oArgs.column {YAHOO.widget.Column} The Column instance.
15200      */
15201
15202     /**
15203      * Fired when a column is shown.
15204      *
15205      * @event columnShowEvent
15206      * @param oArgs.column {YAHOO.widget.Column} The Column instance.
15207      */
15208
15209     /**
15210      * Fired when a column is selected.
15211      *
15212      * @event columnSelectEvent
15213      * @param oArgs.column {YAHOO.widget.Column} The Column instance.
15214      */
15215
15216     /**
15217      * Fired when a column is unselected.
15218      *
15219      * @event columnUnselectEvent
15220      * @param oArgs.column {YAHOO.widget.Column} The Column instance.
15221      */
15222     /**
15223      * Fired when a column is removed.
15224      *
15225      * @event columnRemoveEvent
15226      * @param oArgs.column {YAHOO.widget.Column} The Column instance.
15227      */
15228
15229     /**
15230      * Fired when a column is inserted.
15231      *
15232      * @event columnInsertEvent
15233      * @param oArgs.column {YAHOO.widget.Column} The Column instance.
15234      * @param oArgs.index {Number} The index position.
15235      */
15236
15237     /**
15238      * Fired when a column is highlighted.
15239      *
15240      * @event columnHighlightEvent
15241      * @param oArgs.column {YAHOO.widget.Column} The highlighted Column.
15242      */
15243
15244     /**
15245      * Fired when a column is unhighlighted.
15246      *
15247      * @event columnUnhighlightEvent
15248      * @param oArgs.column {YAHOO.widget.Column} The unhighlighted Column.
15249      */
15250
15251
15252     /**
15253      * Fired when a row has a mouseover.
15254      *
15255      * @event rowMouseoverEvent
15256      * @param oArgs.event {HTMLEvent} The event object.
15257      * @param oArgs.target {HTMLElement} The TR element.
15258      */
15259
15260     /**
15261      * Fired when a row has a mouseout.
15262      *
15263      * @event rowMouseoutEvent
15264      * @param oArgs.event {HTMLEvent} The event object.
15265      * @param oArgs.target {HTMLElement} The TR element.
15266      */
15267
15268     /**
15269      * Fired when a row has a mousedown.
15270      *
15271      * @event rowMousedownEvent
15272      * @param oArgs.event {HTMLEvent} The event object.
15273      * @param oArgs.target {HTMLElement} The TR element.
15274      */
15275
15276     /**
15277      * Fired when a row has a mouseup.
15278      *
15279      * @event rowMouseupEvent
15280      * @param oArgs.event {HTMLEvent} The event object.
15281      * @param oArgs.target {HTMLElement} The TR element.
15282      */
15283
15284     /**
15285      * Fired when a row has a click.
15286      *
15287      * @event rowClickEvent
15288      * @param oArgs.event {HTMLEvent} The event object.
15289      * @param oArgs.target {HTMLElement} The TR element.
15290      */
15291
15292     /**
15293      * Fired when a row has a dblclick.
15294      *
15295      * @event rowDblclickEvent
15296      * @param oArgs.event {HTMLEvent} The event object.
15297      * @param oArgs.target {HTMLElement} The TR element.
15298      */
15299
15300     /**
15301      * Fired when a row is added.
15302      *
15303      * @event rowAddEvent
15304      * @param oArgs.record {YAHOO.widget.Record} The added Record.
15305      */
15306      
15307     /**
15308      * Fired when rows are added.
15309      *
15310      * @event rowsAddEvent
15311      * @param oArgs.record {YAHOO.widget.Record[]} The added Records.
15312      */
15313
15314     /**
15315      * Fired when a row is updated.
15316      *
15317      * @event rowUpdateEvent
15318      * @param oArgs.record {YAHOO.widget.Record} The updated Record.
15319      * @param oArgs.oldData {Object} Object literal of the old data.
15320      */
15321
15322     /**
15323      * Fired when a row is deleted.
15324      *
15325      * @event rowDeleteEvent
15326      * @param oArgs.oldData {Object} Object literal of the deleted data.
15327      * @param oArgs.recordIndex {Number} Index of the deleted Record.
15328      * @param oArgs.trElIndex {Number} Index of the deleted TR element, if on current page.
15329      */
15330      
15331     /**
15332      * Fired when rows are deleted.
15333      *
15334      * @event rowsDeleteEvent
15335      * @param oArgs.oldData {Object[]} Array of object literals of the deleted data.
15336      * @param oArgs.recordIndex {Number} Index of the first deleted Record.
15337      * @param oArgs.count {Number} Number of deleted Records.
15338      */
15339
15340     /**
15341      * Fired when a row is selected.
15342      *
15343      * @event rowSelectEvent
15344      * @param oArgs.el {HTMLElement} The selected TR element, if applicable.
15345      * @param oArgs.record {YAHOO.widget.Record} The selected Record.
15346      */
15347
15348     /**
15349      * Fired when a row is unselected.
15350      *
15351      * @event rowUnselectEvent
15352      * @param oArgs.el {HTMLElement} The unselected TR element, if applicable.
15353      * @param oArgs.record {YAHOO.widget.Record} The unselected Record.
15354      */
15355
15356     /**
15357      * Fired when all row selections are cleared.
15358      *
15359      * @event unselectAllRowsEvent
15360      */
15361
15362     /**
15363      * Fired when a row is highlighted.
15364      *
15365      * @event rowHighlightEvent
15366      * @param oArgs.el {HTMLElement} The highlighted TR element.
15367      * @param oArgs.record {YAHOO.widget.Record} The highlighted Record.
15368      */
15369
15370     /**
15371      * Fired when a row is unhighlighted.
15372      *
15373      * @event rowUnhighlightEvent
15374      * @param oArgs.el {HTMLElement} The highlighted TR element.
15375      * @param oArgs.record {YAHOO.widget.Record} The highlighted Record.
15376      */
15377
15378     /**
15379      * Fired when a cell is updated.
15380      *
15381      * @event cellUpdateEvent
15382      * @param oArgs.record {YAHOO.widget.Record} The updated Record.
15383      * @param oArgs.column {YAHOO.widget.Column} The updated Column.
15384      * @param oArgs.oldData {Object} Original data value of the updated cell.
15385      */
15386
15387     /**
15388      * Fired when a cell has a mouseover.
15389      *
15390      * @event cellMouseoverEvent
15391      * @param oArgs.event {HTMLEvent} The event object.
15392      * @param oArgs.target {HTMLElement} The TD element.
15393      */
15394
15395     /**
15396      * Fired when a cell has a mouseout.
15397      *
15398      * @event cellMouseoutEvent
15399      * @param oArgs.event {HTMLEvent} The event object.
15400      * @param oArgs.target {HTMLElement} The TD element.
15401      */
15402
15403     /**
15404      * Fired when a cell has a mousedown.
15405      *
15406      * @event cellMousedownEvent
15407      * @param oArgs.event {HTMLEvent} The event object.
15408      * @param oArgs.target {HTMLElement} The TD element.
15409      */
15410
15411     /**
15412      * Fired when a cell has a mouseup.
15413      *
15414      * @event cellMouseupEvent
15415      * @param oArgs.event {HTMLEvent} The event object.
15416      * @param oArgs.target {HTMLElement} The TD element.
15417      */
15418
15419     /**
15420      * Fired when a cell has a click.
15421      *
15422      * @event cellClickEvent
15423      * @param oArgs.event {HTMLEvent} The event object.
15424      * @param oArgs.target {HTMLElement} The TD element.
15425      */
15426
15427     /**
15428      * Fired when a cell has a dblclick.
15429      *
15430      * @event cellDblclickEvent
15431      * @param oArgs.event {HTMLEvent} The event object.
15432      * @param oArgs.target {HTMLElement} The TD element.
15433      */
15434
15435     /**
15436      * Fired when a cell is formatted.
15437      *
15438      * @event cellFormatEvent
15439      * @param oArgs.el {HTMLElement} The formatted TD element.
15440      * @param oArgs.record {YAHOO.widget.Record} The associated Record instance.
15441      * @param oArgs.column {YAHOO.widget.Column} The associated Column instance.
15442      * @param oArgs.key {String} (deprecated) The key of the formatted cell.
15443      */
15444
15445     /**
15446      * Fired when a cell is selected.
15447      *
15448      * @event cellSelectEvent
15449      * @param oArgs.el {HTMLElement} The selected TD element.
15450      * @param oArgs.record {YAHOO.widget.Record} The associated Record instance.
15451      * @param oArgs.column {YAHOO.widget.Column} The associated Column instance.
15452      * @param oArgs.key {String} (deprecated) The key of the selected cell.
15453      */
15454
15455     /**
15456      * Fired when a cell is unselected.
15457      *
15458      * @event cellUnselectEvent
15459      * @param oArgs.el {HTMLElement} The unselected TD element.
15460      * @param oArgs.record {YAHOO.widget.Record} The associated Record.
15461      * @param oArgs.column {YAHOO.widget.Column} The associated Column instance.
15462      * @param oArgs.key {String} (deprecated) The key of the unselected cell.
15463
15464      */
15465
15466     /**
15467      * Fired when a cell is highlighted.
15468      *
15469      * @event cellHighlightEvent
15470      * @param oArgs.el {HTMLElement} The highlighted TD element.
15471      * @param oArgs.record {YAHOO.widget.Record} The associated Record instance.
15472      * @param oArgs.column {YAHOO.widget.Column} The associated Column instance.
15473      * @param oArgs.key {String} (deprecated) The key of the highlighted cell.
15474
15475      */
15476
15477     /**
15478      * Fired when a cell is unhighlighted.
15479      *
15480      * @event cellUnhighlightEvent
15481      * @param oArgs.el {HTMLElement} The unhighlighted TD element.
15482      * @param oArgs.record {YAHOO.widget.Record} The associated Record instance.
15483      * @param oArgs.column {YAHOO.widget.Column} The associated Column instance.
15484      * @param oArgs.key {String} (deprecated) The key of the unhighlighted cell.
15485
15486      */
15487
15488     /**
15489      * Fired when all cell selections are cleared.
15490      *
15491      * @event unselectAllCellsEvent
15492      */
15493
15494     /**
15495      * Fired when a CellEditor is shown.
15496      *
15497      * @event editorShowEvent
15498      * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
15499      */
15500
15501     /**
15502      * Fired when a CellEditor has a keydown.
15503      *
15504      * @event editorKeydownEvent
15505      * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
15506      * @param oArgs.event {HTMLEvent} The event object.
15507      */
15508
15509     /**
15510      * Fired when a CellEditor input is reverted.
15511      *
15512      * @event editorRevertEvent
15513      * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
15514      * @param oArgs.newData {Object} New data value from form input field.
15515      * @param oArgs.oldData {Object} Old data value.
15516      */
15517
15518     /**
15519      * Fired when a CellEditor input is saved.
15520      *
15521      * @event editorSaveEvent
15522      * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
15523      * @param oArgs.newData {Object} New data value from form input field.
15524      * @param oArgs.oldData {Object} Old data value.
15525      */
15526
15527     /**
15528      * Fired when a CellEditor input is canceled.
15529      *
15530      * @event editorCancelEvent
15531      * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
15532      */
15533
15534     /**
15535      * Fired when a CellEditor has a blur event.
15536      *
15537      * @event editorBlurEvent
15538      * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
15539      */
15540
15541     /**
15542      * Fired when a CellEditor is blocked.
15543      *
15544      * @event editorBlockEvent
15545      * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
15546      */
15547
15548     /**
15549      * Fired when a CellEditor is unblocked.
15550      *
15551      * @event editorUnblockEvent
15552      * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
15553      */
15554
15555
15556
15557
15558
15559     /**
15560      * Fired when a link is clicked.
15561      *
15562      * @event linkClickEvent
15563      * @param oArgs.event {HTMLEvent} The event object.
15564      * @param oArgs.target {HTMLElement} The A element.
15565      */
15566
15567     /**
15568      * Fired when a BUTTON element or INPUT element of type "button", "image",
15569      * "submit", "reset" is clicked.
15570      *
15571      * @event buttonClickEvent
15572      * @param oArgs.event {HTMLEvent} The event object.
15573      * @param oArgs.target {HTMLElement} The BUTTON element.
15574      */
15575
15576     /**
15577      * Fired when a CHECKBOX element is clicked.
15578      *
15579      * @event checkboxClickEvent
15580      * @param oArgs.event {HTMLEvent} The event object.
15581      * @param oArgs.target {HTMLElement} The CHECKBOX element.
15582      */
15583
15584     /**
15585      * Fired when a SELECT element is changed.
15586      *
15587      * @event dropdownChangeEvent
15588      * @param oArgs.event {HTMLEvent} The event object.
15589      * @param oArgs.target {HTMLElement} The SELECT element.
15590      */
15591
15592     /**
15593      * Fired when a RADIO element is clicked.
15594      *
15595      * @event radioClickEvent
15596      * @param oArgs.event {HTMLEvent} The event object.
15597      * @param oArgs.target {HTMLElement} The RADIO element.
15598      */
15599
15600
15601
15602
15603
15604
15605
15606
15607
15608
15609
15610
15611
15612
15613
15614
15615
15616
15617
15618
15619
15620
15621
15622
15623
15624
15625 /////////////////////////////////////////////////////////////////////////////
15626 //
15627 // Deprecated APIs
15628 //
15629 /////////////////////////////////////////////////////////////////////////////
15630   
15631 /*
15632  * @method showCellEditorBtns
15633  * @deprecated Use CellEditor.renderBtns() 
15634  */
15635 showCellEditorBtns : function(elContainer) {
15636     // Buttons
15637     var elBtnsDiv = elContainer.appendChild(document.createElement("div"));
15638     Dom.addClass(elBtnsDiv, DT.CLASS_BUTTON);
15639
15640     // Save button
15641     var elSaveBtn = elBtnsDiv.appendChild(document.createElement("button"));
15642     Dom.addClass(elSaveBtn, DT.CLASS_DEFAULT);
15643     elSaveBtn.innerHTML = "OK";
15644     Ev.addListener(elSaveBtn, "click", function(oArgs, oSelf) {
15645         oSelf.onEventSaveCellEditor(oArgs, oSelf);
15646         oSelf.focusTbodyEl();
15647     }, this, true);
15648
15649     // Cancel button
15650     var elCancelBtn = elBtnsDiv.appendChild(document.createElement("button"));
15651     elCancelBtn.innerHTML = "Cancel";
15652     Ev.addListener(elCancelBtn, "click", function(oArgs, oSelf) {
15653         oSelf.onEventCancelCellEditor(oArgs, oSelf);
15654         oSelf.focusTbodyEl();
15655     }, this, true);
15656
15657 },
15658
15659 /**
15660  * @method resetCellEditor
15661  * @deprecated Use destroyCellEditor 
15662  */
15663 resetCellEditor : function() {
15664     var elContainer = this._oCellEditor.container;
15665     elContainer.style.display = "none";
15666     Ev.purgeElement(elContainer, true);
15667     elContainer.innerHTML = "";
15668     this._oCellEditor.value = null;
15669     this._oCellEditor.isActive = false;
15670
15671 },
15672
15673 /**
15674  * @event editorUpdateEvent
15675  * @deprecated Use CellEditor class.
15676  */
15677
15678 /**
15679  * @method getBody
15680  * @deprecated Use getTbodyEl().
15681  */
15682 getBody : function() {
15683     // Backward compatibility
15684     return this.getTbodyEl();
15685 },
15686
15687 /**
15688  * @method getCell
15689  * @deprecated Use getTdEl().
15690  */
15691 getCell : function(index) {
15692     // Backward compatibility
15693     return this.getTdEl(index);
15694 },
15695
15696 /**
15697  * @method getRow
15698  * @deprecated Use getTrEl().
15699  */
15700 getRow : function(index) {
15701     // Backward compatibility
15702     return this.getTrEl(index);
15703 },
15704
15705 /**
15706  * @method refreshView
15707  * @deprecated Use render.
15708  */
15709 refreshView : function() {
15710     // Backward compatibility
15711     this.render();
15712 },
15713
15714 /**
15715  * @method select
15716  * @deprecated Use selectRow.
15717  */
15718 select : function(els) {
15719     // Backward compatibility
15720     if(!lang.isArray(els)) {
15721         els = [els];
15722     }
15723     for(var i=0; i<els.length; i++) {
15724         this.selectRow(els[i]);
15725     }
15726 },
15727
15728 /**
15729  * @method onEventEditCell
15730  * @deprecated Use onEventShowCellEditor.
15731  */
15732 onEventEditCell : function(oArgs) {
15733     // Backward compatibility
15734     this.onEventShowCellEditor(oArgs);
15735 },
15736
15737 /**
15738  * @method _syncColWidths
15739  * @deprecated Use validateColumnWidths.
15740  */
15741 _syncColWidths : function() {
15742     // Backward compatibility
15743     this.validateColumnWidths();
15744 }
15745
15746 /**
15747  * @event headerRowMouseoverEvent
15748  * @deprecated Use theadRowMouseoverEvent.
15749  */
15750
15751 /**
15752  * @event headerRowMouseoutEvent
15753  * @deprecated Use theadRowMouseoutEvent.
15754  */
15755
15756 /**
15757  * @event headerRowMousedownEvent
15758  * @deprecated Use theadRowMousedownEvent.
15759  */
15760
15761 /**
15762  * @event headerRowClickEvent
15763  * @deprecated Use theadRowClickEvent.
15764  */
15765
15766 /**
15767  * @event headerRowDblclickEvent
15768  * @deprecated Use theadRowDblclickEvent.
15769  */
15770
15771 /**
15772  * @event headerCellMouseoverEvent
15773  * @deprecated Use theadCellMouseoverEvent.
15774  */
15775
15776 /**
15777  * @event headerCellMouseoutEvent
15778  * @deprecated Use theadCellMouseoutEvent.
15779  */
15780
15781 /**
15782  * @event headerCellMousedownEvent
15783  * @deprecated Use theadCellMousedownEvent.
15784  */
15785
15786 /**
15787  * @event headerCellClickEvent
15788  * @deprecated Use theadCellClickEvent.
15789  */
15790
15791 /**
15792  * @event headerCellDblclickEvent
15793  * @deprecated Use theadCellDblclickEvent.
15794  */
15795
15796 /**
15797  * @event headerLabelMouseoverEvent
15798  * @deprecated Use theadLabelMouseoverEvent.
15799  */
15800
15801 /**
15802  * @event headerLabelMouseoutEvent
15803  * @deprecated Use theadLabelMouseoutEvent.
15804  */
15805
15806 /**
15807  * @event headerLabelMousedownEvent
15808  * @deprecated Use theadLabelMousedownEvent.
15809  */
15810
15811 /**
15812  * @event headerLabelClickEvent
15813  * @deprecated Use theadLabelClickEvent.
15814  */
15815
15816 /**
15817  * @event headerLabelDbllickEvent
15818  * @deprecated Use theadLabelDblclickEvent.
15819  */
15820
15821 });
15822
15823 /**
15824  * Alias for onDataReturnSetRows for backward compatibility
15825  * @method onDataReturnSetRecords
15826  * @deprecated Use onDataReturnSetRows
15827  */
15828 DT.prototype.onDataReturnSetRecords = DT.prototype.onDataReturnSetRows;
15829
15830 /**
15831  * Alias for onPaginatorChange for backward compatibility
15832  * @method onPaginatorChange
15833  * @deprecated Use onPaginatorChangeRequest
15834  */
15835 DT.prototype.onPaginatorChange = DT.prototype.onPaginatorChangeRequest;
15836
15837 /////////////////////////////////////////////////////////////////////////////
15838 //
15839 // Deprecated static APIs
15840 //
15841 /////////////////////////////////////////////////////////////////////////////
15842 /**
15843  * @method DataTable.editCheckbox
15844  * @deprecated  Use YAHOO.widget.CheckboxCellEditor.
15845  */
15846 DT.editCheckbox = function() {};
15847
15848 /**
15849  * @method DataTable.editDate
15850  * @deprecated Use YAHOO.widget.DateCellEditor.
15851  */
15852 DT.editDate = function() {};
15853
15854 /**
15855  * @method DataTable.editDropdown
15856  * @deprecated Use YAHOO.widget.DropdownCellEditor.
15857  */
15858 DT.editDropdown = function() {};
15859
15860 /**
15861  * @method DataTable.editRadio
15862  * @deprecated Use YAHOO.widget.RadioCellEditor.
15863  */
15864 DT.editRadio = function() {};
15865
15866 /**
15867  * @method DataTable.editTextarea
15868  * @deprecated Use YAHOO.widget.TextareaCellEditor
15869  */
15870 DT.editTextarea = function() {};
15871
15872 /**
15873  * @method DataTable.editTextbox
15874  * @deprecated Use YAHOO.widget.TextboxCellEditor
15875  */
15876 DT.editTextbox= function() {};
15877
15878 })();
15879
15880 (function () {
15881
15882 var lang   = YAHOO.lang,
15883     util   = YAHOO.util,
15884     widget = YAHOO.widget,
15885     ua     = YAHOO.env.ua,
15886     
15887     Dom    = util.Dom,
15888     Ev     = util.Event,
15889     DS     = util.DataSourceBase,
15890     DT     = widget.DataTable,
15891     Pag    = widget.Paginator;
15892     
15893 /**
15894  * The ScrollingDataTable class extends the DataTable class to provide
15895  * functionality for x-scrolling, y-scrolling, and xy-scrolling.
15896  *
15897  * @namespace YAHOO.widget
15898  * @class ScrollingDataTable
15899  * @extends YAHOO.widget.DataTable
15900  * @constructor
15901  * @param elContainer {HTMLElement} Container element for the TABLE.
15902  * @param aColumnDefs {Object[]} Array of object literal Column definitions.
15903  * @param oDataSource {YAHOO.util.DataSource} DataSource instance.
15904  * @param oConfigs {object} (optional) Object literal of configuration values.
15905  */
15906 widget.ScrollingDataTable = function(elContainer,aColumnDefs,oDataSource,oConfigs) {
15907     oConfigs = oConfigs || {};
15908     
15909     // Prevent infinite loop
15910     if(oConfigs.scrollable) {
15911         oConfigs.scrollable = false;
15912     }
15913
15914     this._init();
15915
15916     widget.ScrollingDataTable.superclass.constructor.call(this, elContainer,aColumnDefs,oDataSource,oConfigs); 
15917
15918     // Once per instance
15919     this.subscribe("columnShowEvent", this._onColumnChange);
15920 };
15921
15922 var SDT = widget.ScrollingDataTable;
15923
15924 /////////////////////////////////////////////////////////////////////////////
15925 //
15926 // Public constants
15927 //
15928 /////////////////////////////////////////////////////////////////////////////
15929 lang.augmentObject(SDT, {
15930
15931     /**
15932      * Class name assigned to inner DataTable header container.
15933      *
15934      * @property DataTable.CLASS_HEADER
15935      * @type String
15936      * @static
15937      * @final
15938      * @default "yui-dt-hd"
15939      */
15940     CLASS_HEADER : "yui-dt-hd",
15941     
15942     /**
15943      * Class name assigned to inner DataTable body container.
15944      *
15945      * @property DataTable.CLASS_BODY
15946      * @type String
15947      * @static
15948      * @final
15949      * @default "yui-dt-bd"
15950      */
15951     CLASS_BODY : "yui-dt-bd"
15952 });
15953
15954 lang.extend(SDT, DT, {
15955
15956 /**
15957  * Container for fixed header TABLE element.
15958  *
15959  * @property _elHdContainer
15960  * @type HTMLElement
15961  * @private
15962  */
15963 _elHdContainer : null,
15964
15965 /**
15966  * Fixed header TABLE element.
15967  *
15968  * @property _elHdTable
15969  * @type HTMLElement
15970  * @private
15971  */
15972 _elHdTable : null,
15973
15974 /**
15975  * Container for scrolling body TABLE element.
15976  *
15977  * @property _elBdContainer
15978  * @type HTMLElement
15979  * @private
15980  */
15981 _elBdContainer : null,
15982
15983 /**
15984  * Body THEAD element.
15985  *
15986  * @property _elBdThead
15987  * @type HTMLElement
15988  * @private
15989  */
15990 _elBdThead : null,
15991
15992 /**
15993  * Offscreen container to temporarily clone SDT for auto-width calculation.
15994  *
15995  * @property _elTmpContainer
15996  * @type HTMLElement
15997  * @private
15998  */
15999 _elTmpContainer : null,
16000
16001 /**
16002  * Offscreen TABLE element for auto-width calculation.
16003  *
16004  * @property _elTmpTable
16005  * @type HTMLElement
16006  * @private
16007  */
16008 _elTmpTable : null,
16009
16010 /**
16011  * True if x-scrollbar is currently visible.
16012  * @property _bScrollbarX
16013  * @type Boolean
16014  * @private 
16015  */
16016 _bScrollbarX : null,
16017
16018
16019
16020
16021
16022
16023
16024
16025
16026
16027
16028
16029
16030
16031
16032 /////////////////////////////////////////////////////////////////////////////
16033 //
16034 // Superclass methods
16035 //
16036 /////////////////////////////////////////////////////////////////////////////
16037
16038 /**
16039  * Implementation of Element's abstract method. Sets up config values.
16040  *
16041  * @method initAttributes
16042  * @param oConfigs {Object} (Optional) Object literal definition of configuration values.
16043  * @private
16044  */
16045
16046 initAttributes : function(oConfigs) {
16047     oConfigs = oConfigs || {};
16048     SDT.superclass.initAttributes.call(this, oConfigs);
16049
16050     /**
16051     * @attribute width
16052     * @description Table width for scrollable tables (e.g., "40em").
16053     * @type String
16054     */
16055     this.setAttributeConfig("width", {
16056         value: null,
16057         validator: lang.isString,
16058         method: function(oParam) {
16059             if(this._elHdContainer && this._elBdContainer) {
16060                 this._elHdContainer.style.width = oParam;
16061                 this._elBdContainer.style.width = oParam;            
16062                 this._syncScrollX();      
16063                 this._syncScrollOverhang();
16064             }
16065         }
16066     });
16067
16068     /**
16069     * @attribute height
16070     * @description Table body height for scrollable tables, not including headers (e.g., "40em").
16071     * @type String
16072     */
16073     this.setAttributeConfig("height", {
16074         value: null,
16075         validator: lang.isString,
16076         method: function(oParam) {
16077             if(this._elHdContainer && this._elBdContainer) {
16078                 this._elBdContainer.style.height = oParam;    
16079                 this._syncScrollX();   
16080                 this._syncScrollY();
16081                 this._syncScrollOverhang();
16082             }
16083         }
16084     });
16085
16086     /**
16087     * @attribute COLOR_COLUMNFILLER
16088     * @description CSS color value assigned to header filler on scrollable tables.  
16089     * @type String
16090     * @default "#F2F2F2"
16091     */
16092     this.setAttributeConfig("COLOR_COLUMNFILLER", {
16093         value: "#F2F2F2",
16094         validator: lang.isString,
16095         method: function(oParam) {
16096             if(this._elHdContainer) {
16097                 this._elHdContainer.style.backgroundColor = oParam;
16098             }
16099         }
16100     });
16101 },
16102
16103 /**
16104  * Initializes internal variables.
16105  *
16106  * @method _init
16107  * @private
16108  */
16109 _init : function() {
16110     this._elHdContainer = null;
16111     this._elHdTable = null;
16112     this._elBdContainer = null;
16113     this._elBdThead = null;
16114     this._elTmpContainer = null;
16115     this._elTmpTable = null;
16116 },
16117
16118 /**
16119  * Initializes DOM elements for a ScrollingDataTable, including creation of
16120  * two separate TABLE elements.
16121  *
16122  * @method _initDomElements
16123  * @param elContainer {HTMLElement | String} HTML DIV element by reference or ID. 
16124  * return {Boolean} False in case of error, otherwise true 
16125  * @private
16126  */
16127 _initDomElements : function(elContainer) {
16128     // Outer and inner containers
16129     this._initContainerEl(elContainer);
16130     if(this._elContainer && this._elHdContainer && this._elBdContainer) {
16131         // TABLEs
16132         this._initTableEl();
16133         
16134         if(this._elHdTable && this._elTable) {
16135             // COLGROUPs
16136             ///this._initColgroupEl(this._elHdTable, this._elTable);  
16137             this._initColgroupEl(this._elHdTable);        
16138             
16139             // THEADs
16140             this._initTheadEl(this._elHdTable, this._elTable);
16141             
16142             // Primary TBODY
16143             this._initTbodyEl(this._elTable);
16144             // Message TBODY
16145             this._initMsgTbodyEl(this._elTable);            
16146         }
16147     }
16148     if(!this._elContainer || !this._elTable || !this._elColgroup ||  !this._elThead || !this._elTbody || !this._elMsgTbody ||
16149             !this._elHdTable || !this._elBdThead) {
16150         return false;
16151     }
16152     else {
16153         return true;
16154     }
16155 },
16156
16157 /**
16158  * Destroy's the DataTable outer and inner container elements, if available.
16159  *
16160  * @method _destroyContainerEl
16161  * @param elContainer {HTMLElement} Reference to the container element. 
16162  * @private
16163  */
16164 _destroyContainerEl : function(elContainer) {
16165     Dom.removeClass(elContainer, DT.CLASS_SCROLLABLE);
16166     SDT.superclass._destroyContainerEl.call(this, elContainer);
16167     this._elHdContainer = null;
16168     this._elBdContainer = null;
16169 },
16170
16171 /**
16172  * Initializes the DataTable outer container element and creates inner header
16173  * and body container elements.
16174  *
16175  * @method _initContainerEl
16176  * @param elContainer {HTMLElement | String} HTML DIV element by reference or ID.
16177  * @private
16178  */
16179 _initContainerEl : function(elContainer) {
16180     SDT.superclass._initContainerEl.call(this, elContainer);
16181     
16182     if(this._elContainer) {
16183         elContainer = this._elContainer; // was constructor input, now is DOM ref
16184         Dom.addClass(elContainer, DT.CLASS_SCROLLABLE);
16185         
16186         // Container for header TABLE
16187         var elHdContainer = document.createElement("div");
16188         elHdContainer.style.width = this.get("width") || "";
16189         elHdContainer.style.backgroundColor = this.get("COLOR_COLUMNFILLER");
16190         Dom.addClass(elHdContainer, SDT.CLASS_HEADER);
16191         this._elHdContainer = elHdContainer;
16192         elContainer.appendChild(elHdContainer);
16193     
16194         // Container for body TABLE
16195         var elBdContainer = document.createElement("div");
16196         elBdContainer.style.width = this.get("width") || "";
16197         elBdContainer.style.height = this.get("height") || "";
16198         Dom.addClass(elBdContainer, SDT.CLASS_BODY);
16199         Ev.addListener(elBdContainer, "scroll", this._onScroll, this); // to sync horiz scroll headers
16200         this._elBdContainer = elBdContainer;
16201         elContainer.appendChild(elBdContainer);
16202     }
16203 },
16204
16205 /**
16206  * Creates HTML markup CAPTION element.
16207  *
16208  * @method _initCaptionEl
16209  * @param sCaption {String} Text for caption.
16210  * @private
16211  */
16212 _initCaptionEl : function(sCaption) {
16213     // Not yet supported
16214     /*if(this._elHdTable && sCaption) {
16215         // Create CAPTION element
16216         if(!this._elCaption) { 
16217             this._elCaption = this._elHdTable.createCaption();
16218         }
16219         // Set CAPTION value
16220         this._elCaption.innerHTML = sCaption;
16221     }
16222     else if(this._elCaption) {
16223         this._elCaption.parentNode.removeChild(this._elCaption);
16224     }*/
16225 },
16226
16227 /**
16228  * Destroy's the DataTable head TABLE element, if available.
16229  *
16230  * @method _destroyHdTableEl
16231  * @private
16232  */
16233 _destroyHdTableEl : function() {
16234     var elTable = this._elHdTable;
16235     if(elTable) {
16236         Ev.purgeElement(elTable, true);
16237         elTable.parentNode.removeChild(elTable);
16238         
16239         // A little out of place, but where else can we null out these extra elements?
16240         ///this._elBdColgroup = null;
16241         this._elBdThead = null;
16242     }
16243 },
16244
16245 /**
16246  * Initializes ScrollingDataTable TABLE elements into the two inner containers.
16247  *
16248  * @method _initTableEl
16249  * @private
16250  */
16251 _initTableEl : function() {
16252     // Head TABLE
16253     if(this._elHdContainer) {
16254         this._destroyHdTableEl();
16255     
16256         // Create TABLE
16257         this._elHdTable = this._elHdContainer.appendChild(document.createElement("table"));   
16258
16259         // Set up mouseover/mouseout events via mouseenter/mouseleave delegation
16260         Ev.delegate(this._elHdTable, "mouseenter", this._onTableMouseover, "thead ."+DT.CLASS_LABEL, this);
16261         Ev.delegate(this._elHdTable, "mouseleave", this._onTableMouseout, "thead ."+DT.CLASS_LABEL, this);
16262     }
16263     // Body TABLE
16264     SDT.superclass._initTableEl.call(this, this._elBdContainer);
16265 },
16266
16267 /**
16268  * Initializes ScrollingDataTable THEAD elements into the two inner containers.
16269  *
16270  * @method _initTheadEl
16271  * @param elHdTable {HTMLElement} (optional) Fixed header TABLE element reference.
16272  * @param elTable {HTMLElement} (optional) TABLE element reference.
16273  * @private
16274  */
16275 _initTheadEl : function(elHdTable, elTable) {
16276     elHdTable = elHdTable || this._elHdTable;
16277     elTable = elTable || this._elTable;
16278     
16279     // Scrolling body's THEAD
16280     this._initBdTheadEl(elTable);
16281     // Standard fixed head THEAD
16282     SDT.superclass._initTheadEl.call(this, elHdTable);
16283 },
16284
16285 /**
16286  * SDT changes ID so as not to duplicate the accessibility TH IDs.
16287  *
16288  * @method _initThEl
16289  * @param elTh {HTMLElement} TH element reference.
16290  * @param oColumn {YAHOO.widget.Column} Column object.
16291  * @private
16292  */
16293 _initThEl : function(elTh, oColumn) {
16294     SDT.superclass._initThEl.call(this, elTh, oColumn);
16295     elTh.id = this.getId() +"-fixedth-" + oColumn.getSanitizedKey(); // Needed for getColumn by TH and ColumnDD
16296 },
16297
16298 /**
16299  * Destroy's the DataTable body THEAD element, if available.
16300  *
16301  * @method _destroyBdTheadEl
16302  * @private
16303  */
16304 _destroyBdTheadEl : function() {
16305     var elBdThead = this._elBdThead;
16306     if(elBdThead) {
16307         var elTable = elBdThead.parentNode;
16308         Ev.purgeElement(elBdThead, true);
16309         elTable.removeChild(elBdThead);
16310         this._elBdThead = null;
16311
16312         this._destroyColumnHelpers();
16313     }
16314 },
16315
16316 /**
16317  * Initializes body THEAD element.
16318  *
16319  * @method _initBdTheadEl
16320  * @param elTable {HTMLElement} TABLE element into which to create THEAD.
16321  * @return {HTMLElement} Initialized THEAD element. 
16322  * @private
16323  */
16324 _initBdTheadEl : function(elTable) {
16325     if(elTable) {
16326         // Destroy previous
16327         this._destroyBdTheadEl();
16328
16329         var elThead = elTable.insertBefore(document.createElement("thead"), elTable.firstChild);
16330         
16331         // Add TRs to the THEAD;
16332         var oColumnSet = this._oColumnSet,
16333             colTree = oColumnSet.tree,
16334             elTh, elTheadTr, oColumn, i, j, k, len;
16335
16336         for(i=0, k=colTree.length; i<k; i++) {
16337             elTheadTr = elThead.appendChild(document.createElement("tr"));
16338     
16339             // ...and create TH cells
16340             for(j=0, len=colTree[i].length; j<len; j++) {
16341                 oColumn = colTree[i][j];
16342                 elTh = elTheadTr.appendChild(document.createElement("th"));
16343                 this._initBdThEl(elTh,oColumn,i,j);
16344             }
16345         }
16346         this._elBdThead = elThead;
16347     }
16348 },
16349
16350 /**
16351  * Populates TH element for the body THEAD element.
16352  *
16353  * @method _initBdThEl
16354  * @param elTh {HTMLElement} TH element reference.
16355  * @param oColumn {YAHOO.widget.Column} Column object.
16356  * @private
16357  */
16358 _initBdThEl : function(elTh, oColumn) {
16359     elTh.id = this.getId()+"-th-" + oColumn.getSanitizedKey(); // Needed for accessibility
16360     elTh.rowSpan = oColumn.getRowspan();
16361     elTh.colSpan = oColumn.getColspan();
16362     // Assign abbr attribute
16363     if(oColumn.abbr) {
16364         elTh.abbr = oColumn.abbr;
16365     }
16366
16367     // TODO: strip links and form elements
16368     var sKey = oColumn.getKey();
16369     var sLabel = lang.isValue(oColumn.label) ? oColumn.label : sKey;
16370     elTh.innerHTML = sLabel;
16371 },
16372
16373 /**
16374  * Initializes ScrollingDataTable TBODY element for data
16375  *
16376  * @method _initTbodyEl
16377  * @param elTable {HTMLElement} TABLE element into which to create TBODY .
16378  * @private
16379  */
16380 _initTbodyEl : function(elTable) {
16381     SDT.superclass._initTbodyEl.call(this, elTable);
16382     
16383     // Bug 2105534 - Safari 3 gap
16384     // Bug 2492591 - IE8 offsetTop
16385     elTable.style.marginTop = (this._elTbody.offsetTop > 0) ?
16386             "-"+this._elTbody.offsetTop+"px" : 0;
16387 },
16388
16389
16390
16391
16392
16393
16394
16395
16396
16397
16398
16399
16400
16401
16402
16403
16404
16405
16406
16407
16408
16409
16410
16411
16412
16413
16414
16415
16416
16417 /**
16418  * Sets focus on the given element.
16419  *
16420  * @method _focusEl
16421  * @param el {HTMLElement} Element.
16422  * @private
16423  */
16424 _focusEl : function(el) {
16425     el = el || this._elTbody;
16426     var oSelf = this;
16427     this._storeScrollPositions();
16428     // http://developer.mozilla.org/en/docs/index.php?title=Key-navigable_custom_DHTML_widgets
16429     // The timeout is necessary in both IE and Firefox 1.5, to prevent scripts from doing
16430     // strange unexpected things as the user clicks on buttons and other controls.
16431     
16432     // Bug 1921135: Wrap the whole thing in a setTimeout
16433     setTimeout(function() {
16434         setTimeout(function() {
16435             try {
16436                 el.focus();
16437                 oSelf._restoreScrollPositions();
16438             }
16439             catch(e) {
16440             }
16441         },0);
16442     }, 0);
16443 },
16444
16445
16446
16447
16448
16449
16450
16451
16452
16453
16454
16455
16456
16457
16458
16459
16460
16461
16462
16463 /**
16464  * Internal wrapper calls run() on render Chain instance.
16465  *
16466  * @method _runRenderChain
16467  * @private 
16468  */
16469 _runRenderChain : function() {
16470     this._storeScrollPositions();
16471     this._oChainRender.run();
16472 },
16473
16474 /**
16475  * Stores scroll positions so they can be restored after a render.
16476  *
16477  * @method _storeScrollPositions
16478  * @private
16479  */
16480  _storeScrollPositions : function() {
16481     this._nScrollTop = this._elBdContainer.scrollTop;
16482     this._nScrollLeft = this._elBdContainer.scrollLeft;
16483 },
16484
16485 /**
16486  * Clears stored scroll positions to interrupt the automatic restore mechanism.
16487  * Useful for setting scroll positions programmatically rather than as part of
16488  * the post-render cleanup process.
16489  *
16490  * @method clearScrollPositions
16491  * @private
16492  */
16493  clearScrollPositions : function() {
16494     this._nScrollTop = 0;
16495     this._nScrollLeft = 0;
16496 },
16497
16498 /**
16499  * Restores scroll positions to stored value. 
16500  *
16501  * @method _retoreScrollPositions
16502  * @private 
16503  */
16504  _restoreScrollPositions : function() {
16505     // Reset scroll positions
16506     if(this._nScrollTop) {
16507         this._elBdContainer.scrollTop = this._nScrollTop;
16508         this._nScrollTop = null;
16509     } 
16510     if(this._nScrollLeft) {
16511         this._elBdContainer.scrollLeft = this._nScrollLeft;
16512         // Bug 2529024
16513         this._elHdContainer.scrollLeft = this._nScrollLeft; 
16514         this._nScrollLeft = null;
16515     } 
16516 },
16517
16518 /**
16519  * Helper function calculates and sets a validated width for a Column in a ScrollingDataTable.
16520  *
16521  * @method _validateColumnWidth
16522  * @param oColumn {YAHOO.widget.Column} Column instance.
16523  * @param elTd {HTMLElement} TD element to validate against.
16524  * @private
16525  */
16526 _validateColumnWidth : function(oColumn, elTd) {
16527     // Only Columns without widths that are not hidden
16528     if(!oColumn.width && !oColumn.hidden) {
16529         var elTh = oColumn.getThEl();
16530         // Unset a calculated auto-width
16531         if(oColumn._calculatedWidth) {
16532             this._setColumnWidth(oColumn, "auto", "visible");
16533         }
16534         // Compare auto-widths
16535         if(elTh.offsetWidth !== elTd.offsetWidth) {
16536             var elWider = (elTh.offsetWidth > elTd.offsetWidth) ?
16537                     oColumn.getThLinerEl() : elTd.firstChild;               
16538
16539             // Grab the wider liner width, unless the minWidth is wider
16540             var newWidth = Math.max(0,
16541                 (elWider.offsetWidth -(parseInt(Dom.getStyle(elWider,"paddingLeft"),10)|0) - (parseInt(Dom.getStyle(elWider,"paddingRight"),10)|0)),
16542                 oColumn.minWidth);
16543                 
16544             var sOverflow = 'visible';
16545             
16546             // Now validate against maxAutoWidth
16547             if((oColumn.maxAutoWidth > 0) && (newWidth > oColumn.maxAutoWidth)) {
16548                 newWidth = oColumn.maxAutoWidth;
16549                 sOverflow = "hidden";
16550             }
16551
16552             // Set to the wider auto-width
16553             this._elTbody.style.display = "none";
16554             this._setColumnWidth(oColumn, newWidth+'px', sOverflow);
16555             oColumn._calculatedWidth = newWidth;
16556             this._elTbody.style.display = "";
16557         }
16558     }
16559 },
16560
16561 /**
16562  * For one or all Columns of a ScrollingDataTable, when Column is not hidden,
16563  * and width is not set, syncs widths of header and body cells and 
16564  * validates that width against minWidth and/or maxAutoWidth as necessary.
16565  *
16566  * @method validateColumnWidths
16567  * @param oArg.column {YAHOO.widget.Column} (optional) One Column to validate. If null, all Columns' widths are validated.
16568  */
16569 validateColumnWidths : function(oColumn) {
16570     // Validate there is at least one TR with proper TDs
16571     var allKeys   = this._oColumnSet.keys,
16572         allKeysLength = allKeys.length,
16573         elRow     = this.getFirstTrEl();
16574
16575     // Reset overhang for IE
16576     if(ua.ie) {
16577         this._setOverhangValue(1);
16578     }
16579
16580     if(allKeys && elRow && (elRow.childNodes.length === allKeysLength)) {
16581         // Temporarily unsnap container since it causes inaccurate calculations
16582         var sWidth = this.get("width");
16583         if(sWidth) {
16584             this._elHdContainer.style.width = "";
16585             this._elBdContainer.style.width = "";
16586         }
16587         this._elContainer.style.width = "";
16588         
16589         //Validate just one Column
16590         if(oColumn && lang.isNumber(oColumn.getKeyIndex())) {
16591             this._validateColumnWidth(oColumn, elRow.childNodes[oColumn.getKeyIndex()]);
16592         }
16593         // Iterate through all Columns to unset calculated widths in one pass
16594         else {
16595             var elTd, todos = [], thisTodo, i, len;
16596             for(i=0; i<allKeysLength; i++) {
16597                 oColumn = allKeys[i];
16598                 // Only Columns without widths that are not hidden, unset a calculated auto-width
16599                 if(!oColumn.width && !oColumn.hidden && oColumn._calculatedWidth) {
16600                     todos[todos.length] = oColumn;      
16601                 }
16602             }
16603             
16604             this._elTbody.style.display = "none";
16605             for(i=0, len=todos.length; i<len; i++) {
16606                 this._setColumnWidth(todos[i], "auto", "visible");
16607             }
16608             this._elTbody.style.display = "";
16609             
16610             todos = [];
16611
16612             // Iterate through all Columns and make the store the adjustments to make in one pass
16613             for(i=0; i<allKeysLength; i++) {
16614                 oColumn = allKeys[i];
16615                 elTd = elRow.childNodes[i];
16616                 // Only Columns without widths that are not hidden
16617                 if(!oColumn.width && !oColumn.hidden) {
16618                     var elTh = oColumn.getThEl();
16619
16620                     // Compare auto-widths
16621                     if(elTh.offsetWidth !== elTd.offsetWidth) {
16622                         var elWider = (elTh.offsetWidth > elTd.offsetWidth) ?
16623                                 oColumn.getThLinerEl() : elTd.firstChild;               
16624                 
16625                         // Grab the wider liner width, unless the minWidth is wider
16626                         var newWidth = Math.max(0,
16627                             (elWider.offsetWidth -(parseInt(Dom.getStyle(elWider,"paddingLeft"),10)|0) - (parseInt(Dom.getStyle(elWider,"paddingRight"),10)|0)),
16628                             oColumn.minWidth);
16629                             
16630                         var sOverflow = 'visible';
16631                         
16632                         // Now validate against maxAutoWidth
16633                         if((oColumn.maxAutoWidth > 0) && (newWidth > oColumn.maxAutoWidth)) {
16634                             newWidth = oColumn.maxAutoWidth;
16635                             sOverflow = "hidden";
16636                         }
16637                 
16638                         todos[todos.length] = [oColumn, newWidth, sOverflow];
16639                     }
16640                 }
16641             }
16642             
16643             this._elTbody.style.display = "none";
16644             for(i=0, len=todos.length; i<len; i++) {
16645                 thisTodo = todos[i];
16646                 // Set to the wider auto-width
16647                 this._setColumnWidth(thisTodo[0], thisTodo[1]+"px", thisTodo[2]);
16648                 thisTodo[0]._calculatedWidth = thisTodo[1];
16649             }
16650             this._elTbody.style.display = "";
16651         }
16652     
16653         // Resnap unsnapped containers
16654         if(sWidth) {
16655             this._elHdContainer.style.width = sWidth;
16656             this._elBdContainer.style.width = sWidth;
16657         } 
16658     }
16659     
16660     this._syncScroll();
16661     this._restoreScrollPositions();
16662 },
16663
16664 /**
16665  * Syncs padding around scrollable tables, including Column header right-padding
16666  * and container width and height.
16667  *
16668  * @method _syncScroll
16669  * @private 
16670  */
16671 _syncScroll : function() {
16672     this._syncScrollX();
16673     this._syncScrollY();
16674     this._syncScrollOverhang();
16675     if(ua.opera) {
16676         // Bug 1925874
16677         this._elHdContainer.scrollLeft = this._elBdContainer.scrollLeft;
16678         if(!this.get("width")) {
16679             // Bug 1926125
16680             document.body.style += '';
16681         }
16682     }
16683  },
16684
16685 /**
16686  * Snaps container width for y-scrolling tables.
16687  *
16688  * @method _syncScrollY
16689  * @private
16690  */
16691 _syncScrollY : function() {
16692     var elTbody = this._elTbody,
16693         elBdContainer = this._elBdContainer;
16694     
16695     // X-scrolling not enabled
16696     if(!this.get("width")) {
16697         // Snap outer container width to content
16698         this._elContainer.style.width = 
16699                 (elBdContainer.scrollHeight > elBdContainer.clientHeight) ?
16700                 // but account for y-scrollbar since it is visible
16701                 (elTbody.parentNode.clientWidth + 19) + "px" :
16702                 // no y-scrollbar, just borders
16703                 (elTbody.parentNode.clientWidth + 2) + "px";
16704     }
16705 },
16706
16707 /**
16708  * Snaps container height for x-scrolling tables in IE. Syncs message TBODY width.
16709  *
16710  * @method _syncScrollX
16711  * @private
16712  */
16713 _syncScrollX : function() {
16714     var elTbody = this._elTbody,
16715         elBdContainer = this._elBdContainer;
16716     
16717     // IE 6 and 7 only when y-scrolling not enabled
16718     if(!this.get("height") && (ua.ie)) {
16719         // Snap outer container height to content
16720         elBdContainer.style.height = 
16721                 // but account for x-scrollbar if it is visible
16722                 (elBdContainer.scrollWidth > elBdContainer.offsetWidth ) ?
16723                 (elTbody.parentNode.offsetHeight + 18) + "px" : 
16724                 elTbody.parentNode.offsetHeight + "px";
16725     }
16726
16727     // Sync message tbody
16728     if(this._elTbody.rows.length === 0) {
16729         this._elMsgTbody.parentNode.style.width = this.getTheadEl().parentNode.offsetWidth + "px";
16730     }
16731     else {
16732         this._elMsgTbody.parentNode.style.width = "";
16733     }
16734 },
16735
16736 /**
16737  * Adds/removes Column header overhang as necesary.
16738  *
16739  * @method _syncScrollOverhang
16740  * @private
16741  */
16742 _syncScrollOverhang : function() {
16743     var elBdContainer = this._elBdContainer,
16744         // Overhang should be either 1 (default) or 18px, depending on the location of the right edge of the table
16745         nPadding = 1;
16746     
16747     // Y-scrollbar is visible, which is when the overhang needs to jut out
16748     if((elBdContainer.scrollHeight > elBdContainer.clientHeight) &&
16749         // X-scrollbar is also visible, which means the right is jagged, not flush with the Column
16750         (elBdContainer.scrollWidth > elBdContainer.clientWidth)) {
16751         nPadding = 18;
16752     }
16753     
16754     this._setOverhangValue(nPadding);
16755     
16756 },
16757
16758 /**
16759  * Sets Column header overhang to given width.
16760  *
16761  * @method _setOverhangValue
16762  * @param nBorderWidth {Number} Value of new border for overhang. 
16763  * @private
16764  */
16765 _setOverhangValue : function(nBorderWidth) {
16766     var aLastHeaders = this._oColumnSet.headers[this._oColumnSet.headers.length-1] || [],
16767         len = aLastHeaders.length,
16768         sPrefix = this._sId+"-fixedth-",
16769         sValue = nBorderWidth + "px solid " + this.get("COLOR_COLUMNFILLER");
16770
16771     this._elThead.style.display = "none";
16772     for(var i=0; i<len; i++) {
16773         Dom.get(sPrefix+aLastHeaders[i]).style.borderRight = sValue;
16774     }
16775     this._elThead.style.display = "";
16776 },
16777
16778
16779
16780
16781
16782
16783
16784
16785
16786
16787
16788
16789
16790
16791
16792
16793
16794
16795
16796
16797
16798
16799
16800
16801
16802
16803
16804
16805
16806
16807
16808
16809
16810
16811
16812
16813
16814
16815 /**
16816  * Returns DOM reference to the DataTable's fixed header container element.
16817  *
16818  * @method getHdContainerEl
16819  * @return {HTMLElement} Reference to DIV element.
16820  */
16821 getHdContainerEl : function() {
16822     return this._elHdContainer;
16823 },
16824
16825 /**
16826  * Returns DOM reference to the DataTable's scrolling body container element.
16827  *
16828  * @method getBdContainerEl
16829  * @return {HTMLElement} Reference to DIV element.
16830  */
16831 getBdContainerEl : function() {
16832     return this._elBdContainer;
16833 },
16834
16835 /**
16836  * Returns DOM reference to the DataTable's fixed header TABLE element.
16837  *
16838  * @method getHdTableEl
16839  * @return {HTMLElement} Reference to TABLE element.
16840  */
16841 getHdTableEl : function() {
16842     return this._elHdTable;
16843 },
16844
16845 /**
16846  * Returns DOM reference to the DataTable's scrolling body TABLE element.
16847  *
16848  * @method getBdTableEl
16849  * @return {HTMLElement} Reference to TABLE element.
16850  */
16851 getBdTableEl : function() {
16852     return this._elTable;
16853 },
16854
16855 /**
16856  * Disables ScrollingDataTable UI.
16857  *
16858  * @method disable
16859  */
16860 disable : function() {
16861     var elMask = this._elMask;
16862     elMask.style.width = this._elBdContainer.offsetWidth + "px";
16863     elMask.style.height = this._elHdContainer.offsetHeight + this._elBdContainer.offsetHeight + "px";
16864     elMask.style.display = "";
16865     this.fireEvent("disableEvent");
16866 },
16867
16868 /**
16869  * Removes given Column. NOTE: You cannot remove nested Columns. You can only remove
16870  * non-nested Columns, and top-level parent Columns (which will remove all
16871  * children Columns).
16872  *
16873  * @method removeColumn
16874  * @param oColumn {YAHOO.widget.Column} Column instance.
16875  * @return oColumn {YAHOO.widget.Column} Removed Column instance.
16876  */
16877 removeColumn : function(oColumn) {
16878     // Store scroll pos
16879     var hdPos = this._elHdContainer.scrollLeft;
16880     var bdPos = this._elBdContainer.scrollLeft;
16881     
16882     // Call superclass method
16883     oColumn = SDT.superclass.removeColumn.call(this, oColumn);
16884     
16885     // Restore scroll pos
16886     this._elHdContainer.scrollLeft = hdPos;
16887     this._elBdContainer.scrollLeft = bdPos;
16888     
16889     return oColumn;
16890 },
16891
16892 /**
16893  * Inserts given Column at the index if given, otherwise at the end. NOTE: You
16894  * can only add non-nested Columns and top-level parent Columns. You cannot add
16895  * a nested Column to an existing parent.
16896  *
16897  * @method insertColumn
16898  * @param oColumn {Object | YAHOO.widget.Column} Object literal Column
16899  * definition or a Column instance.
16900  * @param index {Number} (optional) New tree index.
16901  * @return oColumn {YAHOO.widget.Column} Inserted Column instance. 
16902  */
16903 insertColumn : function(oColumn, index) {
16904     // Store scroll pos
16905     var hdPos = this._elHdContainer.scrollLeft;
16906     var bdPos = this._elBdContainer.scrollLeft;
16907     
16908     // Call superclass method
16909     var oNewColumn = SDT.superclass.insertColumn.call(this, oColumn, index);
16910     
16911     // Restore scroll pos
16912     this._elHdContainer.scrollLeft = hdPos;
16913     this._elBdContainer.scrollLeft = bdPos;
16914     
16915     return oNewColumn;
16916 },
16917
16918 /**
16919  * Removes given Column and inserts into given tree index. NOTE: You
16920  * can only reorder non-nested Columns and top-level parent Columns. You cannot
16921  * reorder a nested Column to an existing parent.
16922  *
16923  * @method reorderColumn
16924  * @param oColumn {YAHOO.widget.Column} Column instance.
16925  * @param index {Number} New tree index.
16926  */
16927 reorderColumn : function(oColumn, index) {
16928     // Store scroll pos
16929     var hdPos = this._elHdContainer.scrollLeft;
16930     var bdPos = this._elBdContainer.scrollLeft;
16931     
16932     // Call superclass method
16933     var oNewColumn = SDT.superclass.reorderColumn.call(this, oColumn, index);
16934     
16935     // Restore scroll pos
16936     this._elHdContainer.scrollLeft = hdPos;
16937     this._elBdContainer.scrollLeft = bdPos;
16938
16939     return oNewColumn;
16940 },
16941
16942 /**
16943  * Sets given Column to given pixel width. If new width is less than minWidth
16944  * width, sets to minWidth. Updates oColumn.width value.
16945  *
16946  * @method setColumnWidth
16947  * @param oColumn {YAHOO.widget.Column} Column instance.
16948  * @param nWidth {Number} New width in pixels.
16949  */
16950 setColumnWidth : function(oColumn, nWidth) {
16951     oColumn = this.getColumn(oColumn);
16952     if(oColumn) {
16953         this._storeScrollPositions();
16954
16955         // Validate new width against minWidth
16956         if(lang.isNumber(nWidth)) {
16957             nWidth = (nWidth > oColumn.minWidth) ? nWidth : oColumn.minWidth;
16958
16959             // Save state
16960             oColumn.width = nWidth;
16961             
16962             // Resize the DOM elements
16963             this._setColumnWidth(oColumn, nWidth+"px");
16964             this._syncScroll();
16965             
16966             this.fireEvent("columnSetWidthEvent",{column:oColumn,width:nWidth});
16967         }
16968         // Unsets a width to auto-size
16969         else if(nWidth === null) {
16970             // Save state
16971             oColumn.width = nWidth;
16972             
16973             // Resize the DOM elements
16974             this._setColumnWidth(oColumn, "auto");
16975             this.validateColumnWidths(oColumn);
16976             this.fireEvent("columnUnsetWidthEvent",{column:oColumn});
16977         }
16978         
16979         // Bug 2339454: resize then sort misaligment
16980         this._clearTrTemplateEl();
16981     }
16982     else {
16983     }
16984 },
16985
16986 /**
16987  * Scrolls to given row or cell
16988  *
16989  * @method scrollTo
16990  * @param to {YAHOO.widget.Record | HTMLElement } Itme to scroll to.
16991  */
16992 scrollTo : function(to) {
16993         var td = this.getTdEl(to);
16994         if(td) {
16995             this.clearScrollPositions();
16996             this.getBdContainerEl().scrollLeft = td.offsetLeft;
16997             this.getBdContainerEl().scrollTop = td.parentNode.offsetTop;
16998         }
16999         else {
17000             var tr = this.getTrEl(to);
17001             if(tr) {
17002                 this.clearScrollPositions();
17003                 this.getBdContainerEl().scrollTop = tr.offsetTop;
17004             }
17005         }
17006 },
17007
17008 /**
17009  * Displays message within secondary TBODY.
17010  *
17011  * @method showTableMessage
17012  * @param sHTML {String} (optional) Value for innerHTMlang.
17013  * @param sClassName {String} (optional) Classname.
17014  */
17015 showTableMessage : function(sHTML, sClassName) {
17016     var elCell = this._elMsgTd;
17017     if(lang.isString(sHTML)) {
17018         elCell.firstChild.innerHTML = sHTML;
17019     }
17020     if(lang.isString(sClassName)) {
17021         Dom.addClass(elCell.firstChild, sClassName);
17022     }
17023
17024     // Needed for SDT only
17025     var elThead = this.getTheadEl();
17026     var elTable = elThead.parentNode;
17027     var newWidth = elTable.offsetWidth;
17028     this._elMsgTbody.parentNode.style.width = this.getTheadEl().parentNode.offsetWidth + "px";
17029
17030     this._elMsgTbody.style.display = "";
17031
17032     this.fireEvent("tableMsgShowEvent", {html:sHTML, className:sClassName});
17033 },
17034
17035
17036
17037
17038
17039
17040
17041
17042
17043
17044
17045
17046
17047 /////////////////////////////////////////////////////////////////////////////
17048 //
17049 // Private Custom Event Handlers
17050 //
17051 /////////////////////////////////////////////////////////////////////////////
17052
17053 /**
17054  * Handles Column mutations
17055  *
17056  * @method onColumnChange
17057  * @param oArgs {Object} Custom Event data.
17058  */
17059 _onColumnChange : function(oArg) {
17060     // Figure out which Column changed
17061     var oColumn = (oArg.column) ? oArg.column :
17062             (oArg.editor) ? oArg.editor.column : null;
17063     this._storeScrollPositions();
17064     this.validateColumnWidths(oColumn);
17065 },
17066
17067
17068
17069
17070
17071
17072
17073
17074
17075
17076
17077
17078
17079
17080
17081 /////////////////////////////////////////////////////////////////////////////
17082 //
17083 // Private DOM Event Handlers
17084 //
17085 /////////////////////////////////////////////////////////////////////////////
17086
17087 /**
17088  * Syncs scrolltop and scrollleft of all TABLEs.
17089  *
17090  * @method _onScroll
17091  * @param e {HTMLEvent} The scroll event.
17092  * @param oSelf {YAHOO.widget.ScrollingDataTable} ScrollingDataTable instance.
17093  * @private
17094  */
17095 _onScroll : function(e, oSelf) {
17096     oSelf._elHdContainer.scrollLeft = oSelf._elBdContainer.scrollLeft;
17097
17098     if(oSelf._oCellEditor && oSelf._oCellEditor.isActive) {
17099         oSelf.fireEvent("editorBlurEvent", {editor:oSelf._oCellEditor});
17100         oSelf.cancelCellEditor();
17101     }
17102
17103     var elTarget = Ev.getTarget(e);
17104     var elTag = elTarget.nodeName.toLowerCase();
17105     oSelf.fireEvent("tableScrollEvent", {event:e, target:elTarget});
17106 },
17107
17108 /**
17109  * Handles keydown events on the THEAD element.
17110  *
17111  * @method _onTheadKeydown
17112  * @param e {HTMLEvent} The key event.
17113  * @param oSelf {YAHOO.widget.ScrollingDataTable} ScrollingDataTable instance.
17114  * @private
17115  */
17116 _onTheadKeydown : function(e, oSelf) {
17117     // If tabbing to next TH label link causes THEAD to scroll,
17118     // need to sync scrollLeft with TBODY
17119     if(Ev.getCharCode(e) === 9) {
17120         setTimeout(function() {
17121             if((oSelf instanceof SDT) && oSelf._sId) {
17122                 oSelf._elBdContainer.scrollLeft = oSelf._elHdContainer.scrollLeft;
17123             }
17124         },0);
17125     }
17126     
17127     var elTarget = Ev.getTarget(e);
17128     var elTag = elTarget.nodeName.toLowerCase();
17129     var bKeepBubbling = true;
17130     while(elTarget && (elTag != "table")) {
17131         switch(elTag) {
17132             case "body":
17133                 return;
17134             case "input":
17135             case "textarea":
17136                 // TODO: implement textareaKeyEvent
17137                 break;
17138             case "thead":
17139                 bKeepBubbling = oSelf.fireEvent("theadKeyEvent",{target:elTarget,event:e});
17140                 break;
17141             default:
17142                 break;
17143         }
17144         if(bKeepBubbling === false) {
17145             return;
17146         }
17147         else {
17148             elTarget = elTarget.parentNode;
17149             if(elTarget) {
17150                 elTag = elTarget.nodeName.toLowerCase();
17151             }
17152         }
17153     }
17154     oSelf.fireEvent("tableKeyEvent",{target:(elTarget || oSelf._elContainer),event:e});
17155 }
17156
17157
17158
17159
17160 /**
17161  * Fired when a fixed scrolling DataTable has a scroll.
17162  *
17163  * @event tableScrollEvent
17164  * @param oArgs.event {HTMLEvent} The event object.
17165  * @param oArgs.target {HTMLElement} The DataTable's CONTAINER element (in IE)
17166  * or the DataTable's TBODY element (everyone else).
17167  *
17168  */
17169
17170
17171
17172
17173 });
17174
17175 })();
17176
17177 (function () {
17178
17179 var lang   = YAHOO.lang,
17180     util   = YAHOO.util,
17181     widget = YAHOO.widget,
17182     ua     = YAHOO.env.ua,
17183     
17184     Dom    = util.Dom,
17185     Ev     = util.Event,
17186     
17187     DT     = widget.DataTable;
17188 /****************************************************************************/
17189 /****************************************************************************/
17190 /****************************************************************************/
17191     
17192 /**
17193  * The BaseCellEditor class provides base functionality common to all inline cell
17194  * editors for a DataTable widget.
17195  *
17196  * @namespace YAHOO.widget
17197  * @class BaseCellEditor
17198  * @uses YAHOO.util.EventProvider 
17199  * @constructor
17200  * @param sType {String} Type indicator, to map to YAHOO.widget.DataTable.Editors.
17201  * @param oConfigs {Object} (Optional) Object literal of configs.
17202  */
17203 widget.BaseCellEditor = function(sType, oConfigs) {
17204     this._sId = this._sId || Dom.generateId(null, "yui-ceditor"); // "yui-ceditor" + YAHOO.widget.BaseCellEditor._nCount++;
17205     YAHOO.widget.BaseCellEditor._nCount++;
17206     this._sType = sType;
17207     
17208     // Validate inputs
17209     this._initConfigs(oConfigs); 
17210     
17211     // Create Custom Events
17212     this._initEvents();
17213              
17214     // UI needs to be drawn
17215     this._needsRender = true;
17216 };
17217
17218 var BCE = widget.BaseCellEditor;
17219
17220 /////////////////////////////////////////////////////////////////////////////
17221 //
17222 // Static members
17223 //
17224 /////////////////////////////////////////////////////////////////////////////
17225 lang.augmentObject(BCE, {
17226
17227 /**
17228  * Global instance counter.
17229  *
17230  * @property CellEditor._nCount
17231  * @type Number
17232  * @static
17233  * @default 0
17234  * @private 
17235  */
17236 _nCount : 0,
17237
17238 /**
17239  * Class applied to CellEditor container.
17240  *
17241  * @property CellEditor.CLASS_CELLEDITOR
17242  * @type String
17243  * @static
17244  * @default "yui-ceditor"
17245  */
17246 CLASS_CELLEDITOR : "yui-ceditor"
17247
17248 });
17249
17250 BCE.prototype = {
17251 /////////////////////////////////////////////////////////////////////////////
17252 //
17253 // Private members
17254 //
17255 /////////////////////////////////////////////////////////////////////////////
17256 /**
17257  * Unique id assigned to instance "yui-ceditorN", useful prefix for generating unique
17258  * DOM ID strings and log messages.
17259  *
17260  * @property _sId
17261  * @type String
17262  * @private
17263  */
17264 _sId : null,
17265
17266 /**
17267  * Editor type.
17268  *
17269  * @property _sType
17270  * @type String
17271  * @private
17272  */
17273 _sType : null,
17274
17275 /**
17276  * DataTable instance.
17277  *
17278  * @property _oDataTable
17279  * @type YAHOO.widget.DataTable
17280  * @private 
17281  */
17282 _oDataTable : null,
17283
17284 /**
17285  * Column instance.
17286  *
17287  * @property _oColumn
17288  * @type YAHOO.widget.Column
17289  * @default null
17290  * @private 
17291  */
17292 _oColumn : null,
17293
17294 /**
17295  * Record instance.
17296  *
17297  * @property _oRecord
17298  * @type YAHOO.widget.Record
17299  * @default null
17300  * @private 
17301  */
17302 _oRecord : null,
17303
17304 /**
17305  * TD element.
17306  *
17307  * @property _elTd
17308  * @type HTMLElement
17309  * @default null
17310  * @private
17311  */
17312 _elTd : null,
17313
17314 /**
17315  * Container for inline editor.
17316  *
17317  * @property _elContainer
17318  * @type HTMLElement
17319  * @private 
17320  */
17321 _elContainer : null,
17322
17323 /**
17324  * Reference to Cancel button, if available.
17325  *
17326  * @property _elCancelBtn
17327  * @type HTMLElement
17328  * @default null
17329  * @private 
17330  */
17331 _elCancelBtn : null,
17332
17333 /**
17334  * Reference to Save button, if available.
17335  *
17336  * @property _elSaveBtn
17337  * @type HTMLElement
17338  * @default null
17339  * @private 
17340  */
17341 _elSaveBtn : null,
17342
17343
17344
17345
17346
17347
17348
17349
17350 /////////////////////////////////////////////////////////////////////////////
17351 //
17352 // Private methods
17353 //
17354 /////////////////////////////////////////////////////////////////////////////
17355
17356 /**
17357  * Initialize configs.
17358  *
17359  * @method _initConfigs
17360  * @private   
17361  */
17362 _initConfigs : function(oConfigs) {
17363     // Object literal defines CellEditor configs
17364     if(oConfigs && YAHOO.lang.isObject(oConfigs)) {
17365         for(var sConfig in oConfigs) {
17366             if(sConfig) {
17367                 this[sConfig] = oConfigs[sConfig];
17368             }
17369         }
17370     }
17371 },
17372
17373 /**
17374  * Initialize Custom Events.
17375  *
17376  * @method _initEvents
17377  * @private   
17378  */
17379 _initEvents : function() {
17380     this.createEvent("showEvent");
17381     this.createEvent("keydownEvent");
17382     this.createEvent("invalidDataEvent");
17383     this.createEvent("revertEvent");
17384     this.createEvent("saveEvent");
17385     this.createEvent("cancelEvent");
17386     this.createEvent("blurEvent");
17387     this.createEvent("blockEvent");
17388     this.createEvent("unblockEvent");
17389 },
17390
17391 /**
17392  * Initialize container element.
17393  *
17394  * @method _initContainerEl
17395  * @private
17396  */
17397 _initContainerEl : function() {
17398     if(this._elContainer) {
17399         YAHOO.util.Event.purgeElement(this._elContainer, true);
17400         this._elContainer.innerHTML = "";
17401     }
17402
17403     var elContainer = document.createElement("div");
17404     elContainer.id = this.getId() + "-container"; // Needed for tracking blur event
17405     elContainer.style.display = "none";
17406     elContainer.tabIndex = 0;
17407     
17408     this.className = lang.isArray(this.className) ? this.className : this.className ? [this.className] : [];
17409     this.className[this.className.length] = DT.CLASS_EDITOR;
17410     elContainer.className = this.className.join(" ");
17411     
17412     document.body.insertBefore(elContainer, document.body.firstChild);
17413     this._elContainer = elContainer;
17414 },
17415
17416 /**
17417  * Initialize container shim element.
17418  *
17419  * @method _initShimEl
17420  * @private
17421  */
17422 _initShimEl : function() {
17423     // Iframe shim
17424     if(this.useIFrame) {
17425         if(!this._elIFrame) {
17426             var elIFrame = document.createElement("iframe");
17427             elIFrame.src = "javascript:false";
17428             elIFrame.frameBorder = 0;
17429             elIFrame.scrolling = "no";
17430             elIFrame.style.display = "none";
17431             elIFrame.className = DT.CLASS_EDITOR_SHIM;
17432             elIFrame.tabIndex = -1;
17433             elIFrame.role = "presentation";
17434             elIFrame.title = "Presentational iframe shim";
17435             document.body.insertBefore(elIFrame, document.body.firstChild);
17436             this._elIFrame = elIFrame;
17437         }
17438     }
17439 },
17440
17441 /**
17442  * Hides CellEditor UI at end of interaction.
17443  *
17444  * @method _hide
17445  */
17446 _hide : function() {
17447     this.getContainerEl().style.display = "none";
17448     if(this._elIFrame) {
17449         this._elIFrame.style.display = "none";
17450     }
17451     this.isActive = false;
17452     this.getDataTable()._oCellEditor =  null;
17453 },
17454
17455
17456
17457
17458
17459
17460
17461
17462
17463
17464
17465 /////////////////////////////////////////////////////////////////////////////
17466 //
17467 // Public properties
17468 //
17469 /////////////////////////////////////////////////////////////////////////////
17470 /**
17471  * Implementer defined function that can submit the input value to a server. This
17472  * function must accept the arguments fnCallback and oNewValue. When the submission
17473  * is complete, the function must also call fnCallback(bSuccess, oNewValue) to 
17474  * finish the save routine in the CellEditor. This function can also be used to 
17475  * perform extra validation or input value manipulation. 
17476  *
17477  * @property asyncSubmitter
17478  * @type HTMLFunction
17479  */
17480 asyncSubmitter : null,
17481
17482 /**
17483  * Current value.
17484  *
17485  * @property value
17486  * @type MIXED
17487  */
17488 value : null,
17489
17490 /**
17491  * Default value in case Record data is undefined. NB: Null values will not trigger
17492  * the default value.
17493  *
17494  * @property defaultValue
17495  * @type MIXED
17496  * @default null
17497  */
17498 defaultValue : null,
17499
17500 /**
17501  * Validator function for input data, called from the DataTable instance scope,
17502  * receives the arguments (inputValue, currentValue, editorInstance) and returns
17503  * either the validated (or type-converted) value or undefined.
17504  *
17505  * @property validator
17506  * @type HTMLFunction
17507  * @default null
17508  */
17509 validator : null,
17510
17511 /**
17512  * If validation is enabled, resets input field of invalid data.
17513  *
17514  * @property resetInvalidData
17515  * @type Boolean
17516  * @default true
17517  */
17518 resetInvalidData : true,
17519
17520 /**
17521  * True if currently active.
17522  *
17523  * @property isActive
17524  * @type Boolean
17525  */
17526 isActive : false,
17527
17528 /**
17529  * Text to display on Save button.
17530  *
17531  * @property LABEL_SAVE
17532  * @type HTML
17533  * @default "Save"
17534  */
17535 LABEL_SAVE : "Save",
17536
17537 /**
17538  * Text to display on Cancel button.
17539  *
17540  * @property LABEL_CANCEL
17541  * @type HTML
17542  * @default "Cancel"
17543  */
17544 LABEL_CANCEL : "Cancel",
17545
17546 /**
17547  * True if Save/Cancel buttons should not be displayed in the CellEditor.
17548  *
17549  * @property disableBtns
17550  * @type Boolean
17551  * @default false
17552  */
17553 disableBtns : false,
17554
17555 /**
17556  * True if iframe shim for container element should be enabled.
17557  *
17558  * @property useIFrame
17559  * @type Boolean
17560  * @default false
17561  */
17562 useIFrame : false,
17563
17564 /**
17565  * Custom CSS class or array of classes applied to the container element.
17566  *
17567  * @property className
17568  * @type String || String[]
17569  */
17570 className : null,
17571
17572
17573
17574
17575
17576 /////////////////////////////////////////////////////////////////////////////
17577 //
17578 // Public methods
17579 //
17580 /////////////////////////////////////////////////////////////////////////////
17581 /**
17582  * CellEditor instance name, for logging.
17583  *
17584  * @method toString
17585  * @return {String} Unique name of the CellEditor instance.
17586  */
17587
17588 toString : function() {
17589     return "CellEditor instance " + this._sId;
17590 },
17591
17592 /**
17593  * CellEditor unique ID.
17594  *
17595  * @method getId
17596  * @return {String} Unique ID of the CellEditor instance.
17597  */
17598
17599 getId : function() {
17600     return this._sId;
17601 },
17602
17603 /**
17604  * Returns reference to associated DataTable instance.
17605  *
17606  * @method getDataTable
17607  * @return {YAHOO.widget.DataTable} DataTable instance.
17608  */
17609
17610 getDataTable : function() {
17611     return this._oDataTable;
17612 },
17613
17614 /**
17615  * Returns reference to associated Column instance.
17616  *
17617  * @method getColumn
17618  * @return {YAHOO.widget.Column} Column instance.
17619  */
17620
17621 getColumn : function() {
17622     return this._oColumn;
17623 },
17624
17625 /**
17626  * Returns reference to associated Record instance.
17627  *
17628  * @method getRecord
17629  * @return {YAHOO.widget.Record} Record instance.
17630  */
17631
17632 getRecord : function() {
17633     return this._oRecord;
17634 },
17635
17636
17637
17638 /**
17639  * Returns reference to associated TD element.
17640  *
17641  * @method getTdEl
17642  * @return {HTMLElement} TD element.
17643  */
17644
17645 getTdEl : function() {
17646     return this._elTd;
17647 },
17648
17649 /**
17650  * Returns container element.
17651  *
17652  * @method getContainerEl
17653  * @return {HTMLElement} Reference to container element.
17654  */
17655
17656 getContainerEl : function() {
17657     return this._elContainer;
17658 },
17659
17660 /**
17661  * Nulls out the entire CellEditor instance and related objects, removes attached
17662  * event listeners, and clears out DOM elements inside the container, removes
17663  * container from the DOM.
17664  *
17665  * @method destroy
17666  */
17667 destroy : function() {
17668     this.unsubscribeAll();
17669     
17670     // Column is late-binding in attach()
17671     var oColumn = this.getColumn();
17672     if(oColumn) {
17673         oColumn.editor = null;
17674     }
17675     
17676     var elContainer = this.getContainerEl();
17677     if (elContainer) {
17678         Ev.purgeElement(elContainer, true);
17679         elContainer.parentNode.removeChild(elContainer);
17680     }
17681 },
17682
17683 /**
17684  * Renders DOM elements and attaches event listeners.
17685  *
17686  * @method render
17687  */
17688 render : function() {
17689     if (!this._needsRender) {
17690         return;
17691     }
17692
17693     this._initContainerEl();
17694     this._initShimEl();
17695
17696     // Handle ESC key
17697     Ev.addListener(this.getContainerEl(), "keydown", function(e, oSelf) {
17698         // ESC cancels Cell Editor
17699         if((e.keyCode == 27)) {
17700             var target = Ev.getTarget(e);
17701             // workaround for Mac FF3 bug that disabled clicks when ESC hit when
17702             // select is open. [bug 2273056]
17703             if (target.nodeName && target.nodeName.toLowerCase() === 'select') {
17704                 target.blur();
17705             }
17706             oSelf.cancel();
17707         }
17708         // Pass through event
17709         oSelf.fireEvent("keydownEvent", {editor:oSelf, event:e});
17710     }, this);
17711
17712     this.renderForm();
17713
17714     // Show Save/Cancel buttons
17715     if(!this.disableBtns) {
17716         this.renderBtns();
17717     }
17718     
17719     this.doAfterRender();
17720     this._needsRender = false;
17721 },
17722
17723 /**
17724  * Renders Save/Cancel buttons.
17725  *
17726  * @method renderBtns
17727  */
17728 renderBtns : function() {
17729     // Buttons
17730     var elBtnsDiv = this.getContainerEl().appendChild(document.createElement("div"));
17731     elBtnsDiv.className = DT.CLASS_BUTTON;
17732
17733     // Save button
17734     var elSaveBtn = elBtnsDiv.appendChild(document.createElement("button"));
17735     elSaveBtn.className = DT.CLASS_DEFAULT;
17736     elSaveBtn.innerHTML = this.LABEL_SAVE;
17737     Ev.addListener(elSaveBtn, "click", function(oArgs) {
17738         this.save();
17739     }, this, true);
17740     this._elSaveBtn = elSaveBtn;
17741
17742     // Cancel button
17743     var elCancelBtn = elBtnsDiv.appendChild(document.createElement("button"));
17744     elCancelBtn.innerHTML = this.LABEL_CANCEL;
17745     Ev.addListener(elCancelBtn, "click", function(oArgs) {
17746         this.cancel();
17747     }, this, true);
17748     this._elCancelBtn = elCancelBtn;
17749 },
17750
17751 /**
17752  * Attach CellEditor for a new interaction.
17753  *
17754  * @method attach
17755  * @param oDataTable {YAHOO.widget.DataTable} Associated DataTable instance.
17756  * @param elCell {HTMLElement} Cell to edit.  
17757  */
17758 attach : function(oDataTable, elCell) {
17759     // Validate 
17760     if(oDataTable instanceof YAHOO.widget.DataTable) {
17761         this._oDataTable = oDataTable;
17762         
17763         // Validate cell
17764         elCell = oDataTable.getTdEl(elCell);
17765         if(elCell) {
17766             this._elTd = elCell;
17767
17768             // Validate Column
17769             var oColumn = oDataTable.getColumn(elCell);
17770             if(oColumn) {
17771                 this._oColumn = oColumn;
17772                 
17773                 // Validate Record
17774                 var oRecord = oDataTable.getRecord(elCell);
17775                 if(oRecord) {
17776                     this._oRecord = oRecord;
17777                     var value = oRecord.getData(this.getColumn().getField());
17778                     this.value = (value !== undefined) ? value : this.defaultValue;
17779                     return true;
17780                 }
17781             }            
17782         }
17783     }
17784     return false;
17785 },
17786
17787 /**
17788  * Moves container into position for display.
17789  *
17790  * @method move
17791  */
17792 move : function() {
17793     // Move Editor
17794     var elContainer = this.getContainerEl(),
17795         elTd = this.getTdEl(),
17796         x = Dom.getX(elTd),
17797         y = Dom.getY(elTd);
17798
17799     //TODO: remove scrolling logic
17800     // SF doesn't get xy for cells in scrolling table
17801     // when tbody display is set to block
17802     if(isNaN(x) || isNaN(y)) {
17803         var elTbody = this.getDataTable().getTbodyEl();
17804         x = elTd.offsetLeft + // cell pos relative to table
17805                 Dom.getX(elTbody.parentNode) - // plus table pos relative to document
17806                 elTbody.scrollLeft; // minus tbody scroll
17807         y = elTd.offsetTop + // cell pos relative to table
17808                 Dom.getY(elTbody.parentNode) - // plus table pos relative to document
17809                 elTbody.scrollTop + // minus tbody scroll
17810                 this.getDataTable().getTheadEl().offsetHeight; // account for fixed THEAD cells
17811     }
17812
17813     elContainer.style.left = x + "px";
17814     elContainer.style.top = y + "px";
17815
17816     if(this._elIFrame) {
17817         this._elIFrame.style.left = x + "px";
17818         this._elIFrame.style.top = y + "px";
17819     }
17820 },
17821
17822 /**
17823  * Displays CellEditor UI in the correct position.
17824  *
17825  * @method show
17826  */
17827 show : function() {
17828     var elContainer = this.getContainerEl(),
17829         elIFrame = this._elIFrame;
17830     this.resetForm();
17831     this.isActive = true;
17832     elContainer.style.display = "";
17833     if(elIFrame) {
17834         elIFrame.style.width = elContainer.offsetWidth + "px";
17835         elIFrame.style.height = elContainer.offsetHeight + "px";
17836         elIFrame.style.display = "";
17837     }
17838     this.focus();
17839     this.fireEvent("showEvent", {editor:this});
17840 },
17841
17842 /**
17843  * Fires blockEvent
17844  *
17845  * @method block
17846  */
17847 block : function() {
17848     this.fireEvent("blockEvent", {editor:this});
17849 },
17850
17851 /**
17852  * Fires unblockEvent
17853  *
17854  * @method unblock
17855  */
17856 unblock : function() {
17857     this.fireEvent("unblockEvent", {editor:this});
17858 },
17859
17860 /**
17861  * Saves value of CellEditor and hides UI.
17862  *
17863  * @method save
17864  */
17865 save : function() {
17866     // Get new value
17867     var inputValue = this.getInputValue();
17868     var validValue = inputValue;
17869     
17870     // Validate new value
17871     if(this.validator) {
17872         validValue = this.validator.call(this.getDataTable(), inputValue, this.value, this);
17873         if(validValue === undefined ) {
17874             if(this.resetInvalidData) {
17875                 this.resetForm();
17876             }
17877             this.fireEvent("invalidDataEvent",
17878                     {editor:this, oldData:this.value, newData:inputValue});
17879             return;
17880         }
17881     }
17882         
17883     var oSelf = this;
17884     var finishSave = function(bSuccess, oNewValue) {
17885         var oOrigValue = oSelf.value;
17886         if(bSuccess) {
17887             // Update new value
17888             oSelf.value = oNewValue;
17889             oSelf.getDataTable().updateCell(oSelf.getRecord(), oSelf.getColumn(), oNewValue);
17890             
17891             // Hide CellEditor
17892             oSelf._hide();
17893             
17894             oSelf.fireEvent("saveEvent",
17895                     {editor:oSelf, oldData:oOrigValue, newData:oSelf.value});
17896         }
17897         else {
17898             oSelf.resetForm();
17899             oSelf.fireEvent("revertEvent",
17900                     {editor:oSelf, oldData:oOrigValue, newData:oNewValue});
17901         }
17902         oSelf.unblock();
17903     };
17904     
17905     this.block();
17906     if(lang.isFunction(this.asyncSubmitter)) {
17907         this.asyncSubmitter.call(this, finishSave, validValue);
17908     } 
17909     else {   
17910         finishSave(true, validValue);
17911     }
17912 },
17913
17914 /**
17915  * Cancels CellEditor input and hides UI.
17916  *
17917  * @method cancel
17918  */
17919 cancel : function() {
17920     if(this.isActive) {
17921         this._hide();
17922         this.fireEvent("cancelEvent", {editor:this});
17923     }
17924     else {
17925     }
17926 },
17927
17928 /**
17929  * Renders form elements.
17930  *
17931  * @method renderForm
17932  */
17933 renderForm : function() {
17934     // To be implemented by subclass
17935 },
17936
17937 /**
17938  * Access to add additional event listeners.
17939  *
17940  * @method doAfterRender
17941  */
17942 doAfterRender : function() {
17943     // To be implemented by subclass
17944 },
17945
17946
17947 /**
17948  * After rendering form, if disabledBtns is set to true, then sets up a mechanism
17949  * to save input without them. 
17950  *
17951  * @method handleDisabledBtns
17952  */
17953 handleDisabledBtns : function() {
17954     // To be implemented by subclass
17955 },
17956
17957 /**
17958  * Resets CellEditor UI to initial state.
17959  *
17960  * @method resetForm
17961  */
17962 resetForm : function() {
17963     // To be implemented by subclass
17964 },
17965
17966 /**
17967  * Sets focus in CellEditor.
17968  *
17969  * @method focus
17970  */
17971 focus : function() {
17972     // To be implemented by subclass
17973 },
17974
17975 /**
17976  * Retrieves input value from CellEditor.
17977  *
17978  * @method getInputValue
17979  */
17980 getInputValue : function() {
17981     // To be implemented by subclass
17982 }
17983
17984 };
17985
17986 lang.augmentProto(BCE, util.EventProvider);
17987
17988
17989 /////////////////////////////////////////////////////////////////////////////
17990 //
17991 // Custom Events
17992 //
17993 /////////////////////////////////////////////////////////////////////////////
17994
17995 /**
17996  * Fired when a CellEditor is shown.
17997  *
17998  * @event showEvent
17999  * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
18000  */
18001
18002 /**
18003  * Fired when a CellEditor has a keydown.
18004  *
18005  * @event keydownEvent
18006  * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance. 
18007  * @param oArgs.event {HTMLEvent} The event object.
18008  */
18009
18010 /**
18011  * Fired when a CellEditor input is reverted due to invalid data.
18012  *
18013  * @event invalidDataEvent
18014  * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance. 
18015  * @param oArgs.newData {Object} New data value from form input field.
18016  * @param oArgs.oldData {Object} Old data value.
18017  */
18018
18019 /**
18020  * Fired when a CellEditor input is reverted due to asyncSubmitter failure.
18021  *
18022  * @event revertEvent
18023  * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance. 
18024  * @param oArgs.newData {Object} New data value from form input field.
18025  * @param oArgs.oldData {Object} Old data value.
18026  */
18027
18028 /**
18029  * Fired when a CellEditor input is saved.
18030  *
18031  * @event saveEvent
18032  * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance. 
18033  * @param oArgs.newData {Object} New data value from form input field.
18034  * @param oArgs.oldData {Object} Old data value.
18035  */
18036
18037 /**
18038  * Fired when a CellEditor input is canceled.
18039  *
18040  * @event cancelEvent
18041  * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance. 
18042  */
18043
18044 /**
18045  * Fired when a CellEditor has a blur event.
18046  *
18047  * @event blurEvent
18048  * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance. 
18049  */
18050
18051
18052
18053
18054
18055
18056
18057
18058
18059
18060
18061
18062
18063
18064 /****************************************************************************/
18065 /****************************************************************************/
18066 /****************************************************************************/
18067     
18068 /**
18069  * The CheckboxCellEditor class provides functionality for inline editing
18070  * DataTable cell data with checkboxes.
18071  *
18072  * @namespace YAHOO.widget
18073  * @class CheckboxCellEditor
18074  * @extends YAHOO.widget.BaseCellEditor
18075  * @constructor
18076  * @param oConfigs {Object} (Optional) Object literal of configs.
18077  */
18078 widget.CheckboxCellEditor = function(oConfigs) {
18079     oConfigs = oConfigs || {};
18080     this._sId = this._sId || Dom.generateId(null, "yui-checkboxceditor"); // "yui-checkboxceditor" + YAHOO.widget.BaseCellEditor._nCount++;
18081     YAHOO.widget.BaseCellEditor._nCount++;
18082     widget.CheckboxCellEditor.superclass.constructor.call(this, oConfigs.type || "checkbox", oConfigs);
18083 };
18084
18085 // CheckboxCellEditor extends BaseCellEditor
18086 lang.extend(widget.CheckboxCellEditor, BCE, {
18087
18088 /////////////////////////////////////////////////////////////////////////////
18089 //
18090 // CheckboxCellEditor public properties
18091 //
18092 /////////////////////////////////////////////////////////////////////////////
18093 /**
18094  * Array of checkbox values. Can either be a simple array (e.g., ["red","green","blue"])
18095  * or a an array of objects (e.g., [{label:"red", value:"#FF0000"},
18096  * {label:"green", value:"#00FF00"}, {label:"blue", value:"#0000FF"}]). String
18097  * values are treated as markup and inserted into the DOM as innerHTML.
18098  *
18099  * @property checkboxOptions
18100  * @type HTML[] | Object[]
18101  */
18102 checkboxOptions : null,
18103
18104 /**
18105  * Reference to the checkbox elements.
18106  *
18107  * @property checkboxes
18108  * @type HTMLElement[] 
18109  */
18110 checkboxes : null,
18111
18112 /**
18113  * Array of checked values
18114  *
18115  * @property value
18116  * @type String[] 
18117  */
18118 value : null,
18119
18120 /////////////////////////////////////////////////////////////////////////////
18121 //
18122 // CheckboxCellEditor public methods
18123 //
18124 /////////////////////////////////////////////////////////////////////////////
18125
18126 /**
18127  * Render a form with input(s) type=checkbox.
18128  *
18129  * @method renderForm
18130  */
18131 renderForm : function() {
18132     if(lang.isArray(this.checkboxOptions)) {
18133         var checkboxOption, checkboxValue, checkboxId, elLabel, j, len;
18134         
18135         // Create the checkbox buttons in an IE-friendly way...
18136         for(j=0,len=this.checkboxOptions.length; j<len; j++) {
18137             checkboxOption = this.checkboxOptions[j];
18138             checkboxValue = lang.isValue(checkboxOption.value) ?
18139                     checkboxOption.value : checkboxOption;
18140
18141             checkboxId = this.getId() + "-chk" + j;
18142             this.getContainerEl().innerHTML += "<input type=\"checkbox\"" +
18143                     " id=\"" + checkboxId + "\"" + // Needed for label
18144                     " value=\"" + checkboxValue + "\" />";
18145             
18146             // Create the labels in an IE-friendly way
18147             elLabel = this.getContainerEl().appendChild(document.createElement("label"));
18148             elLabel.htmlFor = checkboxId;
18149             elLabel.innerHTML = lang.isValue(checkboxOption.label) ?
18150                     checkboxOption.label : checkboxOption;
18151         }
18152         
18153         // Store the reference to the checkbox elements
18154         var allCheckboxes = [];
18155         for(j=0; j<len; j++) {
18156             allCheckboxes[allCheckboxes.length] = this.getContainerEl().childNodes[j*2];
18157         }
18158         this.checkboxes = allCheckboxes;
18159
18160         if(this.disableBtns) {
18161             this.handleDisabledBtns();
18162         }
18163     }
18164     else {
18165     }
18166 },
18167
18168 /**
18169  * After rendering form, if disabledBtns is set to true, then sets up a mechanism
18170  * to save input without them. 
18171  *
18172  * @method handleDisabledBtns
18173  */
18174 handleDisabledBtns : function() {
18175     Ev.addListener(this.getContainerEl(), "click", function(v){
18176         if(Ev.getTarget(v).tagName.toLowerCase() === "input") {
18177             // Save on blur
18178             this.save();
18179         }
18180     }, this, true);
18181 },
18182
18183 /**
18184  * Resets CheckboxCellEditor UI to initial state.
18185  *
18186  * @method resetForm
18187  */
18188 resetForm : function() {
18189     // Normalize to array
18190     var originalValues = lang.isArray(this.value) ? this.value : [this.value];
18191     
18192     // Match checks to value
18193     for(var i=0, j=this.checkboxes.length; i<j; i++) {
18194         this.checkboxes[i].checked = false;
18195         for(var k=0, len=originalValues.length; k<len; k++) {
18196             if(this.checkboxes[i].value == originalValues[k]) {
18197                 this.checkboxes[i].checked = true;
18198             }
18199         }
18200     }
18201 },
18202
18203 /**
18204  * Sets focus in CheckboxCellEditor.
18205  *
18206  * @method focus
18207  */
18208 focus : function() {
18209     this.checkboxes[0].focus();
18210 },
18211
18212 /**
18213  * Retrieves input value from CheckboxCellEditor.
18214  *
18215  * @method getInputValue
18216  */
18217 getInputValue : function() {
18218     var checkedValues = [];
18219     for(var i=0, j=this.checkboxes.length; i<j; i++) {
18220         if(this.checkboxes[i].checked) {
18221             checkedValues[checkedValues.length] = this.checkboxes[i].value;
18222         }
18223     }  
18224     return checkedValues;
18225 }
18226
18227 });
18228
18229 // Copy static members to CheckboxCellEditor class
18230 lang.augmentObject(widget.CheckboxCellEditor, BCE);
18231
18232
18233
18234
18235
18236
18237
18238
18239 /****************************************************************************/
18240 /****************************************************************************/
18241 /****************************************************************************/
18242     
18243 /**
18244  * The DataCellEditor class provides functionality for inline editing
18245  * DataTable cell data with a YUI Calendar.
18246  *
18247  * @namespace YAHOO.widget
18248  * @class DateCellEditor
18249  * @extends YAHOO.widget.BaseCellEditor 
18250  * @constructor
18251  * @param oConfigs {Object} (Optional) Object literal of configs.
18252  */
18253 widget.DateCellEditor = function(oConfigs) {
18254     oConfigs = oConfigs || {};
18255     this._sId = this._sId || Dom.generateId(null, "yui-dateceditor"); // "yui-dateceditor" + YAHOO.widget.BaseCellEditor._nCount++;
18256     YAHOO.widget.BaseCellEditor._nCount++;
18257     widget.DateCellEditor.superclass.constructor.call(this, oConfigs.type || "date", oConfigs);
18258 };
18259
18260 // CheckboxCellEditor extends BaseCellEditor
18261 lang.extend(widget.DateCellEditor, BCE, {
18262
18263 /////////////////////////////////////////////////////////////////////////////
18264 //
18265 // DateCellEditor public properties
18266 //
18267 /////////////////////////////////////////////////////////////////////////////
18268 /**
18269  * Reference to Calendar instance.
18270  *
18271  * @property calendar
18272  * @type YAHOO.widget.Calendar
18273  */
18274 calendar : null,
18275
18276 /**
18277  * Configs for the calendar instance, to be passed to Calendar constructor.
18278  *
18279  * @property calendarOptions
18280  * @type Object
18281  */
18282 calendarOptions : null,
18283
18284 /**
18285  * Default value.
18286  *
18287  * @property defaultValue
18288  * @type Date
18289  * @default new Date()
18290  */
18291 defaultValue : new Date(),
18292
18293
18294 /////////////////////////////////////////////////////////////////////////////
18295 //
18296 // DateCellEditor public methods
18297 //
18298 /////////////////////////////////////////////////////////////////////////////
18299
18300 /**
18301  * Render a Calendar.
18302  *
18303  * @method renderForm
18304  */
18305 renderForm : function() {
18306     // Calendar widget
18307     if(YAHOO.widget.Calendar) {
18308         var calContainer = this.getContainerEl().appendChild(document.createElement("div"));
18309         calContainer.id = this.getId() + "-dateContainer"; // Needed for Calendar constructor
18310         var calendar =
18311                 new YAHOO.widget.Calendar(this.getId() + "-date",
18312                 calContainer.id, this.calendarOptions);
18313         calendar.render();
18314         calContainer.style.cssFloat = "none";
18315         
18316         // Bug 2528576
18317         calendar.hideEvent.subscribe(function() {this.cancel();}, this, true);
18318
18319         if(ua.ie) {
18320             var calFloatClearer = this.getContainerEl().appendChild(document.createElement("div"));
18321             calFloatClearer.style.clear = "both";
18322         }
18323         
18324         this.calendar = calendar;
18325
18326         if(this.disableBtns) {
18327             this.handleDisabledBtns();
18328         }
18329     }
18330     else {
18331     }
18332     
18333 },
18334
18335 /**
18336  * After rendering form, if disabledBtns is set to true, then sets up a mechanism
18337  * to save input without them. 
18338  *
18339  * @method handleDisabledBtns
18340  */
18341 handleDisabledBtns : function() {
18342     this.calendar.selectEvent.subscribe(function(v){
18343         // Save on select
18344         this.save();
18345     }, this, true);
18346 },
18347
18348 /**
18349  * Resets DateCellEditor UI to initial state.
18350  *
18351  * @method resetForm
18352  */
18353 resetForm : function() {
18354     var value = this.value || (new Date());
18355     this.calendar.select(value);
18356     this.calendar.cfg.setProperty("pagedate",value,false);
18357         this.calendar.render();
18358         // Bug 2528576
18359         this.calendar.show();
18360 },
18361
18362 /**
18363  * Sets focus in DateCellEditor.
18364  *
18365  * @method focus
18366  */
18367 focus : function() {
18368     // To be impmlemented by subclass
18369 },
18370
18371 /**
18372  * Retrieves input value from DateCellEditor.
18373  *
18374  * @method getInputValue
18375  */
18376 getInputValue : function() {
18377     return this.calendar.getSelectedDates()[0];
18378 }
18379
18380 });
18381
18382 // Copy static members to DateCellEditor class
18383 lang.augmentObject(widget.DateCellEditor, BCE);
18384
18385
18386
18387
18388
18389
18390
18391
18392
18393 /****************************************************************************/
18394 /****************************************************************************/
18395 /****************************************************************************/
18396     
18397 /**
18398  * The DropdownCellEditor class provides functionality for inline editing
18399  * DataTable cell data a SELECT element.
18400  *
18401  * @namespace YAHOO.widget
18402  * @class DropdownCellEditor
18403  * @extends YAHOO.widget.BaseCellEditor 
18404  * @constructor
18405  * @param oConfigs {Object} (Optional) Object literal of configs.
18406  */
18407 widget.DropdownCellEditor = function(oConfigs) {
18408     oConfigs = oConfigs || {};
18409     this._sId = this._sId || Dom.generateId(null, "yui-dropdownceditor"); // "yui-dropdownceditor" + YAHOO.widget.BaseCellEditor._nCount++;
18410     YAHOO.widget.BaseCellEditor._nCount++;
18411     widget.DropdownCellEditor.superclass.constructor.call(this, oConfigs.type || "dropdown", oConfigs);
18412 };
18413
18414 // DropdownCellEditor extends BaseCellEditor
18415 lang.extend(widget.DropdownCellEditor, BCE, {
18416
18417 /////////////////////////////////////////////////////////////////////////////
18418 //
18419 // DropdownCellEditor public properties
18420 //
18421 /////////////////////////////////////////////////////////////////////////////
18422 /**
18423  * Array of dropdown values. Can either be a simple array (e.g.,
18424  * ["Alabama","Alaska","Arizona","Arkansas"]) or a an array of objects (e.g., 
18425  * [{label:"Alabama", value:"AL"}, {label:"Alaska", value:"AK"},
18426  * {label:"Arizona", value:"AZ"}, {label:"Arkansas", value:"AR"}]). String
18427  * values are treated as markup and inserted into the DOM as innerHTML.
18428  *
18429  * @property dropdownOptions
18430  * @type HTML[] | Object[]
18431  */
18432 dropdownOptions : null,
18433
18434 /**
18435  * Reference to Dropdown element.
18436  *
18437  * @property dropdown
18438  * @type HTMLElement
18439  */
18440 dropdown : null,
18441
18442 /**
18443  * Enables multi-select.
18444  *
18445  * @property multiple
18446  * @type Boolean
18447  */
18448 multiple : false,
18449
18450 /**
18451  * Specifies number of visible options.
18452  *
18453  * @property size
18454  * @type Number
18455  */
18456 size : null,
18457
18458 /////////////////////////////////////////////////////////////////////////////
18459 //
18460 // DropdownCellEditor public methods
18461 //
18462 /////////////////////////////////////////////////////////////////////////////
18463
18464 /**
18465  * Render a form with select element.
18466  *
18467  * @method renderForm
18468  */
18469 renderForm : function() {
18470     var elDropdown = this.getContainerEl().appendChild(document.createElement("select"));
18471     elDropdown.style.zoom = 1;
18472     if(this.multiple) {
18473         elDropdown.multiple = "multiple";
18474     }
18475     if(lang.isNumber(this.size)) {
18476         elDropdown.size = this.size;
18477     }
18478     this.dropdown = elDropdown;
18479     
18480     if(lang.isArray(this.dropdownOptions)) {
18481         var dropdownOption, elOption;
18482         for(var i=0, j=this.dropdownOptions.length; i<j; i++) {
18483             dropdownOption = this.dropdownOptions[i];
18484             elOption = document.createElement("option");
18485             elOption.value = (lang.isValue(dropdownOption.value)) ?
18486                     dropdownOption.value : dropdownOption;
18487             elOption.innerHTML = (lang.isValue(dropdownOption.label)) ?
18488                     dropdownOption.label : dropdownOption;
18489             elOption = elDropdown.appendChild(elOption);
18490         }
18491         
18492         if(this.disableBtns) {
18493             this.handleDisabledBtns();
18494         }
18495     }
18496 },
18497
18498 /**
18499  * After rendering form, if disabledBtns is set to true, then sets up a mechanism
18500  * to save input without them. 
18501  *
18502  * @method handleDisabledBtns
18503  */
18504 handleDisabledBtns : function() {
18505     // Save on blur for multi-select
18506     if(this.multiple) {
18507         Ev.addListener(this.dropdown, "blur", function(v){
18508             // Save on change
18509             this.save();
18510         }, this, true);
18511     }
18512     // Save on change for single-select
18513     else {
18514         if(!ua.ie) {
18515             Ev.addListener(this.dropdown, "change", function(v){
18516                 // Save on change
18517                 this.save();
18518             }, this, true);
18519         }
18520         else {
18521             // Bug 2529274: "change" event is not keyboard accessible in IE6
18522             Ev.addListener(this.dropdown, "blur", function(v){
18523                 this.save();
18524             }, this, true);
18525             Ev.addListener(this.dropdown, "click", function(v){
18526                 this.save();
18527             }, this, true);
18528         }
18529     }
18530 },
18531
18532 /**
18533  * Resets DropdownCellEditor UI to initial state.
18534  *
18535  * @method resetForm
18536  */
18537 resetForm : function() {
18538     var allOptions = this.dropdown.options,
18539         i=0, j=allOptions.length;
18540
18541     // Look for multi-select selections
18542     if(lang.isArray(this.value)) {
18543         var allValues = this.value,
18544             m=0, n=allValues.length,
18545             hash = {};
18546         // Reset all selections and stash options in a value hash
18547         for(; i<j; i++) {
18548             allOptions[i].selected = false;
18549             hash[allOptions[i].value] = allOptions[i];
18550         }
18551         for(; m<n; m++) {
18552             if(hash[allValues[m]]) {
18553                 hash[allValues[m]].selected = true;
18554             }
18555         }
18556     }
18557     // Only need to look for a single selection
18558     else {
18559         for(; i<j; i++) {
18560             if(this.value == allOptions[i].value) {
18561                 allOptions[i].selected = true;
18562             }
18563         }
18564     }
18565 },
18566
18567 /**
18568  * Sets focus in DropdownCellEditor.
18569  *
18570  * @method focus
18571  */
18572 focus : function() {
18573     this.getDataTable()._focusEl(this.dropdown);
18574 },
18575
18576 /**
18577  * Retrieves input value from DropdownCellEditor.
18578  *
18579  * @method getInputValue
18580  */
18581 getInputValue : function() {
18582     var allOptions = this.dropdown.options;
18583     
18584     // Look for multiple selections
18585     if(this.multiple) {
18586         var values = [],
18587             i=0, j=allOptions.length;
18588         for(; i<j; i++) {
18589             if(allOptions[i].selected) {
18590                 values.push(allOptions[i].value);
18591             }
18592         }
18593         return values;
18594     }
18595     // Only need to look for single selection
18596     else {
18597         return allOptions[allOptions.selectedIndex].value;
18598     }
18599 }
18600
18601 });
18602
18603 // Copy static members to DropdownCellEditor class
18604 lang.augmentObject(widget.DropdownCellEditor, BCE);
18605
18606
18607
18608
18609
18610
18611 /****************************************************************************/
18612 /****************************************************************************/
18613 /****************************************************************************/
18614     
18615 /**
18616  * The RadioCellEditor class provides functionality for inline editing
18617  * DataTable cell data with radio buttons.
18618  *
18619  * @namespace YAHOO.widget
18620  * @class RadioCellEditor
18621  * @extends YAHOO.widget.BaseCellEditor 
18622  * @constructor
18623  * @param oConfigs {Object} (Optional) Object literal of configs.
18624  */
18625 widget.RadioCellEditor = function(oConfigs) {
18626     oConfigs = oConfigs || {};
18627     this._sId = this._sId || Dom.generateId(null, "yui-radioceditor"); // "yui-radioceditor" + YAHOO.widget.BaseCellEditor._nCount++;
18628     YAHOO.widget.BaseCellEditor._nCount++;
18629     widget.RadioCellEditor.superclass.constructor.call(this, oConfigs.type || "radio", oConfigs);
18630 };
18631
18632 // RadioCellEditor extends BaseCellEditor
18633 lang.extend(widget.RadioCellEditor, BCE, {
18634
18635 /////////////////////////////////////////////////////////////////////////////
18636 //
18637 // RadioCellEditor public properties
18638 //
18639 /////////////////////////////////////////////////////////////////////////////
18640 /**
18641  * Reference to radio elements.
18642  *
18643  * @property radios
18644  * @type HTMLElement[]
18645  */
18646 radios : null,
18647
18648 /**
18649  * Array of radio values. Can either be a simple array (e.g., ["yes","no","maybe"])
18650  * or a an array of objects (e.g., [{label:"yes", value:1}, {label:"no", value:-1},
18651  * {label:"maybe", value:0}]). String values are treated as markup and inserted
18652  * into the DOM as innerHTML.
18653  *
18654  * @property radioOptions
18655  * @type HTML[] | Object[]
18656  */
18657 radioOptions : null,
18658
18659 /////////////////////////////////////////////////////////////////////////////
18660 //
18661 // RadioCellEditor public methods
18662 //
18663 /////////////////////////////////////////////////////////////////////////////
18664
18665 /**
18666  * Render a form with input(s) type=radio.
18667  *
18668  * @method renderForm
18669  */
18670 renderForm : function() {
18671     if(lang.isArray(this.radioOptions)) {
18672         var radioOption, radioValue, radioId, elLabel;
18673         
18674         // Create the radio buttons in an IE-friendly way
18675         for(var i=0, len=this.radioOptions.length; i<len; i++) {
18676             radioOption = this.radioOptions[i];
18677             radioValue = lang.isValue(radioOption.value) ?
18678                     radioOption.value : radioOption;
18679             radioId = this.getId() + "-radio" + i;
18680             this.getContainerEl().innerHTML += "<input type=\"radio\"" +
18681                     " name=\"" + this.getId() + "\"" +
18682                     " value=\"" + radioValue + "\"" +
18683                     " id=\"" +  radioId + "\" />"; // Needed for label
18684             
18685             // Create the labels in an IE-friendly way
18686             elLabel = this.getContainerEl().appendChild(document.createElement("label"));
18687             elLabel.htmlFor = radioId;
18688             elLabel.innerHTML = (lang.isValue(radioOption.label)) ?
18689                     radioOption.label : radioOption;
18690         }
18691         
18692         // Store the reference to the checkbox elements
18693         var allRadios = [],
18694             elRadio;
18695         for(var j=0; j<len; j++) {
18696             elRadio = this.getContainerEl().childNodes[j*2];
18697             allRadios[allRadios.length] = elRadio;
18698         }
18699         this.radios = allRadios;
18700
18701         if(this.disableBtns) {
18702             this.handleDisabledBtns();
18703         }
18704     }
18705     else {
18706     }
18707 },
18708
18709 /**
18710  * After rendering form, if disabledBtns is set to true, then sets up a mechanism
18711  * to save input without them. 
18712  *
18713  * @method handleDisabledBtns
18714  */
18715 handleDisabledBtns : function() {
18716     Ev.addListener(this.getContainerEl(), "click", function(v){
18717         if(Ev.getTarget(v).tagName.toLowerCase() === "input") {
18718             // Save on blur
18719             this.save();
18720         }
18721     }, this, true);
18722 },
18723
18724 /**
18725  * Resets RadioCellEditor UI to initial state.
18726  *
18727  * @method resetForm
18728  */
18729 resetForm : function() {
18730     for(var i=0, j=this.radios.length; i<j; i++) {
18731         var elRadio = this.radios[i];
18732         if(this.value == elRadio.value) {
18733             elRadio.checked = true;
18734             return;
18735         }
18736     }
18737 },
18738
18739 /**
18740  * Sets focus in RadioCellEditor.
18741  *
18742  * @method focus
18743  */
18744 focus : function() {
18745     for(var i=0, j=this.radios.length; i<j; i++) {
18746         if(this.radios[i].checked) {
18747             this.radios[i].focus();
18748             return;
18749         }
18750     }
18751 },
18752
18753 /**
18754  * Retrieves input value from RadioCellEditor.
18755  *
18756  * @method getInputValue
18757  */
18758 getInputValue : function() {
18759     for(var i=0, j=this.radios.length; i<j; i++) {
18760         if(this.radios[i].checked) {
18761             return this.radios[i].value;
18762         }
18763     }
18764 }
18765
18766 });
18767
18768 // Copy static members to RadioCellEditor class
18769 lang.augmentObject(widget.RadioCellEditor, BCE);
18770
18771
18772
18773
18774
18775
18776 /****************************************************************************/
18777 /****************************************************************************/
18778 /****************************************************************************/
18779     
18780 /**
18781  * The TextareaCellEditor class provides functionality for inline editing
18782  * DataTable cell data with a TEXTAREA element.
18783  *
18784  * @namespace YAHOO.widget
18785  * @class TextareaCellEditor
18786  * @extends YAHOO.widget.BaseCellEditor 
18787  * @constructor
18788  * @param oConfigs {Object} (Optional) Object literal of configs.
18789  */
18790 widget.TextareaCellEditor = function(oConfigs) {
18791     oConfigs = oConfigs || {};
18792     this._sId = this._sId || Dom.generateId(null, "yui-textareaceditor");// "yui-textareaceditor" + ;
18793     YAHOO.widget.BaseCellEditor._nCount++;
18794     widget.TextareaCellEditor.superclass.constructor.call(this, oConfigs.type || "textarea", oConfigs);
18795 };
18796
18797 // TextareaCellEditor extends BaseCellEditor
18798 lang.extend(widget.TextareaCellEditor, BCE, {
18799
18800 /////////////////////////////////////////////////////////////////////////////
18801 //
18802 // TextareaCellEditor public properties
18803 //
18804 /////////////////////////////////////////////////////////////////////////////
18805 /**
18806  * Reference to textarea element.
18807  *
18808  * @property textarea
18809  * @type HTMLElement
18810  */
18811 textarea : null,
18812
18813
18814 /////////////////////////////////////////////////////////////////////////////
18815 //
18816 // TextareaCellEditor public methods
18817 //
18818 /////////////////////////////////////////////////////////////////////////////
18819
18820 /**
18821  * Render a form with textarea.
18822  *
18823  * @method renderForm
18824  */
18825 renderForm : function() {
18826     var elTextarea = this.getContainerEl().appendChild(document.createElement("textarea"));
18827     this.textarea = elTextarea;
18828
18829     if(this.disableBtns) {
18830         this.handleDisabledBtns();
18831     }
18832 },
18833
18834 /**
18835  * After rendering form, if disabledBtns is set to true, then sets up a mechanism
18836  * to save input without them. 
18837  *
18838  * @method handleDisabledBtns
18839  */
18840 handleDisabledBtns : function() {
18841     Ev.addListener(this.textarea, "blur", function(v){
18842         // Save on blur
18843         this.save();
18844     }, this, true);        
18845 },
18846
18847 /**
18848  * Moves TextareaCellEditor UI to a cell.
18849  *
18850  * @method move
18851  */
18852 move : function() {
18853     this.textarea.style.width = this.getTdEl().offsetWidth + "px";
18854     this.textarea.style.height = "3em";
18855     YAHOO.widget.TextareaCellEditor.superclass.move.call(this);
18856 },
18857
18858 /**
18859  * Resets TextareaCellEditor UI to initial state.
18860  *
18861  * @method resetForm
18862  */
18863 resetForm : function() {
18864     this.textarea.value = this.value;
18865 },
18866
18867 /**
18868  * Sets focus in TextareaCellEditor.
18869  *
18870  * @method focus
18871  */
18872 focus : function() {
18873     // Bug 2303181, Bug 2263600
18874     this.getDataTable()._focusEl(this.textarea);
18875     this.textarea.select();
18876 },
18877
18878 /**
18879  * Retrieves input value from TextareaCellEditor.
18880  *
18881  * @method getInputValue
18882  */
18883 getInputValue : function() {
18884     return this.textarea.value;
18885 }
18886
18887 });
18888
18889 // Copy static members to TextareaCellEditor class
18890 lang.augmentObject(widget.TextareaCellEditor, BCE);
18891
18892
18893
18894
18895
18896
18897
18898
18899
18900 /****************************************************************************/
18901 /****************************************************************************/
18902 /****************************************************************************/
18903     
18904 /**
18905  * The TextboxCellEditor class provides functionality for inline editing
18906  * DataTable cell data with an INPUT TYPE=TEXT element.
18907  *
18908  * @namespace YAHOO.widget
18909  * @class TextboxCellEditor
18910  * @extends YAHOO.widget.BaseCellEditor 
18911  * @constructor
18912  * @param oConfigs {Object} (Optional) Object literal of configs.
18913  */
18914 widget.TextboxCellEditor = function(oConfigs) {
18915     oConfigs = oConfigs || {};
18916     this._sId = this._sId || Dom.generateId(null, "yui-textboxceditor");// "yui-textboxceditor" + YAHOO.widget.BaseCellEditor._nCount++;
18917     YAHOO.widget.BaseCellEditor._nCount++;
18918     widget.TextboxCellEditor.superclass.constructor.call(this, oConfigs.type || "textbox", oConfigs);
18919 };
18920
18921 // TextboxCellEditor extends BaseCellEditor
18922 lang.extend(widget.TextboxCellEditor, BCE, {
18923
18924 /////////////////////////////////////////////////////////////////////////////
18925 //
18926 // TextboxCellEditor public properties
18927 //
18928 /////////////////////////////////////////////////////////////////////////////
18929 /**
18930  * Reference to the textbox element.
18931  *
18932  * @property textbox
18933  */
18934 textbox : null,
18935
18936 /////////////////////////////////////////////////////////////////////////////
18937 //
18938 // TextboxCellEditor public methods
18939 //
18940 /////////////////////////////////////////////////////////////////////////////
18941
18942 /**
18943  * Render a form with input type=text.
18944  *
18945  * @method renderForm
18946  */
18947 renderForm : function() {
18948     var elTextbox;
18949     // Bug 1802582: SF3/Mac needs a form element wrapping the input
18950     if(ua.webkit>420) {
18951         elTextbox = this.getContainerEl().appendChild(document.createElement("form")).appendChild(document.createElement("input"));
18952     }
18953     else {
18954         elTextbox = this.getContainerEl().appendChild(document.createElement("input"));
18955     }
18956     elTextbox.type = "text";
18957     this.textbox = elTextbox;
18958
18959     // Save on enter by default
18960     // Bug: 1802582 Set up a listener on each textbox to track on keypress
18961     // since SF/OP can't preventDefault on keydown
18962     Ev.addListener(elTextbox, "keypress", function(v){
18963         if((v.keyCode === 13)) {
18964             // Prevent form submit
18965             YAHOO.util.Event.preventDefault(v);
18966             this.save();
18967         }
18968     }, this, true);
18969
18970     if(this.disableBtns) {
18971         // By default this is no-op since enter saves by default
18972         this.handleDisabledBtns();
18973     }
18974 },
18975
18976 /**
18977  * Moves TextboxCellEditor UI to a cell.
18978  *
18979  * @method move
18980  */
18981 move : function() {
18982     this.textbox.style.width = this.getTdEl().offsetWidth + "px";
18983     widget.TextboxCellEditor.superclass.move.call(this);
18984 },
18985
18986 /**
18987  * Resets TextboxCellEditor UI to initial state.
18988  *
18989  * @method resetForm
18990  */
18991 resetForm : function() {
18992     this.textbox.value = lang.isValue(this.value) ? this.value.toString() : "";
18993 },
18994
18995 /**
18996  * Sets focus in TextboxCellEditor.
18997  *
18998  * @method focus
18999  */
19000 focus : function() {
19001     // Bug 2303181, Bug 2263600
19002     this.getDataTable()._focusEl(this.textbox);
19003     this.textbox.select();
19004 },
19005
19006 /**
19007  * Returns new value for TextboxCellEditor.
19008  *
19009  * @method getInputValue
19010  */
19011 getInputValue : function() {
19012     return this.textbox.value;
19013 }
19014
19015 });
19016
19017 // Copy static members to TextboxCellEditor class
19018 lang.augmentObject(widget.TextboxCellEditor, BCE);
19019
19020
19021
19022
19023
19024
19025
19026 /////////////////////////////////////////////////////////////////////////////
19027 //
19028 // DataTable extension
19029 //
19030 /////////////////////////////////////////////////////////////////////////////
19031
19032 /**
19033  * CellEditor subclasses.
19034  * @property DataTable.Editors
19035  * @type Object
19036  * @static
19037  */
19038 DT.Editors = {
19039     checkbox : widget.CheckboxCellEditor,
19040     "date"   : widget.DateCellEditor,
19041     dropdown : widget.DropdownCellEditor,
19042     radio    : widget.RadioCellEditor,
19043     textarea : widget.TextareaCellEditor,
19044     textbox  : widget.TextboxCellEditor
19045 };
19046
19047 /****************************************************************************/
19048 /****************************************************************************/
19049 /****************************************************************************/
19050     
19051 /**
19052  * Factory class for instantiating a BaseCellEditor subclass.
19053  *
19054  * @namespace YAHOO.widget
19055  * @class CellEditor
19056  * @extends YAHOO.widget.BaseCellEditor 
19057  * @constructor
19058  * @param sType {String} Type indicator, to map to YAHOO.widget.DataTable.Editors.
19059  * @param oConfigs {Object} (Optional) Object literal of configs.
19060  */
19061 widget.CellEditor = function(sType, oConfigs) {
19062     // Point to one of the subclasses
19063     if(sType && DT.Editors[sType]) {
19064         lang.augmentObject(BCE, DT.Editors[sType]);
19065         return new DT.Editors[sType](oConfigs);
19066     }
19067     else {
19068         return new BCE(null, oConfigs);
19069     }
19070 };
19071
19072 var CE = widget.CellEditor;
19073
19074 // Copy static members to CellEditor class
19075 lang.augmentObject(CE, BCE);
19076
19077
19078 })();
19079
19080 YAHOO.register("datatable", YAHOO.widget.DataTable, {version: "2.9.0", build: "2800"});