/*
jquery.combobox
version 0.1.2.8

Copyright © 2007,2008 Minel Pather|Ahura Mazda|jquery.sanchezsalvador.com
Copyright © 2009 Sachin Mittal
Dual licensed under MIT and GPL licences:
	* www.opensource.org/licenses/mit-license.php
	* www.gnu.org/licenses/gpl.html
*/
jQuery.fn.combobox =
	function (styles, options) {
	    var _context = this;
	    // create a combobox class instance instead of jQuery.fn.combobox which is a namespace.
	    this.combobox = new Function();

	    // Style Settings that determine the look of the control
	    var styleSettings =
		{
		    comboboxContainerClass: null,
		    comboboxValueContentContainerClass: null,
		    comboboxValueContentClass: null,
		    comboboxDropDownButtonClass: null,
		    comboboxDropDownClass: null,
		    comboboxDropDownItemClass: null,
		    comboboxDropDownItemHoverClass: null,
		    comboboxDropDownGroupItemHeaderClass: null,
		    comboboxDropDownGroupItemContainerClass: null
		};

	    // Option settings that determine the functionality of the control
	    var optionSettings =
		{
		    animationType: "slide",
		    animationSpeed: "fast", // can be "fast", "slow", or a number in milliseconds
		    width: 270
		};

	    if (styles) {
	        jQuery.extend(styleSettings, styles);
	    }

	    if (options) {
	        jQuery.extend(optionSettings, options);
	    }

	    //#start public events

	    ///<summary>
	    ///	Called whenever the user selects a different item in the list.
	    ///	By default, event is not called if it has not been assigned.
	    ///</summary>
	    this.combobox.onChange = null;

	    //#end public events

	    //#start private functions

	    ///<summary>
	    ///	Returns the Combobox internal instance
	    ///</summary>
	    function getInstance(context) {
	        return context[0].internalCombobox;
	    }

	    // All make*Function(context) functions, create wrappers around the internal Combobox functions
	    // and makes the function element specific.
	    function makeRemoveFunction(context) {
	        return function () {
	            getInstance(context).remove();
	        };
	    }

	    function makeUpdateFunction(context) {
	        return function () {
	            getInstance(context).update();
	        }
	    }

	    function makeUpdateSelectionFunction(context) {
	        return function () {
	            getInstance(context).updateSelection();
	        }
	    }

	    function makeAddRangeFunction(context) {
	        return function (dataSource) {
	            getInstance(context).addRange(dataSource);
	        }
	    }

	    //#end private functions

	    //#start exposed public methods

	    // Add functionality to the combobox namespace
	    jQuery.fn.extend(
			this.combobox,
			{
			    addRange: makeAddRangeFunction(_context),
			    remove: makeRemoveFunction(_context),
			    update: makeUpdateFunction(_context),
			    updateSelection: makeUpdateSelectionFunction(_context)
			});

	    //#end exposed public methods

	    return this.each(
			function () {
			    // Create a new instance of the Combobox Class, intialising it with the DOM element to operate on and
			    // attach the instance to the DOM
			    this.internalCombobox = new ComboboxClass(this);

			    // Call the instance to initialise itself
			    this.internalCombobox.initialise();

			    ///<summary>
			    ///		a state-based class that is performs all functions necessary for the Combobox to work
			    ///</summary>
			    function ComboboxClass(elementDOM) {
			        //#start 'private' variables

			        // This class can operate on N amount of elements depending how combobox() is called
			        // for example $("select").combobox() could return multiple Selects.
			        // This variable should always be a Select JQuery element.
			        // TODO: Check if select control is used
			        var _originalElementJQuery = jQuery(elementDOM);
			        var _containerJQuery = null;
			        var _containerDefaultStyle = "background-color:#fff;border-left: solid 2px #777;border-top: solid 2px #777;border-right: solid 1px #ccc;border-bottom: solid 1px #ccc;";
			        var _containerEnforcedStyle = "padding:0;";
			        var _dropDownListJQuery = null;
			        var _dropDownListEnforcedStyle = "list-style-type:none;min-height:15px;padding-top:0;margin:0;overflow:auto";
			        var _dropDownListDefaultStyle = "cursor:default;padding:2px;background:#fff;border-right:solid 1px #000;border-bottom:solid 1px #000;border-left:solid 1px #aaa;border-top:solid 1px #aaa;";
			        var _dropDownListItemEnforcedStyle = "display:block;";
			        var _dropDownListItemDefaultStyle = "cursor:default;font-weight:normal;font-style:normal;";
			        var _dropDownListGroupItemContainerEnforcedStyle = "list-style-type:none;";
			        var _dropDownListGroupItemContainerDefaultStyle = "padding-left:0px;margin-left:0;";
			        var _dropDownListGroupItemHeaderEnforcedStyle = "";
			        var _dropDownListGroupItemHeaderDefaultStyle = "font-style:italic;font-weight:bold;";
			        var _dropdownListMaximumHeight = 260; // default max height: 300px
			        var _valueContentContainerJQuery = null;
			        var _valueContentContainerEnforcedStyle = "position:relative;overflow:hidden;";
			        var _valueContentJQuery = null;
			        var _valueContentEnforcedStyle = "float:left;position:absolute;cursor:default;overflow:hidden;";
			        var _valueContentDefaultStyle = "padding-left:3px;";
			        var _dropDownButtonJQuery = null;
			        var _dropDownButtonDefaultStyle = "overflow:hidden;width:16px;height:18px;color:#000;background:#D6D3CE;font-family:arial;font-size:8px;cursor:default;text-align:center;vertical-align:middle;";
			        var _dropDownButtonEnforcedStyle = "background-repeat:no-repeat;float:right;";
			        var _dropDownButtonDefaultUnselectedStyle = "padding-left:0px;padding-top:1px;width:12px;height:13px;border-right:solid 2px #404040;border-bottom:solid 2px #404040;border-left:solid 2px #f0f0f0;border-top:solid 2px #f0f0f0";
			        var _dropDownButtonDefaultSelectedStyle = "padding-left:1px;padding-top:3px;width:12px;height:13px;border:solid 1px #808080";
			        var _dropDownButtonDefaultCharacter = "&#9660;"; //down-arrow character
			        var _lastItemSelectedJQuery = null;
			        var _lastItemHoveredJQuery = null;
			        var _lastValue = null;
			        var _downdownListPositionIsInverted = false;
			        var _maximumItemLength = 0;
			        var _dropDownListOffset = null;
			        var _dropDownListHeight = 0;
			        var _dropDownButtonImageDimension = null;
			        var _valueContentContainerImageDimension = null;
			        var _valueContentMaximumHeight = null;

			        //#end 'private' variables

			        //#start 'private' methods

			        ///<summary>
			        /// Function extension to String.
			        ///	Replaces the placeholder tags '{0}...{n}' with the parameters based on ordinal position of the parameters
			        ///	Example: String.format("The quick {0} fox {2} over the lazy {1}.", "brown", "Dog", "jumps");
			        ///	Output:	The quick brown fox jumps over the lazy Dog.
			        ///</summary>
			        String.format =
						function () {
						    var currentString = null;
						    if (arguments.length != 0) {
						        currentString = arguments[0];
						        for (var argumentIndex = 1; argumentIndex < arguments.length; argumentIndex++) {
						            var modifiedString = new RegExp('\\{' + (argumentIndex - 1) + '\\}', 'gm');
						            currentString = currentString.replace(modifiedString, arguments[argumentIndex]);
						        }
						    }

						    return currentString;
						};

			        ///<summary>
			        ///	Returns the value from a string that has 'px' embedded.
			        ///	This function is normally used when working with CSS values.
			        ///	Note: returns null if the extension is not 'px', i.e. it may be 'em', 'pt', etc.
			        ///</summary>
			        function getPixelValue(object) {
			            var pixelValue = null;

			            if (object) {
			                if (object.substr(-2, 2) == "px") {
			                    pixelValue = object.substr(0, (object.length - 2));
			                }
			            }

			            return pixelValue;
			        }

			        ///<summary>
			        ///	Sets the width of an element taking into consideration any borders and padding.
			        ///	Works exactly like Internet Explorers Box Model, where the padding and border is considered
			        //	part of the width. For the purposes of this control, it is required in certain circumstances.
			        ///	Example:
			        ///	 <div id="container" style="width: 150px; border:solid 2px #000"></div>
			        ///		jQuery('#container').width(); // 150px
			        ///		jQuery('#container').outerWidth(); // 154px (2px border on the left and right)
			        ///		setInnerWidth(jQuery('#container'), 120);
			        ///		jQuery('#container').width(); // 116px
			        ///		jQuery('#container').outerWidth(); // 120px (2px border on the left and right)
			        ///</summary>				
			        function setInnerWidth(elementJQuery, width) {
			            var differenceWidth = (elementJQuery.outerWidth() - elementJQuery.width());

			            elementJQuery.width(width - differenceWidth);
			            elementJQuery.width(162);
			        }

			        ///<summary>
			        ///	Sets the height of an element taking into consideration any borders and padding.
			        ///	Works exactly like Internet Explorers Box Model, where the padding and border is considered
			        //	part of the height. For the purposes of this control, it is required in certain circumstances.			
			        ///</summary>				
			        function setInnerHeight(elementJQuery, height) {
			            var differenceheight = (elementJQuery.outerHeight() - elementJQuery.height());

			            elementJQuery.height(height - differenceheight);
			            if ($.browser.msie && parseInt($.browser.version) == 7)
			                elementJQuery.height(19);
			            else
			                elementJQuery.height(21);
			        }

			        ///<summary>
			        /// Applies CSS styling from a string that contains multiple style styleSettings
			        ///	Example: "background-color:#fff;color:#0f0;border:solid 1px #00f;"
			        ///</summary>			
			        function applyMultipleStyles(elementJQuery, multipleCSSStyles) {
			            var stylePairArray = multipleCSSStyles.split(";");
			            if (stylePairArray.length > 0) {
			                for (var stylePairArrayIndex = 0; stylePairArrayIndex < stylePairArray.length; stylePairArrayIndex++) {
			                    var stylePair = stylePairArray[stylePairArrayIndex];
			                    var splitStylePair = stylePair.split(":");

			                    elementJQuery.css(splitStylePair[0], splitStylePair[1]);
			                }
			            }
			        }

			        ///<summary>
			        ///	Calculates the width and height of an image from its URL
			        ///</summary>
			        function getImageDimension(imageURL) {
			            var dimension = new Object();
			            dimension.width = 0;
			            dimension.height = 0;

			            // sizingImageJQuery = jQuery("<img style='border:none;margin:0;padding:0;'></img>");
			            // sizingImageJQuery.attr("src", imageURL);

			            //_containerJQuery.append(sizingImageJQuery);

			            // dimension.width = sizingImageJQuery.width();
			            // dimension.height = sizingImageJQuery.height();

			            dimension.width = 48;
			            dimension.height = 18;

			            // sizingImageJQuery.remove();

			            return dimension;
			        }

			        ///<summary>
			        ///	Calculates the background image size for an JQuery element if it has a CSS background-image set.
			        ///</summary>
			        function calculateIndividualImageDimension(jqueryElement) {
			            var dimension = null;
			            var backgroundImageURL = jqueryElement.css("background-image");
			            // Depending on the browser, the URL of the background-image sometimes is padded with extra characters
			            backgroundImageURL = backgroundImageURL.replace("url(", "", "gi");
			            backgroundImageURL = backgroundImageURL.replace('"', '', "gi");
			            backgroundImageURL = backgroundImageURL.replace('\"', '', "gi");
			            backgroundImageURL = backgroundImageURL.replace(")", "", "gi");

			            if (backgroundImageURL != "none") {
			                dimension = getImageDimension(backgroundImageURL);
			            }

			            return dimension;
			        }

			        ///<summary>
			        ///	Calculates the background image size for the value display and drop down button.
			        ///	These dimensions are used for control states, normal, pressed [, and hover]
			        ///</summary>
			        function calculateImageDimensions() {
			            _dropDownButtonImageDimension = calculateIndividualImageDimension(_dropDownButtonJQuery);
			            _valueContentContainerImageDimension = calculateIndividualImageDimension(_valueContentContainerJQuery);
			        }

			        ///<summary>
			        ///	Changes the visual of the value container to indicate a state.
			        ///	If the background-image is set and does not contain additional images for states,
			        ///	then the image is not changed for the different states. The Select for Safari works like this.
			        ///	The image states are stored below each other
			        ///	NOTE: This is different from the Drop Down Button where the images are stored side by side.
			        /// for example
			        ///	A value container has a width of 275 pixels and a height of 35 pixels.
			        ///	The background-image is set to valuebackground.gif.
			        ///	valuebackground.gif is 70 pixels in height. The 'pressed' state image is at pixel height 35 in the image.
			        ///	States are:
			        ///	Normal = 0
			        ///	Pressed = 1
			        ///</summary>
			        function setValueContentContainerState(state) {
			            if (styleSettings.comboboxValueContentContainerClass) {
			                // Only process buttomn states if a background-image has been set
			                if (_valueContentContainerImageDimension != null) {
			                    var height = _valueContentContainerJQuery.height();
			                    var offset = (state * height);

			                    // Check if the image is higher than the set height.
			                    // This signifies that the image file contain different images below each other for different
			                    // states.
			                    if (_valueContentContainerImageDimension.height > offset) {
			                        var background_positionCSS = String.format("0px -{0}px", offset);
			                        _valueContentContainerJQuery.css("background-position", background_positionCSS);
			                    }
			                }
			            }
			        }

			        ///<summary>
			        ///	Changes the visual of the drop down button to indicate a state.
			        ///	If the background-image is not set, then the default style is applied.
			        ///	If the background-image is set and does not contain additional images for states,
			        ///	then the image is not changed for the different states. The Select for Safari works like this.
			        ///	The image states are stored side by side: for example
			        ///	A drop-down button has a width of 16 pixel. The background-image is set to button.gif
			        ///	Button.gif is 32 pixels wide. The 'pressed' state image is at pixel position 16 in the image.
			        ///	States are:
			        ///	Normal = 0
			        ///	Pressed = 1
			        ///</summary>
			        function setDropDownButtonState(state) {
			            if (styleSettings.comboboxDropDownButtonClass) {
			                // Only process buttomn states if a background-image has been set
			                if (_dropDownButtonImageDimension != null) {
			                    var width = _dropDownButtonJQuery.width();
			                    var offset = (state * width);

			                    // Check if the image is wider than the set width.
			                    // This signifies that the image file contain different images next to each other for different
			                    // states.
			                    if (_dropDownButtonImageDimension.width > offset) {
			                        var background_positionCSS = String.format("-{0}px 0px", offset);
			                        _dropDownButtonJQuery.css("background-position", background_positionCSS);
			                    }
			                }
			            }
			            else {
			                var style = _dropDownButtonDefaultUnselectedStyle;

			                if (state == 1) {
			                    style = _dropDownButtonDefaultSelectedStyle;
			                }

			                applyMultipleStyles(_dropDownButtonJQuery, style);
			            }
			        }

			        ///<summary>
			        ///	Changes the visual appearance of the controls to represent the current state.
			        ///	States are:
			        ///	Normal = 0
			        ///	Pressed = 1
			        ///</summary>
			        function setControlVisualState(state) {
			            setValueContentContainerState(state);

			            setDropDownButtonState(state);
			        }

			        ///<summary>
			        /// Builds the elements that make up the always visible portion of the control.
			        ///	The equivalent of a Textbox-type element.
			        /// Also creates the DropDownButton
			        ///</summary>
			        function buildValueContent() {
			            // A container for the Display Value and DropDownButton. A container is required as the child elements
			            // are floated
			            var valueContentContainerHTML = "";
			            if (styleSettings.comboboxValueContentContainerClass) {
			                valueContentContainerHTML = String.format("<div class='{0}' style='{1}'></div>", styleSettings.comboboxValueContentContainerClass, _valueContentContainerEnforcedStyle);
			            }
			            else {
			                valueContentContainerHTML = String.format("<div style='{0}'></div>", _valueContentContainerEnforcedStyle);
			            }

			            // Create the equivalent of the select 'textbox' where the current selected value is shown
			            var valueContentHTML = "";
			            if (styleSettings.comboboxValueContentClass) {
			                valueContentHTML = String.format("<div class='{0}' style='{1}'></div>", styleSettings.comboboxValueContentClass, _valueContentEnforcedStyle);
			            }
			            else {
			                valueContentHTML = String.format("<div style='{0}'></div>", _valueContentEnforcedStyle + _valueContentDefaultStyle);
			            }

			            var dropdownButtonHTML = "";
			            if (styleSettings.comboboxDropDownButtonClass) {
			                dropdownButtonHTML = String.format("<div class='{1}' style='{0}'></div>", _dropDownButtonEnforcedStyle, styleSettings.comboboxDropDownButtonClass);
			            }
			            else {
			                dropdownButtonHTML = String.format("<div style='{0}'>{1}</div>", (_dropDownButtonEnforcedStyle + _dropDownButtonDefaultStyle), _dropDownButtonDefaultCharacter);
			            }

			            _valueContentJQuery = jQuery(valueContentHTML);
			            _dropDownButtonJQuery = jQuery(dropdownButtonHTML);
			            _valueContentContainerJQuery = jQuery(valueContentContainerHTML);

			            _valueContentContainerJQuery.appendTo(_containerJQuery);
			            _valueContentJQuery.appendTo(_valueContentContainerJQuery);
			            _dropDownButtonJQuery.appendTo(_valueContentContainerJQuery);

			            calculateImageDimensions();

			            _valueContentMaximumHeight = getPixelValue(_valueContentJQuery.css("max-height"));

			            // Set control to normal state
			            setControlVisualState(0);
			        }

			        ///<summary>
			        ///	Build a drop down list element populating it will values, text, styles and class
			        ///	depending on the source value type. The source value (childJQuery) can be an option or
			        ///	and optgroup element.
			        ///</summary>
			        function buildDropDownItem(childJQuery) {
			            var dataItemHTML = "";
			            var dataItemClass = null;
			            var dataItemText = "";
			            var dataItemTitle = "";
			            var dataItemValue = null;
			            var dataItemStyle = "";
			            var dataItemType = "option";
			            var childElement = childJQuery[0];

			            if (childElement.title) {
			                if (childElement.title != "") {
			                    dataItemTitle = childElement.title;
			                }
			            }

			            if (childJQuery.is('option')) {
			                if (childElement.dataText) {
			                    dataItemText = childElement.dataText;
			                }
			                else {
			                    dataItemText = childJQuery.text();
			                }
			                dataItemValue = childJQuery.val();

			                if (styleSettings.comboboxDropDownItemClass) {
			                    dataItemClass = styleSettings.comboboxDropDownItemClass;
			                    dataItemStyle = _dropDownListItemEnforcedStyle;
			                }
			                else {
			                    dataItemStyle = (_dropDownListItemEnforcedStyle + _dropDownListItemDefaultStyle);
			                }

			                if (dataItemClass) {
			                    dataItemHTML = String.format("<li style='{0}' class='{1}'>{2}</li>", dataItemStyle, dataItemClass, dataItemText);
			                }
			                else {
			                    dataItemHTML = String.format("<li style='{0}'>{1}</li>", dataItemStyle, dataItemText);
			                }

			            }
			            else {
			                if (childJQuery[0].dataText) {
			                    dataItemText = childJQuery[0].dataText;
			                }
			                else {
			                    dataItemText = childJQuery.attr('label');
			                }
			                dataItemValue = childJQuery.attr('class');
			                dataItemType = "optgroup";

			                if (styleSettings.comboboxDropDownGroupItemHeaderClass) {
			                    dataItemClass = styleSettings.comboboxDropDownGroupItemHeaderClass;
			                    dataItemStyle = _dropDownListGroupItemHeaderEnforcedStyle;
			                }
			                else {
			                    dataItemStyle = (_dropDownListGroupItemHeaderEnforcedStyle + _dropDownListGroupItemHeaderDefaultStyle);
			                }

			                if (dataItemClass) {
			                    dataItemHTML = String.format("<li><span style='{0}' class='{1}'>{2}</span></li>", dataItemStyle, dataItemClass, dataItemText);
			                }
			                else {
			                    dataItemHTML = String.format("<li><span style='{0}'>{1}</span></li>", dataItemStyle, dataItemText);
			                }
			            }

			            var dataItemJQuery = jQuery(dataItemHTML);

			            // The element's style is set to inline for the calculation of the true width
			            // The element is then reset to its enforced style (display:block) later
			            dataItemJQuery.css("display", "inline");
			            // Store the value with the DOMElement which is persisted with the Browser
			            dataItemJQuery[0].dataText = dataItemText;
			            dataItemJQuery[0].dataValue = dataItemValue;
			            dataItemJQuery[0].dataType = dataItemType;
			            if (dataItemTitle == "") {
			                dataItemTitle = dataItemText
			            }
			            dataItemJQuery[0].title = dataItemTitle;

			            return dataItemJQuery;
			        }

			        ///<summary>
			        ///	Recusively build the drop down list elements based on the options and optgroups contained
			        ///	with the original Select element
			        ///</summary>
			        function recursivelyBuildList(parentJQuery, childrenOptionsJQuery) {
			            childrenOptionsJQuery.each(
							function () {
							    var childJQuery = jQuery(this);
							    var builtDropDownItemJQuery = buildDropDownItem(childJQuery);
							    parentJQuery.append(builtDropDownItemJQuery);

							    // Calculate the true width of the item taking into consideration the offset from the left-edge
							    // of the drop-down list.
							    // Get the left offset of the DropDown list container and subtract that from the builtDropDownItemJQuery left offset
							    //	to get the distance the builtDropDownItemJQuery is from its container
							    var offsetLeft = builtDropDownItemJQuery.offset().left;

							    offsetLeft -= _dropDownListOffset.left;

							    if (offsetLeft < 0) {
							        offsetLeft = 0;
							    }

							    var width = (offsetLeft + builtDropDownItemJQuery.outerWidth());
							    if (width > _maximumItemLength) {
							        _maximumItemLength = width;
							    }

							    // Set the enforced style of display:block after the width has been calculated.
							    applyMultipleStyles(builtDropDownItemJQuery, _dropDownListItemEnforcedStyle);

							    if (childJQuery.is('optgroup')) {
							        var dataGroupItemHTML = "";
							        if (styleSettings.comboboxDropDownGroupItemContainerClass) {
							            dataGroupItemHTML = String.format("<ul style='{0}' class='{1}'></ul>", _dropDownListGroupItemContainerEnforcedStyle, styleSettings.comboboxDropDownGroupItemContainerClass);
							        }
							        else {
							            dataGroupItemHTML = String.format("<ul style='{0}'></ul>", (_dropDownListGroupItemContainerEnforcedStyle + _dropDownListGroupItemContainerDefaultStyle));
							        }

							        var dataGroupItemJQuery = jQuery(dataGroupItemHTML);
							        builtDropDownItemJQuery.append(dataGroupItemJQuery);

							        // If not an option, then the child of a Select must be an optgroup element
							        recursivelyBuildList(dataGroupItemJQuery, childJQuery.children());
							    }
							});
			        }

			        ///<summary>
			        /// Creates an unordered list of values from the original Select control
			        ///</summary>
			        function buildDropDownList() {
			            var originalElementChildrenJQuery = _originalElementJQuery.children();
			            _lastItemSelectedJQuery = null;
			            _lastValue = null;

			            // If the Drop Down List container already exists, recreate only the items,
			            // else create the container and the items as well.
			            if (_dropDownListJQuery) {
			                // Clear out any existing children elements
			                _dropDownListJQuery.empty();
			            }
			            else {
			                var dropDownHTML = "";
			                if (styleSettings.comboboxDropDownClass) {
			                    dropDownHTML = String.format("<ul class='{0}' style='{1}'></ul>", styleSettings.comboboxDropDownClass, _dropDownListEnforcedStyle);
			                }
			                else {
			                    dropDownHTML = String.format("<ul style='{0}'></ul>", (_dropDownListEnforcedStyle + _dropDownListDefaultStyle));
			                }

			                _dropDownListJQuery = jQuery(dropDownHTML);
			                // Create the equivalent of the drop down list where the all the values are shown
			                _dropDownListJQuery.appendTo(_containerJQuery);

			                // Enable the Drop Down List to be able to receive focus and key events
			                _dropDownListJQuery.attr("tabIndex", 0);
			            }

			            // Create the internal list of values if they exist
			            if (originalElementChildrenJQuery.length > 0) {
			                _maximumItemLength = 0;
			                _dropDownListOffset = _dropDownListJQuery.offset();

			                recursivelyBuildList(_dropDownListJQuery, originalElementChildrenJQuery);
			            }

			            // Check if the max-height has been set as a CSS setting
			            // If it has, determine if the current height of the dropdown list does not exceed it and if 
			            // it does, reset the height to match the setting.
			            var maximumHeight = getPixelValue(_dropDownListJQuery.css("max-height"));

			            // Only use the maximum height if it has been set correctly
			            if (maximumHeight) {
			                _dropdownListMaximumHeight = maximumHeight;
			            }

			            var dropdownListHeight = _dropDownListJQuery.height();
			            if (dropdownListHeight > _dropdownListMaximumHeight) {
			                _dropDownListJQuery.height(_dropdownListMaximumHeight);
			            }

			            // Store the height because the browser flashes (FF) when accessing this function
			            _dropDownListHeight = _dropDownListJQuery.height();
			        }

			        ///<summary>
			        ///	Adjust the width of the DropDown list based on the widest item or the set width (options), whichever
			        ///	is larger.
			        ///</summary>
			        function updateDropDownListWidth() {
			            //Drop down list element
			            var dropdownListWidth = _containerJQuery.outerWidth();
			            if (dropdownListWidth < _maximumItemLength) {
			                dropdownListWidth = _maximumItemLength;
			            }

			            _dropDownListJQuery.width(dropdownListWidth);
			        }

			        ///<summary>
			        /// Repositions the display value based on height of the element.
			        ///	Note: the height will only have meaning if the display value element has text
			        ///</summary>
			        function positionDisplayValue() {
			            // Set the height to the default and allow it to fill the height to accomodate the content
			            _valueContentJQuery.height("auto");
			            var displayValueHeight = _valueContentJQuery.outerHeight();
			            var displayContainerHeight = _valueContentContainerJQuery.height();

			            // Check if the developer wants to clip the content within a region
			            if (_valueContentMaximumHeight) {
			                // Set the height of the content to the maximumContentHeight if it is less
			                // than the current height of the content
			                if (_valueContentMaximumHeight < displayValueHeight) {
			                    displayValueHeight = _valueContentMaximumHeight;
			                    _valueContentJQuery.height(displayValueHeight);
			                }
			            }

			            var difference = ((displayContainerHeight - displayValueHeight) / 2);

			            if (difference < 0) {
			                difference = 0;
			            }

			            //TODO: add other alignments for the user, such as left, top, middle, bottom, etc
			            _valueContentJQuery.css("top", difference);
			        }

			        ///<summary>
			        ///	Applies custom layout position and sizing to the controls
			        ///</summary>
			        function applyLayout() {
			            _containerJQuery.width(optionSettings.width);

			            // Removes any units and retrieves only the value of width
			            var controlWidth = _containerJQuery.width();
			            setInnerWidth(_valueContentContainerJQuery, controlWidth);

			            var displayValueWidth = (_valueContentContainerJQuery.width() - _dropDownButtonJQuery.outerWidth());
			            setInnerWidth(_valueContentJQuery, displayValueWidth);
			            var dropDownButtonHeight = _dropDownButtonJQuery.outerHeight();
			            setInnerHeight(_valueContentContainerJQuery, dropDownButtonHeight);

			            _dropDownListJQuery.css("position", "absolute");
			            _dropDownListJQuery.css("z-index", "20000");

			            updateDropDownListWidth();

			            // Position the drop down list correctly, taking the main display control border into consideration
			            var currentLeftPosition = _dropDownListJQuery.offset().left;
			            var leftPosition = (currentLeftPosition - (_containerJQuery.outerWidth() - _containerJQuery.width()));
			            _dropDownListJQuery.css("left", leftPosition + 1);


			            _dropDownListJQuery.hide();
			        }

			        ///<summary>
			        ///		Sets the value both internally and visually to the user
			        ///</summary>
			        function setContentDisplay() {
			            var valueHasChanged = false;
			            var originalElement = _originalElementJQuery[0];
			            var dataItemJQuery;

			            if (originalElement.length > 0) {
			                //var selectedText = originalElement[originalElement.selectedIndex].text;
			                var selectedDropDownListItemJQuery = jQuery("li[dataValue='" + _originalElementJQuery.val() + "']", _dropDownListJQuery);

			                _valueContentJQuery.html(selectedDropDownListItemJQuery[0].dataText);
			                _valueContentJQuery.attr("title", selectedDropDownListItemJQuery[0].title);

			                // Reposition the display value based on height of the element after the text has changed
			                positionDisplayValue();

			                if (_lastValue != null) {
			                    if (_lastValue != _originalElementJQuery.val()) {
			                        valueHasChanged = true;
			                    }
			                }

			                _lastValue = _originalElementJQuery.val();

			                //  If the selected value has changed since the last click, fire the onChange event
			                if (valueHasChanged) {
			                    // Check if the onChange event is being consumed, otherwise it will be undefined
			                    if (_context.combobox.onChange) {
			                        _context.combobox.onChange();
			                    }
			                }

			                // If _lastItemSelectedJQuery has been set, remove the highlight from it, before setting it to the current
			                // value
			                if (_lastItemSelectedJQuery) {
			                    toggleItemHighlight(_lastItemSelectedJQuery, false);
			                }

			                // Find the DropDown Item Element that corresponds to the current value in the Select element
			                _lastItemSelectedJQuery = selectedDropDownListItemJQuery;

			                toggleItemHighlight(_lastItemSelectedJQuery, true);
			            }
			        }


			        ///<summary>
			        ///	Highlights/Unhighlights a specific option.
			        ///	If a class is not set, then the background and foreground colours are inverted
			        ///</summary>
			        function toggleItemHighlight(elementJQuery, isHighlighted) {
			            if (elementJQuery) {
			                if (styleSettings.comboboxDropDownItemHoverClass) {
			                    if (isHighlighted) {
			                        elementJQuery.addClass(styleSettings.comboboxDropDownItemHoverClass);
			                    }
			                    else {
			                        elementJQuery.removeClass(styleSettings.comboboxDropDownItemHoverClass);
			                    }
			                }
			                else {
			                    if (isHighlighted) {
			                        elementJQuery.css("background", "#000");
			                        elementJQuery.css("color", "#fff");
			                    }
			                    else {
			                        elementJQuery.css("background", "");
			                        elementJQuery.css("color", "");
			                    }
			                }
			            }
			        }

			        ///<summary>
			        ///	Builds the Outermost control and swaps out the original Select element.
			        ///	The Select element then becomes an hidden control within.
			        ///</summary>
			        function buildContainer() {
			            var containerHTML = "";
			            if (styleSettings.comboboxContainerClass) {
			                containerHTML = String.format("<div class='{0}' style='{1}'></div>", styleSettings.comboboxContainerClass, _containerEnforcedStyle);
			            }
			            else {
			                containerHTML = String.format("<div style='{0}' style='{1}'></div>", _containerDefaultStyle, _containerEnforcedStyle);
			            }
			            _containerJQuery = jQuery(containerHTML);
			            _originalElementJQuery.before(_containerJQuery);
			            _containerJQuery.append(_originalElementJQuery);
			            _originalElementJQuery.hide();

			            // Allow the custom jquery.combobox be able to receive focus and key events
			            _containerJQuery.attr("tabIndex", 0);
			        }

			        ///<summary>
			        ///	Converts an existing Select element to a JQuery.combobox.
			        ///	The Select element is kept and updated accordingly, but visually is represented
			        ///	by other richer HTML elements
			        ///</summary>
			        this.initialise =
						function () {
						    buildContainer();

						    buildValueContent();

						    buildDropDownList();

						    applyLayout();

						    bindEvents();

						    setContentDisplay();
						};

			        ///<summary>
			        ///	Slides up the DropDownlist when it is to be placed above the CB
			        ///</summary>
			        function slideUp(newTop) {
			            _dropDownListJQuery.animate(
							{
							    height: "toggle",
							    top: newTop
							},
							optionSettings.animationSpeed);
			        }

			        ///<summary>
			        ///	Slides closed the DropDownlist when it is placed above the CB.
			        ///	Binds the CB Container click event after the DDL is hidden to avoid a bug in IE
			        ///	where the click event fires re-opening the DDL.
			        ///</summary>
			        function slideDown(newTop) {
			            _dropDownListJQuery.animate(
							{
							    height: "toggle",
							    opacity: "toggle",
							    top: newTop
							},
							optionSettings.animationSpeed);
			        }

			        ///<summary>
			        ///	Toggles the slide with a fade and returning execution to the callback function when down
			        ///</summary>
			        function slideToggle(callback) {
			            _dropDownListJQuery.animate(
							{
							    height: "toggle",
							    opacity: "toggle"
							},
							optionSettings.animationSpeed,
							callback);
			        }

			        ///<summary>
			        ///	Get the proposed top position of the drop down list container.
			        ///	Also sets whether the drop down list is inverted. Inverted means that the
			        ///	list is shown above the container as opposed to the normal position of below the combobox 
			        ///	container
			        ///</summary>
			        function getDropDownListTop() {
			            var comboboxTop = _containerJQuery.position().top;
			            var dropdownListHeight = _dropDownListJQuery.outerHeight();
			            var comboboxBottom = (comboboxTop + _containerJQuery.outerHeight());
			            var windowScrollTop = jQuery(window).scrollTop();
			            var windowHeight = jQuery(window).height();
			            var availableSpaceBelow = (windowHeight - (comboboxBottom - windowScrollTop));
			            var dropdownListTop;

			            // Set values to display dropdown list below combobox as default				
			            dropdownListTop = comboboxBottom;
			            _downdownListPositionIsInverted = false;

			            // Check if there is enough space below to display the full height of the drop down list
			            if (availableSpaceBelow < dropdownListHeight) {
			                // There is no available space below the combobox to display the dropdown list
			                // Check if there is available space above. If not, then display below as default
			                if ((comboboxTop - windowScrollTop) > dropdownListHeight) {
			                    // There is space above
			                    dropdownListTop = (comboboxTop - dropdownListHeight);
			                    _downdownListPositionIsInverted = true;
			                }
			            }

			            return dropdownListTop;
			        }

			        ///<summary>
			        ///	Hides/Shows the list of values.
			        ///	The method of display or hiding is specified as optionSettings.animationType.
			        ///	This method also changes the button state
			        ///</summary>					
			        function toggleDropDownList(isShown) {
			            if (isShown) {
			                if (_dropDownListJQuery.is(":hidden")) {
			                    // Remove the highlight from the last item hovered before the DDL was retracted
			                    toggleItemHighlight(_lastItemHoveredJQuery, false);

			                    // When the DropDown list is shown, highlist the current value in the list
			                    toggleItemHighlight(_lastItemSelectedJQuery, true);

			                    setControlVisualState(1);

			                    var dropdownListTop = getDropDownListTop();
			                    _dropDownListJQuery.css("top", dropdownListTop);
			                    //_dropDownListJQuery.css("left", _containerJQuery.offset().left);
			                    _dropDownListJQuery.css("left", _containerJQuery.position().left);
			                    switch (optionSettings.animationType) {
			                        case "slide":
			                            if (_downdownListPositionIsInverted) {
			                                var comboboxTop = _containerJQuery.position().top;
			                                var containerHeight = _containerJQuery.outerHeight();

			                                _dropDownListJQuery.css("top", (comboboxTop - containerHeight));

			                                slideUp(dropdownListTop);
			                            }
			                            else {
			                                slideToggle();
			                            }
			                            break;

			                        case "fade":
			                            _dropDownListJQuery.fadeIn(optionSettings.animationSpeed);
			                            break;

			                        default:
			                            _dropDownListJQuery.show(1);
			                    }
			                }
			            }
			            else {
			                if (_dropDownListJQuery.is(":visible")) {
			                    setControlVisualState(0);

			                    switch (optionSettings.animationType) {
			                        case "slide":
			                            if (_downdownListPositionIsInverted) {
			                                comboboxTop = _containerJQuery.position().top;
			                                dropdownListHeight = _dropDownListJQuery.height();

			                                slideDown(comboboxTop - _containerJQuery.outerHeight());
			                            }
			                            else {
			                                slideToggle();
			                            }
			                            break;

			                        case "fade":
			                            _dropDownListJQuery.fadeOut(optionSettings.animationSpeed);
			                            break;

			                        default:
			                            _dropDownListJQuery.hide();
			                    }
			                }
			            }
			        }

			        ///<summary>
			        ///	Sets the internal select element (original) to match the visually changes made by the user.
			        ///	This ensures that any legacy code working with the original select is kept up to date with changes
			        /// Either selectedIndex or selectedValue can be used, not both at the same time.
			        ///</summary>
			        function setOriginalSelectItem(selectedIndex, selectedValue) {
			            var originalElementDOM = _originalElementJQuery[0];

			            if (selectedValue == null) {
			                originalElementDOM.selectedIndex = selectedIndex;
			            }
			            else {
			                originalElementDOM.value = selectedValue;
			                //some wiered ie bug	
			                if (originalElementDOM.selectedIndex < 0)
			                    originalElementDOM.selectedIndex = 0;
			            }

			            setContentDisplay();
			        }

			        ///<summary>
			        ///	Selects a value from the list of options from the original Select options.
			        ///	Does not use JQuery Selectors ':last' and ':first' because they take optgroup elements into
			        ///	account.
			        ///</summary>					
			        function selectValue(subSelector) {
			            var originalElement = _originalElementJQuery[0];
			            var currentIndex = originalElement.selectedIndex;
			            var newIndex = -1;
			            // {select}.length returns the array size of the options. Does not count optgroup elements
			            var optionCountZeroBased = originalElement.length - 1;

			            switch (subSelector) {
			                case ":next":
			                    newIndex = currentIndex + 1;
			                    if (newIndex > optionCountZeroBased) {
			                        newIndex = optionCountZeroBased;
			                    }
			                    break;

			                case ":previous":
			                    newIndex = currentIndex - 1;
			                    if (newIndex < 0) {
			                        newIndex = 0;
			                    }

			                    break;

			                case ":first":
			                    newIndex = 0;

			                    break;

			                case ":last":
			                    newIndex = optionCountZeroBased;

			                    break;
			            }

			            setOriginalSelectItem(newIndex, null);
			        }

			        ///<summary>
			        ///	Returns true if the DropDownList visible on screen, else false
			        ///</summary>
			        function isDropDownVisible() {
			            return _dropDownListJQuery.is(":visible");
			        }

			        ///<summary>
			        /// Bind all items to mouse events except for UL elements
			        /// and LI elements that are option group labels
			        ///</summary>			
			        function bindItemEvents() {
			            jQuery("li", _dropDownListJQuery).not("ul").not("span").not("[dataType='optgroup']").each(
							function () {
							    var itemJQuery = jQuery(this);
							    itemJQuery.click(
									function (clickEvent) {
									    // Stops the click event propagating to the Container and the Container.onClick firing
									    clickEvent.stopPropagation();

									    dropdownList_onItemClick(itemJQuery);
									});

							    itemJQuery.mouseover(
									function () {
									    dropdownList_onItemMouseOver(itemJQuery);
									});

							    itemJQuery.mouseout(
									function () {
									    dropdownList_onItemMouseOut(itemJQuery);
									});
							});
			        }

			        ///<summary>
			        ///	Bind the document click event for hide logic
			        ///</summary>
			        function bindDocumentClickEvent() {
			            $(document).click(
							function (clickEvent) {
							    var $target = $(clickEvent.target);
							    var $insideBox = $target.parents().filter(function () {
							        return this === _containerJQuery[0];
							    });
							    //if clicked inside and visible hide the box else hide the box
							    if ($insideBox.length) {
							        clickEvent.stopPropagation();
							    }
							    else {
							        dropdownListHide();
							    }
							    //return false;
							});
			        }

			        ///<summary>
			        ///	Bind the click event of the container to a function
			        ///</summary>
			        function bindContainerClickEvent() {
			            _containerJQuery.click(
							function () {
							    container_onClick();
							});
			        }

			        ///<summary>
			        ///		Bind this control to the events to custom functions
			        ///</summary>
			        function bindEvents() {
			            _containerJQuery.keydown(
							function (keyEvent) {
							    keyEvent.preventDefault();
							    container_onKeyDown(keyEvent)
							});

			            bindContainerClickEvent();

			            bindDocumentClickEvent();

			            bindItemEvents();
			        }

			        //#end 'private' functions

			        //#start private events

			        ///<summary>
			        ///	If the drop down list is retracted, it is shown,
			        ///	else if shown, it is retracted
			        ///</summary>
			        function container_onClick() {
			            if (_dropDownListJQuery.is(":hidden")) {
			                toggleDropDownList(true);
			            }
			            else {
			                toggleDropDownList(false);
			            }
			        }

			        ///<summary>
			        ///	Fires when the drop down list loses focus.
			        ///	the drop down list is retracted
			        ///</summary>					
			        function dropdownListHide() {
			            if (_dropDownListJQuery.is(":visible")) {
			                toggleDropDownList(false);
			            }
			        }

			        ///<summary>
			        ///	Retrieves the value of the item clicked, sets the content to that value
			        ///	and retracts the drop-down list
			        ///</summary>
			        function dropdownList_onItemClick(itemJQuery) {
			            setOriginalSelectItem(null, itemJQuery[0].dataValue);

			            toggleDropDownList(false);
			        }

			        ///<summary>
			        ///	Highlights the Drop Down List item currently under the mouse.
			        ///	Removes the highlist from the previous selected item as well.
			        ///</summary>
			        function dropdownList_onItemMouseOver(itemJQuery) {
			            // An item may be selected from the previous selection and will require
			            // to be set to normal.
			            // TODO: find a better method of matching _lastItemSelectedJQuery to itemJQuery and optimising the removal
			            // of the class, instead of removing it consistently
			            toggleItemHighlight(_lastItemSelectedJQuery, false);

			            toggleItemHighlight(_lastItemHoveredJQuery, false);

			            toggleItemHighlight(itemJQuery, true);
			        }

			        ///<summary>
			        ///		Removes the highlight from the selected item
			        ///</summary>
			        function dropdownList_onItemMouseOut(itemJQuery) {
			            _lastItemHoveredJQuery = itemJQuery;
			        }

			        ///<summary>
			        ///	Handles the keyboard navigation aspect of the combobox.
			        ///	Note: Does not jump to item if the first letter is pressed.
			        ///</summary>
			        //TODO: Correctly support page-up and page-down, esp. with scrolling
			        function container_onKeyDown(keyEvent) {
			            switch (keyEvent.which) {
			                case 33:
			                    //Page Up
			                case 36:
			                    //Home
			                    selectValue(":first");
			                    break;

			                case 34:
			                    //Page Down
			                case 35:
			                    //End
			                    selectValue(":last");
			                    break;

			                case 37:
			                    //Left
			                    selectValue(":previous");
			                    break;

			                case 38:
			                    //Up
			                    if (keyEvent.altKey) {
			                        // alt-up
			                        // If DDL is hidden, then it is shown and vice-versa
			                        toggleDropDownList(!(isDropDownVisible()));
			                    }
			                    else {
			                        selectValue(":previous");
			                    }
			                    break;

			                case 39:
			                    //Right
			                    selectValue(":next");
			                    break;

			                case 40:
			                    //Down
			                    if (keyEvent.altKey) {
			                        // alt-down
			                        // If DDL is hidden, then it is shown and vice-versa
			                        toggleDropDownList(!(isDropDownVisible()));
			                    }
			                    else {
			                        selectValue(":next");
			                    }
			                    break;

			                case 27:
			                case 13:
			                    // Escape
			                    toggleDropDownList(false);
			                    break;

			                case 9:
			                    // Tab
			                    //TODO: Support alt-tab
			                    //TODO: Does not truly leave the Combobox if the DropDown is visible
			                    _dropDownListJQuery.blur();

			                    // This is required in Internet Explorer as the blur() order is different
			                    jQuery(window)[0].focus();

			                    break;
			            }
			        }
			        //#end private events

			        //#start public methods

			        ///<summary>
			        ///	Updates the combobox with the current selected item.
			        ///	This function is called if the original Select element selection has been changed
			        ///</summary>
			        this.updateSelection =
						function () {
						    setContentDisplay();
						};

			        ///<summary>
			        ///	The Drop Down List Container will already have been created.
			        ///	This function recreates the items and binds the events to them.
			        ///	Thereafter, the current selection is set.
			        ///</summary>
			        this.update =
						function () {
						    buildDropDownList();
						    updateDropDownListWidth();
						    bindItemEvents();
						    setContentDisplay();
						};

			        ///<summary>
			        ///	Removes the jquery.combobox leaving the original select element
			        ///</summary>					
			        this.remove =
						function () {
						    //Move the original element to a position before the jquery.combobox			
						    _containerJQuery.before(_originalElementJQuery);
						    _containerJQuery.remove();

						    // Remove the internal object property from the DOM
						    //TODO: next statement does not work in Internet Explorer 6.
						    //delete _originalElementJQuery[0].internalCombobox;
						    _originalElementJQuery[0].internalCombobox = null;

						    _originalElementJQuery.show();
						};

			        ///<summary>
			        ///	Adds a range of options into the combobox.
			        ///	Using this function bypasses the browsers restriction of adding
			        ///	html as text values. This allows customisation of the display text
			        ///	Format of dataSource
			        ///	[
			        ///		{
			        ///			value: object, // usual a unique string value
			        ///			text: object,  // can be normal text or html
			        ///			title: string  // optional
			        ///		}
			        ///	]
			        ///	Note: Still in development
			        ///</summary>
			        this.addRange =
						function (dataSource) {
						    if (dataSource) {
						        var originalOptions = _originalElementJQuery[0].options;
						        var optionTotal = originalOptions.length;
						        for (optionIndex in dataSource) {
						            var option = dataSource[optionIndex];
						            var optionElement = document.createElement("option");
						            optionElement.value = option.value;
						            optionElement.text = option.text;
						            // Store the raw text data. Option.text removes all HTML content
						            optionElement.dataText = option.text;
						            if (option.title) {
						                optionElement.title = option.title;
						            }

						            originalOptions[optionTotal + optionIndex] = optionElement;
						        }

						        _originalElementJQuery.combobox.update();
						    }
						};

			        //#end public methods

			    }
			});
	}
/*
TODOS:
- look to moving functions to outside the context and use a state based object to track individual elements [0]
*/
