//
// js_utilities.js
//
// Common JavaScript functions
//
// $Log: js_utilities.js,v $
// Revision 1.16  2008/11/24 17:00:49  david
// Added send_event_by_id()
//
// Revision 1.15  2008/10/21 16:10:45  david
// Added send_event()
//
// Revision 1.14  2008/08/06 18:35:24  david
// Fixed missing semicolons
//
// Revision 1.13  2008/01/09 14:33:11  david
// Rewrote create_http_req_object() to encapsulate it in a higher-level object
// because braindead IE can't attach custom properties to an ActiveX object,
// added code to get_event_target() to allow cross-frame event handling in IE
//
// Revision 1.12  2007/08/23 19:58:06  david
// Added get window pos functions and XMLHTTPRequest factory function
//
// Revision 1.11  2007/05/18 20:32:00  david
// Added remove event handler functions
//
// Revision 1.10  2007/05/11 19:51:23  david
// Some bug fixes, added add_event_by_id()
//
// Revision 1.9  2007/03/15 21:12:02  david
// Fixed get_mouse_x(), added dechex(), rgb()
//
// Revision 1.8  2007/02/09 22:28:31  david
// Added get_json_message()
//
// Revision 1.7  2007/01/30 21:49:53  david
// Added trim() and isspace()
//
// Revision 1.6  2007/01/24 14:15:43  david
// Added HTTP request functions, binsearch()
//
// Revision 1.5  2007/01/22 23:24:17  david
// Added documentation
//
// Revision 1.4  2007/01/22 23:05:34  david
// Added get_mouse_x_pos(), get_mouse_y_pos(), max(), get_x_pos(), get_y_pos()
//
// Revision 1.3  2007/01/22 19:41:44  david
// Added get_x_scroll() and get_y_scroll()
//
// Revision 1.2  2006/09/12 19:43:34  david
// Added get_event_target()
//
// Revision 1.1  2006/03/31 17:45:02  david
// Initial checkin
//
//
//

// add an event listener to a document object
//
// elem: the DOM element as returned by e.g. document.getElementById()
// event_name: the name of the event e.g. click, mousedown etc
// func: the name of the function that this event should call
//
function add_event(elem,event_name,func)
{
	if (elem.addEventListener) // mozilla
		{
		elem.addEventListener(event_name,func,true);
		return true;
		} 
	else if(elem.attachEvent) // IE
		return elem.attachEvent("on"+event_name,func);
	else 
		return false;
}

// add an event listener to a document object by ID
//
// elid: the DOM element ID
// event_name: the name of the event e.g. click, mousedown etc
// func: the name of the function that this event should call
//
function add_event_by_id(elid,event_name,func)
{
	return add_event(document.getElementById(elid),event_name,func);
}

// remove an event listener from a document object
//
// elem: the DOM element as returned by e.g. document.getElementById()
// event_name: the name of the event e.g. click, mousedown etc
// func: the name of the function that the event handler calls
//
function remove_event(elem,event_name,func)
{
	if(elem.removeEventListener)
		{
		elem.removeEventListener(event_name,func,true);
		return true;
		}
	else if(elem.detachEvent)
		return elem.detachEvent("on"+event_name,func);
	else
		return false;
}

// remove an event listener from a document object by ID
//
// elid: the DOM element ID
// event_name: the name of the event e.g. click, mousedown etc
// func: the name of the function that the event handler calls
//
function remove_event_by_id(elid,event_name,func)
{
	return remove_event(document.getElementById(elid),event_name,func);
}

// prevent an event from propagating any further up the chain of event
// handlers
//
// e: the event to cancel
//
function kill_event(e)
{
	if(window.event) // IE
		{
		window.event.cancelBubble=true;
		window.event.returnValue=false;
		}
	else if(e && e.preventDefault && e.stopPropagation) // mozilla
		{
		e.preventDefault();
		e.stopPropagation();
		}
}

// send an event directly to an element as if it had been triggered
// by user interaction
//
// elem: the DOM element as returned by e.g. document.getElementById()
// event_name: the name of the event e.g. click, mousedown etc
//
function send_event(elem,event_name)
{
	if(document.createEventObject) // IE
		{
		var event=document.createEventObject();
		return elem.fireEvent('on'+event_name,event);
		}
	else // Mozilla etc
		{
		var event=document.createEvent("HTMLEvents");
		event.initEvent(event_name,true,true);
		return (elem.dispatchEvent(event)==false);
		}
}

function send_event_by_id(elid,event_name)
{
	return send_event(document.getElementById(elid),event_name);
}

// given an event, usually a mouse event, find the document object
// it is associated with
//
// e: the event
//
// returns DOM element associated with e
//
function get_event_target(e)
{
	if(window.event)
		e=window.event;

	if(e.srcElement) // mozilla
		target=e.srcElement;
	else if(e.target) // IE
		target=e.target;

	return target;
}

// get X (Y) position for a mouse event 
//
// evt: the event
//
// returns X (Y) position of mouse when event was triggered
//
// note: in combination with get_event_target(), get_x_pos
// and get_y_pos() this can be used to calculate where within
// an element a mouse event occurred. This can be handy for
// client-side image maps etc.
//
function get_mouse_x_pos(evt)
{
	var mx;

	if(evt && evt.pageX) // mozilla
		mx=evt.pageX;
	else if(window.event) // IE
		mx=window.event.clientX+document.body.scrollLeft;

	mx=max(mx,0); // clip negative values

	return mx;
}

function get_mouse_y_pos(evt)
{
	var my;

	if(evt && evt.pageY) // mozilla
		my=evt.pageY;
	else if(window.event) // IE
		my=window.event.clientY+document.body.scrollTop;

	my=max(my,0); // clip negative values

	return my;
}

// the maximum of two integers
//
function max(a,b)
{
	if(a>b)
		return a;
	return b;
}

// get the onscreen position of a DOM element
//
// elem: the DOM element 
//
// return X (or Y) position of left (top) of element
//
// see note for get_mouse_x_pos()
//
function get_x_pos(elem)
{
	var curleft=0;
	if (elem.offsetParent)
		{
		while(elem.offsetParent)
			{
			curleft+=elem.offsetLeft;
			elem=elem.offsetParent;
			}
		}
	else if(elem.x)
		curleft+=elem.x;

	return curleft;
}

function get_y_pos(elem)
{
	var curtop=0;
	if (elem.offsetParent)
		{
		while(elem.offsetParent)
			{
			curtop+=elem.offsetTop;
			elem=elem.offsetParent;
			}
		}
	else if(elem.y)
		curtop+=elem.y;

	return curtop;
}

// get amount by which current document is scrolled. Note switches
// to account for retarded Internet Explorer deviation from how
// the rest of the known universe handles this
//
function get_x_scroll()
{
	var xscroll;

	if (window.pageXOffset) // all except Explorer
		xscroll=window.pageXOffset;
	else if(document.documentElement && document.documentElement.scrollLeft)
		xscroll=document.documentElement.scrollLeft;
	else if(document.body)
		xscroll=document.body.scrollLeft;
	
	return xscroll;
}

function get_y_scroll()
{
	var yscroll;

	if (window.pageYOffset) // all except Explorer
		yscroll=window.pageYOffset;
	else if(document.documentElement && document.documentElement.scrollTop)
		yscroll=document.documentElement.scrollTop;
	else if(document.body)
		yscroll=document.body.scrollTop;
	
	return yscroll;
}

// get the position of the current document window
//
function get_window_x_pos()
{
	var winx;

	if(window.screenX)
		winx=window.screenX;
	else if(window.screenLeft)
		winx=window.screenLeft;

	return winx;
}

function get_window_y_pos()
{
	var winy;

	if(window.screenY)
		winy=window.screenY;
	else if(window.screenTop)
		winy=window.screenTop;

	return winy;
}

// factory function to create an empty http request object
//
function create_http_req_object()
{
	var req=new Object();

	if(window.XMLHttpRequest) 
		{
		try
			{
			req.xmlobj=new XMLHttpRequest();
			return req;
			}
		catch(exc)
			{
			for(var i in exc) 
				alert(i+' = '+exc[i]);
			}
		} 
	else if(window.ActiveXObject)
		{
		try
			{
			req.xmlobj=new ActiveXObject("Msxml2.XMLHTTP");
			return req;
			}
		catch(exc)
			{
			try
				{
				req.xmlobj=new ActiveXObject("Microsoft.XMLHTTP");
				return req;
				}
			catch(exc)
				{
				for(var i in exc) 
					alert(i+' = '+exc[i]);
				}
			}
		}
	
	return null;
}

// make an asynchronous HTTP GET request to a web server
//
// url: the HTTP URL of the handler script on the server to call
// vars: the request variables to be passed to the handler script
// handler_func: the JavaScript function to handle the script response
//
function make_http_request(url,vars,handler_func)
{
	if(window.XMLHttpRequest) 
		{
		req=new XMLHttpRequest();
		} 
	else if(window.ActiveXObject)
		{
		try
			{
			req=new ActiveXObject("Msxml2.XMLHTTP");
			}
		catch(exc)
			{
			try
				{
				req=new ActiveXObject("Microsoft.XMLHTTP");
				}
			catch(exc) {}
			}
		}
	 
	if(req)
		{
		req.onreadystatechange=handler_func;
		if(vars.charAt(0)!='?')
			vars='?'+vars;
		req.open("GET",url+vars,true);
		req.send(null);
		}
	else
		alert('BAD XMLHTTP');
}

/**************************************************************************/
/**************************************************************************/
/**************************************************************************/
/**************************************************************************/
/**************************************************************************/

// make an asynchronous HTTP POST request to a web server
//
// url: the HTTP URL of the handler script on the server to call
// vars: the request variables to be passed to the handler script
// handler_func: the JavaScript function to handle the script response
//
function make_http_post_request(url,vars,handler_func)
{
	if(window.XMLHttpRequest) 
		{
		req=new XMLHttpRequest();
		} 
	else if(window.ActiveXObject)
		{
		try
			{
			req=new ActiveXObject("Msxml2.XMLHTTP");
			}
		catch(exc)
			{
			try
				{
				req=new ActiveXObject("Microsoft.XMLHTTP");
				}
			catch(exc) {}
			}
		}
	 
	if(req)
		{
		req.onreadystatechange=handler_func;
		req.open("POST",url,true);
		req.setRequestHeader("Content-Type","application/x-www-form-urlencoded");
      req.setRequestHeader("Content-Length",vars.length);
      req.setRequestHeader("Connection","close");
      req.send(vars);
		}
	else
		alert('BAD XMLHTTP');
}

// retrieve a JSON message encapsulated with json_message()

function get_json_message(str)
{
	var spos=str.indexOf('<json>');
	var epos=str.indexOf('</json>');
	return unescape(str.substring(spos+6,epos));
}

/**************************************************************************/
/**************************************************************************/
/**************************************************************************/
/**************************************************************************/
/**************************************************************************/

// do a binary search on an array of menu options and return the index
// of the element that matches
//
// opts: an array of menu options
// which: the value of the item that is to be searched
//
// returns: the index of the required item or -1 if not found
//
function binsearch(opts,which)
{
	// binary search
	
	var l=0,u=opts.length-1,m,p=-1;
	while(l<=u)
		{
		m=Math.floor((l+u)/2);
		if(opts[m].value<which)
			l=m+1;
		else if(opts[m].value==which)
			{
			p=m;
			break;
			}
		else if(opts[m].value>which)
			u=m-1;
		}

	return p;
}

// remove white space from the beginning and end of a string
//
// str: the string to be stripped
//
// returns: the string with white space removed
//
function trim(str)
{
	while(isspace(str.charCodeAt(0)))
		str=str.substring(1);
	while(isspace(str.charCodeAt(str.length-1)))
		str=str.substring(0,str.length-1);
	return str;
}

// determine whether a character code is white space
//
// ccode: the character to be tested
//
// returns: boolean indicating whether ccode is white space
//
function isspace(ccode)
{
	var spc=false;
	
	switch(ccode)
		{
		case 9:
		case 10:
		case 11:
		case 12:
		case 13:
		case 32:
		  {
		  spc=true;
		  break;
		  }
		}
	
	return spc;
}

// convert a denary number between 0 and 255 into hex
//
// x: the number to be converted
//
// returns: 2 digit hex equivalent of x
//
function dechex(x)
{
	hexdigits="0123456789ABCDEF";
	return hexdigits.charAt(Math.floor(x/16))+hexdigits.charAt(x%16);
}

// get a hex colour string based on RGB components
//
// r: the red component 0 - 255
// g: the green component 0 - 255
// b: the blue component 0 - 255
//
// returns: six hex digit colour string with hash prepended
//
function rgb(r,g,b)
{
	return '#'+dechex(r)+dechex(g)+dechex(b);
}
