/**************************************************************************\

Filename: slider_control.js
Version: 2.3
Author: Ian Nielsen
Date: 05 July 2010

Script updated to calculate the slider position more accurately.

/**************************************************************************/

function sliderControls(arSliders) {
	
	/* 	arSliders format: sliderID, ranges (start:end:step), unit value, current value, units, container, temp upgrade value, stop downgrade
						
		SliderID = 					the id of the DOM element for the slider
		Ranges (start:end:step) = 	comma delimited string that contains the values for each range:
									Start = start value of the range
									End = end value of the range
									Step = Number of slider steps for each unit e.g. 1:5:1,6:10:2 - this would result in values
									1 through 5 occupying slider positions 1 through 5. The second argument would result 
									in the values from 6 to 10 incrementing in steps of 2 (i.e. 6, 8, 10)
		Unit value =				the value for each unit on the slider
		Current value =				value that the slider is preset to
		Units = 					unit of the values (eg GB, MB, KB)
		Container =					DOM element that will contain the slider
		Stop Downgrade =			Sets whether to allow slider to select values that are less than the Current Value supplied. Default is allow downgrade
		*/
						
	// Local variables
	var o;												// used to hold the current slider control object
	var allowDowngrade = false;							// if set to true, locks the sliders so you can't downgrade from the preset
	var sliderCss = 'SliderScale';						// class that identifies slider controls on the page
	var ghosts = true;									// display ghosts or not
	var indicateExisting = true;						// show the existing/current value indicator
	var indicateUpgrade = true;							// show the value indicator for any temporary upgrades
	var indicateWarning = true;							// show the value indicator for any temporary upgrades
	var ghostClass = 'SliderGhost';						// defines the css class name for the ghost
	var indicateExistingClass = 'SliderExisting';		// defines the css class name for the existsing value indicator
	var indicateUpgradeClass = 'SliderTemp';			// defines the css class name for the temp upgrade value indicator
	var indicateNewClass = 'SliderNew';					// defines the css class name for the new value indicator
	var indicateWarningClass = 'SliderWarning';			// defines the css class name for the new value indicator
	var cursorClass = 'SliderCursor';					// defines the css class name for the slider bar/cursor
	var zeroSlider = false;								// defines whether the slider starts at zero or not. 
														// false = slider start value = 1, true = slider start value = 0
	var sliderSpeed = 5;								// adjusts the speed of the cursor for the onclick event (animated cursor)
	var padding = 0;									// sets the amount of horizontal padding required - allows the user to prevent the slider 
														//occupying the full width of the scale
	var oTimer;											// generic timer variable
	var masterOffset = 0;								// allows user to supply a value that can compensate for inaccuracies in the offset calculation
														// caused by using position absolute inside poisiont relative to set the position of the scales
														// and cursors
	var tweakRight = 7;									// allows user to subtract some of the scale length to stop the cursor appearing 
														// off the RH edge of the scale
	var tweakLeft = 0;									// allows user to subtract some of the scale length to stop the cursor appearing 
														// off the LH edge of the scale
	var fnUpdate = function() {};						// blank function that we can use to reference external functions
	var fnPreset = function() {};						// blank function that we can use to reference external functions - used when supplying preset values
	var fnLoad = function() {};
	var zeroAdj = (zeroSlider ? 0 : 1);					// compensates for slider start position = 1 rather than start position = 0
	var arObjects = new Array();						// holds all the DOM objects once they have been initialised
	var animatePreset = true;							// sets whether the animate the slider movements when presets are applied
	var mode = "stepped";								// sets whether the slider runs in steps dictated by the ranges ('stepped') or
														// whether the position of the slider is proportional ('proportional') to the output value ie
														// if slider is at 50% then: 
														// if min value = 100
														// if max value = 200 
														// output value = (max value - min value) * 1.5 + min value
	var percentage = false;								// sets whether the output is as a percentage or a straight value
	var showOutputAs = 'absolute'						// Detirmines if the value posted by the form is an absolute ('absolute') value (e.g. 50GB) 
														// or as a percentage ('percentage')
	var offsetAuto = true;								// automatically calculate the offset
	var offsetRelativeTo = 'self';						// allows us to pass a different page element to calculate the offset relative to - useful 
														// when calculating an offset for an element that has a position absolute within a position
														// relative
	var defaultContainer = 'vpsForm';					// default container that the sliders are in
	var moveQuota = false;								// Whether to position the quota value next to the cursor or not
	var moveQuotaOffset = 10;							// Allows us to offset the position of the quota value
	var cursorAdjust = 0;								// Allows us to 

	var runPreset = function() {			
		// function that runs any update script we want to use
		// executes any external functions that have been passed to the class - triggered on update
		try {
			fnPreset();
		} catch(err) {
			//alert(err);
		};
	};
	
	var runUpdate = function() {			
		// function that runs any update script we want to use
		// executes any external functions that have been passed to the class - triggered on update
		try {
			fnUpdate();
		} catch(err) {
			alert(err);
		};
	};
	
	var runOnce = function() {
		// executes any external functions that have been passed to the class - triggered on load
		try {
			fnLoad();
		} catch(err) {
			//alert(err);
		};
	};
	
	this.onUpdate = function(val) {
		// loads external functions in to the external function reference variable
		fnUpdate = val;
	};
	
	this.onPreset = function(val) {
		// loads external functions in to the external function reference variable - used when supplying preset values
		fnPreset = val;
	};
	
	this.onLoad = function(val) {
		// loads external functions in to the external function reference variable
		fnLoad = val;
	};
	
	this.config = {
		setSliderClass : function(val) { sliderCss = val.toString() },
		setGhosts : function(val) { ghosts = (val == true || val == false ? val : true); },
		setGhostClass : function(val) { ghostClass = val.toString(); },
		setIndicateExisting : function(val) { indicateExisting = (val == true || val == false ? val : true); },
		setIndicateExistingClass : function(val) { indicateExistingClass = val.toString(); },
		setIndicateUpgrade : function(val) { indicateUpgrade = (val == true || val == false ? val : true); },
		setIndicateUpgradeClass : function(val) { indicateUpgradeClass = val.toString(); },
		setIndicateWarning : function(val) { indicateWarning = (val == true || val == false ? val : true); },
		setIndicateWarningClass : function(val) { indicateWarningClass = val.toString(); },
		setIndicateCurrentClass : function(val) { indicateNewClass = val.toString(); },
		setCursorClass : function(val) { cursorClass = val.toString(); },
		setTagName : function(val) { tagName = val.toString(); },
		setSpeed : function(val) { sliderSpeed = val; },
		setZero : function(val) { zeroSlider = (val == true || val == false ? val : true); },
		setPadding : function(val) { padding = val; },
		setOffset : function(val) { offsetAuto = false; masterOffset = val; },		// explicitly set the offset (default = false);
		setOffsetRelativeTo : function(val) { offsetRelativeTo = val; },			// automatically calculate the offset relative to a page element
		setAllowDowngrade : function(val) { allowDowngrade = (val == true || val == false ? val : true); },
		setAnimatePreset : function(val) { animatePreset = (val == true || val == false ? val : true); },
		setMode : function(val) { mode = val.toString(); },
		setOutputAsPercentage : function(val) { percentage = (val == true || val == false ? val : true); },
		setOutputFormat : function(val) { showOutputAs = val.toString(); },
		setContainer : function(val) { defaultContainer = val.toString(); },
		setLeftAdjust : function(val) { tweakLeft = val; },
		setCursorAdjust : function(val) { cursorAdjust = val; },
		moveQuota : function(val,offset) { 
			moveQuota = (val ? true : false);
			if(parseInt(moveQuotaOffset)) { moveQuotaOffset = offset; };
		}
	};

	var moveCursor = function(dist) {
		o.cursor.style.left = dist + 'px';
	};
	
	var Methods = {
		
		getEvent: function (e, el) {
			// gets the event that triggered an action
			if (!e) {
				if (el)
					e = el.document.parentWindow.event;
				else
					e = window.event;
			};
			if (!e.srcElement) {
				var el = e.target;
				while (el != null && el.nodeType != 1)
					el = el.parentNode;
				e.srcElement = el;
			};
			if (typeof e.offsetX == "undefined") {
				e.offsetX = e.layerX;
				e.offsetY = e.layerY;
			};
			return e;
		},

		setPosition : function(el) {
			// calulates position of the cursor
			var scaleWidth = el.scaleWidth;
			var currentNode = el.node;
			var nodeWidth = el.nodeWidth;
			var xPos = Math.round(currentNode * nodeWidth);
			if(xPos < 0) {
				xPos = 0;
			} else if(xPos > scaleWidth) {
				xPos = scaleWidth;
			};
			return xPos;
		},
		
		getNewNode : function(x) {
			
			// gets the node closest to the mouse position
			var scalePos = (x - Math.round(padding/2) - (o.offsetLeft == masterOffset ? masterOffset : o.offsetLeft + masterOffset)) -  
							o.cursorWidthAdj - tweakLeft;
							
			//alert(scalePos);							

			// fix for manage storage quota
			if(offsetRelativeTo == 'self' && o.offsetLeft != masterOffset) {
				scalePos += masterOffset;
			};
							
			var ieVersion = $fh.reportIE();
			if((ieVersion == 5 || ieVersion == 6 || ieVersion == 7) && o.offsetLeft != masterOffset) {
				scalePos = scalePos - 20;	
			} else if(ieVersion == 8) {
			} else {
				scalePos = scalePos - 15;
			};
			
			
			var intOffset = 0 - o.cursor.clientWidth;
			var oLocal = o;
			while (oLocal.offsetParent) {
				intOffset += oLocal.offsetLeft;	
				oLocal = oLocal.offsetParent;
			};
			
			scalePos = x - intOffset  - padding - cursorAdjust;
			
			
			//alert(scalePos);
			
			if(scalePos < 0) {scalePos = 0;};
			if(scalePos > o.scaleWidth) {scalePos = o.scaleWidth;};
			var newNode = Math.round(scalePos / o.nodeWidth);
			if(newNode < 0) { newNode = 0; };
			if(newNode > o.numNodes) { newNode = o.numNodes;};
			
			if(!allowDowngrade || o.blockDowngrade) {
				if(newNode < o.baseNode - o.adjustNode) {
					debug('<span class="warning">You cannot reduce this setting.</span>');
				} else {
					debug('&nbsp;');	
				};
				newNode = (newNode > o.baseNode - o.adjustNode ? newNode : o.baseNode - o.adjustNode);
			};
			
			// is the node valid?
			var isValid = Methods.isValidNode(o.range,newNode,o.unitValue);
			if(isValid != true) {
				var testPos = scalePos / o.nodeWidth;
				var rndPos = Math.round(testPos);
// Commented out so it always selects the higher option to prevent the cursor jumping between upper and lower valid nodes
//				if((testPos - rndPos) < 0) {
//					newNode = isValid[0];
//				} else {
					newNode = isValid[1];
//				};
			};
			return newNode;
		},
		
		fetchNode : function(el, val,i) {
			if(!allowDowngrade || el.blockDowngrade) {
				// check if the new val if less than the initial value
				if(el.currentValue > val) { 
					val = el.currentValue;
					debug('<span class="warning">You cannot reduce this setting.</span>');
				};
			} else {
				debug('');
			};
			// returns the node for a given input value
			var node = (Math.round(val / el.unitValue)) - zeroAdj - o.adjustNode;
			node = (node < 0 ? 0 : node);
			none = (node > el.nodeCount ? el.nodeCount : node);
			return node;
		},
		
		updateSliders : function(arValues) {
			var node = 0;
			for(i = 0; i < arSliders.length; i++) {
				o = arObjects[i];
				node = Methods.fetchNode(o,arValues[i],i);
				// check node is valid and set a default value
				var isValid = Methods.isValidNode(o.range,node,o.unitValue);
				if(isValid != true) { node = isValid[1]; };
				// in theory we now have a valid node so move the cursor
				if(animatePreset) {
					Methods.moveCursor(node);
				} else {
					Methods.presetCursor(node);
				};
			};
			
			// run any onUpdate script we may have
			runPreset();
			
		},
		
		isValidNode : function(arRanges,node,unitVal) {
			// checks whether a node returns a valid value and either returns true or the two closest valid nodes
			var arNodeValues = [];
			var arCurrent;
			var stepVal;
			var minVal;
			var maxVal;
			var x = false;
			var str = 'Values: ';
			// Loop through the value ranges
			for(n = 0; n < arRanges.length; n++) {
				arCurrent = arRanges[n].split(':');
				minVal = parseInt(arCurrent[0]);
				maxVal = parseInt(arCurrent[1]);
				stepVal = parseInt(arCurrent[2]);
				do {
					str = str + minVal.toString() + ',';
					arNodeValues.push(minVal);
					minVal = minVal + (unitVal * stepVal);
				} while(minVal <= maxVal);
			};

			var testVal = (node + zeroAdj + o.adjustNode) * unitVal;

			if(arNodeValues.indexOf(testVal) == -1) {
				// grab the next valid higher value
				var nextVal = testVal;
				var prevVal = testVal;
				x = false;
				do {
					nextVal = nextVal + unitVal;
					if(arNodeValues.indexOf(nextVal) != -1) { x = true; };
				} while(!x);
				// grab the previous valid lower value
				x = false;
				do {
					prevVal = prevVal - unitVal;
					if(arNodeValues.indexOf(prevVal) != -1) { x = true; };
				} while(!x);
				var nextNode = Math.round(nextVal / unitVal) - o.adjustNode - zeroAdj;
				var prevNode = Math.round(prevVal / unitVal) - o.adjustNode - zeroAdj;
				return [prevNode,nextNode];
			} else {
				return true;
			};
		},
		
		presetCursor : function(newNode) {
			// clear any timouts that are running
			clearTimeout(oTimer);

			var endPos = Math.round(newNode * o.nodeWidth);

			endPos = (endPos < 0 ? 0 : endPos);
			endPos = (endPos > o.scaleWidth ? o.scaleWidth : endPos);
		
			// update the object to set the new node position
			o.node = newNode;
			
			// Update the output value for this control
			var curVal = ((o.node + zeroAdj) * o.unitValue) + o.adjustValue;
			Methods.setOutputs(o, curVal, o.minValue, o.maxValue, o.unitType);
			
			o.cursor.style.left = endPos + 'px';
			o.scaleNew.style.width = (endPos + o.cursorWidthAdj) + 'px';
			if(indicateExisting) {					
				// move indicator - existing settings
				if(endPos < o.scaleExists.initWidth) {
					o.scaleExists.style.width = (endPos + o.cursorWidthAdj) + 'px';
				};
			};
			if(indicateUpgrade) {
				// move indicator - temp upgrade settings
				if(endPos < o.scaleTemp.initWidth) {
					o.scaleTemp.style.width = (endPos + o.cursorWidthAdj) + 'px';
				};
			};
			
		},

		moveCursor : function(newNode) {
			// clear any timouts that are running
			clearTimeout(oTimer);

			// get the start and end points
			var curPos = Math.round(o.node * o.nodeWidth);
			var endPos = Math.round(newNode * o.nodeWidth);

			// check that neither of the positions are greater than the scale width or less than 0
			curPos = (curPos < 0 ? 0 : curPos);
			curPos = (curPos > o.scaleWidth ? o.scaleWidth : curPos);
			endPos = (endPos < 0 ? 0 : endPos);
			endPos = (endPos > o.scaleWidth ? o.scaleWidth : endPos);
			
			// update the object to set the new node position
			o.node = newNode;
			
			// Update the output value for this control
			var curVal = ((o.node + zeroAdj) * o.unitValue) + o.adjustValue;
			o.activeValue = curVal;
			Methods.setOutputs(o, curVal, o.minValue, o.maxValue, o.unitType);		
			
			// animate the cursor
			var cursorID = o.cursor.getAttribute('id');
			var indID = o.scaleNew.getAttribute('id');
			if(curPos > endPos) {
				var dist = curPos - endPos;
				for(x = 0; x <= dist; x++){
					// move cursor
					oTimer = setTimeout('document.getElementById(\'' + cursorID + '\').style.left = \'' + 
																 (curPos-x) + 'px' + '\';',(x/(dist/sliderSpeed)) * x);
					
					// move indicator - main
					oTimer = setTimeout('document.getElementById(\'' + indID + '\').style.width = \'' + 
																 (curPos + o.cursorWidthAdj - x) + 'px' + '\';',(x/(dist/sliderSpeed)) * x);
				
					if(moveQuota) {
						var opID = o.outputTextID;
						var ouID = o.unitTextID
						var opWidth = o.outputText.clientWidth;
						o.outputText.style.position = 'relative';
						o.unitText.style.position = 'relative';
						oTimer = setTimeout('document.getElementById(\'' + opID + '\').style.left = \'' + 
																 (curPos-x+moveQuotaOffset) + 'px' + '\';',(x/(dist/sliderSpeed)) * x);
//						oTimer = setTimeout('document.getElementById(\'' + ouID + '\').style.left = \'' + 
//																 (curPos-x+moveQuotaOffset+opWidth) + 'px' + '\';',(x/(dist/sliderSpeed)) * x);
						oTimer = setTimeout('document.getElementById(\'' + ouID + '\').style.left = \'' + 
																 (curPos-x+moveQuotaOffset) + 'px' + '\';',(x/(dist/sliderSpeed)) * x);
					};
					
					if(indicateExisting) {					
						var existID = o.scaleExists.getAttribute('id');
						// move indicator - existing settings
						if(curPos - x < o.scaleExists.initWidth) {
							// only move it if the current cursor position is less than the original position of this slider
							oTimer = setTimeout('document.getElementById(\'' + existID + '\').style.width = \'' + 
																		 (curPos + o.cursorWidthAdj - x) + 'px' + 
																		 '\';',(x/(dist/sliderSpeed)) * x);
						};
					};
					
					if(indicateUpgrade) {
						var tempID = o.scaleTemp.getAttribute('id');
						// move indicator - temp upgrade settings
						if(curPos - x < o.scaleTemp.initWidth) {
							// only move it if the current cursor position is less than the original position of this slider
							oTimer = setTimeout('document.getElementById(\'' + tempID + '\').style.width = \'' + 
																		 (curPos + o.cursorWidthAdj - x) + 'px' + 
																		 '\';',(x/(dist/sliderSpeed)) * x);
						};
					};
					
				};
			} else {
				var dist = endPos - curPos;
				for(x = 0; x <= dist; x++){
					// move cursor
					oTimer = setTimeout('document.getElementById(\'' + cursorID + '\').style.left = \'' + 
																 (curPos+x) + 'px' + '\';',(x/(dist/sliderSpeed)) * x);
					// move indicator - main
					oTimer = setTimeout('document.getElementById(\'' + indID + '\').style.width = \'' + 
																 (curPos + o.cursorWidthAdj + x) + 'px' + '\';',(x/(dist/sliderSpeed)) * x);
					
					if(indicateExisting) {					
						var existID = o.scaleExists.getAttribute('id');
						// move indicator - existing settings
						if(curPos + x < o.scaleExists.initWidth) {
							// only move it if the current cursor position is less than the original position of this slider
							oTimer = setTimeout('document.getElementById(\'' + existID + '\').style.width = \'' + 
																		 (curPos + o.cursorWidthAdj + x) + 'px' + 
																		 '\';',(x/(dist/sliderSpeed)) * x);
						};
					};
					
					if(moveQuota) {
						var opID = o.outputTextID;
						var ouID = o.unitTextID
						var opWidth = o.outputText.clientWidth;
						o.outputText.style.position = 'relative';
						o.unitText.style.position = 'relative';
						oTimer = setTimeout('document.getElementById(\'' + opID + '\').style.left = \'' + 
																 (curPos+x+moveQuotaOffset) + 'px' + '\';',(x/(dist/sliderSpeed)) * x);
//						oTimer = setTimeout('document.getElementById(\'' + ouID + '\').style.left = \'' + 
//																 (curPos+x+moveQuotaOffset+opWidth) + 'px' + '\';',(x/(dist/sliderSpeed)) * x);
						oTimer = setTimeout('document.getElementById(\'' + ouID + '\').style.left = \'' + 
																 (curPos+x+moveQuotaOffset) + 'px' + '\';',(x/(dist/sliderSpeed)) * x);

					};
					
					if(indicateUpgrade) {
						var tempID = o.scaleTemp.getAttribute('id');
						// move indicator - temp upgrade settings
						if(curPos + x < o.scaleTemp.initWidth) {
							// only move it if the current cursor position is less than the original position of this slider
							oTimer = setTimeout('document.getElementById(\'' + tempID + '\').style.width = \'' + 
																		 (curPos + o.cursorWidthAdj + x) + 'px' + 
																		 '\';',(x/(dist/sliderSpeed)) * x);
						};
					};
					
				};
			};
		},
		
		onmousedown: function(e) {
			// triggers the script to start monitoring the mouse movement and mouseup events
			//if(e.preventDefault) { e.preventDefault(); };
			
			
			if (document.addEventListener) {
				// Mozilla
				document.addEventListener("mousemove", Methods.onmousemove, true);
				document.addEventListener("mouseup", Methods.onmouseup, true);
			}
			else if (document.attachEvent) {
				// IE
				document.attachEvent("onmousemove", Methods.onmousemove);
				document.attachEvent("onmouseup", Methods.onmouseup);
				document.attachEvent("onlosecapture", Methods.onmouseup);
			};

			o.cursor.style.cursor = 'e-resize';

		},
		
		onmouseup: function(e) {
			// Stop monitoring the mouse up and mouse movement and mouse up events
			if (document.removeEventListener) {
				document.removeEventListener("mousemove", Methods.onmousemove, true);
				document.removeEventListener("mouseup", Methods.onmouseup, true);
			}
			else if (document.detachEvent) {
				document.detachEvent("onmousemove", Methods.onmousemove);
				document.detachEvent("onmouseup", Methods.onmouseup);
				document.detachEvent("onlosecapture", Methods.onmouseup);
			};
			
			o.style.cursor = 'pointer';
			o.cursor.style.cursor = 'pointer';
			o.scaleNew.style.cursor = 'pointer';
			
		},
		
		onmousemove: function(e,el) {
			// track the mouse movement
			var mouseX;
			e = Methods.getEvent(e, el)
			
			if (document.all) {
				// IE
				mouseX = event.clientX + document.documentElement.scrollLeft
			} else {
				// Mozilla
				mouseX = e.pageX;
			};
			
			// check for megative values
			if (mouseX < 0){mouseX = 0};
			
			// get the potential new node
			var node = Methods.getNewNode(mouseX);
			// update the current node value for this slider
			o.node = node;
			
			// move the cursor to the correct position
			var newPosX = Methods.setPosition(o);
			o.cursor.style.left = newPosX + 'px';
			o.scaleNew.style.width = newPosX + o.cursorWidthAdj + 'px'
			
			if(moveQuota) {
				o.outputText.style.position = 'relative';
				o.unitText.style.position = 'relative';
				o.outputText.style.left = (newPosX + moveQuotaOffset) + 'px';
//				o.unitText.style.left = (newPosX + moveQuotaOffset + o.outputText.clientWidth) + 'px';
				o.unitText.style.left = (newPosX + moveQuotaOffset) + 'px';
			};
			
			// update the background position markers
			if(indicateExisting) {
				if(newPosX > o.scaleExists.initWidth) {
					o.scaleExists.style.width = o.scaleExists.initWidth + o.cursorWidthAdj + 'px';
				} else {
					o.scaleExists.style.width = newPosX + o.cursorWidthAdj + 'px';
				};
			};
			
			if(indicateUpgrade) {
				if(newPosX > o.scaleTemp.initWidth) {
					o.scaleTemp.style.width = o.scaleTemp.initWidth + o.cursorWidthAdj + 'px';
				} else {
					o.scaleTemp.style.width = newPosX + o.cursorWidthAdj + 'px';
				};
			};
			
			// Update the output value for this control
			var curVal = ((o.node + zeroAdj) * o.unitValue) + o.adjustValue;
			o.activeValue = curVal;
			Methods.setOutputs(o, curVal, o.minValue, o.maxValue, o.unitType);
			
			o.style.cursor = 'e-resize';
			o.cursor.style.cursor = 'e-resize';
			o.scaleNew.style.cursor = 'e-resize';

			// run any onUpdate script we may have
			runUpdate();
		},
		
		onclick: function(e,el) {
			clearTimeout(oTimer);
			
			var mouseX;
			e = Methods.getEvent(e, el)
			
			if (document.all) {
				// IE
				mouseX = event.clientX + document.documentElement.scrollLeft;
			} else {
				// Mozilla
				mouseX = e.pageX;
			};
			
			var newNode = Methods.getNewNode(mouseX);
			
			Methods.moveCursor(newNode);

			runUpdate();
		},
		
		setOutputs : function(e,curVal,minVal,maxVal,unitType) {
			// outputs the selected value to the display and form element
			var u = curVal;
			
			// adjust our values to compensate for the mimum value not being 1 or 0
			curVal = curVal + minVal;
			maxVal = maxVal + minVal;
			
			// workout the percentage position
			var p =  Math.round(((curVal - minVal) / (maxVal - minVal)) * 100);
			
			// set upper and lower bounds
			if(p < zeroAdj) { p = zeroAdj; };
			if(p > 100) { p = 100; };
			
			// percentage multiplier
			var pM = (curVal - minVal) / (maxVal - minVal);
			
			// set upper bound
			if(pM > 1) { pM = 1; };
			
			// are we measuring the position proportionally?
			if(mode == 'proportional' || unitType == '%') {
				u = Math.round(((maxVal - minVal) * pM));
			};
			
			// Are we posting the value as a percentage?
			if(showOutputAs == 'percentage') { u = p; };
			
			// Are we measuring a percentage or an absolute value?
			if(percentage) {
				e.unitText.innerHTML = '%';
				e.outputControl.value = u;
				e.outputText.innerHTML = p;
			} else {
				if(mode == 'proportional' && unitType != '%') {
					// if we're doing things proportionally rather than ordinally, display as a fraction (i.e. 10 / 100 GB).
					e.unitText.innerHTML = '/ ' + (maxVal - minVal) + ' ' + unitType;
				} else {
//					e.unitText.innerHTML = unitType;
					// hack to fix vCPU vs vCPUs
					e.unitText.innerHTML = (unitType == 'vCPUs' && u == 1 ? 'vCPU' : unitType);
				};
				e.outputControl.value = u;
				if(e.outputTextAdj > 1) {
					u = u / e.outputTextAdj;
				};
				e.outputText.innerHTML = u;
			};
		},
		
		setValues : function() {
			// loop through the array of sliders
			var curVal = 0;
			var minVal = 0;
			var maxVal = 0;
			var unitType = '';
			for(i = 0; i < arObjects.length; i++) {
				curVal = arObjects[i].activeValue;
				minVal = arObjects[i].minValue;
				maxVal = arObjects[i].maxValue;
				unitType = arObjects[i].unitType;
				Methods.setOutputs(arObjects[i], curVal, minVal, maxVal, unitType);
			};
		}
		
	};
	
	this.updateSliders = Methods.updateSliders;
	this.setValues = Methods.setValues;

	this.init = function() {
		// initialises the class

		
		// first thing is first, loop through the sliders array
		for(i = 0; i < arSliders.length; i++) {
			// add the sliders to the page
			document.getElementById(arSliders[i][5]).innerHTML = '<span id="'+ arSliders[i][0] + '" class="' + sliderCss + 
																 '"></span><span id="'+ arSliders[i][0] + '_output">' +
																 arSliders[i][3] +'</span> '+ '<span id="' + arSliders[i][0] + '_unit">' + arSliders[i][4] + 
																 '</span><input type="hidden" name="' + arSliders[i][0] + 
																 '_val" id="'+ arSliders[i][0] +'_val" value="'+ arSliders[i][3] +'" />';
			// stick the slider in the placeholder variable
			o = document.getElementById(arSliders[i][0]);
			
			// set the master offset
			if(offsetAuto) {
				var offsetEl = offsetRelativeTo == 'self' ? document.getElementById(arSliders[i][5]) : document.getElementById(offsetRelativeTo);
				masterOffset = offsetEl.offsetLeft;
				
				//alert(masterOffset);
				// which version of IE are we running
				var ieVersion = $fh.reportIE();
				if (ieVersion == 5 || ieVersion == 6 || ieVersion == 7) {
					var testOffset = true;
					masterOffset = 0;
					var oTmpEl = offsetEl;
					while(testOffset) {
						if(oTmpEl) {
							masterOffset += (oTmpEl.offsetLeft ? oTmpEl.offsetLeft : 0);
							oTmpEl = oTmpEl.parentNode;
						} else {
							testOffset = false;
						};
					};
					//masterOffset += 30;
				};
			};
			

			// set the properties
			var oTemp = document.getElementById(arSliders[i][0]);
			oTemp.ordinal = i;
			oTemp.id = arSliders[i][0];
			oTemp.range = arSliders[i][1];
			oTemp.unitValue = parseInt(arSliders[i][2]);
			oTemp.currentValue = parseInt(arSliders[i][3]);
			oTemp.baseValue = parseInt(arSliders[i][3]);
			oTemp.unitType = arSliders[i][4];
			oTemp.containerID = arSliders[i][5];
			oTemp.upgradeValue = parseInt(arSliders[i][6]);
			oTemp.outputControlID = oTemp.id + '_val';
			oTemp.outputControl = document.getElementById(oTemp.id + '_val');
			oTemp.outputTextID = oTemp.id + '_output';
			oTemp.outputText = document.getElementById(oTemp.id + '_output');
			oTemp.unitTextID = oTemp.id + '_unit';
			oTemp.unitText = document.getElementById(oTemp.id + '_unit');
			oTemp.node = oTemp.initialNode;
			oTemp.outputTextAdj = parseInt(arSliders[i][7]);
			if(arSliders[i][8]) {
				oTemp.blockDowngrade = true;
			} else {
				oTemp.blockDowngrade = false;
			};
		
			// Is a temporary upgrade in place?
			if(oTemp.upgradeValue >= oTemp.currentValue) {
				oTemp.initialValue = oTemp.upgradeValue;
			} else {
				oTemp.initialValue = oTemp.currentValue;
			};
		
			// parse the value ranges
			var arRanges = arSliders[i][1].split(',');
			oTemp.range = arRanges;
			
			var arCurrent;
			var minVal;
			var maxVal;
			// Loop through the value ranges
			for(n = 0; n < arRanges.length; n++) {
				arCurrent = arRanges[n].split(':');
				// have we set a min val yet
				if(n == 0) {
					// first run through so asign min value and max value
					minVal = parseInt(arCurrent[0]);
					maxVal = parseInt(arCurrent[1]);
				} else {
					// update the minVal if the current one is less, otherwise, keep it
					minVal = (parseInt(arCurrent[0]) < minVal ? parseInt(arCurrent[0]) : minVal)
					// do the opposite for maxVal
					maxVal = (parseInt(arCurrent[1]) > maxVal ? parseInt(arCurrent[1]) : maxVal)
				};
			};
			
			// update the temp object to hold the min and max values
			oTemp.minValue = minVal;
			oTemp.maxValue = maxVal;
			
			// Set adjustment value for where the minimum value is not equal to the unit value
			oTemp.adjustValue = (oTemp.minValue == oTemp.unitValue ? 0 : (oTemp.minValue / oTemp.unitValue - zeroAdj) * oTemp.unitValue);
			oTemp.adjustNode = (oTemp.minValue == oTemp.unitValue ? 0 : (oTemp.minValue / oTemp.unitValue - zeroAdj));
		
			// we can now work out the number of nodes we have
			var numNodes = Math.round((oTemp.maxValue - oTemp.minValue) / oTemp.unitValue);
			oTemp.nodeCount = (zeroSlider ? numNodes + 1 : numNodes);
			
			// draw on the cursor and position indicators
			var oCursor = document.createElement('span');
			oCursor.setAttribute('id', oTemp.id + '_cursor');
			oCursor.className = cursorClass;
			oTemp.appendChild(oCursor);
			oTemp.cursor = oCursor;
			oTemp.cursorWidthAdj = Math.round(oTemp.cursor.clientWidth / 2);
			
			var oScaleNew = document.createElement('span');
			oScaleNew.setAttribute('id', oTemp.id + '_new');
			oScaleNew.className = indicateNewClass;
			oTemp.appendChild(oScaleNew);
			oTemp.scaleNew = oScaleNew;
			
			// now that cursor has been drawn on the page, what is the scale width?
			var scaleWidth = oTemp.clientWidth - padding - Math.round(oCursor.clientWidth/2) - tweakRight;
			oTemp.scaleWidth = scaleWidth;
			
			// now we can work out the width of each node on the scale
			oTemp.nodeWidth = parseInt(oTemp.scaleWidth) / parseInt(oTemp.nodeCount);
			oTemp.adjustWidth = oTemp.adjustNode * oTemp.nodeWidth;
			
			// get the current cursor position
			oTemp.initialNode = Math.floor(oTemp.initialValue / oTemp.unitValue) - zeroAdj;
			oTemp.baseNode = Math.floor(oTemp.baseValue / oTemp.unitValue) - zeroAdj;
			oTemp.node = oTemp.initialNode;
			
			//alert('A: ' + oTemp.initialNode);
			
			// check node is not < 0
			if(oTemp.initialNode < 0) { oTemp.initialNode = 0 };
			if(oTemp.baseNode < 0) { oTemp.baseNode = 0 };
			
			// check that we're not over the nodeCount
			if(oTemp.initialNode > oTemp.nodeCount + oTemp.adjustNode) { oTemp.initialNode = oTemp.nodeCount };
			if(oTemp.baseNode > oTemp.nodeCount + oTemp.adjustNode) { oTemp.baseNode = oTemp.nodeCount };
			
			// Position the cursor
			var tmpCursorPos = Math.floor((oTemp.initialNode - oTemp.adjustNode) * oTemp.nodeWidth);
			
			oCursor.style.left = tmpCursorPos + 'px';
			
			if(moveQuota) {
				o.outputText.style.position = 'relative';
				o.unitText.style.position = 'relative';
				o.outputText.style.left = (tmpCursorPos + moveQuotaOffset) + 'px';
//				o.unitText.style.left = (tmpCursorPos + moveQuotaOffset + o.outputText.clientWidth) + 'px';
				o.unitText.style.left = (tmpCursorPos + moveQuotaOffset) + 'px';
			};
			
			// set up the background for the layer that shows any changes in the cursor position
			// This is always set to the position of the cursor
			oScaleNew.style.width = Math.floor((oTemp.initialNode - oTemp.adjustNode) * oTemp.nodeWidth) + oTemp.cursorWidthAdj + 'px';
			
			if(indicateExisting) {
				var oScaleExists = document.createElement('span');
				oScaleExists.setAttribute('id', oTemp.id + '_exists');
				oScaleExists.className = indicateExistingClass;
				oTemp.appendChild(oScaleExists);
				oTemp.scaleExists = oScaleExists;
				// Get the position for the current value scale background and position the background
				oTemp.currentNode = Math.floor(oTemp.currentValue / oTemp.unitValue) - zeroAdj;
				if(oTemp.currentNode < 0) { oTemp.currentNode = 0 };
				oTemp.scaleExists.initWidth = Math.floor(oTemp.currentNode * oTemp.nodeWidth) - oTemp.adjustWidth;
				var existWidth = oTemp.scaleExists.initWidth + oTemp.cursorWidthAdj
				if(oTemp.currentNode == oTemp.nodeCount + oTemp.adjustNode) { existWidth = oTemp.clientWidth; };
				oScaleExists.style.width = existWidth  + 'px';
			};
			
			if(indicateUpgrade) {
				var oScaleTemp = document.createElement('span');
				oScaleTemp.setAttribute('id', oTemp.id + '_temp');
				oScaleTemp.className = indicateUpgradeClass;
				oTemp.appendChild(oScaleTemp);
				oTemp.scaleTemp = oScaleTemp;
				// Get the position for the upgrade value scale background and position the background
				oTemp.upgradeNode = Math.floor(oTemp.upgradeValue / oTemp.unitValue) - zeroAdj;
				if(oTemp.upgradeNode < 0) { oTemp.upgradeNode = 0 };
				oTemp.scaleTemp.initWidth = Math.floor(oTemp.upgradeNode * oTemp.nodeWidth) - oTemp.adjustWidth;
				var tempWidth = oTemp.scaleTemp.initWidth + oTemp.cursorWidthAdj;
				if(oTemp.upgradeNode == oTemp.nodeCount + oTemp.adjustNode) { tempWidth = oTemp.clientWidth; };
				oScaleTemp.style.width = tempWidth + 'px';
			};
			
			if(indicateWarning) {
				var oScaleWarning = document.createElement('span');
				oScaleWarning.setAttribute('id', oTemp.id + '_new');
				oScaleWarning.className = indicateWarningClass;
				oTemp.appendChild(oScaleWarning);
				oTemp.scaleWarning = oScaleWarning;
				var warningWidth = (Math.floor(oTemp.initialNode * oTemp.nodeWidth) + oTemp.cursorWidthAdj) - oTemp.adjustWidth;
				if(oTemp.initialNode == oTemp.nodeCount + oTemp.adjustNode) { warningWidth = oTemp.clientWidth; };
				oScaleWarning.style.width = warningWidth + 'px';
			};

			// do we need to load a ghost marker?
			if(ghosts) {
				// draw the ghost marker in and position it on the slider
				var oGhost = document.createElement('span');
				oGhost.setAttribute('id', oTemp.id + '_ghost');
				oGhost.className = ghostClass;
				oTemp.appendChild(oGhost);
				oTemp.ghostWidth = oGhost.clientWidth;
				oGhost.style.left = Math.floor((oTemp.initialNode - oTemp.adjustNode) * oTemp.nodeWidth) + 'px';
			};
			
			oTemp.activeValue = oTemp.initialValue;
			Methods.setOutputs(oTemp, oTemp.initialValue, oTemp.minValue, oTemp.maxValue, oTemp.unitType);

			// now that we have set the values, we can start listening to the various events and assign actions to them
			oTemp.onmousedown = Methods.onmousedown;
			oTemp.onmouseup = Methods.onmouseup;
			oTemp.onmouseout = function() {
				try {debug('&nbsp;');} catch(err) {};
			};
			oTemp.onclick = Methods.onclick;
			arObjects.push(oTemp);
			oTemp.onmouseover = function() { 
				o = this;
				try {debug('&nbsp;');} catch(err) {};
			};
			
		};
				
		// run any onLoad code we may have
		runOnce();

		var oContainer = document.getElementById(defaultContainer);
		try { oContainer.onselectstart = function () { return false; } /* ie  */ } catch(err) {};
		try { oContainer.onmousedown = function () { return false; } /* mozilla */ } catch(err) {};
		
	};


};

