var curPage = 0;
var googleOffset = 0;
var pageSize = 75; // normally 50
var rowList = new Array();
var totalCount = 0; // holds total result set size (as opposed to strict length of rowList)
var rangeStart;
var rangeStop;
var searchString = null;
var searchContext = 0;

var isgoog = false;
var isadv = false;

var patVisible = /\bshow\b/;
var patHidden = /\bhide\b/;
var patOdd = /\bodd\b/;
var patEven = /\beven\b/;

var g_resultsTable = null; // global reference to results table (we can always assume that rel_results files generate a specific id)

var fadeList = new Array(); // queue rows to flash, when fade-timer is running

/// basic ajax behavior
function requestSearchResults(kwords, context, start, size, token) {
	try {
			// change table contents
	// 	alert("calling requestSearchResults");
			// for encoding details, see: http://xkr.us/articles/javascript/encode-compare/
		var arg = new Array();
		arg.push("keywords="+encodeURIComponent(kwords));
		arg.push("context="+((context != null) ? context : 0));
		arg.push("start="+((start != null) ? start : curPage*pageSize));
		arg.push("size="+((size != null) ? size : pageSize));
		arg.push("token="+((token != null) ? token : 0));
			// need to calculate non-default column ordering (investigate table head?)
		var customSort = tableCustomSorting(resultsTable());
		if (customSort) {
			arg.push("order="+customSort);
		}
		requestChange("rel/search-back.php", arg.join("&"), function () { genericRespond(respondSearchResults); });
	} catch (m) {
//		alert("caught exception in requestSearchResults: " + m.toString());
	}
}

function respondSearchResults() {
	createResultRows();
		// remove "getting results" tags from results table
}

function respondSidebarSearch() {
	if (!isadv) {
//		console.log("make sure table is set up for inline display (reconstruct headers, etc)");
		enableAdvancedSorting();
	}
	isadv = true;
	isgoog = false;
	createResultRows();
// 	buildPagedNavigation();
	updateEligiblePanes(req.responseXML);
}

function requestSidebarSearch(kwords, context, start, size, token) {
	try {
			// change table contents
	// 	alert("calling requestSearchResults");
			// for encoding details, see: http://xkr.us/articles/javascript/encode-compare/
		var arg = new Array();
		arg.push("keywords="+encodeURIComponent(kwords));
		arg.push("context="+((context != null) ? context : 0));
		arg.push("start="+((start != null) ? start : curPage*pageSize));
		arg.push("size="+((size != null) ? size : pageSize));
		arg.push("token="+((token != null) ? token : 0));
			// get additional sidebar constraints
		var sbArgs = buildSidebarConstraints(document.getElementById("searchSidebarForm")); // needs to get form to pass along
		if (sbArgs && sbArgs.length) {
			arg.push("sb=1");
			for (var i=0; i < sbArgs.length; i++) {
				arg.push(sbArgs[i]);
			}
		}
			// need to calculate non-default column ordering (investigate table head?)
		var customSort = tableCustomSorting(resultsTable());
		if (customSort) {
			arg.push("order="+customSort);
		}
		requestChange("rel/relsearch.php", arg.join("&"), function () { genericRespond(respondSidebarSearch); });
	} catch (m) {
//		alert("caught exception in requestSearchResults: " + m.toString());
	}
}
//// google xml response
function requestGoogleSearch(kwords, context, start, size) {
	try {
			// change table contents
	// 	alert("calling requestSearchResults");
			// for encoding details, see: http://xkr.us/articles/javascript/encode-compare/
		var arg = new Array();
		arg.push("keywords="+encodeURIComponent(kwords));
		arg.push("context="+((context != null) ? context : 0));
		arg.push("start="+((start != null) ? start : googleOffset));
		arg.push("size="+((size != null) ? size : pageSize));
			// need to calculate non-default column ordering (investigate table head?)
		var customSort = tableCustomSorting(resultsTable());
		if (customSort) {
			arg.push("order="+customSort);
		}
		requestChange("rel/google-back.php", arg.join("&"), function () { genericRespond(respondGoogleSearch); });
	} catch (m) {
//		alert("caught exception in requestSearchResults: " + m.toString());
	}
}

function respondGoogleSearch() {
//	alert("back from google search");
	// createGoogleRows();
	isgoog = true; // mark as google search, for paging
	/// update google offset
	if (req.responseXML.documentElement.hasAttribute("goff")) {
		googleOffset = parseInt(req.responseXML.documentElement.getAttribute("goff"));
	}
	createResultRows();
}

function createGoogleRows() {
//	var rows = rewriteGoogleResults(req.responseXML);
	if (!(rows && rows.length && rows.length > 0)) {
//		console.log("No results to write. %o", rows);
	} else {
		insertResultRows(rowsFromProductList(rows)); // see mupl.js
	}
}

function rewriteGoogleResults(doc) {
		//doc.DOMImplementation.createDocument(null, "results"); // 
//	var newStructure = doc.createDocumentFragment(); /// might instead need DOMImplementation.createDocument()
	var newStructure = new Array();
	var mainGoogleResults = doc.getElementsByTagName("RES")[0];
	if (mainGoogleResults) {
		var googleList = mainGoogleResults.getElementsByTagName("R");
		for (var g=0; g<googleList.length; g++) {	
//			newStructure.appendChild(googleItemToMUPL(googleList[g], doc));
			newStructure.push(googleItemToMUPL(googleList[g], doc));
		}
	}
	return newStructure;
}

/// basic table row visibility (for paging internally stored results)
function makeRowVisible(row, ann) {
	var oldClass = row.className;
	if (oldClass.length > 0) { // there is an existing class value
		if (patHidden.test(oldClass)) {
			if (ann) {
				//alert("[mrv] Row matched patHidden (" + oldClass + "), will show");
			}
			// was hidden, so make visible
			row.className = oldClass.replace(patHidden, "show");
		} else if (!patVisible.test(oldClass)) {
			if (ann) {
				//alert("[mrv] Row failed patVisible (" + oldClass + "), will show");
			}
			// not already visible, but must have other class values
			row.className += " show";
		}
	} else {
		if (ann) {
			//alert("[mrv] Row had no CSS class (" + oldClass + "), so set to show");
		}
		row.className = "show";
	}
}

function makeRowHidden(row, ann) {
	if (row.id != "prodcatRow") { // ignore prodcat header
		var oldClass = row.className;
		if (oldClass.length > 0) {
			if (patVisible.test(oldClass)) {
				if (ann) {
					//alert("[mrh] Row matched patVisible (" + oldClass + "), will hide");
				}
				row.className = oldClass.replace(patVisible, "hide");
			} else if (!patHidden.test(oldClass)) {
				if (ann) {
					//alert("[mrh] Row failed patHidden (" + oldClass + "), will hide");
				}
				row.className += " hide";
			}
		} else {
			if (ann) {
				//alert("[mrh] Row had no CSS class (" + oldClass + "), so set to hide");
			}
			row.className = "hide";
		}
	}
}

	/// use Fader class from /fadebg.js to cascade fade (via timer...)
	/// --------
	/// (does overhead make it more or less useful to parametrize show/hidePage())
function showPage(pg, size, rows) {
//	alert("Showing page: " + pg);
	var start = pg * size;
	var rowTot = rows.length;
	var i = 0;
	for (theItem = rows.item(start); i < size && theItem != null; theItem = theItem.nextSibling) {
//	for (var i=0; i<size && start+i<rowTot; i++) {
		if (theItem.nodeType == 1) {
			makeRowVisible(theItem, false);
			trackForCue(theItem); // add this row to the list to "flash" or otherwise signify changes for
			i++;
		}
	}
	changeCursorRange(pg);
}

function hidePage(pg, size, rows) {
//	alert("Hiding page: " + pg);
	var start = pg * size;
	var rowTot = rows.length;
	var i=0;
	for (theItem = rows.item(start); i < size && theItem != null; theItem = theItem.nextSibling) {
//	for (var i=0; i<size && start+i<rowTot; i++) {
		if (theItem.nodeType == 1) {
			makeRowHidden(theItem, false);
			i++;
		}
	}
	changeCursorRange(pg);
}


	/// clearResultRows() makes the _very_ explicit assumption that any TBODY elements
	/// are result rows, which it then removes from the table; ie, any content that
	/// is not a result row should be in the head or the foot of the table!
	/// (this could be changed to delete conditionally, based on the row class 
	/// name matching one of a number of patterns or something, but the clear
	/// normative condition here is probably useful)
function clearResultRows() {
	// get rid of existing rows (cf init_search)
//	console.log("clearing old results");
	var theMainTable = document.getElementById("scored_results");
	for (var tb = 0; tb < theMainTable.tBodies.length; tb++) {
		var theTBody = theMainTable.tBodies.item(tb); // instead of directly referencing item(0)
		var origCount = theTBody.rows.length;
		for (var r=0; r<origCount; r++) {
				// there's some question about whether you could use r as 
				// the index to deleteRow (which assumes that after deleting
				// the n-th row, it doesn't renumber the n+1 and following rows.
				// Deleting from the end as many times as there are rows is
				// probably at least as simple, if not as efficient.
			theTBody.deleteRow(-1); // delete the last row
		}
	}
}

function placePaginateMessage() {
	try {
		// write the "Paginating Results..."
		var theMainTable = document.getElementById("scored_results");
		if (!document.getElementById("paginate")) {
			var thePageMessage = theMainTable.tHead.insertRow(-1);
			thePageMessage.id = "paginate";
			var theMessageCell = thePageMessage.insertCell(-1);
			// (need to calculate colspan for theMessageCell (from other header rows))
			theMessageCell.setAttribute("colspan", theMainTable.tHead.rows.item(0).cells.length);
			theMessageCell.appendChild(document.createTextNode("Building results...hold SHIFT and click reload if stuck"));
		}
	} catch (mesg) {
// 		alert("Building results...hold SHIFT and click reload if stuck [placePaginateMessage threw exception: " + mesg.toString() + "]");
	}
}

function placeEmptyMessage() {
	try {
		// write the "Paginating Results..."
		var theMainTable = document.getElementById("scored_results");
		clearPaginateMessage();
		
		var thePageMessage = theMainTable.tHead.insertRow(-1);
		thePageMessage.id = "noresults";
		var theMessageCell = thePageMessage.insertCell(-1);
		// (need to calculate colspan for theMessageCell (from other header rows))
		theMessageCell.setAttribute("colspan", theMainTable.tHead.rows.item(0).cells.length);
		theMessageCell.appendChild(document.createTextNode("Could not find anything. Try a less specific search (eg, \"text\" instead of \"word processor\")."));
	
		theMessageCell.appendChild(em_catTable());
		theMessageCell.appendChild(em_searchTable());
	} catch (mesg) {
// 		alert("Building results...hold SHIFT and click reload if stuck [placePaginateMessage threw exception: " + mesg.toString() + "]");
	}
}

function clearEmptyMessage() {
// 	try {
// 		var theEmptyMessage = document.getElementById("noresults");
// 		if (theEmptyMessage) {
// 			var theContainingTable = theEmptyMessage.parentNode;
// 			theContainingTable.removeChild(theEmptyMessage);
// 		}
// 	} catch (m) {
// //		console.log("could not clear no-results search: ", m);
// 	}
	clearMessage("noresults");
}

function clearPaginateMessage() {
	clearMessage("paginate");
}

function clearMessage(id) {
	try {
		var theMessage = document.getElementById(id);
		if (theMessage) {
			var theContainingTable = theMessage.parentNode;
			theContainingTable.removeChild(theMessage);
		}
	} catch (m) {
//		console.log("could not clear message for %o: %o", id, m);
	}
}
	

//// building empty message
function em_catTable() {
	var theTable = document.createElement("table");
	theTable.className = "cats";
	theTable.setAttribute("border", "0");
	theTable.setAttribute("cellpadding", "5");
	theTable.setAttribute("cellspacing", "0");
	
	var theFirstRow = theTable.insertRow(-1);
	theFirstRow.appendChild(em_cat("Business", "/business/", "Finance, Presentation tools..."));
	theFirstRow.appendChild(em_cat("Dashboard", "/dashboard/", "Widgets for everything."));
	theFirstRow.appendChild(em_cat("Development", "/development/", "HTML, Editors..."));
	theFirstRow.appendChild(em_cat("Drivers", "/drivers/", "Printer, Scanner..."));
	
	var theSecondRow = theTable.insertRow(-1);
	theSecondRow.appendChild(em_cat("Education", "/education/", "Language, Teaching Tools..."));
	theSecondRow.appendChild(em_cat("Games", "/games/", "Action, Arcade..."));
	theSecondRow.appendChild(em_cat("Home & Personal", "/personal/", "Cooking, Music..."));
	theSecondRow.appendChild(em_cat("Internet", "/internet/", "Browsers, FTP..."));

	var theThirdRow = theTable.insertRow(-1);
	theThirdRow.appendChild(em_cat("Multimedia & Design", "/education/", "Language, Teaching Tools..."));
	theThirdRow.appendChild(em_cat("Games", "/games/", "Action, Arcade..."));
	theThirdRow.appendChild(document.createElement("td"));
	theThirdRow.appendChild(document.createElement("td"));

	
	return theTable;
}

function em_searchTable() {
	var theSearchForm = document.createElement("form");
	theSearchForm.setAttribute("action", "powersearch.php");
	theSearchForm.setAttribute("method", "get");
	
	var theTable = document.createElement("table");
	theTable.className = "psearch";
	theTable.setAttribute("border", "0");
	theTable.setAttribute("cellpadding", "2");
	theTable.setAttribute("cellspacing", "6");
	theTable.setAttribute("width", "400");
	
	theTable.appendChild(em_searchRow("keywords", "Title"));
	theTable.appendChild(em_searchRow("descr", "Description"));
	theTable.appendChild(em_searchRow("developer", "Developer"));
	theTable.appendChild(em_searchLicenseRow());
	
	theTable.appendChild(em_searchControlsRow());
	
	theSearchForm.appendChild(theTable);
	return theSearchForm;
}	

function em_cat(cat, ln, descr) {
	var theCell = document.createElement("td");
	theCell.setAttribute("valign", "top");
	var theLink = document.createElement("a");
	theLink.setAttribute("href", ln);
	theLink.appendChild(document.createTextNode(cat))
	theCell.appendChild(theLink);
	theCell.appendChild(document.createElement("br"));
	theCell.appendChild(document.createTextNode(descr));
	return theCell;
}

function em_searchRow(nm, lab) {
	var theRow = document.createElement("tr");
	var theLabelCell = document.createElement("td");
	theLabelCell.setAttribute("align", "right");
	theLabelCell.appendChild(document.createTextNode(lab+":"));
	var theInputCell = document.createElement("td");
	var theInput = document.createElement("input");
	theInput.setAttribute("type", "text");
	theInput.setAttribute("size", "40");
	theInput.setAttribute("name", nm);
	theInputCell.appendChild(theInput);
	theRow.appendChild(theLabelCell);
	theRow.appendChild(theInputCell);
	return theRow;
}

function em_searchLicenseRow() {
	var theRow = document.createElement("tr");
	var theSelect = document.createElement("select");
	theSelect.setAttribute("name", "license");
	theSelect.appendChild(em_searchLicOpt("", true));
	theSelect.appendChild(em_searchLicOpt("Free"));
	theSelect.appendChild(em_searchLicOpt("Shareware"));
	theSelect.appendChild(em_searchLicOpt("Demo"));
	theSelect.appendChild(em_searchLicOpt("Commercial"));
	var theLabelCell = document.createElement("td");
	theLabelCell.setAttribute("align", "right");
	theLabelCell.appendChild(document.createTextNode("License:"));
	theRow.appendChild(theLabelCell);
	theRow.appendChild(theSelect);
	return theRow;
}

function em_searchLicOpt(val, sel) {
	var theOption = document.createElement("option");
	if (sel) {
		theOption.selected = true;
	}
	theOption.appendChild(document.createTextNode(val));
	return theOption;
}

function em_searchControlsRow() {
	var theRow = document.createElement("tr");
	var theBlank = document.createElement("td");
	var theCtlCell = document.createElement("td");
	var theSubmitButton = document.createElement("input");
	theSubmitButton.setAttribute("type", "submit");
	theSubmitButton.setAttribute("value", "Search");
	var theResetButton = document.createElement("input");
	theResetButton.setAttribute("type", "reset");
	theResetButton.setAttribute("value", "Clear");
	theCtlCell.appendChild(theSubmitButton);
	theCtlCell.appendChild(theResetButton);
	theRow.appendChild(theBlank);
	theRow.appendChild(theCtlCell);
	return theRow;
}

/// It's changePage() that needs to do all the work (differently)
/// for the new dynamically-retrieved paging method. Not really 
/// a hide/show sort of concept anymore.
function changePage(newp) {
	// check bounds
//	alert("changePage(new/size/tot): " + newp + "/" + pageSize + "/" + totalCount);
	if (totalCount > 0 && (newp * pageSize <= totalCount)) {
		
		// remove existing content / place "building results...hold SHIFT and click reload if stuck"
		clearResultRows();
		placePaginateMessage(); // cf clearPaginateMessage();
		
		// get new results
		if (isgoog) {
			newoffset = (newp - curPage == 1) ? null : newp*pageSize;
			requestGoogleSearch(searchString,searchContext,newoffset,pageSize);
		} else if (isadv) {
			requestSidebarSearch(searchString,searchContext,newp*pageSize,pageSize);
		} else {
			requestSearchResults(searchString,searchContext,newp*pageSize,pageSize); // make sure init_search marks the current page (maybe via changeCursorPage())
		}
		curPage = newp;
		markCurrentPage();
//		changeCursorRange(newp);
//		alert("Would get new results here");
		window.location.hash = "resultsTop"; // jumpToTop()?
	} else {
// 		alert("debug: Paging exceeds results (new/size/tot): " + newp + "/" + pageSize + "/" + totalCount);
	}
}

function firstPage() {
	if (rowList) {
		changePage(0);
	} else {
		// alert("No row list (may have been a problem getting results)");
	}
}

function prevPage() {
	if (rowList) {
		if (curPage > 0) {
			changePage(curPage-1);
		} else {
//			alert("Tried to page back too far (" + curPage + ")");
		}
	} else {
// 		alert("No row list available to operate on");
	}
}

function nextPage() {
	if (rowList) {
		if (curPage < Math.ceil(totalCount / pageSize - 1)) {
			changePage(curPage+1);
		} else {
//			alert("Tried to page past end of rowset (" + curPage + ")");
		}
	} else {
// 		alert("No row list available to operate on");
	}
}

function lastPage() {
	if (rowList) {
		maxPage = Math.ceil(totalCount / pageSize - 1);
		if (curPage <= maxPage) {
			changePage(maxPage);
		} else {
//			alert("Tried to go to end of rowset, but already there (" + curPage + ")");
		}
	} else {
// 		alert("No row list available to operate on");
	}
}

/// just reference pageSize and rowList globals directly
/// (instead of expecting them as arguments... they 
/// never change, so technically don't need to be params)
/// may be able to do the same with oldp (== curPage)

/// internal_changePage() is the "old" changePage behavior, 
/// suitable for paging through results when the _total_ 
/// result set is contained in the table, with all but a 
/// page of results hidden
function internal_changePage(newp) {
	hidePage(curPage, pageSize, rowList);
	showPage(newp, pageSize, rowList);
	curPage = newp;
		// start visual notification (disabled at the moment, for testing other things)
// 	window.setTimeout(fadeUpTimer, 100);
// 	window.setTimeout(fadeTimer, 600); //	window.setTimeout(fadeTimer, 100);
		// update bottom nav current page cursor
	markCurrentPage();
		// jump to top
	window.location.hash = "resultsTop";
}

function internal_firstPage() {
	if (rowList) {
		internal_changePage(0);
	} else {
		alert("No row list (may have been a problem getting results)");
	}
}

function internal_prevPage() {
	if (rowList) {
		if (curPage > 0) {
			internal_changePage(curPage-1);
		} else {
//			alert("Tried to page back too far (" + curPage + ")");
		}
	} else {
		alert("No row list available to operate on");
	}
}

function internal_nextPage() {
	if (rowList) {
		if (curPage < Math.ceil(totalCount / pageSize - 1)) {
			internal_changePage(curPage+1);
		} else {
//			alert("Tried to page past end of rowset (" + curPage + ")");
		}
	} else {
		alert("No row list available to operate on");
	}
}

function internal_lastPage() {
	if (rowList) {
		maxPage = Math.ceil(totalCount / pageSize - 1);
		if (curPage <= maxPage) {
			internal_changePage(maxPage);
		} else {
//			alert("Tried to go to end of rowset, but already there (" + curPage + ")");
		}
	} else {
		alert("No row list available to operate on");
	}
}

function changeCursorText(curs, val) {
	if (curs) {
		if (curs.hasChildNodes()) {
			for (var i=0; i<curs.childNodes.length; i++) {
				curs.removeChild(curs.childNodes.item(i));
			}
		}
		curs.appendChild(document.createTextNode(val));
	}
}

function changeCursorRange(page) {
	var start = page * pageSize + 1;
	var end = Math.min((page + 1) * pageSize, totalCount);
	changeCursorText(rangeStart, start);
	changeCursorText(rangeStop, end);
}

/// init_search() is responsible for handling initial visibility, and should be 
/// called from body.onload in target page
function init_search(tot, internal) {
	if (internal == null) internal = false; // assume not paging through internally stored results
	var theMainTable = document.getElementById("scored_results");
	var thePageMessage = document.getElementById("paginate");
		// initialize global references to cursor begin/end
	rangeStart = document.getElementById("rlow");
	rangeStop = document.getElementById("rhigh");
//	if (theMainTable && thePageMessage && thePageMessage.getAttribute("class") != "empty") {
	if (theMainTable && thePageMessage) {
			// if we don't need to makeRowVisible/Hidden, we can just set i += length of rows in this body
		var i = 0; // count rows
		for (var tb = 0; tb < theMainTable.tBodies.length; tb++) {
			if (internal) {
				var theTBody = theMainTable.tBodies.item(tb); // instead of directly referencing item(0)
				rowList = theTBody.rows;
					// for init, just go through entire rowset (a bit less good than starting with everything hidden, but work on that later)
				for (var theRow = theTBody.rows.item(0); theRow != null; theRow = theRow.nextSibling) {
					if (theRow.nodeType == 1) { // this should always be true when (now) using theTBody.rows instead of iterating over the children of the table body with nextSibling()
						if (i < pageSize) {
							makeRowVisible(theRow, false);
						} else {
							makeRowHidden(theRow, false);
						}
						i++; // this could be folded into the for-conditions, but it makes things a bit less clear
		//				rowList.push(theRow);
					}
				}
			} else {
				i += theMainTable.tBodies.item(tb).rows.length;
			}
		}
		if (i > 0) { // actual body rows
				// we don't have to check thePageMessage, because we check above now
//			if (tot == null) { // this may be a poor way to conditionalize setting this value!
			if (internal) { // this may be a poor way to conditionalize setting this value!
				totalCount = rowList.length; // now buildPagedNavigation should work for both full- and partial-inline paging
			} else {
				if (!totalCount || !isgoog) {
					totalCount = tot;
					// make sure that total range is set properly...
					try {
						var theRangeTotal = document.getElementById("rtotal");
						changeCursorText(theRangeTotal, tot);
					} catch (m) { 
						 //alert ("could not get #rtotal. " + m.toString()); 
					}
				}
			}
				
			var theMessageContainer = thePageMessage.parentNode;
			theMessageContainer.removeChild(thePageMessage);
		}
		changeCursorRange(curPage);
		buildPagedNavigation(theMainTable);
	}
	
	
	//document.forms[0].searchField.focus();
	
}

function tableCustomSorting(tab) {
	try {
		if (tab.tHead.hasChildNodes()) {
			var colRow = null;
			for (var i=0; i<tab.tHead.rows.length && colRow == null; i++) {
				var curr = tab.tHead.rows.item(i);
				if (curr.className == "col_heads") {
					colRow = curr;
				}
			}
			if (colRow && colRow.cells && colRow.cells.length > 0) { // row has cells
				theSelHead = null;
				for (var i=0; i<colRow.cells.length && theSelHead == null; i++) {
					if (colRow.cells.item(i).className == "selcol") {
						theSelHead = colRow.cells.item(i);
					}
				}
				if (theSelHead) {
					theSelHead.normalize();
					return theSelHead.firstChild.nodeValue; // get cell contents
				}
			}
		}
	} catch (mesg) {
//		alert("caught exception in tableCustomSorting: " + mesg.toString());
	}
	return null; // check for custom sort selection (eg, look at headers)
}

function enableAdvancedSorting() {
	var theTable = resultsTable();
	if (theTable) {
		var theHeadRow = null;
		var theTHRows = theTable.tHead.rows;
		for (var i=0; i<theTHRows.length && !theHeadRow; i++) {
			if (theTHRows[i].className == "col_heads") {
				theHeadRow = theTHRows[i];
			}
		}
		if (theHeadRow && theHeadRow.cells.length > 0) {
			for (var i=0; i<theHeadRow.cells.length; i++) {
				theHeadRow.cells[i].normalize();
					/// change action from link to javascript trigger
				if (theHeadRow.cells[i].firstChild.nodeType == Node.TEXT_NODE) {
//					theHeadRow.cells[i].onclick = function () { setSidebarSort(theHeadRow.cells[i].firstChild.nodeValue, theHeadRow.cells[i]); establishSearchRefresh(0); };
					theHeadRow.cells[i].setAttribute("onclick", "setSidebarSort(\""+theHeadRow.cells[i].firstChild.nodeValue+"\", this); establishSearchRefresh(0);");
				} else {
					/// should be link; rebuild text outside of link
					theHeadRow.cells[i].appendChild(document.createTextNode("*"));
				}
			}
		} else {
//			console.log("Could not find column heads");
		}
	}
}

	// find (and cache) common reference to result table
function resultsTable() {
	try {
		if (!(g_resultsTable && g_resultsTable.nodeType)) {
			var theTable = document.getElementById("scored_results");
			if (theTable) {
				g_resultsTable = theTable;
				return g_resultsTable;
			} else {
				return null;
			}
		} else { // global already exists
			return g_resultsTable;
		}
	} catch (mesg) {
// 		alert("Caught exception looking for results table: " + mesg.toString());
		return null;
	}
}
		
function buildPagedNavigation(tbl) {
//	theFoot = tbl.tFoot;
//	theNavRow = theFoot.insertRow(0); // prepend new row for nav
//	theNavRow.id = "pagedNavigationRow";
//	theNavCell = document.createElement("td");
//	theNavCell.setAttribute("id", "pagedNavigation");
//	theNavRow.appendChild(theNavCell);
	var theNavCell = document.getElementById("pagedNavigation");
	
	
	try {
		// build and insert contents as appropriate
		var pages = Math.ceil(totalCount / pageSize); // NB: not rowList.length, so can work from _numerically_ specified results when not all in memory
//		console.log("rebuilding nav pages: ", pages);
		if (theNavCell.hasChildNodes()) {
			var thePages = theNavCell.getElementsByTagName("a");
//			console.log("removing pages: ", thePages.length);
			for (var i=thePages.length; i>0; i--) {
				theNavCell.removeChild(thePages[i-1]);
			}
		}
//		if (!theNavCell.hasChildNodes()) { //  || theNavCell.childNodes.length < pages
			for (var i=0; i<pages; i++) {
				var theNavLink = document.createElement("a");
	//			theNavLink.onclick = function() { changePage(i); }; // not +1
				theNavLink.setAttribute("onclick", "changePage("+i+");");
				theNavLink.setAttribute("href", "#resultsTop");
				if (curPage == i) {
					theNavLink.setAttribute("class", "navpage curpage");
				} else {				
					theNavLink.setAttribute("class", "navpage");
				}
				theNavLink.appendChild(document.createTextNode(i+1));
				theNavCell.appendChild(theNavLink);
			}
//		}
	} catch (e) {
		//alert("problem constructing navigation footer");
	}
}

function markCurrentPage() {
	var theNavCell = document.getElementById("pagedNavigation");
	try {
		if (theNavCell.hasChildNodes()) {
			for (var curr = theNavCell.firstChild; curr != null; curr = curr.nextSibling) {
				if (curr.hasChildNodes()) {
					if (curr.firstChild.nodeValue == (curPage + 1)) {
						curr.className = "navpage curpage";
					} else if (curr.className != "navpage") { // also had curpage before?
						curr.className = "navpage";
					}
				}
			}
		}
	} catch (e) {
// 		alert("Problem marking current-page cursor: " + e.toString());
	}
}
					

/// To do: the fade behavior is very strange in Safari, shifting through
/// the wrong colors and much slower than Camino/FF. Possibly this is because
/// of quirks with the Javascript engine, so probably the right thing to do
/// is to shift the timer model around so that we can use _one_ timer acting
/// on multiple rows, instead of each row depending on its own timer instance.
/// This requires changing the behavior of the Fader object, as well, so that
/// it can be configured to run without a timer object (just calling something
/// like this.updateRow(), which returns status indicating whether it's gone
/// through the entire cycle or not).

function trackForCue(row) {
	try {
		fadeList.push(row); // , patOdd.test(row.className));
	} catch (e) {
		//alert("Could not track row");
	}
}

function fadeRow(row, isalt) {
	var theFader = new Fader(row);
		// establish appropriate colors
	if (isalt) {
		theFader.setEndColor(238, 238, 238); // this needs to get color info from row!
	} else {
		theFader.setEndColor(255, 255, 255);
	}
	theFader.calcSteps();
	theFader.fade();
}

function fadeUpRow(row, isalt) {
	var theFader = new Fader(row);
		// establish appropriate colors
/*
	if (isalt) {
		theFader.setStartColor(238, 238, 238); // this needs to get color info from row!
	} else {
		theFader.setStartColor(255, 255, 255);
	}
*/
	theFader.setStartColor(232, 232, 232);
	theFader.setEndColor(252,255,180);
	theFader.steps = 25; // should be .5 sec
	theFader.calcSteps();
	theFader.fade();
}

function fadeUpTimer() {
	if (fadeList.length > 0) {
		for (var r=0; r<fadeList.length; r++) {
			fadeUpRow(fadeList[r], patEven.test(fadeList[r].className));
		}
	}
}	

function fadeTimer() {
	if (fadeList.length > 0) {
		for (var r=0; r<fadeList.length; r++) {
			fadeRow(fadeList[r], patEven.test(fadeList[r].className));
		}
	}
}

function cascadeFadeTimer() {
	if (fadeList.length > 0) {
		var curr = fadeList.shift();
		fadeRow(curr, patEven.test(curr.className));
		window.setTimeout(fadeTimer, 1);
	}
}
