<public:component>
<public:property name="firstChunkCallback" />
<public:property name="transformCallback" />
<public:property name="tableId" />
<public:property name="dataId" />
<public:method name="testForScroll" />
<public:method name="reset" />
<public:attach EVENT="oncontentready" ONEVENT="initialize()" />

<script>
var DataPageSize = 50;
var TotalRecordCount;
var ScrollBuffer = 50;
var LastRecordNumber = -1;
var SpacerHeight = 22;
var FilledArray = new Array();
var SourceTable;
var TargetBody;
var scrollTimer = null;
var FirstChunk = false;
var LargeData;

function reset() {
  FilledArray = new Array(); 
  SourceTable.onreadystatechange = doLoad;
}

function initialize() {
  element.style.overflowY = "scroll";
  SourceTable = element.document.all[element.tableId];
  SourceTable.dataPageSize = 50;
  SourceTable.style.display = "none";

  LargeData = element.document.all[element.dataId];

  window.attachEvent("onload", doLoad);
  tbl = element.document.createElement("TABLE");
  tbdy = element.document.createElement("TBODY");
  tbdy.id = SourceTable.id + "TargetBody";
  TargetBody = tbdy;

  tbl.style.cssText = SourceTable.style.cssText;
  tbl.style.display = "block";
  tbl.width = SourceTable.width;
  tbl.border = SourceTable.border;

  tbl.insertBefore(tbdy, null);
  element.insertBefore(tbl, null);
}

function goToRow(start) {
  var currRow, newPage, delta;
  currRow = SourceTable.rows[0].recordNumber;
  currPage = Math.floor(currRow / DataPageSize);
  newPage = Math.floor(start / DataPageSize);


  if (currPage == newPage) {
    copyRows();
    return;
  }

  delta = newPage - currPage;

  if (delta > 0) {
    for (i=0;i<delta;i++) {
       SourceTable.nextPage();
    }
  } else {
    delta *= -1;
    for (i=0;i<delta;i++) {
       SourceTable.previousPage();
    }
  }
}

// this method actually 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() {
  var i, copyRow, row;
 
  for (i=0; i<SourceTable.rows.length; i++) {
     row = SourceTable.rows[i];

     if ((!FilledArray[row.recordNumber]) || (FilledArray[row.recordNumber] == 2)) {
       // this uses new DOM functionality to deep copy the row from the table
       copyRow = row.cloneNode(true);
       copyRow = doTransformCallback(copyRow, row);
       copyRow.style.position = "absolute";
       copyRow.style.posTop = SpacerHeight * (row.recordNumber - 1);
       copyRow.style.posLeft = 0;
       copyRow.style.display = "block";
       copyRow.style.zIndex = "100";
       TargetBody.insertBefore(copyRow, null);
       // this is used to keep track of how many rows are copied, to insure no duplicates
       LastRecordNumber = row.recordNumber;
       FilledArray[row.recordNumber] = 1;
     }
  }
  if (FirstChunk) {
    FirstChunk = false;
    doFirstChunkCallback();
  }
}

function doTransformCallback(aRow, dataRow) {
  // examples are to set ellipses or do transform
  if (transformCallback) {
    return eval(transformCallback+"(aRow, dataRow)");
  }  
  return aRow;
}

function doFirstChunkCallback() {
  // intializeMessages in mail app
  if (firstChunkCallback) {
    eval(firstChunkCallback+"()");
  }
  return;
}

// to make sure that the scroll bars are the right size, one spacer is
// created for the whole area that blocks off enough space
function insertScrollBlocks() {
  br = document.createElement("DIV");
  br.style.display = "block";
  br.style.visibility = "hidden";
  br.style.zIndex = "-1";
  br.style.posHeight = TotalRecordCount * SpacerHeight;
  element.insertBefore(br, null);
}     

function onscroll() {
  if (scrollTimer) {
    window.clearTimeout(scrollTimer);
    scrollTimer = 0;
  }
  scrollTimer = window.setTimeout(element.uniqueID+".testForScroll()", 100);
}

// event handler for onscroll for the element (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() {
  var startRow, rows, i;

  startRow = Math.floor(element.scrollTop / SpacerHeight);
  rows = Math.floor(element.offsetHeight / SpacerHeight);
  for (i=startRow; i<=startRow+rows; i++) {
    if (!FilledArray[i]) {
      FilledArray[i] = 2;
      getMoreData(i);
      return;
    }
  }
}   

// 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(i) {
  if (SourceTable.readyState != "complete") 
    return;

  goToRow(i);  
}

// when the databound table is readyState == complete, this alerts that there is new data to transfer
// this method calls the copyRows() method, then calls testForScroll() to handle the case where the 
// thumb has been moved by a large amount
function dataAvailable() {
  if (SourceTable.readyState == "complete") {
    copyRows();
  }
  testForScroll();
}

// onload handler for the document.
function doLoad() { 
  var rows;

  if (SourceTable.readyState != "complete") 
     return;

  rows = SourceTable.rows;
  if (!rows) {
    return;
  }

  if ((TargetBody.rows) && (TargetBody.rows.length > 0)) {
    for (i=TargetBody.rows.length-1; i>=0; i--) {
       TargetBody.rows[i].removeNode(true);
    }
  }

  LastRecordNumber = -1;
  FirstChunk = true;

  // test to see if the dataset is smaller than the chunk size
  if (LargeData.recordset.recordcount <= SourceTable.dataPageSize) {
    copyRows();
  } else {
    // figure out total size of virtual list, to get spacers right
    TotalRecordCount = LargeData.recordset.recordcount; 

    // insert spacers into the vtable
    insertScrollBlocks();

    // hook scrolling and data events
    element.attachEvent("onscroll", onscroll);
    SourceTable.onreadystatechange = dataAvailable;

    // initialize the first set of data
    testForScroll();
  }
}

</script>
</public:component>