/*
  The original code (before I improved it) for this wonderful table sorting utility came from:
  http://www.kryogenix.org/code/browser/sorttable/
*/

addEvent(window, "load", sortables_init);


// Returns true if the specified Element has the specified class name.
function hasClassName(elem, name) {
//document.getElementById('log').innerHTML += ' elem.className='+ elem.className;
	var regex = new RegExp('\\b' + name + '\\b');
	return elem.className && elem.className.search(regex)>=0;
}

var SORT_COLUMN_INDEX;

function sortables_init() {
    // Find all tables with class sortable and make them sortable
    if (!document.getElementsByTagName) return;
    tbls = document.getElementsByTagName("table");
    for (ti=0;ti<tbls.length;ti++) {
        thisTbl = tbls[ti];
        if (((' '+thisTbl.className+' ').indexOf("sortable") != -1) && (thisTbl.id)) {
            //initTable(thisTbl.id);
            ts_makeSortable(thisTbl);
        }
    }
}

function ts_makeSortable(table) {
	if(table==null)
		return;
    if (table.rows && table.rows.length > 0) {
        var firstRow = table.rows[0];
    }
    if (!firstRow) return;

    // We have a first row: assume it's the header, and make its contents clickable links
    for (var i=0;i<firstRow.cells.length;i++) {
        var cell = firstRow.cells[i];
        var txt = ts_getInnerText(cell);
        // If the cell is marked to be initially sorted, then mark it with the appropriate arrow.
	    var initSortDirUp = cell.className.indexOf("sortDirUp") >= 0;
	    var initSortDirDown = cell.className.indexOf("sortDirDown") >= 0;

		if(initSortDirUp || initSortDirDown) {
		    var theSortDir;
		    var theSortArrow;
			if(initSortDirUp) {
			    theSortDir = 'up';
			    theSortArrow = '&uarr;';
			} else {
			    theSortDir = 'down';
			    theSortArrow = '&darr;';
			}
	        cell.innerHTML = '<a href="#" class="sortheader" onclick="ts_resortTable(this);return false;">'+txt+'<span class="sortarrow" sortdir='+ theSortDir +'>&nbsp;&nbsp;'+ theSortArrow +'</span></a>';
	    } else {
	        cell.innerHTML = '<a href="#" class="sortheader" onclick="ts_resortTable(this);return false;">'+txt+'<span class="sortarrow"></span></a>';
	    }
    }
}

function ts_getInnerText(el) {
	if (typeof el == "string") return el;
	if (typeof el == "undefined") { return el; };
	if (el.innerText) return el.innerText;	//Not needed but it is faster
	var str = "";

	if (el.childNodes[0]) {
		if(el.childNodes[0].value) {return el.childNodes[0].value;}
	}

	var cs = el.childNodes;
	var l = cs.length;
	for (var i = 0; i < l; i++) {
		// INPUT text field (ignore hidden fields)
		if(cs[i].value && cs[i].type!='hidden') {
			return cs[i].value;
		}
		switch (cs[i].nodeType) {
			case 1: //ELEMENT_NODE
				str += ts_getInnerText(cs[i]);
				break;
			case 3:	//TEXT_NODE
				str += cs[i].nodeValue;
				break;
		}
	}
	return str;
}

function ts_resortTable(lnk) {
    // get the span
    var span;
    for (var ci=0;ci<lnk.childNodes.length;ci++) {
        if (lnk.childNodes[ci].tagName && lnk.childNodes[ci].tagName.toLowerCase() == 'span') span = lnk.childNodes[ci];
    }
    var spantext = ts_getInnerText(span);
    var td = lnk.parentNode;
    var column = td.cellIndex;
    var table = getParent(td,'TABLE');

    // Work out a type for the column
    var tRows = table.rows;
    if (tRows.length <= 1) return;
    var itm;
	// Check all cells in the column, starting with the 2nd row (row=1 so we skip header)
	// It goes until it hits a non-null cell and then uses the value in that cell to determine the column type.
    for(var rowIx=1; rowIx<tRows.length; ++rowIx) {
    	var rCells = tRows[rowIx].cells;
    	if(rCells!=null && rCells[column]) {
		    itm = ts_getInnerText(rCells[column]);
		    if(itm!=null && itm!='')
		    	break;
    	}
    }
    // This is for columns with nothing in them.
//    if(itm==null)
//    	item = '';

    sortfn = ts_sort_caseinsensitive;
    if (itm.match(/^\d\d[\/-]\d\d[\/-]\d\d\d\d$/)) sortfn = ts_sort_date;
    if (itm.match(/^\d\d[\/-]\d\d[\/-]\d\d$/)) sortfn = ts_sort_date;
    if (itm.match(/^[£$]/)) sortfn = ts_sort_currency;
//    if (itm.match(/^[\d\.]+$/)) sortfn = ts_sort_numeric;
    SORT_COLUMN_INDEX = column;
    var firstRow = new Array();
    var newRows = new Array();
    for (i=0;i<table.rows[0].length;i++) { firstRow[i] = table.rows[0][i]; }
    for (j=1;j<table.rows.length;j++) {	newRows[j-1] = table.rows[j]; }

    newRows.sort(sortfn);

    if (span.getAttribute("sortdir") == 'down') {
        ARROW = '&nbsp;&nbsp;&uarr;';
        newRows.reverse();
        span.setAttribute('sortdir','up');
    } else {
        ARROW = '&nbsp;&nbsp;&darr;';
        span.setAttribute('sortdir','down');
    }

    // We appendChild rows that already exist to the tbody, so it moves them rather than creating new ones
    // don't do sortbottom rows
    for (i=0;i<newRows.length;i++) {
   		var newRow = newRows[i];
    	if(!newRow.isSubRow && !hasClassName(newRows,'sortbottom')) {
    		var tBody = table.tBodies[0];
    		tBody.appendChild(newRow);
    		if(newRow.subRows) {
    			for(var subRowIx=0; subRowIx<newRow.subRows.length; ++subRowIx) {
    				tBody.appendChild(newRow.subRows[subRowIx]);
    			}
    		}
    	}
    }
    // do sortbottom rows only
    for (i=0;i<newRows.length;i++) {
   		var newRow = newRows[i];
    	if(!newRow.isSubRow && hasClassName(newRows,'sortbottom')) {
    		var tBody = table.tBodies[0];
    		var newRow = newRows[i];
    		tBody.appendChild(newRow);
    		if(newRow.subRows) {
    			for(var subRowIx=0; subRowIx<newRow.subRows.length; ++subRowIx) {
    				tBody.appendChild(newRow.subRows[subRowIx]);
    			}
    		}
    	}
    }

    // Delete any other arrows there may be showing
    var allspans = document.getElementsByTagName("span");
    for (var ci=0;ci<allspans.length;ci++) {
        if (allspans[ci].className == 'sortarrow') {
            if (getParent(allspans[ci],"table") == getParent(lnk,"table")) { // in the same table as us?
                allspans[ci].innerHTML = '';
            }
        }
    }

    span.innerHTML = ARROW;

	if(isDefined('ts_callback_formatTable'))
	    ts_callback_formatTable(table);
}

// This is for use at startup if you want to mark a column with an arrow.
function markWithArrow(span,sortdir) {

	var ARROW;
    if (sortdir == 'up') {
        ARROW = '&nbsp;&nbsp;&uarr;';
        span.setAttribute('sortdir','up');
    } else {
        ARROW = '&nbsp;&nbsp;&darr;';
        span.setAttribute('sortdir','down');
    }

/*
    // Delete any other arrows there may be showing
    var allspans = document.getElementsByTagName("span");
    for (var ci=0;ci<allspans.length;ci++) {
        if (allspans[ci].className == 'sortarrow') {
            if (getParent(allspans[ci],"table") == getParent(lnk,"table")) { // in the same table as us?
                allspans[ci].innerHTML = '&nbsp;&nbsp;&nbsp;';
            }
        }
    }
*/
    span.innerHTML = ARROW;
}


function getParent(el, pTagName) {
	if (el == null) return null;
	else if (el.nodeType == 1 && el.tagName.toLowerCase() == pTagName.toLowerCase())	// Gecko bug, supposed to be uppercase
		return el;
	else
		return getParent(el.parentNode, pTagName);
}
function ts_sort_date(a,b) {
    // y2k notes: two digit years less than 50 are treated as 20XX, greater than 50 are treated as 19XX
    aa = ts_getInnerText(a.cells[SORT_COLUMN_INDEX]);
    bb = ts_getInnerText(b.cells[SORT_COLUMN_INDEX]);
    if (aa.length == 10) {
        dt1 = aa.substr(6,4)+aa.substr(3,2)+aa.substr(0,2);
    } else {
        yr = aa.substr(6,2);
        if (parseInt(yr) < 50) { yr = '20'+yr; } else { yr = '19'+yr; }
        dt1 = yr+aa.substr(3,2)+aa.substr(0,2);
    }
    if (bb.length == 10) {
        dt2 = bb.substr(6,4)+bb.substr(3,2)+bb.substr(0,2);
    } else {
        yr = bb.substr(6,2);
        if (parseInt(yr) < 50) { yr = '20'+yr; } else { yr = '19'+yr; }
        dt2 = yr+bb.substr(3,2)+bb.substr(0,2);
    }
    if (dt1==dt2) return 0;
    if (dt1<dt2) return -1;
    return 1;
}

function ts_sort_currency(a,b) {
    aa = ts_getInnerText(a.cells[SORT_COLUMN_INDEX]).replace(/[^0-9.]/g,'');
    bb = ts_getInnerText(b.cells[SORT_COLUMN_INDEX]).replace(/[^0-9.]/g,'');
    return parseFloat(aa) - parseFloat(bb);
}

// This sorts numeric data and case insensitive string data all together such that numerical data is sorted
// numerically even when mixed into a list of non-numeric strings.
function ts_sort_caseinsensitive(a,b) {
	if(!a.cells) {
		if(!b.cells)
			return 0;
		return 1;
	} else {
		if(!b.cells)
			return -1;
	}

    var aa = ts_getInnerText(a.cells[SORT_COLUMN_INDEX]);
    var bb = ts_getInnerText(b.cells[SORT_COLUMN_INDEX]);

	if(!aa) {
		if(!bb)
			return 0;
		return 1;
	} else {
		if(!bb)
			return -1;
	}

	if(ts_isNumeric(aa)) {
		if(ts_isNumeric(bb)) {
			// both are numeric
		    aa = parseFloat(aa);
		    bb = parseFloat(bb);
		    return aa-bb;
		} else {
			return -1;
		}
	} else {
		if(ts_isNumeric(bb)) {
			return 1;
		} else {
			// neither is numeric
			aa = aa.toLowerCase();
			bb = bb.toLowerCase();
		    if (aa==bb) return 0;
		    if (aa<bb) return -1;
		    return 1;
		}
	}
}

function ts_sort_default(a,b) {
    aa = ts_getInnerText(a.cells[SORT_COLUMN_INDEX]);
    bb = ts_getInnerText(b.cells[SORT_COLUMN_INDEX]);
    if (aa==bb) return 0;
    if (aa<bb) return -1;
    return 1;
}

function ts_isNumeric(itm) {
    return itm.match(/^[\d\.]+$/);
}

function addEvent(elm, evType, fn, useCapture)
// addEvent and removeEvent
// cross-browser event handling for IE5+,  NS6 and Mozilla
// By Scott Andrew
{
  if (elm.addEventListener){
    elm.addEventListener(evType, fn, useCapture);
    return true;
  } else if (elm.attachEvent){
    var r = elm.attachEvent("on"+evType, fn);
    return r;
  } else {
    alert("Handler could not be removed");
  }
}

function isDefined(variable) {
    return (typeof(window[variable]) == "undefined")?  false: true;
}
// Ver .91 Feb 21 1998
//////////////////////////////////////////////////////////////
//
//	Copyright 1998 Jeremie
//	Free for public non-commercial use and modification
//	as long as this header is kept intact and unmodified.
//	Please see http://www.jeremie.com for more information
//	or email jer@jeremie.com with questions/suggestions.
//
///////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////
////////// Simple XML Processing Library //////////////////////
///////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////
////   Fully complies to the XML 1.0 spec
////   as a well-formed processor, with the
////   exception of full error reporting and
////   the document type declaration(and it's
////   related features, internal entities, etc).
///////////////////////////////////////////////////////////////


/////////////////////////
//// the object constructors for the hybrid DOM

function _element()
{
	this.type = "element";
	this.name = new String();
	this.attributes = new Attributes();
	this.contents = new Array();
	this.uid = _Xparse_count++;
	_Xparse_index[this.uid]=this;

	this.getElementsByTagName = function(tagName) {
		var resultElems = new Array();
		if(this.contents==null)
			return resultsElems;
		for(var contentIx=0; contentIx<this.contents.length; ++contentIx) {
			var elem = this.contents[contentIx];
			if(elem!=null && elem.name!=null && elem.name==tagName) {
				resultElems[resultElems.length] = elem;
			}
		}
		return resultElems;
	}

	this.firstChild = function() {
		if(this.contents==null || this.contents.length==0)
			return null;
		return this.contents[0];
	}
/*
	this.nodeValue = function() {
		if(this.contents==null)
			return null;
		var result = '';
		for(var contentIx=0; contentIx<this.contents.length; ++contentIx) {
			result += this.contents[contentIx];
		}
		if(result=='')
			return null;
		return result;
	}
*/
//	this.value = function() {
//		return contents;
//	}
}

function Attributes() {
	this.attributeArr = new Array();

	this.getNamedItem = function(attribName) {
		var val = this.attributeArr[attribName];
		if(val==null)
			return null;
		var attr = new Attribute();
		attr.value = val;
		return attr;
	}
}
function Attribute() {
	this.value = null;
}


function _chardata() {
	this.type = "chardata";
	this.value = new String();
}

function _pi() {
	this.type = "pi";
	this.value = new String();
}

function _comment() {
	this.type = "comment";
	this.value = new String();
}

// an internal fragment that is passed between functions
function _frag() {
	this.str = new String();
	this.ary = new Array();
	this.end = new String();
}

/////////////////////////


// global vars to track element UID's for the index
var _Xparse_count = 0;
var _Xparse_index = new Array();


/////////////////////////
//// Main public function that is called to
//// parse the XML string and return a root element object

function Xparse(src) {
	var frag = new _frag();

	// remove bad \r characters and the prolog
	frag.str = _prolog(src);

	// create a root element to contain the document
	var root = new _element();
	root.name="ROOT";

	// main recursive function to process the xml
	frag = _compile(frag);

	// all done, lets return the root element + index + document
	root.contents = frag.ary;
	root.index = _Xparse_index;
	_Xparse_index = new Array();
	return root;
}

/////////////////////////


/////////////////////////
//// transforms raw text input into a multilevel array
function _compile(frag) {
	// keep circling and eating the str
	while(1) {
		// when the str is empty, return the fragment
		if(frag.str.length == 0) {
			return frag;
		}

		var TagStart = frag.str.indexOf("<");

		if(TagStart != 0) {
			// theres a chunk of characters here, store it and go on
			var thisary = frag.ary.length;
			frag.ary[thisary] = new _chardata();
			if(TagStart == -1) {
				frag.ary[thisary].value = _entity(frag.str);
				frag.str = "";
			} else {
				frag.ary[thisary].value = _entity(frag.str.substring(0,TagStart));
				frag.str = frag.str.substring(TagStart,frag.str.length);
			}
		} else {
			// determine what the next section is, and process it
			if(frag.str.substring(1,2) == "?") {
				frag = _tag_pi(frag);
			} else {
				if(frag.str.substring(1,4) == "!--") {
					frag = _tag_comment(frag);
				} else {
					if(frag.str.substring(1,9) == "![CDATA[") {
						frag = _tag_cdata(frag);
					} else {
						if(frag.str.substring(1,frag.end.length + 3) == "/" + frag.end + ">" || _strip(frag.str.substring(1,frag.end.length + 3)) == "/" + frag.end) {
							// found the end of the current tag, end the recursive process and return
							frag.str = frag.str.substring(frag.end.length + 3,frag.str.length);
							frag.end = "";
							return frag;
						} else {
							frag = _tag_element(frag);
						}
					}
				}
			}

		}
	}
	return "";
}
///////////////////////


///////////////////////
//// functions to process different tags

function _tag_element(frag) {
	// initialize some temporary variables for manipulating the tag
	var close = frag.str.indexOf(">");
	var empty = (frag.str.substring(close - 1,close) == "/");
	if(empty) {
		close -= 1;
	}

	// split up the name and attributes
	var starttag = _normalize(frag.str.substring(1,close));
	var nextspace = starttag.indexOf(" ");
	var attribs = new String();
	var name = new String();
	if(nextspace != -1) {
		name = starttag.substring(0,nextspace);
		attribs = starttag.substring(nextspace + 1,starttag.length);
	} else {
		name = starttag;
	}

	var thisary = frag.ary.length;
	frag.ary[thisary] = new _element();
	frag.ary[thisary].name = _strip(name);
	if(attribs.length > 0) {
		frag.ary[thisary].attributes.attributeArr = _attribution(attribs);
	}
	if(!empty) {
		// !!!! important,
		// take the contents of the tag and parse them
		var contents = new _frag();
		contents.str = frag.str.substring(close + 1,frag.str.length);
		contents.end = name;
		contents = _compile(contents);
		frag.ary[thisary].contents = contents.ary;
		frag.str = contents.str;
	} else {
		frag.str = frag.str.substring(close + 2,frag.str.length);
	}
	return frag;
}

function _tag_pi(frag) {
	var close = frag.str.indexOf("?>");
	var val = frag.str.substring(2,close);
	var thisary = frag.ary.length;
	frag.ary[thisary] = new _pi();
	frag.ary[thisary].value = val;
	frag.str = frag.str.substring(close + 2,frag.str.length);
	return frag;
}

function _tag_comment(frag) {
	var close = frag.str.indexOf("-->");
	var val = frag.str.substring(4,close);
	var thisary = frag.ary.length;
	frag.ary[thisary] = new _comment();
	frag.ary[thisary].value = val;
	frag.str = frag.str.substring(close + 3,frag.str.length);
	return frag;
}

function _tag_cdata(frag) {
	var close = frag.str.indexOf("]]>");
	var val = frag.str.substring(9,close);
	var thisary = frag.ary.length;
	frag.ary[thisary] = new _chardata();
	frag.ary[thisary].value = val;
	frag.str = frag.str.substring(close + 3,frag.str.length);
	return frag;
}

/////////////////////////


//////////////////
//// util for element attribute parsing
//// returns an array of all of the keys = values
function _attribution(str) {
	var all = new Array();
	while(1) {
		var eq = str.indexOf("=");
		if(str.length == 0 || eq == -1) {
			return all;
		}

		var id1 = str.indexOf("\'");
		var id2 = str.indexOf("\"");
		var ids = new Number();
		var id = new String();
		if((id1 < id2 && id1 != -1) || id2 == -1) {
			ids = id1;
			id = "\'";
		}
		if((id2 < id1 || id1 == -1) && id2 != -1) {
			ids = id2;
			id = "\"";
		}
		var nextid = str.indexOf(id,ids + 1);
		var val = str.substring(ids + 1,nextid);

		var name = _strip(str.substring(0,eq));
		all[name] = _entity(val);
		str = str.substring(nextid + 1,str.length);
	}
	return "";
}
////////////////////


//////////////////////
//// util to remove \r characters from input string
//// and return xml string without a prolog
function _prolog(str)
{
	var A = new Array();

	A = str.split("\r\n");
	str = A.join("\n");
	A = str.split("\r");
	str = A.join("\n");

	var start = str.indexOf("<");
	if(str.substring(start,start + 3) == "<?x" || str.substring(start,start + 3) == "<?X" ) {
		var close = str.indexOf("?>");
		str = str.substring(close + 2,str.length);
	}
	var start = str.indexOf("<!DOCTYPE");
	if(start != -1) {
		var close = str.indexOf(">",start) + 1;
		var dp = str.indexOf("[",start);
		if(dp < close && dp != -1) {
			close = str.indexOf("]>",start) + 2;
		}
		str = str.substring(close,str.length);
	}
	return str;
}
//////////////////


//////////////////////
//// util to remove white characters from input string
function _strip(str) {
	var A = new Array();

	A = str.split("\n");
	str = A.join("");
	A = str.split(" ");
	str = A.join("");
	A = str.split("\t");
	str = A.join("");

	return str;
}
//////////////////


//////////////////////
//// util to replace white characters in input string
function _normalize(str) {
	var A = new Array();

	A = str.split("\n");
	str = A.join(" ");
	A = str.split("\t");
	str = A.join(" ");

	return str;
}
//////////////////


//////////////////////
//// util to replace internal entities in input string
function _entity(str) {
	var A = new Array();

	A = str.split("&lt;");
	str = A.join("<");
	A = str.split("&gt;");
	str = A.join(">");
	A = str.split("&quot;");
	str = A.join("\"");
	A = str.split("&apos;");
	str = A.join("\'");
	A = str.split("&amp;");
	str = A.join("&");

	return str;
}
//////////////////
/*
This script created by Geoff Fortytwo and Chandra Patni. This was a surprisingly tricky thing to make work properly on all browsers.
*/
// convert the specified XML string into DOM XML.
function strToXml(xmlStr) {
	return Xparse(xmlStr);
}

function doCallbackWithXml(varName,callback,callbackParam) {
	var xml = strToXml(eval(varName));

	if(callbackParam==null)
		callback(xml);
	else
		callback(xml,callbackParam);
}

// Used by to handle state changes of script element.
function CheckAgain(happyScript,varName,callback,callbackParam) {
	this.happyScript=happyScript;
	this.varName=varName;
	this.callback=callback;
	this.callbackParam=callbackParam;
	this.isDone=false;

	this.doCheckMoz = function() {
//		alert("doCheckMoz!");
		doCallbackWithXml(this.varName,this.callback,this.callbackParam);
		this.isDone = true;
	}

	this.doCheckIE = function() {
//		alert("dumb varName="+ this.varName);
		if (this.happyScript.readyState == "loaded" || this.happyScript.readyState == "complete") {
			doCallbackWithXml(this.varName,this.callback,this.callbackParam);
			happyScript.onreadystatechange = null;
			this.isDone = true;
		}
	}
}

function getDataFromServer(id, url, varName, callback, callbackParam) {

    // Fetch the element pointed to by the id. If it exists, we destroy it so we can create a new one.
    var oScript = document.getElementById(id);

    // Point at the script tag, if it exists
    var head = document.getElementsByTagName("head").item(0);
     // Destroy the tag, if it exists
    if(oScript) {
	    // Destroy object
	    head.removeChild(oScript);
    }
    // Create the new script tag
    oScript = document.createElement("script");

	var checkAgain = new CheckAgain(oScript,varName,callback,callbackParam);

    var hasEvent = true;
    if(window.opera) {
		hasEvent = false;
    } else if(window.XMLHttpRequest) {
	    // Mozilla/Safari
	    oScript.addEventListener("load",
		    function() {checkAgain.doCheckMoz();}
	    	,false);
    } else if(window.ActiveXObject) {
	    // IE
		oScript.onreadystatechange =
			function() {checkAgain.doCheckIE();}
			;
	} else {
		hasEvent = false;
	}

    // Setup the src attribute of the script tag
	wholeurl = url;
    oScript.setAttribute("src", wholeurl);

    oScript.setAttribute("type","text/javascript");

    // Set the id attribute of the script tag
    oScript.setAttribute("id",id);

    // Create the new script tag which causes the proxy to be called
    head.appendChild(oScript);

    if(!hasEvent)
   	    checkAgain.doCheckMoz();
}
var globalUrlPrefix = 'http://knowlist.com';

function getUrlParam(name){
	var href = window.location.href;

	var pIx = href.indexOf('&'+ name +'=');
	if(pIx < 0)
		pIx = href.indexOf('?'+ name +'=');
	if(pIx < 0)
		return null;

	var pIx2 = href.indexOf('&',pIx+1);
	if(pIx2 < 0)
		return href.substring(pIx+name.length+2);
	else
		return href.substring(pIx+name.length+2,pIx2);
}

// Returns true if the specified Element has the specified class name.
function hasClassName(elem, name) {
	var regex = new RegExp('\\b' + name + '\\b');
	return elem.className && elem.className.search(regex)>=0;
}

function xmlHttpPost(strURL, strSubmit, resultsCallback) {
    var xmlHttpReq = false;
    // Mozilla/Safari
    if (window.XMLHttpRequest) {
		xmlHttpReq = new XMLHttpRequest();
		if(xmlHttpReq.overrideMimeType)
			xmlHttpReq.overrideMimeType('text/xml');
    }
    // IE
    else if (window.ActiveXObject) {
		xmlHttpReq = new ActiveXObject("Microsoft.XMLHTTP");
    }

	xmlHttpReq.open('POST', strURL, true);
    xmlHttpReq.setRequestHeader('Content-Type','application/x-www-form-urlencoded');
    xmlHttpReq.onreadystatechange = function() {
			if(xmlHttpReq.readyState == 4) {
				eval(resultsCallback + 'xmlHttpReq);');
			}
        }
    xmlHttpReq.send(strSubmit);
}

/*
function animateLoadingMsgStart(divName,numDots) {
	var stopVarName = 'stopMsg_'+divName;
	eval(''+ stopVarName +' = null');
	animateLoadingMsg(stopVarName,divName,numDots,0);
}
// Call this with a callback. When the msg stops then the callback will be called.
function animateLoadingMsgStop(divName,callback) {
	var stopVarName = 'stopMsg_'+divName;
	eval(stopVarName) = callback;
}
function animateLoadingMsg(stopVarName,divName,numDots,highlightIx) {
	var msg = 'loading ';
	for(var i=0; i<numDots; ++i) {
		if(i==highlightIx)
			msg += '<b>.</b>';
		else
			msg += '.';
	}

	++highlightIx;
	if(highlightIx>=numDots)
		highlightIx = 0;

	var callback = eval(stopVarName);
	// If no callback was put at the stopVar then continue animating. Otherwise execute the callback and stop animating.
	if(callback==null) {
		document.getElementById(divName).innerHTML = msg;
		setTimeout('animateLoadingMsg(\''+ stopVarName +'\',\''+ divName +'\',\''+ numDots +'\',\''+ highlightIx +'\')',500);
	} else {
		eval(callback);
	}
}
*/

function ListContext() {
/*
  // properties
  this.id = contextID;
  this.busy = true;
  this.callback = null;
  this.container = contextCreateContainer( contextID );

  // methods
  this.GET = contextGET;
  this.POST = contextPOST;
  this.getPayload = contextGetPayload;
  this.setVisibility = contextSetVisibility;
*/
}


function outputList(viewerIsOwner,resultDiv,ownerName,recreateParams,page,maxRows,visibleCells) {
	var scriptVarName = resultDiv+'__var';
	var listTableId = resultDiv +'__table';
	if(!page)
		page = 0;
	var url = globalUrlPrefix +'/know/userobjprof/ShowList?ownerName='+ ownerName +'&page='+ page +'&maxRows='+ maxRows
		+ '&'+ recreateParams
//		+'&lid='+ listType
//		+'&objt='+ objType
		+'&disp=js'
		+'&jsVar='+ scriptVarName
		;
	if(visibleCells!=null && visibleCells!='')
		url += '&cols='+ visibleCells;
//document.getElementById('log').innerHTML += '\nURL='+ url;

	var listContext = new ListContext();
	listContext.viewerIsOwner=viewerIsOwner;
	listContext.resultDiv=resultDiv;
	listContext.listTableId=listTableId;
	listContext.ownerName=ownerName;
//	listContext.listType=listType;
//	listContext.objType=objType;
	listContext.recreateParams=recreateParams;
	listContext.page=page;
	listContext.maxRows=maxRows;

//	var resultsCallback = 'outputListXml('+ viewerIsOwner +',"'+ resultDiv +'","'+ listTableId +'","'+ ownerName +'",'+ listType +','+ objType +','+ page +','+ maxRows +',';

//	getDataFromServer(resultDiv+'__scriptElem',url,scriptVarName,resultsCallback);
	getDataFromServer(resultDiv+'__scriptElem',url,scriptVarName,outputListXml,listContext);
}

/*
function outputList(viewerIsOwner,resultDiv,ownerName,listType,objType,page,maxRows) {
//	document.getElementById(resultDiv).innerHTML += 'loading ...';
//	animateLoadingMsgStart(resultDiv,3);

	var listTableId = resultDiv +'__table';
	if(!page)
		page = 0;
	var url = globalUrlPrefix +'/know/userobjprof/ShowList?ownerName='+ ownerName +'&lid='+ listType +'&objt='+ objType +'&page='+ page +'&maxRows='+ maxRows +'&disp=xml';

	var resultsCallback = 'outputListXmlHttpReq('+ viewerIsOwner +',"'+ resultDiv +'","'+ listTableId +'","'+ ownerName +'",'+ listType +','+ objType +','+ page +','+ maxRows +',';
//	resultsCallback = 'animateLoadingMsgStop("'+ resultDiv +'","'+ resultsCallback.replace(/"/g,'\\"');
	// +'")';
//document.getElementById('log').innerHTML += 'resultsCallback='+ resultsCallback;

	xmlHttpPost(url,'',resultsCallback);
}
*/
function getDisplayCall(listContext,page) {
	return 'outputList('+ listContext.viewerIsOwner +',\''+ listContext.resultDiv +'\',\''+ listContext.ownerName +'\',\''+ listContext.recreateParams +'\','+ page +','+ listContext.maxRows +')';
}

//function outputListXmlStopMsg(resultDiv,listTableId,ownerName,listType,objType,page,maxRows,xmlHttpReq) {
//	var resultsCallback = 'outputListXml("'+ resultDiv +'","'+ listTableId +'","'+ ownerName +'","'+ listType +'","'+ objType +'","'+ page +'","'+ maxRows +'","'+ xmlHttpReq +;
//}
//function outputListXmlHttpReq(viewerIsOwner,resultDiv,listTableId,ownerName,listType,objType,page,maxRows,xmlHttpReq) {
//	outputListXml(viewerIsOwner,resultDiv,listTableId,ownerName,listType,objType,page,maxRows,xmlHttpReq.responseXML);
//}
//function outputListXmlWithContextERROR() {
//	alert('outputListXmlWithContextERROR called!');
//}

/* In order to make this work with the javascript parser, firstChild is a function instead of a variable,
and value is used instead of nodeValue on elements to get their text. */
function outputListXml(xml,listContext) {
	if(listContext==null)
		return;
	var viewerIsOwner = listContext.viewerIsOwner;
	var resultDiv = listContext.resultDiv;
	var listTableId = listContext.listTableId;
	var ownerName = listContext.ownerName;
	var page = listContext.page;
	var maxRows = listContext.maxRows;
	var recreateParams = listContext.recreateParams;

//	var listType = listContext.listType;
//	var objType = listContext.objType;


	var listHtml = null;

	if(xml==null)
		return;

	var list = xml.getElementsByTagName('list')[0];
	var urlPrefix = list.attributes.getNamedItem('urlPrefix').value;
	var pageInfo = list.getElementsByTagName('pageInfo')[0];
	if(pageInfo==null)
		return;

	var title = pageInfo.getElementsByTagName('title')[0].firstChild().value;
//alert('title='+ title);
	var paging = pageInfo.getElementsByTagName('paging')[0];
	var numObjsThisPage = paging.attributes.getNamedItem('numObjsThisPage');
	if(numObjsThisPage!=null)
		numObjsThisPage = numObjsThisPage.value;
	else
		numObjsThisPage = 0;

	var curMin = page*maxRows + 1;
	var curMax = page*maxRows + 1*numObjsThisPage;
	var definitelyNumProfs = paging.attributes.getNamedItem('definitelyNumProfs');
	var atLeastNumProfs = paging.attributes.getNamedItem('atLeastNumProfs');


	var displayFirst = null;
	var displayPrev = null;
	if(page > 0) {
		displayFirst = getDisplayCall(listContext,0);
		displayPrev = getDisplayCall(listContext,(page-1));
	}
	var displayNext = paging.attributes.getNamedItem('hasNext');
	var displayLast = null;
	if(displayNext!=null) {
		displayNext = getDisplayCall(listContext,(page- -1));
//This should be the real last page.
//		displayLast = getDisplayCall(listContext,10000);
	}
	var numPerPage = paging.attributes.getNamedItem('numPerPage');

	var objsElem = list.getElementsByTagName('objs')[0];
	var objs = objsElem.getElementsByTagName('obj');
	if(objs!=null && objs.length > 0) {
		listHtml = '<table id="'+ listTableId +'" class="sortable '+ listTableId +'" cellspacing=0 cellpadding=0>';


		// output header row
		var columns = pageInfo.getElementsByTagName('column');
		if(columns!=null && columns.length > 0) {
			listHtml += '<tr class="rowH">';
			// If necessary, add additional headers for owner stuff.
			if(viewerIsOwner)
				listHtml += '<th><br></th>'
					+'<th><br></th>';

			for(var colIx=0; colIx<columns.length; ++colIx) {
				var column = columns[colIx];
				var header = column.attributes.getNamedItem('header');
				if(header!=null)
					header = header.value;
				else
					header = '';

				listHtml += '<th>'+ header +'</th>';
			}
		}

		// output data rows
		for(var objIx=0; objIx<objs.length; ++objIx) {
			var obj = objs[objIx];
			var profId = obj.attributes.getNamedItem('pid').value;
			var rows = obj.getElementsByTagName('row');
			if(rows!=null && rows.length > 0) {
				for(var rowIx=0; rowIx<rows.length; ++rowIx) {
					var row = rows[rowIx];

					var cells = row.getElementsByTagName('cell');
					if(cells!=null && cells.length > 0) {
						var rowClass;
						var rowTop = false;
						// Using the row class, indicate whether this row is the top, inner, or bottom of the rows for a single object.
						if(rowIx==0) {
							rowTop = true;
							// assign rowTop and rowBottom if this is the only row.
							if(rows.length==1)
								rowClass = "rowTop rowBottom";
							else
								rowClass = "rowTop";
						} else if(rowIx==rows.length-1) {
							rowClass = "rowBottom";
						} else {
							rowClass = "rowInner";
						}

						var additionalColspan = 0;
						listHtml += '<tr class="'+ rowClass +'">';

						if(viewerIsOwner) {
							additionalColspan += 2;
							if(rowTop) {
								var totalRecreateParams = 'ownerName='+ ownerName +'&'+ recreateParams;
								listHtml +=
									'<td><a href="'+ urlPrefix +'/know/list/user/edit/userobjprof/EditUserObjProf.do?prof='+ profId +'&recreateParams='+ escape(totalRecreateParams) +'&page='+ page +'">edit</a>'
									+'<td><INPUT type=checkbox name="prof" value="'+ profId +'">';
							} else {
								listhtml +=
									'<td><br>'
									+'<td><br>';
							}
						}

						for(var cellIx=0; cellIx<cells.length; ++cellIx) {
							var cell = cells[cellIx];

							var colspan = cell.attributes.getNamedItem('colspan');
							if(colspan!=null)
								colspan = ' colspan="'+ (additionalColspan+colspan.value) +'"';
							else
								colspan = '';

							var styleClass = cell.attributes.getNamedItem('class');
							if(styleClass!=null)
								styleClass = ' class="'+ styleClass.value +'"';
							else
								styleClass = '';

							var url = cell.attributes.getNamedItem('url');

							listHtml += '<td'+ colspan + styleClass +'>';

							// This removes the xml encoding so that html will be rendered.
//if(cell.firstChild()!=null)
//	document.getElementById('log').innerHTML += '<br>'+ cell.firstChild().value;
//	document.getElementById('log').innerHTML += '<br>'+ cell.firstChild().nodeValue();
							if(cell.firstChild()!=null && cell.firstChild().value!=null) {
								if(url!=null)
									listHtml += '<a href="'+ urlPrefix + url.value +'">';
								listHtml += cell.firstChild().value.replace(/&lt;/g,'<').replace(/&gt;/g,'>').replace(/&apos;/g,'\'').replace(/&quot;/g,'"');
								if(url!=null)
									listHtml += '</a>';
							} else {
								listHtml += '<br>';
							}
						}
					}
				}
			}

		}
		listHtml += '</table>';
	}


	var formBegin = '';
	var formEnd = '';
	var navButtonsHtml = '';
	if(listHtml==null) {
		listHtml = '<div name="listIsEmpty">';
		if(!title || title=='')
			listHtml += 'This list';
		else
			listHtml += title;
		listHtml += ' is empty.</div>';
	} else {
		formBegin = '<form action="'+ urlPrefix +'/know/user/MixedActivity" method=get>'
//			+'<input type=hidden name="objt" VALUE="'+ objType +'">'
			+'<input type=hidden NAME="activity" VALUE="">'
			;

		formBegin += '<input type=hidden name="recreateParams" VALUE="'+ recreateParams +'">';
//		if(listType!=null)
//			formBegin += '<input type=hidden name="lid" VALUE="'+ listType +'">';
		formEnd = '</form>';

		navButtonsHtml = '<div class="navButtons">';
		if(displayPrev!=null) {
			navButtonsHtml += '<input type=button class=navButton onclick="'+ displayFirst +'" value="&lt;&lt;">&nbsp;&nbsp;';
			navButtonsHtml += '<input type=button class=navButton onclick="'+ displayPrev +'" value="&lt;">';
		} else {
			navButtonsHtml += '<input type=button class=navButton disabled value="&lt;&lt;">&nbsp;&nbsp;';
			navButtonsHtml += '<input type=button class=navButton disabled value="&lt;">';
//			navButtonsHtml += '<input type=button class=navButton disabled value="&lt; previous '+ numPerPage.value +'">';
		}

		navButtonsHtml += '&nbsp;&nbsp;';

		if(displayNext!=null) {
			navButtonsHtml += '<input type=button class=navButton onclick="'+ displayNext +'" value="&gt;">&nbsp;&nbsp;';
//			navButtonsHtml += '<input type=button class=navButton onclick="'+ displayLast +'" value="&gt;&gt;">';
		} else {
			navButtonsHtml += '<input type=button class=navButton disabled value="&gt;">&nbsp;&nbsp;';
//			navButtonsHtml += '<input type=button class=navButton disabled value="&gt;&gt;">';
		}

		navButtonsHtml += '&nbsp;&nbsp;'+ curMin +'-'+ curMax;
		if(definitelyNumProfs!=null)
			navButtonsHtml += ' of '+ definitelyNumProfs.value;
		else
			navButtonsHtml += ' of at least '+ atLeastNumProfs.value;


		if(viewerIsOwner) {
			navButtonsHtml += '&nbsp;&nbsp;&nbsp;&nbsp;<input type=button VALUE="Remove Checked" onclick="form.elements[\'activity\'].value=\'Remove\'; form.submit();">';
		}

		navButtonsHtml += '</div>';
	}

	document.getElementById(resultDiv).innerHTML = formBegin /*+ navButtonsHtml*/ + listHtml + navButtonsHtml + formEnd;

	ts_makeSortable( document.getElementById(listTableId) );

	if(isDefined('ts_callback_formatTable'))
		ts_callback_formatTable( document.getElementById(listTableId) );
}

function ts_callback_formatTable(tab) {
	if(tab==null || tab.rows==null)
		return;
	var rowNum = 0;
	var innerRowOffset = 0;
	var curRow;
	for(var rowIx=0; rowIx<tab.rows.length; ++rowIx) {
		var row = tab.rows[rowIx];

		// First save all class names used for positioning so we can access them after we change the class name for formatting reasons.
		// If a row is part of a group of rows then the first row in that group will have a subRows attribute which is an array of all the other rows in the group.
		// All rows that are not the first in subrow groups will be marked with the isSubRow=true attribute.
		{
			if(hasClassName(row,"rowH"))
				row.rowH = true;
			if(hasClassName(row,"rowF"))
				row.rowF = true;

			row.innerRowPos = '';
			if(hasClassName(row,"rowTop")) {
				curRow = row;
				row.innerRowPos = row.rowTop = 'rowTop';
			}
			if(hasClassName(row,"rowInner")) {
				row.rowInner = 'rowInner';
				if(!row.rowTop) {
					row.isSubRow = true;
					if(!curRow.subRows)
						curRow.subRows = new Array();
					curRow.subRows[curRow.subRows.length] = row;
				}
			}
			if(hasClassName(row,"rowBottom")) {
				row.rowBottom = 'rowBottom';
				if(row.rowTop) {
					row.innerRowPos += ' '+ row.rowBottom;
				} else {
					row.innerRowPos = row.rowBottom;

					row.isSubRow = true;
					if(!curRow.subRows)
						curRow.subRows = new Array();
					curRow.subRows[curRow.subRows.length] = row;
				}
			}
		}

		if(row.rowTop) {
			++rowNum;
			innerRowOffset = 0;
		} else {
			++innerRowOffset;
		}

		// change the row classes for all rows except the header and footer.
		if(!row.rowH && !row.rowF) {
			// row0 or row1 style classes.
			row.className = 'row'+ (rowNum%2);
			if(row.innerRowPos)
				row.className += ' '+ row.innerRowPos;
		}
	}

}
