[ Index ]

PHP Cross Reference of Unnamed Project

title

Body

[close]

/lib/yuilib/2in3/2.9.0/build/yui2-datatable/ -> yui2-datatable-debug.js (source)

   1  YUI.add('yui2-datatable', function(Y) {
   2      var YAHOO    = Y.YUI2;
   3      /*
   4  Copyright (c) 2011, Yahoo! Inc. All rights reserved.
   5  Code licensed under the BSD License:
   6  http://developer.yahoo.com/yui/license.html
   7  version: 2.9.0
   8  */
   9  /**
  10   * 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:
  11   * <ul>
  12   *    <li><code>method</code> - {Function} REQUIRED the callback function.</li>
  13   *    <li><code>scope</code> - {Object} the scope from which to execute the callback.  Default is the global window scope.</li>
  14   *    <li><code>argument</code> - {Array} parameters to be passed to method as individual arguments.</li>
  15   *    <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>
  16   *    <li><code>until</code> - {Function} boolean function executed before each iteration.  Return true to indicate completion and proceed to the next callback.</li>
  17   *    <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>
  18   * </ul>
  19   *
  20   * @namespace YAHOO.util
  21   * @class Chain
  22   * @constructor
  23   * @param callback* {Function|Object} Any number of callbacks to initialize the queue
  24  */
  25  YAHOO.util.Chain = function () {
  26      /**
  27       * The callback queue
  28       * @property q
  29       * @type {Array}
  30       * @private
  31       */
  32      this.q = [].slice.call(arguments);
  33  
  34      /**
  35       * Event fired when the callback queue is emptied via execution (not via
  36       * a call to chain.stop().
  37       * @event end
  38       */
  39      this.createEvent('end');
  40  };
  41  
  42  YAHOO.util.Chain.prototype = {
  43      /**
  44       * 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.
  45       * @property id
  46       * @type {number}
  47       * @private
  48       */
  49      id   : 0,
  50  
  51      /**
  52       * Begin executing the chain, or resume execution from the last paused position.
  53       * @method run
  54       * @return {Chain} the Chain instance
  55       */
  56      run : function () {
  57          // Grab the first callback in the queue
  58          var c  = this.q[0],
  59              fn;
  60  
  61          // If there is no callback in the queue or the Chain is currently
  62          // in an execution mode, return
  63          if (!c) {
  64              this.fireEvent('end');
  65              return this;
  66          } else if (this.id) {
  67              return this;
  68          }
  69  
  70          fn = c.method || c;
  71  
  72          if (typeof fn === 'function') {
  73              var o    = c.scope || {},
  74                  args = c.argument || [],
  75                  ms   = c.timeout || 0,
  76                  me   = this;
  77                  
  78              if (!(args instanceof Array)) {
  79                  args = [args];
  80              }
  81  
  82              // Execute immediately if the callback timeout is negative.
  83              if (ms < 0) {
  84                  this.id = ms;
  85                  if (c.until) {
  86                      for (;!c.until();) {
  87                          // Execute the callback from scope, with argument
  88                          fn.apply(o,args);
  89                      }
  90                  } else if (c.iterations) {
  91                      for (;c.iterations-- > 0;) {
  92                          fn.apply(o,args);
  93                      }
  94                  } else {
  95                      fn.apply(o,args);
  96                  }
  97                  this.q.shift();
  98                  this.id = 0;
  99                  return this.run();
 100              } else {
 101                  // If the until condition is set, check if we're done
 102                  if (c.until) {
 103                      if (c.until()) {
 104                          // Shift this callback from the queue and execute the next
 105                          // callback
 106                          this.q.shift();
 107                          return this.run();
 108                      }
 109                  // Otherwise if either iterations is not set or we're
 110                  // executing the last iteration, shift callback from the queue
 111                  } else if (!c.iterations || !--c.iterations) {
 112                      this.q.shift();
 113                  }
 114  
 115                  // Otherwise set to execute after the configured timeout
 116                  this.id = setTimeout(function () {
 117                      // Execute the callback from scope, with argument
 118                      fn.apply(o,args);
 119                      // Check if the Chain was not paused from inside the callback
 120                      if (me.id) {
 121                          // Indicate ready to run state
 122                          me.id = 0;
 123                          // Start the fun all over again
 124                          me.run();
 125                      }
 126                  },ms);
 127              }
 128          }
 129  
 130          return this;
 131      },
 132      
 133      /**
 134       * Add a callback to the end of the queue
 135       * @method add
 136       * @param c {Function|Object} the callback function ref or object literal
 137       * @return {Chain} the Chain instance
 138       */
 139      add  : function (c) {
 140          this.q.push(c);
 141          return this;
 142      },
 143  
 144      /**
 145       * Pause the execution of the Chain after the current execution of the
 146       * current callback completes.  If called interstitially, clears the
 147       * timeout for the pending callback. Paused Chains can be restarted with
 148       * chain.run()
 149       * @method pause
 150       * @return {Chain} the Chain instance
 151       */
 152      pause: function () {
 153          // Conditional added for Caja compatibility
 154          if (this.id > 0) {
 155              clearTimeout(this.id);
 156          }
 157          this.id = 0;
 158          return this;
 159      },
 160  
 161      /**
 162       * Stop and clear the Chain's queue after the current execution of the
 163       * current callback completes.
 164       * @method stop
 165       * @return {Chain} the Chain instance
 166       */
 167      stop : function () { 
 168          this.pause();
 169          this.q = [];
 170          return this;
 171      }
 172  };
 173  YAHOO.lang.augmentProto(YAHOO.util.Chain,YAHOO.util.EventProvider);
 174  
 175  /**
 176   * Augments the Event Utility with a <code>delegate</code> method that 
 177   * facilitates easy creation of delegated event listeners.  (Note: Using CSS 
 178   * selectors as the filtering criteria for delegated event listeners requires 
 179   * inclusion of the Selector Utility.)
 180   *
 181   * @module event-delegate
 182   * @title Event Utility Event Delegation Module
 183   * @namespace YAHOO.util
 184   * @requires event
 185   */
 186  
 187  (function () {
 188  
 189      var Event = YAHOO.util.Event,
 190          Lang = YAHOO.lang,
 191          delegates = [],
 192  
 193  
 194          getMatch = function(el, selector, container) {
 195          
 196              var returnVal;
 197          
 198              if (!el || el === container) {
 199                  returnVal = false;
 200              }
 201              else {
 202                  returnVal = YAHOO.util.Selector.test(el, selector) ? el: getMatch(el.parentNode, selector, container);
 203              }
 204          
 205              return returnVal;
 206          
 207          };
 208  
 209  
 210      Lang.augmentObject(Event, {
 211  
 212          /**
 213           * Creates a delegate function used to call event listeners specified 
 214           * via the <code>YAHOO.util.Event.delegate</code> method.
 215           *
 216           * @method _createDelegate
 217           *
 218           * @param {Function} fn        The method (event listener) to call.
 219           * @param {Function|string} filter Function or CSS selector used to 
 220           * determine for what element(s) the event listener should be called.        
 221           * @param {Object}   obj    An arbitrary object that will be 
 222           *                             passed as a parameter to the listener.
 223           * @param {Boolean|object}  overrideContext  If true, the value of the 
 224           *                             obj parameter becomes the execution context
 225           *                          of the listener. If an object, this object
 226           *                          becomes the execution context.
 227           * @return {Function} Function that will call the event listener 
 228           * specified by the <code>YAHOO.util.Event.delegate</code> method.
 229           * @private
 230           * @for Event
 231           * @static
 232           */
 233          _createDelegate: function (fn, filter, obj, overrideContext) {
 234  
 235              return function (event) {
 236  
 237                  var container = this,
 238                      target = Event.getTarget(event),
 239                      selector = filter,
 240  
 241                      //    The user might have specified the document object 
 242                      //    as the delegation container, in which case it is not 
 243                      //    nessary to scope the provided CSS selector(s) to the 
 244                      //    delegation container
 245                      bDocument = (container.nodeType === 9),
 246  
 247                      matchedEl,
 248                      context,
 249                      sID,
 250                      sIDSelector;
 251  
 252  
 253                  if (Lang.isFunction(filter)) {
 254                      matchedEl = filter(target);
 255                  }
 256                  else if (Lang.isString(filter)) {
 257  
 258                      if (!bDocument) {
 259  
 260                          sID = container.id;
 261  
 262                          if (!sID) {
 263                              sID = Event.generateId(container);
 264                          }                        
 265  
 266                          //    Scope all selectors to the container
 267                          sIDSelector = ("#" + sID + " ");
 268                          selector = (sIDSelector + filter).replace(/,/gi, ("," + sIDSelector));
 269  
 270                      }
 271  
 272  
 273                      if (YAHOO.util.Selector.test(target, selector)) {
 274                          matchedEl = target;
 275                      }
 276                      else if (YAHOO.util.Selector.test(target, ((selector.replace(/,/gi, " *,")) + " *"))) {
 277  
 278                          //    The target is a descendant of an element matching 
 279                          //    the selector, so crawl up to find the ancestor that 
 280                          //    matches the selector
 281  
 282                          matchedEl = getMatch(target, selector, container);
 283  
 284                      }
 285  
 286                  }
 287  
 288  
 289                  if (matchedEl) {
 290  
 291                      //    The default context for delegated listeners is the 
 292                      //    element that matched the filter.
 293  
 294                      context = matchedEl;
 295  
 296                      if (overrideContext) {
 297                          if (overrideContext === true) {
 298                              context = obj;
 299                          } else {
 300                              context = overrideContext;
 301                          }
 302                      }
 303  
 304                      //    Call the listener passing in the container and the 
 305                      //    element that matched the filter in case the user 
 306                      //    needs those.
 307  
 308                      return fn.call(context, event, matchedEl, container, obj);
 309  
 310                  }
 311  
 312              };
 313  
 314          },
 315  
 316  
 317          /**
 318           * Appends a delegated event listener.  Delegated event listeners 
 319           * receive three arguments by default: the DOM event, the element  
 320           * specified by the filtering function or CSS selector, and the 
 321           * container element (the element to which the event listener is 
 322           * bound).  (Note: Using the delegate method requires the event-delegate 
 323           * module.  Using CSS selectors as the filtering criteria for delegated 
 324           * event listeners requires inclusion of the Selector Utility.)
 325           *
 326           * @method delegate
 327           *
 328           * @param {String|HTMLElement|Array|NodeList} container An id, an element 
 329           *  reference, or a collection of ids and/or elements to assign the 
 330           *  listener to.
 331           * @param {String}   type     The type of event listener to append
 332           * @param {Function} fn        The method the event invokes
 333           * @param {Function|string} filter Function or CSS selector used to 
 334           * determine for what element(s) the event listener should be called. 
 335           * When a function is specified, the function should return an 
 336           * HTML element.  Using a CSS Selector requires the inclusion of the 
 337           * CSS Selector Utility.
 338           * @param {Object}   obj    An arbitrary object that will be 
 339           *                             passed as a parameter to the listener
 340           * @param {Boolean|object}  overrideContext  If true, the value of the obj parameter becomes
 341           *                             the execution context of the listener. If an
 342           *                             object, this object becomes the execution
 343           *                             context.
 344           * @return {Boolean} Returns true if the action was successful or defered,
 345           *                   false if one or more of the elements 
 346           *                   could not have the listener attached,
 347           *                   or if the operation throws an exception.
 348           * @static
 349           * @for Event
 350           */
 351          delegate: function (container, type, fn, filter, obj, overrideContext) {
 352  
 353              var sType = type,
 354                  fnMouseDelegate,
 355                  fnDelegate;
 356  
 357  
 358              if (Lang.isString(filter) && !YAHOO.util.Selector) {
 359                  YAHOO.log("Using a CSS selector to define the filtering criteria for a delegated listener requires the Selector Utility.", "error", "Event");
 360                  return false;
 361              }
 362  
 363  
 364              if (type == "mouseenter" || type == "mouseleave") {
 365  
 366                  if (!Event._createMouseDelegate) {
 367                      YAHOO.log("Delegating a " + type + " event requires the event-mouseenter module.", "error", "Event");
 368                      return false;
 369                  }
 370  
 371                  //    Look up the real event--either mouseover or mouseout
 372                  sType = Event._getType(type);
 373  
 374                  fnMouseDelegate = Event._createMouseDelegate(fn, obj, overrideContext);
 375  
 376                  fnDelegate = Event._createDelegate(function (event, matchedEl, container) {
 377  
 378                      return fnMouseDelegate.call(matchedEl, event, container);
 379  
 380                  }, filter, obj, overrideContext);
 381  
 382              }
 383              else {
 384  
 385                  fnDelegate = Event._createDelegate(fn, filter, obj, overrideContext);
 386  
 387              }
 388  
 389              delegates.push([container, sType, fn, fnDelegate]);
 390              
 391              return Event.on(container, sType, fnDelegate);
 392  
 393          },
 394  
 395  
 396          /**
 397           * Removes a delegated event listener.
 398           *
 399           * @method removeDelegate
 400           *
 401           * @param {String|HTMLElement|Array|NodeList} container An id, an element 
 402           *  reference, or a collection of ids and/or elements to remove
 403           *  the listener from.
 404           * @param {String} type The type of event to remove.
 405           * @param {Function} fn The method the event invokes.  If fn is
 406           *  undefined, then all event listeners for the type of event are 
 407           *  removed.
 408           * @return {boolean} Returns true if the unbind was successful, false 
 409           *  otherwise.
 410           * @static
 411           * @for Event
 412           */
 413          removeDelegate: function (container, type, fn) {
 414  
 415              var sType = type,
 416                  returnVal = false,
 417                  index,
 418                  cacheItem;
 419  
 420              //    Look up the real event--either mouseover or mouseout
 421              if (type == "mouseenter" || type == "mouseleave") {
 422                  sType = Event._getType(type);
 423              }
 424  
 425              index = Event._getCacheIndex(delegates, container, sType, fn);
 426  
 427              if (index >= 0) {
 428                  cacheItem = delegates[index];
 429              }
 430  
 431  
 432              if (container && cacheItem) {
 433  
 434                  returnVal = Event.removeListener(cacheItem[0], cacheItem[1], cacheItem[3]);
 435  
 436                  if (returnVal) {
 437                      delete delegates[index][2];
 438                      delete delegates[index][3];
 439                      delegates.splice(index, 1);
 440                  }        
 441          
 442              }
 443  
 444              return returnVal;
 445  
 446          }
 447          
 448      });
 449  
 450  }());
 451  
 452  
 453  /**
 454   * Augments the Event Utility with support for the mouseenter and mouseleave
 455   * events:  A mouseenter event fires the first time the mouse enters an
 456   * element; a mouseleave event first the first time the mouse leaves an
 457   * element.
 458   *
 459   * @module event-mouseenter
 460   * @title Event Utility mouseenter and mouseout Module
 461   * @namespace YAHOO.util
 462   * @requires event
 463   */
 464  
 465  (function () {
 466  
 467      var Event = YAHOO.util.Event,
 468          Lang = YAHOO.lang,
 469  
 470          addListener = Event.addListener,
 471          removeListener = Event.removeListener,
 472          getListeners = Event.getListeners,
 473  
 474          delegates = [],
 475  
 476          specialTypes = {
 477              mouseenter: "mouseover",
 478              mouseleave: "mouseout"
 479          },
 480  
 481          remove = function(el, type, fn) {
 482  
 483              var index = Event._getCacheIndex(delegates, el, type, fn),
 484                  cacheItem,
 485                  returnVal;
 486  
 487              if (index >= 0) {
 488                  cacheItem = delegates[index];
 489              }
 490  
 491              if (el && cacheItem) {
 492  
 493                  //    removeListener will translate the value of type
 494                  returnVal = removeListener.call(Event, cacheItem[0], type, cacheItem[3]);
 495  
 496                  if (returnVal) {
 497                      delete delegates[index][2];
 498                      delete delegates[index][3];
 499                      delegates.splice(index, 1);
 500                  }
 501  
 502              }
 503  
 504              return returnVal;
 505  
 506          };
 507  
 508  
 509      Lang.augmentObject(Event._specialTypes, specialTypes);
 510  
 511      Lang.augmentObject(Event, {
 512  
 513          /**
 514           * Creates a delegate function used to call mouseover and mouseleave
 515           * event listeners specified via the
 516           * <code>YAHOO.util.Event.addListener</code>
 517           * or <code>YAHOO.util.Event.on</code> method.
 518           *
 519           * @method _createMouseDelegate
 520           *
 521           * @param {Function} fn        The method (event listener) to call
 522           * @param {Object}   obj    An arbitrary object that will be
 523           *                             passed as a parameter to the listener
 524           * @param {Boolean|object}  overrideContext  If true, the value of the
 525           *                             obj parameter becomes the execution context
 526           *                          of the listener. If an object, this object
 527           *                          becomes the execution context.
 528           * @return {Function} Function that will call the event listener
 529           * specified by either the <code>YAHOO.util.Event.addListener</code>
 530           * or <code>YAHOO.util.Event.on</code> method.
 531           * @private
 532           * @static
 533           * @for Event
 534           */
 535          _createMouseDelegate: function (fn, obj, overrideContext) {
 536  
 537              return function (event, container) {
 538  
 539                  var el = this,
 540                      relatedTarget = Event.getRelatedTarget(event),
 541                      context,
 542                      args;
 543  
 544                  if (el != relatedTarget && !YAHOO.util.Dom.isAncestor(el, relatedTarget)) {
 545  
 546                      context = el;
 547  
 548                      if (overrideContext) {
 549                          if (overrideContext === true) {
 550                              context = obj;
 551                          } else {
 552                              context = overrideContext;
 553                          }
 554                      }
 555  
 556                      // The default args passed back to a mouseenter or
 557                      // mouseleave listener are: the event, and any object
 558                      // the user passed when subscribing
 559  
 560                      args = [event, obj];
 561  
 562                      // Add the element and delegation container as arguments
 563                      // when delegating mouseenter and mouseleave
 564  
 565                      if (container) {
 566                          args.splice(1, 0, el, container);
 567                      }
 568  
 569                      return fn.apply(context, args);
 570  
 571                  }
 572  
 573              };
 574  
 575          },
 576  
 577          addListener: function (el, type, fn, obj, overrideContext) {
 578  
 579              var fnDelegate,
 580                  returnVal;
 581  
 582              if (specialTypes[type]) {
 583  
 584                  fnDelegate = Event._createMouseDelegate(fn, obj, overrideContext);
 585  
 586                  fnDelegate.mouseDelegate = true;
 587  
 588                  delegates.push([el, type, fn, fnDelegate]);
 589  
 590                  //    addListener will translate the value of type
 591                  returnVal = addListener.call(Event, el, type, fnDelegate);
 592  
 593              }
 594              else {
 595                  returnVal = addListener.apply(Event, arguments);
 596              }
 597  
 598              return returnVal;
 599  
 600          },
 601  
 602          removeListener: function (el, type, fn) {
 603  
 604              var returnVal;
 605  
 606              if (specialTypes[type]) {
 607                  returnVal = remove.apply(Event, arguments);
 608              }
 609              else {
 610                  returnVal = removeListener.apply(Event, arguments);
 611              }
 612  
 613              return returnVal;
 614  
 615          },
 616  
 617          getListeners: function (el, type) {
 618  
 619              //    If the user specified the type as mouseover or mouseout,
 620              //    need to filter out those used by mouseenter and mouseleave.
 621              //    If the user specified the type as mouseenter or mouseleave,
 622              //    need to filter out the true mouseover and mouseout listeners.
 623  
 624              var listeners = [],
 625                  elListeners,
 626                  bMouseOverOrOut = (type === "mouseover" || type === "mouseout"),
 627                  bMouseDelegate,
 628                  i,
 629                  l;
 630  
 631              if (type && (bMouseOverOrOut || specialTypes[type])) {
 632  
 633                  elListeners = getListeners.call(Event, el, this._getType(type));
 634  
 635                  if (elListeners) {
 636  
 637                      for (i=elListeners.length-1; i>-1; i--) {
 638  
 639                          l = elListeners[i];
 640                          bMouseDelegate = l.fn.mouseDelegate;
 641  
 642                          if ((specialTypes[type] && bMouseDelegate) || (bMouseOverOrOut && !bMouseDelegate)) {
 643                              listeners.push(l);
 644                          }
 645  
 646                      }
 647  
 648                  }
 649  
 650              }
 651              else {
 652                  listeners = getListeners.apply(Event, arguments);
 653              }
 654  
 655              return (listeners && listeners.length) ? listeners : null;
 656  
 657          }
 658  
 659      }, true);
 660  
 661      Event.on = Event.addListener;
 662  
 663  }());
 664  YAHOO.register("event-mouseenter", YAHOO.util.Event, {version: "2.9.0", build: "2800"});
 665  
 666  var Y = YAHOO,
 667      Y_DOM = YAHOO.util.Dom,
 668      EMPTY_ARRAY = [],
 669      Y_UA = Y.env.ua,
 670      Y_Lang = Y.lang,
 671      Y_DOC = document,
 672      Y_DOCUMENT_ELEMENT = Y_DOC.documentElement,
 673  
 674      Y_DOM_inDoc = Y_DOM.inDocument,
 675      Y_mix = Y_Lang.augmentObject,
 676      Y_guid = Y_DOM.generateId,
 677  
 678      Y_getDoc = function(element) {
 679          var doc = Y_DOC;
 680          if (element) {
 681              doc = (element.nodeType === 9) ? element : // element === document
 682                  element.ownerDocument || // element === DOM node
 683                  element.document || // element === window
 684                  Y_DOC; // default
 685          }
 686  
 687          return doc;
 688      },
 689  
 690      Y_Array = function(o, startIdx) {
 691          var l, a, start = startIdx || 0;
 692  
 693          // IE errors when trying to slice HTMLElement collections
 694          try {
 695              return Array.prototype.slice.call(o, start);
 696          } catch (e) {
 697              a = [];
 698              l = o.length;
 699              for (; start < l; start++) {
 700                  a.push(o[start]);
 701              }
 702              return a;
 703          }
 704      },
 705  
 706      Y_DOM_allById = function(id, root) {
 707          root = root || Y_DOC;
 708          var nodes = [],
 709              ret = [],
 710              i,
 711              node;
 712  
 713          if (root.querySelectorAll) {
 714              ret = root.querySelectorAll('[id="' + id + '"]');
 715          } else if (root.all) {
 716              nodes = root.all(id);
 717  
 718              if (nodes) {
 719                  // root.all may return HTMLElement or HTMLCollection.
 720                  // some elements are also HTMLCollection (FORM, SELECT).
 721                  if (nodes.nodeName) {
 722                      if (nodes.id === id) { // avoid false positive on name
 723                          ret.push(nodes);
 724                          nodes = EMPTY_ARRAY; // done, no need to filter
 725                      } else { //  prep for filtering
 726                          nodes = [nodes];
 727                      }
 728                  }
 729  
 730                  if (nodes.length) {
 731                      // filter out matches on node.name
 732                      // and element.id as reference to element with id === 'id'
 733                      for (i = 0; node = nodes[i++];) {
 734                          if (node.id === id  ||
 735                                  (node.attributes && node.attributes.id &&
 736                                  node.attributes.id.value === id)) {
 737                              ret.push(node);
 738                          }
 739                      }
 740                  }
 741              }
 742          } else {
 743              ret = [Y_getDoc(root).getElementById(id)];
 744          }
 745  
 746          return ret;
 747      };
 748  
 749  /**
 750   * The selector-native module provides support for native querySelector
 751   * @module dom
 752   * @submodule selector-native
 753   * @for Selector
 754   */
 755  
 756  /**
 757   * Provides support for using CSS selectors to query the DOM
 758   * @class Selector
 759   * @static
 760   * @for Selector
 761   */
 762  
 763  var COMPARE_DOCUMENT_POSITION = 'compareDocumentPosition',
 764      OWNER_DOCUMENT = 'ownerDocument',
 765  
 766  Selector = {
 767      _foundCache: [],
 768  
 769      useNative: true,
 770  
 771      _compare: ('sourceIndex' in Y_DOCUMENT_ELEMENT) ?
 772          function(nodeA, nodeB) {
 773              var a = nodeA.sourceIndex,
 774                  b = nodeB.sourceIndex;
 775  
 776              if (a === b) {
 777                  return 0;
 778              } else if (a > b) {
 779                  return 1;
 780              }
 781  
 782              return -1;
 783  
 784          } : (Y_DOCUMENT_ELEMENT[COMPARE_DOCUMENT_POSITION] ?
 785          function(nodeA, nodeB) {
 786              if (nodeA[COMPARE_DOCUMENT_POSITION](nodeB) & 4) {
 787                  return -1;
 788              } else {
 789                  return 1;
 790              }
 791          } :
 792          function(nodeA, nodeB) {
 793              var rangeA, rangeB, compare;
 794              if (nodeA && nodeB) {
 795                  rangeA = nodeA[OWNER_DOCUMENT].createRange();
 796                  rangeA.setStart(nodeA, 0);
 797                  rangeB = nodeB[OWNER_DOCUMENT].createRange();
 798                  rangeB.setStart(nodeB, 0);
 799                  compare = rangeA.compareBoundaryPoints(1, rangeB); // 1 === Range.START_TO_END
 800              }
 801  
 802              return compare;
 803  
 804      }),
 805  
 806      _sort: function(nodes) {
 807          if (nodes) {
 808              nodes = Y_Array(nodes, 0, true);
 809              if (nodes.sort) {
 810                  nodes.sort(Selector._compare);
 811              }
 812          }
 813  
 814          return nodes;
 815      },
 816  
 817      _deDupe: function(nodes) {
 818          var ret = [],
 819              i, node;
 820  
 821          for (i = 0; (node = nodes[i++]);) {
 822              if (!node._found) {
 823                  ret[ret.length] = node;
 824                  node._found = true;
 825              }
 826          }
 827  
 828          for (i = 0; (node = ret[i++]);) {
 829              node._found = null;
 830              node.removeAttribute('_found');
 831          }
 832  
 833          return ret;
 834      },
 835  
 836      /**
 837       * Retrieves a set of nodes based on a given CSS selector.
 838       * @method query
 839       *
 840       * @param {string} selector The CSS Selector to test the node against.
 841       * @param {HTMLElement} root optional An HTMLElement to start the query from. Defaults to Y.config.doc
 842       * @param {Boolean} firstOnly optional Whether or not to return only the first match.
 843       * @return {Array} An array of nodes that match the given selector.
 844       * @static
 845       */
 846      query: function(selector, root, firstOnly, skipNative) {
 847          if (typeof root == 'string') {
 848              root = Y_DOM.get(root);
 849              if (!root) {
 850                  return (firstOnly) ? null : [];
 851              }
 852          } else {
 853              root = root || Y_DOC;
 854          }
 855  
 856          var ret = [],
 857              useNative = (Selector.useNative && Y_DOC.querySelector && !skipNative),
 858              queries = [[selector, root]],
 859              query,
 860              result,
 861              i,
 862              fn = (useNative) ? Selector._nativeQuery : Selector._bruteQuery;
 863  
 864          if (selector && fn) {
 865              // split group into seperate queries
 866              if (!skipNative && // already done if skipping
 867                      (!useNative || root.tagName)) { // split native when element scoping is needed
 868                  queries = Selector._splitQueries(selector, root);
 869              }
 870  
 871              for (i = 0; (query = queries[i++]);) {
 872                  result = fn(query[0], query[1], firstOnly);
 873                  if (!firstOnly) { // coerce DOM Collection to Array
 874                      result = Y_Array(result, 0, true);
 875                  }
 876                  if (result) {
 877                      ret = ret.concat(result);
 878                  }
 879              }
 880  
 881              if (queries.length > 1) { // remove dupes and sort by doc order
 882                  ret = Selector._sort(Selector._deDupe(ret));
 883              }
 884          }
 885  
 886          Y.log('query: ' + selector + ' returning: ' + ret.length, 'info', 'Selector');
 887          return (firstOnly) ? (ret[0] || null) : ret;
 888  
 889      },
 890  
 891      // allows element scoped queries to begin with combinator
 892      // e.g. query('> p', document.body) === query('body > p')
 893      _splitQueries: function(selector, node) {
 894          var groups = selector.split(','),
 895              queries = [],
 896              prefix = '',
 897              i, len;
 898  
 899          if (node) {
 900              // enforce for element scoping
 901              if (node.tagName) {
 902                  node.id = node.id || Y_guid();
 903                  prefix = '[id="' + node.id + '"] ';
 904              }
 905  
 906              for (i = 0, len = groups.length; i < len; ++i) {
 907                  selector =  prefix + groups[i];
 908                  queries.push([selector, node]);
 909              }
 910          }
 911  
 912          return queries;
 913      },
 914  
 915      _nativeQuery: function(selector, root, one) {
 916          if (Y_UA.webkit && selector.indexOf(':checked') > -1 &&
 917                  (Selector.pseudos && Selector.pseudos.checked)) { // webkit (chrome, safari) fails to find "selected"
 918              return Selector.query(selector, root, one, true); // redo with skipNative true to try brute query
 919          }
 920          try {
 921              //Y.log('trying native query with: ' + selector, 'info', 'selector-native');
 922              return root['querySelector' + (one ? '' : 'All')](selector);
 923          } catch(e) { // fallback to brute if available
 924              //Y.log('native query error; reverting to brute query with: ' + selector, 'info', 'selector-native');
 925              return Selector.query(selector, root, one, true); // redo with skipNative true
 926          }
 927      },
 928  
 929      filter: function(nodes, selector) {
 930          var ret = [],
 931              i, node;
 932  
 933          if (nodes && selector) {
 934              for (i = 0; (node = nodes[i++]);) {
 935                  if (Selector.test(node, selector)) {
 936                      ret[ret.length] = node;
 937                  }
 938              }
 939          } else {
 940              Y.log('invalid filter input (nodes: ' + nodes +
 941                      ', selector: ' + selector + ')', 'warn', 'Selector');
 942          }
 943  
 944          return ret;
 945      },
 946  
 947      test: function(node, selector, root) {
 948          var ret = false,
 949              groups = selector.split(','),
 950              useFrag = false,
 951              parent,
 952              item,
 953              items,
 954              frag,
 955              i, j, group;
 956  
 957          if (node && node.tagName) { // only test HTMLElements
 958  
 959              // we need a root if off-doc
 960              if (!root && !Y_DOM_inDoc(node)) {
 961                  parent = node.parentNode;
 962                  if (parent) {
 963                      root = parent;
 964                  } else { // only use frag when no parent to query
 965                      frag = node[OWNER_DOCUMENT].createDocumentFragment();
 966                      frag.appendChild(node);
 967                      root = frag;
 968                      useFrag = true;
 969                  }
 970              }
 971              root = root || node[OWNER_DOCUMENT];
 972  
 973              if (!node.id) {
 974                  node.id = Y_guid();
 975              }
 976              for (i = 0; (group = groups[i++]);) { // TODO: off-dom test
 977                  group += '[id="' + node.id + '"]';
 978                  items = Selector.query(group, root);
 979  
 980                  for (j = 0; item = items[j++];) {
 981                      if (item === node) {
 982                          ret = true;
 983                          break;
 984                      }
 985                  }
 986                  if (ret) {
 987                      break;
 988                  }
 989              }
 990  
 991              if (useFrag) { // cleanup
 992                  frag.removeChild(node);
 993              }
 994          }
 995  
 996          return ret;
 997      }
 998  
 999  };
1000  
1001  YAHOO.util.Selector = Selector;
1002  /**
1003   * The selector module provides helper methods allowing CSS2 Selectors to be used with DOM elements.
1004   * @module dom
1005   * @submodule selector-css2
1006   * @for Selector
1007   */
1008  
1009  /**
1010   * Provides helper methods for collecting and filtering DOM elements.
1011   */
1012  
1013  var PARENT_NODE = 'parentNode',
1014      TAG_NAME = 'tagName',
1015      ATTRIBUTES = 'attributes',
1016      COMBINATOR = 'combinator',
1017      PSEUDOS = 'pseudos',
1018  
1019      SelectorCSS2 = {
1020          _reRegExpTokens: /([\^\$\?\[\]\*\+\-\.\(\)\|\\])/, // TODO: move?
1021          SORT_RESULTS: true,
1022          _children: function(node, tag) {
1023              var ret = node.children,
1024                  i,
1025                  children = [],
1026                  childNodes,
1027                  child;
1028  
1029              if (node.children && tag && node.children.tags) {
1030                  children = node.children.tags(tag);
1031              } else if ((!ret && node[TAG_NAME]) || (ret && tag)) { // only HTMLElements have children
1032                  childNodes = ret || node.childNodes;
1033                  ret = [];
1034                  for (i = 0; (child = childNodes[i++]);) {
1035                      if (child.tagName) {
1036                          if (!tag || tag === child.tagName) {
1037                              ret.push(child);
1038                          }
1039                      }
1040                  }
1041              }
1042  
1043              return ret || [];
1044          },
1045  
1046          _re: {
1047              //attr: /(\[.*\])/g,
1048              attr: /(\[[^\]]*\])/g,
1049              //esc: /\\[:\[][\w\d\]]*/gi,
1050              esc: /\\[:\[\]\(\)#\.\'\>+~"]/gi,
1051              //pseudos: /:([\-\w]+(?:\(?:['"]?(.+)['"]?\))*)/i
1052              pseudos: /(\([^\)]*\))/g
1053          },
1054  
1055          /**
1056           * Mapping of shorthand tokens to corresponding attribute selector
1057           * @property shorthand
1058           * @type object
1059           */
1060          shorthand: {
1061              //'\\#([^\\s\\\\(\\[:]*)': '[id=$1]',
1062              '\\#(-?[_a-z]+[-\\w\\uE000]*)': '[id=$1]',
1063              //'\\#([^\\s\\\.:\\[\\]]*)': '[id=$1]',
1064              //'\\.([^\\s\\\\(\\[:]*)': '[className=$1]'
1065              '\\.(-?[_a-z]+[-\\w\\uE000]*)': '[className~=$1]'
1066          },
1067  
1068          /**
1069           * List of operators and corresponding boolean functions.
1070           * These functions are passed the attribute and the current node's value of the attribute.
1071           * @property operators
1072           * @type object
1073           */
1074          operators: {
1075              '': function(node, attr) { return !!node.getAttribute(attr); }, // Just test for existence of attribute
1076              //'': '.+',
1077              //'=': '^{val}$', // equality
1078              '~=': '(?:^|\\s+){val}(?:\\s+|$)', // space-delimited
1079              '|=': '^{val}(?:-|$)' // optional hyphen-delimited
1080          },
1081  
1082          pseudos: {
1083             'first-child': function(node) {
1084                  return Selector._children(node[PARENT_NODE])[0] === node;
1085              }
1086          },
1087  
1088          _bruteQuery: function(selector, root, firstOnly) {
1089              var ret = [],
1090                  nodes = [],
1091                  tokens = Selector._tokenize(selector),
1092                  token = tokens[tokens.length - 1],
1093                  rootDoc = Y_getDoc(root),
1094                  child,
1095                  id,
1096                  className,
1097                  tagName;
1098  
1099  
1100              // if we have an initial ID, set to root when in document
1101              /*
1102              if (tokens[0] && rootDoc === root &&
1103                      (id = tokens[0].id) &&
1104                      rootDoc.getElementById(id)) {
1105                  root = rootDoc.getElementById(id);
1106              }
1107              */
1108  
1109              if (token) {
1110                  // prefilter nodes
1111                  id = token.id;
1112                  className = token.className;
1113                  tagName = token.tagName || '*';
1114  
1115                  if (root.getElementsByTagName) { // non-IE lacks DOM api on doc frags
1116                      // try ID first, unless no root.all && root not in document
1117                      // (root.all works off document, but not getElementById)
1118                      // TODO: move to allById?
1119                      if (id && (root.all || (root.nodeType === 9 || Y_DOM_inDoc(root)))) {
1120                          nodes = Y_DOM_allById(id, root);
1121                      // try className
1122                      } else if (className) {
1123                          nodes = root.getElementsByClassName(className);
1124                      } else { // default to tagName
1125                          nodes = root.getElementsByTagName(tagName);
1126                      }
1127  
1128                  } else { // brute getElementsByTagName('*')
1129                      child = root.firstChild;
1130                      while (child) {
1131                          if (child.tagName) { // only collect HTMLElements
1132                              nodes.push(child);
1133                          }
1134                          child = child.nextSilbing || child.firstChild;
1135                      }
1136                  }
1137                  if (nodes.length) {
1138                      ret = Selector._filterNodes(nodes, tokens, firstOnly);
1139                  }
1140              }
1141  
1142              return ret;
1143          },
1144  
1145          _filterNodes: function(nodes, tokens, firstOnly) {
1146              var i = 0,
1147                  j,
1148                  len = tokens.length,
1149                  n = len - 1,
1150                  result = [],
1151                  node = nodes[0],
1152                  tmpNode = node,
1153                  getters = Selector.getters,
1154                  operator,
1155                  combinator,
1156                  token,
1157                  path,
1158                  pass,
1159                  //FUNCTION = 'function',
1160                  value,
1161                  tests,
1162                  test;
1163  
1164              //do {
1165              for (i = 0; (tmpNode = node = nodes[i++]);) {
1166                  n = len - 1;
1167                  path = null;
1168  
1169                  testLoop:
1170                  while (tmpNode && tmpNode.tagName) {
1171                      token = tokens[n];
1172                      tests = token.tests;
1173                      j = tests.length;
1174                      if (j && !pass) {
1175                          while ((test = tests[--j])) {
1176                              operator = test[1];
1177                              if (getters[test[0]]) {
1178                                  value = getters[test[0]](tmpNode, test[0]);
1179                              } else {
1180                                  value = tmpNode[test[0]];
1181                                  // use getAttribute for non-standard attributes
1182                                  if (value === undefined && tmpNode.getAttribute) {
1183                                      value = tmpNode.getAttribute(test[0]);
1184                                  }
1185                              }
1186  
1187                              if ((operator === '=' && value !== test[2]) ||  // fast path for equality
1188                                  (typeof operator !== 'string' && // protect against String.test monkey-patch (Moo)
1189                                  operator.test && !operator.test(value)) ||  // regex test
1190                                  (!operator.test && // protect against RegExp as function (webkit)
1191                                          typeof operator === 'function' && !operator(tmpNode, test[0], test[2]))) { // function test
1192  
1193                                  // skip non element nodes or non-matching tags
1194                                  if ((tmpNode = tmpNode[path])) {
1195                                      while (tmpNode &&
1196                                          (!tmpNode.tagName ||
1197                                              (token.tagName && token.tagName !== tmpNode.tagName))
1198                                      ) {
1199                                          tmpNode = tmpNode[path];
1200                                      }
1201                                  }
1202                                  continue testLoop;
1203                              }
1204                          }
1205                      }
1206  
1207                      n--; // move to next token
1208                      // now that we've passed the test, move up the tree by combinator
1209                      if (!pass && (combinator = token.combinator)) {
1210                          path = combinator.axis;
1211                          tmpNode = tmpNode[path];
1212  
1213                          // skip non element nodes
1214                          while (tmpNode && !tmpNode.tagName) {
1215                              tmpNode = tmpNode[path];
1216                          }
1217  
1218                          if (combinator.direct) { // one pass only
1219                              path = null;
1220                          }
1221  
1222                      } else { // success if we made it this far
1223                          result.push(node);
1224                          if (firstOnly) {
1225                              return result;
1226                          }
1227                          break;
1228                      }
1229                  }
1230              }// while (tmpNode = node = nodes[++i]);
1231              node = tmpNode = null;
1232              return result;
1233          },
1234  
1235          combinators: {
1236              ' ': {
1237                  axis: 'parentNode'
1238              },
1239  
1240              '>': {
1241                  axis: 'parentNode',
1242                  direct: true
1243              },
1244  
1245  
1246              '+': {
1247                  axis: 'previousSibling',
1248                  direct: true
1249              }
1250          },
1251  
1252          _parsers: [
1253              {
1254                  name: ATTRIBUTES,
1255                  //re: /^\[(-?[a-z]+[\w\-]*)+([~\|\^\$\*!=]=?)?['"]?([^\]]*?)['"]?\]/i,
1256                  re: /^\uE003(-?[a-z]+[\w\-]*)+([~\|\^\$\*!=]=?)?['"]?([^\uE004'"]*)['"]?\uE004/i,
1257                  fn: function(match, token) {
1258                      var operator = match[2] || '',
1259                          operators = Selector.operators,
1260                          escVal = (match[3]) ? match[3].replace(/\\/g, '') : '',
1261                          test;
1262  
1263                      // add prefiltering for ID and CLASS
1264                      if ((match[1] === 'id' && operator === '=') ||
1265                              (match[1] === 'className' &&
1266                              Y_DOCUMENT_ELEMENT.getElementsByClassName &&
1267                              (operator === '~=' || operator === '='))) {
1268                          token.prefilter = match[1];
1269  
1270  
1271                          match[3] = escVal;
1272  
1273                          // escape all but ID for prefilter, which may run through QSA (via Dom.allById)
1274                          token[match[1]] = (match[1] === 'id') ? match[3] : escVal;
1275  
1276                      }
1277  
1278                      // add tests
1279                      if (operator in operators) {
1280                          test = operators[operator];
1281                          if (typeof test === 'string') {
1282                              match[3] = escVal.replace(Selector._reRegExpTokens, '\\$1');
1283                              test = new RegExp(test.replace('{val}', match[3]));
1284                          }
1285                          match[2] = test;
1286                      }
1287                      if (!token.last || token.prefilter !== match[1]) {
1288                          return match.slice(1);
1289                      }
1290                  }
1291  
1292              },
1293              {
1294                  name: TAG_NAME,
1295                  re: /^((?:-?[_a-z]+[\w-]*)|\*)/i,
1296                  fn: function(match, token) {
1297                      var tag = match[1].toUpperCase();
1298                      token.tagName = tag;
1299  
1300                      if (tag !== '*' && (!token.last || token.prefilter)) {
1301                          return [TAG_NAME, '=', tag];
1302                      }
1303                      if (!token.prefilter) {
1304                          token.prefilter = 'tagName';
1305                      }
1306                  }
1307              },
1308              {
1309                  name: COMBINATOR,
1310                  re: /^\s*([>+~]|\s)\s*/,
1311                  fn: function(match, token) {
1312                  }
1313              },
1314              {
1315                  name: PSEUDOS,
1316                  re: /^:([\-\w]+)(?:\uE005['"]?([^\uE005]*)['"]?\uE006)*/i,
1317                  fn: function(match, token) {
1318                      var test = Selector[PSEUDOS][match[1]];
1319                      if (test) { // reorder match array and unescape special chars for tests
1320                          if (match[2]) {
1321                              match[2] = match[2].replace(/\\/g, '');
1322                          }
1323                          return [match[2], test];
1324                      } else { // selector token not supported (possibly missing CSS3 module)
1325                          return false;
1326                      }
1327                  }
1328              }
1329              ],
1330  
1331          _getToken: function(token) {
1332              return {
1333                  tagName: null,
1334                  id: null,
1335                  className: null,
1336                  attributes: {},
1337                  combinator: null,
1338                  tests: []
1339              };
1340          },
1341  
1342          /**
1343              Break selector into token units per simple selector.
1344              Combinator is attached to the previous token.
1345           */
1346          _tokenize: function(selector) {
1347              selector = selector || '';
1348              selector = Selector._replaceShorthand(Y_Lang.trim(selector));
1349              var token = Selector._getToken(),     // one token per simple selector (left selector holds combinator)
1350                  query = selector, // original query for debug report
1351                  tokens = [],    // array of tokens
1352                  found = false,  // whether or not any matches were found this pass
1353                  match,         // the regex match
1354                  test,
1355                  i, parser;
1356  
1357              /*
1358                  Search for selector patterns, store, and strip them from the selector string
1359                  until no patterns match (invalid selector) or we run out of chars.
1360  
1361                  Multiple attributes and pseudos are allowed, in any order.
1362                  for example:
1363                      'form:first-child[type=button]:not(button)[lang|=en]'
1364              */
1365  
1366              outer:
1367              do {
1368                  found = false; // reset after full pass
1369  
1370                  for (i = 0; (parser = Selector._parsers[i++]);) {
1371                      if ( (match = parser.re.exec(selector)) ) { // note assignment
1372                          if (parser.name !== COMBINATOR ) {
1373                              token.selector = selector;
1374                          }
1375                          selector = selector.replace(match[0], ''); // strip current match from selector
1376                          if (!selector.length) {
1377                              token.last = true;
1378                          }
1379  
1380                          if (Selector._attrFilters[match[1]]) { // convert class to className, etc.
1381                              match[1] = Selector._attrFilters[match[1]];
1382                          }
1383  
1384                          test = parser.fn(match, token);
1385                          if (test === false) { // selector not supported
1386                              found = false;
1387                              break outer;
1388                          } else if (test) {
1389                              token.tests.push(test);
1390                          }
1391  
1392                          if (!selector.length || parser.name === COMBINATOR) {
1393                              tokens.push(token);
1394                              token = Selector._getToken(token);
1395                              if (parser.name === COMBINATOR) {
1396                                  token.combinator = Selector.combinators[match[1]];
1397                              }
1398                          }
1399                          found = true;
1400  
1401  
1402                      }
1403                  }
1404              } while (found && selector.length);
1405  
1406              if (!found || selector.length) { // not fully parsed
1407                  Y.log('query: ' + query + ' contains unsupported token in: ' + selector, 'warn', 'Selector');
1408                  tokens = [];
1409              }
1410              return tokens;
1411          },
1412  
1413          _replaceShorthand: function(selector) {
1414              var shorthand = Selector.shorthand,
1415                  esc = selector.match(Selector._re.esc), // pull escaped colon, brackets, etc.
1416                  attrs,
1417                  pseudos,
1418                  re, i, len;
1419  
1420              if (esc) {
1421                  selector = selector.replace(Selector._re.esc, '\uE000');
1422              }
1423  
1424              attrs = selector.match(Selector._re.attr);
1425              pseudos = selector.match(Selector._re.pseudos);
1426  
1427              if (attrs) {
1428                  selector = selector.replace(Selector._re.attr, '\uE001');
1429              }
1430  
1431              if (pseudos) {
1432                  selector = selector.replace(Selector._re.pseudos, '\uE002');
1433              }
1434  
1435  
1436              for (re in shorthand) {
1437                  if (shorthand.hasOwnProperty(re)) {
1438                      selector = selector.replace(new RegExp(re, 'gi'), shorthand[re]);
1439                  }
1440              }
1441  
1442              if (attrs) {
1443                  for (i = 0, len = attrs.length; i < len; ++i) {
1444                      selector = selector.replace(/\uE001/, attrs[i]);
1445                  }
1446              }
1447  
1448              if (pseudos) {
1449                  for (i = 0, len = pseudos.length; i < len; ++i) {
1450                      selector = selector.replace(/\uE002/, pseudos[i]);
1451                  }
1452              }
1453  
1454              selector = selector.replace(/\[/g, '\uE003');
1455              selector = selector.replace(/\]/g, '\uE004');
1456  
1457              selector = selector.replace(/\(/g, '\uE005');
1458              selector = selector.replace(/\)/g, '\uE006');
1459  
1460              if (esc) {
1461                  for (i = 0, len = esc.length; i < len; ++i) {
1462                      selector = selector.replace('\uE000', esc[i]);
1463                  }
1464              }
1465  
1466              return selector;
1467          },
1468  
1469          _attrFilters: {
1470              'class': 'className',
1471              'for': 'htmlFor'
1472          },
1473  
1474          getters: {
1475              href: function(node, attr) {
1476                  return Y_DOM.getAttribute(node, attr);
1477              }
1478          }
1479      };
1480  
1481  Y_mix(Selector, SelectorCSS2, true);
1482  Selector.getters.src = Selector.getters.rel = Selector.getters.href;
1483  
1484  // IE wants class with native queries
1485  if (Selector.useNative && Y_DOC.querySelector) {
1486      Selector.shorthand['\\.([^\\s\\\\(\\[:]*)'] = '[class~=$1]';
1487  }
1488  
1489  /**
1490   * The selector css3 module provides support for css3 selectors.
1491   * @module dom
1492   * @submodule selector-css3
1493   * @for Selector
1494   */
1495  
1496  /*
1497      an+b = get every _a_th node starting at the _b_th
1498      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
1499      1n+b =  get every element starting from b ("1" may may be omitted, e.g. "1n+0" or "n+0" or "n")
1500      an+0 = get every _a_th element, "0" may be omitted
1501  */
1502  
1503  Selector._reNth = /^(?:([\-]?\d*)(n){1}|(odd|even)$)*([\-+]?\d*)$/;
1504  
1505  Selector._getNth = function(node, expr, tag, reverse) {
1506      Selector._reNth.test(expr);
1507      var a = parseInt(RegExp.$1, 10), // include every _a_ elements (zero means no repeat, just first _a_)
1508          n = RegExp.$2, // "n"
1509          oddeven = RegExp.$3, // "odd" or "even"
1510          b = parseInt(RegExp.$4, 10) || 0, // start scan from element _b_
1511          result = [],
1512          siblings = Selector._children(node.parentNode, tag),
1513          op;
1514  
1515      if (oddeven) {
1516          a = 2; // always every other
1517          op = '+';
1518          n = 'n';
1519          b = (oddeven === 'odd') ? 1 : 0;
1520      } else if ( isNaN(a) ) {
1521          a = (n) ? 1 : 0; // start from the first or no repeat
1522      }
1523  
1524      if (a === 0) { // just the first
1525          if (reverse) {
1526              b = siblings.length - b + 1;
1527          }
1528  
1529          if (siblings[b - 1] === node) {
1530              return true;
1531          } else {
1532              return false;
1533          }
1534  
1535      } else if (a < 0) {
1536          reverse = !!reverse;
1537          a = Math.abs(a);
1538      }
1539  
1540      if (!reverse) {
1541          for (var i = b - 1, len = siblings.length; i < len; i += a) {
1542              if ( i >= 0 && siblings[i] === node ) {
1543                  return true;
1544              }
1545          }
1546      } else {
1547          for (var i = siblings.length - b, len = siblings.length; i >= 0; i -= a) {
1548              if ( i < len && siblings[i] === node ) {
1549                  return true;
1550              }
1551          }
1552      }
1553      return false;
1554  };
1555  
1556  Y_mix(Selector.pseudos, {
1557      'root': function(node) {
1558          return node === node.ownerDocument.documentElement;
1559      },
1560  
1561      'nth-child': function(node, expr) {
1562          return Selector._getNth(node, expr);
1563      },
1564  
1565      'nth-last-child': function(node, expr) {
1566          return Selector._getNth(node, expr, null, true);
1567      },
1568  
1569      'nth-of-type': function(node, expr) {
1570          return Selector._getNth(node, expr, node.tagName);
1571      },
1572  
1573      'nth-last-of-type': function(node, expr) {
1574          return Selector._getNth(node, expr, node.tagName, true);
1575      },
1576  
1577      'last-child': function(node) {
1578          var children = Selector._children(node.parentNode);
1579          return children[children.length - 1] === node;
1580      },
1581  
1582      'first-of-type': function(node) {
1583          return Selector._children(node.parentNode, node.tagName)[0] === node;
1584      },
1585  
1586      'last-of-type': function(node) {
1587          var children = Selector._children(node.parentNode, node.tagName);
1588          return children[children.length - 1] === node;
1589      },
1590  
1591      'only-child': function(node) {
1592          var children = Selector._children(node.parentNode);
1593          return children.length === 1 && children[0] === node;
1594      },
1595  
1596      'only-of-type': function(node) {
1597          var children = Selector._children(node.parentNode, node.tagName);
1598          return children.length === 1 && children[0] === node;
1599      },
1600  
1601      'empty': function(node) {
1602          return node.childNodes.length === 0;
1603      },
1604  
1605      'not': function(node, expr) {
1606          return !Selector.test(node, expr);
1607      },
1608  
1609      'contains': function(node, expr) {
1610          var text = node.innerText || node.textContent || '';
1611          return text.indexOf(expr) > -1;
1612      },
1613  
1614      'checked': function(node) {
1615          return (node.checked === true || node.selected === true);
1616      },
1617  
1618      enabled: function(node) {
1619          return (node.disabled !== undefined && !node.disabled);
1620      },
1621  
1622      disabled: function(node) {
1623          return (node.disabled);
1624      }
1625  });
1626  
1627  Y_mix(Selector.operators, {
1628      '^=': '^{val}', // Match starts with value
1629      '!=': function(node, attr, val) { return node[attr] !== val; }, // Match starts with value
1630      '$=': '{val}$', // Match ends with value
1631      '*=': '{val}' // Match contains value as substring
1632  });
1633  
1634  Selector.combinators['~'] = {
1635      axis: 'previousSibling'
1636  };
1637  YAHOO.register("selector", YAHOO.util.Selector, {version: "2.9.0", build: "2800"});
1638  
1639  
1640  
1641  /****************************************************************************/
1642  /****************************************************************************/
1643  /****************************************************************************/
1644  
1645  var Dom = YAHOO.util.Dom;
1646  
1647  /**
1648   * The ColumnSet class defines and manages a DataTable's Columns,
1649   * including nested hierarchies and access to individual Column instances.
1650   *
1651   * @namespace YAHOO.widget
1652   * @class ColumnSet
1653   * @uses YAHOO.util.EventProvider
1654   * @constructor
1655   * @param aDefinitions {Object[]} Array of object literals that define cells in
1656   * the THEAD.
1657   */
1658  YAHOO.widget.ColumnSet = function(aDefinitions) {
1659      this._sId = Dom.generateId(null, "yui-cs"); // "yui-cs" + YAHOO.widget.ColumnSet._nCount;
1660  
1661      // First clone the defs
1662      aDefinitions = YAHOO.widget.DataTable._cloneObject(aDefinitions);
1663      this._init(aDefinitions);
1664  
1665      YAHOO.widget.ColumnSet._nCount++;
1666      YAHOO.log("ColumnSet initialized", "info", this.toString());
1667  };
1668  
1669  /////////////////////////////////////////////////////////////////////////////
1670  //
1671  // Private member variables
1672  //
1673  /////////////////////////////////////////////////////////////////////////////
1674  
1675  /**
1676   * Internal class variable to index multiple ColumnSet instances.
1677   *
1678   * @property ColumnSet._nCount
1679   * @type Number
1680   * @private
1681   * @static
1682   */
1683  YAHOO.widget.ColumnSet._nCount = 0;
1684  
1685  YAHOO.widget.ColumnSet.prototype = {
1686      /**
1687       * Unique instance name.
1688       *
1689       * @property _sId
1690       * @type String
1691       * @private
1692       */
1693      _sId : null,
1694  
1695      /**
1696       * Array of object literal Column definitions passed to the constructor.
1697       *
1698       * @property _aDefinitions
1699       * @type Object[]
1700       * @private
1701       */
1702      _aDefinitions : null,
1703  
1704      /////////////////////////////////////////////////////////////////////////////
1705      //
1706      // Public member variables
1707      //
1708      /////////////////////////////////////////////////////////////////////////////
1709  
1710      /**
1711       * Top-down tree representation of Column hierarchy.
1712       *
1713       * @property tree
1714       * @type YAHOO.widget.Column[]
1715       */
1716      tree : null,
1717  
1718      /**
1719       * Flattened representation of all Columns.
1720       *
1721       * @property flat
1722       * @type YAHOO.widget.Column[]
1723       * @default []
1724       */
1725      flat : null,
1726  
1727      /**
1728       * Array of Columns that map one-to-one to a table column.
1729       *
1730       * @property keys
1731       * @type YAHOO.widget.Column[]
1732       * @default []
1733       */
1734      keys : null,
1735  
1736      /**
1737       * ID index of nested parent hierarchies for HEADERS accessibility attribute.
1738       *
1739       * @property headers
1740       * @type String[]
1741       * @default []
1742       */
1743      headers : null,
1744  
1745      /////////////////////////////////////////////////////////////////////////////
1746      //
1747      // Private methods
1748      //
1749      /////////////////////////////////////////////////////////////////////////////
1750  
1751      /**
1752       * Initializes ColumnSet instance with data from Column definitions.
1753       *
1754       * @method _init
1755       * @param aDefinitions {Object[]} Array of object literals that define cells in
1756       * the THEAD .
1757       * @private
1758       */
1759  
1760      _init : function(aDefinitions) {        
1761          // DOM tree representation of all Columns
1762          var tree = [];
1763          // Flat representation of all Columns
1764          var flat = [];
1765          // Flat representation of only Columns that are meant to display data
1766          var keys = [];
1767          // Array of HEADERS attribute values for all keys in the "keys" array
1768          var headers = [];
1769  
1770          // Tracks current node list depth being tracked
1771          var nodeDepth = -1;
1772  
1773          // Internal recursive function to define Column instances
1774          var parseColumns = function(nodeList, parent) {
1775              // One level down
1776              nodeDepth++;
1777  
1778              // Create corresponding tree node if not already there for this depth
1779              if(!tree[nodeDepth]) {
1780                  tree[nodeDepth] = [];
1781              }
1782  
1783  
1784              // Parse each node at this depth for attributes and any children
1785              for(var j=0; j<nodeList.length; j++) {
1786                  var currentNode = nodeList[j];
1787  
1788                  // Instantiate a new Column for each node
1789                  var oColumn = new YAHOO.widget.Column(currentNode);
1790                  
1791                  // Cross-reference Column ID back to the original object literal definition
1792                  currentNode.yuiColumnId = oColumn._sId;
1793                  
1794                  // Add the new Column to the flat list
1795                  flat.push(oColumn);
1796  
1797                  // Assign its parent as an attribute, if applicable
1798                  if(parent) {
1799                      oColumn._oParent = parent;
1800                  }
1801  
1802                  // The Column has descendants
1803                  if(YAHOO.lang.isArray(currentNode.children)) {
1804                      oColumn.children = currentNode.children;
1805  
1806                      // Determine COLSPAN value for this Column
1807                      var terminalChildNodes = 0;
1808                      var countTerminalChildNodes = function(ancestor) {
1809                          var descendants = ancestor.children;
1810                          // Drill down each branch and count terminal nodes
1811                          for(var k=0; k<descendants.length; k++) {
1812                              // Keep drilling down
1813                              if(YAHOO.lang.isArray(descendants[k].children)) {
1814                                  countTerminalChildNodes(descendants[k]);
1815                              }
1816                              // Reached branch terminus
1817                              else {
1818                                  terminalChildNodes++;
1819                              }
1820                          }
1821                      };
1822                      countTerminalChildNodes(currentNode);
1823                      oColumn._nColspan = terminalChildNodes;
1824  
1825                      // Cascade certain properties to children if not defined on their own
1826                      var currentChildren = currentNode.children;
1827                      for(var k=0; k<currentChildren.length; k++) {
1828                          var child = currentChildren[k];
1829                          if(oColumn.className && (child.className === undefined)) {
1830                              child.className = oColumn.className;
1831                          }
1832                          if(oColumn.editor && (child.editor === undefined)) {
1833                              child.editor = oColumn.editor;
1834                          }
1835                          //TODO: Deprecated
1836                          if(oColumn.editorOptions && (child.editorOptions === undefined)) {
1837                              child.editorOptions = oColumn.editorOptions;
1838                          }
1839                          if(oColumn.formatter && (child.formatter === undefined)) {
1840                              child.formatter = oColumn.formatter;
1841                          }
1842                          if(oColumn.resizeable && (child.resizeable === undefined)) {
1843                              child.resizeable = oColumn.resizeable;
1844                          }
1845                          if(oColumn.sortable && (child.sortable === undefined)) {
1846                              child.sortable = oColumn.sortable;
1847                          }
1848                          if(oColumn.hidden) {
1849                              child.hidden = true;
1850                          }
1851                          if(oColumn.width && (child.width === undefined)) {
1852                              child.width = oColumn.width;
1853                          }
1854                          if(oColumn.minWidth && (child.minWidth === undefined)) {
1855                              child.minWidth = oColumn.minWidth;
1856                          }
1857                          if(oColumn.maxAutoWidth && (child.maxAutoWidth === undefined)) {
1858                              child.maxAutoWidth = oColumn.maxAutoWidth;
1859                          }
1860                          // Backward compatibility
1861                          if(oColumn.type && (child.type === undefined)) {
1862                              child.type = oColumn.type;
1863                          }
1864                          if(oColumn.type && !oColumn.formatter) {
1865                              YAHOO.log("The property type has been" +
1866                              " deprecated in favor of formatter", "warn", oColumn.toString());
1867                              oColumn.formatter = oColumn.type;
1868                          }
1869                          if(oColumn.text && !YAHOO.lang.isValue(oColumn.label)) {
1870                              YAHOO.log("The property text has been" +
1871                              " deprecated in favor of label", "warn", oColumn.toString());
1872                              oColumn.label = oColumn.text;
1873                          }
1874                          if(oColumn.parser) {
1875                              YAHOO.log("The property parser is no longer supported",
1876                              "warn", this.toString());
1877                          }
1878                          if(oColumn.sortOptions && ((oColumn.sortOptions.ascFunction) ||
1879                                  (oColumn.sortOptions.descFunction))) {
1880                              YAHOO.log("The properties sortOptions.ascFunction and " +
1881                              " sortOptions.descFunction have been deprecated in favor " +
1882                              " of sortOptions.sortFunction", "warn", oColumn.toString());
1883                          }
1884                      }
1885  
1886                      // The children themselves must also be parsed for Column instances
1887                      if(!tree[nodeDepth+1]) {
1888                          tree[nodeDepth+1] = [];
1889                      }
1890                      parseColumns(currentChildren, oColumn);
1891                  }
1892                  // This Column does not have any children
1893                  else {
1894                      oColumn._nKeyIndex = keys.length;
1895                      oColumn._nColspan = 1;
1896                      keys.push(oColumn);
1897                  }
1898  
1899                  // Add the Column to the top-down tree
1900                  tree[nodeDepth].push(oColumn);
1901              }
1902              nodeDepth--;
1903          };
1904  
1905          // Parse out Column instances from the array of object literals
1906          if(YAHOO.lang.isArray(aDefinitions)) {
1907              parseColumns(aDefinitions);
1908  
1909              // Store the array
1910              this._aDefinitions = aDefinitions;
1911          }
1912          else {
1913              YAHOO.log("Could not initialize ColumnSet due to invalid definitions","error");
1914              return null;
1915          }
1916  
1917          var i;
1918  
1919          // Determine ROWSPAN value for each Column in the tree
1920          var parseTreeForRowspan = function(tree) {
1921              var maxRowDepth = 1;
1922              var currentRow;
1923              var currentColumn;
1924  
1925              // Calculate the max depth of descendants for this row
1926              var countMaxRowDepth = function(row, tmpRowDepth) {
1927                  tmpRowDepth = tmpRowDepth || 1;
1928  
1929                  for(var n=0; n<row.length; n++) {
1930                      var col = row[n];
1931                      // Column has children, so keep counting
1932                      if(YAHOO.lang.isArray(col.children)) {
1933                          tmpRowDepth++;
1934                          countMaxRowDepth(col.children, tmpRowDepth);
1935                          tmpRowDepth--;
1936                      }
1937                      // No children, is it the max depth?
1938                      else {
1939                          if(tmpRowDepth > maxRowDepth) {
1940                              maxRowDepth = tmpRowDepth;
1941                          }
1942                      }
1943  
1944                  }
1945              };
1946  
1947              // Count max row depth for each row
1948              for(var m=0; m<tree.length; m++) {
1949                  currentRow = tree[m];
1950                  countMaxRowDepth(currentRow);
1951  
1952                  // Assign the right ROWSPAN values to each Column in the row
1953                  for(var p=0; p<currentRow.length; p++) {
1954                      currentColumn = currentRow[p];
1955                      if(!YAHOO.lang.isArray(currentColumn.children)) {
1956                          currentColumn._nRowspan = maxRowDepth;
1957                      }
1958                      else {
1959                          currentColumn._nRowspan = 1;
1960                      }
1961                  }
1962  
1963                  // Reset counter for next row
1964                  maxRowDepth = 1;
1965              }
1966          };
1967          parseTreeForRowspan(tree);
1968  
1969          // Store tree index values
1970          for(i=0; i<tree[0].length; i++) {
1971              tree[0][i]._nTreeIndex = i;
1972          }
1973  
1974          // Store header relationships in an array for HEADERS attribute
1975          var recurseAncestorsForHeaders = function(i, oColumn) {
1976              headers[i].push(oColumn.getSanitizedKey());
1977              if(oColumn._oParent) {
1978                  recurseAncestorsForHeaders(i, oColumn._oParent);
1979              }
1980          };
1981          for(i=0; i<keys.length; i++) {
1982              headers[i] = [];
1983              recurseAncestorsForHeaders(i, keys[i]);
1984              headers[i] = headers[i].reverse();
1985          }
1986  
1987          // Save to the ColumnSet instance
1988          this.tree = tree;
1989          this.flat = flat;
1990          this.keys = keys;
1991          this.headers = headers;
1992      },
1993  
1994      /////////////////////////////////////////////////////////////////////////////
1995      //
1996      // Public methods
1997      //
1998      /////////////////////////////////////////////////////////////////////////////
1999  
2000      /**
2001       * Returns unique name of the ColumnSet instance.
2002       *
2003       * @method getId
2004       * @return {String} Unique name of the ColumnSet instance.
2005       */
2006  
2007      getId : function() {
2008          return this._sId;
2009      },
2010  
2011      /**
2012       * ColumnSet instance name, for logging.
2013       *
2014       * @method toString
2015       * @return {String} Unique name of the ColumnSet instance.
2016       */
2017  
2018      toString : function() {
2019          return "ColumnSet instance " + this._sId;
2020      },
2021  
2022      /**
2023       * Public accessor to the definitions array.
2024       *
2025       * @method getDefinitions
2026       * @return {Object[]} Array of object literal Column definitions.
2027       */
2028  
2029      getDefinitions : function() {
2030          var aDefinitions = this._aDefinitions;
2031          
2032          // Internal recursive function to define Column instances
2033          var parseColumns = function(nodeList, oSelf) {
2034              // Parse each node at this depth for attributes and any children
2035              for(var j=0; j<nodeList.length; j++) {
2036                  var currentNode = nodeList[j];
2037                  
2038                  // Get the Column for each node
2039                  var oColumn = oSelf.getColumnById(currentNode.yuiColumnId);
2040                  
2041                  if(oColumn) {    
2042                      // Update the current values
2043                      var oDefinition = oColumn.getDefinition();
2044                      for(var name in oDefinition) {
2045                          if(YAHOO.lang.hasOwnProperty(oDefinition, name)) {
2046                              currentNode[name] = oDefinition[name];
2047                          }
2048                      }
2049                  }
2050                              
2051                  // The Column has descendants
2052                  if(YAHOO.lang.isArray(currentNode.children)) {
2053                      // The children themselves must also be parsed for Column instances
2054                      parseColumns(currentNode.children, oSelf);
2055                  }
2056              }
2057          };
2058  
2059          parseColumns(aDefinitions, this);
2060          this._aDefinitions = aDefinitions;
2061          return aDefinitions;
2062      },
2063  
2064      /**
2065       * Returns Column instance with given ID.
2066       *
2067       * @method getColumnById
2068       * @param column {String} Column ID.
2069       * @return {YAHOO.widget.Column} Column instance.
2070       */
2071  
2072      getColumnById : function(column) {
2073          if(YAHOO.lang.isString(column)) {
2074              var allColumns = this.flat;
2075              for(var i=allColumns.length-1; i>-1; i--) {
2076                  if(allColumns[i]._sId === column) {
2077                      return allColumns[i];
2078                  }
2079              }
2080          }
2081          return null;
2082      },
2083  
2084      /**
2085       * Returns Column instance with given key or ColumnSet key index.
2086       *
2087       * @method getColumn
2088       * @param column {String | Number} Column key or ColumnSet key index.
2089       * @return {YAHOO.widget.Column} Column instance.
2090       */
2091  
2092      getColumn : function(column) {
2093          if(YAHOO.lang.isNumber(column) && this.keys[column]) {
2094              return this.keys[column];
2095          }
2096          else if(YAHOO.lang.isString(column)) {
2097              var allColumns = this.flat;
2098              var aColumns = [];
2099              for(var i=0; i<allColumns.length; i++) {
2100                  if(allColumns[i].key === column) {
2101                      aColumns.push(allColumns[i]);
2102                  }
2103              }
2104              if(aColumns.length === 1) {
2105                  return aColumns[0];
2106              }
2107              else if(aColumns.length > 1) {
2108                  return aColumns;
2109              }
2110          }
2111          return null;
2112      },
2113  
2114      /**
2115       * Public accessor returns array of given Column's desendants (if any), including itself.
2116       *
2117       * @method getDescendants
2118       * @parem {YAHOO.widget.Column} Column instance.
2119       * @return {Array} Array including the Column itself and all descendants (if any).
2120       */
2121      getDescendants : function(oColumn) {
2122          var oSelf = this;
2123          var allDescendants = [];
2124          var i;
2125  
2126          // Recursive function to loop thru all children
2127          var parse = function(oParent) {
2128              allDescendants.push(oParent);
2129              // This Column has children
2130              if(oParent.children) {
2131                  for(i=0; i<oParent.children.length; i++) {
2132                      parse(oSelf.getColumn(oParent.children[i].key));
2133                  }
2134              }
2135          };
2136          parse(oColumn);
2137  
2138          return allDescendants;
2139      }
2140  };
2141  
2142  /****************************************************************************/
2143  /****************************************************************************/
2144  /****************************************************************************/
2145  
2146  /**
2147   * The Column class defines and manages attributes of DataTable Columns
2148   *
2149   * @namespace YAHOO.widget
2150   * @class Column
2151   * @constructor
2152   * @param oConfigs {Object} Object literal of definitions.
2153   */
2154  YAHOO.widget.Column = function(oConfigs) {
2155      this._sId = Dom.generateId(null, "yui-col"); // "yui-col" + YAHOO.widget.Column._nCount;
2156      
2157      // Object literal defines Column attributes
2158      if(oConfigs && YAHOO.lang.isObject(oConfigs)) {
2159          for(var sConfig in oConfigs) {
2160              if(sConfig) {
2161                  this[sConfig] = oConfigs[sConfig];
2162              }
2163          }
2164      }
2165  
2166      // Assign a key if not found
2167      if(!YAHOO.lang.isValue(this.key)) {
2168          this.key = Dom.generateId(null, "yui-dt-col"); //"yui-dt-col" + YAHOO.widget.Column._nCount;
2169      }
2170      
2171      // Assign a field if not found, defaults to key
2172      if(!YAHOO.lang.isValue(this.field)) {
2173          this.field = this.key;
2174      }
2175  
2176      // Increment counter
2177      YAHOO.widget.Column._nCount++;
2178  
2179      // Backward compatibility
2180      if(this.width && !YAHOO.lang.isNumber(this.width)) {
2181          this.width = null;
2182          YAHOO.log("The Column property width must be a number", "warn", this.toString());
2183      }
2184      if(this.editor && YAHOO.lang.isString(this.editor)) {
2185          this.editor = new YAHOO.widget.CellEditor(this.editor, this.editorOptions);
2186          YAHOO.log("The Column property editor must be an instance of YAHOO.widget.CellEditor", "warn", this.toString());
2187      }
2188  };
2189  
2190  /////////////////////////////////////////////////////////////////////////////
2191  //
2192  // Private member variables
2193  //
2194  /////////////////////////////////////////////////////////////////////////////
2195  
2196  YAHOO.lang.augmentObject(YAHOO.widget.Column, {
2197      /**
2198       * Internal class variable to index multiple Column instances.
2199       *
2200       * @property Column._nCount
2201       * @type Number
2202       * @private
2203       * @static
2204       */
2205      _nCount : 0,
2206  
2207      formatCheckbox : function(elCell, oRecord, oColumn, oData) {
2208          YAHOO.log("The method YAHOO.widget.Column.formatCheckbox() has been" +
2209          " deprecated in favor of YAHOO.widget.DataTable.formatCheckbox()", "warn",
2210          "YAHOO.widget.Column.formatCheckbox");
2211          YAHOO.widget.DataTable.formatCheckbox(elCell, oRecord, oColumn, oData);
2212      },
2213  
2214      formatCurrency : function(elCell, oRecord, oColumn, oData) {
2215          YAHOO.log("The method YAHOO.widget.Column.formatCurrency() has been" +
2216          " deprecated in favor of YAHOO.widget.DataTable.formatCurrency()", "warn",
2217          "YAHOO.widget.Column.formatCurrency");
2218          YAHOO.widget.DataTable.formatCurrency(elCell, oRecord, oColumn, oData);
2219      },
2220  
2221      formatDate : function(elCell, oRecord, oColumn, oData) {
2222          YAHOO.log("The method YAHOO.widget.Column.formatDate() has been" +
2223          " deprecated in favor of YAHOO.widget.DataTable.formatDate()", "warn",
2224          "YAHOO.widget.Column.formatDate");
2225          YAHOO.widget.DataTable.formatDate(elCell, oRecord, oColumn, oData);
2226      },
2227  
2228      formatEmail : function(elCell, oRecord, oColumn, oData) {
2229          YAHOO.log("The method YAHOO.widget.Column.formatEmail() has been" +
2230          " deprecated in favor of YAHOO.widget.DataTable.formatEmail()", "warn",
2231          "YAHOO.widget.Column.formatEmail");
2232          YAHOO.widget.DataTable.formatEmail(elCell, oRecord, oColumn, oData);
2233      },
2234  
2235      formatLink : function(elCell, oRecord, oColumn, oData) {
2236          YAHOO.log("The method YAHOO.widget.Column.formatLink() has been" +
2237          " deprecated in favor of YAHOO.widget.DataTable.formatLink()", "warn",
2238          "YAHOO.widget.Column.formatLink");
2239          YAHOO.widget.DataTable.formatLink(elCell, oRecord, oColumn, oData);
2240      },
2241  
2242      formatNumber : function(elCell, oRecord, oColumn, oData) {
2243          YAHOO.log("The method YAHOO.widget.Column.formatNumber() has been" +
2244          " deprecated in favor of YAHOO.widget.DataTable.formatNumber()", "warn",
2245          "YAHOO.widget.Column.formatNumber");
2246          YAHOO.widget.DataTable.formatNumber(elCell, oRecord, oColumn, oData);
2247      },
2248  
2249      formatSelect : function(elCell, oRecord, oColumn, oData) {
2250          YAHOO.log("The method YAHOO.widget.Column.formatSelect() has been" +
2251          " deprecated in favor of YAHOO.widget.DataTable.formatDropdown()", "warn",
2252          "YAHOO.widget.Column.formatSelect");
2253          YAHOO.widget.DataTable.formatDropdown(elCell, oRecord, oColumn, oData);
2254      }
2255  });
2256  
2257  YAHOO.widget.Column.prototype = {
2258      /**
2259       * Unique String identifier assigned at instantiation.
2260       *
2261       * @property _sId
2262       * @type String
2263       * @private
2264       */
2265      _sId : null,
2266  
2267      /**
2268       * Reference to Column's current position index within its ColumnSet's keys
2269       * array, if applicable. This property only applies to non-nested and bottom-
2270       * level child Columns.
2271       *
2272       * @property _nKeyIndex
2273       * @type Number
2274       * @private
2275       */
2276      _nKeyIndex : null,
2277  
2278      /**
2279       * Reference to Column's current position index within its ColumnSet's tree
2280       * array, if applicable. This property only applies to non-nested and top-
2281       * level parent Columns.
2282       *
2283       * @property _nTreeIndex
2284       * @type Number
2285       * @private
2286       */
2287      _nTreeIndex : null,
2288  
2289      /**
2290       * Number of table cells the Column spans.
2291       *
2292       * @property _nColspan
2293       * @type Number
2294       * @private
2295       */
2296      _nColspan : 1,
2297  
2298      /**
2299       * Number of table rows the Column spans.
2300       *
2301       * @property _nRowspan
2302       * @type Number
2303       * @private
2304       */
2305      _nRowspan : 1,
2306  
2307      /**
2308       * Column's parent Column instance, or null.
2309       *
2310       * @property _oParent
2311       * @type YAHOO.widget.Column
2312       * @private
2313       */
2314      _oParent : null,
2315  
2316      /**
2317       * The DOM reference to the associated TH element.
2318       *
2319       * @property _elTh
2320       * @type HTMLElement
2321       * @private
2322       */
2323      _elTh : null,
2324  
2325      /**
2326       * The DOM reference to the associated TH element's liner DIV element.
2327       *
2328       * @property _elThLiner
2329       * @type HTMLElement
2330       * @private
2331       */
2332      _elThLiner : null,
2333  
2334      /**
2335       * The DOM reference to the associated TH element's label SPAN element.
2336       *
2337       * @property _elThLabel
2338       * @type HTMLElement
2339       * @private
2340       */
2341      _elThLabel : null,
2342  
2343      /**
2344       * The DOM reference to the associated resizerelement (if any).
2345       *
2346       * @property _elResizer
2347       * @type HTMLElement
2348       * @private
2349       */
2350      _elResizer : null,
2351  
2352      /**
2353       * Internal width tracker.
2354       *
2355       * @property _nWidth
2356       * @type Number
2357       * @private
2358       */
2359      _nWidth : null,
2360  
2361      /**
2362       * For unreg() purposes, a reference to the Column's DragDrop instance.
2363       *
2364       * @property _dd
2365       * @type YAHOO.util.DragDrop
2366       * @private
2367       */
2368      _dd : null,
2369  
2370      /**
2371       * For unreg() purposes, a reference to the Column resizer's DragDrop instance.
2372       *
2373       * @property _ddResizer
2374       * @type YAHOO.util.DragDrop
2375       * @private
2376       */
2377      _ddResizer : null,
2378  
2379      /////////////////////////////////////////////////////////////////////////////
2380      //
2381      // Public member variables
2382      //
2383      /////////////////////////////////////////////////////////////////////////////
2384  
2385      /**
2386       * Unique name, required. If "label" property is not provided, the "key"
2387       * value will be treated as markup and inserted into the DOM as innerHTML.
2388       *
2389       * @property key
2390       * @type String|HTML
2391       */
2392      key : null,
2393  
2394      /**
2395       * Associated database field, or null.
2396       *
2397       * @property field
2398       * @type String
2399       */
2400      field : null,
2401  
2402      /**
2403       * Value displayed as Column header in the TH element. String value is
2404       * treated as markup and inserted into the DOM as innerHTML.
2405       *
2406       * @property label
2407       * @type HTML
2408       */
2409      label : null,
2410  
2411      /**
2412       * Column head cell ABBR for accessibility.
2413       *
2414       * @property abbr
2415       * @type String
2416       */
2417      abbr : null,
2418  
2419      /**
2420       * Array of object literals that define children (nested headers) of a Column.
2421       *
2422       * @property children
2423       * @type Object[]
2424       */
2425      children : null,
2426  
2427      /**
2428       * Column width (in pixels).
2429       *
2430       * @property width
2431       * @type Number
2432       */
2433      width : null,
2434  
2435      /**
2436       * Minimum Column width (in pixels).
2437       *
2438       * @property minWidth
2439       * @type Number
2440       * @default null
2441       */
2442      minWidth : null,
2443  
2444      /**
2445       * When a width is not defined for a Column, maxAutoWidth defines an upper
2446       * limit that the Column should be auto-sized to. If resizeable is enabled, 
2447       * users may still resize to a greater width. Most useful for Columns intended
2448       * to hold long unbroken, unwrapped Strings, such as URLs, to prevent very
2449       * wide Columns from disrupting visual readability by inducing truncation.
2450       *
2451       * @property maxAutoWidth
2452       * @type Number
2453       * @default null
2454       */
2455      maxAutoWidth : null,
2456  
2457      /**
2458       * True if Column is in hidden state.
2459       *
2460       * @property hidden
2461       * @type Boolean
2462       * @default false     
2463       */
2464      hidden : false,
2465  
2466      /**
2467       * True if Column is in selected state.
2468       *
2469       * @property selected
2470       * @type Boolean
2471       * @default false     
2472       */
2473      selected : false,
2474  
2475      /**
2476       * Custom CSS class or array of classes to be applied to every cell in the Column.
2477       *
2478       * @property className
2479       * @type String || String[]
2480       */
2481      className : null,
2482  
2483      /**
2484       * Cell formatter function, or a shortcut pointer to a function in the
2485       * DataTable.Formatter object. The function, called from the DataTable's
2486       * formatCell method, renders markup into the cell liner
2487       * element and accepts the following arguments:
2488       * <dl>
2489       *    <dt>elLiner</dt>
2490       *    <dd>The element to write innerHTML to.</dd>
2491       *    <dt>oRecord</dt>
2492       *    <dd>The associated Record for the row.</dd>
2493       *    <dt>oColumn</dt>
2494       *    <dd>The Column instance for the cell.</dd>
2495       *    <dt>oData</dt>
2496       *    <dd>The data value for the cell.</dd>
2497       * </dl>
2498       *
2499       * @property formatter
2500       * @type String || HTMLFunction
2501       */
2502      formatter : null,
2503      
2504      /**
2505       * Config passed to YAHOO.util.Number.format() by the 'currency' Column formatter.
2506       *
2507       * @property currencyOptions
2508       * @type Object
2509       * @default null
2510       */
2511      currencyOptions : null,
2512  
2513      /**
2514       * Config passed to YAHOO.util.Date.format() by the 'date' Column formatter.
2515       *
2516       * @property dateOptions
2517       * @type Object
2518       * @default null
2519       */
2520      dateOptions : null,
2521  
2522      /**
2523       * Array of dropdown values for formatter:"dropdown" cases. Can either be a
2524       * simple array (e.g., ["Alabama","Alaska","Arizona","Arkansas"]) or a an
2525       * array of objects (e.g., [{label:"Alabama", value:"AL"},
2526       * {label:"Alaska", value:"AK"}, {label:"Arizona", value:"AZ"},
2527       * {label:"Arkansas", value:"AR"}]). String values are treated as markup and
2528       * inserted into the DOM as innerHTML.
2529       *
2530       * @property dropdownOptions
2531       * @type HTML[] | Object[]
2532       */
2533      dropdownOptions : null,
2534       
2535      /**
2536       * A CellEditor instance, otherwise Column is not editable.     
2537       *
2538       * @property editor
2539       * @type YAHOO.widget.CellEditor
2540       */
2541      editor : null,
2542  
2543      /**
2544       * True if Column is resizeable, false otherwise. The Drag & Drop Utility is
2545       * required to enable this feature. Only bottom-level and non-nested Columns are
2546       * resizeble. 
2547       *
2548       * @property resizeable
2549       * @type Boolean
2550       * @default false
2551       */
2552      resizeable : false,
2553  
2554      /**
2555       * True if Column is sortable, false otherwise.
2556       *
2557       * @property sortable
2558       * @type Boolean
2559       * @default false
2560       */
2561      sortable : false,
2562  
2563      /**
2564       * @property sortOptions.defaultOrder
2565       * @deprecated Use sortOptions.defaultDir.
2566       */
2567      /**
2568       * Default sort direction for Column: YAHOO.widget.DataTable.CLASS_ASC or YAHOO.widget.DataTable.CLASS_DESC.
2569       *
2570       * @property sortOptions.defaultDir
2571       * @type String
2572       * @default null
2573       */
2574      /**
2575       * Custom field to sort on.
2576       *
2577       * @property sortOptions.field
2578       * @type String
2579       * @default null
2580       */
2581      /**
2582       * Custom sort handler. Signature: sortFunction(a, b, desc, field) where field is the sortOptions.field value
2583       *
2584       * @property sortOptions.sortFunction
2585       * @type Function
2586       * @default null
2587       */
2588      sortOptions : null,
2589  
2590  
2591  
2592  
2593  
2594  
2595  
2596  
2597  
2598  
2599  
2600  
2601  
2602  
2603  
2604      /////////////////////////////////////////////////////////////////////////////
2605      //
2606      // Public methods
2607      //
2608      /////////////////////////////////////////////////////////////////////////////
2609  
2610      /**
2611       * Returns unique ID string.
2612       *
2613       * @method getId
2614       * @return {String} Unique ID string.
2615       */
2616      getId : function() {
2617          return this._sId;
2618      },
2619  
2620      /**
2621       * Column instance name, for logging.
2622       *
2623       * @method toString
2624       * @return {String} Column's unique name.
2625       */
2626      toString : function() {
2627          return "Column instance " + this._sId;
2628      },
2629  
2630      /**
2631       * Returns object literal definition.
2632       *
2633       * @method getDefinition
2634       * @return {Object} Object literal definition.
2635       */
2636      getDefinition : function() {
2637          var oDefinition = {};
2638          
2639          // Update the definition
2640          oDefinition.abbr = this.abbr;
2641          oDefinition.className = this.className;
2642          oDefinition.editor = this.editor;
2643          oDefinition.editorOptions = this.editorOptions; //TODO: deprecated
2644          oDefinition.field = this.field;
2645          oDefinition.formatter = this.formatter;
2646          oDefinition.hidden = this.hidden;
2647          oDefinition.key = this.key;
2648          oDefinition.label = this.label;
2649          oDefinition.minWidth = this.minWidth;
2650          oDefinition.maxAutoWidth = this.maxAutoWidth;
2651          oDefinition.resizeable = this.resizeable;
2652          oDefinition.selected = this.selected;
2653          oDefinition.sortable = this.sortable;
2654          oDefinition.sortOptions = this.sortOptions;
2655          oDefinition.width = this.width;
2656          
2657          // Bug 2529147
2658          oDefinition._calculatedWidth = this._calculatedWidth;
2659  
2660          return oDefinition;
2661      },
2662  
2663      /**
2664       * Returns unique Column key.
2665       *
2666       * @method getKey
2667       * @return {String} Column key.
2668       */
2669      getKey : function() {
2670          return this.key;
2671      },
2672      
2673      /**
2674       * Returns field.
2675       *
2676       * @method getField
2677       * @return {String} Column field.
2678       */
2679      getField : function() {
2680          return this.field;
2681      },
2682      
2683      /**
2684       * Returns Column key which has been sanitized for DOM (class and ID) usage
2685       * starts with letter, contains only letters, numbers, hyphen, or period.
2686       *
2687       * @method getSanitizedKey
2688       * @return {String} Sanitized Column key.
2689       */
2690      getSanitizedKey : function() {
2691          return this.getKey().replace(/[^\w\-]/g,"");
2692      },
2693  
2694      /**
2695       * Public accessor returns Column's current position index within its
2696       * ColumnSet's keys array, if applicable. Only non-nested and bottom-level
2697       * child Columns will return a value.
2698       *
2699       * @method getKeyIndex
2700       * @return {Number} Position index, or null.
2701       */
2702      getKeyIndex : function() {
2703          return this._nKeyIndex;
2704      },
2705  
2706      /**
2707       * Public accessor returns Column's current position index within its
2708       * ColumnSet's tree array, if applicable. Only non-nested and top-level parent
2709       * Columns will return a value;
2710       *
2711       * @method getTreeIndex
2712       * @return {Number} Position index, or null.
2713       */
2714      getTreeIndex : function() {
2715          return this._nTreeIndex;
2716      },
2717  
2718      /**
2719       * Public accessor returns Column's parent instance if any, or null otherwise.
2720       *
2721       * @method getParent
2722       * @return {YAHOO.widget.Column} Column's parent instance.
2723       */
2724      getParent : function() {
2725          return this._oParent;
2726      },
2727  
2728      /**
2729       * Public accessor returns Column's calculated COLSPAN value.
2730       *
2731       * @method getColspan
2732       * @return {Number} Column's COLSPAN value.
2733       */
2734      getColspan : function() {
2735          return this._nColspan;
2736      },
2737      // Backward compatibility
2738      getColSpan : function() {
2739          YAHOO.log("The method getColSpan() has been" +
2740          " deprecated in favor of getColspan()", "warn", this.toString());
2741          return this.getColspan();
2742      },
2743  
2744      /**
2745       * Public accessor returns Column's calculated ROWSPAN value.
2746       *
2747       * @method getRowspan
2748       * @return {Number} Column's ROWSPAN value.
2749       */
2750      getRowspan : function() {
2751          return this._nRowspan;
2752      },
2753  
2754      /**
2755       * Returns DOM reference to the key TH element.
2756       *
2757       * @method getThEl
2758       * @return {HTMLElement} TH element.
2759       */
2760      getThEl : function() {
2761          return this._elTh;
2762      },
2763  
2764      /**
2765       * Returns DOM reference to the TH's liner DIV element. Introduced since
2766       * resizeable Columns may have an extra resizer liner, making the DIV liner
2767       * not reliably the TH element's first child.               
2768       *
2769       * @method getThLInerEl
2770       * @return {HTMLElement} TH element.
2771       */
2772      getThLinerEl : function() {
2773          return this._elThLiner;
2774      },
2775      
2776      /**
2777       * Returns DOM reference to the resizer element, or null.
2778       *
2779       * @method getResizerEl
2780       * @return {HTMLElement} DIV element.
2781       */
2782      getResizerEl : function() {
2783          return this._elResizer;
2784      },
2785  
2786      // Backward compatibility
2787      /**
2788       * @method getColEl
2789       * @deprecated Use getThEl
2790       */
2791      getColEl : function() {
2792          YAHOO.log("The method getColEl() has been" +
2793          " deprecated in favor of getThEl()", "warn",
2794          this.toString());
2795          return this.getThEl();
2796      },
2797      getIndex : function() {
2798          YAHOO.log("The method getIndex() has been" +
2799          " deprecated in favor of getKeyIndex()", "warn",
2800          this.toString());
2801          return this.getKeyIndex();
2802      },
2803      format : function() {
2804          YAHOO.log("The method format() has been deprecated in favor of the " +
2805          "DataTable method formatCell()", "error", this.toString());
2806      }
2807  };
2808  
2809  /****************************************************************************/
2810  /****************************************************************************/
2811  /****************************************************************************/
2812  
2813  /**
2814   * Sort static utility to support Column sorting.
2815   *
2816   * @namespace YAHOO.util
2817   * @class Sort
2818   * @static
2819   */
2820  YAHOO.util.Sort = {
2821      /////////////////////////////////////////////////////////////////////////////
2822      //
2823      // Public methods
2824      //
2825      /////////////////////////////////////////////////////////////////////////////
2826  
2827      /**
2828       * Comparator function for simple case-insensitive string sorting.
2829       *
2830       * @method compare
2831       * @param a {Object} First sort argument.
2832       * @param b {Object} Second sort argument.
2833       * @param desc {Boolean} True if sort direction is descending, false if
2834       * sort direction is ascending.
2835       * @return {Boolean} Return -1 when a < b. Return 0 when a = b.
2836       * Return 1 when a > b.
2837       */
2838      compare: function(a, b, desc) {
2839          if((a === null) || (typeof a == "undefined")) {
2840              if((b === null) || (typeof b == "undefined")) {
2841                  return 0;
2842              }
2843              else {
2844                  return 1;
2845              }
2846          }
2847          else if((b === null) || (typeof b == "undefined")) {
2848              return -1;
2849          }
2850  
2851          if(a.constructor == String) {
2852              a = a.toLowerCase();
2853          }
2854          if(b.constructor == String) {
2855              b = b.toLowerCase();
2856          }
2857          if(a < b) {
2858              return (desc) ? 1 : -1;
2859          }
2860          else if (a > b) {
2861              return (desc) ? -1 : 1;
2862          }
2863          else {
2864              return 0;
2865          }
2866      }
2867  };
2868  
2869  /****************************************************************************/
2870  /****************************************************************************/
2871  /****************************************************************************/
2872  
2873  /**
2874   * ColumnDD subclasses DragDrop to support rearrangeable Columns.
2875   *
2876   * @namespace YAHOO.util
2877   * @class ColumnDD
2878   * @extends YAHOO.util.DDProxy
2879   * @constructor
2880   * @param oDataTable {YAHOO.widget.DataTable} DataTable instance.
2881   * @param oColumn {YAHOO.widget.Column} Column instance.
2882   * @param elTh {HTMLElement} TH element reference.
2883   * @param elTarget {HTMLElement} Drag target element.
2884   */
2885  YAHOO.widget.ColumnDD = function(oDataTable, oColumn, elTh, elTarget) {
2886      if(oDataTable && oColumn && elTh && elTarget) {
2887          this.datatable = oDataTable;
2888          this.table = oDataTable.getTableEl();
2889          this.column = oColumn;
2890          this.headCell = elTh;
2891          this.pointer = elTarget;
2892          this.newIndex = null;
2893          this.init(elTh);
2894          this.initFrame(); // Needed for DDProxy
2895          this.invalidHandleTypes = {};
2896  
2897          // Set top/bottom padding to account for children of nested columns
2898          this.setPadding(10, 0, (this.datatable.getTheadEl().offsetHeight + 10) , 0);
2899  
2900          YAHOO.util.Event.on(window, 'resize', function() {
2901              this.initConstraints();
2902          }, this, true);
2903      }
2904      else {
2905          YAHOO.log("Column dragdrop could not be created","warn",oDataTable.toString());
2906      }
2907  };
2908  
2909  if(YAHOO.util.DDProxy) {
2910      YAHOO.extend(YAHOO.widget.ColumnDD, YAHOO.util.DDProxy, {
2911          initConstraints: function() {
2912              //Get the top, right, bottom and left positions
2913              var region = YAHOO.util.Dom.getRegion(this.table),
2914                  //Get the element we are working on
2915                  el = this.getEl(),
2916                  //Get the xy position of it
2917                  xy = YAHOO.util.Dom.getXY(el),
2918                  //Get the width and height
2919                  width = parseInt(YAHOO.util.Dom.getStyle(el, 'width'), 10),
2920                  height = parseInt(YAHOO.util.Dom.getStyle(el, 'height'), 10),
2921                  //Set left to x minus left
2922                  left = ((xy[0] - region.left) + 15), //Buffer of 15px
2923                  //Set right to right minus x minus width
2924                  right = ((region.right - xy[0] - width) + 15);
2925      
2926              //Set the constraints based on the above calculations
2927              this.setXConstraint(left, right);
2928              this.setYConstraint(10, 10);            
2929          },
2930          _resizeProxy: function() {
2931              YAHOO.widget.ColumnDD.superclass._resizeProxy.apply(this, arguments);
2932              var dragEl = this.getDragEl(),
2933                  el = this.getEl();
2934  
2935              YAHOO.util.Dom.setStyle(this.pointer, 'height', (this.table.parentNode.offsetHeight + 10) + 'px');
2936              YAHOO.util.Dom.setStyle(this.pointer, 'display', 'block');
2937              var xy = YAHOO.util.Dom.getXY(el);
2938              YAHOO.util.Dom.setXY(this.pointer, [xy[0], (xy[1] - 5)]);
2939              
2940              YAHOO.util.Dom.setStyle(dragEl, 'height', this.datatable.getContainerEl().offsetHeight + "px");
2941              YAHOO.util.Dom.setStyle(dragEl, 'width', (parseInt(YAHOO.util.Dom.getStyle(dragEl, 'width'),10) + 4) + 'px');
2942              YAHOO.util.Dom.setXY(this.dragEl, xy);
2943          },
2944          onMouseDown: function() {
2945                  this.initConstraints();
2946                  this.resetConstraints();
2947          },
2948          clickValidator: function(e) {
2949              if(!this.column.hidden) {
2950                  var target = YAHOO.util.Event.getTarget(e);
2951                  return ( this.isValidHandleChild(target) &&
2952                              (this.id == this.handleElId ||
2953                                  this.DDM.handleWasClicked(target, this.id)) );
2954              }
2955          },
2956          onDragOver: function(ev, id) {
2957              // Validate target as a Column
2958              var target = this.datatable.getColumn(id);
2959              if(target) {                
2960                  // Validate target as a top-level parent
2961                  var targetIndex = target.getTreeIndex();
2962                  while((targetIndex === null) && target.getParent()) {
2963                      target = target.getParent();
2964                      targetIndex = target.getTreeIndex();
2965                  }
2966                  if(targetIndex !== null) {
2967                      // Are we placing to left or right of target?
2968                      var elTarget = target.getThEl();
2969                      var newIndex = targetIndex;
2970                      var mouseX = YAHOO.util.Event.getPageX(ev),
2971                          targetX = YAHOO.util.Dom.getX(elTarget),
2972                          midX = targetX + ((YAHOO.util.Dom.get(elTarget).offsetWidth)/2),
2973                          currentIndex =  this.column.getTreeIndex();
2974                      
2975                      if (mouseX < midX) {
2976                         YAHOO.util.Dom.setX(this.pointer, targetX);
2977                      } else {
2978                          var targetWidth = parseInt(elTarget.offsetWidth, 10);
2979                          YAHOO.util.Dom.setX(this.pointer, (targetX + targetWidth));
2980                          newIndex++;
2981                      }
2982                      if (targetIndex > currentIndex) {
2983                          newIndex--;
2984                      }
2985                      if(newIndex < 0) {
2986                          newIndex = 0;
2987                      }
2988                      else if(newIndex > this.datatable.getColumnSet().tree[0].length) {
2989                          newIndex = this.datatable.getColumnSet().tree[0].length;
2990                      }
2991                      this.newIndex = newIndex;
2992                  }
2993              }
2994          },
2995          onDragDrop: function() {
2996              this.datatable.reorderColumn(this.column, this.newIndex);
2997          },
2998          endDrag: function() {
2999              this.newIndex = null;
3000              YAHOO.util.Dom.setStyle(this.pointer, 'display', 'none');
3001          }
3002      });
3003  }
3004  
3005  /****************************************************************************/
3006  /****************************************************************************/
3007  /****************************************************************************/
3008  
3009  /**
3010   * ColumnResizer subclasses DragDrop to support resizeable Columns.
3011   *
3012   * @namespace YAHOO.util
3013   * @class ColumnResizer
3014   * @extends YAHOO.util.DDProxy
3015   * @constructor
3016   * @param oDataTable {YAHOO.widget.DataTable} DataTable instance.
3017   * @param oColumn {YAHOO.widget.Column} Column instance.
3018   * @param elTh {HTMLElement} TH element reference.
3019   * @param sHandleElId {String} DOM ID of the handle element that causes the resize.
3020   * @param elProxy {HTMLElement} Resizer proxy element.
3021   */
3022  YAHOO.util.ColumnResizer = function(oDataTable, oColumn, elTh, sHandleId, elProxy) {
3023      if(oDataTable && oColumn && elTh && sHandleId) {
3024          this.datatable = oDataTable;
3025          this.column = oColumn;
3026          this.headCell = elTh;
3027          this.headCellLiner = oColumn.getThLinerEl();
3028          this.resizerLiner = elTh.firstChild;
3029          this.init(sHandleId, sHandleId, {dragOnly:true, dragElId: elProxy.id});
3030          this.initFrame(); // Needed for proxy
3031          this.resetResizerEl(); // Needed when rowspan > 0
3032  
3033          // Set right padding for bug 1858462
3034          this.setPadding(0, 1, 0, 0);
3035      }
3036      else {
3037          YAHOO.log("Column resizer could not be created","warn",oDataTable.toString());
3038      }
3039  };
3040  
3041  if(YAHOO.util.DD) {
3042      YAHOO.extend(YAHOO.util.ColumnResizer, YAHOO.util.DDProxy, {
3043          /////////////////////////////////////////////////////////////////////////////
3044          //
3045          // Public methods
3046          //
3047          /////////////////////////////////////////////////////////////////////////////
3048          /**
3049           * Resets resizer element.
3050           *
3051           * @method resetResizerEl
3052           */
3053          resetResizerEl : function() {
3054              var resizerStyle = YAHOO.util.Dom.get(this.handleElId).style;
3055              resizerStyle.left = "auto";
3056              resizerStyle.right = 0;
3057              resizerStyle.top = "auto";
3058              resizerStyle.bottom = 0;
3059              resizerStyle.height = this.headCell.offsetHeight+"px";
3060          },
3061      
3062          /////////////////////////////////////////////////////////////////////////////
3063          //
3064          // Public DOM event handlers
3065          //
3066          /////////////////////////////////////////////////////////////////////////////
3067      
3068          /**
3069           * Handles mouseup events on the Column resizer.
3070           *
3071           * @method onMouseUp
3072           * @param e {string} The mouseup event
3073           */
3074          onMouseUp : function(e) {
3075              // Reset height of all resizer els in case TH's have changed height
3076              var allKeys = this.datatable.getColumnSet().keys,
3077                  col;
3078              for(var i=0, len=allKeys.length; i<len; i++) {
3079                  col = allKeys[i];
3080                  if(col._ddResizer) {
3081                      col._ddResizer.resetResizerEl();
3082                  }
3083              }
3084              this.resetResizerEl();
3085              
3086              var el = this.headCellLiner;
3087              var newWidth = el.offsetWidth -
3088                  (parseInt(YAHOO.util.Dom.getStyle(el,"paddingLeft"),10)|0) -
3089                  (parseInt(YAHOO.util.Dom.getStyle(el,"paddingRight"),10)|0);
3090  
3091              this.datatable.fireEvent("columnResizeEvent", {column:this.column,target:this.headCell,width:newWidth});
3092          },
3093      
3094          /**
3095           * Handles mousedown events on the Column resizer.
3096           *
3097           * @method onMouseDown
3098           * @param e {string} The mousedown event
3099           */
3100          onMouseDown : function(e) {
3101              this.startWidth = this.headCellLiner.offsetWidth;
3102              this.startX = YAHOO.util.Event.getXY(e)[0];
3103              this.nLinerPadding = (parseInt(YAHOO.util.Dom.getStyle(this.headCellLiner,"paddingLeft"),10)|0) +
3104                      (parseInt(YAHOO.util.Dom.getStyle(this.headCellLiner,"paddingRight"),10)|0);
3105          },
3106      
3107          /**
3108           * Custom clickValidator to ensure Column is not in hidden state.
3109           *
3110           * @method clickValidator
3111           * @param {Event} e
3112           * @private
3113           */
3114          clickValidator : function(e) {
3115              if(!this.column.hidden) {
3116                  var target = YAHOO.util.Event.getTarget(e);
3117                  return ( this.isValidHandleChild(target) &&
3118                              (this.id == this.handleElId ||
3119                                  this.DDM.handleWasClicked(target, this.id)) );
3120              }
3121          },
3122      
3123          /**
3124           * Handles start drag on the Column resizer.
3125           *
3126           * @method startDrag
3127           * @param e {string} The drag event
3128           */
3129          startDrag : function() {
3130              // Shrinks height of all resizer els to not hold open TH els
3131              var allKeys = this.datatable.getColumnSet().keys,
3132                  thisKey = this.column.getKeyIndex(),
3133                  col;
3134              for(var i=0, len=allKeys.length; i<len; i++) {
3135                  col = allKeys[i];
3136                  if(col._ddResizer) {
3137                      YAHOO.util.Dom.get(col._ddResizer.handleElId).style.height = "1em";
3138                  }
3139              }
3140          },
3141  
3142          /**
3143           * Handles drag events on the Column resizer.
3144           *
3145           * @method onDrag
3146           * @param e {string} The drag event
3147           */
3148          onDrag : function(e) {
3149              var newX = YAHOO.util.Event.getXY(e)[0];
3150              if(newX > YAHOO.util.Dom.getX(this.headCellLiner)) {
3151                  var offsetX = newX - this.startX;
3152                  var newWidth = this.startWidth + offsetX - this.nLinerPadding;
3153                  if(newWidth > 0) {
3154                      this.datatable.setColumnWidth(this.column, newWidth);
3155                  }
3156              }
3157          }
3158      });
3159  }
3160  
3161  /////////////////////////////////////////////////////////////////////////////
3162  //
3163  // Deprecated
3164  //
3165  /////////////////////////////////////////////////////////////////////////////
3166  
3167  /**
3168   * @property editorOptions
3169   * @deprecated Pass configs directly to CellEditor constructor. 
3170   */
3171  
3172  
3173  (function () {
3174  
3175  var lang   = YAHOO.lang,
3176      util   = YAHOO.util,
3177      widget = YAHOO.widget,
3178      
3179      Dom    = util.Dom,
3180      Ev     = util.Event,
3181      DT     = widget.DataTable;
3182  
3183  /****************************************************************************/
3184  /****************************************************************************/
3185  /****************************************************************************/
3186  
3187  /**
3188   * A RecordSet defines and manages a set of Records.
3189   *
3190   * @namespace YAHOO.widget
3191   * @class RecordSet
3192   * @param data {Object || Object[]} An object literal or an array of data.
3193   * @constructor
3194   */
3195  YAHOO.widget.RecordSet = function(data) {
3196      this._init(data);
3197  };
3198  
3199  var RS = widget.RecordSet;
3200  
3201  /**
3202   * Internal class variable to name multiple Recordset instances.
3203   *
3204   * @property RecordSet._nCount
3205   * @type Number
3206   * @private
3207   * @static
3208   */
3209  RS._nCount = 0;
3210  
3211  RS.prototype = {
3212  
3213      /////////////////////////////////////////////////////////////////////////////
3214      //
3215      // Private member variables
3216      //
3217      /////////////////////////////////////////////////////////////////////////////
3218      /**
3219       * Unique String identifier assigned at instantiation.
3220       *
3221       * @property _sId
3222       * @type String
3223       * @private
3224       */
3225      _sId : null,
3226  
3227      /**
3228       * Internal counter of how many Records are in the RecordSet.
3229       *
3230       * @property _length
3231       * @type Number
3232       * @private
3233       * @deprecated No longer used
3234       */
3235      //_length : null,
3236  
3237      /////////////////////////////////////////////////////////////////////////////
3238      //
3239      // Private methods
3240      //
3241      /////////////////////////////////////////////////////////////////////////////
3242      
3243      /**
3244       * Initializer.
3245       *
3246       * @method _init
3247       * @param data {Object || Object[]} An object literal or an array of data.
3248       * @private
3249       */
3250      _init : function(data) {
3251          // Internal variables
3252          this._sId = Dom.generateId(null, "yui-rs");// "yui-rs" + widget.RecordSet._nCount;
3253          widget.RecordSet._nCount++;
3254          this._records = [];
3255          //this._length = 0;
3256  
3257          this._initEvents();
3258  
3259          if(data) {
3260              if(lang.isArray(data)) {
3261                  this.addRecords(data);
3262              }
3263              else if(lang.isObject(data)) {
3264                  this.addRecord(data);
3265              }
3266          }
3267  
3268          YAHOO.log("RecordSet initialized", "info", this.toString());
3269      },
3270      
3271      /**
3272       * Initializes custom events.
3273       *
3274       * @method _initEvents
3275       * @private
3276       */
3277      _initEvents : function() {
3278          this.createEvent("recordAddEvent");
3279          this.createEvent("recordsAddEvent");
3280          this.createEvent("recordSetEvent");
3281          this.createEvent("recordsSetEvent");
3282          this.createEvent("recordUpdateEvent");
3283          this.createEvent("recordDeleteEvent");
3284          this.createEvent("recordsDeleteEvent");
3285          this.createEvent("resetEvent");
3286          this.createEvent("recordValueUpdateEvent");
3287      },
3288  
3289      /**
3290       * Adds one Record to the RecordSet at the given index. If index is null,
3291       * then adds the Record to the end of the RecordSet.
3292       *
3293       * @method _addRecord
3294       * @param oData {Object} An object literal of data.
3295       * @param index {Number} (optional) Position index.
3296       * @return {YAHOO.widget.Record} A Record instance.
3297       * @private
3298       */
3299      _addRecord : function(oData, index) {
3300          var oRecord = new YAHOO.widget.Record(oData);
3301          
3302          if(YAHOO.lang.isNumber(index) && (index > -1)) {
3303              this._records.splice(index,0,oRecord);
3304          }
3305          else {
3306              //index = this.getLength();
3307              //this._records[index] = oRecord;
3308              this._records[this._records.length] = oRecord;
3309          }
3310          //this._length++;
3311          return oRecord;
3312      },
3313  
3314      /**
3315       * Sets/replaces one Record to the RecordSet at the given index.  Existing
3316       * Records with higher indexes are not shifted.  If no index specified, the
3317       * Record is added to the end of the RecordSet.
3318       *
3319       * @method _setRecord
3320       * @param oData {Object} An object literal of data.
3321       * @param index {Number} (optional) Position index.
3322       * @return {YAHOO.widget.Record} A Record instance.
3323       * @private
3324       */
3325      _setRecord : function(oData, index) {
3326          if (!lang.isNumber(index) || index < 0) {
3327              index = this._records.length;
3328          }
3329          return (this._records[index] = new widget.Record(oData));
3330          /*
3331          if(lang.isNumber(index) && (index > -1)) {
3332              this._records[index] = oRecord;
3333              if((index+1) > this.getLength()) {
3334                  this._length = index+1;
3335              }
3336          }
3337          else {
3338              this._records[this.getLength()] = oRecord;
3339              this._length++;
3340          }
3341          return oRecord;
3342          */
3343      },
3344  
3345      /**
3346       * Deletes Records from the RecordSet at the given index. If range is null,
3347       * then only one Record is deleted.
3348       *
3349       * @method _deleteRecord
3350       * @param index {Number} Position index.
3351       * @param range {Number} (optional) How many Records to delete
3352       * @private
3353       */
3354      _deleteRecord : function(index, range) {
3355          if(!lang.isNumber(range) || (range < 0)) {
3356              range = 1;
3357          }
3358          this._records.splice(index, range);
3359          //this._length = this._length - range;
3360      },
3361  
3362      /////////////////////////////////////////////////////////////////////////////
3363      //
3364      // Public methods
3365      //
3366      /////////////////////////////////////////////////////////////////////////////
3367  
3368      /**
3369       * Returns unique name of the RecordSet instance.
3370       *
3371       * @method getId
3372       * @return {String} Unique name of the RecordSet instance.
3373       */
3374      getId : function() {
3375          return this._sId;
3376      },
3377  
3378      /**
3379       * Public accessor to the unique name of the RecordSet instance.
3380       *
3381       * @method toString
3382       * @return {String} Unique name of the RecordSet instance.
3383       */
3384      toString : function() {
3385          return "RecordSet instance " + this._sId;
3386      },
3387  
3388      /**
3389       * Returns the number of Records held in the RecordSet.
3390       *
3391       * @method getLength
3392       * @return {Number} Number of records in the RecordSet.
3393       */
3394      getLength : function() {
3395              //return this._length;
3396              return this._records.length;
3397      },
3398  
3399      /**
3400       * Returns Record by ID or RecordSet position index.
3401       *
3402       * @method getRecord
3403       * @param record {YAHOO.widget.Record | Number | String} Record instance,
3404       * RecordSet position index, or Record ID.
3405       * @return {YAHOO.widget.Record} Record object.
3406       */
3407      getRecord : function(record) {
3408          var i;
3409          if(record instanceof widget.Record) {
3410              for(i=0; i<this._records.length; i++) {
3411                  if(this._records[i] && (this._records[i]._sId === record._sId)) {
3412                      return record;
3413                  }
3414              }
3415          }
3416          else if(lang.isNumber(record)) {
3417              if((record > -1) && (record < this.getLength())) {
3418                  return this._records[record];
3419              }
3420          }
3421          else if(lang.isString(record)) {
3422              for(i=0; i<this._records.length; i++) {
3423                  if(this._records[i] && (this._records[i]._sId === record)) {
3424                      return this._records[i];
3425                  }
3426              }
3427          }
3428          // Not a valid Record for this RecordSet
3429          return null;
3430  
3431      },
3432  
3433      /**
3434       * Returns an array of Records from the RecordSet.
3435       *
3436       * @method getRecords
3437       * @param index {Number} (optional) Recordset position index of which Record to
3438       * start at.
3439       * @param range {Number} (optional) Number of Records to get.
3440       * @return {YAHOO.widget.Record[]} Array of Records starting at given index and
3441       * length equal to given range. If index is not given, all Records are returned.
3442       */
3443      getRecords : function(index, range) {
3444          if(!lang.isNumber(index)) {
3445              return this._records;
3446          }
3447          if(!lang.isNumber(range)) {
3448              return this._records.slice(index);
3449          }
3450          return this._records.slice(index, index+range);
3451      },
3452  
3453      /**
3454       * Returns a boolean indicating whether Records exist in the RecordSet at the
3455       * specified index range.  Returns true if and only if a Record exists at each
3456       * index in the range.
3457       * @method hasRecords
3458       * @param index
3459       * @param range
3460       * @return {Boolean} true if all indices are populated in the RecordSet
3461       */
3462      hasRecords : function (index, range) {
3463          var recs = this.getRecords(index,range);
3464          for (var i = 0; i < range; ++i) {
3465              if (typeof recs[i] === 'undefined') {
3466                  return false;
3467              }
3468          }
3469          return true;
3470      },
3471  
3472      /**
3473       * Returns current position index for the given Record.
3474       *
3475       * @method getRecordIndex
3476       * @param oRecord {YAHOO.widget.Record} Record instance.
3477       * @return {Number} Record's RecordSet position index.
3478       */
3479  
3480      getRecordIndex : function(oRecord) {
3481          if(oRecord) {
3482              for(var i=this._records.length-1; i>-1; i--) {
3483                  if(this._records[i] && oRecord.getId() === this._records[i].getId()) {
3484                      return i;
3485                  }
3486              }
3487          }
3488          return null;
3489  
3490      },
3491  
3492      /**
3493       * Adds one Record to the RecordSet at the given index. If index is null,
3494       * then adds the Record to the end of the RecordSet.
3495       *
3496       * @method addRecord
3497       * @param oData {Object} An object literal of data.
3498       * @param index {Number} (optional) Position index.
3499       * @return {YAHOO.widget.Record} A Record instance.
3500       */
3501      addRecord : function(oData, index) {
3502          if(lang.isObject(oData)) {
3503              var oRecord = this._addRecord(oData, index);
3504              this.fireEvent("recordAddEvent",{record:oRecord,data:oData});
3505              YAHOO.log("Added Record at index " + index +
3506                      " with data " + lang.dump(oData), "info", this.toString());
3507              return oRecord;
3508          }
3509          else {
3510              YAHOO.log("Could not add Record with data" +
3511                      lang.dump(oData), "info", this.toString());
3512              return null;
3513          }
3514      },
3515  
3516      /**
3517       * Adds multiple Records at once to the RecordSet at the given index with the
3518       * given object literal data. If index is null, then the new Records are
3519       * added to the end of the RecordSet.
3520       *
3521       * @method addRecords
3522       * @param aData {Object[]} An object literal data or an array of data object literals.
3523       * @param index {Number} (optional) Position index.
3524       * @return {YAHOO.widget.Record[]} An array of Record instances.
3525       */
3526      addRecords : function(aData, index) {
3527          if(lang.isArray(aData)) {
3528              var newRecords = [],
3529                  idx,i,len;
3530  
3531              index = lang.isNumber(index) ? index : this._records.length;
3532              idx = index;
3533  
3534              // Can't go backwards bc we need to preserve order
3535              for(i=0,len=aData.length; i<len; ++i) {
3536                  if(lang.isObject(aData[i])) {
3537                      var record = this._addRecord(aData[i], idx++);
3538                      newRecords.push(record);
3539                  }
3540             }
3541              this.fireEvent("recordsAddEvent",{records:newRecords,data:aData});
3542              YAHOO.log("Added " + newRecords.length + " Record(s) at index " + index +
3543                      " with data " + lang.dump(aData), "info", this.toString());
3544             return newRecords;
3545          }
3546          else if(lang.isObject(aData)) {
3547              var oRecord = this._addRecord(aData);
3548              this.fireEvent("recordsAddEvent",{records:[oRecord],data:aData});
3549              YAHOO.log("Added 1 Record at index " + index +
3550                      " with data " + lang.dump(aData), "info", this.toString());
3551              return oRecord;
3552          }
3553          else {
3554              YAHOO.log("Could not add Records with data " +
3555                      lang.dump(aData), "info", this.toString());
3556              return null;
3557          }
3558      },
3559  
3560      /**
3561       * Sets or replaces one Record to the RecordSet at the given index. Unlike
3562       * addRecord, an existing Record at that index is not shifted to preserve it.
3563       * If no index is specified, it adds the Record to the end of the RecordSet.
3564       *
3565       * @method setRecord
3566       * @param oData {Object} An object literal of data.
3567       * @param index {Number} (optional) Position index.
3568       * @return {YAHOO.widget.Record} A Record instance.
3569       */
3570      setRecord : function(oData, index) {
3571          if(lang.isObject(oData)) {
3572              var oRecord = this._setRecord(oData, index);
3573              this.fireEvent("recordSetEvent",{record:oRecord,data:oData});
3574              YAHOO.log("Set Record at index " + index +
3575                      " with data " + lang.dump(oData), "info", this.toString());
3576              return oRecord;
3577          }
3578          else {
3579              YAHOO.log("Could not set Record with data" +
3580                      lang.dump(oData), "info", this.toString());
3581              return null;
3582          }
3583      },
3584  
3585      /**
3586       * Sets or replaces multiple Records at once to the RecordSet with the given
3587       * data, starting at the given index. If index is not specified, then the new
3588       * Records are added to the end of the RecordSet.
3589       *
3590       * @method setRecords
3591       * @param aData {Object[]} An array of object literal data.
3592       * @param index {Number} (optional) Position index.
3593       * @return {YAHOO.widget.Record[]} An array of Record instances.
3594       */
3595      setRecords : function(aData, index) {
3596          var Rec   = widget.Record,
3597              a     = lang.isArray(aData) ? aData : [aData],
3598              added = [],
3599              i = 0, l = a.length, j = 0;
3600  
3601          index = parseInt(index,10)|0;
3602  
3603          for(; i < l; ++i) {
3604              if (typeof a[i] === 'object' && a[i]) {
3605                  added[j++] = this._records[index + i] = new Rec(a[i]);
3606              }
3607          }
3608  
3609          this.fireEvent("recordsSetEvent",{records:added,data:aData});
3610          // Backward compatibility for bug 1918245
3611          this.fireEvent("recordsSet",{records:added,data:aData});
3612          YAHOO.log("Set "+j+" Record(s) at index "+index, "info",
3613                    this.toString());
3614  
3615          if (a.length && !added.length) {
3616              YAHOO.log("Could not set Records with data " +
3617                      lang.dump(aData), "info", this.toString());
3618          }
3619  
3620          return added;
3621      },
3622  
3623      /**
3624       * Updates given Record with given data.
3625       *
3626       * @method updateRecord
3627       * @param record {YAHOO.widget.Record | Number | String} A Record instance,
3628       * a RecordSet position index, or a Record ID.
3629       * @param oData {Object} Object literal of new data.
3630       * @return {YAHOO.widget.Record} Updated Record, or null.
3631       */
3632      updateRecord : function(record, oData) {
3633          var oRecord = this.getRecord(record);
3634          if(oRecord && lang.isObject(oData)) {
3635              // Copy data from the Record for the event that gets fired later
3636              var oldData = {};
3637              for(var key in oRecord._oData) {
3638                  if(lang.hasOwnProperty(oRecord._oData, key)) {
3639                      oldData[key] = oRecord._oData[key];
3640                  }
3641              }
3642              oRecord._oData = oData;
3643              this.fireEvent("recordUpdateEvent",{record:oRecord,newData:oData,oldData:oldData});
3644              YAHOO.log("Record at index " + this.getRecordIndex(oRecord) +
3645                      " updated with data " + lang.dump(oData), "info", this.toString());
3646              return oRecord;
3647          }
3648          else {
3649              YAHOO.log("Could not update Record " + record, "error", this.toString());
3650              return null;
3651          }
3652      },
3653  
3654      /**
3655       * @method updateKey
3656       * @deprecated Use updateRecordValue
3657       */
3658      updateKey : function(record, sKey, oData) {
3659          this.updateRecordValue(record, sKey, oData);
3660      },
3661      /**
3662       * Sets given Record at given key to given data.
3663       *
3664       * @method updateRecordValue
3665       * @param record {YAHOO.widget.Record | Number | String} A Record instance,
3666       * a RecordSet position index, or a Record ID.
3667       * @param sKey {String} Key name.
3668       * @param oData {Object} New data.
3669       */
3670      updateRecordValue : function(record, sKey, oData) {
3671          var oRecord = this.getRecord(record);
3672          if(oRecord) {
3673              var oldData = null;
3674              var keyValue = oRecord._oData[sKey];
3675              // Copy data from the Record for the event that gets fired later
3676              if(keyValue && lang.isObject(keyValue)) {
3677                  oldData = {};
3678                  for(var key in keyValue)  {
3679                      if(lang.hasOwnProperty(keyValue, key)) {
3680                          oldData[key] = keyValue[key];
3681                      }
3682                  }
3683              }
3684              // Copy by value
3685              else {
3686                  oldData = keyValue;
3687              }
3688  
3689              oRecord._oData[sKey] = oData;
3690              this.fireEvent("keyUpdateEvent",{record:oRecord,key:sKey,newData:oData,oldData:oldData});
3691              this.fireEvent("recordValueUpdateEvent",{record:oRecord,key:sKey,newData:oData,oldData:oldData});
3692              YAHOO.log("Key \"" + sKey +
3693                      "\" for Record at index " + this.getRecordIndex(oRecord) +
3694                      " updated to \"" + lang.dump(oData) + "\"", "info", this.toString());
3695          }
3696          else {
3697              YAHOO.log("Could not update key " + sKey + " for Record " + record, "error", this.toString());
3698          }
3699      },
3700  
3701      /**
3702       * Replaces all Records in RecordSet with new object literal data.
3703       *
3704       * @method replaceRecords
3705       * @param data {Object || Object[]} An object literal of data or an array of
3706       * data object literals.
3707       * @return {YAHOO.widget.Record || YAHOO.widget.Record[]} A Record instance or
3708       * an array of Records.
3709       */
3710      replaceRecords : function(data) {
3711          this.reset();
3712          return this.addRecords(data);
3713      },
3714  
3715      /**
3716       * Sorts all Records by given function. Records keep their unique IDs but will
3717       * have new RecordSet position indexes.
3718       *
3719       * @method sortRecords
3720       * @param fnSort {Function} Reference to a sort function.
3721       * @param desc {Boolean} True if sort direction is descending, false if sort
3722       * direction is ascending.
3723       * @param field {String} The field to sort by, from sortOptions.field
3724       * @return {YAHOO.widget.Record[]} Sorted array of Records.
3725       */
3726      sortRecords : function(fnSort, desc, field) {
3727          return this._records.sort(function(a, b) {return fnSort(a, b, desc, field);});
3728      },
3729  
3730      /**
3731       * Reverses all Records, so ["one", "two", "three"] becomes ["three", "two", "one"].
3732       *
3733       * @method reverseRecords
3734       * @return {YAHOO.widget.Record[]} Reverse-sorted array of Records.
3735       */
3736      reverseRecords : function() {
3737          return this._records.reverse();
3738      },
3739  
3740      /**
3741       * Removes the Record at the given position index from the RecordSet. If a range
3742       * is also provided, removes that many Records, starting from the index. Length
3743       * of RecordSet is correspondingly shortened.
3744       *
3745       * @method deleteRecord
3746       * @param index {Number} Record's RecordSet position index.
3747       * @return {Object} A copy of the data held by the deleted Record.
3748       */
3749      deleteRecord : function(index) {
3750          if(lang.isNumber(index) && (index > -1) && (index < this.getLength())) {
3751              var oData = this.getRecord(index).getData();
3752              
3753              this._deleteRecord(index);
3754              this.fireEvent("recordDeleteEvent",{data:oData,index:index});
3755              YAHOO.log("Record deleted at index " + index +
3756                      " and containing data " + lang.dump(oData), "info", this.toString());
3757              return oData;
3758          }
3759          else {
3760              YAHOO.log("Could not delete Record at index " + index, "error", this.toString());
3761              return null;
3762          }
3763      },
3764  
3765      /**
3766       * Removes the Record at the given position index from the RecordSet. If a range
3767       * is also provided, removes that many Records, starting from the index. Length
3768       * of RecordSet is correspondingly shortened.
3769       *
3770       * @method deleteRecords
3771       * @param index {Number} Record's RecordSet position index.
3772       * @param range {Number} (optional) How many Records to delete.
3773       * @return {Object[]} An array of copies of the data held by the deleted Records.     
3774       */
3775      deleteRecords : function(index, range) {
3776          if(!lang.isNumber(range)) {
3777              range = 1;
3778          }
3779          if(lang.isNumber(index) && (index > -1) && (index < this.getLength())) {
3780              var recordsToDelete = this.getRecords(index, range);
3781              var deletedData = [], // this mistakenly held Records, not data
3782                  deletedObjects = []; // this passes data only
3783              
3784              for(var i=0; i<recordsToDelete.length; i++) {
3785                  deletedData[deletedData.length] = recordsToDelete[i]; // backward compatibility
3786                  deletedObjects[deletedObjects.length] = recordsToDelete[i].getData();
3787              }
3788              this._deleteRecord(index, range);
3789  
3790              this.fireEvent("recordsDeleteEvent",{data:deletedData,deletedData:deletedObjects,index:index});
3791              YAHOO.log(range + "Record(s) deleted at index " + index +
3792                      " and containing data " + lang.dump(deletedObjects), "info", this.toString());
3793  
3794              return deletedData;
3795          }
3796          else {
3797              YAHOO.log("Could not delete Records at index " + index, "error", this.toString());
3798              return null;
3799          }
3800      },
3801  
3802      /**
3803       * Deletes all Records from the RecordSet.
3804       *
3805       * @method reset
3806       */
3807      reset : function() {
3808          this._records = [];
3809          //this._length = 0;
3810          this.fireEvent("resetEvent");
3811          YAHOO.log("All Records deleted from RecordSet", "info", this.toString());
3812      }
3813  };
3814  
3815  /////////////////////////////////////////////////////////////////////////////
3816  //
3817  // Custom Events
3818  //
3819  /////////////////////////////////////////////////////////////////////////////
3820  
3821  // RecordSet uses EventProvider
3822  lang.augmentProto(RS, util.EventProvider);
3823  
3824  /**
3825   * Fired when a new Record is added to the RecordSet.
3826   *
3827   * @event recordAddEvent
3828   * @param oArgs.record {YAHOO.widget.Record} The Record instance.
3829   * @param oArgs.data {Object} Data added.
3830   */
3831  
3832  /**
3833   * Fired when multiple Records are added to the RecordSet at once.
3834   *
3835   * @event recordsAddEvent
3836   * @param oArgs.records {YAHOO.widget.Record[]} An array of Record instances.
3837   * @param oArgs.data {Object[]} Data added.
3838   */
3839  
3840  /**
3841   * Fired when a Record is set in the RecordSet.
3842   *
3843   * @event recordSetEvent
3844   * @param oArgs.record {YAHOO.widget.Record} The Record instance.
3845   * @param oArgs.data {Object} Data added.
3846   */
3847  
3848  /**
3849   * Fired when multiple Records are set in the RecordSet at once.
3850   *
3851   * @event recordsSetEvent
3852   * @param oArgs.records {YAHOO.widget.Record[]} An array of Record instances.
3853   * @param oArgs.data {Object[]} Data added.
3854   */
3855  
3856  /**
3857   * Fired when a Record is updated with new data.
3858   *
3859   * @event recordUpdateEvent
3860   * @param oArgs.record {YAHOO.widget.Record} The Record instance.
3861   * @param oArgs.newData {Object} New data.
3862   * @param oArgs.oldData {Object} Old data.
3863   */
3864  
3865  /**
3866   * Fired when a Record is deleted from the RecordSet.
3867   *
3868   * @event recordDeleteEvent
3869   * @param oArgs.data {Object} The data held by the deleted Record,
3870   * or an array of data object literals if multiple Records were deleted at once.
3871   * @param oArgs.index {Object} Index of the deleted Record.
3872   */
3873  
3874  /**
3875   * Fired when multiple Records are deleted from the RecordSet at once.
3876   *
3877   * @event recordsDeleteEvent
3878   * @param oArgs.data {Object[]} An array of deleted Records.
3879   * @param oArgs.deletedData {Object[]} An array of deleted data.
3880   * @param oArgs.index {Object} Index of the first deleted Record.
3881   */
3882  
3883  /**
3884   * Fired when all Records are deleted from the RecordSet at once.
3885   *
3886   * @event resetEvent
3887   */
3888  
3889  /**
3890   * @event keyUpdateEvent    
3891   * @deprecated Use recordValueUpdateEvent     
3892   */
3893  
3894  /**
3895   * Fired when a Record value is updated with new data.
3896   *
3897   * @event recordValueUpdateEvent
3898   * @param oArgs.record {YAHOO.widget.Record} The Record instance.
3899   * @param oArgs.key {String} The updated key.
3900   * @param oArgs.newData {Object} New data.
3901   * @param oArgs.oldData {Object} Old data.
3902   *
3903   */
3904  
3905  
3906  /****************************************************************************/
3907  /****************************************************************************/
3908  /****************************************************************************/
3909  
3910  /**
3911   * The Record class defines a DataTable record.
3912   *
3913   * @namespace YAHOO.widget
3914   * @class Record
3915   * @constructor
3916   * @param oConfigs {Object} (optional) Object literal of key/value pairs.
3917   */
3918  YAHOO.widget.Record = function(oLiteral) {
3919      this._nCount = widget.Record._nCount;
3920      this._sId = Dom.generateId(null, "yui-rec");//"yui-rec" + this._nCount;
3921      widget.Record._nCount++;
3922      this._oData = {};
3923      if(lang.isObject(oLiteral)) {
3924          for(var sKey in oLiteral) {
3925              if(lang.hasOwnProperty(oLiteral, sKey)) {
3926                  this._oData[sKey] = oLiteral[sKey];
3927              }
3928          }
3929      }
3930  };
3931  
3932  /////////////////////////////////////////////////////////////////////////////
3933  //
3934  // Private member variables
3935  //
3936  /////////////////////////////////////////////////////////////////////////////
3937  
3938  /**
3939   * Internal class variable to give unique IDs to Record instances.
3940   *
3941   * @property Record._nCount
3942   * @type Number
3943   * @private
3944   */
3945  YAHOO.widget.Record._nCount = 0;
3946  
3947  YAHOO.widget.Record.prototype = {
3948      /**
3949       * Immutable unique count assigned at instantiation. Remains constant while a
3950       * Record's position index can change from sorting.
3951       *
3952       * @property _nCount
3953       * @type Number
3954       * @private
3955       */
3956      _nCount : null,
3957  
3958      /**
3959       * Immutable unique ID assigned at instantiation. Remains constant while a
3960       * Record's position index can change from sorting.
3961       *
3962       * @property _sId
3963       * @type String
3964       * @private
3965       */
3966      _sId : null,
3967  
3968      /**
3969       * Holds data for the Record in an object literal.
3970       *
3971       * @property _oData
3972       * @type Object
3973       * @private
3974       */
3975      _oData : null,
3976  
3977      /////////////////////////////////////////////////////////////////////////////
3978      //
3979      // Public member variables
3980      //
3981      /////////////////////////////////////////////////////////////////////////////
3982  
3983      /////////////////////////////////////////////////////////////////////////////
3984      //
3985      // Public methods
3986      //
3987      /////////////////////////////////////////////////////////////////////////////
3988  
3989      /**
3990       * Returns unique count assigned at instantiation.
3991       *
3992       * @method getCount
3993       * @return Number
3994       */
3995      getCount : function() {
3996          return this._nCount;
3997      },
3998  
3999      /**
4000       * Returns unique ID assigned at instantiation.
4001       *
4002       * @method getId
4003       * @return String
4004       */
4005      getId : function() {
4006          return this._sId;
4007      },
4008  
4009      /**
4010       * Returns data for the Record for a field if given, or the entire object
4011       * literal otherwise.
4012       *
4013       * @method getData
4014       * @param sField {String} (Optional) The field from which to retrieve data value.
4015       * @return Object
4016       */
4017      getData : function(sField) {
4018          if(lang.isString(sField)) {
4019              return this._oData[sField];
4020          }
4021          else {
4022              return this._oData;
4023          }
4024      },
4025  
4026      /**
4027       * Sets given data at the given key. Use the RecordSet method updateRecordValue to trigger
4028       * events. 
4029       *
4030       * @method setData
4031       * @param sKey {String} The key of the new value.
4032       * @param oData {MIXED} The new value.
4033       */
4034      setData : function(sKey, oData) {
4035          this._oData[sKey] = oData;
4036      }
4037  };
4038  
4039  })();
4040  
4041  (function () {
4042  
4043  var lang   = YAHOO.lang,
4044      util   = YAHOO.util,
4045      widget = YAHOO.widget,
4046      ua     = YAHOO.env.ua,
4047      
4048      Dom    = util.Dom,
4049      Ev     = util.Event,
4050      DS     = util.DataSourceBase;
4051  
4052  /**
4053   * The DataTable widget provides a progressively enhanced DHTML control for
4054   * displaying tabular data across A-grade browsers.
4055   *
4056   * @module datatable
4057   * @requires yahoo, dom, event, element, datasource
4058   * @optional dragdrop, dragdrop
4059   * @title DataTable Widget
4060   */
4061  
4062  /****************************************************************************/
4063  /****************************************************************************/
4064  /****************************************************************************/
4065  
4066  /**
4067   * DataTable class for the YUI DataTable widget.
4068   *
4069   * @namespace YAHOO.widget
4070   * @class DataTable
4071   * @extends YAHOO.util.Element
4072   * @constructor
4073   * @param elContainer {HTMLElement} Container element for the TABLE.
4074   * @param aColumnDefs {Object[]} Array of object literal Column definitions.
4075   * @param oDataSource {YAHOO.util.DataSource} DataSource instance.
4076   * @param oConfigs {object} (optional) Object literal of configuration values.
4077   */
4078  YAHOO.widget.DataTable = function(elContainer,aColumnDefs,oDataSource,oConfigs) {
4079      var DT = widget.DataTable;
4080      
4081      ////////////////////////////////////////////////////////////////////////////
4082      // Backward compatibility for SDT, but prevent infinite loops
4083      
4084      if(oConfigs && oConfigs.scrollable) {
4085          return new YAHOO.widget.ScrollingDataTable(elContainer,aColumnDefs,oDataSource,oConfigs);
4086      }
4087      
4088      ////////////////////////////////////////////////////////////////////////////
4089      // Initialization
4090  
4091      // Internal vars
4092      this._nIndex = DT._nCount;
4093      this._sId = Dom.generateId(null, "yui-dt");// "yui-dt"+this._nIndex;
4094      this._oChainRender = new YAHOO.util.Chain();
4095      this._oChainRender.subscribe("end",this._onRenderChainEnd, this, true);
4096  
4097      // Initialize configs
4098      this._initConfigs(oConfigs);
4099  
4100      // Initialize DataSource
4101      this._initDataSource(oDataSource);
4102      if(!this._oDataSource) {
4103          YAHOO.log("Could not instantiate DataTable due to an invalid DataSource", "error", this.toString());
4104          return;
4105      }
4106  
4107      // Initialize ColumnSet
4108      this._initColumnSet(aColumnDefs);
4109      if(!this._oColumnSet) {
4110          YAHOO.log("Could not instantiate DataTable due to an invalid ColumnSet", "error", this.toString());
4111          return;
4112      }
4113  
4114      // Initialize RecordSet
4115      this._initRecordSet();
4116      if(!this._oRecordSet) {
4117      }
4118  
4119      // Initialize Attributes
4120      DT.superclass.constructor.call(this, elContainer, this.configs);
4121  
4122      // Initialize DOM elements
4123      var okDom = this._initDomElements(elContainer);
4124      if(!okDom) {
4125          YAHOO.log("Could not instantiate DataTable due to an invalid DOM element", "error", this.toString());
4126          return;
4127      }
4128              
4129      // Show message as soon as config is available
4130      this.showTableMessage(this.get("MSG_LOADING"), DT.CLASS_LOADING);
4131      
4132      ////////////////////////////////////////////////////////////////////////////
4133      // Once per instance
4134      this._initEvents();
4135  
4136      DT._nCount++;
4137      DT._nCurrentCount++;
4138      
4139      ////////////////////////////////////////////////////////////////////////////
4140      // Data integration
4141  
4142      // Send a simple initial request
4143      var oCallback = {
4144          success : this.onDataReturnSetRows,
4145          failure : this.onDataReturnSetRows,
4146          scope   : this,
4147          argument: this.getState()
4148      };
4149      
4150      var initialLoad = this.get("initialLoad");
4151      if(initialLoad === true) {
4152          this._oDataSource.sendRequest(this.get("initialRequest"), oCallback);
4153      }
4154      // Do not send an initial request at all
4155      else if(initialLoad === false) {
4156          this.showTableMessage(this.get("MSG_EMPTY"), DT.CLASS_EMPTY);
4157      }
4158      // Send an initial request with a custom payload
4159      else {
4160          var oCustom = initialLoad || {};
4161          oCallback.argument = oCustom.argument || {};
4162          this._oDataSource.sendRequest(oCustom.request, oCallback);
4163      }
4164  };
4165  
4166  var DT = widget.DataTable;
4167  
4168  /////////////////////////////////////////////////////////////////////////////
4169  //
4170  // Public constants
4171  //
4172  /////////////////////////////////////////////////////////////////////////////
4173  
4174  lang.augmentObject(DT, {
4175  
4176      /**
4177       * Class name assigned to outer DataTable container.
4178       *
4179       * @property DataTable.CLASS_DATATABLE
4180       * @type String
4181       * @static
4182       * @final
4183       * @default "yui-dt"
4184       */
4185      CLASS_DATATABLE : "yui-dt",
4186  
4187      /**
4188       * Class name assigned to liner DIV elements.
4189       *
4190       * @property DataTable.CLASS_LINER
4191       * @type String
4192       * @static
4193       * @final
4194       * @default "yui-dt-liner"
4195       */
4196      CLASS_LINER : "yui-dt-liner",
4197  
4198      /**
4199       * Class name assigned to display label elements.
4200       *
4201       * @property DataTable.CLASS_LABEL
4202       * @type String
4203       * @static
4204       * @final
4205       * @default "yui-dt-label"
4206       */
4207      CLASS_LABEL : "yui-dt-label",
4208  
4209      /**
4210       * Class name assigned to messaging elements.
4211       *
4212       * @property DataTable.CLASS_MESSAGE
4213       * @type String
4214       * @static
4215       * @final
4216       * @default "yui-dt-message"
4217       */
4218      CLASS_MESSAGE : "yui-dt-message",
4219  
4220      /**
4221       * Class name assigned to mask element when DataTable is disabled.
4222       *
4223       * @property DataTable.CLASS_MASK
4224       * @type String
4225       * @static
4226       * @final
4227       * @default "yui-dt-mask"
4228       */
4229      CLASS_MASK : "yui-dt-mask",
4230  
4231      /**
4232       * Class name assigned to data elements.
4233       *
4234       * @property DataTable.CLASS_DATA
4235       * @type String
4236       * @static
4237       * @final
4238       * @default "yui-dt-data"
4239       */
4240      CLASS_DATA : "yui-dt-data",
4241  
4242      /**
4243       * Class name assigned to Column drag target.
4244       *
4245       * @property DataTable.CLASS_COLTARGET
4246       * @type String
4247       * @static
4248       * @final
4249       * @default "yui-dt-coltarget"
4250       */
4251      CLASS_COLTARGET : "yui-dt-coltarget",
4252  
4253      /**
4254       * Class name assigned to resizer handle elements.
4255       *
4256       * @property DataTable.CLASS_RESIZER
4257       * @type String
4258       * @static
4259       * @final
4260       * @default "yui-dt-resizer"
4261       */
4262      CLASS_RESIZER : "yui-dt-resizer",
4263  
4264      /**
4265       * Class name assigned to resizer liner elements.
4266       *
4267       * @property DataTable.CLASS_RESIZERLINER
4268       * @type String
4269       * @static
4270       * @final
4271       * @default "yui-dt-resizerliner"
4272       */
4273      CLASS_RESIZERLINER : "yui-dt-resizerliner",
4274  
4275      /**
4276       * Class name assigned to resizer proxy elements.
4277       *
4278       * @property DataTable.CLASS_RESIZERPROXY
4279       * @type String
4280       * @static
4281       * @final
4282       * @default "yui-dt-resizerproxy"
4283       */
4284      CLASS_RESIZERPROXY : "yui-dt-resizerproxy",
4285  
4286      /**
4287       * Class name assigned to CellEditor container elements.
4288       *
4289       * @property DataTable.CLASS_EDITOR
4290       * @type String
4291       * @static
4292       * @final
4293       * @default "yui-dt-editor"
4294       */
4295      CLASS_EDITOR : "yui-dt-editor",
4296  
4297      /**
4298       * Class name assigned to CellEditor container shim.
4299       *
4300       * @property DataTable.CLASS_EDITOR_SHIM
4301       * @type String
4302       * @static
4303       * @final
4304       * @default "yui-dt-editor-shim"
4305       */
4306      CLASS_EDITOR_SHIM : "yui-dt-editor-shim",
4307  
4308      /**
4309       * Class name assigned to paginator container elements.
4310       *
4311       * @property DataTable.CLASS_PAGINATOR
4312       * @type String
4313       * @static
4314       * @final
4315       * @default "yui-dt-paginator"
4316       */
4317      CLASS_PAGINATOR : "yui-dt-paginator",
4318  
4319      /**
4320       * Class name assigned to page number indicators.
4321       *
4322       * @property DataTable.CLASS_PAGE
4323       * @type String
4324       * @static
4325       * @final
4326       * @default "yui-dt-page"
4327       */
4328      CLASS_PAGE : "yui-dt-page",
4329  
4330      /**
4331       * Class name assigned to default indicators.
4332       *
4333       * @property DataTable.CLASS_DEFAULT
4334       * @type String
4335       * @static
4336       * @final
4337       * @default "yui-dt-default"
4338       */
4339      CLASS_DEFAULT : "yui-dt-default",
4340  
4341      /**
4342       * Class name assigned to previous indicators.
4343       *
4344       * @property DataTable.CLASS_PREVIOUS
4345       * @type String
4346       * @static
4347       * @final
4348       * @default "yui-dt-previous"
4349       */
4350      CLASS_PREVIOUS : "yui-dt-previous",
4351  
4352      /**
4353       * Class name assigned next indicators.
4354       *
4355       * @property DataTable.CLASS_NEXT
4356       * @type String
4357       * @static
4358       * @final
4359       * @default "yui-dt-next"
4360       */
4361      CLASS_NEXT : "yui-dt-next",
4362  
4363      /**
4364       * Class name assigned to first elements.
4365       *
4366       * @property DataTable.CLASS_FIRST
4367       * @type String
4368       * @static
4369       * @final
4370       * @default "yui-dt-first"
4371       */
4372      CLASS_FIRST : "yui-dt-first",
4373  
4374      /**
4375       * Class name assigned to last elements.
4376       *
4377       * @property DataTable.CLASS_LAST
4378       * @type String
4379       * @static
4380       * @final
4381       * @default "yui-dt-last"
4382       */
4383      CLASS_LAST : "yui-dt-last",
4384  
4385      /**
4386       * Class name assigned to Record elements.
4387       *
4388       * @property DataTable.CLASS_REC
4389       * @type String
4390       * @static
4391       * @final
4392       * @default "yui-dt-rec"
4393       */
4394      CLASS_REC : "yui-dt-rec",
4395  
4396      /**
4397       * Class name assigned to even elements.
4398       *
4399       * @property DataTable.CLASS_EVEN
4400       * @type String
4401       * @static
4402       * @final
4403       * @default "yui-dt-even"
4404       */
4405      CLASS_EVEN : "yui-dt-even",
4406  
4407      /**
4408       * Class name assigned to odd elements.
4409       *
4410       * @property DataTable.CLASS_ODD
4411       * @type String
4412       * @static
4413       * @final
4414       * @default "yui-dt-odd"
4415       */
4416      CLASS_ODD : "yui-dt-odd",
4417  
4418      /**
4419       * Class name assigned to selected elements.
4420       *
4421       * @property DataTable.CLASS_SELECTED
4422       * @type String
4423       * @static
4424       * @final
4425       * @default "yui-dt-selected"
4426       */
4427      CLASS_SELECTED : "yui-dt-selected",
4428  
4429      /**
4430       * Class name assigned to highlighted elements.
4431       *
4432       * @property DataTable.CLASS_HIGHLIGHTED
4433       * @type String
4434       * @static
4435       * @final
4436       * @default "yui-dt-highlighted"
4437       */
4438      CLASS_HIGHLIGHTED : "yui-dt-highlighted",
4439  
4440      /**
4441       * Class name assigned to hidden elements.
4442       *
4443       * @property DataTable.CLASS_HIDDEN
4444       * @type String
4445       * @static
4446       * @final
4447       * @default "yui-dt-hidden"
4448       */
4449      CLASS_HIDDEN : "yui-dt-hidden",
4450  
4451      /**
4452       * Class name assigned to disabled elements.
4453       *
4454       * @property DataTable.CLASS_DISABLED
4455       * @type String
4456       * @static
4457       * @final
4458       * @default "yui-dt-disabled"
4459       */
4460      CLASS_DISABLED : "yui-dt-disabled",
4461  
4462      /**
4463       * Class name assigned to empty indicators.
4464       *
4465       * @property DataTable.CLASS_EMPTY
4466       * @type String
4467       * @static
4468       * @final
4469       * @default "yui-dt-empty"
4470       */
4471      CLASS_EMPTY : "yui-dt-empty",
4472  
4473      /**
4474       * Class name assigned to loading indicatorx.
4475       *
4476       * @property DataTable.CLASS_LOADING
4477       * @type String
4478       * @static
4479       * @final
4480       * @default "yui-dt-loading"
4481       */
4482      CLASS_LOADING : "yui-dt-loading",
4483  
4484      /**
4485       * Class name assigned to error indicators.
4486       *
4487       * @property DataTable.CLASS_ERROR
4488       * @type String
4489       * @static
4490       * @final
4491       * @default "yui-dt-error"
4492       */
4493      CLASS_ERROR : "yui-dt-error",
4494  
4495      /**
4496       * Class name assigned to editable elements.
4497       *
4498       * @property DataTable.CLASS_EDITABLE
4499       * @type String
4500       * @static
4501       * @final
4502       * @default "yui-dt-editable"
4503       */
4504      CLASS_EDITABLE : "yui-dt-editable",
4505  
4506      /**
4507       * Class name assigned to draggable elements.
4508       *
4509       * @property DataTable.CLASS_DRAGGABLE
4510       * @type String
4511       * @static
4512       * @final
4513       * @default "yui-dt-draggable"
4514       */
4515      CLASS_DRAGGABLE : "yui-dt-draggable",
4516  
4517      /**
4518       * Class name assigned to resizeable elements.
4519       *
4520       * @property DataTable.CLASS_RESIZEABLE
4521       * @type String
4522       * @static
4523       * @final
4524       * @default "yui-dt-resizeable"
4525       */
4526      CLASS_RESIZEABLE : "yui-dt-resizeable",
4527  
4528      /**
4529       * Class name assigned to scrollable elements.
4530       *
4531       * @property DataTable.CLASS_SCROLLABLE
4532       * @type String
4533       * @static
4534       * @final
4535       * @default "yui-dt-scrollable"
4536       */
4537      CLASS_SCROLLABLE : "yui-dt-scrollable",
4538  
4539      /**
4540       * Class name assigned to sortable elements.
4541       *
4542       * @property DataTable.CLASS_SORTABLE
4543       * @type String
4544       * @static
4545       * @final
4546       * @default "yui-dt-sortable"
4547       */
4548      CLASS_SORTABLE : "yui-dt-sortable",
4549  
4550      /**
4551       * Class name assigned to ascending elements.
4552       *
4553       * @property DataTable.CLASS_ASC
4554       * @type String
4555       * @static
4556       * @final
4557       * @default "yui-dt-asc"
4558       */
4559      CLASS_ASC : "yui-dt-asc",
4560  
4561      /**
4562       * Class name assigned to descending elements.
4563       *
4564       * @property DataTable.CLASS_DESC
4565       * @type String
4566       * @static
4567       * @final
4568       * @default "yui-dt-desc"
4569       */
4570      CLASS_DESC : "yui-dt-desc",
4571  
4572      /**
4573       * Class name assigned to BUTTON elements and/or container elements.
4574       *
4575       * @property DataTable.CLASS_BUTTON
4576       * @type String
4577       * @static
4578       * @final
4579       * @default "yui-dt-button"
4580       */
4581      CLASS_BUTTON : "yui-dt-button",
4582  
4583      /**
4584       * Class name assigned to INPUT TYPE=CHECKBOX elements and/or container elements.
4585       *
4586       * @property DataTable.CLASS_CHECKBOX
4587       * @type String
4588       * @static
4589       * @final
4590       * @default "yui-dt-checkbox"
4591       */
4592      CLASS_CHECKBOX : "yui-dt-checkbox",
4593  
4594      /**
4595       * Class name assigned to SELECT elements and/or container elements.
4596       *
4597       * @property DataTable.CLASS_DROPDOWN
4598       * @type String
4599       * @static
4600       * @final
4601       * @default "yui-dt-dropdown"
4602       */
4603      CLASS_DROPDOWN : "yui-dt-dropdown",
4604  
4605      /**
4606       * Class name assigned to INPUT TYPE=RADIO elements and/or container elements.
4607       *
4608       * @property DataTable.CLASS_RADIO
4609       * @type String
4610       * @static
4611       * @final
4612       * @default "yui-dt-radio"
4613       */
4614      CLASS_RADIO : "yui-dt-radio",
4615  
4616      /////////////////////////////////////////////////////////////////////////
4617      //
4618      // Private static properties
4619      //
4620      /////////////////////////////////////////////////////////////////////////
4621  
4622      /**
4623       * Internal class variable for indexing multiple DataTable instances.
4624       *
4625       * @property DataTable._nCount
4626       * @type Number
4627       * @private
4628       * @static
4629       */
4630      _nCount : 0,
4631  
4632      /**
4633       * Internal class variable tracking current number of DataTable instances,
4634       * so that certain class values can be reset when all instances are destroyed.          
4635       *
4636       * @property DataTable._nCurrentCount
4637       * @type Number
4638       * @private
4639       * @static
4640       */
4641      _nCurrentCount : 0,
4642  
4643      /**
4644       * Reference to the STYLE node that is dynamically created and updated
4645       * in order to manage Column widths.
4646       *
4647       * @property DataTable._elDynStyleNode
4648       * @type HTMLElement
4649       * @private
4650       * @static     
4651       */
4652      _elDynStyleNode : null,
4653  
4654      /**
4655       * Set to true if _elDynStyleNode cannot be populated due to browser incompatibility.
4656       *
4657       * @property DataTable._bDynStylesFallback
4658       * @type boolean
4659       * @private
4660       * @static     
4661       */
4662      _bDynStylesFallback : (ua.ie) ? true : false,
4663  
4664      /**
4665       * Object literal hash of Columns and their dynamically create style rules.
4666       *
4667       * @property DataTable._oDynStyles
4668       * @type Object
4669       * @private
4670       * @static     
4671       */
4672      _oDynStyles : {},
4673  
4674      /////////////////////////////////////////////////////////////////////////
4675      //
4676      // Private static methods
4677      //
4678      /////////////////////////////////////////////////////////////////////////
4679  
4680      /**
4681       * Clones object literal or array of object literals.
4682       *
4683       * @method DataTable._cloneObject
4684       * @param o {Object} Object.
4685       * @private
4686       * @static     
4687       */
4688      _cloneObject: function(o) {
4689          if(!lang.isValue(o)) {
4690              return o;
4691          }
4692  
4693          var copy = {};
4694  
4695          if(o instanceof YAHOO.widget.BaseCellEditor) {
4696              copy = o;
4697          }
4698          else if(Object.prototype.toString.apply(o) === "[object RegExp]") {
4699              copy = o;
4700          }
4701          else if(lang.isFunction(o)) {
4702              copy = o;
4703          }
4704          else if(lang.isArray(o)) {
4705              var array = [];
4706              for(var i=0,len=o.length;i<len;i++) {
4707                  array[i] = DT._cloneObject(o[i]);
4708              }
4709              copy = array;
4710          }
4711          else if(lang.isObject(o)) {
4712              for (var x in o){
4713                  if(lang.hasOwnProperty(o, x)) {
4714                      if(lang.isValue(o[x]) && lang.isObject(o[x]) || lang.isArray(o[x])) {
4715                          copy[x] = DT._cloneObject(o[x]);
4716                      }
4717                      else {
4718                          copy[x] = o[x];
4719                      }
4720                  }
4721              }
4722          }
4723          else {
4724              copy = o;
4725          }
4726  
4727          return copy;
4728      },
4729  
4730      /**
4731       * Formats a BUTTON element.
4732       *
4733       * @method DataTable.formatButton
4734       * @param el {HTMLElement} The element to format with markup.
4735       * @param oRecord {YAHOO.widget.Record} Record instance.
4736       * @param oColumn {YAHOO.widget.Column} Column instance.
4737       * @param oData {HTML} Data value for the cell. By default, the value
4738       * is what gets written to the BUTTON. String values are treated as markup
4739       * and inserted into the DOM with innerHTML.
4740       * @param oDataTable {YAHOO.widget.DataTable} DataTable instance.
4741       * @static
4742       */
4743      formatButton : function(el, oRecord, oColumn, oData, oDataTable) {
4744          var sValue = lang.isValue(oData) ? oData : "Click";
4745          //TODO: support YAHOO.widget.Button
4746          //if(YAHOO.widget.Button) {
4747  
4748          //}
4749          //else {
4750              el.innerHTML = "<button type=\"button\" class=\""+
4751                      DT.CLASS_BUTTON + "\">" + sValue + "</button>";
4752          //}
4753      },
4754  
4755      /**
4756       * Formats a CHECKBOX element.
4757       *
4758       * @method DataTable.formatCheckbox
4759       * @param el {HTMLElement} The element to format with markup.
4760       * @param oRecord {YAHOO.widget.Record} Record instance.
4761       * @param oColumn {YAHOO.widget.Column} Column instance.
4762       * @param oData {Object | Boolean | HTML} Data value for the cell. Can be a simple
4763       * Boolean to indicate whether checkbox is checked or not. Can be object literal
4764       * {checked:bBoolean, label:sLabel}. String values are treated as markup
4765       * and inserted into the DOM with innerHTML.
4766       * @param oDataTable {YAHOO.widget.DataTable} DataTable instance.
4767       * @static
4768       */
4769      formatCheckbox : function(el, oRecord, oColumn, oData, oDataTable) {
4770          var bChecked = oData;
4771          bChecked = (bChecked) ? " checked=\"checked\"" : "";
4772          el.innerHTML = "<input type=\"checkbox\"" + bChecked +
4773                  " class=\"" + DT.CLASS_CHECKBOX + "\" />";
4774      },
4775  
4776      /**
4777       * Formats currency. Default unit is USD.
4778       *
4779       * @method DataTable.formatCurrency
4780       * @param el {HTMLElement} The element to format with markup.
4781       * @param oRecord {YAHOO.widget.Record} Record instance.
4782       * @param oColumn {YAHOO.widget.Column} Column instance.
4783       * @param oData {Number} Data value for the cell.
4784       * @param oDataTable {YAHOO.widget.DataTable} DataTable instance.
4785       * @static
4786       */
4787      formatCurrency : function(el, oRecord, oColumn, oData, oDataTable) {
4788          var oDT = oDataTable || this;
4789          el.innerHTML = util.Number.format(oData, oColumn.currencyOptions || oDT.get("currencyOptions"));
4790      },
4791  
4792      /**
4793       * Formats JavaScript Dates.
4794       *
4795       * @method DataTable.formatDate
4796       * @param el {HTMLElement} The element to format with markup.
4797       * @param oRecord {YAHOO.widget.Record} Record instance.
4798       * @param oColumn {YAHOO.widget.Column} Column instance.
4799       * @param oData {Object} Data value for the cell, or null. String values are
4800       * treated as markup and inserted into the DOM with innerHTML.
4801       * @param oDataTable {YAHOO.widget.DataTable} DataTable instance.
4802       * @static
4803       */
4804      formatDate : function(el, oRecord, oColumn, oData, oDataTable) {
4805          var oDT = oDataTable || this,
4806              oConfig = oColumn.dateOptions || oDT.get("dateOptions");
4807          el.innerHTML = util.Date.format(oData, oConfig, oConfig.locale);
4808      },
4809  
4810      /**
4811       * Formats SELECT elements.
4812       *
4813       * @method DataTable.formatDropdown
4814       * @param el {HTMLElement} The element to format with markup.
4815       * @param oRecord {YAHOO.widget.Record} Record instance.
4816       * @param oColumn {YAHOO.widget.Column} Column instance.
4817       * @param oData {Object} Data value for the cell, or null. String values may
4818       * be treated as markup and inserted into the DOM with innerHTML as element
4819       * label.
4820       * @param oDataTable {YAHOO.widget.DataTable} DataTable instance.
4821       * @static
4822       */
4823      formatDropdown : function(el, oRecord, oColumn, oData, oDataTable) {
4824          var oDT = oDataTable || this,
4825              selectedValue = (lang.isValue(oData)) ? oData : oRecord.getData(oColumn.field),
4826              options = (lang.isArray(oColumn.dropdownOptions)) ?
4827                  oColumn.dropdownOptions : null,
4828  
4829              selectEl,
4830              collection = el.getElementsByTagName("select");
4831  
4832          // Create the form element only once, so we can attach the onChange listener
4833          if(collection.length === 0) {
4834              // Create SELECT element
4835              selectEl = document.createElement("select");
4836              selectEl.className = DT.CLASS_DROPDOWN;
4837              selectEl = el.appendChild(selectEl);
4838  
4839              // Add event listener
4840              Ev.addListener(selectEl,"change",oDT._onDropdownChange,oDT);
4841          }
4842  
4843          selectEl = collection[0];
4844  
4845          // Update the form element
4846          if(selectEl) {
4847              // Clear out previous options
4848              selectEl.innerHTML = "";
4849  
4850              // We have options to populate
4851              if(options) {
4852                  // Create OPTION elements
4853                  for(var i=0; i<options.length; i++) {
4854                      var option = options[i];
4855                      var optionEl = document.createElement("option");
4856                      optionEl.value = (lang.isValue(option.value)) ?
4857                              option.value : option;
4858                      // Bug 2334323: Support legacy text, support label for consistency with DropdownCellEditor
4859                      optionEl.innerHTML = (lang.isValue(option.text)) ?
4860                              option.text : (lang.isValue(option.label)) ? option.label : option;
4861                      optionEl = selectEl.appendChild(optionEl);
4862                      if (optionEl.value == selectedValue) {
4863                          optionEl.selected = true;
4864                      }
4865                  }
4866              }
4867              // Selected value is our only option
4868              else {
4869                  selectEl.innerHTML = "<option selected value=\"" + selectedValue + "\">" + selectedValue + "</option>";
4870              }
4871          }
4872          else {
4873              el.innerHTML = lang.isValue(oData) ? oData : "";
4874          }
4875      },
4876  
4877      /**
4878       * Formats emails.
4879       *
4880       * @method DataTable.formatEmail
4881       * @param el {HTMLElement} The element to format with markup.
4882       * @param oRecord {YAHOO.widget.Record} Record instance.
4883       * @param oColumn {YAHOO.widget.Column} Column instance.
4884       * @param oData {String} Data value for the cell, or null. Values are
4885       * HTML-escaped.
4886       * @param oDataTable {YAHOO.widget.DataTable} DataTable instance.
4887       * @static
4888       */
4889      formatEmail : function(el, oRecord, oColumn, oData, oDataTable) {
4890          if(lang.isString(oData)) {
4891              oData = lang.escapeHTML(oData);
4892              el.innerHTML = "<a href=\"mailto:" + oData + "\">" + oData + "</a>";
4893          }
4894          else {
4895              el.innerHTML = lang.isValue(oData) ? lang.escapeHTML(oData.toString()) : "";
4896          }
4897      },
4898  
4899      /**
4900       * Formats links.
4901       *
4902       * @method DataTable.formatLink
4903       * @param el {HTMLElement} The element to format with markup.
4904       * @param oRecord {YAHOO.widget.Record} Record instance.
4905       * @param oColumn {YAHOO.widget.Column} Column instance.
4906       * @param oData {String} Data value for the cell, or null. Values are
4907       * HTML-escaped
4908       * @param oDataTable {YAHOO.widget.DataTable} DataTable instance.
4909       * @static
4910       */
4911      formatLink : function(el, oRecord, oColumn, oData, oDataTable) {
4912          if(lang.isString(oData)) {
4913              oData = lang.escapeHTML(oData);
4914              el.innerHTML = "<a href=\"" + oData + "\">" + oData + "</a>";
4915          }
4916          else {
4917              el.innerHTML = lang.isValue(oData) ? lang.escapeHTML(oData.toString()) : "";
4918          }
4919      },
4920  
4921      /**
4922       * Formats numbers.
4923       *
4924       * @method DataTable.formatNumber
4925       * @param el {HTMLElement} The element to format with markup.
4926       * @param oRecord {YAHOO.widget.Record} Record instance.
4927       * @param oColumn {YAHOO.widget.Column} Column instance.
4928       * @param oData {Object} Data value for the cell, or null.
4929       * @param oDataTable {YAHOO.widget.DataTable} DataTable instance.
4930       * @static
4931       */
4932      formatNumber : function(el, oRecord, oColumn, oData, oDataTable) {
4933          var oDT = oDataTable || this;
4934          el.innerHTML = util.Number.format(oData, oColumn.numberOptions || oDT.get("numberOptions"));
4935      },
4936  
4937      /**
4938       * Formats INPUT TYPE=RADIO elements.
4939       *
4940       * @method DataTable.formatRadio
4941       * @param el {HTMLElement} The element to format with markup.
4942       * @param oRecord {YAHOO.widget.Record} Record instance.
4943       * @param oColumn {YAHOO.widget.Column} Column instance.
4944       * @param oData {Object} (Optional) Data value for the cell.
4945       * @param oDataTable {YAHOO.widget.DataTable} DataTable instance.
4946       * @static
4947       */
4948      formatRadio : function(el, oRecord, oColumn, oData, oDataTable) {
4949          var oDT = oDataTable || this,
4950              bChecked = oData;
4951          bChecked = (bChecked) ? " checked=\"checked\"" : "";
4952          el.innerHTML = "<input type=\"radio\"" + bChecked +
4953                  " name=\""+oDT.getId()+"-col-" + oColumn.getSanitizedKey() + "\"" +
4954                  " class=\"" + DT.CLASS_RADIO+ "\" />";
4955      },
4956  
4957      /**
4958       * Formats text strings.
4959       *
4960       * @method DataTable.formatText
4961       * @param el {HTMLElement} The element to format with markup.
4962       * @param oRecord {YAHOO.widget.Record} Record instance.
4963       * @param oColumn {YAHOO.widget.Column} Column instance.
4964       * @param oData {String} (Optional) Data value for the cell. Values are
4965       * HTML-escaped.
4966       * @param oDataTable {YAHOO.widget.DataTable} DataTable instance.
4967       * @static
4968       */
4969      formatText : function(el, oRecord, oColumn, oData, oDataTable) {
4970          var value = (lang.isValue(oData)) ? oData : "";
4971          el.innerHTML = lang.escapeHTML(value.toString());
4972      },
4973  
4974      /**
4975       * Formats TEXTAREA elements.
4976       *
4977       * @method DataTable.formatTextarea
4978       * @param el {HTMLElement} The element to format with markup.
4979       * @param oRecord {YAHOO.widget.Record} Record instance.
4980       * @param oColumn {YAHOO.widget.Column} Column instance.
4981       * @param oData {Object} (Optional) Data value for the cell. Values are
4982       * HTML-escaped.
4983       * @param oDataTable {YAHOO.widget.DataTable} DataTable instance.
4984       * @static
4985       */
4986      formatTextarea : function(el, oRecord, oColumn, oData, oDataTable) {
4987          var value = (lang.isValue(oData)) ? lang.escapeHTML(oData.toString()) : "",
4988              markup = "<textarea>" + value + "</textarea>";
4989          el.innerHTML = markup;
4990      },
4991  
4992      /**
4993       * Formats INPUT TYPE=TEXT elements.
4994       *
4995       * @method DataTable.formatTextbox
4996       * @param el {HTMLElement} The element to format with markup.
4997       * @param oRecord {YAHOO.widget.Record} Record instance.
4998       * @param oColumn {YAHOO.widget.Column} Column instance.
4999       * @param oData {Object} (Optional) Data value for the cell. Values are
5000       * HTML-escaped.
5001       * @param oDataTable {YAHOO.widget.DataTable} DataTable instance.
5002       * @static
5003       */
5004      formatTextbox : function(el, oRecord, oColumn, oData, oDataTable) {
5005          var value = (lang.isValue(oData)) ? lang.escapeHTML(oData.toString()) : "",
5006              markup = "<input type=\"text\" value=\"" + value + "\" />";
5007          el.innerHTML = markup;
5008      },
5009  
5010      /**
5011       * Default cell formatter
5012       *
5013       * @method DataTable.formatDefault
5014       * @param el {HTMLElement} The element to format with markup.
5015       * @param oRecord {YAHOO.widget.Record} Record instance.
5016       * @param oColumn {YAHOO.widget.Column} Column instance.
5017       * @param oData {HTML} (Optional) Data value for the cell. String values are
5018       * treated as markup and inserted into the DOM with innerHTML.
5019       * @param oDataTable {YAHOO.widget.DataTable} DataTable instance.
5020       * @static
5021       */
5022      formatDefault : function(el, oRecord, oColumn, oData, oDataTable) {
5023          el.innerHTML = (lang.isValue(oData) && oData !== "") ? oData.toString() : "&#160;";
5024      },
5025  
5026      /**
5027       * Validates data value to type Number, doing type conversion as
5028       * necessary. A valid Number value is return, else null is returned
5029       * if input value does not validate.
5030       *
5031       *
5032       * @method DataTable.validateNumber
5033       * @param oData {Object} Data to validate.
5034       * @static
5035      */
5036      validateNumber : function(oData) {
5037          //Convert to number
5038          var number = oData * 1;
5039  
5040          // Validate
5041          if(lang.isNumber(number)) {
5042              return number;
5043          }
5044          else {
5045              YAHOO.log("Could not validate data " + lang.dump(oData) + " to type Number", "warn", this.toString());
5046              return undefined;
5047          }
5048      }
5049  });
5050  
5051  // Done in separate step so referenced functions are defined.
5052  /**
5053   * Registry of cell formatting functions, enables shortcut pointers in Column
5054   * definition formatter value (i.e., {key:"myColumn", formatter:"date"}).
5055   * @property DataTable.Formatter
5056   * @type Object
5057   * @static
5058   */
5059  DT.Formatter = {
5060      button   : DT.formatButton,
5061      checkbox : DT.formatCheckbox,
5062      currency : DT.formatCurrency,
5063      "date"   : DT.formatDate,
5064      dropdown : DT.formatDropdown,
5065      email    : DT.formatEmail,
5066      link     : DT.formatLink,
5067      "number" : DT.formatNumber,
5068      radio    : DT.formatRadio,
5069      text     : DT.formatText,
5070      textarea : DT.formatTextarea,
5071      textbox  : DT.formatTextbox,
5072  
5073      defaultFormatter : DT.formatDefault
5074  };
5075  
5076  lang.extend(DT, util.Element, {
5077  
5078  /////////////////////////////////////////////////////////////////////////////
5079  //
5080  // Superclass methods
5081  //
5082  /////////////////////////////////////////////////////////////////////////////
5083  
5084  /**
5085   * Implementation of Element's abstract method. Sets up config values.
5086   *
5087   * @method initAttributes
5088   * @param oConfigs {Object} (Optional) Object literal definition of configuration values.
5089   * @private
5090   */
5091  
5092  initAttributes : function(oConfigs) {
5093      oConfigs = oConfigs || {};
5094      DT.superclass.initAttributes.call(this, oConfigs);
5095  
5096      /**
5097      * @attribute summary
5098      * @description String value for the SUMMARY attribute.
5099      * @type String
5100      * @default ""    
5101      */
5102      this.setAttributeConfig("summary", {
5103          value: "",
5104          validator: lang.isString,
5105          method: function(sSummary) {
5106              if(this._elTable) {
5107                  this._elTable.summary = sSummary;
5108              }
5109          }
5110      });
5111  
5112      /**
5113      * @attribute selectionMode
5114      * @description Specifies row or cell selection mode. Accepts the following strings:
5115      *    <dl>
5116      *      <dt>"standard"</dt>
5117      *      <dd>Standard row selection with support for modifier keys to enable
5118      *      multiple selections.</dd>
5119      *
5120      *      <dt>"single"</dt>
5121      *      <dd>Row selection with modifier keys disabled to not allow
5122      *      multiple selections.</dd>
5123      *
5124      *      <dt>"singlecell"</dt>
5125      *      <dd>Cell selection with modifier keys disabled to not allow
5126      *      multiple selections.</dd>
5127      *
5128      *      <dt>"cellblock"</dt>
5129      *      <dd>Cell selection with support for modifier keys to enable multiple
5130      *      selections in a block-fashion, like a spreadsheet.</dd>
5131      *
5132      *      <dt>"cellrange"</dt>
5133      *      <dd>Cell selection with support for modifier keys to enable multiple
5134      *      selections in a range-fashion, like a calendar.</dd>
5135      *    </dl>
5136      *
5137      * @default "standard"
5138      * @type String
5139      */
5140      this.setAttributeConfig("selectionMode", {
5141          value: "standard",
5142          validator: lang.isString
5143      });
5144  
5145      /**
5146      * @attribute sortedBy
5147      * @description Object literal provides metadata for initial sort values if
5148      * data will arrive pre-sorted:
5149      * <dl>
5150      *     <dt>sortedBy.key</dt>
5151      *     <dd>{String} Key of sorted Column</dd>
5152      *     <dt>sortedBy.dir</dt>
5153      *     <dd>{String} Initial sort direction, either YAHOO.widget.DataTable.CLASS_ASC or YAHOO.widget.DataTable.CLASS_DESC</dd>
5154      * </dl>
5155      * @type Object | null
5156      */
5157      this.setAttributeConfig("sortedBy", {
5158          value: null,
5159          // TODO: accepted array for nested sorts
5160          validator: function(oNewSortedBy) {
5161              if(oNewSortedBy) {
5162                  return (lang.isObject(oNewSortedBy) && oNewSortedBy.key);
5163              }
5164              else {
5165                  return (oNewSortedBy === null);
5166              }
5167          },
5168          method: function(oNewSortedBy) {
5169              // Stash the previous value
5170              var oOldSortedBy = this.get("sortedBy");
5171              
5172              // Workaround for bug 1827195
5173              this._configs.sortedBy.value = oNewSortedBy;
5174  
5175              // Remove ASC/DESC from TH
5176              var oOldColumn,
5177                  nOldColumnKeyIndex,
5178                  oNewColumn,
5179                  nNewColumnKeyIndex;
5180                  
5181              if(this._elThead) {
5182                  if(oOldSortedBy && oOldSortedBy.key && oOldSortedBy.dir) {
5183                      oOldColumn = this._oColumnSet.getColumn(oOldSortedBy.key);
5184                      nOldColumnKeyIndex = oOldColumn.getKeyIndex();
5185                      
5186                      // Remove previous UI from THEAD
5187                      var elOldTh = oOldColumn.getThEl();
5188                      Dom.removeClass(elOldTh, oOldSortedBy.dir);
5189                      this.formatTheadCell(oOldColumn.getThLinerEl().firstChild, oOldColumn, oNewSortedBy);
5190                  }
5191                  if(oNewSortedBy) {
5192                      oNewColumn = (oNewSortedBy.column) ? oNewSortedBy.column : this._oColumnSet.getColumn(oNewSortedBy.key);
5193                      nNewColumnKeyIndex = oNewColumn.getKeyIndex();
5194      
5195                      // Update THEAD with new UI
5196                      var elNewTh = oNewColumn.getThEl();
5197                      // Backward compatibility
5198                      if(oNewSortedBy.dir && ((oNewSortedBy.dir == "asc") ||  (oNewSortedBy.dir == "desc"))) {
5199                          var newClass = (oNewSortedBy.dir == "desc") ?
5200                                  DT.CLASS_DESC :
5201                                  DT.CLASS_ASC;
5202                          Dom.addClass(elNewTh, newClass);
5203                      }
5204                      else {
5205                           var sortClass = oNewSortedBy.dir || DT.CLASS_ASC;
5206                           Dom.addClass(elNewTh, sortClass);
5207                      }
5208                      this.formatTheadCell(oNewColumn.getThLinerEl().firstChild, oNewColumn, oNewSortedBy);
5209                  }
5210              }
5211            
5212              if(this._elTbody) {
5213                  // Update TBODY UI
5214                  this._elTbody.style.display = "none";
5215                  var allRows = this._elTbody.rows,
5216                      allCells;
5217                  for(var i=allRows.length-1; i>-1; i--) {
5218                      allCells = allRows[i].childNodes;
5219                      if(allCells[nOldColumnKeyIndex]) {
5220                          Dom.removeClass(allCells[nOldColumnKeyIndex], oOldSortedBy.dir);
5221                      }
5222                      if(allCells[nNewColumnKeyIndex]) {
5223                          Dom.addClass(allCells[nNewColumnKeyIndex], oNewSortedBy.dir);
5224                      }
5225                  }
5226                  this._elTbody.style.display = "";
5227              }
5228                  
5229              this._clearTrTemplateEl();
5230          }
5231      });
5232      
5233      /**
5234      * @attribute paginator
5235      * @description An instance of YAHOO.widget.Paginator.
5236      * @default null
5237      * @type {Object|YAHOO.widget.Paginator}
5238      */
5239      this.setAttributeConfig("paginator", {
5240          value : null,
5241          validator : function (val) {
5242              return val === null || val instanceof widget.Paginator;
5243          },
5244          method : function () { this._updatePaginator.apply(this,arguments); }
5245      });
5246  
5247      /**
5248      * @attribute caption
5249      * @description Value for the CAPTION element. String values are treated as
5250      * markup and inserted into the DOM with innerHTML. NB: Not supported in
5251      * ScrollingDataTable.    
5252      * @type HTML
5253      */
5254      this.setAttributeConfig("caption", {
5255          value: null,
5256          validator: lang.isString,
5257          method: function(sCaption) {
5258              this._initCaptionEl(sCaption);
5259          }
5260      });
5261  
5262      /**
5263      * @attribute draggableColumns
5264      * @description True if Columns are draggable to reorder, false otherwise.
5265      * The Drag & Drop Utility is required to enable this feature. Only top-level
5266      * and non-nested Columns are draggable. Write once.
5267      * @default false
5268      * @type Boolean
5269      */
5270      this.setAttributeConfig("draggableColumns", {
5271          value: false,
5272          validator: lang.isBoolean,
5273          method: function(oParam) {
5274              if(this._elThead) {
5275                  if(oParam) {
5276                      this._initDraggableColumns();
5277                  }
5278                  else {
5279                      this._destroyDraggableColumns();
5280                  }
5281              }
5282          }
5283      });
5284  
5285      /**
5286      * @attribute renderLoopSize      
5287      * @description A value greater than 0 enables DOM rendering of rows to be
5288      * executed from a non-blocking timeout queue and sets how many rows to be
5289      * rendered per timeout. Recommended for very large data sets.     
5290      * @type Number      
5291      * @default 0      
5292      */      
5293       this.setAttributeConfig("renderLoopSize", {
5294           value: 0,
5295           validator: lang.isNumber
5296       });
5297  
5298      /**
5299      * @attribute sortFunction
5300      * @description Default Column sort function, receives the following args:
5301      *    <dl>
5302      *      <dt>a {Object}</dt>
5303      *      <dd>First sort argument.</dd>
5304      *      <dt>b {Object}</dt>
5305      *      <dd>Second sort argument.</dd>
5306  
5307      *      <dt>desc {Boolean}</dt>
5308      *      <dd>True if sort direction is descending, false if
5309      * sort direction is ascending.</dd>
5310      *      <dt>field {String}</dt>
5311      *      <dd>The field to sort by, from sortOptions.field</dd>
5312      *   </dl>
5313      * @type function
5314      */
5315      this.setAttributeConfig("sortFunction", {
5316          value: function(a, b, desc, field) {
5317              var compare = YAHOO.util.Sort.compare,
5318                  sorted = compare(a.getData(field),b.getData(field), desc);
5319              if(sorted === 0) {
5320                  return compare(a.getCount(),b.getCount(), desc); // Bug 1932978
5321              }
5322              else {
5323                  return sorted;
5324              }
5325          }
5326      });
5327  
5328      /**
5329      * @attribute formatRow
5330      * @description A function that accepts a TR element and its associated Record
5331      * for custom formatting. The function must return TRUE in order to automatically
5332      * continue formatting of child TD elements, else TD elements will not be
5333      * automatically formatted.
5334      * @type function
5335      * @default null
5336      */
5337      this.setAttributeConfig("formatRow", {
5338          value: null,
5339          validator: lang.isFunction
5340      });
5341  
5342      /**
5343      * @attribute generateRequest
5344      * @description A function that converts an object literal of desired DataTable
5345      * states into a request value which is then passed to the DataSource's
5346      * sendRequest method in order to retrieve data for those states. This
5347      * function is passed an object literal of state data and a reference to the
5348      * DataTable instance:
5349      *     
5350      * <dl>
5351      *   <dt>pagination<dt>
5352      *   <dd>        
5353      *         <dt>offsetRecord</dt>
5354      *         <dd>{Number} Index of the first Record of the desired page</dd>
5355      *         <dt>rowsPerPage</dt>
5356      *         <dd>{Number} Number of rows per page</dd>
5357      *   </dd>
5358      *   <dt>sortedBy</dt>
5359      *   <dd>                
5360      *         <dt>key</dt>
5361      *         <dd>{String} Key of sorted Column</dd>
5362      *         <dt>dir</dt>
5363      *         <dd>{String} Sort direction, either YAHOO.widget.DataTable.CLASS_ASC or YAHOO.widget.DataTable.CLASS_DESC</dd>
5364      *   </dd>
5365      *   <dt>self</dt>
5366      *   <dd>The DataTable instance</dd>
5367      * </dl>
5368      * 
5369      * and by default returns a String of syntax:
5370      * "sort={sortColumn}&dir={sortDir}&startIndex={pageStartIndex}&results={rowsPerPage}"
5371      * @type function
5372      * @default HTMLFunction
5373      */
5374      this.setAttributeConfig("generateRequest", {
5375          value: function(oState, oSelf) {
5376              // Set defaults
5377              oState = oState || {pagination:null, sortedBy:null};
5378              var sort = encodeURIComponent((oState.sortedBy) ? oState.sortedBy.key : oSelf.getColumnSet().keys[0].getKey());
5379              var dir = (oState.sortedBy && oState.sortedBy.dir === YAHOO.widget.DataTable.CLASS_DESC) ? "desc" : "asc";
5380              var startIndex = (oState.pagination) ? oState.pagination.recordOffset : 0;
5381              var results = (oState.pagination) ? oState.pagination.rowsPerPage : null;
5382              
5383              // Build the request
5384              return  "sort=" + sort +
5385                      "&dir=" + dir +
5386                      "&startIndex=" + startIndex +
5387                      ((results !== null) ? "&results=" + results : "");
5388          },
5389          validator: lang.isFunction
5390      });
5391  
5392      /**
5393      * @attribute initialRequest
5394      * @description Defines the initial request that gets sent to the DataSource
5395      * during initialization. Value is ignored if initialLoad is set to any value
5396      * other than true.    
5397      * @type MIXED
5398      * @default null
5399      */
5400      this.setAttributeConfig("initialRequest", {
5401          value: null
5402      });
5403  
5404      /**
5405      * @attribute initialLoad
5406      * @description Determines whether or not to load data at instantiation. By
5407      * default, will trigger a sendRequest() to the DataSource and pass in the
5408      * request defined by initialRequest. If set to false, data will not load
5409      * at instantiation. Alternatively, implementers who wish to work with a 
5410      * custom payload may pass in an object literal with the following values:
5411      *     
5412      *    <dl>
5413      *      <dt>request (MIXED)</dt>
5414      *      <dd>Request value.</dd>
5415      *
5416      *      <dt>argument (MIXED)</dt>
5417      *      <dd>Custom data that will be passed through to the callback function.</dd>
5418      *    </dl>
5419      *
5420      *                    
5421      * @type Boolean | Object
5422      * @default true
5423      */
5424      this.setAttributeConfig("initialLoad", {
5425          value: true
5426      });
5427      
5428      /**
5429      * @attribute dynamicData
5430      * @description If true, sorting and pagination are relegated to the DataSource
5431      * for handling, using the request returned by the "generateRequest" function.
5432      * Each new DataSource response blows away all previous Records. False by default, so 
5433      * sorting and pagination will be handled directly on the client side, without
5434      * causing any new requests for data from the DataSource.
5435      * @type Boolean
5436      * @default false
5437      */
5438      this.setAttributeConfig("dynamicData", {
5439          value: false,
5440          validator: lang.isBoolean
5441      });
5442  
5443      /**
5444       * @attribute MSG_EMPTY
5445       * @description Message to display if DataTable has no data. String
5446       * values are treated as markup and inserted into the DOM with innerHTML.
5447       * @type HTML
5448       * @default "No records found."
5449       */
5450       this.setAttributeConfig("MSG_EMPTY", {
5451           value: "No records found.",
5452           validator: lang.isString
5453       });      
5454  
5455      /**
5456       * @attribute MSG_LOADING
5457       * @description Message to display while DataTable is loading data. String
5458       * values are treated as markup and inserted into the DOM with innerHTML.
5459       * @type HTML
5460       * @default "Loading..."
5461       */      
5462       this.setAttributeConfig("MSG_LOADING", {
5463           value: "Loading...",
5464           validator: lang.isString
5465       });      
5466  
5467      /**
5468       * @attribute MSG_ERROR
5469       * @description Message to display while DataTable has data error. String
5470       * values are treated as markup and inserted into the DOM with innerHTML.
5471       * @type HTML
5472       * @default "Data error."
5473       */      
5474       this.setAttributeConfig("MSG_ERROR", {
5475           value: "Data error.",
5476           validator: lang.isString
5477       });
5478  
5479      /**
5480       * @attribute MSG_SORTASC
5481       * @description Message to display in tooltip to sort Column in ascending
5482       * order. String values are treated as markup and inserted into the DOM as
5483       * innerHTML.
5484       * @type HTML
5485       * @default "Click to sort ascending"
5486       */      
5487       this.setAttributeConfig("MSG_SORTASC", {      
5488           value: "Click to sort ascending",      
5489           validator: lang.isString,
5490           method: function(sParam) {
5491              if(this._elThead) {
5492                  for(var i=0, allKeys=this.getColumnSet().keys, len=allKeys.length; i<len; i++) {
5493                      if(allKeys[i].sortable && this.getColumnSortDir(allKeys[i]) === DT.CLASS_ASC) {
5494                          allKeys[i]._elThLabel.firstChild.title = sParam;
5495                      }
5496                  }
5497              }      
5498           }
5499       });
5500  
5501      /**
5502       * @attribute MSG_SORTDESC
5503       * @description Message to display in tooltip to sort Column in descending
5504       * order. String values are treated as markup and inserted into the DOM as
5505       * innerHTML.
5506       * @type HTML
5507       * @default "Click to sort descending"
5508       */      
5509       this.setAttributeConfig("MSG_SORTDESC", {      
5510           value: "Click to sort descending",      
5511           validator: lang.isString,
5512           method: function(sParam) {
5513              if(this._elThead) {
5514                  for(var i=0, allKeys=this.getColumnSet().keys, len=allKeys.length; i<len; i++) {
5515                      if(allKeys[i].sortable && this.getColumnSortDir(allKeys[i]) === DT.CLASS_DESC) {
5516                          allKeys[i]._elThLabel.firstChild.title = sParam;
5517                      }
5518                  }
5519              }               
5520           }
5521       });
5522       
5523      /**
5524       * @attribute currencySymbol
5525       * @deprecated Use currencyOptions.
5526       */
5527      this.setAttributeConfig("currencySymbol", {
5528          value: "$",
5529          validator: lang.isString
5530      });
5531      
5532      /**
5533       * Default config passed to YAHOO.util.Number.format() by the 'currency' Column formatter.
5534       * @attribute currencyOptions
5535       * @type Object
5536       * @default {prefix: $, decimalPlaces:2, decimalSeparator:".", thousandsSeparator:","}
5537       */
5538      this.setAttributeConfig("currencyOptions", {
5539          value: {
5540              prefix: this.get("currencySymbol"), // TODO: deprecate currencySymbol
5541              decimalPlaces:2,
5542              decimalSeparator:".",
5543              thousandsSeparator:","
5544          }
5545      });
5546      
5547      /**
5548       * Default config passed to YAHOO.util.Date.format() by the 'date' Column formatter.
5549       * @attribute dateOptions
5550       * @type Object
5551       * @default {format:"%m/%d/%Y", locale:"en"}
5552       */
5553      this.setAttributeConfig("dateOptions", {
5554          value: {format:"%m/%d/%Y", locale:"en"}
5555      });
5556      
5557      /**
5558       * Default config passed to YAHOO.util.Number.format() by the 'number' Column formatter.
5559       * @attribute numberOptions
5560       * @type Object
5561       * @default {decimalPlaces:0, thousandsSeparator:","}
5562       */
5563      this.setAttributeConfig("numberOptions", {
5564          value: {
5565              decimalPlaces:0,
5566              thousandsSeparator:","
5567          }
5568      });
5569  
5570  },
5571  
5572  /////////////////////////////////////////////////////////////////////////////
5573  //
5574  // Private member variables
5575  //
5576  /////////////////////////////////////////////////////////////////////////////
5577  
5578  /**
5579   * True if instance is initialized, so as to fire the initEvent after render.
5580   *
5581   * @property _bInit
5582   * @type Boolean
5583   * @default true
5584   * @private
5585   */
5586  _bInit : true,
5587  
5588  /**
5589   * Index assigned to instance.
5590   *
5591   * @property _nIndex
5592   * @type Number
5593   * @private
5594   */
5595  _nIndex : null,
5596  
5597  /**
5598   * Counter for IDs assigned to TR elements.
5599   *
5600   * @property _nTrCount
5601   * @type Number
5602   * @private
5603   */
5604  _nTrCount : 0,
5605  
5606  /**
5607   * Counter for IDs assigned to TD elements.
5608   *
5609   * @property _nTdCount
5610   * @type Number
5611   * @private
5612   */
5613  _nTdCount : 0,
5614  
5615  /**
5616   * Unique id assigned to instance "yui-dtN", useful prefix for generating unique
5617   * DOM ID strings and log messages.
5618   *
5619   * @property _sId
5620   * @type String
5621   * @private
5622   */
5623  _sId : null,
5624  
5625  /**
5626   * Render chain.
5627   *
5628   * @property _oChainRender
5629   * @type YAHOO.util.Chain
5630   * @private
5631   */
5632  _oChainRender : null,
5633  
5634  /**
5635   * DOM reference to the container element for the DataTable instance into which
5636   * all other elements get created.
5637   *
5638   * @property _elContainer
5639   * @type HTMLElement
5640   * @private
5641   */
5642  _elContainer : null,
5643  
5644  /**
5645   * DOM reference to the mask element for the DataTable instance which disables it.
5646   *
5647   * @property _elMask
5648   * @type HTMLElement
5649   * @private
5650   */
5651  _elMask : null,
5652  
5653  /**
5654   * DOM reference to the TABLE element for the DataTable instance.
5655   *
5656   * @property _elTable
5657   * @type HTMLElement
5658   * @private
5659   */
5660  _elTable : null,
5661  
5662  /**
5663   * DOM reference to the CAPTION element for the DataTable instance.
5664   *
5665   * @property _elCaption
5666   * @type HTMLElement
5667   * @private
5668   */
5669  _elCaption : null,
5670  
5671  /**
5672   * DOM reference to the COLGROUP element for the DataTable instance.
5673   *
5674   * @property _elColgroup
5675   * @type HTMLElement
5676   * @private
5677   */
5678  _elColgroup : null,
5679  
5680  /**
5681   * DOM reference to the THEAD element for the DataTable instance.
5682   *
5683   * @property _elThead
5684   * @type HTMLElement
5685   * @private
5686   */
5687  _elThead : null,
5688  
5689  /**
5690   * DOM reference to the primary TBODY element for the DataTable instance.
5691   *
5692   * @property _elTbody
5693   * @type HTMLElement
5694   * @private
5695   */
5696  _elTbody : null,
5697  
5698  /**
5699   * DOM reference to the secondary TBODY element used to display DataTable messages.
5700   *
5701   * @property _elMsgTbody
5702   * @type HTMLElement
5703   * @private
5704   */
5705  _elMsgTbody : null,
5706  
5707  /**
5708   * DOM reference to the secondary TBODY element's single TR element used to display DataTable messages.
5709   *
5710   * @property _elMsgTr
5711   * @type HTMLElement
5712   * @private
5713   */
5714  _elMsgTr : null,
5715  
5716  /**
5717   * DOM reference to the secondary TBODY element's single TD element used to display DataTable messages.
5718   *
5719   * @property _elMsgTd
5720   * @type HTMLElement
5721   * @private
5722   */
5723  _elMsgTd : null,
5724  
5725  /**
5726   * Element reference to shared Column drag target.
5727   *
5728   * @property _elColumnDragTarget
5729   * @type HTMLElement
5730   * @private
5731   */
5732  _elColumnDragTarget : null,
5733  
5734  /**
5735   * Element reference to shared Column resizer proxy.
5736   *
5737   * @property _elColumnResizerProxy
5738   * @type HTMLElement
5739   * @private
5740   */
5741  _elColumnResizerProxy : null,
5742  
5743  /**
5744   * DataSource instance for the DataTable instance.
5745   *
5746   * @property _oDataSource
5747   * @type YAHOO.util.DataSource
5748   * @private
5749   */
5750  _oDataSource : null,
5751  
5752  /**
5753   * ColumnSet instance for the DataTable instance.
5754   *
5755   * @property _oColumnSet
5756   * @type YAHOO.widget.ColumnSet
5757   * @private
5758   */
5759  _oColumnSet : null,
5760  
5761  /**
5762   * RecordSet instance for the DataTable instance.
5763   *
5764   * @property _oRecordSet
5765   * @type YAHOO.widget.RecordSet
5766   * @private
5767   */
5768  _oRecordSet : null,
5769  
5770  /**
5771   * The active CellEditor instance for the DataTable instance.
5772   *
5773   * @property _oCellEditor
5774   * @type YAHOO.widget.CellEditor
5775   * @private
5776   */
5777  _oCellEditor : null,
5778  
5779  /**
5780   * ID string of first TR element of the current DataTable page.
5781   *
5782   * @property _sFirstTrId
5783   * @type String
5784   * @private
5785   */
5786  _sFirstTrId : null,
5787  
5788  /**
5789   * ID string of the last TR element of the current DataTable page.
5790   *
5791   * @property _sLastTrId
5792   * @type String
5793   * @private
5794   */
5795  _sLastTrId : null,
5796  
5797  /**
5798   * Template row to create all new rows from.
5799   * @property _elTrTemplate
5800   * @type {HTMLElement}
5801   * @private 
5802   */
5803  _elTrTemplate : null,
5804  
5805  /**
5806   * Sparse array of custom functions to set column widths for browsers that don't
5807   * support dynamic CSS rules.  Functions are added at the index representing
5808   * the number of rows they update.
5809   *
5810   * @property _aDynFunctions
5811   * @type Array
5812   * @private
5813   */
5814  _aDynFunctions : [],
5815  
5816  /**
5817   * Disabled state.
5818   *
5819   * @property _disabled
5820   * @type Boolean
5821   * @private
5822   */
5823  _disabled : false,
5824  
5825  
5826  
5827  
5828  
5829  
5830  
5831  
5832  
5833  
5834  
5835  
5836  
5837  
5838  
5839  
5840  
5841  
5842  
5843  
5844  
5845  
5846  
5847  
5848  
5849  
5850  
5851  
5852  /////////////////////////////////////////////////////////////////////////////
5853  //
5854  // Private methods
5855  //
5856  /////////////////////////////////////////////////////////////////////////////
5857  
5858  /**
5859   * Clears browser text selection. Useful to call on rowSelectEvent or
5860   * cellSelectEvent to prevent clicks or dblclicks from selecting text in the
5861   * browser.
5862   *
5863   * @method clearTextSelection
5864   */
5865  clearTextSelection : function() {
5866      var sel;
5867      if(window.getSelection) {
5868          sel = window.getSelection();
5869      }
5870      else if(document.getSelection) {
5871          sel = document.getSelection();
5872      }
5873      else if(document.selection) {
5874          sel = document.selection;
5875      }
5876      if(sel) {
5877          if(sel.empty) {
5878              sel.empty();
5879          }
5880          else if (sel.removeAllRanges) {
5881              sel.removeAllRanges();
5882          }
5883          else if(sel.collapse) {
5884              sel.collapse();
5885          }
5886      }
5887  },
5888  
5889  /**
5890   * Sets focus on the given element.
5891   *
5892   * @method _focusEl
5893   * @param el {HTMLElement} Element.
5894   * @private
5895   */
5896  _focusEl : function(el) {
5897      el = el || this._elTbody;
5898      // http://developer.mozilla.org/en/docs/index.php?title=Key-navigable_custom_DHTML_widgets
5899      // The timeout is necessary in both IE and Firefox 1.5, to prevent scripts from doing
5900      // strange unexpected things as the user clicks on buttons and other controls.
5901      setTimeout(function() {
5902          try {
5903              el.focus();
5904          }
5905          catch(e) {
5906          }
5907      },0);
5908  },
5909  
5910  /**
5911   * Forces Gecko repaint.
5912   *
5913   * @method _repaintGecko
5914   * @el {HTMLElement} (Optional) Element to repaint, otherwise entire document body.
5915   * @private
5916   */
5917  _repaintGecko : (ua.gecko) ? 
5918      function(el) {
5919          el = el || this._elContainer;
5920          var parent = el.parentNode;
5921          var nextSibling = el.nextSibling;
5922          parent.insertBefore(parent.removeChild(el), nextSibling);
5923      } : function() {},
5924  
5925  /**
5926   * Forces Opera repaint.
5927   *
5928   * @method _repaintOpera
5929   * @private 
5930   */
5931  _repaintOpera : (ua.opera) ? 
5932      function() {
5933          if(ua.opera) {
5934              document.documentElement.className += " ";
5935              document.documentElement.className = YAHOO.lang.trim(document.documentElement.className);
5936          }
5937      } : function() {} ,
5938  
5939  /**
5940   * Forces Webkit repaint.
5941   *
5942   * @method _repaintWebkit
5943   * @el {HTMLElement} (Optional) Element to repaint, otherwise entire document body.
5944   * @private
5945   */
5946  _repaintWebkit : (ua.webkit) ? 
5947      function(el) {
5948          el = el || this._elContainer;
5949          var parent = el.parentNode;
5950          var nextSibling = el.nextSibling;
5951          parent.insertBefore(parent.removeChild(el), nextSibling);
5952      } : function() {},
5953  
5954  
5955  
5956  
5957  
5958  
5959  
5960  
5961  
5962  
5963  
5964  
5965  
5966  
5967  
5968  
5969  
5970  
5971  
5972  
5973  
5974  
5975  // INIT FUNCTIONS
5976  
5977  /**
5978   * Initializes object literal of config values.
5979   *
5980   * @method _initConfigs
5981   * @param oConfig {Object} Object literal of config values.
5982   * @private
5983   */
5984  _initConfigs : function(oConfigs) {
5985      if(!oConfigs || !lang.isObject(oConfigs)) {
5986          oConfigs = {};
5987      }
5988      this.configs = oConfigs;
5989  },
5990  
5991  /**
5992   * Initializes ColumnSet.
5993   *
5994   * @method _initColumnSet
5995   * @param aColumnDefs {Object[]} Array of object literal Column definitions.
5996   * @private
5997   */
5998  _initColumnSet : function(aColumnDefs) {
5999      var oColumn, i, len;
6000      
6001      if(this._oColumnSet) {
6002          // First clear _oDynStyles for existing ColumnSet and
6003          // uregister CellEditor Custom Events
6004          for(i=0, len=this._oColumnSet.keys.length; i<len; i++) {
6005              oColumn = this._oColumnSet.keys[i];
6006              DT._oDynStyles["."+this.getId()+"-col-"+oColumn.getSanitizedKey()+" ."+DT.CLASS_LINER] = undefined;
6007              if(oColumn.editor && oColumn.editor.unsubscribeAll) { // Backward compatibility
6008                  oColumn.editor.unsubscribeAll();
6009              }
6010          }
6011          
6012          this._oColumnSet = null;
6013          this._clearTrTemplateEl();
6014      }
6015      
6016      if(lang.isArray(aColumnDefs)) {
6017          this._oColumnSet =  new YAHOO.widget.ColumnSet(aColumnDefs);
6018      }
6019      // Backward compatibility
6020      else if(aColumnDefs instanceof YAHOO.widget.ColumnSet) {
6021          this._oColumnSet =  aColumnDefs;
6022          YAHOO.log("DataTable's constructor now requires an array" +
6023          " of object literal Column definitions instead of a ColumnSet instance",
6024          "warn", this.toString());
6025      }
6026  
6027      // Register CellEditor Custom Events
6028      var allKeys = this._oColumnSet.keys;
6029      for(i=0, len=allKeys.length; i<len; i++) {
6030          oColumn = allKeys[i];
6031          if(oColumn.editor && oColumn.editor.subscribe) { // Backward incompatibility
6032              oColumn.editor.subscribe("showEvent", this._onEditorShowEvent, this, true);
6033              oColumn.editor.subscribe("keydownEvent", this._onEditorKeydownEvent, this, true);
6034              oColumn.editor.subscribe("revertEvent", this._onEditorRevertEvent, this, true);
6035              oColumn.editor.subscribe("saveEvent", this._onEditorSaveEvent, this, true);
6036              oColumn.editor.subscribe("cancelEvent", this._onEditorCancelEvent, this, true);
6037              oColumn.editor.subscribe("blurEvent", this._onEditorBlurEvent, this, true);
6038              oColumn.editor.subscribe("blockEvent", this._onEditorBlockEvent, this, true);
6039              oColumn.editor.subscribe("unblockEvent", this._onEditorUnblockEvent, this, true);
6040          }
6041      }
6042  },
6043  
6044  /**
6045   * Initializes DataSource.
6046   *
6047   * @method _initDataSource
6048   * @param oDataSource {YAHOO.util.DataSource} DataSource instance.
6049   * @private
6050   */
6051  _initDataSource : function(oDataSource) {
6052      this._oDataSource = null;
6053      if(oDataSource && (lang.isFunction(oDataSource.sendRequest))) {
6054          this._oDataSource = oDataSource;
6055      }
6056      // Backward compatibility
6057      else {
6058          var tmpTable = null;
6059          var tmpContainer = this._elContainer;
6060          var i=0;
6061          //TODO: this will break if re-initing DS at runtime for SDT
6062          // Peek in container child nodes to see if TABLE already exists
6063          if(tmpContainer.hasChildNodes()) {
6064              var tmpChildren = tmpContainer.childNodes;
6065              for(i=0; i<tmpChildren.length; i++) {
6066                  if(tmpChildren[i].nodeName && tmpChildren[i].nodeName.toLowerCase() == "table") {
6067                      tmpTable = tmpChildren[i];
6068                      break;
6069                  }
6070              }
6071              if(tmpTable) {
6072                  var tmpFieldsArray = [];
6073                  for(; i<this._oColumnSet.keys.length; i++) {
6074                      tmpFieldsArray.push({key:this._oColumnSet.keys[i].key});
6075                  }
6076  
6077                  this._oDataSource = new DS(tmpTable);
6078                  this._oDataSource.responseType = DS.TYPE_HTMLTABLE;
6079                  this._oDataSource.responseSchema = {fields: tmpFieldsArray};
6080                  YAHOO.log("Null DataSource for progressive enhancement from" +
6081                  " markup has been deprecated", "warn", this.toString());
6082              }
6083          }
6084      }
6085  },
6086  
6087  /**
6088   * Initializes RecordSet.
6089   *
6090   * @method _initRecordSet
6091   * @private
6092   */
6093  _initRecordSet : function() {
6094      if(this._oRecordSet) {
6095          this._oRecordSet.reset();
6096      }
6097      else {
6098          this._oRecordSet = new YAHOO.widget.RecordSet();
6099      }
6100  },
6101  
6102  /**
6103   * Initializes DOM elements.
6104   *
6105   * @method _initDomElements
6106   * @param elContainer {HTMLElement | String} HTML DIV element by reference or ID. 
6107   * return {Boolean} False in case of error, otherwise true 
6108   * @private
6109   */
6110  _initDomElements : function(elContainer) {
6111      // Outer container
6112      this._initContainerEl(elContainer);
6113      // TABLE
6114      this._initTableEl(this._elContainer);
6115      // COLGROUP
6116      this._initColgroupEl(this._elTable);
6117      // THEAD
6118      this._initTheadEl(this._elTable);
6119      
6120      // Message TBODY
6121      this._initMsgTbodyEl(this._elTable);  
6122  
6123      // Primary TBODY
6124      this._initTbodyEl(this._elTable);
6125  
6126      if(!this._elContainer || !this._elTable || !this._elColgroup ||  !this._elThead || !this._elTbody || !this._elMsgTbody) {
6127          return false;
6128      }
6129      else {
6130          return true;
6131      }
6132  },
6133  
6134  /**
6135   * Destroy's the DataTable outer container element, if available.
6136   *
6137   * @method _destroyContainerEl
6138   * @param elContainer {HTMLElement} Reference to the container element. 
6139   * @private
6140   */
6141  _destroyContainerEl : function(elContainer) {
6142          var columns = this._oColumnSet.keys,
6143          elements, i;
6144  
6145          Dom.removeClass(elContainer, DT.CLASS_DATATABLE);
6146  
6147      // Bug 2528783
6148      Ev.purgeElement( elContainer );
6149      Ev.purgeElement( this._elThead, true ); // recursive to get resize handles
6150      Ev.purgeElement( this._elTbody );
6151      Ev.purgeElement( this._elMsgTbody );
6152  
6153      // because change doesn't bubble, each select (via formatDropdown) gets
6154      // its own subscription
6155      elements = elContainer.getElementsByTagName( 'select' );
6156  
6157      if ( elements.length ) {
6158          Ev.detachListener( elements, 'change' );
6159      }
6160  
6161      for ( i = columns.length - 1; i >= 0; --i ) {
6162          if ( columns[i].editor ) {
6163              Ev.purgeElement( columns[i].editor._elContainer );
6164          }
6165      }
6166  
6167      elContainer.innerHTML = "";
6168      
6169      this._elContainer = null;
6170      this._elColgroup = null;
6171      this._elThead = null;
6172      this._elTbody = null;
6173  },
6174  
6175  /**
6176   * Initializes the DataTable outer container element, including a mask.
6177   *
6178   * @method _initContainerEl
6179   * @param elContainer {HTMLElement | String} HTML DIV element by reference or ID.
6180   * @private
6181   */
6182  _initContainerEl : function(elContainer) {
6183      // Validate container
6184      elContainer = Dom.get(elContainer);
6185      
6186      if(elContainer && elContainer.nodeName && (elContainer.nodeName.toLowerCase() == "div")) {
6187          // Destroy previous
6188          this._destroyContainerEl(elContainer);
6189  
6190          Dom.addClass(elContainer, DT.CLASS_DATATABLE);
6191          Ev.addListener(elContainer, "focus", this._onTableFocus, this);
6192          Ev.addListener(elContainer, "dblclick", this._onTableDblclick, this);
6193          this._elContainer = elContainer;
6194          
6195          var elMask = document.createElement("div");
6196          elMask.className = DT.CLASS_MASK;
6197          elMask.style.display = "none";
6198          this._elMask = elContainer.appendChild(elMask);
6199      }
6200  },
6201  
6202  /**
6203   * Destroy's the DataTable TABLE element, if available.
6204   *
6205   * @method _destroyTableEl
6206   * @private
6207   */
6208  _destroyTableEl : function() {
6209      var elTable = this._elTable;
6210      if(elTable) {
6211          Ev.purgeElement(elTable, true);
6212          elTable.parentNode.removeChild(elTable);
6213          this._elCaption = null;
6214          this._elColgroup = null;
6215          this._elThead = null;
6216          this._elTbody = null;
6217      }
6218  },
6219  
6220  /**
6221   * Creates HTML markup CAPTION element.
6222   *
6223   * @method _initCaptionEl
6224   * @param sCaption {HTML} Caption value. String values are treated as markup and
6225   * inserted into the DOM with innerHTML.
6226   * @private
6227   */
6228  _initCaptionEl : function(sCaption) {
6229      if(this._elTable && sCaption) {
6230          // Create CAPTION element
6231          if(!this._elCaption) { 
6232              this._elCaption = this._elTable.createCaption();
6233          }
6234          // Set CAPTION value
6235          this._elCaption.innerHTML = sCaption;
6236      }
6237      else if(this._elCaption) {
6238          this._elCaption.parentNode.removeChild(this._elCaption);
6239      }
6240  },
6241  
6242  /**
6243   * Creates HTML markup for TABLE, COLGROUP, THEAD and TBODY elements in outer
6244   * container element.
6245   *
6246   * @method _initTableEl
6247   * @param elContainer {HTMLElement} Container element into which to create TABLE.
6248   * @private
6249   */
6250  _initTableEl : function(elContainer) {
6251      if(elContainer) {
6252          // Destroy previous
6253          this._destroyTableEl();
6254      
6255          // Create TABLE
6256          this._elTable = elContainer.appendChild(document.createElement("table"));  
6257           
6258          // Set SUMMARY attribute
6259          this._elTable.summary = this.get("summary");
6260          
6261          // Create CAPTION element
6262          if(this.get("caption")) {
6263              this._initCaptionEl(this.get("caption"));
6264          }
6265  
6266          // Set up mouseover/mouseout events via mouseenter/mouseleave delegation
6267          Ev.delegate(this._elTable, "mouseenter", this._onTableMouseover, "thead ."+DT.CLASS_LABEL, this);
6268          Ev.delegate(this._elTable, "mouseleave", this._onTableMouseout, "thead ."+DT.CLASS_LABEL, this);
6269          Ev.delegate(this._elTable, "mouseenter", this._onTableMouseover, "tbody.yui-dt-data>tr>td", this);
6270          Ev.delegate(this._elTable, "mouseleave", this._onTableMouseout, "tbody.yui-dt-data>tr>td", this);
6271          Ev.delegate(this._elTable, "mouseenter", this._onTableMouseover, "tbody.yui-dt-message>tr>td", this);
6272          Ev.delegate(this._elTable, "mouseleave", this._onTableMouseout, "tbody.yui-dt-message>tr>td", this);
6273      }
6274  },
6275  
6276  /**
6277   * Destroy's the DataTable COLGROUP element, if available.
6278   *
6279   * @method _destroyColgroupEl
6280   * @private
6281   */
6282  _destroyColgroupEl : function() {
6283      var elColgroup = this._elColgroup;
6284      if(elColgroup) {
6285          var elTable = elColgroup.parentNode;
6286          Ev.purgeElement(elColgroup, true);
6287          elTable.removeChild(elColgroup);
6288          this._elColgroup = null;
6289      }
6290  },
6291  
6292  /**
6293   * Initializes COLGROUP and COL elements for managing minWidth.
6294   *
6295   * @method _initColgroupEl
6296   * @param elTable {HTMLElement} TABLE element into which to create COLGROUP.
6297   * @private
6298   */
6299  _initColgroupEl : function(elTable) {
6300      if(elTable) {
6301          // Destroy previous
6302          this._destroyColgroupEl();
6303  
6304          // Add COLs to DOCUMENT FRAGMENT
6305          var allCols = this._aColIds || [],
6306              allKeys = this._oColumnSet.keys,
6307              i = 0, len = allCols.length,
6308              elCol, oColumn,
6309              elFragment = document.createDocumentFragment(),
6310              elColTemplate = document.createElement("col");
6311      
6312          for(i=0,len=allKeys.length; i<len; i++) {
6313              oColumn = allKeys[i];
6314              elCol = elFragment.appendChild(elColTemplate.cloneNode(false));
6315          }
6316      
6317          // Create COLGROUP
6318          var elColgroup = elTable.insertBefore(document.createElement("colgroup"), elTable.firstChild);
6319          elColgroup.appendChild(elFragment);
6320          this._elColgroup = elColgroup;
6321      }
6322  },
6323  
6324  /**
6325   * Adds a COL element to COLGROUP at given index.
6326   *
6327   * @method _insertColgroupColEl
6328   * @param index {Number} Index of new COL element.
6329   * @private
6330   */
6331  _insertColgroupColEl : function(index) {
6332      if(lang.isNumber(index)&& this._elColgroup) {
6333          var nextSibling = this._elColgroup.childNodes[index] || null;
6334          this._elColgroup.insertBefore(document.createElement("col"), nextSibling);
6335      }
6336  },
6337  
6338  /**
6339   * Removes a COL element to COLGROUP at given index.
6340   *
6341   * @method _removeColgroupColEl
6342   * @param index {Number} Index of removed COL element.
6343   * @private
6344   */
6345  _removeColgroupColEl : function(index) {
6346      if(lang.isNumber(index) && this._elColgroup && this._elColgroup.childNodes[index]) {
6347          this._elColgroup.removeChild(this._elColgroup.childNodes[index]);
6348      }
6349  },
6350  
6351  /**
6352   * Reorders a COL element from old index(es) to new index.
6353   *
6354   * @method _reorderColgroupColEl
6355   * @param aKeyIndexes {Number[]} Array of indexes of removed COL element.
6356   * @param newIndex {Number} New index. 
6357   * @private
6358   */
6359  _reorderColgroupColEl : function(aKeyIndexes, newIndex) {
6360      if(lang.isArray(aKeyIndexes) && lang.isNumber(newIndex) && this._elColgroup && (this._elColgroup.childNodes.length > aKeyIndexes[aKeyIndexes.length-1])) {
6361          var i,
6362              tmpCols = [];
6363          // Remove COL
6364          for(i=aKeyIndexes.length-1; i>-1; i--) {
6365              tmpCols.push(this._elColgroup.removeChild(this._elColgroup.childNodes[aKeyIndexes[i]]));
6366          }
6367          // Insert COL
6368          var nextSibling = this._elColgroup.childNodes[newIndex] || null;
6369          for(i=tmpCols.length-1; i>-1; i--) {
6370              this._elColgroup.insertBefore(tmpCols[i], nextSibling);
6371          }
6372      }
6373  },
6374  
6375  /**
6376   * Destroy's the DataTable THEAD element, if available.
6377   *
6378   * @method _destroyTheadEl
6379   * @private
6380   */
6381  _destroyTheadEl : function() {
6382      var elThead = this._elThead;
6383      if(elThead) {
6384          var elTable = elThead.parentNode;
6385          Ev.purgeElement(elThead, true);
6386          this._destroyColumnHelpers();
6387          elTable.removeChild(elThead);
6388          this._elThead = null;
6389      }
6390  },
6391  
6392  /**
6393   * Initializes THEAD element.
6394   *
6395   * @method _initTheadEl
6396   * @param elTable {HTMLElement} TABLE element into which to create COLGROUP.
6397   * @param {HTMLElement} Initialized THEAD element. 
6398   * @private
6399   */
6400  _initTheadEl : function(elTable) {
6401      elTable = elTable || this._elTable;
6402      
6403      if(elTable) {
6404          // Destroy previous
6405          this._destroyTheadEl();
6406      
6407          //TODO: append to DOM later for performance
6408          var elThead = (this._elColgroup) ?
6409              elTable.insertBefore(document.createElement("thead"), this._elColgroup.nextSibling) :
6410              elTable.appendChild(document.createElement("thead"));
6411      
6412          // Set up DOM events for THEAD
6413          Ev.addListener(elThead, "focus", this._onTheadFocus, this);
6414          Ev.addListener(elThead, "keydown", this._onTheadKeydown, this);
6415          Ev.addListener(elThead, "mousedown", this._onTableMousedown, this);
6416          Ev.addListener(elThead, "mouseup", this._onTableMouseup, this);
6417          Ev.addListener(elThead, "click", this._onTheadClick, this);
6418          
6419          // Bug 2528073: mouseover/mouseout handled via mouseenter/mouseleave
6420          // delegation at the TABLE level
6421  
6422          // Since we can't listen for click and dblclick on the same element...
6423          // Attach separately to THEAD and TBODY
6424          ///Ev.addListener(elThead, "dblclick", this._onTableDblclick, this);
6425          
6426         var oColumnSet = this._oColumnSet,
6427              oColumn, i,j, l;
6428          
6429          // Add TRs to the THEAD
6430          var colTree = oColumnSet.tree;
6431          var elTh;
6432          for(i=0; i<colTree.length; i++) {
6433              var elTheadTr = elThead.appendChild(document.createElement("tr"));
6434      
6435              // ...and create TH cells
6436              for(j=0; j<colTree[i].length; j++) {
6437                  oColumn = colTree[i][j];
6438                  elTh = elTheadTr.appendChild(document.createElement("th"));
6439                  this._initThEl(elTh,oColumn);
6440              }
6441      
6442                  // Set FIRST/LAST on THEAD rows
6443                  if(i === 0) {
6444                      Dom.addClass(elTheadTr, DT.CLASS_FIRST);
6445                  }
6446                  if(i === (colTree.length-1)) {
6447                      Dom.addClass(elTheadTr, DT.CLASS_LAST);
6448                  }
6449  
6450          }
6451  
6452          // Set FIRST/LAST on edge TH elements using the values in ColumnSet headers array
6453          var aFirstHeaders = oColumnSet.headers[0] || [];
6454          for(i=0; i<aFirstHeaders.length; i++) {
6455              Dom.addClass(Dom.get(this.getId() +"-th-"+aFirstHeaders[i]), DT.CLASS_FIRST);
6456          }
6457          var aLastHeaders = oColumnSet.headers[oColumnSet.headers.length-1] || [];
6458          for(i=0; i<aLastHeaders.length; i++) {
6459              Dom.addClass(Dom.get(this.getId() +"-th-"+aLastHeaders[i]), DT.CLASS_LAST);
6460          }
6461          
6462          YAHOO.log("TH cells for " + this._oColumnSet.keys.length + " keys created","info",this.toString());
6463  
6464          ///TODO: try _repaintGecko(this._elContainer) instead
6465          // Bug 1806891
6466          if(ua.webkit && ua.webkit < 420) {
6467              var oSelf = this;
6468              setTimeout(function() {
6469                  elThead.style.display = "";
6470              },0);
6471              elThead.style.display = 'none';
6472          }
6473          
6474          this._elThead = elThead;
6475          
6476          // Column helpers needs _elThead to exist
6477          this._initColumnHelpers();  
6478      }
6479  },
6480  
6481  /**
6482   * Populates TH element as defined by Column.
6483   *
6484   * @method _initThEl
6485   * @param elTh {HTMLElement} TH element reference.
6486   * @param oColumn {YAHOO.widget.Column} Column object.
6487   * @private
6488   */
6489  _initThEl : function(elTh, oColumn) {
6490      elTh.id = this.getId() + "-th-" + oColumn.getSanitizedKey(); // Needed for accessibility, getColumn by TH, and ColumnDD
6491      elTh.innerHTML = "";
6492      elTh.rowSpan = oColumn.getRowspan();
6493      elTh.colSpan = oColumn.getColspan();
6494      oColumn._elTh = elTh;
6495      
6496      var elThLiner = elTh.appendChild(document.createElement("div"));
6497      elThLiner.id = elTh.id + "-liner"; // Needed for resizer
6498      elThLiner.className = DT.CLASS_LINER;
6499      oColumn._elThLiner = elThLiner;
6500      
6501      var elThLabel = elThLiner.appendChild(document.createElement("span"));
6502      elThLabel.className = DT.CLASS_LABEL;    
6503  
6504      // Assign abbr attribute
6505      if(oColumn.abbr) {
6506          elTh.abbr = oColumn.abbr;
6507      }
6508      // Clear minWidth on hidden Columns
6509      if(oColumn.hidden) {
6510          this._clearMinWidth(oColumn);
6511      }
6512          
6513      elTh.className = this._getColumnClassNames(oColumn);
6514              
6515      // Set Column width...
6516      if(oColumn.width) {
6517          // Validate minWidth
6518          var nWidth = (oColumn.minWidth && (oColumn.width < oColumn.minWidth)) ?
6519                  oColumn.minWidth : oColumn.width;
6520          // ...for fallback cases
6521          if(DT._bDynStylesFallback) {
6522              elTh.firstChild.style.overflow = 'hidden';
6523              elTh.firstChild.style.width = nWidth + 'px';        
6524          }
6525          // ...for non fallback cases
6526          else {
6527              this._setColumnWidthDynStyles(oColumn, nWidth + 'px', 'hidden');
6528          }
6529      }
6530  
6531      this.formatTheadCell(elThLabel, oColumn, this.get("sortedBy"));
6532      oColumn._elThLabel = elThLabel;
6533  },
6534  
6535  /**
6536   * Outputs markup into the given TH based on given Column.
6537   *
6538   * @method formatTheadCell
6539   * @param elCellLabel {HTMLElement} The label SPAN element within the TH liner,
6540   * not the liner DIV element.     
6541   * @param oColumn {YAHOO.widget.Column} Column instance.
6542   * @param oSortedBy {Object} Sort state object literal.
6543  */
6544  formatTheadCell : function(elCellLabel, oColumn, oSortedBy) {
6545      var sKey = oColumn.getKey();
6546      var sLabel = lang.isValue(oColumn.label) ? oColumn.label : sKey;
6547  
6548      // Add accessibility link for sortable Columns
6549      if(oColumn.sortable) {
6550          // Calculate the direction
6551          var sSortClass = this.getColumnSortDir(oColumn, oSortedBy);
6552          var bDesc = (sSortClass === DT.CLASS_DESC);
6553  
6554          // This is the sorted Column
6555          if(oSortedBy && (oColumn.key === oSortedBy.key)) {
6556              bDesc = !(oSortedBy.dir === DT.CLASS_DESC);
6557          }
6558  
6559          // Generate a unique HREF for visited status
6560          var sHref = this.getId() + "-href-" + oColumn.getSanitizedKey();
6561          
6562          // Generate a dynamic TITLE for sort status
6563          var sTitle = (bDesc) ? this.get("MSG_SORTDESC") : this.get("MSG_SORTASC");
6564          
6565          // Format the element
6566          elCellLabel.innerHTML = "<a href=\"" + sHref + "\" title=\"" + sTitle + "\" class=\"" + DT.CLASS_SORTABLE + "\">" + sLabel + "</a>";
6567      }
6568      // Just display the label for non-sortable Columns
6569      else {
6570          elCellLabel.innerHTML = sLabel;
6571      }
6572  },
6573  
6574  /**
6575   * Disables DD from top-level Column TH elements.
6576   *
6577   * @method _destroyDraggableColumns
6578   * @private
6579   */
6580  _destroyDraggableColumns : function() {
6581      var oColumn, elTh;
6582      for(var i=0, len=this._oColumnSet.tree[0].length; i<len; i++) {
6583          oColumn = this._oColumnSet.tree[0][i];
6584          if(oColumn._dd) {
6585              oColumn._dd = oColumn._dd.unreg();
6586              Dom.removeClass(oColumn.getThEl(), DT.CLASS_DRAGGABLE);       
6587          }
6588      }
6589      
6590      // Destroy column drag proxy
6591      this._destroyColumnDragTargetEl();
6592  },
6593  
6594  /**
6595   * Initializes top-level Column TH elements into DD instances.
6596   *
6597   * @method _initDraggableColumns
6598   * @private
6599   */
6600  _initDraggableColumns : function() {
6601      this._destroyDraggableColumns();
6602      if(util.DD) {
6603          var oColumn, elTh, elDragTarget;
6604          for(var i=0, len=this._oColumnSet.tree[0].length; i<len; i++) {
6605              oColumn = this._oColumnSet.tree[0][i];
6606              elTh = oColumn.getThEl();
6607              Dom.addClass(elTh, DT.CLASS_DRAGGABLE);
6608              elDragTarget = this._initColumnDragTargetEl();
6609              oColumn._dd = new YAHOO.widget.ColumnDD(this, oColumn, elTh, elDragTarget);
6610          }
6611      }
6612      else {
6613          YAHOO.log("Could not find DragDrop for draggable Columns", "warn", this.toString());
6614      }
6615  },
6616  
6617  /**
6618   * Destroys shared Column drag target.
6619   *
6620   * @method _destroyColumnDragTargetEl
6621   * @private
6622   */
6623  _destroyColumnDragTargetEl : function() {
6624      if(this._elColumnDragTarget) {
6625          var el = this._elColumnDragTarget;
6626          YAHOO.util.Event.purgeElement(el);
6627          el.parentNode.removeChild(el);
6628          this._elColumnDragTarget = null;
6629      }
6630  },
6631  
6632  /**
6633   * Creates HTML markup for shared Column drag target.
6634   *
6635   * @method _initColumnDragTargetEl
6636   * @return {HTMLElement} Reference to Column drag target.
6637   * @private
6638   */
6639  _initColumnDragTargetEl : function() {
6640      if(!this._elColumnDragTarget) {
6641          // Attach Column drag target element as first child of body
6642          var elColumnDragTarget = document.createElement('div');
6643          elColumnDragTarget.id = this.getId() + "-coltarget";
6644          elColumnDragTarget.className = DT.CLASS_COLTARGET;
6645          elColumnDragTarget.style.display = "none";
6646          document.body.insertBefore(elColumnDragTarget, document.body.firstChild);
6647  
6648          // Internal tracker of Column drag target
6649          this._elColumnDragTarget = elColumnDragTarget;
6650  
6651      }
6652      return this._elColumnDragTarget;
6653  },
6654  
6655  /**
6656   * Disables resizeability on key Column TH elements.
6657   *
6658   * @method _destroyResizeableColumns
6659   * @private
6660   */
6661  _destroyResizeableColumns : function() {
6662      var aKeys = this._oColumnSet.keys;
6663      for(var i=0, len=aKeys.length; i<len; i++) {
6664          if(aKeys[i]._ddResizer) {
6665              aKeys[i]._ddResizer = aKeys[i]._ddResizer.unreg();
6666              Dom.removeClass(aKeys[i].getThEl(), DT.CLASS_RESIZEABLE);
6667          }
6668      }
6669  
6670      // Destroy resizer proxy
6671      this._destroyColumnResizerProxyEl();
6672  },
6673  
6674  /**
6675   * Initializes resizeability on key Column TH elements.
6676   *
6677   * @method _initResizeableColumns
6678   * @private
6679   */
6680  _initResizeableColumns : function() {
6681      this._destroyResizeableColumns();
6682      if(util.DD) {
6683          var oColumn, elTh, elThLiner, elThResizerLiner, elThResizer, elResizerProxy, cancelClick;
6684          for(var i=0, len=this._oColumnSet.keys.length; i<len; i++) {
6685              oColumn = this._oColumnSet.keys[i];
6686              if(oColumn.resizeable) {
6687                  elTh = oColumn.getThEl();
6688                  Dom.addClass(elTh, DT.CLASS_RESIZEABLE);
6689                  elThLiner = oColumn.getThLinerEl();
6690                  
6691                  // Bug 1915349: So resizer is as tall as TH when rowspan > 1
6692                  // Create a separate resizer liner with position:relative
6693                  elThResizerLiner = elTh.appendChild(document.createElement("div"));
6694                  elThResizerLiner.className = DT.CLASS_RESIZERLINER;
6695                  
6696                  // Move TH contents into the new resizer liner
6697                  elThResizerLiner.appendChild(elThLiner);
6698                  
6699                  // Create the resizer
6700                  elThResizer = elThResizerLiner.appendChild(document.createElement("div"));
6701                  elThResizer.id = elTh.id + "-resizer"; // Needed for ColumnResizer
6702                  elThResizer.className = DT.CLASS_RESIZER;
6703                  oColumn._elResizer = elThResizer;
6704  
6705                  // Create the resizer proxy, once per instance
6706                  elResizerProxy = this._initColumnResizerProxyEl();
6707                  oColumn._ddResizer = new YAHOO.util.ColumnResizer(
6708                          this, oColumn, elTh, elThResizer, elResizerProxy);
6709                  cancelClick = function(e) {
6710                      Ev.stopPropagation(e);
6711                  };
6712                  Ev.addListener(elThResizer,"click",cancelClick);
6713              }
6714          }
6715      }
6716      else {
6717          YAHOO.log("Could not find DragDrop for resizeable Columns", "warn", this.toString());
6718      }
6719  },
6720  
6721  /**
6722   * Destroys shared Column resizer proxy.
6723   *
6724   * @method _destroyColumnResizerProxyEl
6725   * @return {HTMLElement} Reference to Column resizer proxy.
6726   * @private
6727   */
6728  _destroyColumnResizerProxyEl : function() {
6729      if(this._elColumnResizerProxy) {
6730          var el = this._elColumnResizerProxy;
6731          YAHOO.util.Event.purgeElement(el);
6732          el.parentNode.removeChild(el);
6733          this._elColumnResizerProxy = null;
6734      }
6735  },
6736  
6737  /**
6738   * Creates HTML markup for shared Column resizer proxy.
6739   *
6740   * @method _initColumnResizerProxyEl
6741   * @return {HTMLElement} Reference to Column resizer proxy.
6742   * @private
6743   */
6744  _initColumnResizerProxyEl : function() {
6745      if(!this._elColumnResizerProxy) {
6746          // Attach Column resizer element as first child of body
6747          var elColumnResizerProxy = document.createElement("div");
6748          elColumnResizerProxy.id = this.getId() + "-colresizerproxy"; // Needed for ColumnResizer
6749          elColumnResizerProxy.className = DT.CLASS_RESIZERPROXY;
6750          document.body.insertBefore(elColumnResizerProxy, document.body.firstChild);
6751  
6752          // Internal tracker of Column resizer proxy
6753          this._elColumnResizerProxy = elColumnResizerProxy;
6754      }
6755      return this._elColumnResizerProxy;
6756  },
6757  
6758  /**
6759   * Destroys elements associated with Column functionality: ColumnDD and ColumnResizers.
6760   *
6761   * @method _destroyColumnHelpers
6762   * @private
6763   */
6764  _destroyColumnHelpers : function() {
6765      this._destroyDraggableColumns();
6766      this._destroyResizeableColumns();
6767  },
6768  
6769  /**
6770   * Initializes elements associated with Column functionality: ColumnDD and ColumnResizers.
6771   *
6772   * @method _initColumnHelpers
6773   * @private
6774   */
6775  _initColumnHelpers : function() {
6776      if(this.get("draggableColumns")) {
6777          this._initDraggableColumns();
6778      }
6779      this._initResizeableColumns();
6780  },
6781  
6782  /**
6783   * Destroy's the DataTable TBODY element, if available.
6784   *
6785   * @method _destroyTbodyEl
6786   * @private
6787   */
6788  _destroyTbodyEl : function() {
6789      var elTbody = this._elTbody;
6790      if(elTbody) {
6791          var elTable = elTbody.parentNode;
6792          Ev.purgeElement(elTbody, true);
6793          elTable.removeChild(elTbody);
6794          this._elTbody = null;
6795      }
6796  },
6797  
6798  /**
6799   * Initializes TBODY element for data.
6800   *
6801   * @method _initTbodyEl
6802   * @param elTable {HTMLElement} TABLE element into which to create TBODY .
6803   * @private
6804   */
6805  _initTbodyEl : function(elTable) {
6806      if(elTable) {
6807          // Destroy previous
6808          this._destroyTbodyEl();
6809          
6810          // Create TBODY
6811          var elTbody = elTable.appendChild(document.createElement("tbody"));
6812          elTbody.tabIndex = 0;
6813          elTbody.className = DT.CLASS_DATA;
6814      
6815          // Set up DOM events for TBODY
6816          Ev.addListener(elTbody, "focus", this._onTbodyFocus, this);
6817          Ev.addListener(elTbody, "mousedown", this._onTableMousedown, this);
6818          Ev.addListener(elTbody, "mouseup", this._onTableMouseup, this);
6819          Ev.addListener(elTbody, "keydown", this._onTbodyKeydown, this);
6820          Ev.addListener(elTbody, "click", this._onTbodyClick, this);
6821  
6822          // Bug 2528073: mouseover/mouseout handled via mouseenter/mouseleave
6823          // delegation at the TABLE level
6824  
6825          // Since we can't listen for click and dblclick on the same element...
6826          // Attach separately to THEAD and TBODY
6827          ///Ev.addListener(elTbody, "dblclick", this._onTableDblclick, this);
6828          
6829      
6830          // IE puts focus outline in the wrong place
6831          if(ua.ie) {
6832              elTbody.hideFocus=true;
6833          }
6834  
6835          this._elTbody = elTbody;
6836      }
6837  },
6838  
6839  /**
6840   * Destroy's the DataTable message TBODY element, if available.
6841   *
6842   * @method _destroyMsgTbodyEl
6843   * @private
6844   */
6845  _destroyMsgTbodyEl : function() {
6846      var elMsgTbody = this._elMsgTbody;
6847      if(elMsgTbody) {
6848          var elTable = elMsgTbody.parentNode;
6849          Ev.purgeElement(elMsgTbody, true);
6850          elTable.removeChild(elMsgTbody);
6851          this._elTbody = null;
6852      }
6853  },
6854  
6855  /**
6856   * Initializes TBODY element for messaging.
6857   *
6858   * @method _initMsgTbodyEl
6859   * @param elTable {HTMLElement} TABLE element into which to create TBODY 
6860   * @private
6861   */
6862  _initMsgTbodyEl : function(elTable) {
6863      if(elTable) {
6864          var elMsgTbody = document.createElement("tbody");
6865          elMsgTbody.className = DT.CLASS_MESSAGE;
6866          var elMsgTr = elMsgTbody.appendChild(document.createElement("tr"));
6867          elMsgTr.className = DT.CLASS_FIRST + " " + DT.CLASS_LAST;
6868          this._elMsgTr = elMsgTr;
6869          var elMsgTd = elMsgTr.appendChild(document.createElement("td"));
6870          elMsgTd.colSpan = this._oColumnSet.keys.length || 1;
6871          elMsgTd.className = DT.CLASS_FIRST + " " + DT.CLASS_LAST;
6872          this._elMsgTd = elMsgTd;
6873          elMsgTbody = elTable.insertBefore(elMsgTbody, this._elTbody);
6874          var elMsgLiner = elMsgTd.appendChild(document.createElement("div"));
6875          elMsgLiner.className = DT.CLASS_LINER;
6876          this._elMsgTbody = elMsgTbody;
6877  
6878          // Set up DOM events for TBODY
6879          Ev.addListener(elMsgTbody, "focus", this._onTbodyFocus, this);
6880          Ev.addListener(elMsgTbody, "mousedown", this._onTableMousedown, this);
6881          Ev.addListener(elMsgTbody, "mouseup", this._onTableMouseup, this);
6882          Ev.addListener(elMsgTbody, "keydown", this._onTbodyKeydown, this);
6883          Ev.addListener(elMsgTbody, "click", this._onTbodyClick, this);
6884  
6885          // Bug 2528073: mouseover/mouseout handled via mouseenter/mouseleave
6886          // delegation at the TABLE level
6887      }
6888  },
6889  
6890  /**
6891   * Initialize internal event listeners
6892   *
6893   * @method _initEvents
6894   * @private
6895   */
6896  _initEvents : function () {
6897      // Initialize Column sort
6898      this._initColumnSort();
6899          
6900      // Add the document level click listener
6901      YAHOO.util.Event.addListener(document, "click", this._onDocumentClick, this);
6902  
6903      // Paginator integration
6904      this.subscribe("paginatorChange",function () {
6905          this._handlePaginatorChange.apply(this,arguments);
6906      });
6907  
6908      this.subscribe("initEvent",function () {
6909          this.renderPaginator();
6910      });
6911  
6912      // Initialize CellEditor integration
6913      this._initCellEditing();
6914  },
6915  
6916  /**      
6917    * Initializes Column sorting.      
6918    *      
6919    * @method _initColumnSort      
6920    * @private      
6921    */      
6922  _initColumnSort : function() {
6923      this.subscribe("theadCellClickEvent", this.onEventSortColumn);      
6924  
6925      // Backward compatibility
6926      var oSortedBy = this.get("sortedBy");
6927      if(oSortedBy) {
6928          if(oSortedBy.dir == "desc") {
6929              this._configs.sortedBy.value.dir = DT.CLASS_DESC;
6930          }
6931          else if(oSortedBy.dir == "asc") {
6932              this._configs.sortedBy.value.dir = DT.CLASS_ASC;
6933          }
6934      }
6935  },
6936  
6937  /**      
6938    * Initializes CellEditor integration.      
6939    *      
6940    * @method _initCellEditing      
6941    * @private      
6942    */      
6943  _initCellEditing : function() {
6944      this.subscribe("editorBlurEvent",function () {
6945          this.onEditorBlurEvent.apply(this,arguments);
6946      });
6947      this.subscribe("editorBlockEvent",function () {
6948          this.onEditorBlockEvent.apply(this,arguments);
6949      });
6950      this.subscribe("editorUnblockEvent",function () {
6951          this.onEditorUnblockEvent.apply(this,arguments);
6952      });
6953  },
6954  
6955  
6956  
6957  
6958  
6959  
6960  
6961  
6962  
6963  
6964  
6965  
6966  
6967  
6968  
6969  
6970  
6971  
6972  
6973  
6974  
6975  
6976  
6977  
6978  
6979  
6980  
6981  
6982  
6983  
6984  
6985  
6986  
6987  // DOM MUTATION FUNCTIONS
6988  
6989  /**
6990   * Retruns classnames to represent current Column states.
6991   * @method _getColumnClassnames 
6992   * @param oColumn {YAHOO.widget.Column} Column instance.
6993   * @param aAddClasses {String[]} An array of additional classnames to add to the
6994   * return value.  
6995   * @return {String} A String of classnames to be assigned to TH or TD elements
6996   * for given Column.  
6997   * @private 
6998   */
6999  _getColumnClassNames : function (oColumn, aAddClasses) {
7000      var allClasses;
7001      
7002      // Add CSS classes
7003      if(lang.isString(oColumn.className)) {
7004          // Single custom class
7005          allClasses = [oColumn.className];
7006      }
7007      else if(lang.isArray(oColumn.className)) {
7008          // Array of custom classes
7009          allClasses = oColumn.className;
7010      }
7011      else {
7012          // no custom classes
7013          allClasses = [];
7014      }
7015      
7016      // Hook for setting width with via dynamic style uses key since ID is too disposable
7017      allClasses[allClasses.length] = this.getId() + "-col-" +oColumn.getSanitizedKey();
7018  
7019      // Column key - minus any chars other than "A-Z", "a-z", "0-9", "_", "-", ".", or ":"
7020      allClasses[allClasses.length] = "yui-dt-col-" +oColumn.getSanitizedKey();
7021  
7022      var isSortedBy = this.get("sortedBy") || {};
7023      // Sorted
7024      if(oColumn.key === isSortedBy.key) {
7025          allClasses[allClasses.length] = isSortedBy.dir || '';
7026      }
7027      // Hidden
7028      if(oColumn.hidden) {
7029          allClasses[allClasses.length] = DT.CLASS_HIDDEN;
7030      }
7031      // Selected
7032      if(oColumn.selected) {
7033          allClasses[allClasses.length] = DT.CLASS_SELECTED;
7034      }
7035      // Sortable
7036      if(oColumn.sortable) {
7037          allClasses[allClasses.length] = DT.CLASS_SORTABLE;
7038      }
7039      // Resizeable
7040      if(oColumn.resizeable) {
7041          allClasses[allClasses.length] = DT.CLASS_RESIZEABLE;
7042      }
7043      // Editable
7044      if(oColumn.editor) {
7045          allClasses[allClasses.length] = DT.CLASS_EDITABLE;
7046      }
7047      
7048      // Addtnl classes, including First/Last
7049      if(aAddClasses) {
7050          allClasses = allClasses.concat(aAddClasses);
7051      }
7052      
7053      return allClasses.join(' ');  
7054  },
7055  
7056  /**
7057   * Clears TR element template in response to any Column state change.
7058   * @method _clearTrTemplateEl
7059   * @private 
7060   */
7061  _clearTrTemplateEl : function () {
7062      this._elTrTemplate = null;
7063  },
7064  
7065  /**
7066   * Returns a new TR element template with TD elements classed with current
7067   * Column states.
7068   * @method _getTrTemplateEl 
7069   * @return {HTMLElement} A TR element to be cloned and added to the DOM.
7070   * @private 
7071   */
7072  _getTrTemplateEl : function (oRecord, index) {
7073      // Template is already available
7074      if(this._elTrTemplate) {
7075          return this._elTrTemplate;
7076      }
7077      // Template needs to be created
7078      else {
7079          var d   = document,
7080              tr  = d.createElement('tr'),
7081              td  = d.createElement('td'),
7082              div = d.createElement('div');
7083      
7084          // Append the liner element
7085          td.appendChild(div);
7086  
7087          // Create TD elements into DOCUMENT FRAGMENT
7088          var df = document.createDocumentFragment(),
7089              allKeys = this._oColumnSet.keys,
7090              elTd;
7091  
7092          // Set state for each TD;
7093          var aAddClasses;
7094          for(var i=0, keysLen=allKeys.length; i<keysLen; i++) {
7095              // Clone the TD template
7096              elTd = td.cloneNode(true);
7097  
7098              // Format the base TD
7099              elTd = this._formatTdEl(allKeys[i], elTd, i, (i===keysLen-1));
7100                          
7101              df.appendChild(elTd);
7102          }
7103          tr.appendChild(df);
7104          tr.className = DT.CLASS_REC;
7105          this._elTrTemplate = tr;
7106          return tr;
7107      }   
7108  },
7109  
7110  /**
7111   * Formats a basic TD element.
7112   * @method _formatTdEl 
7113   * @param oColumn {YAHOO.widget.Column} Associated Column instance. 
7114   * @param elTd {HTMLElement} An unformatted TD element.
7115   * @param index {Number} Column key index. 
7116   * @param isLast {Boolean} True if Column is last key of the ColumnSet.
7117   * @return {HTMLElement} A formatted TD element.
7118   * @private 
7119   */
7120  _formatTdEl : function (oColumn, elTd, index, isLast) {
7121      var oColumnSet = this._oColumnSet;
7122      
7123      // Set the TD's accessibility headers
7124      var allHeaders = oColumnSet.headers,
7125          allColHeaders = allHeaders[index],
7126          sTdHeaders = "",
7127          sHeader;
7128      for(var j=0, headersLen=allColHeaders.length; j < headersLen; j++) {
7129          sHeader = this._sId + "-th-" + allColHeaders[j] + ' ';
7130          sTdHeaders += sHeader;
7131      }
7132      elTd.headers = sTdHeaders;
7133      
7134      // Class the TD element
7135      var aAddClasses = [];
7136      if(index === 0) {
7137          aAddClasses[aAddClasses.length] = DT.CLASS_FIRST;
7138      }
7139      if(isLast) {
7140          aAddClasses[aAddClasses.length] = DT.CLASS_LAST;
7141      }
7142      elTd.className = this._getColumnClassNames(oColumn, aAddClasses);
7143  
7144      // Class the liner element
7145      elTd.firstChild.className = DT.CLASS_LINER;
7146  
7147      // Set Column width for fallback cases
7148      if(oColumn.width && DT._bDynStylesFallback) {
7149          // Validate minWidth
7150          var nWidth = (oColumn.minWidth && (oColumn.width < oColumn.minWidth)) ?
7151                  oColumn.minWidth : oColumn.width;
7152          elTd.firstChild.style.overflow = 'hidden';
7153          elTd.firstChild.style.width = nWidth + 'px';
7154      }
7155      
7156      return elTd;
7157  },
7158  
7159  
7160  /**
7161   * Create a new TR element for a given Record and appends it with the correct
7162   * number of Column-state-classed TD elements. Striping is the responsibility of
7163   * the calling function, which may decide to stripe the single row, a subset of
7164   * rows, or all the rows.
7165   * @method _createTrEl
7166   * @param oRecord {YAHOO.widget.Record} Record instance
7167   * @return {HTMLElement} The new TR element.  This must be added to the DOM.
7168   * @private 
7169   */
7170  _addTrEl : function (oRecord) {
7171      var elTrTemplate = this._getTrTemplateEl();
7172      
7173      // Clone the TR template.
7174      var elTr = elTrTemplate.cloneNode(true);
7175      
7176      // Populate content
7177      return this._updateTrEl(elTr,oRecord);
7178  },
7179  
7180  /**
7181   * Formats the contents of the given TR's TD elements with data from the given
7182   * Record. Only innerHTML should change, nothing structural.
7183   *
7184   * @method _updateTrEl
7185   * @param elTr {HTMLElement} The TR element to update.
7186   * @param oRecord {YAHOO.widget.Record} The associated Record instance.
7187   * @return {HTMLElement} DOM reference to the new TR element.
7188   * @private
7189   */
7190  _updateTrEl : function(elTr, oRecord) {
7191      var ok = this.get("formatRow") ? this.get("formatRow").call(this, elTr, oRecord) : true;
7192      if(ok) {
7193          // Hide the row to prevent constant reflows
7194          elTr.style.display = 'none';
7195          
7196          // Update TD elements with new data
7197          var allTds = elTr.childNodes,
7198              elTd;
7199          for(var i=0,len=allTds.length; i<len; ++i) {
7200              elTd = allTds[i];
7201              
7202              // Set the cell content
7203              this.formatCell(allTds[i].firstChild, oRecord, this._oColumnSet.keys[i]);
7204          }
7205          
7206          // Redisplay the row for reflow
7207          elTr.style.display = '';
7208      }
7209      
7210       // Record-to-TR association and tracking of FIRST/LAST
7211      var oldId = elTr.id,
7212          newId = oRecord.getId();
7213      if(this._sFirstTrId === oldId) {
7214          this._sFirstTrId = newId;
7215      }
7216      if(this._sLastTrId === oldId) {
7217          this._sLastTrId = newId;
7218      }
7219      elTr.id = newId;
7220      return elTr;
7221  },
7222  
7223  
7224  /**
7225   * Deletes TR element by DOM reference or by DataTable page row index.
7226   *
7227   * @method _deleteTrEl
7228   * @param row {HTMLElement | Number} TR element reference or Datatable page row index.
7229   * @return {Boolean} Returns true if successful, else returns false.
7230   * @private
7231   */
7232  _deleteTrEl : function(row) {
7233      var rowIndex;
7234  
7235      // Get page row index for the element
7236      if(!lang.isNumber(row)) {
7237          rowIndex = Dom.get(row).sectionRowIndex;
7238      }
7239      else {
7240          rowIndex = row;
7241      }
7242      if(lang.isNumber(rowIndex) && (rowIndex > -2) && (rowIndex < this._elTbody.rows.length)) {
7243          // Cannot use tbody.deleteRow due to IE6 instability
7244          //return this._elTbody.deleteRow(rowIndex);
7245          return this._elTbody.removeChild(this._elTbody.rows[row]);
7246      }
7247      else {
7248          return null;
7249      }
7250  },
7251  
7252  
7253  
7254  
7255  
7256  
7257  
7258  
7259  
7260  
7261  
7262  
7263  
7264  
7265  
7266  
7267  
7268  
7269  
7270  
7271  
7272  
7273  
7274  
7275  
7276  
7277  
7278  // CSS/STATE FUNCTIONS
7279  
7280  
7281  
7282  
7283  /**
7284   * Removes the class YAHOO.widget.DataTable.CLASS_FIRST from the first TR element
7285   * of the DataTable page and updates internal tracker.
7286   *
7287   * @method _unsetFirstRow
7288   * @private
7289   */
7290  _unsetFirstRow : function() {
7291      // Remove FIRST
7292      if(this._sFirstTrId) {
7293          Dom.removeClass(this._sFirstTrId, DT.CLASS_FIRST);
7294          this._sFirstTrId = null;
7295      }
7296  },
7297  
7298  /**
7299   * Assigns the class YAHOO.widget.DataTable.CLASS_FIRST to the first TR element
7300   * of the DataTable page and updates internal tracker.
7301   *
7302   * @method _setFirstRow
7303   * @private
7304   */
7305  _setFirstRow : function() {
7306      this._unsetFirstRow();
7307      var elTr = this.getFirstTrEl();
7308      if(elTr) {
7309          // Set FIRST
7310          Dom.addClass(elTr, DT.CLASS_FIRST);
7311          this._sFirstTrId = elTr.id;
7312      }
7313  },
7314  
7315  /**
7316   * Removes the class YAHOO.widget.DataTable.CLASS_LAST from the last TR element
7317   * of the DataTable page and updates internal tracker.
7318   *
7319   * @method _unsetLastRow
7320   * @private
7321   */
7322  _unsetLastRow : function() {
7323      // Unassign previous class
7324      if(this._sLastTrId) {
7325          Dom.removeClass(this._sLastTrId, DT.CLASS_LAST);
7326          this._sLastTrId = null;
7327      }   
7328  },
7329  
7330  /**
7331   * Assigns the class YAHOO.widget.DataTable.CLASS_LAST to the last TR element
7332   * of the DataTable page and updates internal tracker.
7333   *
7334   * @method _setLastRow
7335   * @private
7336   */
7337  _setLastRow : function() {
7338      this._unsetLastRow();
7339      var elTr = this.getLastTrEl();
7340      if(elTr) {
7341          // Assign class
7342          Dom.addClass(elTr, DT.CLASS_LAST);
7343          this._sLastTrId = elTr.id;
7344      }
7345  },
7346  
7347  /**
7348   * Assigns the classes DT.CLASS_EVEN and DT.CLASS_ODD to one, many, or all TR elements.
7349   *
7350   * @method _setRowStripes
7351   * @param row {HTMLElement | String | Number} (optional) HTML TR element reference
7352   * or string ID, or page row index of where to start striping.
7353   * @param range {Number} (optional) If given, how many rows to stripe, otherwise
7354   * stripe all the rows until the end.
7355   * @private
7356   */
7357  _setRowStripes : function(row, range) {
7358      // Default values stripe all rows
7359      var allRows = this._elTbody.rows,
7360          nStartIndex = 0,
7361          nEndIndex = allRows.length,
7362          aOdds = [], nOddIdx = 0,
7363          aEvens = [], nEvenIdx = 0;
7364  
7365      // Stripe a subset
7366      if((row !== null) && (row !== undefined)) {
7367          // Validate given start row
7368          var elStartRow = this.getTrEl(row);
7369          if(elStartRow) {
7370              nStartIndex = elStartRow.sectionRowIndex;
7371  
7372              // Validate given range
7373              if(lang.isNumber(range) && (range > 1)) {
7374                  nEndIndex = nStartIndex + range;
7375              }
7376          }
7377      }
7378  
7379      for(var i=nStartIndex; i<nEndIndex; i++) {
7380          if(i%2) {
7381              aOdds[nOddIdx++] = allRows[i];
7382          } else {
7383              aEvens[nEvenIdx++] = allRows[i];
7384          }
7385      }
7386  
7387      if (aOdds.length) {
7388          Dom.replaceClass(aOdds, DT.CLASS_EVEN, DT.CLASS_ODD);
7389      }
7390  
7391      if (aEvens.length) {
7392          Dom.replaceClass(aEvens, DT.CLASS_ODD, DT.CLASS_EVEN);
7393      }
7394  },
7395  
7396  /**
7397   * Assigns the class DT.CLASS_SELECTED to TR and TD elements.
7398   *
7399   * @method _setSelections
7400   * @private
7401   */
7402  _setSelections : function() {
7403      // Keep track of selected rows
7404      var allSelectedRows = this.getSelectedRows();
7405      // Keep track of selected cells
7406      var allSelectedCells = this.getSelectedCells();
7407      // Anything to select?
7408      if((allSelectedRows.length>0) || (allSelectedCells.length > 0)) {
7409          var oColumnSet = this._oColumnSet,
7410              el;
7411          // Loop over each row
7412          for(var i=0; i<allSelectedRows.length; i++) {
7413              el = Dom.get(allSelectedRows[i]);
7414              if(el) {
7415                  Dom.addClass(el, DT.CLASS_SELECTED);
7416              }
7417          }
7418          // Loop over each cell
7419          for(i=0; i<allSelectedCells.length; i++) {
7420              el = Dom.get(allSelectedCells[i].recordId);
7421              if(el) {
7422                  Dom.addClass(el.childNodes[oColumnSet.getColumn(allSelectedCells[i].columnKey).getKeyIndex()], DT.CLASS_SELECTED);
7423              }
7424          }
7425      }       
7426  },
7427  
7428  
7429  
7430  
7431  
7432  
7433  
7434  
7435  
7436  
7437  
7438  
7439  
7440  
7441  
7442  
7443  
7444  
7445  
7446  
7447  
7448  
7449  
7450  
7451  
7452  
7453  
7454  
7455  
7456  
7457  
7458  
7459  
7460  
7461  
7462  
7463  
7464  
7465  
7466  
7467  
7468  
7469  
7470  /////////////////////////////////////////////////////////////////////////////
7471  //
7472  // Private DOM Event Handlers
7473  //
7474  /////////////////////////////////////////////////////////////////////////////
7475  
7476  /**
7477   * Validates minWidths whenever the render chain ends.
7478   *
7479   * @method _onRenderChainEnd
7480   * @private
7481   */
7482  _onRenderChainEnd : function() {
7483      // Hide loading message
7484      this.hideTableMessage();
7485      
7486      // Show empty message
7487      if(this._elTbody.rows.length === 0) {
7488          this.showTableMessage(this.get("MSG_EMPTY"), DT.CLASS_EMPTY);        
7489      }
7490  
7491      // Execute in timeout thread to give implementers a chance
7492      // to subscribe after the constructor
7493      var oSelf = this;
7494      setTimeout(function() {
7495          if((oSelf instanceof DT) && oSelf._sId) {        
7496              // Init event
7497              if(oSelf._bInit) {
7498                  oSelf._bInit = false;
7499                  oSelf.fireEvent("initEvent");
7500              }
7501      
7502              // Render event
7503              oSelf.fireEvent("renderEvent");
7504              // Backward compatibility
7505              oSelf.fireEvent("refreshEvent");
7506              YAHOO.log("DataTable rendered", "info", oSelf.toString());
7507      
7508              // Post-render routine
7509              oSelf.validateColumnWidths();
7510      
7511              // Post-render event
7512              oSelf.fireEvent("postRenderEvent");
7513              
7514              /*if(YAHOO.example.Performance.trialStart) {
7515                  YAHOO.log((new Date()).getTime() - YAHOO.example.Performance.trialStart.getTime() + " ms", "time");
7516                  YAHOO.example.Performance.trialStart = null;
7517              }*/
7518              
7519              YAHOO.log("Post-render routine executed", "info", oSelf.toString());
7520          }
7521      }, 0);
7522  },
7523  
7524  /**
7525   * Handles click events on the DOCUMENT.
7526   *
7527   * @method _onDocumentClick
7528   * @param e {HTMLEvent} The click event.
7529   * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
7530   * @private
7531   */
7532  _onDocumentClick : function(e, oSelf) {
7533      var elTarget = Ev.getTarget(e);
7534      var elTag = elTarget.nodeName.toLowerCase();
7535  
7536      if(!Dom.isAncestor(oSelf._elContainer, elTarget)) {
7537          oSelf.fireEvent("tableBlurEvent");
7538  
7539          // Fires editorBlurEvent when click is not within the TABLE.
7540          // For cases when click is within the TABLE, due to timing issues,
7541          // the editorBlurEvent needs to get fired by the lower-level DOM click
7542          // handlers below rather than by the TABLE click handler directly.
7543          if(oSelf._oCellEditor) {
7544              if(oSelf._oCellEditor.getContainerEl) {
7545                  var elContainer = oSelf._oCellEditor.getContainerEl();
7546                  // Only if the click was not within the CellEditor container
7547                  if(!Dom.isAncestor(elContainer, elTarget) &&
7548                          (elContainer.id !== elTarget.id)) {
7549                      oSelf._oCellEditor.fireEvent("blurEvent", {editor: oSelf._oCellEditor});
7550                  }
7551              }
7552              // Backward Compatibility
7553              else if(oSelf._oCellEditor.isActive) {
7554                  // Only if the click was not within the Cell Editor container
7555                  if(!Dom.isAncestor(oSelf._oCellEditor.container, elTarget) &&
7556                          (oSelf._oCellEditor.container.id !== elTarget.id)) {
7557                      oSelf.fireEvent("editorBlurEvent", {editor:oSelf._oCellEditor});
7558                  }
7559              }
7560          }
7561      }
7562  },
7563  
7564  /**
7565   * Handles focus events on the DataTable instance.
7566   *
7567   * @method _onTableFocus
7568   * @param e {HTMLEvent} The focus event.
7569   * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
7570   * @private
7571   */
7572  _onTableFocus : function(e, oSelf) {
7573      oSelf.fireEvent("tableFocusEvent");
7574  },
7575  
7576  /**
7577   * Handles focus events on the THEAD element.
7578   *
7579   * @method _onTheadFocus
7580   * @param e {HTMLEvent} The focus event.
7581   * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
7582   * @private
7583   */
7584  _onTheadFocus : function(e, oSelf) {
7585      oSelf.fireEvent("theadFocusEvent");
7586      oSelf.fireEvent("tableFocusEvent");
7587  },
7588  
7589  /**
7590   * Handles focus events on the TBODY element.
7591   *
7592   * @method _onTbodyFocus
7593   * @param e {HTMLEvent} The focus event.
7594   * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
7595   * @private
7596   */
7597  _onTbodyFocus : function(e, oSelf) {
7598      oSelf.fireEvent("tbodyFocusEvent");
7599      oSelf.fireEvent("tableFocusEvent");
7600  },
7601  
7602  /**
7603   * Handles mouseover events on the DataTable instance.
7604   *
7605   * @method _onTableMouseover
7606   * @param e {HTMLEvent} The mouseover event.
7607   * @param origTarget {HTMLElement} The mouseenter delegated element.
7608   * @param container {HTMLElement} The mouseenter delegation container.
7609   * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
7610   * @private
7611   */
7612  _onTableMouseover : function(e, origTarget, container, oSelf) {
7613      var elTarget = origTarget;
7614      var elTag = elTarget.nodeName && elTarget.nodeName.toLowerCase();
7615      var bKeepBubbling = true;
7616      while(elTarget && (elTag != "table")) {
7617          switch(elTag) {
7618              case "body":
7619                   return;
7620              case "a":
7621                  break;
7622              case "td":
7623                  bKeepBubbling = oSelf.fireEvent("cellMouseoverEvent",{target:elTarget,event:e});
7624                  break;
7625              case "span":
7626                  if(Dom.hasClass(elTarget, DT.CLASS_LABEL)) {
7627                      bKeepBubbling = oSelf.fireEvent("theadLabelMouseoverEvent",{target:elTarget,event:e});
7628                      // Backward compatibility
7629                      bKeepBubbling = oSelf.fireEvent("headerLabelMouseoverEvent",{target:elTarget,event:e});
7630                  }
7631                  break;
7632              case "th":
7633                  bKeepBubbling = oSelf.fireEvent("theadCellMouseoverEvent",{target:elTarget,event:e});
7634                  // Backward compatibility
7635                  bKeepBubbling = oSelf.fireEvent("headerCellMouseoverEvent",{target:elTarget,event:e});
7636                  break;
7637              case "tr":
7638                  if(elTarget.parentNode.nodeName.toLowerCase() == "thead") {
7639                      bKeepBubbling = oSelf.fireEvent("theadRowMouseoverEvent",{target:elTarget,event:e});
7640                      // Backward compatibility
7641                      bKeepBubbling = oSelf.fireEvent("headerRowMouseoverEvent",{target:elTarget,event:e});
7642                  }
7643                  else {
7644                      bKeepBubbling = oSelf.fireEvent("rowMouseoverEvent",{target:elTarget,event:e});
7645                  }
7646                  break;
7647              default:
7648                  break;
7649          }
7650          if(bKeepBubbling === false) {
7651              return;
7652          }
7653          else {
7654              elTarget = elTarget.parentNode;
7655              if(elTarget) {
7656                  elTag = elTarget.nodeName.toLowerCase();
7657              }
7658          }
7659      }
7660      oSelf.fireEvent("tableMouseoverEvent",{target:(elTarget || oSelf._elContainer),event:e});
7661  },
7662  
7663  /**
7664   * Handles mouseout events on the DataTable instance.
7665   *
7666   * @method _onTableMouseout
7667   * @param e {HTMLEvent} The mouseout event.
7668   * @param origTarget {HTMLElement} The mouseleave delegated element.
7669   * @param container {HTMLElement} The mouseleave delegation container.
7670   * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
7671   * @private
7672   */
7673  _onTableMouseout : function(e, origTarget, container, oSelf) {
7674      var elTarget = origTarget;
7675      var elTag = elTarget.nodeName && elTarget.nodeName.toLowerCase();
7676      var bKeepBubbling = true;
7677      while(elTarget && (elTag != "table")) {
7678          switch(elTag) {
7679              case "body":
7680                  return;
7681              case "a":
7682                  break;
7683              case "td":
7684                  bKeepBubbling = oSelf.fireEvent("cellMouseoutEvent",{target:elTarget,event:e});
7685                  break;
7686              case "span":
7687                  if(Dom.hasClass(elTarget, DT.CLASS_LABEL)) {
7688                      bKeepBubbling = oSelf.fireEvent("theadLabelMouseoutEvent",{target:elTarget,event:e});
7689                      // Backward compatibility
7690                      bKeepBubbling = oSelf.fireEvent("headerLabelMouseoutEvent",{target:elTarget,event:e});
7691                  }
7692                  break;
7693              case "th":
7694                  bKeepBubbling = oSelf.fireEvent("theadCellMouseoutEvent",{target:elTarget,event:e});
7695                  // Backward compatibility
7696                  bKeepBubbling = oSelf.fireEvent("headerCellMouseoutEvent",{target:elTarget,event:e});
7697                  break;
7698              case "tr":
7699                  if(elTarget.parentNode.nodeName.toLowerCase() == "thead") {
7700                      bKeepBubbling = oSelf.fireEvent("theadRowMouseoutEvent",{target:elTarget,event:e});
7701                      // Backward compatibility
7702                      bKeepBubbling = oSelf.fireEvent("headerRowMouseoutEvent",{target:elTarget,event:e});
7703                  }
7704                  else {
7705                      bKeepBubbling = oSelf.fireEvent("rowMouseoutEvent",{target:elTarget,event:e});
7706                  }
7707                  break;
7708              default:
7709                  break;
7710          }
7711          if(bKeepBubbling === false) {
7712              return;
7713          }
7714          else {
7715              elTarget = elTarget.parentNode;
7716              if(elTarget) {
7717                  elTag = elTarget.nodeName.toLowerCase();
7718              }
7719          }
7720      }
7721      oSelf.fireEvent("tableMouseoutEvent",{target:(elTarget || oSelf._elContainer),event:e});
7722  },
7723  
7724  /**
7725   * Handles mousedown events on the DataTable instance.
7726   *
7727   * @method _onTableMousedown
7728   * @param e {HTMLEvent} The mousedown event.
7729   * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
7730   * @private
7731   */
7732  _onTableMousedown : function(e, oSelf) {
7733      var elTarget = Ev.getTarget(e);
7734      var elTag = elTarget.nodeName && elTarget.nodeName.toLowerCase();
7735      var bKeepBubbling = true;
7736      while(elTarget && (elTag != "table")) {
7737          switch(elTag) {
7738              case "body":
7739                  return;
7740              case "a":
7741                  break;
7742              case "td":
7743                  bKeepBubbling = oSelf.fireEvent("cellMousedownEvent",{target:elTarget,event:e});
7744                  break;
7745              case "span":
7746                  if(Dom.hasClass(elTarget, DT.CLASS_LABEL)) {
7747                      bKeepBubbling = oSelf.fireEvent("theadLabelMousedownEvent",{target:elTarget,event:e});
7748                      // Backward compatibility
7749                      bKeepBubbling = oSelf.fireEvent("headerLabelMousedownEvent",{target:elTarget,event:e});
7750                  }
7751                  break;
7752              case "th":
7753                  bKeepBubbling = oSelf.fireEvent("theadCellMousedownEvent",{target:elTarget,event:e});
7754                  // Backward compatibility
7755                  bKeepBubbling = oSelf.fireEvent("headerCellMousedownEvent",{target:elTarget,event:e});
7756                  break;
7757              case "tr":
7758                  if(elTarget.parentNode.nodeName.toLowerCase() == "thead") {
7759                      bKeepBubbling = oSelf.fireEvent("theadRowMousedownEvent",{target:elTarget,event:e});
7760                      // Backward compatibility
7761                      bKeepBubbling = oSelf.fireEvent("headerRowMousedownEvent",{target:elTarget,event:e});
7762                  }
7763                  else {
7764                      bKeepBubbling = oSelf.fireEvent("rowMousedownEvent",{target:elTarget,event:e});
7765                  }
7766                  break;
7767              default:
7768                  break;
7769          }
7770          if(bKeepBubbling === false) {
7771              return;
7772          }
7773          else {
7774              elTarget = elTarget.parentNode;
7775              if(elTarget) {
7776                  elTag = elTarget.nodeName.toLowerCase();
7777              }
7778          }
7779      }
7780      oSelf.fireEvent("tableMousedownEvent",{target:(elTarget || oSelf._elContainer),event:e});
7781  },
7782  
7783  /**
7784   * Handles mouseup events on the DataTable instance.
7785   *
7786   * @method _onTableMouseup
7787   * @param e {HTMLEvent} The mouseup event.
7788   * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
7789   * @private
7790   */
7791  _onTableMouseup : function(e, oSelf) {
7792      var elTarget = Ev.getTarget(e);
7793      var elTag = elTarget.nodeName && elTarget.nodeName.toLowerCase();
7794      var bKeepBubbling = true;
7795      while(elTarget && (elTag != "table")) {
7796          switch(elTag) {
7797              case "body":
7798                  return;
7799              case "a":
7800                  break;
7801              case "td":
7802                  bKeepBubbling = oSelf.fireEvent("cellMouseupEvent",{target:elTarget,event:e});
7803                  break;
7804              case "span":
7805                  if(Dom.hasClass(elTarget, DT.CLASS_LABEL)) {
7806                      bKeepBubbling = oSelf.fireEvent("theadLabelMouseupEvent",{target:elTarget,event:e});
7807                      // Backward compatibility
7808                      bKeepBubbling = oSelf.fireEvent("headerLabelMouseupEvent",{target:elTarget,event:e});
7809                  }
7810                  break;
7811              case "th":
7812                  bKeepBubbling = oSelf.fireEvent("theadCellMouseupEvent",{target:elTarget,event:e});
7813                  // Backward compatibility
7814                  bKeepBubbling = oSelf.fireEvent("headerCellMouseupEvent",{target:elTarget,event:e});
7815                  break;
7816              case "tr":
7817                  if(elTarget.parentNode.nodeName.toLowerCase() == "thead") {
7818                      bKeepBubbling = oSelf.fireEvent("theadRowMouseupEvent",{target:elTarget,event:e});
7819                      // Backward compatibility
7820                      bKeepBubbling = oSelf.fireEvent("headerRowMouseupEvent",{target:elTarget,event:e});
7821                  }
7822                  else {
7823                      bKeepBubbling = oSelf.fireEvent("rowMouseupEvent",{target:elTarget,event:e});
7824                  }
7825                  break;
7826              default:
7827                  break;
7828          }
7829          if(bKeepBubbling === false) {
7830              return;
7831          }
7832          else {
7833              elTarget = elTarget.parentNode;
7834              if(elTarget) {
7835                  elTag = elTarget.nodeName.toLowerCase();
7836              }
7837          }
7838      }
7839      oSelf.fireEvent("tableMouseupEvent",{target:(elTarget || oSelf._elContainer),event:e});
7840  },
7841  
7842  /**
7843   * Handles dblclick events on the DataTable instance.
7844   *
7845   * @method _onTableDblclick
7846   * @param e {HTMLEvent} The dblclick event.
7847   * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
7848   * @private
7849   */
7850  _onTableDblclick : function(e, oSelf) {
7851      var elTarget = Ev.getTarget(e);
7852      var elTag = elTarget.nodeName && elTarget.nodeName.toLowerCase();
7853      var bKeepBubbling = true;
7854      while(elTarget && (elTag != "table")) {
7855          switch(elTag) {
7856              case "body":
7857                  return;
7858              case "td":
7859                  bKeepBubbling = oSelf.fireEvent("cellDblclickEvent",{target:elTarget,event:e});
7860                  break;
7861              case "span":
7862                  if(Dom.hasClass(elTarget, DT.CLASS_LABEL)) {
7863                      bKeepBubbling = oSelf.fireEvent("theadLabelDblclickEvent",{target:elTarget,event:e});
7864                      // Backward compatibility
7865                      bKeepBubbling = oSelf.fireEvent("headerLabelDblclickEvent",{target:elTarget,event:e});
7866                  }
7867                  break;
7868              case "th":
7869                  bKeepBubbling = oSelf.fireEvent("theadCellDblclickEvent",{target:elTarget,event:e});
7870                  // Backward compatibility
7871                  bKeepBubbling = oSelf.fireEvent("headerCellDblclickEvent",{target:elTarget,event:e});
7872                  break;
7873              case "tr":
7874                  if(elTarget.parentNode.nodeName.toLowerCase() == "thead") {
7875                      bKeepBubbling = oSelf.fireEvent("theadRowDblclickEvent",{target:elTarget,event:e});
7876                      // Backward compatibility
7877                      bKeepBubbling = oSelf.fireEvent("headerRowDblclickEvent",{target:elTarget,event:e});
7878                  }
7879                  else {
7880                      bKeepBubbling = oSelf.fireEvent("rowDblclickEvent",{target:elTarget,event:e});
7881                  }
7882                  break;
7883              default:
7884                  break;
7885          }
7886          if(bKeepBubbling === false) {
7887              return;
7888          }
7889          else {
7890              elTarget = elTarget.parentNode;
7891              if(elTarget) {
7892                  elTag = elTarget.nodeName.toLowerCase();
7893              }
7894          }
7895      }
7896      oSelf.fireEvent("tableDblclickEvent",{target:(elTarget || oSelf._elContainer),event:e});
7897  },
7898  /**
7899   * Handles keydown events on the THEAD element.
7900   *
7901   * @method _onTheadKeydown
7902   * @param e {HTMLEvent} The key event.
7903   * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
7904   * @private
7905   */
7906  _onTheadKeydown : function(e, oSelf) {
7907      var elTarget = Ev.getTarget(e);
7908      var elTag = elTarget.nodeName && elTarget.nodeName.toLowerCase();
7909      var bKeepBubbling = true;
7910      while(elTarget && (elTag != "table")) {
7911          switch(elTag) {
7912              case "body":
7913                  return;
7914              case "input":
7915              case "textarea":
7916                  // TODO: implement textareaKeyEvent
7917                  break;
7918              case "thead":
7919                  bKeepBubbling = oSelf.fireEvent("theadKeyEvent",{target:elTarget,event:e});
7920                  break;
7921              default:
7922                  break;
7923          }
7924          if(bKeepBubbling === false) {
7925              return;
7926          }
7927          else {
7928              elTarget = elTarget.parentNode;
7929              if(elTarget) {
7930                  elTag = elTarget.nodeName.toLowerCase();
7931              }
7932          }
7933      }
7934      oSelf.fireEvent("tableKeyEvent",{target:(elTarget || oSelf._elContainer),event:e});
7935  },
7936  
7937  /**
7938   * Handles keydown events on the TBODY element. Handles selection behavior,
7939   * provides hooks for ENTER to edit functionality.
7940   *
7941   * @method _onTbodyKeydown
7942   * @param e {HTMLEvent} The key event.
7943   * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
7944   * @private
7945   */
7946  _onTbodyKeydown : function(e, oSelf) {
7947      var sMode = oSelf.get("selectionMode");
7948  
7949      if(sMode == "standard") {
7950          oSelf._handleStandardSelectionByKey(e);
7951      }
7952      else if(sMode == "single") {
7953          oSelf._handleSingleSelectionByKey(e);
7954      }
7955      else if(sMode == "cellblock") {
7956          oSelf._handleCellBlockSelectionByKey(e);
7957      }
7958      else if(sMode == "cellrange") {
7959          oSelf._handleCellRangeSelectionByKey(e);
7960      }
7961      else if(sMode == "singlecell") {
7962          oSelf._handleSingleCellSelectionByKey(e);
7963      }
7964      
7965      if(oSelf._oCellEditor) {
7966          if(oSelf._oCellEditor.fireEvent) {
7967              oSelf._oCellEditor.fireEvent("blurEvent", {editor: oSelf._oCellEditor});
7968          }
7969          else if(oSelf._oCellEditor.isActive) {
7970              oSelf.fireEvent("editorBlurEvent", {editor:oSelf._oCellEditor});
7971          }
7972      }
7973  
7974      var elTarget = Ev.getTarget(e);
7975      var elTag = elTarget.nodeName && elTarget.nodeName.toLowerCase();
7976      var bKeepBubbling = true;
7977      while(elTarget && (elTag != "table")) {
7978          switch(elTag) {
7979              case "body":
7980                  return;
7981              case "tbody":
7982                  bKeepBubbling = oSelf.fireEvent("tbodyKeyEvent",{target:elTarget,event:e});
7983                  break;
7984              default:
7985                  break;
7986          }
7987          if(bKeepBubbling === false) {
7988              return;
7989          }
7990          else {
7991              elTarget = elTarget.parentNode;
7992              if(elTarget) {
7993                  elTag = elTarget.nodeName.toLowerCase();
7994              }
7995          }
7996      }
7997      oSelf.fireEvent("tableKeyEvent",{target:(elTarget || oSelf._elContainer),event:e});
7998  },
7999  
8000  /**
8001   * Handles click events on the THEAD element.
8002   *
8003   * @method _onTheadClick
8004   * @param e {HTMLEvent} The click event.
8005   * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
8006   * @private
8007   */
8008  _onTheadClick : function(e, oSelf) {
8009      // This blurs the CellEditor
8010      if(oSelf._oCellEditor) {
8011          if(oSelf._oCellEditor.fireEvent) {
8012              oSelf._oCellEditor.fireEvent("blurEvent", {editor: oSelf._oCellEditor});
8013          }
8014          // Backward compatibility
8015          else if(oSelf._oCellEditor.isActive) {
8016              oSelf.fireEvent("editorBlurEvent", {editor:oSelf._oCellEditor});
8017          }
8018      }
8019  
8020      var elTarget = Ev.getTarget(e),
8021          elTag = elTarget.nodeName && elTarget.nodeName.toLowerCase(),
8022          bKeepBubbling = true;
8023      while(elTarget && (elTag != "table")) {
8024          switch(elTag) {
8025              case "body":
8026                  return;
8027              case "input":
8028                  var sType = elTarget.type.toLowerCase();
8029                  if(sType == "checkbox") {
8030                      bKeepBubbling = oSelf.fireEvent("theadCheckboxClickEvent",{target:elTarget,event:e});
8031                  }
8032                  else if(sType == "radio") {
8033                      bKeepBubbling = oSelf.fireEvent("theadRadioClickEvent",{target:elTarget,event:e});
8034                  }
8035                  else if((sType == "button") || (sType == "image") || (sType == "submit") || (sType == "reset")) {
8036                      if(!elTarget.disabled) {
8037                          bKeepBubbling = oSelf.fireEvent("theadButtonClickEvent",{target:elTarget,event:e});
8038                      }
8039                      else {
8040                          bKeepBubbling = false;
8041                      }
8042                  }
8043                  else if (elTarget.disabled){
8044                      bKeepBubbling = false;
8045                  }
8046                  break;
8047              case "a":
8048                  bKeepBubbling = oSelf.fireEvent("theadLinkClickEvent",{target:elTarget,event:e});
8049                  break;
8050              case "button":
8051                  if(!elTarget.disabled) {
8052                      bKeepBubbling = oSelf.fireEvent("theadButtonClickEvent",{target:elTarget,event:e});
8053                  }
8054                  else {
8055                      bKeepBubbling = false;
8056                  }
8057                  break;
8058              case "span":
8059                  if(Dom.hasClass(elTarget, DT.CLASS_LABEL)) {
8060                      bKeepBubbling = oSelf.fireEvent("theadLabelClickEvent",{target:elTarget,event:e});
8061                      // Backward compatibility
8062                      bKeepBubbling = oSelf.fireEvent("headerLabelClickEvent",{target:elTarget,event:e});
8063                  }
8064                  break;
8065              case "th":
8066                  bKeepBubbling = oSelf.fireEvent("theadCellClickEvent",{target:elTarget,event:e});
8067                  // Backward compatibility
8068                  bKeepBubbling = oSelf.fireEvent("headerCellClickEvent",{target:elTarget,event:e});
8069                  break;
8070              case "tr":
8071                  bKeepBubbling = oSelf.fireEvent("theadRowClickEvent",{target:elTarget,event:e});
8072                  // Backward compatibility
8073                  bKeepBubbling = oSelf.fireEvent("headerRowClickEvent",{target:elTarget,event:e});
8074                  break;
8075              default:
8076                  break;
8077          }
8078          if(bKeepBubbling === false) {
8079              return;
8080          }
8081          else {
8082              elTarget = elTarget.parentNode;
8083              if(elTarget) {
8084                  elTag = elTarget.nodeName.toLowerCase();
8085              }
8086          }
8087      }
8088      oSelf.fireEvent("tableClickEvent",{target:(elTarget || oSelf._elContainer),event:e});
8089  },
8090  
8091  /**
8092   * Handles click events on the primary TBODY element.
8093   *
8094   * @method _onTbodyClick
8095   * @param e {HTMLEvent} The click event.
8096   * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
8097   * @private
8098   */
8099  _onTbodyClick : function(e, oSelf) {
8100      // This blurs the CellEditor
8101      if(oSelf._oCellEditor) {
8102          if(oSelf._oCellEditor.fireEvent) {
8103              oSelf._oCellEditor.fireEvent("blurEvent", {editor: oSelf._oCellEditor});
8104          }
8105          else if(oSelf._oCellEditor.isActive) {
8106              oSelf.fireEvent("editorBlurEvent", {editor:oSelf._oCellEditor});
8107          }
8108      }
8109  
8110      // Fire Custom Events
8111      var elTarget = Ev.getTarget(e),
8112          elTag = elTarget.nodeName && elTarget.nodeName.toLowerCase(),
8113          bKeepBubbling = true;
8114      while(elTarget && (elTag != "table")) {
8115          switch(elTag) {
8116              case "body":
8117                  return;
8118              case "input":
8119                  var sType = elTarget.type.toLowerCase();
8120                  if(sType == "checkbox") {
8121                      bKeepBubbling = oSelf.fireEvent("checkboxClickEvent",{target:elTarget,event:e});
8122                  }
8123                  else if(sType == "radio") {
8124                      bKeepBubbling = oSelf.fireEvent("radioClickEvent",{target:elTarget,event:e});
8125                  }
8126                  else if((sType == "button") || (sType == "image") || (sType == "submit") || (sType == "reset")) {
8127                      if(!elTarget.disabled) {
8128                          bKeepBubbling = oSelf.fireEvent("buttonClickEvent",{target:elTarget,event:e});
8129                      }
8130                      else {
8131                          bKeepBubbling = false;
8132                      }
8133                  }
8134                  else if (elTarget.disabled){
8135                      bKeepBubbling = false;
8136                  }
8137                  break;
8138              case "a":
8139                  bKeepBubbling = oSelf.fireEvent("linkClickEvent",{target:elTarget,event:e});
8140                  break;
8141              case "button":
8142                  if(!elTarget.disabled) {
8143                      bKeepBubbling = oSelf.fireEvent("buttonClickEvent",{target:elTarget,event:e});
8144                  }
8145                  else {
8146                      bKeepBubbling = false;
8147                  }
8148                  break;
8149              case "td":
8150                  bKeepBubbling = oSelf.fireEvent("cellClickEvent",{target:elTarget,event:e});
8151                  break;
8152              case "tr":
8153                  bKeepBubbling = oSelf.fireEvent("rowClickEvent",{target:elTarget,event:e});
8154                  break;
8155              default:
8156                  break;
8157          }
8158          if(bKeepBubbling === false) {
8159              return;
8160          }
8161          else {
8162              elTarget = elTarget.parentNode;
8163              if(elTarget) {
8164                  elTag = elTarget.nodeName.toLowerCase();
8165              }
8166          }
8167      }
8168      oSelf.fireEvent("tableClickEvent",{target:(elTarget || oSelf._elContainer),event:e});
8169  },
8170  
8171  /**
8172   * Handles change events on SELECT elements within DataTable.
8173   *
8174   * @method _onDropdownChange
8175   * @param e {HTMLEvent} The change event.
8176   * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
8177   * @private
8178   */
8179  _onDropdownChange : function(e, oSelf) {
8180      var elTarget = Ev.getTarget(e);
8181      oSelf.fireEvent("dropdownChangeEvent", {event:e, target:elTarget});
8182  },
8183  
8184  
8185  
8186  
8187  
8188  
8189  
8190  
8191  
8192  
8193  
8194  
8195  
8196  
8197  
8198  
8199  
8200  
8201  
8202  
8203  
8204  
8205  
8206  
8207  
8208  
8209  
8210  
8211  
8212  
8213  
8214  
8215  /////////////////////////////////////////////////////////////////////////////
8216  //
8217  // Public member variables
8218  //
8219  /////////////////////////////////////////////////////////////////////////////
8220  /**
8221   * Returns object literal of initial configs.
8222   *
8223   * @property configs
8224   * @type Object
8225   * @default {} 
8226   */
8227  configs: null,
8228  
8229  
8230  /////////////////////////////////////////////////////////////////////////////
8231  //
8232  // Public methods
8233  //
8234  /////////////////////////////////////////////////////////////////////////////
8235  
8236  /**
8237   * Returns unique id assigned to instance, which is a useful prefix for
8238   * generating unique DOM ID strings.
8239   *
8240   * @method getId
8241   * @return {String} Unique ID of the DataSource instance.
8242   */
8243  getId : function() {
8244      return this._sId;
8245  },
8246  
8247  /**
8248   * DataSource instance name, for logging.
8249   *
8250   * @method toString
8251   * @return {String} Unique name of the DataSource instance.
8252   */
8253  
8254  toString : function() {
8255      return "DataTable instance " + this._sId;
8256  },
8257  
8258  /**
8259   * Returns the DataTable instance's DataSource instance.
8260   *
8261   * @method getDataSource
8262   * @return {YAHOO.util.DataSource} DataSource instance.
8263   */
8264  getDataSource : function() {
8265      return this._oDataSource;
8266  },
8267  
8268  /**
8269   * Returns the DataTable instance's ColumnSet instance.
8270   *
8271   * @method getColumnSet
8272   * @return {YAHOO.widget.ColumnSet} ColumnSet instance.
8273   */
8274  getColumnSet : function() {
8275      return this._oColumnSet;
8276  },
8277  
8278  /**
8279   * Returns the DataTable instance's RecordSet instance.
8280   *
8281   * @method getRecordSet
8282   * @return {YAHOO.widget.RecordSet} RecordSet instance.
8283   */
8284  getRecordSet : function() {
8285      return this._oRecordSet;
8286  },
8287  
8288  /**
8289   * Returns on object literal representing the DataTable instance's current
8290   * state with the following properties:
8291   * <dl>
8292   * <dt>pagination</dt>
8293   * <dd>Instance of YAHOO.widget.Paginator</dd>
8294   *
8295   * <dt>sortedBy</dt>
8296   * <dd>
8297   *     <dl>
8298   *         <dt>sortedBy.key</dt>
8299   *         <dd>{String} Key of sorted Column</dd>
8300   *         <dt>sortedBy.dir</dt>
8301   *         <dd>{String} Initial sort direction, either YAHOO.widget.DataTable.CLASS_ASC or YAHOO.widget.DataTable.CLASS_DESC</dd>
8302   *     </dl>
8303   * </dd>
8304   *
8305   * <dt>selectedRows</dt>
8306   * <dd>Array of selected rows by Record ID.</dd>
8307   *
8308   * <dt>selectedCells</dt>
8309   * <dd>Selected cells as an array of object literals:
8310   *     {recordId:sRecordId, columnKey:sColumnKey}</dd>
8311   * </dl>
8312   *  
8313   * @method getState
8314   * @return {Object} DataTable instance state object literal values.
8315   */
8316  getState : function() {
8317      return {
8318          totalRecords: this.get('paginator') ? this.get('paginator').get("totalRecords") : this._oRecordSet.getLength(),
8319          pagination: this.get("paginator") ? this.get("paginator").getState() : null,
8320          sortedBy: this.get("sortedBy"),
8321          selectedRows: this.getSelectedRows(),
8322          selectedCells: this.getSelectedCells()
8323      };
8324  },
8325  
8326  
8327  
8328  
8329  
8330  
8331  
8332  
8333  
8334  
8335  
8336  
8337  
8338  
8339  
8340  
8341  
8342  
8343  
8344  
8345  
8346  
8347  
8348  
8349  
8350  
8351  
8352  
8353  
8354  
8355  
8356  
8357  
8358  
8359  
8360  
8361  
8362  
8363  
8364  
8365  
8366  
8367  
8368  // DOM ACCESSORS
8369  
8370  /**
8371   * Returns DOM reference to the DataTable's container element.
8372   *
8373   * @method getContainerEl
8374   * @return {HTMLElement} Reference to DIV element.
8375   */
8376  getContainerEl : function() {
8377      return this._elContainer;
8378  },
8379  
8380  /**
8381   * Returns DOM reference to the DataTable's TABLE element.
8382   *
8383   * @method getTableEl
8384   * @return {HTMLElement} Reference to TABLE element.
8385   */
8386  getTableEl : function() {
8387      return this._elTable;
8388  },
8389  
8390  /**
8391   * Returns DOM reference to the DataTable's THEAD element.
8392   *
8393   * @method getTheadEl
8394   * @return {HTMLElement} Reference to THEAD element.
8395   */
8396  getTheadEl : function() {
8397      return this._elThead;
8398  },
8399  
8400  /**
8401   * Returns DOM reference to the DataTable's primary TBODY element.
8402   *
8403   * @method getTbodyEl
8404   * @return {HTMLElement} Reference to TBODY element.
8405   */
8406  getTbodyEl : function() {
8407      return this._elTbody;
8408  },
8409  
8410  /**
8411   * Returns DOM reference to the DataTable's secondary TBODY element that is
8412   * used to display messages.
8413   *
8414   * @method getMsgTbodyEl
8415   * @return {HTMLElement} Reference to TBODY element.
8416   */
8417  getMsgTbodyEl : function() {
8418      return this._elMsgTbody;
8419  },
8420  
8421  /**
8422   * Returns DOM reference to the TD element within the secondary TBODY that is
8423   * used to display messages.
8424   *
8425   * @method getMsgTdEl
8426   * @return {HTMLElement} Reference to TD element.
8427   */
8428  getMsgTdEl : function() {
8429      return this._elMsgTd;
8430  },
8431  
8432  /**
8433   * Returns the corresponding TR reference for a given DOM element, ID string or
8434   * page row index. If the given identifier is a child of a TR element,
8435   * then DOM tree is traversed until a parent TR element is returned, otherwise
8436   * null. Returns null if the row is not considered a primary row (i.e., row
8437   * extensions).
8438   *
8439   * @method getTrEl
8440   * @param row {HTMLElement | String | Number | YAHOO.widget.Record} Which row to
8441   * get: by element reference, ID string, page row index, or Record.
8442   * @return {HTMLElement} Reference to TR element, or null.
8443   */
8444  getTrEl : function(row) {
8445      // By Record
8446      if(row instanceof YAHOO.widget.Record) {
8447          return document.getElementById(row.getId());
8448      }
8449      // By page row index
8450      else if(lang.isNumber(row)) {
8451          var dataRows = Dom.getElementsByClassName(DT.CLASS_REC, "tr", this._elTbody);
8452          return dataRows && dataRows[row] ? dataRows[row] : null;
8453      }
8454      // By ID string or element reference
8455      else if(row) {
8456          var elRow = (lang.isString(row)) ? document.getElementById(row) : row;
8457  
8458          // Validate HTML element
8459          if(elRow && elRow.ownerDocument == document) {
8460              // Validate TR element
8461              if(elRow.nodeName.toLowerCase() != "tr") {
8462                  // Traverse up the DOM to find the corresponding TR element
8463                  elRow = Dom.getAncestorByTagName(elRow,"tr");
8464              }
8465  
8466              return elRow;
8467          }
8468      }
8469  
8470      return null;
8471  },
8472  
8473  /**
8474   * Returns DOM reference to the first primary TR element in the DataTable page, or null.
8475   *
8476   * @method getFirstTrEl
8477   * @return {HTMLElement} Reference to TR element.
8478   */
8479  getFirstTrEl : function() {
8480      var allRows = this._elTbody.rows,
8481          i=0;
8482      while(allRows[i]) {
8483          if(this.getRecord(allRows[i])) {
8484              return allRows[i];
8485          }
8486          i++;
8487      }
8488      return null;
8489  
8490  },
8491  
8492  /**
8493   * Returns DOM reference to the last primary TR element in the DataTable page, or null.
8494   *
8495   * @method getLastTrEl
8496   * @return {HTMLElement} Reference to last TR element.
8497   */
8498  getLastTrEl : function() {
8499      var allRows = this._elTbody.rows,
8500          i=allRows.length-1;
8501      while(i>-1) {
8502          if(this.getRecord(allRows[i])) {
8503              return allRows[i];
8504          }
8505          i--;
8506      }
8507      return null;
8508  },
8509  
8510  /**
8511   * Returns DOM reference to the next TR element from the given primary TR element, or null.
8512   *
8513   * @method getNextTrEl
8514   * @param row {HTMLElement | String | Number | YAHOO.widget.Record} Element
8515   * reference, ID string, page row index, or Record from which to get next TR element.
8516   * @param forcePrimary {Boolean} (optional) If true, will only return TR elements
8517   * that correspond to Records. Non-primary rows (such as row expansions)
8518   * will be skipped.
8519   * @return {HTMLElement} Reference to next TR element.
8520   */
8521  getNextTrEl : function(row, forcePrimary) {
8522      var nThisTrIndex = this.getTrIndex(row);
8523      if(nThisTrIndex !== null) {
8524          var allRows = this._elTbody.rows;
8525          if(forcePrimary) {
8526              while(nThisTrIndex < allRows.length-1) {
8527                  row = allRows[nThisTrIndex+1];
8528                  if(this.getRecord(row)) {
8529                      return row;
8530                  }
8531                  nThisTrIndex++;
8532              }
8533          }
8534          else {
8535              if(nThisTrIndex < allRows.length-1) {
8536                  return allRows[nThisTrIndex+1];
8537              }
8538          }
8539      }
8540  
8541      YAHOO.log("Could not get next TR element for row " + row, "info", this.toString());
8542      return null;
8543  },
8544  
8545  /**
8546   * Returns DOM reference to the previous TR element from the given primary TR element, or null.
8547   *
8548   * @method getPreviousTrEl
8549   * @param row {HTMLElement | String | Number | YAHOO.widget.Record} Element
8550   * reference, ID string, page row index, or Record from which to get previous TR element.
8551   * @param forcePrimary {Boolean} (optional) If true, will only return TR elements
8552   * from rothat correspond to Records. Non-primary rows (such as row expansions)
8553   * will be skipped.
8554   * @return {HTMLElement} Reference to previous TR element.
8555   */
8556  getPreviousTrEl : function(row, forcePrimary) {
8557      var nThisTrIndex = this.getTrIndex(row);
8558      if(nThisTrIndex !== null) {
8559          var allRows = this._elTbody.rows;
8560  
8561          if(forcePrimary) {
8562              while(nThisTrIndex > 0) {
8563                  row = allRows[nThisTrIndex-1];
8564                  if(this.getRecord(row)) {
8565                      return row;
8566                  }
8567                  nThisTrIndex--;
8568              }
8569          }
8570          else {
8571              if(nThisTrIndex > 0) {
8572                  return allRows[nThisTrIndex-1];
8573              }
8574          }
8575      }
8576  
8577      YAHOO.log("Could not get previous TR element for row " + row, "info", this.toString());
8578      return null;
8579  },
8580  
8581  
8582  /**
8583   * Workaround for IE bug where hidden or not-in-dom elements cause cellIndex
8584   * value to be incorrect.
8585   *
8586   * @method getCellIndex
8587   * @param cell {HTMLElement | Object} TD element or child of a TD element, or
8588   * object literal of syntax {record:oRecord, column:oColumn}.
8589   * @return {Number} TD.cellIndex value.
8590   */
8591  getCellIndex : function(cell) {
8592      cell = this.getTdEl(cell);
8593      if(cell) {
8594          if(ua.ie > 0) {
8595              var i=0,
8596                  tr = cell.parentNode,
8597                  allCells = tr.childNodes,
8598                  len = allCells.length;
8599              for(; i<len; i++) {
8600                  if(allCells[i] == cell) {
8601                      return i;
8602                  }
8603              }
8604          }
8605          else {
8606              return cell.cellIndex;
8607          }
8608      }
8609  },
8610  
8611  /**
8612   * Returns DOM reference to a TD liner element.
8613   *
8614   * @method getTdLinerEl
8615   * @param cell {HTMLElement | Object} TD element or child of a TD element, or
8616   * object literal of syntax {record:oRecord, column:oColumn}.
8617   * @return {HTMLElement} Reference to TD liner element.
8618   */
8619  getTdLinerEl : function(cell) {
8620      var elCell = this.getTdEl(cell);
8621      return elCell.firstChild || null;
8622  },
8623  
8624  /**
8625   * Returns DOM reference to a TD element. Returns null if the row is not
8626   * considered a primary row (i.e., row extensions).
8627   *
8628   * @method getTdEl
8629   * @param cell {HTMLElement | String | Object} TD element or child of a TD element, or
8630   * object literal of syntax {record:oRecord, column:oColumn}.
8631   * @return {HTMLElement} Reference to TD element.
8632   */
8633  getTdEl : function(cell) {
8634      var elCell;
8635      var el = Dom.get(cell);
8636  
8637      // Validate HTML element
8638      if(el && (el.ownerDocument == document)) {
8639          // Validate TD element
8640          if(el.nodeName.toLowerCase() != "td") {
8641              // Traverse up the DOM to find the corresponding TR element
8642              elCell = Dom.getAncestorByTagName(el, "td");
8643          }
8644          else {
8645              elCell = el;
8646          }
8647          
8648          // Make sure the TD is in this TBODY or is not in DOM
8649          // Bug 2527707 and bug 2263558
8650          if(elCell && ((elCell.parentNode.parentNode == this._elTbody) ||
8651              (elCell.parentNode.parentNode === null) ||
8652              (elCell.parentNode.parentNode.nodeType === 11))) {
8653              // Now we can return the TD element
8654              return elCell;
8655          }
8656      }
8657      else if(cell) {
8658          var oRecord, nColKeyIndex;
8659  
8660          if(lang.isString(cell.columnKey) && lang.isString(cell.recordId)) {
8661              oRecord = this.getRecord(cell.recordId);
8662              var oColumn = this.getColumn(cell.columnKey);
8663              if(oColumn) {
8664                  nColKeyIndex = oColumn.getKeyIndex();
8665              }
8666  
8667          }
8668          if(cell.record && cell.column && cell.column.getKeyIndex) {
8669              oRecord = cell.record;
8670              nColKeyIndex = cell.column.getKeyIndex();
8671          }
8672          var elRow = this.getTrEl(oRecord);
8673          if((nColKeyIndex !== null) && elRow && elRow.cells && elRow.cells.length > 0) {
8674              return elRow.cells[nColKeyIndex] || null;
8675          }
8676      }
8677  
8678      return null;
8679  },
8680  
8681  /**
8682   * Returns DOM reference to the first primary TD element in the DataTable page (by default),
8683   * the first TD element of the optionally given row, or null.
8684   *
8685   * @method getFirstTdEl
8686   * @param row {HTMLElement} (optional) row from which to get first TD
8687   * @return {HTMLElement} Reference to TD element.
8688   */
8689  getFirstTdEl : function(row) {
8690      var elRow = lang.isValue(row) ? this.getTrEl(row) : this.getFirstTrEl();
8691      if(elRow) {
8692          if(elRow.cells && elRow.cells.length > 0) {
8693              return elRow.cells[0];
8694          }
8695          else if(elRow.childNodes && elRow.childNodes.length > 0) {
8696              return elRow.childNodes[0];
8697          }
8698      }
8699      YAHOO.log("Could not get first TD element for row " + elRow, "info", this.toString());
8700      return null;
8701  },
8702  
8703  /**
8704   * Returns DOM reference to the last primary TD element in the DataTable page (by default),
8705   * the first TD element of the optionally given row, or null.
8706   *
8707   * @method getLastTdEl
8708   * @param row {HTMLElement} (optional) row from which to get first TD
8709   * @return {HTMLElement} Reference to last TD element.
8710   */
8711  getLastTdEl : function(row) {
8712      var elRow = lang.isValue(row) ? this.getTrEl(row) : this.getLastTrEl();
8713      if(elRow) {
8714          if(elRow.cells && elRow.cells.length > 0) {
8715              return elRow.cells[elRow.cells.length-1];
8716          }
8717          else if(elRow.childNodes && elRow.childNodes.length > 0) {
8718              return elRow.childNodes[elRow.childNodes.length-1];
8719          }
8720      }
8721      YAHOO.log("Could not get last TD element for row " + elRow, "info", this.toString());
8722      return null;
8723  },
8724  
8725  /**
8726   * Returns DOM reference to the next TD element from the given cell, or null.
8727   *
8728   * @method getNextTdEl
8729   * @param cell {HTMLElement | String | Object} DOM element reference or string ID, or
8730   * object literal of syntax {record:oRecord, column:oColumn} from which to get next TD element.
8731   * @return {HTMLElement} Reference to next TD element, or null.
8732   */
8733  getNextTdEl : function(cell) {
8734      var elCell = this.getTdEl(cell);
8735      if(elCell) {
8736          var nThisTdIndex = this.getCellIndex(elCell);
8737          var elRow = this.getTrEl(elCell);
8738          if(elRow.cells && (elRow.cells.length) > 0 && (nThisTdIndex < elRow.cells.length-1)) {
8739              return elRow.cells[nThisTdIndex+1];
8740          }
8741          else if(elRow.childNodes && (elRow.childNodes.length) > 0 && (nThisTdIndex < elRow.childNodes.length-1)) {
8742              return elRow.childNodes[nThisTdIndex+1];
8743          }
8744          else {
8745              var elNextRow = this.getNextTrEl(elRow);
8746              if(elNextRow) {
8747                  return elNextRow.cells[0];
8748              }
8749          }
8750      }
8751      YAHOO.log("Could not get next TD element for cell " + cell, "info", this.toString());
8752      return null;
8753  },
8754  
8755  /**
8756   * Returns DOM reference to the previous TD element from the given cell, or null.
8757   *
8758   * @method getPreviousTdEl
8759   * @param cell {HTMLElement | String | Object} DOM element reference or string ID, or
8760   * object literal of syntax {record:oRecord, column:oColumn} from which to get previous TD element.
8761   * @return {HTMLElement} Reference to previous TD element, or null.
8762   */
8763  getPreviousTdEl : function(cell) {
8764      var elCell = this.getTdEl(cell);
8765      if(elCell) {
8766          var nThisTdIndex = this.getCellIndex(elCell);
8767          var elRow = this.getTrEl(elCell);
8768          if(nThisTdIndex > 0) {
8769              if(elRow.cells && elRow.cells.length > 0) {
8770                  return elRow.cells[nThisTdIndex-1];
8771              }
8772              else if(elRow.childNodes && elRow.childNodes.length > 0) {
8773                  return elRow.childNodes[nThisTdIndex-1];
8774              }
8775          }
8776          else {
8777              var elPreviousRow = this.getPreviousTrEl(elRow);
8778              if(elPreviousRow) {
8779                  return this.getLastTdEl(elPreviousRow);
8780              }
8781          }
8782      }
8783      YAHOO.log("Could not get next TD element for cell " + cell, "info", this.toString());
8784      return null;
8785  },
8786  
8787  /**
8788   * Returns DOM reference to the above TD element from the given cell, or null.
8789   *
8790   * @method getAboveTdEl
8791   * @param cell {HTMLElement | String | Object} DOM element reference or string ID, or
8792   * object literal of syntax {record:oRecord, column:oColumn} from which to get next TD element.
8793   * @param forcePrimary {Boolean} (optional) If true, will only return TD elements
8794   * from rows that correspond to Records. Non-primary rows (such as row expansions)
8795   * will be skipped.
8796   * @return {HTMLElement} Reference to above TD element, or null.
8797   */
8798  getAboveTdEl : function(cell, forcePrimary) {
8799      var elCell = this.getTdEl(cell);
8800      if(elCell) {
8801          var elPreviousRow = this.getPreviousTrEl(elCell, forcePrimary);
8802          if(elPreviousRow ) {
8803              var cellIndex = this.getCellIndex(elCell);
8804              if(elPreviousRow.cells && elPreviousRow.cells.length > 0) {
8805                  return elPreviousRow.cells[cellIndex] ? elPreviousRow.cells[cellIndex] : null;
8806              }
8807              else if(elPreviousRow.childNodes && elPreviousRow.childNodes.length > 0) {
8808                  return elPreviousRow.childNodes[cellIndex] ? elPreviousRow.childNodes[cellIndex] : null;
8809              }
8810          }
8811      }
8812      YAHOO.log("Could not get above TD element for cell " + cell, "info", this.toString());
8813      return null;
8814  },
8815  
8816  /**
8817   * Returns DOM reference to the below TD element from the given cell, or null.
8818   *
8819   * @method getBelowTdEl
8820   * @param cell {HTMLElement | String | Object} DOM element reference or string ID, or
8821   * object literal of syntax {record:oRecord, column:oColumn} from which to get previous TD element.
8822   * @param forcePrimary {Boolean} (optional) If true, will only return TD elements
8823   * from rows that correspond to Records. Non-primary rows (such as row expansions)
8824   * will be skipped.
8825   * @return {HTMLElement} Reference to below TD element, or null.
8826   */
8827  getBelowTdEl : function(cell, forcePrimary) {
8828      var elCell = this.getTdEl(cell);
8829      if(elCell) {
8830          var elNextRow = this.getNextTrEl(elCell, forcePrimary);
8831          if(elNextRow) {
8832              var cellIndex = this.getCellIndex(elCell);
8833              if(elNextRow.cells && elNextRow.cells.length > 0) {
8834                  return elNextRow.cells[cellIndex] ? elNextRow.cells[cellIndex] : null;
8835              }
8836              else if(elNextRow.childNodes && elNextRow.childNodes.length > 0) {
8837                  return elNextRow.childNodes[cellIndex] ? elNextRow.childNodes[cellIndex] : null;
8838              }
8839          }
8840      }
8841      YAHOO.log("Could not get below TD element for cell " + cell, "info", this.toString());
8842      return null;
8843  },
8844  
8845  /**
8846   * Returns DOM reference to a TH liner element. Needed to normalize for resizeable 
8847   * Columns, which have an additional resizer liner DIV element between the TH
8848   * element and the liner DIV element. 
8849   *
8850   * @method getThLinerEl
8851   * @param theadCell {YAHOO.widget.Column | HTMLElement | String} Column instance,
8852   * DOM element reference, or string ID.
8853   * @return {HTMLElement} Reference to TH liner element.
8854   */
8855  getThLinerEl : function(theadCell) {
8856      var oColumn = this.getColumn(theadCell);
8857      return (oColumn) ? oColumn.getThLinerEl() : null;
8858  },
8859  
8860  /**
8861   * Returns DOM reference to a TH element.
8862   *
8863   * @method getThEl
8864   * @param theadCell {YAHOO.widget.Column | HTMLElement | String} Column instance,
8865   * DOM element reference, or string ID.
8866   * @return {HTMLElement} Reference to TH element.
8867   */
8868  getThEl : function(theadCell) {
8869      var elTh;
8870  
8871      // Validate Column instance
8872      if(theadCell instanceof YAHOO.widget.Column) {
8873          var oColumn = theadCell;
8874          elTh = oColumn.getThEl();
8875          if(elTh) {
8876              return elTh;
8877          }
8878      }
8879      // Validate HTML element
8880      else {
8881          var el = Dom.get(theadCell);
8882  
8883          if(el && (el.ownerDocument == document)) {
8884              // Validate TH element
8885              if(el.nodeName.toLowerCase() != "th") {
8886                  // Traverse up the DOM to find the corresponding TR element
8887                  elTh = Dom.getAncestorByTagName(el,"th");
8888              }
8889              else {
8890                  elTh = el;
8891              }
8892  
8893              return elTh;
8894          }
8895      }
8896  
8897      return null;
8898  },
8899  
8900  /**
8901   * Returns the page row index of given primary row. Returns null if the row is not on the
8902   * current DataTable page, or if row is not considered a primary row (i.e., row
8903   * extensions).
8904   *
8905   * @method getTrIndex
8906   * @param row {HTMLElement | String | YAHOO.widget.Record | Number} DOM or ID
8907   * string reference to an element within the DataTable page, a Record instance,
8908   * or a Record's RecordSet index.
8909   * @return {Number} Page row index, or null if data row does not exist or is not on current page.
8910   */
8911  getTrIndex : function(row) {
8912      var record = this.getRecord(row),
8913          index = this.getRecordIndex(record),
8914          tr;
8915      if(record) {
8916          tr = this.getTrEl(record);
8917          if(tr) {
8918              return tr.sectionRowIndex;
8919          }
8920          else {
8921              var oPaginator = this.get("paginator");
8922              if(oPaginator) {
8923                  return oPaginator.get('recordOffset') + index;
8924              }
8925              else {
8926                  return index;
8927              }
8928          }
8929      }
8930      YAHOO.log("Could not get page row index for row " + row, "info", this.toString());
8931      return null;
8932  },
8933  
8934  
8935  
8936  
8937  
8938  
8939  
8940  
8941  
8942  
8943  
8944  
8945  
8946  
8947  
8948  
8949  
8950  
8951  
8952  
8953  
8954  
8955  
8956  
8957  
8958  
8959  
8960  
8961  
8962  
8963  
8964  
8965  
8966  
8967  
8968  
8969  
8970  
8971  
8972  
8973  
8974  
8975  
8976  
8977  
8978  
8979  // TABLE FUNCTIONS
8980  
8981  /**
8982   * Loads new data. Convenience method that calls DataSource's sendRequest()
8983   * method under the hood.
8984   *
8985   * @method load
8986   * @param oConfig {object} Optional configuration parameters:
8987   *
8988   * <dl>
8989   * <dt>request</dt><dd>Pass in a new request, or initialRequest is used.</dd>
8990   * <dt>callback</dt><dd>Pass in DataSource sendRequest() callback object, or the following is used:
8991   *    <dl>
8992   *      <dt>success</dt><dd>datatable.onDataReturnInitializeTable</dd>
8993   *      <dt>failure</dt><dd>datatable.onDataReturnInitializeTable</dd>
8994   *      <dt>scope</dt><dd>datatable</dd>
8995   *      <dt>argument</dt><dd>datatable.getState()</dd>
8996   *    </dl>
8997   * </dd>
8998   * <dt>datasource</dt><dd>Pass in a new DataSource instance to override the current DataSource for this transaction.</dd>
8999   * </dl>
9000   */
9001  load : function(oConfig) {
9002      oConfig = oConfig || {};
9003  
9004      (oConfig.datasource || this._oDataSource).sendRequest(oConfig.request || this.get("initialRequest"), oConfig.callback || {
9005          success: this.onDataReturnInitializeTable,
9006          failure: this.onDataReturnInitializeTable,
9007          scope: this,
9008          argument: this.getState()
9009      });
9010  },
9011  
9012  /**
9013   * Resets a RecordSet with the given data and populates the page view
9014   * with the new data. Any previous data, and selection and sort states are
9015   * cleared. New data should be added as a separate step. 
9016   *
9017   * @method initializeTable
9018   */
9019  initializeTable : function() {
9020      // Reset init flag
9021      this._bInit = true;
9022      
9023      // Clear the RecordSet
9024      this._oRecordSet.reset();
9025  
9026      // Clear the Paginator's totalRecords if paginating
9027      var pag = this.get('paginator');
9028      if (pag) {
9029          pag.set('totalRecords',0);
9030      }
9031  
9032      // Clear selections
9033      this._unselectAllTrEls();
9034      this._unselectAllTdEls();
9035      this._aSelections = null;
9036      this._oAnchorRecord = null;
9037      this._oAnchorCell = null;
9038      
9039      // Clear sort
9040      this.set("sortedBy", null);
9041  },
9042  
9043  /**
9044   * Internal wrapper calls run() on render Chain instance.
9045   *
9046   * @method _runRenderChain
9047   * @private 
9048   */
9049  _runRenderChain : function() {
9050      this._oChainRender.run();
9051  },
9052  
9053  /**
9054   * Returns array of Records for current view. For example, if paginated, it
9055   * returns the subset of Records for current page.
9056   *
9057   * @method _getViewRecords
9058   * @protected
9059   * @return {Array} Array of Records to display in current view.
9060   */
9061  _getViewRecords : function() {
9062      // Paginator is enabled, show a subset of Records
9063      var oPaginator = this.get('paginator');
9064      if(oPaginator) {
9065          return this._oRecordSet.getRecords(
9066                          oPaginator.getStartIndex(),
9067                          oPaginator.getRowsPerPage());
9068      }
9069      // Not paginated, show all records
9070      else {
9071          return this._oRecordSet.getRecords();
9072      }
9073  
9074  },
9075  
9076  /**
9077   * Renders the view with existing Records from the RecordSet while
9078   * maintaining sort, pagination, and selection states. For performance, reuses
9079   * existing DOM elements when possible while deleting extraneous elements.
9080   *
9081   * @method render
9082   */
9083  render : function() {
9084  //YAHOO.example.Performance.trialStart = new Date();
9085  
9086      this._oChainRender.stop();
9087  
9088      this.fireEvent("beforeRenderEvent");
9089      YAHOO.log("DataTable rendering...", "info", this.toString());
9090  
9091      var i, j, k, len,
9092          allRecords = this._getViewRecords();
9093  
9094  
9095      // From the top, update in-place existing rows, so as to reuse DOM elements
9096      var elTbody = this._elTbody,
9097          loopN = this.get("renderLoopSize"),
9098          nRecordsLength = allRecords.length;
9099      
9100      // Table has rows
9101      if(nRecordsLength > 0) {                
9102          elTbody.style.display = "none";
9103          while(elTbody.lastChild) {
9104              elTbody.removeChild(elTbody.lastChild);
9105          }
9106          elTbody.style.display = "";
9107  
9108          // Set up the loop Chain to render rows
9109          this._oChainRender.add({
9110              method: function(oArg) {
9111                  if((this instanceof DT) && this._sId) {
9112                      var i = oArg.nCurrentRecord,
9113                          endRecordIndex = ((oArg.nCurrentRecord+oArg.nLoopLength) > nRecordsLength) ?
9114                                  nRecordsLength : (oArg.nCurrentRecord+oArg.nLoopLength),
9115                          elRow, nextSibling;
9116  
9117                      elTbody.style.display = "none";
9118                      
9119                      for(; i<endRecordIndex; i++) {
9120                          elRow = Dom.get(allRecords[i].getId());
9121                          elRow = elRow || this._addTrEl(allRecords[i]);
9122                          nextSibling = elTbody.childNodes[i] || null;
9123                          elTbody.insertBefore(elRow, nextSibling);
9124                      }
9125                      elTbody.style.display = "";
9126                      
9127                      // Set up for the next loop
9128                      oArg.nCurrentRecord = i;
9129                  }
9130              },
9131              scope: this,
9132              iterations: (loopN > 0) ? Math.ceil(nRecordsLength/loopN) : 1,
9133              argument: {
9134                  nCurrentRecord: 0,//nRecordsLength-1,  // Start at first Record
9135                  nLoopLength: (loopN > 0) ? loopN : nRecordsLength
9136              },
9137              timeout: (loopN > 0) ? 0 : -1
9138          });
9139          
9140          // Post-render tasks
9141          this._oChainRender.add({
9142              method: function(oArg) {
9143                  if((this instanceof DT) && this._sId) {
9144                      while(elTbody.rows.length > nRecordsLength) {
9145                          elTbody.removeChild(elTbody.lastChild);
9146                      }
9147                      this._setFirstRow();
9148                      this._setLastRow();
9149                      this._setRowStripes();
9150                      this._setSelections();
9151                  }
9152              },
9153              scope: this,
9154              timeout: (loopN > 0) ? 0 : -1
9155          });
9156       
9157      }
9158      // Table has no rows
9159      else {
9160          // Set up the loop Chain to delete rows
9161          var nTotal = elTbody.rows.length;
9162          if(nTotal > 0) {
9163              this._oChainRender.add({
9164                  method: function(oArg) {
9165                      if((this instanceof DT) && this._sId) {
9166                          var i = oArg.nCurrent,
9167                              loopN = oArg.nLoopLength,
9168                              nIterEnd = (i - loopN < 0) ? 0 : i - loopN;
9169      
9170                          elTbody.style.display = "none";
9171                          
9172                          for(; i>nIterEnd; i--) {
9173                              elTbody.deleteRow(-1);
9174                          }
9175                          elTbody.style.display = "";
9176                          
9177                          // Set up for the next loop
9178                          oArg.nCurrent = i;
9179                      }
9180                  },
9181                  scope: this,
9182                  iterations: (loopN > 0) ? Math.ceil(nTotal/loopN) : 1,
9183                  argument: {
9184                      nCurrent: nTotal, 
9185                      nLoopLength: (loopN > 0) ? loopN : nTotal
9186                  },
9187                  timeout: (loopN > 0) ? 0 : -1
9188              });
9189          }
9190      }
9191      this._runRenderChain();
9192  },
9193  
9194  /**
9195   * Disables DataTable UI.
9196   *
9197   * @method disable
9198   */
9199  disable : function() {
9200      this._disabled = true;
9201      var elTable = this._elTable;
9202      var elMask = this._elMask;
9203      elMask.style.width = elTable.offsetWidth + "px";
9204      elMask.style.height = elTable.offsetHeight + "px";
9205      elMask.style.left = elTable.offsetLeft + "px";
9206      elMask.style.display = "";
9207      this.fireEvent("disableEvent");
9208  },
9209  
9210  /**
9211   * Undisables DataTable UI.
9212   *
9213   * @method undisable
9214   */
9215  undisable : function() {
9216      this._disabled = false;
9217      this._elMask.style.display = "none";
9218      this.fireEvent("undisableEvent");
9219  },
9220  
9221   /**
9222   * Returns disabled state.
9223   *
9224   * @method isDisabled
9225   * @return {Boolean} True if UI is disabled, otherwise false
9226   */
9227  isDisabled : function() {
9228      return this._disabled;
9229  },
9230  
9231  /**
9232   * Nulls out the entire DataTable instance and related objects, removes attached
9233   * event listeners, and clears out DOM elements inside the container. After
9234   * calling this method, the instance reference should be expliclitly nulled by
9235   * implementer, as in myDataTable = null. Use with caution!
9236   *
9237   * @method destroy
9238   */
9239  destroy : function() {
9240      // Store for later
9241      var instanceName = this.toString();
9242  
9243      this._oChainRender.stop();
9244      
9245      // Destroy ColumnDD and ColumnResizers
9246      this._destroyColumnHelpers();
9247      
9248      // Destroy all CellEditors
9249      var oCellEditor;
9250      for(var i=0, len=this._oColumnSet.flat.length; i<len; i++) {
9251          oCellEditor = this._oColumnSet.flat[i].editor;
9252          if(oCellEditor && oCellEditor.destroy) {
9253              oCellEditor.destroy();
9254              this._oColumnSet.flat[i].editor = null;
9255          }
9256      }
9257  
9258      // Destroy Paginator
9259      this._destroyPaginator();
9260  
9261      // Unhook custom events
9262      this._oRecordSet.unsubscribeAll();
9263      this.unsubscribeAll();
9264  
9265      // Unhook DOM events
9266      Ev.removeListener(document, "click", this._onDocumentClick);
9267      
9268      // Clear out the container
9269      this._destroyContainerEl(this._elContainer);
9270  
9271      // Null out objects
9272      for(var param in this) {
9273          if(lang.hasOwnProperty(this, param)) {
9274              this[param] = null;
9275          }
9276      }
9277      
9278      // Clean up static values
9279      DT._nCurrentCount--;
9280      
9281      if(DT._nCurrentCount < 1) {
9282          if(DT._elDynStyleNode) {
9283              document.getElementsByTagName('head')[0].removeChild(DT._elDynStyleNode);
9284              DT._elDynStyleNode = null;
9285          }
9286      }
9287  
9288      YAHOO.log("DataTable instance destroyed: " + instanceName);
9289  },
9290  
9291  /**
9292   * Displays message within secondary TBODY.
9293   *
9294   * @method showTableMessage
9295   * @param sHTML {HTML} (optional) Value for innerHTML.
9296   * @param sClassName {String} (optional) Classname.
9297   */
9298  showTableMessage : function(sHTML, sClassName) {
9299      var elCell = this._elMsgTd;
9300      if(lang.isString(sHTML)) {
9301          elCell.firstChild.innerHTML = sHTML;
9302      }
9303      if(lang.isString(sClassName)) {
9304          elCell.className = sClassName;
9305      }
9306  
9307      this._elMsgTbody.style.display = "";
9308  
9309      this.fireEvent("tableMsgShowEvent", {html:sHTML, className:sClassName});
9310      YAHOO.log("DataTable showing message: " + sHTML, "info", this.toString());
9311  },
9312  
9313  /**
9314   * Hides secondary TBODY.
9315   *
9316   * @method hideTableMessage
9317   */
9318  hideTableMessage : function() {
9319      if(this._elMsgTbody.style.display != "none") {
9320          this._elMsgTbody.style.display = "none";
9321          this._elMsgTbody.parentNode.style.width = "";
9322          this.fireEvent("tableMsgHideEvent");
9323          YAHOO.log("DataTable message hidden", "info", this.toString());
9324      }
9325  },
9326  
9327  /**
9328   * Brings focus to the TBODY element. Alias to focusTbodyEl.
9329   *
9330   * @method focus
9331   */
9332  focus : function() {
9333      this.focusTbodyEl();
9334  },
9335  
9336  /**
9337   * Brings focus to the THEAD element.
9338   *
9339   * @method focusTheadEl
9340   */
9341  focusTheadEl : function() {
9342      this._focusEl(this._elThead);
9343  },
9344  
9345  /**
9346   * Brings focus to the TBODY element.
9347   *
9348   * @method focusTbodyEl
9349   */
9350  focusTbodyEl : function() {
9351      this._focusEl(this._elTbody);
9352  },
9353  
9354  /**
9355   * Setting display:none on DataTable or any parent may impact width validations.
9356   * After setting display back to "", implementers should call this method to 
9357   * manually perform those validations.
9358   *
9359   * @method onShow
9360   */
9361  onShow : function() {
9362      this.validateColumnWidths();
9363      
9364      for(var allKeys = this._oColumnSet.keys, i=0, len=allKeys.length, col; i<len; i++) {
9365          col = allKeys[i];
9366          if(col._ddResizer) {
9367              col._ddResizer.resetResizerEl();
9368          }
9369      }
9370  },
9371  
9372  
9373  
9374  
9375  
9376  
9377  
9378  
9379  
9380  
9381  
9382  
9383  
9384  
9385  
9386  
9387  
9388  
9389  
9390  
9391  
9392  
9393  
9394  
9395  
9396  
9397  
9398  
9399  
9400  
9401  
9402  
9403  
9404  
9405  
9406  
9407  
9408  
9409  
9410  
9411  
9412  
9413  
9414  
9415  
9416  
9417  
9418  
9419  
9420  
9421  
9422  
9423  
9424  
9425  
9426  
9427  
9428  
9429  
9430  
9431  
9432  
9433  
9434  
9435  
9436  
9437  
9438  // RECORDSET FUNCTIONS
9439  
9440  /**
9441   * Returns Record index for given TR element or page row index.
9442   *
9443   * @method getRecordIndex
9444   * @param row {YAHOO.widget.Record | HTMLElement | Number} Record instance, TR
9445   * element reference or page row index.
9446   * @return {Number} Record's RecordSet index, or null.
9447   */
9448  getRecordIndex : function(row) {
9449      var nTrIndex;
9450  
9451      if(!lang.isNumber(row)) {
9452          // By Record
9453          if(row instanceof YAHOO.widget.Record) {
9454              return this._oRecordSet.getRecordIndex(row);
9455          }
9456          // By element reference
9457          else {
9458              // Find the TR element
9459              var el = this.getTrEl(row);
9460              if(el) {
9461                  nTrIndex = el.sectionRowIndex;
9462              }
9463          }
9464      }
9465      // By page row index
9466      else {
9467          nTrIndex = row;
9468      }
9469  
9470      if(lang.isNumber(nTrIndex)) {
9471          var oPaginator = this.get("paginator");
9472          if(oPaginator) {
9473              return oPaginator.get('recordOffset') + nTrIndex;
9474          }
9475          else {
9476              return nTrIndex;
9477          }
9478      }
9479  
9480      YAHOO.log("Could not get Record index for row " + row, "info", this.toString());
9481      return null;
9482  },
9483  
9484  /**
9485   * For the given identifier, returns the associated Record instance.
9486   *
9487   * @method getRecord
9488   * @param row {HTMLElement | Number | String} DOM reference to a TR element (or
9489   * child of a TR element), RecordSet position index, or Record ID.
9490   * @return {YAHOO.widget.Record} Record instance.
9491   */
9492  getRecord : function(row) {
9493      var oRecord = this._oRecordSet.getRecord(row);
9494  
9495      if(!oRecord) {
9496          // Validate TR element
9497          var elRow = this.getTrEl(row);
9498          if(elRow) {
9499              oRecord = this._oRecordSet.getRecord(elRow.id);
9500          }
9501      }
9502  
9503      if(oRecord instanceof YAHOO.widget.Record) {
9504          return this._oRecordSet.getRecord(oRecord);
9505      }
9506      else {
9507          YAHOO.log("Could not get Record for row at " + row, "info", this.toString());
9508          return null;
9509      }
9510  },
9511  
9512  
9513  
9514  
9515  
9516  
9517  
9518  
9519  
9520  
9521  
9522  
9523  
9524  
9525  
9526  
9527  
9528  
9529  
9530  
9531  
9532  
9533  
9534  
9535  
9536  
9537  
9538  
9539  
9540  
9541  
9542  
9543  
9544  
9545  
9546  
9547  
9548  
9549  
9550  
9551  
9552  
9553  
9554  
9555  
9556  
9557  // COLUMN FUNCTIONS
9558  
9559  /**
9560   * For the given identifier, returns the associated Column instance. Note: For
9561   * getting Columns by Column ID string, please use the method getColumnById().
9562   *
9563   * @method getColumn
9564   * @param column {HTMLElement | String | Number} TH/TD element (or child of a
9565   * TH/TD element), a Column key, or a ColumnSet key index.
9566   * @return {YAHOO.widget.Column} Column instance.
9567   */
9568  getColumn : function(column) {
9569      var oColumn = this._oColumnSet.getColumn(column);
9570  
9571      if(!oColumn) {
9572          // Validate TD element
9573          var elCell = this.getTdEl(column);
9574          if(elCell) {
9575              oColumn = this._oColumnSet.getColumn(this.getCellIndex(elCell));
9576          }
9577          // Validate TH element
9578          else {
9579              elCell = this.getThEl(column);
9580              if(elCell) {
9581                  // Find by TH el ID
9582                  var allColumns = this._oColumnSet.flat;
9583                  for(var i=0, len=allColumns.length; i<len; i++) {
9584                      if(allColumns[i].getThEl().id === elCell.id) {
9585                          oColumn = allColumns[i];
9586                      } 
9587                  }
9588              }
9589          }
9590      }
9591      if(!oColumn) {
9592          YAHOO.log("Could not get Column for column at " + column, "info", this.toString());
9593      }
9594      return oColumn;
9595  },
9596  
9597  /**
9598   * For the given Column ID, returns the associated Column instance. Note: For
9599   * getting Columns by key, please use the method getColumn().
9600   *
9601   * @method getColumnById
9602   * @param column {String} Column ID string.
9603   * @return {YAHOO.widget.Column} Column instance.
9604   */
9605  getColumnById : function(column) {
9606      return this._oColumnSet.getColumnById(column);
9607  },
9608  
9609  /**
9610   * For the given Column instance, returns next direction to sort.
9611   *
9612   * @method getColumnSortDir
9613   * @param oColumn {YAHOO.widget.Column} Column instance.
9614   * @param oSortedBy {Object} (optional) Specify the state, or use current state. 
9615   * @return {String} YAHOO.widget.DataTable.CLASS_ASC or YAHOO.widget.DataTableCLASS_DESC.
9616   */
9617  getColumnSortDir : function(oColumn, oSortedBy) {
9618      // Backward compatibility
9619      if(oColumn.sortOptions && oColumn.sortOptions.defaultDir) {
9620          if(oColumn.sortOptions.defaultDir == "asc") {
9621              oColumn.sortOptions.defaultDir = DT.CLASS_ASC;
9622          }
9623          else if (oColumn.sortOptions.defaultDir == "desc") {
9624              oColumn.sortOptions.defaultDir = DT.CLASS_DESC;
9625          }
9626      }
9627      
9628      // What is the Column's default sort direction?
9629      var sortDir = (oColumn.sortOptions && oColumn.sortOptions.defaultDir) ? oColumn.sortOptions.defaultDir : DT.CLASS_ASC;
9630  
9631      // Is the Column currently sorted?
9632      var bSorted = false;
9633      oSortedBy = oSortedBy || this.get("sortedBy");
9634      if(oSortedBy && (oSortedBy.key === oColumn.key)) {
9635          bSorted = true;
9636          if(oSortedBy.dir) {
9637              sortDir = (oSortedBy.dir === DT.CLASS_ASC) ? DT.CLASS_DESC : DT.CLASS_ASC;
9638          }
9639          else {
9640              sortDir = (sortDir === DT.CLASS_ASC) ? DT.CLASS_DESC : DT.CLASS_ASC;
9641          }
9642      }
9643      return sortDir;
9644  },
9645  
9646  /**
9647   * Overridable method gives implementers a hook to show loading message before
9648   * sorting Column.
9649   *
9650   * @method doBeforeSortColumn
9651   * @param oColumn {YAHOO.widget.Column} Column instance.
9652   * @param sSortDir {String} YAHOO.widget.DataTable.CLASS_ASC or
9653   * YAHOO.widget.DataTable.CLASS_DESC.
9654   * @return {Boolean} Return true to continue sorting Column.
9655   */
9656  doBeforeSortColumn : function(oColumn, sSortDir) {
9657      this.showTableMessage(this.get("MSG_LOADING"), DT.CLASS_LOADING);
9658      return true;
9659  },
9660  
9661  /**
9662   * Sorts given Column. If "dynamicData" is true, current selections are purged before
9663   * a request is sent to the DataSource for data for the new state (using the
9664   * request returned by "generateRequest()").
9665   *
9666   * @method sortColumn
9667   * @param oColumn {YAHOO.widget.Column} Column instance.
9668   * @param sDir {String} (Optional) YAHOO.widget.DataTable.CLASS_ASC or
9669   * YAHOO.widget.DataTable.CLASS_DESC
9670   */
9671  sortColumn : function(oColumn, sDir) {
9672      if(oColumn && (oColumn instanceof YAHOO.widget.Column)) {
9673          if(!oColumn.sortable) {
9674              Dom.addClass(this.getThEl(oColumn), DT.CLASS_SORTABLE);
9675          }
9676          
9677          // Validate given direction
9678          if(sDir && (sDir !== DT.CLASS_ASC) && (sDir !== DT.CLASS_DESC)) {
9679              sDir = null;
9680          }
9681          
9682          // Get the sort dir
9683          var sSortDir = sDir || this.getColumnSortDir(oColumn);
9684  
9685          // Is the Column currently sorted?
9686          var oSortedBy = this.get("sortedBy") || {};
9687          var bSorted = (oSortedBy.key === oColumn.key) ? true : false;
9688  
9689          var ok = this.doBeforeSortColumn(oColumn, sSortDir);
9690          if(ok) {
9691              // Server-side sort
9692              if(this.get("dynamicData")) {
9693                  // Get current state
9694                  var oState = this.getState();
9695                  
9696                  // Reset record offset, if paginated
9697                  if(oState.pagination) {
9698                      oState.pagination.recordOffset = 0;
9699                  }
9700                  
9701                  // Update sortedBy to new values
9702                  oState.sortedBy = {
9703                      key: oColumn.key,
9704                      dir: sSortDir
9705                  };
9706                  
9707                  // Get the request for the new state
9708                  var request = this.get("generateRequest")(oState, this);
9709  
9710                  // Purge selections
9711                  this.unselectAllRows();
9712                  this.unselectAllCells();
9713  
9714                  // Send request for new data
9715                  var callback = {
9716                      success : this.onDataReturnSetRows,
9717                      failure : this.onDataReturnSetRows,
9718                      argument : oState, // Pass along the new state to the callback
9719                      scope : this
9720                  };
9721                  this._oDataSource.sendRequest(request, callback);            
9722              }
9723              // Client-side sort
9724              else {
9725                  // Is there a custom sort handler function defined?
9726                  var sortFnc = (oColumn.sortOptions && lang.isFunction(oColumn.sortOptions.sortFunction)) ?
9727                          // Custom sort function
9728                          oColumn.sortOptions.sortFunction : null;
9729                     
9730                  // Sort the Records
9731                  if(!bSorted || sDir || sortFnc) {
9732                      // Default sort function if necessary
9733                      sortFnc = sortFnc || this.get("sortFunction");
9734                      // Get the field to sort
9735                      var sField = (oColumn.sortOptions && oColumn.sortOptions.field) ? oColumn.sortOptions.field : oColumn.field;
9736  
9737                      // Sort the Records        
9738                      this._oRecordSet.sortRecords(sortFnc, ((sSortDir == DT.CLASS_DESC) ? true : false), sField);
9739                  }
9740                  // Just reverse the Records
9741                  else {
9742                      this._oRecordSet.reverseRecords();
9743                  }
9744          
9745                  // Reset to first page if paginated
9746                  var oPaginator = this.get('paginator');
9747                  if (oPaginator) {
9748                      // Set page silently, so as not to fire change event.
9749                      oPaginator.setPage(1,true);
9750                  }
9751          
9752                  // Update UI via sortedBy
9753                  this.render();
9754                  this.set("sortedBy", {key:oColumn.key, dir:sSortDir, column:oColumn}); 
9755              }       
9756              
9757              this.fireEvent("columnSortEvent",{column:oColumn,dir:sSortDir});
9758              YAHOO.log("Column \"" + oColumn.key + "\" sorted \"" + sSortDir + "\"", "info", this.toString());
9759              return;
9760          }
9761      }
9762      YAHOO.log("Could not sort Column \"" + oColumn.key + "\"", "warn", this.toString());
9763  },
9764  
9765  /**
9766   * Sets given Column to given pixel width. If new width is less than minimum
9767   * width, sets to minimum width. Updates oColumn.width value.
9768   *
9769   * @method setColumnWidth
9770   * @param oColumn {YAHOO.widget.Column} Column instance.
9771   * @param nWidth {Number} New width in pixels. A null value auto-sizes Column,
9772   * subject to minWidth and maxAutoWidth validations. 
9773   */
9774  setColumnWidth : function(oColumn, nWidth) {
9775      if(!(oColumn instanceof YAHOO.widget.Column)) {
9776          oColumn = this.getColumn(oColumn);
9777      }
9778      if(oColumn) {
9779          // Validate new width against minimum width
9780          if(lang.isNumber(nWidth)) {
9781              // This is why we must require a Number... :-|
9782              nWidth = (nWidth > oColumn.minWidth) ? nWidth : oColumn.minWidth;
9783  
9784              // Save state
9785              oColumn.width = nWidth;
9786              
9787              // Resize the DOM elements
9788              this._setColumnWidth(oColumn, nWidth+"px");
9789              
9790              this.fireEvent("columnSetWidthEvent",{column:oColumn,width:nWidth});
9791              YAHOO.log("Set width of Column " + oColumn + " to " + nWidth + "px", "info", this.toString());
9792          }
9793          // Unsets a width to auto-size
9794          else if(nWidth === null) {
9795              // Save state
9796              oColumn.width = nWidth;
9797              
9798              // Resize the DOM elements
9799              this._setColumnWidth(oColumn, "auto");
9800              this.validateColumnWidths(oColumn);
9801              this.fireEvent("columnUnsetWidthEvent",{column:oColumn});
9802              YAHOO.log("Column " + oColumn + " width unset", "info", this.toString());
9803          }
9804                  
9805          // Bug 2339454: resize then sort misaligment
9806          this._clearTrTemplateEl();
9807      }
9808      else {
9809          YAHOO.log("Could not set width of Column " + oColumn + " to " + nWidth + "px", "warn", this.toString());
9810      }
9811  },
9812  
9813  /**
9814   * Sets liner DIV elements of given Column to given width. When value should be
9815   * auto-calculated to fit content overflow is set to visible, otherwise overflow
9816   * is set to hidden. No validations against minimum width and no updating
9817   * Column.width value.
9818   *
9819   * @method _setColumnWidth
9820   * @param oColumn {YAHOO.widget.Column} Column instance.
9821   * @param sWidth {String} New width value.
9822   * @param sOverflow {String} Should be "hidden" when Column width is explicitly
9823   * being set to a value, but should be "visible" when Column is meant to auto-fit content.  
9824   * @private
9825   */
9826  _setColumnWidth : function(oColumn, sWidth, sOverflow) {
9827      if(oColumn && (oColumn.getKeyIndex() !== null)) {
9828          sOverflow = sOverflow || (((sWidth === '') || (sWidth === 'auto')) ? 'visible' : 'hidden');
9829      
9830          // Dynamic style algorithm
9831          if(!DT._bDynStylesFallback) {
9832              this._setColumnWidthDynStyles(oColumn, sWidth, sOverflow);
9833          }
9834          // Dynamic function algorithm
9835          else {
9836              this._setColumnWidthDynFunction(oColumn, sWidth, sOverflow);
9837          }
9838      }
9839      else {
9840          YAHOO.log("Could not set width of unknown Column " + oColumn + " to " + sWidth, "warn", this.toString());
9841      }
9842  },
9843  
9844  /**
9845   * Updates width of a Column's liner DIV elements by dynamically creating a
9846   * STYLE node and writing and updating CSS style rules to it. If this fails during
9847   * runtime, the fallback method _setColumnWidthDynFunction() will be called.
9848   * Notes: This technique is not performant in IE6. IE7 crashes if DataTable is
9849   * nested within another TABLE element. For these cases, it is recommended to
9850   * use the method _setColumnWidthDynFunction by setting _bDynStylesFallback to TRUE.
9851   *
9852   * @method _setColumnWidthDynStyles
9853   * @param oColumn {YAHOO.widget.Column} Column instance.
9854   * @param sWidth {String} New width value.
9855   * @private
9856   */
9857  _setColumnWidthDynStyles : function(oColumn, sWidth, sOverflow) {
9858      var s = DT._elDynStyleNode,
9859          rule;
9860      
9861      // Create a new STYLE node
9862      if(!s) {
9863          s = document.createElement('style');
9864          s.type = 'text/css';
9865          s = document.getElementsByTagName('head').item(0).appendChild(s);
9866          DT._elDynStyleNode = s;
9867      }
9868      
9869      // We have a STYLE node to update
9870      if(s) {
9871          // Use unique classname for this Column instance as a hook for resizing
9872          var sClassname = "." + this.getId() + "-col-" + oColumn.getSanitizedKey() + " ." + DT.CLASS_LINER;
9873          
9874          // Hide for performance
9875          if(this._elTbody) {
9876              this._elTbody.style.display = 'none';
9877          }
9878          
9879          rule = DT._oDynStyles[sClassname];
9880  
9881          // The Column does not yet have a rule
9882          if(!rule) {
9883              if(s.styleSheet && s.styleSheet.addRule) {
9884                  s.styleSheet.addRule(sClassname,"overflow:"+sOverflow);
9885                  s.styleSheet.addRule(sClassname,'width:'+sWidth);
9886                  rule = s.styleSheet.rules[s.styleSheet.rules.length-1];
9887                  DT._oDynStyles[sClassname] = rule;
9888              }
9889              else if(s.sheet && s.sheet.insertRule) {
9890                  s.sheet.insertRule(sClassname+" {overflow:"+sOverflow+";width:"+sWidth+";}",s.sheet.cssRules.length);
9891                  rule = s.sheet.cssRules[s.sheet.cssRules.length-1];
9892                  DT._oDynStyles[sClassname] = rule;
9893              }
9894          }
9895          // We have a rule to update
9896          else {
9897              rule.style.overflow = sOverflow;
9898              rule.style.width = sWidth;
9899          } 
9900          
9901          // Unhide
9902          if(this._elTbody) {
9903              this._elTbody.style.display = '';
9904          }
9905      }
9906      
9907      // That was not a success, we must call the fallback routine
9908      if(!rule) {
9909          DT._bDynStylesFallback = true;
9910          this._setColumnWidthDynFunction(oColumn, sWidth);
9911      }
9912  },
9913  
9914  /**
9915   * Updates width of a Column's liner DIV elements by dynamically creating a
9916   * function to update all element style properties in one pass. Note: This
9917   * technique is not supported in sandboxed environments that prohibit EVALs.    
9918   *
9919   * @method _setColumnWidthDynFunction
9920   * @param oColumn {YAHOO.widget.Column} Column instance.
9921   * @param sWidth {String} New width value.
9922   * @private
9923   */
9924  _setColumnWidthDynFunction : function(oColumn, sWidth, sOverflow) {
9925      // TODO: why is this here?
9926      if(sWidth == 'auto') {
9927          sWidth = ''; 
9928      }
9929      
9930      // Create one function for each value of rows.length
9931      var rowslen = this._elTbody ? this._elTbody.rows.length : 0;
9932      
9933      // Dynamically create the function
9934      if (!this._aDynFunctions[rowslen]) {
9935          
9936          //Compile a custom function to do all the liner div width
9937          //assignments at the same time.  A unique function is required
9938          //for each unique number of rows in _elTbody.  This will
9939          //result in a function declaration like:
9940          //function (oColumn,sWidth,sOverflow) {
9941          //    var colIdx = oColumn.getKeyIndex();
9942          //    oColumn.getThLinerEl().style.overflow =
9943          //    this._elTbody.rows[0].cells[colIdx].firstChild.style.overflow =
9944          //    this._elTbody.rows[1].cells[colIdx].firstChild.style.overflow =
9945          //    ... (for all row indices in this._elTbody.rows.length - 1)
9946          //    this._elTbody.rows[99].cells[colIdx].firstChild.style.overflow =
9947          //    sOverflow;
9948          //    oColumn.getThLinerEl().style.width =
9949          //    this._elTbody.rows[0].cells[colIdx].firstChild.style.width =
9950          //    this._elTbody.rows[1].cells[colIdx].firstChild.style.width =
9951          //    ... (for all row indices in this._elTbody.rows.length - 1)
9952          //    this._elTbody.rows[99].cells[colIdx].firstChild.style.width =
9953          //    sWidth;
9954          //}
9955          
9956          var i,j,k;
9957          var resizerDef = [
9958              'var colIdx=oColumn.getKeyIndex();',
9959              'oColumn.getThLinerEl().style.overflow='
9960          ];
9961          for (i=rowslen-1, j=2; i >= 0; --i) {
9962              resizerDef[j++] = 'this._elTbody.rows[';
9963              resizerDef[j++] = i;
9964              resizerDef[j++] = '].cells[colIdx].firstChild.style.overflow=';
9965          }
9966          resizerDef[j] = 'sOverflow;';
9967          resizerDef[j+1] = 'oColumn.getThLinerEl().style.width=';
9968          for (i=rowslen-1, k=j+2; i >= 0; --i) {
9969              resizerDef[k++] = 'this._elTbody.rows[';
9970              resizerDef[k++] = i;
9971              resizerDef[k++] = '].cells[colIdx].firstChild.style.width=';
9972          }
9973          resizerDef[k] = 'sWidth;';
9974          this._aDynFunctions[rowslen] =
9975              new Function('oColumn','sWidth','sOverflow',resizerDef.join(''));
9976      }
9977      
9978      // Get the function to execute
9979      var resizerFn = this._aDynFunctions[rowslen];
9980  
9981      // TODO: Hide TBODY for performance in _setColumnWidthDynFunction?
9982      if (resizerFn) {
9983          resizerFn.call(this,oColumn,sWidth,sOverflow);
9984      }
9985  },
9986  
9987  /**
9988   * For one or all Columns, when Column is not hidden, width is not set, and minWidth
9989   * and/or maxAutoWidth is set, validates auto-width against minWidth and maxAutoWidth.
9990   *
9991   * @method validateColumnWidths
9992   * @param oArg.column {YAHOO.widget.Column} (optional) One Column to validate. If null, all Columns' widths are validated.
9993   */
9994  validateColumnWidths : function(oColumn) {
9995      var elColgroup = this._elColgroup;
9996      var elColgroupClone = elColgroup.cloneNode(true);
9997      var bNeedsValidation = false;
9998      var allKeys = this._oColumnSet.keys;
9999      var elThLiner;
10000      // Validate just one Column's minWidth and/or maxAutoWidth
10001      if(oColumn && !oColumn.hidden && !oColumn.width && (oColumn.getKeyIndex() !== null)) {
10002              elThLiner = oColumn.getThLinerEl();
10003              if((oColumn.minWidth > 0) && (elThLiner.offsetWidth < oColumn.minWidth)) {
10004                  elColgroupClone.childNodes[oColumn.getKeyIndex()].style.width = 
10005                          oColumn.minWidth + 
10006                          (parseInt(Dom.getStyle(elThLiner,"paddingLeft"),10)|0) +
10007                          (parseInt(Dom.getStyle(elThLiner,"paddingRight"),10)|0) + "px";
10008                  bNeedsValidation = true;
10009              }
10010              else if((oColumn.maxAutoWidth > 0) && (elThLiner.offsetWidth > oColumn.maxAutoWidth)) {
10011                  this._setColumnWidth(oColumn, oColumn.maxAutoWidth+"px", "hidden");
10012              }
10013      }
10014      // Validate all Columns
10015      else {
10016          for(var i=0, len=allKeys.length; i<len; i++) {
10017              oColumn = allKeys[i];
10018              if(!oColumn.hidden && !oColumn.width) {
10019                  elThLiner = oColumn.getThLinerEl();
10020                  if((oColumn.minWidth > 0) && (elThLiner.offsetWidth < oColumn.minWidth)) {
10021                      elColgroupClone.childNodes[i].style.width = 
10022                              oColumn.minWidth + 
10023                              (parseInt(Dom.getStyle(elThLiner,"paddingLeft"),10)|0) +
10024                              (parseInt(Dom.getStyle(elThLiner,"paddingRight"),10)|0) + "px";
10025                      bNeedsValidation = true;
10026                  }
10027                  else if((oColumn.maxAutoWidth > 0) && (elThLiner.offsetWidth > oColumn.maxAutoWidth)) {
10028                      this._setColumnWidth(oColumn, oColumn.maxAutoWidth+"px", "hidden");
10029                  }
10030              }
10031          }
10032      }
10033      if(bNeedsValidation) {
10034          elColgroup.parentNode.replaceChild(elColgroupClone, elColgroup);
10035          this._elColgroup = elColgroupClone;
10036      }
10037  },
10038  
10039  /**
10040   * Clears minWidth.
10041   *
10042   * @method _clearMinWidth
10043   * @param oColumn {YAHOO.widget.Column} Which Column.
10044   * @private
10045   */
10046  _clearMinWidth : function(oColumn) {
10047      if(oColumn.getKeyIndex() !== null) {
10048          this._elColgroup.childNodes[oColumn.getKeyIndex()].style.width = '';
10049      }
10050  },
10051  
10052  /**
10053   * Restores minWidth.
10054   *
10055   * @method _restoreMinWidth
10056   * @param oColumn {YAHOO.widget.Column} Which Column.
10057   * @private
10058   */
10059  _restoreMinWidth : function(oColumn) {
10060      if(oColumn.minWidth && (oColumn.getKeyIndex() !== null)) {
10061          this._elColgroup.childNodes[oColumn.getKeyIndex()].style.width = oColumn.minWidth + 'px';
10062      }
10063  },
10064  
10065  /**
10066   * Hides given Column. NOTE: You cannot hide/show nested Columns. You can only
10067   * hide/show non-nested Columns, and top-level parent Columns (which will
10068   * hide/show all children Columns).
10069   *
10070   * @method hideColumn
10071   * @param oColumn {YAHOO.widget.Column | HTMLElement | String | Number} Column
10072   * instance, TH/TD element (or child of a TH/TD element), a Column key, or a
10073   * ColumnSet key index.
10074   */
10075  hideColumn : function(oColumn) {
10076      if(!(oColumn instanceof YAHOO.widget.Column)) {
10077          oColumn = this.getColumn(oColumn);
10078      }
10079      // Only top-level Columns can get hidden due to issues in FF2 and SF3
10080      if(oColumn && !oColumn.hidden && oColumn.getTreeIndex() !== null) {
10081          
10082          var allrows = this.getTbodyEl().rows;
10083          var l = allrows.length;
10084          var allDescendants = this._oColumnSet.getDescendants(oColumn);
10085          
10086          // Hide each nested Column
10087          for(var i=0, len=allDescendants.length; i<len; i++) {
10088              var thisColumn = allDescendants[i];
10089              thisColumn.hidden = true;
10090  
10091              // Style the head cell
10092              Dom.addClass(thisColumn.getThEl(), DT.CLASS_HIDDEN);
10093              
10094              // Does this Column have body cells?
10095              var thisKeyIndex = thisColumn.getKeyIndex();
10096              if(thisKeyIndex !== null) {                    
10097                  // Clear minWidth
10098                  this._clearMinWidth(oColumn);
10099                  
10100                  // Style the body cells
10101                  for(var j=0;j<l;j++) {
10102                      Dom.addClass(allrows[j].cells[thisKeyIndex],DT.CLASS_HIDDEN);
10103                  }
10104              }
10105              
10106              this.fireEvent("columnHideEvent",{column:thisColumn});
10107              YAHOO.log("Column \"" + oColumn.key + "\" hidden", "info", this.toString());
10108          }
10109        
10110          this._repaintOpera();
10111          this._clearTrTemplateEl();
10112      }
10113      else {
10114          YAHOO.log("Could not hide Column \"" + lang.dump(oColumn) + "\". Only non-nested Columns can be hidden", "warn", this.toString());
10115      }
10116  },
10117  
10118  /**
10119   * Shows given Column. NOTE: You cannot hide/show nested Columns. You can only
10120   * hide/show non-nested Columns, and top-level parent Columns (which will
10121   * hide/show all children Columns).
10122   *
10123   * @method showColumn
10124   * @param oColumn {YAHOO.widget.Column | HTMLElement | String | Number} Column
10125   * instance, TH/TD element (or child of a TH/TD element), a Column key, or a
10126   * ColumnSet key index.
10127   */
10128  showColumn : function(oColumn) {
10129      if(!(oColumn instanceof YAHOO.widget.Column)) {
10130          oColumn = this.getColumn(oColumn);
10131      }
10132      // Only top-level Columns can get hidden
10133      if(oColumn && oColumn.hidden && (oColumn.getTreeIndex() !== null)) {
10134          var allrows = this.getTbodyEl().rows;
10135          var l = allrows.length;
10136          var allDescendants = this._oColumnSet.getDescendants(oColumn);
10137          
10138          // Show each nested Column
10139          for(var i=0, len=allDescendants.length; i<len; i++) {
10140              var thisColumn = allDescendants[i];
10141              thisColumn.hidden = false;
10142              
10143              // Unstyle the head cell
10144              Dom.removeClass(thisColumn.getThEl(), DT.CLASS_HIDDEN);
10145  
10146              // Does this Column have body cells?
10147              var thisKeyIndex = thisColumn.getKeyIndex();
10148              if(thisKeyIndex !== null) {
10149                  // Restore minWidth
10150                  this._restoreMinWidth(oColumn);
10151                  
10152              
10153                  // Unstyle the body cells
10154                  for(var j=0;j<l;j++) {
10155                      Dom.removeClass(allrows[j].cells[thisKeyIndex],DT.CLASS_HIDDEN);
10156                  }
10157              }
10158  
10159              this.fireEvent("columnShowEvent",{column:thisColumn});
10160              YAHOO.log("Column \"" + oColumn.key + "\" shown", "info", this.toString());
10161          }
10162          this._clearTrTemplateEl();
10163      }
10164      else {
10165          YAHOO.log("Could not show Column \"" + lang.dump(oColumn) + "\". Only non-nested Columns can be shown", "warn", this.toString());
10166      }
10167  },
10168  
10169  /**
10170   * Removes given Column. NOTE: You cannot remove nested Columns. You can only remove
10171   * non-nested Columns, and top-level parent Columns (which will remove all
10172   * children Columns).
10173   *
10174   * @method removeColumn
10175   * @param oColumn {YAHOO.widget.Column} Column instance.
10176   * @return oColumn {YAHOO.widget.Column} Removed Column instance.
10177   */
10178  removeColumn : function(oColumn) {
10179      // Validate Column
10180      if(!(oColumn instanceof YAHOO.widget.Column)) {
10181          oColumn = this.getColumn(oColumn);
10182      }
10183      if(oColumn) {
10184          var nColTreeIndex = oColumn.getTreeIndex();
10185          if(nColTreeIndex !== null) {
10186              // Which key index(es)
10187              var i, len,
10188                  aKeyIndexes = oColumn.getKeyIndex();
10189              // Must be a parent Column
10190              if(aKeyIndexes === null) {
10191                  var descKeyIndexes = [];
10192                  var allDescendants = this._oColumnSet.getDescendants(oColumn);
10193                  for(i=0, len=allDescendants.length; i<len; i++) {
10194                      // Is this descendant a key Column?
10195                      var thisKey = allDescendants[i].getKeyIndex();
10196                      if(thisKey !== null) {
10197                          descKeyIndexes[descKeyIndexes.length] = thisKey;
10198                      }
10199                  }
10200                  if(descKeyIndexes.length > 0) {
10201                      aKeyIndexes = descKeyIndexes;
10202                  }
10203              }
10204              // Must be a key Column
10205              else {
10206                  aKeyIndexes = [aKeyIndexes];
10207              }
10208              
10209              if(aKeyIndexes !== null) {
10210                  // Sort the indexes so we can remove from the right
10211                  aKeyIndexes.sort(function(a, b) {return YAHOO.util.Sort.compare(a, b);});
10212                  
10213                  // Destroy previous THEAD
10214                  this._destroyTheadEl();
10215      
10216                  // Create new THEAD
10217                  var aOrigColumnDefs = this._oColumnSet.getDefinitions();
10218                  oColumn = aOrigColumnDefs.splice(nColTreeIndex,1)[0];
10219                  this._initColumnSet(aOrigColumnDefs);
10220                  this._initTheadEl();
10221                  
10222                  // Remove COL
10223                  for(i=aKeyIndexes.length-1; i>-1; i--) {
10224                      this._removeColgroupColEl(aKeyIndexes[i]);
10225                  }
10226                  
10227                  // Remove TD
10228                  var allRows = this._elTbody.rows;
10229                  if(allRows.length > 0) {
10230                      var loopN = this.get("renderLoopSize"),
10231                          loopEnd = allRows.length;
10232                      this._oChainRender.add({
10233                          method: function(oArg) {
10234                              if((this instanceof DT) && this._sId) {
10235                                  var i = oArg.nCurrentRow,
10236                                      len = loopN > 0 ? Math.min(i + loopN,allRows.length) : allRows.length,
10237                                      aIndexes = oArg.aIndexes,
10238                                      j;
10239                                  for(; i < len; ++i) {
10240                                      for(j = aIndexes.length-1; j>-1; j--) {
10241                                          allRows[i].removeChild(allRows[i].childNodes[aIndexes[j]]);
10242                                      }
10243                                  }
10244                                  oArg.nCurrentRow = i;
10245                              }
10246                          },
10247                          iterations: (loopN > 0) ? Math.ceil(loopEnd/loopN) : 1,
10248                          argument: {nCurrentRow:0, aIndexes:aKeyIndexes},
10249                          scope: this,
10250                          timeout: (loopN > 0) ? 0 : -1
10251                      });
10252                      this._runRenderChain();
10253                  }
10254          
10255                  this.fireEvent("columnRemoveEvent",{column:oColumn});
10256                  YAHOO.log("Column \"" + oColumn.key + "\" removed", "info", this.toString());
10257                  return oColumn;
10258              }
10259          }
10260      }
10261      YAHOO.log("Could not remove Column \"" + oColumn.key + "\". Only non-nested Columns can be removed", "warn", this.toString());
10262  },
10263  
10264  /**
10265   * Inserts given Column at the index if given, otherwise at the end. NOTE: You
10266   * can only add non-nested Columns and top-level parent Columns. You cannot add
10267   * a nested Column to an existing parent.
10268   *
10269   * @method insertColumn
10270   * @param oColumn {Object | YAHOO.widget.Column} Object literal Column
10271   * definition or a Column instance.
10272   * @param index {Number} (optional) New tree index.
10273   * @return oColumn {YAHOO.widget.Column} Inserted Column instance. 
10274   */
10275  insertColumn : function(oColumn, index) {
10276      // Validate Column
10277      if(oColumn instanceof YAHOO.widget.Column) {
10278          oColumn = oColumn.getDefinition();
10279      }
10280      else if(oColumn.constructor !== Object) {
10281          YAHOO.log("Could not insert Column \"" + oColumn + "\" due to invalid argument", "warn", this.toString());
10282          return;
10283      }
10284      
10285      // Validate index or append new Column to the end of the ColumnSet
10286      var oColumnSet = this._oColumnSet;
10287      if(!lang.isValue(index) || !lang.isNumber(index)) {
10288          index = oColumnSet.tree[0].length;
10289      }
10290      
10291      // Destroy previous THEAD
10292      this._destroyTheadEl();
10293      
10294      // Create new THEAD
10295      var aNewColumnDefs = this._oColumnSet.getDefinitions();
10296      aNewColumnDefs.splice(index, 0, oColumn);
10297      this._initColumnSet(aNewColumnDefs);
10298      this._initTheadEl();
10299      
10300      // Need to refresh the reference
10301      oColumnSet = this._oColumnSet;
10302      var oNewColumn = oColumnSet.tree[0][index];
10303      
10304      // Get key index(es) for new Column
10305      var i, len,
10306          descKeyIndexes = [];
10307      var allDescendants = oColumnSet.getDescendants(oNewColumn);
10308      for(i=0, len=allDescendants.length; i<len; i++) {
10309          // Is this descendant a key Column?
10310          var thisKey = allDescendants[i].getKeyIndex();
10311          if(thisKey !== null) {
10312              descKeyIndexes[descKeyIndexes.length] = thisKey;
10313          }
10314      }
10315      
10316      if(descKeyIndexes.length > 0) {  
10317          // Sort the indexes
10318          var newIndex = descKeyIndexes.sort(function(a, b) {return YAHOO.util.Sort.compare(a, b);})[0];
10319          
10320          // Add COL
10321          for(i=descKeyIndexes.length-1; i>-1; i--) {
10322              this._insertColgroupColEl(descKeyIndexes[i]);
10323          }
10324              
10325          // Add TD
10326          var allRows = this._elTbody.rows;
10327          if(allRows.length > 0) {
10328              var loopN = this.get("renderLoopSize"),
10329                  loopEnd = allRows.length;
10330              
10331              // Get templates for each new TD
10332              var aTdTemplates = [],
10333                  elTdTemplate;
10334              for(i=0, len=descKeyIndexes.length; i<len; i++) {
10335                  var thisKeyIndex = descKeyIndexes[i];
10336                  elTdTemplate = this._getTrTemplateEl().childNodes[i].cloneNode(true);
10337                  elTdTemplate = this._formatTdEl(this._oColumnSet.keys[thisKeyIndex], elTdTemplate, thisKeyIndex, (thisKeyIndex===this._oColumnSet.keys.length-1));
10338                  aTdTemplates[thisKeyIndex] = elTdTemplate;
10339              }
10340              
10341              this._oChainRender.add({
10342                  method: function(oArg) {
10343                      if((this instanceof DT) && this._sId) {
10344                          var i = oArg.nCurrentRow, j,
10345                              descKeyIndexes = oArg.descKeyIndexes,
10346                              len = loopN > 0 ? Math.min(i + loopN,allRows.length) : allRows.length,
10347                              nextSibling;
10348                          for(; i < len; ++i) {
10349                              nextSibling = allRows[i].childNodes[newIndex] || null;
10350                              for(j=descKeyIndexes.length-1; j>-1; j--) {
10351                                  allRows[i].insertBefore(oArg.aTdTemplates[descKeyIndexes[j]].cloneNode(true), nextSibling);
10352                              }
10353                          }
10354                          oArg.nCurrentRow = i;
10355                      }
10356                  },
10357                  iterations: (loopN > 0) ? Math.ceil(loopEnd/loopN) : 1,
10358                  argument: {nCurrentRow:0,aTdTemplates:aTdTemplates,descKeyIndexes:descKeyIndexes},
10359                  scope: this,
10360                  timeout: (loopN > 0) ? 0 : -1
10361              });
10362              this._runRenderChain(); 
10363          }
10364  
10365          this.fireEvent("columnInsertEvent",{column:oColumn,index:index});
10366          YAHOO.log("Column \"" + oColumn.key + "\" inserted into index " + index, "info", this.toString());
10367          return oNewColumn;
10368      }
10369  },
10370  
10371  /**
10372   * Removes given Column and inserts into given tree index. NOTE: You
10373   * can only reorder non-nested Columns and top-level parent Columns. You cannot
10374   * reorder a nested Column to an existing parent.
10375   *
10376   * @method reorderColumn
10377   * @param oColumn {YAHOO.widget.Column} Column instance.
10378   * @param index {Number} New tree index.
10379   * @return oColumn {YAHOO.widget.Column} Reordered Column instance. 
10380   */
10381  reorderColumn : function(oColumn, index) {
10382      // Validate Column and new index
10383      if(!(oColumn instanceof YAHOO.widget.Column)) {
10384          oColumn = this.getColumn(oColumn);
10385      }
10386      if(oColumn && YAHOO.lang.isNumber(index)) {
10387          var nOrigTreeIndex = oColumn.getTreeIndex();
10388          if((nOrigTreeIndex !== null) && (nOrigTreeIndex !== index)) {
10389              // Which key index(es)
10390              var i, len,
10391                  aOrigKeyIndexes = oColumn.getKeyIndex(),
10392                  allDescendants,
10393                  descKeyIndexes = [],
10394                  thisKey;
10395              // Must be a parent Column...
10396              if(aOrigKeyIndexes === null) {
10397                  allDescendants = this._oColumnSet.getDescendants(oColumn);
10398                  for(i=0, len=allDescendants.length; i<len; i++) {
10399                      // Is this descendant a key Column?
10400                      thisKey = allDescendants[i].getKeyIndex();
10401                      if(thisKey !== null) {
10402                          descKeyIndexes[descKeyIndexes.length] = thisKey;
10403                      }
10404                  }
10405                  if(descKeyIndexes.length > 0) {
10406                      aOrigKeyIndexes = descKeyIndexes;
10407                  }
10408              }
10409              // ...or else must be a key Column
10410              else {
10411                  aOrigKeyIndexes = [aOrigKeyIndexes];
10412              }
10413              
10414              if(aOrigKeyIndexes !== null) {                   
10415                  // Sort the indexes
10416                  aOrigKeyIndexes.sort(function(a, b) {return YAHOO.util.Sort.compare(a, b);});
10417                  
10418                  // Destroy previous THEAD
10419                  this._destroyTheadEl();
10420      
10421                  // Create new THEAD
10422                  var aColumnDefs = this._oColumnSet.getDefinitions();
10423                  var oColumnDef = aColumnDefs.splice(nOrigTreeIndex,1)[0];
10424                  aColumnDefs.splice(index, 0, oColumnDef);
10425                  this._initColumnSet(aColumnDefs);
10426                  this._initTheadEl();
10427                  
10428                  // Need to refresh the reference
10429                  var oNewColumn = this._oColumnSet.tree[0][index];
10430  
10431                  // What are new key index(es)
10432                  var aNewKeyIndexes = oNewColumn.getKeyIndex();
10433                  // Must be a parent Column
10434                  if(aNewKeyIndexes === null) {
10435                      descKeyIndexes = [];
10436                      allDescendants = this._oColumnSet.getDescendants(oNewColumn);
10437                      for(i=0, len=allDescendants.length; i<len; i++) {
10438                          // Is this descendant a key Column?
10439                          thisKey = allDescendants[i].getKeyIndex();
10440                          if(thisKey !== null) {
10441                              descKeyIndexes[descKeyIndexes.length] = thisKey;
10442                          }
10443                      }
10444                      if(descKeyIndexes.length > 0) {
10445                          aNewKeyIndexes = descKeyIndexes;
10446                      }
10447                  }
10448                  // Must be a key Column
10449                  else {
10450                      aNewKeyIndexes = [aNewKeyIndexes];
10451                  }
10452                  
10453                  // Sort the new indexes and grab the first one for the new location
10454                  var newIndex = aNewKeyIndexes.sort(function(a, b) {return YAHOO.util.Sort.compare(a, b);})[0];
10455  
10456                  // Reorder COL
10457                  this._reorderColgroupColEl(aOrigKeyIndexes, newIndex);
10458                  
10459                  // Reorder TD
10460                  var allRows = this._elTbody.rows;
10461                  if(allRows.length > 0) {
10462                      var loopN = this.get("renderLoopSize"),
10463                          loopEnd = allRows.length;
10464                      this._oChainRender.add({
10465                          method: function(oArg) {
10466                              if((this instanceof DT) && this._sId) {
10467                                  var i = oArg.nCurrentRow, j, tmpTds, nextSibling,
10468                                      len = loopN > 0 ? Math.min(i + loopN,allRows.length) : allRows.length,
10469                                      aIndexes = oArg.aIndexes, thisTr;
10470                                  // For each row
10471                                  for(; i < len; ++i) {
10472                                      tmpTds = [];
10473                                      thisTr = allRows[i];
10474                                      
10475                                      // Remove each TD
10476                                      for(j=aIndexes.length-1; j>-1; j--) {
10477                                          tmpTds.push(thisTr.removeChild(thisTr.childNodes[aIndexes[j]]));
10478                                      }
10479                                      
10480                                      // Insert each TD
10481                                      nextSibling = thisTr.childNodes[newIndex] || null;
10482                                      for(j=tmpTds.length-1; j>-1; j--) {
10483                                          thisTr.insertBefore(tmpTds[j], nextSibling);
10484                                      }                                    
10485                                  }
10486                                  oArg.nCurrentRow = i;
10487                              }
10488                          },
10489                          iterations: (loopN > 0) ? Math.ceil(loopEnd/loopN) : 1,
10490                          argument: {nCurrentRow:0, aIndexes:aOrigKeyIndexes},
10491                          scope: this,
10492                          timeout: (loopN > 0) ? 0 : -1
10493                      });
10494                      this._runRenderChain();
10495                  }
10496          
10497                  this.fireEvent("columnReorderEvent",{column:oNewColumn, oldIndex:nOrigTreeIndex});
10498                  YAHOO.log("Column \"" + oNewColumn.key + "\" reordered", "info", this.toString());
10499                  return oNewColumn;
10500              }
10501          }
10502      }
10503      YAHOO.log("Could not reorder Column \"" + oColumn.key + "\". Only non-nested Columns can be reordered", "warn", this.toString());
10504  },
10505  
10506  /**
10507   * Selects given Column. NOTE: You cannot select/unselect nested Columns. You can only
10508   * select/unselect non-nested Columns, and bottom-level key Columns.
10509   *
10510   * @method selectColumn
10511   * @param column {HTMLElement | String | Number} DOM reference or ID string to a
10512   * TH/TD element (or child of a TH/TD element), a Column key, or a ColumnSet key index.
10513   */
10514  selectColumn : function(oColumn) {
10515      oColumn = this.getColumn(oColumn);
10516      if(oColumn && !oColumn.selected) {
10517          // Only bottom-level Columns can get hidden
10518          if(oColumn.getKeyIndex() !== null) {
10519              oColumn.selected = true;
10520              
10521              // Update head cell
10522              var elTh = oColumn.getThEl();
10523              Dom.addClass(elTh,DT.CLASS_SELECTED);
10524  
10525              // Update body cells
10526              var allRows = this.getTbodyEl().rows;
10527              var oChainRender = this._oChainRender;
10528              oChainRender.add({
10529                  method: function(oArg) {
10530                      if((this instanceof DT) && this._sId && allRows[oArg.rowIndex] && allRows[oArg.rowIndex].cells[oArg.cellIndex]) {
10531                          Dom.addClass(allRows[oArg.rowIndex].cells[oArg.cellIndex],DT.CLASS_SELECTED);                    
10532                      }
10533                      oArg.rowIndex++;
10534                  },
10535                  scope: this,
10536                  iterations: allRows.length,
10537                  argument: {rowIndex:0,cellIndex:oColumn.getKeyIndex()}
10538              });
10539  
10540              this._clearTrTemplateEl();
10541              
10542              this._elTbody.style.display = "none";
10543              this._runRenderChain();
10544              this._elTbody.style.display = "";      
10545              
10546              this.fireEvent("columnSelectEvent",{column:oColumn});
10547              YAHOO.log("Column \"" + oColumn.key + "\" selected", "info", this.toString());
10548          }
10549          else {
10550              YAHOO.log("Could not select Column \"" + oColumn.key + "\". Only non-nested Columns can be selected", "warn", this.toString());
10551          }
10552      }
10553  },
10554  
10555  /**
10556   * Unselects given Column. NOTE: You cannot select/unselect nested Columns. You can only
10557   * select/unselect non-nested Columns, and bottom-level key Columns.
10558   *
10559   * @method unselectColumn
10560   * @param column {HTMLElement | String | Number} DOM reference or ID string to a
10561   * TH/TD element (or child of a TH/TD element), a Column key, or a ColumnSet key index.
10562   */
10563  unselectColumn : function(oColumn) {
10564      oColumn = this.getColumn(oColumn);
10565      if(oColumn && oColumn.selected) {
10566          // Only bottom-level Columns can get hidden
10567          if(oColumn.getKeyIndex() !== null) {
10568              oColumn.selected = false;
10569              
10570              // Update head cell
10571              var elTh = oColumn.getThEl();
10572              Dom.removeClass(elTh,DT.CLASS_SELECTED);
10573  
10574              // Update body cells
10575              var allRows = this.getTbodyEl().rows;
10576              var oChainRender = this._oChainRender;
10577              oChainRender.add({
10578                  method: function(oArg) {
10579                      if((this instanceof DT) && this._sId && allRows[oArg.rowIndex] && allRows[oArg.rowIndex].cells[oArg.cellIndex]) {
10580                          Dom.removeClass(allRows[oArg.rowIndex].cells[oArg.cellIndex],DT.CLASS_SELECTED); 
10581                      }                   
10582                      oArg.rowIndex++;
10583                  },
10584                  scope: this,
10585                  iterations:allRows.length,
10586                  argument: {rowIndex:0,cellIndex:oColumn.getKeyIndex()}
10587              });
10588              
10589              this._clearTrTemplateEl();
10590  
10591              this._elTbody.style.display = "none";
10592              this._runRenderChain();
10593              this._elTbody.style.display = "";      
10594              
10595              this.fireEvent("columnUnselectEvent",{column:oColumn});
10596              YAHOO.log("Column \"" + oColumn.key + "\" unselected", "info", this.toString());
10597          }
10598          else {
10599              YAHOO.log("Could not unselect Column \"" + oColumn.key + "\". Only non-nested Columns can be unselected", "warn", this.toString());
10600          }
10601      }
10602  },
10603  
10604  /**
10605   * Returns an array selected Column instances.
10606   *
10607   * @method getSelectedColumns
10608   * @return {YAHOO.widget.Column[]} Array of Column instances.
10609   */
10610  getSelectedColumns : function(oColumn) {
10611      var selectedColumns = [];
10612      var aKeys = this._oColumnSet.keys;
10613      for(var i=0,len=aKeys.length; i<len; i++) {
10614          if(aKeys[i].selected) {
10615              selectedColumns[selectedColumns.length] = aKeys[i];
10616          }
10617      }
10618      return selectedColumns;
10619  },
10620  
10621  /**
10622   * Assigns the class YAHOO.widget.DataTable.CLASS_HIGHLIGHTED to cells of the given Column.
10623   * NOTE: You cannot highlight/unhighlight nested Columns. You can only
10624   * highlight/unhighlight non-nested Columns, and bottom-level key Columns.
10625   *
10626   * @method highlightColumn
10627   * @param column {HTMLElement | String | Number} DOM reference or ID string to a
10628   * TH/TD element (or child of a TH/TD element), a Column key, or a ColumnSet key index.
10629   */
10630  highlightColumn : function(column) {
10631      var oColumn = this.getColumn(column);
10632      // Only bottom-level Columns can get highlighted
10633      if(oColumn && (oColumn.getKeyIndex() !== null)) {            
10634          // Update head cell
10635          var elTh = oColumn.getThEl();
10636          Dom.addClass(elTh,DT.CLASS_HIGHLIGHTED);
10637  
10638          // Update body cells
10639          var allRows = this.getTbodyEl().rows;
10640          var oChainRender = this._oChainRender;
10641          oChainRender.add({
10642              method: function(oArg) {
10643                  if((this instanceof DT) && this._sId && allRows[oArg.rowIndex] && allRows[oArg.rowIndex].cells[oArg.cellIndex]) {
10644                      Dom.addClass(allRows[oArg.rowIndex].cells[oArg.cellIndex],DT.CLASS_HIGHLIGHTED);   
10645                  }                 
10646                  oArg.rowIndex++;
10647              },
10648              scope: this,
10649              iterations:allRows.length,
10650              argument: {rowIndex:0,cellIndex:oColumn.getKeyIndex()},
10651              timeout: -1
10652          });
10653          this._elTbody.style.display = "none";
10654          this._runRenderChain();
10655          this._elTbody.style.display = "";      
10656              
10657          this.fireEvent("columnHighlightEvent",{column:oColumn});
10658          YAHOO.log("Column \"" + oColumn.key + "\" highlighed", "info", this.toString());
10659      }
10660      else {
10661          YAHOO.log("Could not highlight Column \"" + oColumn.key + "\". Only non-nested Columns can be highlighted", "warn", this.toString());
10662      }
10663  },
10664  
10665  /**
10666   * Removes the class YAHOO.widget.DataTable.CLASS_HIGHLIGHTED to cells of the given Column.
10667   * NOTE: You cannot highlight/unhighlight nested Columns. You can only
10668   * highlight/unhighlight non-nested Columns, and bottom-level key Columns.
10669   *
10670   * @method unhighlightColumn
10671   * @param column {HTMLElement | String | Number} DOM reference or ID string to a
10672   * TH/TD element (or child of a TH/TD element), a Column key, or a ColumnSet key index.
10673   */
10674  unhighlightColumn : function(column) {
10675      var oColumn = this.getColumn(column);
10676      // Only bottom-level Columns can get highlighted
10677      if(oColumn && (oColumn.getKeyIndex() !== null)) {
10678          // Update head cell
10679          var elTh = oColumn.getThEl();
10680          Dom.removeClass(elTh,DT.CLASS_HIGHLIGHTED);
10681  
10682          // Update body cells
10683          var allRows = this.getTbodyEl().rows;
10684          var oChainRender = this._oChainRender;
10685          oChainRender.add({
10686              method: function(oArg) {
10687                  if((this instanceof DT) && this._sId && allRows[oArg.rowIndex] && allRows[oArg.rowIndex].cells[oArg.cellIndex]) {
10688                      Dom.removeClass(allRows[oArg.rowIndex].cells[oArg.cellIndex],DT.CLASS_HIGHLIGHTED);
10689                  }                 
10690                  oArg.rowIndex++;
10691              },
10692              scope: this,
10693              iterations:allRows.length,
10694              argument: {rowIndex:0,cellIndex:oColumn.getKeyIndex()},
10695              timeout: -1
10696          });
10697          this._elTbody.style.display = "none";
10698          this._runRenderChain();
10699          this._elTbody.style.display = "";     
10700              
10701          this.fireEvent("columnUnhighlightEvent",{column:oColumn});
10702          YAHOO.log("Column \"" + oColumn.key + "\" unhighlighted", "info", this.toString());
10703      }
10704      else {
10705          YAHOO.log("Could not unhighlight Column \"" + oColumn.key + "\". Only non-nested Columns can be unhighlighted", "warn", this.toString());
10706      }
10707  },
10708  
10709  
10710  
10711  
10712  
10713  
10714  
10715  
10716  
10717  
10718  
10719  
10720  
10721  
10722  
10723  
10724  
10725  
10726  
10727  
10728  
10729  
10730  
10731  
10732  
10733  
10734  
10735  
10736  
10737  
10738  
10739  
10740  
10741  
10742  
10743  
10744  
10745  
10746  
10747  
10748  
10749  
10750  
10751  
10752  // ROW FUNCTIONS
10753  
10754  /**
10755   * Adds one new Record of data into the RecordSet at the index if given,
10756   * otherwise at the end. If the new Record is in page view, the
10757   * corresponding DOM elements are also updated.
10758   *
10759   * @method addRow
10760   * @param oData {Object} Object literal of data for the row.
10761   * @param index {Number} (optional) RecordSet position index at which to add data.
10762   */
10763  addRow : function(oData, index) {
10764      if(lang.isNumber(index) && (index < 0 || index > this._oRecordSet.getLength())) {
10765          YAHOO.log("Could not add row at index " + index + " with " + lang.dump(oData), "warn", this.toString());
10766          return;
10767      }
10768  
10769      if(oData && lang.isObject(oData)) {
10770          var oRecord = this._oRecordSet.addRecord(oData, index);
10771          if(oRecord) {
10772              var recIndex;
10773              var oPaginator = this.get('paginator');
10774  
10775              // Paginated
10776              if (oPaginator) {     
10777                  // Update the paginator's totalRecords
10778                  var totalRecords = oPaginator.get('totalRecords');
10779                  if (totalRecords !== widget.Paginator.VALUE_UNLIMITED) {
10780                      oPaginator.set('totalRecords',totalRecords + 1);
10781                  }
10782  
10783                  recIndex = this.getRecordIndex(oRecord);
10784                  var endRecIndex = (oPaginator.getPageRecords())[1];
10785  
10786                  // New record affects the view
10787                  if (recIndex <= endRecIndex) {
10788                      // Defer UI updates to the render method
10789                      this.render();
10790                  }
10791                  
10792                  this.fireEvent("rowAddEvent", {record:oRecord});
10793                  YAHOO.log("Added a row for Record " + YAHOO.lang.dump(oRecord) + " at RecordSet index " + recIndex, "info", this.toString()); 
10794                  return;
10795              }
10796              // Not paginated
10797              else {
10798                  recIndex = this.getRecordIndex(oRecord);
10799                  if(lang.isNumber(recIndex)) {
10800                      // Add the TR element
10801                      this._oChainRender.add({
10802                          method: function(oArg) {
10803                              if((this instanceof DT) && this._sId) {
10804                                  var oRecord = oArg.record;
10805                                  var recIndex = oArg.recIndex;
10806                                  var elNewTr = this._addTrEl(oRecord);
10807                                  if(elNewTr) {
10808                                      var elNext = (this._elTbody.rows[recIndex]) ? this._elTbody.rows[recIndex] : null;
10809                                      this._elTbody.insertBefore(elNewTr, elNext);
10810  
10811                                      // Set FIRST/LAST
10812                                      if(recIndex === 0) {
10813                                          this._setFirstRow();
10814                                      }
10815                                      if(elNext === null) {
10816                                          this._setLastRow();
10817                                      }
10818                                      // Set EVEN/ODD
10819                                      this._setRowStripes();                           
10820                                      
10821                                      this.hideTableMessage();
10822              
10823                                      this.fireEvent("rowAddEvent", {record:oRecord});
10824                                      YAHOO.log("Added a row for Record " + YAHOO.lang.dump(oRecord) + " at RecordSet index " + recIndex, "info", this.toString());
10825                                  }
10826                              }
10827                          },
10828                          argument: {record: oRecord, recIndex: recIndex},
10829                          scope: this,
10830                          timeout: (this.get("renderLoopSize") > 0) ? 0 : -1
10831                      });
10832                      this._runRenderChain();
10833                      return;
10834                  }
10835              }            
10836          }
10837      }
10838      YAHOO.log("Could not add row at index " + index + " with " + lang.dump(oData), "warn", this.toString());
10839  },
10840  
10841  /**
10842   * Convenience method to add multiple rows.
10843   *
10844   * @method addRows
10845   * @param aData {Object[]} Array of object literal data for the rows.
10846   * @param index {Number} (optional) RecordSet position index at which to add data.
10847   */
10848  addRows : function(aData, index) {
10849      if(lang.isNumber(index) && (index < 0 || index > this._oRecordSet.getLength())) {
10850          YAHOO.log("Could not add rows at index " + index + " with " + lang.dump(aData), "warn", this.toString());    
10851          return;
10852      }
10853  
10854      if(lang.isArray(aData)) {
10855          var aRecords = this._oRecordSet.addRecords(aData, index);
10856          if(aRecords) {
10857              var recIndex = this.getRecordIndex(aRecords[0]);
10858              
10859              // Paginated
10860              var oPaginator = this.get('paginator');
10861              if (oPaginator) {
10862                  // Update the paginator's totalRecords
10863                  var totalRecords = oPaginator.get('totalRecords');
10864                  if (totalRecords !== widget.Paginator.VALUE_UNLIMITED) {
10865                      oPaginator.set('totalRecords',totalRecords + aRecords.length);
10866                  }
10867      
10868                  var endRecIndex = (oPaginator.getPageRecords())[1];
10869  
10870                  // At least one of the new records affects the view
10871                  if (recIndex <= endRecIndex) {
10872                      this.render();
10873                  }
10874                  
10875                  this.fireEvent("rowsAddEvent", {records:aRecords});
10876                  YAHOO.log("Added " + aRecords.length + 
10877                          " rows at index " + this._oRecordSet.getRecordIndex(aRecords[0]) +
10878                          " with data " + lang.dump(aData), "info", this.toString());
10879                  return;
10880              }
10881              // Not paginated
10882              else {
10883                  // Add the TR elements
10884                  var loopN = this.get("renderLoopSize");
10885                  var loopEnd = recIndex + aData.length;
10886                  var nRowsNeeded = (loopEnd - recIndex); // how many needed
10887                  var isLast = (recIndex >= this._elTbody.rows.length);
10888                  this._oChainRender.add({
10889                      method: function(oArg) {
10890                          if((this instanceof DT) && this._sId) {
10891                              var aRecords = oArg.aRecords,
10892                                  i = oArg.nCurrentRow,
10893                                  j = oArg.nCurrentRecord,
10894                                  len = loopN > 0 ? Math.min(i + loopN,loopEnd) : loopEnd,
10895                                  df = document.createDocumentFragment(),
10896                                  elNext = (this._elTbody.rows[i]) ? this._elTbody.rows[i] : null;
10897                              for(; i < len; i++, j++) {
10898                                  df.appendChild(this._addTrEl(aRecords[j]));
10899                              }
10900                              this._elTbody.insertBefore(df, elNext);
10901                              oArg.nCurrentRow = i;
10902                              oArg.nCurrentRecord = j;
10903                          }
10904                      },
10905                      iterations: (loopN > 0) ? Math.ceil(loopEnd/loopN) : 1,
10906                      argument: {nCurrentRow:recIndex,nCurrentRecord:0,aRecords:aRecords},
10907                      scope: this,
10908                      timeout: (loopN > 0) ? 0 : -1
10909                  });
10910                  this._oChainRender.add({
10911                      method: function(oArg) {
10912                          var recIndex = oArg.recIndex;
10913                          // Set FIRST/LAST
10914                          if(recIndex === 0) {
10915                              this._setFirstRow();
10916                          }
10917                          if(oArg.isLast) {
10918                              this._setLastRow();
10919                          }
10920                          // Set EVEN/ODD
10921                          this._setRowStripes();                           
10922  
10923                          this.fireEvent("rowsAddEvent", {records:aRecords});
10924                          YAHOO.log("Added " + aRecords.length + 
10925                                  " rows at index " + this._oRecordSet.getRecordIndex(aRecords[0]) +
10926                                  " with data " + lang.dump(aData), "info", this.toString());
10927                      },
10928                      argument: {recIndex: recIndex, isLast: isLast},
10929                      scope: this,
10930                      timeout: -1 // Needs to run immediately after the DOM insertions above
10931                  });
10932                  this._runRenderChain();
10933                  this.hideTableMessage();                
10934                  return;
10935              }            
10936          }
10937      }
10938      YAHOO.log("Could not add rows at index " + index + " with " + lang.dump(aData), "warn", this.toString());    
10939  },
10940  
10941  /**
10942   * For the given row, updates the associated Record with the given data. If the
10943   * row is on current page, the corresponding DOM elements are also updated.
10944   *
10945   * @method updateRow
10946   * @param row {YAHOO.widget.Record | Number | HTMLElement | String}
10947   * Which row to update: By Record instance, by Record's RecordSet
10948   * position index, by HTMLElement reference to the TR element, or by ID string
10949   * of the TR element.
10950   * @param oData {Object} Object literal of data for the row.
10951   */
10952  updateRow : function(row, oData) {
10953      var index = row;
10954      if (!lang.isNumber(index)) {
10955          index = this.getRecordIndex(row);
10956      }
10957  
10958      // Update the Record
10959      if(lang.isNumber(index) && (index >= 0)) {
10960          var oRecordSet = this._oRecordSet,
10961              oldRecord = oRecordSet.getRecord(index);
10962  
10963          if(oldRecord) {
10964              var updatedRecord = this._oRecordSet.setRecord(oData, index),
10965                  elRow = this.getTrEl(oldRecord),
10966                  // Copy data from the Record for the event that gets fired later
10967                  oldData = oldRecord ? oldRecord.getData() : null;
10968  
10969              if(updatedRecord) {
10970                  // Update selected rows as necessary
10971                  var tracker = this._aSelections || [],
10972                  i=0,
10973                  oldId = oldRecord.getId(),
10974                  newId = updatedRecord.getId();
10975                  for(; i<tracker.length; i++) {
10976                      if((tracker[i] === oldId)) {
10977                          tracker[i] = newId;
10978                      }
10979                      else if(tracker[i].recordId === oldId) {
10980                          tracker[i].recordId = newId;
10981                      }
10982                  }
10983  
10984                  // Update anchors as necessary
10985                  if(this._oAnchorRecord && this._oAnchorRecord.getId() === oldId) {
10986                      this._oAnchorRecord = updatedRecord;
10987                  }
10988                  if(this._oAnchorCell && this._oAnchorCell.record.getId() === oldId) {
10989                      this._oAnchorCell.record = updatedRecord;
10990                  }
10991  
10992                  // Update the TR only if row is on current page
10993                  this._oChainRender.add({
10994                      method: function() {
10995                          if((this instanceof DT) && this._sId) {
10996                              // Paginated
10997                              var oPaginator = this.get('paginator');
10998                              if (oPaginator) {
10999                                  var pageStartIndex = (oPaginator.getPageRecords())[0],
11000                                      pageLastIndex = (oPaginator.getPageRecords())[1];
11001  
11002                                  // At least one of the new records affects the view
11003                                  if ((index >= pageStartIndex) || (index <= pageLastIndex)) {
11004                                      this.render();
11005                                  }
11006                              }
11007                              else {
11008                                  if(elRow) {
11009                                      this._updateTrEl(elRow, updatedRecord);
11010                                  }
11011                                  else {
11012                                      this.getTbodyEl().appendChild(this._addTrEl(updatedRecord));
11013                                  }
11014                              }
11015                              this.fireEvent("rowUpdateEvent", {record:updatedRecord, oldData:oldData});
11016                              YAHOO.log("DataTable row updated: Record ID = " + updatedRecord.getId() +
11017                                      ", Record index = " + this.getRecordIndex(updatedRecord) +
11018                                      ", page row index = " + this.getTrIndex(updatedRecord), "info", this.toString());
11019                          }
11020                      },
11021                      scope: this,
11022                      timeout: (this.get("renderLoopSize") > 0) ? 0 : -1
11023                  });
11024                  this._runRenderChain();
11025                  return;
11026              }
11027          }
11028      }
11029      YAHOO.log("Could not update row " + row + " with the data : " + lang.dump(oData), "warn", this.toString());
11030      return;
11031  },
11032  
11033  /**
11034   * Starting with the given row, updates associated Records with the given data.
11035   * The number of rows to update are determined by the array of data provided.
11036   * Undefined data (i.e., not an object literal) causes a row to be skipped. If
11037   * any of the rows are on current page, the corresponding DOM elements are also
11038   * updated.
11039   *
11040   * @method updateRows
11041   * @param startrow {YAHOO.widget.Record | Number | HTMLElement | String}
11042   * Starting row to update: By Record instance, by Record's RecordSet
11043   * position index, by HTMLElement reference to the TR element, or by ID string
11044   * of the TR element.
11045   * @param aData {Object[]} Array of object literal of data for the rows.
11046   */
11047  updateRows : function(startrow, aData) {
11048      if(lang.isArray(aData)) {
11049          var startIndex = startrow,
11050              oRecordSet = this._oRecordSet,
11051              lastRowIndex = oRecordSet.getLength();
11052  
11053          if (!lang.isNumber(startrow)) {
11054              startIndex = this.getRecordIndex(startrow);
11055          }
11056              
11057          if(lang.isNumber(startIndex) && (startIndex >= 0) && (startIndex < oRecordSet.getLength())) {
11058              var lastIndex = startIndex + aData.length,
11059                  aOldRecords = oRecordSet.getRecords(startIndex, aData.length),
11060                  aNewRecords = oRecordSet.setRecords(aData, startIndex);
11061              if(aNewRecords) {
11062                  var tracker = this._aSelections || [],
11063                      i=0, j, newRecord, newId, oldId,
11064                      anchorRecord = this._oAnchorRecord ? this._oAnchorRecord.getId() : null,
11065                      anchorCell = this._oAnchorCell ? this._oAnchorCell.record.getId() : null;
11066                  for(; i<aOldRecords.length; i++) {
11067                      oldId = aOldRecords[i].getId();
11068                      newRecord = aNewRecords[i];
11069                      newId = newRecord.getId();
11070  
11071                      // Update selected rows as necessary
11072                      for(j=0; j<tracker.length; j++) {
11073                          if((tracker[j] === oldId)) {
11074                              tracker[j] = newId;
11075                          }
11076                          else if(tracker[j].recordId === oldId) {
11077                              tracker[j].recordId = newId;
11078                          }
11079                      }
11080  
11081                      // Update anchors as necessary
11082                      if(anchorRecord && anchorRecord === oldId) {
11083                          this._oAnchorRecord = newRecord;
11084                      }
11085                      if(anchorCell && anchorCell === oldId) {
11086                          this._oAnchorCell.record = newRecord;
11087                      }
11088                 }
11089  
11090                  // Paginated
11091                  var oPaginator = this.get('paginator');
11092                  if (oPaginator) {
11093                      var pageStartIndex = (oPaginator.getPageRecords())[0],
11094                          pageLastIndex = (oPaginator.getPageRecords())[1];
11095      
11096                      // At least one of the new records affects the view
11097                      if ((startIndex >= pageStartIndex) || (lastIndex <= pageLastIndex)) {
11098                          this.render();
11099                      }
11100  
11101                      this.fireEvent("rowsAddEvent", {newRecords:aNewRecords, oldRecords:aOldRecords});
11102                      YAHOO.log("Added " + aNewRecords.length + 
11103                              " rows starting at index " + startIndex +
11104                              " with data " + lang.dump(aData), "info", this.toString());
11105                      return;
11106                  }
11107                  // Not paginated
11108                  else {
11109                      // Update the TR elements
11110                      var loopN = this.get("renderLoopSize"),
11111                          rowCount = aData.length, // how many needed
11112                          isLast = (lastIndex >= lastRowIndex),
11113                          isAdding = (lastIndex > lastRowIndex);
11114                                             
11115                      this._oChainRender.add({
11116                          method: function(oArg) {
11117                              if((this instanceof DT) && this._sId) {
11118                                  var aRecords = oArg.aRecords,
11119                                      i = oArg.nCurrentRow,
11120                                      j = oArg.nDataPointer,
11121                                      len = loopN > 0 ? Math.min(i+loopN, startIndex+aRecords.length) : startIndex+aRecords.length;
11122                                      
11123                                  for(; i < len; i++,j++) {
11124                                      if(isAdding && (i>=lastRowIndex)) {
11125                                          this._elTbody.appendChild(this._addTrEl(aRecords[j]));
11126                                      }
11127                                      else {
11128                                          this._updateTrEl(this._elTbody.rows[i], aRecords[j]);
11129                                      }
11130                                  }
11131                                  oArg.nCurrentRow = i;
11132                                  oArg.nDataPointer = j;
11133                              }
11134                          },
11135                          iterations: (loopN > 0) ? Math.ceil(rowCount/loopN) : 1,
11136                          argument: {nCurrentRow:startIndex,aRecords:aNewRecords,nDataPointer:0,isAdding:isAdding},
11137                          scope: this,
11138                          timeout: (loopN > 0) ? 0 : -1
11139                      });
11140                      this._oChainRender.add({
11141                          method: function(oArg) {
11142                              var recIndex = oArg.recIndex;
11143                              // Set FIRST/LAST
11144                              if(recIndex === 0) {
11145                                  this._setFirstRow();
11146                              }
11147                              if(oArg.isLast) {
11148                                  this._setLastRow();
11149                              }
11150                              // Set EVEN/ODD
11151                              this._setRowStripes();                           
11152      
11153                              this.fireEvent("rowsAddEvent", {newRecords:aNewRecords, oldRecords:aOldRecords});
11154                              YAHOO.log("Added " + aNewRecords.length + 
11155                                      " rows starting at index " + startIndex +
11156                                      " with data " + lang.dump(aData), "info", this.toString());
11157                          },
11158                          argument: {recIndex: startIndex, isLast: isLast},
11159                          scope: this,
11160                          timeout: -1 // Needs to run immediately after the DOM insertions above
11161                      });
11162                      this._runRenderChain();
11163                      this.hideTableMessage();                
11164                      return;
11165                  }            
11166              }
11167          }
11168      }
11169      YAHOO.log("Could not update rows at " + startrow + " with " + lang.dump(aData), "warn", this.toString());
11170  },
11171  
11172  /**
11173   * Deletes the given row's Record from the RecordSet. If the row is on current page,
11174   * the corresponding DOM elements are also deleted.
11175   *
11176   * @method deleteRow
11177   * @param row {HTMLElement | String | Number} DOM element reference or ID string
11178   * to DataTable page element or RecordSet index.
11179   */
11180  deleteRow : function(row) {
11181      var nRecordIndex = (lang.isNumber(row)) ? row : this.getRecordIndex(row);
11182      if(lang.isNumber(nRecordIndex)) {
11183          var oRecord = this.getRecord(nRecordIndex);
11184          if(oRecord) {
11185              var nTrIndex = this.getTrIndex(nRecordIndex);
11186              
11187              // Remove from selection tracker if there
11188              var sRecordId = oRecord.getId();
11189              var tracker = this._aSelections || [];
11190              for(var j=tracker.length-1; j>-1; j--) {
11191                  if((lang.isString(tracker[j]) && (tracker[j] === sRecordId)) ||
11192                          (lang.isObject(tracker[j]) && (tracker[j].recordId === sRecordId))) {
11193                      tracker.splice(j,1);
11194                  }
11195              }
11196      
11197              // Delete Record from RecordSet
11198              var oData = this._oRecordSet.deleteRecord(nRecordIndex);
11199      
11200              // Update the UI
11201              if(oData) {
11202                  // If paginated and the deleted row was on this or a prior page, just
11203                  // re-render
11204                  var oPaginator = this.get('paginator');
11205                  if (oPaginator) {
11206                      // Update the paginator's totalRecords
11207                      var totalRecords = oPaginator.get('totalRecords'),
11208                          // must capture before the totalRecords change because
11209                          // Paginator shifts to previous page automatically
11210                          rng = oPaginator.getPageRecords();
11211  
11212                      if (totalRecords !== widget.Paginator.VALUE_UNLIMITED) {
11213                          oPaginator.set('totalRecords',totalRecords - 1);
11214                      }
11215      
11216                      // The deleted record was on this or a prior page, re-render
11217                      if (!rng || nRecordIndex <= rng[1]) {
11218                          this.render();
11219                      }
11220  
11221                      this._oChainRender.add({
11222                          method: function() {
11223                              if((this instanceof DT) && this._sId) {
11224                                  this.fireEvent("rowDeleteEvent", {recordIndex:nRecordIndex, oldData:oData, trElIndex:nTrIndex});
11225                                  YAHOO.log("Deleted row with data " + YAHOO.lang.dump(oData) + " at RecordSet index " + nRecordIndex + " and page row index " + nTrIndex, "info", this.toString());     
11226                              }
11227                          },
11228                          scope: this,
11229                          timeout: (this.get("renderLoopSize") > 0) ? 0 : -1
11230                      });
11231                      this._runRenderChain();
11232                  }
11233                  // Not paginated
11234                  else {
11235                      if(lang.isNumber(nTrIndex)) {
11236                          this._oChainRender.add({
11237                              method: function() {
11238                                  if((this instanceof DT) && this._sId) {
11239                                      var isLast = (nRecordIndex === this._oRecordSet.getLength());//(nTrIndex == this.getLastTrEl().sectionRowIndex);
11240                                      this._deleteTrEl(nTrIndex);
11241                      
11242                                      // Post-delete tasks
11243                                      if(this._elTbody.rows.length > 0) {
11244                                          // Set FIRST/LAST
11245                                          if(nTrIndex === 0) {
11246                                              this._setFirstRow();
11247                                          }
11248                                          if(isLast) {
11249                                              this._setLastRow();
11250                                          }
11251                                          // Set EVEN/ODD
11252                                          if(nTrIndex != this._elTbody.rows.length) {
11253                                              this._setRowStripes(nTrIndex);
11254                                          }                                
11255                                      }
11256                      
11257                                      this.fireEvent("rowDeleteEvent", {recordIndex:nRecordIndex,oldData:oData, trElIndex:nTrIndex});
11258                                      YAHOO.log("Deleted row with data " + YAHOO.lang.dump(oData) + " at RecordSet index " + nRecordIndex + " and page row index " + nTrIndex, "info", this.toString());     
11259                                  }
11260                              },
11261                              scope: this,
11262                              timeout: (this.get("renderLoopSize") > 0) ? 0 : -1
11263                          });
11264                          this._runRenderChain();
11265                          return;
11266                      }
11267                  }
11268              }
11269          }
11270      }
11271      YAHOO.log("Could not delete row: " + row, "warn", this.toString());
11272      return null;
11273  },
11274  
11275  /**
11276   * Convenience method to delete multiple rows.
11277   *
11278   * @method deleteRows
11279   * @param row {HTMLElement | String | Number} DOM element reference or ID string
11280   * to DataTable page element or RecordSet index.
11281   * @param count {Number} (optional) How many rows to delete. A negative value
11282   * will delete towards the beginning.
11283   */
11284  deleteRows : function(row, count) {
11285      var nRecordIndex = (lang.isNumber(row)) ? row : this.getRecordIndex(row);
11286      if(lang.isNumber(nRecordIndex)) {
11287          var oRecord = this.getRecord(nRecordIndex);
11288          if(oRecord) {
11289              var nTrIndex = this.getTrIndex(nRecordIndex);
11290              
11291              // Remove from selection tracker if there
11292              var sRecordId = oRecord.getId();
11293              var tracker = this._aSelections || [];
11294              for(var j=tracker.length-1; j>-1; j--) {
11295                  if((lang.isString(tracker[j]) && (tracker[j] === sRecordId)) ||
11296                          (lang.isObject(tracker[j]) && (tracker[j].recordId === sRecordId))) {
11297                      tracker.splice(j,1);
11298                  }
11299              }
11300      
11301              // Delete Record from RecordSet
11302              var highIndex = nRecordIndex;
11303              var lowIndex = nRecordIndex;
11304          
11305              // Validate count and account for negative value
11306              if(count && lang.isNumber(count)) {
11307                  highIndex = (count > 0) ? nRecordIndex + count -1 : nRecordIndex;
11308                  lowIndex = (count > 0) ? nRecordIndex : nRecordIndex + count + 1;
11309                  count = (count > 0) ? count : count*-1;
11310                  if(lowIndex < 0) {
11311                      lowIndex = 0;
11312                      count = highIndex - lowIndex + 1;
11313                  }
11314              }
11315              else {
11316                  count = 1;
11317              }
11318              
11319              var aData = this._oRecordSet.deleteRecords(lowIndex, count);
11320      
11321              // Update the UI
11322              if(aData) {
11323                  var oPaginator = this.get('paginator'),
11324                      loopN = this.get("renderLoopSize");
11325                  // If paginated and the deleted row was on this or a prior page, just
11326                  // re-render
11327                  if (oPaginator) {
11328                      // Update the paginator's totalRecords
11329                      var totalRecords = oPaginator.get('totalRecords'),
11330                          // must capture before the totalRecords change because
11331                          // Paginator shifts to previous page automatically
11332                          rng = oPaginator.getPageRecords();
11333  
11334                      if (totalRecords !== widget.Paginator.VALUE_UNLIMITED) {
11335                          oPaginator.set('totalRecords',totalRecords - aData.length);
11336                      }
11337      
11338                      // The records were on this or a prior page, re-render
11339                      if (!rng || lowIndex <= rng[1]) {
11340                          this.render();
11341                      }
11342  
11343                      this._oChainRender.add({
11344                          method: function(oArg) {
11345                              if((this instanceof DT) && this._sId) {
11346                                  this.fireEvent("rowsDeleteEvent", {recordIndex:lowIndex, oldData:aData, count:count});
11347                                  YAHOO.log("DataTable " + count + " rows deleted starting at index " + lowIndex, "info", this.toString());
11348                              }
11349                          },
11350                          scope: this,
11351                          timeout: (loopN > 0) ? 0 : -1
11352                      });
11353                      this._runRenderChain();
11354                      return;
11355                  }
11356                  // Not paginated
11357                  else {
11358                      if(lang.isNumber(nTrIndex)) {
11359                          // Delete the TR elements starting with highest index
11360                          var loopEnd = lowIndex;
11361                          var nRowsNeeded = count; // how many needed
11362                          this._oChainRender.add({
11363                              method: function(oArg) {
11364                                  if((this instanceof DT) && this._sId) {
11365                                      var i = oArg.nCurrentRow,
11366                                          len = (loopN > 0) ? (Math.max(i - loopN,loopEnd)-1) : loopEnd-1;
11367                                      for(; i>len; --i) {
11368                                          this._deleteTrEl(i);
11369                                      }
11370                                      oArg.nCurrentRow = i;
11371                                  }
11372                              },
11373                              iterations: (loopN > 0) ? Math.ceil(count/loopN) : 1,
11374                              argument: {nCurrentRow:highIndex},
11375                              scope: this,
11376                              timeout: (loopN > 0) ? 0 : -1
11377                          });
11378                          this._oChainRender.add({
11379                              method: function() {    
11380                                  // Post-delete tasks
11381                                  if(this._elTbody.rows.length > 0) {
11382                                      this._setFirstRow();
11383                                      this._setLastRow();
11384                                      this._setRowStripes();
11385                                  }
11386                                  
11387                                  this.fireEvent("rowsDeleteEvent", {recordIndex:lowIndex, oldData:aData, count:count});
11388                                  YAHOO.log("DataTable " + count + " rows deleted starting at index " + lowIndex, "info", this.toString());
11389                              },
11390                              scope: this,
11391                              timeout: -1 // Needs to run immediately after the DOM deletions above
11392                          });
11393                          this._runRenderChain();
11394                          return;
11395                      }
11396                  }
11397              }
11398          }
11399      }
11400      YAHOO.log("Could not delete " + count + " rows at row " + row, "warn", this.toString());
11401      return null;
11402  },
11403  
11404  
11405  
11406  
11407  
11408  
11409  
11410  
11411  
11412  
11413  
11414  
11415  
11416  
11417  
11418  
11419  
11420  
11421  
11422  
11423  
11424  
11425  
11426  
11427  
11428  
11429  
11430  
11431  
11432  
11433  
11434  
11435  
11436  
11437  
11438  
11439  
11440  
11441  
11442  
11443  
11444  
11445  
11446  
11447  
11448  
11449  // CELL FUNCTIONS
11450  
11451  /**
11452   * Outputs markup into the given TD based on given Record.
11453   *
11454   * @method formatCell
11455   * @param elLiner {HTMLElement} The liner DIV element within the TD.
11456   * @param oRecord {YAHOO.widget.Record} (Optional) Record instance.
11457   * @param oColumn {YAHOO.widget.Column} (Optional) Column instance.
11458   */
11459  formatCell : function(elLiner, oRecord, oColumn) {
11460      if(!oRecord) {
11461          oRecord = this.getRecord(elLiner);
11462      }
11463      if(!oColumn) {
11464          oColumn = this.getColumn(this.getCellIndex(elLiner.parentNode));
11465      }
11466  
11467      if(oRecord && oColumn) {
11468          var sField = oColumn.field;
11469          var oData = oRecord.getData(sField);
11470  
11471          var fnFormatter = typeof oColumn.formatter === 'function' ?
11472                            oColumn.formatter :
11473                            DT.Formatter[oColumn.formatter+''] ||
11474                            DT.Formatter.defaultFormatter;
11475  
11476          // Apply special formatter
11477          if(fnFormatter) {
11478              fnFormatter.call(this, elLiner, oRecord, oColumn, oData);
11479          }
11480          else {
11481              elLiner.innerHTML = oData;
11482          }
11483  
11484          this.fireEvent("cellFormatEvent", {record:oRecord, column:oColumn, key:oColumn.key, el:elLiner});
11485      }
11486      else {
11487          YAHOO.log("Could not format cell " + elLiner, "error", this.toString());
11488      }
11489  },
11490  
11491  /**
11492   * For the given row and column, updates the Record with the given data. If the
11493   * cell is on current page, the corresponding DOM elements are also updated.
11494   *
11495   * @method updateCell
11496   * @param oRecord {YAHOO.widget.Record} Record instance.
11497   * @param oColumn {YAHOO.widget.Column | String | Number} A Column key, or a ColumnSet key index.
11498   * @param oData {Object} New data value for the cell.
11499   * @param skipRender {Boolean} Skips render step. Editors that update multiple
11500   * cells in ScrollingDataTable should render only on the last call to updateCell().
11501   */
11502  updateCell : function(oRecord, oColumn, oData, skipRender) {
11503      // Validate Column and Record
11504      oColumn = (oColumn instanceof YAHOO.widget.Column) ? oColumn : this.getColumn(oColumn);
11505      if(oColumn && oColumn.getField() && (oRecord instanceof YAHOO.widget.Record)) {
11506          var sKey = oColumn.getField(),
11507          
11508          // Copy data from the Record for the event that gets fired later
11509          //var oldData = YAHOO.widget.DataTable._cloneObject(oRecord.getData());
11510              oldData = oRecord.getData(sKey);
11511  
11512          // Update Record with new data
11513          this._oRecordSet.updateRecordValue(oRecord, sKey, oData);
11514      
11515          // Update the TD only if row is on current page
11516          var elTd = this.getTdEl({record: oRecord, column: oColumn});
11517          if(elTd) {
11518              this._oChainRender.add({
11519                  method: function() {
11520                      if((this instanceof DT) && this._sId) {
11521                          this.formatCell(elTd.firstChild, oRecord, oColumn);
11522                          this.fireEvent("cellUpdateEvent", {record:oRecord, column: oColumn, oldData:oldData});
11523                          YAHOO.log("DataTable cell updated: Record ID = " + oRecord.getId() +
11524                                  ", Record index = " + this.getRecordIndex(oRecord) +
11525                                  ", page row index = " + this.getTrIndex(oRecord) +
11526                                  ", Column key = " + oColumn.getKey(), "info", this.toString());
11527                      }
11528                  },
11529                  scope: this,
11530                  timeout: (this.get("renderLoopSize") > 0) ? 0 : -1
11531              });
11532              // Bug 2529024
11533              if(!skipRender) {
11534                  this._runRenderChain();
11535              }
11536          }
11537          else {
11538              this.fireEvent("cellUpdateEvent", {record:oRecord, column: oColumn, oldData:oldData});
11539              YAHOO.log("DataTable cell updated: Record ID = " + oRecord.getId() +
11540                      ", Record index = " + this.getRecordIndex(oRecord) +
11541                      ", page row index = " + this.getTrIndex(oRecord) +
11542                      ", Column key = " + oColumn.getKey(), "info", this.toString());   
11543          }
11544      }
11545  },
11546  
11547  
11548  
11549  
11550  
11551  
11552  
11553  
11554  
11555  
11556  
11557  
11558  
11559  
11560  
11561  
11562  
11563  
11564  
11565  
11566  
11567  
11568  
11569  
11570  
11571  
11572  
11573  
11574  
11575  
11576  
11577  
11578  
11579  
11580  
11581  
11582  
11583  
11584  
11585  
11586  
11587  
11588  
11589  
11590  
11591  
11592  
11593  
11594  
11595  
11596  
11597  // PAGINATION
11598  /**
11599   * Method executed during set() operation for the "paginator" attribute.
11600   * Adds and/or severs event listeners between DataTable and Paginator
11601   *
11602   * @method _updatePaginator
11603   * @param newPag {Paginator} Paginator instance (or null) for DataTable to use
11604   * @private
11605   */
11606  _updatePaginator : function (newPag) {
11607      var oldPag = this.get('paginator');
11608      if (oldPag && newPag !== oldPag) {
11609          oldPag.unsubscribe('changeRequest', this.onPaginatorChangeRequest, this, true);
11610      }
11611      if (newPag) {
11612          newPag.subscribe('changeRequest', this.onPaginatorChangeRequest, this, true);
11613      }
11614  },
11615  
11616  /**
11617   * Update the UI infrastructure in response to a "paginator" attribute change.
11618   *
11619   * @method _handlePaginatorChange
11620   * @param e {Object} Change event object containing keys 'type','newValue',
11621   *                   and 'prevValue'
11622   * @private
11623   */
11624  _handlePaginatorChange : function (e) {
11625      if (e.prevValue === e.newValue) { return; }
11626  
11627      var newPag     = e.newValue,
11628          oldPag     = e.prevValue,
11629          containers = this._defaultPaginatorContainers();
11630  
11631      if (oldPag) {
11632          if (oldPag.getContainerNodes()[0] == containers[0]) {
11633              oldPag.set('containers',[]);
11634          }
11635          oldPag.destroy();
11636  
11637          // Convenience: share the default containers if possible.
11638          // Otherwise, remove the default containers from the DOM.
11639          if (containers[0]) {
11640              if (newPag && !newPag.getContainerNodes().length) {
11641                  newPag.set('containers',containers);
11642              } else {
11643                  // No new Paginator to use existing containers, OR new
11644                  // Paginator has configured containers.
11645                  for (var i = containers.length - 1; i >= 0; --i) {
11646                      if (containers[i]) {
11647                          containers[i].parentNode.removeChild(containers[i]);
11648                      }
11649                  }
11650              }
11651          }
11652      }
11653  
11654      if (!this._bInit) {
11655          this.render();
11656  
11657      }
11658  
11659      if (newPag) {
11660          this.renderPaginator();
11661      }
11662  
11663  },
11664  
11665  /**
11666   * Returns the default containers used for Paginators.  If create param is
11667   * passed, the containers will be created and added to the DataTable container.
11668   *
11669   * @method _defaultPaginatorContainers
11670   * @param create {boolean} Create the default containers if not found
11671   * @private
11672   */
11673  _defaultPaginatorContainers : function (create) {
11674      var above_id = this._sId + '-paginator0',
11675          below_id = this._sId + '-paginator1',
11676          above    = Dom.get(above_id),
11677          below    = Dom.get(below_id);
11678  
11679      if (create && (!above || !below)) {
11680          // One above and one below the table
11681          if (!above) {
11682              above    = document.createElement('div');
11683              above.id = above_id;
11684              Dom.addClass(above, DT.CLASS_PAGINATOR);
11685  
11686              this._elContainer.insertBefore(above,this._elContainer.firstChild);
11687          }
11688  
11689          if (!below) {
11690              below    = document.createElement('div');
11691              below.id = below_id;
11692              Dom.addClass(below, DT.CLASS_PAGINATOR);
11693  
11694              this._elContainer.appendChild(below);
11695          }
11696      }
11697  
11698      return [above,below];
11699  },
11700  
11701  /**
11702   * Calls Paginator's destroy() method
11703   *
11704   * @method _destroyPaginator
11705   * @private
11706   */
11707  _destroyPaginator : function () {
11708      var oldPag = this.get('paginator');
11709      if (oldPag) {
11710          oldPag.destroy();
11711      }
11712  },
11713  
11714  /**
11715   * Renders the Paginator to the DataTable UI
11716   *
11717   * @method renderPaginator
11718   */
11719  renderPaginator : function () {
11720      var pag = this.get("paginator");
11721      if (!pag) { return; }
11722  
11723      // Add the containers if the Paginator is not configured with containers
11724      if (!pag.getContainerNodes().length) {
11725          pag.set('containers',this._defaultPaginatorContainers(true));
11726      }
11727  
11728      pag.render();
11729  },
11730  
11731  /**
11732   * Overridable method gives implementers a hook to show loading message before
11733   * changing Paginator value.
11734   *
11735   * @method doBeforePaginatorChange
11736   * @param oPaginatorState {Object} An object literal describing the proposed pagination state.
11737   * @return {Boolean} Return true to continue changing Paginator value.
11738   */
11739  doBeforePaginatorChange : function(oPaginatorState) {
11740      this.showTableMessage(this.get("MSG_LOADING"), DT.CLASS_LOADING);
11741      return true;
11742  },
11743  
11744  /**
11745   * Responds to new Pagination states. By default, updates the UI to reflect the
11746   * new state. If "dynamicData" is true, current selections are purged before
11747   * a request is sent to the DataSource for data for the new state (using the
11748   * request returned by "generateRequest()").
11749   *  
11750   * @method onPaginatorChangeRequest
11751   * @param oPaginatorState {Object} An object literal describing the proposed pagination state.
11752   */
11753  onPaginatorChangeRequest : function (oPaginatorState) {
11754      var ok = this.doBeforePaginatorChange(oPaginatorState);
11755      if(ok) {
11756          // Server-side pagination
11757          if(this.get("dynamicData")) {
11758              // Get the current state
11759              var oState = this.getState();
11760              
11761              // Update pagination values
11762              oState.pagination = oPaginatorState;
11763      
11764              // Get the request for the new state
11765              var request = this.get("generateRequest")(oState, this);
11766              
11767              // Purge selections
11768              this.unselectAllRows();
11769              this.unselectAllCells();
11770              
11771              // Get the new data from the server
11772              var callback = {
11773                  success : this.onDataReturnSetRows,
11774                  failure : this.onDataReturnSetRows,
11775                  argument : oState, // Pass along the new state to the callback
11776                  scope : this
11777              };
11778              this._oDataSource.sendRequest(request, callback);
11779          }
11780          // Client-side pagination
11781          else {
11782              // Set the core pagination values silently (the second param)
11783              // to avoid looping back through the changeRequest mechanism
11784              oPaginatorState.paginator.setStartIndex(oPaginatorState.recordOffset,true);
11785              oPaginatorState.paginator.setRowsPerPage(oPaginatorState.rowsPerPage,true);
11786      
11787              // Update the UI
11788              this.render();
11789          }
11790      }
11791      else {
11792          YAHOO.log("Could not change Paginator value \"" + oPaginatorState + "\"", "warn", this.toString());
11793      }
11794  },
11795  
11796  
11797  
11798  
11799  
11800  
11801  
11802  
11803  
11804  
11805  
11806  
11807  
11808  
11809  
11810  
11811  
11812  
11813  
11814  
11815  
11816  
11817  
11818  
11819  
11820  
11821  
11822  
11823  
11824  
11825  
11826  
11827  
11828  
11829  
11830  
11831  
11832  
11833  
11834  
11835  
11836  
11837  
11838  
11839  
11840  
11841  
11842  
11843  
11844  
11845  // SELECTION/HIGHLIGHTING
11846  
11847  /*
11848   * Reference to last highlighted cell element
11849   *
11850   * @property _elLastHighlightedTd
11851   * @type HTMLElement
11852   * @private
11853   */
11854  _elLastHighlightedTd : null,
11855  
11856  /*
11857   * ID string of last highlighted row element
11858   *
11859   * @property _sLastHighlightedTrElId
11860   * @type String
11861   * @private
11862   */
11863  //_sLastHighlightedTrElId : null,
11864  
11865  /**
11866   * Array to track row selections (by sRecordId) and/or cell selections
11867   * (by {recordId:sRecordId, columnKey:sColumnKey})
11868   *
11869   * @property _aSelections
11870   * @type Object[]
11871   * @private
11872   */
11873  _aSelections : null,
11874  
11875  /**
11876   * Record instance of the row selection anchor.
11877   *
11878   * @property _oAnchorRecord
11879   * @type YAHOO.widget.Record
11880   * @private
11881   */
11882  _oAnchorRecord : null,
11883  
11884  /**
11885   * Object literal representing cell selection anchor:
11886   * {recordId:sRecordId, columnKey:sColumnKey}.
11887   *
11888   * @property _oAnchorCell
11889   * @type Object
11890   * @private
11891   */
11892  _oAnchorCell : null,
11893  
11894  /**
11895   * Convenience method to remove the class YAHOO.widget.DataTable.CLASS_SELECTED
11896   * from all TR elements on the page.
11897   *
11898   * @method _unselectAllTrEls
11899   * @private
11900   */
11901  _unselectAllTrEls : function() {
11902      var selectedRows = Dom.getElementsByClassName(DT.CLASS_SELECTED,"tr",this._elTbody);
11903      Dom.removeClass(selectedRows, DT.CLASS_SELECTED);
11904  },
11905  
11906  /**
11907   * Returns object literal of values that represent the selection trigger. Used
11908   * to determine selection behavior resulting from a key event.
11909   *
11910   * @method _getSelectionTrigger
11911   * @private
11912   */
11913  _getSelectionTrigger : function() {
11914      var sMode = this.get("selectionMode");
11915      var oTrigger = {};
11916      var oTriggerCell, oTriggerRecord, nTriggerRecordIndex, elTriggerRow, nTriggerTrIndex;
11917  
11918      // Cell mode
11919      if((sMode == "cellblock") || (sMode == "cellrange") || (sMode == "singlecell")) {
11920          oTriggerCell = this.getLastSelectedCell();
11921          // No selected cells found
11922          if(!oTriggerCell) {
11923              return null;
11924          }
11925          else {
11926              oTriggerRecord = this.getRecord(oTriggerCell.recordId);
11927              nTriggerRecordIndex = this.getRecordIndex(oTriggerRecord);
11928              elTriggerRow = this.getTrEl(oTriggerRecord);
11929              nTriggerTrIndex = this.getTrIndex(elTriggerRow);
11930  
11931              // Selected cell not found on this page
11932              if(nTriggerTrIndex === null) {
11933                  return null;
11934              }
11935              else {
11936                  oTrigger.record = oTriggerRecord;
11937                  oTrigger.recordIndex = nTriggerRecordIndex;
11938                  oTrigger.el = this.getTdEl(oTriggerCell);
11939                  oTrigger.trIndex = nTriggerTrIndex;
11940                  oTrigger.column = this.getColumn(oTriggerCell.columnKey);
11941                  oTrigger.colKeyIndex = oTrigger.column.getKeyIndex();
11942                  oTrigger.cell = oTriggerCell;
11943                  return oTrigger;
11944              }
11945          }
11946      }
11947      // Row mode
11948      else {
11949          oTriggerRecord = this.getLastSelectedRecord();
11950          // No selected rows found
11951          if(!oTriggerRecord) {
11952                  return null;
11953          }
11954          else {
11955              // Selected row found, but is it on current page?
11956              oTriggerRecord = this.getRecord(oTriggerRecord);
11957              nTriggerRecordIndex = this.getRecordIndex(oTriggerRecord);
11958              elTriggerRow = this.getTrEl(oTriggerRecord);
11959              nTriggerTrIndex = this.getTrIndex(elTriggerRow);
11960  
11961              // Selected row not found on this page
11962              if(nTriggerTrIndex === null) {
11963                  return null;
11964              }
11965              else {
11966                  oTrigger.record = oTriggerRecord;
11967                  oTrigger.recordIndex = nTriggerRecordIndex;
11968                  oTrigger.el = elTriggerRow;
11969                  oTrigger.trIndex = nTriggerTrIndex;
11970                  return oTrigger;
11971              }
11972          }
11973      }
11974  },
11975  
11976  /**
11977   * Returns object literal of values that represent the selection anchor. Used
11978   * to determine selection behavior resulting from a user event.
11979   *
11980   * @method _getSelectionAnchor
11981   * @param oTrigger {Object} (Optional) Object literal of selection trigger values
11982   * (for key events).
11983   * @private
11984   */
11985  _getSelectionAnchor : function(oTrigger) {
11986      var sMode = this.get("selectionMode");
11987      var oAnchor = {};
11988      var oAnchorRecord, nAnchorRecordIndex, nAnchorTrIndex;
11989  
11990      // Cell mode
11991      if((sMode == "cellblock") || (sMode == "cellrange") || (sMode == "singlecell")) {
11992          // Validate anchor cell
11993          var oAnchorCell = this._oAnchorCell;
11994          if(!oAnchorCell) {
11995              if(oTrigger) {
11996                  oAnchorCell = this._oAnchorCell = oTrigger.cell;
11997              }
11998              else {
11999                  return null;
12000              }
12001          }
12002          oAnchorRecord = this._oAnchorCell.record;
12003          nAnchorRecordIndex = this._oRecordSet.getRecordIndex(oAnchorRecord);
12004          nAnchorTrIndex = this.getTrIndex(oAnchorRecord);
12005          // If anchor cell is not on this page...
12006          if(nAnchorTrIndex === null) {
12007              // ...set TR index equal to top TR
12008              if(nAnchorRecordIndex < this.getRecordIndex(this.getFirstTrEl())) {
12009                  nAnchorTrIndex = 0;
12010              }
12011              // ...set TR index equal to bottom TR
12012              else {
12013                  nAnchorTrIndex = this.getRecordIndex(this.getLastTrEl());
12014              }
12015          }
12016  
12017          oAnchor.record = oAnchorRecord;
12018          oAnchor.recordIndex = nAnchorRecordIndex;
12019          oAnchor.trIndex = nAnchorTrIndex;
12020          oAnchor.column = this._oAnchorCell.column;
12021          oAnchor.colKeyIndex = oAnchor.column.getKeyIndex();
12022          oAnchor.cell = oAnchorCell;
12023          return oAnchor;
12024      }
12025      // Row mode
12026      else {
12027          oAnchorRecord = this._oAnchorRecord;
12028          if(!oAnchorRecord) {
12029              if(oTrigger) {
12030                  oAnchorRecord = this._oAnchorRecord = oTrigger.record;
12031              }
12032              else {
12033                  return null;
12034              }
12035          }
12036  
12037          nAnchorRecordIndex = this.getRecordIndex(oAnchorRecord);
12038          nAnchorTrIndex = this.getTrIndex(oAnchorRecord);
12039          // If anchor row is not on this page...
12040          if(nAnchorTrIndex === null) {
12041              // ...set TR index equal to top TR
12042              if(nAnchorRecordIndex < this.getRecordIndex(this.getFirstTrEl())) {
12043                  nAnchorTrIndex = 0;
12044              }
12045              // ...set TR index equal to bottom TR
12046              else {
12047                  nAnchorTrIndex = this.getRecordIndex(this.getLastTrEl());
12048              }
12049          }
12050  
12051          oAnchor.record = oAnchorRecord;
12052          oAnchor.recordIndex = nAnchorRecordIndex;
12053          oAnchor.trIndex = nAnchorTrIndex;
12054          return oAnchor;
12055      }
12056  },
12057  
12058  /**
12059   * Determines selection behavior resulting from a mouse event when selection mode
12060   * is set to "standard".
12061   *
12062   * @method _handleStandardSelectionByMouse
12063   * @param oArgs.event {HTMLEvent} Event object.
12064   * @param oArgs.target {HTMLElement} Target element.
12065   * @private
12066   */
12067  _handleStandardSelectionByMouse : function(oArgs) {
12068      var elTarget = oArgs.target;
12069  
12070      // Validate target row
12071      var elTargetRow = this.getTrEl(elTarget);
12072      if(elTargetRow) {
12073          var e = oArgs.event;
12074          var bSHIFT = e.shiftKey;
12075          var bCTRL = e.ctrlKey || ((navigator.userAgent.toLowerCase().indexOf("mac") != -1) && e.metaKey);
12076  
12077          var oTargetRecord = this.getRecord(elTargetRow);
12078          var nTargetRecordIndex = this._oRecordSet.getRecordIndex(oTargetRecord);
12079  
12080          var oAnchor = this._getSelectionAnchor();
12081  
12082          var i;
12083  
12084          // Both SHIFT and CTRL
12085          if(bSHIFT && bCTRL) {
12086              // Validate anchor
12087              if(oAnchor) {
12088                  if(this.isSelected(oAnchor.record)) {
12089                      // Select all rows between anchor row and target row, including target row
12090                      if(oAnchor.recordIndex < nTargetRecordIndex) {
12091                          for(i=oAnchor.recordIndex+1; i<=nTargetRecordIndex; i++) {
12092                              if(!this.isSelected(i)) {
12093                                  this.selectRow(i);
12094                              }
12095                          }
12096                      }
12097                      // Select all rows between target row and anchor row, including target row
12098                      else {
12099                          for(i=oAnchor.recordIndex-1; i>=nTargetRecordIndex; i--) {
12100                              if(!this.isSelected(i)) {
12101                                  this.selectRow(i);
12102                              }
12103                          }
12104                      }
12105                  }
12106                  else {
12107                      // Unselect all rows between anchor row and target row
12108                      if(oAnchor.recordIndex < nTargetRecordIndex) {
12109                          for(i=oAnchor.recordIndex+1; i<=nTargetRecordIndex-1; i++) {
12110                              if(this.isSelected(i)) {
12111                                  this.unselectRow(i);
12112                              }
12113                          }
12114                      }
12115                      // Unselect all rows between target row and anchor row
12116                      else {
12117                          for(i=nTargetRecordIndex+1; i<=oAnchor.recordIndex-1; i++) {
12118                              if(this.isSelected(i)) {
12119                                  this.unselectRow(i);
12120                              }
12121                          }
12122                      }
12123                      // Select the target row
12124                      this.selectRow(oTargetRecord);
12125                  }
12126              }
12127              // Invalid anchor
12128              else {
12129                  // Set anchor
12130                  this._oAnchorRecord = oTargetRecord;
12131  
12132                  // Toggle selection of target
12133                  if(this.isSelected(oTargetRecord)) {
12134                      this.unselectRow(oTargetRecord);
12135                  }
12136                  else {
12137                      this.selectRow(oTargetRecord);
12138                  }
12139              }
12140          }
12141           // Only SHIFT
12142          else if(bSHIFT) {
12143              this.unselectAllRows();
12144  
12145              // Validate anchor
12146              if(oAnchor) {
12147                  // Select all rows between anchor row and target row,
12148                  // including the anchor row and target row
12149                  if(oAnchor.recordIndex < nTargetRecordIndex) {
12150                      for(i=oAnchor.recordIndex; i<=nTargetRecordIndex; i++) {
12151                          this.selectRow(i);
12152                      }
12153                  }
12154                  // Select all rows between target row and anchor row,
12155                  // including the target row and anchor row
12156                  else {
12157                      for(i=oAnchor.recordIndex; i>=nTargetRecordIndex; i--) {
12158                          this.selectRow(i);
12159                      }
12160                  }
12161              }
12162              // Invalid anchor
12163              else {
12164                  // Set anchor
12165                  this._oAnchorRecord = oTargetRecord;
12166  
12167                  // Select target row only
12168                  this.selectRow(oTargetRecord);
12169              }
12170          }
12171          // Only CTRL
12172          else if(bCTRL) {
12173              // Set anchor
12174              this._oAnchorRecord = oTargetRecord;
12175  
12176              // Toggle selection of target
12177              if(this.isSelected(oTargetRecord)) {
12178                  this.unselectRow(oTargetRecord);
12179              }
12180              else {
12181                  this.selectRow(oTargetRecord);
12182              }
12183          }
12184          // Neither SHIFT nor CTRL
12185          else {
12186              this._handleSingleSelectionByMouse(oArgs);
12187              return;
12188          }
12189      }
12190  },
12191  
12192  /**
12193   * Determines selection behavior resulting from a key event when selection mode
12194   * is set to "standard".
12195   *
12196   * @method _handleStandardSelectionByKey
12197   * @param e {HTMLEvent} Event object.
12198   * @private
12199   */
12200  _handleStandardSelectionByKey : function(e) {
12201      var nKey = Ev.getCharCode(e);
12202  
12203      if((nKey == 38) || (nKey == 40)) {
12204          var bSHIFT = e.shiftKey;
12205  
12206          // Validate trigger
12207          var oTrigger = this._getSelectionTrigger();
12208          // Arrow selection only works if last selected row is on current page
12209          if(!oTrigger) {
12210              return null;
12211          }
12212  
12213          Ev.stopEvent(e);
12214  
12215          // Validate anchor
12216          var oAnchor = this._getSelectionAnchor(oTrigger);
12217  
12218          // Determine which direction we're going to
12219          if(bSHIFT) {
12220              // Selecting down away from anchor row
12221              if((nKey == 40) && (oAnchor.recordIndex <= oTrigger.trIndex)) {
12222                  this.selectRow(this.getNextTrEl(oTrigger.el));
12223              }
12224              // Selecting up away from anchor row
12225              else if((nKey == 38) && (oAnchor.recordIndex >= oTrigger.trIndex)) {
12226                  this.selectRow(this.getPreviousTrEl(oTrigger.el));
12227              }
12228              // Unselect trigger
12229              else {
12230                  this.unselectRow(oTrigger.el);
12231              }
12232          }
12233          else {
12234              this._handleSingleSelectionByKey(e);
12235          }
12236      }
12237  },
12238  
12239  /**
12240   * Determines selection behavior resulting from a mouse event when selection mode
12241   * is set to "single".
12242   *
12243   * @method _handleSingleSelectionByMouse
12244   * @param oArgs.event {HTMLEvent} Event object.
12245   * @param oArgs.target {HTMLElement} Target element.
12246   * @private
12247   */
12248  _handleSingleSelectionByMouse : function(oArgs) {
12249      var elTarget = oArgs.target;
12250  
12251      // Validate target row
12252      var elTargetRow = this.getTrEl(elTarget);
12253      if(elTargetRow) {
12254          var oTargetRecord = this.getRecord(elTargetRow);
12255  
12256          // Set anchor
12257          this._oAnchorRecord = oTargetRecord;
12258  
12259          // Select only target
12260          this.unselectAllRows();
12261          this.selectRow(oTargetRecord);
12262      }
12263  },
12264  
12265  /**
12266   * Determines selection behavior resulting from a key event when selection mode
12267   * is set to "single".
12268   *
12269   * @method _handleSingleSelectionByKey
12270   * @param e {HTMLEvent} Event object.
12271   * @private
12272   */
12273  _handleSingleSelectionByKey : function(e) {
12274      var nKey = Ev.getCharCode(e);
12275  
12276      if((nKey == 38) || (nKey == 40)) {
12277          // Validate trigger
12278          var oTrigger = this._getSelectionTrigger();
12279          // Arrow selection only works if last selected row is on current page
12280          if(!oTrigger) {
12281              return null;
12282          }
12283  
12284          Ev.stopEvent(e);
12285  
12286          // Determine the new row to select
12287          var elNew;
12288          if(nKey == 38) { // arrow up
12289              elNew = this.getPreviousTrEl(oTrigger.el);
12290  
12291              // Validate new row
12292              if(elNew === null) {
12293                  //TODO: wrap around to last tr on current page
12294                  //elNew = this.getLastTrEl();
12295  
12296                  //TODO: wrap back to last tr of previous page
12297  
12298                  // Top row selection is sticky
12299                  elNew = this.getFirstTrEl();
12300              }
12301          }
12302          else if(nKey == 40) { // arrow down
12303              elNew = this.getNextTrEl(oTrigger.el);
12304  
12305              // Validate new row
12306              if(elNew === null) {
12307                  //TODO: wrap around to first tr on current page
12308                  //elNew = this.getFirstTrEl();
12309  
12310                  //TODO: wrap forward to first tr of previous page
12311  
12312                  // Bottom row selection is sticky
12313                  elNew = this.getLastTrEl();
12314              }
12315          }
12316  
12317          // Unselect all rows
12318          this.unselectAllRows();
12319  
12320          // Select the new row
12321          this.selectRow(elNew);
12322  
12323          // Set new anchor
12324          this._oAnchorRecord = this.getRecord(elNew);
12325      }
12326  },
12327  
12328  /**
12329   * Determines selection behavior resulting from a mouse event when selection mode
12330   * is set to "cellblock".
12331   *
12332   * @method _handleCellBlockSelectionByMouse
12333   * @param oArgs.event {HTMLEvent} Event object.
12334   * @param oArgs.target {HTMLElement} Target element.
12335   * @private
12336   */
12337  _handleCellBlockSelectionByMouse : function(oArgs) {
12338      var elTarget = oArgs.target;
12339  
12340      // Validate target cell
12341      var elTargetCell = this.getTdEl(elTarget);
12342      if(elTargetCell) {
12343          var e = oArgs.event;
12344          var bSHIFT = e.shiftKey;
12345          var bCTRL = e.ctrlKey || ((navigator.userAgent.toLowerCase().indexOf("mac") != -1) && e.metaKey);
12346  
12347          var elTargetRow = this.getTrEl(elTargetCell);
12348          var nTargetTrIndex = this.getTrIndex(elTargetRow);
12349          var oTargetColumn = this.getColumn(elTargetCell);
12350          var nTargetColKeyIndex = oTargetColumn.getKeyIndex();
12351          var oTargetRecord = this.getRecord(elTargetRow);
12352          var nTargetRecordIndex = this._oRecordSet.getRecordIndex(oTargetRecord);
12353          var oTargetCell = {record:oTargetRecord, column:oTargetColumn};
12354  
12355          var oAnchor = this._getSelectionAnchor();
12356  
12357          var allRows = this.getTbodyEl().rows;
12358          var startIndex, endIndex, currentRow, i, j;
12359  
12360          // Both SHIFT and CTRL
12361          if(bSHIFT && bCTRL) {
12362  
12363              // Validate anchor
12364              if(oAnchor) {
12365                  // Anchor is selected
12366                  if(this.isSelected(oAnchor.cell)) {
12367                      // All cells are on the same row
12368                      if(oAnchor.recordIndex === nTargetRecordIndex) {
12369                          // Select all cells between anchor cell and target cell, including target cell
12370                          if(oAnchor.colKeyIndex < nTargetColKeyIndex) {
12371                              for(i=oAnchor.colKeyIndex+1; i<=nTargetColKeyIndex; i++) {
12372                                  this.selectCell(elTargetRow.cells[i]);
12373                              }
12374                          }
12375                          // Select all cells between target cell and anchor cell, including target cell
12376                          else if(nTargetColKeyIndex < oAnchor.colKeyIndex) {
12377                              for(i=nTargetColKeyIndex; i<oAnchor.colKeyIndex; i++) {
12378                                  this.selectCell(elTargetRow.cells[i]);
12379                              }
12380                          }
12381                      }
12382                      // Anchor row is above target row
12383                      else if(oAnchor.recordIndex < nTargetRecordIndex) {
12384                          startIndex = Math.min(oAnchor.colKeyIndex, nTargetColKeyIndex);
12385                          endIndex = Math.max(oAnchor.colKeyIndex, nTargetColKeyIndex);
12386  
12387                          // Select all cells from startIndex to endIndex on rows between anchor row and target row
12388                          for(i=oAnchor.trIndex; i<=nTargetTrIndex; i++) {
12389                              for(j=startIndex; j<=endIndex; j++) {
12390                                  this.selectCell(allRows[i].cells[j]);
12391                              }
12392                          }
12393                      }
12394                      // Anchor row is below target row
12395                      else {
12396                          startIndex = Math.min(oAnchor.trIndex, nTargetColKeyIndex);
12397                          endIndex = Math.max(oAnchor.trIndex, nTargetColKeyIndex);
12398  
12399                          // Select all cells from startIndex to endIndex on rows between target row and anchor row
12400                          for(i=oAnchor.trIndex; i>=nTargetTrIndex; i--) {
12401                              for(j=endIndex; j>=startIndex; j--) {
12402                                  this.selectCell(allRows[i].cells[j]);
12403                              }
12404                          }
12405                      }
12406                  }
12407                  // Anchor cell is unselected
12408                  else {
12409                      // All cells are on the same row
12410                      if(oAnchor.recordIndex === nTargetRecordIndex) {
12411                          // Unselect all cells between anchor cell and target cell
12412                          if(oAnchor.colKeyIndex < nTargetColKeyIndex) {
12413                              for(i=oAnchor.colKeyIndex+1; i<nTargetColKeyIndex; i++) {
12414                                  this.unselectCell(elTargetRow.cells[i]);
12415                              }
12416                          }
12417                          // Select all cells between target cell and anchor cell
12418                          else if(nTargetColKeyIndex < oAnchor.colKeyIndex) {
12419                              for(i=nTargetColKeyIndex+1; i<oAnchor.colKeyIndex; i++) {
12420                                  this.unselectCell(elTargetRow.cells[i]);
12421                              }
12422                          }
12423                      }
12424                      // Anchor row is above target row
12425                      if(oAnchor.recordIndex < nTargetRecordIndex) {
12426                          // Unselect all cells from anchor cell to target cell
12427                          for(i=oAnchor.trIndex; i<=nTargetTrIndex; i++) {
12428                              currentRow = allRows[i];
12429                              for(j=0; j<currentRow.cells.length; j++) {
12430                                  // This is the anchor row, only unselect cells after the anchor cell
12431                                  if(currentRow.sectionRowIndex === oAnchor.trIndex) {
12432                                      if(j>oAnchor.colKeyIndex) {
12433                                          this.unselectCell(currentRow.cells[j]);
12434                                      }
12435                                  }
12436                                  // This is the target row, only unelect cells before the target cell
12437                                  else if(currentRow.sectionRowIndex === nTargetTrIndex) {
12438                                      if(j<nTargetColKeyIndex) {
12439                                          this.unselectCell(currentRow.cells[j]);
12440                                      }
12441                                  }
12442                                  // Unselect all cells on this row
12443                                  else {
12444                                      this.unselectCell(currentRow.cells[j]);
12445                                  }
12446                              }
12447                          }
12448                      }
12449                      // Anchor row is below target row
12450                      else {
12451                          // Unselect all cells from target cell to anchor cell
12452                          for(i=nTargetTrIndex; i<=oAnchor.trIndex; i++) {
12453                              currentRow = allRows[i];
12454                              for(j=0; j<currentRow.cells.length; j++) {
12455                                  // This is the target row, only unselect cells after the target cell
12456                                  if(currentRow.sectionRowIndex == nTargetTrIndex) {
12457                                      if(j>nTargetColKeyIndex) {
12458                                          this.unselectCell(currentRow.cells[j]);
12459                                      }
12460                                  }
12461                                  // This is the anchor row, only unselect cells before the anchor cell
12462                                  else if(currentRow.sectionRowIndex == oAnchor.trIndex) {
12463                                      if(j<oAnchor.colKeyIndex) {
12464                                          this.unselectCell(currentRow.cells[j]);
12465                                      }
12466                                  }
12467                                  // Unselect all cells on this row
12468                                  else {
12469                                      this.unselectCell(currentRow.cells[j]);
12470                                  }
12471                              }
12472                          }
12473                      }
12474  
12475                      // Select the target cell
12476                      this.selectCell(elTargetCell);
12477                  }
12478              }
12479              // Invalid anchor
12480              else {
12481                  // Set anchor
12482                  this._oAnchorCell = oTargetCell;
12483  
12484                  // Toggle selection of target
12485                  if(this.isSelected(oTargetCell)) {
12486                      this.unselectCell(oTargetCell);
12487                  }
12488                  else {
12489                      this.selectCell(oTargetCell);
12490                  }
12491              }
12492  
12493          }
12494           // Only SHIFT
12495          else if(bSHIFT) {
12496              this.unselectAllCells();
12497  
12498              // Validate anchor
12499              if(oAnchor) {
12500                  // All cells are on the same row
12501                  if(oAnchor.recordIndex === nTargetRecordIndex) {
12502                      // Select all cells between anchor cell and target cell,
12503                      // including the anchor cell and target cell
12504                      if(oAnchor.colKeyIndex < nTargetColKeyIndex) {
12505                          for(i=oAnchor.colKeyIndex; i<=nTargetColKeyIndex; i++) {
12506                              this.selectCell(elTargetRow.cells[i]);
12507                          }
12508                      }
12509                      // Select all cells between target cell and anchor cell
12510                      // including the target cell and anchor cell
12511                      else if(nTargetColKeyIndex < oAnchor.colKeyIndex) {
12512                          for(i=nTargetColKeyIndex; i<=oAnchor.colKeyIndex; i++) {
12513                              this.selectCell(elTargetRow.cells[i]);
12514                          }
12515                      }
12516                  }
12517                  // Anchor row is above target row
12518                  else if(oAnchor.recordIndex < nTargetRecordIndex) {
12519                      // Select the cellblock from anchor cell to target cell
12520                      // including the anchor cell and the target cell
12521                      startIndex = Math.min(oAnchor.colKeyIndex, nTargetColKeyIndex);
12522                      endIndex = Math.max(oAnchor.colKeyIndex, nTargetColKeyIndex);
12523  
12524                      for(i=oAnchor.trIndex; i<=nTargetTrIndex; i++) {
12525                          for(j=startIndex; j<=endIndex; j++) {
12526                              this.selectCell(allRows[i].cells[j]);
12527                          }
12528                      }
12529                  }
12530                  // Anchor row is below target row
12531                  else {
12532                      // Select the cellblock from target cell to anchor cell
12533                      // including the target cell and the anchor cell
12534                      startIndex = Math.min(oAnchor.colKeyIndex, nTargetColKeyIndex);
12535                      endIndex = Math.max(oAnchor.colKeyIndex, nTargetColKeyIndex);
12536  
12537                      for(i=nTargetTrIndex; i<=oAnchor.trIndex; i++) {
12538                          for(j=startIndex; j<=endIndex; j++) {
12539                              this.selectCell(allRows[i].cells[j]);
12540                          }
12541                      }
12542                  }
12543              }
12544              // Invalid anchor
12545              else {
12546                  // Set anchor
12547                  this._oAnchorCell = oTargetCell;
12548  
12549                  // Select target only
12550                  this.selectCell(oTargetCell);
12551              }
12552          }
12553          // Only CTRL
12554          else if(bCTRL) {
12555  
12556              // Set anchor
12557              this._oAnchorCell = oTargetCell;
12558  
12559              // Toggle selection of target
12560              if(this.isSelected(oTargetCell)) {
12561                  this.unselectCell(oTargetCell);
12562              }
12563              else {
12564                  this.selectCell(oTargetCell);
12565              }
12566  
12567          }
12568          // Neither SHIFT nor CTRL
12569          else {
12570              this._handleSingleCellSelectionByMouse(oArgs);
12571          }
12572      }
12573  },
12574  
12575  /**
12576   * Determines selection behavior resulting from a key event when selection mode
12577   * is set to "cellblock".
12578   *
12579   * @method _handleCellBlockSelectionByKey
12580   * @param e {HTMLEvent} Event object.
12581   * @private
12582   */
12583  _handleCellBlockSelectionByKey : function(e) {
12584      var nKey = Ev.getCharCode(e);
12585      var bSHIFT = e.shiftKey;
12586      if((nKey == 9) || !bSHIFT) {
12587          this._handleSingleCellSelectionByKey(e);
12588          return;
12589      }
12590  
12591      if((nKey > 36) && (nKey < 41)) {
12592          // Validate trigger
12593          var oTrigger = this._getSelectionTrigger();
12594          // Arrow selection only works if last selected row is on current page
12595          if(!oTrigger) {
12596              return null;
12597          }
12598  
12599          Ev.stopEvent(e);
12600  
12601          // Validate anchor
12602          var oAnchor = this._getSelectionAnchor(oTrigger);
12603  
12604          var i, startIndex, endIndex, elNew, elNewRow;
12605          var allRows = this.getTbodyEl().rows;
12606          var elThisRow = oTrigger.el.parentNode;
12607  
12608          // Determine which direction we're going to
12609  
12610          if(nKey == 40) { // arrow down
12611              // Selecting away from anchor cell
12612              if(oAnchor.recordIndex <= oTrigger.recordIndex) {
12613                  // Select the horiz block on the next row...
12614                  // ...making sure there is room below the trigger row
12615                  elNewRow = this.getNextTrEl(oTrigger.el);
12616                  if(elNewRow) {
12617                      startIndex = oAnchor.colKeyIndex;
12618                      endIndex = oTrigger.colKeyIndex;
12619                      // ...going left
12620                      if(startIndex > endIndex) {
12621                          for(i=startIndex; i>=endIndex; i--) {
12622                              elNew = elNewRow.cells[i];
12623                              this.selectCell(elNew);
12624                          }
12625                      }
12626                      // ... going right
12627                      else {
12628                          for(i=startIndex; i<=endIndex; i++) {
12629                              elNew = elNewRow.cells[i];
12630                              this.selectCell(elNew);
12631                          }
12632                      }
12633                  }
12634              }
12635              // Unselecting towards anchor cell
12636              else {
12637                  startIndex = Math.min(oAnchor.colKeyIndex, oTrigger.colKeyIndex);
12638                  endIndex = Math.max(oAnchor.colKeyIndex, oTrigger.colKeyIndex);
12639                  // Unselect the horiz block on this row towards the next row
12640                  for(i=startIndex; i<=endIndex; i++) {
12641                      this.unselectCell(elThisRow.cells[i]);
12642                  }
12643              }
12644          }
12645          // Arrow up
12646          else if(nKey == 38) {
12647              // Selecting away from anchor cell
12648              if(oAnchor.recordIndex >= oTrigger.recordIndex) {
12649                  // Select the horiz block on the previous row...
12650                  // ...making sure there is room
12651                  elNewRow = this.getPreviousTrEl(oTrigger.el);
12652                  if(elNewRow) {
12653                      // Select in order from anchor to trigger...
12654                      startIndex = oAnchor.colKeyIndex;
12655                      endIndex = oTrigger.colKeyIndex;
12656                      // ...going left
12657                      if(startIndex > endIndex) {
12658                          for(i=startIndex; i>=endIndex; i--) {
12659                              elNew = elNewRow.cells[i];
12660                              this.selectCell(elNew);
12661                          }
12662                      }
12663                      // ... going right
12664                      else {
12665                          for(i=startIndex; i<=endIndex; i++) {
12666                              elNew = elNewRow.cells[i];
12667                              this.selectCell(elNew);
12668                          }
12669                      }
12670                  }
12671              }
12672              // Unselecting towards anchor cell
12673              else {
12674                  startIndex = Math.min(oAnchor.colKeyIndex, oTrigger.colKeyIndex);
12675                  endIndex = Math.max(oAnchor.colKeyIndex, oTrigger.colKeyIndex);
12676                  // Unselect the horiz block on this row towards the previous row
12677                  for(i=startIndex; i<=endIndex; i++) {
12678                      this.unselectCell(elThisRow.cells[i]);
12679                  }
12680              }
12681          }
12682          // Arrow right
12683          else if(nKey == 39) {
12684              // Selecting away from anchor cell
12685              if(oAnchor.colKeyIndex <= oTrigger.colKeyIndex) {
12686                  // Select the next vert block to the right...
12687                  // ...making sure there is room
12688                  if(oTrigger.colKeyIndex < elThisRow.cells.length-1) {
12689                      // Select in order from anchor to trigger...
12690                      startIndex = oAnchor.trIndex;
12691                      endIndex = oTrigger.trIndex;
12692                      // ...going up
12693                      if(startIndex > endIndex) {
12694                          for(i=startIndex; i>=endIndex; i--) {
12695                              elNew = allRows[i].cells[oTrigger.colKeyIndex+1];
12696                              this.selectCell(elNew);
12697                          }
12698                      }
12699                      // ... going down
12700                      else {
12701                          for(i=startIndex; i<=endIndex; i++) {
12702                              elNew = allRows[i].cells[oTrigger.colKeyIndex+1];
12703                              this.selectCell(elNew);
12704                          }
12705                      }
12706                  }
12707              }
12708              // Unselecting towards anchor cell
12709              else {
12710                  // Unselect the vert block on this column towards the right
12711                  startIndex = Math.min(oAnchor.trIndex, oTrigger.trIndex);
12712                  endIndex = Math.max(oAnchor.trIndex, oTrigger.trIndex);
12713                  for(i=startIndex; i<=endIndex; i++) {
12714                      this.unselectCell(allRows[i].cells[oTrigger.colKeyIndex]);
12715                  }
12716              }
12717          }
12718          // Arrow left
12719          else if(nKey == 37) {
12720              // Selecting away from anchor cell
12721              if(oAnchor.colKeyIndex >= oTrigger.colKeyIndex) {
12722                  //Select the previous vert block to the left
12723                  if(oTrigger.colKeyIndex > 0) {
12724                      // Select in order from anchor to trigger...
12725                      startIndex = oAnchor.trIndex;
12726                      endIndex = oTrigger.trIndex;
12727                      // ...going up
12728                      if(startIndex > endIndex) {
12729                          for(i=startIndex; i>=endIndex; i--) {
12730                              elNew = allRows[i].cells[oTrigger.colKeyIndex-1];
12731                              this.selectCell(elNew);
12732                          }
12733                      }
12734                      // ... going down
12735                      else {
12736                          for(i=startIndex; i<=endIndex; i++) {
12737                              elNew = allRows[i].cells[oTrigger.colKeyIndex-1];
12738                              this.selectCell(elNew);
12739                          }
12740                      }
12741                  }
12742              }
12743              // Unselecting towards anchor cell
12744              else {
12745                  // Unselect the vert block on this column towards the left
12746                  startIndex = Math.min(oAnchor.trIndex, oTrigger.trIndex);
12747                  endIndex = Math.max(oAnchor.trIndex, oTrigger.trIndex);
12748                  for(i=startIndex; i<=endIndex; i++) {
12749                      this.unselectCell(allRows[i].cells[oTrigger.colKeyIndex]);
12750                  }
12751              }
12752          }
12753      }
12754  },
12755  
12756  /**
12757   * Determines selection behavior resulting from a mouse event when selection mode
12758   * is set to "cellrange".
12759   *
12760   * @method _handleCellRangeSelectionByMouse
12761   * @param oArgs.event {HTMLEvent} Event object.
12762   * @param oArgs.target {HTMLElement} Target element.
12763   * @private
12764   */
12765  _handleCellRangeSelectionByMouse : function(oArgs) {
12766      var elTarget = oArgs.target;
12767  
12768      // Validate target cell
12769      var elTargetCell = this.getTdEl(elTarget);
12770      if(elTargetCell) {
12771          var e = oArgs.event;
12772          var bSHIFT = e.shiftKey;
12773          var bCTRL = e.ctrlKey || ((navigator.userAgent.toLowerCase().indexOf("mac") != -1) && e.metaKey);
12774  
12775          var elTargetRow = this.getTrEl(elTargetCell);
12776          var nTargetTrIndex = this.getTrIndex(elTargetRow);
12777          var oTargetColumn = this.getColumn(elTargetCell);
12778          var nTargetColKeyIndex = oTargetColumn.getKeyIndex();
12779          var oTargetRecord = this.getRecord(elTargetRow);
12780          var nTargetRecordIndex = this._oRecordSet.getRecordIndex(oTargetRecord);
12781          var oTargetCell = {record:oTargetRecord, column:oTargetColumn};
12782  
12783          var oAnchor = this._getSelectionAnchor();
12784  
12785          var allRows = this.getTbodyEl().rows;
12786          var currentRow, i, j;
12787  
12788          // Both SHIFT and CTRL
12789          if(bSHIFT && bCTRL) {
12790  
12791              // Validate anchor
12792              if(oAnchor) {
12793                  // Anchor is selected
12794                  if(this.isSelected(oAnchor.cell)) {
12795                      // All cells are on the same row
12796                      if(oAnchor.recordIndex === nTargetRecordIndex) {
12797                          // Select all cells between anchor cell and target cell, including target cell
12798                          if(oAnchor.colKeyIndex < nTargetColKeyIndex) {
12799                              for(i=oAnchor.colKeyIndex+1; i<=nTargetColKeyIndex; i++) {
12800                                  this.selectCell(elTargetRow.cells[i]);
12801                              }
12802                          }
12803                          // Select all cells between target cell and anchor cell, including target cell
12804                          else if(nTargetColKeyIndex < oAnchor.colKeyIndex) {
12805                              for(i=nTargetColKeyIndex; i<oAnchor.colKeyIndex; i++) {
12806                                  this.selectCell(elTargetRow.cells[i]);
12807                              }
12808                          }
12809                      }
12810                      // Anchor row is above target row
12811                      else if(oAnchor.recordIndex < nTargetRecordIndex) {
12812                          // Select all cells on anchor row from anchor cell to the end of the row
12813                          for(i=oAnchor.colKeyIndex+1; i<elTargetRow.cells.length; i++) {
12814                              this.selectCell(elTargetRow.cells[i]);
12815                          }
12816  
12817                          // Select all cells on all rows between anchor row and target row
12818                          for(i=oAnchor.trIndex+1; i<nTargetTrIndex; i++) {
12819                              for(j=0; j<allRows[i].cells.length; j++){
12820                                  this.selectCell(allRows[i].cells[j]);
12821                              }
12822                          }
12823  
12824                          // Select all cells on target row from first cell to the target cell
12825                          for(i=0; i<=nTargetColKeyIndex; i++) {
12826                              this.selectCell(elTargetRow.cells[i]);
12827                          }
12828                      }
12829                      // Anchor row is below target row
12830                      else {
12831                          // Select all cells on target row from target cell to the end of the row
12832                          for(i=nTargetColKeyIndex; i<elTargetRow.cells.length; i++) {
12833                              this.selectCell(elTargetRow.cells[i]);
12834                          }
12835  
12836                          // Select all cells on all rows between target row and anchor row
12837                          for(i=nTargetTrIndex+1; i<oAnchor.trIndex; i++) {
12838                              for(j=0; j<allRows[i].cells.length; j++){
12839                                  this.selectCell(allRows[i].cells[j]);
12840                              }
12841                          }
12842  
12843                          // Select all cells on anchor row from first cell to the anchor cell
12844                          for(i=0; i<oAnchor.colKeyIndex; i++) {
12845                              this.selectCell(elTargetRow.cells[i]);
12846                          }
12847                      }
12848                  }
12849                  // Anchor cell is unselected
12850                  else {
12851                      // All cells are on the same row
12852                      if(oAnchor.recordIndex === nTargetRecordIndex) {
12853                          // Unselect all cells between anchor cell and target cell
12854                          if(oAnchor.colKeyIndex < nTargetColKeyIndex) {
12855                              for(i=oAnchor.colKeyIndex+1; i<nTargetColKeyIndex; i++) {
12856                                  this.unselectCell(elTargetRow.cells[i]);
12857                              }
12858                          }
12859                          // Select all cells between target cell and anchor cell
12860                          else if(nTargetColKeyIndex < oAnchor.colKeyIndex) {
12861                              for(i=nTargetColKeyIndex+1; i<oAnchor.colKeyIndex; i++) {
12862                                  this.unselectCell(elTargetRow.cells[i]);
12863                              }
12864                          }
12865                      }
12866                      // Anchor row is above target row
12867                      if(oAnchor.recordIndex < nTargetRecordIndex) {
12868                          // Unselect all cells from anchor cell to target cell
12869                          for(i=oAnchor.trIndex; i<=nTargetTrIndex; i++) {
12870                              currentRow = allRows[i];
12871                              for(j=0; j<currentRow.cells.length; j++) {
12872                                  // This is the anchor row, only unselect cells after the anchor cell
12873                                  if(currentRow.sectionRowIndex === oAnchor.trIndex) {
12874                                      if(j>oAnchor.colKeyIndex) {
12875                                          this.unselectCell(currentRow.cells[j]);
12876                                      }
12877                                  }
12878                                  // This is the target row, only unelect cells before the target cell
12879                                  else if(currentRow.sectionRowIndex === nTargetTrIndex) {
12880                                      if(j<nTargetColKeyIndex) {
12881                                          this.unselectCell(currentRow.cells[j]);
12882                                      }
12883                                  }
12884                                  // Unselect all cells on this row
12885                                  else {
12886                                      this.unselectCell(currentRow.cells[j]);
12887                                  }
12888                              }
12889                          }
12890                      }
12891                      // Anchor row is below target row
12892                      else {
12893                          // Unselect all cells from target cell to anchor cell
12894                          for(i=nTargetTrIndex; i<=oAnchor.trIndex; i++) {
12895                              currentRow = allRows[i];
12896                              for(j=0; j<currentRow.cells.length; j++) {
12897                                  // This is the target row, only unselect cells after the target cell
12898                                  if(currentRow.sectionRowIndex == nTargetTrIndex) {
12899                                      if(j>nTargetColKeyIndex) {
12900                                          this.unselectCell(currentRow.cells[j]);
12901                                      }
12902                                  }
12903                                  // This is the anchor row, only unselect cells before the anchor cell
12904                                  else if(currentRow.sectionRowIndex == oAnchor.trIndex) {
12905                                      if(j<oAnchor.colKeyIndex) {
12906                                          this.unselectCell(currentRow.cells[j]);
12907                                      }
12908                                  }
12909                                  // Unselect all cells on this row
12910                                  else {
12911                                      this.unselectCell(currentRow.cells[j]);
12912                                  }
12913                              }
12914                          }
12915                      }
12916  
12917                      // Select the target cell
12918                      this.selectCell(elTargetCell);
12919                  }
12920              }
12921              // Invalid anchor
12922              else {
12923                  // Set anchor
12924                  this._oAnchorCell = oTargetCell;
12925  
12926                  // Toggle selection of target
12927                  if(this.isSelected(oTargetCell)) {
12928                      this.unselectCell(oTargetCell);
12929                  }
12930                  else {
12931                      this.selectCell(oTargetCell);
12932                  }
12933              }
12934          }
12935           // Only SHIFT
12936          else if(bSHIFT) {
12937  
12938              this.unselectAllCells();
12939  
12940              // Validate anchor
12941              if(oAnchor) {
12942                  // All cells are on the same row
12943                  if(oAnchor.recordIndex === nTargetRecordIndex) {
12944                      // Select all cells between anchor cell and target cell,
12945                      // including the anchor cell and target cell
12946                      if(oAnchor.colKeyIndex < nTargetColKeyIndex) {
12947                          for(i=oAnchor.colKeyIndex; i<=nTargetColKeyIndex; i++) {
12948                              this.selectCell(elTargetRow.cells[i]);
12949                          }
12950                      }
12951                      // Select all cells between target cell and anchor cell
12952                      // including the target cell and anchor cell
12953                      else if(nTargetColKeyIndex < oAnchor.colKeyIndex) {
12954                          for(i=nTargetColKeyIndex; i<=oAnchor.colKeyIndex; i++) {
12955                              this.selectCell(elTargetRow.cells[i]);
12956                          }
12957                      }
12958                  }
12959                  // Anchor row is above target row
12960                  else if(oAnchor.recordIndex < nTargetRecordIndex) {
12961                      // Select all cells from anchor cell to target cell
12962                      // including the anchor cell and target cell
12963                      for(i=oAnchor.trIndex; i<=nTargetTrIndex; i++) {
12964                          currentRow = allRows[i];
12965                          for(j=0; j<currentRow.cells.length; j++) {
12966                              // This is the anchor row, only select the anchor cell and after
12967                              if(currentRow.sectionRowIndex == oAnchor.trIndex) {
12968                                  if(j>=oAnchor.colKeyIndex) {
12969                                      this.selectCell(currentRow.cells[j]);
12970                                  }
12971                              }
12972                              // This is the target row, only select the target cell and before
12973                              else if(currentRow.sectionRowIndex == nTargetTrIndex) {
12974                                  if(j<=nTargetColKeyIndex) {
12975                                      this.selectCell(currentRow.cells[j]);
12976                                  }
12977                              }
12978                              // Select all cells on this row
12979                              else {
12980                                  this.selectCell(currentRow.cells[j]);
12981                              }
12982                          }
12983                      }
12984                  }
12985                  // Anchor row is below target row
12986                  else {
12987                      // Select all cells from target cell to anchor cell,
12988                      // including the target cell and anchor cell
12989                      for(i=nTargetTrIndex; i<=oAnchor.trIndex; i++) {
12990                          currentRow = allRows[i];
12991                          for(j=0; j<currentRow.cells.length; j++) {
12992                              // This is the target row, only select the target cell and after
12993                              if(currentRow.sectionRowIndex == nTargetTrIndex) {
12994                                  if(j>=nTargetColKeyIndex) {
12995                                      this.selectCell(currentRow.cells[j]);
12996                                  }
12997                              }
12998                              // This is the anchor row, only select the anchor cell and before
12999                              else if(currentRow.sectionRowIndex == oAnchor.trIndex) {
13000                                  if(j<=oAnchor.colKeyIndex) {
13001                                      this.selectCell(currentRow.cells[j]);
13002                                  }
13003                              }
13004                              // Select all cells on this row
13005                              else {
13006                                  this.selectCell(currentRow.cells[j]);
13007                              }
13008                          }
13009                      }
13010                  }
13011              }
13012              // Invalid anchor
13013              else {
13014                  // Set anchor
13015                  this._oAnchorCell = oTargetCell;
13016  
13017                  // Select target only
13018                  this.selectCell(oTargetCell);
13019              }
13020  
13021  
13022          }
13023          // Only CTRL
13024          else if(bCTRL) {
13025  
13026              // Set anchor
13027              this._oAnchorCell = oTargetCell;
13028  
13029              // Toggle selection of target
13030              if(this.isSelected(oTargetCell)) {
13031                  this.unselectCell(oTargetCell);
13032              }
13033              else {
13034                  this.selectCell(oTargetCell);
13035              }
13036  
13037          }
13038          // Neither SHIFT nor CTRL
13039          else {
13040              this._handleSingleCellSelectionByMouse(oArgs);
13041          }
13042      }
13043  },
13044  
13045  /**
13046   * Determines selection behavior resulting from a key event when selection mode
13047   * is set to "cellrange".
13048   *
13049   * @method _handleCellRangeSelectionByKey
13050   * @param e {HTMLEvent} Event object.
13051   * @private
13052   */
13053  _handleCellRangeSelectionByKey : function(e) {
13054      var nKey = Ev.getCharCode(e);
13055      var bSHIFT = e.shiftKey;
13056      if((nKey == 9) || !bSHIFT) {
13057          this._handleSingleCellSelectionByKey(e);
13058          return;
13059      }
13060  
13061      if((nKey > 36) && (nKey < 41)) {
13062          // Validate trigger
13063          var oTrigger = this._getSelectionTrigger();
13064          // Arrow selection only works if last selected row is on current page
13065          if(!oTrigger) {
13066              return null;
13067          }
13068  
13069          Ev.stopEvent(e);
13070  
13071          // Validate anchor
13072          var oAnchor = this._getSelectionAnchor(oTrigger);
13073  
13074          var i, elNewRow, elNew;
13075          var allRows = this.getTbodyEl().rows;
13076          var elThisRow = oTrigger.el.parentNode;
13077  
13078          // Arrow down
13079          if(nKey == 40) {
13080              elNewRow = this.getNextTrEl(oTrigger.el);
13081  
13082              // Selecting away from anchor cell
13083              if(oAnchor.recordIndex <= oTrigger.recordIndex) {
13084                  // Select all cells to the end of this row
13085                  for(i=oTrigger.colKeyIndex+1; i<elThisRow.cells.length; i++){
13086                      elNew = elThisRow.cells[i];
13087                      this.selectCell(elNew);
13088                  }
13089  
13090                  // Select some of the cells on the next row down
13091                  if(elNewRow) {
13092                      for(i=0; i<=oTrigger.colKeyIndex; i++){
13093                          elNew = elNewRow.cells[i];
13094                          this.selectCell(elNew);
13095                      }
13096                  }
13097              }
13098              // Unselecting towards anchor cell
13099              else {
13100                  // Unselect all cells to the end of this row
13101                  for(i=oTrigger.colKeyIndex; i<elThisRow.cells.length; i++){
13102                      this.unselectCell(elThisRow.cells[i]);
13103                  }
13104  
13105                  // Unselect some of the cells on the next row down
13106                  if(elNewRow) {
13107                      for(i=0; i<oTrigger.colKeyIndex; i++){
13108                          this.unselectCell(elNewRow.cells[i]);
13109                      }
13110                  }
13111              }
13112          }
13113          // Arrow up
13114          else if(nKey == 38) {
13115              elNewRow = this.getPreviousTrEl(oTrigger.el);
13116  
13117              // Selecting away from anchor cell
13118              if(oAnchor.recordIndex >= oTrigger.recordIndex) {
13119                  // Select all the cells to the beginning of this row
13120                  for(i=oTrigger.colKeyIndex-1; i>-1; i--){
13121                      elNew = elThisRow.cells[i];
13122                      this.selectCell(elNew);
13123                  }
13124  
13125                  // Select some of the cells from the end of the previous row
13126                  if(elNewRow) {
13127                      for(i=elThisRow.cells.length-1; i>=oTrigger.colKeyIndex; i--){
13128                          elNew = elNewRow.cells[i];
13129                          this.selectCell(elNew);
13130                      }
13131                  }
13132              }
13133              // Unselecting towards anchor cell
13134              else {
13135                  // Unselect all the cells to the beginning of this row
13136                  for(i=oTrigger.colKeyIndex; i>-1; i--){
13137                      this.unselectCell(elThisRow.cells[i]);
13138                  }
13139  
13140                  // Unselect some of the cells from the end of the previous row
13141                  if(elNewRow) {
13142                      for(i=elThisRow.cells.length-1; i>oTrigger.colKeyIndex; i--){
13143                          this.unselectCell(elNewRow.cells[i]);
13144                      }
13145                  }
13146              }
13147          }
13148          // Arrow right
13149          else if(nKey == 39) {
13150              elNewRow = this.getNextTrEl(oTrigger.el);
13151  
13152              // Selecting away from anchor cell
13153              if(oAnchor.recordIndex < oTrigger.recordIndex) {
13154                  // Select the next cell to the right
13155                  if(oTrigger.colKeyIndex < elThisRow.cells.length-1) {
13156                      elNew = elThisRow.cells[oTrigger.colKeyIndex+1];
13157                      this.selectCell(elNew);
13158                  }
13159                  // Select the first cell of the next row
13160                  else if(elNewRow) {
13161                      elNew = elNewRow.cells[0];
13162                      this.selectCell(elNew);
13163                  }
13164              }
13165              // Unselecting towards anchor cell
13166              else if(oAnchor.recordIndex > oTrigger.recordIndex) {
13167                  this.unselectCell(elThisRow.cells[oTrigger.colKeyIndex]);
13168  
13169                  // Unselect this cell towards the right
13170                  if(oTrigger.colKeyIndex < elThisRow.cells.length-1) {
13171                  }
13172                  // Unselect this cells towards the first cell of the next row
13173                  else {
13174                  }
13175              }
13176              // Anchor is on this row
13177              else {
13178                  // Selecting away from anchor
13179                  if(oAnchor.colKeyIndex <= oTrigger.colKeyIndex) {
13180                      // Select the next cell to the right
13181                      if(oTrigger.colKeyIndex < elThisRow.cells.length-1) {
13182                          elNew = elThisRow.cells[oTrigger.colKeyIndex+1];
13183                          this.selectCell(elNew);
13184                      }
13185                      // Select the first cell on the next row
13186                      else if(oTrigger.trIndex < allRows.length-1){
13187                          elNew = elNewRow.cells[0];
13188                          this.selectCell(elNew);
13189                      }
13190                  }
13191                  // Unselecting towards anchor
13192                  else {
13193                      // Unselect this cell towards the right
13194                      this.unselectCell(elThisRow.cells[oTrigger.colKeyIndex]);
13195                  }
13196              }
13197          }
13198          // Arrow left
13199          else if(nKey == 37) {
13200              elNewRow = this.getPreviousTrEl(oTrigger.el);
13201  
13202              // Unselecting towards the anchor
13203              if(oAnchor.recordIndex < oTrigger.recordIndex) {
13204                  this.unselectCell(elThisRow.cells[oTrigger.colKeyIndex]);
13205  
13206                  // Unselect this cell towards the left
13207                  if(oTrigger.colKeyIndex > 0) {
13208                  }
13209                  // Unselect this cell towards the last cell of the previous row
13210                  else {
13211                  }
13212              }
13213              // Selecting towards the anchor
13214              else if(oAnchor.recordIndex > oTrigger.recordIndex) {
13215                  // Select the next cell to the left
13216                  if(oTrigger.colKeyIndex > 0) {
13217                      elNew = elThisRow.cells[oTrigger.colKeyIndex-1];
13218                      this.selectCell(elNew);
13219                  }
13220                  // Select the last cell of the previous row
13221                  else if(oTrigger.trIndex > 0){
13222                      elNew = elNewRow.cells[elNewRow.cells.length-1];
13223                      this.selectCell(elNew);
13224                  }
13225              }
13226              // Anchor is on this row
13227              else {
13228                  // Selecting away from anchor cell
13229                  if(oAnchor.colKeyIndex >= oTrigger.colKeyIndex) {
13230                      // Select the next cell to the left
13231                      if(oTrigger.colKeyIndex > 0) {
13232                          elNew = elThisRow.cells[oTrigger.colKeyIndex-1];
13233                          this.selectCell(elNew);
13234                      }
13235                      // Select the last cell of the previous row
13236                      else if(oTrigger.trIndex > 0){
13237                          elNew = elNewRow.cells[elNewRow.cells.length-1];
13238                          this.selectCell(elNew);
13239                      }
13240                  }
13241                  // Unselecting towards anchor cell
13242                  else {
13243                      this.unselectCell(elThisRow.cells[oTrigger.colKeyIndex]);
13244  
13245                      // Unselect this cell towards the left
13246                      if(oTrigger.colKeyIndex > 0) {
13247                      }
13248                      // Unselect this cell towards the last cell of the previous row
13249                      else {
13250                      }
13251                  }
13252              }
13253          }
13254      }
13255  },
13256  
13257  /**
13258   * Determines selection behavior resulting from a mouse event when selection mode
13259   * is set to "singlecell".
13260   *
13261   * @method _handleSingleCellSelectionByMouse
13262   * @param oArgs.event {HTMLEvent} Event object.
13263   * @param oArgs.target {HTMLElement} Target element.
13264   * @private
13265   */
13266  _handleSingleCellSelectionByMouse : function(oArgs) {
13267      var elTarget = oArgs.target;
13268  
13269      // Validate target cell
13270      var elTargetCell = this.getTdEl(elTarget);
13271      if(elTargetCell) {
13272          var elTargetRow = this.getTrEl(elTargetCell);
13273          var oTargetRecord = this.getRecord(elTargetRow);
13274          var oTargetColumn = this.getColumn(elTargetCell);
13275          var oTargetCell = {record:oTargetRecord, column:oTargetColumn};
13276  
13277          // Set anchor
13278          this._oAnchorCell = oTargetCell;
13279  
13280          // Select only target
13281          this.unselectAllCells();
13282          this.selectCell(oTargetCell);
13283      }
13284  },
13285  
13286  /**
13287   * Determines selection behavior resulting from a key event when selection mode
13288   * is set to "singlecell".
13289   *
13290   * @method _handleSingleCellSelectionByKey
13291   * @param e {HTMLEvent} Event object.
13292   * @private
13293   */
13294  _handleSingleCellSelectionByKey : function(e) {
13295      var nKey = Ev.getCharCode(e);
13296      if((nKey == 9) || ((nKey > 36) && (nKey < 41))) {
13297          var bSHIFT = e.shiftKey;
13298  
13299          // Validate trigger
13300          var oTrigger = this._getSelectionTrigger();
13301          // Arrow selection only works if last selected row is on current page
13302          if(!oTrigger) {
13303              return null;
13304          }
13305  
13306          // Determine the new cell to select
13307          var elNew;
13308          if(nKey == 40) { // Arrow down
13309              elNew = this.getBelowTdEl(oTrigger.el);
13310  
13311              // Validate new cell
13312              if(elNew === null) {
13313                  //TODO: wrap around to first tr on current page
13314  
13315                  //TODO: wrap forward to first tr of next page
13316  
13317                  // Bottom selection is sticky
13318                  elNew = oTrigger.el;
13319              }
13320          }
13321          else if(nKey == 38) { // Arrow up
13322              elNew = this.getAboveTdEl(oTrigger.el);
13323  
13324              // Validate new cell
13325              if(elNew === null) {
13326                  //TODO: wrap around to last tr on current page
13327  
13328                  //TODO: wrap back to last tr of previous page
13329  
13330                  // Top selection is sticky
13331                  elNew = oTrigger.el;
13332              }
13333          }
13334          else if((nKey == 39) || (!bSHIFT && (nKey == 9))) { // Arrow right or tab
13335              elNew = this.getNextTdEl(oTrigger.el);
13336  
13337              // Validate new cell
13338              if(elNew === null) {
13339                  //TODO: wrap around to first td on current page
13340  
13341                  //TODO: wrap forward to first td of next page
13342  
13343                  // Top-left selection is sticky, and release TAB focus
13344                  //elNew = oTrigger.el;
13345                  return;
13346              }
13347          }
13348          else if((nKey == 37) || (bSHIFT && (nKey == 9))) { // Arrow left or shift-tab
13349              elNew = this.getPreviousTdEl(oTrigger.el);
13350  
13351              // Validate new cell
13352              if(elNew === null) {
13353                  //TODO: wrap around to last td on current page
13354  
13355                  //TODO: wrap back to last td of previous page
13356  
13357                  // Bottom-right selection is sticky, and release TAB focus
13358                  //elNew = oTrigger.el;
13359                  return;
13360              }
13361          }
13362  
13363          Ev.stopEvent(e);
13364          
13365          // Unselect all cells
13366          this.unselectAllCells();
13367  
13368          // Select the new cell
13369          this.selectCell(elNew);
13370  
13371          // Set new anchor
13372          this._oAnchorCell = {record:this.getRecord(elNew), column:this.getColumn(elNew)};
13373      }
13374  },
13375  
13376  /**
13377   * Returns array of selected TR elements on the page.
13378   *
13379   * @method getSelectedTrEls
13380   * @return {HTMLElement[]} Array of selected TR elements.
13381   */
13382  getSelectedTrEls : function() {
13383      return Dom.getElementsByClassName(DT.CLASS_SELECTED,"tr",this._elTbody);
13384  },
13385  
13386  /**
13387   * Sets given row to the selected state.
13388   *
13389   * @method selectRow
13390   * @param row {HTMLElement | String | YAHOO.widget.Record | Number} HTML element
13391   * reference or ID string, Record instance, or RecordSet position index.
13392   */
13393  selectRow : function(row) {
13394      var oRecord, elRow;
13395  
13396      if(row instanceof YAHOO.widget.Record) {
13397          oRecord = this._oRecordSet.getRecord(row);
13398          elRow = this.getTrEl(oRecord);
13399      }
13400      else if(lang.isNumber(row)) {
13401          oRecord = this.getRecord(row);
13402          elRow = this.getTrEl(oRecord);
13403      }
13404      else {
13405          elRow = this.getTrEl(row);
13406          oRecord = this.getRecord(elRow);
13407      }
13408  
13409      if(oRecord) {
13410          // Update selection trackers
13411          var tracker = this._aSelections || [];
13412          var sRecordId = oRecord.getId();
13413          var index = -1;
13414  
13415          // Remove if already there:
13416          // Use Array.indexOf if available...
13417          /*if(tracker.indexOf && (tracker.indexOf(sRecordId) >  -1)) {
13418              tracker.splice(tracker.indexOf(sRecordId),1);
13419          }*/
13420          if(tracker.indexOf) {
13421              index = tracker.indexOf(sRecordId);
13422              
13423          }
13424          // ...or do it the old-fashioned way
13425          else {
13426              for(var j=tracker.length-1; j>-1; j--) {
13427                  if(tracker[j] === sRecordId){
13428                      index = j;
13429                      break;
13430                  }
13431              }
13432          }
13433          if(index > -1) {
13434              tracker.splice(index,1);
13435          }
13436          
13437          // Add to the end
13438          tracker.push(sRecordId);
13439          this._aSelections = tracker;
13440  
13441          // Update trackers
13442          if(!this._oAnchorRecord) {
13443              this._oAnchorRecord = oRecord;
13444          }
13445  
13446          // Update UI
13447          if(elRow) {
13448              Dom.addClass(elRow, DT.CLASS_SELECTED);
13449          }
13450  
13451          this.fireEvent("rowSelectEvent", {record:oRecord, el:elRow});
13452          YAHOO.log("Selected " + elRow, "info", this.toString());
13453      }
13454      else {
13455          YAHOO.log("Could not select row " + row, "warn", this.toString());
13456      }
13457  },
13458  
13459  /**
13460   * Sets given row to the unselected state.
13461   *
13462   * @method unselectRow
13463   * @param row {HTMLElement | String | YAHOO.widget.Record | Number} HTML element
13464   * reference or ID string, Record instance, or RecordSet position index.
13465   */
13466  unselectRow : function(row) {
13467      var elRow = this.getTrEl(row);
13468  
13469      var oRecord;
13470      if(row instanceof YAHOO.widget.Record) {
13471          oRecord = this._oRecordSet.getRecord(row);
13472      }
13473      else if(lang.isNumber(row)) {
13474          oRecord = this.getRecord(row);
13475      }
13476      else {
13477          oRecord = this.getRecord(elRow);
13478      }
13479  
13480      if(oRecord) {
13481          // Update selection trackers
13482          var tracker = this._aSelections || [];
13483          var sRecordId = oRecord.getId();
13484          var index = -1;
13485  
13486          // Use Array.indexOf if available...
13487          if(tracker.indexOf) {
13488              index = tracker.indexOf(sRecordId);
13489          }
13490          // ...or do it the old-fashioned way
13491          else {
13492              for(var j=tracker.length-1; j>-1; j--) {
13493                  if(tracker[j] === sRecordId){
13494                      index = j;
13495                      break;
13496                  }
13497              }
13498          }
13499          if(index > -1) {
13500              // Update tracker
13501              tracker.splice(index,1);
13502              this._aSelections = tracker;
13503  
13504              // Update the UI
13505              Dom.removeClass(elRow, DT.CLASS_SELECTED);
13506  
13507              this.fireEvent("rowUnselectEvent", {record:oRecord, el:elRow});
13508              YAHOO.log("Unselected " + elRow, "info", this.toString());
13509  
13510              return;
13511          }
13512      }
13513      YAHOO.log("Could not unselect row " + row, "warn", this.toString());
13514  },
13515  
13516  /**
13517   * Clears out all row selections.
13518   *
13519   * @method unselectAllRows
13520   */
13521  unselectAllRows : function() {
13522      // Remove all rows from tracker
13523      var tracker = this._aSelections || [],
13524          recId,
13525          removed = [];
13526      for(var j=tracker.length-1; j>-1; j--) {
13527         if(lang.isString(tracker[j])){
13528              recId = tracker.splice(j,1);
13529              removed[removed.length] = this.getRecord(lang.isArray(recId) ? recId[0] : recId);
13530          }
13531      }
13532  
13533      // Update tracker
13534      this._aSelections = tracker;
13535  
13536      // Update UI
13537      this._unselectAllTrEls();
13538  
13539      this.fireEvent("unselectAllRowsEvent", {records: removed});
13540      YAHOO.log("Unselected all rows", "info", this.toString());
13541  },
13542  
13543  /**
13544   * Convenience method to remove the class YAHOO.widget.DataTable.CLASS_SELECTED
13545   * from all TD elements in the internal tracker.
13546   *
13547   * @method _unselectAllTdEls
13548   * @private
13549   */
13550  _unselectAllTdEls : function() {
13551      var selectedCells = Dom.getElementsByClassName(DT.CLASS_SELECTED,"td",this._elTbody);
13552      Dom.removeClass(selectedCells, DT.CLASS_SELECTED);
13553  },
13554  
13555  /**
13556   * Returns array of selected TD elements on the page.
13557   *
13558   * @method getSelectedTdEls
13559   * @return {HTMLElement[]} Array of selected TD elements.
13560   */
13561  getSelectedTdEls : function() {
13562      return Dom.getElementsByClassName(DT.CLASS_SELECTED,"td",this._elTbody);
13563  },
13564  
13565  /**
13566   * Sets given cell to the selected state.
13567   *
13568   * @method selectCell
13569   * @param cell {HTMLElement | String | Object} TD element or child of a TD element, or
13570   * object literal of syntax {record:oRecord, column:oColumn}.
13571   */
13572  selectCell : function(cell) {
13573  //TODO: accept {record} in selectCell()
13574      var elCell = this.getTdEl(cell);
13575  
13576      if(elCell) {
13577          var oRecord = this.getRecord(elCell);
13578          var oColumn = this.getColumn(this.getCellIndex(elCell));
13579          var sColumnKey = oColumn.getKey();
13580  
13581          if(oRecord && sColumnKey) {
13582              // Get Record ID
13583              var tracker = this._aSelections || [];
13584              var sRecordId = oRecord.getId();
13585  
13586              // Remove if there
13587              for(var j=tracker.length-1; j>-1; j--) {
13588                 if((tracker[j].recordId === sRecordId) && (tracker[j].columnKey === sColumnKey)){
13589                      tracker.splice(j,1);
13590                      break;
13591                  }
13592              }
13593  
13594              // Add to the end
13595              tracker.push({recordId:sRecordId, columnKey:sColumnKey});
13596  
13597              // Update trackers
13598              this._aSelections = tracker;
13599              if(!this._oAnchorCell) {
13600                  this._oAnchorCell = {record:oRecord, column:oColumn};
13601              }
13602  
13603              // Update the UI
13604              Dom.addClass(elCell, DT.CLASS_SELECTED);
13605  
13606              this.fireEvent("cellSelectEvent", {record:oRecord, column:oColumn, key: sColumnKey, el:elCell});
13607              YAHOO.log("Selected " + elCell, "info", this.toString());
13608              return;
13609          }
13610      }
13611      YAHOO.log("Could not select cell " + cell, "warn", this.toString());
13612  },
13613  
13614  /**
13615   * Sets given cell to the unselected state.
13616   *
13617   * @method unselectCell
13618   * @param cell {HTMLElement | String | Object} TD element or child of a TD element, or
13619   * object literal of syntax {record:oRecord, column:oColumn}.
13620   * @param cell {HTMLElement | String} DOM element reference or ID string
13621   * to DataTable page element or RecordSet index.
13622   */
13623  unselectCell : function(cell) {
13624      var elCell = this.getTdEl(cell);
13625  
13626      if(elCell) {
13627          var oRecord = this.getRecord(elCell);
13628          var oColumn = this.getColumn(this.getCellIndex(elCell));
13629          var sColumnKey = oColumn.getKey();
13630  
13631          if(oRecord && sColumnKey) {
13632              // Get Record ID
13633              var tracker = this._aSelections || [];
13634              var id = oRecord.getId();
13635  
13636              // Is it selected?
13637              for(var j=tracker.length-1; j>-1; j--) {
13638                  if((tracker[j].recordId === id) && (tracker[j].columnKey === sColumnKey)){
13639                      // Remove from tracker
13640                      tracker.splice(j,1);
13641  
13642                      // Update tracker
13643                      this._aSelections = tracker;
13644  
13645                      // Update the UI
13646                      Dom.removeClass(elCell, DT.CLASS_SELECTED);
13647  
13648                      this.fireEvent("cellUnselectEvent", {record:oRecord, column: oColumn, key:sColumnKey, el:elCell});
13649                      YAHOO.log("Unselected " + elCell, "info", this.toString());
13650                      return;
13651                  }
13652              }
13653          }
13654      }
13655      YAHOO.log("Could not unselect cell " + cell, "warn", this.toString());
13656  },
13657  
13658  /**
13659   * Clears out all cell selections.
13660   *
13661   * @method unselectAllCells
13662   */
13663  unselectAllCells : function() {
13664      // Remove all cells from tracker
13665      var tracker = this._aSelections || [];
13666      for(var j=tracker.length-1; j>-1; j--) {
13667         if(lang.isObject(tracker[j])){
13668              tracker.splice(j,1);
13669          }
13670      }
13671  
13672      // Update tracker
13673      this._aSelections = tracker;
13674  
13675      // Update UI
13676      this._unselectAllTdEls();
13677  
13678      //TODO: send data to unselectAllCellsEvent handler
13679      this.fireEvent("unselectAllCellsEvent");
13680      YAHOO.log("Unselected all cells", "info", this.toString());
13681  },
13682  
13683  /**
13684   * Returns true if given item is selected, false otherwise.
13685   *
13686   * @method isSelected
13687   * @param o {String | HTMLElement | YAHOO.widget.Record | Number
13688   * {record:YAHOO.widget.Record, column:YAHOO.widget.Column} } TR or TD element by
13689   * reference or ID string, a Record instance, a RecordSet position index,
13690   * or an object literal representation
13691   * of a cell.
13692   * @return {Boolean} True if item is selected.
13693   */
13694  isSelected : function(o) {
13695      if(o && (o.ownerDocument == document)) {
13696          return (Dom.hasClass(this.getTdEl(o),DT.CLASS_SELECTED) || Dom.hasClass(this.getTrEl(o),DT.CLASS_SELECTED));
13697      }
13698      else {
13699          var oRecord, sRecordId, j;
13700          var tracker = this._aSelections;
13701          if(tracker && tracker.length > 0) {
13702              // Looking for a Record?
13703              if(o instanceof YAHOO.widget.Record) {
13704                  oRecord = o;
13705              }
13706              else if(lang.isNumber(o)) {
13707                  oRecord = this.getRecord(o);
13708              }
13709              if(oRecord) {
13710                  sRecordId = oRecord.getId();
13711  
13712                  // Is it there?
13713                  // Use Array.indexOf if available...
13714                  if(tracker.indexOf) {
13715                      if(tracker.indexOf(sRecordId) >  -1) {
13716                          return true;
13717                      }
13718                  }
13719                  // ...or do it the old-fashioned way
13720                  else {
13721                      for(j=tracker.length-1; j>-1; j--) {
13722                         if(tracker[j] === sRecordId){
13723                          return true;
13724                         }
13725                      }
13726                  }
13727              }
13728              // Looking for a cell
13729              else if(o.record && o.column){
13730                  sRecordId = o.record.getId();
13731                  var sColumnKey = o.column.getKey();
13732  
13733                  for(j=tracker.length-1; j>-1; j--) {
13734                      if((tracker[j].recordId === sRecordId) && (tracker[j].columnKey === sColumnKey)){
13735                          return true;
13736                      }
13737                  }
13738              }
13739          }
13740      }
13741      return false;
13742  },
13743  
13744  /**
13745   * Returns selected rows as an array of Record IDs.
13746   *
13747   * @method getSelectedRows
13748   * @return {String[]} Array of selected rows by Record ID.
13749   */
13750  getSelectedRows : function() {
13751      var aSelectedRows = [];
13752      var tracker = this._aSelections || [];
13753      for(var j=0; j<tracker.length; j++) {
13754         if(lang.isString(tracker[j])){
13755              aSelectedRows.push(tracker[j]);
13756          }
13757      }
13758      return aSelectedRows;
13759  },
13760  
13761  /**
13762   * Returns selected cells as an array of object literals:
13763   *     {recordId:sRecordId, columnKey:sColumnKey}.
13764   *
13765   * @method getSelectedCells
13766   * @return {Object[]} Array of selected cells by Record ID and Column ID.
13767   */
13768  getSelectedCells : function() {
13769      var aSelectedCells = [];
13770      var tracker = this._aSelections || [];
13771      for(var j=0; j<tracker.length; j++) {
13772         if(tracker[j] && lang.isObject(tracker[j])){
13773              aSelectedCells.push(tracker[j]);
13774          }
13775      }
13776      return aSelectedCells;
13777  },
13778  
13779  /**
13780   * Returns last selected Record ID.
13781   *
13782   * @method getLastSelectedRecord
13783   * @return {String} Record ID of last selected row.
13784   */
13785  getLastSelectedRecord : function() {
13786      var tracker = this._aSelections;
13787      if(tracker && tracker.length > 0) {
13788          for(var i=tracker.length-1; i>-1; i--) {
13789             if(lang.isString(tracker[i])){
13790                  return tracker[i];
13791              }
13792          }
13793      }
13794  },
13795  
13796  /**
13797   * Returns last selected cell as an object literal:
13798   *     {recordId:sRecordId, columnKey:sColumnKey}.
13799   *
13800   * @method getLastSelectedCell
13801   * @return {Object} Object literal representation of a cell.
13802   */
13803  getLastSelectedCell : function() {
13804      var tracker = this._aSelections;
13805      if(tracker && tracker.length > 0) {
13806          for(var i=tracker.length-1; i>-1; i--) {
13807             if(tracker[i].recordId && tracker[i].columnKey){
13808                  return tracker[i];
13809              }
13810          }
13811      }
13812  },
13813  
13814  /**
13815   * Assigns the class YAHOO.widget.DataTable.CLASS_HIGHLIGHTED to the given row.
13816   *
13817   * @method highlightRow
13818   * @param row {HTMLElement | String} DOM element reference or ID string.
13819   */
13820  highlightRow : function(row) {
13821      var elRow = this.getTrEl(row);
13822  
13823      if(elRow) {
13824          // Make sure previous row is unhighlighted
13825  /*        if(this._sLastHighlightedTrElId) {
13826              Dom.removeClass(this._sLastHighlightedTrElId,DT.CLASS_HIGHLIGHTED);
13827          }*/
13828          var oRecord = this.getRecord(elRow);
13829          Dom.addClass(elRow,DT.CLASS_HIGHLIGHTED);
13830          //this._sLastHighlightedTrElId = elRow.id;
13831          this.fireEvent("rowHighlightEvent", {record:oRecord, el:elRow});
13832          YAHOO.log("Highlighted " + elRow, "info", this.toString());
13833          return;
13834      }
13835      YAHOO.log("Could not highlight row " + row, "warn", this.toString());
13836  },
13837  
13838  /**
13839   * Removes the class YAHOO.widget.DataTable.CLASS_HIGHLIGHTED from the given row.
13840   *
13841   * @method unhighlightRow
13842   * @param row {HTMLElement | String} DOM element reference or ID string.
13843   */
13844  unhighlightRow : function(row) {
13845      var elRow = this.getTrEl(row);
13846  
13847      if(elRow) {
13848          var oRecord = this.getRecord(elRow);
13849          Dom.removeClass(elRow,DT.CLASS_HIGHLIGHTED);
13850          this.fireEvent("rowUnhighlightEvent", {record:oRecord, el:elRow});
13851          YAHOO.log("Unhighlighted " + elRow, "info", this.toString());
13852          return;
13853      }
13854      YAHOO.log("Could not unhighlight row " + row, "warn", this.toString());
13855  },
13856  
13857  /**
13858   * Assigns the class YAHOO.widget.DataTable.CLASS_HIGHLIGHTED to the given cell.
13859   *
13860   * @method highlightCell
13861   * @param cell {HTMLElement | String} DOM element reference or ID string.
13862   */
13863  highlightCell : function(cell) {
13864      var elCell = this.getTdEl(cell);
13865  
13866      if(elCell) {
13867          // Make sure previous cell is unhighlighted
13868          if(this._elLastHighlightedTd) {
13869              this.unhighlightCell(this._elLastHighlightedTd);
13870          }
13871  
13872          var oRecord = this.getRecord(elCell);
13873          var oColumn = this.getColumn(this.getCellIndex(elCell));
13874          var sColumnKey = oColumn.getKey();
13875          Dom.addClass(elCell,DT.CLASS_HIGHLIGHTED);
13876          this._elLastHighlightedTd = elCell;
13877          this.fireEvent("cellHighlightEvent", {record:oRecord, column:oColumn, key:sColumnKey, el:elCell});
13878          YAHOO.log("Highlighted " + elCell, "info", this.toString());
13879          return;
13880      }
13881      YAHOO.log("Could not highlight cell " + cell, "warn", this.toString());
13882  },
13883  
13884  /**
13885   * Removes the class YAHOO.widget.DataTable.CLASS_HIGHLIGHTED from the given cell.
13886   *
13887   * @method unhighlightCell
13888   * @param cell {HTMLElement | String} DOM element reference or ID string.
13889   */
13890  unhighlightCell : function(cell) {
13891      var elCell = this.getTdEl(cell);
13892  
13893      if(elCell) {
13894          var oRecord = this.getRecord(elCell);
13895          Dom.removeClass(elCell,DT.CLASS_HIGHLIGHTED);
13896          this._elLastHighlightedTd = null;
13897          this.fireEvent("cellUnhighlightEvent", {record:oRecord, column:this.getColumn(this.getCellIndex(elCell)), key:this.getColumn(this.getCellIndex(elCell)).getKey(), el:elCell});
13898          YAHOO.log("Unhighlighted " + elCell, "info", this.toString());
13899          return;
13900      }
13901      YAHOO.log("Could not unhighlight cell " + cell, "warn", this.toString());
13902  },
13903  
13904  
13905  
13906  
13907  
13908  
13909  
13910  
13911  
13912  
13913  
13914  
13915  
13916  
13917  
13918  
13919  
13920  
13921  
13922  
13923  
13924  
13925  
13926  
13927  
13928  
13929  
13930  
13931  
13932  
13933  
13934  
13935  
13936  
13937  
13938  
13939  
13940  
13941  
13942  
13943  
13944  
13945  
13946  
13947  
13948  // INLINE EDITING
13949  
13950  /**
13951   * Assigns CellEditor instance to existing Column.
13952   * @method addCellEditor
13953   * @param oColumn {YAHOO.widget.Column} Column instance.
13954   * @param oEditor {YAHOO.wdiget.CellEditor} CellEditor instance.
13955   */
13956  addCellEditor : function(oColumn, oEditor) {
13957      oColumn.editor = oEditor;
13958      oColumn.editor.subscribe("showEvent", this._onEditorShowEvent, this, true);
13959      oColumn.editor.subscribe("keydownEvent", this._onEditorKeydownEvent, this, true);
13960      oColumn.editor.subscribe("revertEvent", this._onEditorRevertEvent, this, true);
13961      oColumn.editor.subscribe("saveEvent", this._onEditorSaveEvent, this, true);
13962      oColumn.editor.subscribe("cancelEvent", this._onEditorCancelEvent, this, true);
13963      oColumn.editor.subscribe("blurEvent", this._onEditorBlurEvent, this, true);
13964      oColumn.editor.subscribe("blockEvent", this._onEditorBlockEvent, this, true);
13965      oColumn.editor.subscribe("unblockEvent", this._onEditorUnblockEvent, this, true);
13966  },
13967  
13968  /**
13969   * Returns current CellEditor instance, or null.
13970   * @method getCellEditor
13971   * @return {YAHOO.widget.CellEditor} CellEditor instance.
13972   */
13973  getCellEditor : function() {
13974      return this._oCellEditor;
13975  },
13976  
13977  
13978  /**
13979   * Activates and shows CellEditor instance for the given cell while deactivating and
13980   * canceling previous CellEditor. It is baked into DataTable that only one CellEditor
13981   * can be active at any given time. 
13982   *
13983   * @method showCellEditor
13984   * @param elCell {HTMLElement | String} Cell to edit.
13985   */
13986  showCellEditor : function(elCell, oRecord, oColumn) {
13987      // Get a particular CellEditor
13988      elCell = this.getTdEl(elCell);
13989      if(elCell) {
13990          oColumn = this.getColumn(elCell);
13991          if(oColumn && oColumn.editor) {
13992              var oCellEditor = this._oCellEditor;
13993              // Clean up active CellEditor
13994              if(oCellEditor) {
13995                  if(this._oCellEditor.cancel) {
13996                      this._oCellEditor.cancel();
13997                  }
13998                  else if(oCellEditor.isActive) {
13999                      this.cancelCellEditor();
14000                  }
14001              }
14002              
14003              if(oColumn.editor instanceof YAHOO.widget.BaseCellEditor) {
14004                  // Get CellEditor
14005                  oCellEditor = oColumn.editor;
14006                  var ok = oCellEditor.attach(this, elCell);
14007                  if(ok) {
14008                      oCellEditor.render();
14009                      oCellEditor.move();
14010                      ok = this.doBeforeShowCellEditor(oCellEditor);
14011                      if(ok) {
14012                          oCellEditor.show();
14013                          this._oCellEditor = oCellEditor;
14014                      }
14015                  }
14016              }
14017              // Backward compatibility
14018              else {
14019                      if(!oRecord || !(oRecord instanceof YAHOO.widget.Record)) {
14020                          oRecord = this.getRecord(elCell);
14021                      }
14022                      if(!oColumn || !(oColumn instanceof YAHOO.widget.Column)) {
14023                          oColumn = this.getColumn(elCell);
14024                      }
14025                      if(oRecord && oColumn) {
14026                          if(!this._oCellEditor || this._oCellEditor.container) {
14027                              this._initCellEditorEl();
14028                          }
14029                          
14030                          // Update Editor values
14031                          oCellEditor = this._oCellEditor;
14032                          oCellEditor.cell = elCell;
14033                          oCellEditor.record = oRecord;
14034                          oCellEditor.column = oColumn;
14035                          oCellEditor.validator = (oColumn.editorOptions &&
14036                                  lang.isFunction(oColumn.editorOptions.validator)) ?
14037                                  oColumn.editorOptions.validator : null;
14038                          oCellEditor.value = oRecord.getData(oColumn.key);
14039                          oCellEditor.defaultValue = null;
14040              
14041                          // Move Editor
14042                          var elContainer = oCellEditor.container;
14043                          var x = Dom.getX(elCell);
14044                          var y = Dom.getY(elCell);
14045              
14046                          // SF doesn't get xy for cells in scrolling table
14047                          // when tbody display is set to block
14048                          if(isNaN(x) || isNaN(y)) {
14049                              x = elCell.offsetLeft + // cell pos relative to table
14050                                      Dom.getX(this._elTbody.parentNode) - // plus table pos relative to document
14051                                      this._elTbody.scrollLeft; // minus tbody scroll
14052                              y = elCell.offsetTop + // cell pos relative to table
14053                                      Dom.getY(this._elTbody.parentNode) - // plus table pos relative to document
14054                                      this._elTbody.scrollTop + // minus tbody scroll
14055                                      this._elThead.offsetHeight; // account for fixed THEAD cells
14056                          }
14057              
14058                          elContainer.style.left = x + "px";
14059                          elContainer.style.top = y + "px";
14060              
14061                          // Hook to customize the UI
14062                          this.doBeforeShowCellEditor(this._oCellEditor);
14063              
14064                          //TODO: This is temporarily up here due so elements can be focused
14065                          // Show Editor
14066                          elContainer.style.display = "";
14067              
14068                          // Handle ESC key
14069                          Ev.addListener(elContainer, "keydown", function(e, oSelf) {
14070                              // ESC hides Cell Editor
14071                              if((e.keyCode == 27)) {
14072                                  oSelf.cancelCellEditor();
14073                                  oSelf.focusTbodyEl();
14074                              }
14075                              else {
14076                                  oSelf.fireEvent("editorKeydownEvent", {editor:oSelf._oCellEditor, event:e});
14077                              }
14078                          }, this);
14079              
14080                          // Render Editor markup
14081                          var fnEditor;
14082                          if(lang.isString(oColumn.editor)) {
14083                              switch(oColumn.editor) {
14084                                  case "checkbox":
14085                                      fnEditor = DT.editCheckbox;
14086                                      break;
14087                                  case "date":
14088                                      fnEditor = DT.editDate;
14089                                      break;
14090                                  case "dropdown":
14091                                      fnEditor = DT.editDropdown;
14092                                      break;
14093                                  case "radio":
14094                                      fnEditor = DT.editRadio;
14095                                      break;
14096                                  case "textarea":
14097                                      fnEditor = DT.editTextarea;
14098                                      break;
14099                                  case "textbox":
14100                                      fnEditor = DT.editTextbox;
14101                                      break;
14102                                  default:
14103                                      fnEditor = null;
14104                              }
14105                          }
14106                          else if(lang.isFunction(oColumn.editor)) {
14107                              fnEditor = oColumn.editor;
14108                          }
14109              
14110                          if(fnEditor) {
14111                              // Create DOM input elements
14112                              fnEditor(this._oCellEditor, this);
14113              
14114                              // Show Save/Cancel buttons
14115                              if(!oColumn.editorOptions || !oColumn.editorOptions.disableBtns) {
14116                                  this.showCellEditorBtns(elContainer);
14117                              }
14118              
14119                              oCellEditor.isActive = true;
14120              
14121                              //TODO: verify which args to pass
14122                              this.fireEvent("editorShowEvent", {editor:oCellEditor});
14123                              YAHOO.log("Cell Editor shown for " + elCell, "info", this.toString());
14124                              return;
14125                          }
14126                      }
14127  
14128  
14129  
14130              
14131              }
14132          }
14133      }
14134  },
14135  
14136  /**
14137   * Backward compatibility.
14138   *
14139   * @method _initCellEditorEl
14140   * @private
14141   * @deprecated Use BaseCellEditor class.
14142   */
14143  _initCellEditorEl : function() {
14144      // Attach Cell Editor container element as first child of body
14145      var elCellEditor = document.createElement("div");
14146      elCellEditor.id = this._sId + "-celleditor";
14147      elCellEditor.style.display = "none";
14148      elCellEditor.tabIndex = 0;
14149      Dom.addClass(elCellEditor, DT.CLASS_EDITOR);
14150      var elFirstChild = Dom.getFirstChild(document.body);
14151      if(elFirstChild) {
14152          elCellEditor = Dom.insertBefore(elCellEditor, elFirstChild);
14153      }
14154      else {
14155          elCellEditor = document.body.appendChild(elCellEditor);
14156      }
14157      
14158      // Internal tracker of Cell Editor values
14159      var oCellEditor = {};
14160      oCellEditor.container = elCellEditor;
14161      oCellEditor.value = null;
14162      oCellEditor.isActive = false;
14163      this._oCellEditor = oCellEditor;
14164  },
14165  
14166  /**
14167   * Overridable abstract method to customize CellEditor before showing.
14168   *
14169   * @method doBeforeShowCellEditor
14170   * @param oCellEditor {YAHOO.widget.CellEditor} The CellEditor instance.
14171   * @return {Boolean} Return true to continue showing CellEditor.
14172   */
14173  doBeforeShowCellEditor : function(oCellEditor) {
14174      return true;
14175  },
14176  
14177  /**
14178   * Saves active CellEditor input to Record and upates DOM UI.
14179   *
14180   * @method saveCellEditor
14181   */
14182  saveCellEditor : function() {
14183      if(this._oCellEditor) {
14184          if(this._oCellEditor.save) {
14185              this._oCellEditor.save();
14186          }
14187          // Backward compatibility
14188          else if(this._oCellEditor.isActive) {
14189              var newData = this._oCellEditor.value;
14190              // Copy the data to pass to the event
14191              //var oldData = YAHOO.widget.DataTable._cloneObject(this._oCellEditor.record.getData(this._oCellEditor.column.key));
14192              var oldData = this._oCellEditor.record.getData(this._oCellEditor.column.key);
14193      
14194              // Validate input data
14195              if(this._oCellEditor.validator) {
14196                  newData = this._oCellEditor.value = this._oCellEditor.validator.call(this, newData, oldData, this._oCellEditor);
14197                  if(newData === null ) {
14198                      this.resetCellEditor();
14199                      this.fireEvent("editorRevertEvent",
14200                              {editor:this._oCellEditor, oldData:oldData, newData:newData});
14201                      YAHOO.log("Could not save Cell Editor input due to invalid data " +
14202                              lang.dump(newData), "warn", this.toString());
14203                      return;
14204                  }
14205              }
14206              // Update the Record
14207              this._oRecordSet.updateRecordValue(this._oCellEditor.record, this._oCellEditor.column.key, this._oCellEditor.value);
14208              // Update the UI
14209              this.formatCell(this._oCellEditor.cell.firstChild, this._oCellEditor.record, this._oCellEditor.column);
14210              
14211              // Bug fix 1764044
14212              this._oChainRender.add({
14213                  method: function() {
14214                      this.validateColumnWidths();
14215                  },
14216                  scope: this
14217              });
14218              this._oChainRender.run();
14219              // Clear out the Cell Editor
14220              this.resetCellEditor();
14221      
14222              this.fireEvent("editorSaveEvent",
14223                      {editor:this._oCellEditor, oldData:oldData, newData:newData});
14224              YAHOO.log("Cell Editor input saved", "info", this.toString());
14225          }
14226      }   
14227  },
14228  
14229  /**
14230   * Cancels active CellEditor.
14231   *
14232   * @method cancelCellEditor
14233   */
14234  cancelCellEditor : function() {
14235      if(this._oCellEditor) {
14236          if(this._oCellEditor.cancel) {
14237              this._oCellEditor.cancel();
14238          }
14239          // Backward compatibility
14240          else if(this._oCellEditor.isActive) {
14241              this.resetCellEditor();
14242              //TODO: preserve values for the event?
14243              this.fireEvent("editorCancelEvent", {editor:this._oCellEditor});
14244              YAHOO.log("Cell Editor input canceled", "info", this.toString());
14245          }
14246  
14247          YAHOO.log("CellEditor input canceled", "info", this.toString());
14248      }
14249  },
14250  
14251  /**
14252   * Destroys active CellEditor instance and UI.
14253   *
14254   * @method destroyCellEditor
14255   */
14256  destroyCellEditor : function() {
14257      if(this._oCellEditor) {
14258          this._oCellEditor.destroy();
14259          this._oCellEditor = null;
14260      }   
14261  },
14262  
14263  /**
14264   * Passes through showEvent of the active CellEditor.
14265   *
14266   * @method _onEditorShowEvent
14267   * @param oArgs {Object}  Custom Event args.
14268   * @private 
14269   */
14270  _onEditorShowEvent : function(oArgs) {
14271      this.fireEvent("editorShowEvent", oArgs);
14272  },
14273  
14274  /**
14275   * Passes through keydownEvent of the active CellEditor.
14276   * @param oArgs {Object}  Custom Event args. 
14277   *
14278   * @method _onEditorKeydownEvent
14279   * @private 
14280   */
14281  _onEditorKeydownEvent : function(oArgs) {
14282      this.fireEvent("editorKeydownEvent", oArgs);
14283  },
14284  
14285  /**
14286   * Passes through revertEvent of the active CellEditor.
14287   *
14288   * @method _onEditorRevertEvent
14289   * @param oArgs {Object}  Custom Event args. 
14290   * @private  
14291   */
14292  _onEditorRevertEvent : function(oArgs) {
14293      this.fireEvent("editorRevertEvent", oArgs);
14294  },
14295  
14296  /**
14297   * Passes through saveEvent of the active CellEditor.
14298   *
14299   * @method _onEditorSaveEvent
14300   * @param oArgs {Object}  Custom Event args.  
14301   * @private 
14302   */
14303  _onEditorSaveEvent : function(oArgs) {
14304      this.fireEvent("editorSaveEvent", oArgs);
14305  },
14306  
14307  /**
14308   * Passes through cancelEvent of the active CellEditor.
14309   *
14310   * @method _onEditorCancelEvent
14311   * @param oArgs {Object}  Custom Event args.
14312   * @private   
14313   */
14314  _onEditorCancelEvent : function(oArgs) {
14315      this.fireEvent("editorCancelEvent", oArgs);
14316  },
14317  
14318  /**
14319   * Passes through blurEvent of the active CellEditor.
14320   *
14321   * @method _onEditorBlurEvent
14322   * @param oArgs {Object}  Custom Event args. 
14323   * @private  
14324   */
14325  _onEditorBlurEvent : function(oArgs) {
14326      this.fireEvent("editorBlurEvent", oArgs);
14327  },
14328  
14329  /**
14330   * Passes through blockEvent of the active CellEditor.
14331   *
14332   * @method _onEditorBlockEvent
14333   * @param oArgs {Object}  Custom Event args. 
14334   * @private  
14335   */
14336  _onEditorBlockEvent : function(oArgs) {
14337      this.fireEvent("editorBlockEvent", oArgs);
14338  },
14339  
14340  /**
14341   * Passes through unblockEvent of the active CellEditor.
14342   *
14343   * @method _onEditorUnblockEvent
14344   * @param oArgs {Object}  Custom Event args. 
14345   * @private  
14346   */
14347  _onEditorUnblockEvent : function(oArgs) {
14348      this.fireEvent("editorUnblockEvent", oArgs);
14349  },
14350  
14351  /**
14352   * Public handler of the editorBlurEvent. By default, saves on blur if
14353   * disableBtns is true, otherwise cancels on blur. 
14354   *
14355   * @method onEditorBlurEvent
14356   * @param oArgs {Object}  Custom Event args.  
14357   */
14358  onEditorBlurEvent : function(oArgs) {
14359      if(oArgs.editor.disableBtns) {
14360          // Save on blur
14361          if(oArgs.editor.save) { // Backward incompatible
14362              oArgs.editor.save();
14363          }
14364      }      
14365      else if(oArgs.editor.cancel) { // Backward incompatible
14366          // Cancel on blur
14367          oArgs.editor.cancel();
14368      }      
14369  },
14370  
14371  /**
14372   * Public handler of the editorBlockEvent. By default, disables DataTable UI.
14373   *
14374   * @method onEditorBlockEvent
14375   * @param oArgs {Object}  Custom Event args.  
14376   */
14377  onEditorBlockEvent : function(oArgs) {
14378      this.disable();
14379  },
14380  
14381  /**
14382   * Public handler of the editorUnblockEvent. By default, undisables DataTable UI.
14383   *
14384   * @method onEditorUnblockEvent
14385   * @param oArgs {Object}  Custom Event args.  
14386   */
14387  onEditorUnblockEvent : function(oArgs) {
14388      this.undisable();
14389  },
14390  
14391  
14392  
14393  
14394  
14395  
14396  
14397  
14398  
14399  
14400  
14401  
14402  
14403  
14404  
14405  
14406  
14407  
14408  
14409  
14410  
14411  
14412  
14413  
14414  
14415  
14416  
14417  
14418  
14419  
14420  
14421  
14422  
14423  
14424  
14425  
14426  
14427  
14428  // ABSTRACT METHODS
14429  
14430  /**
14431   * Overridable method gives implementers a hook to access data before
14432   * it gets added to RecordSet and rendered to the TBODY.
14433   *
14434   * @method doBeforeLoadData
14435   * @param sRequest {String} Original request.
14436   * @param oResponse {Object} <a href="http://developer.yahoo.com/yui/datasource/#ds_oParsedResponse">Response object</a>.
14437   * @param oPayload {MIXED} additional arguments
14438   * @return {Boolean} Return true to continue loading data into RecordSet and
14439   * updating DataTable with new Records, false to cancel.
14440   */
14441  doBeforeLoadData : function(sRequest, oResponse, oPayload) {
14442      return true;
14443  },
14444  
14445  
14446  
14447  
14448  
14449  
14450  
14451  
14452  
14453  
14454  
14455  
14456  
14457  
14458  
14459  
14460  
14461  
14462  
14463  
14464  
14465  
14466  
14467  
14468  
14469  
14470  
14471  
14472  
14473  
14474  
14475  
14476  
14477  
14478  
14479  
14480  
14481  
14482  
14483  
14484  
14485  
14486  
14487  
14488  
14489  
14490  
14491  
14492  
14493  
14494  
14495  
14496  
14497  
14498  
14499  
14500  
14501  
14502  
14503  
14504  
14505  
14506  
14507  /////////////////////////////////////////////////////////////////////////////
14508  //
14509  // Public Custom Event Handlers
14510  //
14511  /////////////////////////////////////////////////////////////////////////////
14512  
14513  /**
14514   * Custom event handler to sort Column.
14515   *
14516   * @method onEventSortColumn
14517   * @param oArgs.event {HTMLEvent} Event object.
14518   * @param oArgs.target {HTMLElement} Target element.
14519   */
14520  onEventSortColumn : function(oArgs) {
14521  //TODO: support form elements in sortable columns
14522      var evt = oArgs.event;
14523      var target = oArgs.target;
14524  
14525      var el = this.getThEl(target) || this.getTdEl(target);
14526      if(el) {
14527          var oColumn = this.getColumn(el);
14528          if(oColumn.sortable) {
14529              Ev.stopEvent(evt);
14530              this.sortColumn(oColumn);
14531          }
14532      }
14533      else {
14534          YAHOO.log("Could not find Column for " + target, "warn", this.toString());
14535      }
14536  },
14537  
14538  /**
14539   * Custom event handler to select Column.
14540   *
14541   * @method onEventSelectColumn
14542   * @param oArgs.event {HTMLEvent} Event object.
14543   * @param oArgs.target {HTMLElement} Target element.
14544   */
14545  onEventSelectColumn : function(oArgs) {
14546      this.selectColumn(oArgs.target);
14547  },
14548  
14549  /**
14550   * Custom event handler to highlight Column. Accounts for spurious
14551   * caused-by-child events. 
14552   *
14553   * @method onEventHighlightColumn
14554   * @param oArgs.event {HTMLEvent} Event object.
14555   * @param oArgs.target {HTMLElement} Target element.
14556   */
14557  onEventHighlightColumn : function(oArgs) {
14558      this.highlightColumn(oArgs.target);
14559  },
14560  
14561  /**
14562   * Custom event handler to unhighlight Column. Accounts for spurious
14563   * caused-by-child events. 
14564   *
14565   * @method onEventUnhighlightColumn
14566   * @param oArgs.event {HTMLEvent} Event object.
14567   * @param oArgs.target {HTMLElement} Target element.
14568   */
14569  onEventUnhighlightColumn : function(oArgs) {
14570      this.unhighlightColumn(oArgs.target);
14571  },
14572  
14573  /**
14574   * Custom event handler to manage selection according to desktop paradigm.
14575   *
14576   * @method onEventSelectRow
14577   * @param oArgs.event {HTMLEvent} Event object.
14578   * @param oArgs.target {HTMLElement} Target element.
14579   */
14580  onEventSelectRow : function(oArgs) {
14581      var sMode = this.get("selectionMode");
14582      if(sMode == "single") {
14583          this._handleSingleSelectionByMouse(oArgs);
14584      }
14585      else {
14586          this._handleStandardSelectionByMouse(oArgs);
14587      }
14588  },
14589  
14590  /**
14591   * Custom event handler to select cell.
14592   *
14593   * @method onEventSelectCell
14594   * @param oArgs.event {HTMLEvent} Event object.
14595   * @param oArgs.target {HTMLElement} Target element.
14596   */
14597  onEventSelectCell : function(oArgs) {
14598      var sMode = this.get("selectionMode");
14599      if(sMode == "cellblock") {
14600          this._handleCellBlockSelectionByMouse(oArgs);
14601      }
14602      else if(sMode == "cellrange") {
14603          this._handleCellRangeSelectionByMouse(oArgs);
14604      }
14605      else {
14606          this._handleSingleCellSelectionByMouse(oArgs);
14607      }
14608  },
14609  
14610  /**
14611   * Custom event handler to highlight row. Accounts for spurious
14612   * caused-by-child events. 
14613   *
14614   * @method onEventHighlightRow
14615   * @param oArgs.event {HTMLEvent} Event object.
14616   * @param oArgs.target {HTMLElement} Target element.
14617   */
14618  onEventHighlightRow : function(oArgs) {
14619      this.highlightRow(oArgs.target);
14620  },
14621  
14622  /**
14623   * Custom event handler to unhighlight row. Accounts for spurious
14624   * caused-by-child events. 
14625   *
14626   * @method onEventUnhighlightRow
14627   * @param oArgs.event {HTMLEvent} Event object.
14628   * @param oArgs.target {HTMLElement} Target element.
14629   */
14630  onEventUnhighlightRow : function(oArgs) {
14631      this.unhighlightRow(oArgs.target);
14632  },
14633  
14634  /**
14635   * Custom event handler to highlight cell. Accounts for spurious
14636   * caused-by-child events. 
14637   *
14638   * @method onEventHighlightCell
14639   * @param oArgs.event {HTMLEvent} Event object.
14640   * @param oArgs.target {HTMLElement} Target element.
14641   */
14642  onEventHighlightCell : function(oArgs) {
14643      this.highlightCell(oArgs.target);
14644  },
14645  
14646  /**
14647   * Custom event handler to unhighlight cell. Accounts for spurious
14648   * caused-by-child events. 
14649   *
14650   * @method onEventUnhighlightCell
14651   * @param oArgs.event {HTMLEvent} Event object.
14652   * @param oArgs.target {HTMLElement} Target element.
14653   */
14654  onEventUnhighlightCell : function(oArgs) {
14655      this.unhighlightCell(oArgs.target);
14656  },
14657  
14658  /**
14659   * Custom event handler to format cell.
14660   *
14661   * @method onEventFormatCell
14662   * @param oArgs.event {HTMLEvent} Event object.
14663   * @param oArgs.target {HTMLElement} Target element.
14664   */
14665  onEventFormatCell : function(oArgs) {
14666      var target = oArgs.target;
14667  
14668      var elCell = this.getTdEl(target);
14669      if(elCell) {
14670          var oColumn = this.getColumn(this.getCellIndex(elCell));
14671          this.formatCell(elCell.firstChild, this.getRecord(elCell), oColumn);
14672      }
14673      else {
14674          YAHOO.log("Could not format cell " + target, "warn", this.toString());
14675      }
14676  },
14677  
14678  /**
14679   * Custom event handler to edit cell.
14680   *
14681   * @method onEventShowCellEditor
14682   * @param oArgs.event {HTMLEvent} Event object.
14683   * @param oArgs.target {HTMLElement} Target element.
14684   */
14685  onEventShowCellEditor : function(oArgs) {
14686      if(!this.isDisabled()) {
14687          this.showCellEditor(oArgs.target);
14688      }
14689  },
14690  
14691  /**
14692   * Custom event handler to save active CellEditor input.
14693   *
14694   * @method onEventSaveCellEditor
14695   */
14696  onEventSaveCellEditor : function(oArgs) {
14697      if(this._oCellEditor) {
14698          if(this._oCellEditor.save) {
14699              this._oCellEditor.save();
14700          }
14701          // Backward compatibility
14702          else {
14703              this.saveCellEditor();
14704          }
14705      }
14706  },
14707  
14708  /**
14709   * Custom event handler to cancel active CellEditor.
14710   *
14711   * @method onEventCancelCellEditor
14712   */
14713  onEventCancelCellEditor : function(oArgs) {
14714      if(this._oCellEditor) {
14715          if(this._oCellEditor.cancel) {
14716              this._oCellEditor.cancel();
14717          }
14718          // Backward compatibility
14719          else {
14720              this.cancelCellEditor();
14721          }
14722      }
14723  },
14724  
14725  /**
14726   * Callback function receives data from DataSource and populates an entire
14727   * DataTable with Records and TR elements, clearing previous Records, if any.
14728   *
14729   * @method onDataReturnInitializeTable
14730   * @param sRequest {String} Original request.
14731   * @param oResponse {Object} <a href="http://developer.yahoo.com/yui/datasource/#ds_oParsedResponse">Response object</a>.
14732   * @param oPayload {MIXED} (optional) Additional argument(s)
14733   */
14734  onDataReturnInitializeTable : function(sRequest, oResponse, oPayload) {
14735      if((this instanceof DT) && this._sId) {
14736          this.initializeTable();
14737      
14738          this.onDataReturnSetRows(sRequest,oResponse,oPayload);
14739      }
14740  },
14741  
14742  /**
14743   * Callback function receives reponse from DataSource, replaces all existing
14744   * Records in  RecordSet, updates TR elements with new data, and updates state
14745   * UI for pagination and sorting from payload data, if necessary. 
14746   *  
14747   * @method onDataReturnReplaceRows
14748   * @param oRequest {MIXED} Original generated request.
14749   * @param oResponse {Object} <a href="http://developer.yahoo.com/yui/datasource/#ds_oParsedResponse">Response object</a>.
14750   * @param oPayload {MIXED} (optional) Additional argument(s)
14751   */
14752  onDataReturnReplaceRows : function(oRequest, oResponse, oPayload) {
14753      if((this instanceof DT) && this._sId) {
14754          this.fireEvent("dataReturnEvent", {request:oRequest,response:oResponse,payload:oPayload});
14755      
14756          // Pass data through abstract method for any transformations
14757          var ok    = this.doBeforeLoadData(oRequest, oResponse, oPayload),
14758              pag   = this.get('paginator'),
14759              index = 0;
14760      
14761          // Data ok to set
14762          if(ok && oResponse && !oResponse.error && lang.isArray(oResponse.results)) {
14763              // Update Records
14764              this._oRecordSet.reset();
14765      
14766              if (this.get('dynamicData')) {
14767                  if (oPayload && oPayload.pagination &&
14768                      lang.isNumber(oPayload.pagination.recordOffset)) {
14769                      index = oPayload.pagination.recordOffset;
14770                  } else if (pag) {
14771                      index = pag.getStartIndex();
14772                  }
14773              }
14774      
14775              this._oRecordSet.setRecords(oResponse.results, index | 0);
14776              
14777              // Update state
14778              this._handleDataReturnPayload(oRequest, oResponse, oPayload);
14779              
14780              // Update UI
14781              this.render();    
14782          }
14783          // Error
14784          else if(ok && oResponse.error) {
14785              this.showTableMessage(this.get("MSG_ERROR"), DT.CLASS_ERROR);
14786          }
14787      }
14788  },
14789  
14790  /**
14791   * Callback function receives data from DataSource and appends to an existing
14792   * DataTable new Records and, if applicable, creates or updates
14793   * corresponding TR elements.
14794   *
14795   * @method onDataReturnAppendRows
14796   * @param sRequest {String} Original request.
14797   * @param oResponse {Object} <a href="http://developer.yahoo.com/yui/datasource/#ds_oParsedResponse">Response object</a>.
14798   * @param oPayload {MIXED} (optional) Additional argument(s)
14799   */
14800  onDataReturnAppendRows : function(sRequest, oResponse, oPayload) {
14801      if((this instanceof DT) && this._sId) {
14802          this.fireEvent("dataReturnEvent", {request:sRequest,response:oResponse,payload:oPayload});
14803      
14804          // Pass data through abstract method for any transformations
14805          var ok = this.doBeforeLoadData(sRequest, oResponse, oPayload);
14806      
14807          // Data ok to append
14808          if(ok && oResponse && !oResponse.error && lang.isArray(oResponse.results)) {        
14809              // Append rows
14810              this.addRows(oResponse.results);
14811      
14812              // Update state
14813              this._handleDataReturnPayload(sRequest, oResponse, oPayload);
14814          }
14815          // Error
14816          else if(ok && oResponse.error) {
14817              this.showTableMessage(this.get("MSG_ERROR"), DT.CLASS_ERROR);
14818          }
14819      }
14820  },
14821  
14822  /**
14823   * Callback function receives data from DataSource and inserts new records
14824   * starting at the index specified in oPayload.insertIndex. The value for
14825   * oPayload.insertIndex can be populated when sending the request to the DataSource,
14826   * or by accessing oPayload.insertIndex with the doBeforeLoadData() method at runtime.
14827   * If applicable, creates or updates corresponding TR elements.
14828   *
14829   * @method onDataReturnInsertRows
14830   * @param sRequest {String} Original request.
14831   * @param oResponse {Object} <a href="http://developer.yahoo.com/yui/datasource/#ds_oParsedResponse">Response object</a>.
14832   * @param oPayload {MIXED} Argument payload, looks in oPayload.insertIndex.
14833   */
14834  onDataReturnInsertRows : function(sRequest, oResponse, oPayload) {
14835      if((this instanceof DT) && this._sId) {
14836          this.fireEvent("dataReturnEvent", {request:sRequest,response:oResponse,payload:oPayload});
14837      
14838          // Pass data through abstract method for any transformations
14839          var ok = this.doBeforeLoadData(sRequest, oResponse, oPayload);
14840      
14841          // Data ok to append
14842          if(ok && oResponse && !oResponse.error && lang.isArray(oResponse.results)) {
14843              // Insert rows
14844              this.addRows(oResponse.results, (oPayload ? oPayload.insertIndex : 0));
14845      
14846              // Update state
14847              this._handleDataReturnPayload(sRequest, oResponse, oPayload);
14848          }
14849          // Error
14850          else if(ok && oResponse.error) {
14851              this.showTableMessage(this.get("MSG_ERROR"), DT.CLASS_ERROR);
14852          }
14853      }
14854  },
14855  
14856  /**
14857   * Callback function receives data from DataSource and incrementally updates Records
14858   * starting at the index specified in oPayload.updateIndex. The value for
14859   * oPayload.updateIndex can be populated when sending the request to the DataSource,
14860   * or by accessing oPayload.updateIndex with the doBeforeLoadData() method at runtime.
14861   * If applicable, creates or updates corresponding TR elements.
14862   *
14863   * @method onDataReturnUpdateRows
14864   * @param sRequest {String} Original request.
14865   * @param oResponse {Object} <a href="http://developer.yahoo.com/yui/datasource/#ds_oParsedResponse">Response object</a>.
14866   * @param oPayload {MIXED} Argument payload, looks in oPayload.updateIndex.
14867   */
14868  onDataReturnUpdateRows : function(sRequest, oResponse, oPayload) {
14869      if((this instanceof DT) && this._sId) {
14870          this.fireEvent("dataReturnEvent", {request:sRequest,response:oResponse,payload:oPayload});
14871      
14872          // Pass data through abstract method for any transformations
14873          var ok = this.doBeforeLoadData(sRequest, oResponse, oPayload);
14874      
14875          // Data ok to append
14876          if(ok && oResponse && !oResponse.error && lang.isArray(oResponse.results)) {
14877              // Insert rows
14878              this.updateRows((oPayload ? oPayload.updateIndex : 0), oResponse.results);
14879      
14880              // Update state
14881              this._handleDataReturnPayload(sRequest, oResponse, oPayload);
14882          }
14883          // Error
14884          else if(ok && oResponse.error) {
14885              this.showTableMessage(this.get("MSG_ERROR"), DT.CLASS_ERROR);
14886          }
14887      }
14888  },
14889  
14890  /**
14891   * Callback function receives reponse from DataSource and populates the
14892   * RecordSet with the results.
14893   *  
14894   * @method onDataReturnSetRows
14895   * @param oRequest {MIXED} Original generated request.
14896   * @param oResponse {Object} <a href="http://developer.yahoo.com/yui/datasource/#ds_oParsedResponse">Response object</a>.
14897   * @param oPayload {MIXED} (optional) Additional argument(s)
14898   */
14899  onDataReturnSetRows : function(oRequest, oResponse, oPayload) {
14900      if((this instanceof DT) && this._sId) {
14901          this.fireEvent("dataReturnEvent", {request:oRequest,response:oResponse,payload:oPayload});
14902      
14903          // Pass data through abstract method for any transformations
14904          var ok    = this.doBeforeLoadData(oRequest, oResponse, oPayload),
14905              pag   = this.get('paginator'),
14906              index = 0;
14907      
14908          // Data ok to set
14909          if(ok && oResponse && !oResponse.error && lang.isArray(oResponse.results)) {
14910              // Update Records
14911              if (this.get('dynamicData')) {
14912                  if (oPayload && oPayload.pagination &&
14913                      lang.isNumber(oPayload.pagination.recordOffset)) {
14914                      index = oPayload.pagination.recordOffset;
14915                  } else if (pag) {
14916                      index = pag.getStartIndex();
14917                  }
14918                  
14919                  this._oRecordSet.reset(); // Bug 2290604: dyanmic data shouldn't keep accumulating by default
14920              }
14921      
14922              this._oRecordSet.setRecords(oResponse.results, index | 0);
14923      
14924              // Update state
14925              this._handleDataReturnPayload(oRequest, oResponse, oPayload);
14926              
14927              // Update UI
14928              this.render();
14929          }
14930          // Error
14931          else if(ok && oResponse.error) {
14932              this.showTableMessage(this.get("MSG_ERROR"), DT.CLASS_ERROR);
14933          }
14934      }
14935      else {
14936          YAHOO.log("Instance destroyed before data returned.","info",this.toString());
14937      }
14938  },
14939  
14940  /**
14941   * Hook to update oPayload before consumption.
14942   *  
14943   * @method handleDataReturnPayload
14944   * @param oRequest {MIXED} Original generated request.
14945   * @param oResponse {Object} <a href="http://developer.yahoo.com/yui/datasource/#ds_oParsedResponse">Response object</a>.
14946   * @param oPayload {MIXED} State values.
14947   * @return oPayload {MIXED} State values.
14948   */
14949  handleDataReturnPayload : function (oRequest, oResponse, oPayload) {
14950      return oPayload || {};
14951  },
14952  
14953  /**
14954   * Updates the DataTable with state data sent in an onDataReturn* payload.
14955   *  
14956   * @method _handleDataReturnPayload
14957   * @param oRequest {MIXED} Original generated request.
14958   * @param oResponse {Object} <a href="http://developer.yahoo.com/yui/datasource/#ds_oParsedResponse">Response object</a>.
14959   * @param oPayload {MIXED} State values
14960   * @private
14961   */
14962  _handleDataReturnPayload : function (oRequest, oResponse, oPayload) {
14963      oPayload = this.handleDataReturnPayload(oRequest, oResponse, oPayload);
14964      if(oPayload) {
14965          // Update pagination
14966          var oPaginator = this.get('paginator');
14967          if (oPaginator) {
14968              // Update totalRecords
14969              if(this.get("dynamicData")) {
14970                  if (widget.Paginator.isNumeric(oPayload.totalRecords)) {
14971                      oPaginator.set('totalRecords',oPayload.totalRecords);
14972                  }
14973              }
14974              else {
14975                  oPaginator.set('totalRecords',this._oRecordSet.getLength());
14976              }
14977              // Update other paginator values
14978              if (lang.isObject(oPayload.pagination)) {
14979                  oPaginator.set('rowsPerPage',oPayload.pagination.rowsPerPage);
14980                  oPaginator.set('recordOffset',oPayload.pagination.recordOffset);
14981              }
14982          }
14983  
14984          // Update sorting
14985          if (oPayload.sortedBy) {
14986              // Set the sorting values in preparation for refresh
14987              this.set('sortedBy', oPayload.sortedBy);
14988          }
14989          // Backwards compatibility for sorting
14990          else if (oPayload.sorting) {
14991              // Set the sorting values in preparation for refresh
14992              this.set('sortedBy', oPayload.sorting);
14993          }
14994      }
14995  },
14996  
14997  
14998  
14999  
15000  
15001  
15002  
15003  
15004  
15005  
15006  
15007  
15008  
15009  
15010  
15011  
15012  
15013  
15014  
15015  
15016  
15017  
15018  
15019  
15020  
15021  
15022  
15023  
15024  
15025  
15026  
15027  
15028  
15029      /////////////////////////////////////////////////////////////////////////////
15030      //
15031      // Custom Events
15032      //
15033      /////////////////////////////////////////////////////////////////////////////
15034  
15035      /**
15036       * Fired when the DataTable's rows are rendered from an initialized state.
15037       *
15038       * @event initEvent
15039       */
15040  
15041      /**
15042       * Fired before the DataTable's DOM is rendered or modified.
15043       *
15044       * @event beforeRenderEvent
15045       */
15046  
15047      /**
15048       * Fired when the DataTable's DOM is rendered or modified.
15049       *
15050       * @event renderEvent
15051       */
15052  
15053      /**
15054       * Fired when the DataTable's post-render routine is complete, including
15055       * Column width validations.
15056       *
15057       * @event postRenderEvent
15058       */
15059  
15060      /**
15061       * Fired when the DataTable is disabled.
15062       *
15063       * @event disableEvent
15064       */
15065  
15066      /**
15067       * Fired when the DataTable is undisabled.
15068       *
15069       * @event undisableEvent
15070       */
15071  
15072      /**
15073       * Fired when data is returned from DataSource but before it is consumed by
15074       * DataTable.
15075       *
15076       * @event dataReturnEvent
15077       * @param oArgs.request {String} Original request.
15078       * @param oArgs.response {Object} Response object.
15079       */
15080  
15081      /**
15082       * Fired when the DataTable has a focus event.
15083       *
15084       * @event tableFocusEvent
15085       */
15086  
15087      /**
15088       * Fired when the DataTable THEAD element has a focus event.
15089       *
15090       * @event theadFocusEvent
15091       */
15092  
15093      /**
15094       * Fired when the DataTable TBODY element has a focus event.
15095       *
15096       * @event tbodyFocusEvent
15097       */
15098  
15099      /**
15100       * Fired when the DataTable has a blur event.
15101       *
15102       * @event tableBlurEvent
15103       */
15104  
15105      /*TODO implement theadBlurEvent
15106       * Fired when the DataTable THEAD element has a blur event.
15107       *
15108       * @event theadBlurEvent
15109       */
15110  
15111      /*TODO: implement tbodyBlurEvent
15112       * Fired when the DataTable TBODY element has a blur event.
15113       *
15114       * @event tbodyBlurEvent
15115       */
15116  
15117      /**
15118       * Fired when the DataTable has a key event.
15119       *
15120       * @event tableKeyEvent
15121       * @param oArgs.event {HTMLEvent} The event object.
15122       * @param oArgs.target {HTMLElement} The DataTable's TABLE element.
15123       */
15124  
15125      /**
15126       * Fired when the DataTable THEAD element has a key event.
15127       *
15128       * @event theadKeyEvent
15129       * @param oArgs.event {HTMLEvent} The event object.
15130       * @param oArgs.target {HTMLElement} The DataTable's TABLE element.
15131       */
15132  
15133      /**
15134       * Fired when the DataTable TBODY element has a key event.
15135       *
15136       * @event tbodyKeyEvent
15137       * @param oArgs.event {HTMLEvent} The event object.
15138       * @param oArgs.target {HTMLElement} The DataTable's TABLE element.
15139       */
15140  
15141      /**
15142       * Fired when the DataTable has a mouseover.
15143       *
15144       * @event tableMouseoverEvent
15145       * @param oArgs.event {HTMLEvent} The event object.
15146       * @param oArgs.target {HTMLElement} The DataTable's TABLE element.
15147       *
15148       */
15149  
15150      /**
15151       * Fired when the DataTable has a mouseout.
15152       *
15153       * @event tableMouseoutEvent
15154       * @param oArgs.event {HTMLEvent} The event object.
15155       * @param oArgs.target {HTMLElement} The DataTable's TABLE element.
15156       *
15157       */
15158  
15159      /**
15160       * Fired when the DataTable has a mousedown.
15161       *
15162       * @event tableMousedownEvent
15163       * @param oArgs.event {HTMLEvent} The event object.
15164       * @param oArgs.target {HTMLElement} The DataTable's TABLE element.
15165       *
15166       */
15167  
15168      /**
15169       * Fired when the DataTable has a mouseup.
15170       *
15171       * @event tableMouseupEvent
15172       * @param oArgs.event {HTMLEvent} The event object.
15173       * @param oArgs.target {HTMLElement} The DataTable's TABLE element.
15174       *
15175       */
15176  
15177      /**
15178       * Fired when the DataTable has a click.
15179       *
15180       * @event tableClickEvent
15181       * @param oArgs.event {HTMLEvent} The event object.
15182       * @param oArgs.target {HTMLElement} The DataTable's TABLE element.
15183       *
15184       */
15185  
15186      /**
15187       * Fired when the DataTable has a dblclick.
15188       *
15189       * @event tableDblclickEvent
15190       * @param oArgs.event {HTMLEvent} The event object.
15191       * @param oArgs.target {HTMLElement} The DataTable's TABLE element.
15192       *
15193       */
15194  
15195      /**
15196       * Fired when a message is shown in the DataTable's message element.
15197       *
15198       * @event tableMsgShowEvent
15199       * @param oArgs.html {HTML} The HTML displayed.
15200       * @param oArgs.className {String} The className assigned.
15201       *
15202       */
15203  
15204      /**
15205       * Fired when the DataTable's message element is hidden.
15206       *
15207       * @event tableMsgHideEvent
15208       */
15209  
15210      /**
15211       * Fired when a THEAD row has a mouseover.
15212       *
15213       * @event theadRowMouseoverEvent
15214       * @param oArgs.event {HTMLEvent} The event object.
15215       * @param oArgs.target {HTMLElement} The TR element.
15216       */
15217  
15218      /**
15219       * Fired when a THEAD row has a mouseout.
15220       *
15221       * @event theadRowMouseoutEvent
15222       * @param oArgs.event {HTMLEvent} The event object.
15223       * @param oArgs.target {HTMLElement} The TR element.
15224       */
15225  
15226      /**
15227       * Fired when a THEAD row has a mousedown.
15228       *
15229       * @event theadRowMousedownEvent
15230       * @param oArgs.event {HTMLEvent} The event object.
15231       * @param oArgs.target {HTMLElement} The TR element.
15232       */
15233  
15234      /**
15235       * Fired when a THEAD row has a mouseup.
15236       *
15237       * @event theadRowMouseupEvent
15238       * @param oArgs.event {HTMLEvent} The event object.
15239       * @param oArgs.target {HTMLElement} The TR element.
15240       */
15241  
15242      /**
15243       * Fired when a THEAD row has a click.
15244       *
15245       * @event theadRowClickEvent
15246       * @param oArgs.event {HTMLEvent} The event object.
15247       * @param oArgs.target {HTMLElement} The TR element.
15248       */
15249  
15250      /**
15251       * Fired when a THEAD row has a dblclick.
15252       *
15253       * @event theadRowDblclickEvent
15254       * @param oArgs.event {HTMLEvent} The event object.
15255       * @param oArgs.target {HTMLElement} The TR element.
15256       */
15257  
15258      /**
15259       * Fired when a THEAD cell has a mouseover.
15260       *
15261       * @event theadCellMouseoverEvent
15262       * @param oArgs.event {HTMLEvent} The event object.
15263       * @param oArgs.target {HTMLElement} The TH element.
15264       *
15265       */
15266  
15267      /**
15268       * Fired when a THEAD cell has a mouseout.
15269       *
15270       * @event theadCellMouseoutEvent
15271       * @param oArgs.event {HTMLEvent} The event object.
15272       * @param oArgs.target {HTMLElement} The TH element.
15273       *
15274       */
15275  
15276      /**
15277       * Fired when a THEAD cell has a mousedown.
15278       *
15279       * @event theadCellMousedownEvent
15280       * @param oArgs.event {HTMLEvent} The event object.
15281       * @param oArgs.target {HTMLElement} The TH element.
15282       */
15283  
15284      /**
15285       * Fired when a THEAD cell has a mouseup.
15286       *
15287       * @event theadCellMouseupEvent
15288       * @param oArgs.event {HTMLEvent} The event object.
15289       * @param oArgs.target {HTMLElement} The TH element.
15290       */
15291  
15292      /**
15293       * Fired when a THEAD cell has a click.
15294       *
15295       * @event theadCellClickEvent
15296       * @param oArgs.event {HTMLEvent} The event object.
15297       * @param oArgs.target {HTMLElement} The TH element.
15298       */
15299  
15300      /**
15301       * Fired when a THEAD cell has a dblclick.
15302       *
15303       * @event theadCellDblclickEvent
15304       * @param oArgs.event {HTMLEvent} The event object.
15305       * @param oArgs.target {HTMLElement} The TH element.
15306       */
15307  
15308      /**
15309       * Fired when a THEAD label has a mouseover.
15310       *
15311       * @event theadLabelMouseoverEvent
15312       * @param oArgs.event {HTMLEvent} The event object.
15313       * @param oArgs.target {HTMLElement} The SPAN element.
15314       *
15315       */
15316  
15317      /**
15318       * Fired when a THEAD label has a mouseout.
15319       *
15320       * @event theadLabelMouseoutEvent
15321       * @param oArgs.event {HTMLEvent} The event object.
15322       * @param oArgs.target {HTMLElement} The SPAN element.
15323       *
15324       */
15325  
15326      /**
15327       * Fired when a THEAD label has a mousedown.
15328       *
15329       * @event theadLabelMousedownEvent
15330       * @param oArgs.event {HTMLEvent} The event object.
15331       * @param oArgs.target {HTMLElement} The SPAN element.
15332       */
15333  
15334      /**
15335       * Fired when a THEAD label has a mouseup.
15336       *
15337       * @event theadLabelMouseupEvent
15338       * @param oArgs.event {HTMLEvent} The event object.
15339       * @param oArgs.target {HTMLElement} The SPAN element.
15340       */
15341  
15342      /**
15343       * Fired when a THEAD label has a click.
15344       *
15345       * @event theadLabelClickEvent
15346       * @param oArgs.event {HTMLEvent} The event object.
15347       * @param oArgs.target {HTMLElement} The SPAN element.
15348       */
15349  
15350      /**
15351       * Fired when a THEAD label has a dblclick.
15352       *
15353       * @event theadLabelDblclickEvent
15354       * @param oArgs.event {HTMLEvent} The event object.
15355       * @param oArgs.target {HTMLElement} The SPAN element.
15356       */
15357  
15358      /**
15359       * Fired when a column is sorted.
15360       *
15361       * @event columnSortEvent
15362       * @param oArgs.column {YAHOO.widget.Column} The Column instance.
15363       * @param oArgs.dir {String} Sort direction: YAHOO.widget.DataTable.CLASS_ASC
15364       * or YAHOO.widget.DataTable.CLASS_DESC.
15365       */
15366  
15367      /**
15368       * Fired when a column width is set.
15369       *
15370       * @event columnSetWidthEvent
15371       * @param oArgs.column {YAHOO.widget.Column} The Column instance.
15372       * @param oArgs.width {Number} The width in pixels.
15373       */
15374  
15375      /**
15376       * Fired when a column width is unset.
15377       *
15378       * @event columnUnsetWidthEvent
15379       * @param oArgs.column {YAHOO.widget.Column} The Column instance.
15380       */
15381  
15382      /**
15383       * Fired when a column is drag-resized.
15384       *
15385       * @event columnResizeEvent
15386       * @param oArgs.column {YAHOO.widget.Column} The Column instance.
15387       * @param oArgs.target {HTMLElement} The TH element.
15388       * @param oArgs.width {Number} Width in pixels.     
15389       */
15390  
15391      /**
15392       * Fired when a Column is moved to a new index.
15393       *
15394       * @event columnReorderEvent
15395       * @param oArgs.column {YAHOO.widget.Column} The Column instance.
15396       * @param oArgs.oldIndex {Number} The previous tree index position.
15397       */
15398  
15399      /**
15400       * Fired when a column is hidden.
15401       *
15402       * @event columnHideEvent
15403       * @param oArgs.column {YAHOO.widget.Column} The Column instance.
15404       */
15405  
15406      /**
15407       * Fired when a column is shown.
15408       *
15409       * @event columnShowEvent
15410       * @param oArgs.column {YAHOO.widget.Column} The Column instance.
15411       */
15412  
15413      /**
15414       * Fired when a column is selected.
15415       *
15416       * @event columnSelectEvent
15417       * @param oArgs.column {YAHOO.widget.Column} The Column instance.
15418       */
15419  
15420      /**
15421       * Fired when a column is unselected.
15422       *
15423       * @event columnUnselectEvent
15424       * @param oArgs.column {YAHOO.widget.Column} The Column instance.
15425       */
15426      /**
15427       * Fired when a column is removed.
15428       *
15429       * @event columnRemoveEvent
15430       * @param oArgs.column {YAHOO.widget.Column} The Column instance.
15431       */
15432  
15433      /**
15434       * Fired when a column is inserted.
15435       *
15436       * @event columnInsertEvent
15437       * @param oArgs.column {YAHOO.widget.Column} The Column instance.
15438       * @param oArgs.index {Number} The index position.
15439       */
15440  
15441      /**
15442       * Fired when a column is highlighted.
15443       *
15444       * @event columnHighlightEvent
15445       * @param oArgs.column {YAHOO.widget.Column} The highlighted Column.
15446       */
15447  
15448      /**
15449       * Fired when a column is unhighlighted.
15450       *
15451       * @event columnUnhighlightEvent
15452       * @param oArgs.column {YAHOO.widget.Column} The unhighlighted Column.
15453       */
15454  
15455  
15456      /**
15457       * Fired when a row has a mouseover.
15458       *
15459       * @event rowMouseoverEvent
15460       * @param oArgs.event {HTMLEvent} The event object.
15461       * @param oArgs.target {HTMLElement} The TR element.
15462       */
15463  
15464      /**
15465       * Fired when a row has a mouseout.
15466       *
15467       * @event rowMouseoutEvent
15468       * @param oArgs.event {HTMLEvent} The event object.
15469       * @param oArgs.target {HTMLElement} The TR element.
15470       */
15471  
15472      /**
15473       * Fired when a row has a mousedown.
15474       *
15475       * @event rowMousedownEvent
15476       * @param oArgs.event {HTMLEvent} The event object.
15477       * @param oArgs.target {HTMLElement} The TR element.
15478       */
15479  
15480      /**
15481       * Fired when a row has a mouseup.
15482       *
15483       * @event rowMouseupEvent
15484       * @param oArgs.event {HTMLEvent} The event object.
15485       * @param oArgs.target {HTMLElement} The TR element.
15486       */
15487  
15488      /**
15489       * Fired when a row has a click.
15490       *
15491       * @event rowClickEvent
15492       * @param oArgs.event {HTMLEvent} The event object.
15493       * @param oArgs.target {HTMLElement} The TR element.
15494       */
15495  
15496      /**
15497       * Fired when a row has a dblclick.
15498       *
15499       * @event rowDblclickEvent
15500       * @param oArgs.event {HTMLEvent} The event object.
15501       * @param oArgs.target {HTMLElement} The TR element.
15502       */
15503  
15504      /**
15505       * Fired when a row is added.
15506       *
15507       * @event rowAddEvent
15508       * @param oArgs.record {YAHOO.widget.Record} The added Record.
15509       */
15510       
15511      /**
15512       * Fired when rows are added.
15513       *
15514       * @event rowsAddEvent
15515       * @param oArgs.record {YAHOO.widget.Record[]} The added Records.
15516       */
15517  
15518      /**
15519       * Fired when a row is updated.
15520       *
15521       * @event rowUpdateEvent
15522       * @param oArgs.record {YAHOO.widget.Record} The updated Record.
15523       * @param oArgs.oldData {Object} Object literal of the old data.
15524       */
15525  
15526      /**
15527       * Fired when a row is deleted.
15528       *
15529       * @event rowDeleteEvent
15530       * @param oArgs.oldData {Object} Object literal of the deleted data.
15531       * @param oArgs.recordIndex {Number} Index of the deleted Record.
15532       * @param oArgs.trElIndex {Number} Index of the deleted TR element, if on current page.
15533       */
15534       
15535      /**
15536       * Fired when rows are deleted.
15537       *
15538       * @event rowsDeleteEvent
15539       * @param oArgs.oldData {Object[]} Array of object literals of the deleted data.
15540       * @param oArgs.recordIndex {Number} Index of the first deleted Record.
15541       * @param oArgs.count {Number} Number of deleted Records.
15542       */
15543  
15544      /**
15545       * Fired when a row is selected.
15546       *
15547       * @event rowSelectEvent
15548       * @param oArgs.el {HTMLElement} The selected TR element, if applicable.
15549       * @param oArgs.record {YAHOO.widget.Record} The selected Record.
15550       */
15551  
15552      /**
15553       * Fired when a row is unselected.
15554       *
15555       * @event rowUnselectEvent
15556       * @param oArgs.el {HTMLElement} The unselected TR element, if applicable.
15557       * @param oArgs.record {YAHOO.widget.Record} The unselected Record.
15558       */
15559  
15560      /**
15561       * Fired when all row selections are cleared.
15562       *
15563       * @event unselectAllRowsEvent
15564       */
15565  
15566      /**
15567       * Fired when a row is highlighted.
15568       *
15569       * @event rowHighlightEvent
15570       * @param oArgs.el {HTMLElement} The highlighted TR element.
15571       * @param oArgs.record {YAHOO.widget.Record} The highlighted Record.
15572       */
15573  
15574      /**
15575       * Fired when a row is unhighlighted.
15576       *
15577       * @event rowUnhighlightEvent
15578       * @param oArgs.el {HTMLElement} The highlighted TR element.
15579       * @param oArgs.record {YAHOO.widget.Record} The highlighted Record.
15580       */
15581  
15582      /**
15583       * Fired when a cell is updated.
15584       *
15585       * @event cellUpdateEvent
15586       * @param oArgs.record {YAHOO.widget.Record} The updated Record.
15587       * @param oArgs.column {YAHOO.widget.Column} The updated Column.
15588       * @param oArgs.oldData {Object} Original data value of the updated cell.
15589       */
15590  
15591      /**
15592       * Fired when a cell has a mouseover.
15593       *
15594       * @event cellMouseoverEvent
15595       * @param oArgs.event {HTMLEvent} The event object.
15596       * @param oArgs.target {HTMLElement} The TD element.
15597       */
15598  
15599      /**
15600       * Fired when a cell has a mouseout.
15601       *
15602       * @event cellMouseoutEvent
15603       * @param oArgs.event {HTMLEvent} The event object.
15604       * @param oArgs.target {HTMLElement} The TD element.
15605       */
15606  
15607      /**
15608       * Fired when a cell has a mousedown.
15609       *
15610       * @event cellMousedownEvent
15611       * @param oArgs.event {HTMLEvent} The event object.
15612       * @param oArgs.target {HTMLElement} The TD element.
15613       */
15614  
15615      /**
15616       * Fired when a cell has a mouseup.
15617       *
15618       * @event cellMouseupEvent
15619       * @param oArgs.event {HTMLEvent} The event object.
15620       * @param oArgs.target {HTMLElement} The TD element.
15621       */
15622  
15623      /**
15624       * Fired when a cell has a click.
15625       *
15626       * @event cellClickEvent
15627       * @param oArgs.event {HTMLEvent} The event object.
15628       * @param oArgs.target {HTMLElement} The TD element.
15629       */
15630  
15631      /**
15632       * Fired when a cell has a dblclick.
15633       *
15634       * @event cellDblclickEvent
15635       * @param oArgs.event {HTMLEvent} The event object.
15636       * @param oArgs.target {HTMLElement} The TD element.
15637       */
15638  
15639      /**
15640       * Fired when a cell is formatted.
15641       *
15642       * @event cellFormatEvent
15643       * @param oArgs.el {HTMLElement} The formatted TD element.
15644       * @param oArgs.record {YAHOO.widget.Record} The associated Record instance.
15645       * @param oArgs.column {YAHOO.widget.Column} The associated Column instance.
15646       * @param oArgs.key {String} (deprecated) The key of the formatted cell.
15647       */
15648  
15649      /**
15650       * Fired when a cell is selected.
15651       *
15652       * @event cellSelectEvent
15653       * @param oArgs.el {HTMLElement} The selected TD element.
15654       * @param oArgs.record {YAHOO.widget.Record} The associated Record instance.
15655       * @param oArgs.column {YAHOO.widget.Column} The associated Column instance.
15656       * @param oArgs.key {String} (deprecated) The key of the selected cell.
15657       */
15658  
15659      /**
15660       * Fired when a cell is unselected.
15661       *
15662       * @event cellUnselectEvent
15663       * @param oArgs.el {HTMLElement} The unselected TD element.
15664       * @param oArgs.record {YAHOO.widget.Record} The associated Record.
15665       * @param oArgs.column {YAHOO.widget.Column} The associated Column instance.
15666       * @param oArgs.key {String} (deprecated) The key of the unselected cell.
15667  
15668       */
15669  
15670      /**
15671       * Fired when a cell is highlighted.
15672       *
15673       * @event cellHighlightEvent
15674       * @param oArgs.el {HTMLElement} The highlighted TD element.
15675       * @param oArgs.record {YAHOO.widget.Record} The associated Record instance.
15676       * @param oArgs.column {YAHOO.widget.Column} The associated Column instance.
15677       * @param oArgs.key {String} (deprecated) The key of the highlighted cell.
15678  
15679       */
15680  
15681      /**
15682       * Fired when a cell is unhighlighted.
15683       *
15684       * @event cellUnhighlightEvent
15685       * @param oArgs.el {HTMLElement} The unhighlighted TD element.
15686       * @param oArgs.record {YAHOO.widget.Record} The associated Record instance.
15687       * @param oArgs.column {YAHOO.widget.Column} The associated Column instance.
15688       * @param oArgs.key {String} (deprecated) The key of the unhighlighted cell.
15689  
15690       */
15691  
15692      /**
15693       * Fired when all cell selections are cleared.
15694       *
15695       * @event unselectAllCellsEvent
15696       */
15697  
15698      /**
15699       * Fired when a CellEditor is shown.
15700       *
15701       * @event editorShowEvent
15702       * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
15703       */
15704  
15705      /**
15706       * Fired when a CellEditor has a keydown.
15707       *
15708       * @event editorKeydownEvent
15709       * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
15710       * @param oArgs.event {HTMLEvent} The event object.
15711       */
15712  
15713      /**
15714       * Fired when a CellEditor input is reverted.
15715       *
15716       * @event editorRevertEvent
15717       * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
15718       * @param oArgs.newData {Object} New data value from form input field.
15719       * @param oArgs.oldData {Object} Old data value.
15720       */
15721  
15722      /**
15723       * Fired when a CellEditor input is saved.
15724       *
15725       * @event editorSaveEvent
15726       * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
15727       * @param oArgs.newData {Object} New data value from form input field.
15728       * @param oArgs.oldData {Object} Old data value.
15729       */
15730  
15731      /**
15732       * Fired when a CellEditor input is canceled.
15733       *
15734       * @event editorCancelEvent
15735       * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
15736       */
15737  
15738      /**
15739       * Fired when a CellEditor has a blur event.
15740       *
15741       * @event editorBlurEvent
15742       * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
15743       */
15744  
15745      /**
15746       * Fired when a CellEditor is blocked.
15747       *
15748       * @event editorBlockEvent
15749       * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
15750       */
15751  
15752      /**
15753       * Fired when a CellEditor is unblocked.
15754       *
15755       * @event editorUnblockEvent
15756       * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
15757       */
15758  
15759  
15760  
15761  
15762  
15763      /**
15764       * Fired when a link is clicked.
15765       *
15766       * @event linkClickEvent
15767       * @param oArgs.event {HTMLEvent} The event object.
15768       * @param oArgs.target {HTMLElement} The A element.
15769       */
15770  
15771      /**
15772       * Fired when a BUTTON element or INPUT element of type "button", "image",
15773       * "submit", "reset" is clicked.
15774       *
15775       * @event buttonClickEvent
15776       * @param oArgs.event {HTMLEvent} The event object.
15777       * @param oArgs.target {HTMLElement} The BUTTON element.
15778       */
15779  
15780      /**
15781       * Fired when a CHECKBOX element is clicked.
15782       *
15783       * @event checkboxClickEvent
15784       * @param oArgs.event {HTMLEvent} The event object.
15785       * @param oArgs.target {HTMLElement} The CHECKBOX element.
15786       */
15787  
15788      /**
15789       * Fired when a SELECT element is changed.
15790       *
15791       * @event dropdownChangeEvent
15792       * @param oArgs.event {HTMLEvent} The event object.
15793       * @param oArgs.target {HTMLElement} The SELECT element.
15794       */
15795  
15796      /**
15797       * Fired when a RADIO element is clicked.
15798       *
15799       * @event radioClickEvent
15800       * @param oArgs.event {HTMLEvent} The event object.
15801       * @param oArgs.target {HTMLElement} The RADIO element.
15802       */
15803  
15804  
15805  
15806  
15807  
15808  
15809  
15810  
15811  
15812  
15813  
15814  
15815  
15816  
15817  
15818  
15819  
15820  
15821  
15822  
15823  
15824  
15825  
15826  
15827  
15828  
15829  /////////////////////////////////////////////////////////////////////////////
15830  //
15831  // Deprecated APIs
15832  //
15833  /////////////////////////////////////////////////////////////////////////////
15834    
15835  /*
15836   * @method showCellEditorBtns
15837   * @deprecated Use CellEditor.renderBtns() 
15838   */
15839  showCellEditorBtns : function(elContainer) {
15840      // Buttons
15841      var elBtnsDiv = elContainer.appendChild(document.createElement("div"));
15842      Dom.addClass(elBtnsDiv, DT.CLASS_BUTTON);
15843  
15844      // Save button
15845      var elSaveBtn = elBtnsDiv.appendChild(document.createElement("button"));
15846      Dom.addClass(elSaveBtn, DT.CLASS_DEFAULT);
15847      elSaveBtn.innerHTML = "OK";
15848      Ev.addListener(elSaveBtn, "click", function(oArgs, oSelf) {
15849          oSelf.onEventSaveCellEditor(oArgs, oSelf);
15850          oSelf.focusTbodyEl();
15851      }, this, true);
15852  
15853      // Cancel button
15854      var elCancelBtn = elBtnsDiv.appendChild(document.createElement("button"));
15855      elCancelBtn.innerHTML = "Cancel";
15856      Ev.addListener(elCancelBtn, "click", function(oArgs, oSelf) {
15857          oSelf.onEventCancelCellEditor(oArgs, oSelf);
15858          oSelf.focusTbodyEl();
15859      }, this, true);
15860  
15861      YAHOO.log("The method showCellEditorBtns() has been deprecated." +
15862              " Please use the CellEditor class.", "warn", this.toString());
15863  },
15864  
15865  /**
15866   * @method resetCellEditor
15867   * @deprecated Use destroyCellEditor 
15868   */
15869  resetCellEditor : function() {
15870      var elContainer = this._oCellEditor.container;
15871      elContainer.style.display = "none";
15872      Ev.purgeElement(elContainer, true);
15873      elContainer.innerHTML = "";
15874      this._oCellEditor.value = null;
15875      this._oCellEditor.isActive = false;
15876  
15877      YAHOO.log("The method resetCellEditor() has been deprecated." +
15878              " Please use the CellEditor class.", "warn", this.toString());
15879  },
15880  
15881  /**
15882   * @event editorUpdateEvent
15883   * @deprecated Use CellEditor class.
15884   */
15885  
15886  /**
15887   * @method getBody
15888   * @deprecated Use getTbodyEl().
15889   */
15890  getBody : function() {
15891      // Backward compatibility
15892      YAHOO.log("The method getBody() has been deprecated" +
15893              " in favor of getTbodyEl()", "warn", this.toString());
15894      return this.getTbodyEl();
15895  },
15896  
15897  /**
15898   * @method getCell
15899   * @deprecated Use getTdEl().
15900   */
15901  getCell : function(index) {
15902      // Backward compatibility
15903      YAHOO.log("The method getCell() has been deprecated" +
15904              " in favor of getTdEl()", "warn", this.toString());
15905      return this.getTdEl(index);
15906  },
15907  
15908  /**
15909   * @method getRow
15910   * @deprecated Use getTrEl().
15911   */
15912  getRow : function(index) {
15913      // Backward compatibility
15914      YAHOO.log("The method getRow() has been deprecated" +
15915              " in favor of getTrEl()", "warn", this.toString());
15916      return this.getTrEl(index);
15917  },
15918  
15919  /**
15920   * @method refreshView
15921   * @deprecated Use render.
15922   */
15923  refreshView : function() {
15924      // Backward compatibility
15925      YAHOO.log("The method refreshView() has been deprecated" +
15926              " in favor of render()", "warn", this.toString());
15927      this.render();
15928  },
15929  
15930  /**
15931   * @method select
15932   * @deprecated Use selectRow.
15933   */
15934  select : function(els) {
15935      // Backward compatibility
15936      YAHOO.log("The method select() has been deprecated" +
15937              " in favor of selectRow()", "warn", this.toString());
15938      if(!lang.isArray(els)) {
15939          els = [els];
15940      }
15941      for(var i=0; i<els.length; i++) {
15942          this.selectRow(els[i]);
15943      }
15944  },
15945  
15946  /**
15947   * @method onEventEditCell
15948   * @deprecated Use onEventShowCellEditor.
15949   */
15950  onEventEditCell : function(oArgs) {
15951      // Backward compatibility
15952      YAHOO.log("The method onEventEditCell() has been deprecated" +
15953          " in favor of onEventShowCellEditor()", "warn", this.toString());
15954      this.onEventShowCellEditor(oArgs);
15955  },
15956  
15957  /**
15958   * @method _syncColWidths
15959   * @deprecated Use validateColumnWidths.
15960   */
15961  _syncColWidths : function() {
15962      // Backward compatibility
15963      YAHOO.log("The method _syncColWidths() has been deprecated" +
15964          " in favor of validateColumnWidths()", "warn", this.toString());
15965      this.validateColumnWidths();
15966  }
15967  
15968  /**
15969   * @event headerRowMouseoverEvent
15970   * @deprecated Use theadRowMouseoverEvent.
15971   */
15972  
15973  /**
15974   * @event headerRowMouseoutEvent
15975   * @deprecated Use theadRowMouseoutEvent.
15976   */
15977  
15978  /**
15979   * @event headerRowMousedownEvent
15980   * @deprecated Use theadRowMousedownEvent.
15981   */
15982  
15983  /**
15984   * @event headerRowClickEvent
15985   * @deprecated Use theadRowClickEvent.
15986   */
15987  
15988  /**
15989   * @event headerRowDblclickEvent
15990   * @deprecated Use theadRowDblclickEvent.
15991   */
15992  
15993  /**
15994   * @event headerCellMouseoverEvent
15995   * @deprecated Use theadCellMouseoverEvent.
15996   */
15997  
15998  /**
15999   * @event headerCellMouseoutEvent
16000   * @deprecated Use theadCellMouseoutEvent.
16001   */
16002  
16003  /**
16004   * @event headerCellMousedownEvent
16005   * @deprecated Use theadCellMousedownEvent.
16006   */
16007  
16008  /**
16009   * @event headerCellClickEvent
16010   * @deprecated Use theadCellClickEvent.
16011   */
16012  
16013  /**
16014   * @event headerCellDblclickEvent
16015   * @deprecated Use theadCellDblclickEvent.
16016   */
16017  
16018  /**
16019   * @event headerLabelMouseoverEvent
16020   * @deprecated Use theadLabelMouseoverEvent.
16021   */
16022  
16023  /**
16024   * @event headerLabelMouseoutEvent
16025   * @deprecated Use theadLabelMouseoutEvent.
16026   */
16027  
16028  /**
16029   * @event headerLabelMousedownEvent
16030   * @deprecated Use theadLabelMousedownEvent.
16031   */
16032  
16033  /**
16034   * @event headerLabelClickEvent
16035   * @deprecated Use theadLabelClickEvent.
16036   */
16037  
16038  /**
16039   * @event headerLabelDbllickEvent
16040   * @deprecated Use theadLabelDblclickEvent.
16041   */
16042  
16043  });
16044  
16045  /**
16046   * Alias for onDataReturnSetRows for backward compatibility
16047   * @method onDataReturnSetRecords
16048   * @deprecated Use onDataReturnSetRows
16049   */
16050  DT.prototype.onDataReturnSetRecords = DT.prototype.onDataReturnSetRows;
16051  
16052  /**
16053   * Alias for onPaginatorChange for backward compatibility
16054   * @method onPaginatorChange
16055   * @deprecated Use onPaginatorChangeRequest
16056   */
16057  DT.prototype.onPaginatorChange = DT.prototype.onPaginatorChangeRequest;
16058  
16059  /////////////////////////////////////////////////////////////////////////////
16060  //
16061  // Deprecated static APIs
16062  //
16063  /////////////////////////////////////////////////////////////////////////////
16064  /**
16065   * @method DataTable.editCheckbox
16066   * @deprecated  Use YAHOO.widget.CheckboxCellEditor.
16067   */
16068  DT.editCheckbox = function() {};
16069  
16070  /**
16071   * @method DataTable.editDate
16072   * @deprecated Use YAHOO.widget.DateCellEditor.
16073   */
16074  DT.editDate = function() {};
16075  
16076  /**
16077   * @method DataTable.editDropdown
16078   * @deprecated Use YAHOO.widget.DropdownCellEditor.
16079   */
16080  DT.editDropdown = function() {};
16081  
16082  /**
16083   * @method DataTable.editRadio
16084   * @deprecated Use YAHOO.widget.RadioCellEditor.
16085   */
16086  DT.editRadio = function() {};
16087  
16088  /**
16089   * @method DataTable.editTextarea
16090   * @deprecated Use YAHOO.widget.TextareaCellEditor
16091   */
16092  DT.editTextarea = function() {};
16093  
16094  /**
16095   * @method DataTable.editTextbox
16096   * @deprecated Use YAHOO.widget.TextboxCellEditor
16097   */
16098  DT.editTextbox= function() {};
16099  
16100  })();
16101  
16102  (function () {
16103  
16104  var lang   = YAHOO.lang,
16105      util   = YAHOO.util,
16106      widget = YAHOO.widget,
16107      ua     = YAHOO.env.ua,
16108      
16109      Dom    = util.Dom,
16110      Ev     = util.Event,
16111      DS     = util.DataSourceBase,
16112      DT     = widget.DataTable,
16113      Pag    = widget.Paginator;
16114      
16115  /**
16116   * The ScrollingDataTable class extends the DataTable class to provide
16117   * functionality for x-scrolling, y-scrolling, and xy-scrolling.
16118   *
16119   * @namespace YAHOO.widget
16120   * @class ScrollingDataTable
16121   * @extends YAHOO.widget.DataTable
16122   * @constructor
16123   * @param elContainer {HTMLElement} Container element for the TABLE.
16124   * @param aColumnDefs {Object[]} Array of object literal Column definitions.
16125   * @param oDataSource {YAHOO.util.DataSource} DataSource instance.
16126   * @param oConfigs {object} (optional) Object literal of configuration values.
16127   */
16128  widget.ScrollingDataTable = function(elContainer,aColumnDefs,oDataSource,oConfigs) {
16129      oConfigs = oConfigs || {};
16130      
16131      // Prevent infinite loop
16132      if(oConfigs.scrollable) {
16133          oConfigs.scrollable = false;
16134      }
16135  
16136      this._init();
16137  
16138      widget.ScrollingDataTable.superclass.constructor.call(this, elContainer,aColumnDefs,oDataSource,oConfigs); 
16139  
16140      // Once per instance
16141      this.subscribe("columnShowEvent", this._onColumnChange);
16142  };
16143  
16144  var SDT = widget.ScrollingDataTable;
16145  
16146  /////////////////////////////////////////////////////////////////////////////
16147  //
16148  // Public constants
16149  //
16150  /////////////////////////////////////////////////////////////////////////////
16151  lang.augmentObject(SDT, {
16152  
16153      /**
16154       * Class name assigned to inner DataTable header container.
16155       *
16156       * @property DataTable.CLASS_HEADER
16157       * @type String
16158       * @static
16159       * @final
16160       * @default "yui-dt-hd"
16161       */
16162      CLASS_HEADER : "yui-dt-hd",
16163      
16164      /**
16165       * Class name assigned to inner DataTable body container.
16166       *
16167       * @property DataTable.CLASS_BODY
16168       * @type String
16169       * @static
16170       * @final
16171       * @default "yui-dt-bd"
16172       */
16173      CLASS_BODY : "yui-dt-bd"
16174  });
16175  
16176  lang.extend(SDT, DT, {
16177  
16178  /**
16179   * Container for fixed header TABLE element.
16180   *
16181   * @property _elHdContainer
16182   * @type HTMLElement
16183   * @private
16184   */
16185  _elHdContainer : null,
16186  
16187  /**
16188   * Fixed header TABLE element.
16189   *
16190   * @property _elHdTable
16191   * @type HTMLElement
16192   * @private
16193   */
16194  _elHdTable : null,
16195  
16196  /**
16197   * Container for scrolling body TABLE element.
16198   *
16199   * @property _elBdContainer
16200   * @type HTMLElement
16201   * @private
16202   */
16203  _elBdContainer : null,
16204  
16205  /**
16206   * Body THEAD element.
16207   *
16208   * @property _elBdThead
16209   * @type HTMLElement
16210   * @private
16211   */
16212  _elBdThead : null,
16213  
16214  /**
16215   * Offscreen container to temporarily clone SDT for auto-width calculation.
16216   *
16217   * @property _elTmpContainer
16218   * @type HTMLElement
16219   * @private
16220   */
16221  _elTmpContainer : null,
16222  
16223  /**
16224   * Offscreen TABLE element for auto-width calculation.
16225   *
16226   * @property _elTmpTable
16227   * @type HTMLElement
16228   * @private
16229   */
16230  _elTmpTable : null,
16231  
16232  /**
16233   * True if x-scrollbar is currently visible.
16234   * @property _bScrollbarX
16235   * @type Boolean
16236   * @private 
16237   */
16238  _bScrollbarX : null,
16239  
16240  
16241  
16242  
16243  
16244  
16245  
16246  
16247  
16248  
16249  
16250  
16251  
16252  
16253  
16254  /////////////////////////////////////////////////////////////////////////////
16255  //
16256  // Superclass methods
16257  //
16258  /////////////////////////////////////////////////////////////////////////////
16259  
16260  /**
16261   * Implementation of Element's abstract method. Sets up config values.
16262   *
16263   * @method initAttributes
16264   * @param oConfigs {Object} (Optional) Object literal definition of configuration values.
16265   * @private
16266   */
16267  
16268  initAttributes : function(oConfigs) {
16269      oConfigs = oConfigs || {};
16270      SDT.superclass.initAttributes.call(this, oConfigs);
16271  
16272      /**
16273      * @attribute width
16274      * @description Table width for scrollable tables (e.g., "40em").
16275      * @type String
16276      */
16277      this.setAttributeConfig("width", {
16278          value: null,
16279          validator: lang.isString,
16280          method: function(oParam) {
16281              if(this._elHdContainer && this._elBdContainer) {
16282                  this._elHdContainer.style.width = oParam;
16283                  this._elBdContainer.style.width = oParam;            
16284                  this._syncScrollX();      
16285                  this._syncScrollOverhang();
16286              }
16287          }
16288      });
16289  
16290      /**
16291      * @attribute height
16292      * @description Table body height for scrollable tables, not including headers (e.g., "40em").
16293      * @type String
16294      */
16295      this.setAttributeConfig("height", {
16296          value: null,
16297          validator: lang.isString,
16298          method: function(oParam) {
16299              if(this._elHdContainer && this._elBdContainer) {
16300                  this._elBdContainer.style.height = oParam;    
16301                  this._syncScrollX();   
16302                  this._syncScrollY();
16303                  this._syncScrollOverhang();
16304              }
16305          }
16306      });
16307  
16308      /**
16309      * @attribute COLOR_COLUMNFILLER
16310      * @description CSS color value assigned to header filler on scrollable tables.  
16311      * @type String
16312      * @default "#F2F2F2"
16313      */
16314      this.setAttributeConfig("COLOR_COLUMNFILLER", {
16315          value: "#F2F2F2",
16316          validator: lang.isString,
16317          method: function(oParam) {
16318              if(this._elHdContainer) {
16319                  this._elHdContainer.style.backgroundColor = oParam;
16320              }
16321          }
16322      });
16323  },
16324  
16325  /**
16326   * Initializes internal variables.
16327   *
16328   * @method _init
16329   * @private
16330   */
16331  _init : function() {
16332      this._elHdContainer = null;
16333      this._elHdTable = null;
16334      this._elBdContainer = null;
16335      this._elBdThead = null;
16336      this._elTmpContainer = null;
16337      this._elTmpTable = null;
16338  },
16339  
16340  /**
16341   * Initializes DOM elements for a ScrollingDataTable, including creation of
16342   * two separate TABLE elements.
16343   *
16344   * @method _initDomElements
16345   * @param elContainer {HTMLElement | String} HTML DIV element by reference or ID. 
16346   * return {Boolean} False in case of error, otherwise true 
16347   * @private
16348   */
16349  _initDomElements : function(elContainer) {
16350      // Outer and inner containers
16351      this._initContainerEl(elContainer);
16352      if(this._elContainer && this._elHdContainer && this._elBdContainer) {
16353          // TABLEs
16354          this._initTableEl();
16355          
16356          if(this._elHdTable && this._elTable) {
16357              // COLGROUPs
16358              ///this._initColgroupEl(this._elHdTable, this._elTable);  
16359              this._initColgroupEl(this._elHdTable);        
16360              
16361              // THEADs
16362              this._initTheadEl(this._elHdTable, this._elTable);
16363              
16364              // Primary TBODY
16365              this._initTbodyEl(this._elTable);
16366              // Message TBODY
16367              this._initMsgTbodyEl(this._elTable);            
16368          }
16369      }
16370      if(!this._elContainer || !this._elTable || !this._elColgroup ||  !this._elThead || !this._elTbody || !this._elMsgTbody ||
16371              !this._elHdTable || !this._elBdThead) {
16372          YAHOO.log("Could not instantiate DataTable due to an invalid DOM elements", "error", this.toString());
16373          return false;
16374      }
16375      else {
16376          return true;
16377      }
16378  },
16379  
16380  /**
16381   * Destroy's the DataTable outer and inner container elements, if available.
16382   *
16383   * @method _destroyContainerEl
16384   * @param elContainer {HTMLElement} Reference to the container element. 
16385   * @private
16386   */
16387  _destroyContainerEl : function(elContainer) {
16388      Dom.removeClass(elContainer, DT.CLASS_SCROLLABLE);
16389      SDT.superclass._destroyContainerEl.call(this, elContainer);
16390      this._elHdContainer = null;
16391      this._elBdContainer = null;
16392  },
16393  
16394  /**
16395   * Initializes the DataTable outer container element and creates inner header
16396   * and body container elements.
16397   *
16398   * @method _initContainerEl
16399   * @param elContainer {HTMLElement | String} HTML DIV element by reference or ID.
16400   * @private
16401   */
16402  _initContainerEl : function(elContainer) {
16403      SDT.superclass._initContainerEl.call(this, elContainer);
16404      
16405      if(this._elContainer) {
16406          elContainer = this._elContainer; // was constructor input, now is DOM ref
16407          Dom.addClass(elContainer, DT.CLASS_SCROLLABLE);
16408          
16409          // Container for header TABLE
16410          var elHdContainer = document.createElement("div");
16411          elHdContainer.style.width = this.get("width") || "";
16412          elHdContainer.style.backgroundColor = this.get("COLOR_COLUMNFILLER");
16413          Dom.addClass(elHdContainer, SDT.CLASS_HEADER);
16414          this._elHdContainer = elHdContainer;
16415          elContainer.appendChild(elHdContainer);
16416      
16417          // Container for body TABLE
16418          var elBdContainer = document.createElement("div");
16419          elBdContainer.style.width = this.get("width") || "";
16420          elBdContainer.style.height = this.get("height") || "";
16421          Dom.addClass(elBdContainer, SDT.CLASS_BODY);
16422          Ev.addListener(elBdContainer, "scroll", this._onScroll, this); // to sync horiz scroll headers
16423          this._elBdContainer = elBdContainer;
16424          elContainer.appendChild(elBdContainer);
16425      }
16426  },
16427  
16428  /**
16429   * Creates HTML markup CAPTION element.
16430   *
16431   * @method _initCaptionEl
16432   * @param sCaption {String} Text for caption.
16433   * @private
16434   */
16435  _initCaptionEl : function(sCaption) {
16436      // Not yet supported
16437      /*if(this._elHdTable && sCaption) {
16438          // Create CAPTION element
16439          if(!this._elCaption) { 
16440              this._elCaption = this._elHdTable.createCaption();
16441          }
16442          // Set CAPTION value
16443          this._elCaption.innerHTML = sCaption;
16444      }
16445      else if(this._elCaption) {
16446          this._elCaption.parentNode.removeChild(this._elCaption);
16447      }*/
16448  },
16449  
16450  /**
16451   * Destroy's the DataTable head TABLE element, if available.
16452   *
16453   * @method _destroyHdTableEl
16454   * @private
16455   */
16456  _destroyHdTableEl : function() {
16457      var elTable = this._elHdTable;
16458      if(elTable) {
16459          Ev.purgeElement(elTable, true);
16460          elTable.parentNode.removeChild(elTable);
16461          
16462          // A little out of place, but where else can we null out these extra elements?
16463          ///this._elBdColgroup = null;
16464          this._elBdThead = null;
16465      }
16466  },
16467  
16468  /**
16469   * Initializes ScrollingDataTable TABLE elements into the two inner containers.
16470   *
16471   * @method _initTableEl
16472   * @private
16473   */
16474  _initTableEl : function() {
16475      // Head TABLE
16476      if(this._elHdContainer) {
16477          this._destroyHdTableEl();
16478      
16479          // Create TABLE
16480          this._elHdTable = this._elHdContainer.appendChild(document.createElement("table"));   
16481  
16482          // Set up mouseover/mouseout events via mouseenter/mouseleave delegation
16483          Ev.delegate(this._elHdTable, "mouseenter", this._onTableMouseover, "thead ."+DT.CLASS_LABEL, this);
16484          Ev.delegate(this._elHdTable, "mouseleave", this._onTableMouseout, "thead ."+DT.CLASS_LABEL, this);
16485      }
16486      // Body TABLE
16487      SDT.superclass._initTableEl.call(this, this._elBdContainer);
16488  },
16489  
16490  /**
16491   * Initializes ScrollingDataTable THEAD elements into the two inner containers.
16492   *
16493   * @method _initTheadEl
16494   * @param elHdTable {HTMLElement} (optional) Fixed header TABLE element reference.
16495   * @param elTable {HTMLElement} (optional) TABLE element reference.
16496   * @private
16497   */
16498  _initTheadEl : function(elHdTable, elTable) {
16499      elHdTable = elHdTable || this._elHdTable;
16500      elTable = elTable || this._elTable;
16501      
16502      // Scrolling body's THEAD
16503      this._initBdTheadEl(elTable);
16504      // Standard fixed head THEAD
16505      SDT.superclass._initTheadEl.call(this, elHdTable);
16506  },
16507  
16508  /**
16509   * SDT changes ID so as not to duplicate the accessibility TH IDs.
16510   *
16511   * @method _initThEl
16512   * @param elTh {HTMLElement} TH element reference.
16513   * @param oColumn {YAHOO.widget.Column} Column object.
16514   * @private
16515   */
16516  _initThEl : function(elTh, oColumn) {
16517      SDT.superclass._initThEl.call(this, elTh, oColumn);
16518      elTh.id = this.getId() +"-fixedth-" + oColumn.getSanitizedKey(); // Needed for getColumn by TH and ColumnDD
16519  },
16520  
16521  /**
16522   * Destroy's the DataTable body THEAD element, if available.
16523   *
16524   * @method _destroyBdTheadEl
16525   * @private
16526   */
16527  _destroyBdTheadEl : function() {
16528      var elBdThead = this._elBdThead;
16529      if(elBdThead) {
16530          var elTable = elBdThead.parentNode;
16531          Ev.purgeElement(elBdThead, true);
16532          elTable.removeChild(elBdThead);
16533          this._elBdThead = null;
16534  
16535          this._destroyColumnHelpers();
16536      }
16537  },
16538  
16539  /**
16540   * Initializes body THEAD element.
16541   *
16542   * @method _initBdTheadEl
16543   * @param elTable {HTMLElement} TABLE element into which to create THEAD.
16544   * @return {HTMLElement} Initialized THEAD element. 
16545   * @private
16546   */
16547  _initBdTheadEl : function(elTable) {
16548      if(elTable) {
16549          // Destroy previous
16550          this._destroyBdTheadEl();
16551  
16552          var elThead = elTable.insertBefore(document.createElement("thead"), elTable.firstChild);
16553          
16554          // Add TRs to the THEAD;
16555          var oColumnSet = this._oColumnSet,
16556              colTree = oColumnSet.tree,
16557              elTh, elTheadTr, oColumn, i, j, k, len;
16558  
16559          for(i=0, k=colTree.length; i<k; i++) {
16560              elTheadTr = elThead.appendChild(document.createElement("tr"));
16561      
16562              // ...and create TH cells
16563              for(j=0, len=colTree[i].length; j<len; j++) {
16564                  oColumn = colTree[i][j];
16565                  elTh = elTheadTr.appendChild(document.createElement("th"));
16566                  this._initBdThEl(elTh,oColumn,i,j);
16567              }
16568          }
16569          this._elBdThead = elThead;
16570          YAHOO.log("Accessibility TH cells for " + this._oColumnSet.keys.length + " keys created","info",this.toString());
16571      }
16572  },
16573  
16574  /**
16575   * Populates TH element for the body THEAD element.
16576   *
16577   * @method _initBdThEl
16578   * @param elTh {HTMLElement} TH element reference.
16579   * @param oColumn {YAHOO.widget.Column} Column object.
16580   * @private
16581   */
16582  _initBdThEl : function(elTh, oColumn) {
16583      elTh.id = this.getId()+"-th-" + oColumn.getSanitizedKey(); // Needed for accessibility
16584      elTh.rowSpan = oColumn.getRowspan();
16585      elTh.colSpan = oColumn.getColspan();
16586      // Assign abbr attribute
16587      if(oColumn.abbr) {
16588          elTh.abbr = oColumn.abbr;
16589      }
16590  
16591      // TODO: strip links and form elements
16592      var sKey = oColumn.getKey();
16593      var sLabel = lang.isValue(oColumn.label) ? oColumn.label : sKey;
16594      elTh.innerHTML = sLabel;
16595  },
16596  
16597  /**
16598   * Initializes ScrollingDataTable TBODY element for data
16599   *
16600   * @method _initTbodyEl
16601   * @param elTable {HTMLElement} TABLE element into which to create TBODY .
16602   * @private
16603   */
16604  _initTbodyEl : function(elTable) {
16605      SDT.superclass._initTbodyEl.call(this, elTable);
16606      
16607      // Bug 2105534 - Safari 3 gap
16608      // Bug 2492591 - IE8 offsetTop
16609      elTable.style.marginTop = (this._elTbody.offsetTop > 0) ?
16610              "-"+this._elTbody.offsetTop+"px" : 0;
16611  },
16612  
16613  
16614  
16615  
16616  
16617  
16618  
16619  
16620  
16621  
16622  
16623  
16624  
16625  
16626  
16627  
16628  
16629  
16630  
16631  
16632  
16633  
16634  
16635  
16636  
16637  
16638  
16639  
16640  
16641  /**
16642   * Sets focus on the given element.
16643   *
16644   * @method _focusEl
16645   * @param el {HTMLElement} Element.
16646   * @private
16647   */
16648  _focusEl : function(el) {
16649      el = el || this._elTbody;
16650      var oSelf = this;
16651      this._storeScrollPositions();
16652      // http://developer.mozilla.org/en/docs/index.php?title=Key-navigable_custom_DHTML_widgets
16653      // The timeout is necessary in both IE and Firefox 1.5, to prevent scripts from doing
16654      // strange unexpected things as the user clicks on buttons and other controls.
16655      
16656      // Bug 1921135: Wrap the whole thing in a setTimeout
16657      setTimeout(function() {
16658          setTimeout(function() {
16659              try {
16660                  el.focus();
16661                  oSelf._restoreScrollPositions();
16662              }
16663              catch(e) {
16664              }
16665          },0);
16666      }, 0);
16667  },
16668  
16669  
16670  
16671  
16672  
16673  
16674  
16675  
16676  
16677  
16678  
16679  
16680  
16681  
16682  
16683  
16684  
16685  
16686  
16687  /**
16688   * Internal wrapper calls run() on render Chain instance.
16689   *
16690   * @method _runRenderChain
16691   * @private 
16692   */
16693  _runRenderChain : function() {
16694      this._storeScrollPositions();
16695      this._oChainRender.run();
16696  },
16697  
16698  /**
16699   * Stores scroll positions so they can be restored after a render.
16700   *
16701   * @method _storeScrollPositions
16702   * @private
16703   */
16704   _storeScrollPositions : function() {
16705      this._nScrollTop = this._elBdContainer.scrollTop;
16706      this._nScrollLeft = this._elBdContainer.scrollLeft;
16707  },
16708  
16709  /**
16710   * Clears stored scroll positions to interrupt the automatic restore mechanism.
16711   * Useful for setting scroll positions programmatically rather than as part of
16712   * the post-render cleanup process.
16713   *
16714   * @method clearScrollPositions
16715   * @private
16716   */
16717   clearScrollPositions : function() {
16718      this._nScrollTop = 0;
16719      this._nScrollLeft = 0;
16720  },
16721  
16722  /**
16723   * Restores scroll positions to stored value. 
16724   *
16725   * @method _retoreScrollPositions
16726   * @private 
16727   */
16728   _restoreScrollPositions : function() {
16729      // Reset scroll positions
16730      if(this._nScrollTop) {
16731          this._elBdContainer.scrollTop = this._nScrollTop;
16732          this._nScrollTop = null;
16733      } 
16734      if(this._nScrollLeft) {
16735          this._elBdContainer.scrollLeft = this._nScrollLeft;
16736          // Bug 2529024
16737          this._elHdContainer.scrollLeft = this._nScrollLeft; 
16738          this._nScrollLeft = null;
16739      } 
16740  },
16741  
16742  /**
16743   * Helper function calculates and sets a validated width for a Column in a ScrollingDataTable.
16744   *
16745   * @method _validateColumnWidth
16746   * @param oColumn {YAHOO.widget.Column} Column instance.
16747   * @param elTd {HTMLElement} TD element to validate against.
16748   * @private
16749   */
16750  _validateColumnWidth : function(oColumn, elTd) {
16751      // Only Columns without widths that are not hidden
16752      if(!oColumn.width && !oColumn.hidden) {
16753          var elTh = oColumn.getThEl();
16754          // Unset a calculated auto-width
16755          if(oColumn._calculatedWidth) {
16756              this._setColumnWidth(oColumn, "auto", "visible");
16757          }
16758          // Compare auto-widths
16759          if(elTh.offsetWidth !== elTd.offsetWidth) {
16760              var elWider = (elTh.offsetWidth > elTd.offsetWidth) ?
16761                      oColumn.getThLinerEl() : elTd.firstChild;               
16762  
16763              // Grab the wider liner width, unless the minWidth is wider
16764              var newWidth = Math.max(0,
16765                  (elWider.offsetWidth -(parseInt(Dom.getStyle(elWider,"paddingLeft"),10)|0) - (parseInt(Dom.getStyle(elWider,"paddingRight"),10)|0)),
16766                  oColumn.minWidth);
16767                  
16768              var sOverflow = 'visible';
16769              
16770              // Now validate against maxAutoWidth
16771              if((oColumn.maxAutoWidth > 0) && (newWidth > oColumn.maxAutoWidth)) {
16772                  newWidth = oColumn.maxAutoWidth;
16773                  sOverflow = "hidden";
16774              }
16775  
16776              // Set to the wider auto-width
16777              this._elTbody.style.display = "none";
16778              this._setColumnWidth(oColumn, newWidth+'px', sOverflow);
16779              oColumn._calculatedWidth = newWidth;
16780              this._elTbody.style.display = "";
16781          }
16782      }
16783  },
16784  
16785  /**
16786   * For one or all Columns of a ScrollingDataTable, when Column is not hidden,
16787   * and width is not set, syncs widths of header and body cells and 
16788   * validates that width against minWidth and/or maxAutoWidth as necessary.
16789   *
16790   * @method validateColumnWidths
16791   * @param oArg.column {YAHOO.widget.Column} (optional) One Column to validate. If null, all Columns' widths are validated.
16792   */
16793  validateColumnWidths : function(oColumn) {
16794      // Validate there is at least one TR with proper TDs
16795      var allKeys   = this._oColumnSet.keys,
16796          allKeysLength = allKeys.length,
16797          elRow     = this.getFirstTrEl();
16798  
16799      // Reset overhang for IE
16800      if(ua.ie) {
16801          this._setOverhangValue(1);
16802      }
16803  
16804      if(allKeys && elRow && (elRow.childNodes.length === allKeysLength)) {
16805          // Temporarily unsnap container since it causes inaccurate calculations
16806          var sWidth = this.get("width");
16807          if(sWidth) {
16808              this._elHdContainer.style.width = "";
16809              this._elBdContainer.style.width = "";
16810          }
16811          this._elContainer.style.width = "";
16812          
16813          //Validate just one Column
16814          if(oColumn && lang.isNumber(oColumn.getKeyIndex())) {
16815              this._validateColumnWidth(oColumn, elRow.childNodes[oColumn.getKeyIndex()]);
16816          }
16817          // Iterate through all Columns to unset calculated widths in one pass
16818          else {
16819              var elTd, todos = [], thisTodo, i, len;
16820              for(i=0; i<allKeysLength; i++) {
16821                  oColumn = allKeys[i];
16822                  // Only Columns without widths that are not hidden, unset a calculated auto-width
16823                  if(!oColumn.width && !oColumn.hidden && oColumn._calculatedWidth) {
16824                      todos[todos.length] = oColumn;      
16825                  }
16826              }
16827              
16828              this._elTbody.style.display = "none";
16829              for(i=0, len=todos.length; i<len; i++) {
16830                  this._setColumnWidth(todos[i], "auto", "visible");
16831              }
16832              this._elTbody.style.display = "";
16833              
16834              todos = [];
16835  
16836              // Iterate through all Columns and make the store the adjustments to make in one pass
16837              for(i=0; i<allKeysLength; i++) {
16838                  oColumn = allKeys[i];
16839                  elTd = elRow.childNodes[i];
16840                  // Only Columns without widths that are not hidden
16841                  if(!oColumn.width && !oColumn.hidden) {
16842                      var elTh = oColumn.getThEl();
16843  
16844                      // Compare auto-widths
16845                      if(elTh.offsetWidth !== elTd.offsetWidth) {
16846                          var elWider = (elTh.offsetWidth > elTd.offsetWidth) ?
16847                                  oColumn.getThLinerEl() : elTd.firstChild;               
16848                  
16849                          // Grab the wider liner width, unless the minWidth is wider
16850                          var newWidth = Math.max(0,
16851                              (elWider.offsetWidth -(parseInt(Dom.getStyle(elWider,"paddingLeft"),10)|0) - (parseInt(Dom.getStyle(elWider,"paddingRight"),10)|0)),
16852                              oColumn.minWidth);
16853                              
16854                          var sOverflow = 'visible';
16855                          
16856                          // Now validate against maxAutoWidth
16857                          if((oColumn.maxAutoWidth > 0) && (newWidth > oColumn.maxAutoWidth)) {
16858                              newWidth = oColumn.maxAutoWidth;
16859                              sOverflow = "hidden";
16860                          }
16861                  
16862                          todos[todos.length] = [oColumn, newWidth, sOverflow];
16863                      }
16864                  }
16865              }
16866              
16867              this._elTbody.style.display = "none";
16868              for(i=0, len=todos.length; i<len; i++) {
16869                  thisTodo = todos[i];
16870                  // Set to the wider auto-width
16871                  this._setColumnWidth(thisTodo[0], thisTodo[1]+"px", thisTodo[2]);
16872                  thisTodo[0]._calculatedWidth = thisTodo[1];
16873              }
16874              this._elTbody.style.display = "";
16875          }
16876      
16877          // Resnap unsnapped containers
16878          if(sWidth) {
16879              this._elHdContainer.style.width = sWidth;
16880              this._elBdContainer.style.width = sWidth;
16881          } 
16882      }
16883      
16884      this._syncScroll();
16885      this._restoreScrollPositions();
16886  },
16887  
16888  /**
16889   * Syncs padding around scrollable tables, including Column header right-padding
16890   * and container width and height.
16891   *
16892   * @method _syncScroll
16893   * @private 
16894   */
16895  _syncScroll : function() {
16896      this._syncScrollX();
16897      this._syncScrollY();
16898      this._syncScrollOverhang();
16899      if(ua.opera) {
16900          // Bug 1925874
16901          this._elHdContainer.scrollLeft = this._elBdContainer.scrollLeft;
16902          if(!this.get("width")) {
16903              // Bug 1926125
16904              document.body.style += '';
16905          }
16906      }
16907   },
16908  
16909  /**
16910   * Snaps container width for y-scrolling tables.
16911   *
16912   * @method _syncScrollY
16913   * @private
16914   */
16915  _syncScrollY : function() {
16916      var elTbody = this._elTbody,
16917          elBdContainer = this._elBdContainer;
16918      
16919      // X-scrolling not enabled
16920      if(!this.get("width")) {
16921          // Snap outer container width to content
16922          this._elContainer.style.width = 
16923                  (elBdContainer.scrollHeight > elBdContainer.clientHeight) ?
16924                  // but account for y-scrollbar since it is visible
16925                  (elTbody.parentNode.clientWidth + 19) + "px" :
16926                  // no y-scrollbar, just borders
16927                  (elTbody.parentNode.clientWidth + 2) + "px";
16928      }
16929  },
16930  
16931  /**
16932   * Snaps container height for x-scrolling tables in IE. Syncs message TBODY width.
16933   *
16934   * @method _syncScrollX
16935   * @private
16936   */
16937  _syncScrollX : function() {
16938      var elTbody = this._elTbody,
16939          elBdContainer = this._elBdContainer;
16940      
16941      // IE 6 and 7 only when y-scrolling not enabled
16942      if(!this.get("height") && (ua.ie)) {
16943          // Snap outer container height to content
16944          elBdContainer.style.height = 
16945                  // but account for x-scrollbar if it is visible
16946                  (elBdContainer.scrollWidth > elBdContainer.offsetWidth ) ?
16947                  (elTbody.parentNode.offsetHeight + 18) + "px" : 
16948                  elTbody.parentNode.offsetHeight + "px";
16949      }
16950  
16951      // Sync message tbody
16952      if(this._elTbody.rows.length === 0) {
16953          this._elMsgTbody.parentNode.style.width = this.getTheadEl().parentNode.offsetWidth + "px";
16954      }
16955      else {
16956          this._elMsgTbody.parentNode.style.width = "";
16957      }
16958  },
16959  
16960  /**
16961   * Adds/removes Column header overhang as necesary.
16962   *
16963   * @method _syncScrollOverhang
16964   * @private
16965   */
16966  _syncScrollOverhang : function() {
16967      var elBdContainer = this._elBdContainer,
16968          // Overhang should be either 1 (default) or 18px, depending on the location of the right edge of the table
16969          nPadding = 1;
16970      
16971      // Y-scrollbar is visible, which is when the overhang needs to jut out
16972      if((elBdContainer.scrollHeight > elBdContainer.clientHeight) &&
16973          // X-scrollbar is also visible, which means the right is jagged, not flush with the Column
16974          (elBdContainer.scrollWidth > elBdContainer.clientWidth)) {
16975          nPadding = 18;
16976      }
16977      
16978      this._setOverhangValue(nPadding);
16979      
16980  },
16981  
16982  /**
16983   * Sets Column header overhang to given width.
16984   *
16985   * @method _setOverhangValue
16986   * @param nBorderWidth {Number} Value of new border for overhang. 
16987   * @private
16988   */
16989  _setOverhangValue : function(nBorderWidth) {
16990      var aLastHeaders = this._oColumnSet.headers[this._oColumnSet.headers.length-1] || [],
16991          len = aLastHeaders.length,
16992          sPrefix = this._sId+"-fixedth-",
16993          sValue = nBorderWidth + "px solid " + this.get("COLOR_COLUMNFILLER");
16994  
16995      this._elThead.style.display = "none";
16996      for(var i=0; i<len; i++) {
16997          Dom.get(sPrefix+aLastHeaders[i]).style.borderRight = sValue;
16998      }
16999      this._elThead.style.display = "";
17000  },
17001  
17002  
17003  
17004  
17005  
17006  
17007  
17008  
17009  
17010  
17011  
17012  
17013  
17014  
17015  
17016  
17017  
17018  
17019  
17020  
17021  
17022  
17023  
17024  
17025  
17026  
17027  
17028  
17029  
17030  
17031  
17032  
17033  
17034  
17035  
17036  
17037  
17038  
17039  /**
17040   * Returns DOM reference to the DataTable's fixed header container element.
17041   *
17042   * @method getHdContainerEl
17043   * @return {HTMLElement} Reference to DIV element.
17044   */
17045  getHdContainerEl : function() {
17046      return this._elHdContainer;
17047  },
17048  
17049  /**
17050   * Returns DOM reference to the DataTable's scrolling body container element.
17051   *
17052   * @method getBdContainerEl
17053   * @return {HTMLElement} Reference to DIV element.
17054   */
17055  getBdContainerEl : function() {
17056      return this._elBdContainer;
17057  },
17058  
17059  /**
17060   * Returns DOM reference to the DataTable's fixed header TABLE element.
17061   *
17062   * @method getHdTableEl
17063   * @return {HTMLElement} Reference to TABLE element.
17064   */
17065  getHdTableEl : function() {
17066      return this._elHdTable;
17067  },
17068  
17069  /**
17070   * Returns DOM reference to the DataTable's scrolling body TABLE element.
17071   *
17072   * @method getBdTableEl
17073   * @return {HTMLElement} Reference to TABLE element.
17074   */
17075  getBdTableEl : function() {
17076      return this._elTable;
17077  },
17078  
17079  /**
17080   * Disables ScrollingDataTable UI.
17081   *
17082   * @method disable
17083   */
17084  disable : function() {
17085      var elMask = this._elMask;
17086      elMask.style.width = this._elBdContainer.offsetWidth + "px";
17087      elMask.style.height = this._elHdContainer.offsetHeight + this._elBdContainer.offsetHeight + "px";
17088      elMask.style.display = "";
17089      this.fireEvent("disableEvent");
17090  },
17091  
17092  /**
17093   * Removes given Column. NOTE: You cannot remove nested Columns. You can only remove
17094   * non-nested Columns, and top-level parent Columns (which will remove all
17095   * children Columns).
17096   *
17097   * @method removeColumn
17098   * @param oColumn {YAHOO.widget.Column} Column instance.
17099   * @return oColumn {YAHOO.widget.Column} Removed Column instance.
17100   */
17101  removeColumn : function(oColumn) {
17102      // Store scroll pos
17103      var hdPos = this._elHdContainer.scrollLeft;
17104      var bdPos = this._elBdContainer.scrollLeft;
17105      
17106      // Call superclass method
17107      oColumn = SDT.superclass.removeColumn.call(this, oColumn);
17108      
17109      // Restore scroll pos
17110      this._elHdContainer.scrollLeft = hdPos;
17111      this._elBdContainer.scrollLeft = bdPos;
17112      
17113      return oColumn;
17114  },
17115  
17116  /**
17117   * Inserts given Column at the index if given, otherwise at the end. NOTE: You
17118   * can only add non-nested Columns and top-level parent Columns. You cannot add
17119   * a nested Column to an existing parent.
17120   *
17121   * @method insertColumn
17122   * @param oColumn {Object | YAHOO.widget.Column} Object literal Column
17123   * definition or a Column instance.
17124   * @param index {Number} (optional) New tree index.
17125   * @return oColumn {YAHOO.widget.Column} Inserted Column instance. 
17126   */
17127  insertColumn : function(oColumn, index) {
17128      // Store scroll pos
17129      var hdPos = this._elHdContainer.scrollLeft;
17130      var bdPos = this._elBdContainer.scrollLeft;
17131      
17132      // Call superclass method
17133      var oNewColumn = SDT.superclass.insertColumn.call(this, oColumn, index);
17134      
17135      // Restore scroll pos
17136      this._elHdContainer.scrollLeft = hdPos;
17137      this._elBdContainer.scrollLeft = bdPos;
17138      
17139      return oNewColumn;
17140  },
17141  
17142  /**
17143   * Removes given Column and inserts into given tree index. NOTE: You
17144   * can only reorder non-nested Columns and top-level parent Columns. You cannot
17145   * reorder a nested Column to an existing parent.
17146   *
17147   * @method reorderColumn
17148   * @param oColumn {YAHOO.widget.Column} Column instance.
17149   * @param index {Number} New tree index.
17150   */
17151  reorderColumn : function(oColumn, index) {
17152      // Store scroll pos
17153      var hdPos = this._elHdContainer.scrollLeft;
17154      var bdPos = this._elBdContainer.scrollLeft;
17155      
17156      // Call superclass method
17157      var oNewColumn = SDT.superclass.reorderColumn.call(this, oColumn, index);
17158      
17159      // Restore scroll pos
17160      this._elHdContainer.scrollLeft = hdPos;
17161      this._elBdContainer.scrollLeft = bdPos;
17162  
17163      return oNewColumn;
17164  },
17165  
17166  /**
17167   * Sets given Column to given pixel width. If new width is less than minWidth
17168   * width, sets to minWidth. Updates oColumn.width value.
17169   *
17170   * @method setColumnWidth
17171   * @param oColumn {YAHOO.widget.Column} Column instance.
17172   * @param nWidth {Number} New width in pixels.
17173   */
17174  setColumnWidth : function(oColumn, nWidth) {
17175      oColumn = this.getColumn(oColumn);
17176      if(oColumn) {
17177          this._storeScrollPositions();
17178  
17179          // Validate new width against minWidth
17180          if(lang.isNumber(nWidth)) {
17181              nWidth = (nWidth > oColumn.minWidth) ? nWidth : oColumn.minWidth;
17182  
17183              // Save state
17184              oColumn.width = nWidth;
17185              
17186              // Resize the DOM elements
17187              this._setColumnWidth(oColumn, nWidth+"px");
17188              this._syncScroll();
17189              
17190              this.fireEvent("columnSetWidthEvent",{column:oColumn,width:nWidth});
17191              YAHOO.log("Set width of Column " + oColumn + " to " + nWidth + "px", "info", this.toString());
17192          }
17193          // Unsets a width to auto-size
17194          else if(nWidth === null) {
17195              // Save state
17196              oColumn.width = nWidth;
17197              
17198              // Resize the DOM elements
17199              this._setColumnWidth(oColumn, "auto");
17200              this.validateColumnWidths(oColumn);
17201              this.fireEvent("columnUnsetWidthEvent",{column:oColumn});
17202              YAHOO.log("Column " + oColumn + " width unset", "info", this.toString());
17203          }
17204          
17205          // Bug 2339454: resize then sort misaligment
17206          this._clearTrTemplateEl();
17207      }
17208      else {
17209          YAHOO.log("Could not set width of Column " + oColumn + " to " + nWidth + "px", "warn", this.toString());
17210      }
17211  },
17212  
17213  /**
17214   * Scrolls to given row or cell
17215   *
17216   * @method scrollTo
17217   * @param to {YAHOO.widget.Record | HTMLElement } Itme to scroll to.
17218   */
17219  scrollTo : function(to) {
17220          var td = this.getTdEl(to);
17221          if(td) {
17222              this.clearScrollPositions();
17223              this.getBdContainerEl().scrollLeft = td.offsetLeft;
17224              this.getBdContainerEl().scrollTop = td.parentNode.offsetTop;
17225          }
17226          else {
17227              var tr = this.getTrEl(to);
17228              if(tr) {
17229                  this.clearScrollPositions();
17230                  this.getBdContainerEl().scrollTop = tr.offsetTop;
17231              }
17232          }
17233  },
17234  
17235  /**
17236   * Displays message within secondary TBODY.
17237   *
17238   * @method showTableMessage
17239   * @param sHTML {String} (optional) Value for innerHTMlang.
17240   * @param sClassName {String} (optional) Classname.
17241   */
17242  showTableMessage : function(sHTML, sClassName) {
17243      var elCell = this._elMsgTd;
17244      if(lang.isString(sHTML)) {
17245          elCell.firstChild.innerHTML = sHTML;
17246      }
17247      if(lang.isString(sClassName)) {
17248          Dom.addClass(elCell.firstChild, sClassName);
17249      }
17250  
17251      // Needed for SDT only
17252      var elThead = this.getTheadEl();
17253      var elTable = elThead.parentNode;
17254      var newWidth = elTable.offsetWidth;
17255      this._elMsgTbody.parentNode.style.width = this.getTheadEl().parentNode.offsetWidth + "px";
17256  
17257      this._elMsgTbody.style.display = "";
17258  
17259      this.fireEvent("tableMsgShowEvent", {html:sHTML, className:sClassName});
17260      YAHOO.log("DataTable showing message: " + sHTML, "info", this.toString());
17261  },
17262  
17263  
17264  
17265  
17266  
17267  
17268  
17269  
17270  
17271  
17272  
17273  
17274  
17275  /////////////////////////////////////////////////////////////////////////////
17276  //
17277  // Private Custom Event Handlers
17278  //
17279  /////////////////////////////////////////////////////////////////////////////
17280  
17281  /**
17282   * Handles Column mutations
17283   *
17284   * @method onColumnChange
17285   * @param oArgs {Object} Custom Event data.
17286   */
17287  _onColumnChange : function(oArg) {
17288      // Figure out which Column changed
17289      var oColumn = (oArg.column) ? oArg.column :
17290              (oArg.editor) ? oArg.editor.column : null;
17291      this._storeScrollPositions();
17292      this.validateColumnWidths(oColumn);
17293  },
17294  
17295  
17296  
17297  
17298  
17299  
17300  
17301  
17302  
17303  
17304  
17305  
17306  
17307  
17308  
17309  /////////////////////////////////////////////////////////////////////////////
17310  //
17311  // Private DOM Event Handlers
17312  //
17313  /////////////////////////////////////////////////////////////////////////////
17314  
17315  /**
17316   * Syncs scrolltop and scrollleft of all TABLEs.
17317   *
17318   * @method _onScroll
17319   * @param e {HTMLEvent} The scroll event.
17320   * @param oSelf {YAHOO.widget.ScrollingDataTable} ScrollingDataTable instance.
17321   * @private
17322   */
17323  _onScroll : function(e, oSelf) {
17324      oSelf._elHdContainer.scrollLeft = oSelf._elBdContainer.scrollLeft;
17325  
17326      if(oSelf._oCellEditor && oSelf._oCellEditor.isActive) {
17327          oSelf.fireEvent("editorBlurEvent", {editor:oSelf._oCellEditor});
17328          oSelf.cancelCellEditor();
17329      }
17330  
17331      var elTarget = Ev.getTarget(e);
17332      var elTag = elTarget.nodeName.toLowerCase();
17333      oSelf.fireEvent("tableScrollEvent", {event:e, target:elTarget});
17334  },
17335  
17336  /**
17337   * Handles keydown events on the THEAD element.
17338   *
17339   * @method _onTheadKeydown
17340   * @param e {HTMLEvent} The key event.
17341   * @param oSelf {YAHOO.widget.ScrollingDataTable} ScrollingDataTable instance.
17342   * @private
17343   */
17344  _onTheadKeydown : function(e, oSelf) {
17345      // If tabbing to next TH label link causes THEAD to scroll,
17346      // need to sync scrollLeft with TBODY
17347      if(Ev.getCharCode(e) === 9) {
17348          setTimeout(function() {
17349              if((oSelf instanceof SDT) && oSelf._sId) {
17350                  oSelf._elBdContainer.scrollLeft = oSelf._elHdContainer.scrollLeft;
17351              }
17352          },0);
17353      }
17354      
17355      var elTarget = Ev.getTarget(e);
17356      var elTag = elTarget.nodeName.toLowerCase();
17357      var bKeepBubbling = true;
17358      while(elTarget && (elTag != "table")) {
17359          switch(elTag) {
17360              case "body":
17361                  return;
17362              case "input":
17363              case "textarea":
17364                  // TODO: implement textareaKeyEvent
17365                  break;
17366              case "thead":
17367                  bKeepBubbling = oSelf.fireEvent("theadKeyEvent",{target:elTarget,event:e});
17368                  break;
17369              default:
17370                  break;
17371          }
17372          if(bKeepBubbling === false) {
17373              return;
17374          }
17375          else {
17376              elTarget = elTarget.parentNode;
17377              if(elTarget) {
17378                  elTag = elTarget.nodeName.toLowerCase();
17379              }
17380          }
17381      }
17382      oSelf.fireEvent("tableKeyEvent",{target:(elTarget || oSelf._elContainer),event:e});
17383  }
17384  
17385  
17386  
17387  
17388  /**
17389   * Fired when a fixed scrolling DataTable has a scroll.
17390   *
17391   * @event tableScrollEvent
17392   * @param oArgs.event {HTMLEvent} The event object.
17393   * @param oArgs.target {HTMLElement} The DataTable's CONTAINER element (in IE)
17394   * or the DataTable's TBODY element (everyone else).
17395   *
17396   */
17397  
17398  
17399  
17400  
17401  });
17402  
17403  })();
17404  
17405  (function () {
17406  
17407  var lang   = YAHOO.lang,
17408      util   = YAHOO.util,
17409      widget = YAHOO.widget,
17410      ua     = YAHOO.env.ua,
17411      
17412      Dom    = util.Dom,
17413      Ev     = util.Event,
17414      
17415      DT     = widget.DataTable;
17416  /****************************************************************************/
17417  /****************************************************************************/
17418  /****************************************************************************/
17419      
17420  /**
17421   * The BaseCellEditor class provides base functionality common to all inline cell
17422   * editors for a DataTable widget.
17423   *
17424   * @namespace YAHOO.widget
17425   * @class BaseCellEditor
17426   * @uses YAHOO.util.EventProvider 
17427   * @constructor
17428   * @param sType {String} Type indicator, to map to YAHOO.widget.DataTable.Editors.
17429   * @param oConfigs {Object} (Optional) Object literal of configs.
17430   */
17431  widget.BaseCellEditor = function(sType, oConfigs) {
17432      this._sId = this._sId || Dom.generateId(null, "yui-ceditor"); // "yui-ceditor" + YAHOO.widget.BaseCellEditor._nCount++;
17433      YAHOO.widget.BaseCellEditor._nCount++;
17434      this._sType = sType;
17435      
17436      // Validate inputs
17437      this._initConfigs(oConfigs); 
17438      
17439      // Create Custom Events
17440      this._initEvents();
17441               
17442      // UI needs to be drawn
17443      this._needsRender = true;
17444  };
17445  
17446  var BCE = widget.BaseCellEditor;
17447  
17448  /////////////////////////////////////////////////////////////////////////////
17449  //
17450  // Static members
17451  //
17452  /////////////////////////////////////////////////////////////////////////////
17453  lang.augmentObject(BCE, {
17454  
17455  /**
17456   * Global instance counter.
17457   *
17458   * @property CellEditor._nCount
17459   * @type Number
17460   * @static
17461   * @default 0
17462   * @private 
17463   */
17464  _nCount : 0,
17465  
17466  /**
17467   * Class applied to CellEditor container.
17468   *
17469   * @property CellEditor.CLASS_CELLEDITOR
17470   * @type String
17471   * @static
17472   * @default "yui-ceditor"
17473   */
17474  CLASS_CELLEDITOR : "yui-ceditor"
17475  
17476  });
17477  
17478  BCE.prototype = {
17479  /////////////////////////////////////////////////////////////////////////////
17480  //
17481  // Private members
17482  //
17483  /////////////////////////////////////////////////////////////////////////////
17484  /**
17485   * Unique id assigned to instance "yui-ceditorN", useful prefix for generating unique
17486   * DOM ID strings and log messages.
17487   *
17488   * @property _sId
17489   * @type String
17490   * @private
17491   */
17492  _sId : null,
17493  
17494  /**
17495   * Editor type.
17496   *
17497   * @property _sType
17498   * @type String
17499   * @private
17500   */
17501  _sType : null,
17502  
17503  /**
17504   * DataTable instance.
17505   *
17506   * @property _oDataTable
17507   * @type YAHOO.widget.DataTable
17508   * @private 
17509   */
17510  _oDataTable : null,
17511  
17512  /**
17513   * Column instance.
17514   *
17515   * @property _oColumn
17516   * @type YAHOO.widget.Column
17517   * @default null
17518   * @private 
17519   */
17520  _oColumn : null,
17521  
17522  /**
17523   * Record instance.
17524   *
17525   * @property _oRecord
17526   * @type YAHOO.widget.Record
17527   * @default null
17528   * @private 
17529   */
17530  _oRecord : null,
17531  
17532  /**
17533   * TD element.
17534   *
17535   * @property _elTd
17536   * @type HTMLElement
17537   * @default null
17538   * @private
17539   */
17540  _elTd : null,
17541  
17542  /**
17543   * Container for inline editor.
17544   *
17545   * @property _elContainer
17546   * @type HTMLElement
17547   * @private 
17548   */
17549  _elContainer : null,
17550  
17551  /**
17552   * Reference to Cancel button, if available.
17553   *
17554   * @property _elCancelBtn
17555   * @type HTMLElement
17556   * @default null
17557   * @private 
17558   */
17559  _elCancelBtn : null,
17560  
17561  /**
17562   * Reference to Save button, if available.
17563   *
17564   * @property _elSaveBtn
17565   * @type HTMLElement
17566   * @default null
17567   * @private 
17568   */
17569  _elSaveBtn : null,
17570  
17571  
17572  
17573  
17574  
17575  
17576  
17577  
17578  /////////////////////////////////////////////////////////////////////////////
17579  //
17580  // Private methods
17581  //
17582  /////////////////////////////////////////////////////////////////////////////
17583  
17584  /**
17585   * Initialize configs.
17586   *
17587   * @method _initConfigs
17588   * @private   
17589   */
17590  _initConfigs : function(oConfigs) {
17591      // Object literal defines CellEditor configs
17592      if(oConfigs && YAHOO.lang.isObject(oConfigs)) {
17593          for(var sConfig in oConfigs) {
17594              if(sConfig) {
17595                  this[sConfig] = oConfigs[sConfig];
17596              }
17597          }
17598      }
17599  },
17600  
17601  /**
17602   * Initialize Custom Events.
17603   *
17604   * @method _initEvents
17605   * @private   
17606   */
17607  _initEvents : function() {
17608      this.createEvent("showEvent");
17609      this.createEvent("keydownEvent");
17610      this.createEvent("invalidDataEvent");
17611      this.createEvent("revertEvent");
17612      this.createEvent("saveEvent");
17613      this.createEvent("cancelEvent");
17614      this.createEvent("blurEvent");
17615      this.createEvent("blockEvent");
17616      this.createEvent("unblockEvent");
17617  },
17618  
17619  /**
17620   * Initialize container element.
17621   *
17622   * @method _initContainerEl
17623   * @private
17624   */
17625  _initContainerEl : function() {
17626      if(this._elContainer) {
17627          YAHOO.util.Event.purgeElement(this._elContainer, true);
17628          this._elContainer.innerHTML = "";
17629      }
17630  
17631      var elContainer = document.createElement("div");
17632      elContainer.id = this.getId() + "-container"; // Needed for tracking blur event
17633      elContainer.style.display = "none";
17634      elContainer.tabIndex = 0;
17635      
17636      this.className = lang.isArray(this.className) ? this.className : this.className ? [this.className] : [];
17637      this.className[this.className.length] = DT.CLASS_EDITOR;
17638      elContainer.className = this.className.join(" ");
17639      
17640      document.body.insertBefore(elContainer, document.body.firstChild);
17641      this._elContainer = elContainer;
17642  },
17643  
17644  /**
17645   * Initialize container shim element.
17646   *
17647   * @method _initShimEl
17648   * @private
17649   */
17650  _initShimEl : function() {
17651      // Iframe shim
17652      if(this.useIFrame) {
17653          if(!this._elIFrame) {
17654              var elIFrame = document.createElement("iframe");
17655              elIFrame.src = "javascript:false";
17656              elIFrame.frameBorder = 0;
17657              elIFrame.scrolling = "no";
17658              elIFrame.style.display = "none";
17659              elIFrame.className = DT.CLASS_EDITOR_SHIM;
17660              elIFrame.tabIndex = -1;
17661              elIFrame.role = "presentation";
17662              elIFrame.title = "Presentational iframe shim";
17663              document.body.insertBefore(elIFrame, document.body.firstChild);
17664              this._elIFrame = elIFrame;
17665          }
17666      }
17667  },
17668  
17669  /**
17670   * Hides CellEditor UI at end of interaction.
17671   *
17672   * @method _hide
17673   */
17674  _hide : function() {
17675      this.getContainerEl().style.display = "none";
17676      if(this._elIFrame) {
17677          this._elIFrame.style.display = "none";
17678      }
17679      this.isActive = false;
17680      this.getDataTable()._oCellEditor =  null;
17681  },
17682  
17683  
17684  
17685  
17686  
17687  
17688  
17689  
17690  
17691  
17692  
17693  /////////////////////////////////////////////////////////////////////////////
17694  //
17695  // Public properties
17696  //
17697  /////////////////////////////////////////////////////////////////////////////
17698  /**
17699   * Implementer defined function that can submit the input value to a server. This
17700   * function must accept the arguments fnCallback and oNewValue. When the submission
17701   * is complete, the function must also call fnCallback(bSuccess, oNewValue) to 
17702   * finish the save routine in the CellEditor. This function can also be used to 
17703   * perform extra validation or input value manipulation. 
17704   *
17705   * @property asyncSubmitter
17706   * @type HTMLFunction
17707   */
17708  asyncSubmitter : null,
17709  
17710  /**
17711   * Current value.
17712   *
17713   * @property value
17714   * @type MIXED
17715   */
17716  value : null,
17717  
17718  /**
17719   * Default value in case Record data is undefined. NB: Null values will not trigger
17720   * the default value.
17721   *
17722   * @property defaultValue
17723   * @type MIXED
17724   * @default null
17725   */
17726  defaultValue : null,
17727  
17728  /**
17729   * Validator function for input data, called from the DataTable instance scope,
17730   * receives the arguments (inputValue, currentValue, editorInstance) and returns
17731   * either the validated (or type-converted) value or undefined.
17732   *
17733   * @property validator
17734   * @type HTMLFunction
17735   * @default null
17736   */
17737  validator : null,
17738  
17739  /**
17740   * If validation is enabled, resets input field of invalid data.
17741   *
17742   * @property resetInvalidData
17743   * @type Boolean
17744   * @default true
17745   */
17746  resetInvalidData : true,
17747  
17748  /**
17749   * True if currently active.
17750   *
17751   * @property isActive
17752   * @type Boolean
17753   */
17754  isActive : false,
17755  
17756  /**
17757   * Text to display on Save button.
17758   *
17759   * @property LABEL_SAVE
17760   * @type HTML
17761   * @default "Save"
17762   */
17763  LABEL_SAVE : "Save",
17764  
17765  /**
17766   * Text to display on Cancel button.
17767   *
17768   * @property LABEL_CANCEL
17769   * @type HTML
17770   * @default "Cancel"
17771   */
17772  LABEL_CANCEL : "Cancel",
17773  
17774  /**
17775   * True if Save/Cancel buttons should not be displayed in the CellEditor.
17776   *
17777   * @property disableBtns
17778   * @type Boolean
17779   * @default false
17780   */
17781  disableBtns : false,
17782  
17783  /**
17784   * True if iframe shim for container element should be enabled.
17785   *
17786   * @property useIFrame
17787   * @type Boolean
17788   * @default false
17789   */
17790  useIFrame : false,
17791  
17792  /**
17793   * Custom CSS class or array of classes applied to the container element.
17794   *
17795   * @property className
17796   * @type String || String[]
17797   */
17798  className : null,
17799  
17800  
17801  
17802  
17803  
17804  /////////////////////////////////////////////////////////////////////////////
17805  //
17806  // Public methods
17807  //
17808  /////////////////////////////////////////////////////////////////////////////
17809  /**
17810   * CellEditor instance name, for logging.
17811   *
17812   * @method toString
17813   * @return {String} Unique name of the CellEditor instance.
17814   */
17815  
17816  toString : function() {
17817      return "CellEditor instance " + this._sId;
17818  },
17819  
17820  /**
17821   * CellEditor unique ID.
17822   *
17823   * @method getId
17824   * @return {String} Unique ID of the CellEditor instance.
17825   */
17826  
17827  getId : function() {
17828      return this._sId;
17829  },
17830  
17831  /**
17832   * Returns reference to associated DataTable instance.
17833   *
17834   * @method getDataTable
17835   * @return {YAHOO.widget.DataTable} DataTable instance.
17836   */
17837  
17838  getDataTable : function() {
17839      return this._oDataTable;
17840  },
17841  
17842  /**
17843   * Returns reference to associated Column instance.
17844   *
17845   * @method getColumn
17846   * @return {YAHOO.widget.Column} Column instance.
17847   */
17848  
17849  getColumn : function() {
17850      return this._oColumn;
17851  },
17852  
17853  /**
17854   * Returns reference to associated Record instance.
17855   *
17856   * @method getRecord
17857   * @return {YAHOO.widget.Record} Record instance.
17858   */
17859  
17860  getRecord : function() {
17861      return this._oRecord;
17862  },
17863  
17864  
17865  
17866  /**
17867   * Returns reference to associated TD element.
17868   *
17869   * @method getTdEl
17870   * @return {HTMLElement} TD element.
17871   */
17872  
17873  getTdEl : function() {
17874      return this._elTd;
17875  },
17876  
17877  /**
17878   * Returns container element.
17879   *
17880   * @method getContainerEl
17881   * @return {HTMLElement} Reference to container element.
17882   */
17883  
17884  getContainerEl : function() {
17885      return this._elContainer;
17886  },
17887  
17888  /**
17889   * Nulls out the entire CellEditor instance and related objects, removes attached
17890   * event listeners, and clears out DOM elements inside the container, removes
17891   * container from the DOM.
17892   *
17893   * @method destroy
17894   */
17895  destroy : function() {
17896      this.unsubscribeAll();
17897      
17898      // Column is late-binding in attach()
17899      var oColumn = this.getColumn();
17900      if(oColumn) {
17901          oColumn.editor = null;
17902      }
17903      
17904      var elContainer = this.getContainerEl();
17905      if (elContainer) {
17906          Ev.purgeElement(elContainer, true);
17907          elContainer.parentNode.removeChild(elContainer);
17908      }
17909  },
17910  
17911  /**
17912   * Renders DOM elements and attaches event listeners.
17913   *
17914   * @method render
17915   */
17916  render : function() {
17917      if (!this._needsRender) {
17918          return;
17919      }
17920  
17921      this._initContainerEl();
17922      this._initShimEl();
17923  
17924      // Handle ESC key
17925      Ev.addListener(this.getContainerEl(), "keydown", function(e, oSelf) {
17926          // ESC cancels Cell Editor
17927          if((e.keyCode == 27)) {
17928              var target = Ev.getTarget(e);
17929              // workaround for Mac FF3 bug that disabled clicks when ESC hit when
17930              // select is open. [bug 2273056]
17931              if (target.nodeName && target.nodeName.toLowerCase() === 'select') {
17932                  target.blur();
17933              }
17934              oSelf.cancel();
17935          }
17936          // Pass through event
17937          oSelf.fireEvent("keydownEvent", {editor:oSelf, event:e});
17938      }, this);
17939  
17940      this.renderForm();
17941  
17942      // Show Save/Cancel buttons
17943      if(!this.disableBtns) {
17944          this.renderBtns();
17945      }
17946      
17947      this.doAfterRender();
17948      this._needsRender = false;
17949  },
17950  
17951  /**
17952   * Renders Save/Cancel buttons.
17953   *
17954   * @method renderBtns
17955   */
17956  renderBtns : function() {
17957      // Buttons
17958      var elBtnsDiv = this.getContainerEl().appendChild(document.createElement("div"));
17959      elBtnsDiv.className = DT.CLASS_BUTTON;
17960  
17961      // Save button
17962      var elSaveBtn = elBtnsDiv.appendChild(document.createElement("button"));
17963      elSaveBtn.className = DT.CLASS_DEFAULT;
17964      elSaveBtn.innerHTML = this.LABEL_SAVE;
17965      Ev.addListener(elSaveBtn, "click", function(oArgs) {
17966          this.save();
17967      }, this, true);
17968      this._elSaveBtn = elSaveBtn;
17969  
17970      // Cancel button
17971      var elCancelBtn = elBtnsDiv.appendChild(document.createElement("button"));
17972      elCancelBtn.innerHTML = this.LABEL_CANCEL;
17973      Ev.addListener(elCancelBtn, "click", function(oArgs) {
17974          this.cancel();
17975      }, this, true);
17976      this._elCancelBtn = elCancelBtn;
17977  },
17978  
17979  /**
17980   * Attach CellEditor for a new interaction.
17981   *
17982   * @method attach
17983   * @param oDataTable {YAHOO.widget.DataTable} Associated DataTable instance.
17984   * @param elCell {HTMLElement} Cell to edit.  
17985   */
17986  attach : function(oDataTable, elCell) {
17987      // Validate 
17988      if(oDataTable instanceof YAHOO.widget.DataTable) {
17989          this._oDataTable = oDataTable;
17990          
17991          // Validate cell
17992          elCell = oDataTable.getTdEl(elCell);
17993          if(elCell) {
17994              this._elTd = elCell;
17995  
17996              // Validate Column
17997              var oColumn = oDataTable.getColumn(elCell);
17998              if(oColumn) {
17999                  this._oColumn = oColumn;
18000                  
18001                  // Validate Record
18002                  var oRecord = oDataTable.getRecord(elCell);
18003                  if(oRecord) {
18004                      this._oRecord = oRecord;
18005                      var value = oRecord.getData(this.getColumn().getField());
18006                      this.value = (value !== undefined) ? value : this.defaultValue;
18007                      return true;
18008                  }
18009              }            
18010          }
18011      }
18012      YAHOO.log("Could not attach CellEditor","error",this.toString());
18013      return false;
18014  },
18015  
18016  /**
18017   * Moves container into position for display.
18018   *
18019   * @method move
18020   */
18021  move : function() {
18022      // Move Editor
18023      var elContainer = this.getContainerEl(),
18024          elTd = this.getTdEl(),
18025          x = Dom.getX(elTd),
18026          y = Dom.getY(elTd);
18027  
18028      //TODO: remove scrolling logic
18029      // SF doesn't get xy for cells in scrolling table
18030      // when tbody display is set to block
18031      if(isNaN(x) || isNaN(y)) {
18032          var elTbody = this.getDataTable().getTbodyEl();
18033          x = elTd.offsetLeft + // cell pos relative to table
18034                  Dom.getX(elTbody.parentNode) - // plus table pos relative to document
18035                  elTbody.scrollLeft; // minus tbody scroll
18036          y = elTd.offsetTop + // cell pos relative to table
18037                  Dom.getY(elTbody.parentNode) - // plus table pos relative to document
18038                  elTbody.scrollTop + // minus tbody scroll
18039                  this.getDataTable().getTheadEl().offsetHeight; // account for fixed THEAD cells
18040      }
18041  
18042      elContainer.style.left = x + "px";
18043      elContainer.style.top = y + "px";
18044  
18045      if(this._elIFrame) {
18046          this._elIFrame.style.left = x + "px";
18047          this._elIFrame.style.top = y + "px";
18048      }
18049  },
18050  
18051  /**
18052   * Displays CellEditor UI in the correct position.
18053   *
18054   * @method show
18055   */
18056  show : function() {
18057      var elContainer = this.getContainerEl(),
18058          elIFrame = this._elIFrame;
18059      this.resetForm();
18060      this.isActive = true;
18061      elContainer.style.display = "";
18062      if(elIFrame) {
18063          elIFrame.style.width = elContainer.offsetWidth + "px";
18064          elIFrame.style.height = elContainer.offsetHeight + "px";
18065          elIFrame.style.display = "";
18066      }
18067      this.focus();
18068      this.fireEvent("showEvent", {editor:this});
18069      YAHOO.log("CellEditor shown", "info", this.toString()); 
18070  },
18071  
18072  /**
18073   * Fires blockEvent
18074   *
18075   * @method block
18076   */
18077  block : function() {
18078      this.fireEvent("blockEvent", {editor:this});
18079      YAHOO.log("CellEditor blocked", "info", this.toString()); 
18080  },
18081  
18082  /**
18083   * Fires unblockEvent
18084   *
18085   * @method unblock
18086   */
18087  unblock : function() {
18088      this.fireEvent("unblockEvent", {editor:this});
18089      YAHOO.log("CellEditor unblocked", "info", this.toString()); 
18090  },
18091  
18092  /**
18093   * Saves value of CellEditor and hides UI.
18094   *
18095   * @method save
18096   */
18097  save : function() {
18098      // Get new value
18099      var inputValue = this.getInputValue();
18100      var validValue = inputValue;
18101      
18102      // Validate new value
18103      if(this.validator) {
18104          validValue = this.validator.call(this.getDataTable(), inputValue, this.value, this);
18105          if(validValue === undefined ) {
18106              if(this.resetInvalidData) {
18107                  this.resetForm();
18108              }
18109              this.fireEvent("invalidDataEvent",
18110                      {editor:this, oldData:this.value, newData:inputValue});
18111              YAHOO.log("Could not save Cell Editor input due to invalid data " +
18112                      lang.dump(inputValue), "warn", this.toString());
18113              return;
18114          }
18115      }
18116          
18117      var oSelf = this;
18118      var finishSave = function(bSuccess, oNewValue) {
18119          var oOrigValue = oSelf.value;
18120          if(bSuccess) {
18121              // Update new value
18122              oSelf.value = oNewValue;
18123              oSelf.getDataTable().updateCell(oSelf.getRecord(), oSelf.getColumn(), oNewValue);
18124              
18125              // Hide CellEditor
18126              oSelf._hide();
18127              
18128              oSelf.fireEvent("saveEvent",
18129                      {editor:oSelf, oldData:oOrigValue, newData:oSelf.value});
18130              YAHOO.log("Cell Editor input saved", "info", this.toString());
18131          }
18132          else {
18133              oSelf.resetForm();
18134              oSelf.fireEvent("revertEvent",
18135                      {editor:oSelf, oldData:oOrigValue, newData:oNewValue});
18136              YAHOO.log("Could not save Cell Editor input " +
18137                      lang.dump(oNewValue), "warn", oSelf.toString());
18138          }
18139          oSelf.unblock();
18140      };
18141      
18142      this.block();
18143      if(lang.isFunction(this.asyncSubmitter)) {
18144          this.asyncSubmitter.call(this, finishSave, validValue);
18145      } 
18146      else {   
18147          finishSave(true, validValue);
18148      }
18149  },
18150  
18151  /**
18152   * Cancels CellEditor input and hides UI.
18153   *
18154   * @method cancel
18155   */
18156  cancel : function() {
18157      if(this.isActive) {
18158          this._hide();
18159          this.fireEvent("cancelEvent", {editor:this});
18160          YAHOO.log("CellEditor canceled", "info", this.toString());
18161      }
18162      else {
18163          YAHOO.log("Unable to cancel CellEditor", "warn", this.toString());
18164      }
18165  },
18166  
18167  /**
18168   * Renders form elements.
18169   *
18170   * @method renderForm
18171   */
18172  renderForm : function() {
18173      // To be implemented by subclass
18174  },
18175  
18176  /**
18177   * Access to add additional event listeners.
18178   *
18179   * @method doAfterRender
18180   */
18181  doAfterRender : function() {
18182      // To be implemented by subclass
18183  },
18184  
18185  
18186  /**
18187   * After rendering form, if disabledBtns is set to true, then sets up a mechanism
18188   * to save input without them. 
18189   *
18190   * @method handleDisabledBtns
18191   */
18192  handleDisabledBtns : function() {
18193      // To be implemented by subclass
18194  },
18195  
18196  /**
18197   * Resets CellEditor UI to initial state.
18198   *
18199   * @method resetForm
18200   */
18201  resetForm : function() {
18202      // To be implemented by subclass
18203  },
18204  
18205  /**
18206   * Sets focus in CellEditor.
18207   *
18208   * @method focus
18209   */
18210  focus : function() {
18211      // To be implemented by subclass
18212  },
18213  
18214  /**
18215   * Retrieves input value from CellEditor.
18216   *
18217   * @method getInputValue
18218   */
18219  getInputValue : function() {
18220      // To be implemented by subclass
18221  }
18222  
18223  };
18224  
18225  lang.augmentProto(BCE, util.EventProvider);
18226  
18227  
18228  /////////////////////////////////////////////////////////////////////////////
18229  //
18230  // Custom Events
18231  //
18232  /////////////////////////////////////////////////////////////////////////////
18233  
18234  /**
18235   * Fired when a CellEditor is shown.
18236   *
18237   * @event showEvent
18238   * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
18239   */
18240  
18241  /**
18242   * Fired when a CellEditor has a keydown.
18243   *
18244   * @event keydownEvent
18245   * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance. 
18246   * @param oArgs.event {HTMLEvent} The event object.
18247   */
18248  
18249  /**
18250   * Fired when a CellEditor input is reverted due to invalid data.
18251   *
18252   * @event invalidDataEvent
18253   * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance. 
18254   * @param oArgs.newData {Object} New data value from form input field.
18255   * @param oArgs.oldData {Object} Old data value.
18256   */
18257  
18258  /**
18259   * Fired when a CellEditor input is reverted due to asyncSubmitter failure.
18260   *
18261   * @event revertEvent
18262   * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance. 
18263   * @param oArgs.newData {Object} New data value from form input field.
18264   * @param oArgs.oldData {Object} Old data value.
18265   */
18266  
18267  /**
18268   * Fired when a CellEditor input is saved.
18269   *
18270   * @event saveEvent
18271   * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance. 
18272   * @param oArgs.newData {Object} New data value from form input field.
18273   * @param oArgs.oldData {Object} Old data value.
18274   */
18275  
18276  /**
18277   * Fired when a CellEditor input is canceled.
18278   *
18279   * @event cancelEvent
18280   * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance. 
18281   */
18282  
18283  /**
18284   * Fired when a CellEditor has a blur event.
18285   *
18286   * @event blurEvent
18287   * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance. 
18288   */
18289  
18290  
18291  
18292  
18293  
18294  
18295  
18296  
18297  
18298  
18299  
18300  
18301  
18302  
18303  /****************************************************************************/
18304  /****************************************************************************/
18305  /****************************************************************************/
18306      
18307  /**
18308   * The CheckboxCellEditor class provides functionality for inline editing
18309   * DataTable cell data with checkboxes.
18310   *
18311   * @namespace YAHOO.widget
18312   * @class CheckboxCellEditor
18313   * @extends YAHOO.widget.BaseCellEditor
18314   * @constructor
18315   * @param oConfigs {Object} (Optional) Object literal of configs.
18316   */
18317  widget.CheckboxCellEditor = function(oConfigs) {
18318      oConfigs = oConfigs || {};
18319      this._sId = this._sId || Dom.generateId(null, "yui-checkboxceditor"); // "yui-checkboxceditor" + YAHOO.widget.BaseCellEditor._nCount++;
18320      YAHOO.widget.BaseCellEditor._nCount++;
18321      widget.CheckboxCellEditor.superclass.constructor.call(this, oConfigs.type || "checkbox", oConfigs);
18322  };
18323  
18324  // CheckboxCellEditor extends BaseCellEditor
18325  lang.extend(widget.CheckboxCellEditor, BCE, {
18326  
18327  /////////////////////////////////////////////////////////////////////////////
18328  //
18329  // CheckboxCellEditor public properties
18330  //
18331  /////////////////////////////////////////////////////////////////////////////
18332  /**
18333   * Array of checkbox values. Can either be a simple array (e.g., ["red","green","blue"])
18334   * or a an array of objects (e.g., [{label:"red", value:"#FF0000"},
18335   * {label:"green", value:"#00FF00"}, {label:"blue", value:"#0000FF"}]). String
18336   * values are treated as markup and inserted into the DOM as innerHTML.
18337   *
18338   * @property checkboxOptions
18339   * @type HTML[] | Object[]
18340   */
18341  checkboxOptions : null,
18342  
18343  /**
18344   * Reference to the checkbox elements.
18345   *
18346   * @property checkboxes
18347   * @type HTMLElement[] 
18348   */
18349  checkboxes : null,
18350  
18351  /**
18352   * Array of checked values
18353   *
18354   * @property value
18355   * @type String[] 
18356   */
18357  value : null,
18358  
18359  /////////////////////////////////////////////////////////////////////////////
18360  //
18361  // CheckboxCellEditor public methods
18362  //
18363  /////////////////////////////////////////////////////////////////////////////
18364  
18365  /**
18366   * Render a form with input(s) type=checkbox.
18367   *
18368   * @method renderForm
18369   */
18370  renderForm : function() {
18371      if(lang.isArray(this.checkboxOptions)) {
18372          var checkboxOption, checkboxValue, checkboxId, elLabel, j, len;
18373          
18374          // Create the checkbox buttons in an IE-friendly way...
18375          for(j=0,len=this.checkboxOptions.length; j<len; j++) {
18376              checkboxOption = this.checkboxOptions[j];
18377              checkboxValue = lang.isValue(checkboxOption.value) ?
18378                      checkboxOption.value : checkboxOption;
18379  
18380              checkboxId = this.getId() + "-chk" + j;
18381              this.getContainerEl().innerHTML += "<input type=\"checkbox\"" +
18382                      " id=\"" + checkboxId + "\"" + // Needed for label
18383                      " value=\"" + checkboxValue + "\" />";
18384              
18385              // Create the labels in an IE-friendly way
18386              elLabel = this.getContainerEl().appendChild(document.createElement("label"));
18387              elLabel.htmlFor = checkboxId;
18388              elLabel.innerHTML = lang.isValue(checkboxOption.label) ?
18389                      checkboxOption.label : checkboxOption;
18390          }
18391          
18392          // Store the reference to the checkbox elements
18393          var allCheckboxes = [];
18394          for(j=0; j<len; j++) {
18395              allCheckboxes[allCheckboxes.length] = this.getContainerEl().childNodes[j*2];
18396          }
18397          this.checkboxes = allCheckboxes;
18398  
18399          if(this.disableBtns) {
18400              this.handleDisabledBtns();
18401          }
18402      }
18403      else {
18404          YAHOO.log("Could not find checkboxOptions", "error", this.toString());
18405      }
18406  },
18407  
18408  /**
18409   * After rendering form, if disabledBtns is set to true, then sets up a mechanism
18410   * to save input without them. 
18411   *
18412   * @method handleDisabledBtns
18413   */
18414  handleDisabledBtns : function() {
18415      Ev.addListener(this.getContainerEl(), "click", function(v){
18416          if(Ev.getTarget(v).tagName.toLowerCase() === "input") {
18417              // Save on blur
18418              this.save();
18419          }
18420      }, this, true);
18421  },
18422  
18423  /**
18424   * Resets CheckboxCellEditor UI to initial state.
18425   *
18426   * @method resetForm
18427   */
18428  resetForm : function() {
18429      // Normalize to array
18430      var originalValues = lang.isArray(this.value) ? this.value : [this.value];
18431      
18432      // Match checks to value
18433      for(var i=0, j=this.checkboxes.length; i<j; i++) {
18434          this.checkboxes[i].checked = false;
18435          for(var k=0, len=originalValues.length; k<len; k++) {
18436              if(this.checkboxes[i].value == originalValues[k]) {
18437                  this.checkboxes[i].checked = true;
18438              }
18439          }
18440      }
18441  },
18442  
18443  /**
18444   * Sets focus in CheckboxCellEditor.
18445   *
18446   * @method focus
18447   */
18448  focus : function() {
18449      this.checkboxes[0].focus();
18450  },
18451  
18452  /**
18453   * Retrieves input value from CheckboxCellEditor.
18454   *
18455   * @method getInputValue
18456   */
18457  getInputValue : function() {
18458      var checkedValues = [];
18459      for(var i=0, j=this.checkboxes.length; i<j; i++) {
18460          if(this.checkboxes[i].checked) {
18461              checkedValues[checkedValues.length] = this.checkboxes[i].value;
18462          }
18463      }  
18464      return checkedValues;
18465  }
18466  
18467  });
18468  
18469  // Copy static members to CheckboxCellEditor class
18470  lang.augmentObject(widget.CheckboxCellEditor, BCE);
18471  
18472  
18473  
18474  
18475  
18476  
18477  
18478  
18479  /****************************************************************************/
18480  /****************************************************************************/
18481  /****************************************************************************/
18482      
18483  /**
18484   * The DataCellEditor class provides functionality for inline editing
18485   * DataTable cell data with a YUI Calendar.
18486   *
18487   * @namespace YAHOO.widget
18488   * @class DateCellEditor
18489   * @extends YAHOO.widget.BaseCellEditor 
18490   * @constructor
18491   * @param oConfigs {Object} (Optional) Object literal of configs.
18492   */
18493  widget.DateCellEditor = function(oConfigs) {
18494      oConfigs = oConfigs || {};
18495      this._sId = this._sId || Dom.generateId(null, "yui-dateceditor"); // "yui-dateceditor" + YAHOO.widget.BaseCellEditor._nCount++;
18496      YAHOO.widget.BaseCellEditor._nCount++;
18497      widget.DateCellEditor.superclass.constructor.call(this, oConfigs.type || "date", oConfigs);
18498  };
18499  
18500  // CheckboxCellEditor extends BaseCellEditor
18501  lang.extend(widget.DateCellEditor, BCE, {
18502  
18503  /////////////////////////////////////////////////////////////////////////////
18504  //
18505  // DateCellEditor public properties
18506  //
18507  /////////////////////////////////////////////////////////////////////////////
18508  /**
18509   * Reference to Calendar instance.
18510   *
18511   * @property calendar
18512   * @type YAHOO.widget.Calendar
18513   */
18514  calendar : null,
18515  
18516  /**
18517   * Configs for the calendar instance, to be passed to Calendar constructor.
18518   *
18519   * @property calendarOptions
18520   * @type Object
18521   */
18522  calendarOptions : null,
18523  
18524  /**
18525   * Default value.
18526   *
18527   * @property defaultValue
18528   * @type Date
18529   * @default new Date()
18530   */
18531  defaultValue : new Date(),
18532  
18533  
18534  /////////////////////////////////////////////////////////////////////////////
18535  //
18536  // DateCellEditor public methods
18537  //
18538  /////////////////////////////////////////////////////////////////////////////
18539  
18540  /**
18541   * Render a Calendar.
18542   *
18543   * @method renderForm
18544   */
18545  renderForm : function() {
18546      // Calendar widget
18547      if(YAHOO.widget.Calendar) {
18548          var calContainer = this.getContainerEl().appendChild(document.createElement("div"));
18549          calContainer.id = this.getId() + "-dateContainer"; // Needed for Calendar constructor
18550          var calendar =
18551                  new YAHOO.widget.Calendar(this.getId() + "-date",
18552                  calContainer.id, this.calendarOptions);
18553          calendar.render();
18554          calContainer.style.cssFloat = "none";
18555          
18556          // Bug 2528576
18557          calendar.hideEvent.subscribe(function() {this.cancel();}, this, true);
18558  
18559          if(ua.ie) {
18560              var calFloatClearer = this.getContainerEl().appendChild(document.createElement("div"));
18561              calFloatClearer.style.clear = "both";
18562          }
18563          
18564          this.calendar = calendar;
18565  
18566          if(this.disableBtns) {
18567              this.handleDisabledBtns();
18568          }
18569      }
18570      else {
18571          YAHOO.log("Could not find YUI Calendar", "error", this.toString());
18572      }
18573      
18574  },
18575  
18576  /**
18577   * After rendering form, if disabledBtns is set to true, then sets up a mechanism
18578   * to save input without them. 
18579   *
18580   * @method handleDisabledBtns
18581   */
18582  handleDisabledBtns : function() {
18583      this.calendar.selectEvent.subscribe(function(v){
18584          // Save on select
18585          this.save();
18586      }, this, true);
18587  },
18588  
18589  /**
18590   * Resets DateCellEditor UI to initial state.
18591   *
18592   * @method resetForm
18593   */
18594  resetForm : function() {
18595      var value = this.value || (new Date());
18596      this.calendar.select(value);
18597      this.calendar.cfg.setProperty("pagedate",value,false);
18598      this.calendar.render();
18599      // Bug 2528576
18600      this.calendar.show();
18601  },
18602  
18603  /**
18604   * Sets focus in DateCellEditor.
18605   *
18606   * @method focus
18607   */
18608  focus : function() {
18609      // To be impmlemented by subclass
18610  },
18611  
18612  /**
18613   * Retrieves input value from DateCellEditor.
18614   *
18615   * @method getInputValue
18616   */
18617  getInputValue : function() {
18618      return this.calendar.getSelectedDates()[0];
18619  }
18620  
18621  });
18622  
18623  // Copy static members to DateCellEditor class
18624  lang.augmentObject(widget.DateCellEditor, BCE);
18625  
18626  
18627  
18628  
18629  
18630  
18631  
18632  
18633  
18634  /****************************************************************************/
18635  /****************************************************************************/
18636  /****************************************************************************/
18637      
18638  /**
18639   * The DropdownCellEditor class provides functionality for inline editing
18640   * DataTable cell data a SELECT element.
18641   *
18642   * @namespace YAHOO.widget
18643   * @class DropdownCellEditor
18644   * @extends YAHOO.widget.BaseCellEditor 
18645   * @constructor
18646   * @param oConfigs {Object} (Optional) Object literal of configs.
18647   */
18648  widget.DropdownCellEditor = function(oConfigs) {
18649      oConfigs = oConfigs || {};
18650      this._sId = this._sId || Dom.generateId(null, "yui-dropdownceditor"); // "yui-dropdownceditor" + YAHOO.widget.BaseCellEditor._nCount++;
18651      YAHOO.widget.BaseCellEditor._nCount++;
18652      widget.DropdownCellEditor.superclass.constructor.call(this, oConfigs.type || "dropdown", oConfigs);
18653  };
18654  
18655  // DropdownCellEditor extends BaseCellEditor
18656  lang.extend(widget.DropdownCellEditor, BCE, {
18657  
18658  /////////////////////////////////////////////////////////////////////////////
18659  //
18660  // DropdownCellEditor public properties
18661  //
18662  /////////////////////////////////////////////////////////////////////////////
18663  /**
18664   * Array of dropdown values. Can either be a simple array (e.g.,
18665   * ["Alabama","Alaska","Arizona","Arkansas"]) or a an array of objects (e.g., 
18666   * [{label:"Alabama", value:"AL"}, {label:"Alaska", value:"AK"},
18667   * {label:"Arizona", value:"AZ"}, {label:"Arkansas", value:"AR"}]). String
18668   * values are treated as markup and inserted into the DOM as innerHTML.
18669   *
18670   * @property dropdownOptions
18671   * @type HTML[] | Object[]
18672   */
18673  dropdownOptions : null,
18674  
18675  /**
18676   * Reference to Dropdown element.
18677   *
18678   * @property dropdown
18679   * @type HTMLElement
18680   */
18681  dropdown : null,
18682  
18683  /**
18684   * Enables multi-select.
18685   *
18686   * @property multiple
18687   * @type Boolean
18688   */
18689  multiple : false,
18690  
18691  /**
18692   * Specifies number of visible options.
18693   *
18694   * @property size
18695   * @type Number
18696   */
18697  size : null,
18698  
18699  /////////////////////////////////////////////////////////////////////////////
18700  //
18701  // DropdownCellEditor public methods
18702  //
18703  /////////////////////////////////////////////////////////////////////////////
18704  
18705  /**
18706   * Render a form with select element.
18707   *
18708   * @method renderForm
18709   */
18710  renderForm : function() {
18711      var elDropdown = this.getContainerEl().appendChild(document.createElement("select"));
18712      elDropdown.style.zoom = 1;
18713      if(this.multiple) {
18714          elDropdown.multiple = "multiple";
18715      }
18716      if(lang.isNumber(this.size)) {
18717          elDropdown.size = this.size;
18718      }
18719      this.dropdown = elDropdown;
18720      
18721      if(lang.isArray(this.dropdownOptions)) {
18722          var dropdownOption, elOption;
18723          for(var i=0, j=this.dropdownOptions.length; i<j; i++) {
18724              dropdownOption = this.dropdownOptions[i];
18725              elOption = document.createElement("option");
18726              elOption.value = (lang.isValue(dropdownOption.value)) ?
18727                      dropdownOption.value : dropdownOption;
18728              elOption.innerHTML = (lang.isValue(dropdownOption.label)) ?
18729                      dropdownOption.label : dropdownOption;
18730              elOption = elDropdown.appendChild(elOption);
18731          }
18732          
18733          if(this.disableBtns) {
18734              this.handleDisabledBtns();
18735          }
18736      }
18737  },
18738  
18739  /**
18740   * After rendering form, if disabledBtns is set to true, then sets up a mechanism
18741   * to save input without them. 
18742   *
18743   * @method handleDisabledBtns
18744   */
18745  handleDisabledBtns : function() {
18746      // Save on blur for multi-select
18747      if(this.multiple) {
18748          Ev.addListener(this.dropdown, "blur", function(v){
18749              // Save on change
18750              this.save();
18751          }, this, true);
18752      }
18753      // Save on change for single-select
18754      else {
18755          if(!ua.ie) {
18756              Ev.addListener(this.dropdown, "change", function(v){
18757                  // Save on change
18758                  this.save();
18759              }, this, true);
18760          }
18761          else {
18762              // Bug 2529274: "change" event is not keyboard accessible in IE6
18763              Ev.addListener(this.dropdown, "blur", function(v){
18764                  this.save();
18765              }, this, true);
18766              Ev.addListener(this.dropdown, "click", function(v){
18767                  this.save();
18768              }, this, true);
18769          }
18770      }
18771  },
18772  
18773  /**
18774   * Resets DropdownCellEditor UI to initial state.
18775   *
18776   * @method resetForm
18777   */
18778  resetForm : function() {
18779      var allOptions = this.dropdown.options,
18780          i=0, j=allOptions.length;
18781  
18782      // Look for multi-select selections
18783      if(lang.isArray(this.value)) {
18784          var allValues = this.value,
18785              m=0, n=allValues.length,
18786              hash = {};
18787          // Reset all selections and stash options in a value hash
18788          for(; i<j; i++) {
18789              allOptions[i].selected = false;
18790              hash[allOptions[i].value] = allOptions[i];
18791          }
18792          for(; m<n; m++) {
18793              if(hash[allValues[m]]) {
18794                  hash[allValues[m]].selected = true;
18795              }
18796          }
18797      }
18798      // Only need to look for a single selection
18799      else {
18800          for(; i<j; i++) {
18801              if(this.value == allOptions[i].value) {
18802                  allOptions[i].selected = true;
18803              }
18804          }
18805      }
18806  },
18807  
18808  /**
18809   * Sets focus in DropdownCellEditor.
18810   *
18811   * @method focus
18812   */
18813  focus : function() {
18814      this.getDataTable()._focusEl(this.dropdown);
18815  },
18816  
18817  /**
18818   * Retrieves input value from DropdownCellEditor.
18819   *
18820   * @method getInputValue
18821   */
18822  getInputValue : function() {
18823      var allOptions = this.dropdown.options;
18824      
18825      // Look for multiple selections
18826      if(this.multiple) {
18827          var values = [],
18828              i=0, j=allOptions.length;
18829          for(; i<j; i++) {
18830              if(allOptions[i].selected) {
18831                  values.push(allOptions[i].value);
18832              }
18833          }
18834          return values;
18835      }
18836      // Only need to look for single selection
18837      else {
18838          return allOptions[allOptions.selectedIndex].value;
18839      }
18840  }
18841  
18842  });
18843  
18844  // Copy static members to DropdownCellEditor class
18845  lang.augmentObject(widget.DropdownCellEditor, BCE);
18846  
18847  
18848  
18849  
18850  
18851  
18852  /****************************************************************************/
18853  /****************************************************************************/
18854  /****************************************************************************/
18855      
18856  /**
18857   * The RadioCellEditor class provides functionality for inline editing
18858   * DataTable cell data with radio buttons.
18859   *
18860   * @namespace YAHOO.widget
18861   * @class RadioCellEditor
18862   * @extends YAHOO.widget.BaseCellEditor 
18863   * @constructor
18864   * @param oConfigs {Object} (Optional) Object literal of configs.
18865   */
18866  widget.RadioCellEditor = function(oConfigs) {
18867      oConfigs = oConfigs || {};
18868      this._sId = this._sId || Dom.generateId(null, "yui-radioceditor"); // "yui-radioceditor" + YAHOO.widget.BaseCellEditor._nCount++;
18869      YAHOO.widget.BaseCellEditor._nCount++;
18870      widget.RadioCellEditor.superclass.constructor.call(this, oConfigs.type || "radio", oConfigs);
18871  };
18872  
18873  // RadioCellEditor extends BaseCellEditor
18874  lang.extend(widget.RadioCellEditor, BCE, {
18875  
18876  /////////////////////////////////////////////////////////////////////////////
18877  //
18878  // RadioCellEditor public properties
18879  //
18880  /////////////////////////////////////////////////////////////////////////////
18881  /**
18882   * Reference to radio elements.
18883   *
18884   * @property radios
18885   * @type HTMLElement[]
18886   */
18887  radios : null,
18888  
18889  /**
18890   * Array of radio values. Can either be a simple array (e.g., ["yes","no","maybe"])
18891   * or a an array of objects (e.g., [{label:"yes", value:1}, {label:"no", value:-1},
18892   * {label:"maybe", value:0}]). String values are treated as markup and inserted
18893   * into the DOM as innerHTML.
18894   *
18895   * @property radioOptions
18896   * @type HTML[] | Object[]
18897   */
18898  radioOptions : null,
18899  
18900  /////////////////////////////////////////////////////////////////////////////
18901  //
18902  // RadioCellEditor public methods
18903  //
18904  /////////////////////////////////////////////////////////////////////////////
18905  
18906  /**
18907   * Render a form with input(s) type=radio.
18908   *
18909   * @method renderForm
18910   */
18911  renderForm : function() {
18912      if(lang.isArray(this.radioOptions)) {
18913          var radioOption, radioValue, radioId, elLabel;
18914          
18915          // Create the radio buttons in an IE-friendly way
18916          for(var i=0, len=this.radioOptions.length; i<len; i++) {
18917              radioOption = this.radioOptions[i];
18918              radioValue = lang.isValue(radioOption.value) ?
18919                      radioOption.value : radioOption;
18920              radioId = this.getId() + "-radio" + i;
18921              this.getContainerEl().innerHTML += "<input type=\"radio\"" +
18922                      " name=\"" + this.getId() + "\"" +
18923                      " value=\"" + radioValue + "\"" +
18924                      " id=\"" +  radioId + "\" />"; // Needed for label
18925              
18926              // Create the labels in an IE-friendly way
18927              elLabel = this.getContainerEl().appendChild(document.createElement("label"));
18928              elLabel.htmlFor = radioId;
18929              elLabel.innerHTML = (lang.isValue(radioOption.label)) ?
18930                      radioOption.label : radioOption;
18931          }
18932          
18933          // Store the reference to the checkbox elements
18934          var allRadios = [],
18935              elRadio;
18936          for(var j=0; j<len; j++) {
18937              elRadio = this.getContainerEl().childNodes[j*2];
18938              allRadios[allRadios.length] = elRadio;
18939          }
18940          this.radios = allRadios;
18941  
18942          if(this.disableBtns) {
18943              this.handleDisabledBtns();
18944          }
18945      }
18946      else {
18947          YAHOO.log("Could not find radioOptions", "error", this.toString());
18948      }
18949  },
18950  
18951  /**
18952   * After rendering form, if disabledBtns is set to true, then sets up a mechanism
18953   * to save input without them. 
18954   *
18955   * @method handleDisabledBtns
18956   */
18957  handleDisabledBtns : function() {
18958      Ev.addListener(this.getContainerEl(), "click", function(v){
18959          if(Ev.getTarget(v).tagName.toLowerCase() === "input") {
18960              // Save on blur
18961              this.save();
18962          }
18963      }, this, true);
18964  },
18965  
18966  /**
18967   * Resets RadioCellEditor UI to initial state.
18968   *
18969   * @method resetForm
18970   */
18971  resetForm : function() {
18972      for(var i=0, j=this.radios.length; i<j; i++) {
18973          var elRadio = this.radios[i];
18974          if(this.value == elRadio.value) {
18975              elRadio.checked = true;
18976              return;
18977          }
18978      }
18979  },
18980  
18981  /**
18982   * Sets focus in RadioCellEditor.
18983   *
18984   * @method focus
18985   */
18986  focus : function() {
18987      for(var i=0, j=this.radios.length; i<j; i++) {
18988          if(this.radios[i].checked) {
18989              this.radios[i].focus();
18990              return;
18991          }
18992      }
18993  },
18994  
18995  /**
18996   * Retrieves input value from RadioCellEditor.
18997   *
18998   * @method getInputValue
18999   */
19000  getInputValue : function() {
19001      for(var i=0, j=this.radios.length; i<j; i++) {
19002          if(this.radios[i].checked) {
19003              return this.radios[i].value;
19004          }
19005      }
19006  }
19007  
19008  });
19009  
19010  // Copy static members to RadioCellEditor class
19011  lang.augmentObject(widget.RadioCellEditor, BCE);
19012  
19013  
19014  
19015  
19016  
19017  
19018  /****************************************************************************/
19019  /****************************************************************************/
19020  /****************************************************************************/
19021      
19022  /**
19023   * The TextareaCellEditor class provides functionality for inline editing
19024   * DataTable cell data with a TEXTAREA element.
19025   *
19026   * @namespace YAHOO.widget
19027   * @class TextareaCellEditor
19028   * @extends YAHOO.widget.BaseCellEditor 
19029   * @constructor
19030   * @param oConfigs {Object} (Optional) Object literal of configs.
19031   */
19032  widget.TextareaCellEditor = function(oConfigs) {
19033      oConfigs = oConfigs || {};
19034      this._sId = this._sId || Dom.generateId(null, "yui-textareaceditor");// "yui-textareaceditor" + ;
19035      YAHOO.widget.BaseCellEditor._nCount++;
19036      widget.TextareaCellEditor.superclass.constructor.call(this, oConfigs.type || "textarea", oConfigs);
19037  };
19038  
19039  // TextareaCellEditor extends BaseCellEditor
19040  lang.extend(widget.TextareaCellEditor, BCE, {
19041  
19042  /////////////////////////////////////////////////////////////////////////////
19043  //
19044  // TextareaCellEditor public properties
19045  //
19046  /////////////////////////////////////////////////////////////////////////////
19047  /**
19048   * Reference to textarea element.
19049   *
19050   * @property textarea
19051   * @type HTMLElement
19052   */
19053  textarea : null,
19054  
19055  
19056  /////////////////////////////////////////////////////////////////////////////
19057  //
19058  // TextareaCellEditor public methods
19059  //
19060  /////////////////////////////////////////////////////////////////////////////
19061  
19062  /**
19063   * Render a form with textarea.
19064   *
19065   * @method renderForm
19066   */
19067  renderForm : function() {
19068      var elTextarea = this.getContainerEl().appendChild(document.createElement("textarea"));
19069      this.textarea = elTextarea;
19070  
19071      if(this.disableBtns) {
19072          this.handleDisabledBtns();
19073      }
19074  },
19075  
19076  /**
19077   * After rendering form, if disabledBtns is set to true, then sets up a mechanism
19078   * to save input without them. 
19079   *
19080   * @method handleDisabledBtns
19081   */
19082  handleDisabledBtns : function() {
19083      Ev.addListener(this.textarea, "blur", function(v){
19084          // Save on blur
19085          this.save();
19086      }, this, true);        
19087  },
19088  
19089  /**
19090   * Moves TextareaCellEditor UI to a cell.
19091   *
19092   * @method move
19093   */
19094  move : function() {
19095      this.textarea.style.width = this.getTdEl().offsetWidth + "px";
19096      this.textarea.style.height = "3em";
19097      YAHOO.widget.TextareaCellEditor.superclass.move.call(this);
19098  },
19099  
19100  /**
19101   * Resets TextareaCellEditor UI to initial state.
19102   *
19103   * @method resetForm
19104   */
19105  resetForm : function() {
19106      this.textarea.value = this.value;
19107  },
19108  
19109  /**
19110   * Sets focus in TextareaCellEditor.
19111   *
19112   * @method focus
19113   */
19114  focus : function() {
19115      // Bug 2303181, Bug 2263600
19116      this.getDataTable()._focusEl(this.textarea);
19117      this.textarea.select();
19118  },
19119  
19120  /**
19121   * Retrieves input value from TextareaCellEditor.
19122   *
19123   * @method getInputValue
19124   */
19125  getInputValue : function() {
19126      return this.textarea.value;
19127  }
19128  
19129  });
19130  
19131  // Copy static members to TextareaCellEditor class
19132  lang.augmentObject(widget.TextareaCellEditor, BCE);
19133  
19134  
19135  
19136  
19137  
19138  
19139  
19140  
19141  
19142  /****************************************************************************/
19143  /****************************************************************************/
19144  /****************************************************************************/
19145      
19146  /**
19147   * The TextboxCellEditor class provides functionality for inline editing
19148   * DataTable cell data with an INPUT TYPE=TEXT element.
19149   *
19150   * @namespace YAHOO.widget
19151   * @class TextboxCellEditor
19152   * @extends YAHOO.widget.BaseCellEditor 
19153   * @constructor
19154   * @param oConfigs {Object} (Optional) Object literal of configs.
19155   */
19156  widget.TextboxCellEditor = function(oConfigs) {
19157      oConfigs = oConfigs || {};
19158      this._sId = this._sId || Dom.generateId(null, "yui-textboxceditor");// "yui-textboxceditor" + YAHOO.widget.BaseCellEditor._nCount++;
19159      YAHOO.widget.BaseCellEditor._nCount++;
19160      widget.TextboxCellEditor.superclass.constructor.call(this, oConfigs.type || "textbox", oConfigs);
19161  };
19162  
19163  // TextboxCellEditor extends BaseCellEditor
19164  lang.extend(widget.TextboxCellEditor, BCE, {
19165  
19166  /////////////////////////////////////////////////////////////////////////////
19167  //
19168  // TextboxCellEditor public properties
19169  //
19170  /////////////////////////////////////////////////////////////////////////////
19171  /**
19172   * Reference to the textbox element.
19173   *
19174   * @property textbox
19175   */
19176  textbox : null,
19177  
19178  /////////////////////////////////////////////////////////////////////////////
19179  //
19180  // TextboxCellEditor public methods
19181  //
19182  /////////////////////////////////////////////////////////////////////////////
19183  
19184  /**
19185   * Render a form with input type=text.
19186   *
19187   * @method renderForm
19188   */
19189  renderForm : function() {
19190      var elTextbox;
19191      // Bug 1802582: SF3/Mac needs a form element wrapping the input
19192      if(ua.webkit>420) {
19193          elTextbox = this.getContainerEl().appendChild(document.createElement("form")).appendChild(document.createElement("input"));
19194      }
19195      else {
19196          elTextbox = this.getContainerEl().appendChild(document.createElement("input"));
19197      }
19198      elTextbox.type = "text";
19199      this.textbox = elTextbox;
19200  
19201      // Save on enter by default
19202      // Bug: 1802582 Set up a listener on each textbox to track on keypress
19203      // since SF/OP can't preventDefault on keydown
19204      Ev.addListener(elTextbox, "keypress", function(v){
19205          if((v.keyCode === 13)) {
19206              // Prevent form submit
19207              YAHOO.util.Event.preventDefault(v);
19208              this.save();
19209          }
19210      }, this, true);
19211  
19212      if(this.disableBtns) {
19213          // By default this is no-op since enter saves by default
19214          this.handleDisabledBtns();
19215      }
19216  },
19217  
19218  /**
19219   * Moves TextboxCellEditor UI to a cell.
19220   *
19221   * @method move
19222   */
19223  move : function() {
19224      this.textbox.style.width = this.getTdEl().offsetWidth + "px";
19225      widget.TextboxCellEditor.superclass.move.call(this);
19226  },
19227  
19228  /**
19229   * Resets TextboxCellEditor UI to initial state.
19230   *
19231   * @method resetForm
19232   */
19233  resetForm : function() {
19234      this.textbox.value = lang.isValue(this.value) ? this.value.toString() : "";
19235  },
19236  
19237  /**
19238   * Sets focus in TextboxCellEditor.
19239   *
19240   * @method focus
19241   */
19242  focus : function() {
19243      // Bug 2303181, Bug 2263600
19244      this.getDataTable()._focusEl(this.textbox);
19245      this.textbox.select();
19246  },
19247  
19248  /**
19249   * Returns new value for TextboxCellEditor.
19250   *
19251   * @method getInputValue
19252   */
19253  getInputValue : function() {
19254      return this.textbox.value;
19255  }
19256  
19257  });
19258  
19259  // Copy static members to TextboxCellEditor class
19260  lang.augmentObject(widget.TextboxCellEditor, BCE);
19261  
19262  
19263  
19264  
19265  
19266  
19267  
19268  /////////////////////////////////////////////////////////////////////////////
19269  //
19270  // DataTable extension
19271  //
19272  /////////////////////////////////////////////////////////////////////////////
19273  
19274  /**
19275   * CellEditor subclasses.
19276   * @property DataTable.Editors
19277   * @type Object
19278   * @static
19279   */
19280  DT.Editors = {
19281      checkbox : widget.CheckboxCellEditor,
19282      "date"   : widget.DateCellEditor,
19283      dropdown : widget.DropdownCellEditor,
19284      radio    : widget.RadioCellEditor,
19285      textarea : widget.TextareaCellEditor,
19286      textbox  : widget.TextboxCellEditor
19287  };
19288  
19289  /****************************************************************************/
19290  /****************************************************************************/
19291  /****************************************************************************/
19292      
19293  /**
19294   * Factory class for instantiating a BaseCellEditor subclass.
19295   *
19296   * @namespace YAHOO.widget
19297   * @class CellEditor
19298   * @extends YAHOO.widget.BaseCellEditor 
19299   * @constructor
19300   * @param sType {String} Type indicator, to map to YAHOO.widget.DataTable.Editors.
19301   * @param oConfigs {Object} (Optional) Object literal of configs.
19302   */
19303  widget.CellEditor = function(sType, oConfigs) {
19304      // Point to one of the subclasses
19305      if(sType && DT.Editors[sType]) {
19306          lang.augmentObject(BCE, DT.Editors[sType]);
19307          return new DT.Editors[sType](oConfigs);
19308      }
19309      else {
19310          return new BCE(null, oConfigs);
19311      }
19312  };
19313  
19314  var CE = widget.CellEditor;
19315  
19316  // Copy static members to CellEditor class
19317  lang.augmentObject(CE, BCE);
19318  
19319  
19320  })();
19321  
19322  YAHOO.register("datatable", YAHOO.widget.DataTable, {version: "2.9.0", build: "2800"});
19323  
19324  }, '2.9.0' ,{"requires": ["yui2-yahoo", "yui2-dom", "yui2-event", "yui2-skin-sam-datatable", "yui2-element", "yui2-datasource"], "optional": ["yui2-skin-sam-paginator", "yui2-paginator", "yui2-dragdrop", "yui2-skin-sam-calendar", "yui2-calendar"]});


Generated: Thu Aug 11 10:00:09 2016 Cross-referenced by PHPXref 0.7.1