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
}