/*
####################################################################################################
# SELECTOOL OBJECT
# Version: 1.2.7 | Last Update: 12 June 2008
####################################################################################################
# Copyright © by Cristian Leonte (www.crissleonte.com)
# All reproduction of this file or it's content or pieces of it's content with individual functionality, in comercial or any other purpose is strictly forbidden without 
# the written consent of the copyright holder. For more information please read the legal documents on the copyright holder's website.
####################################################################################################
# DEPENDENCIES:
# functions.js, v1.0.0+, general functions library
####################################################################################################
# NO DOCUMENTATION
####################################################################################################
# NO KNOWN, UNFIXED PROBLEMS
####################################################################################################
*/

function selectool (objName, elID, CFG)
{
	//--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
	// OBJECT PROPERTIES
	
	var self = this; // bind reference to self
	
	this.objName = objName; // the name under which this object is instanced
	this.elID = elID; // the id of the div element that becomes the selector
	this.el = null; // reference to the div element that becomes the selector
	this.CFG = null; // container for local configuration parameters
	this.ATTR = new Array (); // attributes of the current selector
	this.value = null; // the current value of the selector (what values are selected, if any)
	
	this.topSlider = null; // reference to top slider element
	this.bottomSlider = null; // reference to bottom slider element
	this.spacer = null; // reference to spacer element
	this.topOverlay = null; // reference to top overlay element
	this.bottomOverlay = null; // reference to bottom overlay element
	this.area = null; // reference to area element
	this.topCap = null; // reference to top cap element
	this.bottomCap = null; // reference to bottom cap element
	this.lockZone = null; // reference to lock zone element
	
	this.dragging = null; // will contain reference to currently dragged slider element, while it happens
	this.dragStartCoords = null; // this will contain the x,y coordinates at which the dragging started
	
	this.imgPreload = null; // container for image preloading
	
	//--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
	// OBJECT INIT
	
	this.init = function (CFG)
	{
		// load configuration params
		this.loadConfig (CFG);
		
		// get reference to selector element
		this.el = document.getElementById (this.elID);
		
		// fail if no selector element was found
		if (!this.el) return false;
		
		// generate selector
		this.generateSelector ();
		
		// bind mouse events
		this.bindMouseEvents ();
		
		// preload images
		this.preloadImages ([this.CFG.topSliderOnImgURL, this.CFG.bottomSliderOnImgURL]);
		
		// return successful initiation
		return true;
	};
	
	//--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
	// GENERATE SELECTOR METHOD
	
	/*
		Generates a selector of given type inside the container element
	*/
	
	this.generateSelector = function ()
	{
		// fetch selector type
		var selType = this.el.getAttribute ('selectorType');
		
		// fetch base selector attributes and store them
		this.ATTR = this.getSelTypeAttr (selType);
		this.ATTR['type'] = selType;
		
		// init top and bottom margins
		this.ATTR['topMargin'] = 2;
		this.ATTR['bottomMargin'] = 2;
		
		// if valid dimension (large enough to accomodate all inner elements at their minimum possible size) is set, overwrite selector size height
		if (parseInt (this.el.getAttribute ('dimension')) > (this.CFG.sliderSize[1] * 2 + this.ATTR['topMargin'] + this.ATTR['bottomMargin'] + 2)) this.CFG.selectorSize[1] = parseInt (this.el.getAttribute ('dimension'));
		
		// init top and bottom overlay sizes
		this.ATTR['topOverlayHeight'] = this.ATTR['topMargin'];
		this.ATTR['bottomOverlayHeight'] = this.ATTR['bottomMargin'];
		
		// if selector is of range type, extract min and max
		if (this.ATTR['type'] == 'range')
		{
			this.ATTR['minValue'] = parseInt (this.el.getAttribute ('min'));
			this.ATTR['maxValue'] = parseInt (this.el.getAttribute ('max'));
			this.ATTR['decimals'] = parseInt (this.el.getAttribute ('decimals'));
			this.ATTR['decimals'] = (this.ATTR['decimals'] > 0) ? this.ATTR['decimals'] : 0;
		}
		// else, if selector is of option type, extract values
		else if (this.ATTR['type'] == 'options')
		{
			// prep regular expression
			var re = new RegExp (/\-(.)/g);
			
			// extract selected element style properties
			var strStyleData = this.CFG.selectedElementStyle.replace (/[ ]+/g, "");
			var arrStyleData = strStyleData.split (';');
			this.ATTR['selectedOptStyle'] = new Array ();
			for (var idx = 0; idx < arrStyleData.length; idx ++) if (arrStyleData[idx])
			{
				// extract stype property/value set and transform them in javascript assignable format
				var aux = arrStyleData[idx].split (':');
				aux[0].match (re); aux[0] = aux[0].replace (re, RegExp.$1.toUpperCase ());
				this.ATTR['selectedOptStyle'][aux[0]] = aux[1];
			}
			
			// extract excluded element style properties
			var strStyleData = this.CFG.excludedElementStyle.replace (/[ ]+/g, "");
			var arrStyleData = strStyleData.split (';');
			this.ATTR['excludedOptStyle'] = new Array ();
			for (var idx = 0; idx < arrStyleData.length; idx ++) if (arrStyleData[idx])
			{
				// extract stype property/value set and transform them in javascript assignable format
				var aux = arrStyleData[idx].split (':');
				aux[0].match (re); aux[0] = aux[0].replace (re, RegExp.$1.toUpperCase ());
				this.ATTR['excludedOptStyle'][aux[0]] = aux[1];
			}
			
			// calculate option list size
			this.ATTR['optListSize'] = this.CFG.selectorSize[1] - (this.ATTR['topMargin'] + this.ATTR['bottomMargin'] + this.CFG.sliderSize[1] * 2);
			
			var ul = null;
			if (this.el.firstChild && this.el.firstChild.nextSibling && this.el.firstChild.nextSibling.tagName == 'UL') ul = this.el.firstChild.nextSibling;
			else if (this.el.firstChild && this.el.firstChild.tagName == 'UL') ul = this.el.firstChild;
			
			this.ATTR['OPTIONS'] = new Array ();
			this.ATTR['OPTION_VALUES'] = new Array ();
			for (var idx = 0; idx < ul.childNodes.length; idx ++)
			{
				if (ul.childNodes[idx].nodeName == 'LI')
				{
					this.ATTR['OPTIONS'][this.ATTR['OPTIONS'].length] = ul.childNodes[idx].firstChild.nodeValue.replace (/^\s*/, '').replace (/\s*$/, '');
					if (ul.childNodes[idx].getAttribute ('val')) this.ATTR['OPTION_VALUES'][this.ATTR['OPTION_VALUES'].length] = ul.childNodes[idx].getAttribute ('val').replace (/^\s*/, '').replace (/\s*$/, '');
					else this.ATTR['OPTION_VALUES'][this.ATTR['OPTION_VALUES'].length] = this.ATTR['OPTIONS'][(this.ATTR['OPTIONS'].length - 1)];
				}
			}
			
			// set the start option positions of sliders
			this.ATTR['currTopSliderOpt'] = 0;
			this.ATTR['currBottomSliderOpt'] = this.ATTR['OPTIONS'].length;
			
			// calculate option height, if too small, default it to font size plus 2px buffer to top and bottom
			this.ATTR['optHeight'] = Math.floor (this.ATTR['optListSize'] / this.ATTR['OPTIONS'].length);
			this.ATTR['optHeight'] = (this.ATTR['optHeight'] < (this.CFG.optFontSize + this.ATTR['topMargin'] + this.ATTR['bottomMargin'])) ? (this.CFG.optFontSize + this.ATTR['topMargin'] + this.ATTR['bottomMargin']) : this.ATTR['optHeight'];
			
			// calculate offset for options list (the extra space that remains at top and bottom of the selector area after the size of each option is calculated and rounded down)
			this.ATTR['optOffsetTop'] = 0;
			this.ATTR['optOffsetBottom'] = 0;
			var totalOptOffset = this.ATTR['optListSize'] - this.ATTR['optHeight'] * this.ATTR['OPTIONS'].length;
			if (totalOptOffset > 0)
			{
				this.ATTR['optOffsetTop'] = Math.floor (totalOptOffset / 2);
				this.ATTR['optOffsetBottom'] = totalOptOffset - this.ATTR['optOffsetTop'];
			}
			
			// adjust top and bottom overlay sizes
			this.ATTR['topOverlayHeight'] += (this.ATTR['optOffsetTop'] + this.CFG.topSliderSnap);
			this.ATTR['bottomOverlayHeight'] += (this.ATTR['optOffsetBottom'] + this.CFG.bottomSliderSnap);
		}
		
		// set drag limit
		this.ATTR['dragLimitTop'] = this.ATTR['topOverlayHeight'];
		this.ATTR['dragLimitBottom'] = this.ATTR['bottomOverlayHeight'];
		
		// calculate spacer height
		this.ATTR['spacerHeight'] = this.CFG.selectorSize[1] - (this.ATTR['topOverlayHeight'] + this.ATTR['bottomOverlayHeight'] + this.CFG.sliderSize[1] * 2);
		
		// store local coordinates of top and bottom sliders
		this.ATTR['topSliderCoords'] = [0, this.ATTR['topOverlayHeight']];
		this.ATTR['bottomSliderCoords'] = [0, this.ATTR['topOverlayHeight'] + this.CFG.sliderSize[1] + this.ATTR['spacerHeight']];
		
		// create options list for option type selectors
		var optList = '';
		if (this.ATTR['type'] == 'options')
		{
			var optListArr = new Array ();
			for (var idx = 0; idx < this.ATTR['OPTIONS'].length; idx ++)
			{
				optListArr[idx] = '<tr><td id="' + this.elID + '_optEl_' + idx + '" style="width: ' + this.CFG.selectorSize[0] + 'px; height: ' + this.ATTR['optHeight'] + 'px; ' + this.CFG.selectedElementStyle + ((this.CFG.optBorderSize && idx > 0) ? ' border-top: ' + this.CFG.optBorderSize + 'px solid ' + this.CFG.optBorderColor + ';' : '') + '">' + this.ATTR['OPTIONS'][idx] + '</td></tr>';
			}
			optList = '<table cellpadding="0" cellspacing="0">' + optListArr.join ("\n") + '</table>';
		}
		
		// create top cap/label
		var topCap = '';
		if (this.ATTR['labels']) topCap = '<div style="width: ' + this.CFG.capSize[0] + 'px; height: ' + this.CFG.capSize[1] + 'px; background: url(' + this.CFG.capLabelImgURL + ') #ffffff; ' +  this.CFG.capLabelStyle + '" id="' + this.elID + '_topCap"></div>';
		else topCap = '<div style="width: ' + this.CFG.capSize[0] +  'px; height: ' + this.CFG.capSize[1] + 'px; background: url(' + this.CFG.topCapImgURL + ') bottom repeat-x;" id="' + this.elID + '_topCap"></div>';
		
		// create top cap/label
		var bottomCap = '';
		if (this.ATTR['labels']) bottomCap = '<div style="width: ' + this.CFG.capSize[0] + 'px; height: ' + this.CFG.capSize[1] + 'px; background: url(' + this.CFG.capLabelImgURL + ') #ffffff; ' +  this.CFG.capLabelStyle + '" id="' + this.elID + '_bottomCap"></div>';
		else bottomCap = '<div style="width: ' + this.CFG.capSize[0] +  'px; height: ' + this.CFG.capSize[1] + 'px; background: url(' + this.CFG.bottomCapImgURL + ') top repeat-x;" id="' + this.elID + '_bottomCap"></div>';
		
		// create selector areacapLabelImgURL
		var selectorArea = '';
		if (selType == 'range') selectorArea = '<div style="width: ' + this.CFG.selectorSize[0] + 'px; height: ' + this.CFG.selectorSize[1] + 'px; background: url(' + this.CFG.rangeBgImgURL + ') repeat-y #ffffff;" id="' + this.elID + '_area"></div>';
		else if (selType == 'options') selectorArea = '<div style="width: ' + this.CFG.selectorSize[0] + 'px; height: ' + this.CFG.selectorSize[1] + 'px; background: transparent;" id="' + this.elID + '_area"></div>';
		else selectorArea = '<div style="width: ' + this.CFG.selectorSize[0] +  'px; height: ' + this.CFG.selectorSize[1] + 'px; background: #ffffff;" id="' + this.elID + '_area"></div>';
		
		// define selector area holder, this only applies for option type selectors and it will have the exact same size as the area
		var selectorAreaHolder = '';
		if (selType == 'options') selectorAreaHolder = '<div style="width: ' + this.CFG.selectorSize[0] + 'px; height: ' + this.CFG.selectorSize[1] + 'px; background: ' + (this.CFG.optionsBgImgURL ? 'url(' + this.CFG.optionsBgImgURL + ') repeat-y' : 'transparent') + ';" id="' + this.elID + '_areaHolder"></div>';
		
		// create top overlay
		var topOverlay = '<div style="font-size: 1px; width: ' + this.CFG.selectorSize[0] + 'px; height: ' + this.ATTR['topOverlayHeight'] + 'px; background-color: ' + this.CFG.overlayColor + ';" id="' + this.elID + '_topOverlay"></div>';
		
		// create bottom overlay
		var bottomOverlay = '<div style="font-size: 1px; width: ' + this.CFG.selectorSize[0] + 'px; height: ' + this.ATTR['bottomOverlayHeight'] + 'px; background-color: ' + this.CFG.overlayColor + ';" id="' + this.elID + '_botomOverlay"></div>';
		
		// create top slider
		var topSlider = '<div style="font-size: 1px; width: ' + this.CFG.sliderSize[0] + 'px; height: ' + this.CFG.sliderSize[1] + 'px; background: url(' + this.CFG.topSliderOffImgURL + '); cursor: pointer;" id="' + this.elID + '_topSlider"></div>';
		
		// create bottom slider
		var bottomSlider = '<div style="font-size: 1px; width: ' + this.CFG.sliderSize[0] + 'px; height: ' + this.CFG.sliderSize[1] + 'px; background: url(' + this.CFG.bottomSliderOffImgURL + '); cursor: pointer;" id="' + this.elID + '_bottomSlider"></div>';
		
		// create slider spacer
		var spacer = '<div style="font-size: 1px; width: ' + this.CFG.sliderSize[0] + 'px; height: ' + this.ATTR['spacerHeight'] + 'px;" id="' + this.elID + '_spacer"></div>';
		
		if (this.ATTR['type'] == 'range')
		{
			// write selector html inside container element
			this.el.innerHTML = topCap + "\n" + selectorArea + "\n" + bottomCap;
			this.area = document.getElementById (this.elID + '_area');
			this.area.innerHTML = topOverlay + "\n" + topSlider + "\n" + spacer + "\n" + bottomSlider + "\n" + bottomOverlay;
		}
		else if (this.ATTR['type'] == 'options')
		{
			// write selector area holder inside container element
			this.el.innerHTML = topCap + "\n" + selectorAreaHolder + "\n" + bottomCap;
			
			// get handle to selector area holder and create area element inside it
			this.areaHolder = document.getElementById (this.elID + '_areaHolder');
			this.areaHolder.innerHTML = selectorArea;
			
			// get handle to area element and write the selector elements inside it
			this.area = document.getElementById (this.elID + '_area');
			this.area.innerHTML = topOverlay + "\n" + topSlider + "\n" + spacer + "\n" + bottomSlider + "\n" + bottomOverlay;
			
			// reposition the area element exactly above the area holder
			this.alignArea ();
			
			// add options list to selector, inside the area holder
			this.areaHolder.innerHTML += '<div style="font-size: 1px; width: ' + this.CFG.sliderSize[0] + 'px; height: ' + (this.CFG.sliderSize[1] + this.ATTR['topMargin'] + this.ATTR['optOffsetTop']) + 'px;"></div>' + optList;
		}
		
		// make overlays transparent
		this.topOverlay = document.getElementById (this.elID + '_topOverlay');
		this.setOpacity (this.topOverlay, parseInt (this.CFG.overlayOpacity));
		this.bottomOverlay = document.getElementById (this.elID + '_botomOverlay');
		this.setOpacity (this.bottomOverlay, parseInt (this.CFG.overlayOpacity));
		
		// bind remaining elements to their handles
		this.topSlider = document.getElementById (this.elID + '_topSlider');
		this.bottomSlider = document.getElementById (this.elID + '_bottomSlider');
		this.spacer = document.getElementById (this.elID + '_spacer');
		this.topCap = document.getElementById (this.elID + '_topCap');
		this.bottomCap = document.getElementById (this.elID + '_bottomCap');
		
		// create lock zone if enabled
		if (this.CFG.useLockZone)
		{
			// create lock zone element
			var lockZone = document.createElement ('DIV');
			lockZone.innerHTML = "";
			lockZone.setAttribute ('id', this.elID + '_lockZone');
			document.body.appendChild (lockZone);
			
			// get document reference to lockZone
			this.lockZone = document.getElementById (this.elID + '_lockZone');
			
			// set drag zone style
			this.lockZone.style.position = 'absolute';
			this.lockZone.style.backgroundColor = '#efefef';
			this.lockZone.style.borderStyle = 'solid';
			this.lockZone.style.borderWidth = '0px';
			this.lockZone.style.borderColor = '#dddddd';
			this.lockZone.style.width = this.CFG.selectorSize[0] + 'px';
			this.lockZone.style.height = this.CFG.selectorSize[1] + 'px';
			this.lockZone.style.zIndex = '1000';
			
			// set lock zone transparency
			this.setOpacity (this.lockZone, 50);
			
			// reposition lock zone over our selector
			var elPos = getAbsPos ((this.areaHolder ? this.areaHolder : this.area));
			this.lockZone.style.left = elPos[0];
			this.lockZone.style.top = elPos[1];
		}
		
		// show min and max values on their labels, if we have labels
		if (this.ATTR['labels'])
		{
			this.topCap.innerHTML = this.numberFormat (this.ATTR['maxValue'], this.ATTR['decimals']);
			this.bottomCap.innerHTML = this.numberFormat (this.ATTR['minValue'], this.ATTR['decimals']);
		}
	}
	
	//--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
	// SLIDER EVENT MOUSE DOWN METHOD
	
	this.alignArea = function (coords, isOffset)
	{
		if (self.ATTR['type'] == 'options')
		{
			// must call area directly from document, somehow the self.area doesn't point to the corrent object when calling the alignArea method from the outside
			var area = document.getElementById (self.elID + '_area');
			
			if (coords && isOffset)
			{
				area.style.left = parseInt (area.style.left) + coords[0];
				area.style.top = parseInt (area.style.top) + coords[1];
			}
			else if (coords)
			{
				area.style.left = coords[0];
				area.style.top = coords[1];
			}
			else
			{
				var origPos = getAbsPos (self.areaHolder);
				area.style.position = 'absolute';
				area.style.left = origPos[0];
				area.style.top = origPos[1];
			}
		}
	}
	
	//--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
	// SLIDER EVENT MOUSE DOWN METHOD
	
	this.sliderEventMouseDown = function (e)
	{
		// extract event details
		var ev = self.extractEvent (e, true);
		
		// if mouse down over top or bottom slider, start dragging it
		if (ev.element.id == self.topSlider.id) self.startDraggingSlider ('top', ev.event.clientX, ev.event.clientY);
		else if (ev.element.id == self.bottomSlider.id) self.startDraggingSlider ('bottom', ev.event.clientX, ev.event.clientY);
	}
	
	//--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
	// AREA EVENT MOUSE UP METHOD
	
	this.areaEventMouseUp = function (e)
	{
		// extract event details
		var ev = self.extractEvent (e, true);
		
		// stop dragging slider
		self.stopDraggingSlider ();
	}
	
	//--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
	// AREA EVENT MOUSE MOVE METHOD
	
	this.areaEventMouseMove = function (e)
	{
		// extract event details
		var ev = self.extractEvent (e, true);
		
		// debugging output
		// document.getElementById ('debugOutput').innerHTML = 'Dragging: ' + self.dragging;
		
		// if dragging is taking place
		if (self.dragging)
		{
			self.dragSliderTo (ev.event.clientX, ev.event.clientY);
			self.updateSelector ();
		}
	}
	
	//--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
	// UPDATE SELECTOR METHOD
	
	this.updateSelector = function ()
	{
		// if selector is range type
		if (self.ATTR['type'] == 'range')
		{
			// for range, init temp value as array of bottom value, top value
			var tmp_value = [self.numberFormat (self.ATTR['minValue'], self.ATTR['decimals']), self.numberFormat (self.ATTR['maxValue'], self.ATTR['decimals'])];
			
			// if top slider is not at the very top, calculate top value based on slider position
			if (!(parseInt (self.topOverlay.style.height) < this.ATTR['topMargin']))
			{
				var position = parseInt (self.topOverlay.style.height) - this.ATTR['topMargin'];
				var span = self.CFG.selectorSize[1] - (self.CFG.sliderSize[1] * 2) - this.ATTR['topMargin'] - this.ATTR['bottomMargin'];
				var unit = (span * 0.01);
				var percent = Math.round (position / unit);
				tmp_value[1] = self.numberFormat (self.ATTR['minValue'] + (100 - percent) / 100 * (self.ATTR['maxValue'] - self.ATTR['minValue']), self.ATTR['decimals']);
			}
			
			// if bottom slider is not at very botom, calculate bottom value based on slider position
			if (!(parseInt (self.bottomOverlay.style.height) < this.ATTR['bottomMargin']))
			{
				var position = ((parseInt (self.topOverlay.style.height) + self.CFG.sliderSize[1] + parseInt (self.spacer.style.height)) - self.CFG.sliderSize[1] - this.ATTR['bottomMargin']);
				var span = self.CFG.selectorSize[1] - (self.CFG.sliderSize[1] * 2) - this.ATTR['topMargin'] - this.ATTR['bottomMargin'];
				var unit = (span * 0.01);
				var percent = Math.round (position / unit);
				tmp_value[0] = self.numberFormat (self.ATTR['minValue'] + (100 - percent) / 100 * (self.ATTR['maxValue'] - self.ATTR['minValue']), self.ATTR['decimals']);
			}
			
			// update labels
			if (self.ATTR['labels'])
			{
				self.topCap.innerHTML = tmp_value[1];
				self.bottomCap.innerHTML = tmp_value[0];
			}
		}
		// else, if selector is option type
		else if (self.ATTR['type'] == 'options')
		{
			// detect on what option the top slider is above
			var landingPosTop = parseInt (self.topOverlay.style.height) - self.ATTR['dragLimitTop'];
			var landingOptTop = Math.round (landingPosTop / self.ATTR['optHeight']);
			
			// detect on what option the bottom slider is landed on
			var landingPosBottom = (parseInt (self.topOverlay.style.height) + self.CFG.sliderSize[1] + parseInt (self.spacer.style.height)) - self.ATTR['dragLimitBottom'];
			var landingOptBottom = Math.round (landingPosBottom / self.ATTR['optHeight']);
			
			// walk each option
			self.value = new Array ();
			for (var idx = 0; idx < self.ATTR['OPTIONS'].length; idx ++)
			{
				// get handle to option element
				var optEl = document.getElementById (self.elID + '_optEl_' + idx);
				
				// mark all selected options as selected
				if (idx >= landingOptTop && idx <= (landingOptBottom - 1))
				{
					// change the style properties of the option to selected
					for (var idxx in self.ATTR['selectedOptStyle']) eval ('optEl.style.' + idxx + ' = "' + self.ATTR['selectedOptStyle'][idxx] + '"');
				}
				// mark all excluded options as excluded
				else
				{
					// change the style properties of the option to selected
					for (var idxx in self.ATTR['excludedOptStyle']) eval ('optEl.style.' + idxx + ' = "' + self.ATTR['excludedOptStyle'][idxx] + '"');
				}
			}
		}
	}
	
	//--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
	// START DRAGGING SLIDER METHOD
	
	this.startDraggingSlider = function (slider, x, y)
	{
		if (slider == 'top') self.dragging = self.topSlider;
		else if (slider == 'bottom') self.dragging = self.bottomSlider;
		self.dragStartCoords = [x, y];
	}

	//--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
	// SYNC SLIDER ATTRIBUTES METHOD
	
	this.syncSliderAttr = function ()
	{
		self.ATTR['topOverlayHeight'] = parseInt (self.topOverlay.style.height);
		self.ATTR['topSliderCoords'] = [0, self.ATTR['topOverlayHeight']];
		self.ATTR['bottomSliderCoords'] = [0, (self.ATTR['topSliderCoords'][1] + self.CFG.sliderSize[1] + parseInt (self.spacer.style.height))];
		self.ATTR['bottomOverlayHeight'] = parseInt (self.bottomOverlay.style.height);
		self.ATTR['spacerHeight'] = parseInt (self.spacer.style.height);
	}
	
	//--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
	// STOP DRAGGING SLIDER METHOD
	
	this.stopDraggingSlider = function ()
	{
		// fail if not dragging
		if (!self.dragging) return false;
		
		// synchronise slider attributes
		self.syncSliderAttr ();
		
		// if selector is option type, snap to option
		if (self.ATTR['type'] == 'options')
		{
			// detect on what option we landed on
			var landingPos = ((self.dragging.id == self.topSlider.id) ? (self.ATTR['topSliderCoords'][1] - self.ATTR['dragLimitTop']) : (self.ATTR['bottomSliderCoords'][1] - self.ATTR['dragLimitBottom']));
			var landingOpt = Math.round (landingPos / self.ATTR['optHeight']);
			
			// if slider is set to drop on the other slider's position, drop it on the previous option instead
			if (self.dragging.id == self.topSlider.id && landingOpt == self.ATTR['currBottomSliderOpt']) landingOpt --;
			else if (self.dragging.id == self.bottomSlider.id && landingOpt == self.ATTR['currTopSliderOpt']) landingOpt ++;
			
			// calculate landing option position within the options list
			var landingOptPos = self.ATTR['optHeight'] * landingOpt;
			
			// update current option position of sliders
			if (self.dragging.id == self.topSlider.id) self.ATTR['currTopSliderOpt'] = landingOpt;
			else self.ATTR['currBottomSliderOpt'] = landingOpt;
			
			// snap slider to landing option begining
			var snapPos = landingOptPos - landingPos;
			if (snapPos != 0)
			{
				// reposition slider
				self.adjustSlider (((self.dragging.id == self.topSlider.id) ? 'top' : 'bottom'), 0, snapPos);
				
				// re-sync slider attributes
				self.syncSliderAttr ();
			}
			
			// update selector display
			self.updateSelector ();
			
			// debugging output
			// document.getElementById ('debugOutput1').innerHTML = self.ATTR['optHeight'] + ' | ' + (landingPos / self.ATTR['optHeight']) + ' | ' + landingPos + ' | ' + landingOpt + ' |snp: ' + snapPos + ' |landingOptPos: ' + landingOptPos;
		}
		
		// reset dragging
		self.dragging = null;
		self.dragStartCoords = null;
		
		// update selection value
		self.updateSelection ();
		
		// call callback, if set to
		if (self.CFG.callback) self.CFG.callback (self.elID, self.value);
	}
	
	//--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
	// UPDATE SELECTION METHOD
	
	this.updateSelection = function ()
	{
		// calculate selection value for range type
		if (self.ATTR['type'] == 'range')
		{
			// for range, init value as array of bottom value, top value
			self.value = [self.numberFormat (self.ATTR['minValue'], self.ATTR['decimals']), self.numberFormat (self.ATTR['maxValue'], self.ATTR['decimals'])];
			
			// if top slider is at very top, no changes required, max value is used as top value
			if (self.ATTR['topSliderCoords'][1] < this.ATTR['topMargin']) self.value[1] = self.value[1];
			// else, calculate top value based on slider position
			else
			{
				var position = self.ATTR['topSliderCoords'][1] - this.ATTR['topMargin'];
				var span = self.CFG.selectorSize[1] - (self.CFG.sliderSize[1] * 2) - this.ATTR['topMargin'] - this.ATTR['bottomMargin'];
				var unit = (span * 0.01);
				var percent = Math.round (position / unit);
				self.value[1] = self.numberFormat (self.ATTR['minValue'] + (100 - percent) / 100 * (self.ATTR['maxValue'] - self.ATTR['minValue']), self.ATTR['decimals']);
				
				// debugging output
				// document.getElementById ('debugOutput').innerHTML = 'Position: ' + position + ' | span: ' + span + ' | unit: ' + unit + ' | percent : ' + percent;
			}
			
			// if bottom slider is at very botom, no changes required, min value is used as bottom value
			if (self.ATTR['bottomOverlayHeight'] < this.ATTR['bottomMargin']) self.value[0] = self.value[0];
			// else, calculate bottom value based on slider position
			else
			{
				var position = (self.ATTR['bottomSliderCoords'][1] - self.CFG.sliderSize[1] - this.ATTR['bottomMargin']);
				var span = self.CFG.selectorSize[1] - (self.CFG.sliderSize[1] * 2) - this.ATTR['topMargin'] - this.ATTR['bottomMargin'];
				var unit = (span * 0.01);
				var percent = Math.round (position / unit);
				self.value[0] = self.numberFormat (self.ATTR['minValue'] + (100 - percent) / 100 * (self.ATTR['maxValue'] - self.ATTR['minValue']), self.ATTR['decimals']);
				
				// debugging output
				//document.getElementById ('debugOutput2').innerHTML = 'Position: ' + position + ' | span: ' + span + ' | unit: ' + unit + ' | percent : ' + percent;
			}
			
			// update labels
			if (self.ATTR['labels'])
			{
				self.topCap.innerHTML = self.value[1];
				self.bottomCap.innerHTML = self.value[0];
			}
		}
		// calculate selection value for range type
		else if (self.ATTR['type'] == 'options')
		{
			// detect on what option the top slider is landed on
			var landingPosTop = self.ATTR['topSliderCoords'][1] - self.ATTR['dragLimitTop'];
			var landingOptTop = Math.round (landingPosTop / self.ATTR['optHeight']);
			
			// detect on what option the bottom slider is landed on
			var landingPosBottom = self.ATTR['bottomSliderCoords'][1] - self.ATTR['dragLimitBottom'];
			var landingOptBottom = Math.round (landingPosBottom / self.ATTR['optHeight']);
			
			// walk each option and extract the ones selected as selector value
			self.value = new Array ();
			for (var idx = 0; idx < self.ATTR['OPTION_VALUES'].length; idx ++)
			{
				if (idx >= landingOptTop && idx <= (landingOptBottom - 1)) self.value[self.value.length] = self.ATTR['OPTION_VALUES'][idx];
			}
		}
	}
	
	//--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
	// DRAG SLIDER TO METHOD
	
	this.dragSliderTo = function (x, y)
	{
		// fail if start drag coords are not available
		if (!self.dragStartCoords)
		{
			alert ("Selectool Error: cannot drag slider, no drag start coordinates.");
			self.stopDraggingSlider ();
			return false;
		}
		
		// calculate dragging offsets
		var offsetX = x - self.dragStartCoords[0];
		var offsetY = y - self.dragStartCoords[1];
		
		// debugging output
		// document.getElementById ('debugOutput').innerHTML = 'Drag to: ' + x + ',' + y + ' | Offset: ' + offsetX + ',' + offsetY;
		
		// reposition slider
		self.adjustSlider (((self.dragging.id == self.topSlider.id) ? 'top' : 'bottom'), offsetX, offsetY);
		
		return false;
	}
	
	//--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
	// ADJUST SLIDER METHOD
	
	this.adjustSlider = function (slider, offsetX, offsetY)
	{
		// adjust top slider
		if (slider == 'top')
		{
			var new_spacer_height = self.CFG.selectorSize[1] - (self.ATTR['topSliderCoords'][1]  + offsetY + self.ATTR['bottomOverlayHeight'] + self.CFG.sliderSize[1] * 2);
			var new_topOverlay_height = self.ATTR['topSliderCoords'][1] + offsetY;
			
			// restrict movement
			if (new_topOverlay_height >= self.ATTR['dragLimitTop'] && new_spacer_height >= 2)
			{
				self.spacer.style.height = new_spacer_height;
				self.topOverlay.style.height = new_topOverlay_height;
			}
		}
		// adjust bottom slider
		if (slider == 'bottom')
		{
			var new_spacer_height = self.ATTR['bottomSliderCoords'][1] + offsetY - (self.ATTR['topOverlayHeight'] + self.CFG.sliderSize[1]);
			var new_bottomOverlay_height = self.CFG.selectorSize[1] - (self.ATTR['bottomSliderCoords'][1] + offsetY + self.CFG.sliderSize[1]);
			
			// restrict movement
			if (new_bottomOverlay_height >= self.ATTR['dragLimitBottom'] && new_spacer_height >= 2)
			{
				self.spacer.style.height = new_spacer_height;
				self.bottomOverlay.style.height = new_bottomOverlay_height;
			}
		}
	}
	
	//--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
	// SELECTOR EVENT MOUSE OVER METHOD
	
	this.selectorEventMouseOver = function (e)
	{
		// set sliders to look enabled (WARNING: putting semicolon after the closing parenthesis here will result in IE error)
		self.topSlider.style.backgroundImage = "url('" + self.CFG.topSliderOnImgURL + "')";
		self.bottomSlider.style.backgroundImage = 'url(' + self.CFG.bottomSliderOnImgURL + ')';
	}
	
	//--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
	// SELECTOR EVENT MOUSE OUT METHOD
	
	this.selectorEventMouseOut = function (e)
	{
		// set sliders to look disabled (WARNING: putting semicolon after the closing parenthesis here will result in IE error)
		self.topSlider.style.backgroundImage = 'url(' + self.CFG.topSliderOffImgURL + ')';
		self.bottomSlider.style.backgroundImage = 'url(' + self.CFG.bottomSliderOffImgURL + ')';
	}
	
	//--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
	// BIND MOUSE EVENTS METHOD
	
	/*
		Bind mouse events to selector sliders and selector area.
	*/
	
	this.bindMouseEvents = function ()
	{
		this.topSlider.onmousedown = this.sliderEventMouseDown;
		this.bottomSlider.onmousedown = this.sliderEventMouseDown;
		this.el.onmouseup = this.areaEventMouseUp;
		this.el.onmousemove = this.areaEventMouseMove;
		this.el.onmouseover = this.selectorEventMouseOver;
		this.el.onmouseout = this.selectorEventMouseOut;
		this.el.onselectstart = function () { return false; } 
		
		if (document.body.addEventListener) document.body.addEventListener ('mouseup', self.stopDraggingSlider, false);
		else document.body.attachEvent ('mouseup', self.stopDraggingSlider, false);
	}
	
	//--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
	// GET SELECTOR TYPE ATTRIBUTES METHOD
	
	/*
		Get the descriptive attributes of a selector based on it's type.
	*/
	
	this.getSelTypeAttr = function (selType)
	{
		if (selType == 'range')
		{
			var AUX = new Array ();
			AUX['labels'] = true;
			AUX['snap'] = false;
			AUX['options'] = false;
			return AUX;
		}
		else if (selType == 'options')
		{
			var AUX = new Array ();
			AUX['labels'] = false;
			AUX['snap'] = true;
			AUX['options'] = true;
			return AUX;
		}
	}
	
	//--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
	// SET OPACITY METHOD
	
	/*
		Set opacity/transparency for given element.
	*/
	
	this.setOpacity = function (el, value)
	{
		el.style.opacity = value / 100;
		el.style.filter = 'alpha(opacity=' + value + ')';
	}
	
	//--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
	// NUMBER FORMAT METHOD
	
	this.numberFormat = function (pnumber, decimals)
	{
		if (isNaN (pnumber)) return 0;
		if (pnumber == '') return 0;
		if (!(decimals > 0)) return Math.round (pnumber);
		
		var snum = new String (pnumber);
		var sec = snum.split ('.');
		var whole = parseFloat (sec[0]);
		var result = '';
		
		if (sec.length > 1)
		{
			var dot;
			var dec = new String (whole);
			dec += '.';
			dot = dec.indexOf ('.');		
			
			var aux_sec = sec[1].split ('');
			for (var i = 0; i < decimals; i ++) if (parseInt (aux_sec[i]) >= 0 || aux_sec[i] + '' == '0') dec += aux_sec[i];
			while (dec.length <= dot + decimals) dec += '0';
			result = dec;
		}
		else
		{
			var dot;
			var dec = new String (whole);
			dec += '.';
			dot = dec.indexOf ('.');		
			
			while (dec.length <= dot + decimals) dec += '0';
			result = dec;
		}
		
		return result;
	}
	
	//--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
	// EXTRACT EVENT METHOD
	
	this.extractEvent = function (ev, stopPropagation)
	{
		// get event
		if (!ev) var ev = window.event;
		
		// stop event propagation if set to
		if (stopPropagation)
		{
			ev.cancelBubble = true;
			if (ev.stopPropagation) ev.stopPropagation ();
		}
		
		// package event data
		var EVNT = new Object ();
		EVNT.event = ev;
		
		// get event target element
		if (ev.target) EVNT.element = ev.target;
		else if (ev.srcElement) EVNT.element = ev.srcElement;
		else EVNT.element = null;
		
		// return event data
		return EVNT;
	}
	
	//--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
	// PRELOAD IMAGES METHOD
	
	this.preloadImages = function (imgArr)
	{
		this.imgPreload = new Array ();
		for (idx = 0; idx < imgArr.length; idx ++)
		{
			this.imgPreload[idx] = new Image ();
			this.imgPreload[idx].src = imgArr[idx];
		}
	}
	
	//--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
	// LOAD CONFIG
	
	/*
		This method loads the configuration parameters from the given configuration array and uses defaults where values are not set.
	*/
	
	this.loadConfig = function (CFG)
	{
		if (!CFG) CFG = new Object ();
		
		this.CFG = 
		{
			selectorTypes : this.def (CFG.selectorTypes, ['range', 'options']), // possible selector types
			callback : this.def (CFG.callback, null), // callback method for selector adjustment event
			
			useLockZone : this.def (CFG.useLockZone, false), // set true in order to generate and use lockzone (layer absolute positioned above theselector to block access to the selector)
			selectorSize : this.def (CFG.selectorSize, [66, 200]), // width, height in pixels, of the selector (area between top and bottom lables that contains the actual sliding)
			capSize : this.def (CFG.capSize, [66, 16]), // width, height in pixels, of the capping sections (label or images)
			sliderSize : this.def (CFG.sliderSize, [66, 13]), // width, height in pixels, of the top and bottom sliders
			topSliderSnap : this.def (CFG.topSliderSnap, 6), // the number of pixels (from top to bottom) within the top slider image, which is consider the landing location for the slider snap
			bottomSliderSnap : this.def (CFG.bottomSliderSnap, 6), // the number of pixels (from top to bottom) within the bottom slider image, which is consider the landing location for the slider snap
			capLabelStyle : this.def (CFG.capLabelStyle, 'text-align: center; color: #006cff; font-family: verdana; font-size: 11px;'), // styling of the cap labels, width and height must not be provided here
			capLabelImgURL : this.def (CFG.capLabelImgURL, 'selectool/images/capLabel.gif'), // url to the image that represents the cap labels
			topSliderOnImgURL : this.def (CFG.topSliderOnImgURL, 'selectool/images/topSlider.gif'), // url to the image that represents the top slider (when enabled)
			topSliderOffImgURL : this.def (CFG.topSliderOffImgURL, 'selectool/images/topSliderOff.gif'), // url to the image that represents the top slider (when disabled)
			bottomSliderOnImgURL : this.def (CFG.bottomSliderOnImgURL, 'selectool/images/bottomSlider.gif'), // url to the image that represents the bottom slider (when enabled)
			bottomSliderOffImgURL : this.def (CFG.bottomSliderOffImgURL, 'selectool/images/bottomSliderOff.gif'), // url to the image that represents the bottom slider (when disabled)
			topCapImgURL : this.def (CFG.topCapImgURL, 'selectool/images/topCap.gif'), // url to the image that represents the top cap (used when cap labels are disabled)
			bottomCapImgURL : this.def (CFG.bottomCapImgURL, 'selectool/images/bottomCap.gif'), // url to the image that represents the bottom cap (used when cap labels are disabled)
			rangeBgImgURL : this.def (CFG.rangeBgImgURL, 'selectool/images/rangeBgTile.gif'), // url to the image that represents the background of the selector when the selector type is range
			optionsBgImgURL : this.def (CFG.optionsBgImgURL, null), // url to the image that represents the background of the selector when the selector type is option
			overlayColor : this.def (CFG.overlayColor, '#b4b4b4'), // color of the overlay that represents the non-selected areas of the selector
			overlayOpacity : this.def (CFG.overlayOpacity, 30), // percent of opacity of the overlay 
			selectedElementStyle : this.def (CFG.selectedElementStyle, 'text-align: center; vertical-align: middle; color: #006cff; font-family: verdana; font-size: 10px;'), // style to display the selected elements in
			excludedElementStyle : this.def (CFG.excludedElementStyle, 'text-align: center; vertical-align: middle; color: #999999; font-family: verdana; font-size: 10px;'), // style to display the excluded elements in
			optBorderSize : this.def (CFG.optBorderSize, 1), // the size of the border that separates option elements
			optBorderColor : this.def (CFG.optBorderColor, '#dddddd'), // the color of the border that separates option elements
			optFontSize : this.def (CFG.optFontSize, '#dddddd') // the size of the font used within option elements, note that this does not set the font size, the font size is set above using css styling under the selectedElementStyle and excludedElementStyle options, however, this value is needed here for option size calculations so the font-size value should be provided here too
		};
	};
	
	//--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
	// DEF
	
	/*
		Method used to return the value if it is valid (including zeros) or return the default when value is undefined or non existing
	*/
	
	this.def = function (value, def)
	{
		if (value === 0 || (value && value != undefined)) return value;
		else return def;
	}
	
	//--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
	// INIT THE OBJECT AUTOMATICALLY
	
	this.init (CFG);
}
	
/*
####################################################################################################
*/