/**
 * Browser sniffing, straight from Quirksmode (http://www.quirksmode.org/js/detect.html)
 * Don't use this if you can use object detection to test what you want to do - it's 
 * much more robust and will work for browsers you've never even heard of.
 */
var BrowserDetect = {
	init: function () {
		this.browser = this.searchString(this.dataBrowser) || "An unknown browser";
		this.version = this.searchVersion(navigator.userAgent)
			|| this.searchVersion(navigator.appVersion)
			|| "an unknown version";
		this.OS = this.searchString(this.dataOS) || "an unknown OS";
	},
	searchString: function (data) {
		for (var i=0;i<data.length;i++)	{
			var dataString = data[i].string;
			var dataProp = data[i].prop;
			this.versionSearchString = data[i].versionSearch || data[i].identity;
			if (dataString) {
				if (dataString.indexOf(data[i].subString) != -1)
					return data[i].identity;
			}
			else if (dataProp)
				return data[i].identity;
		}
	},
	searchVersion: function (dataString) {
		var index = dataString.indexOf(this.versionSearchString);
		if (index == -1) return;
		return parseFloat(dataString.substring(index+this.versionSearchString.length+1));
	},
	dataBrowser: [
		{ 	string: navigator.userAgent,
			subString: "OmniWeb",
			versionSearch: "OmniWeb/",
			identity: "OmniWeb"
		},
		{
			string: navigator.vendor,
			subString: "Apple",
			identity: "Safari"
		},
		{
			prop: window.opera,
			identity: "Opera"
		},
		{
			string: navigator.vendor,
			subString: "iCab",
			identity: "iCab"
		},
		{
			string: navigator.vendor,
			subString: "KDE",
			identity: "Konqueror"
		},
		{
			string: navigator.userAgent,
			subString: "Firefox",
			identity: "Firefox"
		},
		{
			string: navigator.vendor,
			subString: "Camino",
			identity: "Camino"
		},
		{		// for newer Netscapes (6+)
			string: navigator.userAgent,
			subString: "Netscape",
			identity: "Netscape"
		},
		{
			string: navigator.userAgent,
			subString: "MSIE",
			identity: "Explorer",
			versionSearch: "MSIE"
		},
		{
			string: navigator.userAgent,
			subString: "Gecko",
			identity: "Mozilla",
			versionSearch: "rv"
		},
		{ 		// for older Netscapes (4-)
			string: navigator.userAgent,
			subString: "Mozilla",
			identity: "Netscape",
			versionSearch: "Mozilla"
		}
	],
	dataOS : [
		{
			string: navigator.platform,
			subString: "Win",
			identity: "Windows"
		},
		{
			string: navigator.platform,
			subString: "Mac",
			identity: "Mac"
		},
		{
			string: navigator.platform,
			subString: "Linux",
			identity: "Linux"
		}
	]

};
BrowserDetect.init();

/**
 * Get a reference to an object with the id
 * passed
 */
function ftnGetObject(id) {
	return document.getElementById(id);
}

/**
 * Gets the current location of the object passed,
 * as an array containing the x,y coordinates
 */
function ftnGetObjectPosition(object) {
	var x = 0;
	var y = 0;
	if (object.offsetParent) {
		x = object.offsetLeft
		y = object.offsetTop
		while (object = object.offsetParent) {
			x += object.offsetLeft
			y += object.offsetTop
		}
	}
	return [x,y];
}

function ftnGetObjectHeight(object) {
	if (BrowserDetect.browser == 'Opera' && BrowserDetect.version == 5) { 
		height = object.style.pixelHeight;
	} else {
		height = object.offsetHeight;
	}
	return height;
}

function ftnGetObjectWidth(object) {
	if (BrowserDetect.browser == 'Opera' && BrowserDetect.version == 5) {
		width = object.style.pixelWidth;
	} else {
		width = object.offsetWidth;
	}
	return width;
}

function ftnSetObjectHeight(object) {
	if (BrowserDetect.browser == 'Opera' && BrowserDetect.version == 5) { 
		object.style.pixelHeight = height;
	} else {
		object.offsetHeight = height;
	}
	return height;
}

function ftnSetObjectWidth(object) {
	if (BrowserDetect.browser == 'Opera' && BrowserDetect.version == 5) {
		object.style.pixelWidth = width;
	} else {
		object.offsetWidth = width;
	}
	return width;
}

function ftnMoveObject(object, x, y) {
	objStyle = object.style;
	if (BrowserDetect.browser == 'Opera' && BrowserDetect.version == 5) {
		objStyle.pixelTop = y;
			objStyle.pixelLeft = x;
	} else {
		objStyle.top = y + 'px';
			objStyle.left = x + 'px';
	}	
}

function ftnChangeImgSrc(strImageId,strImageSrc) {
	objImage = ftnGetObject(strImageId);
	objImage.src = strImageSrc;
}

function ftnMouseOut (objElement, objEvent) {
	if (typeof objEvent.toElement != 'undefined' && typeof objElement.contains != 'undefined') {
		return !objElement.contains(objEvent.toElement);
	} else if (typeof objEvent.relatedTarget != 'undefined' && objEvent.relatedTarget) {
		return !ftnIsChildOf(objElement, objEvent.relatedTarget);
	}
}

function ftnIsChildOf(objParent, objChild) {
	while (objChild) {
		if (objParent == objChild) {
			return true;
		}
		objChild = objChild.parentNode;
	}
	return false;
}

/*
 * Get the size of the browser window (the area in which the page is rendered, not
 * the width of the page).
 * Works in Firefox/IE6 and 7/Safari, possibly others.
 * Note that if scrollbars are displayed they are included in this width, and they
 * are different widths in different browsers and with different OS themes. Consider
 * controlling the scrollbar width with CSS where possible if this is a problem.
 */
function ftnGetBrowserRenderArea(){
    var arrDims = Array();
    if(typeof(window.innerWidth) == 'number'){
        // Everything except IE supports this
        arrDims[0] = window.innerWidth;
        arrDims[1] = window.innerHeight;
    }else if(document.documentElement && (document.documentElement.clientWidth || document.documentElement.clientHeight)){
        //IE 6+ in 'standards compliant mode'
        arrDims[0] = document.documentElement.clientWidth;
        arrDims[1] = document.documentElement.clientHeight;
    }else if(document.body && (document.body.clientWidth || document.body.clientHeight)){
        //IE 4 compatible
        arrDims[0] = document.body.clientWidth;
        arrDims[1] = document.body.clientHeight;
    }
    return arrDims;
}

/*
 * Calculate the width of the scrollbars in the current browser.
 * Ugly but effective - this creates two nested divs in the document
 * (but outside the visible area). The difference in the width of the
 * inner div when switching the scrollbar on and off on the outer div
 * is then the width of the scrollbar.
 *
 * Works in Firefox, returns 0 in IE7 and a negative in IE6!
 */
function ftnGetScrollbarWidth() {
    // Create two nested divs
    var objOuter = document.createElement('div');
    var objInner = document.createElement('div');
    objOuter.id = 'scrollbar-ruler';
    objOuter.style.position = 'absolute';
    objOuter.style.left = '-3000px';
    objOuter.style.width = '50px';
    objOuter.style.height = '50px';
    objOuter.style.overflow = 'hidden';
    objInner.style.width = '100%';
    objInner.style.height = '60px';
    objOuter.appendChild(objInner);
    document.body.appendChild(objOuter);
    
    // Get width without scrollbar
    var intWidthWithout = objInner.offsetWidth;
    
    // Add scrollbar and get width again
    objOuter.style.overflow = 'auto';
    var intWidthWith = objInner.offsetWidth;

    // Clean up
    document.body.removeChild(ftnGetObject('scrollbar-ruler'));
    
    // Return scrollbar width
    return intWidthWithout - intWidthWith;
}

/*
 * Cookie handling functions.
 * Bear in mind that some users will have security settings which prevent
 * JavaScript accessing cookies.
 */

// Note that if you want the cookie to expire in less than a day
// you can set fltDays to less than one (eg. six hours = 0.25)
function ftnStoreCookie(strName,strValue,fltDays) {  var strExpiry = '';  if (fltDays) {    var dteExpiry = new Date();    dteExpiry.setTime(dteExpiry.getTime() + (fltDays * 86400000));    strExpiry = "; expires=" + dteExpiry.toGMTString();  }  document.cookie = strName + "=" + strValue + strExpiry + "; path=/";}function ftnReadCookie(strName) {  var strName = strName + "=";  var arrCookies = document.cookie.split(';');  var strCookie;    for(var i=0; i<arrCookies.length; i++) {    strCookie = arrCookies[i];    while (strCookie.charAt(0) == ' '){        strCookie = strCookie.substring(1, strCookie.length);    }    if (strCookie.indexOf(strName) == 0) {        return strCookie.substring(strName.length, strCookie.length);    }  }    return null;}// Generate a guaranteed unique identifier// Note that this is *not* an especially good guid generator - if you// really need to be sure your ids are unique, use a decent server-side// one instead.function ftnGenerateGuid(){	var strOut, i, j;
	strOut = '';
	for(var j = 0; j < 32; j++){
		if(j == 8 || j == 12|| j == 16 || j == 20) strOut += '-';
		i = Math.floor(Math.random()*16).toString(16).toUpperCase();
		strOut += i;
	}
	return strOut;}//// VALIDATION FUNCTIONS// A collection of functions for validating input//// Check that a string is a valid email addressfunction ftnCaBIsEmail(strInput){	var rgxPattern  = /^([a-zA-Z0-9_\.\-])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$/;
	if (rgxPattern.test(strInput)){
		return true;
	} else {
		return false;
	}}// Check that a string contains a valid numberfunction ftnCaBIsNumeric(strInput) {
	var objRegEx = /(^-?\d\d*\.\d*$)|(^-?\d\d*$)|(^-?\.\d\d*$)/;
	return objRegEx.test(strInput);
}

// Check that a string is a valid integer
function ftnCaBIsInteger(strInput) {
	var objRegEx = /(^-?\d\d*$)/;
	return objRegEx.test(strInput);
}

// Check that a string contains non-whitespace characters
function ftnCaBIsNotEmpty(strInput) {
	if(ftnCaBTrim(strInput).length > 0){
		return true;
	} else {
		return false;
	}
}

// Check that a string matches a specified regular expression
function ftnCaBCheckRegex(strInput, strPattern) {
	var objRegEx = new RegExp(strPattern);
	return objRegEx.test(strInput);
}

// Trim trailing and leading whitespace from a string
function ftnCaBTrim(strInput) {
	var objRegEx = /^(\s*)([\W\w]*)(\b\s*$)/;
	if(objRegEx.test(strInput)) {
		strInput = strInput.replace(objRegEx, '$2');
	}
	return strInput;
}

// Strip the specified characters from a string
// Example:
// strStripped = ftnStripCharacters('string with spaces','\s*')
function ftnCaBStripCharacters(strInput, strPattern) {
	var objPattern =  new RegExp(strPattern, 'gi');
	return strInput.replace(objPattern,'');
}// Strip the domain from the start of the URL.// Leaves the / following the domain name to create a URL// relative to the site root.function ftnCaBStripDomainName(strInput){	return ftnCaBStripCharacters(strInput,'http[s]?://[^/]*');}// Validate a form and display any errors as a list in a div (id="errorMessage"), which// is created as a child of the objMessage containing element. Any existing child nodes// of objMessage are removed.//// This function relies on custom attributes being placed on form elements:// required="yes"	- if field is mandatory// message="???"	- where ??? is the label of the field// format="???"		- where permitted values of ??? are: email, integer, numeric or regex:zzz //						(zzz should be a regular expression to test the value against)//// For a page using these custom attributes to validate, you will need to supply a custom DTD// extending the HTML/XHTML standard you are adhering to.function ftnCaBValidateForm(objForm, objMessage){
	var blnValid = true;
	var arrGroupsDone = Array();
	var arrMessages = Array();
	
	for (i=0; i<objForm.length; i++){
		var objField = objForm.elements[i];
		var blnFieldValid = true;
		
		// Validate mandatory field
		if (objField.getAttribute('required') == 'yes'){
			if ((objField.type == 'text' || objField.type == 'textarea' || objField.type == 'password') && !ftnCaBIsNotEmpty(objField.value)){
				// No value entered
				arrMessages.push('Please enter ' + objField.getAttribute('message'));
				blnFieldValid = false;
			} else if (objField.type == 'checkbox' || objField.type == 'radio') {
				var arrGroup = eval('document.' + objForm.name + '.' + objField.name);
				if (arrGroup.length > 1){
					// Check we haven't already validated this group
					var blnSeen = false;
					for(var k=0; k<arrGroupsDone.length; k++){
						if (arrGroupsDone[k] == objField.name){
							blnSeen = true;
						}
					}
					if (blnSeen == false){
						// Note: group checking could be extended to support minimum and maximum permitted selections for checkboxes
						var blnIsChecked = false;
						for(var j=0; j<arrGroup.length; j++) {
							if(arrGroup[i].checked){
								blnIsChecked == true;
							}
						}
						if (blnIsChecked == false){
							// No checkboxes in group are checked
							if (objField.type == 'checkbox'){
								arrMessages.push('Please check at least one ' + objField.getAttribute('message'));
							} else {
								arrMessages.push('Please select a ' + objField.getAttribute('message'));
							}
							blnFieldValid = false;
						}
						// Record this group in the groups done register
						arrGroupsDone.push(objField.name);
					}
				} else {
					if (objField.checked == false){
						// Checkbox is not checked
						arrMessages.push('Please check ' + objField.getAttribute('message'));
						blnFieldValid = false;
					}
				}
			} else if (objField.type.charAt(0) == 's' && objField.selectedIndex == 0){ // Select dropdowns
				// Note that this assumes that the first element of the dropdown is not a valid selection - if
				// it is then usually it isn't possible to select an invalid option and validation the dropdown
				// client side is pointless.
				arrMessages.push('Please select a ' + objField.getAttribute('message'));
				blnFieldValid = false;
			}
		}
		
		// Check format of value
		if (blnFieldValid == true && (objField.type == 'text' || objField.type == 'textarea' || objField.type == 'password') && objField.getAttribute('format') != null){
			var strFormat = objField.getAttribute('format');
			
			switch (strFormat){
				case "email":
					if (!ftnCaBIsEmail(ftnCaBTrim(objField.value))) {
						// String is not a valid email address
						arrMessages.push('Please enter a valid email address for ' + objField.getAttribute('message'));
						blnFieldValid = false;
					}
					break;
				case "integer":
					if (!ftnCaBIsInteger(ftnCaBTrim(objField.value))) {
						// String is not a valid integer
						arrMessages.push('Please enter a valid integer for ' + objField.getAttribute('message'));
						blnFieldValid = false;
					}
					break;
				case "numeric":
					if (!ftnCaBIsNumeric(ftnCaBTrim(objField.value))) {
						// String is not a valid number
						arrMessages.push('Please enter a valid number for ' + objField.getAttribute('message'));
						blnFieldValid = false;
					}
					break;
			}
			
			if (strFormat.length > 4 && strFormat.substr(5) == 'regx:'){
				// Test value with the supplied regular expression
				if (!ftnCaBCheckRegex(objField.value, strFormat.substr(0,4))){
					// String does not match regular expression
					arrMessages.push('Please enter a valid ' + objField.getAttribute('message'));
					blnFieldValid = false;
				}
			}
		}
			
		if (blnFieldValid == false){
			blnValid = blnFieldValid;
		}
		
	}
	
	// Clear existing error content
	for(n=objMessage.childNodes.length-1; n>-1; n--){
		objMessage.removeChild(objMessage.childNodes[n]);
	}
	
	// Display any validation errors
	if (arrMessages.length > 0){
		var objErrorPanel = document.createElement('div');
		objErrorPanel.id = 'errorPanel';
		
		var objIntro = document.createElement('p');
		objIntro.appendChild(document.createTextNode('The following errors need to be corrected before proceeding:'));
		objErrorPanel.appendChild(objIntro);
		
		var objList = document.createElement('ul');				
		for(m=0; m<arrMessages.length; m++){
			var objListItem = document.createElement('li');
			objListItem.appendChild(document.createTextNode(arrMessages[m]));
			objList.appendChild(objListItem);
		}
		objErrorPanel.appendChild(objList);
		
		objMessage.appendChild(objErrorPanel);
	}

	return blnValid;
}