/**
 * core.005w.js
 * core javascript support for herb@home (cut-down functions for standalone use)
 * 
 * provides event handling, error logging, drop boxes
 * 
 * @author Tom Humphrey <t.humphrey@bigfoot.com>
 * @copyright Tom Humphrey 2008
 * @version forked from atHome.005w.js
 * @package docform
 */
 
var coreVersion='005w';

var standardBrowser= (navigator.appName!='Microsoft Internet Explorer');
if (!standardBrowser) {
	var IEversion = navigator.appVersion;
	IEversion = parseInt(IEversion.substr(IEversion.indexOf("MSIE")+4),10);
	
	try {
  		document.execCommand("BackgroundImageCache", false, true);
	} catch(err) {}
}
else {var IEversion=0;}

var iconSuffix=(standardBrowser || IEversion>=7)?'':'_ie'; // suffix for png's without alpha transparency
var globalEventStack=[];
var globalEventMethodName=[];
var globalEventTarget=[];
var globalEventCapturing=[];
var globalEventType=[];
var globalBlockSave;
var globalContainer;
var globalFocusedRowId=null;
var globalDropboxTimeoutHandle=null;

/* global scriptUrl,phpSession */

var errorReportRequest=null;
var trimRegex=/^\s+|\s+$/g;

var globalMessages=[];
var globalIFrameFlag=window.parent!==window;

try
{
	var tr=document.createElement("TR");
	tr.focus=false;
	tr=null;
}
catch (err)
{
	window.location.href=scriptUrl+"content/incompat/";
}

/**
 * generate a unique id, by appending an integer to the specified prefix
 * 
 * @param prefix {string} id prefix
 * @return id string
 */
function uniqueId(prefix) {
	return prefix+(uniqueId.counter++);
}
uniqueId.counter=0;

/**
 * with early versions of IE a place holder handler function must be given to XMLrequest or the request fails silently
 */
function placeholder_handler()
{
	//nop
}

/**
 * reports a javascript error
 */
function log_error(message,url,line) {	
	window.onerror=null;
	
	var doc=create_xml_doc();
	var error=doc.createElement('error');
	
	if (line!==null) {error.setAttribute('line',line);}
	if (url!==null) {error.setAttribute('url',url);}
	//error.setAttribute('caller',window.onerror.caller||window.onerror.arguments.caller);
	if (uid!==null) {error.setAttribute('userid',uid);}
	
	error.setAttribute('version',globalVersion);
	error.setAttribute('browser',navigator.appName);
	error.setAttribute('browserv',navigator.appVersion);

	error.appendChild(doc.createTextNode(message));

	doc.documentElement.appendChild(error);

	errorReportRequest=agnostic_XMLHttpRequest(); // create the request object
	xml_save(errorReportRequest,scriptUrl+'javascriptErrorLog.php?x=x'+phpSession,placeholder_handler,doc);

	window.onerror=log_error; // turn on error handling again
	return true; // suppress normal error reporting
}

/**
 * if element is empty or default then set to it's default value and shade text
 * @param <html input element> element
 * @todo should use css class rather than direct style manipulation
 */
function input_set_default(element)
{
	if (!element.value || element.value==element.defaultValue) {
		element.value=element.defaultValue;
		element.style.color="gray";
	}
}

/**
 * called as focus event handler, with 'this' set to the focused element
 * clear any default value and set text colour
 */
function input_default_focus()
{
	if (this.value==this.defaultValue || this.value==='') {
		this.value='';
		this.style.color="black";
	}
}

/**
 * called as blur event handler, with 'this' set to the blurred element
 * if value is empty then substitute default value and set text colour
 */
function input_default_blur()
{
	if (this.value==='') {
		this.value=this.defaultValue;
		this.style.color="gray";
	}
}

/**
 * Drop down list
 * @constructor
 */
function Dropbox() {
	
}

/**
 * construct dropbox container and input box
 */
Dropbox.prototype.construct_dropbox = function () {
	this.input=document.createElement("input");
	this.input.setAttribute('autocomplete','off');
	this.searchLinks=[];

	this.container=document.createElement("div");
	this.container.className='namecontainer';
	this.container.id=uniqueId("dbcontainer");
	this.dropbox=document.createElement("div");
	this.dropbox.className='dropbox';

	if (!(standardBrowser || IEversion >=7)) {
		this.iframe=document.createElement('iframe');
		//this.iframe.setAttribute('src','/blank.php');
		this.iframe.style.position="absolute";
		this.iframe.style.border="none";
		this.iframe.frameBorder=0;
		this.iframe.style.display="none";
		this.container.appendChild(this.iframe);
	} else {
		this.iframe=null;	
	}
	this.container.appendChild(this.input);

	if (standardBrowser) {
		this.dropbox.style.zIndex='auto';
	} else {
		this.container.style.position="static";
		this.container.style.verticalAlign="top";
	}
	this.container.appendChild(this.dropbox);
	
	this.closeButton=deleteButton('close');
	
	this.container.appendChild(this.closeButton);
	this.closeButton.style.position="absolute";
	this.closeButton.style.top="1.5em";
	//this.closeButton.style.right="2px";
	this.closeButton.style.zIndex="10";
	this.closeButton.style.display="none";
	
	this.closeButton.style.filter="alpha(opacity:40)";
	this.closeButton.style.KHTMLOpacity="0.40";
	this.closeButton.style.MozOpacity="0.40";
	this.closeButton.style.opacity="0.40";
	this.closeButtonClickHandle=register_event_handler(this.closeButton,'click',this,'dropbox_close_now','');
	
	this.mouseOverEventHandler=register_event_handler(this.container,'mouseover',this,'mouse_over','');
	this.mouseOutEventHandler=register_event_handler(this.container,'mouseout',this,'mouse_out','');
	this.blurEventHandle=register_event_handler(this.input,'blur',this,'hide_drop','');
	
	this.suspendBlur=false;
	this.xmlRequest=null;
	this.requestURL=null;
	this.callIntervalHandle=null;
	this.pending=0;
	
	this.dropbox.selectedIndex=0;
	
	this.progress=document.createElement('img');
	this.progress.src="http://static.herbariumathome.org/snake_transparent.gif";
	this.progress.width=16;
	this.progress.height=16;
};

/**
 * wipe contents of drop down list box
 * @param <html div element> dropbox
 */
Dropbox.prototype.clear_box =function () {
	var links=this.dropbox.getElementsByTagName('a');
	for (var n=links.length-1;n>=0;n--) {
		var link=links[n];
		link.onclick=null;
		link.onmouseover=null;
		link.onmouseout=null;
		link.flagChange=null;
		link.params=null;
	}

	// the first element of the dropbox is the close button, hence n>=1 rater than 0
	for (n=this.dropbox.childNodes.length-1;n>=0;n--) {this.dropbox.removeChild(this.dropbox.lastChild);}
	this.dropbox.scrollTop=0;
};

/**
 * destructor
 */
Dropbox.prototype.destruct_dropbox = function () {
	this.clear_box(); // not sure if this is needed
	
	if (this.iframe) {
		this.iframe.parentNode.removeChild(this.iframe);
		this.iframe=null;
	}
	
	if (this.searchLinks) {
		for (var link in this.searchLinks) {
			if (this.searchLinks[link].destruct) {this.searchLinks[link].destruct();}
		}
	}
	
	if (this.mouseOverEventHandler) {this.mouseOverEventHandler=remove_event_handler(this.mouseOverEventHandler);}
	if (this.mouseOutEventHandler) {this.mouseOutEventHandler=remove_event_handler(this.mouseOutEventHandler);}
	if (this.blurEventHandle) {this.blurEventHandle=remove_event_handler(this.blurEventHandle);}
	if (this.closeButtonClickHandle) {this.closeButtonClickHandle=remove_event_handler(this.closeButtonClickHandle);}

	if (this.closeButton.parentNode) {this.closeButton.parentNode.removeChild(this.closeButton);}
	this.closeButton=null;

	this.progress=null;

	this.container.removeChild(this.dropbox);
	this.container.removeChild(this.input);
	this.dropbox.selectedIndex=null;
	this.dropbox=null;
	this.input.defaultValue=null;
	this.input=null;
	this.container.parentNode.removeChild(this.container);
	this.container=null;
	this.idContainerElement=null;
};

/**
 * dropbox mouse-over event
 * delay closing box (suspendblur)
 */
Dropbox.prototype.mouse_over=function (event,element,param) {
	this.suspendBlur=true;
};

Dropbox.prototype.mouse_out=function (event,element,param) {
	this.suspendBlur=false;
};

Dropbox.prototype.show_progress = function () {
	this.container.style.position="relative";
	this.container.appendChild(this.progress);
	this.progress.style.position="absolute";
	this.progress.style.top="2px";
	this.progress.style.left=(this.input.offsetWidth-21)+"px";
};

Dropbox.prototype.hide_progress = function () {
	if (this.progress && this.progress.parentNode) {this.progress.parentNode.removeChild(this.progress);}
};

Dropbox.prototype.abort_xml_request = function () {
	if (this.callIntervalHandle!==null) {
		window.clearTimeout(this.callIntervalHandle);
		this.callIntervalHandle=null;
		this.pending=0;
	}

	if (this.xmlRequest) {
		this.xmlRequest.abort();
		this.xmlRequest=null;
		this.hide_progress();
	}
	this.requestURL=null;
};

Dropbox.prototype.droplist_mouse_over=function (event,element,index) {
	this.set_selection(index,this.dropbox.getElementsByTagName('a'));
};

Dropbox.prototype.droplist_mouse_out=function (event,element,index) {
	if (this.dropbox.selectedIndex == index) {
		element.style.backgroundColor="white";
		element.style.color="green";
		this.dropbox.selectedIndex=null;
	}
};

/**
 * issue queued xml request
 */
Dropbox.prototype.call_back= function () {
	if (this.pending==1) {
		this.issue_xml_request();
	}
	this.callIntervalHandle=null;
};

/**
 * hide dropbox immediately
 */
Dropbox.prototype.dropbox_close_now = function() {
	this.suspendBlur=false;
	
	if (globalDropboxTimeoutHandle) {
		window.clearTimeout(globalDropboxTimeoutHandle);
		globalDropboxTimeoutHandle=null;
		globalDropboxCloseRef=null;
	}

	if (this.dropbox) {
		this.clear_box();
		this.dropbox.style.display='none';
		this.dropbox.style.filter="alpha(opacity:95)";
		this.dropbox.style.KHTMLOpacity="0.95";
		this.dropbox.style.MozOpacity="0.95";
		this.dropbox.style.opacity="0.95";
	}

	if (!standardBrowser) {
		// clean up IE mess
		if (IEversion <7 && this.input) {this.input.style.marginBottom='';}
		if (this.container) {this.container.style.position="static";}
	}
	
	if (this.iframe) {this.iframe.style.display='none';}
	
	if (this.closeButton) {this.closeButton.style.display="none";}
	this.hide_progress();
};

/**
 * hide dropbox after a delay for clicks to complete
 */
Dropbox.prototype.hide_drop = function (event,element,param) {
	if (this.suspendBlur===false) {
		
		/*
		if (this.dropbox) {
			// fade gimmick
			this.dropbox.style.filter="alpha(opacity:40)";
			this.dropbox.style.KHTMLOpacity="0.40";
			this.dropbox.style.MozOpacity="0.40";
			this.dropbox.style.opacity="0.40";
		} */
		
		this.abort_xml_request(); // stop any requests which may not have come back yet
		globalDropboxCloseRef=this;

		globalDropboxTimeoutHandle=setTimeout(Dropbox.commit_hidedrop, 500); // needs to be long enough or links don't get followed
	} else {
		if (this.input) {this.input.focus();}
	}
};

/**
 * static method used by dropbox close call back
 */
Dropbox.commit_hidedrop = function()
{
	globalDropboxTimeoutHandle=null;
	if (globalDropboxCloseRef) {
		globalDropboxCloseRef.dropbox_close_now();
		globalDropboxCloseRef=null;
	}
};

/**
 * signal that request should be issued at next available opportunity
 */
Dropbox.prototype.queue_xml_request = function (shortQuery) {
	if (this.callIntervalHandle===null) {
		if (!shortQuery) {this.issue_xml_request();} // issue request immediately unless little typed, so expecting more
		else {this.pending=1;}

		this.callIntervalHandle=window.setTimeout(associateObjectWithXMLRequest(this,"call_back",null),300);
	}
	else {this.pending=1;}
};

/**
 * immediately issue xml request
 */
Dropbox.prototype.issue_xml_request= function () {
	this.pending=0;
	var url=this.get_request_url();

	if (this.xmlRequest==null || this.xmlRequest.readyState==4 || this.requestURL!=url) {
		this.xmlRequest=agnostic_XMLHttpRequest(); // create the request object
		this.requestURL=url;
		this.show_progress();
		xml_request(this.xmlRequest,url,associateObjectWithXMLRequest(this, "xml_handler", ''));
	}
};

/**
 * change higlight item in dropdown list
 */
Dropbox.prototype.set_selection =function (newIndex,links) {
	if (this.dropbox.selectedIndex && this.dropbox.selectedIndex!=newIndex) {
		// unhighlight previous selection
		links[this.dropbox.selectedIndex-1].style.backgroundColor="white";
		links[this.dropbox.selectedIndex-1].style.color="green";
	}

	links[newIndex-1].style.backgroundColor="green";
	links[newIndex-1].style.color="white";
	this.dropbox.selectedIndex=newIndex;
};

/**
 * 
 */
Dropbox.prototype.register_search_link = function (searchLink)
{
	this.searchLinks.push(searchLink);
	searchLink.register_icon(this);
};

/**
 * 
 */
Dropbox.prototype.show_search_links = function ()
{
	for (var link in this.searchLinks) {
		if (this.searchLinks[link].show) {this.searchLinks[link].show();}
	}
};

/**
 * 
 */
Dropbox.prototype.hide_search_links = function ()
{
	for (var link in this.searchLinks) {
		if (this.searchLinks[link].hide) {this.searchLinks[link].hide();}
	}
};

// ignore all ctrl+key combinations except ctrl+V and ctrl+X
if (standardBrowser)
{
	var valid_key=function(event) {
		return (event.keyCode >= 46 || event.keyCode==32 || event.keyCode==8 || event.keyCode==0) && !(event.ctrlKey && !(event.keyCode==86 || event.keyCode==88));
		// see http://www.js-examples.com/page/tutorials__key_codes.html
	};
	
	var valid_pos_num_key=function(event) {
		return ((event.keyCode>=48 && event.keyCode<=57) || event.keyCode == 46 || event.keyCode==8 || event.keyCode==0) && !(event.ctrlKey && !(event.keyCode==86 || event.keyCode==88));
	};
	
	var valid_pos_int_key=function(event) {
		return ((event.keyCode>=48 && event.keyCode<=57) || event.keyCode==8 || event.keyCode==0) && !(event.ctrlKey && !(event.keyCode==86 || event.keyCode==88));
	};
}
else
{
	var valid_key=function(event) {
		return (event.keyCode >= 46 || event.keyCode==32 || event.keyCode==8) && !(event.ctrlKey && !(event.keyCode==86 || event.keyCode==88));
		//event.returnValue = ((event.keyCode <=58) || (event.keyCode == 191)) && (event.keyCode !=32);
	};
	
	var valid_pos_num_key=function(event) {
		return ((event.keyCode >= 48 && event.keyCode <= 57) || event.keyCode==46 || event.keyCode==8) && !(event.ctrlKey && !(event.keyCode==86 || event.keyCode==88));
	};
	
	var valid_pos_int_key=function(event) {
		return ((event.keyCode >= 48 && event.keyCode <= 57) || event.keyCode==8) && !(event.ctrlKey && !(event.keyCode==86 || event.keyCode==88));
	};
}

/**
 * virtual class for providing search links to augment drop boxes.
 * Search providers typically use an external window (or potentially could integrate more closely)
 * in cases where the external provider returns data to relevent dropboxes then the provider should implement a singleton
 * shared between dropboxes
 */
function ExternalSearchLink ()
{
}

/**
 * uri of 16x16 image
 */
ExternalSearchLink.prototype.iconUrl='/uksearch.png';

/**
 * dropbox object which currently has focus (and 'owns' this search link)
 */
ExternalSearchLink.prototype.dropBox=null;

/**
 * dropbox object which currently has focus (and 'owns' this search link)
 */
ExternalSearchLink.prototype.defaultUrl='#';

/**
 */
ExternalSearchLink.prototype.destruct_external_link= function ()
{
	if (this.clickHandle) {this.clickHandle=remove_event_handler(this.clickHandle);}
	if (this.button && this.button.parentNode) {this.button.parentNode.removeChild(this.button);}
	this.button=null;
};

/**
 * generic destructor, typically replaced in child class
 */
ExternalSearchLink.prototype.destruct = function ()
{
	this.destruct_external_link();
};

/**
 * create icon and register event handler
 */
ExternalSearchLink.prototype.register_icon= function (dropBox)
{
	this.dropBox=dropBox;
	this.button=document.createElement('a');
	this.button.className="img";
	this.button.href=this.defaultUrl;
	this.button.style.display="none";
	var img=document.createElement('img');
	img.src=this.iconUrl;
	img.width=16;
	img.height=16;
	this.button.appendChild(img);
	if (this.linkTitle) {this.button.title=this.linkTitle;}
	this.clickHandle=register_event_handler(this.button,'click',this,'click_handler');
	this.dropBox.container.insertBefore(this.button,this.dropBox.dropbox);
};

/**
 * 
 */
ExternalSearchLink.prototype.hide= function () {
	this.button.style.display="none";
};

/**
 * 
 */
ExternalSearchLink.prototype.show= function () {
	this.button.style.display="inline";
};

/**
 * Input field for taxon names, including dropbox
 * @constructor
 * @param <string> defaultTaxonName or empty string
 * @param <int> defaultTaxonId
 * @param <string> defaultTaxonAuthority
 * @param <string> defaultTaxonCommonName
 */
function TaxonBox (defaultTaxonName,defaultTaxonId,defaultTaxonAuthority,defaultTaxonCommonName) {
	this.taxonId=defaultTaxonId;
	
	this.defaultTaxonName=defaultTaxonName;
	this.defaultTaxonId=defaultTaxonId;
	this.defaultTaxonAuthority=defaultTaxonAuthority;
	this.defaultTaxonCommonName=defaultTaxonCommonName;
	this.authority=defaultTaxonAuthority;
	this.commonName=defaultTaxonCommonName;

	this.construct_dropbox();
	//this.register_search_link(new IPNISearch());
	//this.register_search_link(new UBioSearch());

	this.infoLink=info_link();
	
	this.searchURL='XMLtaxonsearchAtHome';

	this.input.style.width="24em";
	this.input.value=this.defaultTaxonName;
	this.inputUpHandler=register_event_handler(this.input,'keyup',this,'input_key_up','');
	
	this.set_tool_tip();
	this.show_info_link();
}

TaxonBox.prototype=new Dropbox();
TaxonBox.prototype.constructor=TaxonBox;

/**
 * destructor
 */
TaxonBox.prototype.destroy=function () {
	this.inputUpHandler=remove_event_handler(this.inputUpHandler);
	
	this.abort_xml_request();
	this.destruct_dropbox();

	this.taxonId=null;
	this.authority=null;
	this.commonName=null;
	this.defaultTaxonAuthority=null;
	this.defaultTaxonCommonName=null;

	this.infoLink=null;
};

/**
 * 
 */
TaxonBox.prototype.set_tool_tip=function () {
	this.input.setAttribute("title",this.taxonId?this.input.value+" "+this.authority+" "+this.commonName:'');
};

/**
 * key up event
 * if valid character then send lookup request
 * else if cursor key then change selection
 */
TaxonBox.prototype.input_key_up=function (event,element,param) {
	if (valid_key(event)) {
		this.taxonId=''; // reset due to key press
		if (this.idContainerElement) {
			this.idContainerElement.value='';
		}
		
		if (element.value) {
			this.queue_xml_request(element.value.length <2);
			this.show_search_links();
		} else {
			this.abort_xml_request();
			this.dropbox_close_now();
		}
		
		this.input.style.color='black';
		this.hide_info_link();
		this.set_tool_tip();
	} else {
		if (event.keyCode===38) {this.change_selection(-1);} // up arrow
		else if (event.keyCode===40) {this.change_selection(1);} // up arrow
		else if (event.keyCode===13) {
			// return key
			this.abort_xml_request();
			this.dropbox_close_now();
		}
	}
};

/**
 * return xml request url and query parameters
 */
TaxonBox.prototype.get_request_url= function () {
	var taxonString=this.input.value.replace(trimRegex,''); // trim
	return scriptUrl+this.searchURL+"/"+encodeURIComponent(taxonString.toLowerCase())+"/";
};

/**
 * change dropbox selection
 * @param <int> direction -1|+1
 */
TaxonBox.prototype.change_selection =function (direction)
{
	var links=this.dropbox.getElementsByTagName('a');

	if (links.length && ((direction > 0 && this.dropbox.selectedIndex < links.length) || (direction <0 && this.dropbox.selectedIndex >1))) {
		this.set_selection(this.dropbox.selectedIndex+direction,links);
		this.import_params(links[this.dropbox.selectedIndex-1].params);
		
		this.input.style.color='green';
		this.show_info_link();
		if (this.input.onchange) {this.input.onchange();} // artificial change event required for IE
	}
};

TaxonBox.prototype.import_params = function (params)
{
	this.taxonId=params.externalId;
	this.input.value=params.name;
	this.authority=params.authority;
	this.commonName=params.commonName;
	this.set_tool_tip();
	this.hide_search_links();
	
	if (this.idContainerElement) {
		this.idContainerElement.value=this.taxonId;
	}
};

/**
 * An Id container element is used for standalone drop boxes, which are part of forms that
 * need a mechanism to return the selected element id.
 *  
 * @param <string> idKey
 */
TaxonBox.prototype.wrapper_link_related_elements = function (idKey) {
	// look for any additional input element which contains an external id
 	// given idKey may have numerical suffix in square brackets which needs to be stripped then reapplied
 	
 	var idContainerElement=document.getElementById((idKey.search(/\[/)!=-1)?idKey.replace(/\[/,'id['):idKey+'id');
 	if (idContainerElement) {
 		this.idContainerElement=idContainerElement;
		this.taxonId=this.idContainerElement.value;
		this.input.style.color=(!!this.taxonId)?'green':'black';
		this.set_tool_tip();
		this.show_info_link();
 	}
};

/**
 * displays 'info' link icon
 */
TaxonBox.prototype.show_info_link = function () {
	if (this.infoLink.parentNode) {this.infoLink.parentNode.removeChild(this.infoLink);}

	if (this.taxonId) {
		this.infoLink.href=scriptUrl+"taxon/"+this.taxonId+"/";
		this.container.insertBefore(this.infoLink,this.dropbox);
	}
};

/**
 * hides 'info' link icon
 */
TaxonBox.prototype.hide_info_link = function () {
	if (this.infoLink.parentNode) {this.infoLink.parentNode.removeChild(this.infoLink);}
};

TaxonBox.prototype.xml_handler=function (param) {
	if (this.xmlRequest && this.xmlRequest.readyState == 4 && this.xmlRequest.status == 200 && this.xmlRequest.responseXML) {
		this.hide_progress();
    	var response  = this.xmlRequest.responseXML.documentElement;
		var names = response.getElementsByTagName('taxon');

		this.clear_box();
		if (!standardBrowser) {this.container.style.position="relative";} // for IE only make position relative at last moment

		this.dropbox.selectedIndex=0; //nothing selected

		var ul=document.createElement('ul'),li;
		var authorityElement,vulgarElement;
		
		var offset=1;

		if (this.defaultTaxonName) {
			// append the default option
			li=document.createElement('li');
			var nameLink=document.createElement("a");
			nameLink.onclick=associate_obj_with_event(this, "click_set_value",this.defaultTaxonName);
			nameLink.onmouseout=associate_obj_with_event(this, "droplist_mouse_out",1);
			nameLink.onmouseover=associate_obj_with_event(this, "droplist_mouse_over",1);
			
			nameLink.params={
				externalId: this.defaultTaxonId,
				name: this.defaultTaxonName,
				authority: this.defaultTaxonAuthority,
				commonName: this.defaultTaxonCommonName
			};
	
			var defaultTextNode=document.createElement("strong");
			defaultTextNode.style.fontStyle="italic";
			defaultTextNode.appendChild(document.createTextNode(this.defaultTaxonName));
			nameLink.appendChild(defaultTextNode);
			
			if (this.defaultTaxonAuthority) {
				authorityElement=document.createElement('span');
				authorityElement.style.color="gray";
				authorityElement.style.backgroundColor="transparent";
				nameLink.appendChild(authorityElement).appendChild(document.createTextNode(' '+this.defaultTaxonAuthority));
			}
			
			if (this.defaultTaxonCommonName) {
				vulgarElement=document.createElement('span');
				vulgarElement.style.backgroundColor="transparent";
				nameLink.appendChild(vulgarElement).appendChild(document.createTextNode(' '+this.defaultTaxonCommonName));
			}
			
			li.appendChild(nameLink);
			li.appendChild(document.createTextNode(' (default)'));
			ul.appendChild(li);
			
			offset+=1;
		}

		if (names && names.length >0) {
			var l=names.length;
			var nameNode;
			this.dropbox.selectedIndex=0; //nothing selected

			for (var i=0; i<l;i++) {
				if (names[i].hasChildNodes() && names[i].getAttribute('id')!=this.defaultTaxonId) {
					li=document.createElement('li');
					var nameLink=document.createElement("a");
					nameLink.onclick=associate_obj_with_event(this, "click_set_value",names[i].firstChild.data);
					nameLink.onmouseout=associate_obj_with_event(this, "droplist_mouse_out",offset);
					nameLink.onmouseover=associate_obj_with_event(this, "droplist_mouse_over",offset++);
					
					nameLink.params={
						externalId: names[i].getAttribute('id'),
						name: names[i].firstChild.data,
						authority: names[i].getAttribute('authority'),
						commonName: names[i].getAttribute('vulgar')
					};
					
					nameNode=document.createElement('i');
					nameLink.appendChild(nameNode).appendChild(document.createTextNode(nameLink.params.name));
					
					if (nameLink.params.authority) {
						authorityElement=document.createElement('span');
						authorityElement.style.color="gray";
						authorityElement.style.backgroundColor="transparent";
						nameLink.appendChild(authorityElement).appendChild(document.createTextNode(' '+nameLink.params.authority));
					}
					
					if (nameLink.params.commonName) {
						vulgarElement=document.createElement('span');
						vulgarElement.style.backgroundColor="transparent";
						nameLink.appendChild(vulgarElement).appendChild(document.createTextNode(' '+nameLink.params.commonName));
					}
					else {
						nameLink.params.commonName=''; // avoid spurious null values
					}
	
					li.appendChild(nameLink);
					ul.appendChild(li);
				} 
			}	
		} 
		
		if (ul.hasChildNodes()) {
			this.dropbox.appendChild(ul);
			
			this.dropbox.style.width=(this.input.offsetWidth*1.5)+"px";
			this.dropbox.style.display='block';
			this.dropbox.style.zIndex=8;
			this.closeButton.style.left=((this.input.offsetWidth*1.5)-36)+"px";
			this.closeButton.style.display='block';
			
			if (!(standardBrowser || IEversion >=7)) {
				this.iframe.style.top=(this.input.offsetTop+this.input.offsetHeight)+"px";
				this.iframe.style.width=this.dropbox.offsetWidth+"px";
				this.iframe.style.height=this.dropbox.offsetHeight+"px";
				this.iframe.style.display="block";
				this.dropbox.style.top=this.input.offsetHeight+"px";
			}
		} else {
			// nothing to show (no default name and no suggestions)
			this.closeButton.style.display="none";
			this.dropbox.style.display='none';
			if (this.iframe) {this.iframe.style.display="none";}
			this.clear_box();
		}
    }
};

TaxonBox.prototype.click_set_value = function (event,element,value) {
	this.xmlRequest.abort(); // stop any requests which may not have come back yet
	
	this.import_params(element.params);
	this.input.style.color="green";
	this.show_info_link();

	this.dropbox_close_now();

	// stop the default link click action
	if (event.preventDefault) {event.preventDefault();}
	else {event.returnValue=false;}
};

/**
 * writeable place name field box and dropbox,
 * this is the standalone (non-docform version) which is dependent on 
 * external country and region select elements
 * @constructor
 */
function StandalonePlaceName () {
	//this.parentPlaceSet=parentPlaceSet;

	this.construct_dropbox();
	//this.register_search_link(new GooglePlaceSearch());
	//this.register_search_link(new OSPlaceSearch());
	//this.register_search_link(new MultimapSearch());
	this.input.setAttribute('title','Place name, e.g. town/village');
	this.input.defaultValue='placename';
	input_set_default(this.input);
	this.input.onfocus=input_default_focus;
	this.input.onblur=input_default_blur;
	this.input.onkeyup=associate_obj_with_event(this, "name_key_up",'');
}

StandalonePlaceName.prototype=new Dropbox();
StandalonePlaceName.constructor=StandalonePlaceName;

StandalonePlaceName.prototype.destroy = function () {
	//this.parentPlaceSet=null;
	this.input.onkeyup=null;
	this.input.onblur=null;
	this.input.defaultValue=null;
	
	this.countryElement=null;
	this.regionElement=null;
	
	this.abort_xml_request();
	this.destruct_dropbox();
};

StandalonePlaceName.prototype.get_request_url= function () {
	var country=this.countryElement.options[this.countryElement.selectedIndex].value;
	var region=this.regionElement.options[this.regionElement.selectedIndex].value;

	var placeString=this.input.value.replace(trimRegex,''); // trim
	var script=scriptUrl+"XMLplacesearchAtHome/p"+encodeURIComponent(placeString.toLowerCase())+"/c"+country+"/r"+region+"/";
	
	return script;
	//alert('url='+script);
};

StandalonePlaceName.prototype.xml_handler=function (param) {
	if (this.xmlRequest && this.xmlRequest.readyState == 4) {
    	this.hide_progress();
    	
        // only if "OK"
		if (this.xmlRequest && this.xmlRequest.status == 200 && this.xmlRequest.responseXML) {
        	var response  = this.xmlRequest.responseXML.documentElement;
			var places = response?response.getElementsByTagName('place'):[];

			if (places.length) {
				this.clear_box();
				
				this.dropbox.selectedIndex=0; //nothing selected
				if (!standardBrowser) {this.container.style.position="relative";} // for IE ony make position relative at last moment

				var placeLink,ul,li;
				var gridRef;
				var place;
				
				ul=document.createElement('ul');
				
				for (var i=0; (place=places[i]);i++) {
					var placeNames=place.getElementsByTagName('placename');
					if (placeNames.length && placeNames[0].hasChildNodes()) {
						li=ul.appendChild(document.createElement('li'));
						placeLink=li.appendChild(document.createElement("a"));
						
						var element=place.getElementsByTagName('country');	
						var vcElement=place.getElementsByTagName('vc');
						var latLongElement=place.getElementsByTagName('baselatlng');
											
						placeLink.params={
							externalId: place.getAttribute('id'),
							name: place.getElementsByTagName('placename')[0].firstChild.data,
							country: (element.length >0 && element[0].hasChildNodes())?element[0].firstChild.data:'',
							region: (vcElement.length >0 && vcElement[0].hasChildNodes())?vcElement[0].firstChild.data:'',
							pointLatLng: new LatLng()
						};
						
						if (latLongElement.length) {
							placeLink.params.pointLatLng.from_xml(latLongElement[0]);
						}
						
						gridRef=place.getElementsByTagName('gridref');
						if (gridRef.length && gridRef[0].hasChildNodes()) {
							gridRef=gridRef[0];
							placeLink.params.gridref=gridRef.firstChild.data;
							placeLink.params.pointGR=gridRef.getAttribute('centre');
							placeLink.params.defaultPrecision=gridRef.getAttribute('precision');
							placeLink.params.defaultRefType=gridRef.getAttribute('reftype'); // 'gridpoint','gridsquare','point'
						}
						
						placeLink.onclick=associate_obj_with_event(this, "place_click_set_value",placeLink.name);
						placeLink.onmouseout=associate_obj_with_event(this, "droplist_mouse_out",i+1);
						placeLink.onmouseover=associate_obj_with_event(this, "droplist_mouse_over",i+1);

						placeLink.appendChild(document.createTextNode(placeLink.params.name));
						
						element=place.getElementsByTagName('regname');
						if (element.length >0 && element[0].hasChildNodes()) {
							var regionSpan=document.createElement('span');
							regionSpan.style.color=(placeLink.params.region==="AMBIGUOUS")?"red":"gray";
							regionSpan.style.backgroundColor="transparent";
							placeLink.appendChild(regionSpan).appendChild(document.createTextNode(' '+element[0].firstChild.data));
						}
					} 
				}
				
				this.dropbox.appendChild(ul);
				this.dropbox.style.width=(this.input.offsetWidth*3)+"px";
				this.dropbox.style.zIndex=8;
				
				this.closeButton.style.left=((this.input.offsetWidth*3)-32)+"px";
				this.closeButton.style.display="block";
							
				this.dropbox.style.display='block';
			
				if (!(standardBrowser || IEversion >=7)) {
					this.iframe.style.top=(this.input.offsetTop+this.input.offsetHeight)+"px";
					this.iframe.style.width=this.dropbox.offsetWidth+"px";
					this.iframe.style.height=this.dropbox.offsetHeight+"px";
					this.iframe.style.display="block";
					this.dropbox.style.top=this.input.offsetHeight+"px";
				}
				
			} else {
				this.clear_box();
				this.dropbox.style.height="auto";
				this.dropbox.style.display='none';
				if (this.iframe) {this.iframe.style.display="none";}
				this.closeButton.style.display="none";
			}
		} 
    } 
};

StandalonePlaceName.prototype.place_click_set_value = function (event,element,value) {
	this.abort_xml_request(); // stop any requests which may not have come back yet
	//this.parentPlaceSet.apply_change(element.params);
	
	if (element.params) { // allow for weird IE bug where params may not be set
		this.import_params(element.params);
		this.input.style.color='green';
		//this.show_info_link();
		this.dropbox_close_now();
	}
	
	this.dropbox_close_now();
	
	//alert('name='+params.name);

	// stop the default link click action
	if (event.preventDefault) {event.preventDefault();}
	else {event.returnValue=false;}
};

StandalonePlaceName.prototype.import_params = function (params)
{	
	this.placeId=params.externalId;
	this.input.value=params.name;
	
	if (this.idContainerElement) {
		this.idContainerElement.value=this.placeId;
	}
};

/**
 * An Id container element is used for standalone drop boxes, which are part of forms that
 * need a mechanism to return the selected element id or to read associated country and regioncode.
 *  
 * @param <string> idKey
 */
StandalonePlaceName.prototype.wrapper_link_related_elements = function (idKey) {
	// look for any additional input element which contains an external id
 	// given idKey may have numerical suffix in square brackets which needs to be stripped then reapplied
 	
 	//lookup id element
 	var containerElement=document.getElementById((idKey.search(/\[/)!=-1)?idKey.replace(/\[/,'id['):idKey+'id');
 	if (containerElement) {
 		this.idContainerElement=containerElement;
		this.taxonId=this.idContainerElement.value;
		this.input.style.color=(!!this.taxonId)?'green':'black';
		//this.set_tool_tip();
		//this.show_info_link();
 	}
 	
 	//lookup country
 	containerElement=document.getElementById((idKey.search(/\[/)!=-1)?idKey.replace(/\[/,'country['):idKey+'country');
 	if (containerElement) {
 		this.countryElement=containerElement;
 	}
 	
 	//lookup region
 	containerElement=document.getElementById((idKey.search(/\[/)!=-1)?idKey.replace(/\[/,'region['):idKey+'region');
 	if (containerElement) {
 		this.regionElement=containerElement;
 	}
};

StandalonePlaceName.prototype.name_key_up=function (event,element,param) {
	if (valid_key(event)) {
		var placeString=element.value.replace(trimRegex,''); // trim
		//this.parentPlaceSet.reset_place(); // edited entry so id now not valid
		//this.parentPlaceSet.hide_info_link();
		if (placeString.length >0) {	
			this.input.style.color="black";
			this.queue_xml_request(placeString.length <2);
			//this.show_search_links();
		} else {
			this.dropbox_close_now();
		}
	} else {
		if (element.value.length ===0) {	
			this.dropbox_close_now();
		} else {
			if (event.keyCode==38) {this.change_selection(-1);} // up arrow
			else if (event.keyCode===40) {this.change_selection(1);} // down arrow
			else if (event.keyCode===13) {
				// return key
				
				this.dropbox_close_now();
			}
		}
	}
};

StandalonePlaceName.prototype.change_selection =function (direction) {
	var links=this.dropbox.getElementsByTagName('a');
	var linksLength=links.length;

	if (linksLength && ((direction > 0 && this.dropbox.selectedIndex < linksLength) || (direction <0 && this.dropbox.selectedIndex >1))) {
		this.set_selection(this.dropbox.selectedIndex+direction,links);
		//this.parentPlaceSet.apply_change(links[this.dropbox.selectedIndex-1].params);
		this.import_params(links[this.dropbox.selectedIndex-1].params);
	}
};

/**
 *
 */
function LatLng() {
	this.lat=''; // complete (real number) latitude for centroid, for external linkage etc.
	this.latD=''; // integer degrees latitude
	this.latM=''; // integer minutes latitude
	this.latS=''; // real seconds latitude
	this.lng=''; // complete (real number) longitude for centroid, for external linkage etc.
	this.lngD=''; // integer degrees longitude
	this.lngM=''; // integer minutes longitude
	this.lngS=''; // real seconds longitude
	this.hemisphere=''; // hemisphere 'E' | 'W' (caps)
}

LatLng.prototype.from_xml = function (xml) {
	// <latitude d="" m="" s="">nnn</latitude>
	// <longitude d="" m="" s="" hemisphere="E|W">nnn</longitude>
	if (xml.hasChildNodes()) {
		var latElement=xml.getElementsByTagName('latitude')[0];
		var lngElement=xml.getElementsByTagName('longitude')[0];
		
		this.lat=(latElement.hasChildNodes() && latElement.firstChild.data)?latElement.firstChild.data:''; // need to avoid potential null
		this.latD=latElement.getAttribute('d');
		this.latM=latElement.getAttribute('m');
		this.latS=latElement.getAttribute('s');
		
		this.lng=(lngElement.hasChildNodes() && lngElement.firstChild.data)?lngElement.firstChild.data:''; // need to avoid potential null
		this.lngD=lngElement.getAttribute('d');
		this.lngM=lngElement.getAttribute('m');
		this.lngS=lngElement.getAttribute('s');
		this.hemisphere=lngElement.getAttribute('hemisphere');
	}
};

LatLng.prototype.to_xml = function(doc) {
	var fragment=doc.createDocumentFragment();
	var lat=doc.createElement('latitude');
	lat.setAttribute('d',this.latD);
	lat.setAttribute('m',this.latM);
	lat.setAttribute('s',this.latS);
	lat.appendChild(doc.createTextNode(this.lat));
	
	var lng=doc.createElement('longitude');
	lng.setAttribute('d',this.lngD);
	lng.setAttribute('m',this.lngM);
	lng.setAttribute('s',this.lngS);
	lng.setAttribute('hemisphere',this.hemisphere);
	lng.appendChild(doc.createTextNode(this.lng));
	
	fragment.appendChild(lat);
	fragment.appendChild(lng);
	return fragment;
};

LatLng.prototype.to_string = function() {
	
};

LatLng.prototype.is_set = function() {
	return (this.lat || this.lng);
};

/**
 * writeable person name field box and dropbox
 * @constructor
 */
function NameBox (form,context) {
	this.parentForm=form;
	
	this.context=context; // 'col, det or prov'

	this.illegibleButton=document.createElement("input");
	this.illegibleButton.type="checkbox";
	
	this.dob=null;
	this.dod=null;
	this.dateString=null;
	this.wikiName=null;

	this.personId='';
	//this.institutionId=this.parentForm.parentData.institutionId;
	
	this.construct_dropbox();

	this.illegible=document.createElement("label");
	this.illegible.appendChild(document.createTextNode("("));
	this.illegible.appendChild(this.illegibleButton);
	this.illegible.appendChild(document.createTextNode("illegible)"));

	this.illegible.style.verticalAlign='top';

	this.input.onkeyup=associate_obj_with_event(this, "input_key_up",'');

	this.input.defaultValue='name';
	input_set_default(this.input);
	this.input.onfocus=input_default_focus;
	//this.input.onblur=input_default_blur;

	this.infoLink=info_link();
}

NameBox.prototype =new Dropbox();
NameBox.prototype.constructor=NameBox;

NameBox.prototype.destroy=function () {
	this.input.onchange=null;
	this.input.onkeyup=null;
	this.input.onblur=null;
	this.input.onfocus=null;

	this.input.defaultValue=null;

	this.dob=null;
	this.dod=null;
	this.dateString=null;
	this.wikiName=null;

	this.illegible=null;
	
	if (this.focusInfoEventHandle) {this.focusInfoEventHandle=remove_event_handler(this.focusInfoEventHandle);} // handler used to update info pane
	if (this.blurInfoEventHandle) {this.blurInfoEventHandle=remove_event_handler(this.blurInfoEventHandle);} // handler used to update info pane

	this.abort_xml_request();
	this.destruct_dropbox();

	this.button=null;
	this.illegibleButton=null;
	this.requestURL=null;
	this.parentForm=null;
	this.infoLink=null;
	this.institutionId=null;
	this.context=null;
	
	this.parentNameList=null;
	this.rowId=null;
};

/**
 * An Id container element is used for standalone drop boxes, which are part of forms that
 * need a mechanism to return the selected element id.
 * 
 * @param <string> idKey
 */
NameBox.prototype.wrapper_link_related_elements = function (idKey) {
	// look for any additional input element which contains an external id
 	// given idKey may have numerical suffix in square brackets which needs to be stripped then reapplied
 	
 	var idContainerElement=document.getElementById((idKey.search(/\[/)!=-1)?idKey.replace(/\[/,'id['):idKey+'id');
 	if (idContainerElement) {
 		this.idContainerElement=idContainerElement;
		this.personId=this.idContainerElement.value;
		if (this.personId) {
			this.show_info_link();
		}
		this.input.style.color=(!!this.personId)?'green':'black';
 	}
};

NameBox.prototype.show_info_link = function () {
	if (this.infoLink.parentNode) {this.infoLink.parentNode.removeChild(this.infoLink);}

	this.infoLink.href=scriptUrl+"collector/"+this.personId+"/";
	this.container.insertBefore(this.infoLink,this.dropbox);
};

NameBox.prototype.hide_info_link = function () {
	if (this.infoLink.parentNode) {this.infoLink.parentNode.removeChild(this.infoLink);}
};

/**
 * @return url and query parameters for xml request
 */
NameBox.prototype.get_request_url= function () {
	var nameString=this.input.value.replace(trimRegex,''); // trim
	return scriptUrl+"XMLcolsearchAtHome/"+encodeURIComponent(nameString.toLowerCase())+"/"+this.institutionId+"/"+this.context+"/";
};

/**
 * send request to suggest mechanism instead of normal query route
 * used when query field is blank
 * @param form parent SpecimenForm
 * @param <string> type 'col','prov' or 'det'
 * @todo consider how to generalize date and col/prov reference (form.field.colDate)
 */
NameBox.prototype.issue_suggestion_xml_request= function (form,type) {
	var fromDate=form.field.colDate.fromJdMinimum;
	var toDate=form.field.colDate.toJdMaximum;
	var taxon=form.field.taxa.defaultTaxonId;
	var instId=form.parentData.institutionId;
	
	var provIds=(form.field.provenances)?form.field.provenances.id_list():'';
	var colIds=(form.field.collectors)?form.field.collectors.id_list():'';

	this.pending=0;

	var nameString=this.input.value.replace(trimRegex,''); // trim
	var url=scriptUrl+"XMLcolsuggest.php?query="+encodeURIComponent(nameString.toLowerCase())+"&fromdate="+fromDate+"&todate="+toDate+"&taxon="+taxon+"&field="+type+"&instid="+instId+"&prov="+provIds.toString()+"&col="+colIds.toString();

	if (this.xmlRequest==null || this.xmlRequest.readyState==4 || this.requestURL!=url) {
		this.xmlRequest=agnostic_XMLHttpRequest(); // create the request object
		this.requestURL=url;
		this.show_progress();
		xml_request(this.xmlRequest,url,associateObjectWithXMLRequest(this, "xml_handler", ''));
	}
};

NameBox.prototype.input_key_up=function (event,element,param) {
	if (valid_key(event)) {
		if (this.personId) {
			// refresh suggestion list, now that name has been edited
			
			this.personId=''; // edited entry so id now not valid
			if (this.idContainerElement) {
				this.idContainerElement.value='';
			}
			this.dob=null;
			this.dod=null;
			this.dateString=null;
			this.wikiName=null;
			//this.parentForm.parentData.info.seek_provenance(this.parentForm,this.context,element,this,this.rowId,this.parentNameList);
		
			this.hide_info_link();
			element.style.color='black';
		}

		var nameString=element.value.replace(trimRegex,''); // trim

		if (nameString.length >0) {
			this.queue_xml_request(nameString.length <2);
		} else {
			this.abort_xml_request();
			this.dropbox_close_now();
		}	
	} else {
		if (event.keyCode===38) {
			this.change_selection(-1); // up arrow
		} else {
			if (event.keyCode===40) {
				this.change_selection(1); // down arrow
			} else {
				if (event.keyCode===13) {
					// return key
					this.abort_xml_request();
					this.dropbox_close_now();
				}
			}
		}
	}
};

NameBox.prototype.import_params = function (params)
{	
	this.personId=params.externalId;
	this.input.value=params.name;
	this.dob=params.dobjd;
	this.dod=params.dodjd;
	this.dateString=params.dateString;
	this.wikiName=params.wikiName;
	
	if (this.idContainerElement) {
		this.idContainerElement.value=this.personId;
	}
};

NameBox.prototype.change_selection =function (direction)
{
	var links=this.dropbox.getElementsByTagName('a');

	if (links.length && ((direction > 0 && this.dropbox.selectedIndex < links.length) || (direction <0 && this.dropbox.selectedIndex >1))) {
		this.set_selection(this.dropbox.selectedIndex+direction,links);
		this.import_params(links[this.dropbox.selectedIndex-1].params);	
	
		this.input.style.color='green';
		this.show_info_link();
		if (this.input.onchange) {this.input.onchange();} // artificial change event required for IE
	}
	this.parentForm.change(); // new addition
};


NameBox.prototype.xml_handler=function (param) {
	if (this.xmlRequest && this.xmlRequest.readyState == 4) {
    	this.hide_progress();
		var response  = (this.xmlRequest.responseXML)?this.xmlRequest.responseXML.documentElement:null;

		if (response !==null) {var names = response.getElementsByTagName('name');} // if no names then IE can return null doc element
		else {var names=[];}

		var l=names.length;

		if (l) {
			var suggest=response.getAttribute('suggest');
			
			this.clear_box();
			if (!standardBrowser) {this.container.style.position="relative";} // for IE ony make position relative at last moment

			this.dropbox.selectedIndex=0; //nothing selected
			
			var ul=document.createElement('ul');
			var li;
			
			for (var i=0; i<l;i++) {
				var name=names[i];
				
				var nameId=name.getAttribute('id');
				var dob=name.getAttribute('dob');
				var dod=name.getAttribute('dod');
				var dateString='';

				if (name.hasChildNodes()) {
					var linkText=name.firstChild.data;
					var nameText=name.firstChild.data;
				} else {
					var linkText=''; // odd error with null name, upsets IE
					var nameText='';
				}

				if (dob!='' || dod!='') {
					dateString=dob+'-'+dod;
					//linkText+=' ('+dateString+')';
				}

			if (nameId) {
					li=ul.appendChild(document.createElement('li'));
					var nameLink=li.appendChild(document.createElement("a"));
					nameLink.onclick=associate_obj_with_event(this, "click_set_value",nameText);
					nameLink.onmouseover=associate_obj_with_event(this, "droplist_mouse_over",i+1);
					nameLink.onmouseout=associate_obj_with_event(this, "droplist_mouse_out",i+1);
					if (suggest) { 
						nameLink.flagChange=true;
					}

					nameLink.params={
						name: nameText,
						externalId: nameId,
						dob: dob,
						dobjd: name.getAttribute('dobjd'),
						dod: dod,
						dodjd: name.getAttribute('dodjd'),
						dateString: dateString,
						wikiName: (name.getAttribute('wikiname'))?name.getAttribute('wikiname'):null
					};
					
					nameLink.appendChild(document.createTextNode(linkText));
					
					if (dateString) {
						var dateSpan=document.createElement('span');
						dateSpan.style.color="gray";
						dateSpan.style.backgroundColor="transparent";
						nameLink.appendChild(dateSpan).appendChild(document.createTextNode(' ('+dateString+')'));
					}
				}
			}
			this.dropbox.appendChild(ul);
			this.dropbox.style.width=this.input.offsetWidth+"px";
			this.closeButton.style.left=(this.input.offsetWidth-36)+"px";
			this.closeButton.style.display='block';
			this.dropbox.style.display='block';
			this.dropbox.style.zIndex=8;
			
			if (!(standardBrowser || IEversion >=7)) {
				this.iframe.style.top=(this.input.offsetTop+this.input.offsetHeight)+"px";
				this.iframe.style.width=this.dropbox.offsetWidth+"px";
				this.iframe.style.height=this.dropbox.offsetHeight+"px";
				this.iframe.style.display="block";
				this.dropbox.style.top=this.input.offsetHeight+"px";
			}
		} else {
			this.dropbox.style.display='none';
			this.closeButton.style.display="none";
			if (this.iframe) {this.iframe.style.display="none";}
			this.clear_box();
		}
    }
};

/**
 * @return person xml element
 */
NameBox.prototype.get_person_xml = function (doc) {
	var name=doc.createElement('person');
	if (this.personId >0) {name.setAttribute('id',this.personId);}
	name.appendChild(doc.createTextNode(this.input.value.replace(trimRegex,'')));

	if (this.dob >0) {
		name.setAttribute('dob',year_from_JD(this.dob));
		name.setAttribute('dobjd',this.dob);
	}

	if (this.dod >0) {
		name.setAttribute('dod',year_from_JD(this.dod));
		name.setAttribute('dodjd',this.dod);
	}
	
	return name;
};

NameBox.prototype.populate_from_xml = function (personXML) {
	this.personId=personXML.getAttribute('id');

	this.dateString='';
	if (personXML.getAttribute('dobjd')!=null) {
		this.dob=personXML.getAttribute('dobjd');
		this.dateString=personXML.getAttribute('dob');
	}
	this.dateString+='-';
	
	if (personXML.getAttribute('dodjd')!=null) {
		this.dod=personXML.getAttribute('dodjd');
		this.dateString+=personXML.getAttribute('dod');
	}
	
	if (personXML.getAttribute('wikiname')!=null) {
		this.wikiName=personXML.getAttribute('wikiname');
	}

	this.input.value=personXML.firstChild.data;
	if (this.personId >0) {
		this.show_info_link();
		this.input.style.color='green';
	}
	else if (this.input.value.length) {this.input.style.color='black';}
};

NameBox.prototype.click_set_value = function (event,element,value) {
	var flag=element.flagChange;
	this.abort_xml_request(); // stop any requests which may not have come back yet
	
	// stop the default link click action
	if (event.preventDefault) {
		event.preventDefault();
	} else {
		event.returnValue=false;
	}
	
	if (element.params) { // allow for weird IE bug where params may not be set
		this.import_params(element.params);
		this.input.style.color='green';
		this.show_info_link();
		this.dropbox_close_now();
	
		/*
		if (flag) {
			this.parentNameList.input_change(null,null,this.rowId);
		} else {
			this.parentForm.verify();
		}
		this.parentForm.change(); // new addition
		
		if (this.parentNameList) {
			this.parentForm.parentData.info.seek_provenance(this.parentForm,'col',this.input,this,this.rowId,this.parentNameList);
		}
		*/
	}
};

/**
 *
 */
function deleteButton(buttonTitle) {
	// returns a delete (x) button
	var button=document.createElement("button");
	var img=button.appendChild(document.createElement("img"));
	img.src="http://static.herbariumathome.org/Xbutton.png";
	button.className="img";
	button.setAttribute('type','button');
	button.style.background='transparent';
	if (standardBrowser) {img.alt="delete";}
	button.title=buttonTitle;
	return button;
}

/**
 *
 */
function plusButton(buttonTitle) {
	// returns an add (+) button
	var button=document.createElement("button");
	var img=button.appendChild(document.createElement("img"));
	img.src="http://static.herbariumathome.org/plusbutton.png";
	button.className="img";
	button.setAttribute('type','button');
	button.style.background='transparent';
	if (standardBrowser) {img.alt="add";}
	button.title=buttonTitle;
	return button;
}

/**
 *
 */
function more_button(buttonTitle) {
	// returns a 'more' (...) button
	var button=document.createElement("button");
	var img=button.appendChild(document.createElement("img"));
	img.src="http://static.herbariumathome.org/morebutton.png";
	button.className="img";
	button.setAttribute('type','button');
	button.style.background='transparent';
	if (standardBrowser) {img.alt="expand details";}
	button.title=buttonTitle;
	return button;
}

/**
 *
 */
function assimilate_HTML_fragments() {
	var container=document.getElementById('fragmentcontainer');
	var fragments=container.getElementsByTagName('div');
	var i;
	
	if (document.createRange) {
		var range=document.createRange();
		
		for (i=fragments.length-1; i>=0;i--) {
			range.selectNodeContents(fragments[i]);
			globalMessages[fragments[i].id]=range.extractContents();
			fragments[i].parentNode.removeChild(fragments[i]);
		}
	} else {
		var j,l;
		for (i=fragments.length-1; i>=0;i--) {
			globalMessages[fragments[i].id]=document.createDocumentFragment();
			
			l=fragments[i].childNodes.length;
			
			for (j=0; j<l;j++) {
				globalMessages[fragments[i].id].appendChild(fragments[i].childNodes[j].cloneNode(true));
			}
		}
	}
}

if (standardBrowser)
{
	var stdAssociateObjWithEvent = function(target,obj, methodName,param) {
		return (function(e){
			return obj[methodName](e, target, param);
		});
	};

	var register_event_handler = function(target,eventName,obj,methodName,param,capturing) {
		var stackElement=globalEventStack.length;
		target.addEventListener(eventName,globalEventStack[stackElement]=stdAssociateObjWithEvent(target,obj, methodName,param),capturing==true);

		globalEventTarget[stackElement]=target;
		globalEventMethodName[stackElement]=methodName;
		globalEventType[stackElement]=eventName;
		globalEventCapturing[stackElement]=(capturing==true);
		
		return stackElement; // ref to attached function, which is returned (in case needed for removal later)
	};


	var associate_obj_with_event = function(obj, methodName,param) {
	    /* The returned inner function is intended to act as an event
	       handler for a DOM element:-
	    */
	    return (function(e){ 
	        return obj[methodName](e, this, param);
	    });
	};
}
else
{
	var ie_associate_obj_with_event = function(obj, methodName,param){
		return (function(){
			return obj[methodName](window.event, this.event.srcElement, param);
		});
	};

	var register_event_handler = function(target,eventName,obj,methodName,param) {
		var stackElement=globalEventStack.length;
		target.attachEvent('on'+eventName,globalEventStack[stackElement]=ie_associate_obj_with_event(obj, methodName,param));

		globalEventTarget[stackElement]=target;
		globalEventMethodName[stackElement]=methodName;
		globalEventType[stackElement]=eventName;
		return stackElement; // ref to attached function, which is returned (in case needed for removal later)
	};
	
	// from http://jibbering.com/faq/faq_notes/closures.html
	var associate_obj_with_event = function (obj, methodName,param) {
	    // The returned inner function is intended to act as an event handler for a DOM element
	    return (function() {
        	return obj[methodName](window.event, this, param);
    	});
	};
}

if (document.removeEventListener) { // DOM event model
	var remove_event_handler = function(handler) {		
		if (globalEventStack[handler]) {
			globalEventTarget[handler].removeEventListener(globalEventType[handler],globalEventStack[handler], globalEventCapturing[handler]);
			//var idString=globalEventMethodName[handler]+" "+globalEventTarget[handler]+" "+globalEventType[handler];
			delete globalEventStack[handler];
			delete globalEventMethodName[handler];
			delete globalEventType[handler];
			delete globalEventTarget[handler];
			delete globalEventCapturing[handler];
		} else {
			throw new error('herb@home: event remove failure');
		}
		return null;
	};
} else {
	var remove_event_handler = function(handler) {

		if (globalEventStack[handler]) {
			globalEventTarget[handler].detachEvent("on"+globalEventType[handler],globalEventStack[handler]);
			delete globalEventStack[handler];
			delete globalEventMethodName[handler];
			delete globalEventTarget[handler];
			delete globalEventType[handler];	
		} else {
			throw new error('herb@home: event remove failure');
		}
		return null;
	};
}

/**
 * returns an XML http request object
 */
function associateObjectWithXMLRequest(obj, methodName, param) {
	return (function(){
		return obj[methodName](param);
	});
}

if (document.implementation && document.implementation.createDocument) {
	var create_xml_doc = function () {
		doc=document.implementation.createDocument("","response",null); // create blank XML response document

		if (doc.documentElement === null) { // Safari 1.2 bug
			// add documentElement
			doc.appendChild(doc.createElement("response"));
		}
		return doc;
	};
}
else if (window.ActiveXObject) {
	var create_xml_doc = function () {
		doc = new ActiveXObject("Microsoft.XMLDOM");
		doc.documentElement=doc.createElement('response');
		return doc;
	};
}
else {
	alert ('Unfortunately your web browser does not have some capabilites needed.');
}

if (window.XMLHttpRequest) {
	var agnostic_XMLHttpRequest = function () {
		return new XMLHttpRequest();
	};

	var load_xml_doc=function(url,handler)
	{
		// native XMLHttpRequest object
		var req = new XMLHttpRequest();
		req.onreadystatechange = handler;
		req.open("GET", url, true);
		req.send(null);
		return req;
	};
} else {
	var agnostic_XMLHttpRequest = function () {
		return new ActiveXObject("Microsoft.XMLHTTP");
	};

	var load_xml_doc=function(url,handler) {
		var req = new ActiveXObject("Microsoft.XMLHTTP");
		req.onreadystatechange = handler;
		req.open("GET", url, true);
		req.send();
		return req;
	};
}

/**
 *
 */
function xml_request(req,url,handler)
{
	req.onreadystatechange = handler;
	req.open("GET", url, true);
	req.send(null);
}

/**
 *
 */
function xml_save(req,url,handler,data)
{
	req.onreadystatechange = handler;
	req.open("POST", url, true);
	req.send(data);
}

/**
 *
 */
function xml_synchronous_save(req,url,handler,data)
{
	req.open("POST", url, false);
	req.send(data);
	handler();
}

/**
 *
 */
function synchronous_xml_request(req,url,handler)
{
	req.open("GET", url, false);
	req.send(null);
	if (handler) {handler();}
}

/**
 * Set windows title and document h1 tag
 * @param <string> text window title
 * @param <string> header h1 heading
 */
function set_title(text,header) {
	document.title=text;
	var title=document.getElementsByTagName('title');
	title[0].text=text;

	var h1=document.getElementsByTagName('h1')[0];
	h1.firstChild.data=header;
}

/**
 *
 */
function xml_serialize(xmlNode) {
  var text = false;
  try {
    // Gecko-based browsers, Safari, Opera.
    var serializer = new XMLSerializer();
    text = serializer.serializeToString(xmlNode);
  }
  catch (e) {
    try {
      // Internet Explorer.
      text = xmlNode.xml;
    }
    catch (e) {}
  }
  return text;
}

/**
 *
 */
function info_link() {
	var infoLink=document.createElement('a');
	infoLink.style.fontStyle="italic";
	infoLink.style.textDecoration="none";
	infoLink.style.fontWeight="bold";
	infoLink.style.fontSize="1.3em";
	infoLink.style.fontFamily="serif";
	infoLink.appendChild(document.createTextNode(' i '));
	infoLink.setAttribute('target','_blank');
	return infoLink;
}

/**
 *
 */
function info_link_doc(localDoc) {
	var infoLink=localDoc.createElement('a');
	infoLink.style.fontStyle="italic";
	infoLink.style.textDecoration="none";
	infoLink.style.fontWeight="bold";
	infoLink.style.fontSize="1.3em";
	infoLink.style.fontFamily="serif";
	infoLink.appendChild(localDoc.createTextNode(' i '));
	infoLink.setAttribute('target','_blank');
	return infoLink;
}

/**
 * read value from cookie
 * @return value or NULL
 */
function cookie_read_value(key) {
	if (document.cookie.length) {
		var start = document.cookie.indexOf(key+'=');
		if (start >-1) {
			var end=document.cookie.indexOf(';',start);
			return unescape(document.cookie.substring(start+key.length+1,end>0?end:1024));
		}
	}
	return null;
}

/**
 * set cookie value
 * @param {string} key
 * @param {string} value
 * @param {int} expiry (days into the future)
 * @return value or NULL
 */
function cookie_set_value(key,value,days,path) {
	var string=key+'='+escape(value);
	if (days) {
		var today=new Date();
	
		var offsetDate=new Date(today.getTime()+(days*24*60*60*1000));
		string+='; expires='+offsetDate.toUTCString();
	}
	
	string+='; path='+(path?path:'/');
	document.cookie=string;
}

// Internet explorer doesn't natively define xml node type labels
// from http://www.alistapart.com/articles/crossbrowserscripting
if (document.ELEMENT_NODE == null) {
	document.ELEMENT_NODE = 1;
	document.ATTRIBUTE_NODE = 2;
	document.TEXT_NODE = 3;
	document.CDATA_SECTION_NODE = 4;
  	document.ENTITY_REFERENCE_NODE = 5;
	document.ENTITY_NODE = 6;
	document.PROCESSING_INSTRUCTION_NODE = 7;
	document.COMMENT_NODE = 8;
	document.DOCUMENT_NODE = 9;
	document.DOCUMENT_TYPE_NODE = 10;
	document.DOCUMENT_FRAGMENT_NODE = 11;
	document.NOTATION_NODE = 12;
}

if (!document.importNode) {
	/**
	 * Internet Explorer doesn't implement Document.importNode
	 * from http://www.alistapart.com/articles/crossbrowserscripting
	 */
	 
	var import_node = function(doc,node, allChildren) {
		switch (node.nodeType) {
			case document.ELEMENT_NODE:
			var newNode = doc.createElement(node.nodeName);
			// does the node have any attributes to add?
			if (node.attributes && node.attributes.length > 0) {
				for (var i = 0, il = node.attributes.length; i < il;i++) {
					newNode.setAttribute(node.attributes[i].nodeName, node.getAttribute(node.attributes[i].nodeName));
				}
			}
			//are we going after children too, and does the node have any?
			if (allChildren && node.childNodes && node.childNodes.length > 0) {
				for (var i = 0, il = node.childNodes.length; i < il;i++) {
					newNode.appendChild(import_node(doc,node.childNodes[i], allChildren));
				}
			}
			return newNode;
			break;
			
			case document.TEXT_NODE:
			case document.CDATA_SECTION_NODE:
			case document.COMMENT_NODE:
			return doc.createTextNode(node.nodeValue);
			break;
		}
	};
}