Follow Slashdot stories on Twitter

 



Forgot your password?
typodupeerror
×
Programming

Journal codefool's Journal: Multi-Column List Box Emulation in Javascript

<!--
    Skeletal framework for implementing a multiple column list
    box with 'standard' windows selection behavior using
    javascript for dhtml.

    Comments are sadly lacking - more to come.

    Use as you see fit, check back here for updates as I have time.
-->
//
//  manage a DHTML scrollable list
//
function scrollList_Column( owner, colId, index )
{
    this._owner = owner;                                // the owning scrolllist
    this._index = index;
    this._id    = colId;                                // our 'unique' id
}

scrollList_Column.prototype.getId =
    function()
    {
        return this._id;
    };

scrollList_Column.prototype.setWidth    = function( sWidth ) { this._width     = sWidth; return this; }
scrollList_Column.prototype.setStyle    = function( sStyle ) { this._style     = sStyle; return this; }
scrollList_Column.prototype.setClass    = function( sClass ) { this._class     = sClass; return this; }
scrollList_Column.prototype.setPrec     = function( sPrec  ) { this._prec      = sPrec;  return this; }
scrollList_Column.prototype.setAlign    = function( sAlign ) { this._textAlign = sAlign; return this; }
scrollList_Column.prototype.setXmlTag   = function( sTag   ) { this._xmlTag    = sTag;   return this; }
scrollList_Column.prototype.setHeading  = function( sHead  ) { this._heading   = sHead;  return this; }

scrollList_Column.prototype.getHeading =
    function()
    {
        var sRet = "[Unnamed Column]";
        if( this.hasOwnProperty( "_heading" ) )
            sRet = this._heading;
        else if( this.hasOwnProperty( "_id" ) )
            sRet = this._id;
        return sRet;
    };

//
//  create a <TD> element with this column attributes and properties
//
scrollList_Column.prototype.createElement =
    function( sData )
    {
        var oColumn = document.createElement( "TD" );
        oColumn.id = this._id;
        if( this.hasOwnProperty( "_width" ) )       oColumn.style.width     = this._width;
        if( this.hasOwnProperty( "_class" ) )       oColumn.className       = this._class;
        if( this.hasOwnProperty( "_style" ) )       oColumn.styleString     = this._style;
        if( this.hasOwnProperty( "_textAlign" ) )   oColumn.style.textAlign = this._textAlign;
        if( null != sData )
        {
            var oTextNode = document.createTextNode( sData );
            oColumn.appendChild( oTextNode );
        }
        return oColumn;
    };

scrollList_Column.prototype.format =
    function( sVal )
    {
        if( this.hasOwnProperty( "_prec" ) )
            sVal = Number( sVal ).toFixed( this._prec );
        return sVal;
    };

////////////////////////////////////////////////////////////////////////////////
//
//  S T A T I C   F U N C T I O N S
//
////////////////////////////////////////////////////////////////////////////////
function scrollList_onRowClick()
{
    var e = window.event.srcElement;
    while( null != e && e.tagName != "TR" )
        e = e.parentNode;

    if( null != e && null != e._scrollList )
    {
        e._scrollList.hilite( e );
    }
}

function scrollList_onRowDblClick()
{
    //
    //  the only reason to go through this is if there is someone to send
    //  the info to, since we do nothing with double clicks.
    //
    var e = window.event.srcElement;
    while( null != e && e.tagName != "TR" )
        e = e.parentNode;

    if( null != e && null != e._scrollList && null != e._scrollList._extRowDblClickHandler )
    {
        e._scrollList._extRowDblClickHandler( e.rowIndex );
    }
}

//
//  onSelectStart
//
//  This function is used to negate select start events on our owning table. This is so mouse
//  navigation works without having the browswer block-select text for us.
//
//  This function answers false. That's all it does. Just walk away.
//
function scrollList_onSelectStart()
{
    return false;
}

function scrollList_onKeyDown()
{
    var bRet = false;
    var e    = window.event.srcElement;

    while( e != null && e.tagName != "TABLE" )
        e = e.parentElement;

    if( null != e && null != e._scrollList && null != e._scrollList._curRow )
    {
        var list = e._scrollList;
        var ek   = event.keyCode;
        if( !event.shiftKey )
        {
            list._hiliteAnchor = null;
        }

        bRet = true;
        switch( ek )
        {
            case 0x26:  // cursor up    (VK_UP)
                if( null != list._curRow.previousSibling )
                    list.hilite( list._curRow.previousSibling );
                break;

            case 0x28:  // cursor down  (VK_DOWN)
                if( null != list._curRow.nextSibling )
                    list.hilite( list._curRow.nextSibling );
                break;

            case 0x20:  // space        (VK_SPACE)
                //  toggle the entry's selection status.
                {
                var bSelected          = !list._curRow._selected;
                list._curRow._selected = bSelected;
                list._curRow.className = list.getRowStyle( list._curRow );
                list._rowsSelected     = bSelected;
                }
                break;

            case 0x2d:    // insert       (VK_INSERT)
                if( null != list._extInsertRowHandler )
                    list._extInsertRowHandler();
                break;

            case 0x2e:  // delete       (VK_DELETE)
                list.removeSelectedRows();
                break;

            case 0x21:  // page up      (VK_PRIOR)
            case 0x22:  // page down    (VK_NEXT)
                if( list._viewDepth != -1 )
                {
                    var scrollValue = list._viewDepth - 1;
                    if( ek == 0x21 )
                        scrollValue = -scrollValue;

                    var rowIdx = list._curRow.rowIndex + scrollValue;

                    if( rowIdx >= list.rowCount() )
                        rowIdx = list.rowCount() - 1;

                    else if( rowIdx < 0 )
                        rowIdx = 0;

                    list.hilite( list.getRow( rowIdx ) );
                }
                else
                    bRet = false;
                break;

            case 0x24:  // home         (VK_HOME)
                list.hilite( list.getRow( 0 ) );
                break;

            case 0x23:  // end          (VK_END)
                list.hilite( list.getRow( list.rowCount() - 1 ) );
                break;

            default:
                bRet = false;
                break;
        }
        //
        //  if we usurped our table's onKeyDown proc, then call it here
        //
        if( null != list._tableOnKeyDown )
            bRet = list._tableOnKeyDown();
    }

    return bRet;
}

////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
//
//  class scrollList
//
//  The actual row data is stored in the HTML document itself - the scroll
//  list only contains information necessary to manage that piece of the
//  document which must be totally eveloped by a single element, such as
//  a <div> or <tbody>.
//
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
function scrollList( elemId )
{
    this._elem                  = elemId;           // the DHTML element where we live
    this._styleClass            = new Object();     // container for css style classes
    this._cols                  = new Object();     // container for column descriptions
    this._colCnt                = 0;
    this._curRow                = null;             // there is no current row
    this._hiliteAnchor          = null;
    this._rowsSelected          = null;
    this._viewTop               = 0;                // first visible row in the view
    this._viewDepth             = -1;               // show everything
    this._emptyMessage          = "[List is empty]";
    this._extRemoveRowHandler   = null;
    this._extEndRemoveHandler   = null;
    this._extRowSelectHandler   = null;
    this._extRowDblClickHandler = null;

    //
    //  locate our containing <TABLE> element and setup some magic
    //
    var e = elemId;
    while( null != e && e.tagName != "TABLE" )
        e = e.parentElement;
    if( null != e )
    {
        this._table          = e;                           // save our table reference
        this._tableOnKeyDown = e.onkeydown;                 // save our table's onKeyDown handler
        e._scrollList        = this;                        // point our table to us
        e.onkeydown          = scrollList_onKeyDown;      // substitute our own handler
          e.onselectstart      = scrollList_onSelectStart;  // nullify mouse select events
    }
}
scrollList.prototype.DEFAULT_CLASS_STYLE_ID = "__default__";
scrollList.prototype.EMPTY_ROW_ID           = "__emptyrow__";

//
//  Associate an external handler for the insert row event.
//
//  This handler is called whenever a the user presses the 'insert' key on the scoll list.
//  The list itself does nothing, but will evoke this handler if it is defined. The handler
//  is expected to manipulate the to physically insert and populate the row where desired.
//
//  The handler accepts no arguments, and answers nothing.
//
scrollList.prototype.setInsertRowHandler =
    function( h )
    {
        this._extInsertRowHandler  = h;
    };

//
//  Associate an external handler for the end remove event.
//
//  This handler is called whenever a remove operation is completed (and something was
//  actually removed). This gives the owning document an opportunity to adjust from
//  the rows removed.
//
//  The handler accepts no arguments, and answers nothing.
//
scrollList.prototype.setEndRemoveHandler =
    function( h )
    {
        this._extEndRemoveHandler  = h;
    };

//
//  associate an external handler for the remove row event.
//
//  The handler accepts a single argument which is the row index of the row being
//  removed. The handler can get additional information about the targetted row
//  by supplying this index to scrollList.getRow().
//
//  The handler MUST answer true if the row may be removed, false otherwise.
//
scrollList.prototype.setRemoveRowHandler =
    function( h )
    {
        this._extRemoveRowHandler = h;
    };

//
//  Associate an external handler for when a row is selected. This handler is
//  called whenever a new row is selected either by the keyboard or mouse.
//
//  The handler accepts a single argument which is the row index of the row
//  selected. The handler can get additional information about the target row
//  by supplying this index to scrollList.getRow();
//
scrollList.prototype.setRowSelectHandler =
    function( h )
    {
        this._extRowSelectHandler = h;
    };

//
//  associate an external handler for when a user double-clicks a row.
//
//  The handler accepts a single argument which is the row index of the row being
//  double-clicked. The handler can get additional information about the target row
//  by supplying this index to scrollList.getRow();
//
scrollList.prototype.setRowDblClickHandler =
    function( h )
    {
        this._extRowDblClickHandler = h;
    };

scrollList.prototype.setViewDepth =
    function( iSize )
    {
        iSize += 0;
        if( 0 < iSize )
            ; // throw - BAD PARAMETER
        this._viewDepth = iSize;
        return this;
    };

scrollList.prototype.getViewDepth =
    function()
    {
        return this._viewDepth;
    };

//
//  for scrollable lists, scroll the named index into view. Optionaly specify
//  whether it should be scrolled to the center of the view. Otherwise, the
//  row is scrolled to either the first or last item, depending on whether
//  the target row is above or below the view.
//
scrollList.prototype.scrollIntoView =
    function( row, bCenter )
    {
        bCenter = !!bCenter;        // assure boolean

        row = this.getRow( row );

        if( null != row  )
        {
            var trueViewDepth = this._viewDepth - 1;
            var viewBottom    = this._viewTop + trueViewDepth;
            var newTopRow     = -1;
            if( bCenter )
            {
                var centerOffset = trueViewDepth / 2;

                if( row.rowIndex >= centerOffset )
                    newTopRow = row.rowIndex - centerOffset;
                //  else, the row can't be centered cuz there's not enough rows to
                //  fill the view.
            }
            // if the row is below the bottom of the view
            else if( viewBottom < row.rowIndex )
            {
                newTopRow = row.rowIndex - trueViewDepth;
            }
            // if the row is above the top of the view
            else if( row.rowIndex < this._viewTop )
            {
                newTopRow = row.rowIndex;
            }
            //  else the row is already visible

            if( newTopRow != -1 )
            {
                this._viewTop = newTopRow;
                this.paint();
            }
        }
        //else
        //  throw - index out of range
    };

scrollList.prototype.hilite =
    function( e, force )
    {
        force = !!force;
        //
        //  determine if its the row element, or a child TD element that originated
        //  the event
        //
        while( null != e && "TR" != e.tagName.toUpperCase() )
            e = e.parentElement;

        if( null != e && ( e != this._curRow || force ) )
        {
            //
            //  handle keyboard augments
            //
            if( null != event )
            {
                if( !event.shiftKey )
                {
                    this._hiliteAnchor = null;
                    if( !event.ctrlKey )
                        this.unselectAll();
                }
                else if( event.shiftKey && null == this._hiliteAnchor )
                    this._hiliteAnchor = this._curRow;
            }

            //
            //  first, un-hilite the currently hilited row, if there is one
            //
            var priorRow = this._curRow;
            this._curRow = e;
            if( null != priorRow )
                priorRow.className = this.getRowStyle( priorRow );

            //
            //  see if we have an anchor set by a prev shift operation
            //
            if( null != this._hiliteAnchor )
            {
                //
                //  we need to  hilite every node from hiliteAnchor to the current node
                //
                this.unselectAll();

                var lo = ( this._hiliteAnchor.rowIndex < this._curRow.rowIndex )
                       ? this._hiliteAnchor
                       : this._curRow;

                var hi = ( lo == this._hiliteAnchor )
                       ? this._curRow
                       : this._hiliteAnchor;

                for( e = lo; e != hi.nextSibling; e = e.nextSibling )
                {
                    e._selected        = true;
                    e.className        = this.getRowStyle( e );
                    this._rowsSelected = true;
                }
            }
            this._curRow.className = this.getRowStyle( this._curRow );

            //  now make sure the current row is visible
            this.scrollIntoView( this._curRow, false );

            if( null != this._extRowSelectHandler )
                this._extRowSelectHandler( this._curRow.rowIndex );
        }
    };

scrollList.prototype.unselectAll =
    function()
    {
        for( var e = this._elem.firstChild; e != null; e = e.nextSibling )
        {
            e._selected = false;
            e.className = this.getRowStyle( e );
        }
        this._rowsSelected = false;
    };

scrollList.prototype.rowCount =
    function()
    {
        var iRet = 0;
        if( !this.isEmpty() )
            iRet = this._elem.rows.length;
        return iRet;
    };

scrollList.prototype.columnCount =
    function()
    {
        return this._colCnt;
    };

//
//  remove selected rows
//
scrollList.prototype.removeSelectedRows =
    function()
    {
        //
        //  if there are no selected rows, then the current row
        //  is dead meat ...
        //
        if( !this._rowsSelected )
        {
            if( null == this._curRow )
                this._curRow = this._elem.rows[ 0 ];
            this._curRow._selected = true;
        }

        var e                 = this._elem.rows[ this._elem.rows.length - 1 ];
        var bSomethingRemoved = false;
        while( e != null )
        {
            var ePrev = e.previousSibling;
            if( e._selected )
            {
                var bRemove = true;
                //
                //  see if our owner wants to be notified that
                //  we're about to remove a row...
                //
                if( null != this._extRemoveRowHandler )
                    bRemove = this._extRemoveRowHandler( e.rowIndex );

                if( bRemove )
                {
                    this.removeRow( e );
                    bSomethingRemoved = true;
                }
            }
            e = ePrev;
        }

        //
        //  see if our owner wants to know that we're done removing stuff
        //
        if( bSomethingRemoved && null != this._extEndRemoveHandler )
            this._extEndRemoveHandler();

        this.paint();
    };

scrollList.prototype.removeRow =
    function( oRow )
    {
        if( oRow === this._curRow )
        {
            this._curRow = ( this._curRow.nextSibling != null )
                         ? this._curRow.nextSibling
                         : this._curRow.previousSibling;
        }
        this._elem.removeChild( oRow );
        this.createEmptyRow();
    };

//
//  addColumn
//
//  Creates a new column object with the given id (provided it doesn't already exist),
//  and sets its parent and index.
//
//  Answer the column created, or null if something goes wrong.
//
scrollList.prototype.addColumn =
    function( colId )
    {
        var newCol = this._cols[ colId ];
        if( null == newCol )
        {
            newCol = new scrollList_Column( this, colId, this._colCnt++ );
            this._cols[ colId ] = newCol;
        }
        return newCol;
    };

scrollList.prototype.createColumnDataObject =
    function()
    {
        var oRet = new Array();
        for( var idx = 0; idx < this._colCnt; idx++ )
            oRet[ idx ] = "";
        return oRet;
    };

//
//  creates a state-oriented style. Essentially, there are four basic styles:
//  'normal'   - row is not the current row, and is not selected
//  'hilite'   - row is the current row, but is not selected
//  'select'   - row is selected, but not the current row
//  'selecthi' - row is selected and is the current row
//
//  This breaks down into a 2x2 matrix like so:
//
//              | not selected | selected |
//  ------------+--------------+----------+
//  not current |  normal      | select   |
//  ------------+--------------+----------+
//  current row |  hilite      | selecthi |
//  ------------+--------------+----------+
//
//  So the style strings are stored as an array of [false,true]
//
//
scrollList.prototype.setStyleClass =
    function( sState, sNormal, sNormalHilite, sSelect, sSelectHilite )
    {
        //
        //  first, make sure we have four styles to choose from.
        //  sNormal is required, but the others are not
        //
        if( null != sNormal && sNormal != "" )
        {
            this._styleClass[ sState ]                   = new Array();
            this._styleClass[ sState ][ false ]          = new Array();
            this._styleClass[ sState ][ false ][ false ] = sNormal;
            this._styleClass[ sState ][ false ][ true  ] = sNormalHilite;
            this._styleClass[ sState ][ true  ]          = new Array();
            this._styleClass[ sState ][ true  ][ false ] = sSelect;
            this._styleClass[ sState ][ true  ][ true  ] = sSelectHilite;
        }
    };

//  provide a shorthand for setting the default style class
scrollList.prototype.setDefaultClassStyle =
    function( sNormal, sNormalHilite, sSelect, sSelectHilite )
    {
        this.setStyleClass( this.DEFAULT_CLASS_STYLE_ID, sNormal, sNormalHilite, sSelect, sSelectHilite );
    };

scrollList.prototype.getStateClassStyle =
    function( sState, isCurrentRow, isSelected )
    {
        var sRet   = null;
        var oStyle = this._styleClass[ sState ];
        if( null != oStyle )
            sRet = oStyle[ !!isSelected ][ !!isCurrentRow ];

        //
        //  if the style isn't provided, try to use a 'default' style
        //
        if( null == sRet )
            sRet = this.getStateClassStyle( this.DEFAULT_CLASS_STYLE_ID, !!isCurrentRow, !!isSelected );

        //  if there still isn't any style, use the basic 'default' style
        if( null == sRet )
            sRet = this.getStateClassStyle( this.DEFAULT_CLASS_STYLE_ID, false, false );

        return sRet;
    };

scrollList.prototype.getDefaultNormalStyle       = function() { return this.getStateClassStyle( this.DEFAULT_CLASS_STYLE_ID, false, false ); };
scrollList.prototype.getDefaultNormalHiliteStyle = function() { return this.getStateClassStyle( this.DEFAULT_CLASS_STYLE_ID, false, true  ); };
scrollList.prototype.getDefaultSelectStyle       = function() { return this.getStateClassStyle( this.DEFAULT_CLASS_STYLE_ID, true,  false ); };
scrollList.prototype.getDefaultSelectHiliteStyle = function() { return this.getStateClassStyle( this.DEFAULT_CLASS_STYLE_ID, true , true  ); };

scrollList.prototype.getRowStyle =
    function( oRow )
    {
        var sRet = this.getDefaultNormalStyle();
        oRow = this.getRow( oRow );
        if( null != oRow )
            sRet = this.getStateClassStyle( this.getRowState( oRow ), (this._curRow == oRow ),  oRow._selected );
        return sRet;
    };

scrollList.prototype.paint =
    function()
    {
        //
        //  make only the rows between _viewTop and _viewTop + _viewDepth - 1 visible
        //
        var viewBottom;
        if( -1 == this._viewDepth )
            viewBottom = this.rowCount() - 1;
        else
        {
            viewBottom = this._viewTop + this._viewDepth;
            if( viewBottom > this.rowCount() )
                viewBottom = this.rowCount();
        }

        for( var idx = 0; idx < this.rowCount(); idx++ )
        {
            var thisRow = this._elem.rows[ idx ];
            thisRow.style.display = ( this._viewTop <= idx && idx < viewBottom )
                                  ? "inline"
                                  : "none";
        }

        this.hilite( this.getCurrentRow(), true );
    };

scrollList.prototype.setHeaderRow =
    function( tableElem )
    {
        var oTableRow = document.createElement( "TR" );
        for( var colName in this._cols )
        {
            var col = this._cols[ colName ];
            var oTableCol = document.createElement( "TD" );
            var oTextElem = document.createTextNode( col.getHeading() );
            oTableCol.appendChild( oTextElem );
            oTableRow.appendChild( oTableCol );
        }
        tableElem.appendChild( oTableRow );
    };

scrollList.prototype.addRow =
    function()
    {
        this.removeEmptyRow();

        var oTableRow = document.createElement( "TR" );
        oTableRow.style.display = "none";                       // create invisible
          oTableRow.onclick       = scrollList_onRowClick;
          oTableRow.ondblclick    = scrollList_onRowDblClick;
        oTableRow._scrollList   = this;
        oTableRow.csRowState    = this.DEFAULT_CLASS_STYLE_ID;

        this._elem.appendChild( oTableRow );
        return oTableRow;
    };

scrollList.prototype.createColumn =
    function( colIndex )
    {
        var oCol      = this.getColumn( colIndex );
        var oTableCol = oCol.createElement();
        oTableCol.setAttribute( "cellId", colIndex );
        return oTableCol;
    };

//
//  answer the TR element for the given row index.
//
//  rowIndex can be either the TR element or an index to the TR element.
//
scrollList.prototype.getRow =
    function( rowIndex )
    {
        var oRow = null;

        //
        //  first see if we were passed a reference to an Element
        //  object that happens to be a TR object.
        //
        if( typeof rowIndex == "object" && rowIndex.tagName == "TR" )
            oRow = rowIndex;

        //
        //  If rowIndex is -1, then answer the "current row". If there is no
        //  current row, answer the first row.
        //
        else if( -1 == rowIndex )
        {
            if( null == this._curRow )
                this._curRow = this._elem.rows[ 0 ];
            oRow = this._curRow;
        }

        //
        //  else, if the index is sane, answer the row object for that index
        //
        else if( 0 <= rowIndex && rowIndex < this.rowCount() )
            oRow = this._elem.rows[ rowIndex ];
        return oRow;
    };

scrollList.prototype.getRowColumn =
    function( oRow, colIndex )
    {
        var oCol = null;
        if( null != this._cols[ colIndex ] )            // in case colIndex is a column name
            colIndex = this._cols[ colIndex ]._index;

        if( 0 <= colIndex && colIndex < this._colCnt )
        {
            oRow = this.getRow( oRow );

            var thisCol = null;
            if( null != oRow )
            {
                // this is not as simple as just getting the element from the row's cell collection
                // because its possible that the cells are being created out of order. So we have to
                // loop through the cell entries, looking for a cellid property of the desired index.
                // if the index is not found, it is created and inserted into the appropriate spot
                //
                //  however, to possibly save us some work, just peek to see if the cell we want
                //  is in the right place already...
                //
                if( colIndex < oRow.cells.length && oRow.cells[ colIndex ].getAttribute( "cellId" ) == colIndex )
                    oCol = oRow.cells[ colIndex ];

                else
                {
                    for( var iColIdx = 0; iColIdx < oRow.cells.length; iColIdx++ )
                    {
                        thisCol = oRow.cells[ iColIdx ];
                        var cellId = thisCol.getAttribute( "cellId" );
                        if( cellId == colIndex )
                        {
                            oCol = thisCol;
                            break;
                        }
                        else if( cellId > colIndex )
                            // this is our insertion point
                            break;
                        thisCol = null;
                    }

                    if( null == oCol )
                    {
                        //  didn't find the column - so create it
                        oCol = this.createColumn( colIndex );
                        if( null == thisCol )
                            //  this is the first cell in the row
                            oRow.appendChild( oCol );
                        else
                            oRow.insertBefore( oCol, thisCol );
                    }
                }
            }
        }
        return oCol;
    };

scrollList.prototype.setColData =
    function( row, colIndex, vColData )
    {
        var oCol    = this.getColumn( colIndex );
        var oNewCol = this.getRowColumn( row, colIndex );
        if( null != oNewCol )
            oNewCol.innerHTML = oCol.format( vColData );
    };

scrollList.prototype.getColumnData =
    function( row, colIndex )
    {
        var oCol = this.getRowColumn( row, colIndex );
        return oCol.innerHTML;
    };

scrollList.prototype.clear =
    function()
    {
        while( this._elem.hasChildNodes() )
            this._elem.removeChild( this._elem.firstChild );
        this._curRow = null;
        this.createEmptyRow();
    };

//
//  creates a new row from the supplied XML documnt fragment. For this to work,
//  the xmlTag properties of each row must have been supplied prior to calling
//  this method.
//
//  answers the row created
//
scrollList.prototype.createRowFromXML =
    function( oXmlDoc )
    {
        var oRow = this.addRow();
        if( null != oRow )
        {
            for( var colIdx = 0; colIdx < this.columnCount(); colIdx++ )
            {
                var oCol    = this.getColumn( colIdx );
                var sColVal = "";
                if( null != oCol._xmlTag )
                {
                    var oNode = oXmlDoc.selectSingleNode( ".//" + oCol._xmlTag );
                    if( null != oNode )
                        sColVal = oNode.text;
                }
                this.setColData( oRow, colIdx, sColVal );
            }
        }
        return oRow;
    };

scrollList.prototype.getColumn =
    function( colIndex )
    {
        var oRet = this._cols[ colIndex ];
        if( null == oRet )
        {
            for( var colName in this._cols )
            {
                if( this._cols[ colName ]._index == colIndex )
                {
                    oRet = this._cols[ colName ];
                    break;
                }
            }
        }
        return oRet;
    };

scrollList.prototype.swapRows =
    function( row1, row2 )
    {
        if( typeof row1 == "number" )
            row1 = this.getRow( row1 );

        if( typeof row2 == "number" )
            row2 = this.getRow( row2 );

        if( null != row1 && null != row2 )
        {
            row1.swapNode( row2 );
        }
    };

scrollList.prototype.moveRowUp =
    function( row )
    {
        if( typeof row == "number" )
            row = this.getRow( row );
        if( null != row )
            this.swapRows( row, row.previousSibling );
    };

scrollList.prototype.moveRowDown =
    function( row )
    {
        if( typeof row == "number" )
            row = this.getRow( row );
        if( null != row )
            this.swapRows( row, row.previousSibling );
    };

//
//  answers the row element for the currently hilited row
//
scrollList.prototype.getCurrentRow =
    function()
    {
        return this.getRow( -1 );
    }

scrollList.prototype.setCurrentRow =
    function( row )
    {
        row = this.getRow( row );
        if( row != null )
        {
            this.hilite( row );
        }
    };

//
//  sets the state value of a row. This is set as an attribute to the
//  row element as csRowState
//
scrollList.prototype.setRowState =
    function( row, state )
    {
        row = this.getRow( row );
        if( null != row )
            row.csRowState = state;
    };

//
//  answers the state value of a rows, or empty string if none exist
//
scrollList.prototype.getRowState =
    function( row )
    {
        var sRet = "";
        row = this.getRow( row );
        if( null != row && null != row.csRowState )
            sRet = row.csRowState;
        return sRet;
    };

//
//  answers the selected state for a given row
//
scrollList.prototype.isSelected =
    function( row )
    {
        var bRet = false;
        row = this.getRow( row );
        if( null != row )
            bRet = row._selected;
        return bRet;
    };

//
//  answers the CSS class for the given row based on its
//  csRowState and _selected values.
//
scrollList.prototype.getRowStateClass =
    function( row )
    {
        var sClass = "";
        row = this.getRow( row );
        if( null != row )
        {
            var sState = this.getRowState( row );
        }
        return sClass;
    };

//
//  specifies the string to display when the list is empty
//
scrollList.prototype.setEmptyMessage =
    function( sMessage )
    {
        this._emptyMessage = sMessage;
    };

//
//  answer true if the list is currently empty. The list is
//  empty if the only item in the list is the 'special' empty item
//  (which contains the mesage that the list is empty ;-)
//
scrollList.prototype.isEmpty =
    function()
    {
        return ( null != this._elem && this._elem.rows.length == 1 && this._elem.rows[ 0 ].id == this.EMPTY_ROW_ID );
    };

scrollList.prototype.createEmptyRow =
    function()
    {
        if( this._elem.rows.length == 0 )
        {
            var oText      = document.createTextNode( this._emptyMessage );
            var oCol       = document.createElement( "TD" );
            var oRow       = document.createElement( "TR" );
            oCol.colSpan   = this._colCnt;
            oRow.id        = this.EMPTY_ROW_ID;
            oRow.className = this.getDefaultNormalStyle();

            oCol.appendChild( oText );
            oRow.appendChild( oCol );
            this._elem.appendChild( oRow );
        }
    };

scrollList.prototype.removeEmptyRow =
    function()
    {
        if( this.isEmpty() )
        {
            this._elem.removeChild( this._elem.firstChild );
        }
    };   

Suggest you just sit there and wait till life gets easier.

Working...