function Fasthosts() {
	
	var jQ;
	var loadEvents = new Array();
	
	this.init = function() { 
		// sets up anything that needs to be predefined in the class
		try {
			jQ = jQuery.noConflict(); 
		} catch(err) {
			//alert(err);
		};
	};

	// extend JS objects
	Array.prototype.inArray = function (val) { //Checks to see if a value is in an array
		for(i = 0; i < this.length; i++) {
			if(this[i] === val) { return true; };
		};
		return false;
	};
	
	// Private Methods
	var Methods = {
		
		trim : function(str) {
			// trims any extra spaces off the beginning an end of the string
			return str.replace(/^\s+|\s+$/g, '');	
		},
		
		clean : function(str, List) {
			// removes a list of words/strings from the input string
			// bail out if no string supplied
			if(!str) { return ''; };
			
			try {
				var arWords = List.split(',');
				var i = 0;
				for(i = 0; i < arWords.length; i++) {
					if(str.match(arWords[i])) { str = str.replace(arWords[i],'').toString(); };
				};
			} catch(err) {
			};
			return str;
		},
		
		removeElement : function(e) {
			// attempts to remove a DOM element
			e = Methods.isElement(e);
			if(e == false) { return false; };
			try {
				e.parentNode.removeChild(e);					
				return true;
			} catch(err) {
				return false;
			};
		},
		
		isElement : function(el) {
			// checks if the input is a string or an object and returns the object if found
			try {
				if((typeof el) == 'string') { 
					// element is a string so try and grab the element
					if(document.getElementById(el)) {
						// element exists
						el = document.getElementById(el);
						return el;
					} else {
						// element does not exist
						return false;
					};
				} else {
					// not a string - therefore check if we can grab the ID (ie it's a DOM object)
					if(el.getAttribute('id')) {
						// yep, we can grab an id
						return el;
					} else {
						// error
						return false;
					};
				};
			} catch(err) {
				// some sort of error
				return false;
			};

		},
		
		isInteger : function(n) {
			strRegExp = /[0-9]+/g
			return(strRegExp.test(n));
		},
		
		hasChar : function(strInput, strList) {
			// returns true or false depending on if the input string contains any of the
			// charcters in the restricted list
			for(i = 0; i <= strInput.length; i++) {
				// check each character against the values
				if(strList.indexOf(strInput.charAt(i)) == -1) {
					// character not in
					return false;
				} else {
					// character is in the list
					return true
			   };
			};
		},
		
		restrictTo : function(strInput, strList) {
			// loop through the string
			for(i = 0; i <= strInput.length; i++) {
				// check each character against the values
				if(strList.indexOf(strInput.charAt(i)) == -1) {
					// character not in the list so remove it
					strInput = Methods.clean(strInput,strInput.charAt(i));
			   };
			};
			return strInput;
		},
		
		restrictFormInputs : function(strType, strChars) {
			// limits the input value of HTML Inputs with the relevant class
			// strType = the class name of the input tag(s) that are limited
			// If the user supplies a value for strChars, then those inputs
			// are limited to the strChars value. If not, and the user has
			// supplied one of the predefned CSS class names, strType is
			// limited to one of the preset value types.
			if(!strChars) {
				if(strType == 'integer') {
					strChars = '1234567890';
				} else if (strType == 'numeric') {
					strChars = '1234567890.-';
				} else {
					// bail out as we don't know what we're restricting the input values to
					return false;	
				};
			};
	
			// we have a valid set of characters that we wish to limit the input to
			// so grab all the inputs on the page
			var arInputs = document.getElementsByTagName('input');
			// loop through them
			for(i = 0; i < arInputs.length; i++) {
				// find the ones we're interested in by looking at the clasName
				if(arInputs[i].className.match(strType)) {
					// create an event listener that monitors the keystrokes of the input element
					arInputs[i].onkeydown = function(evt) {
						// strip the value of any restricted characters
						this.value = Methods.restrictTo(this.value,strChars);
						// test if the key pressed was in the invalid characters string and return 
						// oposite of the result (ie if found then return false) so that the character
						// is not displayed in the visitor's browser.
						if(hasChar(String.fromCharCode(evt.keyCode),strChars) || evt.keyCode==8 || evt.keyCode==9 || 
													   evt.keyCode==37 || evt.keyCode==39 || evt.keyCode==46 || 
													   evt.keyCode==116) {
							
							return true;
						} else {
							return false;
						};
					};
					arInputs[i].onchange = function() {
						// handles copy and paste
						var strVal = this.value;
						// check the input value and strip out any illegal characters 
						this.value = Methods.restrictTo(strVal,strChars);
						// return whether the value was legal or not
						if(Methods.hasChar(strVal,strChars)) {
							return false;
						} else {
							return true;
						};
					};
				};
			};
		},
		
		animate : function(el,type,duration,mode,opacity) {
			// hides DOM objects - if duration is supplied, the script will attempt to animate the hide

			// local variables
			var id = '';
			var validModes = 'blind,fade,puff,clip,drop,explode,fold,slide,fadeTo,default';
			
			// test if the el supplie dis valid
			if((typeof el) != 'object') { el = Methods.isElement(el); };
			if(el) {
				// get the id
				id = '#' + el.getAttribute('id');
			} else {
				// not a valid element so bail out
				return false;
			};
			
			// test is a duration was supplied
			if(!parseInt(duration)) { mode = duration; };
			if(!parseInt(opacity)) { opacity = 1; };
			
			// was a mode supplied
			mode = (mode && validModes.indexOf(mode) != -1 ? mode : 'blind');
				
			// do the hiding		
			if(duration && jQ) {
				// jQuery loaded and a duration supplied
				if(type == 'hide') {
					if(mode == 'fade') {
						jQ(id).fadeOut(duration);
					} else if(mode == 'fadeTo') {
						jQ(id).fadeTo(duration,opacity);
					} else {
						jQ(id).hide(mode, { }, duration);
					};
				} else {
					if(mode == 'fade') {
						jQ(id).fadeIn(duration);
					} else if(mode == 'fadeTo') {
						jQ(id).fadeTo(duration,opacity);
					} else {
						jQ(id).show(mode, { }, duration);
					};
				};
			} else {
				// either jQuery is not loaded or no duration was supplied
				if(type == 'hide') {
					el.style.display = 'none';
				} else {
					el.style.display = 'block';
				};
			};
		},
		
		monitorCheckbox : function(el, btn, cssOn, cssOff) {
			// monitors a checkbox, disables/enables the form button and switches the css class of the form button depending on the state of the checkbox

			// Check if the value of el and btn are strings (i.e. element ids) and grab the DOM object for then if true
			el = Methods.isElement(el);
			btn = Methods.isElement(btn);

			// check that we have an object for the checkbox (el) and button (btn) and bail out if not
			if(el == false || btn == false) { return false;	};

			// if the form element is not a checkbox bail out
			if(el.getAttribute('type') != 'checkbox') { return false; };
			
			
			var checkIt = function(el,btn,cssOn,cssOff) {
				try {
					// enables or disables the button depenant on the state of the checkbox
				
					// strip the css classes we're using off so that we don't end up repeating them
					var outputCss = Methods.clean(btn.className, cssOn + ',' + cssOff + ',' + 'disabled');
	
					// check the state of the element we're listening to
					if(el.checked) {
						// add the cssOn class to the button object
						outputCss = outputCss + ' ' + cssOn;
						// assign the output value
						btn.className = Methods.trim(outputCss);
						// set the disable/enabled state of the button
						btn.disabled = false;
					} else {
						outputCss = outputCss + ' ' + cssOff;
						btn.className = Methods.trim(outputCss);
						btn.disabled = true;
					};
				} catch(err) {
					// alert(err);
				};
			};
				
			el.cssOn = cssOn;		
			el.cssOff = cssOff;
			el.btn = btn;
			el.onclick = function() {
				checkIt(this,this.btn,this.cssOn,this.cssOff);	
			};

			checkIt(el,btn,cssOn,cssOff);
		
			return true;
		},
		
		monitorSecureCheckbox : function(el, btn, pwd, cssOn, cssOff) {
			// monitors a checkbox, disables/enables the form button and switches the css class of the form button depending on the state of the checkbox

			// Check if the value of el and btn are strings (i.e. element ids) and grab the DOM object for then if true
			el = Methods.isElement(el);
			btn = Methods.isElement(btn);
			pwd = Methods.isElement(pwd);
			
			// check that we have an object for the checkbox (el) and button (btn) and bail out if not
			if(el == false || btn == false || pwd == false) { return false;	};

			// if the form element is not a checkbox bail out
			if(el.getAttribute('type') != 'checkbox') { return false; };
			
			var secureCheck = function(el,btn,pwd,cssOn,cssOff) {
				
				// strip the css classes we're using off so that we don't end up repeating them
				var outputCss = Methods.clean(btn.className.toString(), '' + cssOn + ',' + cssOff + ',' + 'disabled');
				
				if(pwd.value.length > 0 && el.checked) {
					btn.className = Methods.trim(outputCss + ' ' + cssOn);
					btn.disabled = false;
				} else {
					btn.className = Methods.trim(outputCss + ' ' + cssOff);
					btn.disabled = true;
				};
				
			};
				
			// monitor the checkbox
			el.cssOn = cssOn;		
			el.cssOff = cssOff;
			el.btn = btn;
			el.pwd = pwd;
			el.onclick = function() {
				secureCheck(this,this.btn,this.pwd,this.cssOn,this.cssOff);	
			};
			
			// monitor the password field
			pwd.cssOn = cssOn;
			pwd.cssOff = cssOff;
			pwd.btn = btn;
			pwd.chk = el;
			pwd.onblur = function() {
				secureCheck(this.chk,this.btn,this,this.cssOn,this.cssOff);	
			};
			pwd.onkeyup = function() {
				secureCheck(this.chk,this.btn,this,this.cssOn,this.cssOff);	
			};
			
			secureCheck(el,btn,pwd,cssOn,cssOff);
		
			return true;
		},
		
		alternateRows : function(el, defaultClass, alternateClass, excludedClasses) { 
			//Alternates the table row CSS class with the TBODY of a table

			try {

				// Check that the supplied element is valid
				el = Methods.isElement(el);
				if(el == false) { return el; };
				
				// get the child nodes of the table
				var oChild = el.childNodes;
				var oRows;
				var bolState = false;

				// loop through the child nodes
				for(var i = 0; i < oChild.length; i++) {
					// loop through the table and find the TBODY
					if(oChild[i].nodeName == 'TBODY') {
						// TBODY found, grab the child nodes
						oRows = oChild[i].childNodes;
						// exit loop
						break;
					};
				};
			
				if(oRows) {
					// oRows has a value so loop through the elements
					for(var i = 0; i < oRows.length; i++) {
						// loop though oRows and look for TRs
						if(oRows[i].nodeName == 'TR') {
							// TR found, assign the appropriate class name to it
							if(bolState) {		// even row
								oRows[i].className = Methods.trim(Methods.clean(oRows[i].className, defaultClass + ',' + alternateClass + ',' + 
																				excludedClasses) + ' ' + alternateClass);
								bolState = false;
							} else {			// odd row
								oRows[i].className = Methods.trim(Methods.clean(oRows[i].className, defaultClass + ',' + alternateClass + ',' + 
																				excludedClasses) + ' ' + defaultClass) ;
								bolState = true;
							};
						};
					};
				};
			
			} catch(err) {
				//alert(err);
			};

		},
		
		floatCallout : function(e, x) {
			// e = callout id, x = offset from bottom of page where floating/scrolling 
			// should stop (prevents the floated box displaying over the top of footers etc
			
			var offsetTop = 0;
			var el = Methods.isElement(e);
			
			// bailout if callout element does not exist
			if(el == false) { return false; };
			
			var className = el.className + ' ';
			var winHeight = document.documentElement.clientHeight;
			var pageHeight = document.body.clientHeight;
			var boxHeight = el.scrollHeight;

			// bail out if the price box is longer than the window height
			if(boxHeight > winHeight - 30) { 
				className = className.replace('floating','');
				className = className.replace('docked','');
				el.className = className + ' docked';
				return false; 
			};
			
			// else carry on
			if(document.documentElement.scrollTop) {
				  // IE
				offsetTop = document.documentElement.scrollTop;
			} else if(window.pageYOffset) {
				// FF etc
				offsetTop = window.pageYOffset;
			};
			
			className = className.replace('floating','');
			className = className.replace('docked','');
			
			if(offsetTop > x) {
				className = className + ' floating';
			} else {
				className = className + ' docked' ;
			};
			
			var baseLineAdjust = pageHeight - offsetTop - boxHeight - x;
			if(baseLineAdjust < 0) {
				// price box is at the lowest point we want to show it so give is a negative position to move it up
				el.style.top = baseLineAdjust + 'px';
			} else {
				el.style.top = '';
			};
			
			el.className = className;
		},
		
		getPageDimensions : function(){
			
			var xScroll, yScroll;
			if (window.innerHeight && window.scrollMaxY) {	
				xScroll = document.body.scrollWidth;
				yScroll = window.innerHeight + window.scrollMaxY;
			} else if (document.body.scrollHeight > document.body.offsetHeight){ // all but Explorer Mac
				xScroll = document.body.scrollWidth;
				yScroll = document.body.scrollHeight;
			} else { // Explorer Mac...would also work in Explorer 6 Strict, Mozilla and Safari
				xScroll = document.body.offsetWidth;
				yScroll = document.body.offsetHeight;
			};
			
			var windowWidth, windowHeight;
			if (self.innerHeight) {	// all except Explorer
				windowWidth = self.innerWidth;
				windowHeight = self.innerHeight;
			} else if (document.documentElement && document.documentElement.clientHeight) { // Explorer 6 Strict Mode
				windowWidth = document.documentElement.clientWidth;
				windowHeight = document.documentElement.clientHeight;
			} else if (document.body) { // other Explorers
				windowWidth = document.body.clientWidth;
				windowHeight = document.body.clientHeight;
			};
			
			if(yScroll < windowHeight){
				pageHeight = windowHeight;
			} else { 
				pageHeight = yScroll;
			};
			
			if(xScroll < windowWidth){	
				pageWidth = windowWidth;
			} else {
				pageWidth = xScroll;
			};
			
			return [windowWidth, windowHeight, pageWidth, pageHeight ];
			
		},
		
		centerElement : function(el){
			
			el = Methods.isElement(el);
			if(el == false) { return false; };
			
			var windowSize = Methods.getPageDimensions();
			var window_width  = windowSize[0];
			var window_height = windowSize[1];

			var scrollY = 0;

			if(document.documentElement && document.documentElement.scrollTop){
				scrollY = document.documentElement.scrollTop;
			} else if(document.body && document.body.scrollTop){
				scrollY = document.body.scrollTop;
			} else if(window.pageYOffset){
				scrollY = window.pageYOffset;
			} else if(window.scrollY){
				scrollY = window.scrollY;
			};

			var setX = ( window_width  - el.clientWidth  ) / 2;
			var setY = ( window_height - el.clientHeight ) / 2 + scrollY;

			setX = ( setX < 0 ) ? 50 : setX;
			setY = ( setY < 0 ) ? 0 : setY;
			
			el.style.left = setX + 'px';
			el.style.top  = setY + 'px';

		},
		
		isIE6 : function() {
			return(typeof document.addEventListener != 'function' ? true : false);
		},
		
		newAjaxRequest : function() {
			if (window.XMLHttpRequest) {
				return new XMLHttpRequest();
			} else if (window.ActiveXObject) {
				return new ActiveXObject("Microsoft.XMLHTTP");
			} else {
				alert("Your browser does not support XMLHTTP!");
				return null;
			};
		},
		
		observe : function(el, type, action) {
			
			// grab the element if not already supplied
			el = Methods.isElement(el);
			//if(el == false) { return false; };
			
			var runFn = function() {			
				try {
					action();
				} catch(err) {
					alert(err);
				};
			};
			
			switch(type) {
				case 'click' :
					el.onclick = function() { runFn(); };
					break;
				case 'change' :
					el.onchange = function() { runFn(); };
					break;
				case 'keyup' :
					el.onkeyup = function() { runFn(); };
					break;
				case 'keypress' :
					el.onkeypress = function() { runFn(); };
					break;
				case 'keydown' :
					el.onkeydown = function() { runFn(); };
					break;
				default :
					alert('Error: \n The action you requested was not recognised. Valid actions include: ' + 
						  '\n click \n change \n keydown \n keypress \n keyup');
			};
			
		},
		
		reportIE : function() {
			// detects IE version from IE5 to IE8
			var agent = navigator.userAgent.toLowerCase();
			
			// are we IE?
			if(agent.indexOf('msie') == -1) { 
				// no, get outta here
				return false; 
			};
			
			// yes, which version and mode should we report?
			if(agent.indexOf('msie 5') != -1) {
				// IE6 found
				return 5;				 
			};
			if(agent.indexOf('msie 6') != -1) {
				// IE6 found
				return 6;
			};
			// is it IE8?
			if(document.documentMode) {
				// IE8 of some kind - but what mode is it in?
				var docMode = document.documentMode;
				var ieMode = 5;
				if(agent.indexOf('msie 7') != -1) { ieMode = 7; };
				if(agent.indexOf('msie 8') != -1) { ieMode = 8; };
				
				// work out which version of IE to report
				if(docMode == 8 && ieMode == 8) { 
					return 8; 
				} else 	if(docMode == 8 && ieMode == 7) { 
					return 8; 
				} else 	if(docMode == 7 && ieMode == 7) { 
					return 7; 
				} else 	if(docMode == 7 && ieMode == 8) { 
					return 7; 
				} else {
					return 5;
				};
			} else if(agent.indexOf('msie 7') != -1) {
				// IE7
				return 7;
			} else {
				return false;
			};	
		}
			
	};
	
	// Public functions
	this.hide = function(el,duration,mode,opacity,css) {
		// check if a numeric duration was supplied
		try {
			Methods.animate(el,'hide',duration,mode,opacity);
			return true;
		} catch(err) {
			alert(err);
			return false;
		};
	};
	
	this.show = function(el,duration,mode,opacity) {
		// check if a numeric duration was supplied
		try {
			Methods.animate(el,'show',duration,mode,opacity);
			return true;
		} catch(err) {
			return false;
		};
	};
	
	this.toggle = function(ctrl,el,duration,mode,opacity,cssOpen,cssClosed) {
		if(ctrl.isVisible) {
			// hide element
			Methods.animate(el,'hide',duration,mode,opacity);
			ctrl.isVisible = false;
			if(ctrl.className.match(cssOpen)) {
				ctrl.className = ctrl.className.replace(cssOpen,cssClosed);
			} else if(!ctrl.className.match(cssClosed)) {
				ctrl.className = ctrl.className + ' ' + cssClosed;
			};
		} else {
			// show element
			Methods.animate(el,'show',duration,mode,opacity);
			ctrl.isVisible = true;
			if(ctrl.className.match(cssClosed)) {
				ctrl.className = ctrl.className.replace(cssClosed,cssOpen);
			} else if(!ctrl.className.match(cssOpen)) {
				ctrl.className = ctrl.className + ' ' + cssClosed;
			};
		};
	};
	
	this.floatElement = function(id,pad) {
		window.onscroll = function() { Methods.floatCallout(id, pad); };
	};
	
	this.onLoad = function(fn) {
		loadEvents.push(fn);
	};
	
	this.pageLoaded = function() {
		for(i = 0; i < loadEvents.length; i++) {
			try {
				loadEvents[i]();
			} catch(err) {
				alert(err);
			};
		};
	};
	
	// Externalise methods...
	this.trim = Methods.trim;
	this.clean = Methods.clean;
	this.removeElement = Methods.removeElement;
	this.isInteger = Methods.isInteger;
	this.hasChar = Methods.hasChar;
	this.restrictInputs = Methods.restrictFormInputs;
	this.isElement = Methods.isElement;
	this.requireConfirmation = Methods.monitorCheckbox;
	this.requireSecureConfirmation = Methods.monitorSecureCheckbox;
	this.requireConf = Methods.monitorCheckbox;
	this.requireSecureConf = Methods.monitorSecureCheckbox;
	this.alternateRows = Methods.alternateRows;
	this.isIE6 = Methods.isIE6;
	this.ajaxRequest = Methods.newAjaxRequest;
	this.getPageSize = Methods.getPageDimensions;
	this.centerElement = Methods.centerElement;
	this.observe = Methods.observe;
	this.get = Methods.isElement;
	this.fetch = Methods.isElement;
	this.reportIE = Methods.reportIE;

};
var $fh = new Fasthosts();
var oTT;
// Load everything up when the page loads
//window.onload = function() {
//	$fh.pageLoaded();
//};