BDG Scenario 3

LoadList.js

var SpacerHeight = 24;
var ScrollBuffer = 40;

var TotalRecordCount;
var LastRecordNumber = -1;

// used to trigger search for id while load is taking place
var searchId = null;

// mouse rollover effects
var lastElem = null;
function inColor() {
   var e = window.event.srcElement;

   // skip nested elements (like IMG's)
   while (e.tagName != "TD")
      e = e.parentElement;
   
   if (e != lastElem)
      e.className = "rollin";
}
function outColor() {
   var e = window.event.srcElement;
   
   // skip nested elements (like IMG's)
   while (e.tagName != "TD")
      e = e.parentElement;
   
   if (e != lastElem)
      e.className = "rollout";
}
function selectColor() {
   var e = window.event.srcElement;
   
   // skip nested elements (like IMG's)
   while (e.tagName != "TD")
      e = e.parentElement;
   
   if (lastElem) {
      if (lastElem.parentElement.className == "oddrow")
         lastElem.className = "oddrow";
      else
         lastElem.className = "evenrow";
   }
   
   lastElem = e;
   e.className = "marker";
   
   // callback function provided by host page
   selectCell(e);
}
function selectFirst() {
   var e = lastElem;

   if (!e) {
      var r = TargetBody.rows;
      if (r.length > 0) {
         e = r[0].cells[0];
         lastElem = e;
      }
      else return;
   }

   // set visible selection indicator
   e.className = "marker";
   
   // scroll row into view, if not visible
   e.scrollIntoView();
   
   // callback function provided by host page
   selectCell(e);
}

// this method moves the data from the bound table to the virtual table on demand
// it detects when all the data has been copied, and stops the transfer
function copyRows() {

   for (var i=0; i<SourceTable.rows.length; i++) {
      var row = SourceTable.rows[i];
      if (!row.recordNumber) break;
           
      // test if all data copied
      if (row.recordNumber <= LastRecordNumber) {
        // if so, no more data to copy, so unhook events
        ScrollContainer.onscroll = null;
        SourceTable.onreadystatechange = null;
        return;
      }

      // keep track of how many rows are copied, to insure no duplicates
      LastRecordNumber = row.recordNumber;
           
      // create the new row element with callback function
      var copyRow = TargetBody.insertRow();
      var copyCell = copyRow.insertCell();
      copyCell.innerHTML = formatCell(row);

      // if searchId is set, try to find record by id as table loads
      // NOTE: ASSUMES THAT THE FIRST COLUMN OF SourceTable IS ID
      if (searchId == row.cells[0].innerText) lastElem = copyCell;

      // every other row is a different color
      if (LastRecordNumber & 1)
         copyRow.className = "oddrow";
      else
         copyRow.className = "evenrow";
              
      // hook events for mouse rollovers and clicks
      copyCell.onmouseover = inColor;
      copyCell.onmouseout  = outColor;
      copyCell.onclick     = selectColor;
           
      TargetBody.insertBefore(copyRow, null);
           
      // shrink scrolling placeholder for each row inserted in the vtable
      deletePlaceholder();
   }
  
   // if searchId still not found, load the next page
   if (searchId) {
      if (lastElem) {
         searchId = null;
         selectFirst();
         testForScroll();
      }
      else
         SourceTable.nextPage();
   }

}

// a spacer div is used to adjust the scroll bars to the right size
// this spacer is increased to maximum height, and then decreased as rows are added
function insertPlaceholder() {
  firstspacer.style.posHeight = TotalRecordCount * SpacerHeight;
}     
function deletePlaceholder() {
  firstspacer.style.posHeight -= SpacerHeight;
}

// event handler for onscroll for the ScrollContainer (the div containing the vtable)
// each time the table is scrolled, we check to see if the last placeholder is close 
// to in view. if it is, we need to get more data.
function testForScroll() {
   if (ScrollContainer.scrollTop + ScrollContainer.offsetHeight + 
       ScrollBuffer >= firstspacer.offsetTop) {
      getMoreData();
   }
}   

// this function advances the page in the databound table
// note that the nextPage() method is asynch, and will return before the new data is in
// to resolve that problem, the onreadystatechange event is hooked in the databound table.
// this event is hooked by the dataAvailable() method
function getMoreData() {
   if (SourceTable.readyState == "complete") {
      // don't go to next page until all data in the SourceTable has been copied
      var r = SourceTable.rows;
      if (r.length > 0) {
         var lastRow = SourceTable.rows[r.length-1];
         if (lastRow.recordNumber > LastRecordNumber)
            copyRows();
         else
            SourceTable.nextPage();
      }
   }
}

// when the databound table's readyState is complete, transfer new data to list
// this method uses copyRows() method, then calls testForScroll() to handle the
// case where the thumb has been moved by a large amount
function readyStateChange() {
   //alert('DEBUG: readyState=' + SourceTable.readyState);
   if (SourceTable.readyState == "complete") {
      copyRows();
      testForScroll();
   }
}

// event handlers for xmldso during reload (search) operation
function noOp() {}   // HACK: noOp needed because xmldso doesn't allow null event handlers
function dataAvailable() {
   if (xmldso.XMLDocument.readyState == 4) {
      // even though we are "complete" the recordset hasn't been updated yet.
      // allow a few moments to finish processing the XML...
      setTimeout('doLoad()',500);
   }
}

// onload handler for the document.
function doLoad() { 

   if (xmldso.recordset.state > 0) { // !adStateClosed

      // validate the XML data (check for error)
      var docElem = xmldso.XMLDocument.documentElement;
      if (docElem != null) {
         var child = docElem.firstChild;
         if (child != null) {
            if (child.baseName == 'error') {
               var code = child.selectSingleNode('./number').text;
               var desc = child.selectSingleNode('./description').text;
               var src  = child.selectSingleNode('./source').text;
               alert('CODE:\t' + code + '\n' +
                     'DESC:\t' + desc + '\n' +
                     'SRC:\t' + src);
               NoRecords.style.display = '';
               return;
            }
         }
      }
   
      // figure out total size of virtual list, to get spacers right
      TotalRecordCount = xmldso.recordset.recordcount; 

      // insert spacer into the scrolling div
      insertPlaceholder();

      // hook scrolling and data events
      ScrollContainer.onscroll = testForScroll;
      SourceTable.onreadystatechange = readyStateChange;
      
      // initialize the first set of data
      getMoreData();
   }
   else {
      NoRecords.style.display = '';
   }
}

// cancel the current download, and remove all rows from TargetTable.
// called before invoking a new search
function resetRows() {
   xmldso.XMLDocument.abort();
   // no more data to copy, so unhook events
   ScrollContainer.onscroll = null;
   SourceTable.onreadystatechange = null;
   while(TargetTable.rows.length > 0)
      TargetTable.deleteRow(0);  // each delete changes the index
   TargetTable.refresh();
   lastElem = null;
   LastRecordNumber = -1;
   TotalRecordCount = 0;
   insertPlaceholder();    // really, remove (TotalRecordCount is 0)
   NoRecords.style.display = 'none';
}


// reset and reload the list from the xmldso
// called when a record changes, is deleted, or added
function reloadList(id) {
   resetRows();
   searchId = id;
   var xmldoc = xmldso.XMLDocument;
   xmldoc.ondataavailable = noOp; // won't accept null
   xmldoc.onreadystatechange = dataAvailable;
   xmldoc.load(xmldoc.url);    // reload the current dataset
}