/////////////////////
// Data Table
////////////////////

var _awtTables = new Object();  // hashtable -- key == elementId, value == tableInfo
var _awtMouseDown;
var _awtMouseDownTarget;
var _AWTInPostLoad = false;
var _AWTPendingScroll = false;
var _AWTDidRegRefreshCallback = false;
var AWTForceDragStyle = "awtForceDrag";

function awtRegisterNonScrollTableId (id, isDraggable, disableRowSelection, disableCheckNbsps)
{
    //debug("Register nonScrollTable: id=" + id);
    var wrapperTable = awgetElementById(id);
    var bodyTable = _awFindChildUsingPredicate(wrapperTable, function (e) {
                        return e.tagName=="TABLE" && e.className=="tableBody";
                    });
    var tableInfo = _awtTables[id];
    if (!tableInfo || (wrapperTable != tableInfo.wrapperTable) || (bodyTable != tableInfo.bodyTable)) {
        tableInfo = new Object();
        tableInfo.isDraggable = isDraggable;
        tableInfo.wrapperTable = wrapperTable;
        tableInfo.bodyTable = bodyTable;
        tableInfo.isScrollTable = false;
        tableInfo.disableRowSelection = disableRowSelection;
        tableInfo.checkNbsps = !disableCheckNbsps;
        _awtTables[id] = tableInfo;
    }
    var table = tableInfo.bodyTable;
    if (table == null) alert("can't find table in wrapperTable: " + id);
    awtSetupTable(tableInfo);
}

// called on table creation, or change to table contents
function awtRegisterScrollTable (id, disableCheckNbsps, tableHeaderId)
{
    //debug("*** awtRegisterScrollTable: " + id + ", draggable:" + isDraggable + ", isScope: " +  AWIsScopeUpdate);
    var wrapperTable = awgetElementById(id);
    var tableInfo = _awtTables[id];
    var bodyTable = _awFindChildUsingPredicate(wrapperTable, function (e) {
                        return e.tagName=="TABLE" && e.className=="tableBody";
                    });
    if (!tableInfo || (wrapperTable != tableInfo.wrapperTable) || (bodyTable != tableInfo.bodyTable)) {
        // new table or need to reset table
        tableInfo = new Object();
        _awtTables[id] = tableInfo;
        tableInfo.checkNbsps = !disableCheckNbsps;
        tableInfo.wrapperTable = wrapperTable;
        tableInfo.head = _awFindChildUsingPredicate(wrapperTable, function (e) {
                    return e.tagName=="DIV" && e.className=="tableHead";
               });
        // debug("tableInfo.head = " + tableInfo.head);
        tableInfo.headTable = _awFindChild(tableInfo.head, "TABLE");
        // debug("tableInfo.headTable = " + tableInfo.headTable);
        tableInfo.bodyTable = bodyTable;
        tableInfo.body = _awFindParent(bodyTable, "DIV");
        tableInfo.minHeight = parseInt(wrapperTable.minHeight);  // todo: should be passed as arg for Netscape compatability

        if (tableInfo.headTable) {
            tableInfo.selectAll = awtRowSelectElement(tableInfo.headTable.rows[0]);
        }

        tableInfo.isScrollTable = true;

        if (!_AWTDidRegRefreshCallback) {
            // make sure we re-check scroll table sizing on any refresh
            awDomRegisterRefreshCallback(awtWindowResized);
            _AWTDidRegRefreshCallback = true;
        }
    }

    // set up maximize control  -- (performance: restrict scope of search to header if possible)
    // If this is legacy usage (i.e. Sourcing) then tableHeaderId==null and we need to search whole wrapperTable,
    // otherwise '' means no search, and an id means search that table
    var searchElement = ((tableHeaderId == null) ? wrapperTable
                            : ((tableHeaderId.length) ? awgetElementById(tableHeaderId) : null));
    // debug("tableHeaderId: " + ((tableHeaderId == null) ? "*NULL*" : ("'" + tableHeaderId + "'")) + " --> " + searchElement);
    if (searchElement) {
        var mmControl = _awFindChildUsingPredicate(searchElement, function (e) {
                            return e.tagName=="TD" && e.className=="awtMMNone";
                        });
        if (tableInfo.mmControl != mmControl) {
            tableInfo.mmControl = mmControl;
            if (tableInfo.mmControl) {
                tableInfo.mmControl.onclick = function () { return awtMaxMin(tableInfo); };
            }
        }
    }

    awtRegisterPostLoad();
}

// called on init, and on change to parameters
function awtUpdateScrollTable (id, isDraggable, disableRowSelection, isMaximizedId,
                topCount, bottomCount, topIndexId, topOffsetId, scrollFaultActionId, rowIdToForceVisible)
{
    var tableInfo = _awtTables[id];
    if (!tableInfo) return;  // Assert?
    tableInfo.isDraggable = isDraggable;
    tableInfo.isMaximizedId = (isMaximizedId == "") ? null : isMaximizedId;
    tableInfo.maximize =  (isMaximizedId) ? ((awtValue(isMaximizedId) == "true") ? true : false) : false;

    tableInfo.disableRowSelection = disableRowSelection;

    tableInfo.rowIdToForceVisible = (rowIdToForceVisible == "") ? null : rowIdToForceVisible;

    // table faulting properties
    if (topCount) {
        tableInfo.topCount = parseInt(topCount);
        tableInfo.bottomCount = parseInt(bottomCount);
        tableInfo.topIndexId = topIndexId;
        tableInfo.topOffsetId = topOffsetId;
        tableInfo.scrollFaultActionId = scrollFaultActionId;
        tableInfo.repositionScroll = true;
    }

    awtRegisterPostLoad();
}

function awtRegisterPostLoad ()
{
    // register callback to execute after all the content on the page
    // has been rendered
    if (!_AWTInPostLoad) {
        _AWTInPostLoad = true;
        awDomRegisterUpdateCompleteCallback(awtPostLoad);
    }
}

function awtInfoForScrollableTable (id)
{
    var info = awtInfoForTable(id);
    return (info && info.isScrollTable) ? info : null;
}

function awtInfoForTable (id)
{
    var info = _awtTables[id];

    if (!info) {
        alert("Missing table info! " + id + ": " + printStack());
    }

    // make sure table still in document
    if (awgetElementById(id)) {
        return info;
    }

    // delete old info?
    return null;
}

function awtSetupTable (tableInfo)
{
    var bodyTable = tableInfo.bodyTable;

    // debug("table init " + bodyTable.id + " " + isDraggable);

    // add scrolling / selection event handlers
    if (!bodyTable.onmouseover) {
        bodyTable.onmouseover = awtMouseOverEventHandler;
        bodyTable.onmouseout  = awtMouseOutEventHandler;
        bodyTable.onclick     = awtRowClickedEventHandler;

        bodyTable.onmousedown = awtMouseDownEvtHandler;
        bodyTable.onmouseup   = awtMouseUpEvtHandler;
        bodyTable.onmousemove = awtMouseMoveEvtHandler;
        bodyTable.onselectstart = awtSelectStartEvtHandler;
    }

    // highlight selected rows
    if (!tableInfo.disableRowSelection) {
        for (var i=0; i < bodyTable.rows.length; i++) {
            var row = bodyTable.rows[i];
            var isSelected = awtIsRowSelected(row);
            if (isSelected) {
                awtSetRowSelect(row, true, true, tableInfo);
            }
        }
        awtUpdateSelectAll(tableInfo);
    }

    if (tableInfo.checkNbsps) awtAddNbsps(bodyTable);

    // netscape selection support
    if (awIsNetscape()) {
        // have to use actual class since style does not work
        //tableInfo.wrapperTable.style["-moz-user-select"] = "none";
        awAddClass(tableInfo.wrapperTable, "mozillaDisableSelect");
    }
}

function awtAddNbsps (table)
{
    // add nbsps to empty cells  --- Expensive!
    var rows = table.rows;

    for (var i = 0; i < rows.length; i++) {
        var cells = rows[i].cells;
        for (var j = 0; j < cells.length; j++) {
            var cell = cells[j];
            var numChildren = cell.childNodes.length;
            if (numChildren == 0) {
                cell.innerHTML="&nbsp;";
            } else if (numChildren == 1) {
                // look for an empty span
                var innerCell = cell.childNodes[0];
                if (((innerCell.tagName == "SPAN") || (innerCell.tagName == "A")) && innerCell.childNodes.length == 0) {
                    cell.innerHTML="&nbsp;";
                }
            }
        }
    }
}

function awtSetupScrollTable (tableInfo)
{
    // FIXME!  What happens if change causes number of columns to change?  Then need to redo setup?
    if (tableInfo.bodyTable.rows.length == 0) {
        tableInfo.isScrollTable = false;
    } else if (!tableInfo.scrollSetup) {
        tableInfo.scrollSetup = true;

        // Add an invisible row to tableBody that mirrors header row -- so body cols
        // do not compress more than the header should allow
        var bodyCells = tableInfo.bodyTable.rows[0].cells;
        var newRow = document.createElement('TR');
        newRow.className = "AWTColAlignRow";
        if (tableInfo.headTable) {
            var headerRows = tableInfo.headTable.rows;
            var headerRow = headerRows[0];

            // add extra padding cell to header (for scrollbar padding)
            for(var i=0; i<headerRows.length; i++) {
                var th = document.createElement('TH');
                th.appendChild(document.createTextNode(' '));
                th.className = headerRow.cells[headerRow.cells.length - 1].className;
                headerRows[i].appendChild(th);
            }

            // check empty cells in the header
            if (tableInfo.checkNbsps) awtAddNbsps(tableInfo.headTable);

            // and a head table row, so we can sync header widths with body table
            var newHeaderRow = document.createElement('TR');

            // debug ("Adding body cells to preserve spacing for header: ");
            var headCells = headerRow.cells;
            for (var i = 0; i < headCells.length; i++) {
                var headCell = headCells[i];

                // for each of the cells in the header, add an empty TD to the hidden body
                // spacer row with a padding that equals the minimum width of the header cell
                var td = document.createElement('TD');
                td.appendChild(document.createTextNode(''));
                var wd = headCell.offsetWidth;
                if (i != headCells.length -1) {
                    td.style.paddingRight = wd;
                    td.width = headCell.width;
                    // debug("Setting spacer td.width = " + headCell.width);

                } else {
                    td.width="1px";
                }

                // debug("--- cell[" + i + "]" + ".offsetWidth = " + wd);
                newRow.appendChild(td);

                // create an empty TH - later will set padding to sync with body columns
                var th = document.createElement('TH');
                th.appendChild(document.createTextNode(''));
                newHeaderRow.appendChild(th);
            }
            tableInfo.headTable.tBodies[0].insertBefore(newHeaderRow, tableInfo.headTable.rows[0]);

            // remove the forced width on the header table.  This is initially set to 1px in
            // so the header cells are initially displayed with their minimum widths.
            tableInfo.headTable.style.width = "auto";
        } else {
            // add one extra TD to the first row
            for (var i = 0; i < (bodyCells.length + 1); i++) {
                // create an empty TD with a padding that equals the space that we need to consume
                var td = document.createElement('TD');
                td.appendChild(document.createTextNode(''));
                // minimize the last TD (scrollbar padding)
                if (i == bodyCells.length) {
                    td.width="1px";
                }
                newRow.appendChild(td);
            }
        }

        tableInfo.bodyTable.tBodies[0].insertBefore(newRow, tableInfo.bodyTable.rows[0]);

        // Scroll sync between body and header
        tableInfo.body.onscroll = function () {
            /*
            debug("scroll=" + event.srcElement.scrollLeft
                + ",  head:  scrollWd=" + tableInfo.head.scrollWidth + ", clientWd=" + tableInfo.head.clientWidth
                + "scrollLeft=" + tableInfo.head.scrollLeft);
            */
            if (tableInfo.head) {
                tableInfo.head.scrollLeft = event.srcElement.scrollLeft;
            }
            awtHandleVerticalScroll(tableInfo);
        };

    }


    // generic setup
    awtSetupTable(tableInfo);
}

function awtPostLoad ()
{
    //debug("awtPostLoad running...");
    var faultTableInfo = new Array();
    for (var id in _awtTables) {
        var tableInfo = awtInfoForScrollableTable(id);
        // debug("Looking for Id: " + id + ", info:" + _awtTables[id] + ", scroll" + tableInfo);
        if (tableInfo) {
            awtSetupScrollTable(tableInfo);
            // keep a list of all faulting tables, for additional initialization
            if (tableInfo.topIndexId) {
                faultTableInfo.push(tableInfo);
            }
        }
    }

    // force an initial resize
    awtWindowResized();

    window.onresize = null;

    // initialize faulting tables`
    for (var i=0; i < faultTableInfo.length; i++) {
        // setup top and bottom fault areas, and restore scroll position
        awtSetupScrollFaulting(faultTableInfo[i]);
    }

    // use delay so we don't chase these too aggressively -- otherwise it's too jumpy
    window.onresize = function () { setTimeout("awtWindowResized()", 100); };

    _AWTInPostLoad = false;
    _AWTPendingScroll = false;

    if (!_AWTPendingScrollInfo) {
        awtScrollHidePanel();
    }
    else {
        var tableInfo = _AWTPendingScrollInfo;
        //debug("pending scroll info");
        _AWTPendingScrollInfo = null;
        setTimeout(function() {_awtProcessScrollFault(tableInfo);}, 0);
    }
    //debug("awtPostLoad complete!");
}

function awtResizeScrollTable (tableInfo)
{
    if (!tableInfo.headTable) return;

    // go through each cell in the head and resize
    var headCells = tableInfo.headTable.rows[0].cells;
    var bodyCells = tableInfo.bodyTable.rows[0].cells;

    // debug("Setting header cell widths...");
    var totalWd = 0;
    for (var i = 0; i < bodyCells.length; i++) {
        var headCell = headCells[i];
        var newWd = bodyCells[i].offsetWidth;
        totalWd += newWd;

        // if we're in the last cell and both scrolls are visible, then we need to
        // explicitly set the padding on the header spacer since the body spacer will be
        // set to 0px. (see awtWindowResized for bodyTable spacer calc)
        if (i == bodyCells.length-1 && tableInfo.scrollVisible) {
            newWd = 18; // space for scrollbar
        }

        // Idea: If scroll faulting, we might want to force the columns to be the max of
        // the old size and the new size
        headCell.style.paddingRight = newWd;
        //debug("col: " + i + " new width: " + newWd + " bodyCells[i].style.width: " + bodyCells[i].style.width);
    }
    //debug("--- total = " + totalWd + "  (wrapper=" + tableInfo.wrapperTable.offsetWidth + ")");
}

function awtComputeMaxHt (tableInfo) {
    var contentNeeds = tableInfo.body.scrollHeight;

    // if we need a scrollbar, provide for that as well
    if ((tableInfo.body.scrollWidth - 18) > tableInfo.body.clientWidth) {
        contentNeeds += 18;
    }

    return contentNeeds + (tableInfo.wrapperTable.offsetHeight - tableInfo.body.offsetHeight);
}

function awtComputeMinHt (tableInfo) {
    var maxHt = awtComputeMaxHt(tableInfo);
    return (tableInfo.maximize)
      ? Math.min(maxHt, Math.max(document.documentElement.clientHeight - 20, tableInfo.minHeight))
      : Math.min(maxHt, tableInfo.minHeight + (tableInfo.wrapperTable.offsetHeight - tableInfo.body.offsetHeight));
}

function awtMaxMin (tableInfo)
{
    tableInfo.maximize = (tableInfo.maximize) ? false : true;
    awtSetValue(tableInfo.isMaximizedId, ((tableInfo.maximize) ? "true" : "false")); // push value to server
    awtWindowResized();
    awtClearResize();
    awtWindowResized();

    if (tableInfo.maximize) {
        awsetPageScrollTop(awabsoluteTop(tableInfo.wrapperTable) - 10);
    }

    return false;
}

var _didResize = false;
function awtClearResize() {
    _didResize = false;
}

function awtWindowResized ()
{
    // avoid the recalc ping-pong
    if (_didResize) return;
    _didResize = true;
    setTimeout("awtClearResize()", 500);

    var totalReq = 0;
    var totalUsed = 0;
    var totalExtraWanted = 0;
    var windowExtra = -9999;
    var parentContainer = null;

    // calculate needs
    for (var id in _awtTables) {
        var tableInfo = awtInfoForScrollableTable(id);
        if (tableInfo) {
            var wrapperTable = tableInfo.wrapperTable;
            var maxHeight = awtComputeMaxHt(tableInfo);
            var minHeight = awtComputeMinHt(tableInfo);
            var extraWanted = (maxHeight - minHeight);
            totalUsed += wrapperTable.offsetHeight;
            totalExtraWanted += extraWanted;
            totalReq += minHeight;

            if (!parentContainer) {
                /*
                debug(" --- Parent Nodes ---");
                var n = wrapperTable;
                while (n) {
                    debug(n.tagName + " (" + n.className + "): " + n.scrollHeight + "/" + n.offsetHeight);
                    n = n.parentNode;
                }
                */
                parentContainer = _awFindParentUsingPredicate(wrapperTable, function(n) {
                    return n.className=="flexContainer";
                });
            }
        }
    }

    var obj = document.documentElement;
    /*
    debug("--- Resize!  offsetHeight=" + obj.clientHeight + ", scrollHeight=" + obj.scrollHeight
                + ", totalUsed=" + totalUsed + ", totalReq=" + totalReq);
    */
    windowExtra = obj.clientHeight - obj.scrollHeight;

    if (parentContainer) {
    /*
        debug ("Parent container --  clientHeight=" + parentContainer.clientHeight
            + ", scrollHeight=" + parentContainer.scrollHeight
            + ", origWindowExtra=" + windowExtra);
    */
        windowExtra += parentContainer.clientHeight - parentContainer.scrollHeight;
    }

    windowExtra = windowExtra + totalUsed - totalReq;

    // Take out best guess now (before we force another layout) if we should have a window scroll bar
    // -- if we had extra space to dole out, then we fit in the window
    awtCheckWindowScrollbar(false, (windowExtra <= 0));

    // resize tables, doling out any extra space
    for (var id in _awtTables) {
        var tableInfo = awtInfoForScrollableTable(id);
        if (tableInfo) {
            var wrapperTable = tableInfo.wrapperTable;
            var bodyDiv = tableInfo.body;
            var maxHeight = awtComputeMaxHt(tableInfo);
            var minHeight = awtComputeMinHt(tableInfo);
            var extraWanted = (maxHeight - minHeight);

            var max = ((windowExtra > 0) && (extraWanted > 0))
                            ?  (minHeight + Math.round(extraWanted*1.0/totalExtraWanted * windowExtra))
                            : minHeight;
            var newHeight = Math.min(maxHeight, max) - 0;  // was -4?
            var bodyHt = newHeight - (wrapperTable.offsetHeight - bodyDiv.offsetHeight);

            // fix the table body height
            var hasVertScroll = maxHeight > newHeight; // (div.scrollHeight > div.clientHeight);
            var realScrollWidth = bodyDiv.scrollWidth - ((hasVertScroll) ? 18 : 0);
            //debug ("Horiz:  realScrollWidth=" + realScrollWidth + ", clientWidth=" + bodyDiv.clientWidth);
            var showHorizScroll = (realScrollWidth > bodyDiv.clientWidth)

            // bodyDiv.style.height = (showHorizScroll) ? bodyHt : (bodyHt + 18);
            // debug("bodyHt=" + bodyHt + "  (showHorizScroll:" + showHorizScroll + ")");

            bodyDiv.style.height = bodyHt;
            if (showHorizScroll) {
                bodyDiv.style.overflowX = "auto";
            }
            else {
               bodyDiv.scrollLeft = 0;
               if (tableInfo.head) tableInfo.head.scrollLeft = 0;
               bodyDiv.style.overflowX = "hidden";
            }

            // Adjust the space cell to compensate for the scrollbar (or lack thereof)
            var cells = tableInfo.bodyTable.rows[0].cells;
            var spacerCell = cells[cells.length-1];
            // awxdPrintProperties(tableInfo.bodyTable);
            tableInfo.scrollVisible = (hasVertScroll && showHorizScroll);
            // we adjust the spacer in the body rows here and then depend on
            // awtResizeScrollTable to handle the sizing of the spacer in the header column.
            spacerCell.style.paddingRight = ((hasVertScroll && !showHorizScroll) ? 18 : 0) + "px";

            // Fix min/maximize control
            if(tableInfo.mmControl) {
                tableInfo.mmControl.className = (tableInfo.maximize) ? "awtMMMax" : ((hasVertScroll) ? "awtMMScroll" : "awtMMNone");
            }

            // force a resize to fix heading width
            awtResizeScrollTable(tableInfo);
        }
    }

    // double-check that we got the window scrollbar right
    if (awtCheckWindowScrollbar(true)) {
        // if things change, then run through header/body table sync up
        for (var id in _awtTables) {
            var tableInfo = awtInfoForScrollableTable(id);
            if (tableInfo) {
                awtResizeScrollTable(tableInfo);
            }
        }
    }
}

function awtValue (fieldId)  {
    var e = awgetElementById(fieldId);
    return e ? e.value : null;
}

function awtIntValue (fieldId)  {
    var e = awgetElementById(fieldId);
    return e ? parseInt(e.value) : 0;
}

function awtSetValue (fieldId, value)  {
    if (fieldId) {
        var e = awgetElementById(fieldId);
        if (e) e.value = value;
    }
}

var _AWTRowHeight = 25;

function awtFixSpacerRow (table, row, isTop, fakeCount)
{
    if (fakeCount > 0) {
        var newRow = null;
        if (!row) {
            newRow = row = document.createElement('TR');
            row.dr = "1";  // mark as a "real row"
            var td = document.createElement('TD');
            td.className="rowLines";
            td.innerHTML="&nbsp";
            row.appendChild(td);
            var colspan = table.rows[0].childNodes.length;
            //debug("Spacer [" + ((isTop) ? "top" : "bottom") + "] - colSpan=" + colspan);
            td.colSpan = colspan;

        } else {
            // make sure that table edits didn't move us out of position.
            if ((isTop && (table.tBodies[0].childNodes[1] != row)) || (!isTop && (table.tBodies[0].lastChild != row))) {
                newRow = row;
                table.tBodies[0].removeChild(row);
            }
        }

        if (newRow) {
            if (isTop && table.rows.length > 1) {
                table.tBodies[0].insertBefore(newRow, table.rows[1]);
            } else {
                table.tBodies[0].appendChild(newRow);
            }
        }

        // size the cell to show desired number of fake rows
        var size =  (fakeCount > 0) ? ((fakeCount * _AWTRowHeight) -0) : 0;
        row.firstChild.style.height = size + "px";
    } else {
        if (row) {
            table.tBodies[0].removeChild(row);
        }
        row = null;
    }
    return row;
}

var _AWTPendingScrollInfo;

function awtSetupScrollFaulting (tableInfo)
{
    //debug("topCount: " + tableInfo.topCount +", topIndex: " + tableInfo.topIndexId + " = " + awtIntValue(tableInfo.topIndexId));
    tableInfo.topRow = awtFixSpacerRow (tableInfo.bodyTable, tableInfo.topRow, true, tableInfo.topCount);
    tableInfo.bottomRow = awtFixSpacerRow (tableInfo.bodyTable,tableInfo.bottomRow, false, tableInfo.bottomCount);

    if (tableInfo.continueScroll) {
        //debug("continue scrolling");

        var faultData = tableInfo.continueScroll;
        tableInfo.continueScroll = null;

        _AWTPendingScrollInfo = tableInfo;

/*
        // unjiggle
        // if we moved up or down and we refaulted in the same direction
        // AND some of the original content was visible during refault
        // then pin the old content to refault location
        if (faultData[AWTFaultIndex_ScrollType] == tableInfo.scrollType &&
            (faultData[AWTFaultIndex_ScrollType] == "up" ||
             faultData[AWTFaultIndex_ScrollType] == "down")) {
            awtSetValue(tableInfo.topIndexId, faultData[AWTFaultIndex_TopRow]);
            awtSetValue(tableInfo.topOffsetId, faultData[AWTFaultIndex_TopOffset]);
            tableInfo.bottomIndex = faultData[AWTFaultIndex_BottomRow];
            tableInfo.bottomOffset = faultData[AWTFaultIndex_BottomOffset];
        }
        else {
            return;
        }
*/
        return;
    }

    //----------------------  Fix scroll position ------------------------
    var visRow;
    if (tableInfo.rowIdToForceVisible && (visRow = awgetElementById(tableInfo.rowIdToForceVisible))) {
         // debug ("<b>Force Visible:</b><pre>" + visRow.outerHTML + "</pre>");

         var topRow = _awtFirstRealRow (tableInfo);
         var topPos = (topRow) ? topRow.offsetTop : 0;
         var bottomRow = _awtLastRealRow (tableInfo);
         var bottomPos = (bottomRow) ? bottomRow.offsetTop + bottomRow.offsetHeight : tableInfo.body.offsetHeight;

         // debug ("<b>Row Pos: " + visRow.offsetTop + "</b>, topPos: " + topPos + ", bottomPos: " + bottomPos);

         var scrollHeight = tableInfo.body.offsetHeight;

         // goal is to put top of row 40% from top of view
         var scrollTop = Math.floor(visRow.offsetTop - (scrollHeight * 0.4));
         if (scrollTop < topPos) scrollTop = topPos;
         if (scrollTop + scrollHeight > bottomPos) scrollTop = bottomPos - scrollHeight;

         // debug ("scrollTop: " + scrollTop);

         tableInfo.body.scrollTop = scrollTop;

         return;
    }

    // if the change that caused the table to be refreshed did not set the reposition
    // to true, then skip repositioning -- ie for grouping / hierarchy toggling, the
    // scroll position values are not posted since the change always occurs below the
    // "parent node" or the node that causes the change and the basic HTML div scrolling
    // handles this correctly and does not modify the scroll position of the "parent node"
    if (!tableInfo.repositionScroll) {
        return;
    }
    tableInfo.repositionScroll = false;


    var topIndex = awtIntValue(tableInfo.topIndexId);
    var topOffset = awtIntValue(tableInfo.topOffsetId);
    // debug("topIndex:"+topIndex+ ", topOffset:"+topOffset + ", scrollTop=" + tableInfo.body.scrollTop);

    // optimize for the common case...
    if (topIndex == 0 && topOffset==0 && tableInfo.body.scrollTop == 0) return;

    var topPos = awtPosOfRow(tableInfo, topIndex) - topOffset;
    var bottomPos = null;
    if (tableInfo.bottomIndex) {// && tableInfo.bottomOffset) {
        bottomPos = awtPosOfRow(tableInfo, tableInfo.bottomIndex, true) - tableInfo.bottomOffset;
        //debug("bottomIndex: "+bottomPos+" "+tableInfo.bottomIndex+" "+tableInfo.bottomOffset);
    }
    // if scroll down --
    //    if the top position is a "virtual row", then pin to lower row position
    //    else pin to upper row position
    // if scroll up --
    //    if the lower position is a "virtual row", then pin to upper row position
    //    else pin to lower row position
    // - scroll up / scroll down checks cover base case of incrementally showing additional
    // rows while keeping visible content stable
    // - "virtual row" checks cover case of faulting in an entire page of content -- no
    // visible content to keep stable, just need to make sure the scroll bar doesn't move
    // This is most visible when scrolling to the very bottom or the very top of a table.

    // debug("Orig tableInfo.body.scrollTop = " + tableInfo.body.scrollTop);

    // if possible / necessary check for cases where we have to pin to bottom row
    if (tableInfo.scrollType &&
        (tableInfo.scrollType == "up" || tableInfo.scrollType == "bottom")) {
        //debug("<font style='color:blue;'>bottom pin: " + tableInfo.bottomIndex + " pos: " + bottomPos + " " + tableInfo.body.offsetHeight+"</font>");
        tableInfo.body.scrollTop = bottomPos - tableInfo.body.offsetHeight;
    }
    else {
        //debug("<font style='color:blue;'>top pin: " + awtIntValue(tableInfo.topIndexId) + " pos: " + awtPosOfRow(tableInfo, awtIntValue(tableInfo.topIndexId))+"</font>");

        // default pin to top row
        tableInfo.body.scrollTop = topPos;
    }
    // debug("done setting scroll = " + newPos);
}

function awtCheckWindowScrollbar (checkWindow, shouldScroll)
{
    if (checkWindow) {
        var obj = document.documentElement;
        shouldScroll = (obj.scrollHeight > obj.clientHeight);
    }

    if (shouldScroll && document.documentElement.style.overflowY != "scroll") {
        document.documentElement.style.overflowY = "scroll";
        //debug("<font style='color:blue'>vertical scroll enabled</font>");
        return true;
    }

    if (!shouldScroll && document.documentElement.style.overflowY != "hidden") {
        document.documentElement.style.paddingRight = "0px";
        document.documentElement.style.overflowY = "hidden";
        return true;
    }
    return false;
}

var _awtLastTime = -1;
var _AWTScrollDelay = 250;
    // We want to fire only if:
    //   - the current scroll pos has been maintained for 0.25 sec
    //   - we aren't currently loading (i.e. the scroll is user initiated)
    // If we're in the midst of a a request and user continues to scroll, then
    //   - cache the last scroll to and fire it when we're done with the current one.
    // Note: If we're loading and the scroll bar is moved by the end user, we still end up
    //     "losing" the user initiated scroll ... How to differentiate?
function awtHandleVerticalScroll (tableInfo)
{
    // if we're loading or not scroll faulting, then ignore
    if (_AWTInPostLoad || !tableInfo.scrollFaultActionId) {
        debug("<font style='color:blue'>scroll ignored: "+ _AWTInPostLoad + "</font>");
        return;
    }

    // if we're already processing, hide the panel
    if (_AWTPendingScroll) {
        awtScrollHidePanel();
    }

    // set a timer if we don't already have one running
    if (_awtLastTime == -1) {
        _awtLastTime = (new Date()).getTime();
        setTimeout(function() {awtCheckScrollFault(tableInfo);}, _AWTScrollDelay);
    }

    //debug("awtHandleVerticalScroll: " + tableInfo.topIndexId + " " + tableInfo.body.scrollTop);
    // remember time
    _awtLastTime = (new Date()).getTime();
}

function awtCheckScrollFault (tableInfo)
{
    var div = tableInfo.body;
    var scrollTop = div.scrollTop;

    // check that we haven't moved.  If we have, call back after the total delay time has been reached
    var currentTime = (new Date()).getTime();
    var timeLeft = _AWTScrollDelay - (currentTime - _awtLastTime);
    if (timeLeft > 0) {
        // debug("awtCheckScrollFault() -- timeLeft: " + timeLeft);
        setTimeout(function() {awtCheckScrollFault(tableInfo);}, timeLeft);
        return;
    }

    _awtLastTime = -1;  // clear for next time

    // if we're already processing, cache the last one received
    if (_AWTPendingScroll) {
        //debug("continue scroll");
        tableInfo.continueScroll = _awtCalculateScrollFault (tableInfo);
        awtScrollShowPanel(tableInfo);
        return;
    }
    _awtProcessScrollFault(tableInfo);
}

var AWTFaultIndex_Position = 0; // values: -1 top, 0 no fault, 1 bottom
var AWTFaultIndex_ScrollType = 1; // values: top, up, down, bottom
var AWTFaultIndex_TopRow = 2;
var AWTFaultIndex_TopOffset = 3;
var AWTFaultIndex_BottomRow = 4;
var AWTFaultIndex_BottomOffset = 5;
var AWTFaultIndex_size = 6;

function _awtProcessScrollFault (tableInfo)
{
    // if we're loading or already processing cancel
    if (_AWTInPostLoad || _AWTPendingScroll) {
        // debug("<font color='green'>race fixed</font>");
        return;
    }

    debug("_awtProcessScrollFault() -- processing... scroll pos: " + tableInfo.topIndexId + " " + tableInfo.body.scrollTop);

    var faultData = _awtCalculateScrollFault(tableInfo);

    // always push offset data, so the scroll pos is preserved if the user leaves this page and comes back
    var form = awgetElementById(tableInfo.topIndexId).form;
    awtSetValue(tableInfo.topIndexId, faultData[AWTFaultIndex_TopRow]);
    awtSetValue(tableInfo.topOffsetId, faultData[AWTFaultIndex_TopOffset]);

    if (faultData[AWTFaultIndex_Position] != 0) {
        if (_AWTPendingScroll) {
            debug("<font color='red'>RACE CONDITION</font>");
            return;
        }

        debug("fault type:" + faultData);

        // store the bottom index on the client side
        tableInfo.bottomIndex = faultData[AWTFaultIndex_BottomRow];
        tableInfo.bottomOffset = faultData[AWTFaultIndex_BottomOffset];

        tableInfo.scrollType = faultData[AWTFaultIndex_ScrollType];

        tableInfo.repositionScroll = true;

        _awtScrollFaultAction(tableInfo);
    }
    else {
        awtScrollHidePanel();
    }
}

function _awtScrollFaultAction (tableInfo)
{
    // fire request to server
    _AWTPendingScroll = true;
    var form = awgetElementById(tableInfo.topIndexId).form;
    debug("<font style='color:red'>Calling awsenderClicked: " + form.id + "</font>");
    awtScrollShowPanel(tableInfo);
    awsenderClicked(tableInfo.scrollFaultActionId, form.id, null, null, null, null);
}

function _awtFirstRealRow (tableInfo)
{
    var rows = tableInfo.bodyTable.rows;
    var index = (tableInfo.topRow) ? 2 : 1;  // ship spacer + topRow (if present)
    return (index < rows.length) ? rows[index] : null;
}

function _awtLastRealRow (tableInfo)
{
    var rows = tableInfo.bodyTable.rows;
    var lastIndex = rows.length - 1;
    var index = (tableInfo.bottomRow) ? lastIndex - 1 : lastIndex;  // skip bottom fault
    return (index > 0) ? rows[index] : null;
}

function _awtCalculateScrollFault (tableInfo)
{
    var faultData = new Array(AWTFaultIndex_size);

    var div = tableInfo.body;
    var scrollTop = div.scrollTop;

    var rows = tableInfo.bodyTable.rows;
    var scrollHeight = div.offsetHeight;
    // debug("<pre>    Scroll:  pos=" + scrollTop + ", scrollHeight=" + scrollHeight + "</pre>");
    var i;
    var rowCount=0, topRow=-1, topOffset, bottomRow=-1, bottomOffset;
    var lastRowIndex = rows.length - 1;
    for(i=0; i<= lastRowIndex; i++) {
        var row = rows[i];
        if (row.dr == "1") {  // data row
            rowCount++;

            var topPos = row.offsetTop - scrollTop;
            var bottomPos = topPos + row.offsetHeight;

            //debug("&nbsp;&nbsp;&nbsp;row " + i + " pos=" + topPos + ", bottom=" + bottomPos);
            if (topRow==-1 && ((topPos >=0) || (bottomPos>=0))) {
                topRow = rowCount;
                topOffset = topPos;
            }

            if (bottomRow==-1 && ((bottomPos >= scrollHeight) || (i == lastRowIndex))) {
                bottomRow = rowCount;
                bottomOffset = topPos - scrollHeight;
            }
        }
    }

    //debug ("&nbsp;&nbsp;&nbsp;top index:" + topRow + ", offset=" + topOffset + " (rows.length=" + rows.length + ")");
    //debug ("&nbsp;&nbsp;&nbsp;bottom index:" + bottomRow + ", offset=" + bottomOffset);
    //debug ("&nbsp;&nbsp;&nbsp;topRow=" + tableInfo.topRow + ", bottomRow=" + tableInfo.bottomRow);
    //debug (awxdPrintProperties(tableInfo.bodyTable));

    var topFaultExposed = false;
    var bottomFaultExposed = false;
    var topSpacerCount = (tableInfo.topRow) ? 2 : 1;
    var topCount = tableInfo.topCount;
    if (tableInfo.topRow && (topRow <= 1)) {
        topFaultExposed = true;
        faultData[AWTFaultIndex_ScrollType] = "up";
        if (topRow == 1) {
            // figure out where in the top spacer area we are
            if (topOffset > 0) {
                topRow = 0;
            } else {
                topRow = Math.floor(-1 * topOffset / _AWTRowHeight);
                topOffset += (topRow * _AWTRowHeight);
                topFaultExposed = true;
            }
        }

        if (tableInfo.topRow && bottomRow <= 1) {
            //debug("<font style='color:blue'>top</font>"+topRow);
            faultData[AWTFaultIndex_ScrollType] = "top";
        }
        else {
            bottomRow += topCount - topSpacerCount;
        }
    }
    else if (tableInfo.bottomRow && (topRow == rowCount)) {
        faultData[AWTFaultIndex_ScrollType] = "bottom";

        // figure out where in the bottom spacer area we are
        var fakeRow = Math.floor(-1 * topOffset / _AWTRowHeight);
        topRow = topCount + rowCount - topSpacerCount + fakeRow;
        topOffset += (fakeRow * _AWTRowHeight);
        //debug("<font style='color:blue'>bottom area </font>");

        bottomFaultExposed = true;

        fakeRow = Math.floor(-1 * bottomOffset / _AWTRowHeight);
        bottomRow = topCount + rowCount - topSpacerCount + fakeRow;
        //debug("topCount: " + topCount + " rowCount: " + rowCount + " topSpacerCount: " + topSpacerCount +
        //      " fakeBottomRow: " + fakeRow + " bottomOffset: " + bottomOffset);
        bottomOffset += (fakeRow * _AWTRowHeight);
    } else if (topRow != 0) {
        topRow += (topCount - topSpacerCount);
    }

    if (tableInfo.bottomRow && (bottomRow == rowCount)) {
        bottomFaultExposed = true;
        if (faultData[AWTFaultIndex_ScrollType] != "bottom") {
            faultData[AWTFaultIndex_ScrollType] = "down";

            // use top row for positioning
            bottomRow = null;
            bottomOffset = 0;
        }
    }

    //debug("--- fault exposed: " + topFaultExposed + "/" + bottomFaultExposed +
    //      " scrollType: " + tableInfo.scrollType +
    //      " topIndex: " + topRow + " topOffset: " + topOffset +
    //      " bottomIndex: " + bottomRow + " bottomOffset: " + bottomOffset);

//    awtSetValue(tableInfo.topIndexId, topRow);
//    awtSetValue(tableInfo.topOffsetId, topOffset);

    faultData[AWTFaultIndex_TopRow] = topRow;
    faultData[AWTFaultIndex_TopOffset] = topOffset;

    // store the bottom index on the client side
//    tableInfo.bottomIndex = bottomRow;
//    tableInfo.bottomOffset = bottomOffset;
    faultData[AWTFaultIndex_BottomRow] = bottomRow;
    faultData[AWTFaultIndex_BottomOffset] = bottomOffset;

    //debug("&nbsp;&nbsp;&nbsp;Scroll - top row:" + topRow + ", offset=" + topOffset);
    //debug("&nbsp;&nbsp;&nbsp;       - bot row:" + bottomRow + ", offset=" + bottomOffset);
    //debug("&nbsp;&nbsp;&nbsp;topFaultExposed=" + topFaultExposed + ", bottomFaultExposed=" + bottomFaultExposed);
    faultData[AWTFaultIndex_Position] = topFaultExposed ? -1 : (bottomFaultExposed ? 1 : 0);
    return faultData;
}

function awtPosOfRow (tableInfo, logicalRowNum, bottom)
{
    var topCount = tableInfo.topCount;
    if (logicalRowNum < topCount) {
        // in the top area
        return (logicalRowNum * _AWTRowHeight);
    } else {
        // how many real rows do we need to count?
        logicalRowNum = logicalRowNum - topCount + (tableInfo.topRow ? 2 : 1);

        // map the logicalRowNum to a physical row index (skipping "non-real rows").
        var rows = tableInfo.bodyTable.rows;
        var i;
        for(i=0; i<rows.length; i++) {
            var row = rows[i];
            if (row.dr == "1") {  // data row
                logicalRowNum--;
            }
            if (logicalRowNum <= 0) {
                return row.offsetTop;
            }
        }

        // we ran out of rows so either we're running bottom calc and we're at an edge
        if (bottom) {
            var row = rows[rows.length-1];
            return row.offsetTop + row.offsetHeight;
        }
        else {
            // or we're running top calc and we're in the footer
            return rows[rows.length - 1].offsetTop + ((logicalRowNum -1) * _AWTRowHeight);
        }
    }
}

function awtScrollShowPanel (tableInfo)
{
    awShowPanel("awtFaultingPanel", tableInfo.body);
}

function awtScrollHidePanel ()
{
    awHidePanel("awtFaultingPanel");
}

        //
        // row utilities
        //

var _AWTDragHoverStyle = "tableCellDragHover ";
var _AWTDragStyle  = "tableRowDrag ";
var _AWTHoverStyle = "tableRowHover ";
var _AWTSelectStyle = "tableRowSelected ";

// sets the styles on all the TD's in a row
function awtSetClassOnRowTDs (oRow, style)
{
    childNodes = oRow.childNodes;

    for (var i=0; i < childNodes.length; i++) {
        if (childNodes[i].nodeType == ELEMENT_NODE && childNodes[i].className.indexOf(style) == -1) {
            childNodes[i].className = style + childNodes[i].className;
        }
    }
}

function awtRemoveClassOnRowTDs (oRow, style)
{
    childNodes = oRow.childNodes;

    for (var i=0; i < childNodes.length; i++) {
        if (childNodes[i].nodeType == ELEMENT_NODE) {
            awtRemoveClassPrefix(childNodes[i], style);
        }
    }
}

function _awtAddRowStyle (style, row)
{
    do {
        if (row.className && row.className.indexOf(style) == -1) {
            row.className = style + row.className;
        }
        row = row.nextSibling;
    }
    while (row && !_awtIsPrimaryRow(row))
}

function _awtRemoveRowStyle (style, row)
{
    do {
        awtRemoveClassPrefix(row, style);
        row = row.nextSibling;
    }
    while (row && !_awtIsPrimaryRow(row))
}

function awtRemoveClassPrefix(n, className) {
    // remove hover style, if present
    var curName = n.className;
    if (curName && curName.substring(0, className.length) == className)  {
        n.className = curName.substring(className.length, curName.length);
    }
}

function _awtIsPrimaryRow (row)
{
    // cached value
    if (row._AWTIsPrimaryRow == true || row._AWTIsPrimaryRow == false) {
        return row._AWTIsPrimaryRow;
    }

    if ((row.cells && row.cells.length > 0 && row.cells[0].className &&
         row.cells[0].className.indexOf("firstRow") == -1) ||
         awtRowSelectElement(row)) {
        row._AWTIsPrimaryRow = true;
    }
    else {
        // test for real first row
        row._AWTIsPrimaryRow = true;

        var prevRow = row.previousSibling;
        if (!((prevRow && prevRow.cells && prevRow.cells.length > 0 && prevRow.cells[0].nodeName == "TH") ||
            (prevRow && prevRow.className && prevRow.className.indexOf("AWTColAlignRow") != -1))) {
            row._AWTIsPrimaryRow = false;
        }
    }

    return row._AWTIsPrimaryRow;
}

function awtRowForChild (target)
{
    var row = _awFindParentUsingPredicate(target, function(n) {
        return n.nodeName=="TR" && n.parentNode.parentNode.className=="tableBody";
    });

    while (row && !_awtIsPrimaryRow(row) && row.previousSibling) {
        row = row.previousSibling;
    }

    return row;
}

function awtWrapperTableForRow (row)
{
    // find the enclosing wrapper table
    return _awFindParentUsingPredicate(row, function(n) {
        return n.nodeName=="TABLE" && n.className.indexOf("awtWrapperTable") != -1;
    });
}

function awtTableInfoForRow (row)
{
    var wrapperTable = awtWrapperTableForRow(row);
    return awtInfoForTable(wrapperTable.id);
}

        //
        // Event Handlers
        //

function awtMouseOverEventHandler (evt)
{
    var evt = (evt) ? evt : event;
    var target = (evt.target) ? evt.target: evt.srcElement;
    // debug(evt.type + " - " + target.tagName);

    var parentRow = awtRowForChild(target);

    if (parentRow) {
        // debug(" - in: " + parentRow.id);
        if (!awtIsRowSelected(parentRow)) {
            if (!AWDragDiv) {
                _awtAddRowStyle(_AWTHoverStyle, parentRow);
            }
        }
    }
}

function awtMouseOutEventHandler (evt)
{
    var evt = (evt) ? evt : event;
    var target = (evt.target) ? evt.target: evt.srcElement;
    // debug(evt.type + " - " + target.tagName);

    var parentRow = awtRowForChild(target);

    if (parentRow) {
       //debug(" - out: " + parentRow.id);
        if (AWDragDiv) {
            awtRemoveClassOnRowTDs(parentRow, _AWTDragHoverStyle);
        }
        else {
            _awtRemoveRowStyle(_AWTHoverStyle, parentRow);
        }
    }
}

function awtRowClickedEventHandler (evt)
{
    var evt = (evt) ? evt : event;
    var target = (evt.target) ? evt.target: evt.srcElement;
    var handled = false;

    //debug("Row clicked: " + evt.type + " - " + target.tagName + " " + target.className);
    var row = awtRowForChild(target);
    if (row && _awtMouseDown) {
        var tableInfo = awtTableInfoForRow(row);
        var targetTable = tableInfo.bodyTable;
        var isSelected = awtIsRowSelected(row);

        if (tableInfo.disableRowSelection || _awtSelectStart || target.tagName == "TEXTAREA") {
            _awtSelectStart = false;
            handled = false;
        }
        // start mouse and end click on an input field
        // follows behavior of IE -- if mouse down outside of checkbox and mouse up
        // inside, then checkbox does not get selected
        // also keeps disables row selection for other input fields (textfield, etc)
        else if (awtIsSelectElement(target) ||
                 (_awtMouseDownTarget && _awtMouseDownTarget.nodeName == "INPUT" &&
                  target.tagName == "INPUT")) {
            var force = _awtMouseDownTarget ? awtIsSelectElement(_awtMouseDownTarget) : true;
            // Note: using isSelected here since the checkbox should have already changed
            // states by the time mouse click event bubbles to the table
            awtSetRowSelect(row, isSelected, force, tableInfo);
            awtUpdateSelectAll(tableInfo);
            // still need to allow default event handling so "check" appears
            handled = false;
        }
        else if (target.tagName == "A") {
            // for Sourcing / default <a href=""> action.  The standard AWHyperLink
            // registers an onclick handler and does not reach this event handler.
            handled = false;
        }
        else {
            var index = awFindRowIndex(row.id, targetTable);
            var stateChanged = false;
            if (awtIsMultiSelect(row) && evt.shiftKey) {
                awtClearSelection(tableInfo);
                if (tableInfo.lastSelectionIndex != -1) {
                    awtSetSelection(targetTable, tableInfo.lastSelectionIndex, index, tableInfo);
                    stateChanged = true;
                }
                else {
                    tableInfo.lastSelectionIndex = index;
                }
            }
            else {
                if (!awtIsMultiSelect(row) || (evt.ctrlKey)) {
                    awtClearSelection(tableInfo);
                }

                awtSetRowSelect(row, !isSelected, false, tableInfo);
                awtUpdateSelectAll(tableInfo);
                tableInfo.lastSelectionIndex = index;

                stateChanged = true;
            }
            // check to see if there's an event defined on the select element and if
            // there is, fire it
            if (stateChanged) {
                var selectElement = awtRowSelectElement(row);
                if (selectElement && selectElement.onclick) {
                    selectElement.onclick(evt);
                }
            }

            handled = true;
        }
    }
    awtClearMouseDown();

    // prevent propagation
    evt.cancelBubble = handled;
    return !handled;
}

function awtClearSelection (tableInfo) {
    var rows = tableInfo.bodyTable.rows;
    for (var i=0; i < rows.length; i++) {
        awtSetRowSelect(rows[i], false, false, tableInfo);
    }
    awtUpdateSelectAll(tableInfo);
}

function awtSetSelection (table, start, end, tableInfo)
{
    var rows = table.rows;
    if (start > end) {var tmp = end; end = start; start = tmp;}
    for (var i=start; i <= end; i++) {
        if (_awtIsPrimaryRow(rows[i])) {
            awtSetRowSelect(rows[i], true, false, tableInfo);
        }
    }
    awtUpdateSelectAll(tableInfo);
}

function awtUpdateRowSelectColor (row, yn)
{
    // sync row color
    if (yn) {
        _awtAddRowStyle(_AWTSelectStyle,row);
    } else {
        // remove style name
        _awtRemoveRowStyle(_AWTSelectStyle, row);
        _awtRemoveRowStyle(_AWTHoverStyle, row);
    }
}

function awtUpdateSelectAll (tableInfo)
{
    if (tableInfo.selectAll && !tableInfo.selectAll.checked) {

        var rows = tableInfo.bodyTable.rows;

        var allSelected = true;
        var emptyTable = true;
        for (var i=0; i < rows.length; i++) {
            if (rows[i].cells) {
                var n = awtRowSelectElement(rows[i]);
                // n is null for the padding row and "empty table" message row
                if (n) {
                    emptyTable = false;
                    if (!n.checked) {
                        allSelected = false;
                        break;
                    }
                }
            }
        }

        if (!emptyTable && allSelected) {
            tableInfo.selectAll.checked = true;
        }
    }
}

function awtSetRowSelect (row, yn, force, tableInfo)
{
    var n = awtRowSelectElement(row);
    if (n && n.disabled) {
        if (force) {
            n.checked = yn;
            awtUpdateRowSelectColor(row, yn);
        }
        return;
    }

    var isMultiSelect = (n && n.tagName == "INPUT" && n.type == "checkbox");
    if (!isMultiSelect) {
        if (!tableInfo) {
            tableInfo = awtTableInfoForRow(row);
        }

        if (tableInfo) {
            if (tableInfo.lastSelectedRow) {
                tableInfo.lastSelectedRow.checked = false;
                awtUpdateRowSelectColor(tableInfo.lastSelectedRow, false);
            }
            tableInfo.lastSelectedRow = row;
        }
    }
    else {
        if (!yn && tableInfo.selectAll && tableInfo.selectAll.checked) {
            // make sure we unselect the select all icon since we're unselecting one of
            // the multi-select rows
            tableInfo.selectAll.checked = false;
        }
    }

    if (n && (n.checked != yn || force)) {
        // change checkbox
        n.checked = yn;
        awtUpdateRowSelectColor(row, yn);
    }
}

function awtIsSelectElement (node)
{
    return  node.tagName=="INPUT" && (node.type=="checkbox" || node.type=="radio");
}

var AWNullValue = new Object();

// constant to simplify AES 4.2 integration
var AWTSelectColumnIndex = 0;
function awtRowSelectElement (row)
{
    // return the checkbox, if we can find it

    if (row._AWTSelectElement) {
        // return cached value if available
        return (row._AWTSelectElement != AWNullValue) ? row._AWTSelectElement : null;
    }
    var select = null;
    var selectCell = row.cells ? row.cells[AWTSelectColumnIndex] : null;
    if (selectCell && selectCell.childNodes) {
        select = _awFindChildUsingPredicate(selectCell, awtIsSelectElement);
    }

    row._AWTSelectElement = (select != null) ? select : AWNullValue;
    return select;
}

function awtIsRowSelected (row)
{
    var n = awtRowSelectElement(row);
    return n ? n.checked : false;
}

function awtIsMultiSelect (row)
{
    var child = awtRowSelectElement(row);
    if (child) {
        if (child.tagName == "INPUT" && child.type == "checkbox") {
            return true;
        }
    }
    return false;
}

var AWTableDragPrefix = "awtDrg_";
var AWTableDropPrefix = "awtDrp_";

function awtMouseDownEvtHandler (evt)
{
    // NOTE: debug in mouse up/down causes with mouse click event to not fire
    //debug("mouse down");

    var handled = false;
    var evt = (evt) ? evt : event;
    var target = (evt.target) ? evt.target: evt.srcElement;

    // set up for possible on click
    _awtMouseDown = true;
    _awtMouseDownTarget = target;

    // track for text selection
    _awtSelectStart = false;

    // if the target is an input field or a textarea and does not have the force drag
    // enabled, then assume drag is disabled
    if ((target.tagName == "INPUT" || target.tagName == "TEXTAREA") &&
        target.className.indexOf(AWTForceDragStyle) == -1) {
        evt.cancelBubble = handled;
        return !handled;
    }

    if (awIsNetscape()) {
        return !handled;
    }

    var row = awtRowForChild(target);

    //debug(row.className);
    if (row) {
        var tableInfo = awtTableInfoForRow(row);

        if (tableInfo.isDraggable &&
            row.className && row.className.indexOf(AWTableDragPrefix) != -1) {

            //debug("selectable TD " + target.id + " " + row.id);
            var rowId = row.id;
            if (!awisNullOrUndefined(rowId) && rowId != "") {

                awdCreateDragDiv(evt,row,AWTableDragPrefix);

                AWDragDiv.style.border="1px #333333 solid";
                AWDragDiv.style.width = row.scrollWidth;
                var tmpTable = row.parentNode.parentNode.cloneNode(false);
                var tmpTBody = document.createElement("tbody");
                tmpTBody.appendChild(row.cloneNode(true));
                tmpTable.appendChild(tmpTBody);
                AWDragDiv.appendChild(tmpTable);

                // change table styles based on droppable status
                AWDragDiv.droppable = function (isDroppable) {
                    if (isDroppable) {
                        AWDragDiv.style.border="1px #333333 solid";
                    }
                    else {
                        AWDragDiv.style.border="1px red solid";
                    }
                }

                AWDragDiv.nextSrcId=
                    target.parentNode.nextSibling ? target.parentNode.nextSibling.id : null;

                handled = true;
            }
        }
    }

    evt.cancelBubble = handled;
    return !handled;
}

function awtMouseUpEvtHandler (evt)
{
    // NOTE: debug in mouse up/down causes with mouse click event to not fire
    //debug("mouse up");

    var handled = false;

    var evt = (evt) ? evt : event;
    var target = (evt.target) ? evt.target: evt.srcElement;
    var row;

    if (AWDragDiv && (row = awtRowForChild(target))) {

        // hide mouse drag icon
        //awtHideMouseDragIcon();
        var targetRowId = row.id;

        //debug("mouse up: " + AWDragDiv.srcId + " " + AWDragDiv.nextSrcId + " " + targetRowId);

        // if we're on the same row, then let onclick handle the event
        if (AWDragDiv.nextSrcId == targetRowId) {
            awtRemoveClassPrefix(awgetElementById(AWDragDiv.srcId), _AWTHoverStyle);
            awdReleaseDragDiv();
        }
        else if (AWDragDiv.srcId != targetRowId && AWDragDiv.nextSrcId != targetRowId) {
            //debug("handling data table drop: " + row.id);

            awtRemoveClassOnRowTDs(row, _AWTDragHoverStyle);

            handled = awdHandleDropAction(row,evt,AWTableDropPrefix);

            if (handled) {
                awtClearMouseDown();
            }
        }
    }

    evt.cancelBubble = handled;
    return !handled;
}

function awtClearMouseDown ()
{
    _awtMouseDown = false;
    _awtMouseDownTarget = null;
}

function awtMouseMoveEvtHandler (evt)
{
    //debug("mouse move " + AWDragDiv);
    var evt = (evt) ? evt : event;
    var target = (evt.target) ? evt.target: evt.srcElement;

    var handled = false;

    if (AWDragDiv && awdDragDivMoved(evt)) {
        //debug("dragging");
        var dropContainer = awdFindDropContainer(target,AWTableDropPrefix);

        if (dropContainer && awdIsDropContainerValid(dropContainer,AWTableDropPrefix)) {
            //awtShowMouseDragIcon();
            //awtUpdateMouseDragIcon(evt);

            // set droppable since this may have been set to false by the "superclass"
            // ie, document level awMouseMoveEvtHandler
            AWDragDiv.droppable(true);

            // update position of drag div
            AWDragDiv.style.left=(evt.clientX + document.body.scrollLeft)+ "px";
            AWDragDiv.style.top=(evt.clientY + awgetPageScrollTop())+ "px";

            var parentRow = awtRowForChild(target);
            if (parentRow) {
                // "insert" indicator in mouse over row (ie apply style)
                if (!awtIsRowSelected(parentRow)) {
                    var rowId = parentRow.id;
                    // skip selected row and the next row since we're doing insert before)
                    if (rowId != AWDragDiv.srcId && rowId != AWDragDiv.nextSrcId) {
                        awtSetClassOnRowTDs(parentRow, _AWTDragHoverStyle);
                    }
                }
            }

            handled = true;
        }
    }

    evt.cancelBubble = handled;
    return !handled;
}

var _awtSelectStart = false;

function awtSelectStartEvtHandler (evt)
{
    //debug("awtSelectStartEvtHandler");
    // used to avoid selecting background text as we're dragging
    // ### IE only.
    // for Netscape,  use the style
    //  .styleName {
    //      -moz-user-select: none;
    //  }
    var evt = (evt) ? evt : event;
    var target = (evt.target) ? evt.target: evt.srcElement;

    // if drag then disable selection
    // if shift/ctrl down, then assume we're doing row selection
    var handled = (AWDragDiv || evt.shiftKey || evt.ctrlKey) ? true : false;

    // track text selection
    _awtSelectStart = !handled;

    evt.cancelBubble = handled;
    return !handled;
}

var _awtMouseDragIndicator;

function awtShowMouseDragIcon ()
{
    if (!_awtMouseDragIndicator) {
        _awtMouseDragIndicator = awgetElementById("AWDragImage");
    }
    //debug("drag icon: " + dragType + " " + _mouseIcn + " " + AWDragImage);
    _awtMouseDragIndicator.style.visibility = "visible";
}

function awtUpdateMouseDragIcon (evt)
{
    var x = evt.clientX + document.body.scrollLeft;
    var y = evt.clientY + awgetPageScrollTop();

    _awtMouseDragIndicator.style.left = x + 10;
    _awtMouseDragIndicator.style.top = y + 10;
}


function awtHideMouseDragIcon ()
{
    if (_awtMouseDragIndicator) {
        _awtMouseDragIndicator.style.top = 0;
        _awtMouseDragIndicator.style.left = 0;
        _awtMouseDragIndicator.style.visibility = "hidden";
    }
}
