[ Index ]

PHP Cross Reference of Unnamed Project

title

Body

[close]

/lib/yuilib/2in3/2.9.0/build/yui2-datatable/ -> yui2-datatable.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                  return false;
 360              }
 361  
 362  
 363              if (type == "mouseenter" || type == "mouseleave") {
 364  
 365                  if (!Event._createMouseDelegate) {
 366                      return false;
 367                  }
 368  
 369                  //    Look up the real event--either mouseover or mouseout
 370                  sType = Event._getType(type);
 371  
 372                  fnMouseDelegate = Event._createMouseDelegate(fn, obj, overrideContext);
 373  
 374                  fnDelegate = Event._createDelegate(function (event, matchedEl, container) {
 375  
 376                      return fnMouseDelegate.call(matchedEl, event, container);
 377  
 378                  }, filter, obj, overrideContext);
 379  
 380              }
 381              else {
 382  
 383                  fnDelegate = Event._createDelegate(fn, filter, obj, overrideContext);
 384  
 385              }
 386  
 387              delegates.push([container, sType, fn, fnDelegate]);
 388              
 389              return Event.on(container, sType, fnDelegate);
 390  
 391          },
 392  
 393  
 394          /**
 395           * Removes a delegated event listener.
 396           *
 397           * @method removeDelegate
 398           *
 399           * @param {String|HTMLElement|Array|NodeList} container An id, an element 
 400           *  reference, or a collection of ids and/or elements to remove
 401           *  the listener from.
 402           * @param {String} type The type of event to remove.
 403           * @param {Function} fn The method the event invokes.  If fn is
 404           *  undefined, then all event listeners for the type of event are 
 405           *  removed.
 406           * @return {boolean} Returns true if the unbind was successful, false 
 407           *  otherwise.
 408           * @static
 409           * @for Event
 410           */
 411          removeDelegate: function (container, type, fn) {
 412  
 413              var sType = type,
 414                  returnVal = false,
 415                  index,
 416                  cacheItem;
 417  
 418              //    Look up the real event--either mouseover or mouseout
 419              if (type == "mouseenter" || type == "mouseleave") {
 420                  sType = Event._getType(type);
 421              }
 422  
 423              index = Event._getCacheIndex(delegates, container, sType, fn);
 424  
 425              if (index >= 0) {
 426                  cacheItem = delegates[index];
 427              }
 428  
 429  
 430              if (container && cacheItem) {
 431  
 432                  returnVal = Event.removeListener(cacheItem[0], cacheItem[1], cacheItem[3]);
 433  
 434                  if (returnVal) {
 435                      delete delegates[index][2];
 436                      delete delegates[index][3];
 437                      delegates.splice(index, 1);
 438                  }        
 439          
 440              }
 441  
 442              return returnVal;
 443  
 444          }
 445          
 446      });
 447  
 448  }());
 449  
 450  
 451  /**
 452   * Augments the Event Utility with support for the mouseenter and mouseleave
 453   * events:  A mouseenter event fires the first time the mouse enters an
 454   * element; a mouseleave event first the first time the mouse leaves an
 455   * element.
 456   *
 457   * @module event-mouseenter
 458   * @title Event Utility mouseenter and mouseout Module
 459   * @namespace YAHOO.util
 460   * @requires event
 461   */
 462  
 463  (function () {
 464  
 465      var Event = YAHOO.util.Event,
 466          Lang = YAHOO.lang,
 467  
 468          addListener = Event.addListener,
 469          removeListener = Event.removeListener,
 470          getListeners = Event.getListeners,
 471  
 472          delegates = [],
 473  
 474          specialTypes = {
 475              mouseenter: "mouseover",
 476              mouseleave: "mouseout"
 477          },
 478  
 479          remove = function(el, type, fn) {
 480  
 481              var index = Event._getCacheIndex(delegates, el, type, fn),
 482                  cacheItem,
 483                  returnVal;
 484  
 485              if (index >= 0) {
 486                  cacheItem = delegates[index];
 487              }
 488  
 489              if (el && cacheItem) {
 490  
 491                  //    removeListener will translate the value of type
 492                  returnVal = removeListener.call(Event, cacheItem[0], type, cacheItem[3]);
 493  
 494                  if (returnVal) {
 495                      delete delegates[index][2];
 496                      delete delegates[index][3];
 497                      delegates.splice(index, 1);
 498                  }
 499  
 500              }
 501  
 502              return returnVal;
 503  
 504          };
 505  
 506  
 507      Lang.augmentObject(Event._specialTypes, specialTypes);
 508  
 509      Lang.augmentObject(Event, {
 510  
 511          /**
 512           * Creates a delegate function used to call mouseover and mouseleave
 513           * event listeners specified via the
 514           * <code>YAHOO.util.Event.addListener</code>
 515           * or <code>YAHOO.util.Event.on</code> method.
 516           *
 517           * @method _createMouseDelegate
 518           *
 519           * @param {Function} fn        The method (event listener) to call
 520           * @param {Object}   obj    An arbitrary object that will be
 521           *                             passed as a parameter to the listener
 522           * @param {Boolean|object}  overrideContext  If true, the value of the
 523           *                             obj parameter becomes the execution context
 524           *                          of the listener. If an object, this object
 525           *                          becomes the execution context.
 526           * @return {Function} Function that will call the event listener
 527           * specified by either the <code>YAHOO.util.Event.addListener</code>
 528           * or <code>YAHOO.util.Event.on</code> method.
 529           * @private
 530           * @static
 531           * @for Event
 532           */
 533          _createMouseDelegate: function (fn, obj, overrideContext) {
 534  
 535              return function (event, container) {
 536  
 537                  var el = this,
 538                      relatedTarget = Event.getRelatedTarget(event),
 539                      context,
 540                      args;
 541  
 542                  if (el != relatedTarget && !YAHOO.util.Dom.isAncestor(el, relatedTarget)) {
 543  
 544                      context = el;
 545  
 546                      if (overrideContext) {
 547                          if (overrideContext === true) {
 548                              context = obj;
 549                          } else {
 550                              context = overrideContext;
 551                          }
 552                      }
 553  
 554                      // The default args passed back to a mouseenter or
 555                      // mouseleave listener are: the event, and any object
 556                      // the user passed when subscribing
 557  
 558                      args = [event, obj];
 559  
 560                      // Add the element and delegation container as arguments
 561                      // when delegating mouseenter and mouseleave
 562  
 563                      if (container) {
 564                          args.splice(1, 0, el, container);
 565                      }
 566  
 567                      return fn.apply(context, args);
 568  
 569                  }
 570  
 571              };
 572  
 573          },
 574  
 575          addListener: function (el, type, fn, obj, overrideContext) {
 576  
 577              var fnDelegate,
 578                  returnVal;
 579  
 580              if (specialTypes[type]) {
 581  
 582                  fnDelegate = Event._createMouseDelegate(fn, obj, overrideContext);
 583  
 584                  fnDelegate.mouseDelegate = true;
 585  
 586                  delegates.push([el, type, fn, fnDelegate]);
 587  
 588                  //    addListener will translate the value of type
 589                  returnVal = addListener.call(Event, el, type, fnDelegate);
 590  
 591              }
 592              else {
 593                  returnVal = addListener.apply(Event, arguments);
 594              }
 595  
 596              return returnVal;
 597  
 598          },
 599  
 600          removeListener: function (el, type, fn) {
 601  
 602              var returnVal;
 603  
 604              if (specialTypes[type]) {
 605                  returnVal = remove.apply(Event, arguments);
 606              }
 607              else {
 608                  returnVal = removeListener.apply(Event, arguments);
 609              }
 610  
 611              return returnVal;
 612  
 613          },
 614  
 615          getListeners: function (el, type) {
 616  
 617              //    If the user specified the type as mouseover or mouseout,
 618              //    need to filter out those used by mouseenter and mouseleave.
 619              //    If the user specified the type as mouseenter or mouseleave,
 620              //    need to filter out the true mouseover and mouseout listeners.
 621  
 622              var listeners = [],
 623                  elListeners,
 624                  bMouseOverOrOut = (type === "mouseover" || type === "mouseout"),
 625                  bMouseDelegate,
 626                  i,
 627                  l;
 628  
 629              if (type && (bMouseOverOrOut || specialTypes[type])) {
 630  
 631                  elListeners = getListeners.call(Event, el, this._getType(type));
 632  
 633                  if (elListeners) {
 634  
 635                      for (i=elListeners.length-1; i>-1; i--) {
 636  
 637                          l = elListeners[i];
 638                          bMouseDelegate = l.fn.mouseDelegate;
 639  
 640                          if ((specialTypes[type] && bMouseDelegate) || (bMouseOverOrOut && !bMouseDelegate)) {
 641                              listeners.push(l);
 642                          }
 643  
 644                      }
 645  
 646                  }
 647  
 648              }
 649              else {
 650                  listeners = getListeners.apply(Event, arguments);
 651              }
 652  
 653              return (listeners && listeners.length) ? listeners : null;
 654  
 655          }
 656  
 657      }, true);
 658  
 659      Event.on = Event.addListener;
 660  
 661  }());
 662  YAHOO.register("event-mouseenter", YAHOO.util.Event, {version: "2.9.0", build: "2800"});
 663  
 664  var Y = YAHOO,
 665      Y_DOM = YAHOO.util.Dom,
 666      EMPTY_ARRAY = [],
 667      Y_UA = Y.env.ua,
 668      Y_Lang = Y.lang,
 669      Y_DOC = document,
 670      Y_DOCUMENT_ELEMENT = Y_DOC.documentElement,
 671  
 672      Y_DOM_inDoc = Y_DOM.inDocument,
 673      Y_mix = Y_Lang.augmentObject,
 674      Y_guid = Y_DOM.generateId,
 675  
 676      Y_getDoc = function(element) {
 677          var doc = Y_DOC;
 678          if (element) {
 679              doc = (element.nodeType === 9) ? element : // element === document
 680                  element.ownerDocument || // element === DOM node
 681                  element.document || // element === window
 682                  Y_DOC; // default
 683          }
 684  
 685          return doc;
 686      },
 687  
 688      Y_Array = function(o, startIdx) {
 689          var l, a, start = startIdx || 0;
 690  
 691          // IE errors when trying to slice HTMLElement collections
 692          try {
 693              return Array.prototype.slice.call(o, start);
 694          } catch (e) {
 695              a = [];
 696              l = o.length;
 697              for (; start < l; start++) {
 698                  a.push(o[start]);
 699              }
 700              return a;
 701          }
 702      },
 703  
 704      Y_DOM_allById = function(id, root) {
 705          root = root || Y_DOC;
 706          var nodes = [],
 707              ret = [],
 708              i,
 709              node;
 710  
 711          if (root.querySelectorAll) {
 712              ret = root.querySelectorAll('[id="' + id + '"]');
 713          } else if (root.all) {
 714              nodes = root.all(id);
 715  
 716              if (nodes) {
 717                  // root.all may return HTMLElement or HTMLCollection.
 718                  // some elements are also HTMLCollection (FORM, SELECT).
 719                  if (nodes.nodeName) {
 720                      if (nodes.id === id) { // avoid false positive on name
 721                          ret.push(nodes);
 722                          nodes = EMPTY_ARRAY; // done, no need to filter
 723                      } else { //  prep for filtering
 724                          nodes = [nodes];
 725                      }
 726                  }
 727  
 728                  if (nodes.length) {
 729                      // filter out matches on node.name
 730                      // and element.id as reference to element with id === 'id'
 731                      for (i = 0; node = nodes[i++];) {
 732                          if (node.id === id  ||
 733                                  (node.attributes && node.attributes.id &&
 734                                  node.attributes.id.value === id)) {
 735                              ret.push(node);
 736                          }
 737                      }
 738                  }
 739              }
 740          } else {
 741              ret = [Y_getDoc(root).getElementById(id)];
 742          }
 743  
 744          return ret;
 745      };
 746  
 747  /**
 748   * The selector-native module provides support for native querySelector
 749   * @module dom
 750   * @submodule selector-native
 751   * @for Selector
 752   */
 753  
 754  /**
 755   * Provides support for using CSS selectors to query the DOM
 756   * @class Selector
 757   * @static
 758   * @for Selector
 759   */
 760  
 761  var COMPARE_DOCUMENT_POSITION = 'compareDocumentPosition',
 762      OWNER_DOCUMENT = 'ownerDocument',
 763  
 764  Selector = {
 765      _foundCache: [],
 766  
 767      useNative: true,
 768  
 769      _compare: ('sourceIndex' in Y_DOCUMENT_ELEMENT) ?
 770          function(nodeA, nodeB) {
 771              var a = nodeA.sourceIndex,
 772                  b = nodeB.sourceIndex;
 773  
 774              if (a === b) {
 775                  return 0;
 776              } else if (a > b) {
 777                  return 1;
 778              }
 779  
 780              return -1;
 781  
 782          } : (Y_DOCUMENT_ELEMENT[COMPARE_DOCUMENT_POSITION] ?
 783          function(nodeA, nodeB) {
 784              if (nodeA[COMPARE_DOCUMENT_POSITION](nodeB) & 4) {
 785                  return -1;
 786              } else {
 787                  return 1;
 788              }
 789          } :
 790          function(nodeA, nodeB) {
 791              var rangeA, rangeB, compare;
 792              if (nodeA && nodeB) {
 793                  rangeA = nodeA[OWNER_DOCUMENT].createRange();
 794                  rangeA.setStart(nodeA, 0);
 795                  rangeB = nodeB[OWNER_DOCUMENT].createRange();
 796                  rangeB.setStart(nodeB, 0);
 797                  compare = rangeA.compareBoundaryPoints(1, rangeB); // 1 === Range.START_TO_END
 798              }
 799  
 800              return compare;
 801  
 802      }),
 803  
 804      _sort: function(nodes) {
 805          if (nodes) {
 806              nodes = Y_Array(nodes, 0, true);
 807              if (nodes.sort) {
 808                  nodes.sort(Selector._compare);
 809              }
 810          }
 811  
 812          return nodes;
 813      },
 814  
 815      _deDupe: function(nodes) {
 816          var ret = [],
 817              i, node;
 818  
 819          for (i = 0; (node = nodes[i++]);) {
 820              if (!node._found) {
 821                  ret[ret.length] = node;
 822                  node._found = true;
 823              }
 824          }
 825  
 826          for (i = 0; (node = ret[i++]);) {
 827              node._found = null;
 828              node.removeAttribute('_found');
 829          }
 830  
 831          return ret;
 832      },
 833  
 834      /**
 835       * Retrieves a set of nodes based on a given CSS selector.
 836       * @method query
 837       *
 838       * @param {string} selector The CSS Selector to test the node against.
 839       * @param {HTMLElement} root optional An HTMLElement to start the query from. Defaults to Y.config.doc
 840       * @param {Boolean} firstOnly optional Whether or not to return only the first match.
 841       * @return {Array} An array of nodes that match the given selector.
 842       * @static
 843       */
 844      query: function(selector, root, firstOnly, skipNative) {
 845          if (typeof root == 'string') {
 846              root = Y_DOM.get(root);
 847              if (!root) {
 848                  return (firstOnly) ? null : [];
 849              }
 850          } else {
 851              root = root || Y_DOC;
 852          }
 853  
 854          var ret = [],
 855              useNative = (Selector.useNative && Y_DOC.querySelector && !skipNative),
 856              queries = [[selector, root]],
 857              query,
 858              result,
 859              i,
 860              fn = (useNative) ? Selector._nativeQuery : Selector._bruteQuery;
 861  
 862          if (selector && fn) {
 863              // split group into seperate queries
 864              if (!skipNative && // already done if skipping
 865                      (!useNative || root.tagName)) { // split native when element scoping is needed
 866                  queries = Selector._splitQueries(selector, root);
 867              }
 868  
 869              for (i = 0; (query = queries[i++]);) {
 870                  result = fn(query[0], query[1], firstOnly);
 871                  if (!firstOnly) { // coerce DOM Collection to Array
 872                      result = Y_Array(result, 0, true);
 873                  }
 874                  if (result) {
 875                      ret = ret.concat(result);
 876                  }
 877              }
 878  
 879              if (queries.length > 1) { // remove dupes and sort by doc order
 880                  ret = Selector._sort(Selector._deDupe(ret));
 881              }
 882          }
 883  
 884          Y.log('query: ' + selector + ' returning: ' + ret.length, 'info', 'Selector');
 885          return (firstOnly) ? (ret[0] || null) : ret;
 886  
 887      },
 888  
 889      // allows element scoped queries to begin with combinator
 890      // e.g. query('> p', document.body) === query('body > p')
 891      _splitQueries: function(selector, node) {
 892          var groups = selector.split(','),
 893              queries = [],
 894              prefix = '',
 895              i, len;
 896  
 897          if (node) {
 898              // enforce for element scoping
 899              if (node.tagName) {
 900                  node.id = node.id || Y_guid();
 901                  prefix = '[id="' + node.id + '"] ';
 902              }
 903  
 904              for (i = 0, len = groups.length; i < len; ++i) {
 905                  selector =  prefix + groups[i];
 906                  queries.push([selector, node]);
 907              }
 908          }
 909  
 910          return queries;
 911      },
 912  
 913      _nativeQuery: function(selector, root, one) {
 914          if (Y_UA.webkit && selector.indexOf(':checked') > -1 &&
 915                  (Selector.pseudos && Selector.pseudos.checked)) { // webkit (chrome, safari) fails to find "selected"
 916              return Selector.query(selector, root, one, true); // redo with skipNative true to try brute query
 917          }
 918          try {
 919              //Y.log('trying native query with: ' + selector, 'info', 'selector-native');
 920              return root['querySelector' + (one ? '' : 'All')](selector);
 921          } catch(e) { // fallback to brute if available
 922              //Y.log('native query error; reverting to brute query with: ' + selector, 'info', 'selector-native');
 923              return Selector.query(selector, root, one, true); // redo with skipNative true
 924          }
 925      },
 926  
 927      filter: function(nodes, selector) {
 928          var ret = [],
 929              i, node;
 930  
 931          if (nodes && selector) {
 932              for (i = 0; (node = nodes[i++]);) {
 933                  if (Selector.test(node, selector)) {
 934                      ret[ret.length] = node;
 935                  }
 936              }
 937          } else {
 938              Y.log('invalid filter input (nodes: ' + nodes +
 939                      ', selector: ' + selector + ')', 'warn', 'Selector');
 940          }
 941  
 942          return ret;
 943      },
 944  
 945      test: function(node, selector, root) {
 946          var ret = false,
 947              groups = selector.split(','),
 948              useFrag = false,
 949              parent,
 950              item,
 951              items,
 952              frag,
 953              i, j, group;
 954  
 955          if (node && node.tagName) { // only test HTMLElements
 956  
 957              // we need a root if off-doc
 958              if (!root && !Y_DOM_inDoc(node)) {
 959                  parent = node.parentNode;
 960                  if (parent) {
 961                      root = parent;
 962                  } else { // only use frag when no parent to query
 963                      frag = node[OWNER_DOCUMENT].createDocumentFragment();
 964                      frag.appendChild(node);
 965                      root = frag;
 966                      useFrag = true;
 967                  }
 968              }
 969              root = root || node[OWNER_DOCUMENT];
 970  
 971              if (!node.id) {
 972                  node.id = Y_guid();
 973              }
 974              for (i = 0; (group = groups[i++]);) { // TODO: off-dom test
 975                  group += '[id="' + node.id + '"]';
 976                  items = Selector.query(group, root);
 977  
 978                  for (j = 0; item = items[j++];) {
 979                      if (item === node) {
 980                          ret = true;
 981                          break;
 982                      }
 983                  }
 984                  if (ret) {
 985                      break;
 986                  }
 987              }
 988  
 989              if (useFrag) { // cleanup
 990                  frag.removeChild(node);
 991              }
 992          }
 993  
 994          return ret;
 995      }
 996  
 997  };
 998  
 999  YAHOO.util.Selector = Selector;
1000  /**
1001   * The selector module provides helper methods allowing CSS2 Selectors to be used with DOM elements.
1002   * @module dom
1003   * @submodule selector-css2
1004   * @for Selector
1005   */
1006  
1007  /**
1008   * Provides helper methods for collecting and filtering DOM elements.
1009   */
1010  
1011  var PARENT_NODE = 'parentNode',
1012      TAG_NAME = 'tagName',
1013      ATTRIBUTES = 'attributes',
1014      COMBINATOR = 'combinator',
1015      PSEUDOS = 'pseudos',
1016  
1017      SelectorCSS2 = {
1018          _reRegExpTokens: /([\^\$\?\[\]\*\+\-\.\(\)\|\\])/, // TODO: move?
1019          SORT_RESULTS: true,
1020          _children: function(node, tag) {
1021              var ret = node.children,
1022                  i,
1023                  children = [],
1024                  childNodes,
1025                  child;
1026  
1027              if (node.children && tag && node.children.tags) {
1028                  children = node.children.tags(tag);
1029              } else if ((!ret && node[TAG_NAME]) || (ret && tag)) { // only HTMLElements have children
1030                  childNodes = ret || node.childNodes;
1031                  ret = [];
1032                  for (i = 0; (child = childNodes[i++]);) {
1033                      if (child.tagName) {
1034                          if (!tag || tag === child.tagName) {
1035                              ret.push(child);
1036                          }
1037                      }
1038                  }
1039              }
1040  
1041              return ret || [];
1042          },
1043  
1044          _re: {
1045              //attr: /(\[.*\])/g,
1046              attr: /(\[[^\]]*\])/g,
1047              //esc: /\\[:\[][\w\d\]]*/gi,
1048              esc: /\\[:\[\]\(\)#\.\'\>+~"]/gi,
1049              //pseudos: /:([\-\w]+(?:\(?:['"]?(.+)['"]?\))*)/i
1050              pseudos: /(\([^\)]*\))/g
1051          },
1052  
1053          /**
1054           * Mapping of shorthand tokens to corresponding attribute selector
1055           * @property shorthand
1056           * @type object
1057           */
1058          shorthand: {
1059              //'\\#([^\\s\\\\(\\[:]*)': '[id=$1]',
1060              '\\#(-?[_a-z]+[-\\w\\uE000]*)': '[id=$1]',
1061              //'\\#([^\\s\\\.:\\[\\]]*)': '[id=$1]',
1062              //'\\.([^\\s\\\\(\\[:]*)': '[className=$1]'
1063              '\\.(-?[_a-z]+[-\\w\\uE000]*)': '[className~=$1]'
1064          },
1065  
1066          /**
1067           * List of operators and corresponding boolean functions.
1068           * These functions are passed the attribute and the current node's value of the attribute.
1069           * @property operators
1070           * @type object
1071           */
1072          operators: {
1073              '': function(node, attr) { return !!node.getAttribute(attr); }, // Just test for existence of attribute
1074              //'': '.+',
1075              //'=': '^{val}$', // equality
1076              '~=': '(?:^|\\s+){val}(?:\\s+|$)', // space-delimited
1077              '|=': '^{val}(?:-|$)' // optional hyphen-delimited
1078          },
1079  
1080          pseudos: {
1081             'first-child': function(node) {
1082                  return Selector._children(node[PARENT_NODE])[0] === node;
1083              }
1084          },
1085  
1086          _bruteQuery: function(selector, root, firstOnly) {
1087              var ret = [],
1088                  nodes = [],
1089                  tokens = Selector._tokenize(selector),
1090                  token = tokens[tokens.length - 1],
1091                  rootDoc = Y_getDoc(root),
1092                  child,
1093                  id,
1094                  className,
1095                  tagName;
1096  
1097  
1098              // if we have an initial ID, set to root when in document
1099              /*
1100              if (tokens[0] && rootDoc === root &&
1101                      (id = tokens[0].id) &&
1102                      rootDoc.getElementById(id)) {
1103                  root = rootDoc.getElementById(id);
1104              }
1105              */
1106  
1107              if (token) {
1108                  // prefilter nodes
1109                  id = token.id;
1110                  className = token.className;
1111                  tagName = token.tagName || '*';
1112  
1113                  if (root.getElementsByTagName) { // non-IE lacks DOM api on doc frags
1114                      // try ID first, unless no root.all && root not in document
1115                      // (root.all works off document, but not getElementById)
1116                      // TODO: move to allById?
1117                      if (id && (root.all || (root.nodeType === 9 || Y_DOM_inDoc(root)))) {
1118                          nodes = Y_DOM_allById(id, root);
1119                      // try className
1120                      } else if (className) {
1121                          nodes = root.getElementsByClassName(className);
1122                      } else { // default to tagName
1123                          nodes = root.getElementsByTagName(tagName);
1124                      }
1125  
1126                  } else { // brute getElementsByTagName('*')
1127                      child = root.firstChild;
1128                      while (child) {
1129                          if (child.tagName) { // only collect HTMLElements
1130                              nodes.push(child);
1131                          }
1132                          child = child.nextSilbing || child.firstChild;
1133                      }
1134                  }
1135                  if (nodes.length) {
1136                      ret = Selector._filterNodes(nodes, tokens, firstOnly);
1137                  }
1138              }
1139  
1140              return ret;
1141          },
1142  
1143          _filterNodes: function(nodes, tokens, firstOnly) {
1144              var i = 0,
1145                  j,
1146                  len = tokens.length,
1147                  n = len - 1,
1148                  result = [],
1149                  node = nodes[0],
1150                  tmpNode = node,
1151                  getters = Selector.getters,
1152                  operator,
1153                  combinator,
1154                  token,
1155                  path,
1156                  pass,
1157                  //FUNCTION = 'function',
1158                  value,
1159                  tests,
1160                  test;
1161  
1162              //do {
1163              for (i = 0; (tmpNode = node = nodes[i++]);) {
1164                  n = len - 1;
1165                  path = null;
1166  
1167                  testLoop:
1168                  while (tmpNode && tmpNode.tagName) {
1169                      token = tokens[n];
1170                      tests = token.tests;
1171                      j = tests.length;
1172                      if (j && !pass) {
1173                          while ((test = tests[--j])) {
1174                              operator = test[1];
1175                              if (getters[test[0]]) {
1176                                  value = getters[test[0]](tmpNode, test[0]);
1177                              } else {
1178                                  value = tmpNode[test[0]];
1179                                  // use getAttribute for non-standard attributes
1180                                  if (value === undefined && tmpNode.getAttribute) {
1181                                      value = tmpNode.getAttribute(test[0]);
1182                                  }
1183                              }
1184  
1185                              if ((operator === '=' && value !== test[2]) ||  // fast path for equality
1186                                  (typeof operator !== 'string' && // protect against String.test monkey-patch (Moo)
1187                                  operator.test && !operator.test(value)) ||  // regex test
1188                                  (!operator.test && // protect against RegExp as function (webkit)
1189                                          typeof operator === 'function' && !operator(tmpNode, test[0], test[2]))) { // function test
1190  
1191                                  // skip non element nodes or non-matching tags
1192                                  if ((tmpNode = tmpNode[path])) {
1193                                      while (tmpNode &&
1194                                          (!tmpNode.tagName ||
1195                                              (token.tagName && token.tagName !== tmpNode.tagName))
1196                                      ) {
1197                                          tmpNode = tmpNode[path];
1198                                      }
1199                                  }
1200                                  continue testLoop;
1201                              }
1202                          }
1203                      }
1204  
1205                      n--; // move to next token
1206                      // now that we've passed the test, move up the tree by combinator
1207                      if (!pass && (combinator = token.combinator)) {
1208                          path = combinator.axis;
1209                          tmpNode = tmpNode[path];
1210  
1211                          // skip non element nodes
1212                          while (tmpNode && !tmpNode.tagName) {
1213                              tmpNode = tmpNode[path];
1214                          }
1215  
1216                          if (combinator.direct) { // one pass only
1217                              path = null;
1218                          }
1219  
1220                      } else { // success if we made it this far
1221                          result.push(node);
1222                          if (firstOnly) {
1223                              return result;
1224                          }
1225                          break;
1226                      }
1227                  }
1228              }// while (tmpNode = node = nodes[++i]);
1229              node = tmpNode = null;
1230              return result;
1231          },
1232  
1233          combinators: {
1234              ' ': {
1235                  axis: 'parentNode'
1236              },
1237  
1238              '>': {
1239                  axis: 'parentNode',
1240                  direct: true
1241              },
1242  
1243  
1244              '+': {
1245                  axis: 'previousSibling',
1246                  direct: true
1247              }
1248          },
1249  
1250          _parsers: [
1251              {
1252                  name: ATTRIBUTES,
1253                  //re: /^\[(-?[a-z]+[\w\-]*)+([~\|\^\$\*!=]=?)?['"]?([^\]]*?)['"]?\]/i,
1254                  re: /^\uE003(-?[a-z]+[\w\-]*)+([~\|\^\$\*!=]=?)?['"]?([^\uE004'"]*)['"]?\uE004/i,
1255                  fn: function(match, token) {
1256                      var operator = match[2] || '',
1257                          operators = Selector.operators,
1258                          escVal = (match[3]) ? match[3].replace(/\\/g, '') : '',
1259                          test;
1260  
1261                      // add prefiltering for ID and CLASS
1262                      if ((match[1] === 'id' && operator === '=') ||
1263                              (match[1] === 'className' &&
1264                              Y_DOCUMENT_ELEMENT.getElementsByClassName &&
1265                              (operator === '~=' || operator === '='))) {
1266                          token.prefilter = match[1];
1267  
1268  
1269                          match[3] = escVal;
1270  
1271                          // escape all but ID for prefilter, which may run through QSA (via Dom.allById)
1272                          token[match[1]] = (match[1] === 'id') ? match[3] : escVal;
1273  
1274                      }
1275  
1276                      // add tests
1277                      if (operator in operators) {
1278                          test = operators[operator];
1279                          if (typeof test === 'string') {
1280                              match[3] = escVal.replace(Selector._reRegExpTokens, '\\$1');
1281                              test = new RegExp(test.replace('{val}', match[3]));
1282                          }
1283                          match[2] = test;
1284                      }
1285                      if (!token.last || token.prefilter !== match[1]) {
1286                          return match.slice(1);
1287                      }
1288                  }
1289  
1290              },
1291              {
1292                  name: TAG_NAME,
1293                  re: /^((?:-?[_a-z]+[\w-]*)|\*)/i,
1294                  fn: function(match, token) {
1295                      var tag = match[1].toUpperCase();
1296                      token.tagName = tag;
1297  
1298                      if (tag !== '*' && (!token.last || token.prefilter)) {
1299                          return [TAG_NAME, '=', tag];
1300                      }
1301                      if (!token.prefilter) {
1302                          token.prefilter = 'tagName';
1303                      }
1304                  }
1305              },
1306              {
1307                  name: COMBINATOR,
1308                  re: /^\s*([>+~]|\s)\s*/,
1309                  fn: function(match, token) {
1310                  }
1311              },
1312              {
1313                  name: PSEUDOS,
1314                  re: /^:([\-\w]+)(?:\uE005['"]?([^\uE005]*)['"]?\uE006)*/i,
1315                  fn: function(match, token) {
1316                      var test = Selector[PSEUDOS][match[1]];
1317                      if (test) { // reorder match array and unescape special chars for tests
1318                          if (match[2]) {
1319                              match[2] = match[2].replace(/\\/g, '');
1320                          }
1321                          return [match[2], test];
1322                      } else { // selector token not supported (possibly missing CSS3 module)
1323                          return false;
1324                      }
1325                  }
1326              }
1327              ],
1328  
1329          _getToken: function(token) {
1330              return {
1331                  tagName: null,
1332                  id: null,
1333                  className: null,
1334                  attributes: {},
1335                  combinator: null,
1336                  tests: []
1337              };
1338          },
1339  
1340          /**
1341              Break selector into token units per simple selector.
1342              Combinator is attached to the previous token.
1343           */
1344          _tokenize: function(selector) {
1345              selector = selector || '';
1346              selector = Selector._replaceShorthand(Y_Lang.trim(selector));
1347              var token = Selector._getToken(),     // one token per simple selector (left selector holds combinator)
1348                  query = selector, // original query for debug report
1349                  tokens = [],    // array of tokens
1350                  found = false,  // whether or not any matches were found this pass
1351                  match,         // the regex match
1352                  test,
1353                  i, parser;
1354  
1355              /*
1356                  Search for selector patterns, store, and strip them from the selector string
1357                  until no patterns match (invalid selector) or we run out of chars.
1358  
1359                  Multiple attributes and pseudos are allowed, in any order.
1360                  for example:
1361                      'form:first-child[type=button]:not(button)[lang|=en]'
1362              */
1363  
1364              outer:
1365              do {
1366                  found = false; // reset after full pass
1367  
1368                  for (i = 0; (parser = Selector._parsers[i++]);) {
1369                      if ( (match = parser.re.exec(selector)) ) { // note assignment
1370                          if (parser.name !== COMBINATOR ) {
1371                              token.selector = selector;
1372                          }
1373                          selector = selector.replace(match[0], ''); // strip current match from selector
1374                          if (!selector.length) {
1375                              token.last = true;
1376                          }
1377  
1378                          if (Selector._attrFilters[match[1]]) { // convert class to className, etc.
1379                              match[1] = Selector._attrFilters[match[1]];
1380                          }
1381  
1382                          test = parser.fn(match, token);
1383                          if (test === false) { // selector not supported
1384                              found = false;
1385                              break outer;
1386                          } else if (test) {
1387                              token.tests.push(test);
1388                          }
1389  
1390                          if (!selector.length || parser.name === COMBINATOR) {
1391                              tokens.push(token);
1392                              token = Selector._getToken(token);
1393                              if (parser.name === COMBINATOR) {
1394                                  token.combinator = Selector.combinators[match[1]];
1395                              }
1396                          }
1397                          found = true;
1398  
1399  
1400                      }
1401                  }
1402              } while (found && selector.length);
1403  
1404              if (!found || selector.length) { // not fully parsed
1405                  Y.log('query: ' + query + ' contains unsupported token in: ' + selector, 'warn', 'Selector');
1406                  tokens = [];
1407              }
1408              return tokens;
1409          },
1410  
1411          _replaceShorthand: function(selector) {
1412              var shorthand = Selector.shorthand,
1413                  esc = selector.match(Selector._re.esc), // pull escaped colon, brackets, etc.
1414                  attrs,
1415                  pseudos,
1416                  re, i, len;
1417  
1418              if (esc) {
1419                  selector = selector.replace(Selector._re.esc, '\uE000');
1420              }
1421  
1422              attrs = selector.match(Selector._re.attr);
1423              pseudos = selector.match(Selector._re.pseudos);
1424  
1425              if (attrs) {
1426                  selector = selector.replace(Selector._re.attr, '\uE001');
1427              }
1428  
1429              if (pseudos) {
1430                  selector = selector.replace(Selector._re.pseudos, '\uE002');
1431              }
1432  
1433  
1434              for (re in shorthand) {
1435                  if (shorthand.hasOwnProperty(re)) {
1436                      selector = selector.replace(new RegExp(re, 'gi'), shorthand[re]);
1437                  }
1438              }
1439  
1440              if (attrs) {
1441                  for (i = 0, len = attrs.length; i < len; ++i) {
1442                      selector = selector.replace(/\uE001/, attrs[i]);
1443                  }
1444              }
1445  
1446              if (pseudos) {
1447                  for (i = 0, len = pseudos.length; i < len; ++i) {
1448                      selector = selector.replace(/\uE002/, pseudos[i]);
1449                  }
1450              }
1451  
1452              selector = selector.replace(/\[/g, '\uE003');
1453              selector = selector.replace(/\]/g, '\uE004');
1454  
1455              selector = selector.replace(/\(/g, '\uE005');
1456              selector = selector.replace(/\)/g, '\uE006');
1457  
1458              if (esc) {
1459                  for (i = 0, len = esc.length; i < len; ++i) {
1460                      selector = selector.replace('\uE000', esc[i]);
1461                  }
1462              }
1463  
1464              return selector;
1465          },
1466  
1467          _attrFilters: {
1468              'class': 'className',
1469              'for': 'htmlFor'
1470          },
1471  
1472          getters: {
1473              href: function(node, attr) {
1474                  return Y_DOM.getAttribute(node, attr);
1475              }
1476          }
1477      };
1478  
1479  Y_mix(Selector, SelectorCSS2, true);
1480  Selector.getters.src = Selector.getters.rel = Selector.getters.href;
1481  
1482  // IE wants class with native queries
1483  if (Selector.useNative && Y_DOC.querySelector) {
1484      Selector.shorthand['\\.([^\\s\\\\(\\[:]*)'] = '[class~=$1]';
1485  }
1486  
1487  /**
1488   * The selector css3 module provides support for css3 selectors.
1489   * @module dom
1490   * @submodule selector-css3
1491   * @for Selector
1492   */
1493  
1494  /*
1495      an+b = get every _a_th node starting at the _b_th
1496      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
1497      1n+b =  get every element starting from b ("1" may may be omitted, e.g. "1n+0" or "n+0" or "n")
1498      an+0 = get every _a_th element, "0" may be omitted
1499  */
1500  
1501  Selector._reNth = /^(?:([\-]?\d*)(n){1}|(odd|even)$)*([\-+]?\d*)$/;
1502  
1503  Selector._getNth = function(node, expr, tag, reverse) {
1504      Selector._reNth.test(expr);
1505      var a = parseInt(RegExp.$1, 10), // include every _a_ elements (zero means no repeat, just first _a_)
1506          n = RegExp.$2, // "n"
1507          oddeven = RegExp.$3, // "odd" or "even"
1508          b = parseInt(RegExp.$4, 10) || 0, // start scan from element _b_
1509          result = [],
1510          siblings = Selector._children(node.parentNode, tag),
1511          op;
1512  
1513      if (oddeven) {
1514          a = 2; // always every other
1515          op = '+';
1516          n = 'n';
1517          b = (oddeven === 'odd') ? 1 : 0;
1518      } else if ( isNaN(a) ) {
1519          a = (n) ? 1 : 0; // start from the first or no repeat
1520      }
1521  
1522      if (a === 0) { // just the first
1523          if (reverse) {
1524              b = siblings.length - b + 1;
1525          }
1526  
1527          if (siblings[b - 1] === node) {
1528              return true;
1529          } else {
1530              return false;
1531          }
1532  
1533      } else if (a < 0) {
1534          reverse = !!reverse;
1535          a = Math.abs(a);
1536      }
1537  
1538      if (!reverse) {
1539          for (var i = b - 1, len = siblings.length; i < len; i += a) {
1540              if ( i >= 0 && siblings[i] === node ) {
1541                  return true;
1542              }
1543          }
1544      } else {
1545          for (var i = siblings.length - b, len = siblings.length; i >= 0; i -= a) {
1546              if ( i < len && siblings[i] === node ) {
1547                  return true;
1548              }
1549          }
1550      }
1551      return false;
1552  };
1553  
1554  Y_mix(Selector.pseudos, {
1555      'root': function(node) {
1556          return node === node.ownerDocument.documentElement;
1557      },
1558  
1559      'nth-child': function(node, expr) {
1560          return Selector._getNth(node, expr);
1561      },
1562  
1563      'nth-last-child': function(node, expr) {
1564          return Selector._getNth(node, expr, null, true);
1565      },
1566  
1567      'nth-of-type': function(node, expr) {
1568          return Selector._getNth(node, expr, node.tagName);
1569      },
1570  
1571      'nth-last-of-type': function(node, expr) {
1572          return Selector._getNth(node, expr, node.tagName, true);
1573      },
1574  
1575      'last-child': function(node) {
1576          var children = Selector._children(node.parentNode);
1577          return children[children.length - 1] === node;
1578      },
1579  
1580      'first-of-type': function(node) {
1581          return Selector._children(node.parentNode, node.tagName)[0] === node;
1582      },
1583  
1584      'last-of-type': function(node) {
1585          var children = Selector._children(node.parentNode, node.tagName);
1586          return children[children.length - 1] === node;
1587      },
1588  
1589      'only-child': function(node) {
1590          var children = Selector._children(node.parentNode);
1591          return children.length === 1 && children[0] === node;
1592      },
1593  
1594      'only-of-type': function(node) {
1595          var children = Selector._children(node.parentNode, node.tagName);
1596          return children.length === 1 && children[0] === node;
1597      },
1598  
1599      'empty': function(node) {
1600          return node.childNodes.length === 0;
1601      },
1602  
1603      'not': function(node, expr) {
1604          return !Selector.test(node, expr);
1605      },
1606  
1607      'contains': function(node, expr) {
1608          var text = node.innerText || node.textContent || '';
1609          return text.indexOf(expr) > -1;
1610      },
1611  
1612      'checked': function(node) {
1613          return (node.checked === true || node.selected === true);
1614      },
1615  
1616      enabled: function(node) {
1617          return (node.disabled !== undefined && !node.disabled);
1618      },
1619  
1620      disabled: function(node) {
1621          return (node.disabled);
1622      }
1623  });
1624  
1625  Y_mix(Selector.operators, {
1626      '^=': '^{val}', // Match starts with value
1627      '!=': function(node, attr, val) { return node[attr] !== val; }, // Match starts with value
1628      '$=': '{val}$', // Match ends with value
1629      '*=': '{val}' // Match contains value as substring
1630  });
1631  
1632  Selector.combinators['~'] = {
1633      axis: 'previousSibling'
1634  };
1635  YAHOO.register("selector", YAHOO.util.Selector, {version: "2.9.0", build: "2800"});
1636  
1637  
1638  
1639  /****************************************************************************/
1640  /****************************************************************************/
1641  /****************************************************************************/
1642  
1643  var Dom = YAHOO.util.Dom;
1644  
1645  /**
1646   * The ColumnSet class defines and manages a DataTable's Columns,
1647   * including nested hierarchies and access to individual Column instances.
1648   *
1649   * @namespace YAHOO.widget
1650   * @class ColumnSet
1651   * @uses YAHOO.util.EventProvider
1652   * @constructor
1653   * @param aDefinitions {Object[]} Array of object literals that define cells in
1654   * the THEAD.
1655   */
1656  YAHOO.widget.ColumnSet = function(aDefinitions) {
1657      this._sId = Dom.generateId(null, "yui-cs"); // "yui-cs" + YAHOO.widget.ColumnSet._nCount;
1658  
1659      // First clone the defs
1660      aDefinitions = YAHOO.widget.DataTable._cloneObject(aDefinitions);
1661      this._init(aDefinitions);
1662  
1663      YAHOO.widget.ColumnSet._nCount++;
1664  };
1665  
1666  /////////////////////////////////////////////////////////////////////////////
1667  //
1668  // Private member variables
1669  //
1670  /////////////////////////////////////////////////////////////////////////////
1671  
1672  /**
1673   * Internal class variable to index multiple ColumnSet instances.
1674   *
1675   * @property ColumnSet._nCount
1676   * @type Number
1677   * @private
1678   * @static
1679   */
1680  YAHOO.widget.ColumnSet._nCount = 0;
1681  
1682  YAHOO.widget.ColumnSet.prototype = {
1683      /**
1684       * Unique instance name.
1685       *
1686       * @property _sId
1687       * @type String
1688       * @private
1689       */
1690      _sId : null,
1691  
1692      /**
1693       * Array of object literal Column definitions passed to the constructor.
1694       *
1695       * @property _aDefinitions
1696       * @type Object[]
1697       * @private
1698       */
1699      _aDefinitions : null,
1700  
1701      /////////////////////////////////////////////////////////////////////////////
1702      //
1703      // Public member variables
1704      //
1705      /////////////////////////////////////////////////////////////////////////////
1706  
1707      /**
1708       * Top-down tree representation of Column hierarchy.
1709       *
1710       * @property tree
1711       * @type YAHOO.widget.Column[]
1712       */
1713      tree : null,
1714  
1715      /**
1716       * Flattened representation of all Columns.
1717       *
1718       * @property flat
1719       * @type YAHOO.widget.Column[]
1720       * @default []
1721       */
1722      flat : null,
1723  
1724      /**
1725       * Array of Columns that map one-to-one to a table column.
1726       *
1727       * @property keys
1728       * @type YAHOO.widget.Column[]
1729       * @default []
1730       */
1731      keys : null,
1732  
1733      /**
1734       * ID index of nested parent hierarchies for HEADERS accessibility attribute.
1735       *
1736       * @property headers
1737       * @type String[]
1738       * @default []
1739       */
1740      headers : null,
1741  
1742      /////////////////////////////////////////////////////////////////////////////
1743      //
1744      // Private methods
1745      //
1746      /////////////////////////////////////////////////////////////////////////////
1747  
1748      /**
1749       * Initializes ColumnSet instance with data from Column definitions.
1750       *
1751       * @method _init
1752       * @param aDefinitions {Object[]} Array of object literals that define cells in
1753       * the THEAD .
1754       * @private
1755       */
1756  
1757      _init : function(aDefinitions) {        
1758          // DOM tree representation of all Columns
1759          var tree = [];
1760          // Flat representation of all Columns
1761          var flat = [];
1762          // Flat representation of only Columns that are meant to display data
1763          var keys = [];
1764          // Array of HEADERS attribute values for all keys in the "keys" array
1765          var headers = [];
1766  
1767          // Tracks current node list depth being tracked
1768          var nodeDepth = -1;
1769  
1770          // Internal recursive function to define Column instances
1771          var parseColumns = function(nodeList, parent) {
1772              // One level down
1773              nodeDepth++;
1774  
1775              // Create corresponding tree node if not already there for this depth
1776              if(!tree[nodeDepth]) {
1777                  tree[nodeDepth] = [];
1778              }
1779  
1780  
1781              // Parse each node at this depth for attributes and any children
1782              for(var j=0; j<nodeList.length; j++) {
1783                  var currentNode = nodeList[j];
1784  
1785                  // Instantiate a new Column for each node
1786                  var oColumn = new YAHOO.widget.Column(currentNode);
1787                  
1788                  // Cross-reference Column ID back to the original object literal definition
1789                  currentNode.yuiColumnId = oColumn._sId;
1790                  
1791                  // Add the new Column to the flat list
1792                  flat.push(oColumn);
1793  
1794                  // Assign its parent as an attribute, if applicable
1795                  if(parent) {
1796                      oColumn._oParent = parent;
1797                  }
1798  
1799                  // The Column has descendants
1800                  if(YAHOO.lang.isArray(currentNode.children)) {
1801                      oColumn.children = currentNode.children;
1802  
1803                      // Determine COLSPAN value for this Column
1804                      var terminalChildNodes = 0;
1805                      var countTerminalChildNodes = function(ancestor) {
1806                          var descendants = ancestor.children;
1807                          // Drill down each branch and count terminal nodes
1808                          for(var k=0; k<descendants.length; k++) {
1809                              // Keep drilling down
1810                              if(YAHOO.lang.isArray(descendants[k].children)) {
1811                                  countTerminalChildNodes(descendants[k]);
1812                              }
1813                              // Reached branch terminus
1814                              else {
1815                                  terminalChildNodes++;
1816                              }
1817                          }
1818                      };
1819                      countTerminalChildNodes(currentNode);
1820                      oColumn._nColspan = terminalChildNodes;
1821  
1822                      // Cascade certain properties to children if not defined on their own
1823                      var currentChildren = currentNode.children;
1824                      for(var k=0; k<currentChildren.length; k++) {
1825                          var child = currentChildren[k];
1826                          if(oColumn.className && (child.className === undefined)) {
1827                              child.className = oColumn.className;
1828                          }
1829                          if(oColumn.editor && (child.editor === undefined)) {
1830                              child.editor = oColumn.editor;
1831                          }
1832                          //TODO: Deprecated
1833                          if(oColumn.editorOptions && (child.editorOptions === undefined)) {
1834                              child.editorOptions = oColumn.editorOptions;
1835                          }
1836                          if(oColumn.formatter && (child.formatter === undefined)) {
1837                              child.formatter = oColumn.formatter;
1838                          }
1839                          if(oColumn.resizeable && (child.resizeable === undefined)) {
1840                              child.resizeable = oColumn.resizeable;
1841                          }
1842                          if(oColumn.sortable && (child.sortable === undefined)) {
1843                              child.sortable = oColumn.sortable;
1844                          }
1845                          if(oColumn.hidden) {
1846                              child.hidden = true;
1847                          }
1848                          if(oColumn.width && (child.width === undefined)) {
1849                              child.width = oColumn.width;
1850                          }
1851                          if(oColumn.minWidth && (child.minWidth === undefined)) {
1852                              child.minWidth = oColumn.minWidth;
1853                          }
1854                          if(oColumn.maxAutoWidth && (child.maxAutoWidth === undefined)) {
1855                              child.maxAutoWidth = oColumn.maxAutoWidth;
1856                          }
1857                          // Backward compatibility
1858                          if(oColumn.type && (child.type === undefined)) {
1859                              child.type = oColumn.type;
1860                          }
1861                          if(oColumn.type && !oColumn.formatter) {
1862                              oColumn.formatter = oColumn.type;
1863                          }
1864                          if(oColumn.text && !YAHOO.lang.isValue(oColumn.label)) {
1865                              oColumn.label = oColumn.text;
1866                          }
1867                          if(oColumn.parser) {
1868                          }
1869                          if(oColumn.sortOptions && ((oColumn.sortOptions.ascFunction) ||
1870                                  (oColumn.sortOptions.descFunction))) {
1871                          }
1872                      }
1873  
1874                      // The children themselves must also be parsed for Column instances
1875                      if(!tree[nodeDepth+1]) {
1876                          tree[nodeDepth+1] = [];
1877                      }
1878                      parseColumns(currentChildren, oColumn);
1879                  }
1880                  // This Column does not have any children
1881                  else {
1882                      oColumn._nKeyIndex = keys.length;
1883                      oColumn._nColspan = 1;
1884                      keys.push(oColumn);
1885                  }
1886  
1887                  // Add the Column to the top-down tree
1888                  tree[nodeDepth].push(oColumn);
1889              }
1890              nodeDepth--;
1891          };
1892  
1893          // Parse out Column instances from the array of object literals
1894          if(YAHOO.lang.isArray(aDefinitions)) {
1895              parseColumns(aDefinitions);
1896  
1897              // Store the array
1898              this._aDefinitions = aDefinitions;
1899          }
1900          else {
1901              return null;
1902          }
1903  
1904          var i;
1905  
1906          // Determine ROWSPAN value for each Column in the tree
1907          var parseTreeForRowspan = function(tree) {
1908              var maxRowDepth = 1;
1909              var currentRow;
1910              var currentColumn;
1911  
1912              // Calculate the max depth of descendants for this row
1913              var countMaxRowDepth = function(row, tmpRowDepth) {
1914                  tmpRowDepth = tmpRowDepth || 1;
1915  
1916                  for(var n=0; n<row.length; n++) {
1917                      var col = row[n];
1918                      // Column has children, so keep counting
1919                      if(YAHOO.lang.isArray(col.children)) {
1920                          tmpRowDepth++;
1921                          countMaxRowDepth(col.children, tmpRowDepth);
1922                          tmpRowDepth--;
1923                      }
1924                      // No children, is it the max depth?
1925                      else {
1926                          if(tmpRowDepth > maxRowDepth) {
1927                              maxRowDepth = tmpRowDepth;
1928                          }
1929                      }
1930  
1931                  }
1932              };
1933  
1934              // Count max row depth for each row
1935              for(var m=0; m<tree.length; m++) {
1936                  currentRow = tree[m];
1937                  countMaxRowDepth(currentRow);
1938  
1939                  // Assign the right ROWSPAN values to each Column in the row
1940                  for(var p=0; p<currentRow.length; p++) {
1941                      currentColumn = currentRow[p];
1942                      if(!YAHOO.lang.isArray(currentColumn.children)) {
1943                          currentColumn._nRowspan = maxRowDepth;
1944                      }
1945                      else {
1946                          currentColumn._nRowspan = 1;
1947                      }
1948                  }
1949  
1950                  // Reset counter for next row
1951                  maxRowDepth = 1;
1952              }
1953          };
1954          parseTreeForRowspan(tree);
1955  
1956          // Store tree index values
1957          for(i=0; i<tree[0].length; i++) {
1958              tree[0][i]._nTreeIndex = i;
1959          }
1960  
1961          // Store header relationships in an array for HEADERS attribute
1962          var recurseAncestorsForHeaders = function(i, oColumn) {
1963              headers[i].push(oColumn.getSanitizedKey());
1964              if(oColumn._oParent) {
1965                  recurseAncestorsForHeaders(i, oColumn._oParent);
1966              }
1967          };
1968          for(i=0; i<keys.length; i++) {
1969              headers[i] = [];
1970              recurseAncestorsForHeaders(i, keys[i]);
1971              headers[i] = headers[i].reverse();
1972          }
1973  
1974          // Save to the ColumnSet instance
1975          this.tree = tree;
1976          this.flat = flat;
1977          this.keys = keys;
1978          this.headers = headers;
1979      },
1980  
1981      /////////////////////////////////////////////////////////////////////////////
1982      //
1983      // Public methods
1984      //
1985      /////////////////////////////////////////////////////////////////////////////
1986  
1987      /**
1988       * Returns unique name of the ColumnSet instance.
1989       *
1990       * @method getId
1991       * @return {String} Unique name of the ColumnSet instance.
1992       */
1993  
1994      getId : function() {
1995          return this._sId;
1996      },
1997  
1998      /**
1999       * ColumnSet instance name, for logging.
2000       *
2001       * @method toString
2002       * @return {String} Unique name of the ColumnSet instance.
2003       */
2004  
2005      toString : function() {
2006          return "ColumnSet instance " + this._sId;
2007      },
2008  
2009      /**
2010       * Public accessor to the definitions array.
2011       *
2012       * @method getDefinitions
2013       * @return {Object[]} Array of object literal Column definitions.
2014       */
2015  
2016      getDefinitions : function() {
2017          var aDefinitions = this._aDefinitions;
2018          
2019          // Internal recursive function to define Column instances
2020          var parseColumns = function(nodeList, oSelf) {
2021              // Parse each node at this depth for attributes and any children
2022              for(var j=0; j<nodeList.length; j++) {
2023                  var currentNode = nodeList[j];
2024                  
2025                  // Get the Column for each node
2026                  var oColumn = oSelf.getColumnById(currentNode.yuiColumnId);
2027                  
2028                  if(oColumn) {    
2029                      // Update the current values
2030                      var oDefinition = oColumn.getDefinition();
2031                      for(var name in oDefinition) {
2032                          if(YAHOO.lang.hasOwnProperty(oDefinition, name)) {
2033                              currentNode[name] = oDefinition[name];
2034                          }
2035                      }
2036                  }
2037                              
2038                  // The Column has descendants
2039                  if(YAHOO.lang.isArray(currentNode.children)) {
2040                      // The children themselves must also be parsed for Column instances
2041                      parseColumns(currentNode.children, oSelf);
2042                  }
2043              }
2044          };
2045  
2046          parseColumns(aDefinitions, this);
2047          this._aDefinitions = aDefinitions;
2048          return aDefinitions;
2049      },
2050  
2051      /**
2052       * Returns Column instance with given ID.
2053       *
2054       * @method getColumnById
2055       * @param column {String} Column ID.
2056       * @return {YAHOO.widget.Column} Column instance.
2057       */
2058  
2059      getColumnById : function(column) {
2060          if(YAHOO.lang.isString(column)) {
2061              var allColumns = this.flat;
2062              for(var i=allColumns.length-1; i>-1; i--) {
2063                  if(allColumns[i]._sId === column) {
2064                      return allColumns[i];
2065                  }
2066              }
2067          }
2068          return null;
2069      },
2070  
2071      /**
2072       * Returns Column instance with given key or ColumnSet key index.
2073       *
2074       * @method getColumn
2075       * @param column {String | Number} Column key or ColumnSet key index.
2076       * @return {YAHOO.widget.Column} Column instance.
2077       */
2078  
2079      getColumn : function(column) {
2080          if(YAHOO.lang.isNumber(column) && this.keys[column]) {
2081              return this.keys[column];
2082          }
2083          else if(YAHOO.lang.isString(column)) {
2084              var allColumns = this.flat;
2085              var aColumns = [];
2086              for(var i=0; i<allColumns.length; i++) {
2087                  if(allColumns[i].key === column) {
2088                      aColumns.push(allColumns[i]);
2089                  }
2090              }
2091              if(aColumns.length === 1) {
2092                  return aColumns[0];
2093              }
2094              else if(aColumns.length > 1) {
2095                  return aColumns;
2096              }
2097          }
2098          return null;
2099      },
2100  
2101      /**
2102       * Public accessor returns array of given Column's desendants (if any), including itself.
2103       *
2104       * @method getDescendants
2105       * @parem {YAHOO.widget.Column} Column instance.
2106       * @return {Array} Array including the Column itself and all descendants (if any).
2107       */
2108      getDescendants : function(oColumn) {
2109          var oSelf = this;
2110          var allDescendants = [];
2111          var i;
2112  
2113          // Recursive function to loop thru all children
2114          var parse = function(oParent) {
2115              allDescendants.push(oParent);
2116              // This Column has children
2117              if(oParent.children) {
2118                  for(i=0; i<oParent.children.length; i++) {
2119                      parse(oSelf.getColumn(oParent.children[i].key));
2120                  }
2121              }
2122          };
2123          parse(oColumn);
2124  
2125          return allDescendants;
2126      }
2127  };
2128  
2129  /****************************************************************************/
2130  /****************************************************************************/
2131  /****************************************************************************/
2132  
2133  /**
2134   * The Column class defines and manages attributes of DataTable Columns
2135   *
2136   * @namespace YAHOO.widget
2137   * @class Column
2138   * @constructor
2139   * @param oConfigs {Object} Object literal of definitions.
2140   */
2141  YAHOO.widget.Column = function(oConfigs) {
2142      this._sId = Dom.generateId(null, "yui-col"); // "yui-col" + YAHOO.widget.Column._nCount;
2143      
2144      // Object literal defines Column attributes
2145      if(oConfigs && YAHOO.lang.isObject(oConfigs)) {
2146          for(var sConfig in oConfigs) {
2147              if(sConfig) {
2148                  this[sConfig] = oConfigs[sConfig];
2149              }
2150          }
2151      }
2152  
2153      // Assign a key if not found
2154      if(!YAHOO.lang.isValue(this.key)) {
2155          this.key = Dom.generateId(null, "yui-dt-col"); //"yui-dt-col" + YAHOO.widget.Column._nCount;
2156      }
2157      
2158      // Assign a field if not found, defaults to key
2159      if(!YAHOO.lang.isValue(this.field)) {
2160          this.field = this.key;
2161      }
2162  
2163      // Increment counter
2164      YAHOO.widget.Column._nCount++;
2165  
2166      // Backward compatibility
2167      if(this.width && !YAHOO.lang.isNumber(this.width)) {
2168          this.width = null;
2169      }
2170      if(this.editor && YAHOO.lang.isString(this.editor)) {
2171          this.editor = new YAHOO.widget.CellEditor(this.editor, this.editorOptions);
2172      }
2173  };
2174  
2175  /////////////////////////////////////////////////////////////////////////////
2176  //
2177  // Private member variables
2178  //
2179  /////////////////////////////////////////////////////////////////////////////
2180  
2181  YAHOO.lang.augmentObject(YAHOO.widget.Column, {
2182      /**
2183       * Internal class variable to index multiple Column instances.
2184       *
2185       * @property Column._nCount
2186       * @type Number
2187       * @private
2188       * @static
2189       */
2190      _nCount : 0,
2191  
2192      formatCheckbox : function(elCell, oRecord, oColumn, oData) {
2193          YAHOO.widget.DataTable.formatCheckbox(elCell, oRecord, oColumn, oData);
2194      },
2195  
2196      formatCurrency : function(elCell, oRecord, oColumn, oData) {
2197          YAHOO.widget.DataTable.formatCurrency(elCell, oRecord, oColumn, oData);
2198      },
2199  
2200      formatDate : function(elCell, oRecord, oColumn, oData) {
2201          YAHOO.widget.DataTable.formatDate(elCell, oRecord, oColumn, oData);
2202      },
2203  
2204      formatEmail : function(elCell, oRecord, oColumn, oData) {
2205          YAHOO.widget.DataTable.formatEmail(elCell, oRecord, oColumn, oData);
2206      },
2207  
2208      formatLink : function(elCell, oRecord, oColumn, oData) {
2209          YAHOO.widget.DataTable.formatLink(elCell, oRecord, oColumn, oData);
2210      },
2211  
2212      formatNumber : function(elCell, oRecord, oColumn, oData) {
2213          YAHOO.widget.DataTable.formatNumber(elCell, oRecord, oColumn, oData);
2214      },
2215  
2216      formatSelect : function(elCell, oRecord, oColumn, oData) {
2217          YAHOO.widget.DataTable.formatDropdown(elCell, oRecord, oColumn, oData);
2218      }
2219  });
2220  
2221  YAHOO.widget.Column.prototype = {
2222      /**
2223       * Unique String identifier assigned at instantiation.
2224       *
2225       * @property _sId
2226       * @type String
2227       * @private
2228       */
2229      _sId : null,
2230  
2231      /**
2232       * Reference to Column's current position index within its ColumnSet's keys
2233       * array, if applicable. This property only applies to non-nested and bottom-
2234       * level child Columns.
2235       *
2236       * @property _nKeyIndex
2237       * @type Number
2238       * @private
2239       */
2240      _nKeyIndex : null,
2241  
2242      /**
2243       * Reference to Column's current position index within its ColumnSet's tree
2244       * array, if applicable. This property only applies to non-nested and top-
2245       * level parent Columns.
2246       *
2247       * @property _nTreeIndex
2248       * @type Number
2249       * @private
2250       */
2251      _nTreeIndex : null,
2252  
2253      /**
2254       * Number of table cells the Column spans.
2255       *
2256       * @property _nColspan
2257       * @type Number
2258       * @private
2259       */
2260      _nColspan : 1,
2261  
2262      /**
2263       * Number of table rows the Column spans.
2264       *
2265       * @property _nRowspan
2266       * @type Number
2267       * @private
2268       */
2269      _nRowspan : 1,
2270  
2271      /**
2272       * Column's parent Column instance, or null.
2273       *
2274       * @property _oParent
2275       * @type YAHOO.widget.Column
2276       * @private
2277       */
2278      _oParent : null,
2279  
2280      /**
2281       * The DOM reference to the associated TH element.
2282       *
2283       * @property _elTh
2284       * @type HTMLElement
2285       * @private
2286       */
2287      _elTh : null,
2288  
2289      /**
2290       * The DOM reference to the associated TH element's liner DIV element.
2291       *
2292       * @property _elThLiner
2293       * @type HTMLElement
2294       * @private
2295       */
2296      _elThLiner : null,
2297  
2298      /**
2299       * The DOM reference to the associated TH element's label SPAN element.
2300       *
2301       * @property _elThLabel
2302       * @type HTMLElement
2303       * @private
2304       */
2305      _elThLabel : null,
2306  
2307      /**
2308       * The DOM reference to the associated resizerelement (if any).
2309       *
2310       * @property _elResizer
2311       * @type HTMLElement
2312       * @private
2313       */
2314      _elResizer : null,
2315  
2316      /**
2317       * Internal width tracker.
2318       *
2319       * @property _nWidth
2320       * @type Number
2321       * @private
2322       */
2323      _nWidth : null,
2324  
2325      /**
2326       * For unreg() purposes, a reference to the Column's DragDrop instance.
2327       *
2328       * @property _dd
2329       * @type YAHOO.util.DragDrop
2330       * @private
2331       */
2332      _dd : null,
2333  
2334      /**
2335       * For unreg() purposes, a reference to the Column resizer's DragDrop instance.
2336       *
2337       * @property _ddResizer
2338       * @type YAHOO.util.DragDrop
2339       * @private
2340       */
2341      _ddResizer : null,
2342  
2343      /////////////////////////////////////////////////////////////////////////////
2344      //
2345      // Public member variables
2346      //
2347      /////////////////////////////////////////////////////////////////////////////
2348  
2349      /**
2350       * Unique name, required. If "label" property is not provided, the "key"
2351       * value will be treated as markup and inserted into the DOM as innerHTML.
2352       *
2353       * @property key
2354       * @type String|HTML
2355       */
2356      key : null,
2357  
2358      /**
2359       * Associated database field, or null.
2360       *
2361       * @property field
2362       * @type String
2363       */
2364      field : null,
2365  
2366      /**
2367       * Value displayed as Column header in the TH element. String value is
2368       * treated as markup and inserted into the DOM as innerHTML.
2369       *
2370       * @property label
2371       * @type HTML
2372       */
2373      label : null,
2374  
2375      /**
2376       * Column head cell ABBR for accessibility.
2377       *
2378       * @property abbr
2379       * @type String
2380       */
2381      abbr : null,
2382  
2383      /**
2384       * Array of object literals that define children (nested headers) of a Column.
2385       *
2386       * @property children
2387       * @type Object[]
2388       */
2389      children : null,
2390  
2391      /**
2392       * Column width (in pixels).
2393       *
2394       * @property width
2395       * @type Number
2396       */
2397      width : null,
2398  
2399      /**
2400       * Minimum Column width (in pixels).
2401       *
2402       * @property minWidth
2403       * @type Number
2404       * @default null
2405       */
2406      minWidth : null,
2407  
2408      /**
2409       * When a width is not defined for a Column, maxAutoWidth defines an upper
2410       * limit that the Column should be auto-sized to. If resizeable is enabled, 
2411       * users may still resize to a greater width. Most useful for Columns intended
2412       * to hold long unbroken, unwrapped Strings, such as URLs, to prevent very
2413       * wide Columns from disrupting visual readability by inducing truncation.
2414       *
2415       * @property maxAutoWidth
2416       * @type Number
2417       * @default null
2418       */
2419      maxAutoWidth : null,
2420  
2421      /**
2422       * True if Column is in hidden state.
2423       *
2424       * @property hidden
2425       * @type Boolean
2426       * @default false     
2427       */
2428      hidden : false,
2429  
2430      /**
2431       * True if Column is in selected state.
2432       *
2433       * @property selected
2434       * @type Boolean
2435       * @default false     
2436       */
2437      selected : false,
2438  
2439      /**
2440       * Custom CSS class or array of classes to be applied to every cell in the Column.
2441       *
2442       * @property className
2443       * @type String || String[]
2444       */
2445      className : null,
2446  
2447      /**
2448       * Cell formatter function, or a shortcut pointer to a function in the
2449       * DataTable.Formatter object. The function, called from the DataTable's
2450       * formatCell method, renders markup into the cell liner
2451       * element and accepts the following arguments:
2452       * <dl>
2453       *    <dt>elLiner</dt>
2454       *    <dd>The element to write innerHTML to.</dd>
2455       *    <dt>oRecord</dt>
2456       *    <dd>The associated Record for the row.</dd>
2457       *    <dt>oColumn</dt>
2458       *    <dd>The Column instance for the cell.</dd>
2459       *    <dt>oData</dt>
2460       *    <dd>The data value for the cell.</dd>
2461       * </dl>
2462       *
2463       * @property formatter
2464       * @type String || HTMLFunction
2465       */
2466      formatter : null,
2467      
2468      /**
2469       * Config passed to YAHOO.util.Number.format() by the 'currency' Column formatter.
2470       *
2471       * @property currencyOptions
2472       * @type Object
2473       * @default null
2474       */
2475      currencyOptions : null,
2476  
2477      /**
2478       * Config passed to YAHOO.util.Date.format() by the 'date' Column formatter.
2479       *
2480       * @property dateOptions
2481       * @type Object
2482       * @default null
2483       */
2484      dateOptions : null,
2485  
2486      /**
2487       * Array of dropdown values for formatter:"dropdown" cases. Can either be a
2488       * simple array (e.g., ["Alabama","Alaska","Arizona","Arkansas"]) or a an
2489       * array of objects (e.g., [{label:"Alabama", value:"AL"},
2490       * {label:"Alaska", value:"AK"}, {label:"Arizona", value:"AZ"},
2491       * {label:"Arkansas", value:"AR"}]). String values are treated as markup and
2492       * inserted into the DOM as innerHTML.
2493       *
2494       * @property dropdownOptions
2495       * @type HTML[] | Object[]
2496       */
2497      dropdownOptions : null,
2498       
2499      /**
2500       * A CellEditor instance, otherwise Column is not editable.     
2501       *
2502       * @property editor
2503       * @type YAHOO.widget.CellEditor
2504       */
2505      editor : null,
2506  
2507      /**
2508       * True if Column is resizeable, false otherwise. The Drag & Drop Utility is
2509       * required to enable this feature. Only bottom-level and non-nested Columns are
2510       * resizeble. 
2511       *
2512       * @property resizeable
2513       * @type Boolean
2514       * @default false
2515       */
2516      resizeable : false,
2517  
2518      /**
2519       * True if Column is sortable, false otherwise.
2520       *
2521       * @property sortable
2522       * @type Boolean
2523       * @default false
2524       */
2525      sortable : false,
2526  
2527      /**
2528       * @property sortOptions.defaultOrder
2529       * @deprecated Use sortOptions.defaultDir.
2530       */
2531      /**
2532       * Default sort direction for Column: YAHOO.widget.DataTable.CLASS_ASC or YAHOO.widget.DataTable.CLASS_DESC.
2533       *
2534       * @property sortOptions.defaultDir
2535       * @type String
2536       * @default null
2537       */
2538      /**
2539       * Custom field to sort on.
2540       *
2541       * @property sortOptions.field
2542       * @type String
2543       * @default null
2544       */
2545      /**
2546       * Custom sort handler. Signature: sortFunction(a, b, desc, field) where field is the sortOptions.field value
2547       *
2548       * @property sortOptions.sortFunction
2549       * @type Function
2550       * @default null
2551       */
2552      sortOptions : null,
2553  
2554  
2555  
2556  
2557  
2558  
2559  
2560  
2561  
2562  
2563  
2564  
2565  
2566  
2567  
2568      /////////////////////////////////////////////////////////////////////////////
2569      //
2570      // Public methods
2571      //
2572      /////////////////////////////////////////////////////////////////////////////
2573  
2574      /**
2575       * Returns unique ID string.
2576       *
2577       * @method getId
2578       * @return {String} Unique ID string.
2579       */
2580      getId : function() {
2581          return this._sId;
2582      },
2583  
2584      /**
2585       * Column instance name, for logging.
2586       *
2587       * @method toString
2588       * @return {String} Column's unique name.
2589       */
2590      toString : function() {
2591          return "Column instance " + this._sId;
2592      },
2593  
2594      /**
2595       * Returns object literal definition.
2596       *
2597       * @method getDefinition
2598       * @return {Object} Object literal definition.
2599       */
2600      getDefinition : function() {
2601          var oDefinition = {};
2602          
2603          // Update the definition
2604          oDefinition.abbr = this.abbr;
2605          oDefinition.className = this.className;
2606          oDefinition.editor = this.editor;
2607          oDefinition.editorOptions = this.editorOptions; //TODO: deprecated
2608          oDefinition.field = this.field;
2609          oDefinition.formatter = this.formatter;
2610          oDefinition.hidden = this.hidden;
2611          oDefinition.key = this.key;
2612          oDefinition.label = this.label;
2613          oDefinition.minWidth = this.minWidth;
2614          oDefinition.maxAutoWidth = this.maxAutoWidth;
2615          oDefinition.resizeable = this.resizeable;
2616          oDefinition.selected = this.selected;
2617          oDefinition.sortable = this.sortable;
2618          oDefinition.sortOptions = this.sortOptions;
2619          oDefinition.width = this.width;
2620          
2621          // Bug 2529147
2622          oDefinition._calculatedWidth = this._calculatedWidth;
2623  
2624          return oDefinition;
2625      },
2626  
2627      /**
2628       * Returns unique Column key.
2629       *
2630       * @method getKey
2631       * @return {String} Column key.
2632       */
2633      getKey : function() {
2634          return this.key;
2635      },
2636      
2637      /**
2638       * Returns field.
2639       *
2640       * @method getField
2641       * @return {String} Column field.
2642       */
2643      getField : function() {
2644          return this.field;
2645      },
2646      
2647      /**
2648       * Returns Column key which has been sanitized for DOM (class and ID) usage
2649       * starts with letter, contains only letters, numbers, hyphen, or period.
2650       *
2651       * @method getSanitizedKey
2652       * @return {String} Sanitized Column key.
2653       */
2654      getSanitizedKey : function() {
2655          return this.getKey().replace(/[^\w\-]/g,"");
2656      },
2657  
2658      /**
2659       * Public accessor returns Column's current position index within its
2660       * ColumnSet's keys array, if applicable. Only non-nested and bottom-level
2661       * child Columns will return a value.
2662       *
2663       * @method getKeyIndex
2664       * @return {Number} Position index, or null.
2665       */
2666      getKeyIndex : function() {
2667          return this._nKeyIndex;
2668      },
2669  
2670      /**
2671       * Public accessor returns Column's current position index within its
2672       * ColumnSet's tree array, if applicable. Only non-nested and top-level parent
2673       * Columns will return a value;
2674       *
2675       * @method getTreeIndex
2676       * @return {Number} Position index, or null.
2677       */
2678      getTreeIndex : function() {
2679          return this._nTreeIndex;
2680      },
2681  
2682      /**
2683       * Public accessor returns Column's parent instance if any, or null otherwise.
2684       *
2685       * @method getParent
2686       * @return {YAHOO.widget.Column} Column's parent instance.
2687       */
2688      getParent : function() {
2689          return this._oParent;
2690      },
2691  
2692      /**
2693       * Public accessor returns Column's calculated COLSPAN value.
2694       *
2695       * @method getColspan
2696       * @return {Number} Column's COLSPAN value.
2697       */
2698      getColspan : function() {
2699          return this._nColspan;
2700      },
2701      // Backward compatibility
2702      getColSpan : function() {
2703          return this.getColspan();
2704      },
2705  
2706      /**
2707       * Public accessor returns Column's calculated ROWSPAN value.
2708       *
2709       * @method getRowspan
2710       * @return {Number} Column's ROWSPAN value.
2711       */
2712      getRowspan : function() {
2713          return this._nRowspan;
2714      },
2715  
2716      /**
2717       * Returns DOM reference to the key TH element.
2718       *
2719       * @method getThEl
2720       * @return {HTMLElement} TH element.
2721       */
2722      getThEl : function() {
2723          return this._elTh;
2724      },
2725  
2726      /**
2727       * Returns DOM reference to the TH's liner DIV element. Introduced since
2728       * resizeable Columns may have an extra resizer liner, making the DIV liner
2729       * not reliably the TH element's first child.               
2730       *
2731       * @method getThLInerEl
2732       * @return {HTMLElement} TH element.
2733       */
2734      getThLinerEl : function() {
2735          return this._elThLiner;
2736      },
2737      
2738      /**
2739       * Returns DOM reference to the resizer element, or null.
2740       *
2741       * @method getResizerEl
2742       * @return {HTMLElement} DIV element.
2743       */
2744      getResizerEl : function() {
2745          return this._elResizer;
2746      },
2747  
2748      // Backward compatibility
2749      /**
2750       * @method getColEl
2751       * @deprecated Use getThEl
2752       */
2753      getColEl : function() {
2754          return this.getThEl();
2755      },
2756      getIndex : function() {
2757          return this.getKeyIndex();
2758      },
2759      format : function() {
2760      }
2761  };
2762  
2763  /****************************************************************************/
2764  /****************************************************************************/
2765  /****************************************************************************/
2766  
2767  /**
2768   * Sort static utility to support Column sorting.
2769   *
2770   * @namespace YAHOO.util
2771   * @class Sort
2772   * @static
2773   */
2774  YAHOO.util.Sort = {
2775      /////////////////////////////////////////////////////////////////////////////
2776      //
2777      // Public methods
2778      //
2779      /////////////////////////////////////////////////////////////////////////////
2780  
2781      /**
2782       * Comparator function for simple case-insensitive string sorting.
2783       *
2784       * @method compare
2785       * @param a {Object} First sort argument.
2786       * @param b {Object} Second sort argument.
2787       * @param desc {Boolean} True if sort direction is descending, false if
2788       * sort direction is ascending.
2789       * @return {Boolean} Return -1 when a < b. Return 0 when a = b.
2790       * Return 1 when a > b.
2791       */
2792      compare: function(a, b, desc) {
2793          if((a === null) || (typeof a == "undefined")) {
2794              if((b === null) || (typeof b == "undefined")) {
2795                  return 0;
2796              }
2797              else {
2798                  return 1;
2799              }
2800          }
2801          else if((b === null) || (typeof b == "undefined")) {
2802              return -1;
2803          }
2804  
2805          if(a.constructor == String) {
2806              a = a.toLowerCase();
2807          }
2808          if(b.constructor == String) {
2809              b = b.toLowerCase();
2810          }
2811          if(a < b) {
2812              return (desc) ? 1 : -1;
2813          }
2814          else if (a > b) {
2815              return (desc) ? -1 : 1;
2816          }
2817          else {
2818              return 0;
2819          }
2820      }
2821  };
2822  
2823  /****************************************************************************/
2824  /****************************************************************************/
2825  /****************************************************************************/
2826  
2827  /**
2828   * ColumnDD subclasses DragDrop to support rearrangeable Columns.
2829   *
2830   * @namespace YAHOO.util
2831   * @class ColumnDD
2832   * @extends YAHOO.util.DDProxy
2833   * @constructor
2834   * @param oDataTable {YAHOO.widget.DataTable} DataTable instance.
2835   * @param oColumn {YAHOO.widget.Column} Column instance.
2836   * @param elTh {HTMLElement} TH element reference.
2837   * @param elTarget {HTMLElement} Drag target element.
2838   */
2839  YAHOO.widget.ColumnDD = function(oDataTable, oColumn, elTh, elTarget) {
2840      if(oDataTable && oColumn && elTh && elTarget) {
2841          this.datatable = oDataTable;
2842          this.table = oDataTable.getTableEl();
2843          this.column = oColumn;
2844          this.headCell = elTh;
2845          this.pointer = elTarget;
2846          this.newIndex = null;
2847          this.init(elTh);
2848          this.initFrame(); // Needed for DDProxy
2849          this.invalidHandleTypes = {};
2850  
2851          // Set top/bottom padding to account for children of nested columns
2852          this.setPadding(10, 0, (this.datatable.getTheadEl().offsetHeight + 10) , 0);
2853  
2854          YAHOO.util.Event.on(window, 'resize', function() {
2855              this.initConstraints();
2856          }, this, true);
2857      }
2858      else {
2859      }
2860  };
2861  
2862  if(YAHOO.util.DDProxy) {
2863      YAHOO.extend(YAHOO.widget.ColumnDD, YAHOO.util.DDProxy, {
2864          initConstraints: function() {
2865              //Get the top, right, bottom and left positions
2866              var region = YAHOO.util.Dom.getRegion(this.table),
2867                  //Get the element we are working on
2868                  el = this.getEl(),
2869                  //Get the xy position of it
2870                  xy = YAHOO.util.Dom.getXY(el),
2871                  //Get the width and height
2872                  width = parseInt(YAHOO.util.Dom.getStyle(el, 'width'), 10),
2873                  height = parseInt(YAHOO.util.Dom.getStyle(el, 'height'), 10),
2874                  //Set left to x minus left
2875                  left = ((xy[0] - region.left) + 15), //Buffer of 15px
2876                  //Set right to right minus x minus width
2877                  right = ((region.right - xy[0] - width) + 15);
2878      
2879              //Set the constraints based on the above calculations
2880              this.setXConstraint(left, right);
2881              this.setYConstraint(10, 10);            
2882          },
2883          _resizeProxy: function() {
2884              YAHOO.widget.ColumnDD.superclass._resizeProxy.apply(this, arguments);
2885              var dragEl = this.getDragEl(),
2886                  el = this.getEl();
2887  
2888              YAHOO.util.Dom.setStyle(this.pointer, 'height', (this.table.parentNode.offsetHeight + 10) + 'px');
2889              YAHOO.util.Dom.setStyle(this.pointer, 'display', 'block');
2890              var xy = YAHOO.util.Dom.getXY(el);
2891              YAHOO.util.Dom.setXY(this.pointer, [xy[0], (xy[1] - 5)]);
2892              
2893              YAHOO.util.Dom.setStyle(dragEl, 'height', this.datatable.getContainerEl().offsetHeight + "px");
2894              YAHOO.util.Dom.setStyle(dragEl, 'width', (parseInt(YAHOO.util.Dom.getStyle(dragEl, 'width'),10) + 4) + 'px');
2895              YAHOO.util.Dom.setXY(this.dragEl, xy);
2896          },
2897          onMouseDown: function() {
2898                  this.initConstraints();
2899                  this.resetConstraints();
2900          },
2901          clickValidator: function(e) {
2902              if(!this.column.hidden) {
2903                  var target = YAHOO.util.Event.getTarget(e);
2904                  return ( this.isValidHandleChild(target) &&
2905                              (this.id == this.handleElId ||
2906                                  this.DDM.handleWasClicked(target, this.id)) );
2907              }
2908          },
2909          onDragOver: function(ev, id) {
2910              // Validate target as a Column
2911              var target = this.datatable.getColumn(id);
2912              if(target) {                
2913                  // Validate target as a top-level parent
2914                  var targetIndex = target.getTreeIndex();
2915                  while((targetIndex === null) && target.getParent()) {
2916                      target = target.getParent();
2917                      targetIndex = target.getTreeIndex();
2918                  }
2919                  if(targetIndex !== null) {
2920                      // Are we placing to left or right of target?
2921                      var elTarget = target.getThEl();
2922                      var newIndex = targetIndex;
2923                      var mouseX = YAHOO.util.Event.getPageX(ev),
2924                          targetX = YAHOO.util.Dom.getX(elTarget),
2925                          midX = targetX + ((YAHOO.util.Dom.get(elTarget).offsetWidth)/2),
2926                          currentIndex =  this.column.getTreeIndex();
2927                      
2928                      if (mouseX < midX) {
2929                         YAHOO.util.Dom.setX(this.pointer, targetX);
2930                      } else {
2931                          var targetWidth = parseInt(elTarget.offsetWidth, 10);
2932                          YAHOO.util.Dom.setX(this.pointer, (targetX + targetWidth));
2933                          newIndex++;
2934                      }
2935                      if (targetIndex > currentIndex) {
2936                          newIndex--;
2937                      }
2938                      if(newIndex < 0) {
2939                          newIndex = 0;
2940                      }
2941                      else if(newIndex > this.datatable.getColumnSet().tree[0].length) {
2942                          newIndex = this.datatable.getColumnSet().tree[0].length;
2943                      }
2944                      this.newIndex = newIndex;
2945                  }
2946              }
2947          },
2948          onDragDrop: function() {
2949              this.datatable.reorderColumn(this.column, this.newIndex);
2950          },
2951          endDrag: function() {
2952              this.newIndex = null;
2953              YAHOO.util.Dom.setStyle(this.pointer, 'display', 'none');
2954          }
2955      });
2956  }
2957  
2958  /****************************************************************************/
2959  /****************************************************************************/
2960  /****************************************************************************/
2961  
2962  /**
2963   * ColumnResizer subclasses DragDrop to support resizeable Columns.
2964   *
2965   * @namespace YAHOO.util
2966   * @class ColumnResizer
2967   * @extends YAHOO.util.DDProxy
2968   * @constructor
2969   * @param oDataTable {YAHOO.widget.DataTable} DataTable instance.
2970   * @param oColumn {YAHOO.widget.Column} Column instance.
2971   * @param elTh {HTMLElement} TH element reference.
2972   * @param sHandleElId {String} DOM ID of the handle element that causes the resize.
2973   * @param elProxy {HTMLElement} Resizer proxy element.
2974   */
2975  YAHOO.util.ColumnResizer = function(oDataTable, oColumn, elTh, sHandleId, elProxy) {
2976      if(oDataTable && oColumn && elTh && sHandleId) {
2977          this.datatable = oDataTable;
2978          this.column = oColumn;
2979          this.headCell = elTh;
2980          this.headCellLiner = oColumn.getThLinerEl();
2981          this.resizerLiner = elTh.firstChild;
2982          this.init(sHandleId, sHandleId, {dragOnly:true, dragElId: elProxy.id});
2983          this.initFrame(); // Needed for proxy
2984          this.resetResizerEl(); // Needed when rowspan > 0
2985  
2986          // Set right padding for bug 1858462
2987          this.setPadding(0, 1, 0, 0);
2988      }
2989      else {
2990      }
2991  };
2992  
2993  if(YAHOO.util.DD) {
2994      YAHOO.extend(YAHOO.util.ColumnResizer, YAHOO.util.DDProxy, {
2995          /////////////////////////////////////////////////////////////////////////////
2996          //
2997          // Public methods
2998          //
2999          /////////////////////////////////////////////////////////////////////////////
3000          /**
3001           * Resets resizer element.
3002           *
3003           * @method resetResizerEl
3004           */
3005          resetResizerEl : function() {
3006              var resizerStyle = YAHOO.util.Dom.get(this.handleElId).style;
3007              resizerStyle.left = "auto";
3008              resizerStyle.right = 0;
3009              resizerStyle.top = "auto";
3010              resizerStyle.bottom = 0;
3011              resizerStyle.height = this.headCell.offsetHeight+"px";
3012          },
3013      
3014          /////////////////////////////////////////////////////////////////////////////
3015          //
3016          // Public DOM event handlers
3017          //
3018          /////////////////////////////////////////////////////////////////////////////
3019      
3020          /**
3021           * Handles mouseup events on the Column resizer.
3022           *
3023           * @method onMouseUp
3024           * @param e {string} The mouseup event
3025           */
3026          onMouseUp : function(e) {
3027              // Reset height of all resizer els in case TH's have changed height
3028              var allKeys = this.datatable.getColumnSet().keys,
3029                  col;
3030              for(var i=0, len=allKeys.length; i<len; i++) {
3031                  col = allKeys[i];
3032                  if(col._ddResizer) {
3033                      col._ddResizer.resetResizerEl();
3034                  }
3035              }
3036              this.resetResizerEl();
3037              
3038              var el = this.headCellLiner;
3039              var newWidth = el.offsetWidth -
3040                  (parseInt(YAHOO.util.Dom.getStyle(el,"paddingLeft"),10)|0) -
3041                  (parseInt(YAHOO.util.Dom.getStyle(el,"paddingRight"),10)|0);
3042  
3043              this.datatable.fireEvent("columnResizeEvent", {column:this.column,target:this.headCell,width:newWidth});
3044          },
3045      
3046          /**
3047           * Handles mousedown events on the Column resizer.
3048           *
3049           * @method onMouseDown
3050           * @param e {string} The mousedown event
3051           */
3052          onMouseDown : function(e) {
3053              this.startWidth = this.headCellLiner.offsetWidth;
3054              this.startX = YAHOO.util.Event.getXY(e)[0];
3055              this.nLinerPadding = (parseInt(YAHOO.util.Dom.getStyle(this.headCellLiner,"paddingLeft"),10)|0) +
3056                      (parseInt(YAHOO.util.Dom.getStyle(this.headCellLiner,"paddingRight"),10)|0);
3057          },
3058      
3059          /**
3060           * Custom clickValidator to ensure Column is not in hidden state.
3061           *
3062           * @method clickValidator
3063           * @param {Event} e
3064           * @private
3065           */
3066          clickValidator : function(e) {
3067              if(!this.column.hidden) {
3068                  var target = YAHOO.util.Event.getTarget(e);
3069                  return ( this.isValidHandleChild(target) &&
3070                              (this.id == this.handleElId ||
3071                                  this.DDM.handleWasClicked(target, this.id)) );
3072              }
3073          },
3074      
3075          /**
3076           * Handles start drag on the Column resizer.
3077           *
3078           * @method startDrag
3079           * @param e {string} The drag event
3080           */
3081          startDrag : function() {
3082              // Shrinks height of all resizer els to not hold open TH els
3083              var allKeys = this.datatable.getColumnSet().keys,
3084                  thisKey = this.column.getKeyIndex(),
3085                  col;
3086              for(var i=0, len=allKeys.length; i<len; i++) {
3087                  col = allKeys[i];
3088                  if(col._ddResizer) {
3089                      YAHOO.util.Dom.get(col._ddResizer.handleElId).style.height = "1em";
3090                  }
3091              }
3092          },
3093  
3094          /**
3095           * Handles drag events on the Column resizer.
3096           *
3097           * @method onDrag
3098           * @param e {string} The drag event
3099           */
3100          onDrag : function(e) {
3101              var newX = YAHOO.util.Event.getXY(e)[0];
3102              if(newX > YAHOO.util.Dom.getX(this.headCellLiner)) {
3103                  var offsetX = newX - this.startX;
3104                  var newWidth = this.startWidth + offsetX - this.nLinerPadding;
3105                  if(newWidth > 0) {
3106                      this.datatable.setColumnWidth(this.column, newWidth);
3107                  }
3108              }
3109          }
3110      });
3111  }
3112  
3113  /////////////////////////////////////////////////////////////////////////////
3114  //
3115  // Deprecated
3116  //
3117  /////////////////////////////////////////////////////////////////////////////
3118  
3119  /**
3120   * @property editorOptions
3121   * @deprecated Pass configs directly to CellEditor constructor. 
3122   */
3123  
3124  
3125  (function () {
3126  
3127  var lang   = YAHOO.lang,
3128      util   = YAHOO.util,
3129      widget = YAHOO.widget,
3130      
3131      Dom    = util.Dom,
3132      Ev     = util.Event,
3133      DT     = widget.DataTable;
3134  
3135  /****************************************************************************/
3136  /****************************************************************************/
3137  /****************************************************************************/
3138  
3139  /**
3140   * A RecordSet defines and manages a set of Records.
3141   *
3142   * @namespace YAHOO.widget
3143   * @class RecordSet
3144   * @param data {Object || Object[]} An object literal or an array of data.
3145   * @constructor
3146   */
3147  YAHOO.widget.RecordSet = function(data) {
3148      this._init(data);
3149  };
3150  
3151  var RS = widget.RecordSet;
3152  
3153  /**
3154   * Internal class variable to name multiple Recordset instances.
3155   *
3156   * @property RecordSet._nCount
3157   * @type Number
3158   * @private
3159   * @static
3160   */
3161  RS._nCount = 0;
3162  
3163  RS.prototype = {
3164  
3165      /////////////////////////////////////////////////////////////////////////////
3166      //
3167      // Private member variables
3168      //
3169      /////////////////////////////////////////////////////////////////////////////
3170      /**
3171       * Unique String identifier assigned at instantiation.
3172       *
3173       * @property _sId
3174       * @type String
3175       * @private
3176       */
3177      _sId : null,
3178  
3179      /**
3180       * Internal counter of how many Records are in the RecordSet.
3181       *
3182       * @property _length
3183       * @type Number
3184       * @private
3185       * @deprecated No longer used
3186       */
3187      //_length : null,
3188  
3189      /////////////////////////////////////////////////////////////////////////////
3190      //
3191      // Private methods
3192      //
3193      /////////////////////////////////////////////////////////////////////////////
3194      
3195      /**
3196       * Initializer.
3197       *
3198       * @method _init
3199       * @param data {Object || Object[]} An object literal or an array of data.
3200       * @private
3201       */
3202      _init : function(data) {
3203          // Internal variables
3204          this._sId = Dom.generateId(null, "yui-rs");// "yui-rs" + widget.RecordSet._nCount;
3205          widget.RecordSet._nCount++;
3206          this._records = [];
3207          //this._length = 0;
3208  
3209          this._initEvents();
3210  
3211          if(data) {
3212              if(lang.isArray(data)) {
3213                  this.addRecords(data);
3214              }
3215              else if(lang.isObject(data)) {
3216                  this.addRecord(data);
3217              }
3218          }
3219  
3220      },
3221      
3222      /**
3223       * Initializes custom events.
3224       *
3225       * @method _initEvents
3226       * @private
3227       */
3228      _initEvents : function() {
3229          this.createEvent("recordAddEvent");
3230          this.createEvent("recordsAddEvent");
3231          this.createEvent("recordSetEvent");
3232          this.createEvent("recordsSetEvent");
3233          this.createEvent("recordUpdateEvent");
3234          this.createEvent("recordDeleteEvent");
3235          this.createEvent("recordsDeleteEvent");
3236          this.createEvent("resetEvent");
3237          this.createEvent("recordValueUpdateEvent");
3238      },
3239  
3240      /**
3241       * Adds one Record to the RecordSet at the given index. If index is null,
3242       * then adds the Record to the end of the RecordSet.
3243       *
3244       * @method _addRecord
3245       * @param oData {Object} An object literal of data.
3246       * @param index {Number} (optional) Position index.
3247       * @return {YAHOO.widget.Record} A Record instance.
3248       * @private
3249       */
3250      _addRecord : function(oData, index) {
3251          var oRecord = new YAHOO.widget.Record(oData);
3252          
3253          if(YAHOO.lang.isNumber(index) && (index > -1)) {
3254              this._records.splice(index,0,oRecord);
3255          }
3256          else {
3257              //index = this.getLength();
3258              //this._records[index] = oRecord;
3259              this._records[this._records.length] = oRecord;
3260          }
3261          //this._length++;
3262          return oRecord;
3263      },
3264  
3265      /**
3266       * Sets/replaces one Record to the RecordSet at the given index.  Existing
3267       * Records with higher indexes are not shifted.  If no index specified, the
3268       * Record is added to the end of the RecordSet.
3269       *
3270       * @method _setRecord
3271       * @param oData {Object} An object literal of data.
3272       * @param index {Number} (optional) Position index.
3273       * @return {YAHOO.widget.Record} A Record instance.
3274       * @private
3275       */
3276      _setRecord : function(oData, index) {
3277          if (!lang.isNumber(index) || index < 0) {
3278              index = this._records.length;
3279          }
3280          return (this._records[index] = new widget.Record(oData));
3281          /*
3282          if(lang.isNumber(index) && (index > -1)) {
3283              this._records[index] = oRecord;
3284              if((index+1) > this.getLength()) {
3285                  this._length = index+1;
3286              }
3287          }
3288          else {
3289              this._records[this.getLength()] = oRecord;
3290              this._length++;
3291          }
3292          return oRecord;
3293          */
3294      },
3295  
3296      /**
3297       * Deletes Records from the RecordSet at the given index. If range is null,
3298       * then only one Record is deleted.
3299       *
3300       * @method _deleteRecord
3301       * @param index {Number} Position index.
3302       * @param range {Number} (optional) How many Records to delete
3303       * @private
3304       */
3305      _deleteRecord : function(index, range) {
3306          if(!lang.isNumber(range) || (range < 0)) {
3307              range = 1;
3308          }
3309          this._records.splice(index, range);
3310          //this._length = this._length - range;
3311      },
3312  
3313      /////////////////////////////////////////////////////////////////////////////
3314      //
3315      // Public methods
3316      //
3317      /////////////////////////////////////////////////////////////////////////////
3318  
3319      /**
3320       * Returns unique name of the RecordSet instance.
3321       *
3322       * @method getId
3323       * @return {String} Unique name of the RecordSet instance.
3324       */
3325      getId : function() {
3326          return this._sId;
3327      },
3328  
3329      /**
3330       * Public accessor to the unique name of the RecordSet instance.
3331       *
3332       * @method toString
3333       * @return {String} Unique name of the RecordSet instance.
3334       */
3335      toString : function() {
3336          return "RecordSet instance " + this._sId;
3337      },
3338  
3339      /**
3340       * Returns the number of Records held in the RecordSet.
3341       *
3342       * @method getLength
3343       * @return {Number} Number of records in the RecordSet.
3344       */
3345      getLength : function() {
3346              //return this._length;
3347              return this._records.length;
3348      },
3349  
3350      /**
3351       * Returns Record by ID or RecordSet position index.
3352       *
3353       * @method getRecord
3354       * @param record {YAHOO.widget.Record | Number | String} Record instance,
3355       * RecordSet position index, or Record ID.
3356       * @return {YAHOO.widget.Record} Record object.
3357       */
3358      getRecord : function(record) {
3359          var i;
3360          if(record instanceof widget.Record) {
3361              for(i=0; i<this._records.length; i++) {
3362                  if(this._records[i] && (this._records[i]._sId === record._sId)) {
3363                      return record;
3364                  }
3365              }
3366          }
3367          else if(lang.isNumber(record)) {
3368              if((record > -1) && (record < this.getLength())) {
3369                  return this._records[record];
3370              }
3371          }
3372          else if(lang.isString(record)) {
3373              for(i=0; i<this._records.length; i++) {
3374                  if(this._records[i] && (this._records[i]._sId === record)) {
3375                      return this._records[i];
3376                  }
3377              }
3378          }
3379          // Not a valid Record for this RecordSet
3380          return null;
3381  
3382      },
3383  
3384      /**
3385       * Returns an array of Records from the RecordSet.
3386       *
3387       * @method getRecords
3388       * @param index {Number} (optional) Recordset position index of which Record to
3389       * start at.
3390       * @param range {Number} (optional) Number of Records to get.
3391       * @return {YAHOO.widget.Record[]} Array of Records starting at given index and
3392       * length equal to given range. If index is not given, all Records are returned.
3393       */
3394      getRecords : function(index, range) {
3395          if(!lang.isNumber(index)) {
3396              return this._records;
3397          }
3398          if(!lang.isNumber(range)) {
3399              return this._records.slice(index);
3400          }
3401          return this._records.slice(index, index+range);
3402      },
3403  
3404      /**
3405       * Returns a boolean indicating whether Records exist in the RecordSet at the
3406       * specified index range.  Returns true if and only if a Record exists at each
3407       * index in the range.
3408       * @method hasRecords
3409       * @param index
3410       * @param range
3411       * @return {Boolean} true if all indices are populated in the RecordSet
3412       */
3413      hasRecords : function (index, range) {
3414          var recs = this.getRecords(index,range);
3415          for (var i = 0; i < range; ++i) {
3416              if (typeof recs[i] === 'undefined') {
3417                  return false;
3418              }
3419          }
3420          return true;
3421      },
3422  
3423      /**
3424       * Returns current position index for the given Record.
3425       *
3426       * @method getRecordIndex
3427       * @param oRecord {YAHOO.widget.Record} Record instance.
3428       * @return {Number} Record's RecordSet position index.
3429       */
3430  
3431      getRecordIndex : function(oRecord) {
3432          if(oRecord) {
3433              for(var i=this._records.length-1; i>-1; i--) {
3434                  if(this._records[i] && oRecord.getId() === this._records[i].getId()) {
3435                      return i;
3436                  }
3437              }
3438          }
3439          return null;
3440  
3441      },
3442  
3443      /**
3444       * Adds one Record to the RecordSet at the given index. If index is null,
3445       * then adds the Record to the end of the RecordSet.
3446       *
3447       * @method addRecord
3448       * @param oData {Object} An object literal of data.
3449       * @param index {Number} (optional) Position index.
3450       * @return {YAHOO.widget.Record} A Record instance.
3451       */
3452      addRecord : function(oData, index) {
3453          if(lang.isObject(oData)) {
3454              var oRecord = this._addRecord(oData, index);
3455              this.fireEvent("recordAddEvent",{record:oRecord,data:oData});
3456              return oRecord;
3457          }
3458          else {
3459              return null;
3460          }
3461      },
3462  
3463      /**
3464       * Adds multiple Records at once to the RecordSet at the given index with the
3465       * given object literal data. If index is null, then the new Records are
3466       * added to the end of the RecordSet.
3467       *
3468       * @method addRecords
3469       * @param aData {Object[]} An object literal data or an array of data object literals.
3470       * @param index {Number} (optional) Position index.
3471       * @return {YAHOO.widget.Record[]} An array of Record instances.
3472       */
3473      addRecords : function(aData, index) {
3474          if(lang.isArray(aData)) {
3475              var newRecords = [],
3476                  idx,i,len;
3477  
3478              index = lang.isNumber(index) ? index : this._records.length;
3479              idx = index;
3480  
3481              // Can't go backwards bc we need to preserve order
3482              for(i=0,len=aData.length; i<len; ++i) {
3483                  if(lang.isObject(aData[i])) {
3484                      var record = this._addRecord(aData[i], idx++);
3485                      newRecords.push(record);
3486                  }
3487             }
3488              this.fireEvent("recordsAddEvent",{records:newRecords,data:aData});
3489             return newRecords;
3490          }
3491          else if(lang.isObject(aData)) {
3492              var oRecord = this._addRecord(aData);
3493              this.fireEvent("recordsAddEvent",{records:[oRecord],data:aData});
3494              return oRecord;
3495          }
3496          else {
3497              return null;
3498          }
3499      },
3500  
3501      /**
3502       * Sets or replaces one Record to the RecordSet at the given index. Unlike
3503       * addRecord, an existing Record at that index is not shifted to preserve it.
3504       * If no index is specified, it adds the Record to the end of the RecordSet.
3505       *
3506       * @method setRecord
3507       * @param oData {Object} An object literal of data.
3508       * @param index {Number} (optional) Position index.
3509       * @return {YAHOO.widget.Record} A Record instance.
3510       */
3511      setRecord : function(oData, index) {
3512          if(lang.isObject(oData)) {
3513              var oRecord = this._setRecord(oData, index);
3514              this.fireEvent("recordSetEvent",{record:oRecord,data:oData});
3515              return oRecord;
3516          }
3517          else {
3518              return null;
3519          }
3520      },
3521  
3522      /**
3523       * Sets or replaces multiple Records at once to the RecordSet with the given
3524       * data, starting at the given index. If index is not specified, then the new
3525       * Records are added to the end of the RecordSet.
3526       *
3527       * @method setRecords
3528       * @param aData {Object[]} An array of object literal data.
3529       * @param index {Number} (optional) Position index.
3530       * @return {YAHOO.widget.Record[]} An array of Record instances.
3531       */
3532      setRecords : function(aData, index) {
3533          var Rec   = widget.Record,
3534              a     = lang.isArray(aData) ? aData : [aData],
3535              added = [],
3536              i = 0, l = a.length, j = 0;
3537  
3538          index = parseInt(index,10)|0;
3539  
3540          for(; i < l; ++i) {
3541              if (typeof a[i] === 'object' && a[i]) {
3542                  added[j++] = this._records[index + i] = new Rec(a[i]);
3543              }
3544          }
3545  
3546          this.fireEvent("recordsSetEvent",{records:added,data:aData});
3547          // Backward compatibility for bug 1918245
3548          this.fireEvent("recordsSet",{records:added,data:aData});
3549  
3550          if (a.length && !added.length) {
3551          }
3552  
3553          return added;
3554      },
3555  
3556      /**
3557       * Updates given Record with given data.
3558       *
3559       * @method updateRecord
3560       * @param record {YAHOO.widget.Record | Number | String} A Record instance,
3561       * a RecordSet position index, or a Record ID.
3562       * @param oData {Object} Object literal of new data.
3563       * @return {YAHOO.widget.Record} Updated Record, or null.
3564       */
3565      updateRecord : function(record, oData) {
3566          var oRecord = this.getRecord(record);
3567          if(oRecord && lang.isObject(oData)) {
3568              // Copy data from the Record for the event that gets fired later
3569              var oldData = {};
3570              for(var key in oRecord._oData) {
3571                  if(lang.hasOwnProperty(oRecord._oData, key)) {
3572                      oldData[key] = oRecord._oData[key];
3573                  }
3574              }
3575              oRecord._oData = oData;
3576              this.fireEvent("recordUpdateEvent",{record:oRecord,newData:oData,oldData:oldData});
3577              return oRecord;
3578          }
3579          else {
3580              return null;
3581          }
3582      },
3583  
3584      /**
3585       * @method updateKey
3586       * @deprecated Use updateRecordValue
3587       */
3588      updateKey : function(record, sKey, oData) {
3589          this.updateRecordValue(record, sKey, oData);
3590      },
3591      /**
3592       * Sets given Record at given key to given data.
3593       *
3594       * @method updateRecordValue
3595       * @param record {YAHOO.widget.Record | Number | String} A Record instance,
3596       * a RecordSet position index, or a Record ID.
3597       * @param sKey {String} Key name.
3598       * @param oData {Object} New data.
3599       */
3600      updateRecordValue : function(record, sKey, oData) {
3601          var oRecord = this.getRecord(record);
3602          if(oRecord) {
3603              var oldData = null;
3604              var keyValue = oRecord._oData[sKey];
3605              // Copy data from the Record for the event that gets fired later
3606              if(keyValue && lang.isObject(keyValue)) {
3607                  oldData = {};
3608                  for(var key in keyValue)  {
3609                      if(lang.hasOwnProperty(keyValue, key)) {
3610                          oldData[key] = keyValue[key];
3611                      }
3612                  }
3613              }
3614              // Copy by value
3615              else {
3616                  oldData = keyValue;
3617              }
3618  
3619              oRecord._oData[sKey] = oData;
3620              this.fireEvent("keyUpdateEvent",{record:oRecord,key:sKey,newData:oData,oldData:oldData});
3621              this.fireEvent("recordValueUpdateEvent",{record:oRecord,key:sKey,newData:oData,oldData:oldData});
3622          }
3623          else {
3624          }
3625      },
3626  
3627      /**
3628       * Replaces all Records in RecordSet with new object literal data.
3629       *
3630       * @method replaceRecords
3631       * @param data {Object || Object[]} An object literal of data or an array of
3632       * data object literals.
3633       * @return {YAHOO.widget.Record || YAHOO.widget.Record[]} A Record instance or
3634       * an array of Records.
3635       */
3636      replaceRecords : function(data) {
3637          this.reset();
3638          return this.addRecords(data);
3639      },
3640  
3641      /**
3642       * Sorts all Records by given function. Records keep their unique IDs but will
3643       * have new RecordSet position indexes.
3644       *
3645       * @method sortRecords
3646       * @param fnSort {Function} Reference to a sort function.
3647       * @param desc {Boolean} True if sort direction is descending, false if sort
3648       * direction is ascending.
3649       * @param field {String} The field to sort by, from sortOptions.field
3650       * @return {YAHOO.widget.Record[]} Sorted array of Records.
3651       */
3652      sortRecords : function(fnSort, desc, field) {
3653          return this._records.sort(function(a, b) {return fnSort(a, b, desc, field);});
3654      },
3655  
3656      /**
3657       * Reverses all Records, so ["one", "two", "three"] becomes ["three", "two", "one"].
3658       *
3659       * @method reverseRecords
3660       * @return {YAHOO.widget.Record[]} Reverse-sorted array of Records.
3661       */
3662      reverseRecords : function() {
3663          return this._records.reverse();
3664      },
3665  
3666      /**
3667       * Removes the Record at the given position index from the RecordSet. If a range
3668       * is also provided, removes that many Records, starting from the index. Length
3669       * of RecordSet is correspondingly shortened.
3670       *
3671       * @method deleteRecord
3672       * @param index {Number} Record's RecordSet position index.
3673       * @return {Object} A copy of the data held by the deleted Record.
3674       */
3675      deleteRecord : function(index) {
3676          if(lang.isNumber(index) && (index > -1) && (index < this.getLength())) {
3677              var oData = this.getRecord(index).getData();
3678              
3679              this._deleteRecord(index);
3680              this.fireEvent("recordDeleteEvent",{data:oData,index:index});
3681              return oData;
3682          }
3683          else {
3684              return null;
3685          }
3686      },
3687  
3688      /**
3689       * Removes the Record at the given position index from the RecordSet. If a range
3690       * is also provided, removes that many Records, starting from the index. Length
3691       * of RecordSet is correspondingly shortened.
3692       *
3693       * @method deleteRecords
3694       * @param index {Number} Record's RecordSet position index.
3695       * @param range {Number} (optional) How many Records to delete.
3696       * @return {Object[]} An array of copies of the data held by the deleted Records.     
3697       */
3698      deleteRecords : function(index, range) {
3699          if(!lang.isNumber(range)) {
3700              range = 1;
3701          }
3702          if(lang.isNumber(index) && (index > -1) && (index < this.getLength())) {
3703              var recordsToDelete = this.getRecords(index, range);
3704              var deletedData = [], // this mistakenly held Records, not data
3705                  deletedObjects = []; // this passes data only
3706              
3707              for(var i=0; i<recordsToDelete.length; i++) {
3708                  deletedData[deletedData.length] = recordsToDelete[i]; // backward compatibility
3709                  deletedObjects[deletedObjects.length] = recordsToDelete[i].getData();
3710              }
3711              this._deleteRecord(index, range);
3712  
3713              this.fireEvent("recordsDeleteEvent",{data:deletedData,deletedData:deletedObjects,index:index});
3714  
3715              return deletedData;
3716          }
3717          else {
3718              return null;
3719          }
3720      },
3721  
3722      /**
3723       * Deletes all Records from the RecordSet.
3724       *
3725       * @method reset
3726       */
3727      reset : function() {
3728          this._records = [];
3729          //this._length = 0;
3730          this.fireEvent("resetEvent");
3731      }
3732  };
3733  
3734  /////////////////////////////////////////////////////////////////////////////
3735  //
3736  // Custom Events
3737  //
3738  /////////////////////////////////////////////////////////////////////////////
3739  
3740  // RecordSet uses EventProvider
3741  lang.augmentProto(RS, util.EventProvider);
3742  
3743  /**
3744   * Fired when a new Record is added to the RecordSet.
3745   *
3746   * @event recordAddEvent
3747   * @param oArgs.record {YAHOO.widget.Record} The Record instance.
3748   * @param oArgs.data {Object} Data added.
3749   */
3750  
3751  /**
3752   * Fired when multiple Records are added to the RecordSet at once.
3753   *
3754   * @event recordsAddEvent
3755   * @param oArgs.records {YAHOO.widget.Record[]} An array of Record instances.
3756   * @param oArgs.data {Object[]} Data added.
3757   */
3758  
3759  /**
3760   * Fired when a Record is set in the RecordSet.
3761   *
3762   * @event recordSetEvent
3763   * @param oArgs.record {YAHOO.widget.Record} The Record instance.
3764   * @param oArgs.data {Object} Data added.
3765   */
3766  
3767  /**
3768   * Fired when multiple Records are set in the RecordSet at once.
3769   *
3770   * @event recordsSetEvent
3771   * @param oArgs.records {YAHOO.widget.Record[]} An array of Record instances.
3772   * @param oArgs.data {Object[]} Data added.
3773   */
3774  
3775  /**
3776   * Fired when a Record is updated with new data.
3777   *
3778   * @event recordUpdateEvent
3779   * @param oArgs.record {YAHOO.widget.Record} The Record instance.
3780   * @param oArgs.newData {Object} New data.
3781   * @param oArgs.oldData {Object} Old data.
3782   */
3783  
3784  /**
3785   * Fired when a Record is deleted from the RecordSet.
3786   *
3787   * @event recordDeleteEvent
3788   * @param oArgs.data {Object} The data held by the deleted Record,
3789   * or an array of data object literals if multiple Records were deleted at once.
3790   * @param oArgs.index {Object} Index of the deleted Record.
3791   */
3792  
3793  /**
3794   * Fired when multiple Records are deleted from the RecordSet at once.
3795   *
3796   * @event recordsDeleteEvent
3797   * @param oArgs.data {Object[]} An array of deleted Records.
3798   * @param oArgs.deletedData {Object[]} An array of deleted data.
3799   * @param oArgs.index {Object} Index of the first deleted Record.
3800   */
3801  
3802  /**
3803   * Fired when all Records are deleted from the RecordSet at once.
3804   *
3805   * @event resetEvent
3806   */
3807  
3808  /**
3809   * @event keyUpdateEvent    
3810   * @deprecated Use recordValueUpdateEvent     
3811   */
3812  
3813  /**
3814   * Fired when a Record value is updated with new data.
3815   *
3816   * @event recordValueUpdateEvent
3817   * @param oArgs.record {YAHOO.widget.Record} The Record instance.
3818   * @param oArgs.key {String} The updated key.
3819   * @param oArgs.newData {Object} New data.
3820   * @param oArgs.oldData {Object} Old data.
3821   *
3822   */
3823  
3824  
3825  /****************************************************************************/
3826  /****************************************************************************/
3827  /****************************************************************************/
3828  
3829  /**
3830   * The Record class defines a DataTable record.
3831   *
3832   * @namespace YAHOO.widget
3833   * @class Record
3834   * @constructor
3835   * @param oConfigs {Object} (optional) Object literal of key/value pairs.
3836   */
3837  YAHOO.widget.Record = function(oLiteral) {
3838      this._nCount = widget.Record._nCount;
3839      this._sId = Dom.generateId(null, "yui-rec");//"yui-rec" + this._nCount;
3840      widget.Record._nCount++;
3841      this._oData = {};
3842      if(lang.isObject(oLiteral)) {
3843          for(var sKey in oLiteral) {
3844              if(lang.hasOwnProperty(oLiteral, sKey)) {
3845                  this._oData[sKey] = oLiteral[sKey];
3846              }
3847          }
3848      }
3849  };
3850  
3851  /////////////////////////////////////////////////////////////////////////////
3852  //
3853  // Private member variables
3854  //
3855  /////////////////////////////////////////////////////////////////////////////
3856  
3857  /**
3858   * Internal class variable to give unique IDs to Record instances.
3859   *
3860   * @property Record._nCount
3861   * @type Number
3862   * @private
3863   */
3864  YAHOO.widget.Record._nCount = 0;
3865  
3866  YAHOO.widget.Record.prototype = {
3867      /**
3868       * Immutable unique count assigned at instantiation. Remains constant while a
3869       * Record's position index can change from sorting.
3870       *
3871       * @property _nCount
3872       * @type Number
3873       * @private
3874       */
3875      _nCount : null,
3876  
3877      /**
3878       * Immutable unique ID assigned at instantiation. Remains constant while a
3879       * Record's position index can change from sorting.
3880       *
3881       * @property _sId
3882       * @type String
3883       * @private
3884       */
3885      _sId : null,
3886  
3887      /**
3888       * Holds data for the Record in an object literal.
3889       *
3890       * @property _oData
3891       * @type Object
3892       * @private
3893       */
3894      _oData : null,
3895  
3896      /////////////////////////////////////////////////////////////////////////////
3897      //
3898      // Public member variables
3899      //
3900      /////////////////////////////////////////////////////////////////////////////
3901  
3902      /////////////////////////////////////////////////////////////////////////////
3903      //
3904      // Public methods
3905      //
3906      /////////////////////////////////////////////////////////////////////////////
3907  
3908      /**
3909       * Returns unique count assigned at instantiation.
3910       *
3911       * @method getCount
3912       * @return Number
3913       */
3914      getCount : function() {
3915          return this._nCount;
3916      },
3917  
3918      /**
3919       * Returns unique ID assigned at instantiation.
3920       *
3921       * @method getId
3922       * @return String
3923       */
3924      getId : function() {
3925          return this._sId;
3926      },
3927  
3928      /**
3929       * Returns data for the Record for a field if given, or the entire object
3930       * literal otherwise.
3931       *
3932       * @method getData
3933       * @param sField {String} (Optional) The field from which to retrieve data value.
3934       * @return Object
3935       */
3936      getData : function(sField) {
3937          if(lang.isString(sField)) {
3938              return this._oData[sField];
3939          }
3940          else {
3941              return this._oData;
3942          }
3943      },
3944  
3945      /**
3946       * Sets given data at the given key. Use the RecordSet method updateRecordValue to trigger
3947       * events. 
3948       *
3949       * @method setData
3950       * @param sKey {String} The key of the new value.
3951       * @param oData {MIXED} The new value.
3952       */
3953      setData : function(sKey, oData) {
3954          this._oData[sKey] = oData;
3955      }
3956  };
3957  
3958  })();
3959  
3960  (function () {
3961  
3962  var lang   = YAHOO.lang,
3963      util   = YAHOO.util,
3964      widget = YAHOO.widget,
3965      ua     = YAHOO.env.ua,
3966      
3967      Dom    = util.Dom,
3968      Ev     = util.Event,
3969      DS     = util.DataSourceBase;
3970  
3971  /**
3972   * The DataTable widget provides a progressively enhanced DHTML control for
3973   * displaying tabular data across A-grade browsers.
3974   *
3975   * @module datatable
3976   * @requires yahoo, dom, event, element, datasource
3977   * @optional dragdrop, dragdrop
3978   * @title DataTable Widget
3979   */
3980  
3981  /****************************************************************************/
3982  /****************************************************************************/
3983  /****************************************************************************/
3984  
3985  /**
3986   * DataTable class for the YUI DataTable widget.
3987   *
3988   * @namespace YAHOO.widget
3989   * @class DataTable
3990   * @extends YAHOO.util.Element
3991   * @constructor
3992   * @param elContainer {HTMLElement} Container element for the TABLE.
3993   * @param aColumnDefs {Object[]} Array of object literal Column definitions.
3994   * @param oDataSource {YAHOO.util.DataSource} DataSource instance.
3995   * @param oConfigs {object} (optional) Object literal of configuration values.
3996   */
3997  YAHOO.widget.DataTable = function(elContainer,aColumnDefs,oDataSource,oConfigs) {
3998      var DT = widget.DataTable;
3999      
4000      ////////////////////////////////////////////////////////////////////////////
4001      // Backward compatibility for SDT, but prevent infinite loops
4002      
4003      if(oConfigs && oConfigs.scrollable) {
4004          return new YAHOO.widget.ScrollingDataTable(elContainer,aColumnDefs,oDataSource,oConfigs);
4005      }
4006      
4007      ////////////////////////////////////////////////////////////////////////////
4008      // Initialization
4009  
4010      // Internal vars
4011      this._nIndex = DT._nCount;
4012      this._sId = Dom.generateId(null, "yui-dt");// "yui-dt"+this._nIndex;
4013      this._oChainRender = new YAHOO.util.Chain();
4014      this._oChainRender.subscribe("end",this._onRenderChainEnd, this, true);
4015  
4016      // Initialize configs
4017      this._initConfigs(oConfigs);
4018  
4019      // Initialize DataSource
4020      this._initDataSource(oDataSource);
4021      if(!this._oDataSource) {
4022          return;
4023      }
4024  
4025      // Initialize ColumnSet
4026      this._initColumnSet(aColumnDefs);
4027      if(!this._oColumnSet) {
4028          return;
4029      }
4030  
4031      // Initialize RecordSet
4032      this._initRecordSet();
4033      if(!this._oRecordSet) {
4034      }
4035  
4036      // Initialize Attributes
4037      DT.superclass.constructor.call(this, elContainer, this.configs);
4038  
4039      // Initialize DOM elements
4040      var okDom = this._initDomElements(elContainer);
4041      if(!okDom) {
4042          return;
4043      }
4044              
4045      // Show message as soon as config is available
4046      this.showTableMessage(this.get("MSG_LOADING"), DT.CLASS_LOADING);
4047      
4048      ////////////////////////////////////////////////////////////////////////////
4049      // Once per instance
4050      this._initEvents();
4051  
4052      DT._nCount++;
4053      DT._nCurrentCount++;
4054      
4055      ////////////////////////////////////////////////////////////////////////////
4056      // Data integration
4057  
4058      // Send a simple initial request
4059      var oCallback = {
4060          success : this.onDataReturnSetRows,
4061          failure : this.onDataReturnSetRows,
4062          scope   : this,
4063          argument: this.getState()
4064      };
4065      
4066      var initialLoad = this.get("initialLoad");
4067      if(initialLoad === true) {
4068          this._oDataSource.sendRequest(this.get("initialRequest"), oCallback);
4069      }
4070      // Do not send an initial request at all
4071      else if(initialLoad === false) {
4072          this.showTableMessage(this.get("MSG_EMPTY"), DT.CLASS_EMPTY);
4073      }
4074      // Send an initial request with a custom payload
4075      else {
4076          var oCustom = initialLoad || {};
4077          oCallback.argument = oCustom.argument || {};
4078          this._oDataSource.sendRequest(oCustom.request, oCallback);
4079      }
4080  };
4081  
4082  var DT = widget.DataTable;
4083  
4084  /////////////////////////////////////////////////////////////////////////////
4085  //
4086  // Public constants
4087  //
4088  /////////////////////////////////////////////////////////////////////////////
4089  
4090  lang.augmentObject(DT, {
4091  
4092      /**
4093       * Class name assigned to outer DataTable container.
4094       *
4095       * @property DataTable.CLASS_DATATABLE
4096       * @type String
4097       * @static
4098       * @final
4099       * @default "yui-dt"
4100       */
4101      CLASS_DATATABLE : "yui-dt",
4102  
4103      /**
4104       * Class name assigned to liner DIV elements.
4105       *
4106       * @property DataTable.CLASS_LINER
4107       * @type String
4108       * @static
4109       * @final
4110       * @default "yui-dt-liner"
4111       */
4112      CLASS_LINER : "yui-dt-liner",
4113  
4114      /**
4115       * Class name assigned to display label elements.
4116       *
4117       * @property DataTable.CLASS_LABEL
4118       * @type String
4119       * @static
4120       * @final
4121       * @default "yui-dt-label"
4122       */
4123      CLASS_LABEL : "yui-dt-label",
4124  
4125      /**
4126       * Class name assigned to messaging elements.
4127       *
4128       * @property DataTable.CLASS_MESSAGE
4129       * @type String
4130       * @static
4131       * @final
4132       * @default "yui-dt-message"
4133       */
4134      CLASS_MESSAGE : "yui-dt-message",
4135  
4136      /**
4137       * Class name assigned to mask element when DataTable is disabled.
4138       *
4139       * @property DataTable.CLASS_MASK
4140       * @type String
4141       * @static
4142       * @final
4143       * @default "yui-dt-mask"
4144       */
4145      CLASS_MASK : "yui-dt-mask",
4146  
4147      /**
4148       * Class name assigned to data elements.
4149       *
4150       * @property DataTable.CLASS_DATA
4151       * @type String
4152       * @static
4153       * @final
4154       * @default "yui-dt-data"
4155       */
4156      CLASS_DATA : "yui-dt-data",
4157  
4158      /**
4159       * Class name assigned to Column drag target.
4160       *
4161       * @property DataTable.CLASS_COLTARGET
4162       * @type String
4163       * @static
4164       * @final
4165       * @default "yui-dt-coltarget"
4166       */
4167      CLASS_COLTARGET : "yui-dt-coltarget",
4168  
4169      /**
4170       * Class name assigned to resizer handle elements.
4171       *
4172       * @property DataTable.CLASS_RESIZER
4173       * @type String
4174       * @static
4175       * @final
4176       * @default "yui-dt-resizer"
4177       */
4178      CLASS_RESIZER : "yui-dt-resizer",
4179  
4180      /**
4181       * Class name assigned to resizer liner elements.
4182       *
4183       * @property DataTable.CLASS_RESIZERLINER
4184       * @type String
4185       * @static
4186       * @final
4187       * @default "yui-dt-resizerliner"
4188       */
4189      CLASS_RESIZERLINER : "yui-dt-resizerliner",
4190  
4191      /**
4192       * Class name assigned to resizer proxy elements.
4193       *
4194       * @property DataTable.CLASS_RESIZERPROXY
4195       * @type String
4196       * @static
4197       * @final
4198       * @default "yui-dt-resizerproxy"
4199       */
4200      CLASS_RESIZERPROXY : "yui-dt-resizerproxy",
4201  
4202      /**
4203       * Class name assigned to CellEditor container elements.
4204       *
4205       * @property DataTable.CLASS_EDITOR
4206       * @type String
4207       * @static
4208       * @final
4209       * @default "yui-dt-editor"
4210       */
4211      CLASS_EDITOR : "yui-dt-editor",
4212  
4213      /**
4214       * Class name assigned to CellEditor container shim.
4215       *
4216       * @property DataTable.CLASS_EDITOR_SHIM
4217       * @type String
4218       * @static
4219       * @final
4220       * @default "yui-dt-editor-shim"
4221       */
4222      CLASS_EDITOR_SHIM : "yui-dt-editor-shim",
4223  
4224      /**
4225       * Class name assigned to paginator container elements.
4226       *
4227       * @property DataTable.CLASS_PAGINATOR
4228       * @type String
4229       * @static
4230       * @final
4231       * @default "yui-dt-paginator"
4232       */
4233      CLASS_PAGINATOR : "yui-dt-paginator",
4234  
4235      /**
4236       * Class name assigned to page number indicators.
4237       *
4238       * @property DataTable.CLASS_PAGE
4239       * @type String
4240       * @static
4241       * @final
4242       * @default "yui-dt-page"
4243       */
4244      CLASS_PAGE : "yui-dt-page",
4245  
4246      /**
4247       * Class name assigned to default indicators.
4248       *
4249       * @property DataTable.CLASS_DEFAULT
4250       * @type String
4251       * @static
4252       * @final
4253       * @default "yui-dt-default"
4254       */
4255      CLASS_DEFAULT : "yui-dt-default",
4256  
4257      /**
4258       * Class name assigned to previous indicators.
4259       *
4260       * @property DataTable.CLASS_PREVIOUS
4261       * @type String
4262       * @static
4263       * @final
4264       * @default "yui-dt-previous"
4265       */
4266      CLASS_PREVIOUS : "yui-dt-previous",
4267  
4268      /**
4269       * Class name assigned next indicators.
4270       *
4271       * @property DataTable.CLASS_NEXT
4272       * @type String
4273       * @static
4274       * @final
4275       * @default "yui-dt-next"
4276       */
4277      CLASS_NEXT : "yui-dt-next",
4278  
4279      /**
4280       * Class name assigned to first elements.
4281       *
4282       * @property DataTable.CLASS_FIRST
4283       * @type String
4284       * @static
4285       * @final
4286       * @default "yui-dt-first"
4287       */
4288      CLASS_FIRST : "yui-dt-first",
4289  
4290      /**
4291       * Class name assigned to last elements.
4292       *
4293       * @property DataTable.CLASS_LAST
4294       * @type String
4295       * @static
4296       * @final
4297       * @default "yui-dt-last"
4298       */
4299      CLASS_LAST : "yui-dt-last",
4300  
4301      /**
4302       * Class name assigned to Record elements.
4303       *
4304       * @property DataTable.CLASS_REC
4305       * @type String
4306       * @static
4307       * @final
4308       * @default "yui-dt-rec"
4309       */
4310      CLASS_REC : "yui-dt-rec",
4311  
4312      /**
4313       * Class name assigned to even elements.
4314       *
4315       * @property DataTable.CLASS_EVEN
4316       * @type String
4317       * @static
4318       * @final
4319       * @default "yui-dt-even"
4320       */
4321      CLASS_EVEN : "yui-dt-even",
4322  
4323      /**
4324       * Class name assigned to odd elements.
4325       *
4326       * @property DataTable.CLASS_ODD
4327       * @type String
4328       * @static
4329       * @final
4330       * @default "yui-dt-odd"
4331       */
4332      CLASS_ODD : "yui-dt-odd",
4333  
4334      /**
4335       * Class name assigned to selected elements.
4336       *
4337       * @property DataTable.CLASS_SELECTED
4338       * @type String
4339       * @static
4340       * @final
4341       * @default "yui-dt-selected"
4342       */
4343      CLASS_SELECTED : "yui-dt-selected",
4344  
4345      /**
4346       * Class name assigned to highlighted elements.
4347       *
4348       * @property DataTable.CLASS_HIGHLIGHTED
4349       * @type String
4350       * @static
4351       * @final
4352       * @default "yui-dt-highlighted"
4353       */
4354      CLASS_HIGHLIGHTED : "yui-dt-highlighted",
4355  
4356      /**
4357       * Class name assigned to hidden elements.
4358       *
4359       * @property DataTable.CLASS_HIDDEN
4360       * @type String
4361       * @static
4362       * @final
4363       * @default "yui-dt-hidden"
4364       */
4365      CLASS_HIDDEN : "yui-dt-hidden",
4366  
4367      /**
4368       * Class name assigned to disabled elements.
4369       *
4370       * @property DataTable.CLASS_DISABLED
4371       * @type String
4372       * @static
4373       * @final
4374       * @default "yui-dt-disabled"
4375       */
4376      CLASS_DISABLED : "yui-dt-disabled",
4377  
4378      /**
4379       * Class name assigned to empty indicators.
4380       *
4381       * @property DataTable.CLASS_EMPTY
4382       * @type String
4383       * @static
4384       * @final
4385       * @default "yui-dt-empty"
4386       */
4387      CLASS_EMPTY : "yui-dt-empty",
4388  
4389      /**
4390       * Class name assigned to loading indicatorx.
4391       *
4392       * @property DataTable.CLASS_LOADING
4393       * @type String
4394       * @static
4395       * @final
4396       * @default "yui-dt-loading"
4397       */
4398      CLASS_LOADING : "yui-dt-loading",
4399  
4400      /**
4401       * Class name assigned to error indicators.
4402       *
4403       * @property DataTable.CLASS_ERROR
4404       * @type String
4405       * @static
4406       * @final
4407       * @default "yui-dt-error"
4408       */
4409      CLASS_ERROR : "yui-dt-error",
4410  
4411      /**
4412       * Class name assigned to editable elements.
4413       *
4414       * @property DataTable.CLASS_EDITABLE
4415       * @type String
4416       * @static
4417       * @final
4418       * @default "yui-dt-editable"
4419       */
4420      CLASS_EDITABLE : "yui-dt-editable",
4421  
4422      /**
4423       * Class name assigned to draggable elements.
4424       *
4425       * @property DataTable.CLASS_DRAGGABLE
4426       * @type String
4427       * @static
4428       * @final
4429       * @default "yui-dt-draggable"
4430       */
4431      CLASS_DRAGGABLE : "yui-dt-draggable",
4432  
4433      /**
4434       * Class name assigned to resizeable elements.
4435       *
4436       * @property DataTable.CLASS_RESIZEABLE
4437       * @type String
4438       * @static
4439       * @final
4440       * @default "yui-dt-resizeable"
4441       */
4442      CLASS_RESIZEABLE : "yui-dt-resizeable",
4443  
4444      /**
4445       * Class name assigned to scrollable elements.
4446       *
4447       * @property DataTable.CLASS_SCROLLABLE
4448       * @type String
4449       * @static
4450       * @final
4451       * @default "yui-dt-scrollable"
4452       */
4453      CLASS_SCROLLABLE : "yui-dt-scrollable",
4454  
4455      /**
4456       * Class name assigned to sortable elements.
4457       *
4458       * @property DataTable.CLASS_SORTABLE
4459       * @type String
4460       * @static
4461       * @final
4462       * @default "yui-dt-sortable"
4463       */
4464      CLASS_SORTABLE : "yui-dt-sortable",
4465  
4466      /**
4467       * Class name assigned to ascending elements.
4468       *
4469       * @property DataTable.CLASS_ASC
4470       * @type String
4471       * @static
4472       * @final
4473       * @default "yui-dt-asc"
4474       */
4475      CLASS_ASC : "yui-dt-asc",
4476  
4477      /**
4478       * Class name assigned to descending elements.
4479       *
4480       * @property DataTable.CLASS_DESC
4481       * @type String
4482       * @static
4483       * @final
4484       * @default "yui-dt-desc"
4485       */
4486      CLASS_DESC : "yui-dt-desc",
4487  
4488      /**
4489       * Class name assigned to BUTTON elements and/or container elements.
4490       *
4491       * @property DataTable.CLASS_BUTTON
4492       * @type String
4493       * @static
4494       * @final
4495       * @default "yui-dt-button"
4496       */
4497      CLASS_BUTTON : "yui-dt-button",
4498  
4499      /**
4500       * Class name assigned to INPUT TYPE=CHECKBOX elements and/or container elements.
4501       *
4502       * @property DataTable.CLASS_CHECKBOX
4503       * @type String
4504       * @static
4505       * @final
4506       * @default "yui-dt-checkbox"
4507       */
4508      CLASS_CHECKBOX : "yui-dt-checkbox",
4509  
4510      /**
4511       * Class name assigned to SELECT elements and/or container elements.
4512       *
4513       * @property DataTable.CLASS_DROPDOWN
4514       * @type String
4515       * @static
4516       * @final
4517       * @default "yui-dt-dropdown"
4518       */
4519      CLASS_DROPDOWN : "yui-dt-dropdown",
4520  
4521      /**
4522       * Class name assigned to INPUT TYPE=RADIO elements and/or container elements.
4523       *
4524       * @property DataTable.CLASS_RADIO
4525       * @type String
4526       * @static
4527       * @final
4528       * @default "yui-dt-radio"
4529       */
4530      CLASS_RADIO : "yui-dt-radio",
4531  
4532      /////////////////////////////////////////////////////////////////////////
4533      //
4534      // Private static properties
4535      //
4536      /////////////////////////////////////////////////////////////////////////
4537  
4538      /**
4539       * Internal class variable for indexing multiple DataTable instances.
4540       *
4541       * @property DataTable._nCount
4542       * @type Number
4543       * @private
4544       * @static
4545       */
4546      _nCount : 0,
4547  
4548      /**
4549       * Internal class variable tracking current number of DataTable instances,
4550       * so that certain class values can be reset when all instances are destroyed.          
4551       *
4552       * @property DataTable._nCurrentCount
4553       * @type Number
4554       * @private
4555       * @static
4556       */
4557      _nCurrentCount : 0,
4558  
4559      /**
4560       * Reference to the STYLE node that is dynamically created and updated
4561       * in order to manage Column widths.
4562       *
4563       * @property DataTable._elDynStyleNode
4564       * @type HTMLElement
4565       * @private
4566       * @static     
4567       */
4568      _elDynStyleNode : null,
4569  
4570      /**
4571       * Set to true if _elDynStyleNode cannot be populated due to browser incompatibility.
4572       *
4573       * @property DataTable._bDynStylesFallback
4574       * @type boolean
4575       * @private
4576       * @static     
4577       */
4578      _bDynStylesFallback : (ua.ie) ? true : false,
4579  
4580      /**
4581       * Object literal hash of Columns and their dynamically create style rules.
4582       *
4583       * @property DataTable._oDynStyles
4584       * @type Object
4585       * @private
4586       * @static     
4587       */
4588      _oDynStyles : {},
4589  
4590      /////////////////////////////////////////////////////////////////////////
4591      //
4592      // Private static methods
4593      //
4594      /////////////////////////////////////////////////////////////////////////
4595  
4596      /**
4597       * Clones object literal or array of object literals.
4598       *
4599       * @method DataTable._cloneObject
4600       * @param o {Object} Object.
4601       * @private
4602       * @static     
4603       */
4604      _cloneObject: function(o) {
4605          if(!lang.isValue(o)) {
4606              return o;
4607          }
4608  
4609          var copy = {};
4610  
4611          if(o instanceof YAHOO.widget.BaseCellEditor) {
4612              copy = o;
4613          }
4614          else if(Object.prototype.toString.apply(o) === "[object RegExp]") {
4615              copy = o;
4616          }
4617          else if(lang.isFunction(o)) {
4618              copy = o;
4619          }
4620          else if(lang.isArray(o)) {
4621              var array = [];
4622              for(var i=0,len=o.length;i<len;i++) {
4623                  array[i] = DT._cloneObject(o[i]);
4624              }
4625              copy = array;
4626          }
4627          else if(lang.isObject(o)) {
4628              for (var x in o){
4629                  if(lang.hasOwnProperty(o, x)) {
4630                      if(lang.isValue(o[x]) && lang.isObject(o[x]) || lang.isArray(o[x])) {
4631                          copy[x] = DT._cloneObject(o[x]);
4632                      }
4633                      else {
4634                          copy[x] = o[x];
4635                      }
4636                  }
4637              }
4638          }
4639          else {
4640              copy = o;
4641          }
4642  
4643          return copy;
4644      },
4645  
4646      /**
4647       * Formats a BUTTON element.
4648       *
4649       * @method DataTable.formatButton
4650       * @param el {HTMLElement} The element to format with markup.
4651       * @param oRecord {YAHOO.widget.Record} Record instance.
4652       * @param oColumn {YAHOO.widget.Column} Column instance.
4653       * @param oData {HTML} Data value for the cell. By default, the value
4654       * is what gets written to the BUTTON. String values are treated as markup
4655       * and inserted into the DOM with innerHTML.
4656       * @param oDataTable {YAHOO.widget.DataTable} DataTable instance.
4657       * @static
4658       */
4659      formatButton : function(el, oRecord, oColumn, oData, oDataTable) {
4660          var sValue = lang.isValue(oData) ? oData : "Click";
4661          //TODO: support YAHOO.widget.Button
4662          //if(YAHOO.widget.Button) {
4663  
4664          //}
4665          //else {
4666              el.innerHTML = "<button type=\"button\" class=\""+
4667                      DT.CLASS_BUTTON + "\">" + sValue + "</button>";
4668          //}
4669      },
4670  
4671      /**
4672       * Formats a CHECKBOX element.
4673       *
4674       * @method DataTable.formatCheckbox
4675       * @param el {HTMLElement} The element to format with markup.
4676       * @param oRecord {YAHOO.widget.Record} Record instance.
4677       * @param oColumn {YAHOO.widget.Column} Column instance.
4678       * @param oData {Object | Boolean | HTML} Data value for the cell. Can be a simple
4679       * Boolean to indicate whether checkbox is checked or not. Can be object literal
4680       * {checked:bBoolean, label:sLabel}. String values are treated as markup
4681       * and inserted into the DOM with innerHTML.
4682       * @param oDataTable {YAHOO.widget.DataTable} DataTable instance.
4683       * @static
4684       */
4685      formatCheckbox : function(el, oRecord, oColumn, oData, oDataTable) {
4686          var bChecked = oData;
4687          bChecked = (bChecked) ? " checked=\"checked\"" : "";
4688          el.innerHTML = "<input type=\"checkbox\"" + bChecked +
4689                  " class=\"" + DT.CLASS_CHECKBOX + "\" />";
4690      },
4691  
4692      /**
4693       * Formats currency. Default unit is USD.
4694       *
4695       * @method DataTable.formatCurrency
4696       * @param el {HTMLElement} The element to format with markup.
4697       * @param oRecord {YAHOO.widget.Record} Record instance.
4698       * @param oColumn {YAHOO.widget.Column} Column instance.
4699       * @param oData {Number} Data value for the cell.
4700       * @param oDataTable {YAHOO.widget.DataTable} DataTable instance.
4701       * @static
4702       */
4703      formatCurrency : function(el, oRecord, oColumn, oData, oDataTable) {
4704          var oDT = oDataTable || this;
4705          el.innerHTML = util.Number.format(oData, oColumn.currencyOptions || oDT.get("currencyOptions"));
4706      },
4707  
4708      /**
4709       * Formats JavaScript Dates.
4710       *
4711       * @method DataTable.formatDate
4712       * @param el {HTMLElement} The element to format with markup.
4713       * @param oRecord {YAHOO.widget.Record} Record instance.
4714       * @param oColumn {YAHOO.widget.Column} Column instance.
4715       * @param oData {Object} Data value for the cell, or null. String values are
4716       * treated as markup and inserted into the DOM with innerHTML.
4717       * @param oDataTable {YAHOO.widget.DataTable} DataTable instance.
4718       * @static
4719       */
4720      formatDate : function(el, oRecord, oColumn, oData, oDataTable) {
4721          var oDT = oDataTable || this,
4722              oConfig = oColumn.dateOptions || oDT.get("dateOptions");
4723          el.innerHTML = util.Date.format(oData, oConfig, oConfig.locale);
4724      },
4725  
4726      /**
4727       * Formats SELECT elements.
4728       *
4729       * @method DataTable.formatDropdown
4730       * @param el {HTMLElement} The element to format with markup.
4731       * @param oRecord {YAHOO.widget.Record} Record instance.
4732       * @param oColumn {YAHOO.widget.Column} Column instance.
4733       * @param oData {Object} Data value for the cell, or null. String values may
4734       * be treated as markup and inserted into the DOM with innerHTML as element
4735       * label.
4736       * @param oDataTable {YAHOO.widget.DataTable} DataTable instance.
4737       * @static
4738       */
4739      formatDropdown : function(el, oRecord, oColumn, oData, oDataTable) {
4740          var oDT = oDataTable || this,
4741              selectedValue = (lang.isValue(oData)) ? oData : oRecord.getData(oColumn.field),
4742              options = (lang.isArray(oColumn.dropdownOptions)) ?
4743                  oColumn.dropdownOptions : null,
4744  
4745              selectEl,
4746              collection = el.getElementsByTagName("select");
4747  
4748          // Create the form element only once, so we can attach the onChange listener
4749          if(collection.length === 0) {
4750              // Create SELECT element
4751              selectEl = document.createElement("select");
4752              selectEl.className = DT.CLASS_DROPDOWN;
4753              selectEl = el.appendChild(selectEl);
4754  
4755              // Add event listener
4756              Ev.addListener(selectEl,"change",oDT._onDropdownChange,oDT);
4757          }
4758  
4759          selectEl = collection[0];
4760  
4761          // Update the form element
4762          if(selectEl) {
4763              // Clear out previous options
4764              selectEl.innerHTML = "";
4765  
4766              // We have options to populate
4767              if(options) {
4768                  // Create OPTION elements
4769                  for(var i=0; i<options.length; i++) {
4770                      var option = options[i];
4771                      var optionEl = document.createElement("option");
4772                      optionEl.value = (lang.isValue(option.value)) ?
4773                              option.value : option;
4774                      // Bug 2334323: Support legacy text, support label for consistency with DropdownCellEditor
4775                      optionEl.innerHTML = (lang.isValue(option.text)) ?
4776                              option.text : (lang.isValue(option.label)) ? option.label : option;
4777                      optionEl = selectEl.appendChild(optionEl);
4778                      if (optionEl.value == selectedValue) {
4779                          optionEl.selected = true;
4780                      }
4781                  }
4782              }
4783              // Selected value is our only option
4784              else {
4785                  selectEl.innerHTML = "<option selected value=\"" + selectedValue + "\">" + selectedValue + "</option>";
4786              }
4787          }
4788          else {
4789              el.innerHTML = lang.isValue(oData) ? oData : "";
4790          }
4791      },
4792  
4793      /**
4794       * Formats emails.
4795       *
4796       * @method DataTable.formatEmail
4797       * @param el {HTMLElement} The element to format with markup.
4798       * @param oRecord {YAHOO.widget.Record} Record instance.
4799       * @param oColumn {YAHOO.widget.Column} Column instance.
4800       * @param oData {String} Data value for the cell, or null. Values are
4801       * HTML-escaped.
4802       * @param oDataTable {YAHOO.widget.DataTable} DataTable instance.
4803       * @static
4804       */
4805      formatEmail : function(el, oRecord, oColumn, oData, oDataTable) {
4806          if(lang.isString(oData)) {
4807              oData = lang.escapeHTML(oData);
4808              el.innerHTML = "<a href=\"mailto:" + oData + "\">" + oData + "</a>";
4809          }
4810          else {
4811              el.innerHTML = lang.isValue(oData) ? lang.escapeHTML(oData.toString()) : "";
4812          }
4813      },
4814  
4815      /**
4816       * Formats links.
4817       *
4818       * @method DataTable.formatLink
4819       * @param el {HTMLElement} The element to format with markup.
4820       * @param oRecord {YAHOO.widget.Record} Record instance.
4821       * @param oColumn {YAHOO.widget.Column} Column instance.
4822       * @param oData {String} Data value for the cell, or null. Values are
4823       * HTML-escaped
4824       * @param oDataTable {YAHOO.widget.DataTable} DataTable instance.
4825       * @static
4826       */
4827      formatLink : function(el, oRecord, oColumn, oData, oDataTable) {
4828          if(lang.isString(oData)) {
4829              oData = lang.escapeHTML(oData);
4830              el.innerHTML = "<a href=\"" + oData + "\">" + oData + "</a>";
4831          }
4832          else {
4833              el.innerHTML = lang.isValue(oData) ? lang.escapeHTML(oData.toString()) : "";
4834          }
4835      },
4836  
4837      /**
4838       * Formats numbers.
4839       *
4840       * @method DataTable.formatNumber
4841       * @param el {HTMLElement} The element to format with markup.
4842       * @param oRecord {YAHOO.widget.Record} Record instance.
4843       * @param oColumn {YAHOO.widget.Column} Column instance.
4844       * @param oData {Object} Data value for the cell, or null.
4845       * @param oDataTable {YAHOO.widget.DataTable} DataTable instance.
4846       * @static
4847       */
4848      formatNumber : function(el, oRecord, oColumn, oData, oDataTable) {
4849          var oDT = oDataTable || this;
4850          el.innerHTML = util.Number.format(oData, oColumn.numberOptions || oDT.get("numberOptions"));
4851      },
4852  
4853      /**
4854       * Formats INPUT TYPE=RADIO elements.
4855       *
4856       * @method DataTable.formatRadio
4857       * @param el {HTMLElement} The element to format with markup.
4858       * @param oRecord {YAHOO.widget.Record} Record instance.
4859       * @param oColumn {YAHOO.widget.Column} Column instance.
4860       * @param oData {Object} (Optional) Data value for the cell.
4861       * @param oDataTable {YAHOO.widget.DataTable} DataTable instance.
4862       * @static
4863       */
4864      formatRadio : function(el, oRecord, oColumn, oData, oDataTable) {
4865          var oDT = oDataTable || this,
4866              bChecked = oData;
4867          bChecked = (bChecked) ? " checked=\"checked\"" : "";
4868          el.innerHTML = "<input type=\"radio\"" + bChecked +
4869                  " name=\""+oDT.getId()+"-col-" + oColumn.getSanitizedKey() + "\"" +
4870                  " class=\"" + DT.CLASS_RADIO+ "\" />";
4871      },
4872  
4873      /**
4874       * Formats text strings.
4875       *
4876       * @method DataTable.formatText
4877       * @param el {HTMLElement} The element to format with markup.
4878       * @param oRecord {YAHOO.widget.Record} Record instance.
4879       * @param oColumn {YAHOO.widget.Column} Column instance.
4880       * @param oData {String} (Optional) Data value for the cell. Values are
4881       * HTML-escaped.
4882       * @param oDataTable {YAHOO.widget.DataTable} DataTable instance.
4883       * @static
4884       */
4885      formatText : function(el, oRecord, oColumn, oData, oDataTable) {
4886          var value = (lang.isValue(oData)) ? oData : "";
4887          el.innerHTML = lang.escapeHTML(value.toString());
4888      },
4889  
4890      /**
4891       * Formats TEXTAREA elements.
4892       *
4893       * @method DataTable.formatTextarea
4894       * @param el {HTMLElement} The element to format with markup.
4895       * @param oRecord {YAHOO.widget.Record} Record instance.
4896       * @param oColumn {YAHOO.widget.Column} Column instance.
4897       * @param oData {Object} (Optional) Data value for the cell. Values are
4898       * HTML-escaped.
4899       * @param oDataTable {YAHOO.widget.DataTable} DataTable instance.
4900       * @static
4901       */
4902      formatTextarea : function(el, oRecord, oColumn, oData, oDataTable) {
4903          var value = (lang.isValue(oData)) ? lang.escapeHTML(oData.toString()) : "",
4904              markup = "<textarea>" + value + "</textarea>";
4905          el.innerHTML = markup;
4906      },
4907  
4908      /**
4909       * Formats INPUT TYPE=TEXT elements.
4910       *
4911       * @method DataTable.formatTextbox
4912       * @param el {HTMLElement} The element to format with markup.
4913       * @param oRecord {YAHOO.widget.Record} Record instance.
4914       * @param oColumn {YAHOO.widget.Column} Column instance.
4915       * @param oData {Object} (Optional) Data value for the cell. Values are
4916       * HTML-escaped.
4917       * @param oDataTable {YAHOO.widget.DataTable} DataTable instance.
4918       * @static
4919       */
4920      formatTextbox : function(el, oRecord, oColumn, oData, oDataTable) {
4921          var value = (lang.isValue(oData)) ? lang.escapeHTML(oData.toString()) : "",
4922              markup = "<input type=\"text\" value=\"" + value + "\" />";
4923          el.innerHTML = markup;
4924      },
4925  
4926      /**
4927       * Default cell formatter
4928       *
4929       * @method DataTable.formatDefault
4930       * @param el {HTMLElement} The element to format with markup.
4931       * @param oRecord {YAHOO.widget.Record} Record instance.
4932       * @param oColumn {YAHOO.widget.Column} Column instance.
4933       * @param oData {HTML} (Optional) Data value for the cell. String values are
4934       * treated as markup and inserted into the DOM with innerHTML.
4935       * @param oDataTable {YAHOO.widget.DataTable} DataTable instance.
4936       * @static
4937       */
4938      formatDefault : function(el, oRecord, oColumn, oData, oDataTable) {
4939          el.innerHTML = (lang.isValue(oData) && oData !== "") ? oData.toString() : "&#160;";
4940      },
4941  
4942      /**
4943       * Validates data value to type Number, doing type conversion as
4944       * necessary. A valid Number value is return, else null is returned
4945       * if input value does not validate.
4946       *
4947       *
4948       * @method DataTable.validateNumber
4949       * @param oData {Object} Data to validate.
4950       * @static
4951      */
4952      validateNumber : function(oData) {
4953          //Convert to number
4954          var number = oData * 1;
4955  
4956          // Validate
4957          if(lang.isNumber(number)) {
4958              return number;
4959          }
4960          else {
4961              return undefined;
4962          }
4963      }
4964  });
4965  
4966  // Done in separate step so referenced functions are defined.
4967  /**
4968   * Registry of cell formatting functions, enables shortcut pointers in Column
4969   * definition formatter value (i.e., {key:"myColumn", formatter:"date"}).
4970   * @property DataTable.Formatter
4971   * @type Object
4972   * @static
4973   */
4974  DT.Formatter = {
4975      button   : DT.formatButton,
4976      checkbox : DT.formatCheckbox,
4977      currency : DT.formatCurrency,
4978      "date"   : DT.formatDate,
4979      dropdown : DT.formatDropdown,
4980      email    : DT.formatEmail,
4981      link     : DT.formatLink,
4982      "number" : DT.formatNumber,
4983      radio    : DT.formatRadio,
4984      text     : DT.formatText,
4985      textarea : DT.formatTextarea,
4986      textbox  : DT.formatTextbox,
4987  
4988      defaultFormatter : DT.formatDefault
4989  };
4990  
4991  lang.extend(DT, util.Element, {
4992  
4993  /////////////////////////////////////////////////////////////////////////////
4994  //
4995  // Superclass methods
4996  //
4997  /////////////////////////////////////////////////////////////////////////////
4998  
4999  /**
5000   * Implementation of Element's abstract method. Sets up config values.
5001   *
5002   * @method initAttributes
5003   * @param oConfigs {Object} (Optional) Object literal definition of configuration values.
5004   * @private
5005   */
5006  
5007  initAttributes : function(oConfigs) {
5008      oConfigs = oConfigs || {};
5009      DT.superclass.initAttributes.call(this, oConfigs);
5010  
5011      /**
5012      * @attribute summary
5013      * @description String value for the SUMMARY attribute.
5014      * @type String
5015      * @default ""    
5016      */
5017      this.setAttributeConfig("summary", {
5018          value: "",
5019          validator: lang.isString,
5020          method: function(sSummary) {
5021              if(this._elTable) {
5022                  this._elTable.summary = sSummary;
5023              }
5024          }
5025      });
5026  
5027      /**
5028      * @attribute selectionMode
5029      * @description Specifies row or cell selection mode. Accepts the following strings:
5030      *    <dl>
5031      *      <dt>"standard"</dt>
5032      *      <dd>Standard row selection with support for modifier keys to enable
5033      *      multiple selections.</dd>
5034      *
5035      *      <dt>"single"</dt>
5036      *      <dd>Row selection with modifier keys disabled to not allow
5037      *      multiple selections.</dd>
5038      *
5039      *      <dt>"singlecell"</dt>
5040      *      <dd>Cell selection with modifier keys disabled to not allow
5041      *      multiple selections.</dd>
5042      *
5043      *      <dt>"cellblock"</dt>
5044      *      <dd>Cell selection with support for modifier keys to enable multiple
5045      *      selections in a block-fashion, like a spreadsheet.</dd>
5046      *
5047      *      <dt>"cellrange"</dt>
5048      *      <dd>Cell selection with support for modifier keys to enable multiple
5049      *      selections in a range-fashion, like a calendar.</dd>
5050      *    </dl>
5051      *
5052      * @default "standard"
5053      * @type String
5054      */
5055      this.setAttributeConfig("selectionMode", {
5056          value: "standard",
5057          validator: lang.isString
5058      });
5059  
5060      /**
5061      * @attribute sortedBy
5062      * @description Object literal provides metadata for initial sort values if
5063      * data will arrive pre-sorted:
5064      * <dl>
5065      *     <dt>sortedBy.key</dt>
5066      *     <dd>{String} Key of sorted Column</dd>
5067      *     <dt>sortedBy.dir</dt>
5068      *     <dd>{String} Initial sort direction, either YAHOO.widget.DataTable.CLASS_ASC or YAHOO.widget.DataTable.CLASS_DESC</dd>
5069      * </dl>
5070      * @type Object | null
5071      */
5072      this.setAttributeConfig("sortedBy", {
5073          value: null,
5074          // TODO: accepted array for nested sorts
5075          validator: function(oNewSortedBy) {
5076              if(oNewSortedBy) {
5077                  return (lang.isObject(oNewSortedBy) && oNewSortedBy.key);
5078              }
5079              else {
5080                  return (oNewSortedBy === null);
5081              }
5082          },
5083          method: function(oNewSortedBy) {
5084              // Stash the previous value
5085              var oOldSortedBy = this.get("sortedBy");
5086              
5087              // Workaround for bug 1827195
5088              this._configs.sortedBy.value = oNewSortedBy;
5089  
5090              // Remove ASC/DESC from TH
5091              var oOldColumn,
5092                  nOldColumnKeyIndex,
5093                  oNewColumn,
5094                  nNewColumnKeyIndex;
5095                  
5096              if(this._elThead) {
5097                  if(oOldSortedBy && oOldSortedBy.key && oOldSortedBy.dir) {
5098                      oOldColumn = this._oColumnSet.getColumn(oOldSortedBy.key);
5099                      nOldColumnKeyIndex = oOldColumn.getKeyIndex();
5100                      
5101                      // Remove previous UI from THEAD
5102                      var elOldTh = oOldColumn.getThEl();
5103                      Dom.removeClass(elOldTh, oOldSortedBy.dir);
5104                      this.formatTheadCell(oOldColumn.getThLinerEl().firstChild, oOldColumn, oNewSortedBy);
5105                  }
5106                  if(oNewSortedBy) {
5107                      oNewColumn = (oNewSortedBy.column) ? oNewSortedBy.column : this._oColumnSet.getColumn(oNewSortedBy.key);
5108                      nNewColumnKeyIndex = oNewColumn.getKeyIndex();
5109      
5110                      // Update THEAD with new UI
5111                      var elNewTh = oNewColumn.getThEl();
5112                      // Backward compatibility
5113                      if(oNewSortedBy.dir && ((oNewSortedBy.dir == "asc") ||  (oNewSortedBy.dir == "desc"))) {
5114                          var newClass = (oNewSortedBy.dir == "desc") ?
5115                                  DT.CLASS_DESC :
5116                                  DT.CLASS_ASC;
5117                          Dom.addClass(elNewTh, newClass);
5118                      }
5119                      else {
5120                           var sortClass = oNewSortedBy.dir || DT.CLASS_ASC;
5121                           Dom.addClass(elNewTh, sortClass);
5122                      }
5123                      this.formatTheadCell(oNewColumn.getThLinerEl().firstChild, oNewColumn, oNewSortedBy);
5124                  }
5125              }
5126            
5127              if(this._elTbody) {
5128                  // Update TBODY UI
5129                  this._elTbody.style.display = "none";
5130                  var allRows = this._elTbody.rows,
5131                      allCells;
5132                  for(var i=allRows.length-1; i>-1; i--) {
5133                      allCells = allRows[i].childNodes;
5134                      if(allCells[nOldColumnKeyIndex]) {
5135                          Dom.removeClass(allCells[nOldColumnKeyIndex], oOldSortedBy.dir);
5136                      }
5137                      if(allCells[nNewColumnKeyIndex]) {
5138                          Dom.addClass(allCells[nNewColumnKeyIndex], oNewSortedBy.dir);
5139                      }
5140                  }
5141                  this._elTbody.style.display = "";
5142              }
5143                  
5144              this._clearTrTemplateEl();
5145          }
5146      });
5147      
5148      /**
5149      * @attribute paginator
5150      * @description An instance of YAHOO.widget.Paginator.
5151      * @default null
5152      * @type {Object|YAHOO.widget.Paginator}
5153      */
5154      this.setAttributeConfig("paginator", {
5155          value : null,
5156          validator : function (val) {
5157              return val === null || val instanceof widget.Paginator;
5158          },
5159          method : function () { this._updatePaginator.apply(this,arguments); }
5160      });
5161  
5162      /**
5163      * @attribute caption
5164      * @description Value for the CAPTION element. String values are treated as
5165      * markup and inserted into the DOM with innerHTML. NB: Not supported in
5166      * ScrollingDataTable.    
5167      * @type HTML
5168      */
5169      this.setAttributeConfig("caption", {
5170          value: null,
5171          validator: lang.isString,
5172          method: function(sCaption) {
5173              this._initCaptionEl(sCaption);
5174          }
5175      });
5176  
5177      /**
5178      * @attribute draggableColumns
5179      * @description True if Columns are draggable to reorder, false otherwise.
5180      * The Drag & Drop Utility is required to enable this feature. Only top-level
5181      * and non-nested Columns are draggable. Write once.
5182      * @default false
5183      * @type Boolean
5184      */
5185      this.setAttributeConfig("draggableColumns", {
5186          value: false,
5187          validator: lang.isBoolean,
5188          method: function(oParam) {
5189              if(this._elThead) {
5190                  if(oParam) {
5191                      this._initDraggableColumns();
5192                  }
5193                  else {
5194                      this._destroyDraggableColumns();
5195                  }
5196              }
5197          }
5198      });
5199  
5200      /**
5201      * @attribute renderLoopSize      
5202      * @description A value greater than 0 enables DOM rendering of rows to be
5203      * executed from a non-blocking timeout queue and sets how many rows to be
5204      * rendered per timeout. Recommended for very large data sets.     
5205      * @type Number      
5206      * @default 0      
5207      */      
5208       this.setAttributeConfig("renderLoopSize", {
5209           value: 0,
5210           validator: lang.isNumber
5211       });
5212  
5213      /**
5214      * @attribute sortFunction
5215      * @description Default Column sort function, receives the following args:
5216      *    <dl>
5217      *      <dt>a {Object}</dt>
5218      *      <dd>First sort argument.</dd>
5219      *      <dt>b {Object}</dt>
5220      *      <dd>Second sort argument.</dd>
5221  
5222      *      <dt>desc {Boolean}</dt>
5223      *      <dd>True if sort direction is descending, false if
5224      * sort direction is ascending.</dd>
5225      *      <dt>field {String}</dt>
5226      *      <dd>The field to sort by, from sortOptions.field</dd>
5227      *   </dl>
5228      * @type function
5229      */
5230      this.setAttributeConfig("sortFunction", {
5231          value: function(a, b, desc, field) {
5232              var compare = YAHOO.util.Sort.compare,
5233                  sorted = compare(a.getData(field),b.getData(field), desc);
5234              if(sorted === 0) {
5235                  return compare(a.getCount(),b.getCount(), desc); // Bug 1932978
5236              }
5237              else {
5238                  return sorted;
5239              }
5240          }
5241      });
5242  
5243      /**
5244      * @attribute formatRow
5245      * @description A function that accepts a TR element and its associated Record
5246      * for custom formatting. The function must return TRUE in order to automatically
5247      * continue formatting of child TD elements, else TD elements will not be
5248      * automatically formatted.
5249      * @type function
5250      * @default null
5251      */
5252      this.setAttributeConfig("formatRow", {
5253          value: null,
5254          validator: lang.isFunction
5255      });
5256  
5257      /**
5258      * @attribute generateRequest
5259      * @description A function that converts an object literal of desired DataTable
5260      * states into a request value which is then passed to the DataSource's
5261      * sendRequest method in order to retrieve data for those states. This
5262      * function is passed an object literal of state data and a reference to the
5263      * DataTable instance:
5264      *     
5265      * <dl>
5266      *   <dt>pagination<dt>
5267      *   <dd>        
5268      *         <dt>offsetRecord</dt>
5269      *         <dd>{Number} Index of the first Record of the desired page</dd>
5270      *         <dt>rowsPerPage</dt>
5271      *         <dd>{Number} Number of rows per page</dd>
5272      *   </dd>
5273      *   <dt>sortedBy</dt>
5274      *   <dd>                
5275      *         <dt>key</dt>
5276      *         <dd>{String} Key of sorted Column</dd>
5277      *         <dt>dir</dt>
5278      *         <dd>{String} Sort direction, either YAHOO.widget.DataTable.CLASS_ASC or YAHOO.widget.DataTable.CLASS_DESC</dd>
5279      *   </dd>
5280      *   <dt>self</dt>
5281      *   <dd>The DataTable instance</dd>
5282      * </dl>
5283      * 
5284      * and by default returns a String of syntax:
5285      * "sort={sortColumn}&dir={sortDir}&startIndex={pageStartIndex}&results={rowsPerPage}"
5286      * @type function
5287      * @default HTMLFunction
5288      */
5289      this.setAttributeConfig("generateRequest", {
5290          value: function(oState, oSelf) {
5291              // Set defaults
5292              oState = oState || {pagination:null, sortedBy:null};
5293              var sort = encodeURIComponent((oState.sortedBy) ? oState.sortedBy.key : oSelf.getColumnSet().keys[0].getKey());
5294              var dir = (oState.sortedBy && oState.sortedBy.dir === YAHOO.widget.DataTable.CLASS_DESC) ? "desc" : "asc";
5295              var startIndex = (oState.pagination) ? oState.pagination.recordOffset : 0;
5296              var results = (oState.pagination) ? oState.pagination.rowsPerPage : null;
5297              
5298              // Build the request
5299              return  "sort=" + sort +
5300                      "&dir=" + dir +
5301                      "&startIndex=" + startIndex +
5302                      ((results !== null) ? "&results=" + results : "");
5303          },
5304          validator: lang.isFunction
5305      });
5306  
5307      /**
5308      * @attribute initialRequest
5309      * @description Defines the initial request that gets sent to the DataSource
5310      * during initialization. Value is ignored if initialLoad is set to any value
5311      * other than true.    
5312      * @type MIXED
5313      * @default null
5314      */
5315      this.setAttributeConfig("initialRequest", {
5316          value: null
5317      });
5318  
5319      /**
5320      * @attribute initialLoad
5321      * @description Determines whether or not to load data at instantiation. By
5322      * default, will trigger a sendRequest() to the DataSource and pass in the
5323      * request defined by initialRequest. If set to false, data will not load
5324      * at instantiation. Alternatively, implementers who wish to work with a 
5325      * custom payload may pass in an object literal with the following values:
5326      *     
5327      *    <dl>
5328      *      <dt>request (MIXED)</dt>
5329      *      <dd>Request value.</dd>
5330      *
5331      *      <dt>argument (MIXED)</dt>
5332      *      <dd>Custom data that will be passed through to the callback function.</dd>
5333      *    </dl>
5334      *
5335      *                    
5336      * @type Boolean | Object
5337      * @default true
5338      */
5339      this.setAttributeConfig("initialLoad", {
5340          value: true
5341      });
5342      
5343      /**
5344      * @attribute dynamicData
5345      * @description If true, sorting and pagination are relegated to the DataSource
5346      * for handling, using the request returned by the "generateRequest" function.
5347      * Each new DataSource response blows away all previous Records. False by default, so 
5348      * sorting and pagination will be handled directly on the client side, without
5349      * causing any new requests for data from the DataSource.
5350      * @type Boolean
5351      * @default false
5352      */
5353      this.setAttributeConfig("dynamicData", {
5354          value: false,
5355          validator: lang.isBoolean
5356      });
5357  
5358      /**
5359       * @attribute MSG_EMPTY
5360       * @description Message to display if DataTable has no data. String
5361       * values are treated as markup and inserted into the DOM with innerHTML.
5362       * @type HTML
5363       * @default "No records found."
5364       */
5365       this.setAttributeConfig("MSG_EMPTY", {
5366           value: "No records found.",
5367           validator: lang.isString
5368       });      
5369  
5370      /**
5371       * @attribute MSG_LOADING
5372       * @description Message to display while DataTable is loading data. String
5373       * values are treated as markup and inserted into the DOM with innerHTML.
5374       * @type HTML
5375       * @default "Loading..."
5376       */      
5377       this.setAttributeConfig("MSG_LOADING", {
5378           value: "Loading...",
5379           validator: lang.isString
5380       });      
5381  
5382      /**
5383       * @attribute MSG_ERROR
5384       * @description Message to display while DataTable has data error. String
5385       * values are treated as markup and inserted into the DOM with innerHTML.
5386       * @type HTML
5387       * @default "Data error."
5388       */      
5389       this.setAttributeConfig("MSG_ERROR", {
5390           value: "Data error.",
5391           validator: lang.isString
5392       });
5393  
5394      /**
5395       * @attribute MSG_SORTASC
5396       * @description Message to display in tooltip to sort Column in ascending
5397       * order. String values are treated as markup and inserted into the DOM as
5398       * innerHTML.
5399       * @type HTML
5400       * @default "Click to sort ascending"
5401       */      
5402       this.setAttributeConfig("MSG_SORTASC", {      
5403           value: "Click to sort ascending",      
5404           validator: lang.isString,
5405           method: function(sParam) {
5406              if(this._elThead) {
5407                  for(var i=0, allKeys=this.getColumnSet().keys, len=allKeys.length; i<len; i++) {
5408                      if(allKeys[i].sortable && this.getColumnSortDir(allKeys[i]) === DT.CLASS_ASC) {
5409                          allKeys[i]._elThLabel.firstChild.title = sParam;
5410                      }
5411                  }
5412              }      
5413           }
5414       });
5415  
5416      /**
5417       * @attribute MSG_SORTDESC
5418       * @description Message to display in tooltip to sort Column in descending
5419       * order. String values are treated as markup and inserted into the DOM as
5420       * innerHTML.
5421       * @type HTML
5422       * @default "Click to sort descending"
5423       */      
5424       this.setAttributeConfig("MSG_SORTDESC", {      
5425           value: "Click to sort descending",      
5426           validator: lang.isString,
5427           method: function(sParam) {
5428              if(this._elThead) {
5429                  for(var i=0, allKeys=this.getColumnSet().keys, len=allKeys.length; i<len; i++) {
5430                      if(allKeys[i].sortable && this.getColumnSortDir(allKeys[i]) === DT.CLASS_DESC) {
5431                          allKeys[i]._elThLabel.firstChild.title = sParam;
5432                      }
5433                  }
5434              }               
5435           }
5436       });
5437       
5438      /**
5439       * @attribute currencySymbol
5440       * @deprecated Use currencyOptions.
5441       */
5442      this.setAttributeConfig("currencySymbol", {
5443          value: "$",
5444          validator: lang.isString
5445      });
5446      
5447      /**
5448       * Default config passed to YAHOO.util.Number.format() by the 'currency' Column formatter.
5449       * @attribute currencyOptions
5450       * @type Object
5451       * @default {prefix: $, decimalPlaces:2, decimalSeparator:".", thousandsSeparator:","}
5452       */
5453      this.setAttributeConfig("currencyOptions", {
5454          value: {
5455              prefix: this.get("currencySymbol"), // TODO: deprecate currencySymbol
5456              decimalPlaces:2,
5457              decimalSeparator:".",
5458              thousandsSeparator:","
5459          }
5460      });
5461      
5462      /**
5463       * Default config passed to YAHOO.util.Date.format() by the 'date' Column formatter.
5464       * @attribute dateOptions
5465       * @type Object
5466       * @default {format:"%m/%d/%Y", locale:"en"}
5467       */
5468      this.setAttributeConfig("dateOptions", {
5469          value: {format:"%m/%d/%Y", locale:"en"}
5470      });
5471      
5472      /**
5473       * Default config passed to YAHOO.util.Number.format() by the 'number' Column formatter.
5474       * @attribute numberOptions
5475       * @type Object
5476       * @default {decimalPlaces:0, thousandsSeparator:","}
5477       */
5478      this.setAttributeConfig("numberOptions", {
5479          value: {
5480              decimalPlaces:0,
5481              thousandsSeparator:","
5482          }
5483      });
5484  
5485  },
5486  
5487  /////////////////////////////////////////////////////////////////////////////
5488  //
5489  // Private member variables
5490  //
5491  /////////////////////////////////////////////////////////////////////////////
5492  
5493  /**
5494   * True if instance is initialized, so as to fire the initEvent after render.
5495   *
5496   * @property _bInit
5497   * @type Boolean
5498   * @default true
5499   * @private
5500   */
5501  _bInit : true,
5502  
5503  /**
5504   * Index assigned to instance.
5505   *
5506   * @property _nIndex
5507   * @type Number
5508   * @private
5509   */
5510  _nIndex : null,
5511  
5512  /**
5513   * Counter for IDs assigned to TR elements.
5514   *
5515   * @property _nTrCount
5516   * @type Number
5517   * @private
5518   */
5519  _nTrCount : 0,
5520  
5521  /**
5522   * Counter for IDs assigned to TD elements.
5523   *
5524   * @property _nTdCount
5525   * @type Number
5526   * @private
5527   */
5528  _nTdCount : 0,
5529  
5530  /**
5531   * Unique id assigned to instance "yui-dtN", useful prefix for generating unique
5532   * DOM ID strings and log messages.
5533   *
5534   * @property _sId
5535   * @type String
5536   * @private
5537   */
5538  _sId : null,
5539  
5540  /**
5541   * Render chain.
5542   *
5543   * @property _oChainRender
5544   * @type YAHOO.util.Chain
5545   * @private
5546   */
5547  _oChainRender : null,
5548  
5549  /**
5550   * DOM reference to the container element for the DataTable instance into which
5551   * all other elements get created.
5552   *
5553   * @property _elContainer
5554   * @type HTMLElement
5555   * @private
5556   */
5557  _elContainer : null,
5558  
5559  /**
5560   * DOM reference to the mask element for the DataTable instance which disables it.
5561   *
5562   * @property _elMask
5563   * @type HTMLElement
5564   * @private
5565   */
5566  _elMask : null,
5567  
5568  /**
5569   * DOM reference to the TABLE element for the DataTable instance.
5570   *
5571   * @property _elTable
5572   * @type HTMLElement
5573   * @private
5574   */
5575  _elTable : null,
5576  
5577  /**
5578   * DOM reference to the CAPTION element for the DataTable instance.
5579   *
5580   * @property _elCaption
5581   * @type HTMLElement
5582   * @private
5583   */
5584  _elCaption : null,
5585  
5586  /**
5587   * DOM reference to the COLGROUP element for the DataTable instance.
5588   *
5589   * @property _elColgroup
5590   * @type HTMLElement
5591   * @private
5592   */
5593  _elColgroup : null,
5594  
5595  /**
5596   * DOM reference to the THEAD element for the DataTable instance.
5597   *
5598   * @property _elThead
5599   * @type HTMLElement
5600   * @private
5601   */
5602  _elThead : null,
5603  
5604  /**
5605   * DOM reference to the primary TBODY element for the DataTable instance.
5606   *
5607   * @property _elTbody
5608   * @type HTMLElement
5609   * @private
5610   */
5611  _elTbody : null,
5612  
5613  /**
5614   * DOM reference to the secondary TBODY element used to display DataTable messages.
5615   *
5616   * @property _elMsgTbody
5617   * @type HTMLElement
5618   * @private
5619   */
5620  _elMsgTbody : null,
5621  
5622  /**
5623   * DOM reference to the secondary TBODY element's single TR element used to display DataTable messages.
5624   *
5625   * @property _elMsgTr
5626   * @type HTMLElement
5627   * @private
5628   */
5629  _elMsgTr : null,
5630  
5631  /**
5632   * DOM reference to the secondary TBODY element's single TD element used to display DataTable messages.
5633   *
5634   * @property _elMsgTd
5635   * @type HTMLElement
5636   * @private
5637   */
5638  _elMsgTd : null,
5639  
5640  /**
5641   * Element reference to shared Column drag target.
5642   *
5643   * @property _elColumnDragTarget
5644   * @type HTMLElement
5645   * @private
5646   */
5647  _elColumnDragTarget : null,
5648  
5649  /**
5650   * Element reference to shared Column resizer proxy.
5651   *
5652   * @property _elColumnResizerProxy
5653   * @type HTMLElement
5654   * @private
5655   */
5656  _elColumnResizerProxy : null,
5657  
5658  /**
5659   * DataSource instance for the DataTable instance.
5660   *
5661   * @property _oDataSource
5662   * @type YAHOO.util.DataSource
5663   * @private
5664   */
5665  _oDataSource : null,
5666  
5667  /**
5668   * ColumnSet instance for the DataTable instance.
5669   *
5670   * @property _oColumnSet
5671   * @type YAHOO.widget.ColumnSet
5672   * @private
5673   */
5674  _oColumnSet : null,
5675  
5676  /**
5677   * RecordSet instance for the DataTable instance.
5678   *
5679   * @property _oRecordSet
5680   * @type YAHOO.widget.RecordSet
5681   * @private
5682   */
5683  _oRecordSet : null,
5684  
5685  /**
5686   * The active CellEditor instance for the DataTable instance.
5687   *
5688   * @property _oCellEditor
5689   * @type YAHOO.widget.CellEditor
5690   * @private
5691   */
5692  _oCellEditor : null,
5693  
5694  /**
5695   * ID string of first TR element of the current DataTable page.
5696   *
5697   * @property _sFirstTrId
5698   * @type String
5699   * @private
5700   */
5701  _sFirstTrId : null,
5702  
5703  /**
5704   * ID string of the last TR element of the current DataTable page.
5705   *
5706   * @property _sLastTrId
5707   * @type String
5708   * @private
5709   */
5710  _sLastTrId : null,
5711  
5712  /**
5713   * Template row to create all new rows from.
5714   * @property _elTrTemplate
5715   * @type {HTMLElement}
5716   * @private 
5717   */
5718  _elTrTemplate : null,
5719  
5720  /**
5721   * Sparse array of custom functions to set column widths for browsers that don't
5722   * support dynamic CSS rules.  Functions are added at the index representing
5723   * the number of rows they update.
5724   *
5725   * @property _aDynFunctions
5726   * @type Array
5727   * @private
5728   */
5729  _aDynFunctions : [],
5730  
5731  /**
5732   * Disabled state.
5733   *
5734   * @property _disabled
5735   * @type Boolean
5736   * @private
5737   */
5738  _disabled : false,
5739  
5740  
5741  
5742  
5743  
5744  
5745  
5746  
5747  
5748  
5749  
5750  
5751  
5752  
5753  
5754  
5755  
5756  
5757  
5758  
5759  
5760  
5761  
5762  
5763  
5764  
5765  
5766  
5767  /////////////////////////////////////////////////////////////////////////////
5768  //
5769  // Private methods
5770  //
5771  /////////////////////////////////////////////////////////////////////////////
5772  
5773  /**
5774   * Clears browser text selection. Useful to call on rowSelectEvent or
5775   * cellSelectEvent to prevent clicks or dblclicks from selecting text in the
5776   * browser.
5777   *
5778   * @method clearTextSelection
5779   */
5780  clearTextSelection : function() {
5781      var sel;
5782      if(window.getSelection) {
5783          sel = window.getSelection();
5784      }
5785      else if(document.getSelection) {
5786          sel = document.getSelection();
5787      }
5788      else if(document.selection) {
5789          sel = document.selection;
5790      }
5791      if(sel) {
5792          if(sel.empty) {
5793              sel.empty();
5794          }
5795          else if (sel.removeAllRanges) {
5796              sel.removeAllRanges();
5797          }
5798          else if(sel.collapse) {
5799              sel.collapse();
5800          }
5801      }
5802  },
5803  
5804  /**
5805   * Sets focus on the given element.
5806   *
5807   * @method _focusEl
5808   * @param el {HTMLElement} Element.
5809   * @private
5810   */
5811  _focusEl : function(el) {
5812      el = el || this._elTbody;
5813      // http://developer.mozilla.org/en/docs/index.php?title=Key-navigable_custom_DHTML_widgets
5814      // The timeout is necessary in both IE and Firefox 1.5, to prevent scripts from doing
5815      // strange unexpected things as the user clicks on buttons and other controls.
5816      setTimeout(function() {
5817          try {
5818              el.focus();
5819          }
5820          catch(e) {
5821          }
5822      },0);
5823  },
5824  
5825  /**
5826   * Forces Gecko repaint.
5827   *
5828   * @method _repaintGecko
5829   * @el {HTMLElement} (Optional) Element to repaint, otherwise entire document body.
5830   * @private
5831   */
5832  _repaintGecko : (ua.gecko) ? 
5833      function(el) {
5834          el = el || this._elContainer;
5835          var parent = el.parentNode;
5836          var nextSibling = el.nextSibling;
5837          parent.insertBefore(parent.removeChild(el), nextSibling);
5838      } : function() {},
5839  
5840  /**
5841   * Forces Opera repaint.
5842   *
5843   * @method _repaintOpera
5844   * @private 
5845   */
5846  _repaintOpera : (ua.opera) ? 
5847      function() {
5848          if(ua.opera) {
5849              document.documentElement.className += " ";
5850              document.documentElement.className = YAHOO.lang.trim(document.documentElement.className);
5851          }
5852      } : function() {} ,
5853  
5854  /**
5855   * Forces Webkit repaint.
5856   *
5857   * @method _repaintWebkit
5858   * @el {HTMLElement} (Optional) Element to repaint, otherwise entire document body.
5859   * @private
5860   */
5861  _repaintWebkit : (ua.webkit) ? 
5862      function(el) {
5863          el = el || this._elContainer;
5864          var parent = el.parentNode;
5865          var nextSibling = el.nextSibling;
5866          parent.insertBefore(parent.removeChild(el), nextSibling);
5867      } : function() {},
5868  
5869  
5870  
5871  
5872  
5873  
5874  
5875  
5876  
5877  
5878  
5879  
5880  
5881  
5882  
5883  
5884  
5885  
5886  
5887  
5888  
5889  
5890  // INIT FUNCTIONS
5891  
5892  /**
5893   * Initializes object literal of config values.
5894   *
5895   * @method _initConfigs
5896   * @param oConfig {Object} Object literal of config values.
5897   * @private
5898   */
5899  _initConfigs : function(oConfigs) {
5900      if(!oConfigs || !lang.isObject(oConfigs)) {
5901          oConfigs = {};
5902      }
5903      this.configs = oConfigs;
5904  },
5905  
5906  /**
5907   * Initializes ColumnSet.
5908   *
5909   * @method _initColumnSet
5910   * @param aColumnDefs {Object[]} Array of object literal Column definitions.
5911   * @private
5912   */
5913  _initColumnSet : function(aColumnDefs) {
5914      var oColumn, i, len;
5915      
5916      if(this._oColumnSet) {
5917          // First clear _oDynStyles for existing ColumnSet and
5918          // uregister CellEditor Custom Events
5919          for(i=0, len=this._oColumnSet.keys.length; i<len; i++) {
5920              oColumn = this._oColumnSet.keys[i];
5921              DT._oDynStyles["."+this.getId()+"-col-"+oColumn.getSanitizedKey()+" ."+DT.CLASS_LINER] = undefined;
5922              if(oColumn.editor && oColumn.editor.unsubscribeAll) { // Backward compatibility
5923                  oColumn.editor.unsubscribeAll();
5924              }
5925          }
5926          
5927          this._oColumnSet = null;
5928          this._clearTrTemplateEl();
5929      }
5930      
5931      if(lang.isArray(aColumnDefs)) {
5932          this._oColumnSet =  new YAHOO.widget.ColumnSet(aColumnDefs);
5933      }
5934      // Backward compatibility
5935      else if(aColumnDefs instanceof YAHOO.widget.ColumnSet) {
5936          this._oColumnSet =  aColumnDefs;
5937      }
5938  
5939      // Register CellEditor Custom Events
5940      var allKeys = this._oColumnSet.keys;
5941      for(i=0, len=allKeys.length; i<len; i++) {
5942          oColumn = allKeys[i];
5943          if(oColumn.editor && oColumn.editor.subscribe) { // Backward incompatibility
5944              oColumn.editor.subscribe("showEvent", this._onEditorShowEvent, this, true);
5945              oColumn.editor.subscribe("keydownEvent", this._onEditorKeydownEvent, this, true);
5946              oColumn.editor.subscribe("revertEvent", this._onEditorRevertEvent, this, true);
5947              oColumn.editor.subscribe("saveEvent", this._onEditorSaveEvent, this, true);
5948              oColumn.editor.subscribe("cancelEvent", this._onEditorCancelEvent, this, true);
5949              oColumn.editor.subscribe("blurEvent", this._onEditorBlurEvent, this, true);
5950              oColumn.editor.subscribe("blockEvent", this._onEditorBlockEvent, this, true);
5951              oColumn.editor.subscribe("unblockEvent", this._onEditorUnblockEvent, this, true);
5952          }
5953      }
5954  },
5955  
5956  /**
5957   * Initializes DataSource.
5958   *
5959   * @method _initDataSource
5960   * @param oDataSource {YAHOO.util.DataSource} DataSource instance.
5961   * @private
5962   */
5963  _initDataSource : function(oDataSource) {
5964      this._oDataSource = null;
5965      if(oDataSource && (lang.isFunction(oDataSource.sendRequest))) {
5966          this._oDataSource = oDataSource;
5967      }
5968      // Backward compatibility
5969      else {
5970          var tmpTable = null;
5971          var tmpContainer = this._elContainer;
5972          var i=0;
5973          //TODO: this will break if re-initing DS at runtime for SDT
5974          // Peek in container child nodes to see if TABLE already exists
5975          if(tmpContainer.hasChildNodes()) {
5976              var tmpChildren = tmpContainer.childNodes;
5977              for(i=0; i<tmpChildren.length; i++) {
5978                  if(tmpChildren[i].nodeName && tmpChildren[i].nodeName.toLowerCase() == "table") {
5979                      tmpTable = tmpChildren[i];
5980                      break;
5981                  }
5982              }
5983              if(tmpTable) {
5984                  var tmpFieldsArray = [];
5985                  for(; i<this._oColumnSet.keys.length; i++) {
5986                      tmpFieldsArray.push({key:this._oColumnSet.keys[i].key});
5987                  }
5988  
5989                  this._oDataSource = new DS(tmpTable);
5990                  this._oDataSource.responseType = DS.TYPE_HTMLTABLE;
5991                  this._oDataSource.responseSchema = {fields: tmpFieldsArray};
5992              }
5993          }
5994      }
5995  },
5996  
5997  /**
5998   * Initializes RecordSet.
5999   *
6000   * @method _initRecordSet
6001   * @private
6002   */
6003  _initRecordSet : function() {
6004      if(this._oRecordSet) {
6005          this._oRecordSet.reset();
6006      }
6007      else {
6008          this._oRecordSet = new YAHOO.widget.RecordSet();
6009      }
6010  },
6011  
6012  /**
6013   * Initializes DOM elements.
6014   *
6015   * @method _initDomElements
6016   * @param elContainer {HTMLElement | String} HTML DIV element by reference or ID. 
6017   * return {Boolean} False in case of error, otherwise true 
6018   * @private
6019   */
6020  _initDomElements : function(elContainer) {
6021      // Outer container
6022      this._initContainerEl(elContainer);
6023      // TABLE
6024      this._initTableEl(this._elContainer);
6025      // COLGROUP
6026      this._initColgroupEl(this._elTable);
6027      // THEAD
6028      this._initTheadEl(this._elTable);
6029      
6030      // Message TBODY
6031      this._initMsgTbodyEl(this._elTable);  
6032  
6033      // Primary TBODY
6034      this._initTbodyEl(this._elTable);
6035  
6036      if(!this._elContainer || !this._elTable || !this._elColgroup ||  !this._elThead || !this._elTbody || !this._elMsgTbody) {
6037          return false;
6038      }
6039      else {
6040          return true;
6041      }
6042  },
6043  
6044  /**
6045   * Destroy's the DataTable outer container element, if available.
6046   *
6047   * @method _destroyContainerEl
6048   * @param elContainer {HTMLElement} Reference to the container element. 
6049   * @private
6050   */
6051  _destroyContainerEl : function(elContainer) {
6052          var columns = this._oColumnSet.keys,
6053          elements, i;
6054  
6055          Dom.removeClass(elContainer, DT.CLASS_DATATABLE);
6056  
6057      // Bug 2528783
6058      Ev.purgeElement( elContainer );
6059      Ev.purgeElement( this._elThead, true ); // recursive to get resize handles
6060      Ev.purgeElement( this._elTbody );
6061      Ev.purgeElement( this._elMsgTbody );
6062  
6063      // because change doesn't bubble, each select (via formatDropdown) gets
6064      // its own subscription
6065      elements = elContainer.getElementsByTagName( 'select' );
6066  
6067      if ( elements.length ) {
6068          Ev.detachListener( elements, 'change' );
6069      }
6070  
6071      for ( i = columns.length - 1; i >= 0; --i ) {
6072          if ( columns[i].editor ) {
6073              Ev.purgeElement( columns[i].editor._elContainer );
6074          }
6075      }
6076  
6077      elContainer.innerHTML = "";
6078      
6079      this._elContainer = null;
6080      this._elColgroup = null;
6081      this._elThead = null;
6082      this._elTbody = null;
6083  },
6084  
6085  /**
6086   * Initializes the DataTable outer container element, including a mask.
6087   *
6088   * @method _initContainerEl
6089   * @param elContainer {HTMLElement | String} HTML DIV element by reference or ID.
6090   * @private
6091   */
6092  _initContainerEl : function(elContainer) {
6093      // Validate container
6094      elContainer = Dom.get(elContainer);
6095      
6096      if(elContainer && elContainer.nodeName && (elContainer.nodeName.toLowerCase() == "div")) {
6097          // Destroy previous
6098          this._destroyContainerEl(elContainer);
6099  
6100          Dom.addClass(elContainer, DT.CLASS_DATATABLE);
6101          Ev.addListener(elContainer, "focus", this._onTableFocus, this);
6102          Ev.addListener(elContainer, "dblclick", this._onTableDblclick, this);
6103          this._elContainer = elContainer;
6104          
6105          var elMask = document.createElement("div");
6106          elMask.className = DT.CLASS_MASK;
6107          elMask.style.display = "none";
6108          this._elMask = elContainer.appendChild(elMask);
6109      }
6110  },
6111  
6112  /**
6113   * Destroy's the DataTable TABLE element, if available.
6114   *
6115   * @method _destroyTableEl
6116   * @private
6117   */
6118  _destroyTableEl : function() {
6119      var elTable = this._elTable;
6120      if(elTable) {
6121          Ev.purgeElement(elTable, true);
6122          elTable.parentNode.removeChild(elTable);
6123          this._elCaption = null;
6124          this._elColgroup = null;
6125          this._elThead = null;
6126          this._elTbody = null;
6127      }
6128  },
6129  
6130  /**
6131   * Creates HTML markup CAPTION element.
6132   *
6133   * @method _initCaptionEl
6134   * @param sCaption {HTML} Caption value. String values are treated as markup and
6135   * inserted into the DOM with innerHTML.
6136   * @private
6137   */
6138  _initCaptionEl : function(sCaption) {
6139      if(this._elTable && sCaption) {
6140          // Create CAPTION element
6141          if(!this._elCaption) { 
6142              this._elCaption = this._elTable.createCaption();
6143          }
6144          // Set CAPTION value
6145          this._elCaption.innerHTML = sCaption;
6146      }
6147      else if(this._elCaption) {
6148          this._elCaption.parentNode.removeChild(this._elCaption);
6149      }
6150  },
6151  
6152  /**
6153   * Creates HTML markup for TABLE, COLGROUP, THEAD and TBODY elements in outer
6154   * container element.
6155   *
6156   * @method _initTableEl
6157   * @param elContainer {HTMLElement} Container element into which to create TABLE.
6158   * @private
6159   */
6160  _initTableEl : function(elContainer) {
6161      if(elContainer) {
6162          // Destroy previous
6163          this._destroyTableEl();
6164      
6165          // Create TABLE
6166          this._elTable = elContainer.appendChild(document.createElement("table"));  
6167           
6168          // Set SUMMARY attribute
6169          this._elTable.summary = this.get("summary");
6170          
6171          // Create CAPTION element
6172          if(this.get("caption")) {
6173              this._initCaptionEl(this.get("caption"));
6174          }
6175  
6176          // Set up mouseover/mouseout events via mouseenter/mouseleave delegation
6177          Ev.delegate(this._elTable, "mouseenter", this._onTableMouseover, "thead ."+DT.CLASS_LABEL, this);
6178          Ev.delegate(this._elTable, "mouseleave", this._onTableMouseout, "thead ."+DT.CLASS_LABEL, this);
6179          Ev.delegate(this._elTable, "mouseenter", this._onTableMouseover, "tbody.yui-dt-data>tr>td", this);
6180          Ev.delegate(this._elTable, "mouseleave", this._onTableMouseout, "tbody.yui-dt-data>tr>td", this);
6181          Ev.delegate(this._elTable, "mouseenter", this._onTableMouseover, "tbody.yui-dt-message>tr>td", this);
6182          Ev.delegate(this._elTable, "mouseleave", this._onTableMouseout, "tbody.yui-dt-message>tr>td", this);
6183      }
6184  },
6185  
6186  /**
6187   * Destroy's the DataTable COLGROUP element, if available.
6188   *
6189   * @method _destroyColgroupEl
6190   * @private
6191   */
6192  _destroyColgroupEl : function() {
6193      var elColgroup = this._elColgroup;
6194      if(elColgroup) {
6195          var elTable = elColgroup.parentNode;
6196          Ev.purgeElement(elColgroup, true);
6197          elTable.removeChild(elColgroup);
6198          this._elColgroup = null;
6199      }
6200  },
6201  
6202  /**
6203   * Initializes COLGROUP and COL elements for managing minWidth.
6204   *
6205   * @method _initColgroupEl
6206   * @param elTable {HTMLElement} TABLE element into which to create COLGROUP.
6207   * @private
6208   */
6209  _initColgroupEl : function(elTable) {
6210      if(elTable) {
6211          // Destroy previous
6212          this._destroyColgroupEl();
6213  
6214          // Add COLs to DOCUMENT FRAGMENT
6215          var allCols = this._aColIds || [],
6216              allKeys = this._oColumnSet.keys,
6217              i = 0, len = allCols.length,
6218              elCol, oColumn,
6219              elFragment = document.createDocumentFragment(),
6220              elColTemplate = document.createElement("col");
6221      
6222          for(i=0,len=allKeys.length; i<len; i++) {
6223              oColumn = allKeys[i];
6224              elCol = elFragment.appendChild(elColTemplate.cloneNode(false));
6225          }
6226      
6227          // Create COLGROUP
6228          var elColgroup = elTable.insertBefore(document.createElement("colgroup"), elTable.firstChild);
6229          elColgroup.appendChild(elFragment);
6230          this._elColgroup = elColgroup;
6231      }
6232  },
6233  
6234  /**
6235   * Adds a COL element to COLGROUP at given index.
6236   *
6237   * @method _insertColgroupColEl
6238   * @param index {Number} Index of new COL element.
6239   * @private
6240   */
6241  _insertColgroupColEl : function(index) {
6242      if(lang.isNumber(index)&& this._elColgroup) {
6243          var nextSibling = this._elColgroup.childNodes[index] || null;
6244          this._elColgroup.insertBefore(document.createElement("col"), nextSibling);
6245      }
6246  },
6247  
6248  /**
6249   * Removes a COL element to COLGROUP at given index.
6250   *
6251   * @method _removeColgroupColEl
6252   * @param index {Number} Index of removed COL element.
6253   * @private
6254   */
6255  _removeColgroupColEl : function(index) {
6256      if(lang.isNumber(index) && this._elColgroup && this._elColgroup.childNodes[index]) {
6257          this._elColgroup.removeChild(this._elColgroup.childNodes[index]);
6258      }
6259  },
6260  
6261  /**
6262   * Reorders a COL element from old index(es) to new index.
6263   *
6264   * @method _reorderColgroupColEl
6265   * @param aKeyIndexes {Number[]} Array of indexes of removed COL element.
6266   * @param newIndex {Number} New index. 
6267   * @private
6268   */
6269  _reorderColgroupColEl : function(aKeyIndexes, newIndex) {
6270      if(lang.isArray(aKeyIndexes) && lang.isNumber(newIndex) && this._elColgroup && (this._elColgroup.childNodes.length > aKeyIndexes[aKeyIndexes.length-1])) {
6271          var i,
6272              tmpCols = [];
6273          // Remove COL
6274          for(i=aKeyIndexes.length-1; i>-1; i--) {
6275              tmpCols.push(this._elColgroup.removeChild(this._elColgroup.childNodes[aKeyIndexes[i]]));
6276          }
6277          // Insert COL
6278          var nextSibling = this._elColgroup.childNodes[newIndex] || null;
6279          for(i=tmpCols.length-1; i>-1; i--) {
6280              this._elColgroup.insertBefore(tmpCols[i], nextSibling);
6281          }
6282      }
6283  },
6284  
6285  /**
6286   * Destroy's the DataTable THEAD element, if available.
6287   *
6288   * @method _destroyTheadEl
6289   * @private
6290   */
6291  _destroyTheadEl : function() {
6292      var elThead = this._elThead;
6293      if(elThead) {
6294          var elTable = elThead.parentNode;
6295          Ev.purgeElement(elThead, true);
6296          this._destroyColumnHelpers();
6297          elTable.removeChild(elThead);
6298          this._elThead = null;
6299      }
6300  },
6301  
6302  /**
6303   * Initializes THEAD element.
6304   *
6305   * @method _initTheadEl
6306   * @param elTable {HTMLElement} TABLE element into which to create COLGROUP.
6307   * @param {HTMLElement} Initialized THEAD element. 
6308   * @private
6309   */
6310  _initTheadEl : function(elTable) {
6311      elTable = elTable || this._elTable;
6312      
6313      if(elTable) {
6314          // Destroy previous
6315          this._destroyTheadEl();
6316      
6317          //TODO: append to DOM later for performance
6318          var elThead = (this._elColgroup) ?
6319              elTable.insertBefore(document.createElement("thead"), this._elColgroup.nextSibling) :
6320              elTable.appendChild(document.createElement("thead"));
6321      
6322          // Set up DOM events for THEAD
6323          Ev.addListener(elThead, "focus", this._onTheadFocus, this);
6324          Ev.addListener(elThead, "keydown", this._onTheadKeydown, this);
6325          Ev.addListener(elThead, "mousedown", this._onTableMousedown, this);
6326          Ev.addListener(elThead, "mouseup", this._onTableMouseup, this);
6327          Ev.addListener(elThead, "click", this._onTheadClick, this);
6328          
6329          // Bug 2528073: mouseover/mouseout handled via mouseenter/mouseleave
6330          // delegation at the TABLE level
6331  
6332          // Since we can't listen for click and dblclick on the same element...
6333          // Attach separately to THEAD and TBODY
6334          ///Ev.addListener(elThead, "dblclick", this._onTableDblclick, this);
6335          
6336         var oColumnSet = this._oColumnSet,
6337              oColumn, i,j, l;
6338          
6339          // Add TRs to the THEAD
6340          var colTree = oColumnSet.tree;
6341          var elTh;
6342          for(i=0; i<colTree.length; i++) {
6343              var elTheadTr = elThead.appendChild(document.createElement("tr"));
6344      
6345              // ...and create TH cells
6346              for(j=0; j<colTree[i].length; j++) {
6347                  oColumn = colTree[i][j];
6348                  elTh = elTheadTr.appendChild(document.createElement("th"));
6349                  this._initThEl(elTh,oColumn);
6350              }
6351      
6352                  // Set FIRST/LAST on THEAD rows
6353                  if(i === 0) {
6354                      Dom.addClass(elTheadTr, DT.CLASS_FIRST);
6355                  }
6356                  if(i === (colTree.length-1)) {
6357                      Dom.addClass(elTheadTr, DT.CLASS_LAST);
6358                  }
6359  
6360          }
6361  
6362          // Set FIRST/LAST on edge TH elements using the values in ColumnSet headers array
6363          var aFirstHeaders = oColumnSet.headers[0] || [];
6364          for(i=0; i<aFirstHeaders.length; i++) {
6365              Dom.addClass(Dom.get(this.getId() +"-th-"+aFirstHeaders[i]), DT.CLASS_FIRST);
6366          }
6367          var aLastHeaders = oColumnSet.headers[oColumnSet.headers.length-1] || [];
6368          for(i=0; i<aLastHeaders.length; i++) {
6369              Dom.addClass(Dom.get(this.getId() +"-th-"+aLastHeaders[i]), DT.CLASS_LAST);
6370          }
6371          
6372  
6373          ///TODO: try _repaintGecko(this._elContainer) instead
6374          // Bug 1806891
6375          if(ua.webkit && ua.webkit < 420) {
6376              var oSelf = this;
6377              setTimeout(function() {
6378                  elThead.style.display = "";
6379              },0);
6380              elThead.style.display = 'none';
6381          }
6382          
6383          this._elThead = elThead;
6384          
6385          // Column helpers needs _elThead to exist
6386          this._initColumnHelpers();  
6387      }
6388  },
6389  
6390  /**
6391   * Populates TH element as defined by Column.
6392   *
6393   * @method _initThEl
6394   * @param elTh {HTMLElement} TH element reference.
6395   * @param oColumn {YAHOO.widget.Column} Column object.
6396   * @private
6397   */
6398  _initThEl : function(elTh, oColumn) {
6399      elTh.id = this.getId() + "-th-" + oColumn.getSanitizedKey(); // Needed for accessibility, getColumn by TH, and ColumnDD
6400      elTh.innerHTML = "";
6401      elTh.rowSpan = oColumn.getRowspan();
6402      elTh.colSpan = oColumn.getColspan();
6403      oColumn._elTh = elTh;
6404      
6405      var elThLiner = elTh.appendChild(document.createElement("div"));
6406      elThLiner.id = elTh.id + "-liner"; // Needed for resizer
6407      elThLiner.className = DT.CLASS_LINER;
6408      oColumn._elThLiner = elThLiner;
6409      
6410      var elThLabel = elThLiner.appendChild(document.createElement("span"));
6411      elThLabel.className = DT.CLASS_LABEL;    
6412  
6413      // Assign abbr attribute
6414      if(oColumn.abbr) {
6415          elTh.abbr = oColumn.abbr;
6416      }
6417      // Clear minWidth on hidden Columns
6418      if(oColumn.hidden) {
6419          this._clearMinWidth(oColumn);
6420      }
6421          
6422      elTh.className = this._getColumnClassNames(oColumn);
6423              
6424      // Set Column width...
6425      if(oColumn.width) {
6426          // Validate minWidth
6427          var nWidth = (oColumn.minWidth && (oColumn.width < oColumn.minWidth)) ?
6428                  oColumn.minWidth : oColumn.width;
6429          // ...for fallback cases
6430          if(DT._bDynStylesFallback) {
6431              elTh.firstChild.style.overflow = 'hidden';
6432              elTh.firstChild.style.width = nWidth + 'px';        
6433          }
6434          // ...for non fallback cases
6435          else {
6436              this._setColumnWidthDynStyles(oColumn, nWidth + 'px', 'hidden');
6437          }
6438      }
6439  
6440      this.formatTheadCell(elThLabel, oColumn, this.get("sortedBy"));
6441      oColumn._elThLabel = elThLabel;
6442  },
6443  
6444  /**
6445   * Outputs markup into the given TH based on given Column.
6446   *
6447   * @method formatTheadCell
6448   * @param elCellLabel {HTMLElement} The label SPAN element within the TH liner,
6449   * not the liner DIV element.     
6450   * @param oColumn {YAHOO.widget.Column} Column instance.
6451   * @param oSortedBy {Object} Sort state object literal.
6452  */
6453  formatTheadCell : function(elCellLabel, oColumn, oSortedBy) {
6454      var sKey = oColumn.getKey();
6455      var sLabel = lang.isValue(oColumn.label) ? oColumn.label : sKey;
6456  
6457      // Add accessibility link for sortable Columns
6458      if(oColumn.sortable) {
6459          // Calculate the direction
6460          var sSortClass = this.getColumnSortDir(oColumn, oSortedBy);
6461          var bDesc = (sSortClass === DT.CLASS_DESC);
6462  
6463          // This is the sorted Column
6464          if(oSortedBy && (oColumn.key === oSortedBy.key)) {
6465              bDesc = !(oSortedBy.dir === DT.CLASS_DESC);
6466          }
6467  
6468          // Generate a unique HREF for visited status
6469          var sHref = this.getId() + "-href-" + oColumn.getSanitizedKey();
6470          
6471          // Generate a dynamic TITLE for sort status
6472          var sTitle = (bDesc) ? this.get("MSG_SORTDESC") : this.get("MSG_SORTASC");
6473          
6474          // Format the element
6475          elCellLabel.innerHTML = "<a href=\"" + sHref + "\" title=\"" + sTitle + "\" class=\"" + DT.CLASS_SORTABLE + "\">" + sLabel + "</a>";
6476      }
6477      // Just display the label for non-sortable Columns
6478      else {
6479          elCellLabel.innerHTML = sLabel;
6480      }
6481  },
6482  
6483  /**
6484   * Disables DD from top-level Column TH elements.
6485   *
6486   * @method _destroyDraggableColumns
6487   * @private
6488   */
6489  _destroyDraggableColumns : function() {
6490      var oColumn, elTh;
6491      for(var i=0, len=this._oColumnSet.tree[0].length; i<len; i++) {
6492          oColumn = this._oColumnSet.tree[0][i];
6493          if(oColumn._dd) {
6494              oColumn._dd = oColumn._dd.unreg();
6495              Dom.removeClass(oColumn.getThEl(), DT.CLASS_DRAGGABLE);       
6496          }
6497      }
6498      
6499      // Destroy column drag proxy
6500      this._destroyColumnDragTargetEl();
6501  },
6502  
6503  /**
6504   * Initializes top-level Column TH elements into DD instances.
6505   *
6506   * @method _initDraggableColumns
6507   * @private
6508   */
6509  _initDraggableColumns : function() {
6510      this._destroyDraggableColumns();
6511      if(util.DD) {
6512          var oColumn, elTh, elDragTarget;
6513          for(var i=0, len=this._oColumnSet.tree[0].length; i<len; i++) {
6514              oColumn = this._oColumnSet.tree[0][i];
6515              elTh = oColumn.getThEl();
6516              Dom.addClass(elTh, DT.CLASS_DRAGGABLE);
6517              elDragTarget = this._initColumnDragTargetEl();
6518              oColumn._dd = new YAHOO.widget.ColumnDD(this, oColumn, elTh, elDragTarget);
6519          }
6520      }
6521      else {
6522      }
6523  },
6524  
6525  /**
6526   * Destroys shared Column drag target.
6527   *
6528   * @method _destroyColumnDragTargetEl
6529   * @private
6530   */
6531  _destroyColumnDragTargetEl : function() {
6532      if(this._elColumnDragTarget) {
6533          var el = this._elColumnDragTarget;
6534          YAHOO.util.Event.purgeElement(el);
6535          el.parentNode.removeChild(el);
6536          this._elColumnDragTarget = null;
6537      }
6538  },
6539  
6540  /**
6541   * Creates HTML markup for shared Column drag target.
6542   *
6543   * @method _initColumnDragTargetEl
6544   * @return {HTMLElement} Reference to Column drag target.
6545   * @private
6546   */
6547  _initColumnDragTargetEl : function() {
6548      if(!this._elColumnDragTarget) {
6549          // Attach Column drag target element as first child of body
6550          var elColumnDragTarget = document.createElement('div');
6551          elColumnDragTarget.id = this.getId() + "-coltarget";
6552          elColumnDragTarget.className = DT.CLASS_COLTARGET;
6553          elColumnDragTarget.style.display = "none";
6554          document.body.insertBefore(elColumnDragTarget, document.body.firstChild);
6555  
6556          // Internal tracker of Column drag target
6557          this._elColumnDragTarget = elColumnDragTarget;
6558  
6559      }
6560      return this._elColumnDragTarget;
6561  },
6562  
6563  /**
6564   * Disables resizeability on key Column TH elements.
6565   *
6566   * @method _destroyResizeableColumns
6567   * @private
6568   */
6569  _destroyResizeableColumns : function() {
6570      var aKeys = this._oColumnSet.keys;
6571      for(var i=0, len=aKeys.length; i<len; i++) {
6572          if(aKeys[i]._ddResizer) {
6573              aKeys[i]._ddResizer = aKeys[i]._ddResizer.unreg();
6574              Dom.removeClass(aKeys[i].getThEl(), DT.CLASS_RESIZEABLE);
6575          }
6576      }
6577  
6578      // Destroy resizer proxy
6579      this._destroyColumnResizerProxyEl();
6580  },
6581  
6582  /**
6583   * Initializes resizeability on key Column TH elements.
6584   *
6585   * @method _initResizeableColumns
6586   * @private
6587   */
6588  _initResizeableColumns : function() {
6589      this._destroyResizeableColumns();
6590      if(util.DD) {
6591          var oColumn, elTh, elThLiner, elThResizerLiner, elThResizer, elResizerProxy, cancelClick;
6592          for(var i=0, len=this._oColumnSet.keys.length; i<len; i++) {
6593              oColumn = this._oColumnSet.keys[i];
6594              if(oColumn.resizeable) {
6595                  elTh = oColumn.getThEl();
6596                  Dom.addClass(elTh, DT.CLASS_RESIZEABLE);
6597                  elThLiner = oColumn.getThLinerEl();
6598                  
6599                  // Bug 1915349: So resizer is as tall as TH when rowspan > 1
6600                  // Create a separate resizer liner with position:relative
6601                  elThResizerLiner = elTh.appendChild(document.createElement("div"));
6602                  elThResizerLiner.className = DT.CLASS_RESIZERLINER;
6603                  
6604                  // Move TH contents into the new resizer liner
6605                  elThResizerLiner.appendChild(elThLiner);
6606                  
6607                  // Create the resizer
6608                  elThResizer = elThResizerLiner.appendChild(document.createElement("div"));
6609                  elThResizer.id = elTh.id + "-resizer"; // Needed for ColumnResizer
6610                  elThResizer.className = DT.CLASS_RESIZER;
6611                  oColumn._elResizer = elThResizer;
6612  
6613                  // Create the resizer proxy, once per instance
6614                  elResizerProxy = this._initColumnResizerProxyEl();
6615                  oColumn._ddResizer = new YAHOO.util.ColumnResizer(
6616                          this, oColumn, elTh, elThResizer, elResizerProxy);
6617                  cancelClick = function(e) {
6618                      Ev.stopPropagation(e);
6619                  };
6620                  Ev.addListener(elThResizer,"click",cancelClick);
6621              }
6622          }
6623      }
6624      else {
6625      }
6626  },
6627  
6628  /**
6629   * Destroys shared Column resizer proxy.
6630   *
6631   * @method _destroyColumnResizerProxyEl
6632   * @return {HTMLElement} Reference to Column resizer proxy.
6633   * @private
6634   */
6635  _destroyColumnResizerProxyEl : function() {
6636      if(this._elColumnResizerProxy) {
6637          var el = this._elColumnResizerProxy;
6638          YAHOO.util.Event.purgeElement(el);
6639          el.parentNode.removeChild(el);
6640          this._elColumnResizerProxy = null;
6641      }
6642  },
6643  
6644  /**
6645   * Creates HTML markup for shared Column resizer proxy.
6646   *
6647   * @method _initColumnResizerProxyEl
6648   * @return {HTMLElement} Reference to Column resizer proxy.
6649   * @private
6650   */
6651  _initColumnResizerProxyEl : function() {
6652      if(!this._elColumnResizerProxy) {
6653          // Attach Column resizer element as first child of body
6654          var elColumnResizerProxy = document.createElement("div");
6655          elColumnResizerProxy.id = this.getId() + "-colresizerproxy"; // Needed for ColumnResizer
6656          elColumnResizerProxy.className = DT.CLASS_RESIZERPROXY;
6657          document.body.insertBefore(elColumnResizerProxy, document.body.firstChild);
6658  
6659          // Internal tracker of Column resizer proxy
6660          this._elColumnResizerProxy = elColumnResizerProxy;
6661      }
6662      return this._elColumnResizerProxy;
6663  },
6664  
6665  /**
6666   * Destroys elements associated with Column functionality: ColumnDD and ColumnResizers.
6667   *
6668   * @method _destroyColumnHelpers
6669   * @private
6670   */
6671  _destroyColumnHelpers : function() {
6672      this._destroyDraggableColumns();
6673      this._destroyResizeableColumns();
6674  },
6675  
6676  /**
6677   * Initializes elements associated with Column functionality: ColumnDD and ColumnResizers.
6678   *
6679   * @method _initColumnHelpers
6680   * @private
6681   */
6682  _initColumnHelpers : function() {
6683      if(this.get("draggableColumns")) {
6684          this._initDraggableColumns();
6685      }
6686      this._initResizeableColumns();
6687  },
6688  
6689  /**
6690   * Destroy's the DataTable TBODY element, if available.
6691   *
6692   * @method _destroyTbodyEl
6693   * @private
6694   */
6695  _destroyTbodyEl : function() {
6696      var elTbody = this._elTbody;
6697      if(elTbody) {
6698          var elTable = elTbody.parentNode;
6699          Ev.purgeElement(elTbody, true);
6700          elTable.removeChild(elTbody);
6701          this._elTbody = null;
6702      }
6703  },
6704  
6705  /**
6706   * Initializes TBODY element for data.
6707   *
6708   * @method _initTbodyEl
6709   * @param elTable {HTMLElement} TABLE element into which to create TBODY .
6710   * @private
6711   */
6712  _initTbodyEl : function(elTable) {
6713      if(elTable) {
6714          // Destroy previous
6715          this._destroyTbodyEl();
6716          
6717          // Create TBODY
6718          var elTbody = elTable.appendChild(document.createElement("tbody"));
6719          elTbody.tabIndex = 0;
6720          elTbody.className = DT.CLASS_DATA;
6721      
6722          // Set up DOM events for TBODY
6723          Ev.addListener(elTbody, "focus", this._onTbodyFocus, this);
6724          Ev.addListener(elTbody, "mousedown", this._onTableMousedown, this);
6725          Ev.addListener(elTbody, "mouseup", this._onTableMouseup, this);
6726          Ev.addListener(elTbody, "keydown", this._onTbodyKeydown, this);
6727          Ev.addListener(elTbody, "click", this._onTbodyClick, this);
6728  
6729          // Bug 2528073: mouseover/mouseout handled via mouseenter/mouseleave
6730          // delegation at the TABLE level
6731  
6732          // Since we can't listen for click and dblclick on the same element...
6733          // Attach separately to THEAD and TBODY
6734          ///Ev.addListener(elTbody, "dblclick", this._onTableDblclick, this);
6735          
6736      
6737          // IE puts focus outline in the wrong place
6738          if(ua.ie) {
6739              elTbody.hideFocus=true;
6740          }
6741  
6742          this._elTbody = elTbody;
6743      }
6744  },
6745  
6746  /**
6747   * Destroy's the DataTable message TBODY element, if available.
6748   *
6749   * @method _destroyMsgTbodyEl
6750   * @private
6751   */
6752  _destroyMsgTbodyEl : function() {
6753      var elMsgTbody = this._elMsgTbody;
6754      if(elMsgTbody) {
6755          var elTable = elMsgTbody.parentNode;
6756          Ev.purgeElement(elMsgTbody, true);
6757          elTable.removeChild(elMsgTbody);
6758          this._elTbody = null;
6759      }
6760  },
6761  
6762  /**
6763   * Initializes TBODY element for messaging.
6764   *
6765   * @method _initMsgTbodyEl
6766   * @param elTable {HTMLElement} TABLE element into which to create TBODY 
6767   * @private
6768   */
6769  _initMsgTbodyEl : function(elTable) {
6770      if(elTable) {
6771          var elMsgTbody = document.createElement("tbody");
6772          elMsgTbody.className = DT.CLASS_MESSAGE;
6773          var elMsgTr = elMsgTbody.appendChild(document.createElement("tr"));
6774          elMsgTr.className = DT.CLASS_FIRST + " " + DT.CLASS_LAST;
6775          this._elMsgTr = elMsgTr;
6776          var elMsgTd = elMsgTr.appendChild(document.createElement("td"));
6777          elMsgTd.colSpan = this._oColumnSet.keys.length || 1;
6778          elMsgTd.className = DT.CLASS_FIRST + " " + DT.CLASS_LAST;
6779          this._elMsgTd = elMsgTd;
6780          elMsgTbody = elTable.insertBefore(elMsgTbody, this._elTbody);
6781          var elMsgLiner = elMsgTd.appendChild(document.createElement("div"));
6782          elMsgLiner.className = DT.CLASS_LINER;
6783          this._elMsgTbody = elMsgTbody;
6784  
6785          // Set up DOM events for TBODY
6786          Ev.addListener(elMsgTbody, "focus", this._onTbodyFocus, this);
6787          Ev.addListener(elMsgTbody, "mousedown", this._onTableMousedown, this);
6788          Ev.addListener(elMsgTbody, "mouseup", this._onTableMouseup, this);
6789          Ev.addListener(elMsgTbody, "keydown", this._onTbodyKeydown, this);
6790          Ev.addListener(elMsgTbody, "click", this._onTbodyClick, this);
6791  
6792          // Bug 2528073: mouseover/mouseout handled via mouseenter/mouseleave
6793          // delegation at the TABLE level
6794      }
6795  },
6796  
6797  /**
6798   * Initialize internal event listeners
6799   *
6800   * @method _initEvents
6801   * @private
6802   */
6803  _initEvents : function () {
6804      // Initialize Column sort
6805      this._initColumnSort();
6806          
6807      // Add the document level click listener
6808      YAHOO.util.Event.addListener(document, "click", this._onDocumentClick, this);
6809  
6810      // Paginator integration
6811      this.subscribe("paginatorChange",function () {
6812          this._handlePaginatorChange.apply(this,arguments);
6813      });
6814  
6815      this.subscribe("initEvent",function () {
6816          this.renderPaginator();
6817      });
6818  
6819      // Initialize CellEditor integration
6820      this._initCellEditing();
6821  },
6822  
6823  /**      
6824    * Initializes Column sorting.      
6825    *      
6826    * @method _initColumnSort      
6827    * @private      
6828    */      
6829  _initColumnSort : function() {
6830      this.subscribe("theadCellClickEvent", this.onEventSortColumn);      
6831  
6832      // Backward compatibility
6833      var oSortedBy = this.get("sortedBy");
6834      if(oSortedBy) {
6835          if(oSortedBy.dir == "desc") {
6836              this._configs.sortedBy.value.dir = DT.CLASS_DESC;
6837          }
6838          else if(oSortedBy.dir == "asc") {
6839              this._configs.sortedBy.value.dir = DT.CLASS_ASC;
6840          }
6841      }
6842  },
6843  
6844  /**      
6845    * Initializes CellEditor integration.      
6846    *      
6847    * @method _initCellEditing      
6848    * @private      
6849    */      
6850  _initCellEditing : function() {
6851      this.subscribe("editorBlurEvent",function () {
6852          this.onEditorBlurEvent.apply(this,arguments);
6853      });
6854      this.subscribe("editorBlockEvent",function () {
6855          this.onEditorBlockEvent.apply(this,arguments);
6856      });
6857      this.subscribe("editorUnblockEvent",function () {
6858          this.onEditorUnblockEvent.apply(this,arguments);
6859      });
6860  },
6861  
6862  
6863  
6864  
6865  
6866  
6867  
6868  
6869  
6870  
6871  
6872  
6873  
6874  
6875  
6876  
6877  
6878  
6879  
6880  
6881  
6882  
6883  
6884  
6885  
6886  
6887  
6888  
6889  
6890  
6891  
6892  
6893  
6894  // DOM MUTATION FUNCTIONS
6895  
6896  /**
6897   * Retruns classnames to represent current Column states.
6898   * @method _getColumnClassnames 
6899   * @param oColumn {YAHOO.widget.Column} Column instance.
6900   * @param aAddClasses {String[]} An array of additional classnames to add to the
6901   * return value.  
6902   * @return {String} A String of classnames to be assigned to TH or TD elements
6903   * for given Column.  
6904   * @private 
6905   */
6906  _getColumnClassNames : function (oColumn, aAddClasses) {
6907      var allClasses;
6908      
6909      // Add CSS classes
6910      if(lang.isString(oColumn.className)) {
6911          // Single custom class
6912          allClasses = [oColumn.className];
6913      }
6914      else if(lang.isArray(oColumn.className)) {
6915          // Array of custom classes
6916          allClasses = oColumn.className;
6917      }
6918      else {
6919          // no custom classes
6920          allClasses = [];
6921      }
6922      
6923      // Hook for setting width with via dynamic style uses key since ID is too disposable
6924      allClasses[allClasses.length] = this.getId() + "-col-" +oColumn.getSanitizedKey();
6925  
6926      // Column key - minus any chars other than "A-Z", "a-z", "0-9", "_", "-", ".", or ":"
6927      allClasses[allClasses.length] = "yui-dt-col-" +oColumn.getSanitizedKey();
6928  
6929      var isSortedBy = this.get("sortedBy") || {};
6930      // Sorted
6931      if(oColumn.key === isSortedBy.key) {
6932          allClasses[allClasses.length] = isSortedBy.dir || '';
6933      }
6934      // Hidden
6935      if(oColumn.hidden) {
6936          allClasses[allClasses.length] = DT.CLASS_HIDDEN;
6937      }
6938      // Selected
6939      if(oColumn.selected) {
6940          allClasses[allClasses.length] = DT.CLASS_SELECTED;
6941      }
6942      // Sortable
6943      if(oColumn.sortable) {
6944          allClasses[allClasses.length] = DT.CLASS_SORTABLE;
6945      }
6946      // Resizeable
6947      if(oColumn.resizeable) {
6948          allClasses[allClasses.length] = DT.CLASS_RESIZEABLE;
6949      }
6950      // Editable
6951      if(oColumn.editor) {
6952          allClasses[allClasses.length] = DT.CLASS_EDITABLE;
6953      }
6954      
6955      // Addtnl classes, including First/Last
6956      if(aAddClasses) {
6957          allClasses = allClasses.concat(aAddClasses);
6958      }
6959      
6960      return allClasses.join(' ');  
6961  },
6962  
6963  /**
6964   * Clears TR element template in response to any Column state change.
6965   * @method _clearTrTemplateEl
6966   * @private 
6967   */
6968  _clearTrTemplateEl : function () {
6969      this._elTrTemplate = null;
6970  },
6971  
6972  /**
6973   * Returns a new TR element template with TD elements classed with current
6974   * Column states.
6975   * @method _getTrTemplateEl 
6976   * @return {HTMLElement} A TR element to be cloned and added to the DOM.
6977   * @private 
6978   */
6979  _getTrTemplateEl : function (oRecord, index) {
6980      // Template is already available
6981      if(this._elTrTemplate) {
6982          return this._elTrTemplate;
6983      }
6984      // Template needs to be created
6985      else {
6986          var d   = document,
6987              tr  = d.createElement('tr'),
6988              td  = d.createElement('td'),
6989              div = d.createElement('div');
6990      
6991          // Append the liner element
6992          td.appendChild(div);
6993  
6994          // Create TD elements into DOCUMENT FRAGMENT
6995          var df = document.createDocumentFragment(),
6996              allKeys = this._oColumnSet.keys,
6997              elTd;
6998  
6999          // Set state for each TD;
7000          var aAddClasses;
7001          for(var i=0, keysLen=allKeys.length; i<keysLen; i++) {
7002              // Clone the TD template
7003              elTd = td.cloneNode(true);
7004  
7005              // Format the base TD
7006              elTd = this._formatTdEl(allKeys[i], elTd, i, (i===keysLen-1));
7007                          
7008              df.appendChild(elTd);
7009          }
7010          tr.appendChild(df);
7011          tr.className = DT.CLASS_REC;
7012          this._elTrTemplate = tr;
7013          return tr;
7014      }   
7015  },
7016  
7017  /**
7018   * Formats a basic TD element.
7019   * @method _formatTdEl 
7020   * @param oColumn {YAHOO.widget.Column} Associated Column instance. 
7021   * @param elTd {HTMLElement} An unformatted TD element.
7022   * @param index {Number} Column key index. 
7023   * @param isLast {Boolean} True if Column is last key of the ColumnSet.
7024   * @return {HTMLElement} A formatted TD element.
7025   * @private 
7026   */
7027  _formatTdEl : function (oColumn, elTd, index, isLast) {
7028      var oColumnSet = this._oColumnSet;
7029      
7030      // Set the TD's accessibility headers
7031      var allHeaders = oColumnSet.headers,
7032          allColHeaders = allHeaders[index],
7033          sTdHeaders = "",
7034          sHeader;
7035      for(var j=0, headersLen=allColHeaders.length; j < headersLen; j++) {
7036          sHeader = this._sId + "-th-" + allColHeaders[j] + ' ';
7037          sTdHeaders += sHeader;
7038      }
7039      elTd.headers = sTdHeaders;
7040      
7041      // Class the TD element
7042      var aAddClasses = [];
7043      if(index === 0) {
7044          aAddClasses[aAddClasses.length] = DT.CLASS_FIRST;
7045      }
7046      if(isLast) {
7047          aAddClasses[aAddClasses.length] = DT.CLASS_LAST;
7048      }
7049      elTd.className = this._getColumnClassNames(oColumn, aAddClasses);
7050  
7051      // Class the liner element
7052      elTd.firstChild.className = DT.CLASS_LINER;
7053  
7054      // Set Column width for fallback cases
7055      if(oColumn.width && DT._bDynStylesFallback) {
7056          // Validate minWidth
7057          var nWidth = (oColumn.minWidth && (oColumn.width < oColumn.minWidth)) ?
7058                  oColumn.minWidth : oColumn.width;
7059          elTd.firstChild.style.overflow = 'hidden';
7060          elTd.firstChild.style.width = nWidth + 'px';
7061      }
7062      
7063      return elTd;
7064  },
7065  
7066  
7067  /**
7068   * Create a new TR element for a given Record and appends it with the correct
7069   * number of Column-state-classed TD elements. Striping is the responsibility of
7070   * the calling function, which may decide to stripe the single row, a subset of
7071   * rows, or all the rows.
7072   * @method _createTrEl
7073   * @param oRecord {YAHOO.widget.Record} Record instance
7074   * @return {HTMLElement} The new TR element.  This must be added to the DOM.
7075   * @private 
7076   */
7077  _addTrEl : function (oRecord) {
7078      var elTrTemplate = this._getTrTemplateEl();
7079      
7080      // Clone the TR template.
7081      var elTr = elTrTemplate.cloneNode(true);
7082      
7083      // Populate content
7084      return this._updateTrEl(elTr,oRecord);
7085  },
7086  
7087  /**
7088   * Formats the contents of the given TR's TD elements with data from the given
7089   * Record. Only innerHTML should change, nothing structural.
7090   *
7091   * @method _updateTrEl
7092   * @param elTr {HTMLElement} The TR element to update.
7093   * @param oRecord {YAHOO.widget.Record} The associated Record instance.
7094   * @return {HTMLElement} DOM reference to the new TR element.
7095   * @private
7096   */
7097  _updateTrEl : function(elTr, oRecord) {
7098      var ok = this.get("formatRow") ? this.get("formatRow").call(this, elTr, oRecord) : true;
7099      if(ok) {
7100          // Hide the row to prevent constant reflows
7101          elTr.style.display = 'none';
7102          
7103          // Update TD elements with new data
7104          var allTds = elTr.childNodes,
7105              elTd;
7106          for(var i=0,len=allTds.length; i<len; ++i) {
7107              elTd = allTds[i];
7108              
7109              // Set the cell content
7110              this.formatCell(allTds[i].firstChild, oRecord, this._oColumnSet.keys[i]);
7111          }
7112          
7113          // Redisplay the row for reflow
7114          elTr.style.display = '';
7115      }
7116      
7117       // Record-to-TR association and tracking of FIRST/LAST
7118      var oldId = elTr.id,
7119          newId = oRecord.getId();
7120      if(this._sFirstTrId === oldId) {
7121          this._sFirstTrId = newId;
7122      }
7123      if(this._sLastTrId === oldId) {
7124          this._sLastTrId = newId;
7125      }
7126      elTr.id = newId;
7127      return elTr;
7128  },
7129  
7130  
7131  /**
7132   * Deletes TR element by DOM reference or by DataTable page row index.
7133   *
7134   * @method _deleteTrEl
7135   * @param row {HTMLElement | Number} TR element reference or Datatable page row index.
7136   * @return {Boolean} Returns true if successful, else returns false.
7137   * @private
7138   */
7139  _deleteTrEl : function(row) {
7140      var rowIndex;
7141  
7142      // Get page row index for the element
7143      if(!lang.isNumber(row)) {
7144          rowIndex = Dom.get(row).sectionRowIndex;
7145      }
7146      else {
7147          rowIndex = row;
7148      }
7149      if(lang.isNumber(rowIndex) && (rowIndex > -2) && (rowIndex < this._elTbody.rows.length)) {
7150          // Cannot use tbody.deleteRow due to IE6 instability
7151          //return this._elTbody.deleteRow(rowIndex);
7152          return this._elTbody.removeChild(this._elTbody.rows[row]);
7153      }
7154      else {
7155          return null;
7156      }
7157  },
7158  
7159  
7160  
7161  
7162  
7163  
7164  
7165  
7166  
7167  
7168  
7169  
7170  
7171  
7172  
7173  
7174  
7175  
7176  
7177  
7178  
7179  
7180  
7181  
7182  
7183  
7184  
7185  // CSS/STATE FUNCTIONS
7186  
7187  
7188  
7189  
7190  /**
7191   * Removes the class YAHOO.widget.DataTable.CLASS_FIRST from the first TR element
7192   * of the DataTable page and updates internal tracker.
7193   *
7194   * @method _unsetFirstRow
7195   * @private
7196   */
7197  _unsetFirstRow : function() {
7198      // Remove FIRST
7199      if(this._sFirstTrId) {
7200          Dom.removeClass(this._sFirstTrId, DT.CLASS_FIRST);
7201          this._sFirstTrId = null;
7202      }
7203  },
7204  
7205  /**
7206   * Assigns the class YAHOO.widget.DataTable.CLASS_FIRST to the first TR element
7207   * of the DataTable page and updates internal tracker.
7208   *
7209   * @method _setFirstRow
7210   * @private
7211   */
7212  _setFirstRow : function() {
7213      this._unsetFirstRow();
7214      var elTr = this.getFirstTrEl();
7215      if(elTr) {
7216          // Set FIRST
7217          Dom.addClass(elTr, DT.CLASS_FIRST);
7218          this._sFirstTrId = elTr.id;
7219      }
7220  },
7221  
7222  /**
7223   * Removes the class YAHOO.widget.DataTable.CLASS_LAST from the last TR element
7224   * of the DataTable page and updates internal tracker.
7225   *
7226   * @method _unsetLastRow
7227   * @private
7228   */
7229  _unsetLastRow : function() {
7230      // Unassign previous class
7231      if(this._sLastTrId) {
7232          Dom.removeClass(this._sLastTrId, DT.CLASS_LAST);
7233          this._sLastTrId = null;
7234      }   
7235  },
7236  
7237  /**
7238   * Assigns the class YAHOO.widget.DataTable.CLASS_LAST to the last TR element
7239   * of the DataTable page and updates internal tracker.
7240   *
7241   * @method _setLastRow
7242   * @private
7243   */
7244  _setLastRow : function() {
7245      this._unsetLastRow();
7246      var elTr = this.getLastTrEl();
7247      if(elTr) {
7248          // Assign class
7249          Dom.addClass(elTr, DT.CLASS_LAST);
7250          this._sLastTrId = elTr.id;
7251      }
7252  },
7253  
7254  /**
7255   * Assigns the classes DT.CLASS_EVEN and DT.CLASS_ODD to one, many, or all TR elements.
7256   *
7257   * @method _setRowStripes
7258   * @param row {HTMLElement | String | Number} (optional) HTML TR element reference
7259   * or string ID, or page row index of where to start striping.
7260   * @param range {Number} (optional) If given, how many rows to stripe, otherwise
7261   * stripe all the rows until the end.
7262   * @private
7263   */
7264  _setRowStripes : function(row, range) {
7265      // Default values stripe all rows
7266      var allRows = this._elTbody.rows,
7267          nStartIndex = 0,
7268          nEndIndex = allRows.length,
7269          aOdds = [], nOddIdx = 0,
7270          aEvens = [], nEvenIdx = 0;
7271  
7272      // Stripe a subset
7273      if((row !== null) && (row !== undefined)) {
7274          // Validate given start row
7275          var elStartRow = this.getTrEl(row);
7276          if(elStartRow) {
7277              nStartIndex = elStartRow.sectionRowIndex;
7278  
7279              // Validate given range
7280              if(lang.isNumber(range) && (range > 1)) {
7281                  nEndIndex = nStartIndex + range;
7282              }
7283          }
7284      }
7285  
7286      for(var i=nStartIndex; i<nEndIndex; i++) {
7287          if(i%2) {
7288              aOdds[nOddIdx++] = allRows[i];
7289          } else {
7290              aEvens[nEvenIdx++] = allRows[i];
7291          }
7292      }
7293  
7294      if (aOdds.length) {
7295          Dom.replaceClass(aOdds, DT.CLASS_EVEN, DT.CLASS_ODD);
7296      }
7297  
7298      if (aEvens.length) {
7299          Dom.replaceClass(aEvens, DT.CLASS_ODD, DT.CLASS_EVEN);
7300      }
7301  },
7302  
7303  /**
7304   * Assigns the class DT.CLASS_SELECTED to TR and TD elements.
7305   *
7306   * @method _setSelections
7307   * @private
7308   */
7309  _setSelections : function() {
7310      // Keep track of selected rows
7311      var allSelectedRows = this.getSelectedRows();
7312      // Keep track of selected cells
7313      var allSelectedCells = this.getSelectedCells();
7314      // Anything to select?
7315      if((allSelectedRows.length>0) || (allSelectedCells.length > 0)) {
7316          var oColumnSet = this._oColumnSet,
7317              el;
7318          // Loop over each row
7319          for(var i=0; i<allSelectedRows.length; i++) {
7320              el = Dom.get(allSelectedRows[i]);
7321              if(el) {
7322                  Dom.addClass(el, DT.CLASS_SELECTED);
7323              }
7324          }
7325          // Loop over each cell
7326          for(i=0; i<allSelectedCells.length; i++) {
7327              el = Dom.get(allSelectedCells[i].recordId);
7328              if(el) {
7329                  Dom.addClass(el.childNodes[oColumnSet.getColumn(allSelectedCells[i].columnKey).getKeyIndex()], DT.CLASS_SELECTED);
7330              }
7331          }
7332      }       
7333  },
7334  
7335  
7336  
7337  
7338  
7339  
7340  
7341  
7342  
7343  
7344  
7345  
7346  
7347  
7348  
7349  
7350  
7351  
7352  
7353  
7354  
7355  
7356  
7357  
7358  
7359  
7360  
7361  
7362  
7363  
7364  
7365  
7366  
7367  
7368  
7369  
7370  
7371  
7372  
7373  
7374  
7375  
7376  
7377  /////////////////////////////////////////////////////////////////////////////
7378  //
7379  // Private DOM Event Handlers
7380  //
7381  /////////////////////////////////////////////////////////////////////////////
7382  
7383  /**
7384   * Validates minWidths whenever the render chain ends.
7385   *
7386   * @method _onRenderChainEnd
7387   * @private
7388   */
7389  _onRenderChainEnd : function() {
7390      // Hide loading message
7391      this.hideTableMessage();
7392      
7393      // Show empty message
7394      if(this._elTbody.rows.length === 0) {
7395          this.showTableMessage(this.get("MSG_EMPTY"), DT.CLASS_EMPTY);        
7396      }
7397  
7398      // Execute in timeout thread to give implementers a chance
7399      // to subscribe after the constructor
7400      var oSelf = this;
7401      setTimeout(function() {
7402          if((oSelf instanceof DT) && oSelf._sId) {        
7403              // Init event
7404              if(oSelf._bInit) {
7405                  oSelf._bInit = false;
7406                  oSelf.fireEvent("initEvent");
7407              }
7408      
7409              // Render event
7410              oSelf.fireEvent("renderEvent");
7411              // Backward compatibility
7412              oSelf.fireEvent("refreshEvent");
7413      
7414              // Post-render routine
7415              oSelf.validateColumnWidths();
7416      
7417              // Post-render event
7418              oSelf.fireEvent("postRenderEvent");
7419              
7420              /*if(YAHOO.example.Performance.trialStart) {
7421                  YAHOO.example.Performance.trialStart = null;
7422              }*/
7423              
7424          }
7425      }, 0);
7426  },
7427  
7428  /**
7429   * Handles click events on the DOCUMENT.
7430   *
7431   * @method _onDocumentClick
7432   * @param e {HTMLEvent} The click event.
7433   * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
7434   * @private
7435   */
7436  _onDocumentClick : function(e, oSelf) {
7437      var elTarget = Ev.getTarget(e);
7438      var elTag = elTarget.nodeName.toLowerCase();
7439  
7440      if(!Dom.isAncestor(oSelf._elContainer, elTarget)) {
7441          oSelf.fireEvent("tableBlurEvent");
7442  
7443          // Fires editorBlurEvent when click is not within the TABLE.
7444          // For cases when click is within the TABLE, due to timing issues,
7445          // the editorBlurEvent needs to get fired by the lower-level DOM click
7446          // handlers below rather than by the TABLE click handler directly.
7447          if(oSelf._oCellEditor) {
7448              if(oSelf._oCellEditor.getContainerEl) {
7449                  var elContainer = oSelf._oCellEditor.getContainerEl();
7450                  // Only if the click was not within the CellEditor container
7451                  if(!Dom.isAncestor(elContainer, elTarget) &&
7452                          (elContainer.id !== elTarget.id)) {
7453                      oSelf._oCellEditor.fireEvent("blurEvent", {editor: oSelf._oCellEditor});
7454                  }
7455              }
7456              // Backward Compatibility
7457              else if(oSelf._oCellEditor.isActive) {
7458                  // Only if the click was not within the Cell Editor container
7459                  if(!Dom.isAncestor(oSelf._oCellEditor.container, elTarget) &&
7460                          (oSelf._oCellEditor.container.id !== elTarget.id)) {
7461                      oSelf.fireEvent("editorBlurEvent", {editor:oSelf._oCellEditor});
7462                  }
7463              }
7464          }
7465      }
7466  },
7467  
7468  /**
7469   * Handles focus events on the DataTable instance.
7470   *
7471   * @method _onTableFocus
7472   * @param e {HTMLEvent} The focus event.
7473   * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
7474   * @private
7475   */
7476  _onTableFocus : function(e, oSelf) {
7477      oSelf.fireEvent("tableFocusEvent");
7478  },
7479  
7480  /**
7481   * Handles focus events on the THEAD element.
7482   *
7483   * @method _onTheadFocus
7484   * @param e {HTMLEvent} The focus event.
7485   * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
7486   * @private
7487   */
7488  _onTheadFocus : function(e, oSelf) {
7489      oSelf.fireEvent("theadFocusEvent");
7490      oSelf.fireEvent("tableFocusEvent");
7491  },
7492  
7493  /**
7494   * Handles focus events on the TBODY element.
7495   *
7496   * @method _onTbodyFocus
7497   * @param e {HTMLEvent} The focus event.
7498   * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
7499   * @private
7500   */
7501  _onTbodyFocus : function(e, oSelf) {
7502      oSelf.fireEvent("tbodyFocusEvent");
7503      oSelf.fireEvent("tableFocusEvent");
7504  },
7505  
7506  /**
7507   * Handles mouseover events on the DataTable instance.
7508   *
7509   * @method _onTableMouseover
7510   * @param e {HTMLEvent} The mouseover event.
7511   * @param origTarget {HTMLElement} The mouseenter delegated element.
7512   * @param container {HTMLElement} The mouseenter delegation container.
7513   * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
7514   * @private
7515   */
7516  _onTableMouseover : function(e, origTarget, container, oSelf) {
7517      var elTarget = origTarget;
7518      var elTag = elTarget.nodeName && elTarget.nodeName.toLowerCase();
7519      var bKeepBubbling = true;
7520      while(elTarget && (elTag != "table")) {
7521          switch(elTag) {
7522              case "body":
7523                   return;
7524              case "a":
7525                  break;
7526              case "td":
7527                  bKeepBubbling = oSelf.fireEvent("cellMouseoverEvent",{target:elTarget,event:e});
7528                  break;
7529              case "span":
7530                  if(Dom.hasClass(elTarget, DT.CLASS_LABEL)) {
7531                      bKeepBubbling = oSelf.fireEvent("theadLabelMouseoverEvent",{target:elTarget,event:e});
7532                      // Backward compatibility
7533                      bKeepBubbling = oSelf.fireEvent("headerLabelMouseoverEvent",{target:elTarget,event:e});
7534                  }
7535                  break;
7536              case "th":
7537                  bKeepBubbling = oSelf.fireEvent("theadCellMouseoverEvent",{target:elTarget,event:e});
7538                  // Backward compatibility
7539                  bKeepBubbling = oSelf.fireEvent("headerCellMouseoverEvent",{target:elTarget,event:e});
7540                  break;
7541              case "tr":
7542                  if(elTarget.parentNode.nodeName.toLowerCase() == "thead") {
7543                      bKeepBubbling = oSelf.fireEvent("theadRowMouseoverEvent",{target:elTarget,event:e});
7544                      // Backward compatibility
7545                      bKeepBubbling = oSelf.fireEvent("headerRowMouseoverEvent",{target:elTarget,event:e});
7546                  }
7547                  else {
7548                      bKeepBubbling = oSelf.fireEvent("rowMouseoverEvent",{target:elTarget,event:e});
7549                  }
7550                  break;
7551              default:
7552                  break;
7553          }
7554          if(bKeepBubbling === false) {
7555              return;
7556          }
7557          else {
7558              elTarget = elTarget.parentNode;
7559              if(elTarget) {
7560                  elTag = elTarget.nodeName.toLowerCase();
7561              }
7562          }
7563      }
7564      oSelf.fireEvent("tableMouseoverEvent",{target:(elTarget || oSelf._elContainer),event:e});
7565  },
7566  
7567  /**
7568   * Handles mouseout events on the DataTable instance.
7569   *
7570   * @method _onTableMouseout
7571   * @param e {HTMLEvent} The mouseout event.
7572   * @param origTarget {HTMLElement} The mouseleave delegated element.
7573   * @param container {HTMLElement} The mouseleave delegation container.
7574   * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
7575   * @private
7576   */
7577  _onTableMouseout : function(e, origTarget, container, oSelf) {
7578      var elTarget = origTarget;
7579      var elTag = elTarget.nodeName && elTarget.nodeName.toLowerCase();
7580      var bKeepBubbling = true;
7581      while(elTarget && (elTag != "table")) {
7582          switch(elTag) {
7583              case "body":
7584                  return;
7585              case "a":
7586                  break;
7587              case "td":
7588                  bKeepBubbling = oSelf.fireEvent("cellMouseoutEvent",{target:elTarget,event:e});
7589                  break;
7590              case "span":
7591                  if(Dom.hasClass(elTarget, DT.CLASS_LABEL)) {
7592                      bKeepBubbling = oSelf.fireEvent("theadLabelMouseoutEvent",{target:elTarget,event:e});
7593                      // Backward compatibility
7594                      bKeepBubbling = oSelf.fireEvent("headerLabelMouseoutEvent",{target:elTarget,event:e});
7595                  }
7596                  break;
7597              case "th":
7598                  bKeepBubbling = oSelf.fireEvent("theadCellMouseoutEvent",{target:elTarget,event:e});
7599                  // Backward compatibility
7600                  bKeepBubbling = oSelf.fireEvent("headerCellMouseoutEvent",{target:elTarget,event:e});
7601                  break;
7602              case "tr":
7603                  if(elTarget.parentNode.nodeName.toLowerCase() == "thead") {
7604                      bKeepBubbling = oSelf.fireEvent("theadRowMouseoutEvent",{target:elTarget,event:e});
7605                      // Backward compatibility
7606                      bKeepBubbling = oSelf.fireEvent("headerRowMouseoutEvent",{target:elTarget,event:e});
7607                  }
7608                  else {
7609                      bKeepBubbling = oSelf.fireEvent("rowMouseoutEvent",{target:elTarget,event:e});
7610                  }
7611                  break;
7612              default:
7613                  break;
7614          }
7615          if(bKeepBubbling === false) {
7616              return;
7617          }
7618          else {
7619              elTarget = elTarget.parentNode;
7620              if(elTarget) {
7621                  elTag = elTarget.nodeName.toLowerCase();
7622              }
7623          }
7624      }
7625      oSelf.fireEvent("tableMouseoutEvent",{target:(elTarget || oSelf._elContainer),event:e});
7626  },
7627  
7628  /**
7629   * Handles mousedown events on the DataTable instance.
7630   *
7631   * @method _onTableMousedown
7632   * @param e {HTMLEvent} The mousedown event.
7633   * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
7634   * @private
7635   */
7636  _onTableMousedown : function(e, oSelf) {
7637      var elTarget = Ev.getTarget(e);
7638      var elTag = elTarget.nodeName && elTarget.nodeName.toLowerCase();
7639      var bKeepBubbling = true;
7640      while(elTarget && (elTag != "table")) {
7641          switch(elTag) {
7642              case "body":
7643                  return;
7644              case "a":
7645                  break;
7646              case "td":
7647                  bKeepBubbling = oSelf.fireEvent("cellMousedownEvent",{target:elTarget,event:e});
7648                  break;
7649              case "span":
7650                  if(Dom.hasClass(elTarget, DT.CLASS_LABEL)) {
7651                      bKeepBubbling = oSelf.fireEvent("theadLabelMousedownEvent",{target:elTarget,event:e});
7652                      // Backward compatibility
7653                      bKeepBubbling = oSelf.fireEvent("headerLabelMousedownEvent",{target:elTarget,event:e});
7654                  }
7655                  break;
7656              case "th":
7657                  bKeepBubbling = oSelf.fireEvent("theadCellMousedownEvent",{target:elTarget,event:e});
7658                  // Backward compatibility
7659                  bKeepBubbling = oSelf.fireEvent("headerCellMousedownEvent",{target:elTarget,event:e});
7660                  break;
7661              case "tr":
7662                  if(elTarget.parentNode.nodeName.toLowerCase() == "thead") {
7663                      bKeepBubbling = oSelf.fireEvent("theadRowMousedownEvent",{target:elTarget,event:e});
7664                      // Backward compatibility
7665                      bKeepBubbling = oSelf.fireEvent("headerRowMousedownEvent",{target:elTarget,event:e});
7666                  }
7667                  else {
7668                      bKeepBubbling = oSelf.fireEvent("rowMousedownEvent",{target:elTarget,event:e});
7669                  }
7670                  break;
7671              default:
7672                  break;
7673          }
7674          if(bKeepBubbling === false) {
7675              return;
7676          }
7677          else {
7678              elTarget = elTarget.parentNode;
7679              if(elTarget) {
7680                  elTag = elTarget.nodeName.toLowerCase();
7681              }
7682          }
7683      }
7684      oSelf.fireEvent("tableMousedownEvent",{target:(elTarget || oSelf._elContainer),event:e});
7685  },
7686  
7687  /**
7688   * Handles mouseup events on the DataTable instance.
7689   *
7690   * @method _onTableMouseup
7691   * @param e {HTMLEvent} The mouseup event.
7692   * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
7693   * @private
7694   */
7695  _onTableMouseup : function(e, oSelf) {
7696      var elTarget = Ev.getTarget(e);
7697      var elTag = elTarget.nodeName && elTarget.nodeName.toLowerCase();
7698      var bKeepBubbling = true;
7699      while(elTarget && (elTag != "table")) {
7700          switch(elTag) {
7701              case "body":
7702                  return;
7703              case "a":
7704                  break;
7705              case "td":
7706                  bKeepBubbling = oSelf.fireEvent("cellMouseupEvent",{target:elTarget,event:e});
7707                  break;
7708              case "span":
7709                  if(Dom.hasClass(elTarget, DT.CLASS_LABEL)) {
7710                      bKeepBubbling = oSelf.fireEvent("theadLabelMouseupEvent",{target:elTarget,event:e});
7711                      // Backward compatibility
7712                      bKeepBubbling = oSelf.fireEvent("headerLabelMouseupEvent",{target:elTarget,event:e});
7713                  }
7714                  break;
7715              case "th":
7716                  bKeepBubbling = oSelf.fireEvent("theadCellMouseupEvent",{target:elTarget,event:e});
7717                  // Backward compatibility
7718                  bKeepBubbling = oSelf.fireEvent("headerCellMouseupEvent",{target:elTarget,event:e});
7719                  break;
7720              case "tr":
7721                  if(elTarget.parentNode.nodeName.toLowerCase() == "thead") {
7722                      bKeepBubbling = oSelf.fireEvent("theadRowMouseupEvent",{target:elTarget,event:e});
7723                      // Backward compatibility
7724                      bKeepBubbling = oSelf.fireEvent("headerRowMouseupEvent",{target:elTarget,event:e});
7725                  }
7726                  else {
7727                      bKeepBubbling = oSelf.fireEvent("rowMouseupEvent",{target:elTarget,event:e});
7728                  }
7729                  break;
7730              default:
7731                  break;
7732          }
7733          if(bKeepBubbling === false) {
7734              return;
7735          }
7736          else {
7737              elTarget = elTarget.parentNode;
7738              if(elTarget) {
7739                  elTag = elTarget.nodeName.toLowerCase();
7740              }
7741          }
7742      }
7743      oSelf.fireEvent("tableMouseupEvent",{target:(elTarget || oSelf._elContainer),event:e});
7744  },
7745  
7746  /**
7747   * Handles dblclick events on the DataTable instance.
7748   *
7749   * @method _onTableDblclick
7750   * @param e {HTMLEvent} The dblclick event.
7751   * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
7752   * @private
7753   */
7754  _onTableDblclick : function(e, oSelf) {
7755      var elTarget = Ev.getTarget(e);
7756      var elTag = elTarget.nodeName && elTarget.nodeName.toLowerCase();
7757      var bKeepBubbling = true;
7758      while(elTarget && (elTag != "table")) {
7759          switch(elTag) {
7760              case "body":
7761                  return;
7762              case "td":
7763                  bKeepBubbling = oSelf.fireEvent("cellDblclickEvent",{target:elTarget,event:e});
7764                  break;
7765              case "span":
7766                  if(Dom.hasClass(elTarget, DT.CLASS_LABEL)) {
7767                      bKeepBubbling = oSelf.fireEvent("theadLabelDblclickEvent",{target:elTarget,event:e});
7768                      // Backward compatibility
7769                      bKeepBubbling = oSelf.fireEvent("headerLabelDblclickEvent",{target:elTarget,event:e});
7770                  }
7771                  break;
7772              case "th":
7773                  bKeepBubbling = oSelf.fireEvent("theadCellDblclickEvent",{target:elTarget,event:e});
7774                  // Backward compatibility
7775                  bKeepBubbling = oSelf.fireEvent("headerCellDblclickEvent",{target:elTarget,event:e});
7776                  break;
7777              case "tr":
7778                  if(elTarget.parentNode.nodeName.toLowerCase() == "thead") {
7779                      bKeepBubbling = oSelf.fireEvent("theadRowDblclickEvent",{target:elTarget,event:e});
7780                      // Backward compatibility
7781                      bKeepBubbling = oSelf.fireEvent("headerRowDblclickEvent",{target:elTarget,event:e});
7782                  }
7783                  else {
7784                      bKeepBubbling = oSelf.fireEvent("rowDblclickEvent",{target:elTarget,event:e});
7785                  }
7786                  break;
7787              default:
7788                  break;
7789          }
7790          if(bKeepBubbling === false) {
7791              return;
7792          }
7793          else {
7794              elTarget = elTarget.parentNode;
7795              if(elTarget) {
7796                  elTag = elTarget.nodeName.toLowerCase();
7797              }
7798          }
7799      }
7800      oSelf.fireEvent("tableDblclickEvent",{target:(elTarget || oSelf._elContainer),event:e});
7801  },
7802  /**
7803   * Handles keydown events on the THEAD element.
7804   *
7805   * @method _onTheadKeydown
7806   * @param e {HTMLEvent} The key event.
7807   * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
7808   * @private
7809   */
7810  _onTheadKeydown : function(e, oSelf) {
7811      var elTarget = Ev.getTarget(e);
7812      var elTag = elTarget.nodeName && elTarget.nodeName.toLowerCase();
7813      var bKeepBubbling = true;
7814      while(elTarget && (elTag != "table")) {
7815          switch(elTag) {
7816              case "body":
7817                  return;
7818              case "input":
7819              case "textarea":
7820                  // TODO: implement textareaKeyEvent
7821                  break;
7822              case "thead":
7823                  bKeepBubbling = oSelf.fireEvent("theadKeyEvent",{target:elTarget,event:e});
7824                  break;
7825              default:
7826                  break;
7827          }
7828          if(bKeepBubbling === false) {
7829              return;
7830          }
7831          else {
7832              elTarget = elTarget.parentNode;
7833              if(elTarget) {
7834                  elTag = elTarget.nodeName.toLowerCase();
7835              }
7836          }
7837      }
7838      oSelf.fireEvent("tableKeyEvent",{target:(elTarget || oSelf._elContainer),event:e});
7839  },
7840  
7841  /**
7842   * Handles keydown events on the TBODY element. Handles selection behavior,
7843   * provides hooks for ENTER to edit functionality.
7844   *
7845   * @method _onTbodyKeydown
7846   * @param e {HTMLEvent} The key event.
7847   * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
7848   * @private
7849   */
7850  _onTbodyKeydown : function(e, oSelf) {
7851      var sMode = oSelf.get("selectionMode");
7852  
7853      if(sMode == "standard") {
7854          oSelf._handleStandardSelectionByKey(e);
7855      }
7856      else if(sMode == "single") {
7857          oSelf._handleSingleSelectionByKey(e);
7858      }
7859      else if(sMode == "cellblock") {
7860          oSelf._handleCellBlockSelectionByKey(e);
7861      }
7862      else if(sMode == "cellrange") {
7863          oSelf._handleCellRangeSelectionByKey(e);
7864      }
7865      else if(sMode == "singlecell") {
7866          oSelf._handleSingleCellSelectionByKey(e);
7867      }
7868      
7869      if(oSelf._oCellEditor) {
7870          if(oSelf._oCellEditor.fireEvent) {
7871              oSelf._oCellEditor.fireEvent("blurEvent", {editor: oSelf._oCellEditor});
7872          }
7873          else if(oSelf._oCellEditor.isActive) {
7874              oSelf.fireEvent("editorBlurEvent", {editor:oSelf._oCellEditor});
7875          }
7876      }
7877  
7878      var elTarget = Ev.getTarget(e);
7879      var elTag = elTarget.nodeName && elTarget.nodeName.toLowerCase();
7880      var bKeepBubbling = true;
7881      while(elTarget && (elTag != "table")) {
7882          switch(elTag) {
7883              case "body":
7884                  return;
7885              case "tbody":
7886                  bKeepBubbling = oSelf.fireEvent("tbodyKeyEvent",{target:elTarget,event:e});
7887                  break;
7888              default:
7889                  break;
7890          }
7891          if(bKeepBubbling === false) {
7892              return;
7893          }
7894          else {
7895              elTarget = elTarget.parentNode;
7896              if(elTarget) {
7897                  elTag = elTarget.nodeName.toLowerCase();
7898              }
7899          }
7900      }
7901      oSelf.fireEvent("tableKeyEvent",{target:(elTarget || oSelf._elContainer),event:e});
7902  },
7903  
7904  /**
7905   * Handles click events on the THEAD element.
7906   *
7907   * @method _onTheadClick
7908   * @param e {HTMLEvent} The click event.
7909   * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
7910   * @private
7911   */
7912  _onTheadClick : function(e, oSelf) {
7913      // This blurs the CellEditor
7914      if(oSelf._oCellEditor) {
7915          if(oSelf._oCellEditor.fireEvent) {
7916              oSelf._oCellEditor.fireEvent("blurEvent", {editor: oSelf._oCellEditor});
7917          }
7918          // Backward compatibility
7919          else if(oSelf._oCellEditor.isActive) {
7920              oSelf.fireEvent("editorBlurEvent", {editor:oSelf._oCellEditor});
7921          }
7922      }
7923  
7924      var elTarget = Ev.getTarget(e),
7925          elTag = elTarget.nodeName && elTarget.nodeName.toLowerCase(),
7926          bKeepBubbling = true;
7927      while(elTarget && (elTag != "table")) {
7928          switch(elTag) {
7929              case "body":
7930                  return;
7931              case "input":
7932                  var sType = elTarget.type.toLowerCase();
7933                  if(sType == "checkbox") {
7934                      bKeepBubbling = oSelf.fireEvent("theadCheckboxClickEvent",{target:elTarget,event:e});
7935                  }
7936                  else if(sType == "radio") {
7937                      bKeepBubbling = oSelf.fireEvent("theadRadioClickEvent",{target:elTarget,event:e});
7938                  }
7939                  else if((sType == "button") || (sType == "image") || (sType == "submit") || (sType == "reset")) {
7940                      if(!elTarget.disabled) {
7941                          bKeepBubbling = oSelf.fireEvent("theadButtonClickEvent",{target:elTarget,event:e});
7942                      }
7943                      else {
7944                          bKeepBubbling = false;
7945                      }
7946                  }
7947                  else if (elTarget.disabled){
7948                      bKeepBubbling = false;
7949                  }
7950                  break;
7951              case "a":
7952                  bKeepBubbling = oSelf.fireEvent("theadLinkClickEvent",{target:elTarget,event:e});
7953                  break;
7954              case "button":
7955                  if(!elTarget.disabled) {
7956                      bKeepBubbling = oSelf.fireEvent("theadButtonClickEvent",{target:elTarget,event:e});
7957                  }
7958                  else {
7959                      bKeepBubbling = false;
7960                  }
7961                  break;
7962              case "span":
7963                  if(Dom.hasClass(elTarget, DT.CLASS_LABEL)) {
7964                      bKeepBubbling = oSelf.fireEvent("theadLabelClickEvent",{target:elTarget,event:e});
7965                      // Backward compatibility
7966                      bKeepBubbling = oSelf.fireEvent("headerLabelClickEvent",{target:elTarget,event:e});
7967                  }
7968                  break;
7969              case "th":
7970                  bKeepBubbling = oSelf.fireEvent("theadCellClickEvent",{target:elTarget,event:e});
7971                  // Backward compatibility
7972                  bKeepBubbling = oSelf.fireEvent("headerCellClickEvent",{target:elTarget,event:e});
7973                  break;
7974              case "tr":
7975                  bKeepBubbling = oSelf.fireEvent("theadRowClickEvent",{target:elTarget,event:e});
7976                  // Backward compatibility
7977                  bKeepBubbling = oSelf.fireEvent("headerRowClickEvent",{target:elTarget,event:e});
7978                  break;
7979              default:
7980                  break;
7981          }
7982          if(bKeepBubbling === false) {
7983              return;
7984          }
7985          else {
7986              elTarget = elTarget.parentNode;
7987              if(elTarget) {
7988                  elTag = elTarget.nodeName.toLowerCase();
7989              }
7990          }
7991      }
7992      oSelf.fireEvent("tableClickEvent",{target:(elTarget || oSelf._elContainer),event:e});
7993  },
7994  
7995  /**
7996   * Handles click events on the primary TBODY element.
7997   *
7998   * @method _onTbodyClick
7999   * @param e {HTMLEvent} The click event.
8000   * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
8001   * @private
8002   */
8003  _onTbodyClick : function(e, oSelf) {
8004      // This blurs the CellEditor
8005      if(oSelf._oCellEditor) {
8006          if(oSelf._oCellEditor.fireEvent) {
8007              oSelf._oCellEditor.fireEvent("blurEvent", {editor: oSelf._oCellEditor});
8008          }
8009          else if(oSelf._oCellEditor.isActive) {
8010              oSelf.fireEvent("editorBlurEvent", {editor:oSelf._oCellEditor});
8011          }
8012      }
8013  
8014      // Fire Custom Events
8015      var elTarget = Ev.getTarget(e),
8016          elTag = elTarget.nodeName && elTarget.nodeName.toLowerCase(),
8017          bKeepBubbling = true;
8018      while(elTarget && (elTag != "table")) {
8019          switch(elTag) {
8020              case "body":
8021                  return;
8022              case "input":
8023                  var sType = elTarget.type.toLowerCase();
8024                  if(sType == "checkbox") {
8025                      bKeepBubbling = oSelf.fireEvent("checkboxClickEvent",{target:elTarget,event:e});
8026                  }
8027                  else if(sType == "radio") {
8028                      bKeepBubbling = oSelf.fireEvent("radioClickEvent",{target:elTarget,event:e});
8029                  }
8030                  else if((sType == "button") || (sType == "image") || (sType == "submit") || (sType == "reset")) {
8031                      if(!elTarget.disabled) {
8032                          bKeepBubbling = oSelf.fireEvent("buttonClickEvent",{target:elTarget,event:e});
8033                      }
8034                      else {
8035                          bKeepBubbling = false;
8036                      }
8037                  }
8038                  else if (elTarget.disabled){
8039                      bKeepBubbling = false;
8040                  }
8041                  break;
8042              case "a":
8043                  bKeepBubbling = oSelf.fireEvent("linkClickEvent",{target:elTarget,event:e});
8044                  break;
8045              case "button":
8046                  if(!elTarget.disabled) {
8047                      bKeepBubbling = oSelf.fireEvent("buttonClickEvent",{target:elTarget,event:e});
8048                  }
8049                  else {
8050                      bKeepBubbling = false;
8051                  }
8052                  break;
8053              case "td":
8054                  bKeepBubbling = oSelf.fireEvent("cellClickEvent",{target:elTarget,event:e});
8055                  break;
8056              case "tr":
8057                  bKeepBubbling = oSelf.fireEvent("rowClickEvent",{target:elTarget,event:e});
8058                  break;
8059              default:
8060                  break;
8061          }
8062          if(bKeepBubbling === false) {
8063              return;
8064          }
8065          else {
8066              elTarget = elTarget.parentNode;
8067              if(elTarget) {
8068                  elTag = elTarget.nodeName.toLowerCase();
8069              }
8070          }
8071      }
8072      oSelf.fireEvent("tableClickEvent",{target:(elTarget || oSelf._elContainer),event:e});
8073  },
8074  
8075  /**
8076   * Handles change events on SELECT elements within DataTable.
8077   *
8078   * @method _onDropdownChange
8079   * @param e {HTMLEvent} The change event.
8080   * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
8081   * @private
8082   */
8083  _onDropdownChange : function(e, oSelf) {
8084      var elTarget = Ev.getTarget(e);
8085      oSelf.fireEvent("dropdownChangeEvent", {event:e, target:elTarget});
8086  },
8087  
8088  
8089  
8090  
8091  
8092  
8093  
8094  
8095  
8096  
8097  
8098  
8099  
8100  
8101  
8102  
8103  
8104  
8105  
8106  
8107  
8108  
8109  
8110  
8111  
8112  
8113  
8114  
8115  
8116  
8117  
8118  
8119  /////////////////////////////////////////////////////////////////////////////
8120  //
8121  // Public member variables
8122  //
8123  /////////////////////////////////////////////////////////////////////////////
8124  /**
8125   * Returns object literal of initial configs.
8126   *
8127   * @property configs
8128   * @type Object
8129   * @default {} 
8130   */
8131  configs: null,
8132  
8133  
8134  /////////////////////////////////////////////////////////////////////////////
8135  //
8136  // Public methods
8137  //
8138  /////////////////////////////////////////////////////////////////////////////
8139  
8140  /**
8141   * Returns unique id assigned to instance, which is a useful prefix for
8142   * generating unique DOM ID strings.
8143   *
8144   * @method getId
8145   * @return {String} Unique ID of the DataSource instance.
8146   */
8147  getId : function() {
8148      return this._sId;
8149  },
8150  
8151  /**
8152   * DataSource instance name, for logging.
8153   *
8154   * @method toString
8155   * @return {String} Unique name of the DataSource instance.
8156   */
8157  
8158  toString : function() {
8159      return "DataTable instance " + this._sId;
8160  },
8161  
8162  /**
8163   * Returns the DataTable instance's DataSource instance.
8164   *
8165   * @method getDataSource
8166   * @return {YAHOO.util.DataSource} DataSource instance.
8167   */
8168  getDataSource : function() {
8169      return this._oDataSource;
8170  },
8171  
8172  /**
8173   * Returns the DataTable instance's ColumnSet instance.
8174   *
8175   * @method getColumnSet
8176   * @return {YAHOO.widget.ColumnSet} ColumnSet instance.
8177   */
8178  getColumnSet : function() {
8179      return this._oColumnSet;
8180  },
8181  
8182  /**
8183   * Returns the DataTable instance's RecordSet instance.
8184   *
8185   * @method getRecordSet
8186   * @return {YAHOO.widget.RecordSet} RecordSet instance.
8187   */
8188  getRecordSet : function() {
8189      return this._oRecordSet;
8190  },
8191  
8192  /**
8193   * Returns on object literal representing the DataTable instance's current
8194   * state with the following properties:
8195   * <dl>
8196   * <dt>pagination</dt>
8197   * <dd>Instance of YAHOO.widget.Paginator</dd>
8198   *
8199   * <dt>sortedBy</dt>
8200   * <dd>
8201   *     <dl>
8202   *         <dt>sortedBy.key</dt>
8203   *         <dd>{String} Key of sorted Column</dd>
8204   *         <dt>sortedBy.dir</dt>
8205   *         <dd>{String} Initial sort direction, either YAHOO.widget.DataTable.CLASS_ASC or YAHOO.widget.DataTable.CLASS_DESC</dd>
8206   *     </dl>
8207   * </dd>
8208   *
8209   * <dt>selectedRows</dt>
8210   * <dd>Array of selected rows by Record ID.</dd>
8211   *
8212   * <dt>selectedCells</dt>
8213   * <dd>Selected cells as an array of object literals:
8214   *     {recordId:sRecordId, columnKey:sColumnKey}</dd>
8215   * </dl>
8216   *  
8217   * @method getState
8218   * @return {Object} DataTable instance state object literal values.
8219   */
8220  getState : function() {
8221      return {
8222          totalRecords: this.get('paginator') ? this.get('paginator').get("totalRecords") : this._oRecordSet.getLength(),
8223          pagination: this.get("paginator") ? this.get("paginator").getState() : null,
8224          sortedBy: this.get("sortedBy"),
8225          selectedRows: this.getSelectedRows(),
8226          selectedCells: this.getSelectedCells()
8227      };
8228  },
8229  
8230  
8231  
8232  
8233  
8234  
8235  
8236  
8237  
8238  
8239  
8240  
8241  
8242  
8243  
8244  
8245  
8246  
8247  
8248  
8249  
8250  
8251  
8252  
8253  
8254  
8255  
8256  
8257  
8258  
8259  
8260  
8261  
8262  
8263  
8264  
8265  
8266  
8267  
8268  
8269  
8270  
8271  
8272  // DOM ACCESSORS
8273  
8274  /**
8275   * Returns DOM reference to the DataTable's container element.
8276   *
8277   * @method getContainerEl
8278   * @return {HTMLElement} Reference to DIV element.
8279   */
8280  getContainerEl : function() {
8281      return this._elContainer;
8282  },
8283  
8284  /**
8285   * Returns DOM reference to the DataTable's TABLE element.
8286   *
8287   * @method getTableEl
8288   * @return {HTMLElement} Reference to TABLE element.
8289   */
8290  getTableEl : function() {
8291      return this._elTable;
8292  },
8293  
8294  /**
8295   * Returns DOM reference to the DataTable's THEAD element.
8296   *
8297   * @method getTheadEl
8298   * @return {HTMLElement} Reference to THEAD element.
8299   */
8300  getTheadEl : function() {
8301      return this._elThead;
8302  },
8303  
8304  /**
8305   * Returns DOM reference to the DataTable's primary TBODY element.
8306   *
8307   * @method getTbodyEl
8308   * @return {HTMLElement} Reference to TBODY element.
8309   */
8310  getTbodyEl : function() {
8311      return this._elTbody;
8312  },
8313  
8314  /**
8315   * Returns DOM reference to the DataTable's secondary TBODY element that is
8316   * used to display messages.
8317   *
8318   * @method getMsgTbodyEl
8319   * @return {HTMLElement} Reference to TBODY element.
8320   */
8321  getMsgTbodyEl : function() {
8322      return this._elMsgTbody;
8323  },
8324  
8325  /**
8326   * Returns DOM reference to the TD element within the secondary TBODY that is
8327   * used to display messages.
8328   *
8329   * @method getMsgTdEl
8330   * @return {HTMLElement} Reference to TD element.
8331   */
8332  getMsgTdEl : function() {
8333      return this._elMsgTd;
8334  },
8335  
8336  /**
8337   * Returns the corresponding TR reference for a given DOM element, ID string or
8338   * page row index. If the given identifier is a child of a TR element,
8339   * then DOM tree is traversed until a parent TR element is returned, otherwise
8340   * null. Returns null if the row is not considered a primary row (i.e., row
8341   * extensions).
8342   *
8343   * @method getTrEl
8344   * @param row {HTMLElement | String | Number | YAHOO.widget.Record} Which row to
8345   * get: by element reference, ID string, page row index, or Record.
8346   * @return {HTMLElement} Reference to TR element, or null.
8347   */
8348  getTrEl : function(row) {
8349      // By Record
8350      if(row instanceof YAHOO.widget.Record) {
8351          return document.getElementById(row.getId());
8352      }
8353      // By page row index
8354      else if(lang.isNumber(row)) {
8355          var dataRows = Dom.getElementsByClassName(DT.CLASS_REC, "tr", this._elTbody);
8356          return dataRows && dataRows[row] ? dataRows[row] : null;
8357      }
8358      // By ID string or element reference
8359      else if(row) {
8360          var elRow = (lang.isString(row)) ? document.getElementById(row) : row;
8361  
8362          // Validate HTML element
8363          if(elRow && elRow.ownerDocument == document) {
8364              // Validate TR element
8365              if(elRow.nodeName.toLowerCase() != "tr") {
8366                  // Traverse up the DOM to find the corresponding TR element
8367                  elRow = Dom.getAncestorByTagName(elRow,"tr");
8368              }
8369  
8370              return elRow;
8371          }
8372      }
8373  
8374      return null;
8375  },
8376  
8377  /**
8378   * Returns DOM reference to the first primary TR element in the DataTable page, or null.
8379   *
8380   * @method getFirstTrEl
8381   * @return {HTMLElement} Reference to TR element.
8382   */
8383  getFirstTrEl : function() {
8384      var allRows = this._elTbody.rows,
8385          i=0;
8386      while(allRows[i]) {
8387          if(this.getRecord(allRows[i])) {
8388              return allRows[i];
8389          }
8390          i++;
8391      }
8392      return null;
8393  
8394  },
8395  
8396  /**
8397   * Returns DOM reference to the last primary TR element in the DataTable page, or null.
8398   *
8399   * @method getLastTrEl
8400   * @return {HTMLElement} Reference to last TR element.
8401   */
8402  getLastTrEl : function() {
8403      var allRows = this._elTbody.rows,
8404          i=allRows.length-1;
8405      while(i>-1) {
8406          if(this.getRecord(allRows[i])) {
8407              return allRows[i];
8408          }
8409          i--;
8410      }
8411      return null;
8412  },
8413  
8414  /**
8415   * Returns DOM reference to the next TR element from the given primary TR element, or null.
8416   *
8417   * @method getNextTrEl
8418   * @param row {HTMLElement | String | Number | YAHOO.widget.Record} Element
8419   * reference, ID string, page row index, or Record from which to get next TR element.
8420   * @param forcePrimary {Boolean} (optional) If true, will only return TR elements
8421   * that correspond to Records. Non-primary rows (such as row expansions)
8422   * will be skipped.
8423   * @return {HTMLElement} Reference to next TR element.
8424   */
8425  getNextTrEl : function(row, forcePrimary) {
8426      var nThisTrIndex = this.getTrIndex(row);
8427      if(nThisTrIndex !== null) {
8428          var allRows = this._elTbody.rows;
8429          if(forcePrimary) {
8430              while(nThisTrIndex < allRows.length-1) {
8431                  row = allRows[nThisTrIndex+1];
8432                  if(this.getRecord(row)) {
8433                      return row;
8434                  }
8435                  nThisTrIndex++;
8436              }
8437          }
8438          else {
8439              if(nThisTrIndex < allRows.length-1) {
8440                  return allRows[nThisTrIndex+1];
8441              }
8442          }
8443      }
8444  
8445      return null;
8446  },
8447  
8448  /**
8449   * Returns DOM reference to the previous TR element from the given primary TR element, or null.
8450   *
8451   * @method getPreviousTrEl
8452   * @param row {HTMLElement | String | Number | YAHOO.widget.Record} Element
8453   * reference, ID string, page row index, or Record from which to get previous TR element.
8454   * @param forcePrimary {Boolean} (optional) If true, will only return TR elements
8455   * from rothat correspond to Records. Non-primary rows (such as row expansions)
8456   * will be skipped.
8457   * @return {HTMLElement} Reference to previous TR element.
8458   */
8459  getPreviousTrEl : function(row, forcePrimary) {
8460      var nThisTrIndex = this.getTrIndex(row);
8461      if(nThisTrIndex !== null) {
8462          var allRows = this._elTbody.rows;
8463  
8464          if(forcePrimary) {
8465              while(nThisTrIndex > 0) {
8466                  row = allRows[nThisTrIndex-1];
8467                  if(this.getRecord(row)) {
8468                      return row;
8469                  }
8470                  nThisTrIndex--;
8471              }
8472          }
8473          else {
8474              if(nThisTrIndex > 0) {
8475                  return allRows[nThisTrIndex-1];
8476              }
8477          }
8478      }
8479  
8480      return null;
8481  },
8482  
8483  
8484  /**
8485   * Workaround for IE bug where hidden or not-in-dom elements cause cellIndex
8486   * value to be incorrect.
8487   *
8488   * @method getCellIndex
8489   * @param cell {HTMLElement | Object} TD element or child of a TD element, or
8490   * object literal of syntax {record:oRecord, column:oColumn}.
8491   * @return {Number} TD.cellIndex value.
8492   */
8493  getCellIndex : function(cell) {
8494      cell = this.getTdEl(cell);
8495      if(cell) {
8496          if(ua.ie > 0) {
8497              var i=0,
8498                  tr = cell.parentNode,
8499                  allCells = tr.childNodes,
8500                  len = allCells.length;
8501              for(; i<len; i++) {
8502                  if(allCells[i] == cell) {
8503                      return i;
8504                  }
8505              }
8506          }
8507          else {
8508              return cell.cellIndex;
8509          }
8510      }
8511  },
8512  
8513  /**
8514   * Returns DOM reference to a TD liner element.
8515   *
8516   * @method getTdLinerEl
8517   * @param cell {HTMLElement | Object} TD element or child of a TD element, or
8518   * object literal of syntax {record:oRecord, column:oColumn}.
8519   * @return {HTMLElement} Reference to TD liner element.
8520   */
8521  getTdLinerEl : function(cell) {
8522      var elCell = this.getTdEl(cell);
8523      return elCell.firstChild || null;
8524  },
8525  
8526  /**
8527   * Returns DOM reference to a TD element. Returns null if the row is not
8528   * considered a primary row (i.e., row extensions).
8529   *
8530   * @method getTdEl
8531   * @param cell {HTMLElement | String | Object} TD element or child of a TD element, or
8532   * object literal of syntax {record:oRecord, column:oColumn}.
8533   * @return {HTMLElement} Reference to TD element.
8534   */
8535  getTdEl : function(cell) {
8536      var elCell;
8537      var el = Dom.get(cell);
8538  
8539      // Validate HTML element
8540      if(el && (el.ownerDocument == document)) {
8541          // Validate TD element
8542          if(el.nodeName.toLowerCase() != "td") {
8543              // Traverse up the DOM to find the corresponding TR element
8544              elCell = Dom.getAncestorByTagName(el, "td");
8545          }
8546          else {
8547              elCell = el;
8548          }
8549          
8550          // Make sure the TD is in this TBODY or is not in DOM
8551          // Bug 2527707 and bug 2263558
8552          if(elCell && ((elCell.parentNode.parentNode == this._elTbody) ||
8553              (elCell.parentNode.parentNode === null) ||
8554              (elCell.parentNode.parentNode.nodeType === 11))) {
8555              // Now we can return the TD element
8556              return elCell;
8557          }
8558      }
8559      else if(cell) {
8560          var oRecord, nColKeyIndex;
8561  
8562          if(lang.isString(cell.columnKey) && lang.isString(cell.recordId)) {
8563              oRecord = this.getRecord(cell.recordId);
8564              var oColumn = this.getColumn(cell.columnKey);
8565              if(oColumn) {
8566                  nColKeyIndex = oColumn.getKeyIndex();
8567              }
8568  
8569          }
8570          if(cell.record && cell.column && cell.column.getKeyIndex) {
8571              oRecord = cell.record;
8572              nColKeyIndex = cell.column.getKeyIndex();
8573          }
8574          var elRow = this.getTrEl(oRecord);
8575          if((nColKeyIndex !== null) && elRow && elRow.cells && elRow.cells.length > 0) {
8576              return elRow.cells[nColKeyIndex] || null;
8577          }
8578      }
8579  
8580      return null;
8581  },
8582  
8583  /**
8584   * Returns DOM reference to the first primary TD element in the DataTable page (by default),
8585   * the first TD element of the optionally given row, or null.
8586   *
8587   * @method getFirstTdEl
8588   * @param row {HTMLElement} (optional) row from which to get first TD
8589   * @return {HTMLElement} Reference to TD element.
8590   */
8591  getFirstTdEl : function(row) {
8592      var elRow = lang.isValue(row) ? this.getTrEl(row) : this.getFirstTrEl();
8593      if(elRow) {
8594          if(elRow.cells && elRow.cells.length > 0) {
8595              return elRow.cells[0];
8596          }
8597          else if(elRow.childNodes && elRow.childNodes.length > 0) {
8598              return elRow.childNodes[0];
8599          }
8600      }
8601      return null;
8602  },
8603  
8604  /**
8605   * Returns DOM reference to the last primary TD element in the DataTable page (by default),
8606   * the first TD element of the optionally given row, or null.
8607   *
8608   * @method getLastTdEl
8609   * @param row {HTMLElement} (optional) row from which to get first TD
8610   * @return {HTMLElement} Reference to last TD element.
8611   */
8612  getLastTdEl : function(row) {
8613      var elRow = lang.isValue(row) ? this.getTrEl(row) : this.getLastTrEl();
8614      if(elRow) {
8615          if(elRow.cells && elRow.cells.length > 0) {
8616              return elRow.cells[elRow.cells.length-1];
8617          }
8618          else if(elRow.childNodes && elRow.childNodes.length > 0) {
8619              return elRow.childNodes[elRow.childNodes.length-1];
8620          }
8621      }
8622      return null;
8623  },
8624  
8625  /**
8626   * Returns DOM reference to the next TD element from the given cell, or null.
8627   *
8628   * @method getNextTdEl
8629   * @param cell {HTMLElement | String | Object} DOM element reference or string ID, or
8630   * object literal of syntax {record:oRecord, column:oColumn} from which to get next TD element.
8631   * @return {HTMLElement} Reference to next TD element, or null.
8632   */
8633  getNextTdEl : function(cell) {
8634      var elCell = this.getTdEl(cell);
8635      if(elCell) {
8636          var nThisTdIndex = this.getCellIndex(elCell);
8637          var elRow = this.getTrEl(elCell);
8638          if(elRow.cells && (elRow.cells.length) > 0 && (nThisTdIndex < elRow.cells.length-1)) {
8639              return elRow.cells[nThisTdIndex+1];
8640          }
8641          else if(elRow.childNodes && (elRow.childNodes.length) > 0 && (nThisTdIndex < elRow.childNodes.length-1)) {
8642              return elRow.childNodes[nThisTdIndex+1];
8643          }
8644          else {
8645              var elNextRow = this.getNextTrEl(elRow);
8646              if(elNextRow) {
8647                  return elNextRow.cells[0];
8648              }
8649          }
8650      }
8651      return null;
8652  },
8653  
8654  /**
8655   * Returns DOM reference to the previous TD element from the given cell, or null.
8656   *
8657   * @method getPreviousTdEl
8658   * @param cell {HTMLElement | String | Object} DOM element reference or string ID, or
8659   * object literal of syntax {record:oRecord, column:oColumn} from which to get previous TD element.
8660   * @return {HTMLElement} Reference to previous TD element, or null.
8661   */
8662  getPreviousTdEl : function(cell) {
8663      var elCell = this.getTdEl(cell);
8664      if(elCell) {
8665          var nThisTdIndex = this.getCellIndex(elCell);
8666          var elRow = this.getTrEl(elCell);
8667          if(nThisTdIndex > 0) {
8668              if(elRow.cells && elRow.cells.length > 0) {
8669                  return elRow.cells[nThisTdIndex-1];
8670              }
8671              else if(elRow.childNodes && elRow.childNodes.length > 0) {
8672                  return elRow.childNodes[nThisTdIndex-1];
8673              }
8674          }
8675          else {
8676              var elPreviousRow = this.getPreviousTrEl(elRow);
8677              if(elPreviousRow) {
8678                  return this.getLastTdEl(elPreviousRow);
8679              }
8680          }
8681      }
8682      return null;
8683  },
8684  
8685  /**
8686   * Returns DOM reference to the above TD element from the given cell, or null.
8687   *
8688   * @method getAboveTdEl
8689   * @param cell {HTMLElement | String | Object} DOM element reference or string ID, or
8690   * object literal of syntax {record:oRecord, column:oColumn} from which to get next TD element.
8691   * @param forcePrimary {Boolean} (optional) If true, will only return TD elements
8692   * from rows that correspond to Records. Non-primary rows (such as row expansions)
8693   * will be skipped.
8694   * @return {HTMLElement} Reference to above TD element, or null.
8695   */
8696  getAboveTdEl : function(cell, forcePrimary) {
8697      var elCell = this.getTdEl(cell);
8698      if(elCell) {
8699          var elPreviousRow = this.getPreviousTrEl(elCell, forcePrimary);
8700          if(elPreviousRow ) {
8701              var cellIndex = this.getCellIndex(elCell);
8702              if(elPreviousRow.cells && elPreviousRow.cells.length > 0) {
8703                  return elPreviousRow.cells[cellIndex] ? elPreviousRow.cells[cellIndex] : null;
8704              }
8705              else if(elPreviousRow.childNodes && elPreviousRow.childNodes.length > 0) {
8706                  return elPreviousRow.childNodes[cellIndex] ? elPreviousRow.childNodes[cellIndex] : null;
8707              }
8708          }
8709      }
8710      return null;
8711  },
8712  
8713  /**
8714   * Returns DOM reference to the below TD element from the given cell, or null.
8715   *
8716   * @method getBelowTdEl
8717   * @param cell {HTMLElement | String | Object} DOM element reference or string ID, or
8718   * object literal of syntax {record:oRecord, column:oColumn} from which to get previous TD element.
8719   * @param forcePrimary {Boolean} (optional) If true, will only return TD elements
8720   * from rows that correspond to Records. Non-primary rows (such as row expansions)
8721   * will be skipped.
8722   * @return {HTMLElement} Reference to below TD element, or null.
8723   */
8724  getBelowTdEl : function(cell, forcePrimary) {
8725      var elCell = this.getTdEl(cell);
8726      if(elCell) {
8727          var elNextRow = this.getNextTrEl(elCell, forcePrimary);
8728          if(elNextRow) {
8729              var cellIndex = this.getCellIndex(elCell);
8730              if(elNextRow.cells && elNextRow.cells.length > 0) {
8731                  return elNextRow.cells[cellIndex] ? elNextRow.cells[cellIndex] : null;
8732              }
8733              else if(elNextRow.childNodes && elNextRow.childNodes.length > 0) {
8734                  return elNextRow.childNodes[cellIndex] ? elNextRow.childNodes[cellIndex] : null;
8735              }
8736          }
8737      }
8738      return null;
8739  },
8740  
8741  /**
8742   * Returns DOM reference to a TH liner element. Needed to normalize for resizeable 
8743   * Columns, which have an additional resizer liner DIV element between the TH
8744   * element and the liner DIV element. 
8745   *
8746   * @method getThLinerEl
8747   * @param theadCell {YAHOO.widget.Column | HTMLElement | String} Column instance,
8748   * DOM element reference, or string ID.
8749   * @return {HTMLElement} Reference to TH liner element.
8750   */
8751  getThLinerEl : function(theadCell) {
8752      var oColumn = this.getColumn(theadCell);
8753      return (oColumn) ? oColumn.getThLinerEl() : null;
8754  },
8755  
8756  /**
8757   * Returns DOM reference to a TH element.
8758   *
8759   * @method getThEl
8760   * @param theadCell {YAHOO.widget.Column | HTMLElement | String} Column instance,
8761   * DOM element reference, or string ID.
8762   * @return {HTMLElement} Reference to TH element.
8763   */
8764  getThEl : function(theadCell) {
8765      var elTh;
8766  
8767      // Validate Column instance
8768      if(theadCell instanceof YAHOO.widget.Column) {
8769          var oColumn = theadCell;
8770          elTh = oColumn.getThEl();
8771          if(elTh) {
8772              return elTh;
8773          }
8774      }
8775      // Validate HTML element
8776      else {
8777          var el = Dom.get(theadCell);
8778  
8779          if(el && (el.ownerDocument == document)) {
8780              // Validate TH element
8781              if(el.nodeName.toLowerCase() != "th") {
8782                  // Traverse up the DOM to find the corresponding TR element
8783                  elTh = Dom.getAncestorByTagName(el,"th");
8784              }
8785              else {
8786                  elTh = el;
8787              }
8788  
8789              return elTh;
8790          }
8791      }
8792  
8793      return null;
8794  },
8795  
8796  /**
8797   * Returns the page row index of given primary row. Returns null if the row is not on the
8798   * current DataTable page, or if row is not considered a primary row (i.e., row
8799   * extensions).
8800   *
8801   * @method getTrIndex
8802   * @param row {HTMLElement | String | YAHOO.widget.Record | Number} DOM or ID
8803   * string reference to an element within the DataTable page, a Record instance,
8804   * or a Record's RecordSet index.
8805   * @return {Number} Page row index, or null if data row does not exist or is not on current page.
8806   */
8807  getTrIndex : function(row) {
8808      var record = this.getRecord(row),
8809          index = this.getRecordIndex(record),
8810          tr;
8811      if(record) {
8812          tr = this.getTrEl(record);
8813          if(tr) {
8814              return tr.sectionRowIndex;
8815          }
8816          else {
8817              var oPaginator = this.get("paginator");
8818              if(oPaginator) {
8819                  return oPaginator.get('recordOffset') + index;
8820              }
8821              else {
8822                  return index;
8823              }
8824          }
8825      }
8826      return null;
8827  },
8828  
8829  
8830  
8831  
8832  
8833  
8834  
8835  
8836  
8837  
8838  
8839  
8840  
8841  
8842  
8843  
8844  
8845  
8846  
8847  
8848  
8849  
8850  
8851  
8852  
8853  
8854  
8855  
8856  
8857  
8858  
8859  
8860  
8861  
8862  
8863  
8864  
8865  
8866  
8867  
8868  
8869  
8870  
8871  
8872  
8873  
8874  // TABLE FUNCTIONS
8875  
8876  /**
8877   * Loads new data. Convenience method that calls DataSource's sendRequest()
8878   * method under the hood.
8879   *
8880   * @method load
8881   * @param oConfig {object} Optional configuration parameters:
8882   *
8883   * <dl>
8884   * <dt>request</dt><dd>Pass in a new request, or initialRequest is used.</dd>
8885   * <dt>callback</dt><dd>Pass in DataSource sendRequest() callback object, or the following is used:
8886   *    <dl>
8887   *      <dt>success</dt><dd>datatable.onDataReturnInitializeTable</dd>
8888   *      <dt>failure</dt><dd>datatable.onDataReturnInitializeTable</dd>
8889   *      <dt>scope</dt><dd>datatable</dd>
8890   *      <dt>argument</dt><dd>datatable.getState()</dd>
8891   *    </dl>
8892   * </dd>
8893   * <dt>datasource</dt><dd>Pass in a new DataSource instance to override the current DataSource for this transaction.</dd>
8894   * </dl>
8895   */
8896  load : function(oConfig) {
8897      oConfig = oConfig || {};
8898  
8899      (oConfig.datasource || this._oDataSource).sendRequest(oConfig.request || this.get("initialRequest"), oConfig.callback || {
8900          success: this.onDataReturnInitializeTable,
8901          failure: this.onDataReturnInitializeTable,
8902          scope: this,
8903          argument: this.getState()
8904      });
8905  },
8906  
8907  /**
8908   * Resets a RecordSet with the given data and populates the page view
8909   * with the new data. Any previous data, and selection and sort states are
8910   * cleared. New data should be added as a separate step. 
8911   *
8912   * @method initializeTable
8913   */
8914  initializeTable : function() {
8915      // Reset init flag
8916      this._bInit = true;
8917      
8918      // Clear the RecordSet
8919      this._oRecordSet.reset();
8920  
8921      // Clear the Paginator's totalRecords if paginating
8922      var pag = this.get('paginator');
8923      if (pag) {
8924          pag.set('totalRecords',0);
8925      }
8926  
8927      // Clear selections
8928      this._unselectAllTrEls();
8929      this._unselectAllTdEls();
8930      this._aSelections = null;
8931      this._oAnchorRecord = null;
8932      this._oAnchorCell = null;
8933      
8934      // Clear sort
8935      this.set("sortedBy", null);
8936  },
8937  
8938  /**
8939   * Internal wrapper calls run() on render Chain instance.
8940   *
8941   * @method _runRenderChain
8942   * @private 
8943   */
8944  _runRenderChain : function() {
8945      this._oChainRender.run();
8946  },
8947  
8948  /**
8949   * Returns array of Records for current view. For example, if paginated, it
8950   * returns the subset of Records for current page.
8951   *
8952   * @method _getViewRecords
8953   * @protected
8954   * @return {Array} Array of Records to display in current view.
8955   */
8956  _getViewRecords : function() {
8957      // Paginator is enabled, show a subset of Records
8958      var oPaginator = this.get('paginator');
8959      if(oPaginator) {
8960          return this._oRecordSet.getRecords(
8961                          oPaginator.getStartIndex(),
8962                          oPaginator.getRowsPerPage());
8963      }
8964      // Not paginated, show all records
8965      else {
8966          return this._oRecordSet.getRecords();
8967      }
8968  
8969  },
8970  
8971  /**
8972   * Renders the view with existing Records from the RecordSet while
8973   * maintaining sort, pagination, and selection states. For performance, reuses
8974   * existing DOM elements when possible while deleting extraneous elements.
8975   *
8976   * @method render
8977   */
8978  render : function() {
8979  //YAHOO.example.Performance.trialStart = new Date();
8980  
8981      this._oChainRender.stop();
8982  
8983      this.fireEvent("beforeRenderEvent");
8984  
8985      var i, j, k, len,
8986          allRecords = this._getViewRecords();
8987  
8988  
8989      // From the top, update in-place existing rows, so as to reuse DOM elements
8990      var elTbody = this._elTbody,
8991          loopN = this.get("renderLoopSize"),
8992          nRecordsLength = allRecords.length;
8993      
8994      // Table has rows
8995      if(nRecordsLength > 0) {                
8996          elTbody.style.display = "none";
8997          while(elTbody.lastChild) {
8998              elTbody.removeChild(elTbody.lastChild);
8999          }
9000          elTbody.style.display = "";
9001  
9002          // Set up the loop Chain to render rows
9003          this._oChainRender.add({
9004              method: function(oArg) {
9005                  if((this instanceof DT) && this._sId) {
9006                      var i = oArg.nCurrentRecord,
9007                          endRecordIndex = ((oArg.nCurrentRecord+oArg.nLoopLength) > nRecordsLength) ?
9008                                  nRecordsLength : (oArg.nCurrentRecord+oArg.nLoopLength),
9009                          elRow, nextSibling;
9010  
9011                      elTbody.style.display = "none";
9012                      
9013                      for(; i<endRecordIndex; i++) {
9014                          elRow = Dom.get(allRecords[i].getId());
9015                          elRow = elRow || this._addTrEl(allRecords[i]);
9016                          nextSibling = elTbody.childNodes[i] || null;
9017                          elTbody.insertBefore(elRow, nextSibling);
9018                      }
9019                      elTbody.style.display = "";
9020                      
9021                      // Set up for the next loop
9022                      oArg.nCurrentRecord = i;
9023                  }
9024              },
9025              scope: this,
9026              iterations: (loopN > 0) ? Math.ceil(nRecordsLength/loopN) : 1,
9027              argument: {
9028                  nCurrentRecord: 0,//nRecordsLength-1,  // Start at first Record
9029                  nLoopLength: (loopN > 0) ? loopN : nRecordsLength
9030              },
9031              timeout: (loopN > 0) ? 0 : -1
9032          });
9033          
9034          // Post-render tasks
9035          this._oChainRender.add({
9036              method: function(oArg) {
9037                  if((this instanceof DT) && this._sId) {
9038                      while(elTbody.rows.length > nRecordsLength) {
9039                          elTbody.removeChild(elTbody.lastChild);
9040                      }
9041                      this._setFirstRow();
9042                      this._setLastRow();
9043                      this._setRowStripes();
9044                      this._setSelections();
9045                  }
9046              },
9047              scope: this,
9048              timeout: (loopN > 0) ? 0 : -1
9049          });
9050       
9051      }
9052      // Table has no rows
9053      else {
9054          // Set up the loop Chain to delete rows
9055          var nTotal = elTbody.rows.length;
9056          if(nTotal > 0) {
9057              this._oChainRender.add({
9058                  method: function(oArg) {
9059                      if((this instanceof DT) && this._sId) {
9060                          var i = oArg.nCurrent,
9061                              loopN = oArg.nLoopLength,
9062                              nIterEnd = (i - loopN < 0) ? 0 : i - loopN;
9063      
9064                          elTbody.style.display = "none";
9065                          
9066                          for(; i>nIterEnd; i--) {
9067                              elTbody.deleteRow(-1);
9068                          }
9069                          elTbody.style.display = "";
9070                          
9071                          // Set up for the next loop
9072                          oArg.nCurrent = i;
9073                      }
9074                  },
9075                  scope: this,
9076                  iterations: (loopN > 0) ? Math.ceil(nTotal/loopN) : 1,
9077                  argument: {
9078                      nCurrent: nTotal, 
9079                      nLoopLength: (loopN > 0) ? loopN : nTotal
9080                  },
9081                  timeout: (loopN > 0) ? 0 : -1
9082              });
9083          }
9084      }
9085      this._runRenderChain();
9086  },
9087  
9088  /**
9089   * Disables DataTable UI.
9090   *
9091   * @method disable
9092   */
9093  disable : function() {
9094      this._disabled = true;
9095      var elTable = this._elTable;
9096      var elMask = this._elMask;
9097      elMask.style.width = elTable.offsetWidth + "px";
9098      elMask.style.height = elTable.offsetHeight + "px";
9099      elMask.style.left = elTable.offsetLeft + "px";
9100      elMask.style.display = "";
9101      this.fireEvent("disableEvent");
9102  },
9103  
9104  /**
9105   * Undisables DataTable UI.
9106   *
9107   * @method undisable
9108   */
9109  undisable : function() {
9110      this._disabled = false;
9111      this._elMask.style.display = "none";
9112      this.fireEvent("undisableEvent");
9113  },
9114  
9115   /**
9116   * Returns disabled state.
9117   *
9118   * @method isDisabled
9119   * @return {Boolean} True if UI is disabled, otherwise false
9120   */
9121  isDisabled : function() {
9122      return this._disabled;
9123  },
9124  
9125  /**
9126   * Nulls out the entire DataTable instance and related objects, removes attached
9127   * event listeners, and clears out DOM elements inside the container. After
9128   * calling this method, the instance reference should be expliclitly nulled by
9129   * implementer, as in myDataTable = null. Use with caution!
9130   *
9131   * @method destroy
9132   */
9133  destroy : function() {
9134      // Store for later
9135      var instanceName = this.toString();
9136  
9137      this._oChainRender.stop();
9138      
9139      // Destroy ColumnDD and ColumnResizers
9140      this._destroyColumnHelpers();
9141      
9142      // Destroy all CellEditors
9143      var oCellEditor;
9144      for(var i=0, len=this._oColumnSet.flat.length; i<len; i++) {
9145          oCellEditor = this._oColumnSet.flat[i].editor;
9146          if(oCellEditor && oCellEditor.destroy) {
9147              oCellEditor.destroy();
9148              this._oColumnSet.flat[i].editor = null;
9149          }
9150      }
9151  
9152      // Destroy Paginator
9153      this._destroyPaginator();
9154  
9155      // Unhook custom events
9156      this._oRecordSet.unsubscribeAll();
9157      this.unsubscribeAll();
9158  
9159      // Unhook DOM events
9160      Ev.removeListener(document, "click", this._onDocumentClick);
9161      
9162      // Clear out the container
9163      this._destroyContainerEl(this._elContainer);
9164  
9165      // Null out objects
9166      for(var param in this) {
9167          if(lang.hasOwnProperty(this, param)) {
9168              this[param] = null;
9169          }
9170      }
9171      
9172      // Clean up static values
9173      DT._nCurrentCount--;
9174      
9175      if(DT._nCurrentCount < 1) {
9176          if(DT._elDynStyleNode) {
9177              document.getElementsByTagName('head')[0].removeChild(DT._elDynStyleNode);
9178              DT._elDynStyleNode = null;
9179          }
9180      }
9181  
9182  },
9183  
9184  /**
9185   * Displays message within secondary TBODY.
9186   *
9187   * @method showTableMessage
9188   * @param sHTML {HTML} (optional) Value for innerHTML.
9189   * @param sClassName {String} (optional) Classname.
9190   */
9191  showTableMessage : function(sHTML, sClassName) {
9192      var elCell = this._elMsgTd;
9193      if(lang.isString(sHTML)) {
9194          elCell.firstChild.innerHTML = sHTML;
9195      }
9196      if(lang.isString(sClassName)) {
9197          elCell.className = sClassName;
9198      }
9199  
9200      this._elMsgTbody.style.display = "";
9201  
9202      this.fireEvent("tableMsgShowEvent", {html:sHTML, className:sClassName});
9203  },
9204  
9205  /**
9206   * Hides secondary TBODY.
9207   *
9208   * @method hideTableMessage
9209   */
9210  hideTableMessage : function() {
9211      if(this._elMsgTbody.style.display != "none") {
9212          this._elMsgTbody.style.display = "none";
9213          this._elMsgTbody.parentNode.style.width = "";
9214          this.fireEvent("tableMsgHideEvent");
9215      }
9216  },
9217  
9218  /**
9219   * Brings focus to the TBODY element. Alias to focusTbodyEl.
9220   *
9221   * @method focus
9222   */
9223  focus : function() {
9224      this.focusTbodyEl();
9225  },
9226  
9227  /**
9228   * Brings focus to the THEAD element.
9229   *
9230   * @method focusTheadEl
9231   */
9232  focusTheadEl : function() {
9233      this._focusEl(this._elThead);
9234  },
9235  
9236  /**
9237   * Brings focus to the TBODY element.
9238   *
9239   * @method focusTbodyEl
9240   */
9241  focusTbodyEl : function() {
9242      this._focusEl(this._elTbody);
9243  },
9244  
9245  /**
9246   * Setting display:none on DataTable or any parent may impact width validations.
9247   * After setting display back to "", implementers should call this method to 
9248   * manually perform those validations.
9249   *
9250   * @method onShow
9251   */
9252  onShow : function() {
9253      this.validateColumnWidths();
9254      
9255      for(var allKeys = this._oColumnSet.keys, i=0, len=allKeys.length, col; i<len; i++) {
9256          col = allKeys[i];
9257          if(col._ddResizer) {
9258              col._ddResizer.resetResizerEl();
9259          }
9260      }
9261  },
9262  
9263  
9264  
9265  
9266  
9267  
9268  
9269  
9270  
9271  
9272  
9273  
9274  
9275  
9276  
9277  
9278  
9279  
9280  
9281  
9282  
9283  
9284  
9285  
9286  
9287  
9288  
9289  
9290  
9291  
9292  
9293  
9294  
9295  
9296  
9297  
9298  
9299  
9300  
9301  
9302  
9303  
9304  
9305  
9306  
9307  
9308  
9309  
9310  
9311  
9312  
9313  
9314  
9315  
9316  
9317  
9318  
9319  
9320  
9321  
9322  
9323  
9324  
9325  
9326  
9327  
9328  
9329  // RECORDSET FUNCTIONS
9330  
9331  /**
9332   * Returns Record index for given TR element or page row index.
9333   *
9334   * @method getRecordIndex
9335   * @param row {YAHOO.widget.Record | HTMLElement | Number} Record instance, TR
9336   * element reference or page row index.
9337   * @return {Number} Record's RecordSet index, or null.
9338   */
9339  getRecordIndex : function(row) {
9340      var nTrIndex;
9341  
9342      if(!lang.isNumber(row)) {
9343          // By Record
9344          if(row instanceof YAHOO.widget.Record) {
9345              return this._oRecordSet.getRecordIndex(row);
9346          }
9347          // By element reference
9348          else {
9349              // Find the TR element
9350              var el = this.getTrEl(row);
9351              if(el) {
9352                  nTrIndex = el.sectionRowIndex;
9353              }
9354          }
9355      }
9356      // By page row index
9357      else {
9358          nTrIndex = row;
9359      }
9360  
9361      if(lang.isNumber(nTrIndex)) {
9362          var oPaginator = this.get("paginator");
9363          if(oPaginator) {
9364              return oPaginator.get('recordOffset') + nTrIndex;
9365          }
9366          else {
9367              return nTrIndex;
9368          }
9369      }
9370  
9371      return null;
9372  },
9373  
9374  /**
9375   * For the given identifier, returns the associated Record instance.
9376   *
9377   * @method getRecord
9378   * @param row {HTMLElement | Number | String} DOM reference to a TR element (or
9379   * child of a TR element), RecordSet position index, or Record ID.
9380   * @return {YAHOO.widget.Record} Record instance.
9381   */
9382  getRecord : function(row) {
9383      var oRecord = this._oRecordSet.getRecord(row);
9384  
9385      if(!oRecord) {
9386          // Validate TR element
9387          var elRow = this.getTrEl(row);
9388          if(elRow) {
9389              oRecord = this._oRecordSet.getRecord(elRow.id);
9390          }
9391      }
9392  
9393      if(oRecord instanceof YAHOO.widget.Record) {
9394          return this._oRecordSet.getRecord(oRecord);
9395      }
9396      else {
9397          return null;
9398      }
9399  },
9400  
9401  
9402  
9403  
9404  
9405  
9406  
9407  
9408  
9409  
9410  
9411  
9412  
9413  
9414  
9415  
9416  
9417  
9418  
9419  
9420  
9421  
9422  
9423  
9424  
9425  
9426  
9427  
9428  
9429  
9430  
9431  
9432  
9433  
9434  
9435  
9436  
9437  
9438  
9439  
9440  
9441  
9442  
9443  
9444  
9445  
9446  // COLUMN FUNCTIONS
9447  
9448  /**
9449   * For the given identifier, returns the associated Column instance. Note: For
9450   * getting Columns by Column ID string, please use the method getColumnById().
9451   *
9452   * @method getColumn
9453   * @param column {HTMLElement | String | Number} TH/TD element (or child of a
9454   * TH/TD element), a Column key, or a ColumnSet key index.
9455   * @return {YAHOO.widget.Column} Column instance.
9456   */
9457  getColumn : function(column) {
9458      var oColumn = this._oColumnSet.getColumn(column);
9459  
9460      if(!oColumn) {
9461          // Validate TD element
9462          var elCell = this.getTdEl(column);
9463          if(elCell) {
9464              oColumn = this._oColumnSet.getColumn(this.getCellIndex(elCell));
9465          }
9466          // Validate TH element
9467          else {
9468              elCell = this.getThEl(column);
9469              if(elCell) {
9470                  // Find by TH el ID
9471                  var allColumns = this._oColumnSet.flat;
9472                  for(var i=0, len=allColumns.length; i<len; i++) {
9473                      if(allColumns[i].getThEl().id === elCell.id) {
9474                          oColumn = allColumns[i];
9475                      } 
9476                  }
9477              }
9478          }
9479      }
9480      if(!oColumn) {
9481      }
9482      return oColumn;
9483  },
9484  
9485  /**
9486   * For the given Column ID, returns the associated Column instance. Note: For
9487   * getting Columns by key, please use the method getColumn().
9488   *
9489   * @method getColumnById
9490   * @param column {String} Column ID string.
9491   * @return {YAHOO.widget.Column} Column instance.
9492   */
9493  getColumnById : function(column) {
9494      return this._oColumnSet.getColumnById(column);
9495  },
9496  
9497  /**
9498   * For the given Column instance, returns next direction to sort.
9499   *
9500   * @method getColumnSortDir
9501   * @param oColumn {YAHOO.widget.Column} Column instance.
9502   * @param oSortedBy {Object} (optional) Specify the state, or use current state. 
9503   * @return {String} YAHOO.widget.DataTable.CLASS_ASC or YAHOO.widget.DataTableCLASS_DESC.
9504   */
9505  getColumnSortDir : function(oColumn, oSortedBy) {
9506      // Backward compatibility
9507      if(oColumn.sortOptions && oColumn.sortOptions.defaultDir) {
9508          if(oColumn.sortOptions.defaultDir == "asc") {
9509              oColumn.sortOptions.defaultDir = DT.CLASS_ASC;
9510          }
9511          else if (oColumn.sortOptions.defaultDir == "desc") {
9512              oColumn.sortOptions.defaultDir = DT.CLASS_DESC;
9513          }
9514      }
9515      
9516      // What is the Column's default sort direction?
9517      var sortDir = (oColumn.sortOptions && oColumn.sortOptions.defaultDir) ? oColumn.sortOptions.defaultDir : DT.CLASS_ASC;
9518  
9519      // Is the Column currently sorted?
9520      var bSorted = false;
9521      oSortedBy = oSortedBy || this.get("sortedBy");
9522      if(oSortedBy && (oSortedBy.key === oColumn.key)) {
9523          bSorted = true;
9524          if(oSortedBy.dir) {
9525              sortDir = (oSortedBy.dir === DT.CLASS_ASC) ? DT.CLASS_DESC : DT.CLASS_ASC;
9526          }
9527          else {
9528              sortDir = (sortDir === DT.CLASS_ASC) ? DT.CLASS_DESC : DT.CLASS_ASC;
9529          }
9530      }
9531      return sortDir;
9532  },
9533  
9534  /**
9535   * Overridable method gives implementers a hook to show loading message before
9536   * sorting Column.
9537   *
9538   * @method doBeforeSortColumn
9539   * @param oColumn {YAHOO.widget.Column} Column instance.
9540   * @param sSortDir {String} YAHOO.widget.DataTable.CLASS_ASC or
9541   * YAHOO.widget.DataTable.CLASS_DESC.
9542   * @return {Boolean} Return true to continue sorting Column.
9543   */
9544  doBeforeSortColumn : function(oColumn, sSortDir) {
9545      this.showTableMessage(this.get("MSG_LOADING"), DT.CLASS_LOADING);
9546      return true;
9547  },
9548  
9549  /**
9550   * Sorts given Column. If "dynamicData" is true, current selections are purged before
9551   * a request is sent to the DataSource for data for the new state (using the
9552   * request returned by "generateRequest()").
9553   *
9554   * @method sortColumn
9555   * @param oColumn {YAHOO.widget.Column} Column instance.
9556   * @param sDir {String} (Optional) YAHOO.widget.DataTable.CLASS_ASC or
9557   * YAHOO.widget.DataTable.CLASS_DESC
9558   */
9559  sortColumn : function(oColumn, sDir) {
9560      if(oColumn && (oColumn instanceof YAHOO.widget.Column)) {
9561          if(!oColumn.sortable) {
9562              Dom.addClass(this.getThEl(oColumn), DT.CLASS_SORTABLE);
9563          }
9564          
9565          // Validate given direction
9566          if(sDir && (sDir !== DT.CLASS_ASC) && (sDir !== DT.CLASS_DESC)) {
9567              sDir = null;
9568          }
9569          
9570          // Get the sort dir
9571          var sSortDir = sDir || this.getColumnSortDir(oColumn);
9572  
9573          // Is the Column currently sorted?
9574          var oSortedBy = this.get("sortedBy") || {};
9575          var bSorted = (oSortedBy.key === oColumn.key) ? true : false;
9576  
9577          var ok = this.doBeforeSortColumn(oColumn, sSortDir);
9578          if(ok) {
9579              // Server-side sort
9580              if(this.get("dynamicData")) {
9581                  // Get current state
9582                  var oState = this.getState();
9583                  
9584                  // Reset record offset, if paginated
9585                  if(oState.pagination) {
9586                      oState.pagination.recordOffset = 0;
9587                  }
9588                  
9589                  // Update sortedBy to new values
9590                  oState.sortedBy = {
9591                      key: oColumn.key,
9592                      dir: sSortDir
9593                  };
9594                  
9595                  // Get the request for the new state
9596                  var request = this.get("generateRequest")(oState, this);
9597  
9598                  // Purge selections
9599                  this.unselectAllRows();
9600                  this.unselectAllCells();
9601  
9602                  // Send request for new data
9603                  var callback = {
9604                      success : this.onDataReturnSetRows,
9605                      failure : this.onDataReturnSetRows,
9606                      argument : oState, // Pass along the new state to the callback
9607                      scope : this
9608                  };
9609                  this._oDataSource.sendRequest(request, callback);            
9610              }
9611              // Client-side sort
9612              else {
9613                  // Is there a custom sort handler function defined?
9614                  var sortFnc = (oColumn.sortOptions && lang.isFunction(oColumn.sortOptions.sortFunction)) ?
9615                          // Custom sort function
9616                          oColumn.sortOptions.sortFunction : null;
9617                     
9618                  // Sort the Records
9619                  if(!bSorted || sDir || sortFnc) {
9620                      // Default sort function if necessary
9621                      sortFnc = sortFnc || this.get("sortFunction");
9622                      // Get the field to sort
9623                      var sField = (oColumn.sortOptions && oColumn.sortOptions.field) ? oColumn.sortOptions.field : oColumn.field;
9624  
9625                      // Sort the Records        
9626                      this._oRecordSet.sortRecords(sortFnc, ((sSortDir == DT.CLASS_DESC) ? true : false), sField);
9627                  }
9628                  // Just reverse the Records
9629                  else {
9630                      this._oRecordSet.reverseRecords();
9631                  }
9632          
9633                  // Reset to first page if paginated
9634                  var oPaginator = this.get('paginator');
9635                  if (oPaginator) {
9636                      // Set page silently, so as not to fire change event.
9637                      oPaginator.setPage(1,true);
9638                  }
9639          
9640                  // Update UI via sortedBy
9641                  this.render();
9642                  this.set("sortedBy", {key:oColumn.key, dir:sSortDir, column:oColumn}); 
9643              }       
9644              
9645              this.fireEvent("columnSortEvent",{column:oColumn,dir:sSortDir});
9646              return;
9647          }
9648      }
9649  },
9650  
9651  /**
9652   * Sets given Column to given pixel width. If new width is less than minimum
9653   * width, sets to minimum width. Updates oColumn.width value.
9654   *
9655   * @method setColumnWidth
9656   * @param oColumn {YAHOO.widget.Column} Column instance.
9657   * @param nWidth {Number} New width in pixels. A null value auto-sizes Column,
9658   * subject to minWidth and maxAutoWidth validations. 
9659   */
9660  setColumnWidth : function(oColumn, nWidth) {
9661      if(!(oColumn instanceof YAHOO.widget.Column)) {
9662          oColumn = this.getColumn(oColumn);
9663      }
9664      if(oColumn) {
9665          // Validate new width against minimum width
9666          if(lang.isNumber(nWidth)) {
9667              // This is why we must require a Number... :-|
9668              nWidth = (nWidth > oColumn.minWidth) ? nWidth : oColumn.minWidth;
9669  
9670              // Save state
9671              oColumn.width = nWidth;
9672              
9673              // Resize the DOM elements
9674              this._setColumnWidth(oColumn, nWidth+"px");
9675              
9676              this.fireEvent("columnSetWidthEvent",{column:oColumn,width:nWidth});
9677          }
9678          // Unsets a width to auto-size
9679          else if(nWidth === null) {
9680              // Save state
9681              oColumn.width = nWidth;
9682              
9683              // Resize the DOM elements
9684              this._setColumnWidth(oColumn, "auto");
9685              this.validateColumnWidths(oColumn);
9686              this.fireEvent("columnUnsetWidthEvent",{column:oColumn});
9687          }
9688                  
9689          // Bug 2339454: resize then sort misaligment
9690          this._clearTrTemplateEl();
9691      }
9692      else {
9693      }
9694  },
9695  
9696  /**
9697   * Sets liner DIV elements of given Column to given width. When value should be
9698   * auto-calculated to fit content overflow is set to visible, otherwise overflow
9699   * is set to hidden. No validations against minimum width and no updating
9700   * Column.width value.
9701   *
9702   * @method _setColumnWidth
9703   * @param oColumn {YAHOO.widget.Column} Column instance.
9704   * @param sWidth {String} New width value.
9705   * @param sOverflow {String} Should be "hidden" when Column width is explicitly
9706   * being set to a value, but should be "visible" when Column is meant to auto-fit content.  
9707   * @private
9708   */
9709  _setColumnWidth : function(oColumn, sWidth, sOverflow) {
9710      if(oColumn && (oColumn.getKeyIndex() !== null)) {
9711          sOverflow = sOverflow || (((sWidth === '') || (sWidth === 'auto')) ? 'visible' : 'hidden');
9712      
9713          // Dynamic style algorithm
9714          if(!DT._bDynStylesFallback) {
9715              this._setColumnWidthDynStyles(oColumn, sWidth, sOverflow);
9716          }
9717          // Dynamic function algorithm
9718          else {
9719              this._setColumnWidthDynFunction(oColumn, sWidth, sOverflow);
9720          }
9721      }
9722      else {
9723      }
9724  },
9725  
9726  /**
9727   * Updates width of a Column's liner DIV elements by dynamically creating a
9728   * STYLE node and writing and updating CSS style rules to it. If this fails during
9729   * runtime, the fallback method _setColumnWidthDynFunction() will be called.
9730   * Notes: This technique is not performant in IE6. IE7 crashes if DataTable is
9731   * nested within another TABLE element. For these cases, it is recommended to
9732   * use the method _setColumnWidthDynFunction by setting _bDynStylesFallback to TRUE.
9733   *
9734   * @method _setColumnWidthDynStyles
9735   * @param oColumn {YAHOO.widget.Column} Column instance.
9736   * @param sWidth {String} New width value.
9737   * @private
9738   */
9739  _setColumnWidthDynStyles : function(oColumn, sWidth, sOverflow) {
9740      var s = DT._elDynStyleNode,
9741          rule;
9742      
9743      // Create a new STYLE node
9744      if(!s) {
9745          s = document.createElement('style');
9746          s.type = 'text/css';
9747          s = document.getElementsByTagName('head').item(0).appendChild(s);
9748          DT._elDynStyleNode = s;
9749      }
9750      
9751      // We have a STYLE node to update
9752      if(s) {
9753          // Use unique classname for this Column instance as a hook for resizing
9754          var sClassname = "." + this.getId() + "-col-" + oColumn.getSanitizedKey() + " ." + DT.CLASS_LINER;
9755          
9756          // Hide for performance
9757          if(this._elTbody) {
9758              this._elTbody.style.display = 'none';
9759          }
9760          
9761          rule = DT._oDynStyles[sClassname];
9762  
9763          // The Column does not yet have a rule
9764          if(!rule) {
9765              if(s.styleSheet && s.styleSheet.addRule) {
9766                  s.styleSheet.addRule(sClassname,"overflow:"+sOverflow);
9767                  s.styleSheet.addRule(sClassname,'width:'+sWidth);
9768                  rule = s.styleSheet.rules[s.styleSheet.rules.length-1];
9769                  DT._oDynStyles[sClassname] = rule;
9770              }
9771              else if(s.sheet && s.sheet.insertRule) {
9772                  s.sheet.insertRule(sClassname+" {overflow:"+sOverflow+";width:"+sWidth+";}",s.sheet.cssRules.length);
9773                  rule = s.sheet.cssRules[s.sheet.cssRules.length-1];
9774                  DT._oDynStyles[sClassname] = rule;
9775              }
9776          }
9777          // We have a rule to update
9778          else {
9779              rule.style.overflow = sOverflow;
9780              rule.style.width = sWidth;
9781          } 
9782          
9783          // Unhide
9784          if(this._elTbody) {
9785              this._elTbody.style.display = '';
9786          }
9787      }
9788      
9789      // That was not a success, we must call the fallback routine
9790      if(!rule) {
9791          DT._bDynStylesFallback = true;
9792          this._setColumnWidthDynFunction(oColumn, sWidth);
9793      }
9794  },
9795  
9796  /**
9797   * Updates width of a Column's liner DIV elements by dynamically creating a
9798   * function to update all element style properties in one pass. Note: This
9799   * technique is not supported in sandboxed environments that prohibit EVALs.    
9800   *
9801   * @method _setColumnWidthDynFunction
9802   * @param oColumn {YAHOO.widget.Column} Column instance.
9803   * @param sWidth {String} New width value.
9804   * @private
9805   */
9806  _setColumnWidthDynFunction : function(oColumn, sWidth, sOverflow) {
9807      // TODO: why is this here?
9808      if(sWidth == 'auto') {
9809          sWidth = ''; 
9810      }
9811      
9812      // Create one function for each value of rows.length
9813      var rowslen = this._elTbody ? this._elTbody.rows.length : 0;
9814      
9815      // Dynamically create the function
9816      if (!this._aDynFunctions[rowslen]) {
9817          
9818          //Compile a custom function to do all the liner div width
9819          //assignments at the same time.  A unique function is required
9820          //for each unique number of rows in _elTbody.  This will
9821          //result in a function declaration like:
9822          //function (oColumn,sWidth,sOverflow) {
9823          //    var colIdx = oColumn.getKeyIndex();
9824          //    oColumn.getThLinerEl().style.overflow =
9825          //    this._elTbody.rows[0].cells[colIdx].firstChild.style.overflow =
9826          //    this._elTbody.rows[1].cells[colIdx].firstChild.style.overflow =
9827          //    ... (for all row indices in this._elTbody.rows.length - 1)
9828          //    this._elTbody.rows[99].cells[colIdx].firstChild.style.overflow =
9829          //    sOverflow;
9830          //    oColumn.getThLinerEl().style.width =
9831          //    this._elTbody.rows[0].cells[colIdx].firstChild.style.width =
9832          //    this._elTbody.rows[1].cells[colIdx].firstChild.style.width =
9833          //    ... (for all row indices in this._elTbody.rows.length - 1)
9834          //    this._elTbody.rows[99].cells[colIdx].firstChild.style.width =
9835          //    sWidth;
9836          //}
9837          
9838          var i,j,k;
9839          var resizerDef = [
9840              'var colIdx=oColumn.getKeyIndex();',
9841              'oColumn.getThLinerEl().style.overflow='
9842          ];
9843          for (i=rowslen-1, j=2; i >= 0; --i) {
9844              resizerDef[j++] = 'this._elTbody.rows[';
9845              resizerDef[j++] = i;
9846              resizerDef[j++] = '].cells[colIdx].firstChild.style.overflow=';
9847          }
9848          resizerDef[j] = 'sOverflow;';
9849          resizerDef[j+1] = 'oColumn.getThLinerEl().style.width=';
9850          for (i=rowslen-1, k=j+2; i >= 0; --i) {
9851              resizerDef[k++] = 'this._elTbody.rows[';
9852              resizerDef[k++] = i;
9853              resizerDef[k++] = '].cells[colIdx].firstChild.style.width=';
9854          }
9855          resizerDef[k] = 'sWidth;';
9856          this._aDynFunctions[rowslen] =
9857              new Function('oColumn','sWidth','sOverflow',resizerDef.join(''));
9858      }
9859      
9860      // Get the function to execute
9861      var resizerFn = this._aDynFunctions[rowslen];
9862  
9863      // TODO: Hide TBODY for performance in _setColumnWidthDynFunction?
9864      if (resizerFn) {
9865          resizerFn.call(this,oColumn,sWidth,sOverflow);
9866      }
9867  },
9868  
9869  /**
9870   * For one or all Columns, when Column is not hidden, width is not set, and minWidth
9871   * and/or maxAutoWidth is set, validates auto-width against minWidth and maxAutoWidth.
9872   *
9873   * @method validateColumnWidths
9874   * @param oArg.column {YAHOO.widget.Column} (optional) One Column to validate. If null, all Columns' widths are validated.
9875   */
9876  validateColumnWidths : function(oColumn) {
9877      var elColgroup = this._elColgroup;
9878      var elColgroupClone = elColgroup.cloneNode(true);
9879      var bNeedsValidation = false;
9880      var allKeys = this._oColumnSet.keys;
9881      var elThLiner;
9882      // Validate just one Column's minWidth and/or maxAutoWidth
9883      if(oColumn && !oColumn.hidden && !oColumn.width && (oColumn.getKeyIndex() !== null)) {
9884              elThLiner = oColumn.getThLinerEl();
9885              if((oColumn.minWidth > 0) && (elThLiner.offsetWidth < oColumn.minWidth)) {
9886                  elColgroupClone.childNodes[oColumn.getKeyIndex()].style.width = 
9887                          oColumn.minWidth + 
9888                          (parseInt(Dom.getStyle(elThLiner,"paddingLeft"),10)|0) +
9889                          (parseInt(Dom.getStyle(elThLiner,"paddingRight"),10)|0) + "px";
9890                  bNeedsValidation = true;
9891              }
9892              else if((oColumn.maxAutoWidth > 0) && (elThLiner.offsetWidth > oColumn.maxAutoWidth)) {
9893                  this._setColumnWidth(oColumn, oColumn.maxAutoWidth+"px", "hidden");
9894              }
9895      }
9896      // Validate all Columns
9897      else {
9898          for(var i=0, len=allKeys.length; i<len; i++) {
9899              oColumn = allKeys[i];
9900              if(!oColumn.hidden && !oColumn.width) {
9901                  elThLiner = oColumn.getThLinerEl();
9902                  if((oColumn.minWidth > 0) && (elThLiner.offsetWidth < oColumn.minWidth)) {
9903                      elColgroupClone.childNodes[i].style.width = 
9904                              oColumn.minWidth + 
9905                              (parseInt(Dom.getStyle(elThLiner,"paddingLeft"),10)|0) +
9906                              (parseInt(Dom.getStyle(elThLiner,"paddingRight"),10)|0) + "px";
9907                      bNeedsValidation = true;
9908                  }
9909                  else if((oColumn.maxAutoWidth > 0) && (elThLiner.offsetWidth > oColumn.maxAutoWidth)) {
9910                      this._setColumnWidth(oColumn, oColumn.maxAutoWidth+"px", "hidden");
9911                  }
9912              }
9913          }
9914      }
9915      if(bNeedsValidation) {
9916          elColgroup.parentNode.replaceChild(elColgroupClone, elColgroup);
9917          this._elColgroup = elColgroupClone;
9918      }
9919  },
9920  
9921  /**
9922   * Clears minWidth.
9923   *
9924   * @method _clearMinWidth
9925   * @param oColumn {YAHOO.widget.Column} Which Column.
9926   * @private
9927   */
9928  _clearMinWidth : function(oColumn) {
9929      if(oColumn.getKeyIndex() !== null) {
9930          this._elColgroup.childNodes[oColumn.getKeyIndex()].style.width = '';
9931      }
9932  },
9933  
9934  /**
9935   * Restores minWidth.
9936   *
9937   * @method _restoreMinWidth
9938   * @param oColumn {YAHOO.widget.Column} Which Column.
9939   * @private
9940   */
9941  _restoreMinWidth : function(oColumn) {
9942      if(oColumn.minWidth && (oColumn.getKeyIndex() !== null)) {
9943          this._elColgroup.childNodes[oColumn.getKeyIndex()].style.width = oColumn.minWidth + 'px';
9944      }
9945  },
9946  
9947  /**
9948   * Hides given Column. NOTE: You cannot hide/show nested Columns. You can only
9949   * hide/show non-nested Columns, and top-level parent Columns (which will
9950   * hide/show all children Columns).
9951   *
9952   * @method hideColumn
9953   * @param oColumn {YAHOO.widget.Column | HTMLElement | String | Number} Column
9954   * instance, TH/TD element (or child of a TH/TD element), a Column key, or a
9955   * ColumnSet key index.
9956   */
9957  hideColumn : function(oColumn) {
9958      if(!(oColumn instanceof YAHOO.widget.Column)) {
9959          oColumn = this.getColumn(oColumn);
9960      }
9961      // Only top-level Columns can get hidden due to issues in FF2 and SF3
9962      if(oColumn && !oColumn.hidden && oColumn.getTreeIndex() !== null) {
9963          
9964          var allrows = this.getTbodyEl().rows;
9965          var l = allrows.length;
9966          var allDescendants = this._oColumnSet.getDescendants(oColumn);
9967          
9968          // Hide each nested Column
9969          for(var i=0, len=allDescendants.length; i<len; i++) {
9970              var thisColumn = allDescendants[i];
9971              thisColumn.hidden = true;
9972  
9973              // Style the head cell
9974              Dom.addClass(thisColumn.getThEl(), DT.CLASS_HIDDEN);
9975              
9976              // Does this Column have body cells?
9977              var thisKeyIndex = thisColumn.getKeyIndex();
9978              if(thisKeyIndex !== null) {                    
9979                  // Clear minWidth
9980                  this._clearMinWidth(oColumn);
9981                  
9982                  // Style the body cells
9983                  for(var j=0;j<l;j++) {
9984                      Dom.addClass(allrows[j].cells[thisKeyIndex],DT.CLASS_HIDDEN);
9985                  }
9986              }
9987              
9988              this.fireEvent("columnHideEvent",{column:thisColumn});
9989          }
9990        
9991          this._repaintOpera();
9992          this._clearTrTemplateEl();
9993      }
9994      else {
9995      }
9996  },
9997  
9998  /**
9999   * Shows given Column. NOTE: You cannot hide/show nested Columns. You can only
10000   * hide/show non-nested Columns, and top-level parent Columns (which will
10001   * hide/show all children Columns).
10002   *
10003   * @method showColumn
10004   * @param oColumn {YAHOO.widget.Column | HTMLElement | String | Number} Column
10005   * instance, TH/TD element (or child of a TH/TD element), a Column key, or a
10006   * ColumnSet key index.
10007   */
10008  showColumn : function(oColumn) {
10009      if(!(oColumn instanceof YAHOO.widget.Column)) {
10010          oColumn = this.getColumn(oColumn);
10011      }
10012      // Only top-level Columns can get hidden
10013      if(oColumn && oColumn.hidden && (oColumn.getTreeIndex() !== null)) {
10014          var allrows = this.getTbodyEl().rows;
10015          var l = allrows.length;
10016          var allDescendants = this._oColumnSet.getDescendants(oColumn);
10017          
10018          // Show each nested Column
10019          for(var i=0, len=allDescendants.length; i<len; i++) {
10020              var thisColumn = allDescendants[i];
10021              thisColumn.hidden = false;
10022              
10023              // Unstyle the head cell
10024              Dom.removeClass(thisColumn.getThEl(), DT.CLASS_HIDDEN);
10025  
10026              // Does this Column have body cells?
10027              var thisKeyIndex = thisColumn.getKeyIndex();
10028              if(thisKeyIndex !== null) {
10029                  // Restore minWidth
10030                  this._restoreMinWidth(oColumn);
10031                  
10032              
10033                  // Unstyle the body cells
10034                  for(var j=0;j<l;j++) {
10035                      Dom.removeClass(allrows[j].cells[thisKeyIndex],DT.CLASS_HIDDEN);
10036                  }
10037              }
10038  
10039              this.fireEvent("columnShowEvent",{column:thisColumn});
10040          }
10041          this._clearTrTemplateEl();
10042      }
10043      else {
10044      }
10045  },
10046  
10047  /**
10048   * Removes given Column. NOTE: You cannot remove nested Columns. You can only remove
10049   * non-nested Columns, and top-level parent Columns (which will remove all
10050   * children Columns).
10051   *
10052   * @method removeColumn
10053   * @param oColumn {YAHOO.widget.Column} Column instance.
10054   * @return oColumn {YAHOO.widget.Column} Removed Column instance.
10055   */
10056  removeColumn : function(oColumn) {
10057      // Validate Column
10058      if(!(oColumn instanceof YAHOO.widget.Column)) {
10059          oColumn = this.getColumn(oColumn);
10060      }
10061      if(oColumn) {
10062          var nColTreeIndex = oColumn.getTreeIndex();
10063          if(nColTreeIndex !== null) {
10064              // Which key index(es)
10065              var i, len,
10066                  aKeyIndexes = oColumn.getKeyIndex();
10067              // Must be a parent Column
10068              if(aKeyIndexes === null) {
10069                  var descKeyIndexes = [];
10070                  var allDescendants = this._oColumnSet.getDescendants(oColumn);
10071                  for(i=0, len=allDescendants.length; i<len; i++) {
10072                      // Is this descendant a key Column?
10073                      var thisKey = allDescendants[i].getKeyIndex();
10074                      if(thisKey !== null) {
10075                          descKeyIndexes[descKeyIndexes.length] = thisKey;
10076                      }
10077                  }
10078                  if(descKeyIndexes.length > 0) {
10079                      aKeyIndexes = descKeyIndexes;
10080                  }
10081              }
10082              // Must be a key Column
10083              else {
10084                  aKeyIndexes = [aKeyIndexes];
10085              }
10086              
10087              if(aKeyIndexes !== null) {
10088                  // Sort the indexes so we can remove from the right
10089                  aKeyIndexes.sort(function(a, b) {return YAHOO.util.Sort.compare(a, b);});
10090                  
10091                  // Destroy previous THEAD
10092                  this._destroyTheadEl();
10093      
10094                  // Create new THEAD
10095                  var aOrigColumnDefs = this._oColumnSet.getDefinitions();
10096                  oColumn = aOrigColumnDefs.splice(nColTreeIndex,1)[0];
10097                  this._initColumnSet(aOrigColumnDefs);
10098                  this._initTheadEl();
10099                  
10100                  // Remove COL
10101                  for(i=aKeyIndexes.length-1; i>-1; i--) {
10102                      this._removeColgroupColEl(aKeyIndexes[i]);
10103                  }
10104                  
10105                  // Remove TD
10106                  var allRows = this._elTbody.rows;
10107                  if(allRows.length > 0) {
10108                      var loopN = this.get("renderLoopSize"),
10109                          loopEnd = allRows.length;
10110                      this._oChainRender.add({
10111                          method: function(oArg) {
10112                              if((this instanceof DT) && this._sId) {
10113                                  var i = oArg.nCurrentRow,
10114                                      len = loopN > 0 ? Math.min(i + loopN,allRows.length) : allRows.length,
10115                                      aIndexes = oArg.aIndexes,
10116                                      j;
10117                                  for(; i < len; ++i) {
10118                                      for(j = aIndexes.length-1; j>-1; j--) {
10119                                          allRows[i].removeChild(allRows[i].childNodes[aIndexes[j]]);
10120                                      }
10121                                  }
10122                                  oArg.nCurrentRow = i;
10123                              }
10124                          },
10125                          iterations: (loopN > 0) ? Math.ceil(loopEnd/loopN) : 1,
10126                          argument: {nCurrentRow:0, aIndexes:aKeyIndexes},
10127                          scope: this,
10128                          timeout: (loopN > 0) ? 0 : -1
10129                      });
10130                      this._runRenderChain();
10131                  }
10132          
10133                  this.fireEvent("columnRemoveEvent",{column:oColumn});
10134                  return oColumn;
10135              }
10136          }
10137      }
10138  },
10139  
10140  /**
10141   * Inserts given Column at the index if given, otherwise at the end. NOTE: You
10142   * can only add non-nested Columns and top-level parent Columns. You cannot add
10143   * a nested Column to an existing parent.
10144   *
10145   * @method insertColumn
10146   * @param oColumn {Object | YAHOO.widget.Column} Object literal Column
10147   * definition or a Column instance.
10148   * @param index {Number} (optional) New tree index.
10149   * @return oColumn {YAHOO.widget.Column} Inserted Column instance. 
10150   */
10151  insertColumn : function(oColumn, index) {
10152      // Validate Column
10153      if(oColumn instanceof YAHOO.widget.Column) {
10154          oColumn = oColumn.getDefinition();
10155      }
10156      else if(oColumn.constructor !== Object) {
10157          return;
10158      }
10159      
10160      // Validate index or append new Column to the end of the ColumnSet
10161      var oColumnSet = this._oColumnSet;
10162      if(!lang.isValue(index) || !lang.isNumber(index)) {
10163          index = oColumnSet.tree[0].length;
10164      }
10165      
10166      // Destroy previous THEAD
10167      this._destroyTheadEl();
10168      
10169      // Create new THEAD
10170      var aNewColumnDefs = this._oColumnSet.getDefinitions();
10171      aNewColumnDefs.splice(index, 0, oColumn);
10172      this._initColumnSet(aNewColumnDefs);
10173      this._initTheadEl();
10174      
10175      // Need to refresh the reference
10176      oColumnSet = this._oColumnSet;
10177      var oNewColumn = oColumnSet.tree[0][index];
10178      
10179      // Get key index(es) for new Column
10180      var i, len,
10181          descKeyIndexes = [];
10182      var allDescendants = oColumnSet.getDescendants(oNewColumn);
10183      for(i=0, len=allDescendants.length; i<len; i++) {
10184          // Is this descendant a key Column?
10185          var thisKey = allDescendants[i].getKeyIndex();
10186          if(thisKey !== null) {
10187              descKeyIndexes[descKeyIndexes.length] = thisKey;
10188          }
10189      }
10190      
10191      if(descKeyIndexes.length > 0) {  
10192          // Sort the indexes
10193          var newIndex = descKeyIndexes.sort(function(a, b) {return YAHOO.util.Sort.compare(a, b);})[0];
10194          
10195          // Add COL
10196          for(i=descKeyIndexes.length-1; i>-1; i--) {
10197              this._insertColgroupColEl(descKeyIndexes[i]);
10198          }
10199              
10200          // Add TD
10201          var allRows = this._elTbody.rows;
10202          if(allRows.length > 0) {
10203              var loopN = this.get("renderLoopSize"),
10204                  loopEnd = allRows.length;
10205              
10206              // Get templates for each new TD
10207              var aTdTemplates = [],
10208                  elTdTemplate;
10209              for(i=0, len=descKeyIndexes.length; i<len; i++) {
10210                  var thisKeyIndex = descKeyIndexes[i];
10211                  elTdTemplate = this._getTrTemplateEl().childNodes[i].cloneNode(true);
10212                  elTdTemplate = this._formatTdEl(this._oColumnSet.keys[thisKeyIndex], elTdTemplate, thisKeyIndex, (thisKeyIndex===this._oColumnSet.keys.length-1));
10213                  aTdTemplates[thisKeyIndex] = elTdTemplate;
10214              }
10215              
10216              this._oChainRender.add({
10217                  method: function(oArg) {
10218                      if((this instanceof DT) && this._sId) {
10219                          var i = oArg.nCurrentRow, j,
10220                              descKeyIndexes = oArg.descKeyIndexes,
10221                              len = loopN > 0 ? Math.min(i + loopN,allRows.length) : allRows.length,
10222                              nextSibling;
10223                          for(; i < len; ++i) {
10224                              nextSibling = allRows[i].childNodes[newIndex] || null;
10225                              for(j=descKeyIndexes.length-1; j>-1; j--) {
10226                                  allRows[i].insertBefore(oArg.aTdTemplates[descKeyIndexes[j]].cloneNode(true), nextSibling);
10227                              }
10228                          }
10229                          oArg.nCurrentRow = i;
10230                      }
10231                  },
10232                  iterations: (loopN > 0) ? Math.ceil(loopEnd/loopN) : 1,
10233                  argument: {nCurrentRow:0,aTdTemplates:aTdTemplates,descKeyIndexes:descKeyIndexes},
10234                  scope: this,
10235                  timeout: (loopN > 0) ? 0 : -1
10236              });
10237              this._runRenderChain(); 
10238          }
10239  
10240          this.fireEvent("columnInsertEvent",{column:oColumn,index:index});
10241          return oNewColumn;
10242      }
10243  },
10244  
10245  /**
10246   * Removes given Column and inserts into given tree index. NOTE: You
10247   * can only reorder non-nested Columns and top-level parent Columns. You cannot
10248   * reorder a nested Column to an existing parent.
10249   *
10250   * @method reorderColumn
10251   * @param oColumn {YAHOO.widget.Column} Column instance.
10252   * @param index {Number} New tree index.
10253   * @return oColumn {YAHOO.widget.Column} Reordered Column instance. 
10254   */
10255  reorderColumn : function(oColumn, index) {
10256      // Validate Column and new index
10257      if(!(oColumn instanceof YAHOO.widget.Column)) {
10258          oColumn = this.getColumn(oColumn);
10259      }
10260      if(oColumn && YAHOO.lang.isNumber(index)) {
10261          var nOrigTreeIndex = oColumn.getTreeIndex();
10262          if((nOrigTreeIndex !== null) && (nOrigTreeIndex !== index)) {
10263              // Which key index(es)
10264              var i, len,
10265                  aOrigKeyIndexes = oColumn.getKeyIndex(),
10266                  allDescendants,
10267                  descKeyIndexes = [],
10268                  thisKey;
10269              // Must be a parent Column...
10270              if(aOrigKeyIndexes === null) {
10271                  allDescendants = this._oColumnSet.getDescendants(oColumn);
10272                  for(i=0, len=allDescendants.length; i<len; i++) {
10273                      // Is this descendant a key Column?
10274                      thisKey = allDescendants[i].getKeyIndex();
10275                      if(thisKey !== null) {
10276                          descKeyIndexes[descKeyIndexes.length] = thisKey;
10277                      }
10278                  }
10279                  if(descKeyIndexes.length > 0) {
10280                      aOrigKeyIndexes = descKeyIndexes;
10281                  }
10282              }
10283              // ...or else must be a key Column
10284              else {
10285                  aOrigKeyIndexes = [aOrigKeyIndexes];
10286              }
10287              
10288              if(aOrigKeyIndexes !== null) {                   
10289                  // Sort the indexes
10290                  aOrigKeyIndexes.sort(function(a, b) {return YAHOO.util.Sort.compare(a, b);});
10291                  
10292                  // Destroy previous THEAD
10293                  this._destroyTheadEl();
10294      
10295                  // Create new THEAD
10296                  var aColumnDefs = this._oColumnSet.getDefinitions();
10297                  var oColumnDef = aColumnDefs.splice(nOrigTreeIndex,1)[0];
10298                  aColumnDefs.splice(index, 0, oColumnDef);
10299                  this._initColumnSet(aColumnDefs);
10300                  this._initTheadEl();
10301                  
10302                  // Need to refresh the reference
10303                  var oNewColumn = this._oColumnSet.tree[0][index];
10304  
10305                  // What are new key index(es)
10306                  var aNewKeyIndexes = oNewColumn.getKeyIndex();
10307                  // Must be a parent Column
10308                  if(aNewKeyIndexes === null) {
10309                      descKeyIndexes = [];
10310                      allDescendants = this._oColumnSet.getDescendants(oNewColumn);
10311                      for(i=0, len=allDescendants.length; i<len; i++) {
10312                          // Is this descendant a key Column?
10313                          thisKey = allDescendants[i].getKeyIndex();
10314                          if(thisKey !== null) {
10315                              descKeyIndexes[descKeyIndexes.length] = thisKey;
10316                          }
10317                      }
10318                      if(descKeyIndexes.length > 0) {
10319                          aNewKeyIndexes = descKeyIndexes;
10320                      }
10321                  }
10322                  // Must be a key Column
10323                  else {
10324                      aNewKeyIndexes = [aNewKeyIndexes];
10325                  }
10326                  
10327                  // Sort the new indexes and grab the first one for the new location
10328                  var newIndex = aNewKeyIndexes.sort(function(a, b) {return YAHOO.util.Sort.compare(a, b);})[0];
10329  
10330                  // Reorder COL
10331                  this._reorderColgroupColEl(aOrigKeyIndexes, newIndex);
10332                  
10333                  // Reorder TD
10334                  var allRows = this._elTbody.rows;
10335                  if(allRows.length > 0) {
10336                      var loopN = this.get("renderLoopSize"),
10337                          loopEnd = allRows.length;
10338                      this._oChainRender.add({
10339                          method: function(oArg) {
10340                              if((this instanceof DT) && this._sId) {
10341                                  var i = oArg.nCurrentRow, j, tmpTds, nextSibling,
10342                                      len = loopN > 0 ? Math.min(i + loopN,allRows.length) : allRows.length,
10343                                      aIndexes = oArg.aIndexes, thisTr;
10344                                  // For each row
10345                                  for(; i < len; ++i) {
10346                                      tmpTds = [];
10347                                      thisTr = allRows[i];
10348                                      
10349                                      // Remove each TD
10350                                      for(j=aIndexes.length-1; j>-1; j--) {
10351                                          tmpTds.push(thisTr.removeChild(thisTr.childNodes[aIndexes[j]]));
10352                                      }
10353                                      
10354                                      // Insert each TD
10355                                      nextSibling = thisTr.childNodes[newIndex] || null;
10356                                      for(j=tmpTds.length-1; j>-1; j--) {
10357                                          thisTr.insertBefore(tmpTds[j], nextSibling);
10358                                      }                                    
10359                                  }
10360                                  oArg.nCurrentRow = i;
10361                              }
10362                          },
10363                          iterations: (loopN > 0) ? Math.ceil(loopEnd/loopN) : 1,
10364                          argument: {nCurrentRow:0, aIndexes:aOrigKeyIndexes},
10365                          scope: this,
10366                          timeout: (loopN > 0) ? 0 : -1
10367                      });
10368                      this._runRenderChain();
10369                  }
10370          
10371                  this.fireEvent("columnReorderEvent",{column:oNewColumn, oldIndex:nOrigTreeIndex});
10372                  return oNewColumn;
10373              }
10374          }
10375      }
10376  },
10377  
10378  /**
10379   * Selects given Column. NOTE: You cannot select/unselect nested Columns. You can only
10380   * select/unselect non-nested Columns, and bottom-level key Columns.
10381   *
10382   * @method selectColumn
10383   * @param column {HTMLElement | String | Number} DOM reference or ID string to a
10384   * TH/TD element (or child of a TH/TD element), a Column key, or a ColumnSet key index.
10385   */
10386  selectColumn : function(oColumn) {
10387      oColumn = this.getColumn(oColumn);
10388      if(oColumn && !oColumn.selected) {
10389          // Only bottom-level Columns can get hidden
10390          if(oColumn.getKeyIndex() !== null) {
10391              oColumn.selected = true;
10392              
10393              // Update head cell
10394              var elTh = oColumn.getThEl();
10395              Dom.addClass(elTh,DT.CLASS_SELECTED);
10396  
10397              // Update body cells
10398              var allRows = this.getTbodyEl().rows;
10399              var oChainRender = this._oChainRender;
10400              oChainRender.add({
10401                  method: function(oArg) {
10402                      if((this instanceof DT) && this._sId && allRows[oArg.rowIndex] && allRows[oArg.rowIndex].cells[oArg.cellIndex]) {
10403                          Dom.addClass(allRows[oArg.rowIndex].cells[oArg.cellIndex],DT.CLASS_SELECTED);                    
10404                      }
10405                      oArg.rowIndex++;
10406                  },
10407                  scope: this,
10408                  iterations: allRows.length,
10409                  argument: {rowIndex:0,cellIndex:oColumn.getKeyIndex()}
10410              });
10411  
10412              this._clearTrTemplateEl();
10413              
10414              this._elTbody.style.display = "none";
10415              this._runRenderChain();
10416              this._elTbody.style.display = "";      
10417              
10418              this.fireEvent("columnSelectEvent",{column:oColumn});
10419          }
10420          else {
10421          }
10422      }
10423  },
10424  
10425  /**
10426   * Unselects given Column. NOTE: You cannot select/unselect nested Columns. You can only
10427   * select/unselect non-nested Columns, and bottom-level key Columns.
10428   *
10429   * @method unselectColumn
10430   * @param column {HTMLElement | String | Number} DOM reference or ID string to a
10431   * TH/TD element (or child of a TH/TD element), a Column key, or a ColumnSet key index.
10432   */
10433  unselectColumn : function(oColumn) {
10434      oColumn = this.getColumn(oColumn);
10435      if(oColumn && oColumn.selected) {
10436          // Only bottom-level Columns can get hidden
10437          if(oColumn.getKeyIndex() !== null) {
10438              oColumn.selected = false;
10439              
10440              // Update head cell
10441              var elTh = oColumn.getThEl();
10442              Dom.removeClass(elTh,DT.CLASS_SELECTED);
10443  
10444              // Update body cells
10445              var allRows = this.getTbodyEl().rows;
10446              var oChainRender = this._oChainRender;
10447              oChainRender.add({
10448                  method: function(oArg) {
10449                      if((this instanceof DT) && this._sId && allRows[oArg.rowIndex] && allRows[oArg.rowIndex].cells[oArg.cellIndex]) {
10450                          Dom.removeClass(allRows[oArg.rowIndex].cells[oArg.cellIndex],DT.CLASS_SELECTED); 
10451                      }                   
10452                      oArg.rowIndex++;
10453                  },
10454                  scope: this,
10455                  iterations:allRows.length,
10456                  argument: {rowIndex:0,cellIndex:oColumn.getKeyIndex()}
10457              });
10458              
10459              this._clearTrTemplateEl();
10460  
10461              this._elTbody.style.display = "none";
10462              this._runRenderChain();
10463              this._elTbody.style.display = "";      
10464              
10465              this.fireEvent("columnUnselectEvent",{column:oColumn});
10466          }
10467          else {
10468          }
10469      }
10470  },
10471  
10472  /**
10473   * Returns an array selected Column instances.
10474   *
10475   * @method getSelectedColumns
10476   * @return {YAHOO.widget.Column[]} Array of Column instances.
10477   */
10478  getSelectedColumns : function(oColumn) {
10479      var selectedColumns = [];
10480      var aKeys = this._oColumnSet.keys;
10481      for(var i=0,len=aKeys.length; i<len; i++) {
10482          if(aKeys[i].selected) {
10483              selectedColumns[selectedColumns.length] = aKeys[i];
10484          }
10485      }
10486      return selectedColumns;
10487  },
10488  
10489  /**
10490   * Assigns the class YAHOO.widget.DataTable.CLASS_HIGHLIGHTED to cells of the given Column.
10491   * NOTE: You cannot highlight/unhighlight nested Columns. You can only
10492   * highlight/unhighlight non-nested Columns, and bottom-level key Columns.
10493   *
10494   * @method highlightColumn
10495   * @param column {HTMLElement | String | Number} DOM reference or ID string to a
10496   * TH/TD element (or child of a TH/TD element), a Column key, or a ColumnSet key index.
10497   */
10498  highlightColumn : function(column) {
10499      var oColumn = this.getColumn(column);
10500      // Only bottom-level Columns can get highlighted
10501      if(oColumn && (oColumn.getKeyIndex() !== null)) {            
10502          // Update head cell
10503          var elTh = oColumn.getThEl();
10504          Dom.addClass(elTh,DT.CLASS_HIGHLIGHTED);
10505  
10506          // Update body cells
10507          var allRows = this.getTbodyEl().rows;
10508          var oChainRender = this._oChainRender;
10509          oChainRender.add({
10510              method: function(oArg) {
10511                  if((this instanceof DT) && this._sId && allRows[oArg.rowIndex] && allRows[oArg.rowIndex].cells[oArg.cellIndex]) {
10512                      Dom.addClass(allRows[oArg.rowIndex].cells[oArg.cellIndex],DT.CLASS_HIGHLIGHTED);   
10513                  }                 
10514                  oArg.rowIndex++;
10515              },
10516              scope: this,
10517              iterations:allRows.length,
10518              argument: {rowIndex:0,cellIndex:oColumn.getKeyIndex()},
10519              timeout: -1
10520          });
10521          this._elTbody.style.display = "none";
10522          this._runRenderChain();
10523          this._elTbody.style.display = "";      
10524              
10525          this.fireEvent("columnHighlightEvent",{column:oColumn});
10526      }
10527      else {
10528      }
10529  },
10530  
10531  /**
10532   * Removes the class YAHOO.widget.DataTable.CLASS_HIGHLIGHTED to cells of the given Column.
10533   * NOTE: You cannot highlight/unhighlight nested Columns. You can only
10534   * highlight/unhighlight non-nested Columns, and bottom-level key Columns.
10535   *
10536   * @method unhighlightColumn
10537   * @param column {HTMLElement | String | Number} DOM reference or ID string to a
10538   * TH/TD element (or child of a TH/TD element), a Column key, or a ColumnSet key index.
10539   */
10540  unhighlightColumn : function(column) {
10541      var oColumn = this.getColumn(column);
10542      // Only bottom-level Columns can get highlighted
10543      if(oColumn && (oColumn.getKeyIndex() !== null)) {
10544          // Update head cell
10545          var elTh = oColumn.getThEl();
10546          Dom.removeClass(elTh,DT.CLASS_HIGHLIGHTED);
10547  
10548          // Update body cells
10549          var allRows = this.getTbodyEl().rows;
10550          var oChainRender = this._oChainRender;
10551          oChainRender.add({
10552              method: function(oArg) {
10553                  if((this instanceof DT) && this._sId && allRows[oArg.rowIndex] && allRows[oArg.rowIndex].cells[oArg.cellIndex]) {
10554                      Dom.removeClass(allRows[oArg.rowIndex].cells[oArg.cellIndex],DT.CLASS_HIGHLIGHTED);
10555                  }                 
10556                  oArg.rowIndex++;
10557              },
10558              scope: this,
10559              iterations:allRows.length,
10560              argument: {rowIndex:0,cellIndex:oColumn.getKeyIndex()},
10561              timeout: -1
10562          });
10563          this._elTbody.style.display = "none";
10564          this._runRenderChain();
10565          this._elTbody.style.display = "";     
10566              
10567          this.fireEvent("columnUnhighlightEvent",{column:oColumn});
10568      }
10569      else {
10570      }
10571  },
10572  
10573  
10574  
10575  
10576  
10577  
10578  
10579  
10580  
10581  
10582  
10583  
10584  
10585  
10586  
10587  
10588  
10589  
10590  
10591  
10592  
10593  
10594  
10595  
10596  
10597  
10598  
10599  
10600  
10601  
10602  
10603  
10604  
10605  
10606  
10607  
10608  
10609  
10610  
10611  
10612  
10613  
10614  
10615  
10616  // ROW FUNCTIONS
10617  
10618  /**
10619   * Adds one new Record of data into the RecordSet at the index if given,
10620   * otherwise at the end. If the new Record is in page view, the
10621   * corresponding DOM elements are also updated.
10622   *
10623   * @method addRow
10624   * @param oData {Object} Object literal of data for the row.
10625   * @param index {Number} (optional) RecordSet position index at which to add data.
10626   */
10627  addRow : function(oData, index) {
10628      if(lang.isNumber(index) && (index < 0 || index > this._oRecordSet.getLength())) {
10629          return;
10630      }
10631  
10632      if(oData && lang.isObject(oData)) {
10633          var oRecord = this._oRecordSet.addRecord(oData, index);
10634          if(oRecord) {
10635              var recIndex;
10636              var oPaginator = this.get('paginator');
10637  
10638              // Paginated
10639              if (oPaginator) {     
10640                  // Update the paginator's totalRecords
10641                  var totalRecords = oPaginator.get('totalRecords');
10642                  if (totalRecords !== widget.Paginator.VALUE_UNLIMITED) {
10643                      oPaginator.set('totalRecords',totalRecords + 1);
10644                  }
10645  
10646                  recIndex = this.getRecordIndex(oRecord);
10647                  var endRecIndex = (oPaginator.getPageRecords())[1];
10648  
10649                  // New record affects the view
10650                  if (recIndex <= endRecIndex) {
10651                      // Defer UI updates to the render method
10652                      this.render();
10653                  }
10654                  
10655                  this.fireEvent("rowAddEvent", {record:oRecord});
10656                  return;
10657              }
10658              // Not paginated
10659              else {
10660                  recIndex = this.getRecordIndex(oRecord);
10661                  if(lang.isNumber(recIndex)) {
10662                      // Add the TR element
10663                      this._oChainRender.add({
10664                          method: function(oArg) {
10665                              if((this instanceof DT) && this._sId) {
10666                                  var oRecord = oArg.record;
10667                                  var recIndex = oArg.recIndex;
10668                                  var elNewTr = this._addTrEl(oRecord);
10669                                  if(elNewTr) {
10670                                      var elNext = (this._elTbody.rows[recIndex]) ? this._elTbody.rows[recIndex] : null;
10671                                      this._elTbody.insertBefore(elNewTr, elNext);
10672  
10673                                      // Set FIRST/LAST
10674                                      if(recIndex === 0) {
10675                                          this._setFirstRow();
10676                                      }
10677                                      if(elNext === null) {
10678                                          this._setLastRow();
10679                                      }
10680                                      // Set EVEN/ODD
10681                                      this._setRowStripes();                           
10682                                      
10683                                      this.hideTableMessage();
10684              
10685                                      this.fireEvent("rowAddEvent", {record:oRecord});
10686                                  }
10687                              }
10688                          },
10689                          argument: {record: oRecord, recIndex: recIndex},
10690                          scope: this,
10691                          timeout: (this.get("renderLoopSize") > 0) ? 0 : -1
10692                      });
10693                      this._runRenderChain();
10694                      return;
10695                  }
10696              }            
10697          }
10698      }
10699  },
10700  
10701  /**
10702   * Convenience method to add multiple rows.
10703   *
10704   * @method addRows
10705   * @param aData {Object[]} Array of object literal data for the rows.
10706   * @param index {Number} (optional) RecordSet position index at which to add data.
10707   */
10708  addRows : function(aData, index) {
10709      if(lang.isNumber(index) && (index < 0 || index > this._oRecordSet.getLength())) {
10710          return;
10711      }
10712  
10713      if(lang.isArray(aData)) {
10714          var aRecords = this._oRecordSet.addRecords(aData, index);
10715          if(aRecords) {
10716              var recIndex = this.getRecordIndex(aRecords[0]);
10717              
10718              // Paginated
10719              var oPaginator = this.get('paginator');
10720              if (oPaginator) {
10721                  // Update the paginator's totalRecords
10722                  var totalRecords = oPaginator.get('totalRecords');
10723                  if (totalRecords !== widget.Paginator.VALUE_UNLIMITED) {
10724                      oPaginator.set('totalRecords',totalRecords + aRecords.length);
10725                  }
10726      
10727                  var endRecIndex = (oPaginator.getPageRecords())[1];
10728  
10729                  // At least one of the new records affects the view
10730                  if (recIndex <= endRecIndex) {
10731                      this.render();
10732                  }
10733                  
10734                  this.fireEvent("rowsAddEvent", {records:aRecords});
10735                  return;
10736              }
10737              // Not paginated
10738              else {
10739                  // Add the TR elements
10740                  var loopN = this.get("renderLoopSize");
10741                  var loopEnd = recIndex + aData.length;
10742                  var nRowsNeeded = (loopEnd - recIndex); // how many needed
10743                  var isLast = (recIndex >= this._elTbody.rows.length);
10744                  this._oChainRender.add({
10745                      method: function(oArg) {
10746                          if((this instanceof DT) && this._sId) {
10747                              var aRecords = oArg.aRecords,
10748                                  i = oArg.nCurrentRow,
10749                                  j = oArg.nCurrentRecord,
10750                                  len = loopN > 0 ? Math.min(i + loopN,loopEnd) : loopEnd,
10751                                  df = document.createDocumentFragment(),
10752                                  elNext = (this._elTbody.rows[i]) ? this._elTbody.rows[i] : null;
10753                              for(; i < len; i++, j++) {
10754                                  df.appendChild(this._addTrEl(aRecords[j]));
10755                              }
10756                              this._elTbody.insertBefore(df, elNext);
10757                              oArg.nCurrentRow = i;
10758                              oArg.nCurrentRecord = j;
10759                          }
10760                      },
10761                      iterations: (loopN > 0) ? Math.ceil(loopEnd/loopN) : 1,
10762                      argument: {nCurrentRow:recIndex,nCurrentRecord:0,aRecords:aRecords},
10763                      scope: this,
10764                      timeout: (loopN > 0) ? 0 : -1
10765                  });
10766                  this._oChainRender.add({
10767                      method: function(oArg) {
10768                          var recIndex = oArg.recIndex;
10769                          // Set FIRST/LAST
10770                          if(recIndex === 0) {
10771                              this._setFirstRow();
10772                          }
10773                          if(oArg.isLast) {
10774                              this._setLastRow();
10775                          }
10776                          // Set EVEN/ODD
10777                          this._setRowStripes();                           
10778  
10779                          this.fireEvent("rowsAddEvent", {records:aRecords});
10780                      },
10781                      argument: {recIndex: recIndex, isLast: isLast},
10782                      scope: this,
10783                      timeout: -1 // Needs to run immediately after the DOM insertions above
10784                  });
10785                  this._runRenderChain();
10786                  this.hideTableMessage();                
10787                  return;
10788              }            
10789          }
10790      }
10791  },
10792  
10793  /**
10794   * For the given row, updates the associated Record with the given data. If the
10795   * row is on current page, the corresponding DOM elements are also updated.
10796   *
10797   * @method updateRow
10798   * @param row {YAHOO.widget.Record | Number | HTMLElement | String}
10799   * Which row to update: By Record instance, by Record's RecordSet
10800   * position index, by HTMLElement reference to the TR element, or by ID string
10801   * of the TR element.
10802   * @param oData {Object} Object literal of data for the row.
10803   */
10804  updateRow : function(row, oData) {
10805      var index = row;
10806      if (!lang.isNumber(index)) {
10807          index = this.getRecordIndex(row);
10808      }
10809  
10810      // Update the Record
10811      if(lang.isNumber(index) && (index >= 0)) {
10812          var oRecordSet = this._oRecordSet,
10813              oldRecord = oRecordSet.getRecord(index);
10814  
10815          if(oldRecord) {
10816              var updatedRecord = this._oRecordSet.setRecord(oData, index),
10817                  elRow = this.getTrEl(oldRecord),
10818                  // Copy data from the Record for the event that gets fired later
10819                  oldData = oldRecord ? oldRecord.getData() : null;
10820  
10821              if(updatedRecord) {
10822                  // Update selected rows as necessary
10823                  var tracker = this._aSelections || [],
10824                  i=0,
10825                  oldId = oldRecord.getId(),
10826                  newId = updatedRecord.getId();
10827                  for(; i<tracker.length; i++) {
10828                      if((tracker[i] === oldId)) {
10829                          tracker[i] = newId;
10830                      }
10831                      else if(tracker[i].recordId === oldId) {
10832                          tracker[i].recordId = newId;
10833                      }
10834                  }
10835  
10836                  // Update anchors as necessary
10837                  if(this._oAnchorRecord && this._oAnchorRecord.getId() === oldId) {
10838                      this._oAnchorRecord = updatedRecord;
10839                  }
10840                  if(this._oAnchorCell && this._oAnchorCell.record.getId() === oldId) {
10841                      this._oAnchorCell.record = updatedRecord;
10842                  }
10843  
10844                  // Update the TR only if row is on current page
10845                  this._oChainRender.add({
10846                      method: function() {
10847                          if((this instanceof DT) && this._sId) {
10848                              // Paginated
10849                              var oPaginator = this.get('paginator');
10850                              if (oPaginator) {
10851                                  var pageStartIndex = (oPaginator.getPageRecords())[0],
10852                                      pageLastIndex = (oPaginator.getPageRecords())[1];
10853  
10854                                  // At least one of the new records affects the view
10855                                  if ((index >= pageStartIndex) || (index <= pageLastIndex)) {
10856                                      this.render();
10857                                  }
10858                              }
10859                              else {
10860                                  if(elRow) {
10861                                      this._updateTrEl(elRow, updatedRecord);
10862                                  }
10863                                  else {
10864                                      this.getTbodyEl().appendChild(this._addTrEl(updatedRecord));
10865                                  }
10866                              }
10867                              this.fireEvent("rowUpdateEvent", {record:updatedRecord, oldData:oldData});
10868                          }
10869                      },
10870                      scope: this,
10871                      timeout: (this.get("renderLoopSize") > 0) ? 0 : -1
10872                  });
10873                  this._runRenderChain();
10874                  return;
10875              }
10876          }
10877      }
10878      return;
10879  },
10880  
10881  /**
10882   * Starting with the given row, updates associated Records with the given data.
10883   * The number of rows to update are determined by the array of data provided.
10884   * Undefined data (i.e., not an object literal) causes a row to be skipped. If
10885   * any of the rows are on current page, the corresponding DOM elements are also
10886   * updated.
10887   *
10888   * @method updateRows
10889   * @param startrow {YAHOO.widget.Record | Number | HTMLElement | String}
10890   * Starting row to update: By Record instance, by Record's RecordSet
10891   * position index, by HTMLElement reference to the TR element, or by ID string
10892   * of the TR element.
10893   * @param aData {Object[]} Array of object literal of data for the rows.
10894   */
10895  updateRows : function(startrow, aData) {
10896      if(lang.isArray(aData)) {
10897          var startIndex = startrow,
10898              oRecordSet = this._oRecordSet,
10899              lastRowIndex = oRecordSet.getLength();
10900  
10901          if (!lang.isNumber(startrow)) {
10902              startIndex = this.getRecordIndex(startrow);
10903          }
10904              
10905          if(lang.isNumber(startIndex) && (startIndex >= 0) && (startIndex < oRecordSet.getLength())) {
10906              var lastIndex = startIndex + aData.length,
10907                  aOldRecords = oRecordSet.getRecords(startIndex, aData.length),
10908                  aNewRecords = oRecordSet.setRecords(aData, startIndex);
10909              if(aNewRecords) {
10910                  var tracker = this._aSelections || [],
10911                      i=0, j, newRecord, newId, oldId,
10912                      anchorRecord = this._oAnchorRecord ? this._oAnchorRecord.getId() : null,
10913                      anchorCell = this._oAnchorCell ? this._oAnchorCell.record.getId() : null;
10914                  for(; i<aOldRecords.length; i++) {
10915                      oldId = aOldRecords[i].getId();
10916                      newRecord = aNewRecords[i];
10917                      newId = newRecord.getId();
10918  
10919                      // Update selected rows as necessary
10920                      for(j=0; j<tracker.length; j++) {
10921                          if((tracker[j] === oldId)) {
10922                              tracker[j] = newId;
10923                          }
10924                          else if(tracker[j].recordId === oldId) {
10925                              tracker[j].recordId = newId;
10926                          }
10927                      }
10928  
10929                      // Update anchors as necessary
10930                      if(anchorRecord && anchorRecord === oldId) {
10931                          this._oAnchorRecord = newRecord;
10932                      }
10933                      if(anchorCell && anchorCell === oldId) {
10934                          this._oAnchorCell.record = newRecord;
10935                      }
10936                 }
10937  
10938                  // Paginated
10939                  var oPaginator = this.get('paginator');
10940                  if (oPaginator) {
10941                      var pageStartIndex = (oPaginator.getPageRecords())[0],
10942                          pageLastIndex = (oPaginator.getPageRecords())[1];
10943      
10944                      // At least one of the new records affects the view
10945                      if ((startIndex >= pageStartIndex) || (lastIndex <= pageLastIndex)) {
10946                          this.render();
10947                      }
10948  
10949                      this.fireEvent("rowsAddEvent", {newRecords:aNewRecords, oldRecords:aOldRecords});
10950                      return;
10951                  }
10952                  // Not paginated
10953                  else {
10954                      // Update the TR elements
10955                      var loopN = this.get("renderLoopSize"),
10956                          rowCount = aData.length, // how many needed
10957                          isLast = (lastIndex >= lastRowIndex),
10958                          isAdding = (lastIndex > lastRowIndex);
10959                                             
10960                      this._oChainRender.add({
10961                          method: function(oArg) {
10962                              if((this instanceof DT) && this._sId) {
10963                                  var aRecords = oArg.aRecords,
10964                                      i = oArg.nCurrentRow,
10965                                      j = oArg.nDataPointer,
10966                                      len = loopN > 0 ? Math.min(i+loopN, startIndex+aRecords.length) : startIndex+aRecords.length;
10967                                      
10968                                  for(; i < len; i++,j++) {
10969                                      if(isAdding && (i>=lastRowIndex)) {
10970                                          this._elTbody.appendChild(this._addTrEl(aRecords[j]));
10971                                      }
10972                                      else {
10973                                          this._updateTrEl(this._elTbody.rows[i], aRecords[j]);
10974                                      }
10975                                  }
10976                                  oArg.nCurrentRow = i;
10977                                  oArg.nDataPointer = j;
10978                              }
10979                          },
10980                          iterations: (loopN > 0) ? Math.ceil(rowCount/loopN) : 1,
10981                          argument: {nCurrentRow:startIndex,aRecords:aNewRecords,nDataPointer:0,isAdding:isAdding},
10982                          scope: this,
10983                          timeout: (loopN > 0) ? 0 : -1
10984                      });
10985                      this._oChainRender.add({
10986                          method: function(oArg) {
10987                              var recIndex = oArg.recIndex;
10988                              // Set FIRST/LAST
10989                              if(recIndex === 0) {
10990                                  this._setFirstRow();
10991                              }
10992                              if(oArg.isLast) {
10993                                  this._setLastRow();
10994                              }
10995                              // Set EVEN/ODD
10996                              this._setRowStripes();                           
10997      
10998                              this.fireEvent("rowsAddEvent", {newRecords:aNewRecords, oldRecords:aOldRecords});
10999                          },
11000                          argument: {recIndex: startIndex, isLast: isLast},
11001                          scope: this,
11002                          timeout: -1 // Needs to run immediately after the DOM insertions above
11003                      });
11004                      this._runRenderChain();
11005                      this.hideTableMessage();                
11006                      return;
11007                  }            
11008              }
11009          }
11010      }
11011  },
11012  
11013  /**
11014   * Deletes the given row's Record from the RecordSet. If the row is on current page,
11015   * the corresponding DOM elements are also deleted.
11016   *
11017   * @method deleteRow
11018   * @param row {HTMLElement | String | Number} DOM element reference or ID string
11019   * to DataTable page element or RecordSet index.
11020   */
11021  deleteRow : function(row) {
11022      var nRecordIndex = (lang.isNumber(row)) ? row : this.getRecordIndex(row);
11023      if(lang.isNumber(nRecordIndex)) {
11024          var oRecord = this.getRecord(nRecordIndex);
11025          if(oRecord) {
11026              var nTrIndex = this.getTrIndex(nRecordIndex);
11027              
11028              // Remove from selection tracker if there
11029              var sRecordId = oRecord.getId();
11030              var tracker = this._aSelections || [];
11031              for(var j=tracker.length-1; j>-1; j--) {
11032                  if((lang.isString(tracker[j]) && (tracker[j] === sRecordId)) ||
11033                          (lang.isObject(tracker[j]) && (tracker[j].recordId === sRecordId))) {
11034                      tracker.splice(j,1);
11035                  }
11036              }
11037      
11038              // Delete Record from RecordSet
11039              var oData = this._oRecordSet.deleteRecord(nRecordIndex);
11040      
11041              // Update the UI
11042              if(oData) {
11043                  // If paginated and the deleted row was on this or a prior page, just
11044                  // re-render
11045                  var oPaginator = this.get('paginator');
11046                  if (oPaginator) {
11047                      // Update the paginator's totalRecords
11048                      var totalRecords = oPaginator.get('totalRecords'),
11049                          // must capture before the totalRecords change because
11050                          // Paginator shifts to previous page automatically
11051                          rng = oPaginator.getPageRecords();
11052  
11053                      if (totalRecords !== widget.Paginator.VALUE_UNLIMITED) {
11054                          oPaginator.set('totalRecords',totalRecords - 1);
11055                      }
11056      
11057                      // The deleted record was on this or a prior page, re-render
11058                      if (!rng || nRecordIndex <= rng[1]) {
11059                          this.render();
11060                      }
11061  
11062                      this._oChainRender.add({
11063                          method: function() {
11064                              if((this instanceof DT) && this._sId) {
11065                                  this.fireEvent("rowDeleteEvent", {recordIndex:nRecordIndex, oldData:oData, trElIndex:nTrIndex});
11066                              }
11067                          },
11068                          scope: this,
11069                          timeout: (this.get("renderLoopSize") > 0) ? 0 : -1
11070                      });
11071                      this._runRenderChain();
11072                  }
11073                  // Not paginated
11074                  else {
11075                      if(lang.isNumber(nTrIndex)) {
11076                          this._oChainRender.add({
11077                              method: function() {
11078                                  if((this instanceof DT) && this._sId) {
11079                                      var isLast = (nRecordIndex === this._oRecordSet.getLength());//(nTrIndex == this.getLastTrEl().sectionRowIndex);
11080                                      this._deleteTrEl(nTrIndex);
11081                      
11082                                      // Post-delete tasks
11083                                      if(this._elTbody.rows.length > 0) {
11084                                          // Set FIRST/LAST
11085                                          if(nTrIndex === 0) {
11086                                              this._setFirstRow();
11087                                          }
11088                                          if(isLast) {
11089                                              this._setLastRow();
11090                                          }
11091                                          // Set EVEN/ODD
11092                                          if(nTrIndex != this._elTbody.rows.length) {
11093                                              this._setRowStripes(nTrIndex);
11094                                          }                                
11095                                      }
11096                      
11097                                      this.fireEvent("rowDeleteEvent", {recordIndex:nRecordIndex,oldData:oData, trElIndex:nTrIndex});
11098                                  }
11099                              },
11100                              scope: this,
11101                              timeout: (this.get("renderLoopSize") > 0) ? 0 : -1
11102                          });
11103                          this._runRenderChain();
11104                          return;
11105                      }
11106                  }
11107              }
11108          }
11109      }
11110      return null;
11111  },
11112  
11113  /**
11114   * Convenience method to delete multiple rows.
11115   *
11116   * @method deleteRows
11117   * @param row {HTMLElement | String | Number} DOM element reference or ID string
11118   * to DataTable page element or RecordSet index.
11119   * @param count {Number} (optional) How many rows to delete. A negative value
11120   * will delete towards the beginning.
11121   */
11122  deleteRows : function(row, count) {
11123      var nRecordIndex = (lang.isNumber(row)) ? row : this.getRecordIndex(row);
11124      if(lang.isNumber(nRecordIndex)) {
11125          var oRecord = this.getRecord(nRecordIndex);
11126          if(oRecord) {
11127              var nTrIndex = this.getTrIndex(nRecordIndex);
11128              
11129              // Remove from selection tracker if there
11130              var sRecordId = oRecord.getId();
11131              var tracker = this._aSelections || [];
11132              for(var j=tracker.length-1; j>-1; j--) {
11133                  if((lang.isString(tracker[j]) && (tracker[j] === sRecordId)) ||
11134                          (lang.isObject(tracker[j]) && (tracker[j].recordId === sRecordId))) {
11135                      tracker.splice(j,1);
11136                  }
11137              }
11138      
11139              // Delete Record from RecordSet
11140              var highIndex = nRecordIndex;
11141              var lowIndex = nRecordIndex;
11142          
11143              // Validate count and account for negative value
11144              if(count && lang.isNumber(count)) {
11145                  highIndex = (count > 0) ? nRecordIndex + count -1 : nRecordIndex;
11146                  lowIndex = (count > 0) ? nRecordIndex : nRecordIndex + count + 1;
11147                  count = (count > 0) ? count : count*-1;
11148                  if(lowIndex < 0) {
11149                      lowIndex = 0;
11150                      count = highIndex - lowIndex + 1;
11151                  }
11152              }
11153              else {
11154                  count = 1;
11155              }
11156              
11157              var aData = this._oRecordSet.deleteRecords(lowIndex, count);
11158      
11159              // Update the UI
11160              if(aData) {
11161                  var oPaginator = this.get('paginator'),
11162                      loopN = this.get("renderLoopSize");
11163                  // If paginated and the deleted row was on this or a prior page, just
11164                  // re-render
11165                  if (oPaginator) {
11166                      // Update the paginator's totalRecords
11167                      var totalRecords = oPaginator.get('totalRecords'),
11168                          // must capture before the totalRecords change because
11169                          // Paginator shifts to previous page automatically
11170                          rng = oPaginator.getPageRecords();
11171  
11172                      if (totalRecords !== widget.Paginator.VALUE_UNLIMITED) {
11173                          oPaginator.set('totalRecords',totalRecords - aData.length);
11174                      }
11175      
11176                      // The records were on this or a prior page, re-render
11177                      if (!rng || lowIndex <= rng[1]) {
11178                          this.render();
11179                      }
11180  
11181                      this._oChainRender.add({
11182                          method: function(oArg) {
11183                              if((this instanceof DT) && this._sId) {
11184                                  this.fireEvent("rowsDeleteEvent", {recordIndex:lowIndex, oldData:aData, count:count});
11185                              }
11186                          },
11187                          scope: this,
11188                          timeout: (loopN > 0) ? 0 : -1
11189                      });
11190                      this._runRenderChain();
11191                      return;
11192                  }
11193                  // Not paginated
11194                  else {
11195                      if(lang.isNumber(nTrIndex)) {
11196                          // Delete the TR elements starting with highest index
11197                          var loopEnd = lowIndex;
11198                          var nRowsNeeded = count; // how many needed
11199                          this._oChainRender.add({
11200                              method: function(oArg) {
11201                                  if((this instanceof DT) && this._sId) {
11202                                      var i = oArg.nCurrentRow,
11203                                          len = (loopN > 0) ? (Math.max(i - loopN,loopEnd)-1) : loopEnd-1;
11204                                      for(; i>len; --i) {
11205                                          this._deleteTrEl(i);
11206                                      }
11207                                      oArg.nCurrentRow = i;
11208                                  }
11209                              },
11210                              iterations: (loopN > 0) ? Math.ceil(count/loopN) : 1,
11211                              argument: {nCurrentRow:highIndex},
11212                              scope: this,
11213                              timeout: (loopN > 0) ? 0 : -1
11214                          });
11215                          this._oChainRender.add({
11216                              method: function() {    
11217                                  // Post-delete tasks
11218                                  if(this._elTbody.rows.length > 0) {
11219                                      this._setFirstRow();
11220                                      this._setLastRow();
11221                                      this._setRowStripes();
11222                                  }
11223                                  
11224                                  this.fireEvent("rowsDeleteEvent", {recordIndex:lowIndex, oldData:aData, count:count});
11225                              },
11226                              scope: this,
11227                              timeout: -1 // Needs to run immediately after the DOM deletions above
11228                          });
11229                          this._runRenderChain();
11230                          return;
11231                      }
11232                  }
11233              }
11234          }
11235      }
11236      return null;
11237  },
11238  
11239  
11240  
11241  
11242  
11243  
11244  
11245  
11246  
11247  
11248  
11249  
11250  
11251  
11252  
11253  
11254  
11255  
11256  
11257  
11258  
11259  
11260  
11261  
11262  
11263  
11264  
11265  
11266  
11267  
11268  
11269  
11270  
11271  
11272  
11273  
11274  
11275  
11276  
11277  
11278  
11279  
11280  
11281  
11282  
11283  
11284  // CELL FUNCTIONS
11285  
11286  /**
11287   * Outputs markup into the given TD based on given Record.
11288   *
11289   * @method formatCell
11290   * @param elLiner {HTMLElement} The liner DIV element within the TD.
11291   * @param oRecord {YAHOO.widget.Record} (Optional) Record instance.
11292   * @param oColumn {YAHOO.widget.Column} (Optional) Column instance.
11293   */
11294  formatCell : function(elLiner, oRecord, oColumn) {
11295      if(!oRecord) {
11296          oRecord = this.getRecord(elLiner);
11297      }
11298      if(!oColumn) {
11299          oColumn = this.getColumn(this.getCellIndex(elLiner.parentNode));
11300      }
11301  
11302      if(oRecord && oColumn) {
11303          var sField = oColumn.field;
11304          var oData = oRecord.getData(sField);
11305  
11306          var fnFormatter = typeof oColumn.formatter === 'function' ?
11307                            oColumn.formatter :
11308                            DT.Formatter[oColumn.formatter+''] ||
11309                            DT.Formatter.defaultFormatter;
11310  
11311          // Apply special formatter
11312          if(fnFormatter) {
11313              fnFormatter.call(this, elLiner, oRecord, oColumn, oData);
11314          }
11315          else {
11316              elLiner.innerHTML = oData;
11317          }
11318  
11319          this.fireEvent("cellFormatEvent", {record:oRecord, column:oColumn, key:oColumn.key, el:elLiner});
11320      }
11321      else {
11322      }
11323  },
11324  
11325  /**
11326   * For the given row and column, updates the Record with the given data. If the
11327   * cell is on current page, the corresponding DOM elements are also updated.
11328   *
11329   * @method updateCell
11330   * @param oRecord {YAHOO.widget.Record} Record instance.
11331   * @param oColumn {YAHOO.widget.Column | String | Number} A Column key, or a ColumnSet key index.
11332   * @param oData {Object} New data value for the cell.
11333   * @param skipRender {Boolean} Skips render step. Editors that update multiple
11334   * cells in ScrollingDataTable should render only on the last call to updateCell().
11335   */
11336  updateCell : function(oRecord, oColumn, oData, skipRender) {
11337      // Validate Column and Record
11338      oColumn = (oColumn instanceof YAHOO.widget.Column) ? oColumn : this.getColumn(oColumn);
11339      if(oColumn && oColumn.getField() && (oRecord instanceof YAHOO.widget.Record)) {
11340          var sKey = oColumn.getField(),
11341          
11342          // Copy data from the Record for the event that gets fired later
11343          //var oldData = YAHOO.widget.DataTable._cloneObject(oRecord.getData());
11344              oldData = oRecord.getData(sKey);
11345  
11346          // Update Record with new data
11347          this._oRecordSet.updateRecordValue(oRecord, sKey, oData);
11348      
11349          // Update the TD only if row is on current page
11350          var elTd = this.getTdEl({record: oRecord, column: oColumn});
11351          if(elTd) {
11352              this._oChainRender.add({
11353                  method: function() {
11354                      if((this instanceof DT) && this._sId) {
11355                          this.formatCell(elTd.firstChild, oRecord, oColumn);
11356                          this.fireEvent("cellUpdateEvent", {record:oRecord, column: oColumn, oldData:oldData});
11357                      }
11358                  },
11359                  scope: this,
11360                  timeout: (this.get("renderLoopSize") > 0) ? 0 : -1
11361              });
11362              // Bug 2529024
11363              if(!skipRender) {
11364                  this._runRenderChain();
11365              }
11366          }
11367          else {
11368              this.fireEvent("cellUpdateEvent", {record:oRecord, column: oColumn, oldData:oldData});
11369          }
11370      }
11371  },
11372  
11373  
11374  
11375  
11376  
11377  
11378  
11379  
11380  
11381  
11382  
11383  
11384  
11385  
11386  
11387  
11388  
11389  
11390  
11391  
11392  
11393  
11394  
11395  
11396  
11397  
11398  
11399  
11400  
11401  
11402  
11403  
11404  
11405  
11406  
11407  
11408  
11409  
11410  
11411  
11412  
11413  
11414  
11415  
11416  
11417  
11418  
11419  
11420  
11421  
11422  
11423  // PAGINATION
11424  /**
11425   * Method executed during set() operation for the "paginator" attribute.
11426   * Adds and/or severs event listeners between DataTable and Paginator
11427   *
11428   * @method _updatePaginator
11429   * @param newPag {Paginator} Paginator instance (or null) for DataTable to use
11430   * @private
11431   */
11432  _updatePaginator : function (newPag) {
11433      var oldPag = this.get('paginator');
11434      if (oldPag && newPag !== oldPag) {
11435          oldPag.unsubscribe('changeRequest', this.onPaginatorChangeRequest, this, true);
11436      }
11437      if (newPag) {
11438          newPag.subscribe('changeRequest', this.onPaginatorChangeRequest, this, true);
11439      }
11440  },
11441  
11442  /**
11443   * Update the UI infrastructure in response to a "paginator" attribute change.
11444   *
11445   * @method _handlePaginatorChange
11446   * @param e {Object} Change event object containing keys 'type','newValue',
11447   *                   and 'prevValue'
11448   * @private
11449   */
11450  _handlePaginatorChange : function (e) {
11451      if (e.prevValue === e.newValue) { return; }
11452  
11453      var newPag     = e.newValue,
11454          oldPag     = e.prevValue,
11455          containers = this._defaultPaginatorContainers();
11456  
11457      if (oldPag) {
11458          if (oldPag.getContainerNodes()[0] == containers[0]) {
11459              oldPag.set('containers',[]);
11460          }
11461          oldPag.destroy();
11462  
11463          // Convenience: share the default containers if possible.
11464          // Otherwise, remove the default containers from the DOM.
11465          if (containers[0]) {
11466              if (newPag && !newPag.getContainerNodes().length) {
11467                  newPag.set('containers',containers);
11468              } else {
11469                  // No new Paginator to use existing containers, OR new
11470                  // Paginator has configured containers.
11471                  for (var i = containers.length - 1; i >= 0; --i) {
11472                      if (containers[i]) {
11473                          containers[i].parentNode.removeChild(containers[i]);
11474                      }
11475                  }
11476              }
11477          }
11478      }
11479  
11480      if (!this._bInit) {
11481          this.render();
11482  
11483      }
11484  
11485      if (newPag) {
11486          this.renderPaginator();
11487      }
11488  
11489  },
11490  
11491  /**
11492   * Returns the default containers used for Paginators.  If create param is
11493   * passed, the containers will be created and added to the DataTable container.
11494   *
11495   * @method _defaultPaginatorContainers
11496   * @param create {boolean} Create the default containers if not found
11497   * @private
11498   */
11499  _defaultPaginatorContainers : function (create) {
11500      var above_id = this._sId + '-paginator0',
11501          below_id = this._sId + '-paginator1',
11502          above    = Dom.get(above_id),
11503          below    = Dom.get(below_id);
11504  
11505      if (create && (!above || !below)) {
11506          // One above and one below the table
11507          if (!above) {
11508              above    = document.createElement('div');
11509              above.id = above_id;
11510              Dom.addClass(above, DT.CLASS_PAGINATOR);
11511  
11512              this._elContainer.insertBefore(above,this._elContainer.firstChild);
11513          }
11514  
11515          if (!below) {
11516              below    = document.createElement('div');
11517              below.id = below_id;
11518              Dom.addClass(below, DT.CLASS_PAGINATOR);
11519  
11520              this._elContainer.appendChild(below);
11521          }
11522      }
11523  
11524      return [above,below];
11525  },
11526  
11527  /**
11528   * Calls Paginator's destroy() method
11529   *
11530   * @method _destroyPaginator
11531   * @private
11532   */
11533  _destroyPaginator : function () {
11534      var oldPag = this.get('paginator');
11535      if (oldPag) {
11536          oldPag.destroy();
11537      }
11538  },
11539  
11540  /**
11541   * Renders the Paginator to the DataTable UI
11542   *
11543   * @method renderPaginator
11544   */
11545  renderPaginator : function () {
11546      var pag = this.get("paginator");
11547      if (!pag) { return; }
11548  
11549      // Add the containers if the Paginator is not configured with containers
11550      if (!pag.getContainerNodes().length) {
11551          pag.set('containers',this._defaultPaginatorContainers(true));
11552      }
11553  
11554      pag.render();
11555  },
11556  
11557  /**
11558   * Overridable method gives implementers a hook to show loading message before
11559   * changing Paginator value.
11560   *
11561   * @method doBeforePaginatorChange
11562   * @param oPaginatorState {Object} An object literal describing the proposed pagination state.
11563   * @return {Boolean} Return true to continue changing Paginator value.
11564   */
11565  doBeforePaginatorChange : function(oPaginatorState) {
11566      this.showTableMessage(this.get("MSG_LOADING"), DT.CLASS_LOADING);
11567      return true;
11568  },
11569  
11570  /**
11571   * Responds to new Pagination states. By default, updates the UI to reflect the
11572   * new state. If "dynamicData" is true, current selections are purged before
11573   * a request is sent to the DataSource for data for the new state (using the
11574   * request returned by "generateRequest()").
11575   *  
11576   * @method onPaginatorChangeRequest
11577   * @param oPaginatorState {Object} An object literal describing the proposed pagination state.
11578   */
11579  onPaginatorChangeRequest : function (oPaginatorState) {
11580      var ok = this.doBeforePaginatorChange(oPaginatorState);
11581      if(ok) {
11582          // Server-side pagination
11583          if(this.get("dynamicData")) {
11584              // Get the current state
11585              var oState = this.getState();
11586              
11587              // Update pagination values
11588              oState.pagination = oPaginatorState;
11589      
11590              // Get the request for the new state
11591              var request = this.get("generateRequest")(oState, this);
11592              
11593              // Purge selections
11594              this.unselectAllRows();
11595              this.unselectAllCells();
11596              
11597              // Get the new data from the server
11598              var callback = {
11599                  success : this.onDataReturnSetRows,
11600                  failure : this.onDataReturnSetRows,
11601                  argument : oState, // Pass along the new state to the callback
11602                  scope : this
11603              };
11604              this._oDataSource.sendRequest(request, callback);
11605          }
11606          // Client-side pagination
11607          else {
11608              // Set the core pagination values silently (the second param)
11609              // to avoid looping back through the changeRequest mechanism
11610              oPaginatorState.paginator.setStartIndex(oPaginatorState.recordOffset,true);
11611              oPaginatorState.paginator.setRowsPerPage(oPaginatorState.rowsPerPage,true);
11612      
11613              // Update the UI
11614              this.render();
11615          }
11616      }
11617      else {
11618      }
11619  },
11620  
11621  
11622  
11623  
11624  
11625  
11626  
11627  
11628  
11629  
11630  
11631  
11632  
11633  
11634  
11635  
11636  
11637  
11638  
11639  
11640  
11641  
11642  
11643  
11644  
11645  
11646  
11647  
11648  
11649  
11650  
11651  
11652  
11653  
11654  
11655  
11656  
11657  
11658  
11659  
11660  
11661  
11662  
11663  
11664  
11665  
11666  
11667  
11668  
11669  
11670  // SELECTION/HIGHLIGHTING
11671  
11672  /*
11673   * Reference to last highlighted cell element
11674   *
11675   * @property _elLastHighlightedTd
11676   * @type HTMLElement
11677   * @private
11678   */
11679  _elLastHighlightedTd : null,
11680  
11681  /*
11682   * ID string of last highlighted row element
11683   *
11684   * @property _sLastHighlightedTrElId
11685   * @type String
11686   * @private
11687   */
11688  //_sLastHighlightedTrElId : null,
11689  
11690  /**
11691   * Array to track row selections (by sRecordId) and/or cell selections
11692   * (by {recordId:sRecordId, columnKey:sColumnKey})
11693   *
11694   * @property _aSelections
11695   * @type Object[]
11696   * @private
11697   */
11698  _aSelections : null,
11699  
11700  /**
11701   * Record instance of the row selection anchor.
11702   *
11703   * @property _oAnchorRecord
11704   * @type YAHOO.widget.Record
11705   * @private
11706   */
11707  _oAnchorRecord : null,
11708  
11709  /**
11710   * Object literal representing cell selection anchor:
11711   * {recordId:sRecordId, columnKey:sColumnKey}.
11712   *
11713   * @property _oAnchorCell
11714   * @type Object
11715   * @private
11716   */
11717  _oAnchorCell : null,
11718  
11719  /**
11720   * Convenience method to remove the class YAHOO.widget.DataTable.CLASS_SELECTED
11721   * from all TR elements on the page.
11722   *
11723   * @method _unselectAllTrEls
11724   * @private
11725   */
11726  _unselectAllTrEls : function() {
11727      var selectedRows = Dom.getElementsByClassName(DT.CLASS_SELECTED,"tr",this._elTbody);
11728      Dom.removeClass(selectedRows, DT.CLASS_SELECTED);
11729  },
11730  
11731  /**
11732   * Returns object literal of values that represent the selection trigger. Used
11733   * to determine selection behavior resulting from a key event.
11734   *
11735   * @method _getSelectionTrigger
11736   * @private
11737   */
11738  _getSelectionTrigger : function() {
11739      var sMode = this.get("selectionMode");
11740      var oTrigger = {};
11741      var oTriggerCell, oTriggerRecord, nTriggerRecordIndex, elTriggerRow, nTriggerTrIndex;
11742  
11743      // Cell mode
11744      if((sMode == "cellblock") || (sMode == "cellrange") || (sMode == "singlecell")) {
11745          oTriggerCell = this.getLastSelectedCell();
11746          // No selected cells found
11747          if(!oTriggerCell) {
11748              return null;
11749          }
11750          else {
11751              oTriggerRecord = this.getRecord(oTriggerCell.recordId);
11752              nTriggerRecordIndex = this.getRecordIndex(oTriggerRecord);
11753              elTriggerRow = this.getTrEl(oTriggerRecord);
11754              nTriggerTrIndex = this.getTrIndex(elTriggerRow);
11755  
11756              // Selected cell not found on this page
11757              if(nTriggerTrIndex === null) {
11758                  return null;
11759              }
11760              else {
11761                  oTrigger.record = oTriggerRecord;
11762                  oTrigger.recordIndex = nTriggerRecordIndex;
11763                  oTrigger.el = this.getTdEl(oTriggerCell);
11764                  oTrigger.trIndex = nTriggerTrIndex;
11765                  oTrigger.column = this.getColumn(oTriggerCell.columnKey);
11766                  oTrigger.colKeyIndex = oTrigger.column.getKeyIndex();
11767                  oTrigger.cell = oTriggerCell;
11768                  return oTrigger;
11769              }
11770          }
11771      }
11772      // Row mode
11773      else {
11774          oTriggerRecord = this.getLastSelectedRecord();
11775          // No selected rows found
11776          if(!oTriggerRecord) {
11777                  return null;
11778          }
11779          else {
11780              // Selected row found, but is it on current page?
11781              oTriggerRecord = this.getRecord(oTriggerRecord);
11782              nTriggerRecordIndex = this.getRecordIndex(oTriggerRecord);
11783              elTriggerRow = this.getTrEl(oTriggerRecord);
11784              nTriggerTrIndex = this.getTrIndex(elTriggerRow);
11785  
11786              // Selected row not found on this page
11787              if(nTriggerTrIndex === null) {
11788                  return null;
11789              }
11790              else {
11791                  oTrigger.record = oTriggerRecord;
11792                  oTrigger.recordIndex = nTriggerRecordIndex;
11793                  oTrigger.el = elTriggerRow;
11794                  oTrigger.trIndex = nTriggerTrIndex;
11795                  return oTrigger;
11796              }
11797          }
11798      }
11799  },
11800  
11801  /**
11802   * Returns object literal of values that represent the selection anchor. Used
11803   * to determine selection behavior resulting from a user event.
11804   *
11805   * @method _getSelectionAnchor
11806   * @param oTrigger {Object} (Optional) Object literal of selection trigger values
11807   * (for key events).
11808   * @private
11809   */
11810  _getSelectionAnchor : function(oTrigger) {
11811      var sMode = this.get("selectionMode");
11812      var oAnchor = {};
11813      var oAnchorRecord, nAnchorRecordIndex, nAnchorTrIndex;
11814  
11815      // Cell mode
11816      if((sMode == "cellblock") || (sMode == "cellrange") || (sMode == "singlecell")) {
11817          // Validate anchor cell
11818          var oAnchorCell = this._oAnchorCell;
11819          if(!oAnchorCell) {
11820              if(oTrigger) {
11821                  oAnchorCell = this._oAnchorCell = oTrigger.cell;
11822              }
11823              else {
11824                  return null;
11825              }
11826          }
11827          oAnchorRecord = this._oAnchorCell.record;
11828          nAnchorRecordIndex = this._oRecordSet.getRecordIndex(oAnchorRecord);
11829          nAnchorTrIndex = this.getTrIndex(oAnchorRecord);
11830          // If anchor cell is not on this page...
11831          if(nAnchorTrIndex === null) {
11832              // ...set TR index equal to top TR
11833              if(nAnchorRecordIndex < this.getRecordIndex(this.getFirstTrEl())) {
11834                  nAnchorTrIndex = 0;
11835              }
11836              // ...set TR index equal to bottom TR
11837              else {
11838                  nAnchorTrIndex = this.getRecordIndex(this.getLastTrEl());
11839              }
11840          }
11841  
11842          oAnchor.record = oAnchorRecord;
11843          oAnchor.recordIndex = nAnchorRecordIndex;
11844          oAnchor.trIndex = nAnchorTrIndex;
11845          oAnchor.column = this._oAnchorCell.column;
11846          oAnchor.colKeyIndex = oAnchor.column.getKeyIndex();
11847          oAnchor.cell = oAnchorCell;
11848          return oAnchor;
11849      }
11850      // Row mode
11851      else {
11852          oAnchorRecord = this._oAnchorRecord;
11853          if(!oAnchorRecord) {
11854              if(oTrigger) {
11855                  oAnchorRecord = this._oAnchorRecord = oTrigger.record;
11856              }
11857              else {
11858                  return null;
11859              }
11860          }
11861  
11862          nAnchorRecordIndex = this.getRecordIndex(oAnchorRecord);
11863          nAnchorTrIndex = this.getTrIndex(oAnchorRecord);
11864          // If anchor row is not on this page...
11865          if(nAnchorTrIndex === null) {
11866              // ...set TR index equal to top TR
11867              if(nAnchorRecordIndex < this.getRecordIndex(this.getFirstTrEl())) {
11868                  nAnchorTrIndex = 0;
11869              }
11870              // ...set TR index equal to bottom TR
11871              else {
11872                  nAnchorTrIndex = this.getRecordIndex(this.getLastTrEl());
11873              }
11874          }
11875  
11876          oAnchor.record = oAnchorRecord;
11877          oAnchor.recordIndex = nAnchorRecordIndex;
11878          oAnchor.trIndex = nAnchorTrIndex;
11879          return oAnchor;
11880      }
11881  },
11882  
11883  /**
11884   * Determines selection behavior resulting from a mouse event when selection mode
11885   * is set to "standard".
11886   *
11887   * @method _handleStandardSelectionByMouse
11888   * @param oArgs.event {HTMLEvent} Event object.
11889   * @param oArgs.target {HTMLElement} Target element.
11890   * @private
11891   */
11892  _handleStandardSelectionByMouse : function(oArgs) {
11893      var elTarget = oArgs.target;
11894  
11895      // Validate target row
11896      var elTargetRow = this.getTrEl(elTarget);
11897      if(elTargetRow) {
11898          var e = oArgs.event;
11899          var bSHIFT = e.shiftKey;
11900          var bCTRL = e.ctrlKey || ((navigator.userAgent.toLowerCase().indexOf("mac") != -1) && e.metaKey);
11901  
11902          var oTargetRecord = this.getRecord(elTargetRow);
11903          var nTargetRecordIndex = this._oRecordSet.getRecordIndex(oTargetRecord);
11904  
11905          var oAnchor = this._getSelectionAnchor();
11906  
11907          var i;
11908  
11909          // Both SHIFT and CTRL
11910          if(bSHIFT && bCTRL) {
11911              // Validate anchor
11912              if(oAnchor) {
11913                  if(this.isSelected(oAnchor.record)) {
11914                      // Select all rows between anchor row and target row, including target row
11915                      if(oAnchor.recordIndex < nTargetRecordIndex) {
11916                          for(i=oAnchor.recordIndex+1; i<=nTargetRecordIndex; i++) {
11917                              if(!this.isSelected(i)) {
11918                                  this.selectRow(i);
11919                              }
11920                          }
11921                      }
11922                      // Select all rows between target row and anchor row, including target row
11923                      else {
11924                          for(i=oAnchor.recordIndex-1; i>=nTargetRecordIndex; i--) {
11925                              if(!this.isSelected(i)) {
11926                                  this.selectRow(i);
11927                              }
11928                          }
11929                      }
11930                  }
11931                  else {
11932                      // Unselect all rows between anchor row and target row
11933                      if(oAnchor.recordIndex < nTargetRecordIndex) {
11934                          for(i=oAnchor.recordIndex+1; i<=nTargetRecordIndex-1; i++) {
11935                              if(this.isSelected(i)) {
11936                                  this.unselectRow(i);
11937                              }
11938                          }
11939                      }
11940                      // Unselect all rows between target row and anchor row
11941                      else {
11942                          for(i=nTargetRecordIndex+1; i<=oAnchor.recordIndex-1; i++) {
11943                              if(this.isSelected(i)) {
11944                                  this.unselectRow(i);
11945                              }
11946                          }
11947                      }
11948                      // Select the target row
11949                      this.selectRow(oTargetRecord);
11950                  }
11951              }
11952              // Invalid anchor
11953              else {
11954                  // Set anchor
11955                  this._oAnchorRecord = oTargetRecord;
11956  
11957                  // Toggle selection of target
11958                  if(this.isSelected(oTargetRecord)) {
11959                      this.unselectRow(oTargetRecord);
11960                  }
11961                  else {
11962                      this.selectRow(oTargetRecord);
11963                  }
11964              }
11965          }
11966           // Only SHIFT
11967          else if(bSHIFT) {
11968              this.unselectAllRows();
11969  
11970              // Validate anchor
11971              if(oAnchor) {
11972                  // Select all rows between anchor row and target row,
11973                  // including the anchor row and target row
11974                  if(oAnchor.recordIndex < nTargetRecordIndex) {
11975                      for(i=oAnchor.recordIndex; i<=nTargetRecordIndex; i++) {
11976                          this.selectRow(i);
11977                      }
11978                  }
11979                  // Select all rows between target row and anchor row,
11980                  // including the target row and anchor row
11981                  else {
11982                      for(i=oAnchor.recordIndex; i>=nTargetRecordIndex; i--) {
11983                          this.selectRow(i);
11984                      }
11985                  }
11986              }
11987              // Invalid anchor
11988              else {
11989                  // Set anchor
11990                  this._oAnchorRecord = oTargetRecord;
11991  
11992                  // Select target row only
11993                  this.selectRow(oTargetRecord);
11994              }
11995          }
11996          // Only CTRL
11997          else if(bCTRL) {
11998              // Set anchor
11999              this._oAnchorRecord = oTargetRecord;
12000  
12001              // Toggle selection of target
12002              if(this.isSelected(oTargetRecord)) {
12003                  this.unselectRow(oTargetRecord);
12004              }
12005              else {
12006                  this.selectRow(oTargetRecord);
12007              }
12008          }
12009          // Neither SHIFT nor CTRL
12010          else {
12011              this._handleSingleSelectionByMouse(oArgs);
12012              return;
12013          }
12014      }
12015  },
12016  
12017  /**
12018   * Determines selection behavior resulting from a key event when selection mode
12019   * is set to "standard".
12020   *
12021   * @method _handleStandardSelectionByKey
12022   * @param e {HTMLEvent} Event object.
12023   * @private
12024   */
12025  _handleStandardSelectionByKey : function(e) {
12026      var nKey = Ev.getCharCode(e);
12027  
12028      if((nKey == 38) || (nKey == 40)) {
12029          var bSHIFT = e.shiftKey;
12030  
12031          // Validate trigger
12032          var oTrigger = this._getSelectionTrigger();
12033          // Arrow selection only works if last selected row is on current page
12034          if(!oTrigger) {
12035              return null;
12036          }
12037  
12038          Ev.stopEvent(e);
12039  
12040          // Validate anchor
12041          var oAnchor = this._getSelectionAnchor(oTrigger);
12042  
12043          // Determine which direction we're going to
12044          if(bSHIFT) {
12045              // Selecting down away from anchor row
12046              if((nKey == 40) && (oAnchor.recordIndex <= oTrigger.trIndex)) {
12047                  this.selectRow(this.getNextTrEl(oTrigger.el));
12048              }
12049              // Selecting up away from anchor row
12050              else if((nKey == 38) && (oAnchor.recordIndex >= oTrigger.trIndex)) {
12051                  this.selectRow(this.getPreviousTrEl(oTrigger.el));
12052              }
12053              // Unselect trigger
12054              else {
12055                  this.unselectRow(oTrigger.el);
12056              }
12057          }
12058          else {
12059              this._handleSingleSelectionByKey(e);
12060          }
12061      }
12062  },
12063  
12064  /**
12065   * Determines selection behavior resulting from a mouse event when selection mode
12066   * is set to "single".
12067   *
12068   * @method _handleSingleSelectionByMouse
12069   * @param oArgs.event {HTMLEvent} Event object.
12070   * @param oArgs.target {HTMLElement} Target element.
12071   * @private
12072   */
12073  _handleSingleSelectionByMouse : function(oArgs) {
12074      var elTarget = oArgs.target;
12075  
12076      // Validate target row
12077      var elTargetRow = this.getTrEl(elTarget);
12078      if(elTargetRow) {
12079          var oTargetRecord = this.getRecord(elTargetRow);
12080  
12081          // Set anchor
12082          this._oAnchorRecord = oTargetRecord;
12083  
12084          // Select only target
12085          this.unselectAllRows();
12086          this.selectRow(oTargetRecord);
12087      }
12088  },
12089  
12090  /**
12091   * Determines selection behavior resulting from a key event when selection mode
12092   * is set to "single".
12093   *
12094   * @method _handleSingleSelectionByKey
12095   * @param e {HTMLEvent} Event object.
12096   * @private
12097   */
12098  _handleSingleSelectionByKey : function(e) {
12099      var nKey = Ev.getCharCode(e);
12100  
12101      if((nKey == 38) || (nKey == 40)) {
12102          // Validate trigger
12103          var oTrigger = this._getSelectionTrigger();
12104          // Arrow selection only works if last selected row is on current page
12105          if(!oTrigger) {
12106              return null;
12107          }
12108  
12109          Ev.stopEvent(e);
12110  
12111          // Determine the new row to select
12112          var elNew;
12113          if(nKey == 38) { // arrow up
12114              elNew = this.getPreviousTrEl(oTrigger.el);
12115  
12116              // Validate new row
12117              if(elNew === null) {
12118                  //TODO: wrap around to last tr on current page
12119                  //elNew = this.getLastTrEl();
12120  
12121                  //TODO: wrap back to last tr of previous page
12122  
12123                  // Top row selection is sticky
12124                  elNew = this.getFirstTrEl();
12125              }
12126          }
12127          else if(nKey == 40) { // arrow down
12128              elNew = this.getNextTrEl(oTrigger.el);
12129  
12130              // Validate new row
12131              if(elNew === null) {
12132                  //TODO: wrap around to first tr on current page
12133                  //elNew = this.getFirstTrEl();
12134  
12135                  //TODO: wrap forward to first tr of previous page
12136  
12137                  // Bottom row selection is sticky
12138                  elNew = this.getLastTrEl();
12139              }
12140          }
12141  
12142          // Unselect all rows
12143          this.unselectAllRows();
12144  
12145          // Select the new row
12146          this.selectRow(elNew);
12147  
12148          // Set new anchor
12149          this._oAnchorRecord = this.getRecord(elNew);
12150      }
12151  },
12152  
12153  /**
12154   * Determines selection behavior resulting from a mouse event when selection mode
12155   * is set to "cellblock".
12156   *
12157   * @method _handleCellBlockSelectionByMouse
12158   * @param oArgs.event {HTMLEvent} Event object.
12159   * @param oArgs.target {HTMLElement} Target element.
12160   * @private
12161   */
12162  _handleCellBlockSelectionByMouse : function(oArgs) {
12163      var elTarget = oArgs.target;
12164  
12165      // Validate target cell
12166      var elTargetCell = this.getTdEl(elTarget);
12167      if(elTargetCell) {
12168          var e = oArgs.event;
12169          var bSHIFT = e.shiftKey;
12170          var bCTRL = e.ctrlKey || ((navigator.userAgent.toLowerCase().indexOf("mac") != -1) && e.metaKey);
12171  
12172          var elTargetRow = this.getTrEl(elTargetCell);
12173          var nTargetTrIndex = this.getTrIndex(elTargetRow);
12174          var oTargetColumn = this.getColumn(elTargetCell);
12175          var nTargetColKeyIndex = oTargetColumn.getKeyIndex();
12176          var oTargetRecord = this.getRecord(elTargetRow);
12177          var nTargetRecordIndex = this._oRecordSet.getRecordIndex(oTargetRecord);
12178          var oTargetCell = {record:oTargetRecord, column:oTargetColumn};
12179  
12180          var oAnchor = this._getSelectionAnchor();
12181  
12182          var allRows = this.getTbodyEl().rows;
12183          var startIndex, endIndex, currentRow, i, j;
12184  
12185          // Both SHIFT and CTRL
12186          if(bSHIFT && bCTRL) {
12187  
12188              // Validate anchor
12189              if(oAnchor) {
12190                  // Anchor is selected
12191                  if(this.isSelected(oAnchor.cell)) {
12192                      // All cells are on the same row
12193                      if(oAnchor.recordIndex === nTargetRecordIndex) {
12194                          // Select all cells between anchor cell and target cell, including target cell
12195                          if(oAnchor.colKeyIndex < nTargetColKeyIndex) {
12196                              for(i=oAnchor.colKeyIndex+1; i<=nTargetColKeyIndex; i++) {
12197                                  this.selectCell(elTargetRow.cells[i]);
12198                              }
12199                          }
12200                          // Select all cells between target cell and anchor cell, including target cell
12201                          else if(nTargetColKeyIndex < oAnchor.colKeyIndex) {
12202                              for(i=nTargetColKeyIndex; i<oAnchor.colKeyIndex; i++) {
12203                                  this.selectCell(elTargetRow.cells[i]);
12204                              }
12205                          }
12206                      }
12207                      // Anchor row is above target row
12208                      else if(oAnchor.recordIndex < nTargetRecordIndex) {
12209                          startIndex = Math.min(oAnchor.colKeyIndex, nTargetColKeyIndex);
12210                          endIndex = Math.max(oAnchor.colKeyIndex, nTargetColKeyIndex);
12211  
12212                          // Select all cells from startIndex to endIndex on rows between anchor row and target row
12213                          for(i=oAnchor.trIndex; i<=nTargetTrIndex; i++) {
12214                              for(j=startIndex; j<=endIndex; j++) {
12215                                  this.selectCell(allRows[i].cells[j]);
12216                              }
12217                          }
12218                      }
12219                      // Anchor row is below target row
12220                      else {
12221                          startIndex = Math.min(oAnchor.trIndex, nTargetColKeyIndex);
12222                          endIndex = Math.max(oAnchor.trIndex, nTargetColKeyIndex);
12223  
12224                          // Select all cells from startIndex to endIndex on rows between target row and anchor row
12225                          for(i=oAnchor.trIndex; i>=nTargetTrIndex; i--) {
12226                              for(j=endIndex; j>=startIndex; j--) {
12227                                  this.selectCell(allRows[i].cells[j]);
12228                              }
12229                          }
12230                      }
12231                  }
12232                  // Anchor cell is unselected
12233                  else {
12234                      // All cells are on the same row
12235                      if(oAnchor.recordIndex === nTargetRecordIndex) {
12236                          // Unselect all cells between anchor cell and target cell
12237                          if(oAnchor.colKeyIndex < nTargetColKeyIndex) {
12238                              for(i=oAnchor.colKeyIndex+1; i<nTargetColKeyIndex; i++) {
12239                                  this.unselectCell(elTargetRow.cells[i]);
12240                              }
12241                          }
12242                          // Select all cells between target cell and anchor cell
12243                          else if(nTargetColKeyIndex < oAnchor.colKeyIndex) {
12244                              for(i=nTargetColKeyIndex+1; i<oAnchor.colKeyIndex; i++) {
12245                                  this.unselectCell(elTargetRow.cells[i]);
12246                              }
12247                          }
12248                      }
12249                      // Anchor row is above target row
12250                      if(oAnchor.recordIndex < nTargetRecordIndex) {
12251                          // Unselect all cells from anchor cell to target cell
12252                          for(i=oAnchor.trIndex; i<=nTargetTrIndex; i++) {
12253                              currentRow = allRows[i];
12254                              for(j=0; j<currentRow.cells.length; j++) {
12255                                  // This is the anchor row, only unselect cells after the anchor cell
12256                                  if(currentRow.sectionRowIndex === oAnchor.trIndex) {
12257                                      if(j>oAnchor.colKeyIndex) {
12258                                          this.unselectCell(currentRow.cells[j]);
12259                                      }
12260                                  }
12261                                  // This is the target row, only unelect cells before the target cell
12262                                  else if(currentRow.sectionRowIndex === nTargetTrIndex) {
12263                                      if(j<nTargetColKeyIndex) {
12264                                          this.unselectCell(currentRow.cells[j]);
12265                                      }
12266                                  }
12267                                  // Unselect all cells on this row
12268                                  else {
12269                                      this.unselectCell(currentRow.cells[j]);
12270                                  }
12271                              }
12272                          }
12273                      }
12274                      // Anchor row is below target row
12275                      else {
12276                          // Unselect all cells from target cell to anchor cell
12277                          for(i=nTargetTrIndex; i<=oAnchor.trIndex; i++) {
12278                              currentRow = allRows[i];
12279                              for(j=0; j<currentRow.cells.length; j++) {
12280                                  // This is the target row, only unselect cells after the target cell
12281                                  if(currentRow.sectionRowIndex == nTargetTrIndex) {
12282                                      if(j>nTargetColKeyIndex) {
12283                                          this.unselectCell(currentRow.cells[j]);
12284                                      }
12285                                  }
12286                                  // This is the anchor row, only unselect cells before the anchor cell
12287                                  else if(currentRow.sectionRowIndex == oAnchor.trIndex) {
12288                                      if(j<oAnchor.colKeyIndex) {
12289                                          this.unselectCell(currentRow.cells[j]);
12290                                      }
12291                                  }
12292                                  // Unselect all cells on this row
12293                                  else {
12294                                      this.unselectCell(currentRow.cells[j]);
12295                                  }
12296                              }
12297                          }
12298                      }
12299  
12300                      // Select the target cell
12301                      this.selectCell(elTargetCell);
12302                  }
12303              }
12304              // Invalid anchor
12305              else {
12306                  // Set anchor
12307                  this._oAnchorCell = oTargetCell;
12308  
12309                  // Toggle selection of target
12310                  if(this.isSelected(oTargetCell)) {
12311                      this.unselectCell(oTargetCell);
12312                  }
12313                  else {
12314                      this.selectCell(oTargetCell);
12315                  }
12316              }
12317  
12318          }
12319           // Only SHIFT
12320          else if(bSHIFT) {
12321              this.unselectAllCells();
12322  
12323              // Validate anchor
12324              if(oAnchor) {
12325                  // All cells are on the same row
12326                  if(oAnchor.recordIndex === nTargetRecordIndex) {
12327                      // Select all cells between anchor cell and target cell,
12328                      // including the anchor cell and target cell
12329                      if(oAnchor.colKeyIndex < nTargetColKeyIndex) {
12330                          for(i=oAnchor.colKeyIndex; i<=nTargetColKeyIndex; i++) {
12331                              this.selectCell(elTargetRow.cells[i]);
12332                          }
12333                      }
12334                      // Select all cells between target cell and anchor cell
12335                      // including the target cell and anchor cell
12336                      else if(nTargetColKeyIndex < oAnchor.colKeyIndex) {
12337                          for(i=nTargetColKeyIndex; i<=oAnchor.colKeyIndex; i++) {
12338                              this.selectCell(elTargetRow.cells[i]);
12339                          }
12340                      }
12341                  }
12342                  // Anchor row is above target row
12343                  else if(oAnchor.recordIndex < nTargetRecordIndex) {
12344                      // Select the cellblock from anchor cell to target cell
12345                      // including the anchor cell and the target cell
12346                      startIndex = Math.min(oAnchor.colKeyIndex, nTargetColKeyIndex);
12347                      endIndex = Math.max(oAnchor.colKeyIndex, nTargetColKeyIndex);
12348  
12349                      for(i=oAnchor.trIndex; i<=nTargetTrIndex; i++) {
12350                          for(j=startIndex; j<=endIndex; j++) {
12351                              this.selectCell(allRows[i].cells[j]);
12352                          }
12353                      }
12354                  }
12355                  // Anchor row is below target row
12356                  else {
12357                      // Select the cellblock from target cell to anchor cell
12358                      // including the target cell and the anchor cell
12359                      startIndex = Math.min(oAnchor.colKeyIndex, nTargetColKeyIndex);
12360                      endIndex = Math.max(oAnchor.colKeyIndex, nTargetColKeyIndex);
12361  
12362                      for(i=nTargetTrIndex; i<=oAnchor.trIndex; i++) {
12363                          for(j=startIndex; j<=endIndex; j++) {
12364                              this.selectCell(allRows[i].cells[j]);
12365                          }
12366                      }
12367                  }
12368              }
12369              // Invalid anchor
12370              else {
12371                  // Set anchor
12372                  this._oAnchorCell = oTargetCell;
12373  
12374                  // Select target only
12375                  this.selectCell(oTargetCell);
12376              }
12377          }
12378          // Only CTRL
12379          else if(bCTRL) {
12380  
12381              // Set anchor
12382              this._oAnchorCell = oTargetCell;
12383  
12384              // Toggle selection of target
12385              if(this.isSelected(oTargetCell)) {
12386                  this.unselectCell(oTargetCell);
12387              }
12388              else {
12389                  this.selectCell(oTargetCell);
12390              }
12391  
12392          }
12393          // Neither SHIFT nor CTRL
12394          else {
12395              this._handleSingleCellSelectionByMouse(oArgs);
12396          }
12397      }
12398  },
12399  
12400  /**
12401   * Determines selection behavior resulting from a key event when selection mode
12402   * is set to "cellblock".
12403   *
12404   * @method _handleCellBlockSelectionByKey
12405   * @param e {HTMLEvent} Event object.
12406   * @private
12407   */
12408  _handleCellBlockSelectionByKey : function(e) {
12409      var nKey = Ev.getCharCode(e);
12410      var bSHIFT = e.shiftKey;
12411      if((nKey == 9) || !bSHIFT) {
12412          this._handleSingleCellSelectionByKey(e);
12413          return;
12414      }
12415  
12416      if((nKey > 36) && (nKey < 41)) {
12417          // Validate trigger
12418          var oTrigger = this._getSelectionTrigger();
12419          // Arrow selection only works if last selected row is on current page
12420          if(!oTrigger) {
12421              return null;
12422          }
12423  
12424          Ev.stopEvent(e);
12425  
12426          // Validate anchor
12427          var oAnchor = this._getSelectionAnchor(oTrigger);
12428  
12429          var i, startIndex, endIndex, elNew, elNewRow;
12430          var allRows = this.getTbodyEl().rows;
12431          var elThisRow = oTrigger.el.parentNode;
12432  
12433          // Determine which direction we're going to
12434  
12435          if(nKey == 40) { // arrow down
12436              // Selecting away from anchor cell
12437              if(oAnchor.recordIndex <= oTrigger.recordIndex) {
12438                  // Select the horiz block on the next row...
12439                  // ...making sure there is room below the trigger row
12440                  elNewRow = this.getNextTrEl(oTrigger.el);
12441                  if(elNewRow) {
12442                      startIndex = oAnchor.colKeyIndex;
12443                      endIndex = oTrigger.colKeyIndex;
12444                      // ...going left
12445                      if(startIndex > endIndex) {
12446                          for(i=startIndex; i>=endIndex; i--) {
12447                              elNew = elNewRow.cells[i];
12448                              this.selectCell(elNew);
12449                          }
12450                      }
12451                      // ... going right
12452                      else {
12453                          for(i=startIndex; i<=endIndex; i++) {
12454                              elNew = elNewRow.cells[i];
12455                              this.selectCell(elNew);
12456                          }
12457                      }
12458                  }
12459              }
12460              // Unselecting towards anchor cell
12461              else {
12462                  startIndex = Math.min(oAnchor.colKeyIndex, oTrigger.colKeyIndex);
12463                  endIndex = Math.max(oAnchor.colKeyIndex, oTrigger.colKeyIndex);
12464                  // Unselect the horiz block on this row towards the next row
12465                  for(i=startIndex; i<=endIndex; i++) {
12466                      this.unselectCell(elThisRow.cells[i]);
12467                  }
12468              }
12469          }
12470          // Arrow up
12471          else if(nKey == 38) {
12472              // Selecting away from anchor cell
12473              if(oAnchor.recordIndex >= oTrigger.recordIndex) {
12474                  // Select the horiz block on the previous row...
12475                  // ...making sure there is room
12476                  elNewRow = this.getPreviousTrEl(oTrigger.el);
12477                  if(elNewRow) {
12478                      // Select in order from anchor to trigger...
12479                      startIndex = oAnchor.colKeyIndex;
12480                      endIndex = oTrigger.colKeyIndex;
12481                      // ...going left
12482                      if(startIndex > endIndex) {
12483                          for(i=startIndex; i>=endIndex; i--) {
12484                              elNew = elNewRow.cells[i];
12485                              this.selectCell(elNew);
12486                          }
12487                      }
12488                      // ... going right
12489                      else {
12490                          for(i=startIndex; i<=endIndex; i++) {
12491                              elNew = elNewRow.cells[i];
12492                              this.selectCell(elNew);
12493                          }
12494                      }
12495                  }
12496              }
12497              // Unselecting towards anchor cell
12498              else {
12499                  startIndex = Math.min(oAnchor.colKeyIndex, oTrigger.colKeyIndex);
12500                  endIndex = Math.max(oAnchor.colKeyIndex, oTrigger.colKeyIndex);
12501                  // Unselect the horiz block on this row towards the previous row
12502                  for(i=startIndex; i<=endIndex; i++) {
12503                      this.unselectCell(elThisRow.cells[i]);
12504                  }
12505              }
12506          }
12507          // Arrow right
12508          else if(nKey == 39) {
12509              // Selecting away from anchor cell
12510              if(oAnchor.colKeyIndex <= oTrigger.colKeyIndex) {
12511                  // Select the next vert block to the right...
12512                  // ...making sure there is room
12513                  if(oTrigger.colKeyIndex < elThisRow.cells.length-1) {
12514                      // Select in order from anchor to trigger...
12515                      startIndex = oAnchor.trIndex;
12516                      endIndex = oTrigger.trIndex;
12517                      // ...going up
12518                      if(startIndex > endIndex) {
12519                          for(i=startIndex; i>=endIndex; i--) {
12520                              elNew = allRows[i].cells[oTrigger.colKeyIndex+1];
12521                              this.selectCell(elNew);
12522                          }
12523                      }
12524                      // ... going down
12525                      else {
12526                          for(i=startIndex; i<=endIndex; i++) {
12527                              elNew = allRows[i].cells[oTrigger.colKeyIndex+1];
12528                              this.selectCell(elNew);
12529                          }
12530                      }
12531                  }
12532              }
12533              // Unselecting towards anchor cell
12534              else {
12535                  // Unselect the vert block on this column towards the right
12536                  startIndex = Math.min(oAnchor.trIndex, oTrigger.trIndex);
12537                  endIndex = Math.max(oAnchor.trIndex, oTrigger.trIndex);
12538                  for(i=startIndex; i<=endIndex; i++) {
12539                      this.unselectCell(allRows[i].cells[oTrigger.colKeyIndex]);
12540                  }
12541              }
12542          }
12543          // Arrow left
12544          else if(nKey == 37) {
12545              // Selecting away from anchor cell
12546              if(oAnchor.colKeyIndex >= oTrigger.colKeyIndex) {
12547                  //Select the previous vert block to the left
12548                  if(oTrigger.colKeyIndex > 0) {
12549                      // Select in order from anchor to trigger...
12550                      startIndex = oAnchor.trIndex;
12551                      endIndex = oTrigger.trIndex;
12552                      // ...going up
12553                      if(startIndex > endIndex) {
12554                          for(i=startIndex; i>=endIndex; i--) {
12555                              elNew = allRows[i].cells[oTrigger.colKeyIndex-1];
12556                              this.selectCell(elNew);
12557                          }
12558                      }
12559                      // ... going down
12560                      else {
12561                          for(i=startIndex; i<=endIndex; i++) {
12562                              elNew = allRows[i].cells[oTrigger.colKeyIndex-1];
12563                              this.selectCell(elNew);
12564                          }
12565                      }
12566                  }
12567              }
12568              // Unselecting towards anchor cell
12569              else {
12570                  // Unselect the vert block on this column towards the left
12571                  startIndex = Math.min(oAnchor.trIndex, oTrigger.trIndex);
12572                  endIndex = Math.max(oAnchor.trIndex, oTrigger.trIndex);
12573                  for(i=startIndex; i<=endIndex; i++) {
12574                      this.unselectCell(allRows[i].cells[oTrigger.colKeyIndex]);
12575                  }
12576              }
12577          }
12578      }
12579  },
12580  
12581  /**
12582   * Determines selection behavior resulting from a mouse event when selection mode
12583   * is set to "cellrange".
12584   *
12585   * @method _handleCellRangeSelectionByMouse
12586   * @param oArgs.event {HTMLEvent} Event object.
12587   * @param oArgs.target {HTMLElement} Target element.
12588   * @private
12589   */
12590  _handleCellRangeSelectionByMouse : function(oArgs) {
12591      var elTarget = oArgs.target;
12592  
12593      // Validate target cell
12594      var elTargetCell = this.getTdEl(elTarget);
12595      if(elTargetCell) {
12596          var e = oArgs.event;
12597          var bSHIFT = e.shiftKey;
12598          var bCTRL = e.ctrlKey || ((navigator.userAgent.toLowerCase().indexOf("mac") != -1) && e.metaKey);
12599  
12600          var elTargetRow = this.getTrEl(elTargetCell);
12601          var nTargetTrIndex = this.getTrIndex(elTargetRow);
12602          var oTargetColumn = this.getColumn(elTargetCell);
12603          var nTargetColKeyIndex = oTargetColumn.getKeyIndex();
12604          var oTargetRecord = this.getRecord(elTargetRow);
12605          var nTargetRecordIndex = this._oRecordSet.getRecordIndex(oTargetRecord);
12606          var oTargetCell = {record:oTargetRecord, column:oTargetColumn};
12607  
12608          var oAnchor = this._getSelectionAnchor();
12609  
12610          var allRows = this.getTbodyEl().rows;
12611          var currentRow, i, j;
12612  
12613          // Both SHIFT and CTRL
12614          if(bSHIFT && bCTRL) {
12615  
12616              // Validate anchor
12617              if(oAnchor) {
12618                  // Anchor is selected
12619                  if(this.isSelected(oAnchor.cell)) {
12620                      // All cells are on the same row
12621                      if(oAnchor.recordIndex === nTargetRecordIndex) {
12622                          // Select all cells between anchor cell and target cell, including target cell
12623                          if(oAnchor.colKeyIndex < nTargetColKeyIndex) {
12624                              for(i=oAnchor.colKeyIndex+1; i<=nTargetColKeyIndex; i++) {
12625                                  this.selectCell(elTargetRow.cells[i]);
12626                              }
12627                          }
12628                          // Select all cells between target cell and anchor cell, including target cell
12629                          else if(nTargetColKeyIndex < oAnchor.colKeyIndex) {
12630                              for(i=nTargetColKeyIndex; i<oAnchor.colKeyIndex; i++) {
12631                                  this.selectCell(elTargetRow.cells[i]);
12632                              }
12633                          }
12634                      }
12635                      // Anchor row is above target row
12636                      else if(oAnchor.recordIndex < nTargetRecordIndex) {
12637                          // Select all cells on anchor row from anchor cell to the end of the row
12638                          for(i=oAnchor.colKeyIndex+1; i<elTargetRow.cells.length; i++) {
12639                              this.selectCell(elTargetRow.cells[i]);
12640                          }
12641  
12642                          // Select all cells on all rows between anchor row and target row
12643                          for(i=oAnchor.trIndex+1; i<nTargetTrIndex; i++) {
12644                              for(j=0; j<allRows[i].cells.length; j++){
12645                                  this.selectCell(allRows[i].cells[j]);
12646                              }
12647                          }
12648  
12649                          // Select all cells on target row from first cell to the target cell
12650                          for(i=0; i<=nTargetColKeyIndex; i++) {
12651                              this.selectCell(elTargetRow.cells[i]);
12652                          }
12653                      }
12654                      // Anchor row is below target row
12655                      else {
12656                          // Select all cells on target row from target cell to the end of the row
12657                          for(i=nTargetColKeyIndex; i<elTargetRow.cells.length; i++) {
12658                              this.selectCell(elTargetRow.cells[i]);
12659                          }
12660  
12661                          // Select all cells on all rows between target row and anchor row
12662                          for(i=nTargetTrIndex+1; i<oAnchor.trIndex; i++) {
12663                              for(j=0; j<allRows[i].cells.length; j++){
12664                                  this.selectCell(allRows[i].cells[j]);
12665                              }
12666                          }
12667  
12668                          // Select all cells on anchor row from first cell to the anchor cell
12669                          for(i=0; i<oAnchor.colKeyIndex; i++) {
12670                              this.selectCell(elTargetRow.cells[i]);
12671                          }
12672                      }
12673                  }
12674                  // Anchor cell is unselected
12675                  else {
12676                      // All cells are on the same row
12677                      if(oAnchor.recordIndex === nTargetRecordIndex) {
12678                          // Unselect all cells between anchor cell and target cell
12679                          if(oAnchor.colKeyIndex < nTargetColKeyIndex) {
12680                              for(i=oAnchor.colKeyIndex+1; i<nTargetColKeyIndex; i++) {
12681                                  this.unselectCell(elTargetRow.cells[i]);
12682                              }
12683                          }
12684                          // Select all cells between target cell and anchor cell
12685                          else if(nTargetColKeyIndex < oAnchor.colKeyIndex) {
12686                              for(i=nTargetColKeyIndex+1; i<oAnchor.colKeyIndex; i++) {
12687                                  this.unselectCell(elTargetRow.cells[i]);
12688                              }
12689                          }
12690                      }
12691                      // Anchor row is above target row
12692                      if(oAnchor.recordIndex < nTargetRecordIndex) {
12693                          // Unselect all cells from anchor cell to target cell
12694                          for(i=oAnchor.trIndex; i<=nTargetTrIndex; i++) {
12695                              currentRow = allRows[i];
12696                              for(j=0; j<currentRow.cells.length; j++) {
12697                                  // This is the anchor row, only unselect cells after the anchor cell
12698                                  if(currentRow.sectionRowIndex === oAnchor.trIndex) {
12699                                      if(j>oAnchor.colKeyIndex) {
12700                                          this.unselectCell(currentRow.cells[j]);
12701                                      }
12702                                  }
12703                                  // This is the target row, only unelect cells before the target cell
12704                                  else if(currentRow.sectionRowIndex === nTargetTrIndex) {
12705                                      if(j<nTargetColKeyIndex) {
12706                                          this.unselectCell(currentRow.cells[j]);
12707                                      }
12708                                  }
12709                                  // Unselect all cells on this row
12710                                  else {
12711                                      this.unselectCell(currentRow.cells[j]);
12712                                  }
12713                              }
12714                          }
12715                      }
12716                      // Anchor row is below target row
12717                      else {
12718                          // Unselect all cells from target cell to anchor cell
12719                          for(i=nTargetTrIndex; i<=oAnchor.trIndex; i++) {
12720                              currentRow = allRows[i];
12721                              for(j=0; j<currentRow.cells.length; j++) {
12722                                  // This is the target row, only unselect cells after the target cell
12723                                  if(currentRow.sectionRowIndex == nTargetTrIndex) {
12724                                      if(j>nTargetColKeyIndex) {
12725                                          this.unselectCell(currentRow.cells[j]);
12726                                      }
12727                                  }
12728                                  // This is the anchor row, only unselect cells before the anchor cell
12729                                  else if(currentRow.sectionRowIndex == oAnchor.trIndex) {
12730                                      if(j<oAnchor.colKeyIndex) {
12731                                          this.unselectCell(currentRow.cells[j]);
12732                                      }
12733                                  }
12734                                  // Unselect all cells on this row
12735                                  else {
12736                                      this.unselectCell(currentRow.cells[j]);
12737                                  }
12738                              }
12739                          }
12740                      }
12741  
12742                      // Select the target cell
12743                      this.selectCell(elTargetCell);
12744                  }
12745              }
12746              // Invalid anchor
12747              else {
12748                  // Set anchor
12749                  this._oAnchorCell = oTargetCell;
12750  
12751                  // Toggle selection of target
12752                  if(this.isSelected(oTargetCell)) {
12753                      this.unselectCell(oTargetCell);
12754                  }
12755                  else {
12756                      this.selectCell(oTargetCell);
12757                  }
12758              }
12759          }
12760           // Only SHIFT
12761          else if(bSHIFT) {
12762  
12763              this.unselectAllCells();
12764  
12765              // Validate anchor
12766              if(oAnchor) {
12767                  // All cells are on the same row
12768                  if(oAnchor.recordIndex === nTargetRecordIndex) {
12769                      // Select all cells between anchor cell and target cell,
12770                      // including the anchor cell and target cell
12771                      if(oAnchor.colKeyIndex < nTargetColKeyIndex) {
12772                          for(i=oAnchor.colKeyIndex; i<=nTargetColKeyIndex; i++) {
12773                              this.selectCell(elTargetRow.cells[i]);
12774                          }
12775                      }
12776                      // Select all cells between target cell and anchor cell
12777                      // including the target cell and anchor cell
12778                      else if(nTargetColKeyIndex < oAnchor.colKeyIndex) {
12779                          for(i=nTargetColKeyIndex; i<=oAnchor.colKeyIndex; i++) {
12780                              this.selectCell(elTargetRow.cells[i]);
12781                          }
12782                      }
12783                  }
12784                  // Anchor row is above target row
12785                  else if(oAnchor.recordIndex < nTargetRecordIndex) {
12786                      // Select all cells from anchor cell to target cell
12787                      // including the anchor cell and target cell
12788                      for(i=oAnchor.trIndex; i<=nTargetTrIndex; i++) {
12789                          currentRow = allRows[i];
12790                          for(j=0; j<currentRow.cells.length; j++) {
12791                              // This is the anchor row, only select the anchor cell and after
12792                              if(currentRow.sectionRowIndex == oAnchor.trIndex) {
12793                                  if(j>=oAnchor.colKeyIndex) {
12794                                      this.selectCell(currentRow.cells[j]);
12795                                  }
12796                              }
12797                              // This is the target row, only select the target cell and before
12798                              else if(currentRow.sectionRowIndex == nTargetTrIndex) {
12799                                  if(j<=nTargetColKeyIndex) {
12800                                      this.selectCell(currentRow.cells[j]);
12801                                  }
12802                              }
12803                              // Select all cells on this row
12804                              else {
12805                                  this.selectCell(currentRow.cells[j]);
12806                              }
12807                          }
12808                      }
12809                  }
12810                  // Anchor row is below target row
12811                  else {
12812                      // Select all cells from target cell to anchor cell,
12813                      // including the target cell and anchor cell
12814                      for(i=nTargetTrIndex; i<=oAnchor.trIndex; i++) {
12815                          currentRow = allRows[i];
12816                          for(j=0; j<currentRow.cells.length; j++) {
12817                              // This is the target row, only select the target cell and after
12818                              if(currentRow.sectionRowIndex == nTargetTrIndex) {
12819                                  if(j>=nTargetColKeyIndex) {
12820                                      this.selectCell(currentRow.cells[j]);
12821                                  }
12822                              }
12823                              // This is the anchor row, only select the anchor cell and before
12824                              else if(currentRow.sectionRowIndex == oAnchor.trIndex) {
12825                                  if(j<=oAnchor.colKeyIndex) {
12826                                      this.selectCell(currentRow.cells[j]);
12827                                  }
12828                              }
12829                              // Select all cells on this row
12830                              else {
12831                                  this.selectCell(currentRow.cells[j]);
12832                              }
12833                          }
12834                      }
12835                  }
12836              }
12837              // Invalid anchor
12838              else {
12839                  // Set anchor
12840                  this._oAnchorCell = oTargetCell;
12841  
12842                  // Select target only
12843                  this.selectCell(oTargetCell);
12844              }
12845  
12846  
12847          }
12848          // Only CTRL
12849          else if(bCTRL) {
12850  
12851              // Set anchor
12852              this._oAnchorCell = oTargetCell;
12853  
12854              // Toggle selection of target
12855              if(this.isSelected(oTargetCell)) {
12856                  this.unselectCell(oTargetCell);
12857              }
12858              else {
12859                  this.selectCell(oTargetCell);
12860              }
12861  
12862          }
12863          // Neither SHIFT nor CTRL
12864          else {
12865              this._handleSingleCellSelectionByMouse(oArgs);
12866          }
12867      }
12868  },
12869  
12870  /**
12871   * Determines selection behavior resulting from a key event when selection mode
12872   * is set to "cellrange".
12873   *
12874   * @method _handleCellRangeSelectionByKey
12875   * @param e {HTMLEvent} Event object.
12876   * @private
12877   */
12878  _handleCellRangeSelectionByKey : function(e) {
12879      var nKey = Ev.getCharCode(e);
12880      var bSHIFT = e.shiftKey;
12881      if((nKey == 9) || !bSHIFT) {
12882          this._handleSingleCellSelectionByKey(e);
12883          return;
12884      }
12885  
12886      if((nKey > 36) && (nKey < 41)) {
12887          // Validate trigger
12888          var oTrigger = this._getSelectionTrigger();
12889          // Arrow selection only works if last selected row is on current page
12890          if(!oTrigger) {
12891              return null;
12892          }
12893  
12894          Ev.stopEvent(e);
12895  
12896          // Validate anchor
12897          var oAnchor = this._getSelectionAnchor(oTrigger);
12898  
12899          var i, elNewRow, elNew;
12900          var allRows = this.getTbodyEl().rows;
12901          var elThisRow = oTrigger.el.parentNode;
12902  
12903          // Arrow down
12904          if(nKey == 40) {
12905              elNewRow = this.getNextTrEl(oTrigger.el);
12906  
12907              // Selecting away from anchor cell
12908              if(oAnchor.recordIndex <= oTrigger.recordIndex) {
12909                  // Select all cells to the end of this row
12910                  for(i=oTrigger.colKeyIndex+1; i<elThisRow.cells.length; i++){
12911                      elNew = elThisRow.cells[i];
12912                      this.selectCell(elNew);
12913                  }
12914  
12915                  // Select some of the cells on the next row down
12916                  if(elNewRow) {
12917                      for(i=0; i<=oTrigger.colKeyIndex; i++){
12918                          elNew = elNewRow.cells[i];
12919                          this.selectCell(elNew);
12920                      }
12921                  }
12922              }
12923              // Unselecting towards anchor cell
12924              else {
12925                  // Unselect all cells to the end of this row
12926                  for(i=oTrigger.colKeyIndex; i<elThisRow.cells.length; i++){
12927                      this.unselectCell(elThisRow.cells[i]);
12928                  }
12929  
12930                  // Unselect some of the cells on the next row down
12931                  if(elNewRow) {
12932                      for(i=0; i<oTrigger.colKeyIndex; i++){
12933                          this.unselectCell(elNewRow.cells[i]);
12934                      }
12935                  }
12936              }
12937          }
12938          // Arrow up
12939          else if(nKey == 38) {
12940              elNewRow = this.getPreviousTrEl(oTrigger.el);
12941  
12942              // Selecting away from anchor cell
12943              if(oAnchor.recordIndex >= oTrigger.recordIndex) {
12944                  // Select all the cells to the beginning of this row
12945                  for(i=oTrigger.colKeyIndex-1; i>-1; i--){
12946                      elNew = elThisRow.cells[i];
12947                      this.selectCell(elNew);
12948                  }
12949  
12950                  // Select some of the cells from the end of the previous row
12951                  if(elNewRow) {
12952                      for(i=elThisRow.cells.length-1; i>=oTrigger.colKeyIndex; i--){
12953                          elNew = elNewRow.cells[i];
12954                          this.selectCell(elNew);
12955                      }
12956                  }
12957              }
12958              // Unselecting towards anchor cell
12959              else {
12960                  // Unselect all the cells to the beginning of this row
12961                  for(i=oTrigger.colKeyIndex; i>-1; i--){
12962                      this.unselectCell(elThisRow.cells[i]);
12963                  }
12964  
12965                  // Unselect some of the cells from the end of the previous row
12966                  if(elNewRow) {
12967                      for(i=elThisRow.cells.length-1; i>oTrigger.colKeyIndex; i--){
12968                          this.unselectCell(elNewRow.cells[i]);
12969                      }
12970                  }
12971              }
12972          }
12973          // Arrow right
12974          else if(nKey == 39) {
12975              elNewRow = this.getNextTrEl(oTrigger.el);
12976  
12977              // Selecting away from anchor cell
12978              if(oAnchor.recordIndex < oTrigger.recordIndex) {
12979                  // Select the next cell to the right
12980                  if(oTrigger.colKeyIndex < elThisRow.cells.length-1) {
12981                      elNew = elThisRow.cells[oTrigger.colKeyIndex+1];
12982                      this.selectCell(elNew);
12983                  }
12984                  // Select the first cell of the next row
12985                  else if(elNewRow) {
12986                      elNew = elNewRow.cells[0];
12987                      this.selectCell(elNew);
12988                  }
12989              }
12990              // Unselecting towards anchor cell
12991              else if(oAnchor.recordIndex > oTrigger.recordIndex) {
12992                  this.unselectCell(elThisRow.cells[oTrigger.colKeyIndex]);
12993  
12994                  // Unselect this cell towards the right
12995                  if(oTrigger.colKeyIndex < elThisRow.cells.length-1) {
12996                  }
12997                  // Unselect this cells towards the first cell of the next row
12998                  else {
12999                  }
13000              }
13001              // Anchor is on this row
13002              else {
13003                  // Selecting away from anchor
13004                  if(oAnchor.colKeyIndex <= oTrigger.colKeyIndex) {
13005                      // Select the next cell to the right
13006                      if(oTrigger.colKeyIndex < elThisRow.cells.length-1) {
13007                          elNew = elThisRow.cells[oTrigger.colKeyIndex+1];
13008                          this.selectCell(elNew);
13009                      }
13010                      // Select the first cell on the next row
13011                      else if(oTrigger.trIndex < allRows.length-1){
13012                          elNew = elNewRow.cells[0];
13013                          this.selectCell(elNew);
13014                      }
13015                  }
13016                  // Unselecting towards anchor
13017                  else {
13018                      // Unselect this cell towards the right
13019                      this.unselectCell(elThisRow.cells[oTrigger.colKeyIndex]);
13020                  }
13021              }
13022          }
13023          // Arrow left
13024          else if(nKey == 37) {
13025              elNewRow = this.getPreviousTrEl(oTrigger.el);
13026  
13027              // Unselecting towards the anchor
13028              if(oAnchor.recordIndex < oTrigger.recordIndex) {
13029                  this.unselectCell(elThisRow.cells[oTrigger.colKeyIndex]);
13030  
13031                  // Unselect this cell towards the left
13032                  if(oTrigger.colKeyIndex > 0) {
13033                  }
13034                  // Unselect this cell towards the last cell of the previous row
13035                  else {
13036                  }
13037              }
13038              // Selecting towards the anchor
13039              else if(oAnchor.recordIndex > oTrigger.recordIndex) {
13040                  // Select the next cell to the left
13041                  if(oTrigger.colKeyIndex > 0) {
13042                      elNew = elThisRow.cells[oTrigger.colKeyIndex-1];
13043                      this.selectCell(elNew);
13044                  }
13045                  // Select the last cell of the previous row
13046                  else if(oTrigger.trIndex > 0){
13047                      elNew = elNewRow.cells[elNewRow.cells.length-1];
13048                      this.selectCell(elNew);
13049                  }
13050              }
13051              // Anchor is on this row
13052              else {
13053                  // Selecting away from anchor cell
13054                  if(oAnchor.colKeyIndex >= oTrigger.colKeyIndex) {
13055                      // Select the next cell to the left
13056                      if(oTrigger.colKeyIndex > 0) {
13057                          elNew = elThisRow.cells[oTrigger.colKeyIndex-1];
13058                          this.selectCell(elNew);
13059                      }
13060                      // Select the last cell of the previous row
13061                      else if(oTrigger.trIndex > 0){
13062                          elNew = elNewRow.cells[elNewRow.cells.length-1];
13063                          this.selectCell(elNew);
13064                      }
13065                  }
13066                  // Unselecting towards anchor cell
13067                  else {
13068                      this.unselectCell(elThisRow.cells[oTrigger.colKeyIndex]);
13069  
13070                      // Unselect this cell towards the left
13071                      if(oTrigger.colKeyIndex > 0) {
13072                      }
13073                      // Unselect this cell towards the last cell of the previous row
13074                      else {
13075                      }
13076                  }
13077              }
13078          }
13079      }
13080  },
13081  
13082  /**
13083   * Determines selection behavior resulting from a mouse event when selection mode
13084   * is set to "singlecell".
13085   *
13086   * @method _handleSingleCellSelectionByMouse
13087   * @param oArgs.event {HTMLEvent} Event object.
13088   * @param oArgs.target {HTMLElement} Target element.
13089   * @private
13090   */
13091  _handleSingleCellSelectionByMouse : function(oArgs) {
13092      var elTarget = oArgs.target;
13093  
13094      // Validate target cell
13095      var elTargetCell = this.getTdEl(elTarget);
13096      if(elTargetCell) {
13097          var elTargetRow = this.getTrEl(elTargetCell);
13098          var oTargetRecord = this.getRecord(elTargetRow);
13099          var oTargetColumn = this.getColumn(elTargetCell);
13100          var oTargetCell = {record:oTargetRecord, column:oTargetColumn};
13101  
13102          // Set anchor
13103          this._oAnchorCell = oTargetCell;
13104  
13105          // Select only target
13106          this.unselectAllCells();
13107          this.selectCell(oTargetCell);
13108      }
13109  },
13110  
13111  /**
13112   * Determines selection behavior resulting from a key event when selection mode
13113   * is set to "singlecell".
13114   *
13115   * @method _handleSingleCellSelectionByKey
13116   * @param e {HTMLEvent} Event object.
13117   * @private
13118   */
13119  _handleSingleCellSelectionByKey : function(e) {
13120      var nKey = Ev.getCharCode(e);
13121      if((nKey == 9) || ((nKey > 36) && (nKey < 41))) {
13122          var bSHIFT = e.shiftKey;
13123  
13124          // Validate trigger
13125          var oTrigger = this._getSelectionTrigger();
13126          // Arrow selection only works if last selected row is on current page
13127          if(!oTrigger) {
13128              return null;
13129          }
13130  
13131          // Determine the new cell to select
13132          var elNew;
13133          if(nKey == 40) { // Arrow down
13134              elNew = this.getBelowTdEl(oTrigger.el);
13135  
13136              // Validate new cell
13137              if(elNew === null) {
13138                  //TODO: wrap around to first tr on current page
13139  
13140                  //TODO: wrap forward to first tr of next page
13141  
13142                  // Bottom selection is sticky
13143                  elNew = oTrigger.el;
13144              }
13145          }
13146          else if(nKey == 38) { // Arrow up
13147              elNew = this.getAboveTdEl(oTrigger.el);
13148  
13149              // Validate new cell
13150              if(elNew === null) {
13151                  //TODO: wrap around to last tr on current page
13152  
13153                  //TODO: wrap back to last tr of previous page
13154  
13155                  // Top selection is sticky
13156                  elNew = oTrigger.el;
13157              }
13158          }
13159          else if((nKey == 39) || (!bSHIFT && (nKey == 9))) { // Arrow right or tab
13160              elNew = this.getNextTdEl(oTrigger.el);
13161  
13162              // Validate new cell
13163              if(elNew === null) {
13164                  //TODO: wrap around to first td on current page
13165  
13166                  //TODO: wrap forward to first td of next page
13167  
13168                  // Top-left selection is sticky, and release TAB focus
13169                  //elNew = oTrigger.el;
13170                  return;
13171              }
13172          }
13173          else if((nKey == 37) || (bSHIFT && (nKey == 9))) { // Arrow left or shift-tab
13174              elNew = this.getPreviousTdEl(oTrigger.el);
13175  
13176              // Validate new cell
13177              if(elNew === null) {
13178                  //TODO: wrap around to last td on current page
13179  
13180                  //TODO: wrap back to last td of previous page
13181  
13182                  // Bottom-right selection is sticky, and release TAB focus
13183                  //elNew = oTrigger.el;
13184                  return;
13185              }
13186          }
13187  
13188          Ev.stopEvent(e);
13189          
13190          // Unselect all cells
13191          this.unselectAllCells();
13192  
13193          // Select the new cell
13194          this.selectCell(elNew);
13195  
13196          // Set new anchor
13197          this._oAnchorCell = {record:this.getRecord(elNew), column:this.getColumn(elNew)};
13198      }
13199  },
13200  
13201  /**
13202   * Returns array of selected TR elements on the page.
13203   *
13204   * @method getSelectedTrEls
13205   * @return {HTMLElement[]} Array of selected TR elements.
13206   */
13207  getSelectedTrEls : function() {
13208      return Dom.getElementsByClassName(DT.CLASS_SELECTED,"tr",this._elTbody);
13209  },
13210  
13211  /**
13212   * Sets given row to the selected state.
13213   *
13214   * @method selectRow
13215   * @param row {HTMLElement | String | YAHOO.widget.Record | Number} HTML element
13216   * reference or ID string, Record instance, or RecordSet position index.
13217   */
13218  selectRow : function(row) {
13219      var oRecord, elRow;
13220  
13221      if(row instanceof YAHOO.widget.Record) {
13222          oRecord = this._oRecordSet.getRecord(row);
13223          elRow = this.getTrEl(oRecord);
13224      }
13225      else if(lang.isNumber(row)) {
13226          oRecord = this.getRecord(row);
13227          elRow = this.getTrEl(oRecord);
13228      }
13229      else {
13230          elRow = this.getTrEl(row);
13231          oRecord = this.getRecord(elRow);
13232      }
13233  
13234      if(oRecord) {
13235          // Update selection trackers
13236          var tracker = this._aSelections || [];
13237          var sRecordId = oRecord.getId();
13238          var index = -1;
13239  
13240          // Remove if already there:
13241          // Use Array.indexOf if available...
13242          /*if(tracker.indexOf && (tracker.indexOf(sRecordId) >  -1)) {
13243              tracker.splice(tracker.indexOf(sRecordId),1);
13244          }*/
13245          if(tracker.indexOf) {
13246              index = tracker.indexOf(sRecordId);
13247              
13248          }
13249          // ...or do it the old-fashioned way
13250          else {
13251              for(var j=tracker.length-1; j>-1; j--) {
13252                  if(tracker[j] === sRecordId){
13253                      index = j;
13254                      break;
13255                  }
13256              }
13257          }
13258          if(index > -1) {
13259              tracker.splice(index,1);
13260          }
13261          
13262          // Add to the end
13263          tracker.push(sRecordId);
13264          this._aSelections = tracker;
13265  
13266          // Update trackers
13267          if(!this._oAnchorRecord) {
13268              this._oAnchorRecord = oRecord;
13269          }
13270  
13271          // Update UI
13272          if(elRow) {
13273              Dom.addClass(elRow, DT.CLASS_SELECTED);
13274          }
13275  
13276          this.fireEvent("rowSelectEvent", {record:oRecord, el:elRow});
13277      }
13278      else {
13279      }
13280  },
13281  
13282  /**
13283   * Sets given row to the unselected state.
13284   *
13285   * @method unselectRow
13286   * @param row {HTMLElement | String | YAHOO.widget.Record | Number} HTML element
13287   * reference or ID string, Record instance, or RecordSet position index.
13288   */
13289  unselectRow : function(row) {
13290      var elRow = this.getTrEl(row);
13291  
13292      var oRecord;
13293      if(row instanceof YAHOO.widget.Record) {
13294          oRecord = this._oRecordSet.getRecord(row);
13295      }
13296      else if(lang.isNumber(row)) {
13297          oRecord = this.getRecord(row);
13298      }
13299      else {
13300          oRecord = this.getRecord(elRow);
13301      }
13302  
13303      if(oRecord) {
13304          // Update selection trackers
13305          var tracker = this._aSelections || [];
13306          var sRecordId = oRecord.getId();
13307          var index = -1;
13308  
13309          // Use Array.indexOf if available...
13310          if(tracker.indexOf) {
13311              index = tracker.indexOf(sRecordId);
13312          }
13313          // ...or do it the old-fashioned way
13314          else {
13315              for(var j=tracker.length-1; j>-1; j--) {
13316                  if(tracker[j] === sRecordId){
13317                      index = j;
13318                      break;
13319                  }
13320              }
13321          }
13322          if(index > -1) {
13323              // Update tracker
13324              tracker.splice(index,1);
13325              this._aSelections = tracker;
13326  
13327              // Update the UI
13328              Dom.removeClass(elRow, DT.CLASS_SELECTED);
13329  
13330              this.fireEvent("rowUnselectEvent", {record:oRecord, el:elRow});
13331  
13332              return;
13333          }
13334      }
13335  },
13336  
13337  /**
13338   * Clears out all row selections.
13339   *
13340   * @method unselectAllRows
13341   */
13342  unselectAllRows : function() {
13343      // Remove all rows from tracker
13344      var tracker = this._aSelections || [],
13345          recId,
13346          removed = [];
13347      for(var j=tracker.length-1; j>-1; j--) {
13348         if(lang.isString(tracker[j])){
13349              recId = tracker.splice(j,1);
13350              removed[removed.length] = this.getRecord(lang.isArray(recId) ? recId[0] : recId);
13351          }
13352      }
13353  
13354      // Update tracker
13355      this._aSelections = tracker;
13356  
13357      // Update UI
13358      this._unselectAllTrEls();
13359  
13360      this.fireEvent("unselectAllRowsEvent", {records: removed});
13361  },
13362  
13363  /**
13364   * Convenience method to remove the class YAHOO.widget.DataTable.CLASS_SELECTED
13365   * from all TD elements in the internal tracker.
13366   *
13367   * @method _unselectAllTdEls
13368   * @private
13369   */
13370  _unselectAllTdEls : function() {
13371      var selectedCells = Dom.getElementsByClassName(DT.CLASS_SELECTED,"td",this._elTbody);
13372      Dom.removeClass(selectedCells, DT.CLASS_SELECTED);
13373  },
13374  
13375  /**
13376   * Returns array of selected TD elements on the page.
13377   *
13378   * @method getSelectedTdEls
13379   * @return {HTMLElement[]} Array of selected TD elements.
13380   */
13381  getSelectedTdEls : function() {
13382      return Dom.getElementsByClassName(DT.CLASS_SELECTED,"td",this._elTbody);
13383  },
13384  
13385  /**
13386   * Sets given cell to the selected state.
13387   *
13388   * @method selectCell
13389   * @param cell {HTMLElement | String | Object} TD element or child of a TD element, or
13390   * object literal of syntax {record:oRecord, column:oColumn}.
13391   */
13392  selectCell : function(cell) {
13393  //TODO: accept {record} in selectCell()
13394      var elCell = this.getTdEl(cell);
13395  
13396      if(elCell) {
13397          var oRecord = this.getRecord(elCell);
13398          var oColumn = this.getColumn(this.getCellIndex(elCell));
13399          var sColumnKey = oColumn.getKey();
13400  
13401          if(oRecord && sColumnKey) {
13402              // Get Record ID
13403              var tracker = this._aSelections || [];
13404              var sRecordId = oRecord.getId();
13405  
13406              // Remove if there
13407              for(var j=tracker.length-1; j>-1; j--) {
13408                 if((tracker[j].recordId === sRecordId) && (tracker[j].columnKey === sColumnKey)){
13409                      tracker.splice(j,1);
13410                      break;
13411                  }
13412              }
13413  
13414              // Add to the end
13415              tracker.push({recordId:sRecordId, columnKey:sColumnKey});
13416  
13417              // Update trackers
13418              this._aSelections = tracker;
13419              if(!this._oAnchorCell) {
13420                  this._oAnchorCell = {record:oRecord, column:oColumn};
13421              }
13422  
13423              // Update the UI
13424              Dom.addClass(elCell, DT.CLASS_SELECTED);
13425  
13426              this.fireEvent("cellSelectEvent", {record:oRecord, column:oColumn, key: sColumnKey, el:elCell});
13427              return;
13428          }
13429      }
13430  },
13431  
13432  /**
13433   * Sets given cell to the unselected state.
13434   *
13435   * @method unselectCell
13436   * @param cell {HTMLElement | String | Object} TD element or child of a TD element, or
13437   * object literal of syntax {record:oRecord, column:oColumn}.
13438   * @param cell {HTMLElement | String} DOM element reference or ID string
13439   * to DataTable page element or RecordSet index.
13440   */
13441  unselectCell : function(cell) {
13442      var elCell = this.getTdEl(cell);
13443  
13444      if(elCell) {
13445          var oRecord = this.getRecord(elCell);
13446          var oColumn = this.getColumn(this.getCellIndex(elCell));
13447          var sColumnKey = oColumn.getKey();
13448  
13449          if(oRecord && sColumnKey) {
13450              // Get Record ID
13451              var tracker = this._aSelections || [];
13452              var id = oRecord.getId();
13453  
13454              // Is it selected?
13455              for(var j=tracker.length-1; j>-1; j--) {
13456                  if((tracker[j].recordId === id) && (tracker[j].columnKey === sColumnKey)){
13457                      // Remove from tracker
13458                      tracker.splice(j,1);
13459  
13460                      // Update tracker
13461                      this._aSelections = tracker;
13462  
13463                      // Update the UI
13464                      Dom.removeClass(elCell, DT.CLASS_SELECTED);
13465  
13466                      this.fireEvent("cellUnselectEvent", {record:oRecord, column: oColumn, key:sColumnKey, el:elCell});
13467                      return;
13468                  }
13469              }
13470          }
13471      }
13472  },
13473  
13474  /**
13475   * Clears out all cell selections.
13476   *
13477   * @method unselectAllCells
13478   */
13479  unselectAllCells : function() {
13480      // Remove all cells from tracker
13481      var tracker = this._aSelections || [];
13482      for(var j=tracker.length-1; j>-1; j--) {
13483         if(lang.isObject(tracker[j])){
13484              tracker.splice(j,1);
13485          }
13486      }
13487  
13488      // Update tracker
13489      this._aSelections = tracker;
13490  
13491      // Update UI
13492      this._unselectAllTdEls();
13493  
13494      //TODO: send data to unselectAllCellsEvent handler
13495      this.fireEvent("unselectAllCellsEvent");
13496  },
13497  
13498  /**
13499   * Returns true if given item is selected, false otherwise.
13500   *
13501   * @method isSelected
13502   * @param o {String | HTMLElement | YAHOO.widget.Record | Number
13503   * {record:YAHOO.widget.Record, column:YAHOO.widget.Column} } TR or TD element by
13504   * reference or ID string, a Record instance, a RecordSet position index,
13505   * or an object literal representation
13506   * of a cell.
13507   * @return {Boolean} True if item is selected.
13508   */
13509  isSelected : function(o) {
13510      if(o && (o.ownerDocument == document)) {
13511          return (Dom.hasClass(this.getTdEl(o),DT.CLASS_SELECTED) || Dom.hasClass(this.getTrEl(o),DT.CLASS_SELECTED));
13512      }
13513      else {
13514          var oRecord, sRecordId, j;
13515          var tracker = this._aSelections;
13516          if(tracker && tracker.length > 0) {
13517              // Looking for a Record?
13518              if(o instanceof YAHOO.widget.Record) {
13519                  oRecord = o;
13520              }
13521              else if(lang.isNumber(o)) {
13522                  oRecord = this.getRecord(o);
13523              }
13524              if(oRecord) {
13525                  sRecordId = oRecord.getId();
13526  
13527                  // Is it there?
13528                  // Use Array.indexOf if available...
13529                  if(tracker.indexOf) {
13530                      if(tracker.indexOf(sRecordId) >  -1) {
13531                          return true;
13532                      }
13533                  }
13534                  // ...or do it the old-fashioned way
13535                  else {
13536                      for(j=tracker.length-1; j>-1; j--) {
13537                         if(tracker[j] === sRecordId){
13538                          return true;
13539                         }
13540                      }
13541                  }
13542              }
13543              // Looking for a cell
13544              else if(o.record && o.column){
13545                  sRecordId = o.record.getId();
13546                  var sColumnKey = o.column.getKey();
13547  
13548                  for(j=tracker.length-1; j>-1; j--) {
13549                      if((tracker[j].recordId === sRecordId) && (tracker[j].columnKey === sColumnKey)){
13550                          return true;
13551                      }
13552                  }
13553              }
13554          }
13555      }
13556      return false;
13557  },
13558  
13559  /**
13560   * Returns selected rows as an array of Record IDs.
13561   *
13562   * @method getSelectedRows
13563   * @return {String[]} Array of selected rows by Record ID.
13564   */
13565  getSelectedRows : function() {
13566      var aSelectedRows = [];
13567      var tracker = this._aSelections || [];
13568      for(var j=0; j<tracker.length; j++) {
13569         if(lang.isString(tracker[j])){
13570              aSelectedRows.push(tracker[j]);
13571          }
13572      }
13573      return aSelectedRows;
13574  },
13575  
13576  /**
13577   * Returns selected cells as an array of object literals:
13578   *     {recordId:sRecordId, columnKey:sColumnKey}.
13579   *
13580   * @method getSelectedCells
13581   * @return {Object[]} Array of selected cells by Record ID and Column ID.
13582   */
13583  getSelectedCells : function() {
13584      var aSelectedCells = [];
13585      var tracker = this._aSelections || [];
13586      for(var j=0; j<tracker.length; j++) {
13587         if(tracker[j] && lang.isObject(tracker[j])){
13588              aSelectedCells.push(tracker[j]);
13589          }
13590      }
13591      return aSelectedCells;
13592  },
13593  
13594  /**
13595   * Returns last selected Record ID.
13596   *
13597   * @method getLastSelectedRecord
13598   * @return {String} Record ID of last selected row.
13599   */
13600  getLastSelectedRecord : function() {
13601      var tracker = this._aSelections;
13602      if(tracker && tracker.length > 0) {
13603          for(var i=tracker.length-1; i>-1; i--) {
13604             if(lang.isString(tracker[i])){
13605                  return tracker[i];
13606              }
13607          }
13608      }
13609  },
13610  
13611  /**
13612   * Returns last selected cell as an object literal:
13613   *     {recordId:sRecordId, columnKey:sColumnKey}.
13614   *
13615   * @method getLastSelectedCell
13616   * @return {Object} Object literal representation of a cell.
13617   */
13618  getLastSelectedCell : function() {
13619      var tracker = this._aSelections;
13620      if(tracker && tracker.length > 0) {
13621          for(var i=tracker.length-1; i>-1; i--) {
13622             if(tracker[i].recordId && tracker[i].columnKey){
13623                  return tracker[i];
13624              }
13625          }
13626      }
13627  },
13628  
13629  /**
13630   * Assigns the class YAHOO.widget.DataTable.CLASS_HIGHLIGHTED to the given row.
13631   *
13632   * @method highlightRow
13633   * @param row {HTMLElement | String} DOM element reference or ID string.
13634   */
13635  highlightRow : function(row) {
13636      var elRow = this.getTrEl(row);
13637  
13638      if(elRow) {
13639          // Make sure previous row is unhighlighted
13640  /*        if(this._sLastHighlightedTrElId) {
13641              Dom.removeClass(this._sLastHighlightedTrElId,DT.CLASS_HIGHLIGHTED);
13642          }*/
13643          var oRecord = this.getRecord(elRow);
13644          Dom.addClass(elRow,DT.CLASS_HIGHLIGHTED);
13645          //this._sLastHighlightedTrElId = elRow.id;
13646          this.fireEvent("rowHighlightEvent", {record:oRecord, el:elRow});
13647          return;
13648      }
13649  },
13650  
13651  /**
13652   * Removes the class YAHOO.widget.DataTable.CLASS_HIGHLIGHTED from the given row.
13653   *
13654   * @method unhighlightRow
13655   * @param row {HTMLElement | String} DOM element reference or ID string.
13656   */
13657  unhighlightRow : function(row) {
13658      var elRow = this.getTrEl(row);
13659  
13660      if(elRow) {
13661          var oRecord = this.getRecord(elRow);
13662          Dom.removeClass(elRow,DT.CLASS_HIGHLIGHTED);
13663          this.fireEvent("rowUnhighlightEvent", {record:oRecord, el:elRow});
13664          return;
13665      }
13666  },
13667  
13668  /**
13669   * Assigns the class YAHOO.widget.DataTable.CLASS_HIGHLIGHTED to the given cell.
13670   *
13671   * @method highlightCell
13672   * @param cell {HTMLElement | String} DOM element reference or ID string.
13673   */
13674  highlightCell : function(cell) {
13675      var elCell = this.getTdEl(cell);
13676  
13677      if(elCell) {
13678          // Make sure previous cell is unhighlighted
13679          if(this._elLastHighlightedTd) {
13680              this.unhighlightCell(this._elLastHighlightedTd);
13681          }
13682  
13683          var oRecord = this.getRecord(elCell);
13684          var oColumn = this.getColumn(this.getCellIndex(elCell));
13685          var sColumnKey = oColumn.getKey();
13686          Dom.addClass(elCell,DT.CLASS_HIGHLIGHTED);
13687          this._elLastHighlightedTd = elCell;
13688          this.fireEvent("cellHighlightEvent", {record:oRecord, column:oColumn, key:sColumnKey, el:elCell});
13689          return;
13690      }
13691  },
13692  
13693  /**
13694   * Removes the class YAHOO.widget.DataTable.CLASS_HIGHLIGHTED from the given cell.
13695   *
13696   * @method unhighlightCell
13697   * @param cell {HTMLElement | String} DOM element reference or ID string.
13698   */
13699  unhighlightCell : function(cell) {
13700      var elCell = this.getTdEl(cell);
13701  
13702      if(elCell) {
13703          var oRecord = this.getRecord(elCell);
13704          Dom.removeClass(elCell,DT.CLASS_HIGHLIGHTED);
13705          this._elLastHighlightedTd = null;
13706          this.fireEvent("cellUnhighlightEvent", {record:oRecord, column:this.getColumn(this.getCellIndex(elCell)), key:this.getColumn(this.getCellIndex(elCell)).getKey(), el:elCell});
13707          return;
13708      }
13709  },
13710  
13711  
13712  
13713  
13714  
13715  
13716  
13717  
13718  
13719  
13720  
13721  
13722  
13723  
13724  
13725  
13726  
13727  
13728  
13729  
13730  
13731  
13732  
13733  
13734  
13735  
13736  
13737  
13738  
13739  
13740  
13741  
13742  
13743  
13744  
13745  
13746  
13747  
13748  
13749  
13750  
13751  
13752  
13753  
13754  
13755  // INLINE EDITING
13756  
13757  /**
13758   * Assigns CellEditor instance to existing Column.
13759   * @method addCellEditor
13760   * @param oColumn {YAHOO.widget.Column} Column instance.
13761   * @param oEditor {YAHOO.wdiget.CellEditor} CellEditor instance.
13762   */
13763  addCellEditor : function(oColumn, oEditor) {
13764      oColumn.editor = oEditor;
13765      oColumn.editor.subscribe("showEvent", this._onEditorShowEvent, this, true);
13766      oColumn.editor.subscribe("keydownEvent", this._onEditorKeydownEvent, this, true);
13767      oColumn.editor.subscribe("revertEvent", this._onEditorRevertEvent, this, true);
13768      oColumn.editor.subscribe("saveEvent", this._onEditorSaveEvent, this, true);
13769      oColumn.editor.subscribe("cancelEvent", this._onEditorCancelEvent, this, true);
13770      oColumn.editor.subscribe("blurEvent", this._onEditorBlurEvent, this, true);
13771      oColumn.editor.subscribe("blockEvent", this._onEditorBlockEvent, this, true);
13772      oColumn.editor.subscribe("unblockEvent", this._onEditorUnblockEvent, this, true);
13773  },
13774  
13775  /**
13776   * Returns current CellEditor instance, or null.
13777   * @method getCellEditor
13778   * @return {YAHOO.widget.CellEditor} CellEditor instance.
13779   */
13780  getCellEditor : function() {
13781      return this._oCellEditor;
13782  },
13783  
13784  
13785  /**
13786   * Activates and shows CellEditor instance for the given cell while deactivating and
13787   * canceling previous CellEditor. It is baked into DataTable that only one CellEditor
13788   * can be active at any given time. 
13789   *
13790   * @method showCellEditor
13791   * @param elCell {HTMLElement | String} Cell to edit.
13792   */
13793  showCellEditor : function(elCell, oRecord, oColumn) {
13794      // Get a particular CellEditor
13795      elCell = this.getTdEl(elCell);
13796      if(elCell) {
13797          oColumn = this.getColumn(elCell);
13798          if(oColumn && oColumn.editor) {
13799              var oCellEditor = this._oCellEditor;
13800              // Clean up active CellEditor
13801              if(oCellEditor) {
13802                  if(this._oCellEditor.cancel) {
13803                      this._oCellEditor.cancel();
13804                  }
13805                  else if(oCellEditor.isActive) {
13806                      this.cancelCellEditor();
13807                  }
13808              }
13809              
13810              if(oColumn.editor instanceof YAHOO.widget.BaseCellEditor) {
13811                  // Get CellEditor
13812                  oCellEditor = oColumn.editor;
13813                  var ok = oCellEditor.attach(this, elCell);
13814                  if(ok) {
13815                      oCellEditor.render();
13816                      oCellEditor.move();
13817                      ok = this.doBeforeShowCellEditor(oCellEditor);
13818                      if(ok) {
13819                          oCellEditor.show();
13820                          this._oCellEditor = oCellEditor;
13821                      }
13822                  }
13823              }
13824              // Backward compatibility
13825              else {
13826                      if(!oRecord || !(oRecord instanceof YAHOO.widget.Record)) {
13827                          oRecord = this.getRecord(elCell);
13828                      }
13829                      if(!oColumn || !(oColumn instanceof YAHOO.widget.Column)) {
13830                          oColumn = this.getColumn(elCell);
13831                      }
13832                      if(oRecord && oColumn) {
13833                          if(!this._oCellEditor || this._oCellEditor.container) {
13834                              this._initCellEditorEl();
13835                          }
13836                          
13837                          // Update Editor values
13838                          oCellEditor = this._oCellEditor;
13839                          oCellEditor.cell = elCell;
13840                          oCellEditor.record = oRecord;
13841                          oCellEditor.column = oColumn;
13842                          oCellEditor.validator = (oColumn.editorOptions &&
13843                                  lang.isFunction(oColumn.editorOptions.validator)) ?
13844                                  oColumn.editorOptions.validator : null;
13845                          oCellEditor.value = oRecord.getData(oColumn.key);
13846                          oCellEditor.defaultValue = null;
13847              
13848                          // Move Editor
13849                          var elContainer = oCellEditor.container;
13850                          var x = Dom.getX(elCell);
13851                          var y = Dom.getY(elCell);
13852              
13853                          // SF doesn't get xy for cells in scrolling table
13854                          // when tbody display is set to block
13855                          if(isNaN(x) || isNaN(y)) {
13856                              x = elCell.offsetLeft + // cell pos relative to table
13857                                      Dom.getX(this._elTbody.parentNode) - // plus table pos relative to document
13858                                      this._elTbody.scrollLeft; // minus tbody scroll
13859                              y = elCell.offsetTop + // cell pos relative to table
13860                                      Dom.getY(this._elTbody.parentNode) - // plus table pos relative to document
13861                                      this._elTbody.scrollTop + // minus tbody scroll
13862                                      this._elThead.offsetHeight; // account for fixed THEAD cells
13863                          }
13864              
13865                          elContainer.style.left = x + "px";
13866                          elContainer.style.top = y + "px";
13867              
13868                          // Hook to customize the UI
13869                          this.doBeforeShowCellEditor(this._oCellEditor);
13870              
13871                          //TODO: This is temporarily up here due so elements can be focused
13872                          // Show Editor
13873                          elContainer.style.display = "";
13874              
13875                          // Handle ESC key
13876                          Ev.addListener(elContainer, "keydown", function(e, oSelf) {
13877                              // ESC hides Cell Editor
13878                              if((e.keyCode == 27)) {
13879                                  oSelf.cancelCellEditor();
13880                                  oSelf.focusTbodyEl();
13881                              }
13882                              else {
13883                                  oSelf.fireEvent("editorKeydownEvent", {editor:oSelf._oCellEditor, event:e});
13884                              }
13885                          }, this);
13886              
13887                          // Render Editor markup
13888                          var fnEditor;
13889                          if(lang.isString(oColumn.editor)) {
13890                              switch(oColumn.editor) {
13891                                  case "checkbox":
13892                                      fnEditor = DT.editCheckbox;
13893                                      break;
13894                                  case "date":
13895                                      fnEditor = DT.editDate;
13896                                      break;
13897                                  case "dropdown":
13898                                      fnEditor = DT.editDropdown;
13899                                      break;
13900                                  case "radio":
13901                                      fnEditor = DT.editRadio;
13902                                      break;
13903                                  case "textarea":
13904                                      fnEditor = DT.editTextarea;
13905                                      break;
13906                                  case "textbox":
13907                                      fnEditor = DT.editTextbox;
13908                                      break;
13909                                  default:
13910                                      fnEditor = null;
13911                              }
13912                          }
13913                          else if(lang.isFunction(oColumn.editor)) {
13914                              fnEditor = oColumn.editor;
13915                          }
13916              
13917                          if(fnEditor) {
13918                              // Create DOM input elements
13919                              fnEditor(this._oCellEditor, this);
13920              
13921                              // Show Save/Cancel buttons
13922                              if(!oColumn.editorOptions || !oColumn.editorOptions.disableBtns) {
13923                                  this.showCellEditorBtns(elContainer);
13924                              }
13925              
13926                              oCellEditor.isActive = true;
13927              
13928                              //TODO: verify which args to pass
13929                              this.fireEvent("editorShowEvent", {editor:oCellEditor});
13930                              return;
13931                          }
13932                      }
13933  
13934  
13935  
13936              
13937              }
13938          }
13939      }
13940  },
13941  
13942  /**
13943   * Backward compatibility.
13944   *
13945   * @method _initCellEditorEl
13946   * @private
13947   * @deprecated Use BaseCellEditor class.
13948   */
13949  _initCellEditorEl : function() {
13950      // Attach Cell Editor container element as first child of body
13951      var elCellEditor = document.createElement("div");
13952      elCellEditor.id = this._sId + "-celleditor";
13953      elCellEditor.style.display = "none";
13954      elCellEditor.tabIndex = 0;
13955      Dom.addClass(elCellEditor, DT.CLASS_EDITOR);
13956      var elFirstChild = Dom.getFirstChild(document.body);
13957      if(elFirstChild) {
13958          elCellEditor = Dom.insertBefore(elCellEditor, elFirstChild);
13959      }
13960      else {
13961          elCellEditor = document.body.appendChild(elCellEditor);
13962      }
13963      
13964      // Internal tracker of Cell Editor values
13965      var oCellEditor = {};
13966      oCellEditor.container = elCellEditor;
13967      oCellEditor.value = null;
13968      oCellEditor.isActive = false;
13969      this._oCellEditor = oCellEditor;
13970  },
13971  
13972  /**
13973   * Overridable abstract method to customize CellEditor before showing.
13974   *
13975   * @method doBeforeShowCellEditor
13976   * @param oCellEditor {YAHOO.widget.CellEditor} The CellEditor instance.
13977   * @return {Boolean} Return true to continue showing CellEditor.
13978   */
13979  doBeforeShowCellEditor : function(oCellEditor) {
13980      return true;
13981  },
13982  
13983  /**
13984   * Saves active CellEditor input to Record and upates DOM UI.
13985   *
13986   * @method saveCellEditor
13987   */
13988  saveCellEditor : function() {
13989      if(this._oCellEditor) {
13990          if(this._oCellEditor.save) {
13991              this._oCellEditor.save();
13992          }
13993          // Backward compatibility
13994          else if(this._oCellEditor.isActive) {
13995              var newData = this._oCellEditor.value;
13996              // Copy the data to pass to the event
13997              //var oldData = YAHOO.widget.DataTable._cloneObject(this._oCellEditor.record.getData(this._oCellEditor.column.key));
13998              var oldData = this._oCellEditor.record.getData(this._oCellEditor.column.key);
13999      
14000              // Validate input data
14001              if(this._oCellEditor.validator) {
14002                  newData = this._oCellEditor.value = this._oCellEditor.validator.call(this, newData, oldData, this._oCellEditor);
14003                  if(newData === null ) {
14004                      this.resetCellEditor();
14005                      this.fireEvent("editorRevertEvent",
14006                              {editor:this._oCellEditor, oldData:oldData, newData:newData});
14007                      return;
14008                  }
14009              }
14010              // Update the Record
14011              this._oRecordSet.updateRecordValue(this._oCellEditor.record, this._oCellEditor.column.key, this._oCellEditor.value);
14012              // Update the UI
14013              this.formatCell(this._oCellEditor.cell.firstChild, this._oCellEditor.record, this._oCellEditor.column);
14014              
14015              // Bug fix 1764044
14016              this._oChainRender.add({
14017                  method: function() {
14018                      this.validateColumnWidths();
14019                  },
14020                  scope: this
14021              });
14022              this._oChainRender.run();
14023              // Clear out the Cell Editor
14024              this.resetCellEditor();
14025      
14026              this.fireEvent("editorSaveEvent",
14027                      {editor:this._oCellEditor, oldData:oldData, newData:newData});
14028          }
14029      }   
14030  },
14031  
14032  /**
14033   * Cancels active CellEditor.
14034   *
14035   * @method cancelCellEditor
14036   */
14037  cancelCellEditor : function() {
14038      if(this._oCellEditor) {
14039          if(this._oCellEditor.cancel) {
14040              this._oCellEditor.cancel();
14041          }
14042          // Backward compatibility
14043          else if(this._oCellEditor.isActive) {
14044              this.resetCellEditor();
14045              //TODO: preserve values for the event?
14046              this.fireEvent("editorCancelEvent", {editor:this._oCellEditor});
14047          }
14048  
14049      }
14050  },
14051  
14052  /**
14053   * Destroys active CellEditor instance and UI.
14054   *
14055   * @method destroyCellEditor
14056   */
14057  destroyCellEditor : function() {
14058      if(this._oCellEditor) {
14059          this._oCellEditor.destroy();
14060          this._oCellEditor = null;
14061      }   
14062  },
14063  
14064  /**
14065   * Passes through showEvent of the active CellEditor.
14066   *
14067   * @method _onEditorShowEvent
14068   * @param oArgs {Object}  Custom Event args.
14069   * @private 
14070   */
14071  _onEditorShowEvent : function(oArgs) {
14072      this.fireEvent("editorShowEvent", oArgs);
14073  },
14074  
14075  /**
14076   * Passes through keydownEvent of the active CellEditor.
14077   * @param oArgs {Object}  Custom Event args. 
14078   *
14079   * @method _onEditorKeydownEvent
14080   * @private 
14081   */
14082  _onEditorKeydownEvent : function(oArgs) {
14083      this.fireEvent("editorKeydownEvent", oArgs);
14084  },
14085  
14086  /**
14087   * Passes through revertEvent of the active CellEditor.
14088   *
14089   * @method _onEditorRevertEvent
14090   * @param oArgs {Object}  Custom Event args. 
14091   * @private  
14092   */
14093  _onEditorRevertEvent : function(oArgs) {
14094      this.fireEvent("editorRevertEvent", oArgs);
14095  },
14096  
14097  /**
14098   * Passes through saveEvent of the active CellEditor.
14099   *
14100   * @method _onEditorSaveEvent
14101   * @param oArgs {Object}  Custom Event args.  
14102   * @private 
14103   */
14104  _onEditorSaveEvent : function(oArgs) {
14105      this.fireEvent("editorSaveEvent", oArgs);
14106  },
14107  
14108  /**
14109   * Passes through cancelEvent of the active CellEditor.
14110   *
14111   * @method _onEditorCancelEvent
14112   * @param oArgs {Object}  Custom Event args.
14113   * @private   
14114   */
14115  _onEditorCancelEvent : function(oArgs) {
14116      this.fireEvent("editorCancelEvent", oArgs);
14117  },
14118  
14119  /**
14120   * Passes through blurEvent of the active CellEditor.
14121   *
14122   * @method _onEditorBlurEvent
14123   * @param oArgs {Object}  Custom Event args. 
14124   * @private  
14125   */
14126  _onEditorBlurEvent : function(oArgs) {
14127      this.fireEvent("editorBlurEvent", oArgs);
14128  },
14129  
14130  /**
14131   * Passes through blockEvent of the active CellEditor.
14132   *
14133   * @method _onEditorBlockEvent
14134   * @param oArgs {Object}  Custom Event args. 
14135   * @private  
14136   */
14137  _onEditorBlockEvent : function(oArgs) {
14138      this.fireEvent("editorBlockEvent", oArgs);
14139  },
14140  
14141  /**
14142   * Passes through unblockEvent of the active CellEditor.
14143   *
14144   * @method _onEditorUnblockEvent
14145   * @param oArgs {Object}  Custom Event args. 
14146   * @private  
14147   */
14148  _onEditorUnblockEvent : function(oArgs) {
14149      this.fireEvent("editorUnblockEvent", oArgs);
14150  },
14151  
14152  /**
14153   * Public handler of the editorBlurEvent. By default, saves on blur if
14154   * disableBtns is true, otherwise cancels on blur. 
14155   *
14156   * @method onEditorBlurEvent
14157   * @param oArgs {Object}  Custom Event args.  
14158   */
14159  onEditorBlurEvent : function(oArgs) {
14160      if(oArgs.editor.disableBtns) {
14161          // Save on blur
14162          if(oArgs.editor.save) { // Backward incompatible
14163              oArgs.editor.save();
14164          }
14165      }      
14166      else if(oArgs.editor.cancel) { // Backward incompatible
14167          // Cancel on blur
14168          oArgs.editor.cancel();
14169      }      
14170  },
14171  
14172  /**
14173   * Public handler of the editorBlockEvent. By default, disables DataTable UI.
14174   *
14175   * @method onEditorBlockEvent
14176   * @param oArgs {Object}  Custom Event args.  
14177   */
14178  onEditorBlockEvent : function(oArgs) {
14179      this.disable();
14180  },
14181  
14182  /**
14183   * Public handler of the editorUnblockEvent. By default, undisables DataTable UI.
14184   *
14185   * @method onEditorUnblockEvent
14186   * @param oArgs {Object}  Custom Event args.  
14187   */
14188  onEditorUnblockEvent : function(oArgs) {
14189      this.undisable();
14190  },
14191  
14192  
14193  
14194  
14195  
14196  
14197  
14198  
14199  
14200  
14201  
14202  
14203  
14204  
14205  
14206  
14207  
14208  
14209  
14210  
14211  
14212  
14213  
14214  
14215  
14216  
14217  
14218  
14219  
14220  
14221  
14222  
14223  
14224  
14225  
14226  
14227  
14228  
14229  // ABSTRACT METHODS
14230  
14231  /**
14232   * Overridable method gives implementers a hook to access data before
14233   * it gets added to RecordSet and rendered to the TBODY.
14234   *
14235   * @method doBeforeLoadData
14236   * @param sRequest {String} Original request.
14237   * @param oResponse {Object} <a href="http://developer.yahoo.com/yui/datasource/#ds_oParsedResponse">Response object</a>.
14238   * @param oPayload {MIXED} additional arguments
14239   * @return {Boolean} Return true to continue loading data into RecordSet and
14240   * updating DataTable with new Records, false to cancel.
14241   */
14242  doBeforeLoadData : function(sRequest, oResponse, oPayload) {
14243      return true;
14244  },
14245  
14246  
14247  
14248  
14249  
14250  
14251  
14252  
14253  
14254  
14255  
14256  
14257  
14258  
14259  
14260  
14261  
14262  
14263  
14264  
14265  
14266  
14267  
14268  
14269  
14270  
14271  
14272  
14273  
14274  
14275  
14276  
14277  
14278  
14279  
14280  
14281  
14282  
14283  
14284  
14285  
14286  
14287  
14288  
14289  
14290  
14291  
14292  
14293  
14294  
14295  
14296  
14297  
14298  
14299  
14300  
14301  
14302  
14303  
14304  
14305  
14306  
14307  
14308  /////////////////////////////////////////////////////////////////////////////
14309  //
14310  // Public Custom Event Handlers
14311  //
14312  /////////////////////////////////////////////////////////////////////////////
14313  
14314  /**
14315   * Custom event handler to sort Column.
14316   *
14317   * @method onEventSortColumn
14318   * @param oArgs.event {HTMLEvent} Event object.
14319   * @param oArgs.target {HTMLElement} Target element.
14320   */
14321  onEventSortColumn : function(oArgs) {
14322  //TODO: support form elements in sortable columns
14323      var evt = oArgs.event;
14324      var target = oArgs.target;
14325  
14326      var el = this.getThEl(target) || this.getTdEl(target);
14327      if(el) {
14328          var oColumn = this.getColumn(el);
14329          if(oColumn.sortable) {
14330              Ev.stopEvent(evt);
14331              this.sortColumn(oColumn);
14332          }
14333      }
14334      else {
14335      }
14336  },
14337  
14338  /**
14339   * Custom event handler to select Column.
14340   *
14341   * @method onEventSelectColumn
14342   * @param oArgs.event {HTMLEvent} Event object.
14343   * @param oArgs.target {HTMLElement} Target element.
14344   */
14345  onEventSelectColumn : function(oArgs) {
14346      this.selectColumn(oArgs.target);
14347  },
14348  
14349  /**
14350   * Custom event handler to highlight Column. Accounts for spurious
14351   * caused-by-child events. 
14352   *
14353   * @method onEventHighlightColumn
14354   * @param oArgs.event {HTMLEvent} Event object.
14355   * @param oArgs.target {HTMLElement} Target element.
14356   */
14357  onEventHighlightColumn : function(oArgs) {
14358      this.highlightColumn(oArgs.target);
14359  },
14360  
14361  /**
14362   * Custom event handler to unhighlight Column. Accounts for spurious
14363   * caused-by-child events. 
14364   *
14365   * @method onEventUnhighlightColumn
14366   * @param oArgs.event {HTMLEvent} Event object.
14367   * @param oArgs.target {HTMLElement} Target element.
14368   */
14369  onEventUnhighlightColumn : function(oArgs) {
14370      this.unhighlightColumn(oArgs.target);
14371  },
14372  
14373  /**
14374   * Custom event handler to manage selection according to desktop paradigm.
14375   *
14376   * @method onEventSelectRow
14377   * @param oArgs.event {HTMLEvent} Event object.
14378   * @param oArgs.target {HTMLElement} Target element.
14379   */
14380  onEventSelectRow : function(oArgs) {
14381      var sMode = this.get("selectionMode");
14382      if(sMode == "single") {
14383          this._handleSingleSelectionByMouse(oArgs);
14384      }
14385      else {
14386          this._handleStandardSelectionByMouse(oArgs);
14387      }
14388  },
14389  
14390  /**
14391   * Custom event handler to select cell.
14392   *
14393   * @method onEventSelectCell
14394   * @param oArgs.event {HTMLEvent} Event object.
14395   * @param oArgs.target {HTMLElement} Target element.
14396   */
14397  onEventSelectCell : function(oArgs) {
14398      var sMode = this.get("selectionMode");
14399      if(sMode == "cellblock") {
14400          this._handleCellBlockSelectionByMouse(oArgs);
14401      }
14402      else if(sMode == "cellrange") {
14403          this._handleCellRangeSelectionByMouse(oArgs);
14404      }
14405      else {
14406          this._handleSingleCellSelectionByMouse(oArgs);
14407      }
14408  },
14409  
14410  /**
14411   * Custom event handler to highlight row. Accounts for spurious
14412   * caused-by-child events. 
14413   *
14414   * @method onEventHighlightRow
14415   * @param oArgs.event {HTMLEvent} Event object.
14416   * @param oArgs.target {HTMLElement} Target element.
14417   */
14418  onEventHighlightRow : function(oArgs) {
14419      this.highlightRow(oArgs.target);
14420  },
14421  
14422  /**
14423   * Custom event handler to unhighlight row. Accounts for spurious
14424   * caused-by-child events. 
14425   *
14426   * @method onEventUnhighlightRow
14427   * @param oArgs.event {HTMLEvent} Event object.
14428   * @param oArgs.target {HTMLElement} Target element.
14429   */
14430  onEventUnhighlightRow : function(oArgs) {
14431      this.unhighlightRow(oArgs.target);
14432  },
14433  
14434  /**
14435   * Custom event handler to highlight cell. Accounts for spurious
14436   * caused-by-child events. 
14437   *
14438   * @method onEventHighlightCell
14439   * @param oArgs.event {HTMLEvent} Event object.
14440   * @param oArgs.target {HTMLElement} Target element.
14441   */
14442  onEventHighlightCell : function(oArgs) {
14443      this.highlightCell(oArgs.target);
14444  },
14445  
14446  /**
14447   * Custom event handler to unhighlight cell. Accounts for spurious
14448   * caused-by-child events. 
14449   *
14450   * @method onEventUnhighlightCell
14451   * @param oArgs.event {HTMLEvent} Event object.
14452   * @param oArgs.target {HTMLElement} Target element.
14453   */
14454  onEventUnhighlightCell : function(oArgs) {
14455      this.unhighlightCell(oArgs.target);
14456  },
14457  
14458  /**
14459   * Custom event handler to format cell.
14460   *
14461   * @method onEventFormatCell
14462   * @param oArgs.event {HTMLEvent} Event object.
14463   * @param oArgs.target {HTMLElement} Target element.
14464   */
14465  onEventFormatCell : function(oArgs) {
14466      var target = oArgs.target;
14467  
14468      var elCell = this.getTdEl(target);
14469      if(elCell) {
14470          var oColumn = this.getColumn(this.getCellIndex(elCell));
14471          this.formatCell(elCell.firstChild, this.getRecord(elCell), oColumn);
14472      }
14473      else {
14474      }
14475  },
14476  
14477  /**
14478   * Custom event handler to edit cell.
14479   *
14480   * @method onEventShowCellEditor
14481   * @param oArgs.event {HTMLEvent} Event object.
14482   * @param oArgs.target {HTMLElement} Target element.
14483   */
14484  onEventShowCellEditor : function(oArgs) {
14485      if(!this.isDisabled()) {
14486          this.showCellEditor(oArgs.target);
14487      }
14488  },
14489  
14490  /**
14491   * Custom event handler to save active CellEditor input.
14492   *
14493   * @method onEventSaveCellEditor
14494   */
14495  onEventSaveCellEditor : function(oArgs) {
14496      if(this._oCellEditor) {
14497          if(this._oCellEditor.save) {
14498              this._oCellEditor.save();
14499          }
14500          // Backward compatibility
14501          else {
14502              this.saveCellEditor();
14503          }
14504      }
14505  },
14506  
14507  /**
14508   * Custom event handler to cancel active CellEditor.
14509   *
14510   * @method onEventCancelCellEditor
14511   */
14512  onEventCancelCellEditor : function(oArgs) {
14513      if(this._oCellEditor) {
14514          if(this._oCellEditor.cancel) {
14515              this._oCellEditor.cancel();
14516          }
14517          // Backward compatibility
14518          else {
14519              this.cancelCellEditor();
14520          }
14521      }
14522  },
14523  
14524  /**
14525   * Callback function receives data from DataSource and populates an entire
14526   * DataTable with Records and TR elements, clearing previous Records, if any.
14527   *
14528   * @method onDataReturnInitializeTable
14529   * @param sRequest {String} Original request.
14530   * @param oResponse {Object} <a href="http://developer.yahoo.com/yui/datasource/#ds_oParsedResponse">Response object</a>.
14531   * @param oPayload {MIXED} (optional) Additional argument(s)
14532   */
14533  onDataReturnInitializeTable : function(sRequest, oResponse, oPayload) {
14534      if((this instanceof DT) && this._sId) {
14535          this.initializeTable();
14536      
14537          this.onDataReturnSetRows(sRequest,oResponse,oPayload);
14538      }
14539  },
14540  
14541  /**
14542   * Callback function receives reponse from DataSource, replaces all existing
14543   * Records in  RecordSet, updates TR elements with new data, and updates state
14544   * UI for pagination and sorting from payload data, if necessary. 
14545   *  
14546   * @method onDataReturnReplaceRows
14547   * @param oRequest {MIXED} Original generated request.
14548   * @param oResponse {Object} <a href="http://developer.yahoo.com/yui/datasource/#ds_oParsedResponse">Response object</a>.
14549   * @param oPayload {MIXED} (optional) Additional argument(s)
14550   */
14551  onDataReturnReplaceRows : function(oRequest, oResponse, oPayload) {
14552      if((this instanceof DT) && this._sId) {
14553          this.fireEvent("dataReturnEvent", {request:oRequest,response:oResponse,payload:oPayload});
14554      
14555          // Pass data through abstract method for any transformations
14556          var ok    = this.doBeforeLoadData(oRequest, oResponse, oPayload),
14557              pag   = this.get('paginator'),
14558              index = 0;
14559      
14560          // Data ok to set
14561          if(ok && oResponse && !oResponse.error && lang.isArray(oResponse.results)) {
14562              // Update Records
14563              this._oRecordSet.reset();
14564      
14565              if (this.get('dynamicData')) {
14566                  if (oPayload && oPayload.pagination &&
14567                      lang.isNumber(oPayload.pagination.recordOffset)) {
14568                      index = oPayload.pagination.recordOffset;
14569                  } else if (pag) {
14570                      index = pag.getStartIndex();
14571                  }
14572              }
14573      
14574              this._oRecordSet.setRecords(oResponse.results, index | 0);
14575              
14576              // Update state
14577              this._handleDataReturnPayload(oRequest, oResponse, oPayload);
14578              
14579              // Update UI
14580              this.render();    
14581          }
14582          // Error
14583          else if(ok && oResponse.error) {
14584              this.showTableMessage(this.get("MSG_ERROR"), DT.CLASS_ERROR);
14585          }
14586      }
14587  },
14588  
14589  /**
14590   * Callback function receives data from DataSource and appends to an existing
14591   * DataTable new Records and, if applicable, creates or updates
14592   * corresponding TR elements.
14593   *
14594   * @method onDataReturnAppendRows
14595   * @param sRequest {String} Original request.
14596   * @param oResponse {Object} <a href="http://developer.yahoo.com/yui/datasource/#ds_oParsedResponse">Response object</a>.
14597   * @param oPayload {MIXED} (optional) Additional argument(s)
14598   */
14599  onDataReturnAppendRows : function(sRequest, oResponse, oPayload) {
14600      if((this instanceof DT) && this._sId) {
14601          this.fireEvent("dataReturnEvent", {request:sRequest,response:oResponse,payload:oPayload});
14602      
14603          // Pass data through abstract method for any transformations
14604          var ok = this.doBeforeLoadData(sRequest, oResponse, oPayload);
14605      
14606          // Data ok to append
14607          if(ok && oResponse && !oResponse.error && lang.isArray(oResponse.results)) {        
14608              // Append rows
14609              this.addRows(oResponse.results);
14610      
14611              // Update state
14612              this._handleDataReturnPayload(sRequest, oResponse, oPayload);
14613          }
14614          // Error
14615          else if(ok && oResponse.error) {
14616              this.showTableMessage(this.get("MSG_ERROR"), DT.CLASS_ERROR);
14617          }
14618      }
14619  },
14620  
14621  /**
14622   * Callback function receives data from DataSource and inserts new records
14623   * starting at the index specified in oPayload.insertIndex. The value for
14624   * oPayload.insertIndex can be populated when sending the request to the DataSource,
14625   * or by accessing oPayload.insertIndex with the doBeforeLoadData() method at runtime.
14626   * If applicable, creates or updates corresponding TR elements.
14627   *
14628   * @method onDataReturnInsertRows
14629   * @param sRequest {String} Original request.
14630   * @param oResponse {Object} <a href="http://developer.yahoo.com/yui/datasource/#ds_oParsedResponse">Response object</a>.
14631   * @param oPayload {MIXED} Argument payload, looks in oPayload.insertIndex.
14632   */
14633  onDataReturnInsertRows : function(sRequest, oResponse, oPayload) {
14634      if((this instanceof DT) && this._sId) {
14635          this.fireEvent("dataReturnEvent", {request:sRequest,response:oResponse,payload:oPayload});
14636      
14637          // Pass data through abstract method for any transformations
14638          var ok = this.doBeforeLoadData(sRequest, oResponse, oPayload);
14639      
14640          // Data ok to append
14641          if(ok && oResponse && !oResponse.error && lang.isArray(oResponse.results)) {
14642              // Insert rows
14643              this.addRows(oResponse.results, (oPayload ? oPayload.insertIndex : 0));
14644      
14645              // Update state
14646              this._handleDataReturnPayload(sRequest, oResponse, oPayload);
14647          }
14648          // Error
14649          else if(ok && oResponse.error) {
14650              this.showTableMessage(this.get("MSG_ERROR"), DT.CLASS_ERROR);
14651          }
14652      }
14653  },
14654  
14655  /**
14656   * Callback function receives data from DataSource and incrementally updates Records
14657   * starting at the index specified in oPayload.updateIndex. The value for
14658   * oPayload.updateIndex can be populated when sending the request to the DataSource,
14659   * or by accessing oPayload.updateIndex with the doBeforeLoadData() method at runtime.
14660   * If applicable, creates or updates corresponding TR elements.
14661   *
14662   * @method onDataReturnUpdateRows
14663   * @param sRequest {String} Original request.
14664   * @param oResponse {Object} <a href="http://developer.yahoo.com/yui/datasource/#ds_oParsedResponse">Response object</a>.
14665   * @param oPayload {MIXED} Argument payload, looks in oPayload.updateIndex.
14666   */
14667  onDataReturnUpdateRows : function(sRequest, oResponse, oPayload) {
14668      if((this instanceof DT) && this._sId) {
14669          this.fireEvent("dataReturnEvent", {request:sRequest,response:oResponse,payload:oPayload});
14670      
14671          // Pass data through abstract method for any transformations
14672          var ok = this.doBeforeLoadData(sRequest, oResponse, oPayload);
14673      
14674          // Data ok to append
14675          if(ok && oResponse && !oResponse.error && lang.isArray(oResponse.results)) {
14676              // Insert rows
14677              this.updateRows((oPayload ? oPayload.updateIndex : 0), oResponse.results);
14678      
14679              // Update state
14680              this._handleDataReturnPayload(sRequest, oResponse, oPayload);
14681          }
14682          // Error
14683          else if(ok && oResponse.error) {
14684              this.showTableMessage(this.get("MSG_ERROR"), DT.CLASS_ERROR);
14685          }
14686      }
14687  },
14688  
14689  /**
14690   * Callback function receives reponse from DataSource and populates the
14691   * RecordSet with the results.
14692   *  
14693   * @method onDataReturnSetRows
14694   * @param oRequest {MIXED} Original generated request.
14695   * @param oResponse {Object} <a href="http://developer.yahoo.com/yui/datasource/#ds_oParsedResponse">Response object</a>.
14696   * @param oPayload {MIXED} (optional) Additional argument(s)
14697   */
14698  onDataReturnSetRows : function(oRequest, oResponse, oPayload) {
14699      if((this instanceof DT) && this._sId) {
14700          this.fireEvent("dataReturnEvent", {request:oRequest,response:oResponse,payload:oPayload});
14701      
14702          // Pass data through abstract method for any transformations
14703          var ok    = this.doBeforeLoadData(oRequest, oResponse, oPayload),
14704              pag   = this.get('paginator'),
14705              index = 0;
14706      
14707          // Data ok to set
14708          if(ok && oResponse && !oResponse.error && lang.isArray(oResponse.results)) {
14709              // Update Records
14710              if (this.get('dynamicData')) {
14711                  if (oPayload && oPayload.pagination &&
14712                      lang.isNumber(oPayload.pagination.recordOffset)) {
14713                      index = oPayload.pagination.recordOffset;
14714                  } else if (pag) {
14715                      index = pag.getStartIndex();
14716                  }
14717                  
14718                  this._oRecordSet.reset(); // Bug 2290604: dyanmic data shouldn't keep accumulating by default
14719              }
14720      
14721              this._oRecordSet.setRecords(oResponse.results, index | 0);
14722      
14723              // Update state
14724              this._handleDataReturnPayload(oRequest, oResponse, oPayload);
14725              
14726              // Update UI
14727              this.render();
14728          }
14729          // Error
14730          else if(ok && oResponse.error) {
14731              this.showTableMessage(this.get("MSG_ERROR"), DT.CLASS_ERROR);
14732          }
14733      }
14734      else {
14735      }
14736  },
14737  
14738  /**
14739   * Hook to update oPayload before consumption.
14740   *  
14741   * @method handleDataReturnPayload
14742   * @param oRequest {MIXED} Original generated request.
14743   * @param oResponse {Object} <a href="http://developer.yahoo.com/yui/datasource/#ds_oParsedResponse">Response object</a>.
14744   * @param oPayload {MIXED} State values.
14745   * @return oPayload {MIXED} State values.
14746   */
14747  handleDataReturnPayload : function (oRequest, oResponse, oPayload) {
14748      return oPayload || {};
14749  },
14750  
14751  /**
14752   * Updates the DataTable with state data sent in an onDataReturn* payload.
14753   *  
14754   * @method _handleDataReturnPayload
14755   * @param oRequest {MIXED} Original generated request.
14756   * @param oResponse {Object} <a href="http://developer.yahoo.com/yui/datasource/#ds_oParsedResponse">Response object</a>.
14757   * @param oPayload {MIXED} State values
14758   * @private
14759   */
14760  _handleDataReturnPayload : function (oRequest, oResponse, oPayload) {
14761      oPayload = this.handleDataReturnPayload(oRequest, oResponse, oPayload);
14762      if(oPayload) {
14763          // Update pagination
14764          var oPaginator = this.get('paginator');
14765          if (oPaginator) {
14766              // Update totalRecords
14767              if(this.get("dynamicData")) {
14768                  if (widget.Paginator.isNumeric(oPayload.totalRecords)) {
14769                      oPaginator.set('totalRecords',oPayload.totalRecords);
14770                  }
14771              }
14772              else {
14773                  oPaginator.set('totalRecords',this._oRecordSet.getLength());
14774              }
14775              // Update other paginator values
14776              if (lang.isObject(oPayload.pagination)) {
14777                  oPaginator.set('rowsPerPage',oPayload.pagination.rowsPerPage);
14778                  oPaginator.set('recordOffset',oPayload.pagination.recordOffset);
14779              }
14780          }
14781  
14782          // Update sorting
14783          if (oPayload.sortedBy) {
14784              // Set the sorting values in preparation for refresh
14785              this.set('sortedBy', oPayload.sortedBy);
14786          }
14787          // Backwards compatibility for sorting
14788          else if (oPayload.sorting) {
14789              // Set the sorting values in preparation for refresh
14790              this.set('sortedBy', oPayload.sorting);
14791          }
14792      }
14793  },
14794  
14795  
14796  
14797  
14798  
14799  
14800  
14801  
14802  
14803  
14804  
14805  
14806  
14807  
14808  
14809  
14810  
14811  
14812  
14813  
14814  
14815  
14816  
14817  
14818  
14819  
14820  
14821  
14822  
14823  
14824  
14825  
14826  
14827      /////////////////////////////////////////////////////////////////////////////
14828      //
14829      // Custom Events
14830      //
14831      /////////////////////////////////////////////////////////////////////////////
14832  
14833      /**
14834       * Fired when the DataTable's rows are rendered from an initialized state.
14835       *
14836       * @event initEvent
14837       */
14838  
14839      /**
14840       * Fired before the DataTable's DOM is rendered or modified.
14841       *
14842       * @event beforeRenderEvent
14843       */
14844  
14845      /**
14846       * Fired when the DataTable's DOM is rendered or modified.
14847       *
14848       * @event renderEvent
14849       */
14850  
14851      /**
14852       * Fired when the DataTable's post-render routine is complete, including
14853       * Column width validations.
14854       *
14855       * @event postRenderEvent
14856       */
14857  
14858      /**
14859       * Fired when the DataTable is disabled.
14860       *
14861       * @event disableEvent
14862       */
14863  
14864      /**
14865       * Fired when the DataTable is undisabled.
14866       *
14867       * @event undisableEvent
14868       */
14869  
14870      /**
14871       * Fired when data is returned from DataSource but before it is consumed by
14872       * DataTable.
14873       *
14874       * @event dataReturnEvent
14875       * @param oArgs.request {String} Original request.
14876       * @param oArgs.response {Object} Response object.
14877       */
14878  
14879      /**
14880       * Fired when the DataTable has a focus event.
14881       *
14882       * @event tableFocusEvent
14883       */
14884  
14885      /**
14886       * Fired when the DataTable THEAD element has a focus event.
14887       *
14888       * @event theadFocusEvent
14889       */
14890  
14891      /**
14892       * Fired when the DataTable TBODY element has a focus event.
14893       *
14894       * @event tbodyFocusEvent
14895       */
14896  
14897      /**
14898       * Fired when the DataTable has a blur event.
14899       *
14900       * @event tableBlurEvent
14901       */
14902  
14903      /*TODO implement theadBlurEvent
14904       * Fired when the DataTable THEAD element has a blur event.
14905       *
14906       * @event theadBlurEvent
14907       */
14908  
14909      /*TODO: implement tbodyBlurEvent
14910       * Fired when the DataTable TBODY element has a blur event.
14911       *
14912       * @event tbodyBlurEvent
14913       */
14914  
14915      /**
14916       * Fired when the DataTable has a key event.
14917       *
14918       * @event tableKeyEvent
14919       * @param oArgs.event {HTMLEvent} The event object.
14920       * @param oArgs.target {HTMLElement} The DataTable's TABLE element.
14921       */
14922  
14923      /**
14924       * Fired when the DataTable THEAD element has a key event.
14925       *
14926       * @event theadKeyEvent
14927       * @param oArgs.event {HTMLEvent} The event object.
14928       * @param oArgs.target {HTMLElement} The DataTable's TABLE element.
14929       */
14930  
14931      /**
14932       * Fired when the DataTable TBODY element has a key event.
14933       *
14934       * @event tbodyKeyEvent
14935       * @param oArgs.event {HTMLEvent} The event object.
14936       * @param oArgs.target {HTMLElement} The DataTable's TABLE element.
14937       */
14938  
14939      /**
14940       * Fired when the DataTable has a mouseover.
14941       *
14942       * @event tableMouseoverEvent
14943       * @param oArgs.event {HTMLEvent} The event object.
14944       * @param oArgs.target {HTMLElement} The DataTable's TABLE element.
14945       *
14946       */
14947  
14948      /**
14949       * Fired when the DataTable has a mouseout.
14950       *
14951       * @event tableMouseoutEvent
14952       * @param oArgs.event {HTMLEvent} The event object.
14953       * @param oArgs.target {HTMLElement} The DataTable's TABLE element.
14954       *
14955       */
14956  
14957      /**
14958       * Fired when the DataTable has a mousedown.
14959       *
14960       * @event tableMousedownEvent
14961       * @param oArgs.event {HTMLEvent} The event object.
14962       * @param oArgs.target {HTMLElement} The DataTable's TABLE element.
14963       *
14964       */
14965  
14966      /**
14967       * Fired when the DataTable has a mouseup.
14968       *
14969       * @event tableMouseupEvent
14970       * @param oArgs.event {HTMLEvent} The event object.
14971       * @param oArgs.target {HTMLElement} The DataTable's TABLE element.
14972       *
14973       */
14974  
14975      /**
14976       * Fired when the DataTable has a click.
14977       *
14978       * @event tableClickEvent
14979       * @param oArgs.event {HTMLEvent} The event object.
14980       * @param oArgs.target {HTMLElement} The DataTable's TABLE element.
14981       *
14982       */
14983  
14984      /**
14985       * Fired when the DataTable has a dblclick.
14986       *
14987       * @event tableDblclickEvent
14988       * @param oArgs.event {HTMLEvent} The event object.
14989       * @param oArgs.target {HTMLElement} The DataTable's TABLE element.
14990       *
14991       */
14992  
14993      /**
14994       * Fired when a message is shown in the DataTable's message element.
14995       *
14996       * @event tableMsgShowEvent
14997       * @param oArgs.html {HTML} The HTML displayed.
14998       * @param oArgs.className {String} The className assigned.
14999       *
15000       */
15001  
15002      /**
15003       * Fired when the DataTable's message element is hidden.
15004       *
15005       * @event tableMsgHideEvent
15006       */
15007  
15008      /**
15009       * Fired when a THEAD row has a mouseover.
15010       *
15011       * @event theadRowMouseoverEvent
15012       * @param oArgs.event {HTMLEvent} The event object.
15013       * @param oArgs.target {HTMLElement} The TR element.
15014       */
15015  
15016      /**
15017       * Fired when a THEAD row has a mouseout.
15018       *
15019       * @event theadRowMouseoutEvent
15020       * @param oArgs.event {HTMLEvent} The event object.
15021       * @param oArgs.target {HTMLElement} The TR element.
15022       */
15023  
15024      /**
15025       * Fired when a THEAD row has a mousedown.
15026       *
15027       * @event theadRowMousedownEvent
15028       * @param oArgs.event {HTMLEvent} The event object.
15029       * @param oArgs.target {HTMLElement} The TR element.
15030       */
15031  
15032      /**
15033       * Fired when a THEAD row has a mouseup.
15034       *
15035       * @event theadRowMouseupEvent
15036       * @param oArgs.event {HTMLEvent} The event object.
15037       * @param oArgs.target {HTMLElement} The TR element.
15038       */
15039  
15040      /**
15041       * Fired when a THEAD row has a click.
15042       *
15043       * @event theadRowClickEvent
15044       * @param oArgs.event {HTMLEvent} The event object.
15045       * @param oArgs.target {HTMLElement} The TR element.
15046       */
15047  
15048      /**
15049       * Fired when a THEAD row has a dblclick.
15050       *
15051       * @event theadRowDblclickEvent
15052       * @param oArgs.event {HTMLEvent} The event object.
15053       * @param oArgs.target {HTMLElement} The TR element.
15054       */
15055  
15056      /**
15057       * Fired when a THEAD cell has a mouseover.
15058       *
15059       * @event theadCellMouseoverEvent
15060       * @param oArgs.event {HTMLEvent} The event object.
15061       * @param oArgs.target {HTMLElement} The TH element.
15062       *
15063       */
15064  
15065      /**
15066       * Fired when a THEAD cell has a mouseout.
15067       *
15068       * @event theadCellMouseoutEvent
15069       * @param oArgs.event {HTMLEvent} The event object.
15070       * @param oArgs.target {HTMLElement} The TH element.
15071       *
15072       */
15073  
15074      /**
15075       * Fired when a THEAD cell has a mousedown.
15076       *
15077       * @event theadCellMousedownEvent
15078       * @param oArgs.event {HTMLEvent} The event object.
15079       * @param oArgs.target {HTMLElement} The TH element.
15080       */
15081  
15082      /**
15083       * Fired when a THEAD cell has a mouseup.
15084       *
15085       * @event theadCellMouseupEvent
15086       * @param oArgs.event {HTMLEvent} The event object.
15087       * @param oArgs.target {HTMLElement} The TH element.
15088       */
15089  
15090      /**
15091       * Fired when a THEAD cell has a click.
15092       *
15093       * @event theadCellClickEvent
15094       * @param oArgs.event {HTMLEvent} The event object.
15095       * @param oArgs.target {HTMLElement} The TH element.
15096       */
15097  
15098      /**
15099       * Fired when a THEAD cell has a dblclick.
15100       *
15101       * @event theadCellDblclickEvent
15102       * @param oArgs.event {HTMLEvent} The event object.
15103       * @param oArgs.target {HTMLElement} The TH element.
15104       */
15105  
15106      /**
15107       * Fired when a THEAD label has a mouseover.
15108       *
15109       * @event theadLabelMouseoverEvent
15110       * @param oArgs.event {HTMLEvent} The event object.
15111       * @param oArgs.target {HTMLElement} The SPAN element.
15112       *
15113       */
15114  
15115      /**
15116       * Fired when a THEAD label has a mouseout.
15117       *
15118       * @event theadLabelMouseoutEvent
15119       * @param oArgs.event {HTMLEvent} The event object.
15120       * @param oArgs.target {HTMLElement} The SPAN element.
15121       *
15122       */
15123  
15124      /**
15125       * Fired when a THEAD label has a mousedown.
15126       *
15127       * @event theadLabelMousedownEvent
15128       * @param oArgs.event {HTMLEvent} The event object.
15129       * @param oArgs.target {HTMLElement} The SPAN element.
15130       */
15131  
15132      /**
15133       * Fired when a THEAD label has a mouseup.
15134       *
15135       * @event theadLabelMouseupEvent
15136       * @param oArgs.event {HTMLEvent} The event object.
15137       * @param oArgs.target {HTMLElement} The SPAN element.
15138       */
15139  
15140      /**
15141       * Fired when a THEAD label has a click.
15142       *
15143       * @event theadLabelClickEvent
15144       * @param oArgs.event {HTMLEvent} The event object.
15145       * @param oArgs.target {HTMLElement} The SPAN element.
15146       */
15147  
15148      /**
15149       * Fired when a THEAD label has a dblclick.
15150       *
15151       * @event theadLabelDblclickEvent
15152       * @param oArgs.event {HTMLEvent} The event object.
15153       * @param oArgs.target {HTMLElement} The SPAN element.
15154       */
15155  
15156      /**
15157       * Fired when a column is sorted.
15158       *
15159       * @event columnSortEvent
15160       * @param oArgs.column {YAHOO.widget.Column} The Column instance.
15161       * @param oArgs.dir {String} Sort direction: YAHOO.widget.DataTable.CLASS_ASC
15162       * or YAHOO.widget.DataTable.CLASS_DESC.
15163       */
15164  
15165      /**
15166       * Fired when a column width is set.
15167       *
15168       * @event columnSetWidthEvent
15169       * @param oArgs.column {YAHOO.widget.Column} The Column instance.
15170       * @param oArgs.width {Number} The width in pixels.
15171       */
15172  
15173      /**
15174       * Fired when a column width is unset.
15175       *
15176       * @event columnUnsetWidthEvent
15177       * @param oArgs.column {YAHOO.widget.Column} The Column instance.
15178       */
15179  
15180      /**
15181       * Fired when a column is drag-resized.
15182       *
15183       * @event columnResizeEvent
15184       * @param oArgs.column {YAHOO.widget.Column} The Column instance.
15185       * @param oArgs.target {HTMLElement} The TH element.
15186       * @param oArgs.width {Number} Width in pixels.     
15187       */
15188  
15189      /**
15190       * Fired when a Column is moved to a new index.
15191       *
15192       * @event columnReorderEvent
15193       * @param oArgs.column {YAHOO.widget.Column} The Column instance.
15194       * @param oArgs.oldIndex {Number} The previous tree index position.
15195       */
15196  
15197      /**
15198       * Fired when a column is hidden.
15199       *
15200       * @event columnHideEvent
15201       * @param oArgs.column {YAHOO.widget.Column} The Column instance.
15202       */
15203  
15204      /**
15205       * Fired when a column is shown.
15206       *
15207       * @event columnShowEvent
15208       * @param oArgs.column {YAHOO.widget.Column} The Column instance.
15209       */
15210  
15211      /**
15212       * Fired when a column is selected.
15213       *
15214       * @event columnSelectEvent
15215       * @param oArgs.column {YAHOO.widget.Column} The Column instance.
15216       */
15217  
15218      /**
15219       * Fired when a column is unselected.
15220       *
15221       * @event columnUnselectEvent
15222       * @param oArgs.column {YAHOO.widget.Column} The Column instance.
15223       */
15224      /**
15225       * Fired when a column is removed.
15226       *
15227       * @event columnRemoveEvent
15228       * @param oArgs.column {YAHOO.widget.Column} The Column instance.
15229       */
15230  
15231      /**
15232       * Fired when a column is inserted.
15233       *
15234       * @event columnInsertEvent
15235       * @param oArgs.column {YAHOO.widget.Column} The Column instance.
15236       * @param oArgs.index {Number} The index position.
15237       */
15238  
15239      /**
15240       * Fired when a column is highlighted.
15241       *
15242       * @event columnHighlightEvent
15243       * @param oArgs.column {YAHOO.widget.Column} The highlighted Column.
15244       */
15245  
15246      /**
15247       * Fired when a column is unhighlighted.
15248       *
15249       * @event columnUnhighlightEvent
15250       * @param oArgs.column {YAHOO.widget.Column} The unhighlighted Column.
15251       */
15252  
15253  
15254      /**
15255       * Fired when a row has a mouseover.
15256       *
15257       * @event rowMouseoverEvent
15258       * @param oArgs.event {HTMLEvent} The event object.
15259       * @param oArgs.target {HTMLElement} The TR element.
15260       */
15261  
15262      /**
15263       * Fired when a row has a mouseout.
15264       *
15265       * @event rowMouseoutEvent
15266       * @param oArgs.event {HTMLEvent} The event object.
15267       * @param oArgs.target {HTMLElement} The TR element.
15268       */
15269  
15270      /**
15271       * Fired when a row has a mousedown.
15272       *
15273       * @event rowMousedownEvent
15274       * @param oArgs.event {HTMLEvent} The event object.
15275       * @param oArgs.target {HTMLElement} The TR element.
15276       */
15277  
15278      /**
15279       * Fired when a row has a mouseup.
15280       *
15281       * @event rowMouseupEvent
15282       * @param oArgs.event {HTMLEvent} The event object.
15283       * @param oArgs.target {HTMLElement} The TR element.
15284       */
15285  
15286      /**
15287       * Fired when a row has a click.
15288       *
15289       * @event rowClickEvent
15290       * @param oArgs.event {HTMLEvent} The event object.
15291       * @param oArgs.target {HTMLElement} The TR element.
15292       */
15293  
15294      /**
15295       * Fired when a row has a dblclick.
15296       *
15297       * @event rowDblclickEvent
15298       * @param oArgs.event {HTMLEvent} The event object.
15299       * @param oArgs.target {HTMLElement} The TR element.
15300       */
15301  
15302      /**
15303       * Fired when a row is added.
15304       *
15305       * @event rowAddEvent
15306       * @param oArgs.record {YAHOO.widget.Record} The added Record.
15307       */
15308       
15309      /**
15310       * Fired when rows are added.
15311       *
15312       * @event rowsAddEvent
15313       * @param oArgs.record {YAHOO.widget.Record[]} The added Records.
15314       */
15315  
15316      /**
15317       * Fired when a row is updated.
15318       *
15319       * @event rowUpdateEvent
15320       * @param oArgs.record {YAHOO.widget.Record} The updated Record.
15321       * @param oArgs.oldData {Object} Object literal of the old data.
15322       */
15323  
15324      /**
15325       * Fired when a row is deleted.
15326       *
15327       * @event rowDeleteEvent
15328       * @param oArgs.oldData {Object} Object literal of the deleted data.
15329       * @param oArgs.recordIndex {Number} Index of the deleted Record.
15330       * @param oArgs.trElIndex {Number} Index of the deleted TR element, if on current page.
15331       */
15332       
15333      /**
15334       * Fired when rows are deleted.
15335       *
15336       * @event rowsDeleteEvent
15337       * @param oArgs.oldData {Object[]} Array of object literals of the deleted data.
15338       * @param oArgs.recordIndex {Number} Index of the first deleted Record.
15339       * @param oArgs.count {Number} Number of deleted Records.
15340       */
15341  
15342      /**
15343       * Fired when a row is selected.
15344       *
15345       * @event rowSelectEvent
15346       * @param oArgs.el {HTMLElement} The selected TR element, if applicable.
15347       * @param oArgs.record {YAHOO.widget.Record} The selected Record.
15348       */
15349  
15350      /**
15351       * Fired when a row is unselected.
15352       *
15353       * @event rowUnselectEvent
15354       * @param oArgs.el {HTMLElement} The unselected TR element, if applicable.
15355       * @param oArgs.record {YAHOO.widget.Record} The unselected Record.
15356       */
15357  
15358      /**
15359       * Fired when all row selections are cleared.
15360       *
15361       * @event unselectAllRowsEvent
15362       */
15363  
15364      /**
15365       * Fired when a row is highlighted.
15366       *
15367       * @event rowHighlightEvent
15368       * @param oArgs.el {HTMLElement} The highlighted TR element.
15369       * @param oArgs.record {YAHOO.widget.Record} The highlighted Record.
15370       */
15371  
15372      /**
15373       * Fired when a row is unhighlighted.
15374       *
15375       * @event rowUnhighlightEvent
15376       * @param oArgs.el {HTMLElement} The highlighted TR element.
15377       * @param oArgs.record {YAHOO.widget.Record} The highlighted Record.
15378       */
15379  
15380      /**
15381       * Fired when a cell is updated.
15382       *
15383       * @event cellUpdateEvent
15384       * @param oArgs.record {YAHOO.widget.Record} The updated Record.
15385       * @param oArgs.column {YAHOO.widget.Column} The updated Column.
15386       * @param oArgs.oldData {Object} Original data value of the updated cell.
15387       */
15388  
15389      /**
15390       * Fired when a cell has a mouseover.
15391       *
15392       * @event cellMouseoverEvent
15393       * @param oArgs.event {HTMLEvent} The event object.
15394       * @param oArgs.target {HTMLElement} The TD element.
15395       */
15396  
15397      /**
15398       * Fired when a cell has a mouseout.
15399       *
15400       * @event cellMouseoutEvent
15401       * @param oArgs.event {HTMLEvent} The event object.
15402       * @param oArgs.target {HTMLElement} The TD element.
15403       */
15404  
15405      /**
15406       * Fired when a cell has a mousedown.
15407       *
15408       * @event cellMousedownEvent
15409       * @param oArgs.event {HTMLEvent} The event object.
15410       * @param oArgs.target {HTMLElement} The TD element.
15411       */
15412  
15413      /**
15414       * Fired when a cell has a mouseup.
15415       *
15416       * @event cellMouseupEvent
15417       * @param oArgs.event {HTMLEvent} The event object.
15418       * @param oArgs.target {HTMLElement} The TD element.
15419       */
15420  
15421      /**
15422       * Fired when a cell has a click.
15423       *
15424       * @event cellClickEvent
15425       * @param oArgs.event {HTMLEvent} The event object.
15426       * @param oArgs.target {HTMLElement} The TD element.
15427       */
15428  
15429      /**
15430       * Fired when a cell has a dblclick.
15431       *
15432       * @event cellDblclickEvent
15433       * @param oArgs.event {HTMLEvent} The event object.
15434       * @param oArgs.target {HTMLElement} The TD element.
15435       */
15436  
15437      /**
15438       * Fired when a cell is formatted.
15439       *
15440       * @event cellFormatEvent
15441       * @param oArgs.el {HTMLElement} The formatted TD element.
15442       * @param oArgs.record {YAHOO.widget.Record} The associated Record instance.
15443       * @param oArgs.column {YAHOO.widget.Column} The associated Column instance.
15444       * @param oArgs.key {String} (deprecated) The key of the formatted cell.
15445       */
15446  
15447      /**
15448       * Fired when a cell is selected.
15449       *
15450       * @event cellSelectEvent
15451       * @param oArgs.el {HTMLElement} The selected TD element.
15452       * @param oArgs.record {YAHOO.widget.Record} The associated Record instance.
15453       * @param oArgs.column {YAHOO.widget.Column} The associated Column instance.
15454       * @param oArgs.key {String} (deprecated) The key of the selected cell.
15455       */
15456  
15457      /**
15458       * Fired when a cell is unselected.
15459       *
15460       * @event cellUnselectEvent
15461       * @param oArgs.el {HTMLElement} The unselected TD element.
15462       * @param oArgs.record {YAHOO.widget.Record} The associated Record.
15463       * @param oArgs.column {YAHOO.widget.Column} The associated Column instance.
15464       * @param oArgs.key {String} (deprecated) The key of the unselected cell.
15465  
15466       */
15467  
15468      /**
15469       * Fired when a cell is highlighted.
15470       *
15471       * @event cellHighlightEvent
15472       * @param oArgs.el {HTMLElement} The highlighted TD element.
15473       * @param oArgs.record {YAHOO.widget.Record} The associated Record instance.
15474       * @param oArgs.column {YAHOO.widget.Column} The associated Column instance.
15475       * @param oArgs.key {String} (deprecated) The key of the highlighted cell.
15476  
15477       */
15478  
15479      /**
15480       * Fired when a cell is unhighlighted.
15481       *
15482       * @event cellUnhighlightEvent
15483       * @param oArgs.el {HTMLElement} The unhighlighted TD element.
15484       * @param oArgs.record {YAHOO.widget.Record} The associated Record instance.
15485       * @param oArgs.column {YAHOO.widget.Column} The associated Column instance.
15486       * @param oArgs.key {String} (deprecated) The key of the unhighlighted cell.
15487  
15488       */
15489  
15490      /**
15491       * Fired when all cell selections are cleared.
15492       *
15493       * @event unselectAllCellsEvent
15494       */
15495  
15496      /**
15497       * Fired when a CellEditor is shown.
15498       *
15499       * @event editorShowEvent
15500       * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
15501       */
15502  
15503      /**
15504       * Fired when a CellEditor has a keydown.
15505       *
15506       * @event editorKeydownEvent
15507       * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
15508       * @param oArgs.event {HTMLEvent} The event object.
15509       */
15510  
15511      /**
15512       * Fired when a CellEditor input is reverted.
15513       *
15514       * @event editorRevertEvent
15515       * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
15516       * @param oArgs.newData {Object} New data value from form input field.
15517       * @param oArgs.oldData {Object} Old data value.
15518       */
15519  
15520      /**
15521       * Fired when a CellEditor input is saved.
15522       *
15523       * @event editorSaveEvent
15524       * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
15525       * @param oArgs.newData {Object} New data value from form input field.
15526       * @param oArgs.oldData {Object} Old data value.
15527       */
15528  
15529      /**
15530       * Fired when a CellEditor input is canceled.
15531       *
15532       * @event editorCancelEvent
15533       * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
15534       */
15535  
15536      /**
15537       * Fired when a CellEditor has a blur event.
15538       *
15539       * @event editorBlurEvent
15540       * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
15541       */
15542  
15543      /**
15544       * Fired when a CellEditor is blocked.
15545       *
15546       * @event editorBlockEvent
15547       * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
15548       */
15549  
15550      /**
15551       * Fired when a CellEditor is unblocked.
15552       *
15553       * @event editorUnblockEvent
15554       * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
15555       */
15556  
15557  
15558  
15559  
15560  
15561      /**
15562       * Fired when a link is clicked.
15563       *
15564       * @event linkClickEvent
15565       * @param oArgs.event {HTMLEvent} The event object.
15566       * @param oArgs.target {HTMLElement} The A element.
15567       */
15568  
15569      /**
15570       * Fired when a BUTTON element or INPUT element of type "button", "image",
15571       * "submit", "reset" is clicked.
15572       *
15573       * @event buttonClickEvent
15574       * @param oArgs.event {HTMLEvent} The event object.
15575       * @param oArgs.target {HTMLElement} The BUTTON element.
15576       */
15577  
15578      /**
15579       * Fired when a CHECKBOX element is clicked.
15580       *
15581       * @event checkboxClickEvent
15582       * @param oArgs.event {HTMLEvent} The event object.
15583       * @param oArgs.target {HTMLElement} The CHECKBOX element.
15584       */
15585  
15586      /**
15587       * Fired when a SELECT element is changed.
15588       *
15589       * @event dropdownChangeEvent
15590       * @param oArgs.event {HTMLEvent} The event object.
15591       * @param oArgs.target {HTMLElement} The SELECT element.
15592       */
15593  
15594      /**
15595       * Fired when a RADIO element is clicked.
15596       *
15597       * @event radioClickEvent
15598       * @param oArgs.event {HTMLEvent} The event object.
15599       * @param oArgs.target {HTMLElement} The RADIO element.
15600       */
15601  
15602  
15603  
15604  
15605  
15606  
15607  
15608  
15609  
15610  
15611  
15612  
15613  
15614  
15615  
15616  
15617  
15618  
15619  
15620  
15621  
15622  
15623  
15624  
15625  
15626  
15627  /////////////////////////////////////////////////////////////////////////////
15628  //
15629  // Deprecated APIs
15630  //
15631  /////////////////////////////////////////////////////////////////////////////
15632    
15633  /*
15634   * @method showCellEditorBtns
15635   * @deprecated Use CellEditor.renderBtns() 
15636   */
15637  showCellEditorBtns : function(elContainer) {
15638      // Buttons
15639      var elBtnsDiv = elContainer.appendChild(document.createElement("div"));
15640      Dom.addClass(elBtnsDiv, DT.CLASS_BUTTON);
15641  
15642      // Save button
15643      var elSaveBtn = elBtnsDiv.appendChild(document.createElement("button"));
15644      Dom.addClass(elSaveBtn, DT.CLASS_DEFAULT);
15645      elSaveBtn.innerHTML = "OK";
15646      Ev.addListener(elSaveBtn, "click", function(oArgs, oSelf) {
15647          oSelf.onEventSaveCellEditor(oArgs, oSelf);
15648          oSelf.focusTbodyEl();
15649      }, this, true);
15650  
15651      // Cancel button
15652      var elCancelBtn = elBtnsDiv.appendChild(document.createElement("button"));
15653      elCancelBtn.innerHTML = "Cancel";
15654      Ev.addListener(elCancelBtn, "click", function(oArgs, oSelf) {
15655          oSelf.onEventCancelCellEditor(oArgs, oSelf);
15656          oSelf.focusTbodyEl();
15657      }, this, true);
15658  
15659  },
15660  
15661  /**
15662   * @method resetCellEditor
15663   * @deprecated Use destroyCellEditor 
15664   */
15665  resetCellEditor : function() {
15666      var elContainer = this._oCellEditor.container;
15667      elContainer.style.display = "none";
15668      Ev.purgeElement(elContainer, true);
15669      elContainer.innerHTML = "";
15670      this._oCellEditor.value = null;
15671      this._oCellEditor.isActive = false;
15672  
15673  },
15674  
15675  /**
15676   * @event editorUpdateEvent
15677   * @deprecated Use CellEditor class.
15678   */
15679  
15680  /**
15681   * @method getBody
15682   * @deprecated Use getTbodyEl().
15683   */
15684  getBody : function() {
15685      // Backward compatibility
15686      return this.getTbodyEl();
15687  },
15688  
15689  /**
15690   * @method getCell
15691   * @deprecated Use getTdEl().
15692   */
15693  getCell : function(index) {
15694      // Backward compatibility
15695      return this.getTdEl(index);
15696  },
15697  
15698  /**
15699   * @method getRow
15700   * @deprecated Use getTrEl().
15701   */
15702  getRow : function(index) {
15703      // Backward compatibility
15704      return this.getTrEl(index);
15705  },
15706  
15707  /**
15708   * @method refreshView
15709   * @deprecated Use render.
15710   */
15711  refreshView : function() {
15712      // Backward compatibility
15713      this.render();
15714  },
15715  
15716  /**
15717   * @method select
15718   * @deprecated Use selectRow.
15719   */
15720  select : function(els) {
15721      // Backward compatibility
15722      if(!lang.isArray(els)) {
15723          els = [els];
15724      }
15725      for(var i=0; i<els.length; i++) {
15726          this.selectRow(els[i]);
15727      }
15728  },
15729  
15730  /**
15731   * @method onEventEditCell
15732   * @deprecated Use onEventShowCellEditor.
15733   */
15734  onEventEditCell : function(oArgs) {
15735      // Backward compatibility
15736      this.onEventShowCellEditor(oArgs);
15737  },
15738  
15739  /**
15740   * @method _syncColWidths
15741   * @deprecated Use validateColumnWidths.
15742   */
15743  _syncColWidths : function() {
15744      // Backward compatibility
15745      this.validateColumnWidths();
15746  }
15747  
15748  /**
15749   * @event headerRowMouseoverEvent
15750   * @deprecated Use theadRowMouseoverEvent.
15751   */
15752  
15753  /**
15754   * @event headerRowMouseoutEvent
15755   * @deprecated Use theadRowMouseoutEvent.
15756   */
15757  
15758  /**
15759   * @event headerRowMousedownEvent
15760   * @deprecated Use theadRowMousedownEvent.
15761   */
15762  
15763  /**
15764   * @event headerRowClickEvent
15765   * @deprecated Use theadRowClickEvent.
15766   */
15767  
15768  /**
15769   * @event headerRowDblclickEvent
15770   * @deprecated Use theadRowDblclickEvent.
15771   */
15772  
15773  /**
15774   * @event headerCellMouseoverEvent
15775   * @deprecated Use theadCellMouseoverEvent.
15776   */
15777  
15778  /**
15779   * @event headerCellMouseoutEvent
15780   * @deprecated Use theadCellMouseoutEvent.
15781   */
15782  
15783  /**
15784   * @event headerCellMousedownEvent
15785   * @deprecated Use theadCellMousedownEvent.
15786   */
15787  
15788  /**
15789   * @event headerCellClickEvent
15790   * @deprecated Use theadCellClickEvent.
15791   */
15792  
15793  /**
15794   * @event headerCellDblclickEvent
15795   * @deprecated Use theadCellDblclickEvent.
15796   */
15797  
15798  /**
15799   * @event headerLabelMouseoverEvent
15800   * @deprecated Use theadLabelMouseoverEvent.
15801   */
15802  
15803  /**
15804   * @event headerLabelMouseoutEvent
15805   * @deprecated Use theadLabelMouseoutEvent.
15806   */
15807  
15808  /**
15809   * @event headerLabelMousedownEvent
15810   * @deprecated Use theadLabelMousedownEvent.
15811   */
15812  
15813  /**
15814   * @event headerLabelClickEvent
15815   * @deprecated Use theadLabelClickEvent.
15816   */
15817  
15818  /**
15819   * @event headerLabelDbllickEvent
15820   * @deprecated Use theadLabelDblclickEvent.
15821   */
15822  
15823  });
15824  
15825  /**
15826   * Alias for onDataReturnSetRows for backward compatibility
15827   * @method onDataReturnSetRecords
15828   * @deprecated Use onDataReturnSetRows
15829   */
15830  DT.prototype.onDataReturnSetRecords = DT.prototype.onDataReturnSetRows;
15831  
15832  /**
15833   * Alias for onPaginatorChange for backward compatibility
15834   * @method onPaginatorChange
15835   * @deprecated Use onPaginatorChangeRequest
15836   */
15837  DT.prototype.onPaginatorChange = DT.prototype.onPaginatorChangeRequest;
15838  
15839  /////////////////////////////////////////////////////////////////////////////
15840  //
15841  // Deprecated static APIs
15842  //
15843  /////////////////////////////////////////////////////////////////////////////
15844  /**
15845   * @method DataTable.editCheckbox
15846   * @deprecated  Use YAHOO.widget.CheckboxCellEditor.
15847   */
15848  DT.editCheckbox = function() {};
15849  
15850  /**
15851   * @method DataTable.editDate
15852   * @deprecated Use YAHOO.widget.DateCellEditor.
15853   */
15854  DT.editDate = function() {};
15855  
15856  /**
15857   * @method DataTable.editDropdown
15858   * @deprecated Use YAHOO.widget.DropdownCellEditor.
15859   */
15860  DT.editDropdown = function() {};
15861  
15862  /**
15863   * @method DataTable.editRadio
15864   * @deprecated Use YAHOO.widget.RadioCellEditor.
15865   */
15866  DT.editRadio = function() {};
15867  
15868  /**
15869   * @method DataTable.editTextarea
15870   * @deprecated Use YAHOO.widget.TextareaCellEditor
15871   */
15872  DT.editTextarea = function() {};
15873  
15874  /**
15875   * @method DataTable.editTextbox
15876   * @deprecated Use YAHOO.widget.TextboxCellEditor
15877   */
15878  DT.editTextbox= function() {};
15879  
15880  })();
15881  
15882  (function () {
15883  
15884  var lang   = YAHOO.lang,
15885      util   = YAHOO.util,
15886      widget = YAHOO.widget,
15887      ua     = YAHOO.env.ua,
15888      
15889      Dom    = util.Dom,
15890      Ev     = util.Event,
15891      DS     = util.DataSourceBase,
15892      DT     = widget.DataTable,
15893      Pag    = widget.Paginator;
15894      
15895  /**
15896   * The ScrollingDataTable class extends the DataTable class to provide
15897   * functionality for x-scrolling, y-scrolling, and xy-scrolling.
15898   *
15899   * @namespace YAHOO.widget
15900   * @class ScrollingDataTable
15901   * @extends YAHOO.widget.DataTable
15902   * @constructor
15903   * @param elContainer {HTMLElement} Container element for the TABLE.
15904   * @param aColumnDefs {Object[]} Array of object literal Column definitions.
15905   * @param oDataSource {YAHOO.util.DataSource} DataSource instance.
15906   * @param oConfigs {object} (optional) Object literal of configuration values.
15907   */
15908  widget.ScrollingDataTable = function(elContainer,aColumnDefs,oDataSource,oConfigs) {
15909      oConfigs = oConfigs || {};
15910      
15911      // Prevent infinite loop
15912      if(oConfigs.scrollable) {
15913          oConfigs.scrollable = false;
15914      }
15915  
15916      this._init();
15917  
15918      widget.ScrollingDataTable.superclass.constructor.call(this, elContainer,aColumnDefs,oDataSource,oConfigs); 
15919  
15920      // Once per instance
15921      this.subscribe("columnShowEvent", this._onColumnChange);
15922  };
15923  
15924  var SDT = widget.ScrollingDataTable;
15925  
15926  /////////////////////////////////////////////////////////////////////////////
15927  //
15928  // Public constants
15929  //
15930  /////////////////////////////////////////////////////////////////////////////
15931  lang.augmentObject(SDT, {
15932  
15933      /**
15934       * Class name assigned to inner DataTable header container.
15935       *
15936       * @property DataTable.CLASS_HEADER
15937       * @type String
15938       * @static
15939       * @final
15940       * @default "yui-dt-hd"
15941       */
15942      CLASS_HEADER : "yui-dt-hd",
15943      
15944      /**
15945       * Class name assigned to inner DataTable body container.
15946       *
15947       * @property DataTable.CLASS_BODY
15948       * @type String
15949       * @static
15950       * @final
15951       * @default "yui-dt-bd"
15952       */
15953      CLASS_BODY : "yui-dt-bd"
15954  });
15955  
15956  lang.extend(SDT, DT, {
15957  
15958  /**
15959   * Container for fixed header TABLE element.
15960   *
15961   * @property _elHdContainer
15962   * @type HTMLElement
15963   * @private
15964   */
15965  _elHdContainer : null,
15966  
15967  /**
15968   * Fixed header TABLE element.
15969   *
15970   * @property _elHdTable
15971   * @type HTMLElement
15972   * @private
15973   */
15974  _elHdTable : null,
15975  
15976  /**
15977   * Container for scrolling body TABLE element.
15978   *
15979   * @property _elBdContainer
15980   * @type HTMLElement
15981   * @private
15982   */
15983  _elBdContainer : null,
15984  
15985  /**
15986   * Body THEAD element.
15987   *
15988   * @property _elBdThead
15989   * @type HTMLElement
15990   * @private
15991   */
15992  _elBdThead : null,
15993  
15994  /**
15995   * Offscreen container to temporarily clone SDT for auto-width calculation.
15996   *
15997   * @property _elTmpContainer
15998   * @type HTMLElement
15999   * @private
16000   */
16001  _elTmpContainer : null,
16002  
16003  /**
16004   * Offscreen TABLE element for auto-width calculation.
16005   *
16006   * @property _elTmpTable
16007   * @type HTMLElement
16008   * @private
16009   */
16010  _elTmpTable : null,
16011  
16012  /**
16013   * True if x-scrollbar is currently visible.
16014   * @property _bScrollbarX
16015   * @type Boolean
16016   * @private 
16017   */
16018  _bScrollbarX : null,
16019  
16020  
16021  
16022  
16023  
16024  
16025  
16026  
16027  
16028  
16029  
16030  
16031  
16032  
16033  
16034  /////////////////////////////////////////////////////////////////////////////
16035  //
16036  // Superclass methods
16037  //
16038  /////////////////////////////////////////////////////////////////////////////
16039  
16040  /**
16041   * Implementation of Element's abstract method. Sets up config values.
16042   *
16043   * @method initAttributes
16044   * @param oConfigs {Object} (Optional) Object literal definition of configuration values.
16045   * @private
16046   */
16047  
16048  initAttributes : function(oConfigs) {
16049      oConfigs = oConfigs || {};
16050      SDT.superclass.initAttributes.call(this, oConfigs);
16051  
16052      /**
16053      * @attribute width
16054      * @description Table width for scrollable tables (e.g., "40em").
16055      * @type String
16056      */
16057      this.setAttributeConfig("width", {
16058          value: null,
16059          validator: lang.isString,
16060          method: function(oParam) {
16061              if(this._elHdContainer && this._elBdContainer) {
16062                  this._elHdContainer.style.width = oParam;
16063                  this._elBdContainer.style.width = oParam;            
16064                  this._syncScrollX();      
16065                  this._syncScrollOverhang();
16066              }
16067          }
16068      });
16069  
16070      /**
16071      * @attribute height
16072      * @description Table body height for scrollable tables, not including headers (e.g., "40em").
16073      * @type String
16074      */
16075      this.setAttributeConfig("height", {
16076          value: null,
16077          validator: lang.isString,
16078          method: function(oParam) {
16079              if(this._elHdContainer && this._elBdContainer) {
16080                  this._elBdContainer.style.height = oParam;    
16081                  this._syncScrollX();   
16082                  this._syncScrollY();
16083                  this._syncScrollOverhang();
16084              }
16085          }
16086      });
16087  
16088      /**
16089      * @attribute COLOR_COLUMNFILLER
16090      * @description CSS color value assigned to header filler on scrollable tables.  
16091      * @type String
16092      * @default "#F2F2F2"
16093      */
16094      this.setAttributeConfig("COLOR_COLUMNFILLER", {
16095          value: "#F2F2F2",
16096          validator: lang.isString,
16097          method: function(oParam) {
16098              if(this._elHdContainer) {
16099                  this._elHdContainer.style.backgroundColor = oParam;
16100              }
16101          }
16102      });
16103  },
16104  
16105  /**
16106   * Initializes internal variables.
16107   *
16108   * @method _init
16109   * @private
16110   */
16111  _init : function() {
16112      this._elHdContainer = null;
16113      this._elHdTable = null;
16114      this._elBdContainer = null;
16115      this._elBdThead = null;
16116      this._elTmpContainer = null;
16117      this._elTmpTable = null;
16118  },
16119  
16120  /**
16121   * Initializes DOM elements for a ScrollingDataTable, including creation of
16122   * two separate TABLE elements.
16123   *
16124   * @method _initDomElements
16125   * @param elContainer {HTMLElement | String} HTML DIV element by reference or ID. 
16126   * return {Boolean} False in case of error, otherwise true 
16127   * @private
16128   */
16129  _initDomElements : function(elContainer) {
16130      // Outer and inner containers
16131      this._initContainerEl(elContainer);
16132      if(this._elContainer && this._elHdContainer && this._elBdContainer) {
16133          // TABLEs
16134          this._initTableEl();
16135          
16136          if(this._elHdTable && this._elTable) {
16137              // COLGROUPs
16138              ///this._initColgroupEl(this._elHdTable, this._elTable);  
16139              this._initColgroupEl(this._elHdTable);        
16140              
16141              // THEADs
16142              this._initTheadEl(this._elHdTable, this._elTable);
16143              
16144              // Primary TBODY
16145              this._initTbodyEl(this._elTable);
16146              // Message TBODY
16147              this._initMsgTbodyEl(this._elTable);            
16148          }
16149      }
16150      if(!this._elContainer || !this._elTable || !this._elColgroup ||  !this._elThead || !this._elTbody || !this._elMsgTbody ||
16151              !this._elHdTable || !this._elBdThead) {
16152          return false;
16153      }
16154      else {
16155          return true;
16156      }
16157  },
16158  
16159  /**
16160   * Destroy's the DataTable outer and inner container elements, if available.
16161   *
16162   * @method _destroyContainerEl
16163   * @param elContainer {HTMLElement} Reference to the container element. 
16164   * @private
16165   */
16166  _destroyContainerEl : function(elContainer) {
16167      Dom.removeClass(elContainer, DT.CLASS_SCROLLABLE);
16168      SDT.superclass._destroyContainerEl.call(this, elContainer);
16169      this._elHdContainer = null;
16170      this._elBdContainer = null;
16171  },
16172  
16173  /**
16174   * Initializes the DataTable outer container element and creates inner header
16175   * and body container elements.
16176   *
16177   * @method _initContainerEl
16178   * @param elContainer {HTMLElement | String} HTML DIV element by reference or ID.
16179   * @private
16180   */
16181  _initContainerEl : function(elContainer) {
16182      SDT.superclass._initContainerEl.call(this, elContainer);
16183      
16184      if(this._elContainer) {
16185          elContainer = this._elContainer; // was constructor input, now is DOM ref
16186          Dom.addClass(elContainer, DT.CLASS_SCROLLABLE);
16187          
16188          // Container for header TABLE
16189          var elHdContainer = document.createElement("div");
16190          elHdContainer.style.width = this.get("width") || "";
16191          elHdContainer.style.backgroundColor = this.get("COLOR_COLUMNFILLER");
16192          Dom.addClass(elHdContainer, SDT.CLASS_HEADER);
16193          this._elHdContainer = elHdContainer;
16194          elContainer.appendChild(elHdContainer);
16195      
16196          // Container for body TABLE
16197          var elBdContainer = document.createElement("div");
16198          elBdContainer.style.width = this.get("width") || "";
16199          elBdContainer.style.height = this.get("height") || "";
16200          Dom.addClass(elBdContainer, SDT.CLASS_BODY);
16201          Ev.addListener(elBdContainer, "scroll", this._onScroll, this); // to sync horiz scroll headers
16202          this._elBdContainer = elBdContainer;
16203          elContainer.appendChild(elBdContainer);
16204      }
16205  },
16206  
16207  /**
16208   * Creates HTML markup CAPTION element.
16209   *
16210   * @method _initCaptionEl
16211   * @param sCaption {String} Text for caption.
16212   * @private
16213   */
16214  _initCaptionEl : function(sCaption) {
16215      // Not yet supported
16216      /*if(this._elHdTable && sCaption) {
16217          // Create CAPTION element
16218          if(!this._elCaption) { 
16219              this._elCaption = this._elHdTable.createCaption();
16220          }
16221          // Set CAPTION value
16222          this._elCaption.innerHTML = sCaption;
16223      }
16224      else if(this._elCaption) {
16225          this._elCaption.parentNode.removeChild(this._elCaption);
16226      }*/
16227  },
16228  
16229  /**
16230   * Destroy's the DataTable head TABLE element, if available.
16231   *
16232   * @method _destroyHdTableEl
16233   * @private
16234   */
16235  _destroyHdTableEl : function() {
16236      var elTable = this._elHdTable;
16237      if(elTable) {
16238          Ev.purgeElement(elTable, true);
16239          elTable.parentNode.removeChild(elTable);
16240          
16241          // A little out of place, but where else can we null out these extra elements?
16242          ///this._elBdColgroup = null;
16243          this._elBdThead = null;
16244      }
16245  },
16246  
16247  /**
16248   * Initializes ScrollingDataTable TABLE elements into the two inner containers.
16249   *
16250   * @method _initTableEl
16251   * @private
16252   */
16253  _initTableEl : function() {
16254      // Head TABLE
16255      if(this._elHdContainer) {
16256          this._destroyHdTableEl();
16257      
16258          // Create TABLE
16259          this._elHdTable = this._elHdContainer.appendChild(document.createElement("table"));   
16260  
16261          // Set up mouseover/mouseout events via mouseenter/mouseleave delegation
16262          Ev.delegate(this._elHdTable, "mouseenter", this._onTableMouseover, "thead ."+DT.CLASS_LABEL, this);
16263          Ev.delegate(this._elHdTable, "mouseleave", this._onTableMouseout, "thead ."+DT.CLASS_LABEL, this);
16264      }
16265      // Body TABLE
16266      SDT.superclass._initTableEl.call(this, this._elBdContainer);
16267  },
16268  
16269  /**
16270   * Initializes ScrollingDataTable THEAD elements into the two inner containers.
16271   *
16272   * @method _initTheadEl
16273   * @param elHdTable {HTMLElement} (optional) Fixed header TABLE element reference.
16274   * @param elTable {HTMLElement} (optional) TABLE element reference.
16275   * @private
16276   */
16277  _initTheadEl : function(elHdTable, elTable) {
16278      elHdTable = elHdTable || this._elHdTable;
16279      elTable = elTable || this._elTable;
16280      
16281      // Scrolling body's THEAD
16282      this._initBdTheadEl(elTable);
16283      // Standard fixed head THEAD
16284      SDT.superclass._initTheadEl.call(this, elHdTable);
16285  },
16286  
16287  /**
16288   * SDT changes ID so as not to duplicate the accessibility TH IDs.
16289   *
16290   * @method _initThEl
16291   * @param elTh {HTMLElement} TH element reference.
16292   * @param oColumn {YAHOO.widget.Column} Column object.
16293   * @private
16294   */
16295  _initThEl : function(elTh, oColumn) {
16296      SDT.superclass._initThEl.call(this, elTh, oColumn);
16297      elTh.id = this.getId() +"-fixedth-" + oColumn.getSanitizedKey(); // Needed for getColumn by TH and ColumnDD
16298  },
16299  
16300  /**
16301   * Destroy's the DataTable body THEAD element, if available.
16302   *
16303   * @method _destroyBdTheadEl
16304   * @private
16305   */
16306  _destroyBdTheadEl : function() {
16307      var elBdThead = this._elBdThead;
16308      if(elBdThead) {
16309          var elTable = elBdThead.parentNode;
16310          Ev.purgeElement(elBdThead, true);
16311          elTable.removeChild(elBdThead);
16312          this._elBdThead = null;
16313  
16314          this._destroyColumnHelpers();
16315      }
16316  },
16317  
16318  /**
16319   * Initializes body THEAD element.
16320   *
16321   * @method _initBdTheadEl
16322   * @param elTable {HTMLElement} TABLE element into which to create THEAD.
16323   * @return {HTMLElement} Initialized THEAD element. 
16324   * @private
16325   */
16326  _initBdTheadEl : function(elTable) {
16327      if(elTable) {
16328          // Destroy previous
16329          this._destroyBdTheadEl();
16330  
16331          var elThead = elTable.insertBefore(document.createElement("thead"), elTable.firstChild);
16332          
16333          // Add TRs to the THEAD;
16334          var oColumnSet = this._oColumnSet,
16335              colTree = oColumnSet.tree,
16336              elTh, elTheadTr, oColumn, i, j, k, len;
16337  
16338          for(i=0, k=colTree.length; i<k; i++) {
16339              elTheadTr = elThead.appendChild(document.createElement("tr"));
16340      
16341              // ...and create TH cells
16342              for(j=0, len=colTree[i].length; j<len; j++) {
16343                  oColumn = colTree[i][j];
16344                  elTh = elTheadTr.appendChild(document.createElement("th"));
16345                  this._initBdThEl(elTh,oColumn,i,j);
16346              }
16347          }
16348          this._elBdThead = elThead;
16349      }
16350  },
16351  
16352  /**
16353   * Populates TH element for the body THEAD element.
16354   *
16355   * @method _initBdThEl
16356   * @param elTh {HTMLElement} TH element reference.
16357   * @param oColumn {YAHOO.widget.Column} Column object.
16358   * @private
16359   */
16360  _initBdThEl : function(elTh, oColumn) {
16361      elTh.id = this.getId()+"-th-" + oColumn.getSanitizedKey(); // Needed for accessibility
16362      elTh.rowSpan = oColumn.getRowspan();
16363      elTh.colSpan = oColumn.getColspan();
16364      // Assign abbr attribute
16365      if(oColumn.abbr) {
16366          elTh.abbr = oColumn.abbr;
16367      }
16368  
16369      // TODO: strip links and form elements
16370      var sKey = oColumn.getKey();
16371      var sLabel = lang.isValue(oColumn.label) ? oColumn.label : sKey;
16372      elTh.innerHTML = sLabel;
16373  },
16374  
16375  /**
16376   * Initializes ScrollingDataTable TBODY element for data
16377   *
16378   * @method _initTbodyEl
16379   * @param elTable {HTMLElement} TABLE element into which to create TBODY .
16380   * @private
16381   */
16382  _initTbodyEl : function(elTable) {
16383      SDT.superclass._initTbodyEl.call(this, elTable);
16384      
16385      // Bug 2105534 - Safari 3 gap
16386      // Bug 2492591 - IE8 offsetTop
16387      elTable.style.marginTop = (this._elTbody.offsetTop > 0) ?
16388              "-"+this._elTbody.offsetTop+"px" : 0;
16389  },
16390  
16391  
16392  
16393  
16394  
16395  
16396  
16397  
16398  
16399  
16400  
16401  
16402  
16403  
16404  
16405  
16406  
16407  
16408  
16409  
16410  
16411  
16412  
16413  
16414  
16415  
16416  
16417  
16418  
16419  /**
16420   * Sets focus on the given element.
16421   *
16422   * @method _focusEl
16423   * @param el {HTMLElement} Element.
16424   * @private
16425   */
16426  _focusEl : function(el) {
16427      el = el || this._elTbody;
16428      var oSelf = this;
16429      this._storeScrollPositions();
16430      // http://developer.mozilla.org/en/docs/index.php?title=Key-navigable_custom_DHTML_widgets
16431      // The timeout is necessary in both IE and Firefox 1.5, to prevent scripts from doing
16432      // strange unexpected things as the user clicks on buttons and other controls.
16433      
16434      // Bug 1921135: Wrap the whole thing in a setTimeout
16435      setTimeout(function() {
16436          setTimeout(function() {
16437              try {
16438                  el.focus();
16439                  oSelf._restoreScrollPositions();
16440              }
16441              catch(e) {
16442              }
16443          },0);
16444      }, 0);
16445  },
16446  
16447  
16448  
16449  
16450  
16451  
16452  
16453  
16454  
16455  
16456  
16457  
16458  
16459  
16460  
16461  
16462  
16463  
16464  
16465  /**
16466   * Internal wrapper calls run() on render Chain instance.
16467   *
16468   * @method _runRenderChain
16469   * @private 
16470   */
16471  _runRenderChain : function() {
16472      this._storeScrollPositions();
16473      this._oChainRender.run();
16474  },
16475  
16476  /**
16477   * Stores scroll positions so they can be restored after a render.
16478   *
16479   * @method _storeScrollPositions
16480   * @private
16481   */
16482   _storeScrollPositions : function() {
16483      this._nScrollTop = this._elBdContainer.scrollTop;
16484      this._nScrollLeft = this._elBdContainer.scrollLeft;
16485  },
16486  
16487  /**
16488   * Clears stored scroll positions to interrupt the automatic restore mechanism.
16489   * Useful for setting scroll positions programmatically rather than as part of
16490   * the post-render cleanup process.
16491   *
16492   * @method clearScrollPositions
16493   * @private
16494   */
16495   clearScrollPositions : function() {
16496      this._nScrollTop = 0;
16497      this._nScrollLeft = 0;
16498  },
16499  
16500  /**
16501   * Restores scroll positions to stored value. 
16502   *
16503   * @method _retoreScrollPositions
16504   * @private 
16505   */
16506   _restoreScrollPositions : function() {
16507      // Reset scroll positions
16508      if(this._nScrollTop) {
16509          this._elBdContainer.scrollTop = this._nScrollTop;
16510          this._nScrollTop = null;
16511      } 
16512      if(this._nScrollLeft) {
16513          this._elBdContainer.scrollLeft = this._nScrollLeft;
16514          // Bug 2529024
16515          this._elHdContainer.scrollLeft = this._nScrollLeft; 
16516          this._nScrollLeft = null;
16517      } 
16518  },
16519  
16520  /**
16521   * Helper function calculates and sets a validated width for a Column in a ScrollingDataTable.
16522   *
16523   * @method _validateColumnWidth
16524   * @param oColumn {YAHOO.widget.Column} Column instance.
16525   * @param elTd {HTMLElement} TD element to validate against.
16526   * @private
16527   */
16528  _validateColumnWidth : function(oColumn, elTd) {
16529      // Only Columns without widths that are not hidden
16530      if(!oColumn.width && !oColumn.hidden) {
16531          var elTh = oColumn.getThEl();
16532          // Unset a calculated auto-width
16533          if(oColumn._calculatedWidth) {
16534              this._setColumnWidth(oColumn, "auto", "visible");
16535          }
16536          // Compare auto-widths
16537          if(elTh.offsetWidth !== elTd.offsetWidth) {
16538              var elWider = (elTh.offsetWidth > elTd.offsetWidth) ?
16539                      oColumn.getThLinerEl() : elTd.firstChild;               
16540  
16541              // Grab the wider liner width, unless the minWidth is wider
16542              var newWidth = Math.max(0,
16543                  (elWider.offsetWidth -(parseInt(Dom.getStyle(elWider,"paddingLeft"),10)|0) - (parseInt(Dom.getStyle(elWider,"paddingRight"),10)|0)),
16544                  oColumn.minWidth);
16545                  
16546              var sOverflow = 'visible';
16547              
16548              // Now validate against maxAutoWidth
16549              if((oColumn.maxAutoWidth > 0) && (newWidth > oColumn.maxAutoWidth)) {
16550                  newWidth = oColumn.maxAutoWidth;
16551                  sOverflow = "hidden";
16552              }
16553  
16554              // Set to the wider auto-width
16555              this._elTbody.style.display = "none";
16556              this._setColumnWidth(oColumn, newWidth+'px', sOverflow);
16557              oColumn._calculatedWidth = newWidth;
16558              this._elTbody.style.display = "";
16559          }
16560      }
16561  },
16562  
16563  /**
16564   * For one or all Columns of a ScrollingDataTable, when Column is not hidden,
16565   * and width is not set, syncs widths of header and body cells and 
16566   * validates that width against minWidth and/or maxAutoWidth as necessary.
16567   *
16568   * @method validateColumnWidths
16569   * @param oArg.column {YAHOO.widget.Column} (optional) One Column to validate. If null, all Columns' widths are validated.
16570   */
16571  validateColumnWidths : function(oColumn) {
16572      // Validate there is at least one TR with proper TDs
16573      var allKeys   = this._oColumnSet.keys,
16574          allKeysLength = allKeys.length,
16575          elRow     = this.getFirstTrEl();
16576  
16577      // Reset overhang for IE
16578      if(ua.ie) {
16579          this._setOverhangValue(1);
16580      }
16581  
16582      if(allKeys && elRow && (elRow.childNodes.length === allKeysLength)) {
16583          // Temporarily unsnap container since it causes inaccurate calculations
16584          var sWidth = this.get("width");
16585          if(sWidth) {
16586              this._elHdContainer.style.width = "";
16587              this._elBdContainer.style.width = "";
16588          }
16589          this._elContainer.style.width = "";
16590          
16591          //Validate just one Column
16592          if(oColumn && lang.isNumber(oColumn.getKeyIndex())) {
16593              this._validateColumnWidth(oColumn, elRow.childNodes[oColumn.getKeyIndex()]);
16594          }
16595          // Iterate through all Columns to unset calculated widths in one pass
16596          else {
16597              var elTd, todos = [], thisTodo, i, len;
16598              for(i=0; i<allKeysLength; i++) {
16599                  oColumn = allKeys[i];
16600                  // Only Columns without widths that are not hidden, unset a calculated auto-width
16601                  if(!oColumn.width && !oColumn.hidden && oColumn._calculatedWidth) {
16602                      todos[todos.length] = oColumn;      
16603                  }
16604              }
16605              
16606              this._elTbody.style.display = "none";
16607              for(i=0, len=todos.length; i<len; i++) {
16608                  this._setColumnWidth(todos[i], "auto", "visible");
16609              }
16610              this._elTbody.style.display = "";
16611              
16612              todos = [];
16613  
16614              // Iterate through all Columns and make the store the adjustments to make in one pass
16615              for(i=0; i<allKeysLength; i++) {
16616                  oColumn = allKeys[i];
16617                  elTd = elRow.childNodes[i];
16618                  // Only Columns without widths that are not hidden
16619                  if(!oColumn.width && !oColumn.hidden) {
16620                      var elTh = oColumn.getThEl();
16621  
16622                      // Compare auto-widths
16623                      if(elTh.offsetWidth !== elTd.offsetWidth) {
16624                          var elWider = (elTh.offsetWidth > elTd.offsetWidth) ?
16625                                  oColumn.getThLinerEl() : elTd.firstChild;               
16626                  
16627                          // Grab the wider liner width, unless the minWidth is wider
16628                          var newWidth = Math.max(0,
16629                              (elWider.offsetWidth -(parseInt(Dom.getStyle(elWider,"paddingLeft"),10)|0) - (parseInt(Dom.getStyle(elWider,"paddingRight"),10)|0)),
16630                              oColumn.minWidth);
16631                              
16632                          var sOverflow = 'visible';
16633                          
16634                          // Now validate against maxAutoWidth
16635                          if((oColumn.maxAutoWidth > 0) && (newWidth > oColumn.maxAutoWidth)) {
16636                              newWidth = oColumn.maxAutoWidth;
16637                              sOverflow = "hidden";
16638                          }
16639                  
16640                          todos[todos.length] = [oColumn, newWidth, sOverflow];
16641                      }
16642                  }
16643              }
16644              
16645              this._elTbody.style.display = "none";
16646              for(i=0, len=todos.length; i<len; i++) {
16647                  thisTodo = todos[i];
16648                  // Set to the wider auto-width
16649                  this._setColumnWidth(thisTodo[0], thisTodo[1]+"px", thisTodo[2]);
16650                  thisTodo[0]._calculatedWidth = thisTodo[1];
16651              }
16652              this._elTbody.style.display = "";
16653          }
16654      
16655          // Resnap unsnapped containers
16656          if(sWidth) {
16657              this._elHdContainer.style.width = sWidth;
16658              this._elBdContainer.style.width = sWidth;
16659          } 
16660      }
16661      
16662      this._syncScroll();
16663      this._restoreScrollPositions();
16664  },
16665  
16666  /**
16667   * Syncs padding around scrollable tables, including Column header right-padding
16668   * and container width and height.
16669   *
16670   * @method _syncScroll
16671   * @private 
16672   */
16673  _syncScroll : function() {
16674      this._syncScrollX();
16675      this._syncScrollY();
16676      this._syncScrollOverhang();
16677      if(ua.opera) {
16678          // Bug 1925874
16679          this._elHdContainer.scrollLeft = this._elBdContainer.scrollLeft;
16680          if(!this.get("width")) {
16681              // Bug 1926125
16682              document.body.style += '';
16683          }
16684      }
16685   },
16686  
16687  /**
16688   * Snaps container width for y-scrolling tables.
16689   *
16690   * @method _syncScrollY
16691   * @private
16692   */
16693  _syncScrollY : function() {
16694      var elTbody = this._elTbody,
16695          elBdContainer = this._elBdContainer;
16696      
16697      // X-scrolling not enabled
16698      if(!this.get("width")) {
16699          // Snap outer container width to content
16700          this._elContainer.style.width = 
16701                  (elBdContainer.scrollHeight > elBdContainer.clientHeight) ?
16702                  // but account for y-scrollbar since it is visible
16703                  (elTbody.parentNode.clientWidth + 19) + "px" :
16704                  // no y-scrollbar, just borders
16705                  (elTbody.parentNode.clientWidth + 2) + "px";
16706      }
16707  },
16708  
16709  /**
16710   * Snaps container height for x-scrolling tables in IE. Syncs message TBODY width.
16711   *
16712   * @method _syncScrollX
16713   * @private
16714   */
16715  _syncScrollX : function() {
16716      var elTbody = this._elTbody,
16717          elBdContainer = this._elBdContainer;
16718      
16719      // IE 6 and 7 only when y-scrolling not enabled
16720      if(!this.get("height") && (ua.ie)) {
16721          // Snap outer container height to content
16722          elBdContainer.style.height = 
16723                  // but account for x-scrollbar if it is visible
16724                  (elBdContainer.scrollWidth > elBdContainer.offsetWidth ) ?
16725                  (elTbody.parentNode.offsetHeight + 18) + "px" : 
16726                  elTbody.parentNode.offsetHeight + "px";
16727      }
16728  
16729      // Sync message tbody
16730      if(this._elTbody.rows.length === 0) {
16731          this._elMsgTbody.parentNode.style.width = this.getTheadEl().parentNode.offsetWidth + "px";
16732      }
16733      else {
16734          this._elMsgTbody.parentNode.style.width = "";
16735      }
16736  },
16737  
16738  /**
16739   * Adds/removes Column header overhang as necesary.
16740   *
16741   * @method _syncScrollOverhang
16742   * @private
16743   */
16744  _syncScrollOverhang : function() {
16745      var elBdContainer = this._elBdContainer,
16746          // Overhang should be either 1 (default) or 18px, depending on the location of the right edge of the table
16747          nPadding = 1;
16748      
16749      // Y-scrollbar is visible, which is when the overhang needs to jut out
16750      if((elBdContainer.scrollHeight > elBdContainer.clientHeight) &&
16751          // X-scrollbar is also visible, which means the right is jagged, not flush with the Column
16752          (elBdContainer.scrollWidth > elBdContainer.clientWidth)) {
16753          nPadding = 18;
16754      }
16755      
16756      this._setOverhangValue(nPadding);
16757      
16758  },
16759  
16760  /**
16761   * Sets Column header overhang to given width.
16762   *
16763   * @method _setOverhangValue
16764   * @param nBorderWidth {Number} Value of new border for overhang. 
16765   * @private
16766   */
16767  _setOverhangValue : function(nBorderWidth) {
16768      var aLastHeaders = this._oColumnSet.headers[this._oColumnSet.headers.length-1] || [],
16769          len = aLastHeaders.length,
16770          sPrefix = this._sId+"-fixedth-",
16771          sValue = nBorderWidth + "px solid " + this.get("COLOR_COLUMNFILLER");
16772  
16773      this._elThead.style.display = "none";
16774      for(var i=0; i<len; i++) {
16775          Dom.get(sPrefix+aLastHeaders[i]).style.borderRight = sValue;
16776      }
16777      this._elThead.style.display = "";
16778  },
16779  
16780  
16781  
16782  
16783  
16784  
16785  
16786  
16787  
16788  
16789  
16790  
16791  
16792  
16793  
16794  
16795  
16796  
16797  
16798  
16799  
16800  
16801  
16802  
16803  
16804  
16805  
16806  
16807  
16808  
16809  
16810  
16811  
16812  
16813  
16814  
16815  
16816  
16817  /**
16818   * Returns DOM reference to the DataTable's fixed header container element.
16819   *
16820   * @method getHdContainerEl
16821   * @return {HTMLElement} Reference to DIV element.
16822   */
16823  getHdContainerEl : function() {
16824      return this._elHdContainer;
16825  },
16826  
16827  /**
16828   * Returns DOM reference to the DataTable's scrolling body container element.
16829   *
16830   * @method getBdContainerEl
16831   * @return {HTMLElement} Reference to DIV element.
16832   */
16833  getBdContainerEl : function() {
16834      return this._elBdContainer;
16835  },
16836  
16837  /**
16838   * Returns DOM reference to the DataTable's fixed header TABLE element.
16839   *
16840   * @method getHdTableEl
16841   * @return {HTMLElement} Reference to TABLE element.
16842   */
16843  getHdTableEl : function() {
16844      return this._elHdTable;
16845  },
16846  
16847  /**
16848   * Returns DOM reference to the DataTable's scrolling body TABLE element.
16849   *
16850   * @method getBdTableEl
16851   * @return {HTMLElement} Reference to TABLE element.
16852   */
16853  getBdTableEl : function() {
16854      return this._elTable;
16855  },
16856  
16857  /**
16858   * Disables ScrollingDataTable UI.
16859   *
16860   * @method disable
16861   */
16862  disable : function() {
16863      var elMask = this._elMask;
16864      elMask.style.width = this._elBdContainer.offsetWidth + "px";
16865      elMask.style.height = this._elHdContainer.offsetHeight + this._elBdContainer.offsetHeight + "px";
16866      elMask.style.display = "";
16867      this.fireEvent("disableEvent");
16868  },
16869  
16870  /**
16871   * Removes given Column. NOTE: You cannot remove nested Columns. You can only remove
16872   * non-nested Columns, and top-level parent Columns (which will remove all
16873   * children Columns).
16874   *
16875   * @method removeColumn
16876   * @param oColumn {YAHOO.widget.Column} Column instance.
16877   * @return oColumn {YAHOO.widget.Column} Removed Column instance.
16878   */
16879  removeColumn : function(oColumn) {
16880      // Store scroll pos
16881      var hdPos = this._elHdContainer.scrollLeft;
16882      var bdPos = this._elBdContainer.scrollLeft;
16883      
16884      // Call superclass method
16885      oColumn = SDT.superclass.removeColumn.call(this, oColumn);
16886      
16887      // Restore scroll pos
16888      this._elHdContainer.scrollLeft = hdPos;
16889      this._elBdContainer.scrollLeft = bdPos;
16890      
16891      return oColumn;
16892  },
16893  
16894  /**
16895   * Inserts given Column at the index if given, otherwise at the end. NOTE: You
16896   * can only add non-nested Columns and top-level parent Columns. You cannot add
16897   * a nested Column to an existing parent.
16898   *
16899   * @method insertColumn
16900   * @param oColumn {Object | YAHOO.widget.Column} Object literal Column
16901   * definition or a Column instance.
16902   * @param index {Number} (optional) New tree index.
16903   * @return oColumn {YAHOO.widget.Column} Inserted Column instance. 
16904   */
16905  insertColumn : function(oColumn, index) {
16906      // Store scroll pos
16907      var hdPos = this._elHdContainer.scrollLeft;
16908      var bdPos = this._elBdContainer.scrollLeft;
16909      
16910      // Call superclass method
16911      var oNewColumn = SDT.superclass.insertColumn.call(this, oColumn, index);
16912      
16913      // Restore scroll pos
16914      this._elHdContainer.scrollLeft = hdPos;
16915      this._elBdContainer.scrollLeft = bdPos;
16916      
16917      return oNewColumn;
16918  },
16919  
16920  /**
16921   * Removes given Column and inserts into given tree index. NOTE: You
16922   * can only reorder non-nested Columns and top-level parent Columns. You cannot
16923   * reorder a nested Column to an existing parent.
16924   *
16925   * @method reorderColumn
16926   * @param oColumn {YAHOO.widget.Column} Column instance.
16927   * @param index {Number} New tree index.
16928   */
16929  reorderColumn : function(oColumn, index) {
16930      // Store scroll pos
16931      var hdPos = this._elHdContainer.scrollLeft;
16932      var bdPos = this._elBdContainer.scrollLeft;
16933      
16934      // Call superclass method
16935      var oNewColumn = SDT.superclass.reorderColumn.call(this, oColumn, index);
16936      
16937      // Restore scroll pos
16938      this._elHdContainer.scrollLeft = hdPos;
16939      this._elBdContainer.scrollLeft = bdPos;
16940  
16941      return oNewColumn;
16942  },
16943  
16944  /**
16945   * Sets given Column to given pixel width. If new width is less than minWidth
16946   * width, sets to minWidth. Updates oColumn.width value.
16947   *
16948   * @method setColumnWidth
16949   * @param oColumn {YAHOO.widget.Column} Column instance.
16950   * @param nWidth {Number} New width in pixels.
16951   */
16952  setColumnWidth : function(oColumn, nWidth) {
16953      oColumn = this.getColumn(oColumn);
16954      if(oColumn) {
16955          this._storeScrollPositions();
16956  
16957          // Validate new width against minWidth
16958          if(lang.isNumber(nWidth)) {
16959              nWidth = (nWidth > oColumn.minWidth) ? nWidth : oColumn.minWidth;
16960  
16961              // Save state
16962              oColumn.width = nWidth;
16963              
16964              // Resize the DOM elements
16965              this._setColumnWidth(oColumn, nWidth+"px");
16966              this._syncScroll();
16967              
16968              this.fireEvent("columnSetWidthEvent",{column:oColumn,width:nWidth});
16969          }
16970          // Unsets a width to auto-size
16971          else if(nWidth === null) {
16972              // Save state
16973              oColumn.width = nWidth;
16974              
16975              // Resize the DOM elements
16976              this._setColumnWidth(oColumn, "auto");
16977              this.validateColumnWidths(oColumn);
16978              this.fireEvent("columnUnsetWidthEvent",{column:oColumn});
16979          }
16980          
16981          // Bug 2339454: resize then sort misaligment
16982          this._clearTrTemplateEl();
16983      }
16984      else {
16985      }
16986  },
16987  
16988  /**
16989   * Scrolls to given row or cell
16990   *
16991   * @method scrollTo
16992   * @param to {YAHOO.widget.Record | HTMLElement } Itme to scroll to.
16993   */
16994  scrollTo : function(to) {
16995          var td = this.getTdEl(to);
16996          if(td) {
16997              this.clearScrollPositions();
16998              this.getBdContainerEl().scrollLeft = td.offsetLeft;
16999              this.getBdContainerEl().scrollTop = td.parentNode.offsetTop;
17000          }
17001          else {
17002              var tr = this.getTrEl(to);
17003              if(tr) {
17004                  this.clearScrollPositions();
17005                  this.getBdContainerEl().scrollTop = tr.offsetTop;
17006              }
17007          }
17008  },
17009  
17010  /**
17011   * Displays message within secondary TBODY.
17012   *
17013   * @method showTableMessage
17014   * @param sHTML {String} (optional) Value for innerHTMlang.
17015   * @param sClassName {String} (optional) Classname.
17016   */
17017  showTableMessage : function(sHTML, sClassName) {
17018      var elCell = this._elMsgTd;
17019      if(lang.isString(sHTML)) {
17020          elCell.firstChild.innerHTML = sHTML;
17021      }
17022      if(lang.isString(sClassName)) {
17023          Dom.addClass(elCell.firstChild, sClassName);
17024      }
17025  
17026      // Needed for SDT only
17027      var elThead = this.getTheadEl();
17028      var elTable = elThead.parentNode;
17029      var newWidth = elTable.offsetWidth;
17030      this._elMsgTbody.parentNode.style.width = this.getTheadEl().parentNode.offsetWidth + "px";
17031  
17032      this._elMsgTbody.style.display = "";
17033  
17034      this.fireEvent("tableMsgShowEvent", {html:sHTML, className:sClassName});
17035  },
17036  
17037  
17038  
17039  
17040  
17041  
17042  
17043  
17044  
17045  
17046  
17047  
17048  
17049  /////////////////////////////////////////////////////////////////////////////
17050  //
17051  // Private Custom Event Handlers
17052  //
17053  /////////////////////////////////////////////////////////////////////////////
17054  
17055  /**
17056   * Handles Column mutations
17057   *
17058   * @method onColumnChange
17059   * @param oArgs {Object} Custom Event data.
17060   */
17061  _onColumnChange : function(oArg) {
17062      // Figure out which Column changed
17063      var oColumn = (oArg.column) ? oArg.column :
17064              (oArg.editor) ? oArg.editor.column : null;
17065      this._storeScrollPositions();
17066      this.validateColumnWidths(oColumn);
17067  },
17068  
17069  
17070  
17071  
17072  
17073  
17074  
17075  
17076  
17077  
17078  
17079  
17080  
17081  
17082  
17083  /////////////////////////////////////////////////////////////////////////////
17084  //
17085  // Private DOM Event Handlers
17086  //
17087  /////////////////////////////////////////////////////////////////////////////
17088  
17089  /**
17090   * Syncs scrolltop and scrollleft of all TABLEs.
17091   *
17092   * @method _onScroll
17093   * @param e {HTMLEvent} The scroll event.
17094   * @param oSelf {YAHOO.widget.ScrollingDataTable} ScrollingDataTable instance.
17095   * @private
17096   */
17097  _onScroll : function(e, oSelf) {
17098      oSelf._elHdContainer.scrollLeft = oSelf._elBdContainer.scrollLeft;
17099  
17100      if(oSelf._oCellEditor && oSelf._oCellEditor.isActive) {
17101          oSelf.fireEvent("editorBlurEvent", {editor:oSelf._oCellEditor});
17102          oSelf.cancelCellEditor();
17103      }
17104  
17105      var elTarget = Ev.getTarget(e);
17106      var elTag = elTarget.nodeName.toLowerCase();
17107      oSelf.fireEvent("tableScrollEvent", {event:e, target:elTarget});
17108  },
17109  
17110  /**
17111   * Handles keydown events on the THEAD element.
17112   *
17113   * @method _onTheadKeydown
17114   * @param e {HTMLEvent} The key event.
17115   * @param oSelf {YAHOO.widget.ScrollingDataTable} ScrollingDataTable instance.
17116   * @private
17117   */
17118  _onTheadKeydown : function(e, oSelf) {
17119      // If tabbing to next TH label link causes THEAD to scroll,
17120      // need to sync scrollLeft with TBODY
17121      if(Ev.getCharCode(e) === 9) {
17122          setTimeout(function() {
17123              if((oSelf instanceof SDT) && oSelf._sId) {
17124                  oSelf._elBdContainer.scrollLeft = oSelf._elHdContainer.scrollLeft;
17125              }
17126          },0);
17127      }
17128      
17129      var elTarget = Ev.getTarget(e);
17130      var elTag = elTarget.nodeName.toLowerCase();
17131      var bKeepBubbling = true;
17132      while(elTarget && (elTag != "table")) {
17133          switch(elTag) {
17134              case "body":
17135                  return;
17136              case "input":
17137              case "textarea":
17138                  // TODO: implement textareaKeyEvent
17139                  break;
17140              case "thead":
17141                  bKeepBubbling = oSelf.fireEvent("theadKeyEvent",{target:elTarget,event:e});
17142                  break;
17143              default:
17144                  break;
17145          }
17146          if(bKeepBubbling === false) {
17147              return;
17148          }
17149          else {
17150              elTarget = elTarget.parentNode;
17151              if(elTarget) {
17152                  elTag = elTarget.nodeName.toLowerCase();
17153              }
17154          }
17155      }
17156      oSelf.fireEvent("tableKeyEvent",{target:(elTarget || oSelf._elContainer),event:e});
17157  }
17158  
17159  
17160  
17161  
17162  /**
17163   * Fired when a fixed scrolling DataTable has a scroll.
17164   *
17165   * @event tableScrollEvent
17166   * @param oArgs.event {HTMLEvent} The event object.
17167   * @param oArgs.target {HTMLElement} The DataTable's CONTAINER element (in IE)
17168   * or the DataTable's TBODY element (everyone else).
17169   *
17170   */
17171  
17172  
17173  
17174  
17175  });
17176  
17177  })();
17178  
17179  (function () {
17180  
17181  var lang   = YAHOO.lang,
17182      util   = YAHOO.util,
17183      widget = YAHOO.widget,
17184      ua     = YAHOO.env.ua,
17185      
17186      Dom    = util.Dom,
17187      Ev     = util.Event,
17188      
17189      DT     = widget.DataTable;
17190  /****************************************************************************/
17191  /****************************************************************************/
17192  /****************************************************************************/
17193      
17194  /**
17195   * The BaseCellEditor class provides base functionality common to all inline cell
17196   * editors for a DataTable widget.
17197   *
17198   * @namespace YAHOO.widget
17199   * @class BaseCellEditor
17200   * @uses YAHOO.util.EventProvider 
17201   * @constructor
17202   * @param sType {String} Type indicator, to map to YAHOO.widget.DataTable.Editors.
17203   * @param oConfigs {Object} (Optional) Object literal of configs.
17204   */
17205  widget.BaseCellEditor = function(sType, oConfigs) {
17206      this._sId = this._sId || Dom.generateId(null, "yui-ceditor"); // "yui-ceditor" + YAHOO.widget.BaseCellEditor._nCount++;
17207      YAHOO.widget.BaseCellEditor._nCount++;
17208      this._sType = sType;
17209      
17210      // Validate inputs
17211      this._initConfigs(oConfigs); 
17212      
17213      // Create Custom Events
17214      this._initEvents();
17215               
17216      // UI needs to be drawn
17217      this._needsRender = true;
17218  };
17219  
17220  var BCE = widget.BaseCellEditor;
17221  
17222  /////////////////////////////////////////////////////////////////////////////
17223  //
17224  // Static members
17225  //
17226  /////////////////////////////////////////////////////////////////////////////
17227  lang.augmentObject(BCE, {
17228  
17229  /**
17230   * Global instance counter.
17231   *
17232   * @property CellEditor._nCount
17233   * @type Number
17234   * @static
17235   * @default 0
17236   * @private 
17237   */
17238  _nCount : 0,
17239  
17240  /**
17241   * Class applied to CellEditor container.
17242   *
17243   * @property CellEditor.CLASS_CELLEDITOR
17244   * @type String
17245   * @static
17246   * @default "yui-ceditor"
17247   */
17248  CLASS_CELLEDITOR : "yui-ceditor"
17249  
17250  });
17251  
17252  BCE.prototype = {
17253  /////////////////////////////////////////////////////////////////////////////
17254  //
17255  // Private members
17256  //
17257  /////////////////////////////////////////////////////////////////////////////
17258  /**
17259   * Unique id assigned to instance "yui-ceditorN", useful prefix for generating unique
17260   * DOM ID strings and log messages.
17261   *
17262   * @property _sId
17263   * @type String
17264   * @private
17265   */
17266  _sId : null,
17267  
17268  /**
17269   * Editor type.
17270   *
17271   * @property _sType
17272   * @type String
17273   * @private
17274   */
17275  _sType : null,
17276  
17277  /**
17278   * DataTable instance.
17279   *
17280   * @property _oDataTable
17281   * @type YAHOO.widget.DataTable
17282   * @private 
17283   */
17284  _oDataTable : null,
17285  
17286  /**
17287   * Column instance.
17288   *
17289   * @property _oColumn
17290   * @type YAHOO.widget.Column
17291   * @default null
17292   * @private 
17293   */
17294  _oColumn : null,
17295  
17296  /**
17297   * Record instance.
17298   *
17299   * @property _oRecord
17300   * @type YAHOO.widget.Record
17301   * @default null
17302   * @private 
17303   */
17304  _oRecord : null,
17305  
17306  /**
17307   * TD element.
17308   *
17309   * @property _elTd
17310   * @type HTMLElement
17311   * @default null
17312   * @private
17313   */
17314  _elTd : null,
17315  
17316  /**
17317   * Container for inline editor.
17318   *
17319   * @property _elContainer
17320   * @type HTMLElement
17321   * @private 
17322   */
17323  _elContainer : null,
17324  
17325  /**
17326   * Reference to Cancel button, if available.
17327   *
17328   * @property _elCancelBtn
17329   * @type HTMLElement
17330   * @default null
17331   * @private 
17332   */
17333  _elCancelBtn : null,
17334  
17335  /**
17336   * Reference to Save button, if available.
17337   *
17338   * @property _elSaveBtn
17339   * @type HTMLElement
17340   * @default null
17341   * @private 
17342   */
17343  _elSaveBtn : null,
17344  
17345  
17346  
17347  
17348  
17349  
17350  
17351  
17352  /////////////////////////////////////////////////////////////////////////////
17353  //
17354  // Private methods
17355  //
17356  /////////////////////////////////////////////////////////////////////////////
17357  
17358  /**
17359   * Initialize configs.
17360   *
17361   * @method _initConfigs
17362   * @private   
17363   */
17364  _initConfigs : function(oConfigs) {
17365      // Object literal defines CellEditor configs
17366      if(oConfigs && YAHOO.lang.isObject(oConfigs)) {
17367          for(var sConfig in oConfigs) {
17368              if(sConfig) {
17369                  this[sConfig] = oConfigs[sConfig];
17370              }
17371          }
17372      }
17373  },
17374  
17375  /**
17376   * Initialize Custom Events.
17377   *
17378   * @method _initEvents
17379   * @private   
17380   */
17381  _initEvents : function() {
17382      this.createEvent("showEvent");
17383      this.createEvent("keydownEvent");
17384      this.createEvent("invalidDataEvent");
17385      this.createEvent("revertEvent");
17386      this.createEvent("saveEvent");
17387      this.createEvent("cancelEvent");
17388      this.createEvent("blurEvent");
17389      this.createEvent("blockEvent");
17390      this.createEvent("unblockEvent");
17391  },
17392  
17393  /**
17394   * Initialize container element.
17395   *
17396   * @method _initContainerEl
17397   * @private
17398   */
17399  _initContainerEl : function() {
17400      if(this._elContainer) {
17401          YAHOO.util.Event.purgeElement(this._elContainer, true);
17402          this._elContainer.innerHTML = "";
17403      }
17404  
17405      var elContainer = document.createElement("div");
17406      elContainer.id = this.getId() + "-container"; // Needed for tracking blur event
17407      elContainer.style.display = "none";
17408      elContainer.tabIndex = 0;
17409      
17410      this.className = lang.isArray(this.className) ? this.className : this.className ? [this.className] : [];
17411      this.className[this.className.length] = DT.CLASS_EDITOR;
17412      elContainer.className = this.className.join(" ");
17413      
17414      document.body.insertBefore(elContainer, document.body.firstChild);
17415      this._elContainer = elContainer;
17416  },
17417  
17418  /**
17419   * Initialize container shim element.
17420   *
17421   * @method _initShimEl
17422   * @private
17423   */
17424  _initShimEl : function() {
17425      // Iframe shim
17426      if(this.useIFrame) {
17427          if(!this._elIFrame) {
17428              var elIFrame = document.createElement("iframe");
17429              elIFrame.src = "javascript:false";
17430              elIFrame.frameBorder = 0;
17431              elIFrame.scrolling = "no";
17432              elIFrame.style.display = "none";
17433              elIFrame.className = DT.CLASS_EDITOR_SHIM;
17434              elIFrame.tabIndex = -1;
17435              elIFrame.role = "presentation";
17436              elIFrame.title = "Presentational iframe shim";
17437              document.body.insertBefore(elIFrame, document.body.firstChild);
17438              this._elIFrame = elIFrame;
17439          }
17440      }
17441  },
17442  
17443  /**
17444   * Hides CellEditor UI at end of interaction.
17445   *
17446   * @method _hide
17447   */
17448  _hide : function() {
17449      this.getContainerEl().style.display = "none";
17450      if(this._elIFrame) {
17451          this._elIFrame.style.display = "none";
17452      }
17453      this.isActive = false;
17454      this.getDataTable()._oCellEditor =  null;
17455  },
17456  
17457  
17458  
17459  
17460  
17461  
17462  
17463  
17464  
17465  
17466  
17467  /////////////////////////////////////////////////////////////////////////////
17468  //
17469  // Public properties
17470  //
17471  /////////////////////////////////////////////////////////////////////////////
17472  /**
17473   * Implementer defined function that can submit the input value to a server. This
17474   * function must accept the arguments fnCallback and oNewValue. When the submission
17475   * is complete, the function must also call fnCallback(bSuccess, oNewValue) to 
17476   * finish the save routine in the CellEditor. This function can also be used to 
17477   * perform extra validation or input value manipulation. 
17478   *
17479   * @property asyncSubmitter
17480   * @type HTMLFunction
17481   */
17482  asyncSubmitter : null,
17483  
17484  /**
17485   * Current value.
17486   *
17487   * @property value
17488   * @type MIXED
17489   */
17490  value : null,
17491  
17492  /**
17493   * Default value in case Record data is undefined. NB: Null values will not trigger
17494   * the default value.
17495   *
17496   * @property defaultValue
17497   * @type MIXED
17498   * @default null
17499   */
17500  defaultValue : null,
17501  
17502  /**
17503   * Validator function for input data, called from the DataTable instance scope,
17504   * receives the arguments (inputValue, currentValue, editorInstance) and returns
17505   * either the validated (or type-converted) value or undefined.
17506   *
17507   * @property validator
17508   * @type HTMLFunction
17509   * @default null
17510   */
17511  validator : null,
17512  
17513  /**
17514   * If validation is enabled, resets input field of invalid data.
17515   *
17516   * @property resetInvalidData
17517   * @type Boolean
17518   * @default true
17519   */
17520  resetInvalidData : true,
17521  
17522  /**
17523   * True if currently active.
17524   *
17525   * @property isActive
17526   * @type Boolean
17527   */
17528  isActive : false,
17529  
17530  /**
17531   * Text to display on Save button.
17532   *
17533   * @property LABEL_SAVE
17534   * @type HTML
17535   * @default "Save"
17536   */
17537  LABEL_SAVE : "Save",
17538  
17539  /**
17540   * Text to display on Cancel button.
17541   *
17542   * @property LABEL_CANCEL
17543   * @type HTML
17544   * @default "Cancel"
17545   */
17546  LABEL_CANCEL : "Cancel",
17547  
17548  /**
17549   * True if Save/Cancel buttons should not be displayed in the CellEditor.
17550   *
17551   * @property disableBtns
17552   * @type Boolean
17553   * @default false
17554   */
17555  disableBtns : false,
17556  
17557  /**
17558   * True if iframe shim for container element should be enabled.
17559   *
17560   * @property useIFrame
17561   * @type Boolean
17562   * @default false
17563   */
17564  useIFrame : false,
17565  
17566  /**
17567   * Custom CSS class or array of classes applied to the container element.
17568   *
17569   * @property className
17570   * @type String || String[]
17571   */
17572  className : null,
17573  
17574  
17575  
17576  
17577  
17578  /////////////////////////////////////////////////////////////////////////////
17579  //
17580  // Public methods
17581  //
17582  /////////////////////////////////////////////////////////////////////////////
17583  /**
17584   * CellEditor instance name, for logging.
17585   *
17586   * @method toString
17587   * @return {String} Unique name of the CellEditor instance.
17588   */
17589  
17590  toString : function() {
17591      return "CellEditor instance " + this._sId;
17592  },
17593  
17594  /**
17595   * CellEditor unique ID.
17596   *
17597   * @method getId
17598   * @return {String} Unique ID of the CellEditor instance.
17599   */
17600  
17601  getId : function() {
17602      return this._sId;
17603  },
17604  
17605  /**
17606   * Returns reference to associated DataTable instance.
17607   *
17608   * @method getDataTable
17609   * @return {YAHOO.widget.DataTable} DataTable instance.
17610   */
17611  
17612  getDataTable : function() {
17613      return this._oDataTable;
17614  },
17615  
17616  /**
17617   * Returns reference to associated Column instance.
17618   *
17619   * @method getColumn
17620   * @return {YAHOO.widget.Column} Column instance.
17621   */
17622  
17623  getColumn : function() {
17624      return this._oColumn;
17625  },
17626  
17627  /**
17628   * Returns reference to associated Record instance.
17629   *
17630   * @method getRecord
17631   * @return {YAHOO.widget.Record} Record instance.
17632   */
17633  
17634  getRecord : function() {
17635      return this._oRecord;
17636  },
17637  
17638  
17639  
17640  /**
17641   * Returns reference to associated TD element.
17642   *
17643   * @method getTdEl
17644   * @return {HTMLElement} TD element.
17645   */
17646  
17647  getTdEl : function() {
17648      return this._elTd;
17649  },
17650  
17651  /**
17652   * Returns container element.
17653   *
17654   * @method getContainerEl
17655   * @return {HTMLElement} Reference to container element.
17656   */
17657  
17658  getContainerEl : function() {
17659      return this._elContainer;
17660  },
17661  
17662  /**
17663   * Nulls out the entire CellEditor instance and related objects, removes attached
17664   * event listeners, and clears out DOM elements inside the container, removes
17665   * container from the DOM.
17666   *
17667   * @method destroy
17668   */
17669  destroy : function() {
17670      this.unsubscribeAll();
17671      
17672      // Column is late-binding in attach()
17673      var oColumn = this.getColumn();
17674      if(oColumn) {
17675          oColumn.editor = null;
17676      }
17677      
17678      var elContainer = this.getContainerEl();
17679      if (elContainer) {
17680          Ev.purgeElement(elContainer, true);
17681          elContainer.parentNode.removeChild(elContainer);
17682      }
17683  },
17684  
17685  /**
17686   * Renders DOM elements and attaches event listeners.
17687   *
17688   * @method render
17689   */
17690  render : function() {
17691      if (!this._needsRender) {
17692          return;
17693      }
17694  
17695      this._initContainerEl();
17696      this._initShimEl();
17697  
17698      // Handle ESC key
17699      Ev.addListener(this.getContainerEl(), "keydown", function(e, oSelf) {
17700          // ESC cancels Cell Editor
17701          if((e.keyCode == 27)) {
17702              var target = Ev.getTarget(e);
17703              // workaround for Mac FF3 bug that disabled clicks when ESC hit when
17704              // select is open. [bug 2273056]
17705              if (target.nodeName && target.nodeName.toLowerCase() === 'select') {
17706                  target.blur();
17707              }
17708              oSelf.cancel();
17709          }
17710          // Pass through event
17711          oSelf.fireEvent("keydownEvent", {editor:oSelf, event:e});
17712      }, this);
17713  
17714      this.renderForm();
17715  
17716      // Show Save/Cancel buttons
17717      if(!this.disableBtns) {
17718          this.renderBtns();
17719      }
17720      
17721      this.doAfterRender();
17722      this._needsRender = false;
17723  },
17724  
17725  /**
17726   * Renders Save/Cancel buttons.
17727   *
17728   * @method renderBtns
17729   */
17730  renderBtns : function() {
17731      // Buttons
17732      var elBtnsDiv = this.getContainerEl().appendChild(document.createElement("div"));
17733      elBtnsDiv.className = DT.CLASS_BUTTON;
17734  
17735      // Save button
17736      var elSaveBtn = elBtnsDiv.appendChild(document.createElement("button"));
17737      elSaveBtn.className = DT.CLASS_DEFAULT;
17738      elSaveBtn.innerHTML = this.LABEL_SAVE;
17739      Ev.addListener(elSaveBtn, "click", function(oArgs) {
17740          this.save();
17741      }, this, true);
17742      this._elSaveBtn = elSaveBtn;
17743  
17744      // Cancel button
17745      var elCancelBtn = elBtnsDiv.appendChild(document.createElement("button"));
17746      elCancelBtn.innerHTML = this.LABEL_CANCEL;
17747      Ev.addListener(elCancelBtn, "click", function(oArgs) {
17748          this.cancel();
17749      }, this, true);
17750      this._elCancelBtn = elCancelBtn;
17751  },
17752  
17753  /**
17754   * Attach CellEditor for a new interaction.
17755   *
17756   * @method attach
17757   * @param oDataTable {YAHOO.widget.DataTable} Associated DataTable instance.
17758   * @param elCell {HTMLElement} Cell to edit.  
17759   */
17760  attach : function(oDataTable, elCell) {
17761      // Validate 
17762      if(oDataTable instanceof YAHOO.widget.DataTable) {
17763          this._oDataTable = oDataTable;
17764          
17765          // Validate cell
17766          elCell = oDataTable.getTdEl(elCell);
17767          if(elCell) {
17768              this._elTd = elCell;
17769  
17770              // Validate Column
17771              var oColumn = oDataTable.getColumn(elCell);
17772              if(oColumn) {
17773                  this._oColumn = oColumn;
17774                  
17775                  // Validate Record
17776                  var oRecord = oDataTable.getRecord(elCell);
17777                  if(oRecord) {
17778                      this._oRecord = oRecord;
17779                      var value = oRecord.getData(this.getColumn().getField());
17780                      this.value = (value !== undefined) ? value : this.defaultValue;
17781                      return true;
17782                  }
17783              }            
17784          }
17785      }
17786      return false;
17787  },
17788  
17789  /**
17790   * Moves container into position for display.
17791   *
17792   * @method move
17793   */
17794  move : function() {
17795      // Move Editor
17796      var elContainer = this.getContainerEl(),
17797          elTd = this.getTdEl(),
17798          x = Dom.getX(elTd),
17799          y = Dom.getY(elTd);
17800  
17801      //TODO: remove scrolling logic
17802      // SF doesn't get xy for cells in scrolling table
17803      // when tbody display is set to block
17804      if(isNaN(x) || isNaN(y)) {
17805          var elTbody = this.getDataTable().getTbodyEl();
17806          x = elTd.offsetLeft + // cell pos relative to table
17807                  Dom.getX(elTbody.parentNode) - // plus table pos relative to document
17808                  elTbody.scrollLeft; // minus tbody scroll
17809          y = elTd.offsetTop + // cell pos relative to table
17810                  Dom.getY(elTbody.parentNode) - // plus table pos relative to document
17811                  elTbody.scrollTop + // minus tbody scroll
17812                  this.getDataTable().getTheadEl().offsetHeight; // account for fixed THEAD cells
17813      }
17814  
17815      elContainer.style.left = x + "px";
17816      elContainer.style.top = y + "px";
17817  
17818      if(this._elIFrame) {
17819          this._elIFrame.style.left = x + "px";
17820          this._elIFrame.style.top = y + "px";
17821      }
17822  },
17823  
17824  /**
17825   * Displays CellEditor UI in the correct position.
17826   *
17827   * @method show
17828   */
17829  show : function() {
17830      var elContainer = this.getContainerEl(),
17831          elIFrame = this._elIFrame;
17832      this.resetForm();
17833      this.isActive = true;
17834      elContainer.style.display = "";
17835      if(elIFrame) {
17836          elIFrame.style.width = elContainer.offsetWidth + "px";
17837          elIFrame.style.height = elContainer.offsetHeight + "px";
17838          elIFrame.style.display = "";
17839      }
17840      this.focus();
17841      this.fireEvent("showEvent", {editor:this});
17842  },
17843  
17844  /**
17845   * Fires blockEvent
17846   *
17847   * @method block
17848   */
17849  block : function() {
17850      this.fireEvent("blockEvent", {editor:this});
17851  },
17852  
17853  /**
17854   * Fires unblockEvent
17855   *
17856   * @method unblock
17857   */
17858  unblock : function() {
17859      this.fireEvent("unblockEvent", {editor:this});
17860  },
17861  
17862  /**
17863   * Saves value of CellEditor and hides UI.
17864   *
17865   * @method save
17866   */
17867  save : function() {
17868      // Get new value
17869      var inputValue = this.getInputValue();
17870      var validValue = inputValue;
17871      
17872      // Validate new value
17873      if(this.validator) {
17874          validValue = this.validator.call(this.getDataTable(), inputValue, this.value, this);
17875          if(validValue === undefined ) {
17876              if(this.resetInvalidData) {
17877                  this.resetForm();
17878              }
17879              this.fireEvent("invalidDataEvent",
17880                      {editor:this, oldData:this.value, newData:inputValue});
17881              return;
17882          }
17883      }
17884          
17885      var oSelf = this;
17886      var finishSave = function(bSuccess, oNewValue) {
17887          var oOrigValue = oSelf.value;
17888          if(bSuccess) {
17889              // Update new value
17890              oSelf.value = oNewValue;
17891              oSelf.getDataTable().updateCell(oSelf.getRecord(), oSelf.getColumn(), oNewValue);
17892              
17893              // Hide CellEditor
17894              oSelf._hide();
17895              
17896              oSelf.fireEvent("saveEvent",
17897                      {editor:oSelf, oldData:oOrigValue, newData:oSelf.value});
17898          }
17899          else {
17900              oSelf.resetForm();
17901              oSelf.fireEvent("revertEvent",
17902                      {editor:oSelf, oldData:oOrigValue, newData:oNewValue});
17903          }
17904          oSelf.unblock();
17905      };
17906      
17907      this.block();
17908      if(lang.isFunction(this.asyncSubmitter)) {
17909          this.asyncSubmitter.call(this, finishSave, validValue);
17910      } 
17911      else {   
17912          finishSave(true, validValue);
17913      }
17914  },
17915  
17916  /**
17917   * Cancels CellEditor input and hides UI.
17918   *
17919   * @method cancel
17920   */
17921  cancel : function() {
17922      if(this.isActive) {
17923          this._hide();
17924          this.fireEvent("cancelEvent", {editor:this});
17925      }
17926      else {
17927      }
17928  },
17929  
17930  /**
17931   * Renders form elements.
17932   *
17933   * @method renderForm
17934   */
17935  renderForm : function() {
17936      // To be implemented by subclass
17937  },
17938  
17939  /**
17940   * Access to add additional event listeners.
17941   *
17942   * @method doAfterRender
17943   */
17944  doAfterRender : function() {
17945      // To be implemented by subclass
17946  },
17947  
17948  
17949  /**
17950   * After rendering form, if disabledBtns is set to true, then sets up a mechanism
17951   * to save input without them. 
17952   *
17953   * @method handleDisabledBtns
17954   */
17955  handleDisabledBtns : function() {
17956      // To be implemented by subclass
17957  },
17958  
17959  /**
17960   * Resets CellEditor UI to initial state.
17961   *
17962   * @method resetForm
17963   */
17964  resetForm : function() {
17965      // To be implemented by subclass
17966  },
17967  
17968  /**
17969   * Sets focus in CellEditor.
17970   *
17971   * @method focus
17972   */
17973  focus : function() {
17974      // To be implemented by subclass
17975  },
17976  
17977  /**
17978   * Retrieves input value from CellEditor.
17979   *
17980   * @method getInputValue
17981   */
17982  getInputValue : function() {
17983      // To be implemented by subclass
17984  }
17985  
17986  };
17987  
17988  lang.augmentProto(BCE, util.EventProvider);
17989  
17990  
17991  /////////////////////////////////////////////////////////////////////////////
17992  //
17993  // Custom Events
17994  //
17995  /////////////////////////////////////////////////////////////////////////////
17996  
17997  /**
17998   * Fired when a CellEditor is shown.
17999   *
18000   * @event showEvent
18001   * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
18002   */
18003  
18004  /**
18005   * Fired when a CellEditor has a keydown.
18006   *
18007   * @event keydownEvent
18008   * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance. 
18009   * @param oArgs.event {HTMLEvent} The event object.
18010   */
18011  
18012  /**
18013   * Fired when a CellEditor input is reverted due to invalid data.
18014   *
18015   * @event invalidDataEvent
18016   * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance. 
18017   * @param oArgs.newData {Object} New data value from form input field.
18018   * @param oArgs.oldData {Object} Old data value.
18019   */
18020  
18021  /**
18022   * Fired when a CellEditor input is reverted due to asyncSubmitter failure.
18023   *
18024   * @event revertEvent
18025   * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance. 
18026   * @param oArgs.newData {Object} New data value from form input field.
18027   * @param oArgs.oldData {Object} Old data value.
18028   */
18029  
18030  /**
18031   * Fired when a CellEditor input is saved.
18032   *
18033   * @event saveEvent
18034   * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance. 
18035   * @param oArgs.newData {Object} New data value from form input field.
18036   * @param oArgs.oldData {Object} Old data value.
18037   */
18038  
18039  /**
18040   * Fired when a CellEditor input is canceled.
18041   *
18042   * @event cancelEvent
18043   * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance. 
18044   */
18045  
18046  /**
18047   * Fired when a CellEditor has a blur event.
18048   *
18049   * @event blurEvent
18050   * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance. 
18051   */
18052  
18053  
18054  
18055  
18056  
18057  
18058  
18059  
18060  
18061  
18062  
18063  
18064  
18065  
18066  /****************************************************************************/
18067  /****************************************************************************/
18068  /****************************************************************************/
18069      
18070  /**
18071   * The CheckboxCellEditor class provides functionality for inline editing
18072   * DataTable cell data with checkboxes.
18073   *
18074   * @namespace YAHOO.widget
18075   * @class CheckboxCellEditor
18076   * @extends YAHOO.widget.BaseCellEditor
18077   * @constructor
18078   * @param oConfigs {Object} (Optional) Object literal of configs.
18079   */
18080  widget.CheckboxCellEditor = function(oConfigs) {
18081      oConfigs = oConfigs || {};
18082      this._sId = this._sId || Dom.generateId(null, "yui-checkboxceditor"); // "yui-checkboxceditor" + YAHOO.widget.BaseCellEditor._nCount++;
18083      YAHOO.widget.BaseCellEditor._nCount++;
18084      widget.CheckboxCellEditor.superclass.constructor.call(this, oConfigs.type || "checkbox", oConfigs);
18085  };
18086  
18087  // CheckboxCellEditor extends BaseCellEditor
18088  lang.extend(widget.CheckboxCellEditor, BCE, {
18089  
18090  /////////////////////////////////////////////////////////////////////////////
18091  //
18092  // CheckboxCellEditor public properties
18093  //
18094  /////////////////////////////////////////////////////////////////////////////
18095  /**
18096   * Array of checkbox values. Can either be a simple array (e.g., ["red","green","blue"])
18097   * or a an array of objects (e.g., [{label:"red", value:"#FF0000"},
18098   * {label:"green", value:"#00FF00"}, {label:"blue", value:"#0000FF"}]). String
18099   * values are treated as markup and inserted into the DOM as innerHTML.
18100   *
18101   * @property checkboxOptions
18102   * @type HTML[] | Object[]
18103   */
18104  checkboxOptions : null,
18105  
18106  /**
18107   * Reference to the checkbox elements.
18108   *
18109   * @property checkboxes
18110   * @type HTMLElement[] 
18111   */
18112  checkboxes : null,
18113  
18114  /**
18115   * Array of checked values
18116   *
18117   * @property value
18118   * @type String[] 
18119   */
18120  value : null,
18121  
18122  /////////////////////////////////////////////////////////////////////////////
18123  //
18124  // CheckboxCellEditor public methods
18125  //
18126  /////////////////////////////////////////////////////////////////////////////
18127  
18128  /**
18129   * Render a form with input(s) type=checkbox.
18130   *
18131   * @method renderForm
18132   */
18133  renderForm : function() {
18134      if(lang.isArray(this.checkboxOptions)) {
18135          var checkboxOption, checkboxValue, checkboxId, elLabel, j, len;
18136          
18137          // Create the checkbox buttons in an IE-friendly way...
18138          for(j=0,len=this.checkboxOptions.length; j<len; j++) {
18139              checkboxOption = this.checkboxOptions[j];
18140              checkboxValue = lang.isValue(checkboxOption.value) ?
18141                      checkboxOption.value : checkboxOption;
18142  
18143              checkboxId = this.getId() + "-chk" + j;
18144              this.getContainerEl().innerHTML += "<input type=\"checkbox\"" +
18145                      " id=\"" + checkboxId + "\"" + // Needed for label
18146                      " value=\"" + checkboxValue + "\" />";
18147              
18148              // Create the labels in an IE-friendly way
18149              elLabel = this.getContainerEl().appendChild(document.createElement("label"));
18150              elLabel.htmlFor = checkboxId;
18151              elLabel.innerHTML = lang.isValue(checkboxOption.label) ?
18152                      checkboxOption.label : checkboxOption;
18153          }
18154          
18155          // Store the reference to the checkbox elements
18156          var allCheckboxes = [];
18157          for(j=0; j<len; j++) {
18158              allCheckboxes[allCheckboxes.length] = this.getContainerEl().childNodes[j*2];
18159          }
18160          this.checkboxes = allCheckboxes;
18161  
18162          if(this.disableBtns) {
18163              this.handleDisabledBtns();
18164          }
18165      }
18166      else {
18167      }
18168  },
18169  
18170  /**
18171   * After rendering form, if disabledBtns is set to true, then sets up a mechanism
18172   * to save input without them. 
18173   *
18174   * @method handleDisabledBtns
18175   */
18176  handleDisabledBtns : function() {
18177      Ev.addListener(this.getContainerEl(), "click", function(v){
18178          if(Ev.getTarget(v).tagName.toLowerCase() === "input") {
18179              // Save on blur
18180              this.save();
18181          }
18182      }, this, true);
18183  },
18184  
18185  /**
18186   * Resets CheckboxCellEditor UI to initial state.
18187   *
18188   * @method resetForm
18189   */
18190  resetForm : function() {
18191      // Normalize to array
18192      var originalValues = lang.isArray(this.value) ? this.value : [this.value];
18193      
18194      // Match checks to value
18195      for(var i=0, j=this.checkboxes.length; i<j; i++) {
18196          this.checkboxes[i].checked = false;
18197          for(var k=0, len=originalValues.length; k<len; k++) {
18198              if(this.checkboxes[i].value == originalValues[k]) {
18199                  this.checkboxes[i].checked = true;
18200              }
18201          }
18202      }
18203  },
18204  
18205  /**
18206   * Sets focus in CheckboxCellEditor.
18207   *
18208   * @method focus
18209   */
18210  focus : function() {
18211      this.checkboxes[0].focus();
18212  },
18213  
18214  /**
18215   * Retrieves input value from CheckboxCellEditor.
18216   *
18217   * @method getInputValue
18218   */
18219  getInputValue : function() {
18220      var checkedValues = [];
18221      for(var i=0, j=this.checkboxes.length; i<j; i++) {
18222          if(this.checkboxes[i].checked) {
18223              checkedValues[checkedValues.length] = this.checkboxes[i].value;
18224          }
18225      }  
18226      return checkedValues;
18227  }
18228  
18229  });
18230  
18231  // Copy static members to CheckboxCellEditor class
18232  lang.augmentObject(widget.CheckboxCellEditor, BCE);
18233  
18234  
18235  
18236  
18237  
18238  
18239  
18240  
18241  /****************************************************************************/
18242  /****************************************************************************/
18243  /****************************************************************************/
18244      
18245  /**
18246   * The DataCellEditor class provides functionality for inline editing
18247   * DataTable cell data with a YUI Calendar.
18248   *
18249   * @namespace YAHOO.widget
18250   * @class DateCellEditor
18251   * @extends YAHOO.widget.BaseCellEditor 
18252   * @constructor
18253   * @param oConfigs {Object} (Optional) Object literal of configs.
18254   */
18255  widget.DateCellEditor = function(oConfigs) {
18256      oConfigs = oConfigs || {};
18257      this._sId = this._sId || Dom.generateId(null, "yui-dateceditor"); // "yui-dateceditor" + YAHOO.widget.BaseCellEditor._nCount++;
18258      YAHOO.widget.BaseCellEditor._nCount++;
18259      widget.DateCellEditor.superclass.constructor.call(this, oConfigs.type || "date", oConfigs);
18260  };
18261  
18262  // CheckboxCellEditor extends BaseCellEditor
18263  lang.extend(widget.DateCellEditor, BCE, {
18264  
18265  /////////////////////////////////////////////////////////////////////////////
18266  //
18267  // DateCellEditor public properties
18268  //
18269  /////////////////////////////////////////////////////////////////////////////
18270  /**
18271   * Reference to Calendar instance.
18272   *
18273   * @property calendar
18274   * @type YAHOO.widget.Calendar
18275   */
18276  calendar : null,
18277  
18278  /**
18279   * Configs for the calendar instance, to be passed to Calendar constructor.
18280   *
18281   * @property calendarOptions
18282   * @type Object
18283   */
18284  calendarOptions : null,
18285  
18286  /**
18287   * Default value.
18288   *
18289   * @property defaultValue
18290   * @type Date
18291   * @default new Date()
18292   */
18293  defaultValue : new Date(),
18294  
18295  
18296  /////////////////////////////////////////////////////////////////////////////
18297  //
18298  // DateCellEditor public methods
18299  //
18300  /////////////////////////////////////////////////////////////////////////////
18301  
18302  /**
18303   * Render a Calendar.
18304   *
18305   * @method renderForm
18306   */
18307  renderForm : function() {
18308      // Calendar widget
18309      if(YAHOO.widget.Calendar) {
18310          var calContainer = this.getContainerEl().appendChild(document.createElement("div"));
18311          calContainer.id = this.getId() + "-dateContainer"; // Needed for Calendar constructor
18312          var calendar =
18313                  new YAHOO.widget.Calendar(this.getId() + "-date",
18314                  calContainer.id, this.calendarOptions);
18315          calendar.render();
18316          calContainer.style.cssFloat = "none";
18317          
18318          // Bug 2528576
18319          calendar.hideEvent.subscribe(function() {this.cancel();}, this, true);
18320  
18321          if(ua.ie) {
18322              var calFloatClearer = this.getContainerEl().appendChild(document.createElement("div"));
18323              calFloatClearer.style.clear = "both";
18324          }
18325          
18326          this.calendar = calendar;
18327  
18328          if(this.disableBtns) {
18329              this.handleDisabledBtns();
18330          }
18331      }
18332      else {
18333      }
18334      
18335  },
18336  
18337  /**
18338   * After rendering form, if disabledBtns is set to true, then sets up a mechanism
18339   * to save input without them. 
18340   *
18341   * @method handleDisabledBtns
18342   */
18343  handleDisabledBtns : function() {
18344      this.calendar.selectEvent.subscribe(function(v){
18345          // Save on select
18346          this.save();
18347      }, this, true);
18348  },
18349  
18350  /**
18351   * Resets DateCellEditor UI to initial state.
18352   *
18353   * @method resetForm
18354   */
18355  resetForm : function() {
18356      var value = this.value || (new Date());
18357      this.calendar.select(value);
18358      this.calendar.cfg.setProperty("pagedate",value,false);
18359      this.calendar.render();
18360      // Bug 2528576
18361      this.calendar.show();
18362  },
18363  
18364  /**
18365   * Sets focus in DateCellEditor.
18366   *
18367   * @method focus
18368   */
18369  focus : function() {
18370      // To be impmlemented by subclass
18371  },
18372  
18373  /**
18374   * Retrieves input value from DateCellEditor.
18375   *
18376   * @method getInputValue
18377   */
18378  getInputValue : function() {
18379      return this.calendar.getSelectedDates()[0];
18380  }
18381  
18382  });
18383  
18384  // Copy static members to DateCellEditor class
18385  lang.augmentObject(widget.DateCellEditor, BCE);
18386  
18387  
18388  
18389  
18390  
18391  
18392  
18393  
18394  
18395  /****************************************************************************/
18396  /****************************************************************************/
18397  /****************************************************************************/
18398      
18399  /**
18400   * The DropdownCellEditor class provides functionality for inline editing
18401   * DataTable cell data a SELECT element.
18402   *
18403   * @namespace YAHOO.widget
18404   * @class DropdownCellEditor
18405   * @extends YAHOO.widget.BaseCellEditor 
18406   * @constructor
18407   * @param oConfigs {Object} (Optional) Object literal of configs.
18408   */
18409  widget.DropdownCellEditor = function(oConfigs) {
18410      oConfigs = oConfigs || {};
18411      this._sId = this._sId || Dom.generateId(null, "yui-dropdownceditor"); // "yui-dropdownceditor" + YAHOO.widget.BaseCellEditor._nCount++;
18412      YAHOO.widget.BaseCellEditor._nCount++;
18413      widget.DropdownCellEditor.superclass.constructor.call(this, oConfigs.type || "dropdown", oConfigs);
18414  };
18415  
18416  // DropdownCellEditor extends BaseCellEditor
18417  lang.extend(widget.DropdownCellEditor, BCE, {
18418  
18419  /////////////////////////////////////////////////////////////////////////////
18420  //
18421  // DropdownCellEditor public properties
18422  //
18423  /////////////////////////////////////////////////////////////////////////////
18424  /**
18425   * Array of dropdown values. Can either be a simple array (e.g.,
18426   * ["Alabama","Alaska","Arizona","Arkansas"]) or a an array of objects (e.g., 
18427   * [{label:"Alabama", value:"AL"}, {label:"Alaska", value:"AK"},
18428   * {label:"Arizona", value:"AZ"}, {label:"Arkansas", value:"AR"}]). String
18429   * values are treated as markup and inserted into the DOM as innerHTML.
18430   *
18431   * @property dropdownOptions
18432   * @type HTML[] | Object[]
18433   */
18434  dropdownOptions : null,
18435  
18436  /**
18437   * Reference to Dropdown element.
18438   *
18439   * @property dropdown
18440   * @type HTMLElement
18441   */
18442  dropdown : null,
18443  
18444  /**
18445   * Enables multi-select.
18446   *
18447   * @property multiple
18448   * @type Boolean
18449   */
18450  multiple : false,
18451  
18452  /**
18453   * Specifies number of visible options.
18454   *
18455   * @property size
18456   * @type Number
18457   */
18458  size : null,
18459  
18460  /////////////////////////////////////////////////////////////////////////////
18461  //
18462  // DropdownCellEditor public methods
18463  //
18464  /////////////////////////////////////////////////////////////////////////////
18465  
18466  /**
18467   * Render a form with select element.
18468   *
18469   * @method renderForm
18470   */
18471  renderForm : function() {
18472      var elDropdown = this.getContainerEl().appendChild(document.createElement("select"));
18473      elDropdown.style.zoom = 1;
18474      if(this.multiple) {
18475          elDropdown.multiple = "multiple";
18476      }
18477      if(lang.isNumber(this.size)) {
18478          elDropdown.size = this.size;
18479      }
18480      this.dropdown = elDropdown;
18481      
18482      if(lang.isArray(this.dropdownOptions)) {
18483          var dropdownOption, elOption;
18484          for(var i=0, j=this.dropdownOptions.length; i<j; i++) {
18485              dropdownOption = this.dropdownOptions[i];
18486              elOption = document.createElement("option");
18487              elOption.value = (lang.isValue(dropdownOption.value)) ?
18488                      dropdownOption.value : dropdownOption;
18489              elOption.innerHTML = (lang.isValue(dropdownOption.label)) ?
18490                      dropdownOption.label : dropdownOption;
18491              elOption = elDropdown.appendChild(elOption);
18492          }
18493          
18494          if(this.disableBtns) {
18495              this.handleDisabledBtns();
18496          }
18497      }
18498  },
18499  
18500  /**
18501   * After rendering form, if disabledBtns is set to true, then sets up a mechanism
18502   * to save input without them. 
18503   *
18504   * @method handleDisabledBtns
18505   */
18506  handleDisabledBtns : function() {
18507      // Save on blur for multi-select
18508      if(this.multiple) {
18509          Ev.addListener(this.dropdown, "blur", function(v){
18510              // Save on change
18511              this.save();
18512          }, this, true);
18513      }
18514      // Save on change for single-select
18515      else {
18516          if(!ua.ie) {
18517              Ev.addListener(this.dropdown, "change", function(v){
18518                  // Save on change
18519                  this.save();
18520              }, this, true);
18521          }
18522          else {
18523              // Bug 2529274: "change" event is not keyboard accessible in IE6
18524              Ev.addListener(this.dropdown, "blur", function(v){
18525                  this.save();
18526              }, this, true);
18527              Ev.addListener(this.dropdown, "click", function(v){
18528                  this.save();
18529              }, this, true);
18530          }
18531      }
18532  },
18533  
18534  /**
18535   * Resets DropdownCellEditor UI to initial state.
18536   *
18537   * @method resetForm
18538   */
18539  resetForm : function() {
18540      var allOptions = this.dropdown.options,
18541          i=0, j=allOptions.length;
18542  
18543      // Look for multi-select selections
18544      if(lang.isArray(this.value)) {
18545          var allValues = this.value,
18546              m=0, n=allValues.length,
18547              hash = {};
18548          // Reset all selections and stash options in a value hash
18549          for(; i<j; i++) {
18550              allOptions[i].selected = false;
18551              hash[allOptions[i].value] = allOptions[i];
18552          }
18553          for(; m<n; m++) {
18554              if(hash[allValues[m]]) {
18555                  hash[allValues[m]].selected = true;
18556              }
18557          }
18558      }
18559      // Only need to look for a single selection
18560      else {
18561          for(; i<j; i++) {
18562              if(this.value == allOptions[i].value) {
18563                  allOptions[i].selected = true;
18564              }
18565          }
18566      }
18567  },
18568  
18569  /**
18570   * Sets focus in DropdownCellEditor.
18571   *
18572   * @method focus
18573   */
18574  focus : function() {
18575      this.getDataTable()._focusEl(this.dropdown);
18576  },
18577  
18578  /**
18579   * Retrieves input value from DropdownCellEditor.
18580   *
18581   * @method getInputValue
18582   */
18583  getInputValue : function() {
18584      var allOptions = this.dropdown.options;
18585      
18586      // Look for multiple selections
18587      if(this.multiple) {
18588          var values = [],
18589              i=0, j=allOptions.length;
18590          for(; i<j; i++) {
18591              if(allOptions[i].selected) {
18592                  values.push(allOptions[i].value);
18593              }
18594          }
18595          return values;
18596      }
18597      // Only need to look for single selection
18598      else {
18599          return allOptions[allOptions.selectedIndex].value;
18600      }
18601  }
18602  
18603  });
18604  
18605  // Copy static members to DropdownCellEditor class
18606  lang.augmentObject(widget.DropdownCellEditor, BCE);
18607  
18608  
18609  
18610  
18611  
18612  
18613  /****************************************************************************/
18614  /****************************************************************************/
18615  /****************************************************************************/
18616      
18617  /**
18618   * The RadioCellEditor class provides functionality for inline editing
18619   * DataTable cell data with radio buttons.
18620   *
18621   * @namespace YAHOO.widget
18622   * @class RadioCellEditor
18623   * @extends YAHOO.widget.BaseCellEditor 
18624   * @constructor
18625   * @param oConfigs {Object} (Optional) Object literal of configs.
18626   */
18627  widget.RadioCellEditor = function(oConfigs) {
18628      oConfigs = oConfigs || {};
18629      this._sId = this._sId || Dom.generateId(null, "yui-radioceditor"); // "yui-radioceditor" + YAHOO.widget.BaseCellEditor._nCount++;
18630      YAHOO.widget.BaseCellEditor._nCount++;
18631      widget.RadioCellEditor.superclass.constructor.call(this, oConfigs.type || "radio", oConfigs);
18632  };
18633  
18634  // RadioCellEditor extends BaseCellEditor
18635  lang.extend(widget.RadioCellEditor, BCE, {
18636  
18637  /////////////////////////////////////////////////////////////////////////////
18638  //
18639  // RadioCellEditor public properties
18640  //
18641  /////////////////////////////////////////////////////////////////////////////
18642  /**
18643   * Reference to radio elements.
18644   *
18645   * @property radios
18646   * @type HTMLElement[]
18647   */
18648  radios : null,
18649  
18650  /**
18651   * Array of radio values. Can either be a simple array (e.g., ["yes","no","maybe"])
18652   * or a an array of objects (e.g., [{label:"yes", value:1}, {label:"no", value:-1},
18653   * {label:"maybe", value:0}]). String values are treated as markup and inserted
18654   * into the DOM as innerHTML.
18655   *
18656   * @property radioOptions
18657   * @type HTML[] | Object[]
18658   */
18659  radioOptions : null,
18660  
18661  /////////////////////////////////////////////////////////////////////////////
18662  //
18663  // RadioCellEditor public methods
18664  //
18665  /////////////////////////////////////////////////////////////////////////////
18666  
18667  /**
18668   * Render a form with input(s) type=radio.
18669   *
18670   * @method renderForm
18671   */
18672  renderForm : function() {
18673      if(lang.isArray(this.radioOptions)) {
18674          var radioOption, radioValue, radioId, elLabel;
18675          
18676          // Create the radio buttons in an IE-friendly way
18677          for(var i=0, len=this.radioOptions.length; i<len; i++) {
18678              radioOption = this.radioOptions[i];
18679              radioValue = lang.isValue(radioOption.value) ?
18680                      radioOption.value : radioOption;
18681              radioId = this.getId() + "-radio" + i;
18682              this.getContainerEl().innerHTML += "<input type=\"radio\"" +
18683                      " name=\"" + this.getId() + "\"" +
18684                      " value=\"" + radioValue + "\"" +
18685                      " id=\"" +  radioId + "\" />"; // Needed for label
18686              
18687              // Create the labels in an IE-friendly way
18688              elLabel = this.getContainerEl().appendChild(document.createElement("label"));
18689              elLabel.htmlFor = radioId;
18690              elLabel.innerHTML = (lang.isValue(radioOption.label)) ?
18691                      radioOption.label : radioOption;
18692          }
18693          
18694          // Store the reference to the checkbox elements
18695          var allRadios = [],
18696              elRadio;
18697          for(var j=0; j<len; j++) {
18698              elRadio = this.getContainerEl().childNodes[j*2];
18699              allRadios[allRadios.length] = elRadio;
18700          }
18701          this.radios = allRadios;
18702  
18703          if(this.disableBtns) {
18704              this.handleDisabledBtns();
18705          }
18706      }
18707      else {
18708      }
18709  },
18710  
18711  /**
18712   * After rendering form, if disabledBtns is set to true, then sets up a mechanism
18713   * to save input without them. 
18714   *
18715   * @method handleDisabledBtns
18716   */
18717  handleDisabledBtns : function() {
18718      Ev.addListener(this.getContainerEl(), "click", function(v){
18719          if(Ev.getTarget(v).tagName.toLowerCase() === "input") {
18720              // Save on blur
18721              this.save();
18722          }
18723      }, this, true);
18724  },
18725  
18726  /**
18727   * Resets RadioCellEditor UI to initial state.
18728   *
18729   * @method resetForm
18730   */
18731  resetForm : function() {
18732      for(var i=0, j=this.radios.length; i<j; i++) {
18733          var elRadio = this.radios[i];
18734          if(this.value == elRadio.value) {
18735              elRadio.checked = true;
18736              return;
18737          }
18738      }
18739  },
18740  
18741  /**
18742   * Sets focus in RadioCellEditor.
18743   *
18744   * @method focus
18745   */
18746  focus : function() {
18747      for(var i=0, j=this.radios.length; i<j; i++) {
18748          if(this.radios[i].checked) {
18749              this.radios[i].focus();
18750              return;
18751          }
18752      }
18753  },
18754  
18755  /**
18756   * Retrieves input value from RadioCellEditor.
18757   *
18758   * @method getInputValue
18759   */
18760  getInputValue : function() {
18761      for(var i=0, j=this.radios.length; i<j; i++) {
18762          if(this.radios[i].checked) {
18763              return this.radios[i].value;
18764          }
18765      }
18766  }
18767  
18768  });
18769  
18770  // Copy static members to RadioCellEditor class
18771  lang.augmentObject(widget.RadioCellEditor, BCE);
18772  
18773  
18774  
18775  
18776  
18777  
18778  /****************************************************************************/
18779  /****************************************************************************/
18780  /****************************************************************************/
18781      
18782  /**
18783   * The TextareaCellEditor class provides functionality for inline editing
18784   * DataTable cell data with a TEXTAREA element.
18785   *
18786   * @namespace YAHOO.widget
18787   * @class TextareaCellEditor
18788   * @extends YAHOO.widget.BaseCellEditor 
18789   * @constructor
18790   * @param oConfigs {Object} (Optional) Object literal of configs.
18791   */
18792  widget.TextareaCellEditor = function(oConfigs) {
18793      oConfigs = oConfigs || {};
18794      this._sId = this._sId || Dom.generateId(null, "yui-textareaceditor");// "yui-textareaceditor" + ;
18795      YAHOO.widget.BaseCellEditor._nCount++;
18796      widget.TextareaCellEditor.superclass.constructor.call(this, oConfigs.type || "textarea", oConfigs);
18797  };
18798  
18799  // TextareaCellEditor extends BaseCellEditor
18800  lang.extend(widget.TextareaCellEditor, BCE, {
18801  
18802  /////////////////////////////////////////////////////////////////////////////
18803  //
18804  // TextareaCellEditor public properties
18805  //
18806  /////////////////////////////////////////////////////////////////////////////
18807  /**
18808   * Reference to textarea element.
18809   *
18810   * @property textarea
18811   * @type HTMLElement
18812   */
18813  textarea : null,
18814  
18815  
18816  /////////////////////////////////////////////////////////////////////////////
18817  //
18818  // TextareaCellEditor public methods
18819  //
18820  /////////////////////////////////////////////////////////////////////////////
18821  
18822  /**
18823   * Render a form with textarea.
18824   *
18825   * @method renderForm
18826   */
18827  renderForm : function() {
18828      var elTextarea = this.getContainerEl().appendChild(document.createElement("textarea"));
18829      this.textarea = elTextarea;
18830  
18831      if(this.disableBtns) {
18832          this.handleDisabledBtns();
18833      }
18834  },
18835  
18836  /**
18837   * After rendering form, if disabledBtns is set to true, then sets up a mechanism
18838   * to save input without them. 
18839   *
18840   * @method handleDisabledBtns
18841   */
18842  handleDisabledBtns : function() {
18843      Ev.addListener(this.textarea, "blur", function(v){
18844          // Save on blur
18845          this.save();
18846      }, this, true);        
18847  },
18848  
18849  /**
18850   * Moves TextareaCellEditor UI to a cell.
18851   *
18852   * @method move
18853   */
18854  move : function() {
18855      this.textarea.style.width = this.getTdEl().offsetWidth + "px";
18856      this.textarea.style.height = "3em";
18857      YAHOO.widget.TextareaCellEditor.superclass.move.call(this);
18858  },
18859  
18860  /**
18861   * Resets TextareaCellEditor UI to initial state.
18862   *
18863   * @method resetForm
18864   */
18865  resetForm : function() {
18866      this.textarea.value = this.value;
18867  },
18868  
18869  /**
18870   * Sets focus in TextareaCellEditor.
18871   *
18872   * @method focus
18873   */
18874  focus : function() {
18875      // Bug 2303181, Bug 2263600
18876      this.getDataTable()._focusEl(this.textarea);
18877      this.textarea.select();
18878  },
18879  
18880  /**
18881   * Retrieves input value from TextareaCellEditor.
18882   *
18883   * @method getInputValue
18884   */
18885  getInputValue : function() {
18886      return this.textarea.value;
18887  }
18888  
18889  });
18890  
18891  // Copy static members to TextareaCellEditor class
18892  lang.augmentObject(widget.TextareaCellEditor, BCE);
18893  
18894  
18895  
18896  
18897  
18898  
18899  
18900  
18901  
18902  /****************************************************************************/
18903  /****************************************************************************/
18904  /****************************************************************************/
18905      
18906  /**
18907   * The TextboxCellEditor class provides functionality for inline editing
18908   * DataTable cell data with an INPUT TYPE=TEXT element.
18909   *
18910   * @namespace YAHOO.widget
18911   * @class TextboxCellEditor
18912   * @extends YAHOO.widget.BaseCellEditor 
18913   * @constructor
18914   * @param oConfigs {Object} (Optional) Object literal of configs.
18915   */
18916  widget.TextboxCellEditor = function(oConfigs) {
18917      oConfigs = oConfigs || {};
18918      this._sId = this._sId || Dom.generateId(null, "yui-textboxceditor");// "yui-textboxceditor" + YAHOO.widget.BaseCellEditor._nCount++;
18919      YAHOO.widget.BaseCellEditor._nCount++;
18920      widget.TextboxCellEditor.superclass.constructor.call(this, oConfigs.type || "textbox", oConfigs);
18921  };
18922  
18923  // TextboxCellEditor extends BaseCellEditor
18924  lang.extend(widget.TextboxCellEditor, BCE, {
18925  
18926  /////////////////////////////////////////////////////////////////////////////
18927  //
18928  // TextboxCellEditor public properties
18929  //
18930  /////////////////////////////////////////////////////////////////////////////
18931  /**
18932   * Reference to the textbox element.
18933   *
18934   * @property textbox
18935   */
18936  textbox : null,
18937  
18938  /////////////////////////////////////////////////////////////////////////////
18939  //
18940  // TextboxCellEditor public methods
18941  //
18942  /////////////////////////////////////////////////////////////////////////////
18943  
18944  /**
18945   * Render a form with input type=text.
18946   *
18947   * @method renderForm
18948   */
18949  renderForm : function() {
18950      var elTextbox;
18951      // Bug 1802582: SF3/Mac needs a form element wrapping the input
18952      if(ua.webkit>420) {
18953          elTextbox = this.getContainerEl().appendChild(document.createElement("form")).appendChild(document.createElement("input"));
18954      }
18955      else {
18956          elTextbox = this.getContainerEl().appendChild(document.createElement("input"));
18957      }
18958      elTextbox.type = "text";
18959      this.textbox = elTextbox;
18960  
18961      // Save on enter by default
18962      // Bug: 1802582 Set up a listener on each textbox to track on keypress
18963      // since SF/OP can't preventDefault on keydown
18964      Ev.addListener(elTextbox, "keypress", function(v){
18965          if((v.keyCode === 13)) {
18966              // Prevent form submit
18967              YAHOO.util.Event.preventDefault(v);
18968              this.save();
18969          }
18970      }, this, true);
18971  
18972      if(this.disableBtns) {
18973          // By default this is no-op since enter saves by default
18974          this.handleDisabledBtns();
18975      }
18976  },
18977  
18978  /**
18979   * Moves TextboxCellEditor UI to a cell.
18980   *
18981   * @method move
18982   */
18983  move : function() {
18984      this.textbox.style.width = this.getTdEl().offsetWidth + "px";
18985      widget.TextboxCellEditor.superclass.move.call(this);
18986  },
18987  
18988  /**
18989   * Resets TextboxCellEditor UI to initial state.
18990   *
18991   * @method resetForm
18992   */
18993  resetForm : function() {
18994      this.textbox.value = lang.isValue(this.value) ? this.value.toString() : "";
18995  },
18996  
18997  /**
18998   * Sets focus in TextboxCellEditor.
18999   *
19000   * @method focus
19001   */
19002  focus : function() {
19003      // Bug 2303181, Bug 2263600
19004      this.getDataTable()._focusEl(this.textbox);
19005      this.textbox.select();
19006  },
19007  
19008  /**
19009   * Returns new value for TextboxCellEditor.
19010   *
19011   * @method getInputValue
19012   */
19013  getInputValue : function() {
19014      return this.textbox.value;
19015  }
19016  
19017  });
19018  
19019  // Copy static members to TextboxCellEditor class
19020  lang.augmentObject(widget.TextboxCellEditor, BCE);
19021  
19022  
19023  
19024  
19025  
19026  
19027  
19028  /////////////////////////////////////////////////////////////////////////////
19029  //
19030  // DataTable extension
19031  //
19032  /////////////////////////////////////////////////////////////////////////////
19033  
19034  /**
19035   * CellEditor subclasses.
19036   * @property DataTable.Editors
19037   * @type Object
19038   * @static
19039   */
19040  DT.Editors = {
19041      checkbox : widget.CheckboxCellEditor,
19042      "date"   : widget.DateCellEditor,
19043      dropdown : widget.DropdownCellEditor,
19044      radio    : widget.RadioCellEditor,
19045      textarea : widget.TextareaCellEditor,
19046      textbox  : widget.TextboxCellEditor
19047  };
19048  
19049  /****************************************************************************/
19050  /****************************************************************************/
19051  /****************************************************************************/
19052      
19053  /**
19054   * Factory class for instantiating a BaseCellEditor subclass.
19055   *
19056   * @namespace YAHOO.widget
19057   * @class CellEditor
19058   * @extends YAHOO.widget.BaseCellEditor 
19059   * @constructor
19060   * @param sType {String} Type indicator, to map to YAHOO.widget.DataTable.Editors.
19061   * @param oConfigs {Object} (Optional) Object literal of configs.
19062   */
19063  widget.CellEditor = function(sType, oConfigs) {
19064      // Point to one of the subclasses
19065      if(sType && DT.Editors[sType]) {
19066          lang.augmentObject(BCE, DT.Editors[sType]);
19067          return new DT.Editors[sType](oConfigs);
19068      }
19069      else {
19070          return new BCE(null, oConfigs);
19071      }
19072  };
19073  
19074  var CE = widget.CellEditor;
19075  
19076  // Copy static members to CellEditor class
19077  lang.augmentObject(CE, BCE);
19078  
19079  
19080  })();
19081  
19082  YAHOO.register("datatable", YAHOO.widget.DataTable, {version: "2.9.0", build: "2800"});
19083  
19084  }, '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