
//---------------------------------------------------------------------------
//  Common Javascript extensions that are standard on many browsers but
//  that also are not available on many others.

function isFunction(a) {
	return typeof a == 'function';
}

// Push an element onto the end of an array, like a stack.
if (!isFunction(Array.prototype.push)) {
	Array.prototype.push = 
		function arrayPush(x) {
			this[this.length] = x;
		}
}

// Pop an element from the end of an array, like a stack.
if (!isFunction(Array.prototype.pop)) {
	Array.prototype.pop = 
		function arrayPop() {
			if (this.length <= 0) return null;
			var x = this[this.length - 1]; 
			this.length--;
			return x;	
		}
}

// This function returns random numbers on [a, b].
function Rand(a, b) {
	return Math.floor(Math.random() * (b - a + 1)) + a;
}

//---------------------------------------------------------------------------
//  Common shared Javascript.

// Return a reference to a named ID.
function GetByName(name) {
	if (document.layers) // Netscape layers
		return document.layers[name];
	else if (document.getElementById) // DOM; IE5, NS6, Mozilla, Opera
		return document.getElementById(name);
	else if (document.all) // Proprietary DOM; IE4
		return document.all[name];
	else if (document[name]) // Netscape alternative
		return document[name];
	else return false;
}

// Show or hide a block element by ID.
function ShowElement(ref, show) {
	if (OldBrowser || !ref) return;
	if (ref.style) // DOM & proprietary DOM
		ref.style.visibility = show ? 'visible' : 'hidden';
	else if (ref.visibility) // Netscape
		ref.visibility = show ? 'show' : 'hide';
}

// Query a block element's current location.
function GetElementPos(ref) {
	if (OldBrowser) return [0,0];

	if (ref.offsetParent) {
		for (var posX = 0, posY = 0; ref.offsetParent; ref = ref.offsetParent)
			posX += ref.offsetLeft, posY += ref.offsetTop;
		return [posX, posY];
	}
	else if (!ref.x && !ref.y) return [0,0];
	else return [ref.x, ref.y];
}

// Set a block element's current location, from the top-left corner.
function SetElementPos(ref, x, y) {
	if (OldBrowser || !ref) return;
	if (ref.style) ref = ref.style;
	var need_px = document.childNodes ? 'px' : 0;
	ref.left = x + need_px;
	ref.top = y + need_px;
}

// Query a block element's current size.  The clientWidth and
// clientHeight members are not official or standard, but they
// work in every tested browser (Firefox, IE, old Netscape, Opera,
// Safari, you name it).
function GetElementSize(ref) {
	if (OldBrowser || !ref) return [0,0];
	return [ref.clientWidth, ref.clientHeight];
}

// Write out a scrambled mail address.
function UnscrambleLink(n) {
	var v, c, ch;
	while (n.length && (v = n.shift()))
		for (c = 0; c < 5; c++, v >>= 6)
			if ((ch = ((v & 0x3F) ^ 0x1A) + 0x20) != 0x2A)
				document.write(String.fromCharCode(ch).toLowerCase());
}

// Change one image into another.  Useful for rollover links.
function ChangeImage(name, new_img_href) {
	if (OldBrowser) return;
	ref = GetByName(name);
	if (!ref) return;
	ref.src = new_img_href;
}

// Alter the display style of a div.
function SetDisplayStyle(ref, kind)
{
	if (!ref) return;
	if (ref.style) ref = ref.style;
	ref.display = kind;
}

// Query the display style of a div.
function GetDisplayStyle(ref)
{
	if (!ref) return;
	if (ref.style) ref = ref.style;
	return ref.display;
}

// Helper functions for showing/hiding divs.
function HideDiv(name)
	{ SetDisplayStyle(GetByName(name), 'none'); }
function ShowDiv(name)
	{ SetDisplayStyle(GetByName(name), 'block'); }
function ToggleDiv(name)
	{ var ref = GetByName(name);
		SetDisplayStyle(ref,
			GetDisplayStyle(ref) == 'block' ? 'none' : 'block'); }

// Change the contents of a <div> to the given text.
function ChangeDivContents(name, text)
{
	var ref = GetByName(name);
	if (ref) ref.innerHTML = text;
}

// Set the value of an <input> element.
function SetValue(name, value) {
    var ref = GetByName(name);
    if (ref) ref.value = value;
}

// Get the value of an <input> element.
function GetValue(name) {
	var ref = GetByName(name);
	if (ref) return ref.value;
	else return '';
}

// Limit input to numbers only (nothing else).
function LimitToNumbers(event) {
	var key = window.event ? event.keyCode : event.which;
	if (event && (event.ctrlKey || event.altKey)) return true;
	var keychar = String.fromCharCode(key);
	reg = /[0-9]/;
	return reg.test(keychar) || (key < 0x20) || (key > 0x7E);
}

// Limit input to numbers, space, and hyphen.
function WeakLimitToNumbers(event) {
	var key = window.event ? event.keyCode : event.which;
	if (event && (event.ctrlKey || event.altKey)) return true;
	var keychar = String.fromCharCode(key);
	reg = /[0-9\ -]/;
	return reg.test(keychar) || (key < 0x20) || (key > 0x7E);
}

//---------------------------------------------------------------------------
//  Image preloading/postloading support.

PreloadImageList = new Array();
PostloadImageList = new Array();
PreloadImageCache = new Array();
PostloadImageCache = new Array();

// Add one or more images to the pre-page-load list.
function PreloadImage() {
	var i;
	for (i = 0; i < arguments.length; i++)
		PreloadImageList.push(arguments[i]);
}

// Add an image to the post-page-load list.
function PostloadImage() {
	var i;
	for (i = 0; i < arguments.length; i++)
		PostloadImageList.push(arguments[i]);
}

// Preload all images in the preload list.  They will not
// be freed until the PreloadImageCache is destroyed.
function PreloadAllImages() {
	var i;
	for (i = 0; i < PreloadImageList.length; i++) {
		var name = PreloadImageList[i];
		var img = new Image();
		img.src = name;
		PreloadImageCache.push(img);
	}
	PreloadImageList.length = 0;
}

// Postload all images in the postload list.  They will not
// be freed until the PostloadImageCache is destroyed.
function PostloadAllImages() {
	var i;
	for (i = 0; i < PostloadImageList.length; i++) {
		var name = PostloadImageList[i];
		var img = new Image();
		img.src = name;
		PostloadImageCache.push(img);
	}
	PostloadImageList.length = 0;
}


//---------------------------------------------------------------------------
//  AJAX support code.

AjaxArray = new Array();

// Create an XMLHttpRequest object, portably.
function AjaxCreate() {
	var request = null;
	/*@cc_on @*/
	/*@if (@_jscript_version >= 5)
		try {
			request = new ActiveXObject("Msxml2.XMLHTTP");
		}
		catch (e) {
			try {
				request = new ActiveXObject("Microsoft.XMLHTTP");
			}
			catch (e2) {
				request = null;
			}
		}
	@end @*/
	if (!request && typeof(XMLHttpRequest) != 'undefined')
		request = new XMLHttpRequest();
	request.lastReadyState = -1;
	return request;
}

// Send an HTTP GET request to the remote server.  On the
// server's response, the given callback function will be
// called.  This returns true if it could initiate the request,
// and false if it could not.  These are the callback functions:
//
//   function onready(param) { }
//   function onfailed(httpstatus, param) { }
//   function onprocessing(statenumber, param) { }
//
// The onprocessing function, if provided, is called whenever
// the connection's state changes, but NOT when the communication
// is complete (onready or onfailed are called then).  This can
// be used to provide feedback to the user about the status
// of the communication.  Note that the returned state numbers
// vary between browsers; some browsers will call this function
// four times (0, 1, 2, 3), and some browsers will only call this
// function twice (0, 3).
//
// The optional 'id' parameter lets you uniquely identify this
// request in case you want to do something with it before it has
// completed (such as call AjaxStop on it).  ID values should be
// strings, like 'getimages' or 'sendlogin', and need not necessarily
// be unique.
//
// All of the callback functions have the XMLHttpRequest as their
// 'this' parameter.
//
// This returns true if the request could be initiated, or false if
// it could not.

function AjaxGet(url, onready, onfailed, onprocessing, param, id) {
	var ajax = AjaxCreate();
	if (!ajax) return false;

	ajax.open("GET", url, true);
	ajax.onreadystatechange = AjaxReadyStateChange;
	ajax.readyfunc = onready;
	ajax.failedfunc = onfailed;
	ajax.processingfunc = onprocessing;
	ajax.funcparam = param;
	ajax.userid = id;

	// Add this to the pile of AJAX requests waiting to be processed.
	// We have to use a global array of these things because the
	// XMLHttpRequest's onreadystatechange method doesn't provide any
	// direct way to identify which XMLHttpRequest called it.
	for (i = 0; i < AjaxArray.length; i++)
		if (AjaxArray[i] == null)
			break;
	AjaxArray[i] = ajax;

	// Send off the zeroth onprocessing notification directly.
	// Technically, the state is already beyond this; but since the
	// browsers often don't send states 1 or 2 directly, we need
	// to send *something* meaningful initially ourselves.
	if (ajax.onprocessing)
		ajax.onprocessing(0, param);

	// Last, but not least, send the request out.  When the request
	// comes back, AjaxReadyStateChange will be called below.
	ajax.send();

	return true;
}

// Send an HTTP POST request to the remote server.  This function
// works mostly like the AjaxGet() function above, but with one
// new parameter:  The 'data' parameter.  This parameter contains
// the form data to send to the remote server, and may be any
// non-recursive Javascript object tree.  It is converted by the
// JSONToURL function (see documentation on that function for the
// exact data format produced).

function AjaxPost(url, data, onready, onfailed, onprocessing, param, id) {
	var ajax = AjaxCreate();
	if (!ajax) return false;

	ajax.setRequestHeader('Content-Type',
		'application/x-www-form-urlencoded');
	ajax.open("POST", url, true);
	ajax.onreadystatechange = AjaxReadyStateChange;
	ajax.readyfunc = onready;
	ajax.failedfunc = onfailed;
	ajax.processingfunc = onprocessing;
	ajax.funcparam = param;
	ajax.userid = id;

	// Add this to the pile of AJAX requests waiting to be processed.
	// We have to use a global array of these things because the
	// XMLHttpRequest's onreadystatechange method doesn't provide any
	// direct way to identify which XMLHttpRequest called it.
	for (i = 0; i < AjaxArray.length; i++)
		if (AjaxArray[i] == null)
			break;
	AjaxArray[i] = ajax;

	// Send off the zeroth onprocessing notification directly.
	// Technically, the state is already beyond this; but since the
	// browsers often don't send states 1 or 2 directly, we need
	// to send *something* meaningful initially ourselves.
	if (ajax.onprocessing)
		ajax.onprocessing(0, param);

	// Last, but not least, send the request out.  When the request
	// comes back, AjaxReadyStateChange will be called below.
	ajax.send(JsonDataToURL(data));

	return true;
}

// Stop (cancel, abort) an HTTP request that's currently in-process.
// Provide the identifier of the HTTP request to stop.
function AjaxStop(id) {
	var i;
	for (i = 0; i < AjaxArray.length; i++) {
		if (AjaxArray[i].userid == id) {
			AjaxArray[i].abort();
			AjaxArray[i] = null;
		}
	}
}

// Called when an HTTP request changes state.  This has to
// figure out which request did it, and then call the
// appropriate callback functions.
function AjaxReadyStateChange() {
	var i;
	for (i = 0; i < AjaxArray.length; i++) {
		if (AjaxArray[i].readyState != AjaxArray[i].lastReadyState) {
			if (AjaxArray[i].readyState == 4) {
				if (AjaxArray[i].status == 200) {
					if (AjaxArray[i].readyfunc)
						AjaxArray[i].readyfunc(AjaxArray[i].funcparam);
				}
				else {
					if (AjaxArray[i].failedfunc)
						AjaxArray[i].failedfunc(AjaxArray[i].status, AjaxArray[i].funcparam);
				}
				AjaxArray[i] = null;
			}
			else {
				if (AjaxArray[i].processingfunc)
					AjaxArray[i].processingfunc(AjaxArray[i].readyState, AjaxArray[i].funcparam);
				AjaxArray[i].lastReadyState = AjaxArray[i].readyState;
			}
		}
	}
}

// This is an easy helper function that you can use to dynamically
// swap out the contents of a <div> tag.  It works somewhat like the
// AjaxGet function above (and uses AjaxGet), but on success, it will
// automatically fill the contents of the given <div> with the data
// returned from the server.  On failure, if 'failed_text' is provided,
// it will be called; or if not, the contents of the <div> tag will
// be set to the empty string.  During processing, the text given in
// 'processing_text' will be inserted into the <div> tag.  As with
// AjaxGet, the 'id' may be used to stop the request midway.  Note that
// if you *do* stop the request midway, you're reponsible for putting
// something sensible in the <div> tag yourself.
function AjaxReplaceContent(tagid, url, failed_text, processing_text, id) {
	return AjaxGet(url,
		function (param) { ChangeDivContents(tagid, this.responseText); },
		function (param) {
			if (failed_text) ChangeDivContents(tagid, failed_text);
			else ChangeDivContents(tagid, '');
		},
		function (param) {
			if (processing_text) ChangeDivContents(tagid, processing_text);
		},
		null, id);
}

// This function allows you to easily simulate asynchronous RPC.
// A typical call looks like this:
//
//  AjaxCall('http://someurl', 'myfunc', ['abc', 123],
//    function (result, param) {
//      // Do something with the return value from calling
//      // myfunc('abc', 123).  The return value will be Javascript
//      // data, just like the calling parameters were.
//    },
//    function (param) {
//      // Only gets here if the call failed.
//    }
//  );
//
// AjaxCall uses the POST method to ensure that the call is not cached.
// The remote server will receive the function name as an argument
// named 'function' (i.e., "function=myfunc" ) in the URI itself.
function AjaxCall(url, remote_funcname, remote_args,
	onreturn, onfailed, param, id) {
	AjaxPost(url + "?function=" + remote_funcname, remote_args,
		function (param) {
			if (onreturn)
				onreturn(JsonStringToData(this.responseText), param);
		},
		null, function (param) {
			if (onfailed) onfailed(param);
		},
		param, id);
}

//---------------------------------------------------------------------------
//  JSON support code.

// Call this to turn a string of text in JSON format into a
// usable collection of Javascript-friendly data.  Returns
// the newly-created Javascript object.  If the data was
// garbage or not parse-able, this returns null.
function JsonStringToData(str) {
	if (/[^,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]/.test(
		str.replace(/"(\\.|[^"\\])*"/g, '')))
		return null;
	try {
		return eval('(' + str + ')');
	}
	catch (e) {
		return null;
	}
}

// Call this to turn a collection of data into a string in
// JSON format.  The data must not contain any cyclic
// references, or this will loop forever.  Returns a
// string describing the original data; or if there was
// something wrong with the original data such that it could
// not be converted, this returns false.  You can optionally
// override the automatic type detection here by specifying
// a specific type conversion to use, such as 'string' or
// 'array'.
function JsonDataToString(x) {
	switch (typeof(x)) {
	default:
		return typeof(x);
	case 'boolean':
		return String(x);
	case 'number':
		return isFinite(x) ? String(x) : 'null';
	case 'string':
		if (/["\\\x00-\x1f]/.test(x)) {
			var m = {
				'\b': '\\b', '\t': '\\t', '\n': '\\n',
				'\f': '\\f', '\r': '\\r', '"' : '\\"',
				'\\': '\\\\'
			};
			x = x.replace(/([\x00-\x1f\\"])/g, function(a, b) {
				var c = m[b];
				if (c) return c;
				c = b.charCodeAt();
				return '\\u00' + Math.floor(c / 16).toString(16)
					+ (c % 16).toString(16);
			});
		}
		return '"' + x + '"';
	case 'object':
		if (!x) return 'null';
		if (!(x instanceof Array)) {
			var result = '{', i;
			for (i in x) {
				if (i != 0) result += ',';
				result += JsonDataToString(i);
				result += ':';
				result += JsonDataToString(x[i]);
			}
			result += '}';
			return result;
		}
	case 'array':
		{
			var result = '[', i;
			for (i = 0; i < x.length; i++) {
				if (i != 0) result += ',';
				result += JsonDataToString(x[i]);
			}
			result += ']';
			return result;
		}
	}
}

// JsonDataToURL converts Javascript data into data that can safely be
// attached to the end of a URL.
//
// If the root object of 'vars' is a Javascript object, the data sent
// to the server will be in JSON notation, where each parameter
// represents one member of the object.  In other words:
//
//    { a: 'foo', b: 'bar' }  becomes  "a=%27foo%27&b=%27bar%27"
//
// If the root object is an array, the data sent to the server will
// be in JSON notation, with the parameter names assigned from the
// array indices:
//
//    [ 'foo', 'bar' ]  becomes  "0=%27foo%27&1=%27bar%27"
//
// For all other types, the data sent to the server will be a single
// parameter:
//
//    1      becomes  "x=1"
//    'foo'  becomes  "x=%27foo%27"
//    false  becomes  "x=false"
function JsonDataToURL(x) {
	var result, i, first = true;
	if (typeof(x) == 'object' || typeof(x) == 'array') {
		if (!x)
			result = 'x=null';
		else {
			for (i in x) {
				if (!first) result += '&';
				first = false;
				result += encodeURIComponent(JsonDataToString(i));
				result += '=';
				result += encodeURIComponent(JsonDataToString(x[i]));
			}
		}
	}
	else {
		result = 'x=';
		result += encodeURIComponent(JsonDataToString(x));
	}
	return result;
}

//------
// EOF
//------

