<!--

/**
 * This method is the DynaList constructor.
 */
function DynaList(pId, pSizeToFit, pSizeToRows, pSizeToRowsFixed, pShowStripes, pShowBorders, pRowsFoundText, pRowsFilteredText) {
  
  // Unique identifier corresponds to id attribute on DynaList.
  this.mId = pId;
  
  // Elements and collections of elements generated by the DynaList.
  this.mHeaderDiv = document.all[this.mId + "Header"];
  this.mHeaderTable = this.mHeaderDiv.firstChild;
  this.mBodyDiv = document.all[this.mId + "Body"];
  this.mBodyTable = this.mBodyDiv.firstChild;
  this.mBodyTableBody = this.mBodyTable.all.tags("TBODY")[0];
  this.mRowCountLabel = document.all[this.mId + "RowCount"];
  this.mRows = this.mBodyTableBody.rows;
  this.mCells = this.mBodyTable.cells;
  this.mColumnCount = this.mBodyTable.cells.length / this.mBodyTable.rows.length;
  
  // Row counts.
  this.mRowCount = this.mRows.length;
  this.mRowCountFiltered = 0;
  
  // Width and height required to display the entire list.
  this.mRequiredWidth = 0;
  this.mRequiredHeight = 0;
  
  // Additional space above, to the left and to the right of the list.
  this.mOffsetTop = 0;
  this.mOffsetLeft = 0;
  this.mOffsetRight = 0;
  
  // Do we need room for a horizontal scrollbar.
  this.mNeedHorizontalScrollbar = false;
  
  // Split scrolling elements.
  this.mUsingSplitScrolling = false;
  if (document.all[this.mId + "SplitHeader"]) {
    this.mUsingSplitScrolling = true;
    this.mSplitHeaderDiv = document.all[this.mId + "SplitHeader"];
    this.mSplitHeaderTable = this.mSplitHeaderDiv.firstChild;
    this.mSplitBodyDiv = document.all[this.mId + "SplitBody"];
    this.mSplitBodyTable = this.mSplitBodyDiv.firstChild;
    this.mSplitBodyTableBody = this.mSplitBodyTable.all.tags("TBODY")[0];
    this.mSplitRows = this.mSplitBodyTableBody.rows;
    this.mSplitColumnCount = this.mSplitBodyTable.cells.length / this.mSplitBodyTable.rows.length;
    this.mSplitRequiredWidth = 0;
    this.mSplitNeedHorizontalScrollbar = false;
  }
  
  // Width and height of scrollbars. Really shouldn't be hard-coded.
  this.mScrollbarWidth = 17;
  this.mScrollbarHeight = 17;
  
  // Attributes used to change the display of the list.
  this.mSizeToFit = true;
  this.mColumnNoWraps = new Array();
  this.mFoundWrappedColumn = false;
  this.mSizeToRows = pSizeToRows;
  this.mShowStripes = true;
  this.mShowBorders = true;
  this.mSortHeaderTable;
  this.mSortColumn = -1;
  this.mSortDirection = "A";
  this.mFilterColumn = -1;
  this.mFilterType = "";
  this.mFilterCriteria = "";
  this.mFilterOn = false;
  
  // Source element when the sort and filter functions are fired.
  this.mSourceElement;
  
  // Boolean indicating whether or not we've already called the load function for the list.
  this.mLoaded = false;
  
  // Boolean indicating whether or not a second adjustment was needed for the width of the list.
  this.mNeedSecondAdjustment = false;
  
  // Internationalized text for display.
  this.mRowsFoundText = "Found";
  this.mRowsLimitReachedText = "";
  this.mRowsFilteredText = "{0} displayed and {1} filtered";
  
  // Used in resizing method.
  this.mDivWorkspaceOffsetWidth = 0;
  this.mDivWorkspaceOffsetHeight = 0;
  
  // Process the optional arguments.
  switch (arguments.length) {
    case 8:
      this.mRowsFilteredText = pRowsFilteredText;
    case 7:
      this.mRowsFoundText = pRowsFoundText;
    case 6:
      this.mShowBorders = pShowBorders;
    case 5:
      this.mShowStripes = pShowStripes;
    case 4:
      this.mSizeToRowsFixed = pSizeToRowsFixed;
    case 3:
      this.mSizeToRows = pSizeToRows;
      if (this.mSizeToRows > 0) {
        this.mSizeToFit = true;
      }
    case 2:
      this.mSizeToFit = pSizeToFit;
  }
  
  // Display the row counts.
  this._drawRowCounts();
}

/**
 * This method is typically called after the page is done loading.
 * If the list is not visible when the page is done loading, it will
 * need to be called again once it is visible. For example, if the
 * list appears on a client-side tab and that tab isn't displayed
 * when the page is loaded, then when we change tabs, we'll want to
 * call this method. It can be called more than once, although it
 * won't do anything after it's been loaded successfully.
 */
DynaList.prototype.load = function() {
  var lElement;
  var lNewWidth;
  
  // Don't continue if we've already loaded the list.
  if (this.mLoaded) {
    return;
  }
  
  // If the list isn't visible at the moment, then don't do anything.
  if (!this._isVisible()) {
    return;
  }
  
  // Check to see if we've requested a minimum width on any of the columns.
  // We need to check the header row since that's where we're setting it in
  // the tag.
  for (var i = 0; i < this.mBodyTable.rows[0].cells.length; i++) {
    var lColumnWidth = this.mBodyTable.rows[0].cells[i].offsetWidth;
    var lMinimumWidth = this.mBodyTable.rows[0].cells[i].minwidth;
    if (lMinimumWidth != null) {
      if (lColumnWidth < lMinimumWidth) {
        this.mBodyTable.rows[0].cells[i].style.width = lMinimumWidth;
      }
    }
  }
  
  // Save the width required to display the full list.
  this.mRequiredWidth = this.mBodyTable.offsetWidth + this.mScrollbarWidth;
  
  // Now do the same for the split scrolling stuff.
  if (this.mUsingSplitScrolling) {
    this.mSplitRequiredWidth = this.mSplitBodyTable.offsetWidth;
  }
  
  // Resize the width of the list to its maximum width.
  this.mHeaderDiv.style.width = this.mRequiredWidth;
  this.mBodyDiv.style.width = this.mRequiredWidth;
  this.mNeedHorizontalScrollbar = false;
  
  // If we're using the split scrolling stuff, see if we need to make
  // the split side (left side) scroll. This will happen when we make
  // the split scroll column way to the right.
  if (this.mUsingSplitScrolling) {
    if (this.mSplitBodyDiv.offsetWidth > (divWorkspace.clientWidth * .7)) {
      var lSplitBodyPercentage = .6;
      var lBodyPercentage = (this.mBodyDiv.offsetWidth + this.mScrollbarWidth) / divWorkspace.clientWidth;
      if (lBodyPercentage < .39) {
        lSplitBodyPercentage = .99 - lBodyPercentage;
      }
      this.mSplitHeaderDiv.style.width = (divWorkspace.clientWidth * lSplitBodyPercentage);
      this.mSplitBodyDiv.style.width = (divWorkspace.clientWidth * lSplitBodyPercentage);
      this.mSplitNeedHorizontalScrollbar = true;
    }
  }
  
  // Save the left and top positions of the list.
  lElement = this.mBodyTable;
  this.mOffsetLeft = lElement.offsetLeft + lElement.clientLeft;
  this.mOffsetTop = lElement.offsetTop + lElement.clientTop;
  while (lElement.offsetParent != null) {
    lElement = lElement.offsetParent;
    this.mOffsetLeft += lElement.offsetLeft + lElement.clientLeft;
    this.mOffsetTop = lElement.offsetTop + lElement.clientTop;
  }
  
  // Calculate how much space to the right we need to account for.
  this.mOffsetRight = divWorkspace.scrollWidth - this.mBodyDiv.offsetWidth - this.mOffsetLeft;
  
  // If we're using the size-to-fit logic.
  if (this.mSizeToFit) {
    
    // Make it fit in the workspace if it's too wide.
    if (divWorkspace.scrollWidth > divWorkspace.clientWidth) {
      lNewWidth = divWorkspace.offsetWidth - this.mOffsetLeft - this.mOffsetRight;
      if (lNewWidth > 0) {
        if (lNewWidth > this.mRequiredWidth) {
          lNewWidth = this.mRequiredWidth;
        }
        this.mHeaderDiv.style.width = lNewWidth;
        this.mBodyDiv.style.width = lNewWidth;
      }
      this.mNeedHorizontalScrollbar = true;
    }
    
    // This might require two adjustments if other elements on the page
    // are affecting the width of the workspace.
    if (divWorkspace.scrollWidth > divWorkspace.offsetWidth) {
      this.mNeedSecondAdjustment = true;
      lNewWidth = this.mBodyDiv.offsetWidth + (divWorkspace.scrollWidth - divWorkspace.clientWidth);
      if (lNewWidth > this.mRequiredWidth) {
        lNewWidth = this.mRequiredWidth;
      }
      this.mHeaderDiv.style.width = lNewWidth;
      this.mBodyDiv.style.width = lNewWidth;
    }
  }
  
  // Save the column widths for the list body. Use the data row,
  // if any, for the list body for determining the column widths.
  // Otherwise, use the width of the header.
  var lColumnWidths = new Array();
  for (var i = 0; i < this.mBodyTable.rows[0].cells.length; i++) {
    lColumnWidths[i] = this.mBodyTable.rows[0].cells[i].offsetWidth;
    if (this.mBodyTable.rows.length > 1) {
      var lTempColumnWidth = this.mBodyTable.rows[1].cells[i].offsetWidth;
      if (lTempColumnWidth > lColumnWidths[i]) {
        lColumnWidths[i] = lTempColumnWidth;
      }
    }
  }
  
  // Now do the same for the split scrolling stuff.
  var lSplitColumnWidths = new Array();
  if (this.mUsingSplitScrolling) {
    for (var i = 0; i < this.mSplitBodyTable.rows[0].cells.length; i++) {
      lSplitColumnWidths[i] = this.mSplitBodyTable.rows[0].cells[i].offsetWidth;
      if (this.mSplitBodyTable.rows.length > 1) {
        var lTempColumnWidth = this.mSplitBodyTable.rows[1].cells[i].offsetWidth;
        if (lTempColumnWidth > lSplitColumnWidths[i]) {
          lSplitColumnWidths[i] = lTempColumnWidth;
        }
      }
    }
  }
  
  // Save the current nowrap property of each column before setting
  // the nowrap to true. Need to set the nowrap to true before
  // we set the actual width for the divs and tables.
  if (this.mBodyTable.rows.length > 1) {
    for (var i = 0; i < this.mBodyTable.rows[1].cells.length; i++) {
      this.mColumnNoWraps[i] = this.mBodyTable.rows[1].cells[i].noWrap;
      if (!this.mColumnNoWraps[i]) {
        this.mFoundWrappedColumn = true;
        this.mBodyTable.rows[1].cells[i].noWrap = true;
      }
    }
  }
  
  // Hide the first row in the list body and show the header div instead.
  this.mBodyTable.rows[0].style.display = "none";
  this.mHeaderDiv.style.display = "block";
  
  // Now do the same for the split scrolling stuff.
  if (this.mUsingSplitScrolling) {
    this.mSplitBodyTable.rows[0].style.display = "none";
    this.mSplitHeaderDiv.style.display = "block";
  }
  
  // Set the column widths on list header and body.
  for (var i = 0; i < lColumnWidths.length; i++) {
    this.mHeaderTable.rows[0].cells[i].width = lColumnWidths[i];
    if (this.mBodyTable.rows.length > 1) {
      this.mBodyTable.rows[1].cells[i].width = lColumnWidths[i];
    }
  }
  
  // Now do the same for the split scrolling stuff.
  if (this.mUsingSplitScrolling) {
    for (var i = 0; i < lSplitColumnWidths.length; i++) {
      this.mSplitHeaderTable.rows[0].cells[i].width = lSplitColumnWidths[i];
      if (this.mSplitBodyTable.rows.length > 1) {
        this.mSplitBodyTable.rows[1].cells[i].width = lSplitColumnWidths[i];
      }
    }
  }
  
  // Set the table width on the list header and body.
  if(this.mBodyTable.offsetWidth > 0) {
    this.mHeaderTable.width = this.mBodyTable.offsetWidth + this.mScrollbarWidth;
    if (this.mHeaderTable.offsetWidth > this.mRequiredWidth) {
      this.mHeaderTable.width = this.mRequiredWidth;
    }
    if (this.mBodyTable.rows.length > 1) {
      this.mBodyTable.width = this.mBodyTable.offsetWidth;
      if (this.mBodyTable.offsetWidth > this.mRequiredWidth) {
        this.mBodyTable.width = this.mRequiredWidth;
      }
    }
  }
  
  // Now do the same for the split scrolling stuff.
  if (this.mUsingSplitScrolling) {
    if(this.mSplitBodyTable.offsetWidth > 0) {
      this.mSplitHeaderTable.width = this.mSplitBodyTable.offsetWidth;
      if (this.mSplitHeaderTable.offsetWidth > this.mSplitRequiredWidth) {
        this.mSplitHeaderTable.width = this.mSplitRequiredWidth;
      }
      if (this.mSplitBodyTable.rows.length > 1) {
        this.mSplitBodyTable.width = this.mSplitBodyTable.offsetWidth;
        if (this.mSplitBodyTable.offsetWidth > this.mSplitRequiredWidth) {
          this.mSplitBodyTable.width = this.mSplitRequiredWidth;
        }
      }
    }
  }
  
  // Reset the width required to display the full list. This really
  // only changes when we have a wrapped column, but it doesn't hurt
  // to set it again.
  if (this.mBodyTable.offsetWidth > 0) {
    this.mRequiredWidth = this.mBodyTable.offsetWidth + this.mScrollbarWidth;
  }
  
  // Check if we truly need the horizontal scrollbar after all width resizing.
  this.mNeedHorizontalScrollbar = false;
  if (this.mBodyTable.rows.length > 1) {
    if (this.mBodyDiv.scrollWidth > this.mBodyDiv.clientWidth) {
      this.mNeedHorizontalScrollbar = true;
    }
  }
  
  // Save the height required to display the full list. If we've specified
  // a maximum number of rows to display, take that into consideration.
  var lRowClientHeight = 25;  // default height in case there are no rows
  this.mRequiredHeight = this.mBodyDiv.offsetHeight;
  if ((this.mSizeToRows > 0 && this.mRowCount > this.mSizeToRows) || (this.mSizeToRowsFixed)) {
    if (this.mRows.length) {
      lRowClientHeight = this.mRows[0].clientHeight;
    }
    this.mRequiredHeight = lRowClientHeight * this.mSizeToRows;
  }
  
  // If we're using the size-to-fit logic.
  if (this.mSizeToFit) {
    
    // Resize the height of the list to its maximum height.
    if (this.mNeedHorizontalScrollbar || this.mSplitNeedHorizontalScrollbar) {
      this.mBodyDiv.style.height = this.mRequiredHeight + this.mScrollbarHeight;
    }
    else {
      this.mBodyDiv.style.height = this.mRequiredHeight;
    }
    
    // Make it fit in the workspace if it's too long.
    if (this.mSizeToRows <= 0) {
      if (divWorkspace.scrollHeight > divWorkspace.offsetHeight && divWorkspace.offsetHeight > divWorkspace.scrollHeight - this.mBodyDiv.offsetHeight) {
        this.mBodyDiv.style.height = divWorkspace.offsetHeight - (divWorkspace.scrollHeight - this.mBodyDiv.offsetHeight);
      }
    }
  }
  
  // Now do the same for the split scrolling stuff.
  if (this.mUsingSplitScrolling) {
    this.mSplitBodyDiv.style.height = this.mBodyDiv.offsetHeight;
    this.mSplitBodyDiv.style.overflowX = this.mNeedHorizontalScrollbar ? "scroll" : "auto";
    this.mBodyDiv.style.overflowX = this.mSplitNeedHorizontalScrollbar ? "scroll" : "auto";
  }
  
  // Save the values of the divWorkspace to avoid resizing logic unnecessarily.
  this.mDivWorkspaceOffsetWidth = divWorkspace.offsetWidth;
  this.mDivWorkspaceOffsetHeight = divWorkspace.offsetHeight;
  
  // At this point, the list has been loaded and we want to make sure
  // we don't load it again.
  this.mLoaded = true;
}

/**
 * This method resizes the width and height of the list. It was written to
 * be called when the window resizes.
 */
DynaList.prototype.resize = function() {
  var lNewWidth;
  
  // If we're using the size-to-fit logic and we're not in the middle of loading.
  if (this.mSizeToFit && this.mLoaded) {
    
    // If the list isn't visible at the moment, then don't do anything.
    if (!this._isVisible()) {
      return;
    }
    
    // If the workspace hasn't changed, then don't do anything.
    var lOffsetWidth = divWorkspace.offsetWidth;
    var lOffsetHeight = divWorkspace.offsetHeight;
    if (this.mDivWorkspaceOffsetWidth == lOffsetWidth && this.mDivWorkspaceOffsetHeight == lOffsetHeight) {
      return;
    }
    
    // We have extra work to do if we found a wrapping column during
    // the load process.
    if (this.mFoundWrappedColumn) {
      // not doing anything for now - resizing won't take advantage
      // of extra space for wrapped columns
    }
    
    // If the list falls into the "normal" category and didn't need
    // a second adjustment when loaded.
    if (!this.mNeedSecondAdjustment) {
      
      // Resize the width of the list to its maximum width.
      this.mHeaderDiv.style.width = this.mRequiredWidth;
      this.mBodyDiv.style.width = this.mRequiredWidth;
      this.mNeedHorizontalScrollbar = false;
      
      // Calculate how much space to the right we need to account for.
      this.mOffsetRight = divWorkspace.scrollWidth - this.mBodyDiv.offsetWidth - this.mOffsetLeft;
      
      // Make it fit in the workspace if it's too wide.
      if (this.mOffsetLeft + this.mBodyDiv.offsetWidth + this.mOffsetRight > lOffsetWidth) {
        lNewWidth = lOffsetWidth - this.mOffsetLeft - this.mOffsetRight;
        if (lNewWidth > 0) {
          this.mHeaderDiv.style.width = lNewWidth;
          this.mBodyDiv.style.width = lNewWidth;
        }
        this.mNeedHorizontalScrollbar = true;
      }
    }
    
    // Resize the height of the list to its maximum height.
    if (this.mNeedHorizontalScrollbar || this.mSplitNeedHorizontalScrollbar) {
      this.mBodyDiv.style.height = this.mRequiredHeight + this.mScrollbarHeight;
    }
    else {
      this.mBodyDiv.style.height = this.mRequiredHeight;
    }
    
    // Make it fit in the workspace if it's too long.
    if (divWorkspace.scrollHeight > divWorkspace.offsetHeight && divWorkspace.offsetHeight > divWorkspace.scrollHeight - this.mBodyDiv.offsetHeight) {
      this.mBodyDiv.style.height = divWorkspace.offsetHeight - (divWorkspace.scrollHeight - this.mBodyDiv.offsetHeight);
    }
    
    // Now do the same for the split scrolling stuff.
    if (this.mUsingSplitScrolling) {
      this.mSplitBodyDiv.style.height = this.mBodyDiv.offsetHeight;
      this.mSplitBodyDiv.style.overflowX = this.mNeedHorizontalScrollbar ? "scroll" : "auto";
      this.mBodyDiv.style.overflowX = this.mSplitNeedHorizontalScrollbar ? "scroll" : "auto";
    }
    
    // Save the values of the divWorkspace to avoid resizing logic unnecessarily.
    this.mDivWorkspaceOffsetWidth = lOffsetWidth;
    this.mDivWorkspaceOffsetHeight = lOffsetHeight;
  }
}

/**
 * This method recalculates the height of the list. It's called when we toggle the
 * fitler panel on and off, for example.
 */
DynaList.prototype.recalculateHeight = function() {
  
  // If we're using the size-to-fit logic and we're not in the middle of loading.
  if (this.mSizeToFit && this.mLoaded) {
    
    // Resize the height of the list to its maximum height.
    if (this.mNeedHorizontalScrollbar || this.mSplitNeedHorizontalScrollbar) {
      this.mBodyDiv.style.height = this.mRequiredHeight + this.mScrollbarHeight;
    }
    else {
      this.mBodyDiv.style.height = this.mRequiredHeight;
    }
    
    // Now make it fit in the workspace if it's too long.
    if (divWorkspace.scrollHeight > divWorkspace.offsetHeight && divWorkspace.offsetHeight > divWorkspace.scrollHeight - this.mBodyDiv.offsetHeight) {
      this.mBodyDiv.style.height = divWorkspace.offsetHeight - (divWorkspace.scrollHeight - this.mBodyDiv.offsetHeight);
    }
    
    // Now do the same for the split scrolling stuff.
    if (this.mUsingSplitScrolling) {
      this.mSplitBodyDiv.style.height = this.mBodyDiv.offsetHeight;
      this.mSplitBodyDiv.style.overflowX = this.mNeedHorizontalScrollbar ? "scroll" : "auto";
      this.mBodyDiv.style.overflowX = this.mSplitNeedHorizontalScrollbar ? "scroll" : "auto";
      
      // Weird redraw problem requires hiding and showing header to redraw properly.
      this.mSplitHeaderDiv.style.display = "none";
      this.mSplitHeaderDiv.style.display = "block";
    }
  }
}

/**
 * This method handles the onscroll event of the body of the list. It
 * makes sure the header and body of the list remain in sync if we're
 * scrolling horizontally.
 */
DynaList.prototype.scroll = function() {
  if (this.mHeaderDiv.scrollLeft != this.mBodyDiv.scrollLeft) {
    this.mHeaderDiv.scrollLeft = this.mBodyDiv.scrollLeft;
  }
  if (this.mUsingSplitScrolling) {
    if (this.mSplitHeaderDiv.scrollLeft != this.mSplitBodyDiv.scrollLeft) {
      this.mSplitHeaderDiv.scrollLeft = this.mSplitBodyDiv.scrollLeft;
    }
    if (this.mSplitBodyDiv.scrollTop != this.mBodyDiv.scrollTop) {
      this.mSplitBodyDiv.scrollTop = this.mBodyDiv.scrollTop;
    }
  }
}

/**
 * This method is called by a function on the page which is generated by the
 * DynaList. The calling function sets the cursor to the "wait" cursor and then
 * uses a timeout to trigger this method so that the cursor change has time to
 * take effect. This method performs the actual row sorting and then resets the
 * cursor to the "default" cursor.
 */
DynaList.prototype.sort = function(pSortColumn, pSortType) {
  var lSortColumn = pSortColumn;
  var lMasterHeaderTable = this.mHeaderTable;
  var lMasterBodyTableBody = this.mBodyTableBody;
  var lMasterRows = this.mRows;
  var lSlaveHeaderTable;
  var lSlaveBodyTableBody;
  var lSlaveRows;
  
  // If we're using the split scrolling stuff, first determine which table we're getting the
  // sort data from. That table will be the master and the other will be the slave.
  if (this.mUsingSplitScrolling) {
    if (pSortColumn < this.mSplitColumnCount) {
      lMasterHeaderTable = this.mSplitHeaderTable;
      lMasterBodyTableBody = this.mSplitBodyTableBody;
      lMasterRows = this.mSplitRows;
      lSlaveHeaderTable = this.mHeaderTable;
      lSlaveBodyTableBody = this.mBodyTableBody;
      lSlaveRows = this.mRows;
    }
    else {
      lSortColumn = pSortColumn - this.mSplitColumnCount;
      lSlaveHeaderTable = this.mSplitHeaderTable;
      lSlaveBodyTableBody = this.mSplitBodyTableBody;
      lSlaveRows = this.mSplitRows;
    }
  }
  
  // Get pointers to any checkbox arrays within the list.  This is to work around a problem where
  // checkboxes the user had checked since the page loading getting blanked out during the sort.
  // The rows are shuffled after the sort using the appendChild function which for some reason loses 
  // the changes to checkboxes that have occurred since the page loaded.
  var lChild;
  var lCheckboxPtrs = new Array(); // array of pointers in case multipe checkboxes per row
  if (this.mRows.length > 0) {  
    for (var i = 0; i < this.mColumnCount; i++) {
      for (var j = 0; j < this.mRows[0].cells[i].children.length; j++) {
        lChild = this.mRows[0].cells[i].children[j];
        if (lChild.type == "checkbox") {
          lCheckboxPtrs[lCheckboxPtrs.length] = document.all[lChild.name];
        }
      }
    }
    
    // Now do the same for the split scrolling stuff.
    if (this.mUsingSplitScrolling) {
      for (var i = 0; i < this.mSplitColumnCount; i++) {
        for (var j = 0; j < this.mSplitRows[0].cells[i].children.length; j++) {
          lChild = this.mSplitRows[0].cells[i].children[j];
          if (lChild.type == "checkbox") {
            lCheckboxPtrs[lCheckboxPtrs.length] = document.all[lChild.name];
          }
        }
      }
    }
  }
  
  // Build an array representing the table to be sorted.
  var lSortRows = new Array(this.mRowCount);
  var lRowCheckArray;
  for (var i = 0; i < this.mRowCount; i++) {
    
    // Capture checkbox values for the row to an array.
    lRowCheckArray = new Array();
    for (var j = 0; j < lCheckboxPtrs.length; j++) {
      if (this.mRowCount == 1) {
        lRowCheckArray[j] = lCheckboxPtrs[j].checked;
      }
      else {
        lRowCheckArray[j] = lCheckboxPtrs[j][i].checked;
      }
    }
    
    // If we're using split scrolling, then we have a slave row.
    var lSlaveRow;
    if (this.mUsingSplitScrolling) {
      lSlaveRow = lSlaveRows[i];
    }
    
    // Add the master row to the array for sorting.
    lSortRows[i] = new _sortRow(lMasterRows[i], lSlaveRow, new _sortRowColumn(lMasterRows[i].cells[lSortColumn], pSortType), lRowCheckArray);
  }
  
  // Determine the sort direction.
  if (pSortColumn != this.mSortColumn) {
    if (this.mSortColumn != -1) {
      var lLastSortColumn = this.mSortColumn;
      if (this.mSortColumn >= this.mSplitColumnCount) {
        lLastSortColumn = this.mSortColumn - this.mSplitColumnCount;
      }
      var lImages = this.mSortHeaderTable.cells[lLastSortColumn].all.tags("IMG");
      if (lImages.length > 0) {
        lImages[0].src = "image/framework/imgSortOff.gif";
      }
    }
    this.mSortHeaderTable = lMasterHeaderTable;
    this.mSortColumn = pSortColumn;
    this.mSortDirection = "A";
  }
  else {
    this.mSortDirection = (this.mSortDirection == "A" ? "D" : "A");
  }
  
  // Sort the array.
  if (this.mSortDirection == "A") {
    lSortRows.sort(function(a,b) { if (a.sortRowColumn.sortValue < b.sortRowColumn.sortValue) { return -1 } else { return 1 }; });
    var lImages = lMasterHeaderTable.cells[lSortColumn].all.tags("IMG");
    if (lImages.length > 0) {
      lImages[0].src = "image/framework/imgSortAscending.gif";
    }
  }
  else {
    lSortRows.sort(function(a,b) { if (a.sortRowColumn.sortValue > b.sortRowColumn.sortValue) { return -1 } else { return 1 }; });
    var lImages = lMasterHeaderTable.cells[lSortColumn].all.tags("IMG");
    if (lImages.length > 0) {
      lImages[0].src = "image/framework/imgSortDescending.gif";
    }
  }
  
  // Shuffle the cells.
  for (var i = 0; i < lSortRows.length; i++) {
    lMasterBodyTableBody.appendChild(lSortRows[i].masterRowPtr);
    
    // Now do the same for the split scrolling stuff.
    if (this.mUsingSplitScrolling) {
      lSlaveBodyTableBody.appendChild(lSortRows[i].slaveRowPtr);
    }
  }
  
  // Set the checks back like they were. The appendChild function sets them back to what they were when the
  // page loaded.
  for (var i = 0; i < lSortRows.length; i++) {
    for (var j = 0; j < lSortRows[i].checkboxValues.length; j++) {
      if (this.mRowCount == 1) {
        lCheckboxPtrs[j].checked = lSortRows[i].checkboxValues[j];
      }
      else {
        lCheckboxPtrs[j][i].checked = lSortRows[i].checkboxValues[j];
      }
    }
  }
  
  // Reapply any filters.
  if (this.mFilterOn) {
    this.filter(this.mFilterColumn, this.mFilterCriteria, this.mFilterType);
  }
  
  // Restripe the rows.
  if (this.mShowStripes) {
    this._drawStripes(this.mShowStripes);
  }
  
  // Reset the cursor. The cursor should have been changed in the calling page using
  // a setTimeout so that it has time to take affect.
  this.cursorDefault();
}

/**
 * This method is called by a function on the page which is generated by the
 * DynaList. The calling function sets the cursor to the "wait" cursor and then
 * uses a timeout to trigger this method so that the cursor change has time to
 * take effect. This method performs the actual row filtering and then resets the
 * cursor to the "default" cursor.
 */
DynaList.prototype.filter = function(pFilterColumn, pFilterCriteria, pFilterType) {
  
  // If there aren't any rows, then don't do anything.
  if (this.mRowCount > 0) {
    
    // Save the filter criteria.
    this.mFilterColumn = pFilterColumn;
    this.mFilterCriteria = pFilterCriteria;
    this.mFilterType = pFilterType;
    
    // Determine if we need to apply the filter.
    var lFilterOn = false;
    if (pFilterCriteria != "") {
      lFilterOn = true;
    }
    else if (pFilterType == "isempty") {
     lFilterOn = true;
    }
    else if (pFilterType == "isnotempty") {
      lFilterOn = true;
    }
    this.mFilterOn = lFilterOn;
    
    // Save the table width.
    var lWidth = this.mBodyTable.offsetWidth;
    
    // Now do the same for the split scrolling stuff.
    var lSplitWidth;
    if (this.mUsingSplitScrolling) {
      lSplitWidth = this.mSplitBodyTable.offsetWidth;
    }
    
    // Save the column widths.
    var lColumnWidths = new Array();
    for (var i = 0; i < this.mHeaderTable.rows[0].cells.length - 1; i++) {
      lColumnWidths[i] = this.mHeaderTable.rows[0].cells[i].offsetWidth;
    }
    
    // Now do the same for the split scrolling stuff.
    var lSplitColumnWidths = new Array();
    if (this.mUsingSplitScrolling) {
      for (var i = 0; i < this.mSplitHeaderTable.rows[0].cells.length - 1; i++) {
        lSplitColumnWidths[i] = this.mSplitHeaderTable.rows[0].cells[i].offsetWidth;
      }
    }
    
    // Filter the rows.
    var lFirstRow = -1;
    this.mRowCountFiltered = 0;
    if (lFilterOn) {
      pFilterCriteria = pFilterCriteria.toLowerCase();
      for (var i = 0; i < this.mRowCount; i ++) {
        
        // Are we filtering based on a specific column.
        var lFilterRow = true;
        var lAllSpacesRegExp = / /g;
        if (pFilterColumn >= 0) {
          var lFilterColumn = pFilterColumn;
          var lMasterRows = this.mRows;
          
          // If we're using the split scrolling stuff, first determine which table we're getting the
          // filter data from. That table will be the master and the other will be the slave.
          if (this.mUsingSplitScrolling) {
            if (pFilterColumn < this.mSplitColumnCount) {
              lMasterRows = this.mSplitRows;
            }
            else {
              lFilterColumn = pFilterColumn - this.mSplitColumnCount;
            }
          }
          
          var lText = _getCellValue(lMasterRows[i].cells[lFilterColumn]);
          if (pFilterType == "isempty") {
            if (lText == null || lText.replace(lAllSpacesRegExp,"") == "") {
              lFilterRow = false;
            }
          }
          else if (pFilterType == "isnotempty") {
            if (lText != null && lText.replace(lAllSpacesRegExp,"") != "") {
              lFilterRow = false;
            }
          }
          else if (lText.toLowerCase().indexOf(pFilterCriteria) > -1) {
            if (pFilterType == "contains") {
              lFilterRow = false;
            }
          }
          else {
            if (pFilterType == "doesnotcontain") {
              lFilterRow = false;
            }
          }
        }
        
        // Are we filtering based on all columns.
        else {
          for(var j = 0; j < this.mRows[i].cells.length; j ++) {
            var lText = _getCellValue(this.mRows[i].cells[j]);
            if (pFilterType == "isempty") {
              if (lText == null || lText.replace(lAllSpacesRegExp,"") == "") {
                lFilterRow = false;
              }
            }
            else if (pFilterType == "isnotempty") {
              if (lText != null && lText.replace(lAllSpacesRegExp,"") != "") {
                lFilterRow = false;
              }
            }
            else if (lText.toLowerCase().indexOf(pFilterCriteria) > -1) {
              if (pFilterType == "contains") {
                lFilterRow = false;
              }
            }
            else {
              if (pFilterType == "doesnotcontain") {
                lFilterRow = false;
              }
            }
            if (!lFilterRow) {
              break;
            }
          }
          
          // Now do the same for the split scrolling stuff.
          if (this.mUsingSplitScrolling) {
            if (!lFilterRow) {
              for(var j = 0; j < this.mSplitRows[i].cells.length; j ++) {
                var lText = _getCellValue(this.mSplitRows[i].cells[j]);
                if (pFilterType == "isempty") {
                  if (lText == null || lText.replace(lAllSpacesRegExp,"") == "") {
                    lFilterRow = false;
                  }
                }
                else if (pFilterType == "isnotempty") {
                  if (lText != null && lText.replace(lAllSpacesRegExp,"") != "") {
                    lFilterRow = false;
                  }
                }
                else if (lText.toLowerCase().indexOf(pFilterCriteria) > -1) {
                  if (pFilterType == "contains") {
                    lFilterRow = false;
                  }
                }
                else {
                  if (pFilterType == "doesnotcontain") {
                    lFilterRow = false;
                  }
                }
                if (!lFilterRow) {
                  break;
                }
              }
            }
          }
        }
        
        // Filter the row.
        if (lFilterRow) {
          this.mRowCountFiltered ++;
          this.mRows[i].style.display = "none";
          
          // Now do the same for the split scrolling stuff.
          if (this.mUsingSplitScrolling) {
            this.mSplitRows[i].style.display = "none";
          }
        }
        else {
          this.mRows[i].style.display = "block";
          
          // Now do the same for the split scrolling stuff.
          if (this.mUsingSplitScrolling) {
            this.mSplitRows[i].style.display = "block";
          }
          if (lFirstRow == -1) {
            lFirstRow = i;
          }
        }
      }
    }
    
    // Clear the filter.
    else {
      for (i = 0; i < this.mRowCount; i ++) {
        this.mRows[i].style.display = "block";
        if (this.mUsingSplitScrolling) {
          this.mSplitRows[i].style.display = "block";
        }
      }
    }
    
    // Reset the column widths.
    if (lFirstRow >= 0) {
      for (var i = 0; i < lColumnWidths.length; i++) {
        this.mRows[lFirstRow].cells[i].width = lColumnWidths[i];
      }
      
      // Now do the same for the split scrolling stuff.
      if (this.mUsingSplitScrolling) {
        for (var i = 0; i < lSplitColumnWidths.length; i++) {
          this.mSplitRows[lFirstRow].cells[i].width = lSplitColumnWidths[i];
        }
      }
    }
    
    // Reset the table width.
    this.mBodyTable.width = lWidth;
    
    // Now do the same for the split scrolling stuff.
    if (this.mUsingSplitScrolling) {
      this.mSplitBodyTable.width = lSplitWidth;
    }
    
    // Restripe the rows.
    if (this.mShowStripes) {
      this._drawStripes(this.mShowStripes);
    }
    
    // Display the row counts.
    this._drawRowCounts();
  }
  
  // Reset the cursor. The cursor should have been changed in the calling page using
  // a setTimeout so that it has time to take affect.
  this.cursorDefault();
}

/**
 * This method is called by a function on the page which is generated by the
 * DynaList. The calling function sets the cursor to the "wait" cursor and then
 * uses a timeout to trigger this method so that the cursor change has time to
 * take effect. This method performs the actual column fill and then resets the
 * cursor to the "default" cursor.
 */
DynaList.prototype.fill = function(pFillColumn, pFillColumnText, pFillValue, pFillType, pFillChangeValue) {
  var lValidInput = true;
  var lFillColumn = pFillColumn;
  var lFillValue = pFillValue;
  var lFillValuePrefix = pFillValue; // alpha
  var lFillValueSuffix = ""; // numeric
  var lFillValueSuffixLength = 0;
  var lFillChangeValue = 0;
  
  // Make sure we have enough arguments.
  if (arguments.length < 5) {
    lValidInput = false;
  }
  
  // Make sure the fill value ends in a number we can increment or decrement.
  if (lValidInput) {
    if (pFillType == "increment" || pFillType == "decrement") {
      var lIndex = lFillValue.length - 1;
      while (lIndex >= 0 && "0123456789".indexOf(lFillValue.charAt(lIndex)) >= 0) {
        lIndex -= 1;
      }
      lIndex += 1;
      lFillValuePrefix = lFillValue.substring(0, lIndex);
      lFillValueSuffix = lFillValue.substring(lIndex);
      lFillValueSuffixLength = lFillValueSuffix.length;
      if (lFillValueSuffixLength == 0) {
        alert("Cannot increment or decrement \"" + lFillValue + "\" because it does not end with a number.");
        lValidInput = false;
      }
    }
  }
  
  // Make sure the increment or decrement value is a number.
  if (lValidInput) {
    if (pFillType == "increment" || pFillType == "decrement") {
      lFillChangeValue = parseInt(pFillChangeValue, 10);
      if (isNaN(lFillChangeValue)) {
        alert("Cannot increment or decrement by \"" + pFillChangeValue + "\" because it is not a number.");
        lValidInput = false;
      }
    }
  }
  
  // Get pointers to any checkbox arrays within the list. We'll have to assume
  // the first column with checkboxes is the row selector.
  var lMasterHeaderTable = this.mHeaderTable;
  var lMasterBodyTableBody = this.mBodyTableBody;
  var lMasterRows = this.mRows;
  if (lValidInput) {
    if (this.mUsingSplitScrolling) {
      if (pFillColumn < this.mSplitColumnCount) {
        lMasterHeaderTable = this.mSplitHeaderTable;
        lMasterBodyTableBody = this.mSplitBodyTableBody;
        lMasterRows = this.mSplitRows;
      }
      else {
        lFillColumn = pFillColumn - this.mSplitColumnCount;
      }
    }
    var lCheckboxPtrs = new Array();
    if (this.mRows.length > 0) {  
      if (this.mUsingSplitScrolling) {
        for (var i = 0; i < this.mSplitColumnCount; i++) {
          for (var j = 0; j < this.mSplitRows[0].cells[i].children.length; j++) {
            var lChild = this.mSplitRows[0].cells[i].children[j];
            if (lChild.type == "checkbox") {
              lCheckboxPtrs[lCheckboxPtrs.length] = document.all[lChild.name];
            }
          }
        }
      }
      for (var i = 0; i < this.mColumnCount; i++) {
        for (var j = 0; j < this.mRows[0].cells[i].children.length; j++) {
          var lChild = this.mRows[0].cells[i].children[j];
          if (lChild.type == "checkbox") {
            lCheckboxPtrs[lCheckboxPtrs.length] = document.all[lChild.name];
          }
        }
      }
    }
  }
  
  // Make sure we have at least one row that qualifies for filling.
  var lRowsToFill = 0;
  if (lValidInput) {
    if (this.mRows.length == 0) {
      lValidInput = false;
    }
  }
  if (lValidInput) {
    var lCheckboxPtrsLength = (!lCheckboxPtrs[0].length ? 1 : lCheckboxPtrs[0].length);
    for (var i = 0; i < lCheckboxPtrsLength; i++) {
      if ((lCheckboxPtrsLength == 1 && lCheckboxPtrs[0].checked) || (lCheckboxPtrsLength > 1 && lCheckboxPtrs[0][i].checked)) {
        var lElements = lMasterRows[i].cells[lFillColumn].childNodes;
        for (var j = 0; j < lElements.length; j++) {
          if (lElements[j].tagName == "INPUT") {
            lRowsToFill ++;
          }
          else if (lElements[j].tagName == "SELECT") {
            lRowsToFill ++;
          }
        }
      }
    }
    if (lRowsToFill == 0) {
      alert("There are no rows checked where \"" + pFillColumnText + "\" can be filled.");
      lValidInput = false;
    }
  }
  
  // Make sure we have enough room to increment or decrement the fill value for each row.
  if (lValidInput) {
    var lTotalChange = (lRowsToFill - 1) * lFillChangeValue;
    var lTotalResults = 0;
    if (pFillType == "increment") {
      lTotalResults = parseInt(lFillValueSuffix, 10) + lTotalChange;
      if (new Number(lTotalResults).toString().length > lFillValueSuffix.length) {
        alert("There are not enough digits available to increment every row by " + lFillChangeValue + ".");
        lValidInput = false;
      }
    }
    else if (pFillType == "decrement") {
      lTotalResults = parseInt(lFillValueSuffix, 10) - lTotalChange;
      if (lTotalResults < 0) {
        alert("There are not enough digits available to decrement every row by " + lFillChangeValue + ".");
        lValidInput = false;
      }
    }
  }
  
  // If we passed all validation, do the fill.
  if (lValidInput) {
    var lFillValueSuffixAsInt = parseInt(lFillValueSuffix, 10);
    var lFillChangeValueAsInt = parseInt(lFillChangeValue, 10);
    
    // For each row that is checked.
    var lCheckboxPtrsLength = (!lCheckboxPtrs[0].length ? 1 : lCheckboxPtrs[0].length);
    for (var i = 0; i < lCheckboxPtrsLength; i++) {
      if ((lCheckboxPtrsLength == 1 && lCheckboxPtrs[0].checked) || (lCheckboxPtrsLength > 1 && lCheckboxPtrs[0][i].checked)) {
        var lElements = lMasterRows[i].cells[lFillColumn].childNodes;
        for (var j = 0; j < lElements.length; j++) {
          if (lElements[j].tagName == "INPUT") {
            if (pFillType == "nochange") {
              lElements[j].value = lFillValue;
            }
            else if (pFillType == "increment") {
              var lNewSuffix = "00000000000000000000";
              lNewSuffix += lFillValueSuffixAsInt;
              lNewSuffix = lNewSuffix.substring(lNewSuffix.length - lFillValueSuffix.length, lNewSuffix.length);
              lElements[j].value = lFillValuePrefix + lNewSuffix;
              lFillValueSuffixAsInt += lFillChangeValueAsInt;
            }
            else if (pFillType == "decrement") {
              var lNewSuffix = "00000000000000000000";
              lNewSuffix += lFillValueSuffixAsInt;
              lNewSuffix = lNewSuffix.substring(lNewSuffix.length - lFillValueSuffix.length, lNewSuffix.length);
              lElements[j].value = lFillValuePrefix + lNewSuffix;
              lFillValueSuffixAsInt -= lFillChangeValueAsInt;
            }
          }
          else if (lElements[j].tagName == "SELECT") {
            for (var k = 0; k < lElements[j].options.length; k++) {
              if (lElements[j].options[k].text.toUpperCase() == lFillValue.toUpperCase()) {
                lElements[j].options[k].selected = true;
              }
            }
          }
        }
      }
    }
  }
  
  // Reset the cursor. The cursor should have been changed in the calling page using
  // a setTimeout so that it has time to take affect.
  this.cursorDefault();
}

/**
 * This method toggles the stripes on the list. If you pass in a boolean,
 * it will ignore the toggle and use the given setting instead.
 */
DynaList.prototype.toggleStripes = function(pShowStripes) {
  var lMenuDiv = document.all[this.mId + "MenuDiv"];
  if (lMenuDiv != null) {
    lMenuDiv.style.display='none';
  }
  this.mShowStripes = (pShowStripes == null ? !this.mShowStripes : pShowStripes);
  this._drawStripes(this.mShowStripes);
}

/**
 * This method toggles the borders on the list. If you pass in a boolean,
 * it will ignore the toggle and use the given setting instead.
 */
DynaList.prototype.toggleBorders = function(pShowBorders) {
  var lMenuDiv = document.all[this.mId + "MenuDiv"];
  if (lMenuDiv != null) {
    lMenuDiv.style.display='none';
  }
  this.mShowBorders = (pShowBorders == null ? !this.mShowBorders : pShowBorders);
  this._drawBorders(this.mShowBorders);
}

/**
 * This method sets the cursor to the wait cursor for the given element
 * and the workspace.
 */
DynaList.prototype.cursorWait = function(pElement) {
  this.mSourceElement = pElement;
  if (this.mSourceElement != null) {
    if (this.mSourceElement.className == "Sort") {
      this.mSourceElement.className = "Sorting";
    }
    this.mSourceElement.style.cursor = "wait";
  }
  divWorkspace.style.cursor = "wait";
}

/**
 * This method sets the cursor to the default cursor for the previously
 * saved source element and the workspace.
 */
DynaList.prototype.cursorDefault = function() {
  if (this.mSourceElement != null) {
    if (this.mSourceElement.className == "Sorting") {
      this.mSourceElement.className = "Sort";
    }
    this.mSourceElement.style.cursor = "";
  }
  divWorkspace.style.cursor = "";
}

/**
 * This method handles the onmouseover event for the Options menu associated
 * with the list. It shows the div associated with the Options menu.
 */
DynaList.prototype.mouseOverMenu = function() {
  var lMenuTd = document.all[this.mId + "Menu"];
  var lMenuDiv = document.all[this.mId + "MenuDiv"];
  if (lMenuTd != null && lMenuDiv != null) {
    if (!lMenuDiv.contains(window.event.srcElement) || lMenuDiv.style.display != 'block') {
      if (document.body.offsetWidth - lMenuDiv.style.pixelWidth < lMenuTd.offsetLeft + 3) {
        lMenuDiv.style.posRight = 0;
      }
      else {
        lMenuDiv.style.posLeft = lMenuTd.offsetLeft;
      }
      lMenuDiv.style.posTop = lMenuTd.offsetTop + lMenuTd.offsetHeight;
      lMenuDiv.style.display = 'block';
    }
  }
}

/**
 * This method handles the onmouseout event for the Options menu associated
 * with the list. It hides the div associated with the Options menu.
 */
DynaList.prototype.mouseOutMenu = function() {
  var lMenuDiv = document.all[this.mId + "MenuDiv"];
  if (lMenuDiv != null) {
    if (!lMenuDiv.contains(window.event.toElement)) {
      lMenuDiv.style.display = 'none';
    }
  }
}

// PRIVATE METHODS

/**
 * This method returns a boolean indicating whether the list is visible.
 */
DynaList.prototype._isVisible = function() {
  lElement = document.getElementById(this.mId);
  while (lElement.parentElement != null) {
    lElement = lElement.parentElement;
    if (lElement.style.display == "none") {
      return false;
    }
  }
  return true;
}

/**
 * This method changes the html for the row striping.
 */
DynaList.prototype._drawStripes = function(pShowStripes) {
  if (pShowStripes) {
    var lDisplayedRowCount = 0;
    for (var i = 0; i < this.mRowCount; i++) {
      if (this.mRows[i].style.display != "none") {
        lDisplayedRowCount++;
        this.mRows[i].className = (lDisplayedRowCount & 0x01 ? "Odd" : "Even");
        
        // Now do the same for the split scrolling stuff.
        if (this.mUsingSplitScrolling) {
          this.mSplitRows[i].className = (lDisplayedRowCount & 0x01 ? "Odd" : "Even");
        }
      }
    }
  }
  else {
    for (var i = 0; i < this.mRowCount; i++) {
      this.mRows[i].className = "Odd";
      
      // Now do the same for the split scrolling stuff.
      if (this.mUsingSplitScrolling) {
        this.mSplitRows[i].className = "Odd";
      }
    }
  }
}

/**
 * This method changes the html for the cell bordering.
 */
DynaList.prototype._drawBorders = function(pShowBorders) {
  this.mBodyTableBody.className = (pShowBorders ? "Bordered" : "");
  
  // Now do the same for the split scrolling stuff.
  if (this.mUsingSplitScrolling) {
    this.mSplitBodyTableBody.className = (pShowBorders ? "Bordered" : "");
  }
}

/**
 * This method changes the html for the row counts.
 */
DynaList.prototype._drawRowCounts = function() {
  if (this.mRowCountLabel != null) {
    var lRowCounts = "<b>" + this.mRowsFoundText + "</b>";
    if (this.mRowCountFiltered == 0) {
      this.mRowCountLabel.innerHTML = lRowCounts;
    }
    else {
      var lFilteredText = this.mRowsFilteredText.replace("{0}", (this.mRowCount - this.mRowCountFiltered));
      lFilteredText = lFilteredText.replace("{1}", this.mRowCountFiltered);
      this.mRowCountLabel.innerHTML = lRowCounts + " (" + lFilteredText + ")";
    }
  }
}

/**
 * This constructor is used to represent a row in the sort method.
 */
function _sortRow(pMasterRowPtr, pSlaveRowPtr, pSortRowColumn, pCheckboxValues) {
  this.masterRowPtr = pMasterRowPtr; // Reference to the row.
  this.slaveRowPtr = pSlaveRowPtr; // Reference to the row in the other table when using split scrolling.
  this.sortRowColumn = pSortRowColumn; // _sortRowColumn object reference
  this.checkboxValues = pCheckboxValues; // Array of booleans representing checkbox values for the row.
}


/**
 * This constructor is used to represent a column in the sort method.
 */
function _sortRowColumn(pCell, pSortType) {
  this.innerText = pCell.innerText;
  this.innerHTML = pCell.innerHTML;
  this.containsHTML = (this.innerText != this.innerHTML);
  var lCellValue = _getCellValue(pCell);
  this.style = pCell.parentNode.style.cssText;
  if (arguments.length > 1) {
    if (pSortType == "string") {
      this.sortValue = lCellValue.toLowerCase();
    }
    else if (pSortType == "number") {
      this.sortValue = parseInt(lCellValue, 10);
    }
    else if (pSortType == "date" || pSortType == "datetime") {
      this.sortValue = Date.parse(lCellValue);
      if (isNaN(this.sortValue)) this.sortValue = 0;
    }
    else {
      this.sortValue = lCellValue.toLowerCase();
    }
  }
}

/**
 * This method gets the value of the given cell.
 */
function _getCellValue(pCell) {
  var lInnerText = pCell.innerText;
  var lInnerHTML = pCell.innerHTML;
  var lContainsHTML = (lInnerText != lInnerHTML);
  var lCellValue;
  if (!lContainsHTML || !pCell.all["sortElement"]) {
    lCellValue = lInnerText;
  }
  else {    
    var lSortElementName = pCell.all["sortElement"].innerText;
    var lElement = pCell.all[lSortElementName];
    if (!lElement) {
      lCellValue = lInnerText;
    }
    else {
      if (lElement.type == "select-one") {
        lCellValue = lElement.options[lElement.selectedIndex].text;
      }
      else {
        lCellValue = lElement.value;
      }
    }
  }
  return lCellValue;
}

/**
 * This method is for debugging purposes. It's not used in production.
 */
DynaList.prototype._debugWidth = function(pEntryPoint) {
  var lTotalWidth = 0;
  for (var i = 0; i < this.mBodyTable.rows[1].cells.length; i++) {
    lTotalWidth += this.mBodyTable.rows[1].cells[i].offsetWidth;
  }
  var lDebugWidth = pEntryPoint + "\n\ndivWorkspace.scrollWidth = " + divWorkspace.scrollWidth + "\ndivWorkspace.clientWidth = " + divWorkspace.clientWidth + "\ndivWorkspace.offsetWidth = " + divWorkspace.offsetWidth + "\n\nthis.mRequiredWidth = " + this.mRequiredWidth + "\nthis.mBodyDiv.style.width = " + this.mBodyDiv.style.width + "\nthis.mBodyDiv.offsetWidth = " + this.mBodyDiv.offsetWidth + "\nthis.mBodyTable.offsetWidth = " + this.mBodyTable.offsetWidth + "\ntotal of column widths = " + lTotalWidth + "\nthis.mNeedHorizontalScrollbar = " + this.mNeedHorizontalScrollbar + "\n\nthis.mOffsetLeft = " + this.mOffsetLeft + "\nthis.mOffsetRight = " + this.mOffsetRight;
  if (this.mUsingSplitScrolling) {
    var lSplitTotalWidth = 0;
    var lTempDebug = "";
    for (var i = 0; i < this.mSplitBodyTable.rows[1].cells.length; i++) {
      lTempDebug += "\nheader = " + this.mSplitHeaderTable.rows[0].cells[i].offsetWidth + " and body = " + this.mSplitBodyTable.rows[1].cells[i].offsetWidth;
      lSplitTotalWidth += this.mSplitBodyTable.rows[1].cells[i].offsetWidth;
    }
//    alert(lTempDebug);
    lDebugWidth += "\n\nthis.mSplitRequiredWidth = " + this.mSplitRequiredWidth + "\nthis.mSplitBodyDiv.style.width = " + this.mSplitBodyDiv.style.width + "\nthis.mSplitBodyDiv.offsetWidth = " + this.mSplitBodyDiv.offsetWidth + "\nthis.mSplitBodyTable.offsetWidth = " + this.mSplitBodyTable.offsetWidth + "\ntotal of column widths = " + lSplitTotalWidth + "\nthis.mSplitNeedHorizontalScrollbar = " + this.mSplitNeedHorizontalScrollbar;
  }
  alert(lDebugWidth);
}

/**
 * This method is for debugging purposes. It's not used in production.
 */
DynaList.prototype._debugHeight = function(pEntryPoint) {
  alert(pEntryPoint + "\n\ndivWorkspace.scrollHeight = " + divWorkspace.scrollHeight + "\ndivWorkspace.clientHeight = " + divWorkspace.clientHeight + "\ndivWorkspace.offsetHeight = " + divWorkspace.offsetHeight + "\n\nthis.mRequiredHeight = " + this.mRequiredHeight + "\nthis.mBodyDiv.style.height = " + this.mBodyDiv.style.height + "\nthis.mBodyDiv.offsetHeight = " + this.mBodyDiv.offsetHeight + "\nthis.mBodyTable.offsetHeight = " + this.mBodyTable.offsetHeight + "\n\nthis.mOffsetTop = " + this.mOffsetTop);
}

//-->
