

function get_browser_name() {
    if (navigator.appName == "Microsoft Internet Explorer") {
        return "MSIE";
    } else if (navigator.userAgent.indexOf('Firefox') != -1) {
        return "Firefox";
    } else if (navigator.userAgent.indexOf('Safari') != -1) {
        return "Safari";
    } else if (navigator.userAgent.indexOf('Opera') != -1) {
        return "Opera";
    } else {
        return "other";
    }
}

function randomString() {
    var chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz";
    var string_length = 8;
    var randomstring = '';
    for (var i = 0; i < string_length; i++) {
        var rnum = Math.floor(Math.random() * chars.length);
        randomstring += chars.substring(rnum, rnum + 1);
    }
    return randomstring;
}

function clean_imageurl(instr) { //only works for png
    s = instr.substr(instr.indexOf("img"));
    return s.substr(0, s.indexOf(".png") + 4);
}

function loadimg(url) {
    if (arr_loadedimages[url] == undefined) {
        tempi = new Image();
        tempi.src = url;
        arr_loadedimages[url] = tempi;
    }
}

function get_first_numericchar_index(inStr) {
    validchars = "1234567890.-";
    for (i = 0; i < inStr.length; i++) {
        if (validchars.indexOf(inStr.charAt(i)) != -1) {
            return i;
        }
    }
    return false;
}
function get_first_numeric_string(inStr) {
    validchars = "1234567890.-";
    relevantString = inStr.substring(get_first_numericchar_index(inStr));
    if (relevantString == false) {
        return false;
    } else {
        for (i = 0; i < relevantString.length; i++) {
            if (validchars.indexOf(relevantString.charAt(i)) == -1) {
                return relevantString.substring(0, i);
            }
        }
    }
    return relevantString;
}

function booltostring(invar) {
    if (invar == true) {
        return "true";
    } else {
        return "false";
    }
}


function idof(element) {
    if (element.id.length == 0) {
        element.id = randomString();
    }
    return element.id;
}







//From http://www.robertnyman.com/2005/11/07/the-ultimate-getelementsbyclassname/
function getElementsByClassName(oElm, strTagName, oClassNames) {
    var arrElements = (strTagName == "*" && oElm.all) ? oElm.all : oElm.getElementsByTagName(strTagName);
    var arrReturnElements = new Array();
    var arrRegExpClassNames = new Array();
    if (typeof oClassNames == "object") {
        for (var i = 0; i < oClassNames.length; i++) {
            arrRegExpClassNames.push(new RegExp("(^|\\s)" + oClassNames[i].replace(/\-/g, "\\-") + "(\\s|$)"));
        }
    }
    else {
        arrRegExpClassNames.push(new RegExp("(^|\\s)" + oClassNames.replace(/\-/g, "\\-") + "(\\s|$)"));
    }
    var oElement;
    var bMatchesAll;
    for (var j = 0; j < arrElements.length; j++) {
        oElement = arrElements[j];
        bMatchesAll = true;
        for (var k = 0; k < arrRegExpClassNames.length; k++) {
            if (!arrRegExpClassNames[k].test(oElement.className)) {
                bMatchesAll = false;
                break;
            }
        }
        if (bMatchesAll) {
            arrReturnElements.push(oElement);
        }
    }
    return (arrReturnElements)
}

//From http://www.howtocreate.co.uk/tutorials/javascript/browserwindow
function getScrollXY() {
    var scrOfX = 0, scrOfY = 0;
    if (typeof (window.pageYOffset) == 'number') {
        //Netscape compliant
        scrOfY = window.pageYOffset;
        scrOfX = window.pageXOffset;
    } else if (document.body && (document.body.scrollLeft || document.body.scrollTop)) {
        //DOM compliant
        scrOfY = document.body.scrollTop;
        scrOfX = document.body.scrollLeft;
    } else if (document.documentElement && (document.documentElement.scrollLeft || document.documentElement.scrollTop)) {
        //IE6 standards compliant mode
        scrOfY = document.documentElement.scrollTop;
        scrOfX = document.documentElement.scrollLeft;
    }
    return [scrOfX, scrOfY];
}

//mouse coords
var mc_IE = document.all ? true : false;
if (!mc_IE) document.captureEvents(Event.MOUSEMOVE)
document.onmousemove = getMouseXY;
var mouseX = 0;
var mouseY = 0;
function getMouseXY(e) {
    if (mc_IE) { // grab the x-y pos.s if browser is IE
        mouseX = event.clientX + document.documentElement.scrollLeft;
        mouseY = event.clientY + document.documentElement.scrollTop;
    }
    else {  // grab the x-y pos.s if browser is NS
        mouseX = e.pageX;
        mouseY = e.pageY;
    }
    if (mouseX < 0) { mouseX = 0; }
    if (mouseY < 0) { mouseY = 0; }
    return true;
}







//from kobalt 20100111
function addclass(node, classes) {
    classes = classes.split(" ");
    for (var k in classes) {
        if (node.className.indexOf(classes[k]) == -1) {
            node.className = node.className + (node.className.length == 0 ? "" : " ") + classes[k];
        }
    }
}
function remclass(node, classes) {
    classes = classes.split(" ");
    for (var k in classes) {
        if ((i = node.className.indexOf(classes[k])) != -1) {
            node.className = node.className.substr(0, i) + node.className.substr(i + classes[k].length);
        }
    }
}




//from kobalt 20100114
function registerEventHandler(node, event, handler) {
    if (typeof node.addEventListener == "function")
        node.addEventListener(event, handler, false);
    else
        node.attachEvent("on" + event, handler);
}
function unregisterEventHandler(node, event, handler) {
    if (typeof node.removeEventListener == "function")
        node.removeEventListener(event, handler, false);
    else
        node.detachEvent("on" + event, handler);
}

function prepevent(e) {
    e = e || window.event;
    e.target = e.target || e.srcElement;
}





///*** below - copied entire contents on kobalt utility.js







///<reference path="formatting.js" />
///<reference path="javascript/jquery/jQuery-vsdoc.js" />









var debugging = true;















function removeallnodes(t) {
	while (t.firstChild) {
		t.removeChild(t.firstChild);
	}
}
function correctnodes(t) {
	if (t.childNodes.length > 1) {
		var goodchildren = [];
		var i = 0;
		//find all non-null children
		for (i = 0; i < t.childNodes.length; i++) {
			if (t.childNodes[i].innerHTML != undefined) {
				correctnodes(t.childNodes[i]);
				goodchildren.push(t.childNodes[i]);
			}
		}
		//remove all children
		removeallnodes(t);
		//add the good children
		var i = 0;
		for (i = 0; i < goodchildren.length; i++) {
			t.appendChild(goodchildren[i]);
		}
	}
}

function findnode(tbody_id, rowID) {
	var t = document.getElementById(tbody_id);
	for (i = 0; i < t.childNodes.length; i++) {
		if (t.childNodes[i].id == rowID) {
			return i;
		}
	}
}

function move_node(tbody_id, currpos, direction, distanceorposition_value, header_row) {
	var t = document.getElementById(tbody_id);
	//currpos will be found using findnode() above.
	var thisnode = t.childNodes[currpos];
	var destnode;
	var nextnode;
	//what should the new position be?
	var newpos;
	if (direction == 'up') {
		newpos = Math.max((header_row ? 1 : 0), currpos - distanceorposition_value);
	} else if (direction == 'down') {
		newpos = Math.min(t.childNodes.length - 1, currpos + distanceorposition_value);
	} else {
		newpos = Math.min(t.childNodes.length - 1, Math.max((header_row ? 1 : 0), distanceorposition_value));
	}
	if (currpos != newpos) {
		//find the node to replace
		destnode = t.childNodes[newpos];
		//find the next node, so we can put the destination node back in the right place. Special case when node is moving 1 position backward, since it is itself the next node then.
		if (newpos < (t.childNodes.length - 1)) {
			if (newpos != (currpos - 1)) {
				nextnode = t.childNodes[newpos + 1];
			} else {
				if (newpos < (t.childNodes.length - 2)) {//check again for end
					nextnode = t.childNodes[newpos + 2];
				} else {
					nextnode = false;
				}
			}
		} else {
			nextnode = false; //signifies to add at end
		}
		//replace
		t.replaceChild(thisnode, destnode);
		//put the replaced node back in the right place
		if (currpos < newpos) {
			t.insertBefore(destnode, thisnode);
		} else { // (currpos > newpos)
			if (nextnode == false) {
				t.appendChild(destnode);
			} else {
				t.insertBefore(destnode, nextnode);
			}
		}
	}
	return newpos; //together with calling findnode() first (returns the node's current position) and this value, you can find which intermediate nodes if any to change the 'order' value of, if you want to.
}



















//created 26-02-09 for baseoptiongroup noneoption form
function toggle_input_readonly(input_id, in_bool) {
	t = document.getElementById(input_id);
	if (in_bool) {
		t.readOnly = true;
		t.style.color = "#999";
		t.style.backgroundColor = "#eee";
	} else {
		t.readOnly = false;
		t.style.color = "#000";
		t.style.backgroundColor = "#fff";
	}
}






















function serialize(mixed_value, arr_omit_keys) {
	// http://kevin.vanzonneveld.net
	// +   original by: Arpad Ray (mailto:arpad@php.net)
	// +   improved by: Dino
	// +   bugfixed by: Andrej Pavlovic
	// +   bugfixed by: Garagoth
	// %          note: We feel the main purpose of this function should be to ease the transport of data between php & js
	// %          note: Aiming for PHP-compatibility, we have to translate objects to arrays
	// *     example 1: serialize(['Kevin', 'van', 'Zonneveld']);
	// *     returns 1: 'a:3:{i:0;s:5:"Kevin";i:1;s:3:"van";i:2;s:9:"Zonneveld";}'
	// *     example 2: serialize({firstName: 'Kevin', midName: 'van', surName: 'Zonneveld'});
	// *     returns 2: 'a:3:{s:9:"firstName";s:5:"Kevin";s:7:"midName";s:3:"van";s:7:"surName";s:9:"Zonneveld";}'

	var _getType = function(inp) {
		var type = typeof inp, match;
		var key;
		if (type == 'object' && !inp) {
			return 'null';
		}
		if (type == "object") {
			if (!inp.constructor) {
				return 'object';
			}
			var cons = inp.constructor.toString();
			if (match = cons.match(/(\w+)\(/)) {
				cons = match[1].toLowerCase();
			}
			var types = ["boolean", "number", "string", "array"];
			for (key in types) {
				if (cons == types[key]) {
					type = types[key];
					break;
				}
			}
		}
		return type;
	};
	var type = _getType(mixed_value);
	var val, ktype = '';

	switch (type) {
		case "function":
			val = "";
			break;
		case "undefined":
			val = "N";
			break;
		case "boolean":
			val = "b:" + (mixed_value ? "1" : "0");
			break;
		case "number":
			val = (Math.round(mixed_value) == mixed_value ? "i" : "d") + ":" + mixed_value;
			break;
		case "string":
			val = "s:" + mixed_value.length + ":\"" + mixed_value + "\"";
			break;
		case "array":
		case "object":
			val = "a";
			/*
			if (type == "object") {
			var objname = mixed_value.constructor.toString().match(/(\w+)\(\)/);
			if (objname == undefined) {
			return;
			}
			objname[1] = serialize(objname[1]);
			val = "O" + objname[1].substring(1, objname[1].length - 1);
			}
			*/
			var count = 0;
			var vals = "";
			var okey;
			var key;
			for (key in mixed_value) {
				if (!isset(arr_omit_keys) ? true : (array_search(key, arr_omit_keys) === -1)) { //added by me - just checks if this variable name isn't in an optional list of ones to exclude. This doesn't get passed through child calls.
					ktype = _getType(mixed_value[key]);
					if (ktype == "function") {
						continue;
					}

					okey = (key.match(/^[0-9]+$/) ? parseInt(key, 10) : key);
					vals += serialize(okey) +
							serialize(mixed_value[key]);
					count++;
				}
			}
			val += ":" + count + ":{" + vals + "}";
			break;
	}
	if (type != "object" && type != "array") {
		val += ";";
	}
	return val;
}
function unserialize(data, arraymode) {
    arraymode = isset(arraymode) ? arraymode : unserialize.ARRAYMODE_ARRAY;
	
	// http://kevin.vanzonneveld.net
	// +     original by: Arpad Ray (mailto:arpad@php.net)
	// +     improved by: Pedro Tainha (http://www.pedrotainha.com)
	// +     bugfixed by: dptr1988
	// +      revised by: d3x
	// +     improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
	// +      input by: Brett Zamir (http://brettz9.blogspot.com)
	// +   improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
	// %            note: We feel the main purpose of this function should be to ease the transport of data between php & js
	// %            note: Aiming for PHP-compatibility, we have to translate objects to arrays 
	// *       example 1: unserialize('a:3:{i:0;s:5:"Kevin";i:1;s:3:"van";i:2;s:9:"Zonneveld";}');
	// *       returns 1: ['Kevin', 'van', 'Zonneveld']
	// *       example 2: unserialize('a:3:{s:9:"firstName";s:5:"Kevin";s:7:"midName";s:3:"van";s:7:"surName";s:9:"Zonneveld";}');
	// *       returns 2: {firstName: 'Kevin', midName: 'van', surName: 'Zonneveld'}

	var error = function(type, msg, filename, line) { throw new window[type](msg, filename, line); };
	var read_until = function(data, offset, stopchr) {
		var buf = [];
		var chr = data.slice(offset, offset + 1);
		var i = 2;
		while (chr != stopchr) {
			if ((i + offset) > data.length) {
				error('Error', 'Invalid');
			}
			buf.push(chr);
			chr = data.slice(offset + (i - 1), offset + i);
			i += 1;
		}
		return [buf.length, buf.join('')];
	};
	var read_chrs = function(data, offset, length) {
		var buf;

		buf = [];
		for (var i = 0; i < length; i++) {
			var chr = data.slice(offset + (i - 1), offset + i);
			buf.push(chr);
		}
		return [buf.length, buf.join('')];
	};
	var _unserialize = function(data, offset, arraymode) {
	    if (data.length == 0) {
	        return [[], [], []]; //added by me. see below - it returns 2nd index of this return value, for some reason.
	    }
	    var readdata;
	    var readData;
	    var chrs = 0;
	    var ccount;
	    var stringlength;
	    var keyandchrs;
	    var keys;

	    if (!offset) offset = 0;
	    var dtype = (data.slice(offset, offset + 1)).toLowerCase();

	    var dataoffset = offset + 2;
	    var typeconvert = new Function('x', 'return x');

	    switch (dtype) {
	        case "i":
	            typeconvert = new Function('x', 'return parseInt(x)');
	            readData = read_until(data, dataoffset, ';');
	            chrs = readData[0];
	            readdata = readData[1];
	            dataoffset += chrs + 1;
	            break;
	        case "b":
	            typeconvert = new Function('x', 'return (parseInt(x) == 1)');
	            readData = read_until(data, dataoffset, ';');
	            chrs = readData[0];
	            readdata = readData[1];
	            dataoffset += chrs + 1;
	            break;
	        case "d":
	            typeconvert = new Function('x', 'return parseFloat(x)');
	            readData = read_until(data, dataoffset, ';');
	            chrs = readData[0];
	            readdata = readData[1];
	            dataoffset += chrs + 1;
	            break;
	        case "n":
	            readdata = null;
	            break;
	        case "s":
	            ccount = read_until(data, dataoffset, ':');
	            chrs = ccount[0];
	            stringlength = ccount[1];
	            dataoffset += chrs + 2;

	            readData = read_chrs(data, dataoffset + 1, parseInt(stringlength));
	            chrs = readData[0];
	            readdata = readData[1];
	            dataoffset += chrs + 2;
	            if (chrs != parseInt(stringlength) && chrs != readdata.length) {
	                error('SyntaxError', 'String length mismatch');
	            }
	            break;
	        case "a":
	            keyandchrs = read_until(data, dataoffset, ':');
	            chrs = keyandchrs[0];
	            keys = keyandchrs[1];
	            dataoffset += chrs + 2;

	            var allintegerkeys = true;
	            var datalist = [];

	            for (var i = 0; i < parseInt(keys); i++) {
	                var kprops = _unserialize(data, dataoffset, arraymode);
	                var kchrs = kprops[1];
	                var key = kprops[2];
	                dataoffset += kchrs;
	                if (!is_int(key)) allintegerkeys = false;

	                var vprops = _unserialize(data, dataoffset, arraymode);
	                var vchrs = vprops[1];
	                var value = vprops[2];
	                dataoffset += vchrs;

	                datalist.push(value);
	                datalist.push(key);
	            }

	            if ((arraymode === unserialize.ARRAYMODE_COLLECTION) || ((arraymode === unserialize.ARRAYMODE_FLEXIBLE) && !allintegerkeys)) {
	                readdata = new collection(null, datalist);
	            } else {
	                readdata = (arraymode === unserialize.ARRAYMODE_OBJECT) ? {} : [];
	                for (var i = 0; i < datalist.length; i += 2) {
	                    readdata[datalist[i + 1]] = datalist[i];
	                }
	            }

	            dataoffset += 1;
	            
	            break;
	        case "o":
	            var classname_charcount_READ = read_until(data, dataoffset, ":");
	            chrs = classname_charcount_READ[0];
	            var classnamelength = classname_charcount_READ[1];
	            dataoffset += chrs + 2;

	            var classname_READ = read_chrs(data, dataoffset, parseInt(classnamelength));
	            chrs = classname_READ[0];
	            var classname = classname_READ[1];
	            dataoffset += chrs + 2;
	            if (chrs != parseInt(classnamelength) && chrs != readdata.length) {
	                error('SyntaxError', 'String length mismatch in class name');
	            }
	            try {
	                eval("readdata = new " + classname + "();");
	            } catch (e) {
	                readdata = new Object();
	            }

	            keyscount_READ = read_until(data, dataoffset, ':');
	            chrs = keyscount_READ[0];
	            keys = keyscount_READ[1];
	            dataoffset += chrs + 2;

	            for (var i = 0; i < parseInt(keys); i++) {
	                var kprops = _unserialize(data, dataoffset, arraymode);
	                var kchrs = kprops[1];
	                var key = kprops[2];
	                dataoffset += kchrs;

	                var vprops = _unserialize(data, dataoffset, arraymode);
	                var vchrs = vprops[1];
	                var value = vprops[2];
	                dataoffset += vchrs;

	                readdata[key] = value;
	            }

	            dataoffset += 1;
	            
	            break;
	        default:
	            error('SyntaxError', 'Within "unserialize" - Unknown / Unhandled data type(s): ' + dtype);
	            break;
	    }
	    return [dtype, dataoffset - offset, typeconvert(readdata)];
	};
	return _unserialize(data, 0, arraymode)[2];
}
unserialize.ARRAYMODE_ARRAY = 1;
unserialize.ARRAYMODE_OBJECT = 0;
unserialize.ARRAYMODE_COLLECTION = 2;
unserialize.ARRAYMODE_FLEXIBLE = 3;

function array_from_object(in_obj) { //above function returns objects (see 'readdata ] {};'), which is fine, but sometimes need to handle like real array!
	var ret = [];
	for (var key in in_obj) {
		ret[key] = in_obj[key];
	}
	return ret;
}






function array_search(needle, haystack) {
	/// <summary>Returns -1 if not found</summary>
	for (var i = 0; i < haystack.length; i++) {
		if (haystack[i] === needle) {
			return i;
		}
	}
	return -1;
}
function object_search(needle, haystack) {
	///<summary>Returns false if not found</summary>
	for (var k in haystack) {
		if (haystack[k] == needle) {
			return k;
		}
	}
	return false;
}

function object_keys(in_array) {
	///<return type="Array" />
	var r = [];
	for (var k in in_array) {
		r.push(k);
	}
	return r;
}

function array_addremove(bool_addIIremove, array, value) {
	var index = array_search(value, array);
	if (bool_addIIremove) {
		if (index === -1) array.push(value);
	} else {
		if (index > -1) array.splice(index, 1);
	}
}

function appendfunction(mainfunc, newfunc) {
	var nf = newfunc.toString();
	var nfbody = nf.substr(nf.indexOf("{") + 1);

	var mf = mainfunc.toString();
	eval("retfunc = " + (mf.substr(0, mf.length - 1) + nfbody));
	return retfunc;
}








//function Numcast(inval, bool_floatIIint, bool_emptystring_or_invalid_equals_nought) {
function numbercast(inval, bool_floatIIint, return_if_invalid_or_empty) {
	bool_floatIIint = isset(bool_floatIIint) ? bool_floatIIint : true;
	if (typeof (return_if_invalid_or_empty) === "object") { //can't test for undefined, because undefined might be desired return. For this to work, param must always be supplied in array, so [undefined]
		return_if_invalid_or_empty = return_if_invalid_or_empty[0];
	} else {
		return_if_invalid_or_empty = NaN;
	}

	if (typeof (inval) == "string") {
		inval = inval.replace(/[^0-9-.]/g, "");
	}
	var returnval;
	if (bool_floatIIint) {
		returnval = parseFloat(inval);
		if (bool_floatIIint !== true) { //limit number of decimal places. warning - should check integer was provided
			returnval = parseFloat(returnval.toString().substr(0, returnval.toString().indexOf(".") + 1 + bool_floatIIint));
		}
	} else {
		returnval = Math.round(parseFloat(inval));
	}

	if (isNaN(returnval)) {
		return return_if_invalid_or_empty;
	} else {
		return returnval;
	}
}



//from eloquent javascript
function registerEventHandler(node, event, handler) {
	if (typeof node.addEventListener == "function")
		node.addEventListener(event, handler, false);
	else
		node.attachEvent("on" + event, handler);
}
function unregisterEventHandler(node, event, handler) {
	if (typeof node.removeEventListener == "function")
		node.removeEventListener(event, handler, false);
	else
		node.detachEvent("on" + event, handler);
}

function stop(event) {
	if (!event.stopPropagation) {
		event.cancelBubble = true;
		event.returnValue = false;
	} else {
		event.stopPropagation();
		event.preventDefault();
	}
}


function addclass(node, classes) {
    classes = classes.split(" ");
    var currentclasses = node.className.split(" ");
	for (var i = 0; i < classes.length; i++) {
	    var add = true;
	    for (var j = 0; j < currentclasses.length; j++) {
	        if (currentclasses[j] === classes[i]) add = false;
	    }
        if (add) node.className = node.className + (node.className.length == 0 ? "" : " ") + classes[i];
	}
}
function remclass(node, classes) {
    classes = classes.split(" ");
    var currentclasses = node.className.split(" ");
    var newclassname = "";
    for (var i = 0; i < currentclasses.length; i++) {
        if (array_search(currentclasses[i], classes) === -1) {
            newclassname += (newclassname.length ? " " : "") + currentclasses[i];
        }
    }
    node.className = newclassname;
}







//CODE TO MAKE ACCESSING DOCUMENT STYLESHEETS EASIER
function getstyle(selector, rule) {
	for (k = 0; k < document.styleSheets.length; k++) {
		for (kk = 0; kk < document.styleSheets[k].rules.length; kk++) {
			if (document.styleSheets[k].rules[kk].selectorText.indexOf(selector) > -1) {
				return document.styleSheets[k].rules[kk].style[rule];
			}
		}
	}
}



//Thanks http://my.opera.com/GreyWyvern/blog/show.dml/1725165
clone = function(obj_clone, arrays, objects) {
	arrays = arrays ? arrays : true;
	objects = objects ? objects : false;
	var newObj = (obj_clone instanceof Array) ? [] : {};
	for (var i in obj_clone) {
		if (obj_clone[i] && ((arrays && (obj_clone[i] instanceof Array)) || (objects && (typeof obj_clone[i] == "object")))) {
			newObj[i] = clone(obj_clone[i], arrays, objects);
		} else newObj[i] = obj_clone[i];
	} return newObj;
}







getabsoluteposition = function(element, opt_stopatparent, stupidbug) {
    ///<returns type="Point" />
    var ret = new Point();
    do {
        if (element === opt_stopatparent) break;
        if (!stupidbug || (element.offsetParent !== document.body)) {
            ret.x += element.offsetLeft;
        }
        ret.y += element.offsetTop;
    } while (element = element.offsetParent);
    return ret;
}







//thank you	http://kevin.vanzonneveld.net/techblog/article/javascript_equivalent_for_phps_addslashes/
function addslashes(str) {
	return (str + '').replace(/([\\"'])/g, "\\$1").replace(/\0/g, "\\0");
}



array_to_string = function(in_array, numslashes) {
	var ret = "[";
	for (var k in in_array) {
		if (in_array[k] instanceof Array) {
			ret += array_to_string(in_array[k], numslashes);
			continue;
		}
		switch (typeof in_array[k]) {
			case "undefined":
				ret += "undefined";
				break;
			case "object": //Null will report as object
				ret += "null";
				break;
			case "boolean":
				ret += (in_array[k] ? "true" : "false");
				break;
			case "number":
				ret += in_array[k].toString();
				break;
			case "string":
				ret += "'" + in_array[k] + "'";
				break;
			default:
				ret += "null";
				break;
		}
		ret += ",";
	}
	return ret.substring(0, ret.length - 1) + "]";
}




var globaltemplates = {};
function findnamedreferences(node, obj_context) {
    if (obj_context) {
        var name = node.getAttribute("name");
		if (isset(name) && name.substring(0, 1) == "#") {
			var spaceindex = name.indexOf(" ");
			var objvarname = name.substring(1, (spaceindex > -1) ? spaceindex : name.length);
			obj_context[objvarname] = node;
			if (spaceindex > 0) {
				node.setAttribute("name", name.substring(spaceindex + 1));
			} else {
				node.removeAttribute("name");
			}
		}
		for (var i = 0; i < node.children.length; i++) {
			findnamedreferences(node.children[i], obj_context);
		}
	}
}
function clonetemplate(storedtemplate, obj_context) {
    var ret = storedtemplate.cloneNode(true);
	findnamedreferences(ret, obj_context ? obj_context : null);
	return ret;
}





//function must be called on an element to work, hence it must have been assigned to element through prototype or manually.
insertatindex = function(insertnode, index, parentelement, bool_allchildnodes) {
	var childrenarray = bool_allchildnodes ? parentelement.childNodes : parentelement.children;
	if (childrenarray[index] !== insertnode) {
		if (index < childrenarray.length) {
			parentelement.insertBefore(insertnode, childrenarray[index]);
		} else {
			parentelement.appendChild(insertnode);
		}
	}
}



error = function(message, fatal) {
	if (debugging) {
		throw new Error(message);
	} if (fatal) {
		//recover smoothly
	}
}




var hits = 0;
function hitcount() {	
	hits++;
}




hasclass = function(element, classname) {
	if (array_search(classname, element.className.split(" ")) > -1) {
		return true;
	}
	return false;
}











var collection = function(varname, arr_content, coll_issubsetof) {
	///<summary>An object allowing unique ID 'keys' of any type to be associated with objects of any type. An implementation of associative arrays in Javascript.</summary>
	///<param name="varname" optional="true">Name of the javascript variable. Only necessary if it will contain objects with the 'constructor_global' function that require global access to themselves for timers/eval functionality.</param>
	this.items = [];
	this.objectpositionmap = []; //The objectpositionmap was created for maximum optimisation, but is perhaps pointless - objects which need to write their position into eval and therefore need an unchanging string representation can either do coll.item(\"+thing.ID+\"), or coll.arr_items[coll.objectpositionmap[\"+thing.constantkey+\"]] where the constant key is assigned when the object is created, and is updated in updatepositions so that that array element always leads to the current actual key of the object in coll.arr_items. Clever, but the first way loops through an ID comparing probably short string values for each element, and the second looks up values in 2 arrays - is there actually much of a speed difference?
	this.varname = varname;
	this.length = 0;
	if ((typeof arr_content !== "undefined") && isset(arr_content)) {
		if ((arr_content.length % 2) == 1) {
			error("Non-even number of objects and IDs supplied in collection constructor");
		}
		for (var i = 0; i < arr_content.length; i = i + 2) {
			this.push(arr_content[i], arr_content[i + 1]);
		}
	}
	this.parentcollection = null;
	if (isset(coll_issubsetof)) {
		this.parentcollection = coll_issubsetof;
		this.parentcollection.childcollections.push(this);
	}
	this.childcollections = [];
}
collection.prototype.push = function(obj, ID, allowobjectvariables) {
	///<summary>Adds an object of any type except undefined to the collection, associated for retrieval with a unique ID 'key' of any type except Boolean, null or undefined. If the object has a 'constructglobal' member function, two members will be assigned to the object: obj.collection, a reference to this collection; and obj.collectionkey, the internal key which can be used for optimised retrieval: The obj.constructglobal will then be called giving one parameter - the string "this.collection[this.collectionkey]", which the object should assign to a string variable which it uses to obtain a global reference for timers/eval situations.</summary>
	///<param name="ID">The unique ID 'key', of any type, that the object will be associated with.</param>
	///<param name="obj">The object to add to the collection.</param>
	return this.pushat(this.length, obj, ID, allowobjectvariables);
}
collection.prototype.pushat = function(position, obj, ID, allowobjectvariables) {
	///<summary>Does not throw an error if position is greater than array length</summary>
	allowobjectvariables = isset(allowobjectvariables) ? allowobjectvariables : true;
	if ((ID === true) || (ID === false)) {
		error("ID cannot be boolean in collection.push");
	} else if (obj === undefined) {
		error("No object supplied to collection.push");
	} else if (this.getIDposition(ID) > -1) {
		error("Error adding to collection: unique ID '" + ID + "' is already used");
	}
	if ((ID === undefined) || (ID === null)) {
		ID = this.length;
		while (this.getIDposition(ID) > -1) {
			ID++;
		};
	}
	position = Math.max(Math.min(this.length, position), 0);
	var newitem = { "ID": ID, "obj": obj };
	if (allowobjectvariables) {
		if ((typeof obj == "object") && (obj !== null)) {
			if (obj.collection !== undefined) error("Error adding to collection: object already has the 'collection' property defined");
			if (obj.collectionposition !== undefined) error("Error adding to collection: object already has the 'collectionposition' property defined");
			try {
				obj.collection = this;
				obj.collectionposition = position;
			} catch (exception) { }
			if (obj.constructor_global) {
				newitem.constantkey = this.objectpositionmap.push(position) - 1;
				obj.constructor_global(this.varname + ".itemat(" + this.varname + ".objectpositionmap[" + newitem.constantkey + "])");
			}
		}
	}
	this.items.splice(position, 0, newitem);
	this.length = this.items.length;
	this.updatepositions(position + 1);
	if (this.parentcollection) {
		this.parentcollection.push(obj, ID, false);
	}
	return obj;
}
collection.prototype.getIDposition = function(ID) {
///<summary>Returns the internal array key where the ID is stored, or -1 if the given ID was not found.</summary>
///<param name="ID">The unique ID entry</param>
///<returns type="Integer" />
	for (var i = 0; i < this.items.length; i++) {
		if (this.items[i].ID === ID) {
			return i;
		}
	}
	return -1;
}
collection.prototype.getitemposition = function(obj) {
///<summary>Returns the internal array key where the object is stored, or -1 if the given object was not found.</summary>
///<param name="ID">The object to find</param>
///<returns type="Integer" />
    for (var i = 0; i < this.items.length; i++) {
        if (this.items[i].obj === obj) {
            return i;
        }
    }
    return -1;
}
collection.prototype.itemat = function(order) {
	return this.items[order].obj;
}
collection.prototype.IDat = function(order) {
	return this.items[order].ID;
}
collection.prototype.item = function(ID) {
///<summary>Returns the object associated with the given ID, or undefined if not found.</summary>
///<param name="ID">The unique ID entry</param>
	if ((i = this.getIDposition(ID)) > -1) {
		return this.items[i].obj;
	}
	return undefined;
}
collection.prototype.find = function(obj) {
///<summary>Returns the unique ID for the object if found, false otherwise</summary>
///<param name="obj">The exact object or value to search for</param>
	for (var i = 0; i < this.items.length; i++) {
		if (this.items[i].obj === obj) {
			return this.items[i].ID;
		}
	}
	return false;
}
collection.prototype.remove = function(ID) {
///<summary>Returns true if the ID was found and the item removed, false if it wasn't found</summary>
///<param name="ID">The unique ID entry</param>
///<returns type="Boolean" />
	if ((i = this.getIDposition(ID)) > -1) {
		this.removerange(i, 1);
		return true;
	}
	return false;
}
collection.prototype.removerange = function(startindex, deletecount) {
	///<summary>Acts like Array.splice. Removes to end of collection if deletecount is not supplied.</summary>
	if (startindex > (this.length - 1)) {
		error("Start index out of bounds in collection.removerange");
	}
	deletecount = (deletecount === undefined) ? (this.length - startindex) : deletecount;
	var arr_deleted = this.items.splice(startindex, deletecount);
	for (var i = 0; i < arr_deleted.length; i++) {
		if (arr_deleted[i].obj.collection !== undefined) delete arr_deleted[i].obj.collection;
		if (arr_deleted[i].obj.collectionconstantkey !== undefined) delete arr_deleted[i].obj.collectionconstantkey;
		if (arr_deleted[i].obj.collectionposition !== undefined) delete arr_deleted[i].obj.collectionposition;
		if (arr_deleted[i].obj.destructor_global) {
			arr_deleted[i].obj.destructor_global();
		}
		for (var j = 0; j < this.childcollections.length; j++) {
			this.childcollections[j].remove(arr_deleted[i].ID);
		}
	}
	this.length = this.items.length;
	this.updatepositions(startindex);
}
collection.prototype.foreach = function(applyfunction, arr_extraparams, obj_context, arr_exceptionIDs, arr_exceptionObjects) {
	///<summary>Calls the given function for each object, in internal order, with the object as the first parameter and ID as the 2nd parameter, and returns the unique ID of the first object which causes the function to return true, or false otherwise.</summary>
	///<param name="applyfunction">The function.</param>
	///<param name="arr_extraparams">An array of extra values to be passed to each call of applyfunction, from the 3rd parameter onwards.</param>
	///<param name="obj_context" optional="true">If supplied, 'applyfunction' will be called ON the given object.</param>
	///<param name="arr_exceptionIDs" optional="true">An array of unique IDs for the objects not to perform the function on. Useful for an object to exclude itself.</param>
	///<param name="arr_exceptionObjects" optional="true">An array of objects to match and avoid.</param>
	arr_extraparams = arr_extraparams ? arr_extraparams : [];
	arr_exceptionIDs = arr_exceptionIDs ? arr_exceptionIDs : [];
	arr_exceptionObjects = arr_exceptionObjects ? arr_exceptionObjects : [];
	for (var i = 0; i < this.items.length; i++) {
		if ((array_search(this.items[i].ID, arr_exceptionIDs) === -1) && (array_search(this.items[i].obj, arr_exceptionObjects) === -1)) {
			var arr_params = [this.items[i].obj, this.items[i].ID];
			arr_params = arr_params.concat(arr_extraparams);
			obj_context = obj_context ? obj_context : new Object();
			if (applyfunction.apply(obj_context, arr_params) === true) {
				return this.items[i].ID;
			}
		}
	}
	return false;
}
collection.prototype.moveitem = function(currposition, newposition) {
	if ((currposition > (this.length - 1)) || (currposition < 0)) error("Specified origin position is out of bounds");
	newposition = Math.min(this.length - 1, Math.max(0, newposition));
	var memory = this.items[currposition];
	this.items.splice(currposition, 1);
	this.items.splice(newposition, 0, memory);
	this.updatepositions(Math.min(currposition, newposition));
}
collection.prototype.updatepositions = function(startposition) {
	for (var i = startposition; i < this.length; i++) {
		if (typeof this.items[i].obj.collectionposition !== "undefined") this.items[i].obj.collectionposition = i;
		if (typeof this.items[i].constantkey !== "undefined") this.objectpositionmap[this.items[i].constantkey] = i;
	}
}
collection.prototype.sort = function(sortby, ascdesc) {
///<param name="sortby">Leave blank or pass null, false, or 0 to sort by ID, or pass 1 or true to sort by objects. Or pass a custom sorting function, in which the two parameters will be an object {ID, obj}. For a to be nearer the start of the list than b, return a negative number.</param>
///<param name="ascdesc">true or leave blank for ascending order, false for descending order</param>
	switch (sortby) {
		case undefined:
		case null:
		case false:
		case 0:
			this.items.sort(function(a, b) {
				a = a.ID;
				b = b.ID;
				var a_ok = (typeof a == "string") || (typeof a == "number");
				var b_ok = (typeof b == "string") || (typeof b == "number");
				if (!a_ok && !b_ok) {
					return 0;
				} else if (a_ok && !b_ok) {
					return -1;
				} else if (b_ok && !a_ok) {
					return 1;
				}
				a = a.toString().substring(0,1).toLowerCase();
				b = b.toString().substring(0,1).toLowerCase();
				if (a < b) { return -1; } else if (a > b) { return 1; } else { return 0; }
			});
			break;
		case true:
		case 1:		
			this.items.sort(function(a, b) {
				a = a.obj;
				b = b.obj;
				var a_ok = (typeof a == "string") || (typeof a == "number");
				var b_ok = (typeof b == "string") || (typeof b == "number");
				if (!a_ok && !b_ok) {
					return 0;
				} else if (a_ok && !b_ok) {
					return -1;
				} else if (b_ok && !a_ok) {
					return 1;
				}
				a = a.toString().substring(0,1).toLowerCase();
				b = b.toString().substring(0,1).toLowerCase();
				if (a < b) { return -1; } else if (a > b) { return 1; } else { return 0; }
			});
			break;
		default:
			if (typeof sortby != "function") {
				error("Error: don't know what to sort by");
			}
			this.items.sort(sortby);
			break;
	}
	if (!ascdesc) {
		this.items.reverse();
	}
	this.updatepositions(0);
}









function positionelement(element, in_Point) {
	element.style.left = in_Point.x.toString() + "px";
	element.style.top = in_Point.y.toString() + "px";
}



function literal(in_value) {
///<summary>Takes any literal value and returns the string suitable for assigning it in code. So, a string will be wrapped in quotes.</summary>
	switch (typeof in_value) {
	    case "object":
	        if (in_value === null) {
	            return "null";
	        } else {
	            error("Can't literalise an object (yet!)");
	        }
	        break;
		case "function":
			error("Can't literalise a function (yet!)");
			break;
		case "string":
			return "\"" + addslashes(in_value) + "\"";
			break;
		case "boolean":
			return in_value ? "true" : "false";
			break;
        case "undefined":
            return "undefined";
            break;
		default:
			return in_value.toString();
	}
}




function addclassloop(rootelement, matchname, classes) {
	for (var i = 0; i < rootelement.children.length; i++) {
		if (rootelement.children[i].name === matchname) {
			addclass(rootelement.children[i], classes);
		}
		addclassloop(rootelement.children[i], matchname, classes);
	}
}
function remclassloop(rootelement, matchname, classes) {
	for (var i = 0; i < rootelement.children.length; i++) {
		if (rootelement.children[i].name === matchname) {
			remclass(rootelement.children[i], classes);
		}
		remclassloop(rootelement.children[i], matchname, classes);
	}
}





function computetextsize(text, element_copystyle, font, size, weight, fontstyle) {
	var d = document.createElement("div");
	d.style.visibility = "hidden";
	d.style.position = "absolute";
	d.style.margin = "0px";
	d.style.padding = "0px";
	d.style.border = "0px";
	if (element_copystyle) {
		var compstyle = getcomputedstyle(element_copystyle);
		if (compstyle.getAttribute("fontFamily")) d.style.fontFamily = compstyle.getAttribute("fontFamily");
		if (compstyle.getAttribute("fontSize")) d.style.fontSize = compstyle.getAttribute("fontSize");
		if (compstyle.getAttribute("fontWeight")) d.style.fontWeight = compstyle.getAttribute("fontWeight");
		if (compstyle.getAttribute("fontStyle")) d.style.fontStyle = compstyle.getAttribute("fontStyle");
	} else {
		if (font) d.style.fontFamily = font;
		if (size) d.style.fontSize = size;
		if (weight) d.style.fontWeight = weight;
		if (fontstyle) d.style.fontStyle = fontstyle;
	}
	document.body.appendChild(d);
	d.innerHTML = text;
	var p = new Point(d.clientWidth, d.clientHeight);
	document.body.removeChild(d);
	return p;
}



function getcomputedstyle(element) {
	var ret;
	if (element.currentStyle) {
		ret = element.currentStyle;
	} else if (window.getComputedStyle) {
		ret = document.defaultView.getComputedStyle(element, null);
		ret.getAttribute = function(propertyname) {
			return this.getPropertyValue(propertyname);
		};
	}
	return ret;
}


function parseXML(xmlstring, onreadystatechange) {
	var xmlDoc = createXMLDocument();
	if (ActiveXObject) {
		xmlDoc.async = "false";
		if (onreadystatechange) xmlDoc.onreadystatechange = onreadystatechange;
		xmlDoc.loadXML(xmlstring);
	} else {
		if (onreadystatechange) xmlDoc.onreadystatechange = onreadystatechange;
		var parser = new DOMParser();
		xmlDoc = parser.parseFromString(xmlstring, "text/xml");
    }
    return xmlDoc;
}




function resizetextinputsbycontent(thiselementandwithin, addroomforcursor, vertical) {
	vertical = (vertical === undefined) ? true : vertical;
	addroomforcursor = (addroomforcursor === undefined) ? false : addroomforcursor;
	if ((thiselementandwithin.tagName.toLowerCase() === "input") && (thiselementandwithin.getAttribute("type").toLowerCase() === "text")) {
		var size = computetextsize(thiselementandwithin.value, thiselementandwithin);
		thiselementandwithin.style.width = (size.x + (addroomforcursor ? 1 : 0)).toString() + "px";
		if (vertical === true) {
			thiselementandwithin.style.height = size.y + "px";
		}
	} else {
		for (var i = 0; i < thiselementandwithin.children.length; i++) {
			resizetextinputsbycontent(thiselementandwithin.children[i], addroomforcursor);
		}
	}
}


function toggleinputsreadonly(thiselementandwithin, truefalse) {
	if (thiselementandwithin.tagName.toLowerCase() === "input") {
		thiselementandwithin.readOnly = truefalse;
	} else {
		for (var i = 0; i < thiselementandwithin.children.length; i++) {
			toggleinputsreadonly(thiselementandwithin.children[i], truefalse);
		}
	}
}


function XMLRequest(url, POSTparameters) {
	var xmlhttp;
	if (window.XMLHttpRequest) {
		xmlhttp = new XMLHttpRequest();
	} else if (ActiveXObject) {
		xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
	}
	if (POSTparameters) {
		xmlhttp.open("POST", url, false);
		xmlhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
		xmlhttp.setRequestHeader("Content-length", POSTparameters.length);
		xmlhttp.setRequestHeader("Connection", "close");
		xmlhttp.send(POSTparameters);
	} else {
		xmlhttp.open("GET", url, false);
		xmlhttp.send("");
	}
	return xmlhttp;
}






function loadtemplates(contextelement, destinationobject) {
    var templateelements = $(".TEMPLATE", contextelement).get();
    destinationobject = (destinationobject === undefined) ? globaltemplates : destinationobject;
    for (var i = 0; i < templateelements.length; i++) {
        if (templateelements[i].id.length == 0) {
            error("Template element has no ID");
        } else if (array_search(templateelements[i].id, object_keys(destinationobject)) > -1) {
            error("Two template elements share the same unique ID");
        } else {
            var id = templateelements[i].id;
            destinationobject[id] = templateelements[i];
            destinationobject[id].id = "";
            remclass(destinationobject[id], "TEMPLATE");
            templateelements[i].parentNode.removeChild(templateelements[i]);
        }
    }
}
function loadtemplatesfromstring(markupstring, destinationobject) {
	var div = document.createElement("div");
	div.innerHTML = markupstring;
	loadtemplates(div, destinationobject);
}




function hasparent(startelement, matchparent) {
	var currparent = startelement;
	do {
		if (currparent === matchparent) {
			return true;
		}
	} while (currparent = currparent.parentNode);
	return false;
}




function getelementglobalvarname(element) {
	var path = "";
	do {
		if (element === document.body) {
			path = "document.body" + path;
			break;
		}
		var siblingcounter = 0;
		var currsibling = element;
		while (currsibling = currsibling.previousSibling) {
			siblingcounter++;
		};
		path = ".childNodes[" + siblingcounter.toString() + "]" + path;
	} while (element = element.parentNode);
	return path;
}




function focusnext(currelement, alsoselect) {
	if (!hasparent(currelement, document.body)) {
		return false;
	}
	var iterate = function(currelement, stemelementsoignore) {
		stemelementsoignore = (stemelementsoignore === undefined) ? false : stemelementsoignore;
		if (!stemelementsoignore && (typeof currelement.tagName !== "undefined") && (currelement.tagName.toLowerCase() === "input")) {
			currelement.focus();
			if ((alsoselect === true) && (typeof currelement.select !== "undefined")) {
				currelement.select();
			}
			return true;
		}
		for (var i = 0; i < currelement.children.length; i++) {
			if (iterate(currelement.children[i]) !== false) return true;
		}
		return false;
	}
	var veryfirsttime = true;
	var startindex = array_search(currelement, currelement.parentNode.children);
	do {
		for (var i = startindex; i < currelement.parentNode.children.length; i++) {
			if (iterate(currelement.parentNode.children[i], veryfirsttime) !== false) return true;
			veryfirsttime = false;
		}
		if (currelement.parentNode === document.body) {
			window.focus();
			return true;
		}
		currelement = currelement.parentNode;
		startindex = array_search(currelement, currelement.parentNode.children) + 1;
	} while (true);
}

function focusprevious(currelement) {
	return true;
}





function focusandselect(element) {
	element.focus();
	element.select();
}




function inheritstyle(element, rules) {
	var arr_rules = rules.split(" ");
	forloop:
	for (var i = 0; i < arr_rules.length; i++) {
		var currparent = element;
		while (currparent = currparent.parentNode) {
			if (typeof (rule = currparent.currentStyle[arr_rules[i]]) !== "undefined") {
				if ((rule !== null) && (rule.length > 0)) {
					element.style[arr_rules[i]] = rule;
					continue forloop;
				}
			}
		};
	}
}






function replacenodewithnewtagname(xmldoc, xmlelement, newtagname) {
	if (xmlelement.nodeType === 1) {
		var newnode = xmldoc.createElement(newtagname);
		for (var i = 0; i < xmlelement.attributes.length; i++) {
			newnode.setAttribute(xmlelement.attributes[i].name, xmlelement.attributes[i].nodeValue);
		}
		for (var i = 0; i < xmlelement.childNodes.length; i++) {
			newnode.appendChild(xmlelement.childNodes[i]);
		}
		if (xmlelement.parentNode) {
			xmlelement.parentNode.replaceChild(newnode, xmlelement);
		}
		return newnode;
	} else {
		error("xmlelement is not an xml element node");
	}
}




var xmlnodetype = {
	ELEMENT_NODE: 1,
	ATTRIBUTE_NODE: 2,
	TEXT_NODE: 3,
	CDATA_SECTION_NODE: 4,
	ENTITY_REFERENCE_NODE: 5,
	ENTITY_NODE: 6,
	PROCESSING_INSTRUCTION_NODE: 7,
	COMMENT_NODE: 8,
	DOCUMENT_NODE: 9,
	DOCUMENT_TYPE_NODE: 10,
	DOCUMENT_FRAGMENT_NODE: 11,
	NOTATION_NODE: 12
}


///<summary>Assumes node is like a 'record' with each of its child nodes holding one column's worth of data, as if created with my php mysqlresult_to_xml function</summary>
function simplexmlchildvalue (node, childtagname) {
	return node.getElementsByTagName(childtagname)[0].childNodes[0].nodeValue;
}



//moved from bogedit_block 20100127
function randomString() {
	var chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz";
	var string_length = 8;
	var randomstring = '';
	for (var i = 0; i < string_length; i++) {
		var rnum = Math.floor(Math.random() * chars.length);
		randomstring += chars.substring(rnum, rnum + 1);
	}
	return randomstring;
}
function generate_random_id() {
	var s = randomString();
	if (!document.getElementById(s)) {
		return s;
	}
}






//moved from bogedit_mouse 20100128
function scrollLeft() {
	if (Math.round(scrollleftfloat) != document.body.scrollLeft) {
		scrollleftfloat = document.body.scrollLeft;
	}
	return scrollleftfloat;
}
function scrollTop() {
	if (Math.round(scrolltopfloat) != document.body.scrollTop) {
		scrolltopfloat = document.body.scrollTop;
	}
	return scrolltopfloat;
}
function scrollLeftset(value) {
	scrollleftfloat = value;
	if (document.body.scrollLeft != null) document.body.scrollLeft = Math.round(value);
	else document.documentElement.scrollLeft = value;
}
function scrollTopset(value) {
	scrolltopfloat = value;
	if (document.body.scrollTop != null) document.body.scrollTop = Math.round(value);
	else document.documentElement.scrollTop = value;
}

function scrollbarsize() {
	return 10;
}

function scrollWidth(element) {
	element = element ? element : document.body;
	return element.scrollWidth;
}
function scrollHeight(element) {
	element = element ? element : document.body;
	return element.scrollHeight;
}



function simplexmlnode(xmldoc, tagname, innertext, coll_attributes) {
	var retnode = xmldoc.createElement(tagname);
	if ((innertext !== undefined) && (innertext !== null) && (typeof innertext == "string")) {
		setInnerText(retnode, innertext, xmldoc);
	}
	if (coll_attributes) {
		for (var i = 0; i < coll_attributes.length; i++) {
			retnode.setAttribute(coll_attributes.IDat(i), coll_attributes.itemat(i));
		}
	}
	return retnode;
}



function realpage() {
	///<summary>Returns false if code is running for intellisenes environment, in order to prevent trying to work with document elements that don't exist. Works in VS.</summary>
	if ((document.body === null) || (typeof document.body.childNodes !== "undefined")) {
		return true;
	}
	return false;
	//document.body is null for scripts in the head section, then in body section document.body existed in VS environment, a bit strange, but its childNodes was undefined - used that.
}


function isset(input) {
	///<summary>Checks for null and undefined - used e.g for a typical optional functional parameter where leaving it off would result in undefined but if you're typing past it you probably want to type null</summary>
	if ((input !== null) && (input !== undefined)) {
		return true;
	}
	return false;
}


function createXMLDocument() {
	if (ActiveXObject) {
		return new ActiveXObject("Microsoft.XMLDOM");
	} else {
		return document.implementation.createDocument("", "", null);
	}
}




function boolcast(in_val) {
	if (typeof in_val === "string") {
		if ((in_val.length === 0) || (in_val.substring(0, 1) === "0")) {
			return false;
		} else {
			return true;
		}
	}
	return in_val;
	//more obviously needed.
}




Point = function(x, y) {
	this.x = x ? x : 0;
	this.y = y ? y : 0;
}
Point.prototype.add = function(in_Point) {
	this.x += in_Point.x;
	this.y += in_Point.y;
}
Point.prototype.addxy = function(x, y) {
	this.x += x;
	this.y += y;
}



function addlinkattribute(type, rel, href, media) {
	var head = document.getElementsByTagName("head")[0];
	var link = document.createElement("link");
	link.type = type;
	link.rel = rel;
	link.href = href;
	if (media) link.media = media;
	head.appendChild(link);
}



function getPathToSelf() {
///<summary>Don't use this! There might be two js files with same filename used in this document.</summary>
	var myName = /(^|[\/\\])path\.js(\?|$)/;
	var scripts = document.getElementsByTagName("script");
	for (var i = 0; i < scripts.length; i++) {
		var src;
		if (src = scripts[i].getAttribute("src")) {
			if (src.match(myName)) {
				return src;
			}
		}
	}
}


function parseHTML(intext) {
	var newdiv = document.createElement("div");
	newdiv.innerHTML = intext;
	return newdiv;
}



function uri_variable(name, value, bool_firstvaluequestionmark, bool_insertevenifvaluenotset) {
	if (isset(value) || bool_insertevenifvaluenotset) {
		return (bool_firstvaluequestionmark ? "?" : "&") + name + "=" + encodeURIComponent(value);
	}
	return "";
}



function processdateselectschange(parentspan) { //paired with php utility function makedateselects
	var dayselect = $("[name$='day']", parentspan).get(0);
	var monthselect = $("[name$='month']", parentspan).get(0);
	var yearselect = $("[name$='year']", parentspan).get(0);
	var correctdays = getmonthdays(monthselect.value, yearselect.value);
	while ((dayselect.children.length - 1) < correctdays) {
		var newoption = document.createElement("option");
		newoption.value = dayselect.children.length;
		setInnerText(newoption, dayselect.children.length);
		dayselect.appendChild(newoption);
	}
	while ((dayselect.children.length - 1) > correctdays) {
		if (dayselect.children[dayselect.children.length - 1].selected) dayselect.children[dayselect.children.length - 2].selected = true;
		dayselect.removeChild(dayselect.lastChild);
	}
}

function getmonthdays(month, year) {
	month = numbercast(month);
	year = numbercast(year);
	switch (month) {
		case 2:
			if (year % 4 === 0) return 29;
			else return 28;
		case 4:
		case 6:
		case 9:
		case 11:
			return 30;
		default:
			return 31;
	}
}



function prepevent(e) {
	e = e || window.event;
	e.target = e.target || e.srcElement;
}



function loadHTMLmodule(inHTML, parentelement, templatearray) {
    if (!templatearray) templatearray = globaltemplates;
    if (!parentelement) parentelement = document.body;
    loadtemplates(inHTML, templatearray);
    while (inHTML.firstChild) {
        parentelement.appendChild(inHTML.firstChild);
    }
}


var evalstring = "";
var waitevaltimer;
function evalwait(command) {
	evalstring += "\r" + command + ";";
}
function evalflush() {
	if (evalstring.length) {
		eval(evalstring);
		evalstring = "";
	}
}
function evaltimerstart() {
	waitevaltimer = setInterval("evalflush();", 200);
}
function evaltimerstop() {
	clearInterval(waitevaltimer);
}




function percenttofactor(val) {
    return (val / 100) + 1;
}
function factortopercent(val) {
    return (val - 1) * 100;
}



function isnumber(val) {
    return (typeof numbercast(val) === "number") && !isNaN(val);
}



function toggleinputsdisabled(thiselementandwithin, truefalse) {
	if (thiselementandwithin.tagName.toLowerCase() === "input") {
		thiselementandwithin.disabled = truefalse;
	} else {
		for (var i = 0; i < thiselementandwithin.children.length; i++) {
			toggleinputsdisabled(thiselementandwithin.children[i], truefalse);
		}
	}
}



function scrolltoelement(p) {
    window.scrollTo(p.x, p.y);
}



function strtotime(datevalue) {
    if (datevalue.toString().length) {
        datevalue = datevalue.split(" ");
        var date = datevalue[0].split("-");
        var time = datevalue[1].split(":");
        return Date.UTC(date[0], date[1], date[2], time[0], time[1], time[2]);
    }
    return null;
}


function substr(instring, start, length) {
    return instring.substring(start, isset(length) ? ((length < 0) ? (instring.length + length) : start + length) : null);
}


function equal(a, b) {
    if ((a === b) || (isNaN(a) && isNaN(b))) return true;
    return false;
}



var globalrefs = [];
function getglobalreference(obj) {
    obj.globalvarname = "globalrefs[" + getglobalreferencekey(obj) + "]";
}
function getglobalreferencekey(obj) {
    return globalrefs.push(obj) - 1;
}



function stopdefault(e) {
    if (e && e.preventDefault) {
        e.preventDefault();
    } else {
        window.event.returnValue = false;
    }
    return false;
}



function a() {
}



//<a href="http://www.somacon.com/p355.php">Javascript Trim LTrim and RTrim Functions</a>
function trim(stringToTrim) {
    return stringToTrim.replace(/^\s+|\s+$/g, "");
}
function ltrim(stringToTrim) {
    return stringToTrim.replace(/^\s+/, "");
}
function rtrim(stringToTrim) {
    return stringToTrim.replace(/\s+$/, "");
}



function ampify(xmlstr) {
    return xmlstr.replace(/&/g, "&amp;");
}
function deamp(xmlstr) {
    return xmlstr.replace(/&amp;/g, "&");
}

//http://www.inventpartners.com/content/javascript_is_int
function is_int(value){
    for (i = 0 ; i < value.length ; i++) {
        if ((value.charAt(i) < '0') || (value.charAt(i) > '9')) return false 
    }
    return true;
}


function isXmlDocument(invar) {
    return (isset(invar.nodeType) && (invar.nodeType === xmlnodetype.DOCUMENT_NODE));
}


//returns the index of where the strings first differ, or false otherwise. If they are the same up to the end of a shorter string its length is returned, i.e one index beyond its end.
function findstringdifference(string1, string2) {
    for (var i = 0; i < Math.max(string1.length, string2.length); i++) {
        if ((i == string1.length) || (i == string2.length) || (string1.charAt(i) !== string2.charAt(i))) {
            return i;
        }
    }
    return false;
}

//basically just replaces \n with \r\n - can always be improved.
function normaliselinebreaks(string) {
    return string.replace(/\n/g, "\r\n").replace(/\r\r/g, "\r");
}


function validateEmail(elementValue){  
    var emailPattern = /^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}$/;  
    return emailPattern.test(elementValue);
   }


function setcookie(c_name, value, expireWholeDays, expireSeconds) {
    var exdate;
    if ((expireWholeDays != null) || (expireSeconds !== null)) {
        exdate = new Date();
        if (expireWholeDays != null) exdate.setDate(exdate.getDate() + expireWholeDays);
        if (expireSeconds != null) exdate.setSeconds(exdate.getSeconds() + expireSeconds);
    }
	document.cookie = c_name + "=" + escape(value) + ((exdate == null) ? "" : ";expires=" + exdate.toUTCString());
}




function getInnerText(node) {
    return getInnerText_recurse(node, "");
}

function getInnerText_recurse(node, result) {
    for (var i = 0; i < node.childNodes.length; i++) {
        var currChild = node.childNodes[i];
        if (currChild.nodeType == xmlnodetype.TEXT_NODE) {
            result += currChild.nodeValue;
        } else if (xmlnodetype.ELEMENT_NODE) {
            result = getInnerText_recurse(currChild, result);
        }
    }
    return result;
}


function setInnerText(node, text, opt_xmldoc) {
    while (node.firstChild) node.removeChild(node.firstChild);
    node.appendChild((isset(opt_xmldoc) ? opt_xmldoc : document).createTextNode(text));
}
