/*
Select Box Logic 1.0 by Ari N. Karp, September 2008
using mooTools v.1.11, easily convertable to v1.2

Blog: theuiguy.blogspot.com

There is a ui log in here that is not supplied. If you'd like the logger I 
created, please ask.

You are free to use at will, just at least visit my blog and tell me
1. if you are using it and how (for curiosity sake)
2. some feedback on how you changed, enhanced, or minimized it. 

I believe in sharing code to the community, so if you feel the same, then
please share your changes with me to help make the UI a better place.

For help on this, please visit theuiguy.blogspot.com and I will get back asap.
*/



/*
Script: Assets.js
	provides dynamic loading for images, css and javascript files.

License:
	MIT-style license.
*/

var Asset = new Abstract({

	/*
	Property: javascript
		Injects a javascript file in the page.

	Arguments:
		source - the path of the javascript file
		properties - some additional attributes you might want to add to the script element

	Example:
		> new Asset.javascript('/scripts/myScript.js', {id: 'myScript'});
	*/

	javascript: function(source, properties){
		properties = $merge({
			'onload': Class.empty
		}, properties);
		var script = new Element('script', {'src': source}).addEvents({
			'load': properties.onload,
			'readystatechange': function(){
				if (this.readyState == 'complete') this.fireEvent('load');
			}
		});
		delete properties.onload;
		return script.setProperties(properties).inject(document.head);
	},

	/*
	Property: css
		Injects a css file in the page.

	Arguments:
		source - the path of the css file
		properties - some additional attributes you might want to add to the link element

	Example:
		> new Asset.css('/css/myStyle.css', {id: 'myStyle', title: 'myStyle'});
	*/

	css: function(source, properties){
		return new Element('link', $merge({
			'rel': 'stylesheet', 'media': 'screen', 'type': 'text/css', 'href': source
		}, properties)).inject(document.head);
	},

	/*
	Property: image
		Preloads an image and returns the img element. does not inject it to the page.

	Arguments:
		source - the path of the image file
		properties - some additional attributes you might want to add to the img element

	Example:
		> new Asset.image('/images/myImage.png', {id: 'myImage', title: 'myImage', onload: myFunction});

	Returns:
		the img element. you can inject it anywhere you want with <Element.injectInside>/<Element.injectAfter>/<Element.injectBefore>
	*/

	image: function(source, properties){
		properties = $merge({
			'onload': Class.empty,
			'onabort': Class.empty,
			'onerror': Class.empty
		}, properties);
		var image = new Image();
		image.src = source;
		var element = new Element('img', {'src': source});
		['load', 'abort', 'error'].each(function(type){
			var event = properties['on' + type];
			delete properties['on' + type];
			element.addEvent(type, function(){
				this.removeEvent(type, arguments.callee);
				event.call(this);
			});
		});
		if (image.width && image.height) element.fireEvent('load', element, 1);
		return element.setProperties(properties);
	},

	/*
	Property: images
		Preloads an array of images (as strings) and returns an array of img elements. does not inject them to the page.

	Arguments:
		sources - array, the paths of the image files
		options - object, see below

	Options:
		onComplete - a function to execute when all image files are loaded in the browser's cache
		onProgress - a function to execute when one image file is loaded in the browser's cache

	Example:
		(start code)
		new Asset.images(['/images/myImage.png', '/images/myImage2.gif'], {
			onComplete: function(){
				alert('all images loaded!');
			}
		});
		(end)

	Returns:
		the img elements as $$. you can inject them anywhere you want with <Element.injectInside>/<Element.injectAfter>/<Element.injectBefore>
	*/

	images: function(sources, options){
		options = $merge({
			onComplete: Class.empty,
			onProgress: Class.empty
		}, options);
		if (!sources.push) sources = [sources];
		var images = [];
		var counter = 0;
		sources.each(function(source){
			var img = new Asset.image(source, {
				'onload': function(){
					options.onProgress.call(this, counter, source);
					counter++;
					if (counter == sources.length) options.onComplete(this);  //ui.ignore, adding a refernece to the obj;
				}
			});
			images.push(img);
		});
		return new Elements(images);
	}

});

Element.extend({

	/*
	Property: set
		you can set events, styles and properties with this shortcut. same as calling new Element.
	*/

	set: function(props){
		for (var prop in props){
			var val = props[prop];
			switch(prop){
				case 'styles': this.setStyles(val); break;
				case 'events': if (this.addEvents) this.addEvents(val); break;
				case 'properties': this.setProperties(val); break;
				default: this.setProperty(prop, val);
			}
		}
		return this;
	},

	inject: function(el, where){
		el = $(el);
		switch(where){
			case 'before': el.parentNode.insertBefore(this, el); break;
			case 'after':
				var next = el.getNext();
				if (!next) el.parentNode.appendChild(this);
				else el.parentNode.insertBefore(this, next);
				break;
			case 'top':
				var first = el.firstChild;
				if (first){
					el.insertBefore(this, first);
					break;
				}
			default: el.appendChild(this);
		}
		return this;
	},

	/*
	Property: injectBefore
		Inserts the Element before the passed element.

	Arguments:
		el - an element reference or the id of the element to be injected in.

	Example:
		>html:
		><div id="myElement"></div>
		><div id="mySecondElement"></div>
		>js:
		>$('mySecondElement').injectBefore('myElement');
		>resulting html:
		><div id="mySecondElement"></div>
		><div id="myElement"></div>
	*/

	injectBefore: function(el){
		return this.inject(el, 'before');
	},

	/*
	Property: injectAfter
		Same as <Element.injectBefore>, but inserts the element after.
	*/

	injectAfter: function(el){
		return this.inject(el, 'after');
	},

	/*
	Property: injectInside
		Same as <Element.injectBefore>, but inserts the element inside.
	*/

	injectInside: function(el){
		return this.inject(el, 'bottom');
	},

	/*
	Property: injectTop
		Same as <Element.injectInside>, but inserts the element inside, at the top.
	*/

	injectTop: function(el){
		return this.inject(el, 'top');
	},

	/*
	Property: adopt
		Inserts the passed elements inside the Element.

	Arguments:
		accepts elements references, element ids as string, selectors ($$('stuff')) / array of elements, array of ids as strings and collections.
	*/

	adopt: function(){
		var elements = [];
		$each(arguments, function(argument){
			elements = elements.concat(argument);
		});
		$$(elements).inject(this);
		return this;
	},

	/*
	Property: remove
		Removes the Element from the DOM.

	Example:
		>$('myElement').remove() //bye bye
	*/

	remove: function(){
		return this.parentNode.removeChild(this);
	},

	/*
	Property: clone
		Clones the Element and returns the cloned one.

	Arguments:
		contents - boolean, when true the Element is cloned with childNodes, default true

	Returns:
		the cloned element

	Example:
		>var clone = $('myElement').clone().injectAfter('myElement');
		>//clones the Element and append the clone after the Element.
	*/

	clone: function(contents){
		var el = $(this.cloneNode(contents !== false));
		if (!el.$events) return el;
		el.$events = {};
		for (var type in this.$events) el.$events[type] = {
			'keys': $A(this.$events[type].keys),
			'values': $A(this.$events[type].values)
		};
		return el.removeEvents();
	},

	/*
	Property: replaceWith
		Replaces the Element with an element passed.

	Arguments:
		el - a string representing the element to be injected in (myElementId, or div), or an element reference.
		If you pass div or another tag, the element will be created.

	Returns:
		the passed in element

	Example:
		>$('myOldElement').replaceWith($('myNewElement')); //$('myOldElement') is gone, and $('myNewElement') is in its place.
	*/

	replaceWith: function(el){
		el = $(el);
		this.parentNode.replaceChild(el, this);
		return el;
	},

	/*
	Property: appendText
		Appends text node to a DOM element.

	Arguments:
		text - the text to append.

	Example:
		><div id="myElement">hey</div>
		>$('myElement').appendText(' howdy'); //myElement innerHTML is now "hey howdy"
	*/

	appendText: function(text){
		this.appendChild(document.createTextNode(text));
		return this;
	},

	/*
	Property: hasClass
		Tests the Element to see if it has the passed in className.

	Returns:
		true - the Element has the class
		false - it doesn't

	Arguments:
		className - string; the class name to test.

	Example:
		><div id="myElement" class="testClass"></div>
		>$('myElement').hasClass('testClass'); //returns true
	*/

	hasClass: function(className){
		return this.className.contains(className, ' ');
	},

	/*
	Property: addClass
		Adds the passed in class to the Element, if the element doesnt already have it.

	Arguments:
		className - string; the class name to add

	Example:
		><div id="myElement" class="testClass"></div>
		>$('myElement').addClass('newClass'); //<div id="myElement" class="testClass newClass"></div>
	*/

	addClass: function(className){
		if (!this.hasClass(className)) this.className = (this.className + ' ' + className).clean();
		return this;
	},

	/*
	Property: setClass
		Sets the passed in class to the Element ui.ignore: added 5.8.08

	Arguments:
		className - string; the class name to add

	Example:
		><div id="myElement" class="testClass"></div>
		>$('myElement').setClass('newClass'); //<div id="myElement" class="newClass"></div>
	*/

	setClass: function(className){
		this.className = className;
		return this;
	},
		
	/*
	Property: removeClass
		Works like <Element.addClass>, but removes the class from the element.
	*/

	removeClass: function(className){
		this.className = this.className.replace(new RegExp('(^|\\s)' + className + '(?:\\s|$)'), '$1').clean();
		return this;
	},

	/*
	Property: toggleClass
		Adds or removes the passed in class name to the element, depending on if it's present or not.

	Arguments:
		className - the class to add or remove

	Example:
		><div id="myElement" class="myClass"></div>
		>$('myElement').toggleClass('myClass');
		><div id="myElement" class=""></div>
		>$('myElement').toggleClass('myClass');
		><div id="myElement" class="myClass"></div>
	*/

	toggleClass: function(className){
		return this.hasClass(className) ? this.removeClass(className) : this.addClass(className);
	},

	/*
	Property: setStyle
		Sets a css property to the Element.

		Arguments:
			property - the property to set
			value - the value to which to set it; for numeric values that require "px" you can pass an integer

		Example:
			>$('myElement').setStyle('width', '300px'); //the width is now 300px
			>$('myElement').setStyle('width', 300); //the width is now 300px
	*/

	setStyle: function(property, value){
		switch(property){
			case 'opacity': return this.setOpacity(parseFloat(value));
			case 'float': property = (window.ie) ? 'styleFloat' : 'cssFloat';
		}
		property = property.camelCase();
		switch($type(value)){
			case 'number': if (!['zIndex', 'zoom'].contains(property)) value += 'px'; break;
			case 'array': value = 'rgb(' + value.join(',') + ')';
		}		
		this.style[property] = value;
		return this;
	},

	/*
	Property: setStyles
		Applies a collection of styles to the Element.

	Arguments:
		source - an object or string containing all the styles to apply. When its a string it overrides old style.

	Examples:
		>$('myElement').setStyles({
		>	border: '1px solid #000',
		>	width: 300,
		>	height: 400
		>});

		OR

		>$('myElement').setStyles('border: 1px solid #000; width: 300px; height: 400px;');
	*/

	setStyles: function(source){
		switch($type(source)){
			case 'object': Element.setMany(this, 'setStyle', source); break;
			case 'string': this.style.cssText = source;
		}
		return this;
	},

	/*
	Property: setOpacity
		Sets the opacity of the Element, and sets also visibility == "hidden" if opacity == 0, and visibility = "visible" if opacity > 0.

	Arguments:
		opacity - float; Accepts values from 0 to 1.

	Example:
		>$('myElement').setOpacity(0.5) //make it 50% transparent
	*/

	setOpacity: function(opacity){
		if (opacity == 0){
			if (this.style.visibility != "hidden") this.style.visibility = "hidden";
		} else {
			if (this.style.visibility != "visible") this.style.visibility = "visible";
		}
		if (!this.currentStyle || !this.currentStyle.hasLayout) this.style.zoom = 1;
		if (window.ie) this.style.filter = (opacity == 1) ? '' : "alpha(opacity=" + opacity * 100 + ")";
		this.style.opacity = this.$tmp.opacity = opacity;
		return this;
	},

	/*
	Property: getStyle
		Returns the style of the Element given the property passed in.

	Arguments:
		property - the css style property you want to retrieve

	Example:
		>$('myElement').getStyle('width'); //returns "400px"
		>//but you can also use
		>$('myElement').getStyle('width').toInt(); //returns 400

	Returns:
		the style as a string
	*/

	getStyle: function(property){
		property = property.camelCase();
		var result = this.style[property];
		if (!$chk(result)){
			if (property == 'opacity') return this.$tmp.opacity;
			result = [];
			for (var style in Element.Styles){
				if (property == style){
					Element.Styles[style].each(function(s){
						var style = this.getStyle(s);
						result.push(parseInt(style) ? style : '0px');
					}, this);
					if (property == 'border'){
						var every = result.every(function(bit){
							return (bit == result[0]);
						});
						return (every) ? result[0] : false;
					}
					return result.join(' ');
				}
			}
			if (property.contains('border')){
				if (Element.Styles.border.contains(property)){
					return ['Width', 'Style', 'Color'].map(function(p){
						return this.getStyle(property + p);
					}, this).join(' ');
				} else if (Element.borderShort.contains(property)){
					return ['Top', 'Right', 'Bottom', 'Left'].map(function(p){
						return this.getStyle('border' + p + property.replace('border', ''));
					}, this).join(' ');
				}
			}
			if (document.defaultView) result = document.defaultView.getComputedStyle(this, null).getPropertyValue(property.hyphenate());
			else if (this.currentStyle) result = this.currentStyle[property];
		}
		if (window.ie) result = Element.fixStyle(property, result, this);
		if (result && property.test(/color/i) && result.contains('rgb')){
			return result.split('rgb').splice(1,4).map(function(color){
				return color.rgbToHex();
			}).join(' ');
		}
		return result;
	},

	/*
	Property: getStyles
		Returns an object of styles of the Element for each argument passed in.
		Arguments:
		properties - strings; any number of style properties
	Example:
		>$('myElement').getStyles('width','height','padding');
		>//returns an object like:
		>{width: "10px", height: "10px", padding: "10px 0px 10px 0px"}
	*/

	getStyles: function(){
		return Element.getMany(this, 'getStyle', arguments);
	},

	walk: function(brother, start){
		brother += 'Sibling';
		var el = (start) ? this[start] : this[brother];
		while (el && $type(el) != 'element') el = el[brother];
		return $(el);
	},

	/*
	Property: getPrevious
		Returns the previousSibling of the Element, excluding text nodes.

	Example:
		>$('myElement').getPrevious(); //get the previous DOM element from myElement

	Returns:
		the sibling element or undefined if none found.
	*/

	getPrevious: function(){
		return this.walk('previous');
	},

	/*
	Property: getNext
		Works as Element.getPrevious, but tries to find the nextSibling.
	*/

	getNext: function(){
		return this.walk('next');
	},

	/*
	Property: getFirst
		Works as <Element.getPrevious>, but tries to find the firstChild.
	*/

	getFirst: function(){
		return this.walk('next', 'firstChild');
	},

	/*
	Property: getLast
		Works as <Element.getPrevious>, but tries to find the lastChild.
	*/

	getLast: function(){
		return this.walk('previous', 'lastChild');
	},

	/*
	Property: getParent
		returns the $(element.parentNode)
	*/

	getParent: function(){
		return $(this.parentNode);
	},

	/*
	Property: getChildren
		returns all the $(element.childNodes), excluding text nodes. Returns as <Elements>.
	*/

	getChildren: function(){
		return $$(this.childNodes);
	},

	/*
	Property: hasChild
		returns true if the passed in element is a child of the $(element).
	*/

	hasChild: function(el){
		return !!$A(this.getElementsByTagName('*')).contains(el);
	},

	/*
	Property: getProperty
		Gets the an attribute of the Element.

	Arguments:
		property - string; the attribute to retrieve

	Example:
		>$('myImage').getProperty('src') // returns whatever.gif

	Returns:
		the value, or an empty string
	*/

	getProperty: function(property){
		var index = Element.Properties[property];
		if (index) return this[index];
		var flag = Element.PropertiesIFlag[property] || 0;
		if (!window.ie || flag) return this.getAttribute(property, flag);
		var node = this.attributes[property];
		return (node) ? node.nodeValue : null;
	},

	/*
	Property: removeProperty
		Removes an attribute from the Element

	Arguments:
		property - string; the attribute to remove
	*/

	removeProperty: function(property){
		var index = Element.Properties[property];
		if (index) this[index] = '';
		else this.removeAttribute(property);
		return this;
	},

	/*
	Property: getProperties
		same as <Element.getStyles>, but for properties
	*/

	getProperties: function(){
		return Element.getMany(this, 'getProperty', arguments);
	},

	/*
	Property: setProperty
		Sets an attribute for the Element.

	Arguments:
		property - string; the property to assign the value passed in
		value - the value to assign to the property passed in

	Example:
		>$('myImage').setProperty('src', 'whatever.gif'); //myImage now points to whatever.gif for its source
	*/

	setProperty: function(property, value){
		var index = Element.Properties[property];
		if (index) this[index] = value;
		else this.setAttribute(property, value);
		return this;
	},

	/*
	Property: setProperties
		Sets numerous attributes for the Element.

	Arguments:
		source - an object with key/value pairs.

	Example:
		(start code)
		$('myElement').setProperties({
			src: 'whatever.gif',
			alt: 'whatever dude'
		});
		<img src="whatever.gif" alt="whatever dude">
		(end)
	*/

	setProperties: function(source){
		return Element.setMany(this, 'setProperty', source);
	},

	/*
	Property: setHTML
		Sets the innerHTML of the Element.

	Arguments:
		html - string; the new innerHTML for the element.

	Example:
		>$('myElement').setHTML(newHTML) //the innerHTML of myElement is now = newHTML
	*/

	setHTML: function(){
		this.innerHTML = $A(arguments).join('');
		return this;
	},

	/*
	Property: setText
		Sets the inner text of the Element.

	Arguments:
		text - string; the new text content for the element.

	Example:
		>$('myElement').setText('some text') //the text of myElement is now = 'some text'
	*/

	setText: function(text){
		var tag = this.getTag();
		if (['style', 'script'].contains(tag)){
			if (window.ie){
				if (tag == 'style') this.styleSheet.cssText = text;
				else if (tag ==  'script') this.setProperty('text', text);
				return this;
			} else {
				this.removeChild(this.firstChild);
				return this.appendText(text);
			}
		}
		this[$defined(this.innerText) ? 'innerText' : 'textContent'] = text;
		return this;
	},

	/*
	Property: getText
		Gets the inner text of the Element.
	*/

	getText: function(){
		var tag = this.getTag();
		if (['style', 'script'].contains(tag)){
			if (window.ie){
				if (tag == 'style') return this.styleSheet.cssText;
				else if (tag ==  'script') return this.getProperty('text');
			} else {
				return this.innerHTML;
			}
		}
		return ($pick(this.innerText, this.textContent));
	},

	/*
	Property: getTag
		Returns the tagName of the element in lower case.

	Example:
		>$('myImage').getTag() // returns 'img'

	Returns:
		The tag name in lower case
	*/

	getTag: function(){
		return this.tagName.toLowerCase();
	},

	/*
	Property: empty
		Empties an element of all its children.

	Example:
		>$('myDiv').empty() // empties the Div and returns it

	Returns:
		The element.
	*/

	empty: function(){
		Garbage.trash(this.getElementsByTagName('*'));
		return this.setHTML('');
	}

});



/*****************************************************/
/* UI FACTORY CLASS (THE FIRST CUT)  SEE DEFS BELOW
/*****************************************************/
var selectBoxFactory = new Class({		
	
	options: {
		id : "",
		type : "default",
		coords : null,
		definition : null
	},

	initialize: function(options){	
		this.setOptions(options);
		this.type = this.options.type; // for a module with multiple types, use a comma
		this.coords = this.options.coords; // a json object of position requirements.
		this.id = this.options.id;
		this.definition = this.options.definition;
		this.classSets = this.options.classSets;
		this.result = null; // set by selectBoxFactory that returns, like dialogs	
		this.defaultErrorMessage = "A note of failure from selectBoxFactory:\n\n";
		if(!this.type || !this.definition) return;
		$each(this.type.split(","), function(type, index){		
 			try{
				return this._initSelectBox(type);	
			}catch(e){
				this._alertProblem(e.description);
			}finally{			
				return false;
			}			
		},this)
	},
	
	_initSelectBox : function(type){		
		this._setupSelectBox(type);
		if(this.choices) this._setupChoices();
		if(this.useInfo) this._setupInfo();			
		if(this.useToggle || this.sift) this._setupToggle();			
		if(this.heading) this._setupHeading();		
		if(!this.choices) this._setupSelectBoxStack();				
		if(this.useStates && this.dropdownStack.childNodes.length > 0) this._setupStateButtons(this.uiChoices);			
		if(this.sift && this.type !== "dropdown") this._setupSiftTool();
		this._displaySelectBox();
		this._setupToggleEvents();
		this._setupSelectBoxEvents();		
		this._beginSelectBoxRoutine();
	},
	
	_beginSelectBoxRoutine : function(){ // the very last thing to happen.
		this.dropdown.fireEvent('checkAble');					
		if(this.toggleStyle === "closed"){	// dropdowns will enable based on this finishing.		
			this.uiSelectBoxRep.toggle();
			this.dropdown.style.display = "none";
			this.dropdownStack.style.display = "none";
			if(this.useInfo) this.infoBar.style.display = "none";
			this.uiToggle.fireEvent("click");
		}
	},
	
	_createChoice : function(choice,index){	
		var c = new Element("div",{ // for more than 50 choices, dont use mootools createElement in v1.11, use createElement
			count : index,
			id : choice[0],
			title : choice[1],				
			displayName : choice[2],
			isSelected : choice[3],
			state : choice[4]
		}).setHTML(choice[2]);		
		return c;
	},
	
	_setupChoices : function(){		// should use addchoice.
		$each(this.choices, function(choice, index){ 
			if($(choice[0])) return;
			this._insertChoice(this._createChoice(choice,index));	
		},this);
		// now emulate setting up the stack;
		$each(this.uiChoices, function(choice, index){ 
			if($(choice[0])) return;
			this._setupChoice(choice,this);
			if(this.useStates) this._setupStateButtons([choice]);
		},this);
		// to test add and remove, turn this on:
		// this._addChoice(['Aardvark4','Aardvark4','Aardvark4',false])
		// this._removeChoice('Aardvark4');	
	},
	
	_insertChoice : function(choice){		
		if(this.defaultSort === "ascending"){
			choice.inject(this.dropdownStack);
		}else{
			if(this.dropdownStack.childNodes.length > 0){
				choice.injectBefore(this.dropdownStack.childNodes[0]);
			}else{
				choice.injectTop(this.dropdownStack);
			}			
		}	
		this.uiChoices.push(choice);
	},
	
	_addChoice : function(choice){ // must be an array
		var index = this.dropdownStack.childNodes.length + 1;	
		if(!$(choice[0])){
			this._insertChoice(this._createChoice(choice,index));				
			this._setupChoices();
			if(this.useStates) this._setupStateButtons([$(choice[0])]);
			this._displaySelectBox();
		}
	},

	_removeChoice : function(choice){
		if($(choice)){
			this.uiChoices.remove($(choice))
			this.dropdownStack.removeChild($(choice));
			this._displaySelectBox();
		}
	},
	
	_setupSelectBox : function(type){ // the very first thing to happen.
		/*
		_setup:
			You set up a dropdown by creating a JSON object anywhere. See the html example.
			You have to have a set of HTML divs already built into the page. (I may allow a dynamic creator at some point)
			You don't need to assign this object to a variable in order for this to work, but it helps for later reference.		
		Defaults:
		*/
		this.postponeMouseover = false;	
		this.availableHeightForSelectBox = 0;
		this.showScrollbars = false;
		this.headingHeight = 0;
		this.hBuffer = 0;		
		this.vAlign = "50%";
		this.vAlignImage = (window.ie || window.gecko) ? "20%" : "50%";
		this.selectedNodes = [];
		this.nameArray = [];		
		
		/*
		this.siftTitle 
			the sift title shows when the user mouses over the box. In this version, all the user has to do is begin
			typing. However, you may want to enable a search button. If you do use a search button then you
			will have no room for heatmap filter buttons unless you make the dropdown
			very wide, so be careful what you choose to show.
		*/
		this.siftTitle = "Begin typing to sift results. Use AND (+) 'plus', or OR (|) 'pipe' to extend search. Use (!) 'not' to flip the search."
		
		/*
		this.choices:
			allows you to programmatically decide up front what the choices will be. Your definition must
			be fully defined in this case (since there is no dom container)		
			these MUST be unique! 
			It is an array like so: [id, display, title, selected, state]
			0 : ['Aardvark','Aardvark','Aardvark',false,0],
			1 : ['Aardvark1','Aardvark1','Aardvark1',false,1],
			2 : ['Aardvark2','Aardvark2','Aardvark2',false,2],
			
		this.sort
			how to sort the choices.
				
		*/		
		this.choices = this.definition.choices;
		this.defaultSort = this.definition.defaultSort || false;
		
		/*
		this.states:
			a JSON object, when present, allows the ui to generate state by changing the
			classes (bg color). The way it works is based on states of 1-5, and can 
			be represented any way we want, as long as there is a corresponding class to 1-5
			.uiSelectBoxChoice.uiSelectBoxChoice1...2...3... so forth.
					
		*/		
		this.useStates = this.definition.useStates || false;		
		if(this.useStates){
			this.states = [];
			this.stateButtons = [];
			this.stateColors = this.definition.stateColors; //as many as there are states you want to show.
			this.selectedItemBGColor = this.definition.selectedItemBGColor || "#364F80";
			this.showStateButtons = this.definition.showStateButtons || false;
		}
		/*
		this.classSet:
			4 styles that make the dropdown look nice. Define this in your css. 
			if you don't want a toggle, specify a class anyway. You will be happy you did.
			Defaults are uiSelectBox,uiSelectBoxToggle,uiSelectBoxChoice,uiSelectBoxStack.
			
		this.type:
			the type of dropdown.
		*/
		this.classSet = this.definition.classSets.selectClassSet.split(",") || $(this.id).getAttribute("classSetSelect");
		this.type = type;
		
		/*
		this.container:
			Does the dropdown need to fill a particular container or should it exist by itself on the screen?
			If it needs a container, be sure to add container to the definition.
		
		this.dropdown:
			I am a reference to a container or I am a reference to a standalone object.
			We force this to show as block level element.

		this.dropdownStack:
			dropdownStack we use as the "viewport" for scrolling.
		*/
		this.container = (this.definition.useSelf) ? $(this.id) : $(this.definition.container);
		this.container["originalHeight"] = this.container.clientHeight; // helps ie figure out height.
		this.dropdown = (this.definition.useSelf) ? $(this.id) : $$('#' + this.container.id + ' div.' + this.classSet[0])[0];
		this.dropdown.style.display = "block"; 
		this.dropdownStack = this.dropdown.firstChild; 
				
		/*
		this.heading :
			Does this dropdown fit into a container that has some kind of heading?
			If it does, then be sure to let the code add the dimensions of the heading to the logic of how tall that dropdown should be.
			If you are using the dropdown by itself (not in a container) then this is null.		
		*/
		this.heading = (this.definition.useSelf) ? null : this.container.moduleHeading;

		/*
		this.defaultToggleColor :
			If specified, it is what the toggle background color will be if the dropdown class is not used.
			Meaning, if you ask for sift, then the arrow graphic is not necessary.
			
		*/		
		this.defaultToggleColor = this.definition.defaultToggleColor || "#DBE2F0";
		
		/*
		this.maxChoices:
			Comes from definition or the object in the DOM.
			This is how many choices we should show before creating a scrollbar. Also used in calcs.			
		*/
		this.maxChoices = this.definition.maxSize || this.dropdown.getAttribute("maxSize");		

		//this.maxChoices = (this.maxChoices === 0) ? 1 : this.maxChoices; // what happens when max is 0, then its a dropdown.	
	
		/*
		this.useInfo:
			A count of items selected.
		*/		
		//this.useInfo = (this.definition.useInfo || this.dropdown.getAttribute("info") === "true") ? true : false; // force it to be boolean
		
		/*
		this.toggleStyle:
			Normally, the type of dropdown tells us how to show the toggle.
			This is here to allow the user the options of showing a toggle open or closed by default if not a dropdown.
			Values are open, closed, none
			
		this.useToggle:
			Internal code to help with calcs, etc.
		
		this.toggleLabel
			What to show in the toggle by default, if nothing has been chosen programmatically.
		
		this.toggleLabelChosen
			What to show in the toggle when something is selected.
		*/				
		this.toggleStyle = this.definition.toggleStyle || this.dropdown.getAttribute("toggleStyle"); 
		this.toggleLabel = this.definition.toggleLabel || this.dropdown.getProperty("toggleLabel") ||  "";		
		this.toggleLabelChosen = this.definition.introMessage || "<font style='font-size:11px;color:#000;'>";
		this.useToggle = ((this.toggleStyle !== undefined && this.toggleStyle !== "none" && this.toggleStyle !== null) || type==="dropdown") ? true : false;

		/*
		this.useEraser:
			Puts a little eraser icon that the use can click to clear a selection.
			If you don't use this, a clear image is used to help maintain dropdown size.
		*/
		this.useEraser = (this.definition.useEraser || this.dropdown.getAttribute("useEraser") === "true") ? true : false; // force it to be boolean
		
		/*
		this.sift:
			Adds a neat widget which acts as a data filter.
			Even though any select type can take sift, it really is for "open" select boxes (size > 1)
		*/
		this.sift = (this.definition.useSift || this.dropdown.getAttribute("useSift") === "true") ? true : false; // force it to be boolean
		
		/*
		this.extend:
			offers you more flexibility in case you want to override the width for one dropdown in specific cases.
		*/
		this.extend = this.definition.extend || 0;
		this.uiChoices = [];   
		if(!this.dropdown || !this.container || this.dropdownStack.childNodes.length === 0) return;	 
	},
	
	_setupInfo : function(){
		this.dropdown.knownSelected = 0;
		this.infoBar = new Element("div",{
			id : 'divUpdate' + this.dropdown.id,
		 	'class' : 'selectUpdate'
		}).inject(this.dropdown,'after');
		this.infoBar.setHTML(this.dropdown.knownSelected  + " selected");	
	},
	
	_setupHeading : function(){
		this.headingHeight = (this.heading.style.marginBottom) ? this.heading.style.marginBottom.toInt() : 0; 	
		this.headingHeight += (this.heading.style.paddingTop) ?  this.heading.style.paddingTop.toInt() : 0;  			
	},
			
	_setupToggle : function(){	
		var p = this;
		this.header = new Element("div",{'class':'toggleText'});		
		this.clearIconContainer = new Element("div",{styles:{'display':'inline'}});	
		this.uiToggle = new Element("div",{
			id : 'uiSelectBoxToggle' + this.id,
			'clicked' : 0,
			'class' :  this.classSet[1], 
			'toggleCount' : 0
		});			
		this.uiToggle.inject(this.dropdown,"before");	
		this.cImage = (this.useEraser) ? app.images[1].clone(true) : app.images[0].clone(true);			
		this.cIWidth = (this.useEraser) ? 30 : 6;			
		this.clearIcon  = new Element(this.cImage,{ 
			id: 'clearButton' + this.id, 
			width : this.cIWidth, 
			height : 20, // for ie
			events : {
				'click': function(event){			
					if(p.currentStateButtonInFocus) p.currentStateButtonInFocus.fireEvent("click");
					p.uiChoices.forEach(function(c, index){
						if(c.className ===  p.classSet[2] + ' '+ p.classSet[2]+'Selected'){ 
							if(p.type !== "selectmultiple" || p.type==="dropdown"){
								this.lastunselected = c.others.filter(function(item, i){return item.className === (p.classSet[2])});	/* only one other item could have been selected. grab it */				
								this.lastunselected = this.lastunselected[0];
								//c.cloneEvents(this.lastunselected,'mouseover').cloneEvents(this.lastunselected,'mouseout');
			     			}else{
								//c.cloneEvents(c.cloneMe,'mouseover').cloneEvents(c.cloneMe,'mouseout');
							}
							c.setClass(p.classSet[2] + ' '+p.classSet[2]); 
							if(p.useStates) c.style.backgroundColor = p.stateColors[c.getAttribute("state").toInt()];
							c.selected = false;
							c.setProperty("isSelected","false");						
							p.dropdown.knownSelected = 0;
						}			
						if(p.useInfo) p.infoBar.setHTML(p.dropdown.knownSelected + " selected");
						if(p.dropdownStack.firstChild.tagName === "IMG") p.uiToggle.childNodes[1].style.verticalAlign = p.vAlign;
						if(p.type === "dropdown") p.uiToggle.childNodes[1].setHTML(p.toggleLabel);
						if(event) event.cancelBubble = true;
					});	
				}											
			}
		});
		this.clearIconContainer.inject(this.uiToggle);	
		this.header.inject(this.uiToggle);
		(this.sift) ? new Element("input",{title: this.siftTitle, id:'siftBox'+this.id,type:"text",'class':'siftBox'}).injectTop(this.header) : this.header.setHTML(this.toggleLabel);				
		if(this.sift) this.uiToggle.style.background = this.defaultToggleColor; 
		this.clearIcon.inject(this.clearIconContainer);
		if(this.useStates){
			this.statesHeader = new Element("div",{'class':'statesHeader'});
			this.statesHeader.inject(this.header);
		}
		//alert(this.statesHeader.offsetWidth)
		if(window.ie){ // YAIEH
			this.clearIcon.style.verticalAlign = "0%"; 
			if(this.sift){ this.header.style.verticalAlign = "20%"; }else{ this.header.style.verticalAlign = "40%"; }
		}else{
			this.header.style.verticalAlign = this.vAlign;
		}		
	},
		
	_setupStateButtons : function(obj){		
		var p = this; // in lieu of enclosure.
		obj.forEach(function(o, index){			
			this.states.include(o.getAttribute("state").toInt());
			o.setAttribute("data",o.getAttribute("data") + "|" + o.getAttribute("state"));// for sifting.
		},this);		
		if(this.showStateButtons){
			this.states.forEach(function(state, index){			
				var sElement = new Element("div",{id:'state'+index,'class':'statesHeaderItem unPushedHeaderItem'}).setHTML("&nbsp;&nbsp;&nbsp;").inject(this.statesHeader);					
				this.stateButtons.push(sElement);
				this.statesHeader.style.width = (this.statesHeader.offsetWidth + sElement.offsetWidth) + "px";				
				sElement.style.backgroundColor = this.stateColors[index];				
			},this);				
			this.stateButtons.forEach(function(stateButton, index){			
				stateButton.others = this.stateButtons.filter(function(item, i){return item.id !== stateButton.id});		
				stateButton.addEvent('click', function(e){												
					p.currentStateButtonInFocus = stateButton;
					if(this.selected === false || this.selected === undefined){
						$('siftBox'+p.id).value = index;
						this.setClass("statesHeaderItem pushedHeaderItem");
						this.selected = true;
					}else{
						$('siftBox'+p.id).value = "";							
						this.setClass("statesHeaderItem unPushedHeaderItem");
						this.selected = false;
					}
					$each(this.others, function(other, index){		
						other.setClass("statesHeaderItem unPushedHeaderItem");
					},this);
				p._captureKeys();
				});
			},this);
		}	
	},
	
	_setupToggleEvents : function(){
		var p = this; // binds p to this in lieu of having an enclosure.
		if(this.useToggle){		
			this.timeOut = 8 * this.actualCountOfChoices;
			this.bothTime = 300+ this.timeOut; 
			this.uiSelectBoxRep = new Fx.Slide(this.dropdown.id);
			this.fx = new Fx.Styles(this.uiToggle, {duration: 300, wait:false, transition: Fx.Transitions.Circ.easeInOut});		
			this.dropdownStack.style.overflowX = "hidden";
			if(window.gecko) this.dropdownStack.style.overflowY = "hidden";
			this.uiToggle.addEvent('click', function(e){
				/* 
				Special considerations for FF (gecko) are peppered in.
				The reason is that setting the scrollbar to auto causes the rendering engine
				to "flash" as it passes underneath a positioned div. All these timeouts
				allow the overflow to be set on a timer, so it never shows in FF passing under the div.
				*/
				if(p.maxChoices > 0){	
					if(p.showScrollbars){							
						if(window.gecko){ 	
							window.setTimeout(function(){
								//alert(p.uiToggle.getAttribute("clicked").toInt())
								if(p.uiToggle.getAttribute("clicked").toInt() % 2 === 0){
									if(p.dropdownStack.style.display === "block") p.dropdownStack.style.overflowY = 'auto';
								}
							},p.bothTime);
						}else{
							p.dropdownStack.style.overflowY = 'auto';
						}																																						
					}
				}	
				if(window.gecko){					
					if(p.dropdownStack.style.overflowY === 'auto'){
						if(this.getAttribute("clicked").toInt() % 2 === 0) p.dropdownStack.style.overflowY = "hidden";
					}				
				}
				window.setTimeout(function(){ 
					if(p.toggleStyle === "closed"){
						p.dropdown.style.display = "block";	
						p.dropdownStack.style.display = "block";	
						if(p.useInfo) p.infoBar.style.display = "block";
					}			
					if(p.dropdown.getProperty("isDisabled") === "true") p.dropdown.fireEvent("enable");		
				},(p.bothTime+150));							
				p.uiSelectBoxRep.toggle();		
				if(window.gecko) this.setAttribute("clicked",this.getAttribute("clicked").toInt()+1);
			});	
		}else{
			if(this.showScrollbars){
				this.dropdownStack.style.overflowY = 'auto';
				this.dropdownStack.style.overflowX = 'hidden';				
			}
		}		
	},
	
	_setupSelectBoxStack : function(){				
		$each(this.dropdownStack.childNodes, function(c, index){
			if(c.id !== undefined) this.uiChoices.push(new Element(c,{id:c.id}));
		},this);
	},
	
	_setupSelectBoxEvents : function(){		
		var p = this; // binds p to this in lieu of having an enclosure.
		this.dropdown.setProperty('tabIndex', '-1'); // allows key focus to work in ff;
		this.dropdown.addEvent('keydown', function(e){
			e.cancelBubble = true;
			e = new Event(e);
			var instanceOf = false;
			p.nameArray.forEach(function(c, index){
				$(c).fireEvent("mouseout");
				if(e.key === c.toLowerCase().charAt(0) && instanceOf === false){
					instanceOf = true;
					$(c).fireEvent("graze");					
					this.firstChild.scrollTop = $(c).offsetTop - $(c).offsetHeight;
				}				
			},this);
		});	
		this.dropdown.addEvent('mouseover', function(e){
			this.focus(); // ensures keys fire on correct dropdown.
		});			
		this.dropdown.addEvent('clearSelections', function(event){			
			p.uiChoices.forEach(function(c, index){
				c.setClass(p.classSet[2]);
				c.selected = false;
				c.setProperty("isSelected","false");	
			});	
			this.knownSelected = 0;
			if(p.useInfo) infoBar.setHTML(this.knownSelected + " selected");
			if(p.useToggle && p.uiToggle.childNodes[1].firstChild.tagName !== "IMG") p.uiToggle.childNodes[1].setHTML(p.toggleLabel);			
			this.fireEvent('shutDown');
		});		
		this.dropdown.addEvent('shutDown', function(event){			
			if(p.useToggle) if(this.firstChild.display === "block") p.uiSelectBoxRep.toggle();
		});
		this.dropdown.addEvent('disable', function(e){
			this.setProperty("isDisabled","true");
			this.setClass(p.classSet[0]+'Disabled');
			if(p.useToggle) p.uiToggle.setClass(p.classSet[1]+'Disabled');
			p.uiChoices.forEach(function(c, index){
				c.removeEvents();
				c.selected = false;
				c.setProperty("isSelected","false");
				c.setClass(p.classSet[2] + ' '+p.classSet[2]+'Disabled');
			});
		});	
		this.dropdown.addEvent('enable', function(e){			
			this.setClass(p.classSet[0]);	
			this.setProperty("isDisabled","false");
			if(p.useToggle) p.uiToggle.setClass(p.classSet[1]);			
			if(!p.choices){
				p.uiChoices.forEach(function(c, index){			
					p._setupChoice(c,p);
					if(c.getProperty("isSelected") === "true") c.fireEvent('click',c);
				});			
			}
		});	
		this.dropdown.addEvent('checkAble', function(e){ // will disable dropdowns at first.
			if((p.type === "dropdown" && p.toggleStyle==="closed")|| this.getProperty("isDisabled") === "true" || this.getProperty("isDisabled") === null){				
				this.fireEvent('disable');
			}else{
				this.fireEvent('enable');
			}				
		});							
	},

	_setupChoice : function(c,p){		
		c.setClass(p.classSet[2] + ' '+p.classSet[2]); 		
		if(this.useStates) c.style.backgroundColor = p.stateColors[c.getAttribute("state").toInt()];		
		p.nameArray.push(c.id);		
		if(c.getAttribute("data")) c.setAttribute("data", c.getAttribute("data") + "|" + c.id); // forces id to be there.
		c.data = (c.getAttribute("data")) ? $A(c.getAttribute("data").split("|")) : [c.id]; //For more comprehensive searches, you can use... data. $A(c.getAttribute("data").split("||")); 
		c.others = p.uiChoices.filter(function(item, i){return item.id !== c.id});		
		c.addEvent('graze', function(e){
			if(!this.selected && !p.postponeMouseover){ this.setClass(p.classSet[2] + ' '+p.classSet[2]+'Graze');}
		});
		c.addEvent('mouseover', function(e){
			if(!this.selected && !p.postponeMouseover){ this.setClass(p.classSet[2] + ' '+p.classSet[2]+'Active');}
		});
		c.addEvent('mouseout', function(e){
			if(!this.selected) this.setClass(p.classSet[2] + ' '+p.classSet[2]);
			if(this.useStates) this.style.backgroundColor = p.stateColors[this.getAttribute("state").toInt()];
		});
		c.addEvent('click', function(e){
			var ctl = true; // this is always true (programmatic or user) unless user clicks. (this way we can show saved items)
			var sft = false;
			if(e && e.type !== undefined) ctl = e.ctrlKey;
			if(e && e.type !== undefined) sft = e.shiftKey;
			p._setupSelectBoxItemEvents(this,sft,ctl);
		});		
	},
	
	_setupSelectBoxItemEvents : function(node,shift,ctrl){
		//alert(node.id + " // ctrl " + ctrl + " // shift " + shift)	
		this.clearSelection = true;		
		this.dropdown.highlightNode = node;
		if(node.selected && !ctrl){				
			this._resetSelectBoxItem(node);														
		}else{
			if(!(shift) && this.dropdown.sourceNode === undefined){				
				this._setSelectBoxItem(node,false);
			}else{
				if(shift && this.dropdown.sourceNode !== undefined && this.type === "selectmultiple"){ 								
					this._shiftSelectBoxItem(node);
				}else{
					if(ctrl && this.dropdown.sourceNode !== undefined && this.type === "selectmultiple"){
						this._controlSelectBoxItem(node);
					}else{
						if(this.dropdown.sourceNode !== undefined){
							this._setSelectBoxItem(node,true);
						}
					}
				}			
			}
		}
		if(!(ctrl) && !(shift)){
			node.lastselected = node.others.filter(function(item, i){return item.className === (this.classSet[2] + ' '+this.classSet[2]+'Selected')},this);
		}
		if(this.type === "dropdown"){
			this.uiToggle.fireEvent('click');
			this.uiToggle.childNodes[1].empty();
			if(node.firstChild.tagName !== undefined){
				this.uiToggle.childNodes[1].style.verticalAlign = this.vAlignImage; // what does safari need?
				var newC = new Element(node.firstChild.tagName,{src:node.firstChild.src});
				newC.inject(this.uiToggle.childNodes[1]);
			}else{
				var printId = (this.toggleLabelChosen === "") ? ""+node.getText() : node.getAttribute("title") +"</font>";								
// 				this.uiToggle.childNodes[1].style.verticalAlign = this.vAlign;
				this.uiToggle.childNodes[1].setHTML(this.toggleLabelChosen + printId);							
			}
			this.uiToggle.title = node.id;
		}
		if(this.clearSelection) $("focusBox").focus();
		if(node.lastselected && node.lastselected.length > 0 && !ctrl && !shift){
			node.lastselected = node.lastselected[0];
			node.lastselected.setClass(p.classSet[2] + ' '+p.classSet[2]);
			if(this.useStates) node.style.backgroundColor = p.stateColors[node.getAttribute("state").toInt()];	
			//node.lastselected.cloneEvents(node.cloneMe,'mouseover').cloneEvents(node.cloneMe,'mouseout');
			node.lastselected.selected = false;
			node.lastselected.setProperty("isSelected","false");
			if(this.useInfo) $('divUpdate' + this.dropdown.id).setHTML(--this.dropdown.knownSelected + " selected");
		}		
	},
	
	_resetSelectBoxItem : function(node){
		//log.out("<b>Scenario E</b>: Reset")
		this.dropdown.sourceNode = node;
		node.lastselected = [node];		
		node.setProperty("isSelected",node.selected.toString());		
		this.selectedNodes = [];
		this.selectedNodes.push(node);
		if(this.type === "selectmultiple"){
			$each(node.others, function(n, index){				
				if(n.selected){				
					n.selected = false;
					n.setClass(this.classSet[2] + ' '+this.classSet[2]);
					if(this.useStates) n.style.backgroundColor = this.stateColors[n.getAttribute("state").toInt()];	
					n.setProperty("isSelected",n.selected.toString());		
					if(this.useInfo) $('divUpdate' + this.dropdown.id).setHTML(--this.dropdown.knownSelected + " selected");
				 }
			},this);
		}			
	},
	
	_setSelectBoxItem : function(node,isNodeDefined){
	//log.out("<b>Scenario B</b>: User clicked subsequent times, but never use shift or ctrl key.")		
		this.dropdown.sourceNode = node;						
		node.setClass(this.classSet[2] + ' '+this.classSet[2]+'Selected');
		node.style.backgroundColor = this.selectedItemBGColor;
		node.selected = true;
		this.clearSelection = false;
		this.selectedNodes = [];
		node.setProperty("isSelected",node.selected.toString());		
		//node.cloneMe = new Element("div",{}).cloneEvents(node,'mouseover').cloneEvents(node,'mouseout');		
		//node.removeEvents('mouseout').removeEvents('mouseover');
		if(this.useInfo) $('divUpdate' + this.dropdown.id).setHTML(++this.dropdown.knownSelected + " selected");

		if(isNodeDefined){
			$each(node.others, function(n, index){				
				if(n.selected){
					n.selected = false;
					n.setClass(this.classSet[2] + ' '+this.classSet[2]);
					if(this.useStates) n.style.backgroundColor = this.stateColors[n.getAttribute("state").toInt()];	
					n.setProperty("isSelected",n.selected.toString());		
					if(this.useInfo) $('divUpdate' + this.dropdown.id).setHTML(--this.dropdown.knownSelected + " selected");
				 }
			},this);		
		}else{
			this.selectedNodes.push(node);
		}
	},

	_shiftSelectBoxItem : function(node){
		//log.out("<b>Scenario B</b>: User is shift clicking")
		if(this.useInfo) this.dropdown.knownSelected = 0;
		this.selectedNodes = [];
		this._rangeSelections(); 		
		$each(this.selectedNodes, function(node, index){		
			node.selected = true;
			node.setProperty("isSelected",node.selected.toString());
			node.setClass(this.classSet[2] + ' '+this.classSet[2]+'Selected');
			node.style.backgroundColor = this.selectedItemBGColor;
			//node.cloneMe = new Element("div",{}).cloneEvents(node,'mouseover').cloneEvents(node,'mouseout');
			//node.removeEvents('mouseout').removeEvents('mouseover');
			if(this.useInfo) $('divUpdate' + this.dropdown.id).setHTML(++this.dropdown.knownSelected + " selected");
		},this);		
	},
	
	_controlSelectBoxItem : function(node){
		if(node.selected){
			//log.out("<b>Scenario C.1</b>: User clicked and used CTRl, but item was selected. So unselecting it.")
			node.selected = false;
			node.setClass(this.classSet[2] + ' '+this.classSet[2]);
			if(this.useStates) node.style.backgroundColor = this.stateColors[node.getAttribute("state").toInt()];	
			node.setProperty("isSelected",node.selected.toString());		
			this.selectedNodes.remove(node);
			if(this.useInfo) $('divUpdate' + this.dropdown.id).setHTML(--this.dropdown.knownSelected + " selected");
		}else{
			//log.out("<b>Scenario C.2</b>: User clicked and used CTRl, but item was unselected. So selecting it.")
			node.selected = true;
			node.setProperty("isSelected",node.selected.toString());
			this.selectedNodes.push(node);
			node.setClass(this.classSet[2] + ' '+this.classSet[2]+'Selected');
			node.style.backgroundColor = this.selectedItemBGColor;
			//node.cloneMe = new Element("div",{}).cloneEvents(node,'mouseover').cloneEvents(node,'mouseout');
			//node.removeEvents('mouseout').removeEvents('mouseover');
			if(this.useInfo) $('divUpdate' + this.dropdown.id).setHTML(++this.dropdown.knownSelected + " selected");
		}	
	},
			
	_rangeSelections : function(){
		var x = 0;
		var directionLeft;
		var directionRight;
		var startingPoint = this.dropdown.sourceNode.getAttribute("count").toInt();
		var endPoint = this.dropdown.highlightNode.getAttribute("count").toInt();
		var difference = endPoint - startingPoint;
		if(difference.toString().indexOf("-") != -1){ 
			directionLeft =  parseInt(endPoint,10);
			directionRight = parseInt(startingPoint,10);
		}else{
			directionLeft = parseInt(startingPoint,10);
			directionRight = parseInt(endPoint,10);
		}			
		while(directionLeft <= directionRight){
			for(i=0;i<this.dropdownStack.childNodes.length;i++){
				if(this.dropdownStack.childNodes[i].getAttribute("count").toInt() === directionLeft) var tResult = this.dropdownStack.childNodes[i];
			}
			this.selectedNodes[x] = tResult;		
			directionLeft++;
			x++;
		}
	},	

	_setupSiftTool : function(){
		var p = this;
		var stateHeaderWidth = (this.useStates) ? this.statesHeader.offsetWidth : 0;
		var headCalc = (this.uiToggle.offsetWidth - (this.clearIconContainer.offsetWidth + 4 + stateHeaderWidth)); // taking 4 away for looks.
		//alert(this.uiToggle.offsetWidth + " ?? " + this.clearIconContainer.offsetWidth + " ?? "  + this.statesHeader.offsetWidth)
		this.header.firstChild.style.width = Math.max(headCalc,50) + "px"; 
		this.header.addEvent('keyup', function(event){
			p._captureKeys();				
		});			
		this.clearIcon.addEvent('click',function(event){
	    	p.header.firstChild.value = "";
	    	p._captureKeys();
		});
		this.cArray = [];
	},

	_captureKeys : function(){
	    this.uiChoices.forEach(function(obj, index){
			 if(this.testFailAttribute(obj,'siftBox'+this.id)){
	              if(this.cArray.contains(obj.id)){
	                   return false;
	              }else{
	                   this.cArray[this.cArray.length] = obj.id;
	              }
	         }else{
	              if(this.cArray.contains(obj.id)){
	                   this.cArray.remove(obj.id);
	                   obj.style.display = "block";
	              }
	         }
	    },this);
    	this.tM = null;
		this.isAnd = -1;
		this.isOr = -1;
		this.isNot = -1
		this.firstStyle = "";
		this.secondStyle = "";
		this.returnNeg = false;
		this.returnPos = true;
	},
		
	testFailAttribute : function(obj,box){
	    if(!this.tM){
			 this.firstStyle = "block";
			 this.secondStyle = "none";
			 this.returnNeg = false;
			 this.returnPos = true;
			 this.isNot = $(box).value.indexOf("!");
	         this.isAnd = $(box).value.indexOf("+");
	         this.isOr = $(box).value.indexOf("|"); //must be one or the other.
			 if(this.isOr === -1) this.tM = $(box).value.toLowerCase().split(/\+/g);
			 if(this.isAnd === -1) this.tM = $(box).value.toLowerCase().split(/\|/g);
	         if(this.isOr !== -1 && this.isAnd !== -1) return;
	    }
	    obj["doShowObj"] = 0;
	    this.tM.forEach(function(m, index){
	         index = index+1;
	         m = m.escapeRegExp();
	         if(!this.testFailMatch(obj,m)){
	              obj["doShowObj"]++;
	         }else{
	              if(this.isOr !== -1){
	                   if(index === 1 && obj["doShowObj"] > 0) obj["doShowObj"]--;
	              }else{
	                   if(obj["doShowObj"] > 0) obj["doShowObj"]--;
	              }
	         }
	    },this);
	  	if(this.isNot !== -1){
	   		this.firstStyle = "none";
			this.secondStyle = "block";
			this.returnPos = false;
			this.returnNeg = true;
		}	
		if(this.isOr === -1){
			if(obj["doShowObj"] === this.tM.length){
				obj.style.display = this.firstStyle;
				return this.returnNeg;		
			}else{
				obj.style.display = this.secondStyle;	
				return this.returnPos;	
			}
		}
		if(this.isOr !== -1){
			if(obj["doShowObj"] > 0){
				obj.style.display = this.firstStyle;
				return this.returnNeg;		
			}else{
				obj.style.display = this.secondStyle;	
				return this.returnPos;	
			}				
		}		
	},

	testFailMatch : function(obj,testMatch){
		var patternFailsMatch = [];
		testMatch = testMatch.replace("!","");
			obj.data.forEach(function(c, index){		
				if(!c.toLowerCase().test(testMatch)) patternFailsMatch.push(1);
			});				
			if(patternFailsMatch.length === obj.data.length){
				return true;
			}else{
				return false; 		    
			}
	},
		
	_displaySelectBox : function(){
		/*
		A few rules of thumb...
			- If you are using a container and set max size to end up taller than the height can support,
			the scrollbars will ignore your max size and show what it can. You must either increase
			the height in your style, or reduce max width.

		*/		
		if(this.dropdownStack.childNodes.length === 0) return;
		//if(this.sift) this.dropdown.offsetHeight + this.uiToggle.offsetHeight;
		this.actualHeightOfSelectBoxChoice = this.dropdownStack.childNodes[0].offsetHeight;				
		this.knownHeightOfContainer = (this.definition.useSelf) ? this.container.offsetHeight : this.container["originalHeight"];	
		if(this.maxChoices > 0){
			this.actualCountOfChoices = this.dropdownStack.childNodes.length;		
			this.heightOfMaxColumns = (this.actualHeightOfSelectBoxChoice * this.maxChoices);		
			this.heightOfActualColumns = (this.actualHeightOfSelectBoxChoice * this.actualCountOfChoices);	
			if(this.type !== "dropdown"){					
				//log.clearUiLog();
				//log.out("HEIGHT OF MAX: " + this.heightOfMaxColumns)
				//log.out("HEIGHT OF ACTUAL COLUMNS: " + this.heightOfActualColumns)
				//log.out("HEIGHT OF DROPDOWN:" + this.dropdown.offsetHeight)
				//log.out("HEIGHT OF INFO: " + this.infoBar.offsetHeight)
				//log.out("HEIGHT OF TOGGLE: " + this.uiToggle.offsetHeight)				
				//log.out("HEIGHT OF ACTUAL PLUS HEADER: " + (this.heightOfActualColumns + this.uiToggle.offsetHeight))
				//log.out("HEIGHT OF CONTAINER: " + this.knownHeightOfContainer)	
				//log.out("<strong>HEIGHT OF EVERYTHING</strong>: " + (this.heightOfActualColumns + this.uiToggle.offsetHeight + this.infoBar.offsetHeight))					
				if(!this.definition.useSelf){					
					this.infoHeight = (this.useInfo) ? this.infoBar.offsetHeight : 0; 					
					this.toggleHeight = (this.useToggle || this.sift) ? this.uiToggle.offsetHeight : 0;				
					this.availableHeightForSelectBox =  this.knownHeightOfContainer - (this.infoHeight + this.toggleHeight);					
					if(this.availableHeightForSelectBox < this.heightOfActualColumns || this.heightOfActualColumns > this.heightOfMaxColumns) this.showScrollbars = true;						
					this.dropdownStack.style.height = (this.availableHeightForSelectBox) + "px";	
					if(this.heightOfActualColumns > this.heightOfMaxColumns){
						this.dropdownStack.style.height = (this.heightOfMaxColumns) + "px";	
					}									
				}else{
					this.dropdownStack.style.height = (this.heightOfActualColumns) + "px";
					if(this.heightOfActualColumns > this.heightOfMaxColumns){
						this.showScrollbars = true;
						this.dropdownStack.style.height = (this.heightOfMaxColumns) + "px";	
					}
				}				
				//log.out("AVAILABLE:" + this.availableHeightForSelectBox)
			}else{				
				this.dropdownOverflow = (this.actualHeightOfSelectBoxChoice * this.maxChoices);
				if(this.heightOfActualColumns > this.dropdownOverflow) this.showScrollbars = true;
				if((this.actualCountOfChoices >= this.maxChoices) && this.showScrollbars === true){
					this.dropdownStack.style.height = this.dropdownOverflow;	
				}		
			}		
		}		
	},	

	_alertProblem : function(d){
		var msg = this.defaultErrorMessage;
		// classet
		var newErr = d.toLowerCase().indexOf("classset");
		var newErrMsg = "Classsets must be in the definition under a JSON object called 'classSets' (whose values are separated by commas), or it must be an attribute on the DOM element you are trying to reference. (also separated by commas).\n\n";
		//container
		var newErr = d.toLowerCase().indexOf("firstchild");
		var newErrMsg = "DOM Error. Chances are you did something as follows:\n\n1.Referenced an invalid container for the dropdown.\n2.Referenced an invalid dropdown.\n\nRemember, if you want the dropdown to be inside a container, be sure to add container to the definition.\n\n";
		//dropdown
		var newErr = d.toLowerCase().indexOf("dropdown"); // let it use the previous msg
		// set error
		if(newErr > -1) msg = msg + newErrMsg;	
		alert(msg + "Original Error:\n" + d);
	}
			
});
selectBoxFactory.implement(new Options, new Events);