/*~********************************************************************

File:	General.js, v2.2

Useful general JS functionality for the site www.macfh.co.uk

Charles Macfarlane Harrison, jj@javajive.macfh.co.uk, May 2010

***********************************************************************

Charles Harrison's Work is licensed under a Creative Commons Licence -
Attribution-Non-Commercial-No Derivative Works 2.0 UK: England & Wales
http://creativecommons.org/licenses/by-nc-nd/2.0/uk/

For further details, please see:
http://www.macfh.co.uk/JavaJive/JavaJive.html

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

//	Note: for( var x in y ) on Array bugs in some browsers, especially if Array.prototype has been altered

//	A document branch or block that can be opened/closed or shown/hidden with a mouse click
//	Associated style class names
ToggleBlock.prototype.cName	= "toggle";
ToggleBlock.prototype.cOpen	= "opened";
ToggleBlock.prototype.cClose = "closed";
ToggleBlock.prototype.cHide	= "invis";
function ToggleBlock( anElement, aCompArr, init, aToggleFunc1, aToggleFunc2 )
	{
	//	The toggle element for the toggleBlock
	this.elem	= typeof anElement == "string" ? document.getElementById( anElement ) : anElement;
	//	The array of component elements
	this.comps	= null;
	//	A method to set the state of the toggleBlock
	this.set	= set;
	//	A method to change the state of the toggleBlock
	this.toggle = toggle;
	//	A method to change the state of the toggle element
	this.toggleElement = aToggleFunc1 ? aToggleFunc1 : toggleLi;
	//	A method to change the state of the components
	this.toggleComponents = aToggleFunc2 ? aToggleFunc2 : toggleComponents;

	//	If object instance rather than prototype
	if( this.elem )
		{
		this.elem.tabIndex = 0;
		this.elem.onmousedown = function( event ){ evPrevDeft(event); };
		}
	if( aCompArr )
		{
		this.comps = [];
		if( !(aCompArr instanceof Array) )
			aCompArr = [aCompArr];
		for( var i = 0, I = aCompArr.length; i < I; i++ )
			{
			var el = typeof aCompArr[i] == "string" ? document.getElementById( aCompArr[i] ) : aCompArr[i];
			if( el )
				this.comps.push( el );
			}
		}
	if( init != undefined )
		this.set( init );
	}

//	Set the state of a toggleBlock
function set( state )
	{
	return this.toggle( null, state );
	}

//	Given a click on a toggleBlock, change the state of it
function toggle( anEvent, state )
	{
	var result = false;
	var re = new RegExp("\\b" + this.cName + "\\b","g");
	var target = anEvent ? ( anEvent.target ? anEvent.target : (anEvent.srcElement ? anEvent.srcElement : null) ) : null;
//	console.log( "target:\t" + (target ? target.id + ", " + target.nodeName : "null") + "\nthis.elem:\t" + (this.elem ? this.elem.id : null) );
	switch( target )
		{
		default:		while( (target != this.elem) && !result )
							{
							result = (target.className.search(re) != -1);
							target = target.parentNode;
							}
						if( !result )
							break;
						//	Deliberate fall through

		case this.elem:	result = selectEnter( anEvent );
						if( !result )
							break;
						//	Deliberate fall through

		case null:		result = (this.toggleElement ? this.toggleElement(state) : true)
									& (this.toggleComponents ? this.toggleComponents(state) : true);
						//	Stop event being handled by parents
						result = evStop( anEvent );
						break;
		}
	return result;
	}

//	Given a click on a toggleBlock <li>, change the state of it
function toggleLi(state)
	{
	var result = false;
	if( this.elem && (this.elem.nodeName == "LI") )
		{
		if( state != undefined )
			{
			setClass( this.elem, this.cClose, !state );
			setClass( this.elem, this.cOpen, state );
			}
		else
			{
			toggleClass( this.elem, this.cClose );
			toggleClass( this.elem, this.cOpen );
			}
		result = true;
		}
	return result;
	}

//	Given a click on a toggleBlock, change the state of the components
function toggleComponents(state)
	{
	var result = true;
	if( this.comps )
		for( var i = 0, I = this.comps.length; i < I; i++ )
			result &= ( ( state != undefined ? setClass(this.comps[i], this.cHide, !state) : toggleClass(this.comps[i], this.cHide) ) != null );
	return result;
	}


//	A document branch or block that can be opened/closed or shown/hidden by a button
ButtonBlock.prototype = new ToggleBlock;
ButtonBlock.prototype.showTxt = "Show";
ButtonBlock.prototype.hideTxt = "Hide";
function ButtonBlock( aCaption, aButton, aCompArr, init, aToggleFunc1, aToggleFunc2 )
	{
	//	The button text to be combine with 'Show'/'Hide'
	this.caption = aCaption;

	//	Object inheritance from toggleBlock
	this.base = ToggleBlock;
	this.base( aButton, aCompArr, init, aToggleFunc1 ? aToggleFunc1 : toggleButton, aToggleFunc2 );
	}

//	Given a click on a ButtonBlock <button>, change the caption
function toggleButton(state)
	{
//	console.log( this.elem.nodeName + "\n" + this.caption );
	var result = false;
	switch( this.elem.nodeName )
		{
		case "BUTTON":	if( state != undefined ? state : (this.elem.innerHTML == this.showTxt + " " + this.caption) )
							this.elem.innerHTML = this.hideTxt + " " + this.caption;
						else
							this.elem.innerHTML = this.showTxt + " " + this.caption;
						result = true;
						break;
		case "INPUT":	if( state != undefined ? state : (this.elem.value == this.showTxt + " " + this.caption) )
							this.elem.value = this.hideTxt + " " + this.caption;
						else
							this.elem.value = this.showTxt + " " + this.caption;
						result = true;
						break;
		}
	return result;
	}


//	A slideshow associated with an img tag
SlideShow.prototype.pauseTxt	= "Paused";
SlideShow.prototype.runTxt		= "Running";
SlideShow.prototype.intervals	= [];
SlideShow.prototype.loads		= [];
function SlideShow( anImg, aPicArr, aURL, interval, isRandom, repeatBlock )
	{
	//	Base URL for the pictures
	var sURL	= URLClean( aURL ? aURL : document.URL );
	this.srcURL	= sURL.indexOf("/") != -1 ? sURL.substring( 0, sURL.lastIndexOf("/") + 1 ) : sURL;
	//	Img tag
	this.img	= typeof anImg == "string" ? document.getElementById( anImg ) : anImg;
	this.img.tabIndex = 0;
	//	Preview object to get original size of image
	this.peek;
	//	The array of picture files
	this.pics	= aPicArr;
	//	Method to choose and set the next image in a slideshow
	this.next	= sNext;
	//	Method to handle a click or a keypress on a slideshow
	this.click	= sClick;
	//	Method to centre the current slideshow image within its parent container
	this.show	= sShow;

	//	If appropriate, init random mode
	if( isRandom )
		{
		//	In random mode, holds random index order to display the pics
		this.order = [];
		for( var i = 0, I = this.pics.length; i < I; i++ )
			this.order.splice( Math.randN(this.order.length), 0, i );
		this.repBlock = repeatBlock ? repeatBlock : this.pics.length < 3 ? this.pics.length - 1 : Math.round( this.pics.length/3 );
		}

	//	If no initial pic specified in HTML, show one
	if( !this.img.src || (this.img.src == this.srcURL) || (this.img.src == URLClean(document.URL)) )
		this.next();

	//	If appropriate, init autochange by timer interval
	if( interval )
		{
		//	Slideshow autochange interval
		this.intrvl	= interval;
		//	Method to start or resume a slideshow
		this.start	= slideStart;
		//	Method to stop or pause a slideshow
		this.stop	= slideStop;
		this.start();
		}
	}

//	Handle a click or a keypress on a slideshow
function sClick( anEvent )
	{
	var result = null;
	var target = anEvent ? ( anEvent.target ? anEvent.target : (anEvent.srcElement ? anEvent.srcElement : null) ) : this.img;

//	var msg = "anEvent:\n\t" + (anEvent ? anEvent.type : "null") + "\n"
//				+ "target:\n\t" + target.id + "\n"
//				+ "Base:\n\t" + this.srcURL + "\n"
//				+ "Img:\n\t" + this.img + "\n"
//				+ "Pics:\n\t{\n";
//	for( var i = 0, I = this.pics.length; i < I; i++ )
//		msg += "\t" + this.pics[i] + "\n";
//	msg +="\t}";
//	console.log( msg );

	if( (target == this.img) && selectEnter(anEvent) )
		{
		if( this.intrvl )
			{
			if( this.intIndex == undefined )
				this.start();
			else
				this.stop();
			}
		else
			this.next();
		result = evStop( anEvent );
		}
	else
		result = true;
	}

//	Start or resume a slideshow
function slideStart()
	{
	//	Give this instance a global handle
	this.intIndex	= SlideShow.prototype.intervals.length;
	SlideShow.prototype.intervals.push( this );
	this.intTimer	= setInterval( "SlideShow.prototype.intervals[" + this.intIndex + "].next()", this.intrvl );
	this.img.title	= this.runTxt + ": " + this.img.src;
	}

//	Stop or pause a slideshow
function slideStop()
	{
	//	Give this instance a global handle
	this.img.title	= this.pauseTxt + ": " + this.img.src;
	clearInterval( this.intTimer );
	SlideShow.prototype.intervals[this.intIndex] = undefined;
	this.intIndex = undefined;
	while( SlideShow.prototype.intervals.length && !SlideShow.prototype.intervals[SlideShow.prototype.intervals.length-1] )
		SlideShow.prototype.intervals.pop();
	}

//	Choose and set the next image in a slideshow
function sNext( anEvent )
	{
	var result = null;
	if( this.order )
		{
		result = this.order.shift();
		var nextTime = this.repBlock + Math.randN(this.order.length - this.repBlock);
		this.order.splice( nextTime, 0, result );
		}
	else
		{
		result = 0;
		if( this.img.src && (this.img.src != this.srcURL) && (this.img.src != URLClean(document.URL)) )
			{
			var src = URLRel( URLClean(this.img.src), this.srcURL );
			for( var i = 0, I = this.pics.length; (i < I) && !result; i++ )
				if( src == URLRel( URLClean(this.pics[i]), this.srcURL ) )
					result = (i + 1) % this.pics.length;
			}
		}
	this.peek = new Image();
	ldsIndex = SlideShow.prototype.loads.length;
	SlideShow.prototype.loads.push( this );
	this.peek.onload = new Function( "anEvent", "sShow.apply( SlideShow.prototype.loads[" + ldsIndex + "], [" + ldsIndex + "] );" );
	this.peek.src	= this.pics[ result ];
	return result;
	}

//	Show and centre the current image within the parent container
function sShow(index)
	{
	this.img.src = this.peek.src;
	if( this.intrvl )
		this.img.title	= (this.intIndex != undefined ? this.runTxt : this.pauseTxt) + ": " + this.img.src;
	try
		{
		var	iw, ih, ia, pw, ph, pa, iwn, ihn, lm, tm;
		iw	= this.peek.naturalWidth ? this.peek.naturalWidth : this.peek.width ? this.peek.width : this.img.width;
		ih	= this.peek.naturalHeight ? this.peek.naturalHeight : this.peek.height ? this.peek.height : this.img.height;
		ia	= iw/ih;
		//	Use currentStyle rather than client* because clientHeight is 3px too large in IE6!
		try
			{
			pw = parseInt( this.img.parentNode.currentStyle.width, 10 );
			if( isNaN(pw) )
				throw( "NaN" );
			}
		catch(e)
			{
			pw	= this.img.parentNode.clientWidth ? this.img.parentNode.clientWidth : this.img.parentNode.width;
			}
		try
			{
			ph = parseInt( this.img.parentNode.currentStyle.height, 10 );
			if( isNaN(ph) )
				throw( "NaN" );
			}
		catch(e)
			{
			ph	= this.img.parentNode.clientHeight ? this.img.parentNode.clientHeight : this.img.parentNode.height;
			}
		pa	= pw/ph;
		if( pa > ia )
			{
			ihn	= ph;
			iwn	= Math.round( ihn*ia );
			}
		else
			{
			iwn	= pw;
			ihn	= Math.round( iwn/ia );
			}
		lm = Math.max( Math.round((pw-iwn)/2), 0 );
		tm = Math.max( Math.round((ph-ihn)/2), 0 );
		this.img.style.width		= iwn + "px";
		this.img.style.height		= ihn + "px";
		this.img.style.marginLeft	= lm + "px";
		this.img.style.marginTop	= tm + "px";
		}
	catch(e)
		{
//		alert(
//				e + "\n\n"
//				+ "Image WH:\t" + iw + "," + ih + "," + (ia ? ia.toFixed(2) : "") + "\n"
//				+ "Parent WH:\t" + pw + "," + ph + "," + (pa ? pa.toFixed(2) : "") + "\n"
//				+ "New Img WH:\t" + iwn + "," + ihn + "\n"
//				+ "Margins LT:\t" + lm + "," + tm
//			);
		}
	SlideShow.prototype.loads[index] = undefined;
	while( SlideShow.prototype.loads.length && !SlideShow.prototype.loads[SlideShow.prototype.loads.length-1] )
		SlideShow.prototype.loads.pop();
	}


//	Cross-Browser & Utility Section

//~	Browser sniffer - WARNING: Not for critical use! Use object/function detection wherever possible!
var __br = (function()
	{
	var	browsers =
		[
			["Chrome(?:\\s|/)*([0-9]+)","Ch"],
			["Version(?:\\s|/)*([0-9]+)(?:[0-9./()]|\\s*)+Mobile(?:[0-9A-Z./)]|\\s*)+Safari","Sm"],
			["Version(?:\\s|/)*([0-9]+)(?:[0-9./()]|\\s*)+Safari","Sa"],
			["Safari(?:\\s|/)*([0-9]+)","Sa"],
			["KDE(?:\\s|/)*([0-9]+)","Ko"],
			["Konqueror(?:\\s|/)*([0-9]+)","Ko"],
			["KHTML(?:\\s|/)*([0-9]+)","KH"],
			["AppleWebKit(?:\\s|/)*([0-9]+)","WK"],
			["Firefox(?:\\s|/)*([0-9]+)", "FF"],
			["rv:([0-9]+)(?:[0-9./)]|\\s*)+Gecko", "Ge"],
			["Opera.*\\s+Version(?:\\s|/)*([0-9]+)","Op"],
			["Opera(?:\\s|/)*([0-9]+)","Op"],
			["MSIE(?:\\s|/)*([0-9]+)", "IE"],
			["iCab(?:\\s|/)*([0-9]+)","iC"],
			["Camino(?:\\s|/)*([0-9]+)","Ca"],
			["OmniWeb(?:\\s|/)*([0-9]+)","OW"],
			["Netscape(?:\\s|/)*([0-4]+)","NS"],
			["Mozilla(?:\\s|/)*([0-4]+)","Mo"]
		];
	var	result = false;
	for( var i = 0, I = browsers.length; (i < I) && !result; i++ )
		{
		result = navigator.userAgent.match( new RegExp(browsers[i][0],"i") );
		if( result )
			{
			try			{ result = { n:browsers[i][1], v:parseInt(result[1],10) }; }
			catch(e)	{ result = { n:browsers[i][1], v:result[1] }; }
			}
		}
	return result ? result : {n:"XX", v:0 };
	})();

//~	Doctype sniffer - sets an object __dt with d = doctype declaration text and x true if XHTML, otherwise false
var __dt = ( function( dt )
		{
		var pub, sys, isXHTML;
		if( dt )
			{
			pub	= dt.publicId;
			sys	= dt.systemId;
			isXHTML	= /XHTML/.test( pub );
			}
		else
			{
			pub	= "-//W3C//DTD HTML 4.01 Transitional//EN";
			sys	= "http://www.w3.org/TR/html4/loose.dtd";
			isXHTML	= false;
			}
		return {d:"<!DOCTYPE html PUBLIC \"" + pub + "\"" + (sys ? " \"" + sys + "\"" : "") + ">",x:isXHTML};
		})(document.doctype);


//	CSS

//	Toggle a class being set on an element
function toggleClass( anElement, aClass )
	{
	var theElem = typeof anElement == "string" ? document.getElementById( anElement ) : anElement;
//	console.log( "theElem:\t" + theElem.id + "\naClass:\t" + aClass );
	var classStr = null;
	try
		{
		var regExp = new RegExp( "\\b" + aClass + "\\b", "g" );
		classStr = theElem.className;
		classStr = classStr.search(regExp) == -1 ?
						classStr + " " + aClass :
						classStr.replace(regExp,"").replace(/\s+/g, " ").trim();
		theElem.className = classStr;
		}
	catch(e){}
//	console.log( "classStr:\t>>>" + classStr + "<<<");
	return classStr;
	}

//	Add or remove a class to or from an element
function setClass( anElement, aClass, set )
	{
	var theElem = typeof anElement == "string" ? document.getElementById( anElement ) : anElement;
	var classStr = null;
	try
		{
//	console.log( "theElem:\t" + theElem.id + "\naClass:\t" + aClass + "\nset:\t" + set );
		var regExp = new RegExp( "\\b" + aClass + "\\b", "g" );
		classStr = theElem.className.replace(regExp,"").replace(/\s+/g, " ").trim();
		if( set )
			classStr += " " + aClass;
		theElem.className = classStr;
		}
	catch(e){}
//	console.log( "classStr:\t>>>" + classStr + "<<<");
	return classStr;
	}

//	Show or hide an element
function setShowHide( anElement, show )
	{
	return setClass( anElement, "invis", !show );
	}

//~	Style rule finder - WARNING: May corrupt CSS in IE6 without giving any indication of error!
function findStyles( selectors, styleSheets )
	{
	function _fs( targets, sheet )
		{
		var	results	= [];
		try
			{
			var rules = sheet.cssRules ? sheet.cssRules : sheet.rules ? sheet.rules : [];
			for( var r = 0, R = rules.length; r < R; r++ )
				{
				if( rules[r].selectorText )
					{
					for( var t = 0, T = targets.length; t < T; t++ )
						if( rules[r].selectorText.match(typeof targets[t] == "string" ? new RegExp(targets[t]) : targets[t]) )
							results.push( rules[r] );
					}
				else
					results = results.concat( _fs(targets, rules[r]) );
				}
			}
		catch(e){};
		return results;
		}
	var	targets	= selectors ? (selectors instanceof Array ? selectors : [selectors]) : [];
	var	sheets	= styleSheets ? (styleSheets instanceof Array ? styleSheets : [styleSheets]) : document.styleSheets ? document.styleSheets : [];
	var	results	= [];
	if( targets.length && sheets.length )
		for( var s = 0, S = sheets.length; s < S; s++ )
			results = results.concat( _fs(targets, sheets[s]) );
	return results;
	}


//	DOM

//	Walk a DOM subtree, looking for descendants of certain types
function TreeWalk( aNode, theTypes )
	{
	var node	= typeof aNode == "string" ? document.getElementById(aNode) : aNode;
	var types	= theTypes instanceof Array ? theTypes : [theTypes];
	var result	= [];
	var nodes;
	if( node )
		{
		if( node.tagName )
			for( var i = 0, I = types.length; i < I; i++ )
				if( node.tagName.match(typeof types[i] == "string" ? new RegExp(types[i],"i") : types[i]) )
					result.push( node );
		nodes = node.childNodes;
		for( i = 0, I = nodes.length; i < I; i++ )
			result = result.concat( TreeWalk(nodes[i], types) );
		}
	return result;
	}

//	Print a document node from a new window/tag
function printNode( oElement, aHeadings )
	{
	var	cT		= __dt.x ? " />" : ">";
	var	title	= aHeadings.slice(0,3).join(" - ");
	var	hdings	= "";
	var	styleSheet, node;
	for( var i = 0, I = Math.min(6, aHeadings.length); i < I; i++ )
		hdings += "<h" + (i + 1) + ">" + aHeadings[i] + "</h" + (i + 1) + ">";
	if( document.styleSheets && document.styleSheets.length )
		styleSheet = document.styleSheets[0].href;
	node = typeof oElement == "string" ? document.getElementById( oElement ) : oElement;

//	console.log( title + "\n" + hdings + "\n" + styleSheet + "\n" + node.innerHTML );

	var printNode = open();
	printNode.document.open();
	printNode.document.write(
								__dt.d
									+ "<html>"
										+ "<head>"
											+ "<title>" + title + "</title>"
											+ (styleSheet ? "<link rel=\"stylesheet\" type=\"text/css\" href=\"" + styleSheet + "\"" + cT : "")
										+ "</head>"
										+ "<body onload='print();close();'>"
											+ hdings
											+ node.innerHTML
										+ "</body>"
									+ "</html>"
							);
	printNode.document.close();
	}


//	Events

//	Select only <Return>, <Enter>, and if not IE <Space>, keystrokes
//	(IE has idiosyncracy of treating <Space> as <PageDown>)
function selectEnter( anEvent )
	{
	var result = true;
	if( anEvent && ( (anEvent.type == "keypress") || (anEvent.type == "keydown") || (anEvent.type == "keyup") ) )
		{
		switch( anEvent.which ? anEvent.which : anEvent.keyCode )
			{
			//	<Space>
			case 32:	result = ( __br.n != "IE" );	//	Don't mind browser sniffing for this
						break;

			//	<Enter>
			case 13:	result = true;
						break;

			//	Other
			default:	result = false;
						break;
			}
		}
	return result;
	}

//	Cross-browser preventDefault
function evPrevDeft( anEvent )
	{
	if( anEvent )
		{
		if( anEvent.preventDefault )
			anEvent.preventDefault();
		else if( anEvent.returnValue != undefined )
			anEvent.returnValue = false;
		}
	}

//	Cross-browser stopPropagation
function evStopProp( anEvent )
	{
	if( anEvent )
		{
		if( anEvent.stopPropagation )
			anEvent.stopPropagation();
		else if( anEvent.cancelBubble != undefined )
			anEvent.cancelBubble = true;
		}
	}

//	Cross-browser stop (both above)
function evStop( anEvent )
	{
	evPrevDeft( anEvent );
	evStopProp( anEvent );
	return false;
	}


//	URLs

//	Determine if a URL is local
function URLLocal( aURL, incLL, incLAN )
	{
	var locs	= [ "^file://", "^http://localhost[/:]", "^http://127\.[0-9]+\.[0-9]+\.[0-9]+[/:]", "^http://169\.254\.[0-9]+\.[0-9]+[/:]", "^http://192\.168\.[0-9]+\.[0-9]+[/:]", "^http://172\.([0-9]+)\.[0-9]+\.[0-9]+[/:]", "^http://0?10\.[0-9]+\.[0-9]+\.[0-9]+[/:]" ];
	var	tURL	= URLClean( aURL, false );
	var	result	= false;
	for( var i = 0, I = incLAN ? locs.length : incLL ? 4 : 3; (i < I) && !result; i++ )
		{
		result = ( tURL.match(new RegExp(locs[i],"i")) );
		if( i == 5 )
			{
			try{ result = parseInt(result[1], 10); if( (result < 16) || (result > 31) ) throw "False"; }
			catch(e){ result = null; }
			}
		}
	return result ? true : false;
	}

//	Get a URL into a standard form
function URLClean( aURL, www )
	{
	var result = aURL;

	//	Remove trailing ? and # parameters
	var re = new RegExp("[\\?#]","g");
	var index = result.replace("%20", " ").replace(re, "\x01").indexOf("\x01");
	result = index != -1 ? result.substring( 0, index ) : result;

	//	Replace \ with /
	re = new RegExp("\\\\","g");
	result = result.replace(re, "/");

	//	Clean up Windows drive letters in IE
	if( result.substr(0,9).search("file://[A-Za-z]:") == 0 )
		result = result.substr(0,7) + "/" + result.substr(7);
	else if( result.substr(0,2).search("[A-Za-z]:") == 0 )
		result = "file:///" + result;

	//	Insert www if parameter set
	if( www && (result.substr(0,7) == "http://") && (result.substr(0,11) != "http://www.") && !URLLocal(result) )
		result = result.substr(0,7) + "www." + result.substr(7);

//	console.log( "URLClean:\n" + result );

	return result;
	}

//	Calculate a URL relative to a base
function URLRel( aURL, aBase )
	{
	var result = URLClean( aURL );
	var base = URLClean( aBase );

	//	If not already relative ...
	if( result.substr(0, 2) != "./" )
		{
		//	Remove base from URL
		var index = result.indexOf(base);
		if( index >= 0 )
			result = "./" + result.substring( index + base.length );
		else
			{
			for( var i = base.indexOf("/"); (base.substr(0,i) == result.substr(0,i)) && (i < base.length); i = base.indexOf("/") )
				{
				base	= base.substr( i + 1 );
				result	= result.substr( i + 1 );
				}
			for( i = base.indexOf("/"); i >= 0; i = base.indexOf("/") )
				{
				base	= base.substr( i + 1 );
				result	= "../" + result;
				}
			result	= "./" + result;
			}
		}

//	console.log(
//					"Document's URL:\n" + document.URL + "\n"
//						+ "Document's base:\n" + base + "\n"
//						+ "URL:\n" + result
//				);

	return result;
	}


//	Extensions to standard objects

//	Compare two values which may or may not be defined
function compValues( one, two, fCompare )
	{
	var result;
	if( one != undefined )
		{
		if( two != undefined )
			{
			if( fCompare )
				result = fCompare( one, two );
			else
				result = one < two ? -1 : one > two ? 1 : 0;
			}
		else
			result = 1;
		}
	else
		{
		if( two != undefined )
			result = -1;
		else
			result = 0;
		}
	return result;
	}

//	Array function to push only a new value
function arrPushNew( thisArr, newValue, fCompare )
	{
	for( var i = 0, I = thisArr.length, found = false; (i < I) && !found; i++ )
		found = compValues(thisArr[i], newValue, fCompare) == 0;
	if( !found )
		thisArr.push( newValue );
	return thisArr;
	}

//	Array function to remove all copies of one value
function arrDelAll( thisArr, oldValue, fCompare )
	{
	for( var i = thisArr.length - 1; i >= 0; i-- )
		if( compValues(thisArr[i], oldValue, fCompare) == 0 )
			thisArr.splice( i, 1 );
	return thisArr;
	}

//	Array function to remove all duplicate copies
function arrDelDups( thisArr, fCompare )
	{
	for( var i = thisArr.length - 1; i >= 0; i-- )
		for( var j = i - 1; j >= 0; j-- )
			if( compValues(thisArr[i], thisArr[j], fCompare) == 0 )
				thisArr.splice( i, 1 );
	return thisArr;
	}
/*
//	Extend Array to give more useful string representation
Array.prototype.toString = function()
	{
	var result = "";
	for( var i = 0, I = this.length; i < I; i++ )
		result += i + ": " + this[i] + "\n";
	return "[\n" + result + "]\n";
	}
*/
//	Extend Math to give a random integer 0 - N
Math.randN = function( N )
	{
	return this.floor( (N+1)*this.random() );
	}

//	Extend String to have trim function
String.prototype.trim = function()
	{
    return this.replace(/(^\s+)|(\s+$)/g, "");
	}

