/**
 *  util.js
 *
 *  Miscellaneous common utility functions.
 *
 *  History:
 *	2010/08/28	try to get rid of undefined in tagToString
 *	2010/10/17	add definition of javascript String.trim
 *			return value of getText(HtmlElement) is trimmed
 *			finally fixed tagToString!
 *	2010/11/14	if required trim trailing digits off element names
 *			to obtain help division name
 *	2010/11/20	move function changeDiv here
 *	2010/11/24	correct style setting for left and top
 *	2011/03/08	add keyboard shortcuts for buttons on
 *			QueryDetail9999.html pages
 *	2011/03/27	set default location for cities different from
 *			towns and townships
 *	2011/04/10	functions specific to Canadian Census queries
 *			moved to database/QueryDetail.js
 *	2011/04/22	put try catch around code that IE7 screws up
 *			change addOption to comply with IE7
 *	2011/05/18	display abbreviations in 3 columns to compress help
 *			balloons
 *	2011/09/25	improve comments
 *			add getOffsetRight function
 *	2011/10/15	add support for popping up help balloon in response
 *			to holding the mouse over an input element
 *	2011/10/24	try to pop up help for the dynamically added
 *			<button id='rightTop'> and pages where there is
 *			no page specific onload method.
 *	2011/11/10	take down old balloon before displaying new balloon
 *			if it was not taken down previously
 *	2011/11/12	change selectOptByValue to return selected option
 *	2012/01/02	suppress warning message for no parent in displayHelp
 *	2012/01/07	add functions (not yet used) for selecting table cells
 *	2012/01/16	add method getParmsFromXml
 *	2012/01/24	add methods popupLoading and hideLoading to manage
 *			an AJAX request busy indicator
 *			Move defaultOnLoad to default.js
 *
 *  Copyright &copy; 2012 James A. Cobban
 **/

//  loaddiv
//
//  This division contains the "loading" indicator when a script is
//  waiting for an AJAX response from the server.
var	loaddiv	= null;

//  loadelt
//
//  This is the element with which the "loading" indicator is associated.
var	loadelt	= null;

/**
 *  getArgs
 *
 *  Extract the arguments from the location URL.
 **/
function getArgs()
{
    var	args	= new Object();
    var query	= location.search.substring(1);	// search excluding '?'
    var	pairs	= query.split("&");		// split on ampersands
    for (var i = 0; i < pairs.length; i++)
    {		// loop through all pairs
	var	pos	= pairs[i].indexOf('=');
	if (pos == -1)
	    continue;
	var	name	= pairs[i].substring(0, pos);
	var	value	= pairs[i].substring(pos + 1);
	value		= decodeURIComponent(value);
	args[name]	= value;
    }		// loop through all pairs
    return args;
}		// getArgs

// make arguments from the search portion of the URL available globally
var	args	= getArgs();

// the last help division displayed
var	helpDiv		= null;

// the element for which help is to be displayed when the timer pops
var	helpElt		= null;

// timer to control when help is displayed as a result of mouse over events
var	helpDelayTimer	= null;

/**
 *  addOption
 *
 *  Add an Option object to a Select statement.
 *
 *  Parameters:
 *	select	the Select statement to add to
 *	text	the text to display to the user
 *	value	the value to pass on to the server
 *
 *  Returns:
 *	The new Option object
 *
 **/
function addOption(select, text, value)
{
    // create a new HTML Option object and add it to the Select
    var	newOption	= document.createElement("option");
    select.appendChild(newOption);		
    newOption.text	= text;		// ie7 demands done after append
    newOption.value	= value;
    return newOption;
}		//	addOption

/**
 *  createNamedElement
 *
 *  IE<9 does not permit modifying the name attribute of an input element
 *  after it is created.
 *
 *  Parameters:
 *	type	the type of element to create
 *	name	the name to set
 *
 *  Returns:
 *	The new Input object
 *
 **/
function createNamedElement(type, name)
{
    var element = null;
    // Try the IE way; this fails on standards-compliant browsers
    try {
	element = document.createElement('<'+type+' name="'+name+'">');
    } catch (e) {
    }
    if (!element || element.nodeName != type.toUpperCase()) {
      // Non-IE browser; use canonical method to create named element
      element		= document.createElement(type);
      element.name	= name;
    }
    return element;
}		//	createNamedElement

/**
 *  getElt
 *
 *  This method finds the first instance of a child element node
 *  that matches the supplied tag name.
 *
 *  Parameters:
 *	parent	the parent node within which to search for a child
 *	tagName	the name of the tag to search for.  By convention
 *		HTML tag names are specified in upper case.
 *
 *  Returns:
 *	The matching element node.
 **/
function getElt(parent, tagName)
{
    if (parent)
    {		// valid parent
	for (var i = 0; i < parent.childNodes.length; i++)
	{
	    var	cNode	= parent.childNodes[i];
	    if ((cNode.nodeType == 1) &&
		(cNode.nodeName.toUpperCase() == tagName.toUpperCase()))
		    return cNode;
	}		// loop through children of parent
    }		// valid parent
    else
    {		// no parent
	alert("util.js: getElt(null,'" + tagName + "')");
    }		// no parent
    return null;
}		// getElt

/**
 *  getEltId
 *
 *  This method finds the first instance of a child node
 *  that matches the supplied tag name and id value.
 *
 *  Parameters:
 *	parent	the parent node within which to search for a child
 *	tagName	the name of the tag to search for.  By convention
 *		HTML tag names are specified in upper case.
 *	idValue	the value of the id attribute to search for
 *
 *  Returns:
 *	The matching element node.
 **/
function getEltId(parent, tagName, idValue)
{
    if (parent)
    {		// valid parent
	for (var i = 0; i < parent.childNodes.length; i++)
	{
	    var	cNode	= parent.childNodes[i];
	    if ((cNode.nodeType == 1) 
		&& (cNode.nodeName == tagName)
		&& (cNode.id == idValue))
	    {
		return cNode;
	    }
	}		// loop through children of parent
    }		// valid parent
    else
    {
	alert("util.js: getEltId(null,\"" + tagName + "\", \"" + idValue + "\")");
	return null;
    }

    return null;
}		// getEltId

String.prototype.trim = function() { return this.replace(/^\s+|\s+$/g, ''); };

/**
 *  getText
 *
 *  This method accumulates the text nodes under a specified node.
 *
 *  Parameters:
 *	element	the parent node possibly containing text nodes as children
 *
 *  Returns:
 *	The accumulated text from the text nodes.
 **/
function getText(element)
{
    var		text	= "";
    if (element.childNodes === undefined)
	alert("util.js: getText: parameter is not a document element");
    for (var j = 0; j < element.childNodes.length; ++j)
    {
	var	sub	= element.childNodes[j];
	if ((sub.nodeType == 3) && (sub.nodeValue))
	{		// text node
	    if (text.length > 0)
		text	+= " ";
	    text 		+= sub.nodeValue;
	}		// concatenate all text elements
    }		// loop through children of source node
    return text.trim();
}		// getText 

/**
 *  copyChildren
 *
 *  This method copies all of the children of one node and adds
 *  them to another node.
 *
 *  Parameters:
 *	fromNode	the node to copy from
 *	toNode		the node to copy to
 *
 **/
function copyChildren(fromNode, toNode)
{
    for (var j = 0; j < fromNode.childNodes.length; ++j)
    {
	var	sub	= parent.childNodes[j];
	var	newNode	= sub.cloneNode(true);
	toNode.appendChild(newNode);
    }
}		// copyChildren

/**
 *  tagToString
 *
 *  Extract a the string representing the XML or HTML corresponding
 *  to the specified node and its children.
 *
 *  Parameters:
 *	node	the top of the tree of nodes to be interpreted
 *
 *  Returns:
 *	String representation of the node and its children.
 **/
function tagToString(node)
{
    if (node === null)
	return "null";
    else
    if (node === undefined)
	return "undefined";

    var	retval;
    if (node.nodeType == 1)
    {		// element
	retval	= "<" + node.nodeName;
    }
    else
    if (node.nodeType == 3)
    {		// text
    }
    else
    {
	retval	= "<nodeType=" + node.nodeType;
    }

    if (node.attributes)	
    {
	for (var i = 0; i < node.attributes.length; i++)
	{
	    var attrname	= node.attributes[i].name;
	    if (attrname.substr(0,2) == "on")
		continue;		// ignore event methods			
	    if (attrname.substr(0,4) == "aria")
		continue;		// ignore M$ aria attributes
	    var	value	= node.attributes[i].value;
	    if (value.length > 32)
		value	= value.substr(0,31) + "...";
	    retval	+= " " + node.attributes[i].name + "=\'" 
		+ value + "\'";
	}
    }

    if (node.nodeType == 3)
    {		// text node
	if (node.nodeValue !== undefined)
	    retval += node.nodeValue.trim();
    }		// text node
    else
    {		// not a text node
	retval	+= ">\n";
	for (var i = 0; i < node.childNodes.length; i++)
	{
	    var	child	= node.childNodes[i];
	    if (child.nodeType == 1)
		retval	+= tagToString(child);
	    else
	    if (child.nodeType == 3)
		if (child.nodeValue !== undefined)
		    retval += child.nodeValue.trim();
		
	}
    
	if (node.nodeValue)
	{		// text
	    retval += "value=\"" + node.nodeValue.trim() + "\"\n";
	}		// text
    }		// not a text node

    if (node.nodeType == 1)
    {		// close element
	retval	+= "</" + node.nodeName + ">\n";
    }		// close element
    else
    if (node.nodeType == 3)
    {		// close text
    }		// close text
    else
    {		// close other node type
	retval	+= "</nodeType=" + node.nodeType + ">\n";
    }		// close other node type
    return retval;
}		// tagToString

/**
 *  selectOptByValue
 *
 *  Selects the Option under the specified Select element that has
 *  a value matching the specified value.
 *
 *  Parameters:
 *	select	an HTML Select element
 *	val	the value to look for
 *
 *  Side Effects:
 *	The matching Option is selected.
 *
 *  Returns:
 *	The selected option or null if not matched
 **/
function selectOptByValue(select, val)
{
    try {
    var	opts		= select.options;
    } catch(e) {
	alert("util.js: selectOptByValue: select=<" + select.nodeName +
		"> select.options=" + select.options);
    }
    if (val == "")
	val	= "?";
    for (var i = 0; i < opts.length; i++)
    {
	if (opts[i].value == val)
	{	// found
	    select.selectedIndex	= i;
	    if (select.onchange)
		select.onchange();	// invoke the onchange method
	    return opts[i];
	}	// found 
    }		// loop through options until find value
    // no match found, point at first entry, which usually has the
    // "Select an specify entry" text and an empty string value
    select.selectedIndex	= 0;
    return null;
}		// selectOptByValue

/**
 *  show
 *
 *  Make the element identified by the identifier visible.
 *  This is primarily used to display popup help balloons.
 *
 *  Parameters:
 *	id		value of the id parameter of the element to be
 *			made visible.
 **/
function show(id)
{
    document.getElementById(id).style.display = 'block';
}	// show

/**
 *  hide
 *
 *  Make the element identified by the identifier invisible.
 *  This is primarily used to hide popup help balloons.
 *
 *  Parameters:
 *	id		value of the id parameter of the element to be
 *			hidden.
 **/
function hide(id)
{
    document.getElementById(id).style.display = 'none';
}	// hide

/**
 *  getOffsetLeft
 *
 *  Get the offsetLeft of an HTML element relative to the page.
 *
 *  Input:
 *	elt	an element from an HTML form
 **/
function getOffsetLeft(elt)
{
    var	left	= 0;
    while(elt)
    {
	left	+= elt.offsetLeft;
	elt	= elt.offsetParent;
    }		// increment up to top element
    return left;
}	// getOffsetLeft

/**
 *  getOffsetRight
 *
 *  Get the offsetRight of an HTML element relative to the page.
 *
 *  Input:
 *	elt	an element from an HTML form
 **/
function getOffsetRight(elt)
{
    var	left	= 0;
    var	right	= elt.offsetWidth;
    while(elt)
    {
	left	+= elt.offsetLeft;
	elt	= elt.offsetParent;
    }		// increment up to top element
    return right + left;
}	// getOffsetRight

/**
 *  getOffsetTop
 *
 *  Get the offsetTop of an HTML element relative to the page.
 *
 *  Input:
 *	elt	an element from an HTML form
 **/
function getOffsetTop(elt)
{
    // note that "top" is a reserved word
    var	y	= 0;
    while(elt)
    {
	y	+= elt.offsetTop;
	elt	= elt.offsetParent;
    }		// increment up to top element
    return y;
}	// getOffsetTop

/**
 *  addHelpAbbrs
 *
 *  The cell for which help is to be displayed supports the expansion
 *  of abbreviations according to a table.  Display the contents of the
 *  abbreviation table as part of the help text.
 *
 *  Input:
 *	helpDiv		the 'help' DIV element
 *	abbrTable	the table of abbreviations and their expansions
 **/
function addHelpAbbrs(helpDiv, abbrTable)
{
    // if either of the input parameters is null, do nothing
    if (!helpDiv)
	return;
    if (!abbrTable)
	return;

    // if there is already a <TABLE> tag in this help division, then 
    // the abbreviation expansion information has already been added
    var ptags	= helpDiv.getElementsByTagName("TABLE");
    if (ptags.length > 0)
	return;

    // add the abbreviation expansion documentation to the help panel
    var	p1	= helpDiv.appendChild(document.createElement('P'));
    p1.className= 'label';
    p1.appendChild(document.createTextNode(
	"The following abbreviations are expanded:"
			));
    var tbl	= helpDiv.appendChild(document.createElement('TABLE'));
    var	numCols	= 3;
    var	col	= 0;
    var	tr;
    for(abbr in abbrTable)
    {
	if (col == 0)
	{
	    tr	= tbl.appendChild(document.createElement('TR'));
	    col	= numCols;
	}
	col--;
	var td1	= tr.appendChild(document.createElement('TH'));
	td1.className	= "left";
	td1.appendChild(document.createTextNode(abbr));
	var td2	= tr.appendChild(document.createElement('TD'));
	td2.appendChild(document.createTextNode(abbrTable[abbr]));
    }		// run through abbreviations
}		// addHelpAbbrs

/**
 *  showHelp
 *
 *  Display the current help text;
 **/
function showHelp()
{
    helpDiv.style.display	= 'block';
}		// showHelp

/**
 *  hideHelp
 *
 *  Hide the current help text;
 **/
function hideHelp()
{
    helpDiv.style.display	= 'none';
}		// hideHelp

/**
 *  keyDown
 *
 *  Handle key strokes in text input fields.
 *
 *  Parameters:
 *	e	W3C compliant browsers pass an event as a parameter
 **/
function keyDown(e)
{
    if (!e)
    {		// browser is not W3C compliant
	e	=  window.event;	// IE
    }		// browser is not W3C compliant
    var	code	= e.keyCode;

    // hide the help balloon on any keystroke
    if (helpDiv)
    {		// helpDiv currently displayed
	helpDiv.style.display	= 'none';
	helpDiv			= null;	// no longe displayed
    }		// helpDiv currently displayed
    clearTimeout(helpDelayTimer);
    helpDelayTimer		= null;

    // take action based upon code
    switch (code)
    {
	case 112:	// F1
	{
	    displayHelp(this);		// display help page
	    return false;		// suppress default action
	}	    // F1
    }	    // switch on key code

    return;
}		// keyDown

/**
 *  displayHelp
 *
 *  Display the help division associated with a particular element in the
 *  web page.
 *
 *  Parameters:
 *	elt	the HTML element for which help is to be displayed
 **/
function displayHelp(elt)
{
    // if a previous help balloon is still being displayed, hide it
    if (helpDiv)
    {
	helpDiv.style.display	= 'none';
	helpDiv			= null;
    }

    var helpDivName	= "";
    try {
	// the help division name may be supplied by an explicit private
	// attribute "helpDiv"
	if (elt.helpDiv)
	    helpDivName	= elt.helpDiv;
	else
	if (elt.name && elt.name.length > 0)
	{	// element has a name
	    if (elt.name.length > 2 &&
		elt.name.substring(elt.name.length - 2) == "[]")
	    {		// multiple selection
		helpDivName	= elt.name.substring(0, elt.name.length - 2);
	    }		// multiple selection
	    else
	    {		// ordinary element
		helpDivName	= elt.name;
	    }		// ordinary element
	}	// element has a name
	else
	{	// try id attribute
	    helpDivName		= elt.id;
	}	// try id attribute
    } catch(e)
    {		// exception thrown trying to get help division name
	var attrList	= "";
	for(attr in elt)
	    attrList	+= attr + ", ";
	alert("util.js: displayHelp: exception=" + e + ", elt=" + attrList);
    }		// exception thrown trying to get help division name

    // to ensure unique id values, the supplied name is
    // prefixed with "Help".  However some forms have not been
    // updated to use this convention

    // accumulate information to be used in diagnostic messages
    var msg	= '<' + elt.nodeName;
    if (elt.name.length > 0)
	msg	+= " name='" + elt.name + "'";
    if (elt.id.length > 0)
	msg	+= " id='" + elt.id + "'";
    msg	+= '>';

    // 1) try for the div with id='Help<name>'
    // 2) try without any row number at end of <name>
    // 3) try back-level page without Help prefix
    helpDiv	= document.getElementById("Help" + helpDivName);
    if (!helpDiv)
    {		// first choice not found
	// strip off trailing decimal digits if any representing
	// a row number
	for (var l = helpDivName.length; l > 1; l--)
	{	// find last non-numeric character
	    if (helpDivName[l - 1] < '0' || 
		helpDivName[l - 1] > '9')
	    {	// non-digit
		helpDivName	= helpDivName.substr(0, l);
		helpDiv	= document.getElementById("Help" + helpDivName);
		break;
	    }	// non-digit
	}	// find last non-numeric character

	// if cannot find division with name prefixed with 'Help'
	// then try without
	if (!helpDiv)
	    helpDiv	= document.getElementById(helpDivName);
    }		// first choice not found

    // display the help division if found
    if (helpDiv && (helpDiv != elt))
    {		// have a help division to display
	// If presentation style requires capitalization,
	// report it in help
	var textTransform	= "";
	if (elt.currentStyle)
	{		// browser supports IE API
	    textTransform	= elt.currentStyle.textTransform;
	}		// browser supports IE API
	else
	if (window.getComputedStyle)
	{		// browser supports W3C API
	    var	style		= window.getComputedStyle(elt, null);
	    textTransform	= style.textTransform;
	}		// browser supports W3C API

	if (textTransform == "capitalize")
	{
	    if (!helpDiv.capitalized)
	    {		// add text to help
		helpDiv.appendChild(document.createTextNode(
		  "Text entered in this field is automatically capitalized."
							));
		helpDiv.capitalized	= true;
	    }
	}

	// if the field has automatic abbreviation expansion
	// describe it
	addHelpAbbrs(helpDiv, elt.abbrTbl);


	// display the help balloon in an appropriate place on the page
	var cell	= elt.parentNode;
	if (cell)
	{		// cell is a child
	    var row	= cell.parentNode;
	    var tbody	= row.parentNode;
	    var table	= tbody.parentNode;
//alert("helpDiv=" + tagToString(helpDiv));
	    helpDiv.style.left		= Math.max(Math.min(getOffsetLeft(elt) - 50, table.offsetWidth - Math.floor(window.innerWidth/2)), 2) + 'px';
	    helpDiv.style.top		= (getOffsetTop(elt) + elt.offsetHeight + 5) + 'px';
	    helpDiv.style.display		= 'block';
	    // so key strokes in balloon will close window
	    helpDiv.onkeydown		= keyDown;
	}		// cell is a child
//	else
//	    alert("util.js: displayHelp: cannot find parent of " + msg);
    }		// have a help division to display
    else
	alert("util.js: displayHelp: Cannot find <div id='Help" + helpDivName + "'>");
}		// displayHelp

/**
 *  setDefault
 *
 *  A number of fields have their value defaulted
 *
 *  Input:
 *	fld	an input text element
 *	value	default value if the field is empty
 **/
function setDefault(fld, value)
{
    if (fld && fld.value.length == 0)
	fld.value	= value;
}		// setDefault


/**
 *  xmlencode
 *
 *  Replace all characters that have a special meaning in XML
 *  with their associated symbols;
 *
 *  Input:
 *	string	a string to be encoded
 *
 *  Returns:
 *	string with all special characters encoded
 **/
function xmlencode(string) {
    return string.replace(/\&/g,'&'+'amp;').replace(/</g,'&'+'lt;')
	.replace(/>/g,'&'+'gt;').replace(/\'/g,'&'+'apos;').replace(/\"/g,'&'+'quot;');
}

/**
 *  getRightTop
 *
 *  Use Ajax to retrieve the HTML to place in the right-hand cell of
 *  the page header.
 **/
function getRightTop()
{
    // get an XML file containing the HTML
    HTTP.getXML("getRightTopXml.php",
		gotRightTop,
		noRightTop);
}		// getRightTop

/**
 *  gotRightTop
 *
 *  This method is called when the XML file representing
 *  the HTML text to insert is retrieved
 **/
function gotRightTop(xmlDoc)
{
    var	topXml	= xmlDoc.documentElement;
    if (topXml && typeof(topXml) == "object" && topXml.nodeName == 'top')
    {			// valid response
	var	body		= document.body;
	var	headerDiv	= null;
	var	child;
	var	msg;
	for (child = body.firstChild; child; child = child.nextSibling)
	{		// loop through children of body
	    if ((child.nodeType == 1) &&
		(child.nodeName == 'DIV') &&
		(child.className == 'topcrumbs'))
	    {		// got header div
		headerDiv	= child;
		break;		// stop search
	    }		// got header div
	}		// loop through children of body

	if (headerDiv)
	{		// update header
	    for (child = headerDiv.firstChild; child; child = child.nextSibling)
	    {	// loop through children of div
		if (child.nodeName == 'TABLE')
		{	// update table
		    var cell	= updateHdrTable(child, topXml);
		    cell.onmouseover		= eltMouseOver;
		    cell.onmouseout		= eltMouseOut;
	
		    break;
		}	// update table
		else
		if (child.nodeName == 'SPAN')
		{	// update table
		    var htmlElt	= updateHdrSpan(headerDiv, topXml);
		    htmlElt.onmouseover		= eltMouseOver;
		    htmlElt.onmouseout		= eltMouseOut;
		    break;
		}	// update table
	    }	// loop through children of div
	}		// update header

    }			// valid response
    else
    {
	if (topXml && typeof(topXml) == "object")
	    alert("util.js: gotRightTop: " + tagToString(topXml));
	else
	    alert("util.js: gotRightTop: '" + xmlDoc + "'");
    }
	
}		// gotRightTop

/**
 *  updateHdrTable
 *
 *  Update the table in the header to insert the dynamic content
 *  from the retrieved XML file.
 *
 *  Parameters:
 *	table		HTML table tag
 *	topXml		root element of XML file
 **/
function updateHdrTable(table,
			topXml)
{
    var	row	= table.rows[0];		// first row
    var	cols	= row.cells.length;
    var	cell	= null;
    if (cols < 2)
    {		// target cell not yet defined
	cell	= row.insertCell(cols);
	cols++;
	cell.className	= 'right';
    }		// target cell not yet defined
    else
	cell	= row.cells[row.cells.length - 1];// last cell

    // remove any existing contents of this cell
    while (cell.firstChild)
	cell.removeChild(cell.firstChild);

    // copy contents of XML file to table cell
    return copyXmlToHtml(topXml, cell);
}		// updateHdrTable

/**
 *  updateHdrSpan
 *
 *  Update the header division to insert the dynamic content
 *  from the retrieved XML file.
 *
 *  Parameters:
 *	div		the header division of the page
 *	topXml		root element of XML file
 **/
function updateHdrSpan(div,
			topXml)
{
    var	cell	= div.ownerDocument.createElement("SPAN");
    cell.className	= 'right';
    var htmlElt	= copyXmlToHtml(topXml, cell);
    div.appendChild(cell);
    

    cell	= div.ownerDocument.createElement("DIV");
    cell.style.cssText	= "clear: both;";
    div.appendChild(cell);
    return htmlElt;
}		// updateHdrSpan

/**
 *  copyXmlToHtml
 *
 *  Copy the node tree under an XML node and add it
 *  under an HTML node.
 *
 *  Parameters:
 *	xmlNode		XML node at top of tree
 *	htmlNode	target HTML node
 **/
function copyXmlToHtml(xmlNode,
		       htmlNode)
{
    var	xmlOld;
    var htmlNew;
    var	htmlElt	= null;

    for (xmlOld = xmlNode.firstChild; xmlOld; xmlOld = xmlOld.nextSibling)
    {
	switch(xmlOld.nodeType)
	{
	    case 1:	// Element
	    {
		htmlElt	= document.createElement(xmlOld.nodeName);
		for (var i = 0; i < xmlOld.attributes.length; i++)
		{	// loop through attributes
		    var	attr	= xmlOld.attributes[i];
		    if (attr.name == 'onclick')
		    {
			if (attr.value.substring(0,10) == "openSignon")
			    htmlElt.onclick	= openSignon;
			else
			    htmlElt.onclick	= openAccount;
		    }		// onclick
		    else
		    if (attr.name == 'class')
			htmlElt.className	= attr.value;
		    else
			htmlElt.setAttribute(attr.name, attr.value);
		}	// loop through attributes
		copyXmlToHtml(xmlOld, htmlElt);
		htmlNode.appendChild(htmlElt);
		break;
	    }		// Element

	    case 3:	// Text
	    {
		htmlNew	= document.createTextNode(xmlOld.nodeValue);
		htmlNode.appendChild(htmlNew);
		break;
	    }		// Text

	}		// switch on source node type
    }			// loop through children of xmlOld

    return htmlElt;	// last element created at this level
}		// copyXmlToHtml

/**
 *  noRightTop
 *
 *  This method is called if there is no response
 *  file.
 **/
function noRightTop()
{
    alert("util.js: noRightTop error");
}		// noRightTop

/**
 *  openSignon
 *
 *  This method is called to open the signon dialog if the user is not
 *  yet signed on.
 **/
function openSignon()
{
    var	server	= location.protocol + "//" +
		  location.hostname;
    if (location.port.length > 0)
	server	+= ":" + location.port;
    if (location.pathname.substring(0,12) == "/jamescobban")
	server	+= "/jamescobban/";
    else
	server	+= "/";
    window.open(server + "Signon.php");
}		// openSignon

/**
 *  openAccount
 *
 *  This method is called to open the account dialog if the user is
 *  already signed on.
 **/
function openAccount()
{
    var	server	= location.protocol + "//" +
		  location.hostname;
    if (location.port.length > 0)
	server	+= ":" + location.port;
    if (location.pathname.substring(0,12) == "/jamescobban")
	server	+= "/jamescobban/";
    else
	server	+= "/";
    window.open(server + "Account.php");
}		// openAccount

/**
 *  changeDiv
 *
 *  This method is called when the user selects a new division.
 *  It is called by the scripts ReqUpdateXxxxx.js.
 *
 *  Input:
 *	divNode		an XML element containing information about
 *			a division as retrieved from the database
 *			table SubDistTable.
 **/	  
function changeDiv(divNode)
{
    // locate cell to prompt for page number in
    var	tableNode	= getElt(document.distForm, "TABLE");
    var	tbNode		= getElt(tableNode,"TBODY");
    var	trNode		= getEltId(tbNode, "TR", "pageRow");

    // remove any existing HTML from the table row with id='pageRow'
    if (trNode)
    {		// have <tr id='pageRow'>
	var	tdNode		= getEltId(trNode, "TD", "pageCell");
	if (tdNode)
	{	// have cell containing number of pages
	    // remove previous contents of cell, if any
	    while (tdNode.hasChildNodes())
		tdNode.removeChild(tdNode.firstChild);

	    // add information from database record
	    var pages		= divNode.getAttribute("pages");
	    var page1		= divNode.getAttribute("page1");
	    if ((pages.length > 0) && 
		(Number(pages) > 0))
	    {	// explicit number of pages available from database
		// create selection element to choose page
		var select	= document.createElement("select");
		select.name	= "Page";
		select.size	= 1;
		var pageoff	= 0;
		if ((page1.length > 0) &&
		    (Number(page1) > 0))
		    pageoff	= Number(page1) - 1;

		// add option element for each page in division
		for(var i = 1; i <= Number(pages); i++)
		{	// loop through pages
		    addOption(select,
			      i + pageoff,
			      i + pageoff);
		}	// loop through pages
	
		tdNode.appendChild(select);
	    }	// explicit number of pages
	    else
	    {	// explicit number of pages not available from database
		// use simple text input to obtain page number
		var	input	= document.createElement("input");
		input.type="text";
		input.name="Page";
		input.size=2;
		input.value="1";
		tdNode.appendChild(input);
	    }	// explicit number of pages not available
	}	// have cell containing number of pages
    }		// have row containing number of pages
}		// changeDiv

/**
 *  eltMouseOver
 *
 *  This function is called if the mouse moves over an input element
 *  on the invoking page.  Delay popping up the help balloon for two seconds.
 **/
function eltMouseOver()
{
    // if there is already a help division displayed, hide it
    if (helpDiv && helpDiv.id != 'msgDiv')
    {
	helpDiv.style.display	= 'none';
	helpDiv			= null;
    }

    // in some cases the mouseover event is against the table cell
    // containing the input element.  Locate the first element node
    // under the cell to display help for
    if (this.nodeName == 'TD')
    {		// mouseover defined for the cell containing the element
	for (var i = 0; i < this.childNodes.length; i++)
	{	// loop through children of this
	    var	cNode	= this.childNodes[i];
	    if (cNode.nodeType == 1)
	    {	// element
		helpElt	= cNode;
		break;
	    }	// element
	}	// loop through children of this
    }		// mouseover defined for the cell containing the element
    else
	helpElt		= this;
    helpDelayTimer	= setTimeout(popupHelp, 2000);
}		// eltMouseOver

/**
 *  popupHelp
 *
 *  This function is called if the mouse is held over an input element
 *  on the invoking page for more than 2 seconds.
 **/
function popupHelp()
{
    displayHelp(helpElt);
}		// popupHelp

/**
 *  eltMouseOut
 *
 *  This function is called if the mouse moves off an input element
 *  on the invoking page.
 **/
function eltMouseOut()
{
    clearTimeout(helpDelayTimer);
    helpDelayTimer		= null;
    if (helpDiv && helpDiv.id != 'msgDiv')
    {
	helpDiv.style.display	= 'none';
	helpDiv			= null;
    }
}		// eltMouseOut

/**
 *  Fields to track mouse operations on a table for performing copy and paste
 **/
var	mouseIsDown	= false;
var	pendStartCell	= null;		// pending possible selection
var	startCell	= null;		// first cell of selection
var	endCell		= null;		// last cell of selection

/**
 *  getStartSelection
 *
 *  This function is obtain the first table cell in the selection.
 **/
function getStartSelection()
{
alert("getStartSelection: startCell=" + startCell);
    return startCell;
}		// getStartSelection

/**
 *  getEndSelection
 *
 *  This function is obtain the first table cell in the selection.
 **/
function getEndSelection()
{
    return endCell;
}		// getEndSelection

/**
 *  cancelSelection
 *
 *  This function is called to cancel an existing selection.
 **/
function cancelSelection()
{
	alert("cancelSelection from row=" +
		startCell.parentNode.rowIndex + " col=" +
		startCell.cellIndex + " " + 
		tagToString(startCell) + " to row=" + 
		endCell.parentNode.rowIndex + 
		" col=" + endCell.cellIndex + " "  +
		tagToString(endCell));

    if (startCell && endCell)
    {		// have a selection to cancel
	var firstRowIndex	= startCell.parentNode.rowIndex;
	var lastRowIndex	= endCell.parentNode.rowIndex;
	var firstCellIndex	= startCell.cellIndex;
	var lastCellIndex	= endCell.cellIndex;
	if (firstRowIndex > lastRowIndex)
	{	// swap
	    var tri		= firstRowIndex;
	    firstRowIndex	= lastRowIndex;
	    lastRowIndex	= tri;
	}	// swap
	if (firstCellIndex > lastCellIndex)
	{	// swap
	    var tci		= firstCellIndex;
	    firstCellIndex	= lastCellIndex;
	    lastCellIndex	= tci;
	}	// swap

	var	tableBody	= startCell.parentNode.parentNode;

	for (var ri = firstRowIndex; ri <= lastRowIndex; ri++)
	{	// loop through selected rows
	    var row	= tableBody.rows[ri];
	    for (var ci = firstCellIndex; ci <= lastCellIndex; ci++)
	    {	// loop through selected columns
		var	cell	= row.cells[ci];
		// find first element in selected cell
		var element	= cell.firstChild;
		while(element && element.nodeType != 1)
		    element	= element.nextSibling;
		var	oldcn	= element.className;
		var	newcn	= oldcn;
		if (oldcn.substring(oldcn.length - 8) == "Selected");
		    newcn	= oldcn.substring(0,oldcn.length - 8);
		element.className	= newcn;
	    }	// loop through selected columns
	}	// loop through selected rows
    }		// have a selection to cancel
    startCell		= null;
    endCell		= null;
}		// cancelSelection

/**
 *  markSelection
 *
 *  This function is called to mark a new selection.
 **/
function markSelection()
{
    if (startCell && endCell)
    {		// have a selection to mark
	alert("markSelection from row=" +
		startCell.parentNode.rowIndex + " col=" +
		startCell.cellIndex + " " + 
		tagToString(startCell) + " to row=" + 
		endCell.parentNode.rowIndex + 
		" col=" + endCell.cellIndex + " "  +
		tagToString(endCell));

	var firstRowIndex	= startCell.parentNode.rowIndex;
	var lastRowIndex	= endCell.parentNode.rowIndex;
	var firstCellIndex	= startCell.cellIndex;
	var lastCellIndex	= endCell.cellIndex;
	if (firstRowIndex > lastRowIndex)
	{	// swap
	    var tri		= firstRowIndex;
	    firstRowIndex	= lastRowIndex;
	    lastRowIndex	= tri;
	}	// swap
	if (firstCellIndex > lastCellIndex)
	{	// swap
	    var tci		= firstCellIndex;
	    firstCellIndex	= lastCellIndex;
	    lastCellIndex	= tci;
	}	// swap

	var	tableBody	= startCell.parentNode.parentNode;

	for (var ri = firstRowIndex; ri <= lastRowIndex; ri++)
	{	// loop through selected rows
	    var row	= tableBody.rows[ri];
	    for (var ci = firstCellIndex; ci <= lastCellIndex; ci++)
	    {	// loop through selected columns
		var	cell	= row.cells[ci];
		// find first element in selected cell
		var element	= cell.firstChild;
		while(element && element.nodeType != 1)
		    element	= element.nextSibling;
		element.className	= element.className + "Selected";
	    }	// loop through selected columns
	}	// loop through selected rows
    }		// have a selection to mark
}		// markSelection

/**
 *  eltMouseDown
 *
 *  This function is called if the mouse button is pressed on a table cell.
 *
 *  Parameters:
 *	this		element the mouse was pressed on
 **/
function eltMouseDown()
{
    mouseIsDown		= true;
    pendStartCell	= this;
    return false;	// suppress default processing
}		// eltMouseDown

/**
 *  eltMouseUp
 *
 *  This function is called if the mouse button is pressed on a table cell.
 *
 *  Parameters:
 *	this		element the mouse was pressed on
 **/
function eltMouseUp()
{
    // find first element in selected cell
    var element	= this.firstChild;
    while(element && element.nodeType != 1)
	element	= element.nextSibling;

    // check for a simple click: mouse down and then up on same cell
    if (pendStartCell === this)
    {		// click on cell
	element.focus();
	return true;
    }		// click on cell

    // if a previous selection has not been acted on, cancel it first
    // this will reverse highlighting of selected cells
    if (startCell && endCell)
    {		// cancel existing selection
	cancelSelection();
    }		// cancel existing selection

    // process as new selection
    startCell		= pendStartCell;
    pendStartCell	= this;

    // if a valid selection, record it for future actions
    if (startCell && this.nodeName.toUpperCase() == 'TD')
    {		// valid selection
//	alert("select from row=" +
//		startCell.parentNode.rowIndex + " col=" + startCell.cellIndex + " " + 
//		tagToString(startCell) + " to row=" + 
//		this.parentNode.rowIndex + " col=" + this.cellIndex + " "  +
//		tagToString(this));
	mouseIsUp	= false;
	endCell		= this;
	markSelection();
	if (element)
	    element.focus();	// move focus to input element
	return false;
    }		// valid selection
//    else
//	alert("unexpected from " + tagToString(startCell) + " to " +
//		tagToString(this));

    // not a valid selection, just move focus
    mouseIsUp		= false;
    if (element)
	element.focus();	// move focus to input element
    return true;
}		// eltMouseUp

/**
 *  getParmsFromXml
 *
 *  Create a Javascript Object from an XML element by storing
 *  the text value containing in each child element as the value of a
 *  child of the Object.  If the text value can be represented as an
 *  integer value it is converted to one.
 **/
function getParmsFromXml(element)
{
    var	parms	= {};
    // store the parameters in an object
    for (var j = 0; j < element.childNodes.length; j++)
    {		// loop through elements within XML response
	var	elt	= element.childNodes[j];
	if (elt.nodeType != 1)
	    continue;	// ignore text & comments between elements

	var	value	= getText(elt).trim();
	if (value.search("^[0-9]+$") == 0)
	    parms[elt.nodeName]	= parseInt(value);
	else
	    parms[elt.nodeName]	= value;
    }		// loop through elements within XML response
    return	parms;
}		// getParmsFromXml

/**
 *  popupLoading
 *
 *  Popup a "loading" indicator to the user.  This indicator warns the
 *  user that an extended operation has begun and the user should wait
 *  for the indicator to disappear before taking further actions in the
 *  current dialog.
 *
 *  Input:
 *	element		an input element for positioning the popup
 *			if this is null, position relative to last element
 *			passed to this method
 **/
function popupLoading(element)
{
    if (loaddiv == null)
    {		// indicator not currently displayed
	loaddiv	= document.getElementById('loading');

	// if there is no "loading" division, create a default one
	if (loaddiv === null || loaddiv === undefined)
	{		// create missing division
	    var	body		= document.bodyElement;
	    if (body)
	    {
		var	div	= document.createElement('div');
		div.id		= 'loading';
		div.className	= 'popup';
		div.appendChild(document.createTextNode("Loading..."));
		body.appendChild(div);
		loaddiv		= div;
	    }
	}		// create missing division

	if (loaddiv)
	{		// display loading indicator to user
	    if (element === null)
		element		= loadelt;
	    else
		loadelt		= element;
	    var leftOffset	= getOffsetLeft(element);
	    if (leftOffset > 500)
		leftOffset	-= 200;
	    loaddiv.style.left	= leftOffset + "px";
	    loaddiv.style.top	= (getOffsetTop(element) - 30) + 'px';
	    loaddiv.style.display	= 'block';
	}		// load indicator to user
    }		// indicator not currently displayed
}		// popupLoading

/**
 *  popupLoadingText
 *
 *  Popup a "loading" indicator to the user with application supplied
 *  text message.  This indicator warns the
 *  user that an extended operation has begun and the user should wait
 *  for the indicator to disappear before taking further actions in the
 *  current dialog.
 *
 *  Input:
 *	element		an input element for positioning the popup
 *			if this is null, position relative to last element
 *			passed to this method
 *	text		string of text to display to user
 **/
function popupLoadingText(element,
			  text)
{
    if (loaddiv == null)
    {		// indicator not currently displayed
	loaddiv	= document.getElementById('loading');

	// if there is no "loading" division, create a default one
	if (loaddiv === null || loaddiv === undefined)
	{		// create missing division
	    var	body		= document.bodyElement;
	    var	div		= document.createElement('div');
	    div.id		= 'loading';
	    div.className	= 'popup';
	    div.appendChild(document.createTextNode("Loading..."));
	    body.appendChild(div);
	    loaddiv		= div;
	}		// create missing division

	if (loaddiv)
	{		// display loading indicator to user
	    // replace text in loading division
	    while(loaddiv.firstChild)
		loaddiv.removeChild(loaddiv.firstChild);
	    loaddiv.appendChild(document.createTextNode(text));
	    // position and display loading division
	    if (element === null)
		element		= loadelt;
	    else
		loadelt		= element;
	    var leftOffset	= getOffsetLeft(element);
	    if (leftOffset > 500)
		leftOffset	-= 200;
	    loaddiv.style.left	= leftOffset + "px";
	    loaddiv.style.top	= (getOffsetTop(element) - 30) + 'px';
	    loaddiv.style.display	= 'block';
//alert("loaddiv: " + tagToString(loaddiv));
	}		// load indicator to user
    }		// indicator not currently displayed
}		// popupLoadingText

/**
 *  hideLoading
 *
 *  Hide the "loading" indicator from the user.  This notifies the
 *  user that the extended operation has completed.
 **/
function hideLoading()
{
    if (loaddiv)
    {		// indicator currently displayed
	loaddiv.style.display	= 'none';	// hide it
	loaddiv			= null;		// not being displayed
    }		// indicator currently displayed
}		// hideLoading

