/*****************************************************************************************
 *
 *	Areeba Javascript Library
 *	Author: Eric Orton
 *
 ****************************************************************************************/
// $$Revision: 1 $

/**
 * This provides us with a namespace.  Everything in this file is under arb.XXX
 *  so as not to interfere with the operation of any other included scripts.
 */
var arb = { };


arb.prefs = {
	promptText: true,
	focusHighlighting: true,
	popupHelp: false,
	tableStriping: true
};

arb.init = function(options) {
	options = $.extend(arb.prefs, options);
	arb.element.addClass(document.body.parentNode, "js");
	if (options.promptText) { arb.form.initPromptText(); }	
	if (options.focusHighlighting) { arb.form.initFocusHighlighting(); }
	if (options.popupHelp) { arb.form.initPopupHelp(); }
	if (options.tableStriping) { arb.functions.stripeTables(); }
};

/**
 * Javascript language extensions for inheritance etc.
 */
arb.lang = {
	/**
	 * Implements inheritance from a given base class.  Adds a 'superClass' member
	 * to the subclass which points to the base class's 'prototype' member.  This allows
	 * calling methods in the base class via "SubClassName.superClass.method.call(this, arg1, arg2);".
	 * @param {Object} subClass
	 * @param {Object} baseClass
	 * @return {Object}
	 */
	inherit: function(subClass, baseClass) {
		// create an instance of the base class with an empty constructor (the constructor
		// might have side effects.
		function inheritance() {}
		inheritance.prototype = baseClass.prototype;
	
		// attach the instance of the base class to our sub class's 'prototype' member,
		// this is the js lookup chain for members not implemented in an instance of an 
		// object
		subClass.prototype = new inheritance();
		subClass.prototype.constructor = subClass;
		subClass.superClass = baseClass.prototype;
		subClass.superClass.constructor = baseClass;
	},
	
	/**
	 * Add the methods and properties from the augmenter to the augmentee.
	 * 
	 * @param {Object} augmenter
	 * @param {Object} augmentee
	 */
	augment: function(augmentee, augmenter){
		for (p in augmenter.prototype) {
			if (!augmentee.prototype[p]) {
				augmentee.prototype[p] = augmenter.prototype[p];
			}
		}
	}
};

arb.event = {
	/**
	 * 	Add the given event handler, preserving existing event handler functions.   The event handler
	 *	is passed the event object as it's only argument, and within the event handler "this" refers to the element 
	 *	being acted on.
	 *
	 *	@param	{DOM Node} elem, the element to add the event handler to
	 *	@param	{String} eventName, the name of the event, eg/ "load", "mouseover"
	 *	@param	{function} func, the function to call when the event is fired.
	 *	@author	Eric Orton &lt;eric.orton@areeba.com.au&gt;
	 */
	addHandler: function (elem, eventName, func) {
		// make a function which lets 'this' be used in our handlers and fixes e to point to an event
		var handlerFunc = function(e) {
			e = e ? e : window.event;
			elem.__f = func;
			var s = elem.__f(e);
			elem.__f = null;
			return s;
		};
		
		var currentHandler = elem['on' + eventName];
		if (typeof(currentHandler) == 'function') { // not first handler
			elem['on' + eventName] = function(e) {
				var x = currentHandler(e);
				var y = handlerFunc(e);
				return x && y;
			};
		} else { // first handler
			elem['on' + eventName] = handlerFunc;
		}
	 }
};


arb.event.standardHandler = {

	/**
	 * 	Adds a classname on mouseover and removes it on mouseout.
	 *
	 *	@param	{DOM Node} elem, the element to add the event handlers to
	 *	@param	{String} optional hoverClass, the class to apply when hovering, defaults to "arb-hover"
	 *	@author	Eric Orton &lt;eric.orton@areeba.com.au&gt;
	 */
	hover: function(element, hoverClass) {
		hoverClass = (hoverClass != null) ? hoverClass : 'arb-hover'; 
		arb.event.addHandler(element, 'mouseover', function() { arb.element.addClass(this, 'arb-hover'); } );
		arb.event.addHandler(element, 'mouseout', function() { arb.element.removeClass(this, 'arb-hover'); } );
	},

	/**
	 * 	add event handlers for mouseover and mouseout which are only called when the event applies to the given 
	 *	DOM node, and not when the target is a contained node.
	 *
	 *	@param	{DOM Node} element, the element to add the event handlers to
	 *	@param	{Function} f, the mouseover event handler
	 *	@param	{Function} g, the mouseout event handler
	 *	@author	Eric Orton &lt;eric.orton@areeba.com.au&gt;
	 */
	hoverFns: function(element, f, g) {
		// A private function for handling mouse 'hovering'
		function handleHover(e) {
			// Check if mouse(over|out) are still within the same parent element
			var p = (e.type == "mouseover" ? e.fromElement : e.toElement) || e.relatedTarget;
	
			// Traverse up the tree
			while ( p && p != element ) p = p.parentNode;
			
			// If we actually just moused on to a sub-element, ignore it
			if ( p == element ) return false;
			
			// Execute the right function
			element.__g = (e.type == "mouseover" ? f : g);
			var s = element.__g(e);
			element.__g = null;
			return s;
		}
		
		// Bind the function to the two event listeners
		arb.event.addHandler(element, 'mouseover', handleHover);
		arb.event.addHandler(element, 'mouseout', handleHover);
	}
};


arb.element = {
	/**
	 * 	Add a class to a DOM node.
	 *
	 *	@param	{DOM Node} theNode, the element to add the class to
	 *	@param	{String} theClass, the class name to add
	 *	@author	Eric Orton &lt;eric.orton@areeba.com.au&gt;
	 */
	addClass: function(theNode, theClass) {
		if (theNode.className == '') {
			theNode.className = theClass;
		} else {
			theNode.className += ' ' + theClass;
		}
	},
	
	/**
	 * 	Removes the specified class from the given element
	 *
	 *	@param	{DOM Node} theNode, the element to remove the class from
	 *	@param	{String} theClass, the class name to remove
	 *	@author	Eric Orton &lt;eric.orton@areeba.com.au&gt;
	 */
	removeClass: function(theNode, theClass) {
		var oldClass = theNode.className;
		var regExp = new RegExp('\\s?\\b'+theClass+'\\b');
		if (oldClass.match(regExp) != null) {
			theNode.className = oldClass.replace(regExp,'');
		}
	},
	
	/**
	 * 	Checks whether the DOM node has a specified class
	 *
	 *	@param	{DOM Node} elem, the element to check
	 *	@param	{String} theClass, the class name to check for
	 *	@type	Boolean, true if it has the class
	 *	@author	Eric Orton &lt;eric.orton@areeba.com.au&gt;
	 */
	hasClass: function(elem, theClass) {
		var regExp = new RegExp('\\b'+theClass+'\\b');
		return (elem.className.match(regExp) != null);
	},
	
	/**
	 * 	Read the value of the style for the given element.
	 *
	 *	@param	{DOM Node} oNode, the element to query
	 *	@param	{String} sProperty, the css property to retrieve the value for
	 *	@type	(String), the property value, or null if it can't be retrieved
	 *	@author	Eric Orton &lt;eric.orton@areeba.com.au&gt;
	 */
	getCSSProperty: function(oNode, sProperty) {
		if(document.defaultView) {
			return document.defaultView.getComputedStyle(oNode, null).getPropertyValue(sProperty);
		} else if(oNode.currentStyle) {
			for(var reExp = /-([a-z])/; reExp.test(sProperty); sProperty = sProperty.replace(reExp, RegExp.$1.toUpperCase())) {
				;
			}
			return oNode.currentStyle[sProperty];
		}
		else {
			return null;
		}
	},
	
	/**
	 * 	Move a DOM nodes children to be children of another node
	 *
	 *	@param	{DOM Node} fromElem, the DOM node to move the children from
	 *	@param	{DOM Node} fromElem, the DOM node to move the children to
	 *	@author	Eric Orton &lt;eric.orton@areeba.com.au&gt;
	 */
	moveChildren: function(fromElem, toElem) {
		var nextChild;
		for(var child = fromElem.childNodes[0]; child != null; child = nextChild) {
			var nextChild = child.nextSibling;
			fromElem.removeChild(child);
			toElem.appendChild(child);
		}
	}
};

arb.element.position = {
	/**
	 * 	Return the absolute distance of the left of the element to the left of the document
	 *
	 *	@param	{DOM Node} el, the DOM node to get the position of
	 *	@type	(Number), the distance in pixels
	 *	@author	Eric Orton &lt;eric.orton@areeba.com.au&gt;
	 */
	left: function(el){
		var l=el.offsetLeft;
		while((el=el.parentNode) && el!=document)
			l+=el.offsetLeft;
		return l;
	},
	
	/**
	 * 	Return the absolute distance of the top of the element to the top of the document
	 *
	 *	@param	{DOM Node} el, the DOM node to get the position of
	 *	@type	(Number), the distance in pixels
	 *	@author	Eric Orton &lt;eric.orton@areeba.com.au&gt;
	 */
	top: function(el){
		var t=el.offsetTop;
		while((el=el.parentNode) && el!=document)
			t+=el.offsetTop || 0;
		return t;
	}		
};


arb.form = {
	/**
	 * 	Add event handlers to any &lt;input&gt; on the page with a 'promptValue' attribute to 
	 *  display the 'promptValue' as the 'Value' when the 'Value' is empty, and to clear the 'Value'
	 *  when the input is focus'ed.  While the &lt;input&gt; is displaying the 'promptValue', it has 
	 *  a class of 'displayingPromptValue' applied to it.
	 *
	 *	@author	Eric Orton &lt;eric.orton@areeba.com.au&gt;
	 */
	initPromptText: function() {
		/* 
		 * local function definitions 
		 */
		function _cleanupPromptTextOnSubmit(e) {
			var inputs = this.getElementsByTagName('input');
			for(var j=0; j < inputs.length; j++) {
				_clearPromptText(inputs[j]);
			}			
		}	
		function _clearPromptText(elem) {
			if($(elem).hasClass('replaced-password')) {
				var $text = $(elem);
				elem = $text.prev()[0];
				_clearPromptText(elem);
				$(elem).show();
				elem.focus();
				$text.remove();
				return;
			}
			if(elem.value == elem.getAttribute("title")) {
				elem.value = "";
				$(elem).removeClass('displayingPromptValue');
			}
		}
		function _addPromptText(elem) {
			/* only add the prompt text if this item isn't disabled */
			if((elem.getAttribute('disabled') == null || elem.getAttribute('disabled') == false) 
				&& (!elem.value || elem.value == "" || elem.value == elem.getAttribute("title"))) {
				elem.value = elem.getAttribute("title");
				$(elem).addClass('displayingPromptValue');
				if (elem.type == 'password') {
					$(elem).hide();
					var $text = $("<input type='text' />");
					$text.val(elem.value);
					$text.attr('class', $(elem).attr('class'));
					$text.addClass('replaced-password');
					$(elem).after($text);
					$text.bind('focus', _clearPromptTextHandler);
				}
			}
		}

		function _clearPromptTextHandler(e) { _clearPromptText(this); }
		function _replacePromptText(e) { _addPromptText(this); } 			

		/* 
		 * Start of function code 
		 */
		if(!document.getElementsByTagName) return false;
		var forms = document.getElementsByTagName('form');
		
		for(var i = (forms.length - 1); i > -1; i--) { 
			var inputs = forms[i].getElementsByTagName('input');
			arb.event.addHandler(forms[i], 'submit', _cleanupPromptTextOnSubmit);
			for(var j=0; j < inputs.length; j++) {
				var theInput = inputs[j];
				if(theInput.getAttribute("title")) {
					var selectedNode;
					// IE only, check if we're already focused
					if (typeof document.selection != "undefined" && document.selection != null && typeof window.opera == "undefined") {
						/* find the currently focused page element */
						selectedNode = document.selection.createRange().parentElement();
					}
					/* only insert prompt text at start if not focused in input */
					if(selectedNode != theInput) {
						_addPromptText(theInput);
					}
					/* add the focus and blur handlers to add/remove prompt text */
					arb.event.addHandler(theInput, 'focus', _clearPromptTextHandler);
					arb.event.addHandler(theInput, 'blur', _replacePromptText);
				}
			}
		}		
	},
				
	/**
	 * 	Add event handlers to any &lt;input&gt; on the page with an associated &lt;label&gt; which will
	 *	add a class of 'arbHasFocus' to both items when the input is focused.
	 *
	 *	@author	Eric Orton &lt;eric.orton@areeba.com.au&gt;
	 */
	initFocusHighlighting: function() {
		/* 
		 * local function definitions 
		 */
		function _focus(e) {
			if(arb.element.hasClass(this, 'arbHasFocus')) return true;
			arb.element.addClass(this, 'arbHasFocus');
			for(var i = 0; i < this._labels.length; i++) {
				arb.element.addClass(this._labels[i], 'arbHasFocus');
			}
			if($(this).parents(".form-item").length > 0) {
				$(this).parents(".form-item").addClass('focussed-form-item');	
			}
		}
		
		function _blur(e) {
			arb.element.removeClass(this, 'arbHasFocus');
			for(var i = 0; i < this._labels.length; i++) {
				arb.element.removeClass(this._labels[i], 'arbHasFocus');			
			}
			if ($(this).parents(".form-item").length > 0) {
			    $(this).parents(".form-item").removeClass('focussed-form-item');	
			}
		}

		/* 
		 * Start of function code 
		 */
		
		var labels = document.getElementsByTagName('label');
		
		for(var i = (labels.length -1); i > -1; i--) {
			var inputID = labels[i].getAttribute('for') ? labels[i].getAttribute('for') : labels[i].getAttribute('htmlFor');
			if(typeof(inputID) != 'undefined' && inputID) {
				var input = document.getElementById(inputID);
				if(input) {
					if(!input._labels) input._labels = new Array();
					input._labels.push(labels[i]);
					$(input).bind( 'focus', _focus);
					$(input).bind( 'blur', _blur);
				}
			}
		}
	}
};

arb.debug = {
	/**
	 * 	Print passed messages to an onscreen box.
	 *
	 *	@param	{String | Object}, the message to print, or an object to dump
	 *	@author	Eric Orton &lt;eric.orton@areeba.com.au&gt;
	 */
	debugCounter: 0,
	
	log: function(strMessage) {
		var debugBox = document.getElementById('debugBox');
		var msgP = document.createElement('DIV');

		if (typeof(strMessage) == 'object') {
			var object = strMessage;
			strMessage = object + '';
			for (var key in object) {
				strMessage += "<br />" + key + ' => ' + object[key];
			}
		}
		
		msgP.innerHTML = arb.debug.debugCounter + ': ' + strMessage;
		arb.debug.debugCounter++;
		
		if(!debugBox) {
			debugBox = document.createElement('DIV');
			debugBox.style.position = 'absolute';
			debugBox.style.zIndex = '3000';
			debugBox.style.width = '280px';
			debugBox.style.height = '280px';
			debugBox.style.top = '0';
			debugBox.style.right = '0';
			debugBox.style.backgroundColor = "#dddddd";
			debugBox.style.overflow = 'auto';
			debugBox.onclick = function(){this.style.display = 'none';};
			debugBox.id = 'debugBox';
			document.body.appendChild(debugBox);
		}
		debugBox.style.display = 'block';
		debugBox.insertBefore(msgP, debugBox.firstChild);
	}
};

arb.url = {
	/**
	 * 	Test if the two URL's refer to the same resource.
	 *
	 *	@param	{String} left, the first URL
	 *	@param	{String} right, the second URL
	 *	@author	Eric Orton &lt;eric.orton@areeba.com.au&gt;
	 */
	equal: function(left, right) {
		// normalise our url's
		left = arb.url.normalise(left);
		right = arb.url.normalise(right);

		// compare them, simple when they're normalised
		return left == right;
	},
	
	/**
	 * 	Return a normalised URL.  The URL includes no query
	 *	string or page anchor and has the default.aspx or index.htm(l) stripped off.
	 *
	 *	@param	{String} url, the URL to normalise
	 *	@type	(String), the normalised URL
	 *	@author	Eric Orton &lt;eric.orton@areeba.com.au&gt;
	 */
	normalise: function(url) {
		// Lowercase our URL.  Very, very few web servers out there are case sensitive
		url = url.toLowerCase();
		
		// Chop off any query string
		url = url.replace(/^(.*)\?.*$/, "$1");

		// Chop off any page anchor
		url = url.replace(/^(.*)#.*$/, "$1");

		// Chop off any "default.aspx etc."
		url = url.replace(/^(.*)\/(default|index)\.(aspx|htm(l?))$/i, "$1");

		// Chop off any "/"
		url = url.replace(/^(.*)\/$/i, "$1");

		return url;
	},
	
	/**
	 * 	Return the parent page of the given page in the form of a normalised URL.
	 *
	 *	@param	{String} url, the URL to normalise
	 *	@type	(String), the normalised URL of the parent page
	 *	@author	Eric Orton &lt;eric.orton@areeba.com.au&gt;
	 */
	parentPage: function(url) {
			// normalise the url (no default.aspx etc.)
			url = arb.url.normalise(url);
			
			// If the path ends in .aspx or .html or .htm, chop the filename to
			// try matching our parent
			if (url.match(/^.*\/.*?\.(aspx|htm(l?))$/i)) 
			{
				return url.replace(/^(.*)\/.*?$/i, "$1");
			}
			// Else, if it's a directory reference, chop off a path component and return that
			else if (url.match(/^http(s)?:\/\/.+\/.*$/)) 
			{
				return url.replace(/^(http(s)?:\/\/.+)\/.*?$/i, "$1");
			}
			
			// else, we're outta luck
			return "";
	},
	
	/**
	 * 	Get the value of a given query string parameter
	 *
	 *	@param	{String} variable, the name of the parameter to retrieve
	 *	@type	(String), parameter value
	 *	@author	Eric Orton &lt;eric.orton@areeba.com.au&gt;
	 */
	getParam: function(variable) {
		var query = window.location.search.substring(1);
		var vars = query.split("&");
		for (var i = 0; i < vars.length; i++) {
			var pair = vars[i].split("=");
			if (pair[0] == variable) {
				return pair[1];
			}
		} 
		return null;
	}
};

arb.text = {
	
	/**
	 * 	This function is to percent encoding a string for URIs
	 *
	 *	@alias 	arb.text.percentEncode
	 *	@param	{String} string to encode
	 */
	percentEncode: function( textString ) {
		
		var haut = 0;
		var n = 0;
		CPstring = '';
		for (var i = 0; i < textString.length; i++) {
			var b = textString.charCodeAt(i); 
			if (b < 0 || b > 0xFFFF) {
				CPstring += 'Error ' + arb.text.dec2hex(b) + '!';
			}
			if (haut != 0) {
				if (0xDC00 <= b && b <= 0xDFFF) {
					CPstring += arb.text.dec2hex(0x10000 + ((haut - 0xD800) << 10) + (b - 0xDC00)) + ' ';
					haut = 0;
					continue;
				} else {
					CPstring += '!erreur ' + arb.text.dec2hex(haut) + '!';
					haut = 0;
				}
			}
			if (0xD800 <= b && b <= 0xDBFF) {
				haut = b;
			} else {
				CPstring += arb.text.dec2hex(b) + ' ';
			}
		}
		textString = CPstring.substring(0, CPstring.length-1);
		
		// textstring: sequence of Unicode code points, derived from convertChar2CP()
		var outputString = "";
		// remove initial spaces
		textString = textString.replace(/^\s+/, '');
		if (textString.length == 0) { return ""; }
		// make all multiple spaces a single space
		textString = textString.replace(/\s+/g, ' ');
		var listArray = textString.split(' ');
		// process each codepoint
		for ( var i = 0; i < listArray.length; i++ ) {
			var n = parseInt(listArray[i], 16);
			if (n == 0x20) { outputString += '%20'; }
			else if (n >= 0x41 && n <= 0x5A) { outputString += String.fromCharCode(n); } // alpha
			else if (n >= 0x61 && n <= 0x7A) { outputString += String.fromCharCode(n); } // alpha
			else if (n >= 0x30 && n <= 0x39) { outputString += String.fromCharCode(n); } // digits
			else if (n == 0x2D || n == 0x2E || n == 0x5F || n == 0x7E) { outputString += String.fromCharCode(n); } // - . _ ~
			else if (n <= 0x7F) { outputString += '%'+arb.text.dec2hex2(n); }
			else if (n <= 0x7FF) { outputString += '%'+arb.text.dec2hex2(0xC0 | ((n>>6) & 0x1F)) + '%' + arb.text.dec2hex2(0x80 | (n & 0x3F)); } 
			else if (n <= 0xFFFF) { outputString += '%'+arb.text.dec2hex2(0xE0 | ((n>>12) & 0x0F)) + '%' + arb.text.dec2hex2(0x80 | ((n>>6) & 0x3F)) + '%' + arb.text.dec2hex2(0x80 | (n & 0x3F)); } 
			else if (n <= 0x10FFFF) {outputString += '%'+arb.text.dec2hex2(0xF0 | ((n>>18) & 0x07)) + '%' + arb.text.dec2hex2(0x80 | ((n>>12) & 0x3F)) + '%' + arb.text.dec2hex2(0x80 | ((n>>6) & 0x3F)) + '%' + arb.text.dec2hex2(0x80 | (n & 0x3F)); } 
			else { outputString += '!Error ' + arb.text.dec2hex(n) +'!'; }
		}
		return( outputString );
	},
	
	/**
	 * 	This function is to change a character to a two character hexidecimal
	 *
	 *	@alias 	arb.text.dec2hex2
	 *	@param	{String} string to encode
	 */
	dec2hex2: function( textString ) {
		var hexequiv = new Array ("0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F");
		return hexequiv[(textString >> 4) & 0xF] + hexequiv[textString & 0xF];
	},
	
	/**
	 * 	This function is to change a character to a one character hexidecimal
	 *
	 *	@alias 	arb.text.dec2hex
	 *	@param	{String} string to encode
	 */
	dec2hex: function ( textString ) {
		return (textString+0).toString(16).toUpperCase();
	}
	
};

arb.functions = {
	/**
	 * 	Traverses a ul/li/a menu structure under an element with the passed id
	 *	and determines which item is the current page.
	 *	Marks the current page's item with a class of 'active', and all it's 
	 *	parents with a class of 'expanded'.
	 *
	 *	@param	{String} id, id of the element containing the navigation menu
	 *	@author	Eric Orton &lt;eric.orton@areeba.com.au&gt;
	 */
	expandMenu: function(id) {
alert('1');
		if(!document.getElementById) { 
			return; 
		}
		
		var menu = document.getElementById(id);
		if(!menu) { 
			return;
		}
		
		var menuItems = menu.getElementsByTagName("a");
		var activeMenuItem;
		var searchLocation = document.location.href;
		
		// set the class of "menus-expanded" on a new child of the top level menu
		// containing all the previous children.
		var innerDiv = document.createElement('div');
		arb.element.addClass(innerDiv, 'menus-expanded');	
		arb.element.moveChildren(menu, innerDiv);
		menu.appendChild(innerDiv);
		
		// find the currently active menu item.  If the page exists in more than one place
		// in the navigation heirarchy, this will only find one.  Which one depends on the
		// order of links returned by getElementsByTagName("a")
		do {
			for (var i=0; i<menuItems.length; i++){
				if (menuItems[i].href && arb.url.equal(menuItems[i].href, searchLocation)) {
					activeMenuItem = menuItems[i];
					break;
				}
			}
		
			// If we've exited the above loop without a match, adjust our searchLocation and try again.
			if (!activeMenuItem) {
				searchLocation = arb.url.parentPage(searchLocation);
			}
		} while (!activeMenuItem && searchLocation);
alert(activeMenuItem);
		
		// deal with the active menu item
		// put a class of 'active' on the active menu item, and 'expanded' on it's parent items
		if(activeMenuItem) {
			var parent = activeMenuItem.parentNode;  // our enclosing 'li'
			arb.element.addClass(parent, 'active');
			while(parent != menu) {
				if(parent.nodeName == "LI" || parent.nodeName == "UL") {
					arb.element.addClass(parent, 'expanded');
					if (parent.id != "") {
						arb.element.addClass(parent, parent.id + "-expanded");
					}
				}
				parent = parent.parentNode;
			}
		}
alert('2');		
	},
	
	/**
	 * 	Applies "drop down" functionality to a nested &lt;ul&gt;/&lt;li&gt;/&lt;a&gt; menu list.
	 *
	 *	@param	{String} id, id of the element containing the navigation menu
	 *	@author	Eric Orton &lt;eric.orton@areeba.com.au&gt;
	 */
	dropdownMenus: function(id) {
		function _showMenu(e) {
			arb.element.addClass(this, "arb-hover");
			if (typeof(jQuery) != "undefined") {
				$("ul", this).slideDown('fast');		
			}
		}
		function _hideMenu(e) { 
			arb.element.removeClass(this, "arb-hover");
		}
		var elements = document.getElementById(id).getElementsByTagName("LI");
		for (var i=0; i<elements.length; i++) {
			arb.event.standardHandler.hoverFns(elements[i], _showMenu, _hideMenu);
			arb.event.addHandler(elements[i], "focus", _showMenu);
			arb.event.addHandler(elements[i], "blur", _hideMenu);
		}
	},
	
	/**
	 * 	Scans for every table in the page and adds a class of "arb-even" to the &lt;tr&gt; element of each even table row to allow 
	 *	styling of alternate rows.  Also add a class of "arb-hover" to the &lt;tr&gt; element currently being hovered over to allow
	 *	higlighting.
	 *
	 *	@alias 	arb.functions.stripeTables
	 *	@author	Eric Orton &lt;eric.orton@areeba.com.au&gt;
	 */
	 stripeTables: function() {
		if (typeof(jQuery) != "undefined") {
			$(document).ready(function(){
			$("#content tr").hover(function() {$(this).addClass("arb-hover");}, function() {$(this).removeClass("arb-hover");});
			$("#content tr:even").addClass("arb-even");
			});		
		}
	},
		
	/**
	 * 	Replaces selected button inputs with links that call the given callback when clicked.
	 *
	 *	@param	{String} selector, a jquery selector that returns the button inputs to be replaced.
	 *	@param	{Function} callback - optional, the function that will be executed when the link is clicked.
	 *			The function is called as "callback(link, button)" where link is the jquery link
	 *			object, and button is the jquery button object.
	 *			If no callback is specified a default callback which submits the original button
	 *			will be used.
	 *	@author	Eric Orton &lt;eric.orton@areeba.com.au&gt;
	 */
	linkButtons: function(selector, callback) {
		callback = (callback == null) ? defaultCallback : callback;
		
		$(selector).each(function() {
			var button = $(this);
			var text = button.val();
			var link = $('<a href="#" class="link-button"><span>' + text + '</span></a>');
			link.addClass(button.attr("class"));
			button.before(link).hide();
			if (!button.is(":disabled")) {
				link.click(function() { callback(link, button); return false; });
			}
			else {
				link.click(function() { return false; }).attr("href", "");
			}		
		});	
		
		function defaultCallback(link, button) {
			button.click(); 
		}
	},
	
	/**
	 *	Prevents double click on sensitive transaction form submissions 
	 *	
	 *	@param {String} form, a jquery selector for the transaction form
	 *	@param {String} submit, a jquery selector for the transaction form submit button
	 *	@param {String} link, an optional jquery selector for the transaction form linkButton
	 * 
	 */
	transactionButtons: function(form, submit, link) {
		
		$(form).submit(paymentFormSubmit);
		$(link).click(function(){ $(form).submit() });
		
		function paymentFormSubmit(event) {
			$(submit).attr('disabled', 'disabled');
			$(submit).attr('value', 'Please wait');
			
			if (link != null) {
				$(link+' span').text('Please wait');
				$(link).click(function() { return false; });
			}
			
			return true;
		}
		
	},
	
	enhanceTextareas: function () {
		$('textarea:not([wysiwyg-textarea=true])').each(function() {
			$(this)
				.wrap("<div class=\"enhanced-textarea\"></div>")
				.parent()
				.append($("<div class=\"textarea-footer\"></div>"));
			
			arb.functions.expandableTextarea($(this).parent());
			arb.functions.maxlengthTextarea($(this).parent());
		});
	},
	
	expandableTextarea: function (textareaWrapper) {
		var staticOffset = null;
		var textarea = $("textarea", textareaWrapper);
		
		$(".textarea-footer", textareaWrapper)
			.addClass("grippie")
			.mousedown(startDrag);
		
		var grippie = $(".textarea-footer", textareaWrapper)[0];
		
		$(grippie).css("margin-right", grippie.offsetWidth - textarea[0].offsetWidth + "px");

		function startDrag(e) {
			staticOffset = textarea.height() - arb.functions.mousePosition(e).y;
			textarea.css("opacity", 0.25);
			$(document).bind("mousemove", performDrag).bind("mouseup", endDrag);
			return false;
		}

		function performDrag(e) {
			textarea.height(Math.max(32, staticOffset + arb.functions.mousePosition(e).y) + "px");
			return false;
		}

		function endDrag(e) {
			$(document).unbind("mousemove", performDrag).unbind("mouseup", endDrag);
			textarea.css("opacity", 1);
		}
	},

	/**
	 * Returns an object with the x & y coordinates of the mouse relating to the given Event
	 * @param	{Event} e
	 * @return	{Object}	
	 */
	mousePosition: function (e) {
		return {x:e.clientX + document.documentElement.scrollLeft, y:e.clientY + document.documentElement.scrollTop};
	},
	
	maxlengthTextarea: function (textareaWrapper) {
		$("textarea[maxlength]", textareaWrapper)
			// set the max chars
			.each(function(){
				var maxLength  = $(this).attr('maxlength');
				var currentLength = $(this).val().length;
				var html_counter = $("<div class=\"textarea-length-counter\"><span>" + currentLength + "</span>/" + maxLength +" characters</div>");
				$(".textarea-footer", textareaWrapper).append(html_counter);
				this.relatedElement = $('span', html_counter);
			})
			// check the max chars
			.keyup(function(){
				var maxLength     = $(this).attr('maxlength');
				var currentLength = $(this).val().length;
				if(currentLength >= maxLength) {
					this.value = this.value.substring(0, maxLength - 1);
				}
				this.relatedElement.html(currentLength);
			});
	},
	
	/**
	 * 	Inserts font size up/down and print controls into page
	 *
	 *	@param	{String} selector, a jquery selector that identifies the element 
	 *	the controls should be appended to.
	 *	@author	Eric Orton &lt;eric.orton@areeba.com.au&gt;
	 */
	fontSizing: function(insertSelector, options) {
		
		var options = $.extend({
			printText: 'Print Page',
			maxFontSize: 140, // %
			minFontSize: 70, // %
			fontIncrement: 16.66, // %
			controlsFontSize: 11 // %
		}, options);
    options = $.extend({
      insertHTML: '<li id="font-size-down-control" title="Decrease font size"><span>A-</span></li><li id="font-size-up-control" title="Increase font size"><span>A+</span></li><li class="print-link" title="'+options.printText+'"><a href="#">'+options.printText+'</a></li>'
    }, options);
    
		function incrementFontSize(sizeDelta) {
			var newSize = parseFloat(arb.cookie.get("font-size")) + sizeDelta;
			if (newSize > options.minFontSize && newSize < options.maxFontSize) {
				setBodyFontSize(newSize);
				arb.cookie.set("font-size", newSize, {path: "/"});
			}
		}
		
		function setBodyFontSize(size) {
			$("#content").css("font-size", size + "%");
			$("#content #page-controls").css("font-size", options.controlsFontSize+"px"); // Do not change the page controls
			if (((size*1) - options.fontIncrement) < options.minFontSize) {
				$("#font-size-down-control").css("opacity", 0.5);
				$("#font-size-up-control").css("opacity", 1);
			}
			else if (((size*1) + options.fontIncrement) > options.maxFontSize) {
				$("#font-size-down-control").css("opacity", 1);
				$("#font-size-up-control").css("opacity", 0.5);
			}
			else {
				$("#font-size-down-control").css("opacity", 1);
				$("#font-size-up-control").css("opacity", 1);
			}
		}
		
		var fontSize = arb.cookie.get("font-size");
		
		if (!fontSize) {
			arb.cookie.set("font-size", "100", {path: "/"}); // set the default if there' no value
			fontSize = arb.cookie.get("font-size");  // read it back to make sure cookies are supported
		}
	
		// check we can support this functionality
		if (window.print && fontSize) {
			$(insertSelector).prepend(options.insertHTML);
			
			setBodyFontSize(fontSize);
			
			// attach print handler
			$(".print-link a").click(function() {
				window.print();
			});
			
			// attach font down handler
			$("#font-size-down-control").click(function() {
				incrementFontSize(-options.fontIncrement);
			});
	
			// attach font up handler
			$("#font-size-up-control").click(function() {
				incrementFontSize(options.fontIncrement);
			});
			
			// attach hover handler so we can style cursor with a hand on mouseover
			$("#page-controls li").hover(
				function() { $(this).addClass("arb-hover"); },
				function() { $(this).removeClass("arb-hover"); }
			);
		}
	}
};

arb.cookie = {
	/**
	 * 	Set a cookie
	 *
	 *	@param 	String name The name of the cookie.
	 *	@param 	String value The value of the cookie.
	 *	@param 	Hash options A set of key/value pairs for optional cookie parameters.
	 *	@option	Number|Date expires Either an integer specifying the expiration date from now on in days or a Date object.
	 *                             If a negative value is specified (e.g. a date in the past), the cookie will be deleted.
	 *                             If set to null or omitted, the cookie will be a session cookie and will not be retained
	 *                             when the the browser exits.
	 *	@option	String path The value of the path atribute of the cookie (default: path of page that created the cookie).
	 *	@option	String domain The value of the domain attribute of the cookie (default: domain of page that created the cookie).
	 * 	@option	Boolean secure If true, the secure attribute of the cookie will be set and the cookie transmission will
	 *                        require a secure protocol (like HTTPS).
	 *	@author	Eric Orton &lt;eric.orton@areeba.com.au&gt;
	 */
	set: function(name, value, options) {
        options = options || {};
        var expires = '';
		
		options.expires = options.expires || 356;
		
		var date = new Date();
		date.setTime(date.getTime()+(options.expires*24*60*60*1000));
		var expires = "; expires="+date.toGMTString();
		
		var path = options.path ? '; path=' + options.path : '';
        var domain = options.domain ? '; domain=' + options.domain : '';
        var secure = options.secure ? '; secure' : '';
        document.cookie = [name, '=', encodeURIComponent(value), expires, path, domain, secure].join('');
    },
	
	/**
	 * 	Get the value of a cookie
	 *
	 *	@param 	String name The name of the cookie.
 	 *	@return	The value of the cookie.
 	 *	@type 	String
	 *	@author	Eric Orton &lt;eric.orton@areeba.com.au&gt;
	 */
	get: function(name) {
        var cookieValue = null;
        if (document.cookie && document.cookie != '') {
            var cookies = document.cookie.split(';');
            for (var i = 0; i < cookies.length; i++) {
                var cookie = cookies[i].trim();
                // Does this cookie string begin with the name we want?
                if (cookie.substring(0, name.length + 1) == (name + '=')) {
                    cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
                    break;
                }
            }
        }
        return cookieValue;
    },
	
	/**
	 * 	Delete a cookie
	 *
	 *	@param 	String name The name of the cookie.
	 *	@author	Eric Orton &lt;eric.orton@areeba.com.au&gt;
	 */
	remove: function(name) {
		arb.cookie.set(name, "", {expires: -1});
	}
};	
	
$(document).ready(arb.init);



// ---------------------------------------------------------------------
//                      array.push (if unsupported)
// ---------------------------------------------------------------------
if(Array.prototype.push == null) {
  Array.prototype.push = function(item) {
    this[this.length] = item;
    return this.length;
  };
};


// ---------------------------------------------------------------------
//                      string.trim (our own language addition)
// ---------------------------------------------------------------------
if (String.prototype.trim == null) {
	String.prototype.trim = function() { 
		return this.replace(/^\s+|\s+$/, ''); 
	};
}


// ---------------------------------------------------------------------
// kill dotnets sucky validation rendering and replaces with our own
// ---------------------------------------------------------------------
$(function() {
	if (typeof(ValidatorUpdateDisplay) == 'function') {
		ValidatorUpdateDisplay = function(val) {
			if (typeof(val.display) == "string") {
				if (val.display == "Dynamic") {
					if (val.isvalid && $(val).is(':visible')) {
						$(val).animate({height: 'hide', opacity: 'hide'},'slow', function() {
							if (!$(val).siblings('.error-message').is(':visible')) {
								$(val).parent('.error-messages').parent('.form-item').removeClass('error-form-item');
							}
						});
					}
					else if (!val.isvalid && !$(val).is(':visible')) {
						$(val).animate({height: 'show', opacity: 'show'},'slow').parent('.error-messages').parent('.form-item').addClass('error-form-item');
					}
				}
			}
		}
	}
});

/************************************************************************************
 * @desc intercept .net asynchronous postback responses and insert the content
 *		via jQuery methods so that live-query behaviours can take effect
 ************************************************************************************/
(function() {
	$(document).ready(function() {
		// hook up .net page request event handlers
		if (typeof(Sys) != 'undefined') {
		//	Sys.WebForms.PageRequestManager.getInstance().add_pageLoading(onPageLoading);
		//	Sys.WebForms.PageRequestManager.getInstance().add_pageLoaded(onPageLoaded);
		Sys.WebForms.PageRequestManager.getInstance().add_endRequest(onEndRequest);
		}
		
		if (!$.browser.msie) {
			$('button').each(function() {
				if (typeof(this.addEventListener) == 'function') {
					this.addEventListener('click', function(e) {
						if (e.target != this) {
							// stop current event
							e.preventDefault();						
							var me = this;
							window.setTimeout(function() {
								var evt = document.createEvent("MouseEvents");
								evt.initMouseEvent("click", true, true, window,
									0, 0, 0, 0, 0, false, false, false, false, 0, null);
								var canceled = !me.dispatchEvent(evt);
							}, 0);
						}
					}, true);
				}
			});
		}
	});
	
	function onPageLoading(sender, args) {

	}
	function onPageLoaded(sender, args) {

	}
	
	function onEndRequest(sender, args) {
	    arb.functions.enhanceTextareas();
	}
})();

