﻿//-----------------------------------------------------------------------------
// XList
//
// Copyright 2005-2010 - Xcential Group LLC.
//
//-----------------------------------------------------------------------------

XList.prototype = new XControl;
XList.prototype.constructor = XList;

//=============================================================================
// Constructor

function XList(
   id,
   create
)
{
   id = (id == null) ? null : id;
   create = (create == null) ? true : false;

   if (create)
      return new XList(id, false);

   //--------------------------------------------------------------------------
   // Private Interface

   //--------------------------------------------------------------------------
   // Privileged Interface

   this.valueOf = function()
   {

      return oSpec;
   }

   //--------------------------------------------------------------------------

   this.setObjectValue = function(
      id
   )
   {
      id = (id == null) ? null : id;

      oSpec = XList.getSpecFrom(id);

      return oSpec;
   }

   //--------------------------------------------------------------------------
   // Initialization

   this.listItemColDefs = new Array();

   var oSpec = this.setObjectValue(id);

   XControl.controls[oSpec.id] = this;

}

XList.prototype.objectClass = "XList";

//=============================================================================
// Static Interface

XList.COL_LABEL = 0;
XList.COL_WIDTH = 1;
XList.COL_ALIGN = 2;
XList.COL_STYLE = 3;

XList.SCROLL_INTO_VIEW = true;

//-----------------------------------------------------------------------------

XList.getSpecFrom = function(
   id,
   uri
)
{
   id = (id == null) ? XUtils.generateId("urn:xcential-com:list:", XUtils.SCHEME_RANDOM) : id;
   uri = (uri == null) ? null : uri;

   var spec = new Array();
   spec.id = id;
   spec.uri = uri;
   spec.className = "XList";
   spec.tabIndex = null;
   spec.htmlNode = null;
   spec.mouseLeave = false;
   spec.visible = true;
   spec.left = 10;
   spec.top = 10;
   spec.right = null;
   spec.bottom = null;
   spec.width = null;
   spec.height = null;
   spec.shadowSize = 0;
   spec.showHeader = false;
   spec.showHeaderSelector = false;
   spec.showSelectors = false;
   spec.showIcons = false;
   spec.numberItems = false;
   spec.startIndex = null;
   spec.itemsClickable = false;
   spec.alternatingColors = false;
   spec.readOnlyCheckBox = false;
   spec.activeIndex = null;
   spec.sortName = null;
   spec.sortDir = XArray.SORT_ASCENDING;

   return spec;
}

//-----------------------------------------------------------------------------

XList.normalizeSortNames = function(
   listItemColDefs,
   sortName
)
{
   sortName = (sortName == null) ? null : sortName;

   for (var i=0; i<listItemColDefs.length; i++)
   {
      var listItemColDef = listItemColDefs[i];
      listItemColDef.sortName = (XString(listItemColDef.sortName).isSomething()) ? listItemColDef.sortName : listItemColDef.name;
      if (sortName && sortName == listItemColDef.name)
         sortName = listItemColDef.sortName;
   }

   return sortName;
}

//=============================================================================
// Event Handlers

XList.prototype.onListItemMouseOver = null;
XList.prototype.onListItemMouseOut = null;
XList.prototype.onListItemFocus = null;
XList.prototype.onListItemBlur = null;
XList.prototype.onListItemClick = null;
XList.prototype.onOption = null;
XList.prototype.onSort = null;

XList.prototype.listItems = new Array();
XList.prototype.listItemColDefs = new Array();
XList.prototype.listItemIcons = new Array();
XList.prototype.listItemIconWrap = null
XList.prototype.listItemTypeIdentifier = "type";
XList.prototype.listItemColorSelector = null;
XList.prototype.listItemDetail = null;
XList.prototype.ListItemDetailLabel = null;

//-----------------------------------------------------------------------------

XList.prototype.doOption = function(
   optionName,
   optionArg
)
{
   optionArg = (optionArg == null) ? null : optionArg;

   var spec = this.valueOf();

   if (this.onOption)
      this.onOption(this, optionName, optionArg);

}

//-----------------------------------------------------------------------------

XList.prototype.doHighlight = function(
   event,
   index
)
{
   event = (event == null) ? window.event : event;

   var spec = this.valueOf();

   try
   {
      if (spec.activeIndex)
         this.doUnhighlight(event, spec.activeIndex);

      var listItemId = this.getListItemId(index);

      var listItemNode = $(spec.id + "." + listItemId);
      if (listItemNode)
      {
         if (this.onListItemClick || listItemNode.getAttribute("onclick") || $$("a", listItemNode).length > 0)
         {
            XUI.doHighlight(event, spec.id + "." + listItemId);
            spec.activeIndex = index;
         }
      }

      var listItemDetailNode = $(spec.id + "." + listItemId + ".detail");
      if (listItemDetailNode)
      {
         if (this.onListItemClick || listItemNode.getAttribute("onclick") || $$("a", listItemNode).length > 0)
         {
            XUI.doHighlight(event, spec.id + "." + listItemId + ".detail");
         }
      }

      var listItemLink = $(spec.id + "." + listItemId + ".link");
      if (listItemLink)
      {
         XUI.doHighlight(event, spec.id + "." + listItemId + ".link");
         spec.activeIndex = index;
      }
   }
   catch (error)
   {
      XApp.logEvent(XApp.EVENT_ERROR, error);
      XMsg.alert(error);
   }

}

//-----------------------------------------------------------------------------

XList.prototype.doUnhighlight = function(
   event,
   index
)
{
   event = (event == null) ? window.event : event;

   var spec = this.valueOf();

   try
   {

      var listItemId = this.getListItemId(index);

      XUI.doUnhighlight(event, spec.id + "." + listItemId);
      XUI.doUnhighlight(event, spec.id + "." + listItemId + ".detail");
      XUI.doUnhighlight(event, spec.id + "." + listItemId + ".link");

      spec.activeIndex = null;
   }
   catch (error)
   {
      XApp.logEvent(XApp.EVENT_ERROR, error);
      XMsg.alert(error);
   }

}

//-----------------------------------------------------------------------------

XList.prototype.doListItemMouseOver = function(
   event,
   index
)
{
   event = (event == null) ? window.event : event;
   index = (index == null) ? -1 : index;

   var spec = this.valueOf();

   try
   {
      var listItemId = this.getListItemId(index);

      spec.mouseLeave = false;

      this.doHighlight(event, index);

      if (this.onListItemMouseOver)
         this.onListItemMouseOver(this, index);
   }
   catch (error)
   {
      XApp.logEvent(XApp.EVENT_ERROR, error);
      XMsg.alert(error);
   }

}

//-----------------------------------------------------------------------------

XList.prototype.doListItemMouseOut = function(
   event,
   index
)
{
   event = (event == null) ? window.event : event;
   index = (index == null) ? -1 : index;

   var spec = this.valueOf();

   try
   {
      var listItemId = this.getListItemId(index);

      spec.mouseLeave = true;

      this.doUnhighlight(event, index);

      if (this.onListItemMouseOut)
         this.onListItemMouseOut(this, index);

   }
   catch (error)
   {
      XApp.logEvent(XApp.EVENT_ERROR, error);
      XMsg.alert(error);
   }

}

//-----------------------------------------------------------------------------

XList.prototype.doListItemFocus = function(
   event,
   index
)
{
   event = (event == null) ? window.event : event;
   index = (index == null) ? -1 : index;

   var spec = this.valueOf();

   try
   {
      if (spec.itemsClickable)
         this.doHighlight(event, index);

      if (this.onListItemFocus)
         this.onListItemFocus(this, index);
   }
   catch (error)
   {
      XApp.logEvent(XApp.EVENT_ERROR, error);
      XMsg.alert(error);
   }

}

//-----------------------------------------------------------------------------

XList.prototype.doListItemBlur = function(
   event,
   index
)
{
   event = (event == null) ? window.event : event;
   index = (index == null) ? -1 : index;

   var spec = this.valueOf();

   try
   {
      if (spec.itemsClickable)
         this.doUnhighlight(event, index);

      if (this.onListItemBlur)
         this.onListItemBlur(this, index);
   }
   catch (error)
   {
      XApp.logEvent(XApp.EVENT_ERROR, error);
      XMsg.alert(error);
   }

}

//-----------------------------------------------------------------------------

XList.prototype.doListItemClick = function(
   event,
   index
)
{
   event = (event == null) ? window.event : event;
   index = (index == null) ? -1 : index;

   var spec = this.valueOf();

   try
   {
      var listItemId = this.getListItemId(index);

      if (!this.onListItemClick)
      {
         var listItemNode = $(spec.id + "." + listItemId);
         if (listItemNode)
         {
            var links = $$$("a", listItemNode);
            if (links.length > 0)
            {
               var link = links.item(0);
               link.click();
            }
         }
      }

      if (this.onListItemClick)
         this.onListItemClick(this, index);
   }
   catch (error)
   {
      XApp.logEvent(XApp.EVENT_ERROR, error);
      XMsg.alert(error);
   }

}

//-----------------------------------------------------------------------------

XList.prototype.doListItemKeyDown = function(
   event,
   index
)
{
   event = (event == null) ? window.event : event;
   index = (index == null) ? -1 : index;

   var keyCode = event.keyCode;

   if (keyCode == XUI.KEY_ENTER)
      this.doListItemClick(event, index);

}

//-----------------------------------------------------------------------------

XList.prototype.doHeaderSelectorClick = function(
   event
)
{
   event = (event == null) ? window.event : event;

   var spec = this.valueOf();

   try
   {
      var headerCheckBox = $(spec.id + ".headerCheckBox");
      if (headerCheckBox)
      {
         if (headerCheckBox.checked)
            this.selectAll();
         else
            this.unselectAll();
      }
   }
   catch (error)
   {
      XApp.logEvent(XApp.EVENT_ERROR, error);
      XMsg.alert(error);
   }

}

//-----------------------------------------------------------------------------

XList.prototype.doColumnHeaderHighlight = function(
   event,
   columnNum
)
{
   event = (event == null) ? window.event : event;

   var spec = this.valueOf();

   try
   {

      XUI.doHighlight(event, spec.id + ".headerCol" + columnNum);

   }
   catch (error)
   {
      XApp.logEvent(XApp.EVENT_ERROR, error);
      XMsg.alert(error);
   }

}

//-----------------------------------------------------------------------------

XList.prototype.doColumnHeaderUnhighlight = function(
   event,
   columnNum
)
{
   event = (event == null) ? window.event : event;

   var spec = this.valueOf();

   try
   {

      XUI.doUnhighlight(event, spec.id + ".headerCol" + columnNum);

   }
   catch (error)
   {
      XApp.logEvent(XApp.EVENT_ERROR, error);
      XMsg.alert(error);
   }

}

//-----------------------------------------------------------------------------

XList.prototype.doColumnHeaderClick = function(
   event,
   columnNum
)
{
   event = (event == null) ? window.event : event;

   var spec = this.valueOf();

   try
   {

      XUI.doUnhighlight(event, spec.id + ".headerCol" + columnNum);
      this.setSortName(this.listItemColDefs[columnNum].name);

   }
   catch (error)
   {
      XApp.logEvent(XApp.EVENT_ERROR, error);
      XMsg.alert(error);
   }

}

//-----------------------------------------------------------------------------

XList.prototype.doListItemSelectorClick = function(
   event,
   index
)
{
   event = (event == null) ? window.event : event;
   index = (index == null) ? -1 : index;

   var spec = this.valueOf();

   try
   {
      if (index == -1)
         return;

      var listItemId = this.getListItemId(index);

      var listItemCheckBox = $(spec.id + "." + listItemId + ".checkBox");
      var headerCheckBox = $(spec.id + ".headerCheckBox");
      if (this.listItems)
         this.listItems[index].checked = listItemCheckBox.checked;
      if (listItemCheckBox && headerCheckBox)
         headerCheckBox.checked = false;
   }
   catch (error)
   {
      XApp.logEvent(XApp.EVENT_ERROR, error);
      XMsg.alert(error);
   }

   if (event)
      event.cancelBubble = true;

}

//=============================================================================
// Public Interface

XList.prototype.setClass = function(
   className
)
{
   className = (className == null) ? "XList" : className;

   var spec = this.valueOf();

   spec.className = className;

   return className;
}

//-----------------------------------------------------------------------------

XList.prototype.getShowHeader = function(
   castAs
)
{
   castAs = (castAs == null) ? null : castAs;

   var spec = this.valueOf();

   var showHeader = spec.showHeader;

   return (castAs == null) ? showHeader : castAs(showHeader);
}

//-----------------------------------------------------------------------------

XList.prototype.showHeader = function(
   showHeader
)
{
   showHeader = (showHeader == null) ? true : showHeader;

   var spec = this.valueOf();

   spec.showHeader = showHeader;

   return showHeader;
}

//-----------------------------------------------------------------------------

XList.prototype.getShowHeaderSelector = function(
   castAs
)
{
   castAs = (castAs == null) ? null : castAs;

   var spec = this.valueOf();

   var showHeaderSelector = spec.showHeaderSelector;

   if (showHeaderSelector)
      showSelector(true);

   return (castAs == null) ? showHeaderSelector : castAs(showHeaderSelector);
}

//-----------------------------------------------------------------------------

XList.prototype.showHeaderSelector = function(
   showHeaderSelector
)
{
   showHeaderSelector = (showHeaderSelector == null) ? true : showHeaderSelector;

   var spec = this.valueOf();

   spec.showHeaderSelector = showHeaderSelector;

   return showHeaderSelector;
}

//-----------------------------------------------------------------------------

XList.prototype.getShowSelectors = function(
   castAs
)
{
   castAs = (castAs == null) ? null : castAs;

   var spec = this.valueOf();

   var showSelectors = spec.showSelectors;

   return (castAs == null) ? showSelectors : castAs(showSelectors);
}

//-----------------------------------------------------------------------------

XList.prototype.showSelectors = function(
   showSelectors
)
{
   showSelectors = (showSelectors == null) ? true : showSelectors;

   var spec = this.valueOf();

   spec.showSelectors = showSelectors;

   return showSelectors;
}

//-----------------------------------------------------------------------------

XList.prototype.selectAll = function()
{

   var spec = this.valueOf();

   if (this.listItems)
   {
      for (var i=0; i<this.listItems.length; i++)
      {
         var listItem = this.listItems[i];
         listItem.index = (listItem.index != null) ? listItem.index : i + spec.startIndex;
         listItem.id = (listItem.id != null) ? listItem.id : "ListItem" + listItem.index;
         var listItemCheckBox = $(spec.id + "." + listItem.id + ".checkBox");
         if (listItemCheckBox)
         {
            this.listItems.checked = true;
            listItemCheckBox.checked = true;
         }
      }
   }

}

//-----------------------------------------------------------------------------

XList.prototype.unselectAll = function()
{

   var spec = this.valueOf();

   if (this.listItems)
   {
      for (var i=0; i<this.listItems.length; i++)
      {
         var listItem = this.listItems[i];
         listItem.index = (listItem.index != null) ? listItem.index : i + spec.startIndex;
         listItem.id = (listItem.id != null) ? listItem.id : "ListItem" + listItem.index;
         var listItemCheckBox = $(spec.id + "." + listItem.id + ".checkBox");
         if (listItemCheckBox)
         {
            this.listItems.checked = false;
            listItemCheckBox.checked = false;
         }
      }
   }

}

//-----------------------------------------------------------------------------

XList.prototype.getShowIcons = function(
   castAs
)
{
   castAs = (castAs == null) ? null : castAs;

   var spec = this.valueOf();

   var showIcons = spec.showIcons;

   return (castAs == null) ? showIcons : castAs(showIcons);
}

//-----------------------------------------------------------------------------

XList.prototype.showIcons = function(
   showIcons
)
{
   showIcons = (showIcons == null) ? true : showIcons;

   var spec = this.valueOf();

   spec.showIcons = showIcons;

   return showIcons;
}

//-----------------------------------------------------------------------------

XList.prototype.getNumberItems = function(
   castAs
)
{
   castAs = (castAs == null) ? null : castAs;

   var spec = this.valueOf();

   var numberItems = spec.numberItems;

   return (castAs == null) ? numberItems : castAs(numberItems);
}

//-----------------------------------------------------------------------------

XList.prototype.numberItems = function(
   startIndex
)
{
   startIndex = (startIndex == null) ? 1 : startIndex;

   var spec = this.valueOf();

   spec.numberItems = (startIndex == false) ? false : true;
   spec.startIndex = (typeof(startIndex) == "number") ? startIndex : null;

   return startIndex;
}

//-----------------------------------------------------------------------------

XList.prototype.clearAll = function()
{

   //--------------------------------------------------------------------------

   function removeChildRows(
      element
   )
   {

      var childNodes = element.childNodes;
      for (var i=childNodes.length-1; i>=0; i--)
      {
         var childNode = childNodes.item(i);
         if (childNode.nodeName.toLowerCase() == "tbody")
         {
            removeChildRows(childNode);
            continue;
         }
         else if (childNode.nodeName.toLowerCase() != "tr")
            continue;
         else if (childNode.nodeType == XNode.NODE_ELEMENT && childNode.getAttribute("id") == (spec.id + ".header"))
            continue;
         childNode.parentNode.removeChild(childNode);
      }

   }

   //--------------------------------------------------------------------------

   var spec = this.valueOf();

   if (spec.htmlNode)
   {
      var tableNode = spec.htmlNode;

      removeChildRows(tableNode);
   }

}

//-----------------------------------------------------------------------------

XList.prototype.getListItemId = function(
   index
)
{
   index = (index == null) ? 0 : index;

   if (this.listItems && index < this.listItems.length && this.listItems[index].id != null)
      return this.listItems[index].id

   return "ListItem" + index;
}

//-----------------------------------------------------------------------------

XList.prototype.getState = function()
{

   var spec = this.valueOf();

   var state = new Array();
   if (spec.htmlNode)
   {
      for (var i=0; i<this.listItems.length; i++)
      {
         var listItem = this.listItems[i];
         var itemState = "";
         var checkBox = $(spec.id + "." + listItem.id + ".checkBox");
         if (checkBox && checkBox.checked)
            itemState += "|selected|";
         if (XString(itemState).isSomething())
            state[listItem.id] = itemState;
      }
   }

   return state;
}

//-----------------------------------------------------------------------------

XList.prototype.getListItemHTML = function(
   listItem,
   rowNum,
   state
)
{
   rowNum = (rowNum == null) ? 0 : rowNum;
   state = (state == null) ? null : state;

   //--------------------------------------------------------------------------

   function getColumnHTML(
      spec,
      listItem,
      textName,
      textAlternate,
      transform,
      wrap,
      maxLength
   )
   {
      textName = (textName == null) ? "text" : textName;
      textAlternate = (textAlternate == null) ? null : textAlternate;
      transform = (transform == null) ? null : transform;
      wrap = (wrap == null) ? null : wrap;
      maxLength = (maxLength == null) ? null : maxLength;

      var columnHTML = "";

      var textValue = listItem[textName];
      if (XString(textValue).isNothing() && textAlternate != null)
      {
         switch (typeof(textAlternate))
         {
            case "string":
            case "number":
               textValue = listItem[textAlternate];
               break;
            case "function":
               textValue = textAlternate(listItem);
               break;
         }
      }
      if (XString(textValue).isNothing())
         columnHTML = "-";
      else
      {
         if (transform && typeof(transform) == "function")
            textValue = transform(textValue);

         if (!spec.itemsClickable && XString(listItem.ref).isSomething())
         {
            columnHTML += "<a " +
               ((listItem.index != null) ? "id=\"" + spec.id + "." + listItem.id + ".hiddenLink\" " : "") +
               "class=\"Link\" " +
               ((listItem.ref && !listItem.onClick) ? "href=\"" + XString(listItem.ref).encode(XString.ENCODE_ENTITY) + "\" " : "") +
               ((listItem.onClick) ? "onclick=\"" + listItem.onClick.replace(/\"/g, "&quot;") + "\" " : "") +
               ((listItem.target && listItem.ref && !listItem.onClick) ? "target=\"" + listItem.target + "\" " : "") +
               "unselectable=\"on\" " +
               "hidefocus=\"true\" " +
            ">";
         }

         var columnValue = XString(textValue).truncate(maxLength, null, null, XString.ADD_ELLIPSIS)
         columnHTML += XString(columnValue).encode(XString.ENCODE_ENTITY);

         if (!spec.itemsClickable && XString(listItem.ref).isSomething())
         {
            columnHTML += "</a>";
            if ((/^http:/i).test(listItem.ref) &&
               !(/legisweb.net/i).test(listItem.ref) &&
               !(/legisweb.com/i).test(listItem.ref) &&
               !(/127.0.0.1/i).test(listItem.ref) &&
               !(/localhost/i).test(listItem.ref) &&
               !(/asterix/i).test(listItem.ref))
            {
               columnHTML += "&#160;";
               columnHTML += "<a " +
                  ((listItem.index != null) ? "id=\"" + spec.id + "." + listItem.id + ".hiddenLink\" " : "") +
                  "class=\"Link\" " +
                  ((listItem.ref && !listItem.onClick) ? "href=\"" + XString(listItem.ref).encode(XString.ENCODE_ENTITY) + "\" " : "") +
                  ((listItem.onClick) ? "onclick=\"" + listItem.onClick.replace(/\"/g, "&quot;") + "\" " : "") +
                  ((listItem.target && listItem.ref && !listItem.onClick) ? "target=\"" + listItem.target + "\" " : "") +
                  "unselectable=\"on\" " +
                  "hidefocus=\"true\" " +
               ">";
                  columnHTML += "<img class=\"ActionIcon\" src=\"/app/pkgs/xfw/images/ExternalLink.gif\" title=\"External link\"/>";
               columnHTML += "</a>";
            }
         }
      }

      return (wrap && typeof(wrap) == "function") ? wrap(listItem, columnHTML) : columnHTML;
   }

   //--------------------------------------------------------------------------

   function getListItemDef(
      spec,
      listItem,
      def
   )
   {

      if (def==null)
         return "";

      if (typeof(def) == "function")
         return def(spec, listItem);

      return def;
   }

   //--------------------------------------------------------------------------

   var spec = this.valueOf();

   listItem.id = (listItem.id != null) ? listItem.id : "ListItem" + listItem.index;

   var classModifier = (!spec.alternatingColors) ? "" : ((listItem.index) % 2) ? "-Odd" : "-Even";

   var listItemHTML = "";
   listItemHTML += "<tr " +
      "id=\"" + spec.id + "." + listItem.id + "\" " +
      "class=\"" + spec.className + "ListItem" + classModifier + " ListItem" + classModifier + "\" " +
      ((spec.tabIndex != null) ? "tabIndex=\"" + (spec.tabIndex + rowNum) + "\" " : "") +
      "style=\"" +
         ((this.listItemColorSelector) ? "background-color: " + this.listItemColorSelector(spec, listItem, classModifier) + "; " : "") +
      "\" " +
      "unselectable=\"on\" " +
      ((spec.itemsClickable) ? "onmouseover=\"XControl.controls['" + spec.id + "'].doListItemMouseOver(event," + listItem.index +")\" " : "") +
      ((spec.itemsClickable) ? "onmouseout=\"XControl.controls['" + spec.id + "'].doListItemMouseOut(event," + listItem.index +")\" " : "") +
      "onfocus=\"XControl.controls['" + spec.id + "'].doListItemFocus(event," + listItem.index + ")\" " +
      "onblur=\"XControl.controls['" + spec.id + "'].doListItemBlur(event," + listItem.index + ")\" " +
      ((spec.itemsClickable && (listItem.onClick || this.onListItemClick)) ? "onclick=\"" + ((listItem.onClick) ? listItem.onClick.replace(/\"/g, "&quot;") : "XControl.controls['" + spec.id + "'].doListItemClick(event," + listItem.index + ")") + "\" " : "") +
      "onkeydown=\"XControl.controls['" + spec.id + "'].doListItemKeyDown(event," + listItem.index + ")\" " +
   ">";
      if (spec.showSelectors)
      {
         listItemHTML += "<td " +
            "id=\"" + spec.id + "." + listItem.id + ".selector\" " +
            "class=\"" + spec.className + "ListItemSelector\" " +
            "style=\"" +
               ((!spec.alternatingColors && !this.listItemDetail) ? "border-bottom: solid lightgrey 1px; " : "") +
            "\" " +
            "unselectable=\"on\" " +
            "hidefocus=\"true\" " +
         ">";
            if (spec.readOnlyCheckBox || listItem.editable != false)
            {
               listItemHTML += "<input " +
                  "type=\"checkbox\" " +
                  "id=\"" + spec.id + "." + listItem.id + ".checkBox\" " +
                  "style=\"" +
                  "\" " +
                  "unselectable=\"on\" " +
                  "hidefocus=\"false\" " +
                  ((state && (/selected/i).test(state)) ? "checked=\"on\" " : "") +
                  "onclick=\"XControl.controls['" + spec.id + "'].doListItemSelectorClick(event," + listItem.index + ")\" " +
               "/>";
            }
            else
               listItemHTML += "&#160;";
         listItemHTML += "</td>";
      }
      else if (spec.showHeaderSelector)
      {
         listItemHTML += "<td " +
            "class=\"" + spec.className + "ListItemSelector\" " +
            "style=\"" +
               ((!spec.alternatingColors && !this.listItemDetail) ? "border-bottom: solid lightgrey 1px; " : "") +
            "\"" +
            "unselectable=\"on\" " +
            "hidefocus=\"true\" " +
         ">";
         listItemHTML += "&#160;</td>";
      }
      if (spec.showIcons)
      {
         listItemHTML += "<td " +
            "id=\"" + spec.id + "." + listItem.id + ".icon\" " +
            "class=\"" + spec.className + "ListItemIcon\" " +
            "style=\"" +
               ((!spec.alternatingColors && !this.listItemDetail) ? "border-bottom: solid lightgrey 1px; " : "") +
            "\" " +
            "unselectable=\"on\" " +
            "hidefocus=\"true\" " +
         ">";
            var iconURL = "/app/pkgs/xfw/themes/blue/images/Bullet.gif";
            var iconTitle = listItem.type;
            if (this.listItemIcons && this.listItemTypeIdentifier && listItem[this.listItemTypeIdentifier])
            {
               var configIconURL = this.listItemIcons[listItem[this.listItemTypeIdentifier]];
               iconURL = (XString(configIconURL).isSomething()) ? configIconURL : null;
               iconTitle = (XString(configIconURL).isSomething()) ? listItem[this.listItemTypeIdentifier] : null;
            }
            if (iconURL)
            {
               var iconHTML = "<img " +
                  "id=\"" + spec.id + "." + listItem.id + ".image\" " +
                  "class=\"" + spec.className + "ListItemImage LinkImage\" " +
                  "src=\"" + iconURL + "\" " +
                  ((!this.listItemIconWrap) ? "title=\"" + iconTitle + "\" "  : "") +
                  "unselectable=\"on\" " +
                  "hidefocus=\"true\" " +
               "/> ";
               listItemHTML += (this.listItemIconWrap) ? this.listItemIconWrap(listItem, iconHTML) : iconHTML;
            }
         listItemHTML += "</td>";
      }
      if (this.listItemColDefs && this.listItemColDefs.length > 0)
      {
         for (var j=0; j<this.listItemColDefs.length; j++)
         {
            var listItemColDef = this.listItemColDefs[j];
            var colspan=1;
            //for (var k=j+1; k<this.listItemColDefs.length; k++)
            //{
            //   if (listItem[this.listItemColDefs[k].name] != null)
            //      break;
            //   colspan++;
            //}
            listItemHTML += "<td " +
               "id=\"" + spec.id + "." + listItem.id + "." + listItemColDef.name + "\" " +
               "class=\"" +
                  spec.className + "ListItemCol" +
                  ((XString(listItemColDef.className).isSomething()) ? " " + listItemColDef.className : "") +
               "\" " +
               "style=\"" +
                  ((!spec.alternatingColors && !this.listItemDetail) ?
                     "border-bottom: solid lightgrey 1px; " : "") +
                  ((listItemColDef.backgroundColor) ?
                     "background-color: " + getListItemDef(spec, listItem, listItemColDef.backgroundColor) + "; " : "") +
                  ((listItemColDef.color) ?
                     "color: " + getListItemDef(spec, listItem, listItemColDef.color) + "; " : "") +
                  ((listItemColDef.fontWeight) ?
                     "font-weight: " + getListItemDef(spec, listItem, listItemColDef.fontWeight) + "; " : "") +
                  ((listItemColDef.margin) ?
                     "margin: " + getListItemDef(spec, listItem, listItemColDef.margin) + "; " : "") +
                  ((listItemColDef.padding) ?
                     "padding: " + getListItemDef(spec, listItem, listItemColDef.padding) + "; " : "") +
                  ((listItemColDef.textAlign) ?
                     "text-align: " + getListItemDef(spec, listItem, listItemColDef.textAlign) + "; " : "") +
                  ((listItemColDef.verticalAlign) ?
                     "vertical-align: " + getListItemDef(spec, listItem, listItemColDef.verticalAlign) + "; " : "") +
                  ((listItemColDef.textDecoration) ?
                     "text-decoration: " + getListItemDef(spec, listItem, listItemColDef.textDecoration) + "; " : "") +
                  ((listItemColDef.width && colspan==1) ?
                     "width: " + XUtils.dimText(getListItemDef(spec, listItem, listItemColDef.width)) + "; " : "") +
               "\" " +
               ((colspan > 1) ? "colspan=\"" + colspan + "\" " : "") +
               "unselectable=\"on\" " +
               "hidefocus=\"true\" " +
            ">";
               var columnValue = listItem[listItemColDef.name];
               if (XString(columnValue).isNothing() && listItemColDef.alternate != null)
               {
                  switch (typeof(listItemColDef.alternate))
                  {
                     case "string":
                     case "number":
                        columnValue = listItem[listItemColDef.alternate];
                        break;
                     case "function":
                        columnValue = listItemColDef.alternate(listItem);
                        break;
                  }
               }
               if (spec.numberItems && j==0)
               {
                  listItemHTML += String(listItem.index) + ".&#160;";
               }
               if (listItemColDef.grabTo)
               {
                  listItemHTML += "<a ";
                  listItemHTML += "   class=\"" +  spec.className + "ListItemPermalink\" ";
                  listItemHTML += "   onclick=\"" + listItemColDef.grabTo + "(event, '" + ((XString(columnValue).isSomething()) ? columnValue.replace(/'/g, "\\'") : "-") + "')\" ";
                  listItemHTML += "   title=\"Get a permalink\" ";
                  listItemHTML += ">";
                  listItemHTML += "<img src=\"" + "/app/pkgs/xfw/images/Permalink.gif" + "\"/>";
                  listItemHTML += "</a>";
               }
               listItemHTML += getColumnHTML(spec, listItem, listItemColDef.name, listItemColDef.alternate, listItemColDef.transform, listItemColDef.wrap, listItemColDef.maxLength);
            listItemHTML += "</td>";
         }
      }
      else
      {
         listItemHTML += "<td>";
            listItemHTML += getColumnHTML(spec, listItem);
         listItemHTML += "</td>";
      }
   listItemHTML += "</tr>";

   // Add details (spanning all columns) if asked for
   if (this.listItemDetail != null &&
       (typeof(this.listItemDetail) == "function") ||
       (XString(this.listItemDetail).isSomething() && listItem[this.listItemDetail]))
   {
      if (typeof(this.listItemDetail) == "function")
         var listItemDetailHTML = this.listItemDetail(spec, listItem);
      else
         var listItemDetailHTML = getColumnHTML(spec, listItem, this.listItemDetail);

      if (XString(listItemDetailHTML).isSomething())
      {
         listItemHTML += "<tr " +
            "id=\"" + spec.id + "." + listItem.id + ".detail\" " +
            "class=\"" + spec.className + "ListItem" + classModifier + " ListItem" + classModifier + "\" " +
            "style=\"" +
               ((this.listItemColorSelector) ? "background-color: " + this.listItemColorSelector(spec, listItem, classModifier) + "; " : "") +
            "\" " +
            "unselectable=\"on\" " +
            ((spec.itemsClickable) ? "onmouseover=\"XControl.controls['" + spec.id + "'].doListItemMouseOver(event," + listItem.index +")\" " : "") +
            ((spec.itemsClickable) ? "onmouseout=\"XControl.controls['" + spec.id + "'].doListItemMouseOut(event," + listItem.index +")\" " : "") +
            "onfocus=\"XControl.controls['" + spec.id + "'].doListItemFocus(event," + listItem.index + ")\" " +
            "onblur=\"XControl.controls['" + spec.id + "'].doListItemBlur(event" + listItem.index + ")\" " +
            ((spec.itemsClickable && (listItem.onClick || this.onListItemClick)) ? "onclick=\"" + ((listItem.onClick) ? listItem.onClick.replace(/\"/g, "&quot;") : "XControl.controls['" + spec.id + "'].doListItemClick(event," + listItem.index + ")") + "\" " : "") +
            "onkeydown=\"XControl.controls['" + spec.id + "'].doListItemKeyDown(event," + listItem.index + ")\" " +
         ">";
            if (spec.showSelectors || spec.showHeaderSelector)
               listItemHTML += "<td " +
                  "style=\"" +
                     ((!spec.alternatingColors) ? "border-bottom: solid lightgrey 1px; " : "") +
                  "\" " +
                  "unselectable=\"on\" " +
                  "hidefocus=\"true\" " +
               ">&#160;</td>";
            if (spec.showIcons)
               listItemHTML += "<td " +
                  "style=\"" +
                     ((!spec.alternatingColors) ? "border-bottom: solid lightgrey 1px; " : "") +
                  "\" " +
                  "unselectable=\"on\" " +
                  "hidefocus=\"true\" " +
               ">&#160;</td>";
            listItemHTML += "<td " +
               "id=\"" + spec.id + "." + listItem.id + ".detailCol\" " +
               "class=\"" + spec.className + "ListItemDetail\" " +
               "style=\"" +
                  ((!spec.alternatingColors) ? "border-bottom: solid lightgrey 1px; " : "") +
               "\" " +
               "colspan=\"" + this.listItemColDefs.length + "\" " +
               "unselectable=\"on\" " +
               "hidefocus=\"true\" " +
            ">";
               listItemHTML += listItemDetailHTML;
            listItemHTML += "</td>";
         listItemHTML += "</tr>";
      }
   }

   return listItemHTML;
}

//-----------------------------------------------------------------------------

XList.prototype.getListItemsHTML = function(
   listItems,
   startIndex,
   state
)
{
   listItems = (listItems == null) ? [] : listItems;
   startIndex = (startIndex == null) ? this.valueOf().startIndex : startIndex;
   state = (state == null) ? [] : state;

   var listItemsHTML = "";
   for (var i=0; i<listItems.length; i++)
   {
      var listItem = listItems[i];
      listItem.index = i + startIndex;
      listItemsHTML += this.getListItemHTML(listItem, i, state[listItem.id]);
   }

   return listItemsHTML;
}

//-----------------------------------------------------------------------------

XList.prototype.defineColumn = function(
   listItemColDef
)
{
   listItemColDef = (listItemColDef == null) ? null : listItemColDef;

   if (!this.listItemColDefs)
      this.listItemColDefs = new Array();

   if (!listItemColDef || typeof(listItemColDef) == "string")
   {
      var name = (listItemColDef && typeof(listItemColDef) == "string") ? listItemColDef : "column" + this.listItemColDefs.length;
      listItemColDef = new Array();
      listItemColDef.name = name;
      listItemColDef.identifier = false;
      listItemColDef.alternate = null;
      listItemColDef.className = null;
      listItemColDef.headerLabel = "Column " + this.listItemColDefs.length;
      listItemColDef.textAlign = "left";
      listItemColDef.verticalAlign = "top";
      listItemColDef.maxLength = null;
      listItemColDef.padding = null;
      listItemColDef.width = null;
      listItemColDef.textDecoration = null;
      listItemColDef.allowSort = false;
      listItemColDef.sortName = null;
      listItemColDef.sortDir = XArray.SORT_ASCENDING;
      listItemColDef.transform = null;
      listItemColDef.wrap = null;
      listItemColDef.grabTo = null;
   }

   this.listItemColDefs.push(listItemColDef);

   return listItemColDef;
}

//-----------------------------------------------------------------------------

XList.prototype.appendListItems = function(
   listItems
)
{
   listItems = (listItems == null) ? [] : listItems;

   var spec = this.valueOf();

   if (spec.htmlNode)
   {
      var tableNode = spec.htmlNode;
      var parentNode = tableNode;
      var childNodes = parentNode.childNodes;
      for (var i=0; i<childNodes.length; i++)
      {
         var childNode = childNodes.item(i);
         if (childNode.nodeType != XNode.NODE_ELEMENT)
            continue;
         if (childNode.nodeName.toLowerCase() == "tbody")
         {
            parentNode = childNode;
            break;
         }
      }

      var listItemsHTML = this.getListItemsHTML(listItems, Math.floor(parentNode.childNodes.length/2)+1);
      var listItemsXDoc = XDoc("<div>" + listItemsHTML + "</div>");
      var listItems = listItemsXDoc.getRoot().getChildren();
      for (var i=0; i<listItems.length; i++)
      {
         var listItem = listItems.X$(i);
         var listItemNode = listItem.clone(XNode.CLONE_DEEP, parentNode.ownerDocument, XNode.HTML_MODE);
         parentNode.appendChild(listItemNode.valueOf());
         if (XApp.isBrowser("MSIE"))
            tableNode.refresh();
      }
   }

   this.listItems = this.listItems.concat(listItems);

}

//-----------------------------------------------------------------------------

XList.prototype.removeOptions = function()
{

   var spec = this.valueOf();

   var optionsRow = $(spec.id + ".optionsRow");
   if (optionsRow)
      optionsRow.parentNode.removeChild(optionsRow);

}

//-----------------------------------------------------------------------------

XList.prototype.addOptions = function(
   options,
   scrollIntoView
)
{
   options = (options == null) ? [] : options;
   scrollIntoView = (scrollIntoView == null) ? false : scrollIntoView

   var spec = this.valueOf();

   if (spec.htmlNode)
   {
      var tableNode = spec.htmlNode;
      var parentNode = tableNode;
      var childNodes = parentNode.childNodes;
      for (var i=0; i<childNodes.length; i++)
      {
         var childNode = childNodes.item(i);
         if (childNode.nodeType != XNode.NODE_ELEMENT)
            continue;
         if (childNode.nodeName.toLowerCase() == "tbody")
         {
            parentNode = childNode;
            break;
         }
      }

      var optionsHTML = "";
      optionsHTML += "<tr ";
      optionsHTML += "   id=\"" + spec.id + ".optionsRow\" ";
      optionsHTML += "   class=\"" + spec.className + "Footer\" ";
      optionsHTML += ">";
      optionsHTML += "   <td ";
      optionsHTML += "      class=\"" + spec.className + "Options\" ";
      optionsHTML += "      colspan=\"" + (this.listItemColDefs.length+((spec.showSelectors || spec.showHeaderSelector) ? 1 : 0)+((spec.showIcons) ? 1 : 0)) + "\" ";
      optionsHTML += "   >";
      optionsHTML += "      <span ";
      optionsHTML += "         id=\"" + spec.id + ".options\" ";
      optionsHTML += "      >";
      for (var i=0; i<options.length; i++)
      {
         var option = options[i];
         optionsHTML += "<a ";
         optionsHTML +=   "class=\"Link\" ";
         optionsHTML +=   "onclick=\"XControl.controls['" + spec.id + "'].doOption('" + option[0] + "','" + XString(option[1]).encode(XString.ENCODE_ENTITY) + "')\" ";
         optionsHTML +=   "title=\"" + ((option.length > 2) ? option[2] : option[0]) + "\" ";
         optionsHTML += ">";
         optionsHTML +=      option[0];
         optionsHTML += "</a>";
         if (i+1<options.length)
            optionsHTML += "&#160;<span class=\"" + spec.className + "OptionsSeparator Accent\">|</span>&#160;";
      }
      optionsHTML += "      </span>";
      optionsHTML += "   </td>";
      optionsHTML += "</tr>";

      var optionsDoc = XDoc(optionsHTML);
      var optionsRowNode = optionsDoc.getRoot().clone(XNode.CLONE_DEEP, parentNode.ownerDocument, XNode.HTML_MODE);
      parentNode.appendChild(optionsRowNode.valueOf());

      if (XApp.isBrowser("MSIE"))
         tableNode.refresh();

      if (scrollIntoView)
      {
         var options = $(spec.id + ".optionsRow");
         if (options)
            options.scrollIntoView(false);
      }
   }

}

//-----------------------------------------------------------------------------

XList.prototype.getItemsClickable = function(
   castAs
)
{
   castAs = (castAs == null) ? null : castAs;

   var spec = this.valueOf();

   var itemsClickable = spec.itemsClickable;

   return (castAs == null) ? itemsClickable : castAs(itemsClickable);
}

//-----------------------------------------------------------------------------

XList.prototype.setItemsClickable = function(
   itemsClickable
)
{
   itemsClickable = (itemsClickable == null) ? true : itemsClickable;

   var spec = this.valueOf();

   spec.itemsClickable = itemsClickable;

   return itemsClickable;
}

//-----------------------------------------------------------------------------

XList.prototype.getAlternatingColors = function(
   castAs
)
{
   castAs = (castAs == null) ? null : castAs;

   var spec = this.valueOf();

   var alternatingColors = spec.alternatingColors;

   return (castAs == null) ? alternatingColors : castAs(alternatingColors);
}

//-----------------------------------------------------------------------------

XList.prototype.setAlternatingColors = function(
   alternatingColors
)
{
   alternatingColors = (alternatingColors == null) ? true : alternatingColors;

   var spec = this.valueOf();

   spec.alternatingColors = alternatingColors;

   return alternatingColors;
}

//-----------------------------------------------------------------------------

XList.prototype.getReadOnlyCheckBox = function(
   castAs
)
{
   castAs = (castAs == null) ? null : castAs;

   var spec = this.valueOf();

   var readOnlyCheckBox = spec.readOnlyCheckBox;

   return (castAs == null) ? readOnlyCheckBox : castAs(readOnlyCheckBox);
}

//-----------------------------------------------------------------------------

XList.prototype.setReadOnlyCheckBox = function(
   readOnlyCheckBox
)
{
   readOnlyCheckBox = (readOnlyCheckBox == null) ? true : readOnlyCheckBox;

   var spec = this.valueOf();

   spec.readOnlyCheckBox = readOnlyCheckBox;

   return readOnlyCheckBox;
}

//-----------------------------------------------------------------------------

XList.prototype.getSortName = function(
   castAs
)
{
   castAs = (castAs == null) ? null : castAs;

   var spec = this.valueOf();

   var sortName = spec.sortName;

   return (castAs == null) ? sortName : castAs(sortName);
}
//-----------------------------------------------------------------------------

XList.prototype.setSortName = function(
   sortName
)
{
   sortName = (sortName == null) ? null : sortName;

   //--------------------------------------------------------------------------

   function setSortDirIcon(
      sortName,
      listItemColDefs
   )
   {

      var sortColIndex = null;
      if (typeof(sortName) == "string")
      {
         for (var i=0; i<listItemColDefs.length; i++)
         {
            var listItemColDef = listItemColDefs[i];
            if (listItemColDef.sortName == sortName)
            {
               sortColIndex = i;
               break;
            }
         }
      }
      else
         sortColIndex = sortName;

      if (sortColIndex != null)
      {
         var colDef = listItemColDefs[sortColIndex];
         var isAscVisible = (spec.sortDir == XArray.SORT_ASCENDING && (spec.sortName == colDef.sortName)) ? true : false;
         var isDescVisible = (spec.sortDir == XArray.SORT_DESCENDING && (spec.sortName == colDef.sortName)) ? true : false;
         XUI(spec.id + ".headerCol" + sortColIndex + ".ascIcon").setRuntimeStyle("display", (isAscVisible) ? "inline" : "none");
         XUI(spec.id + ".headerCol" + sortColIndex + ".descIcon").setRuntimeStyle("display", (isDescVisible) ? "inline" : "none");
      }

   }

   //--------------------------------------------------------------------------

   var spec = this.valueOf();

   sortName = XList.normalizeSortNames(this.listItemColDefs, sortName);

   if (spec.htmlNode)
   {

      if (sortName && spec.sortName == sortName)
      {
         spec.sortDir = (spec.sortDir == XArray.SORT_ASCENDING) ? XArray.SORT_DESCENDING : XArray.SORT_ASCENDING;
         setSortDirIcon(sortName, this.listItemColDefs);

         if (this.onSort)
            this.onSort(this, spec.sortName, spec.sortDir);
      }
      else
      {
         // Unset any existing sort column
         if (spec.sortName)
         {
            for (var i=0; i<this.listItemColDefs.length; i++)
            {
               var listItemColDef = this.listItemColDefs[i];
               if (listItemColDef.sortName == spec.sortName)
               {
                  var element = $(spec.id + ".headerCol" + i);
                  if (element)
                     element.className = element.className.replace(/\-Active/, "");
                  spec.sortName = null;
                  setSortDirIcon(i, this.listItemColDefs);
                  break;
               }
            }
         }

         // Set the new sort column
         if (sortName)
         {
            for (var i=0; i<this.listItemColDefs.length; i++)
            {
               var listItemColDef = this.listItemColDefs[i];
               if (listItemColDef.sortName == sortName)
               {
                  var element = $(spec.id + ".headerCol" + i);
                  if (element)
                     element.className = element.className.replace(/\-Active/, "").replace(/\-Highlight/, "") + "-Active";

                  spec.sortName = listItemColDef.sortName;
                  spec.sortDir = listItemColDef.sortDir;
                  setSortDirIcon(i, this.listItemColDefs);

                  if (this.onSort)
                     this.onSort(this, spec.sortName, spec.sortDir);

                  break;
               }
            }
         }
      }
   }
   else
   {
      for (var i=0; i<this.listItemColDefs.length; i++)
      {
         var listItemColDef = this.listItemColDefs[i];
         if (listItemColDef.sortName == sortName)
         {
            spec.sortName = listItemColDef.sortName;
            spec.sortDir = listItemColDef.sortDir;
            break;
         }
      }
   }

   return spec.sortName;
}

//-----------------------------------------------------------------------------

XList.prototype.getSortDir = function(
   castAs
)
{
   castAs = (castAs == null) ? null : castAs;

   var spec = this.valueOf();

   var sortDir = spec.sortDir;

   return (castAs == null) ? sortDir : castAs(sortDir);
}
//-----------------------------------------------------------------------------

XList.prototype.setSortDir = function(
   sortDir
)
{
   sortDir = (sortDir == null) ? XArray.SORT_ASCENDING : sortDir;

   var spec = this.valueOf();

   spec.sortDir = sortDir;

   if (spec.htmlNode)
   {

      spec.sortDir = (spec.sortDir == XArray.SORT_ASCENDING) ? XArray.SORT_DESCENDING : XArray.SORT_ASCENDING;
      setSortDirIcon(spec.sortName, this.listItemColDefs);

      if (this.onSort)
         this.onSort(this, spec.sortName, spec.sortDir);

   }

   return spec.sortDir;
}

//-----------------------------------------------------------------------------

XList.prototype.toHTML = function(
   state
)
{
   state = (state == null) ? [] : state;

   var spec = this.valueOf();

   XList.normalizeSortNames(this.listItemColDefs);

   var controlHTML = "<table " +
      "id=\"" + spec.id + "\" " +
      "class=\"" + spec.className + "\" " +
      "style=\"" +
         "display: " + ((spec.visible) ? XUI.TABLE_DISPLAY : "none" ) + "; " +
         "left: " + XUtils.dimText(spec.left) + "; " +
         "top: " + XUtils.dimText(spec.top) + "; " +
         "width: " + ((spec.width) ? XUtils.dimText(spec.width) : "100%") + "; " +
      "\" " +
      "unselectable=\"on\" " +
      ((XApp.isBrowser("MSIE")) ? "onmouseenter=\"XControl.controls['" + spec.id + "'].doMouseEnter(event)\" " : "") +
      ((!XApp.isBrowser("MSIE")) ? "onmouseover=\"XControl.controls['" + spec.id + "'].doMouseOver(event)\" " : "") +
      ((!XApp.isBrowser("MSIE")) ? "onmouseout=\"XControl.controls['" + spec.id + "'].doMouseOut(event)\" " : "") +
      ((XApp.isBrowser("MSIE")) ? "onmouseleave=\"XControl.controls['" + spec.id + "'].doMouseLeave(event)\" " : "") +
      "onclick=\"XControl.controls['" + spec.id + "'].doClick(event)\" " +
   ">";

      if (spec.showHeader && this.listItemColDefs)
      {
         controlHTML += "<thead><tr " +
            "id=\"" + spec.id + ".header\" " +
            "class=\"" + spec.className + "Header Header\" " +
            "style=\"" +
            "\" " +
            "unselectable=\"on\" " +
         ">";
            if (spec.showHeaderSelector)
            {
               controlHTML += "<th " +
                  "id=\"" + spec.id + ".headerSelector\" " +
                  "class=\"" + spec.className + "HeaderSelector\" " +
                  "style=\"" +
                  "\" " +
                  "unselectable=\"on\" " +
               ">";
                  controlHTML += "<input " +
                     "type=\"checkbox\" " +
                     "id=\"" + spec.id + ".headerCheckBox\" " +
                     "style=\"" +
                        "height: 14px; " +
                     "\" " +
                     "unselectable=\"on\" " +
                     "onclick=\"XControl.controls['" + spec.id + "'].doHeaderSelectorClick(event)\" " +
                  "/>";
               controlHTML += "</th>";
            }
            else if (spec.showSelectors)
            {
               controlHTML += "<th " +
                  "class=\"" + spec.className + "HeaderSelector\" " +
                  "style=\"" +
                  "\" " +
               ">";
               controlHTML += "&#160;</th>";
            }
            if (spec.showIcons)
            {
               controlHTML += "<th " +
                  "class=\"" + spec.className + "HeaderIcon\" " +
                  "style=\"" +
                  "\"" +
               ">";
               controlHTML += "</th>";
            }
            for (var i=0; i<this.listItemColDefs.length; i++)
            {
               var listItemColDef = this.listItemColDefs[i];
               var columnModifier = (listItemColDef.sortName == spec.sortName) ? "-Active" : "";
               var sortAsc = (spec.sortDir == XArray.SORT_ASCENDING && (listItemColDef.sortName == spec.sortName)) ? true : false;
               var sortDesc = (spec.sortDir == XArray.SORT_DESCENDING && (listItemColDef.sortName == spec.sortName)) ? true : false;
               controlHTML += "<th " +
                  "id=\"" + spec.id + ".headerCol" + i + "\" " +
                  "class=\"" + spec.className + "HeaderCol Header" + columnModifier + "\" " +
                  "style=\"" +
                     ((listItemColDef.width) ? "width: " + XUtils.dimText(listItemColDef.width) + "; " : "") +
                     ((listItemColDef.textAlign) ? "text-align: " + listItemColDef.textAlign + "; " : "") +
                  "\" " +
                  "unselectable=\"on\" " +
                  ((listItemColDef.allowSort) ? "onmouseover=\"XControl.controls['" + spec.id + "'].doColumnHeaderHighlight(event, " + i + ")\" " : "") +
                  ((listItemColDef.allowSort) ? "onmouseout=\"XControl.controls['" + spec.id + "'].doColumnHeaderUnhighlight(event, " + i + ")\" " : "") +
                  ((listItemColDef.allowSort) ? "onclick=\"XControl.controls['" + spec.id + "'].doColumnHeaderClick(event, " + i + ")\" " : "") +
               ">";
                  controlHTML += (listItemColDef.headerLabel != null) ? listItemColDef.headerLabel : "Header " + i;
                  controlHTML += "<img " +
                     "class=\"XListHeaderDirIcon\" " +
                     "id=\"" + spec.id + ".headerCol" + i + ".ascIcon\" " +
                     "style=\"display: " + ((sortAsc) ? "inline" : "none") + "\" " +
                     "src=\"/app/pkgs/xfw/images/Down.Blue.gif\" " +
                     "title=\"Sort ascending\" " +
                  "/>";
                  controlHTML += "<img " +
                     "class=\"XListHeaderDirIcon\" " +
                     "id=\"" + spec.id + ".headerCol" + i + ".descIcon\" " +
                     "style=\"display: " + ((sortDesc) ? "inline" : "none") + "\" " +
                     "src=\"/app/pkgs/xfw/images/Up.Blue.gif\" " +
                     "title=\"Sort descending\" " +
                  "/>";
               controlHTML += "</th>";
            }
         controlHTML += "</tr></thead>";
      }

      if (this.listItems)
      {
        controlHTML += "<tbody>";
        controlHTML += this.getListItemsHTML(this.listItems, null, state);
        controlHTML += "</tbody>";
      }

   controlHTML += "</table>";

   return controlHTML;
}

//=============================================================================

