// ------------------------------------------------------------------------------------------------------------------
/*
Browser independent helper functions
(c) 2007 Rob Watkins
This code is free to use for any purpose, provided the copyright notice and credit to original author is maintained.
The code is provided AS IS. No guarantee is made on the fitness of this code for any purpose. Use at your own risk.
The documentation must be kept with this source on redistibution.
*/

// BLANK NAMESPACES FOR OPTIONAL COMPONENTS
// As you will get an undefined error trying to check for a non-existent global variable, define some global namespaces here that will be overridden
// if the files are actually included
var Form = {}; // tiny_form.js (used as an option by tiny_windowing)
var UI = {};		// Used in validation routines
//

// GLOBAL FUNCTIONS NOT IN A NAMESPACE FOR BREVITY ------------------------------
function INT(o, def) //API: Return a checked and parsed integer, suitable for calculations
{
	def = !isNaN(parseInt(def)) ? parseInt(def) : 0;
	o = parseInt(o);
	return !isNaN(o) ? o : def;
}	

function IDOF(ido, intidsonly) //API: Return the numerical id portion of an element id of form e.g. element21
{
	var res = null;
	
	if(ido != null)
	{
		var s = typeof(ido) == 'string' ? ido : ido.id;
		if(typeof(s) != 'undefined')
		{
			var re = intidsonly == false ? /([^_]+)$/ : /([\d\-]+)$/;
			return re.exec(s) ? RegExp.$1 : null;
		}
	}
	
	return res;
}	

function DEF(o) //API: Return true if o is not undefined
{
	return !(typeof(o) == 'undefined');
}	

//API: Check types in an array, and cause interpreter to fail if invalid. Probably quite slow, use for debugging only
function ASSERT_TYPES(values, types)
{
	if(!ASSERTS) return;
	
	// If types exists, it is "*, int, float". If not, or no type entry at the same place, int is assumed. Use * to allow any type
	for(var i = 0; i < values.length; i++)
	{
		var type = types && types.length < i ? types[i] : 'int';
		var val = values[i];
		var actualtype = typeof[val];
		if
		(	
			(type == 'int' && isNaN(parseInt(val))) || 
			(type == 'float' && isNaN(parseFloat(val)))
		)
		{
			alert('Parameter ' + i + ' failed ' + type + ' assertion; actual type ' + actualtype);
			___Parameter_failed_assertion___();
		}
	}
}

function META(id) //API: Return a Javascript object from a JSON string stored in value attribute of hidden (or other) input
{
	var el = Utils.get(id);
	if(el)
		return {element:el, data:eval(el.value.charAt(0) != '(' ? '(' + el.value + ')' : el.value)};
	else
		return null;
}

var DEBUG = false;
var ASSERTS = false;
var STOP_THE_MAGIC = false;
// ------------------------------------------------------------------------------

var Core = 
{
	createNamespace:function(newnsname, existingnamespace)
	{
		existingnamespace = existingnamespace ? existingnamespace : HS;
		
		if(existingnamespace && (typeof(existingnamespace[newnsname]) == 'undefined'))
			existingnamespace[newnsname] = {};
	}
};

var HS = {};
var Helmstone = HS;

var Utils =
{
	// API:Utils:Navigate to a URL
	go:function(url, args, norefresh, delay)
	{
		if(!isNaN(parseInt(delay)))
			setTimeout(function() { Utils.go(url, args, norefresh); }, parseInt(delay));
		else
		{		
			if(url)
			{
				var re = /\{\d+\}/;		
				url = re.exec(url) ? this.formatString(url, args) : url;
			}
			
			if( (!url || window.location.href == url) && !norefresh)
				window.location.reload();
			else
				window.location.href = url;			
		}
	},
	
	// API:Utils:Return a DOM element from an ID. If an element is passed in, just returns that
	get:function(ido, frameorwindow)
	{
		var doc = document;
		if(frameorwindow)
			doc = this.getDocument(frameorwindow);			
		
		if(doc)
		{
			if(typeof ido == 'string')
				return doc.getElementById && (o = doc.getElementById(ido)) ? o : null;
			else
				return ido;
		}
		else
			return null;
	},
	
	// API:Utils:Get window object from a frame
	getWindow:function(frame) 
	{
		var frame = Utils.get(frame);
		var wnd = null;
				
		if(frame)
		{
			if(frame.contentWindow)
				wnd = frame.contentWindow;
			else
				wnd = frame.window;
		}		
			
		return wnd;
	},
	
	// API:Utils:Get document object from a window or frame
	getDocument:function(frameorwindow) 
	{
		var wnd = Utils.get(frameorwindow);
				
		if(wnd && wnd.contentWindow)
			wnd = wnd.contentWindow;
			
		return (wnd ? wnd.document : document);
	},
	
	// API:Utils:Get body element, for appending elements to root
	getBody:function(frameorwindow) 
	{
		var doc = this.getDocument(frameorwindow);
				
		if(doc)
			return doc.body ? doc.body : doc.getElementsByTagName('body')[0];
		else
			return null;
	},
	
	// API:Utils:Get all elements of a given class. Optionally can start at a particular root element, or only search particular tags
	getElementsByClassName:function(classname, root, nodename)
	{
		return this.find(root, nodename, classname, '*');
	},
	
	// Based on getElementsByClassNAme by Jonathan Snook, http://www.snook.ca/jonathan, add-ons by Robert Nyman, http://www.robertnyman.com, 
	// Rewritten for added functionality by me
	// API:Utils: Generic find. Returns a list of elements searching by tag, class or ID. Note that this should be used with caution on large document trees; avoid global searches.
	find:function(root, nodename, classname, id)
	{		
		var res = [];
		var body = Utils.getBody();
		var idisre = Utils.getType(id) == 'RegExp';
		
		// If doing a global search only by ID, use Utils.get() as will be quicker
		if(id && (!root || root == body) && (!nodename || nodename == '*') && (!classname || classname == '*') && !idisre)
		{
			var o = this.get(id);
			return o ? [o] : [];
		}
			
		// Otherwise, we search the DOM for the supplied criteria
		root = root ? this.get(root) : body;
		
		if(root)
		{
			nodename = nodename ? nodename : '*';
			classname = classname ? classname : '*';
			id = id ? id : '*';
							
			// We have to check that getElementsByTagName exists as it doesn't for text nodes in FF
			var els = (nodename == '*' && root.all) ? root.all : (root.getElementsByTagName ? root.getElementsByTagName(nodename) : []);
			
			var el;
			
			for(var i = 0; i < els.length; i++)
			{
				el = els[i];
															
				if( ((id == '*') || (idisre && id.test(el.id)) || (el.id == id)) && ((classname == '*') || this.hasClass(el, classname)) )
					res.push(el);
			}
		}
		
		return res;
	}, 
		
	// API:Utils:Find a control by it's ASP.NET ID. The control should be uniquely IDed within the container element asonly returns first match
	findServerControl:function(containerido, id)
	{
		var a = this.find(containerido, null, null, new RegExp('_' + id + '$|^' + id + '$'));
		return a.length > 0 ? a[0] : null;
	},
	
	// API:Utils:Hide an element, optionally removing from document flow (as in display:none)
	hide:function(ido, removefromflow) 
	{ 
		if(removefromflow)
		{
			var o = Utils.get(ido);
			if( o && ((o.style.display != 'none') || !o.style.display)) // Doesn't seem to pick up initial style, so also run if blank so it works on first call
			{
				o.__prevDisplay__ = o.style.display;
				this.safeSetStyle(ido, 'display', 'none'); 
			}
		}
		else
			this.safeSetStyle(ido, 'visibility', 'hidden'); 
	},
	
	// API:Utils:Show an element, optionally replacing in document flow. If previously hidden, then inline or block will be automatically set
	show:function(ido, replaceinflow, inline) 
	{ 
		if(replaceinflow)
		{
			var o = Utils.get(ido);
			
			if( o && ((o.style.display == 'none') || !o.style.display)) // Doesn't seem to pick up initial style, so also run if blank so it works on first call
			{				
				var disp = o.__prevDisplay__ ? o.__prevDisplay__ : (inline ? 'inline' : 'block');

				this.safeSetStyle(ido, 'display', disp); 
			}
		}
		else			
			this.safeSetStyle(ido, 'visibility', 'visible'); 
	},
	
	// API:Utils:Either show or hide an object depending on visibility flag passed in
	showHide:function(ido, visible, changeflow, inline)
	{
		if(visible)
			this.show(ido, changeflow, inline);
		else
			this.hide(ido, changeflow, inline);
	},
	
	// API:Utils:Swap object visibility
	showToggle:function(ido, changeflow, inline)
	{	
		ido = this.get(ido);
		if(ido)
		{
			var visible = changeflow ? ido.style.display != 'none' : ido.style.visibility != 'hidden';
			this.showHide(ido, !visible, changeflow, inline);
			
			return !visible;
		}
	},
	
	// API:Utils:If the passed in object has the specified class, returns that object. Otherwise goes back up the HTML containing elements until it finds that class, and returns that element
	getNearest:function(ido, classname)
	{
		var o = this.get(ido);
		
		while(o && o.parentNode && !this.hasClass(o, classname))
			o = o.parentNode;
			
		return o;
	},
	
	// API:Utils:Returns true if child is any descendant of parent, direct or not
	isDescendant:function(child, parent)
	{
		var o = this.get(child);
		parent = this.get(parent);
		
		if(o && parent)
			for(o = o.parentNode;o;o = o.parentNode)
				if(o == parent)
					return true;
					
		return false;
	},
	
	// API:Utils:Better typeof; return actual type of an object if possible, or 'null'
	getType:function(o)
	{
		if(o != null)
		{		
			if(o.__typeName) 
				return o.__typeName;
			else
			{
				/^\s*function ([\w_]+[\w\d_]*)/.exec(o.constructor);
				return RegExp.$1;
			}
		}
		else
			return 'null';
	},
	
	// API:Utils:Add extra properties to an object. It's up to you to ensure names don't clash; if they do, this will overwrite current values
	addProperties:function(o, props)
	{
		if(o && props)
			for(var el in props)
				o[el] = props[el];
				
		return o;
	},
	
	//API:Utils:DEPRECATED: Use Utils.arrayExtend(). call f(param) on every element in array
	iterate:function(a, f, param)
	{
		if(a != null)
			for(var i = 0; i < a.length; i++)
				f(a[i], i, param);
	},
	
	// API:Utils:Set a style on an element, if it exists
	safeSetStyle:function(ido, prop, value)
	{
		(ido = this.get(ido)) && (ido.style[prop] = value);
	},

	// API:Utils:Set an arbitrary property on an element, if it exists
	safeSetProperty:function(ido, prop, value)
	{
		(ido = this.get(ido)) && (ido[prop] = value);
	},
	
	// API:Utils:Does the element have the specified class?
	hasClass:function(ido, match)
	{
		ido = this.get(ido);
		if(ido)
		{		
			var classisre = Utils.getType(match) == 'RegExp';
				
			var a = this.getClasses(ido);
			for(var i = 0; i < a.length; i++)
				if( (classisre && match.test(a[i])) || (match == a[i]) )
					return true;
					
			return false;
		}
		else
			return false;
	},
	
	// API:Utils:Add a classname to an object. This and removeClass VERY useful for changing object look and feel in Javascript
	addClass:function(ido, name)
	{
		if(ido = this.get(ido))
			if(this.inArray(name, this.getClasses(ido)) < 0)
				ido.className += " " + name;
	},

	// API:Utils:Remove classname from an object. This and addClass VERY useful for changing object look and feel in Javascript
	removeClass:function(ido, name)
	{
		var a, idx;
		if(ido = this.get(ido))
		{
			a = this.getClasses(ido);
			idx = this.inArray(name, a);
			
			if(idx >= 0)
				a.splice(idx, 1);
				
			ido.className = a.join(" ");
		}
	},
	
	// API:Utils:Flip between classes; utility function, useful for UI state changing
	flipClass:function(ido, ison, classname)
	{
		if(ison)
			this.addClass(ido, classname);
		else
			this.removeClass(ido, classname);
	},
	
	// API:Utils:Get all classes applied to an object as an array
	getClasses:function(ido)
	{
		var a = [];
		ido = this.get(ido);
		
		if(ido && ido.className)
			a = ido.className.split(/\s+/);
		
		return a;
	},
	
	// API:Utils:Roughly equivalent to using object.innerText, only standards compliant
	createElementText:function(ido, text)
	{
		if(ido = this.get(ido))
		{		
			var elt = document.createTextNode(text);
			ido.appendChild(elt);
		}
	},
	
	// API:Utils:Empty a node of all its descendants, including text
	clearChildren:function(node)
	{
		while(node.firstChild)
			node.removeChild(node.firstChild);
	},	

	//API:Utils:Empty a node of all descendants, then delete node
	deleteNode:function(node)
	{		
		if(node != null)
		{
			var p = node.parentNode ? node.parentNode : document;
			this.clearChildren(node);
				
			p.removeChild(node);
		}
	},	
	
	changeParent:function(node, parent, insertbefore)
	{
		node = Utils.get(node);
		parent = Utils.get(parent);
		insertbefore = Utils.get(insertbefore);
		
		if(node && parent)
		{	
			var n = node.cloneNode(true);
			this.deleteNode(node);
		
			insertbefore ? parent.insertBefore(n, insertbefore) : parent.appendChild(n);
			
			return n;
		}
		else
			return null;
	},

	// API:Utils:Find the absolute screen rendered position of an element. Returns an object with x and y properties. Useful for popping up dialogs / tooltips regardless of scroll state
	findPos:function(ido) 
	{
		if(ido = this.get(ido))
		{		
			var curleft = curtop = 0;
			
			if(ido.offsetParent != null) 
			{
				curleft = ido.offsetLeft;
				curtop = ido.offsetTop;
				
				while((ido = ido.offsetParent) != null)
				{
					curleft += ido.offsetLeft;
					curtop += ido.offsetTop;
				}								
			}
			
			return {x:curleft, y:curtop};
		}
		else
			return null;
	},
	
	// API:Utils:Find absolute rendered size and current visibility of an object. Returns an object with w, h, and visible properties. <b>!!!NOTE!!!</b> USE Utils.matchSize to set an element to match another one, as it accounts for box model differences
	findDim:function(ido)
	{
		// Get client dimensions
		if(ido == null)
		{
			var w = 0, h = 0;
		
			//IE
			if(!window.innerWidth)
			{
				//strict mode
				if(document.documentElement.clientWidth != 0)
					return {w:document.documentElement.clientWidth, h:document.documentElement.clientHeight};
				//quirks mode
				else
					return {w:document.body.clientWidth, h:document.body.clientHeight};
			}
			// W3C
			else
				return {w:window.innerWidth, h:window.innerHeight};
		}
		// Get element dimensions
		else	if(ido = this.get(ido))
			return {w:ido.offsetWidth, h:ido.offsetHeight, visible:((ido.style.visibility != 'hidden') && (ido.style.display != 'none')) };
		else
			return null;
	},
	
	findViewportDim:function()
	{
		// the more standards compliant browsers (mozilla/netscape/opera/IE7) use window.innerWidth and window.innerHeight 
		if (typeof window.innerWidth != 'undefined')
			return {w:window.innerWidth, h:window.innerHeight};
		
		// IE6 in standards compliant mode (i.e. with a valid doctype as the first line in the document)		
		if (typeof document.documentElement != 'undefined' && typeof document.documentElement.clientWidth != 'undefined' && document.documentElement.clientWidth != 0)
			return {w:document.documentElement.clientWidth, h:document.documentElement.clientHeight};
		
		// older versions of IE		
		else
			return {w:document.getElementsByTagName('body')[0].clientWidth, h:document.getElementsByTagName('body')[0].clientHeight};
	},
	
	// API:Utils:Left scroll value of document
	getDocumentScrollLeft:function(doc) 
	{
			doc = doc || document;
			return Math.max(doc.documentElement.scrollLeft, doc.body.scrollLeft);
	}, 

	// API:Utils:Top scroll value of document
	getDocumentScrollTop:function(doc) 
	{
			doc = doc || document;
			return Math.max(doc.documentElement.scrollTop, doc.body.scrollTop);
	},	
	
	// API:Utils:Get an object containing properties that are the current value of the styles specified in the stylenames parameter
	getStyles:function(ido, stylenames)
	{
		if(ido = this.get(ido))
		{
			var res = {};
			
			for(var i = 0; i < stylenames.length; i++)
			{
				var style = stylenames[i];
				
				if(ido.currentStyle && ido.currentStyle[style])
					res[style] = ido.currentStyle[style];
				else if (window.getComputedStyle)
				{
					var o = document.defaultView.getComputedStyle(ido, null);
					o = o ? o.getPropertyValue(style) : null;
					
					if(o)
						res[style] = o;
				}
			}
			
			return res;
		}
		else
			return null;
	},	
	
	// If exact is true, then width / height will be set; if false, and browser supports minHeight / minWidth, they will be used
	// API:Utils:Set the width and / or height of an element to match another, taking into account box model retardedness
	matchSize:function(src, dest, matchw, matchh, exact)
	{
		var a = ['padding-top', 'padding-bottom', 'padding-left', 'padding-right', 'border-top-width', 'border-bottom-width', 'border-left-width', 'border-right-width', 
							'min-height', 'min-width'];
		var dim = this.findDim(src);
		var ds = this.getStyles(dest, a);

		var hasmin = typeof(ds['min-height']) != 'undefined';
		
		var w = dim.w;
		var h = dim.h;
		
		// W3C box model compensations - we don't need to do this in IE, so it is convenient that the IE style names are different
		dest = this.get(dest);
		if(matchw)
		{
			w -= (INT(ds['padding-left']) + INT(ds['padding-right']) + INT(ds['border-left-width']) + INT(ds['border-right-width']));
			
			if(hasmin && !exact)
				dest.style.minWidth = w;
			else
				dest.style.width = w;
		}
			
		if(matchh)
		{
			h -= (INT(ds['padding-top']) + INT(ds['padding-bottom']) + INT(ds['border-top-width']) + INT(ds['border-bottom-width']));
			
			if(hasmin && !exact)
				dest.style.minHeight = h;
			else
				dest.style.height = h;
		}		
	},
	
	// API:Utils:Find cursor position from a provided Event object, as provided by Evt.get(e)
	findCursorPos:function(e)
	{
		e = Evt.get(e);
		var x, y;
		
		// Get cursor position with respect to the page.
		if(window.scrollX) 
		{
			x = e.clientX + window.scrollX;
			y = e.clientY + window.scrollY;
		}
		else if(e.pageX)
		{
			x = e.pageX;
			y = e.pageY;
		}
		else
		{
			x = e.clientX + document.documentElement.scrollLeft + document.body.scrollLeft;
			y = e.clientY + document.documentElement.scrollTop + document.body.scrollTop;
		}
		
		return {x:x, y:y};
	},
	
	// This will break if the object is moved, obviously - for example on a centred object if the page is resized
	// API:Utils:Given absolute screen coordinates, returns coordinates that are relative to the object passed in.
	screenToClient:function(ido, a_pt)
	{
		if(ido = this.get(ido))
		{
			var r_pt = this.findPos(ido);
			return {x:a_pt.x - r_pt.x, y:a_pt.y - r_pt.y};
		}
		else
			return null;
	},
	
	// API:Utils:Limit a number between two values
	limit:function(i, min, max)
	{
		return i < min ? min : (i > max ? max : i);
	},
	
	//API:Utils:Returns an object with integer size properties given a CSS style rect() string
	parseRect:function(rect)
	{
		var t, r, b, l;
		var re = /rect\(\s*([^\s,]+)[\s,]+([^\s,]+)[\s,]+([^\s,]+)[\s,]+([^\s,]+)\s*\)/;
		
		if(re.exec(rect))
		{
			t = parseFloat(RegExp.$1);
			r = parseFloat(RegExp.$2);
			b = parseFloat(RegExp.$3);
			l = parseFloat(RegExp.$4);
			
			if(!isNaN(t) && !isNaN(r) &&!isNaN(b) &&!isNaN(l))
				return { t:t, r:r, b:b, l:l };
		}
		
		return null;
	},
	
	// API:Utils:Convert a flat object into its JSON representation. Mainly for easier debugging of objects
	objectToJSON:function(o)
	{	
		if(o == null)
			return 'null';
		else if(typeof(o) == 'undefined')
			return "'undefined'";
		else
		{
			var s = "{ ";
			for(el in o)	
				s += el + ":'" + o[el] + "', ";
			
			return s == "{ " ? "{}" : s.substring(0, s.length - 2) + " }";
		}
	},
	
	// API:Utils:Use like .NET String.Format(), only with no fancy formatting commands, just variable replacement
	formatString:function(s, args)
	{
		if(args)
			for(var i = 0; i < args.length; i++)
				s = s.replace(new RegExp('\\{' + i + '\\}', 'g'), args[i]);
			
		return s;
	},
	
	// API:Utils:Useful for putting tags around a portion of a string
	wrapExpressionInString:function(s, re, head, tail)
	{
		var a;		
		if(s != '' && s != null && typeof(s) != 'undefined' && (a = re.exec(s)))
		{
			var start = RegExp.index;
			var end = start + a[0].length;
			
			return s.substring(0, start) + head + a[0] + tail + s.slice(end);
		}
		
		return s;
	},
	
	// API:Utils:Similar to formatString but replaces AJAX / SQL style @var with values from an object
	replaceParams:function(s, obj)
	{
		if(o)
			for(var el in obj)
				s = s.replace(new RegExp('@' + el, 'g'), obj[el]);
			
		return s;
	},
	
	// API:Utils:Remove whitespace from start and end of string
	trim:function(s)
	{
		//                                 Trim start           Trim end
		return s != null ? new String(s).replace(/^\s+/, '').replace(/\s+$/, '') : null;
	},
	
	// API:Utils:Add a querystring onto the end of a URL, respecting whether the URL already has one
	makeUrl:function(url, qs)
	{
		if(url != null)
			return url + (url.indexOf('?') < 0 ? '?' : '&') + qs;
		else
				return null;
	},

	// API:Utils:DEPRECATED:Use Utils.arrayExtend. Find object in an array
	inArray:function(o, a)
	{
		var i;
		
		for(i = 0; i < a.length; i++)
			if(a[i] == o)	
				return i;
				
		return -1;
	},
	
	// API:Utils:Add extra methods to an arbitrary array to increase functionality. All the extended methods work within the current array, not on a returned new array
	arrayExtend:function(a)
	{
		if(a != null)
		{
			a.find = this.ArX_find;								// Find either a key value in the array, or an object with a specified property that equals that key
			a.contains = this.ArX_contains;				// Like find(), but just boolean result for convenience
			a.update = this.ArX_update;						// Add or replace an object in the array, based on whether it exists according to a find()
			a.remove = this.ArX_remove;						// Remove an object, again based on a find()
			a.removeAt = this.ArX_removeAt;				// Remove an object at a specified index
			a.joinObjects = this.ArX_joinObjects;	// Like join(), but joins the specified property of objects in the array instead
			a.coalesce = this.ArX_coalesce;				// Remove all null elements from the array
			a.insert = this.ArX_insert;						// Insert an item at a specified index, and move everything else up
			a.append = this.ArX_append;						// Add an item to the end of the array
			a.iterate = this.ArX_iterate;					// Run a function on each element
		}
	
		return a;
	},
	
	// ARRAY EXTEND FUNCTIONS - CONTEXT OF ALL IS OWNER ARRAY
	ArX_find:function(key, property) { for(var i = 0; i < this.length; i++) if((property ? this[i][property] : this[i]) == key) return i; return -1; },
	ArX_contains:function(key, property) { return this.find(key, property) >= 0; },
	ArX_update:function(key, val, property) { var i = this.find(key, property); if(i >= 0) this[i] = val; else this[this.length] = val; return i; },
	ArX_remove:function(key, property) { this.removeAt(this.find(key, property)); },
	ArX_removeAt:function(index) 
	{ 
		if(this.length > 0)
		{
			var shift = false;
			var stop = this.length;
			
			for(var i = 0; i < stop; i++)
			{
				if(index == i)
				{
					shift = true;
					stop--;
				}
					
				this[i] = shift ? this[i + 1] : this[i];
			}
			
			this.length--;
		}
	},
	ArX_joinObjects:function(sep, field)
	{
		var _a = [];
		for(var i = 0; i < this.length; i++)
			_a[i] = this[i][field];
			
		return _a.join(sep);
	},			
	ArX_coalesce:function(sep, field) // Coalesces current array, AND return string representation.
	{
		for(var i = 0; i < this.length; i++)
			if(this[i] == null)
				this.removeAt(i--);
				
		return field ? this.joinObjects(sep, field) : this.join(sep);
	},
	ArX_insert:function(idx, o)
	{
		if(idx >= this.length)				
			this[this.length] = o;
		else
		{
			idx = idx < 0 ? 0 : idx;
			var stop = this.length;
			
			for(var i = stop; i >= idx; i--)
				this[i] = this[i - 1];
			
			this[idx] = o;
		}
	},
	ArX_append:function(o)
	{
		this[this.length] = o;
	},
	ArX_iterate:function(f, param)
	{
		for(var i = 0; i < this.length; i++)
			f(this[i], i, param);
	},
	//
				
	// API:Utils:Returns an array with the item removed, does NOT affect original array
	arrayDelete:function(a, index)
	{
		var res = [];
		for(var i = 0; i < a.length; i++)
			if(i != index)
				res[res.length] = a[i];
				
		return res;
	},
	
	// API:Utils:Generate a unique ctrlid and autoincrement the internal id counter
	id:function(prefix)
	{
		return (prefix ? prefix : '_ctrl_') + (nextID++);
	},
	
	// API:Utils:Create a div containing the provided message. It can be expanded / contracted as necessary. Each call creates a new one, unless you provide an id of a previous debug div to add messages to
	debugMessage:function(s, id, format)
	{
		var div = (typeof(id) != 'undefined' && id != null) ? Utils.get('__HUI__debug_div__' + id) : null;
		
		if(format == 'asxml')
			s = Utils.makeReadableHtml(Utils.Data.objectToXml(s));
		else if(format == 'isxml' || format == 'ishtml')
			s = Utils.makeReadableHtml(s);
		else if(format == 'raw')
			s = '<xmp>' + s + '</xmp>';

		if(div)
		{
			div.innerHTML += '<hr>' + s;
			div.__show(false);
		}
		else
		{
			var div = document.createElement('div');
			id = (typeof(id) != 'undefined' && id != null) ? id : this.nextDebugID++;
			
			var cw = Utils.findDim(null).w; // Client widths
			var offset = (this.nextDebugPos * 12) + (this.nextDebugPos * 2) + 10; this.nextDebugPos++;
			
			div.id = '__HUI__debug_div__' + id;
			div.style.position = 'absolute';
			div.style.backgroundColor = '#ffff00';
			div.style.border = '2px solid #ff0000';
			div.style.fontSize = '11px';
			div.style.left =  ((cw - (cw / 2)) - offset) + 'px'; 
			div.style.top = '10px';
			div.style.padding = '10px';
			div.style.overflow = 'hidden';
			div.style.width = '50%';
			div.innerHTML = s;
			div.__collapsed = false;
			div.__show = function(collapse) 
				{ 
					this.style.width = collapse ? '16px' : 'auto'; 
					this.style.height = collapse ? '16px' : 'auto';
					FX.setOpacity(this, collapse ? 50 : 100);
				};

			Evt.trackMouse(div, {click:function(e, id) {var o = Utils.get(id); o.__collapsed = !o.__collapsed; o.__show(o.__collapsed); }});
			
			Utils.getBody().appendChild(div);
		}
		
		return id;
	},
			
	//API:Utils:Escape HTML tag / entity characters so it can be displayed and read as HTML
	makeReadableHtml:function(s) { return this.makeReadableHTML(s); },
	makeReadableHTML:function(s)
	{
		if(s == null || typeof(s) == 'undefined' || s == '')
			return '';
		else
			return new String(s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
	},
	
	getQueryString:function()
	{
		var res = {};
		var s = window.location.search;
		s = s != null && s != '' ? s.substring(1) : null;
		
		if(s)
		{
			var a = s.split(/&/);
			var re = /^([^=]+)=(.*)$/;
			for(var i = 0; i < a.length; i++)
				if(re.exec(a[i]))
					res[RegExp.$1] = RegExp.$2 == '' ? null : RegExp.$2;					
		}		

		return res;
	},
	
	nextID:0,
	nextDebugID:0,
	nextDebugPos:0
};

// ========================================================================================================================================================
// ========================================================================================================================================================
// BROWSER INDEPENDENT EVENT UTILS
// ========================================================================================================================================================
// ========================================================================================================================================================
var Evt =
{	
	// API:Evt:Get the event object. Pass in first parameter of event handler, will return actual event object in all browsers
	get:function(e)
	{
		return e ? e : window.event;
	},
	
	// API:Evt:Get event source element
	sender:function(e)
	{
		e = Evt.get(e);
		
		var res;
				
		if(e.target)	
			res = e.target;
		else if(e.srcElement)
			res = e.srcElement;
			
		if(res.nodeType == 3) // defeat Safari bug
			res = res.parentNode;
			
		return res;
	},
	
	// API:Evt:Swallow an event - i.e. stop it propagating
	swallow:function(e, allowdefault) 
	{
		e = Evt.get(e);
		
		e.cancelBubble = true;
		e.returnValue = allowdefault ? true : false;
		
		if(e.stopPropagation)
			e.stopPropagation();
	
		if(e.preventDefault && !allowdefault)
			e.preventDefault();
			
			return false;
	},
	
	// - LOADING - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
	// API:Evt:Add a new event handler to be called onLoad. It will be called LAST.
	appendLoadHandler:function(handler)
	{
		if(this.loadEvents.length == 0)
			this.registerLoadHandlers();
			
		this.loadEvents.append(handler);
	},

	// API:Evt:Add a new event handler to be called onLoad. It will be called FIRST.
	prependLoadHandler:function(handler)
	{
		if(this.loadEvents.length == 0)
			this.registerLoadHandlers();
			
		this.loadEvents.insert(0, handler);
	},

	// API:Evt:Insert an event handler into the specified position in the load queue
	insertLoadHandler:function(idx, handler)
	{
		if(this.loadEvents.length == 0)
			this.registerLoadHandlers();
		
		this.loadEvents.insert(idx, handler);
	},
	
	registerLoadHandlers:function()
	{
		this.attach('load', function(e) { for(var i = 0; i < Evt.loadEvents.length; i++) Evt.loadEvents[i](e); }, window, false);		
	},
	
	// API:Evt:Add a new event handler to be called onLoad. It will be called LAST.
	onLoad:function(handler) // Shortcut
	{
		this.appendLoadHandler(handler);
	},

	// API:Evt:Add a new event handler to be called onLoad. It will be called FIRST.
	onLoadFirst:function(handler) // Shortcut
	{
		this.prependLoadHandler(handler);
	},
	
	loadEvents:Utils.arrayExtend([]),
	// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
	
	// API:Evt:DEPRECATED: use Evt.trackEvent(). Attach an event to an object - omit the "on"; so onmousedown will be "mousedown" etc.
	attach:function(eventname, handler, ido, capture)
	{
		ido = Utils.get(ido);
		if(ido)
		{
			if(ido.attachEvent) 
				ido.attachEvent('on' + eventname, handler); 
			else if(ido.addEventListener)
				ido.addEventListener(eventname, handler, capture);	
		}
	},
	
	// API:Evt:DEPRECATED: use Evt.trackEvent()
	detach:function(eventname, handler, ido, capture)
	{
		ido = Utils.get(ido);
		if(ido)
		{
			if(ido.detachEvent) 
				ido.detachEvent('on' + eventname, handler); 
			else if(ido.removeEventListener)
				ido.removeEventListener(eventname, handler, capture);	
		}
	},
	
	// Example: Evt.trackEvent('change', function(e, id, data) { alert(data + ' changed a text box'); }, 'myTextbox', {data:'John'})
	// API:Evt:Track an arbitrary event. The handler will be passed the event, ID of the object that actually registered the event, and an optional user param
	trackEvent:function(eventname, handler, ido, options)
	{
		if(Utils.getType(ido) != 'Array')
		 ido = [ido];
		 
		var a = [];
		for(var i = 0; i < ido.length; i++)
			a[a.length] = new Evt.EventTracker(eventname, handler, ido[i], options);
			
		return a.length == 1 ? a[0] : a;
	},
	
	EventTracker:function(eventname, handler, ido, options)
	{
		// -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  
		this._handler = function(e) { handler(Utils.get(e), options.id, options.data) };
		
		// -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  
		this.detach = function()
		{
			Evt.detach(this.event, this._handler, this.element, this.options.capture)
		}
		// -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  
		
		ido = Utils.get(ido);
		if(ido)
		{
			options = options ? options : {};
			options.id = ido.id;
			options.data = options.data ? options.data : {};
			options.capture = options.capture ? options.capture : false;
			
			Evt.attach(eventname, this._handler, ido, options.capture);
			
			this.options = options;
			this.event = eventname;
			this.element = ido;
		}
	},
	
	// Example: Evt.trackMouse('myButton', {click:function(e, id, pt, data) { alert(data + ' clicked at ' + pt.x + ', ' + pt.y); }, 'myTextbox', {data:'John'})
	// Use Utils.screenToClient to get relative coordinates to an object
	// API:Evt:Track mouse events. The handler will be passed the event, ID of the object that actually registered the event, a pt object with screen x and y params of the event and an optional user param
	trackMouse:function(ido, options)
	{
		var isarray = Utils.getType(ido) == 'Array';
		var a = isarray ? ido : [ido];
		var res = [];
		
		for(var i = 0; i < a.length; i++)
		{
			var oclone = {};
			Utils.addProperties(oclone, options);
			
			res[res.length] = new Evt.MouseTracker(a[i], oclone);
		}
			
		return isarray ? res : res[0];
	},
	
	MouseTracker:function(ido, options)
	{
		// -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  
		this._move = function(e) { options.mouseMove(Evt.get(e), options.id, Utils.findCursorPos(e), options.data) };
		this._over = function(e) { options.mouseOver(Evt.get(e), options.id, Utils.findCursorPos(e), options.data) };
		this._out = function(e) { options.mouseOut(Evt.get(e), options.id, Utils.findCursorPos(e), options.data) };
		this._down = function(e) { options.mouseDown(Evt.get(e), options.id, Utils.findCursorPos(e), options.data) };
		this._up = function(e) { options.mouseUp(Evt.get(e), options.id, Utils.findCursorPos(e), options.data) };
		this._click = function(e) { options.click(Evt.get(e), options.id, Utils.findCursorPos(e), options.data) };
		this._dblclick = function(e) { options.dblClick(Evt.get(e), options.id, Utils.findCursorPos(e), options.data) };
				
		// -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  
		this.detach = function(events)
		{			
			events = events ? events : this.events;// If none provided, detach all
			
			for(var i = 0; i < events.length; i++)
			{
				var evt = events[i].toLowerCase();
								
				Evt.detach(evt, this.handlers[evt].handler, this.element, this.handlers[evt].capture);
			}
		};
		// -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  
		ido = Utils.get(ido);
		if(ido)
		{			
			options = options ? options : {};
			options.id = ido.id;
			options.data = options.data ? options.data : {};
			
			this.handlers ={	mousemove: { handler:this._move,     capture: typeof(options.captureMove) == 'undefined' ? true : options.captureMove },
												mouseover: { handler:this._over,     capture: typeof(options.captureOver) == 'undefined' ? true : options.captureOver },
												mouseout:  { handler:this._out,      capture: typeof(options.captureOut) == 'undefined' ? true : options.captureOut },
												mousedown: { handler:this._down,     capture: options.captureDown },
												mouseup:   { handler:this._up,       capture: options.captureUp },
												click:     { handler:this._click,    capture: options.captureClick },
												dblclick:  { handler:this._dblclick, capture: options.captureDblClick } };
																										
			this.events = [];
			for(var el in options)
			{
				var evt = el.toLowerCase();
								
				if(this.handlers[evt])
				{
					Evt.attach(evt, this.handlers[evt].handler, ido, this.handlers[evt].capture);
					this.events[this.events.length] = evt;
				}
			}
			
			this.options = options;
			this.element = ido;
		}
	}
};

// ========================================================================================================================================================
// ========================================================================================================================================================
// BROWSER HACKS
// ========================================================================================================================================================
// ========================================================================================================================================================
var Hack = 
{
	fixIE6Styles:function(stylepath)
	{
		var re = /MSIE 6\.0/;
		if(re.test(navigator.userAgent))
			OnDemand.loadStyles(stylepath ? stylepath : 'ie6hacks.css');
	}
};

// ========================================================================================================================================================
// ========================================================================================================================================================
// DEMAND LOAD
// ========================================================================================================================================================
// ========================================================================================================================================================
var OnDemand =
{
	loadStyles:function(path)
	{
		document.write('<link rel="stylesheet" type="text/css" href="' + path + '">');
	}
};

// ========================================================================================================================================================
// INIT code
// ========================================================================================================================================================
// DEPRECATED: Use Utils.debugMessage, which will create a message DIV for you.
var DEBUG = 
{
	write:function(s)
	{
		var o;
		if(o = Utils.get('_DEBUG_'))
			o.innerHTML += s + '<br>';
	}
};

// API:QS[<fieldname>] get querystring param
// function QS
var QS = Utils.getQueryString();

