﻿/*
jquery.combobox
version 0.1.2.3 alpha

ahura mazda
copyright 2007
jquery.sanchezsalvador.com
*/

(function($) {


	$.fn.combobox = function(options) {
		return this.each(function() {
			new $.combobox(this, options);
		});
	};

	// Setting class
	var defaults =
	{
		comboboxContainerClass: null,
		comboboxValueContainerClass: null,
		comboboxValueContentClass: null,
		comboboxDropDownButtonClass: null,
		comboboxDropDownClass: null,
		comboboxDropDownItemClass: null,
		comboboxDropDownItemHoverClass: null,
		comboboxDropDownGroupItemHeaderClass: null,
		comboboxDropDownGroupItemContainerClass: null,
		animationType: "slide",
		width: 120,
		height: 20
	};

	$.combobox = function(e, o) {
		var settings = $.extend({}, defaults, o || {});

		//#region 'private' variables

		// This class can operate of N elements depending on how ComboBox is called
		// for example jQuery('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(e);
		var _containerJQuery = null;
		var _containerDefaultStyle = "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;";
		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;overflow:auto";
		var _dropDownListItemEnforcedStyle = "display:block;";
		var _dropDownListItemDefaultStyle = "cursor:default;padding-left:2px;font-weight:normal;font-style:normal;";
		var _dropDownListGroupItemContainerEnforcedStyle = "list-style-type:none;";
		var _dropDownListGroupItemContainerDefaultStyle = "padding-left:10px;margin-left:0;";
		var _dropDownListGroupItemHeaderEnforcedStyle = "";
		var _dropDownListGroupItemHeaderDefaultStyle = "font-style:italic;font-weight:bold;";
		var _valueDisplayContainerJQuery = null;
		var _valueDisplayContainerEnforcedStyle = "position:relative;overflow:hidden;";
		var _valueDisplayJQuery = null;
		var _valueDisplayEnforcedStyle = "float:left;position:absolute;cursor:default;overflow:hidden;";
		var _dropDownButtonJQuery = null;
		var _dropDownButtonDefaultStyle = "overflow:hidden;width: 16px;height: 18px;color:#000;background: #D6D3CE;,font-family: verdana;font-size: 10px;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;";
		var _lastItemSelectedJQuery = null;
		var _lastValue = null;
		var _downdownListPositionIsInverted = false;
		var _maximumItemLength = 0;
		var _dropDownListOffset = null;

		//#endregion 'private' variables

		//#region '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>
		///	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 require 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);
		}

		///<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 require in certain circumstances.			
		///</summary>				
		function setInnerHeight(elementJQuery, height) {
			var differenceheight = (elementJQuery.outerHeight() - elementJQuery.height());

			elementJQuery.height(height - differenceheight);
		}

		///<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 buildValueDisplay() {
			// A container for the Display Value and DropDownButton. A container is required as the child elements
			// are floated
			var valueDisplayContainerHTML = "";
			if (settings.comboboxValueContainerClass) {
				valueDisplayContainerHTML = String.format("<div class='{0}' style='{1}'></div>", settings.comboboxValueContainerClass, _valueDisplayContainerEnforcedStyle);
			}
			else {
				valueDisplayContainerHTML = String.format("<div style='{0}'></div>", _valueDisplayContainerEnforcedStyle);
			}

			// Create the equivalent of the select 'textbox' where the current selected value is shown
			var valueDisplayHTML = "";
			if (settings.comboboxValueContentClass) {
				valueDisplayHTML = String.format("<div class='{0}' style='{1}'></div>", settings.comboboxValueContentClass, _valueDisplayEnforcedStyle);
			}
			else {
				valueDisplayHTML = String.format("<div style='{0}'></div>", _valueDisplayEnforcedStyle);
			}

			var dropdownButtonHTML = "";
			if (settings.comboboxDropDownButtonClass) {
				dropdownButtonHTML = String.format("<div class='{1}' style='{0}'></div>", _dropDownButtonEnforcedStyle, settings.comboboxDropDownButtonClass);
			}
			else {
				dropdownButtonHTML = String.format("<div style='{0}'>{1}</div>", (_dropDownButtonEnforcedStyle + _dropDownButtonDefaultStyle), _dropDownButtonDefaultCharacter);
			}

			_valueDisplayJQuery = jQuery(valueDisplayHTML);
			_dropDownButtonJQuery = jQuery(dropdownButtonHTML);
			_valueDisplayContainerJQuery = jQuery(valueDisplayContainerHTML);

			_valueDisplayContainerJQuery.appendTo(_containerJQuery);
			_valueDisplayJQuery.appendTo(_valueDisplayContainerJQuery);
			_dropDownButtonJQuery.appendTo(_valueDisplayContainerJQuery);

			setDropDownButtonState(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 dataItemValue = null;
			var dataItemStyle = "";
			var dataItemType = "option";

			if (childJQuery.is('option')) {
				dataItemText = childJQuery.text();
				dataItemValue = childJQuery.val();

				if (settings.comboboxDropDownItemClass) {
					dataItemClass = settings.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 {
				dataItemText = childJQuery.attr('label');
				dataItemValue = childJQuery.attr('class');
				dataItemType = "optgroup";

				if (settings.comboboxDropDownGroupItemHeaderClass) {
					dataItemClass = settings.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].dataValue = dataItemValue;
			dataItemJQuery[0].dataType = dataItemType;
			dataItemJQuery[0].title = dataItemText;

			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 (settings.comboboxDropDownGroupItemContainerClass) {
								dataGroupItemHTML = String.format("<ul style='{0}' class='{1}'></ul>", _dropDownListGroupItemContainerEnforcedStyle, settings.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 (settings.comboboxDropDownClass) {
					dropDownHTML = String.format("<ul class='{0}' style='{1}'></ul>", settings.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);
			}
		}

		///<summary>
		/// Applies CSS styling from a string that contains multiple style settings
		///	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>
		///	Changes the image of the drop down button based on the state
		///	Normal = 0
		///	Pressed = 1
		///</summary>
		function setDropDownButtonState(state) {
			if (settings.comboboxDropDownButtonClass) {
				var width = _dropDownButtonJQuery.width();
				var offset = state * width;
				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>
		///	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 = settings.width;
			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() {
			var displayValueHeight = _valueDisplayJQuery.outerHeight();
			var displayContainerHeight = _valueDisplayContainerJQuery.height();
			var difference = ((displayContainerHeight - displayValueHeight) / 2);

			if (difference < 0) {
				difference = 0;
			}

			//TODO: add other alignments for the user, such as left, top, middle, bottom, etc
			_valueDisplayJQuery.css("top", difference);
		}

		///<summary>
		///	Applies custom layout position and sizing to the controls
		///</summary>
		function applyLayout() {
			_containerJQuery.width(settings.width);

			// Removes any units and retrieves only the value of width
			var controlWidth = settings.width;
			setInnerWidth(_valueDisplayContainerJQuery, controlWidth);

			var displayValueWidth = (controlWidth - _dropDownButtonJQuery.outerWidth());
			setInnerWidth(_valueDisplayJQuery, displayValueWidth);
			setInnerHeight(_valueDisplayContainerJQuery, settings.height);

			_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>
		/// Bind all items to mouse events except for UL elements
		/// and LI elements that are option group labels
		///</summary>			
		function bindItemEvents() {
			jQuery("*", _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();

								container_onItemClick(itemJQuery);
							});

						itemJQuery.mouseover(
							function() {
								container_onItemMouseOver(itemJQuery);
							});

						itemJQuery.mouseout(
							function() {
								container_onItemMouseOut(itemJQuery);
							});
					});
		}

		///<summary>
		///		Bind the dropdown list control blur event to a function
		///</summary>
		function bindBlurEvent() {
			_dropDownListJQuery.blur(
					function(blurEvent) {
						blurEvent.stopPropagation();

						dropDownListJQuery_onBlur();
					});
		}

		///<summary>
		///	Bind the click event of the container to a function
		///</summary>
		function bindContainerClickEvent() {
			_containerJQuery.click(
					function(clickEvent) {
						container_onClick();
					});
		}

		///<summary>
		///	Remove the binding of a custom function from the container's click event
		///</summary>
		function unbindContainerClickEvent() {
			_containerJQuery.unbind("click");
		}

		///<summary>
		///		Bind this control to the events to custom functions
		///</summary>
		function bindEvents() {
			_containerJQuery.keydown(
					function(keyEvent) {
						keyEvent.preventDefault(); container_onKeyDown(keyEvent)
					});

			bindContainerClickEvent();

			bindBlurEvent();

			bindItemEvents();
		}

		///<summary>
		///		Sets the value both internally and visually to the user
		///</summary>
		function setDisplayValue() {
			var valueHasChanged = false;
			var originalElement = _originalElementJQuery[0];

			if (originalElement.length > 0) {
				var selectedText = originalElement[originalElement.selectedIndex].text;
				_valueDisplayJQuery.text(selectedText);
				_valueDisplayJQuery.attr("title", selectedText);

				// Reposition the display value based on height of the element after the text has changed
				positionDisplayValue();

				if (_lastValue) {
					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 (settings.onChange) {
						settings.onChange(selectedText, _lastValue);
					}
				}

				// 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 = jQuery("li[dataValue='" + _lastValue + "']", _dropDownListJQuery);

				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 (settings.comboboxDropDownItemHoverClass) {
					if (isHighlighted) {
						elementJQuery.addClass(settings.comboboxDropDownItemHoverClass);
					}
					else {
						elementJQuery.removeClass(settings.comboboxDropDownItemHoverClass);
					}
				}
				else {
					if (isHighlighted) {
						elementJQuery.css("background", "#999999");
						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 (settings.comboboxContainerClass) {
				containerHTML = String.format("<div class='{0}' style='{1}'></div>", settings.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>
		function initialiseControl() {
			buildContainer();

			buildValueDisplay();

			buildDropDownList();

			applyLayout();

			bindEvents();

			setDisplayValue();
		}

		///<summary>
		///	Focus must be set to the DropDown list element only after it has shown.
		///	This is due to IE executing the Blur event before the list has immediately shown
		///</summary>
		function setDropDownListFocus() {
			_dropDownListJQuery.focus();
		}

		///<summary>
		///	Focus set to the Combobox Container
		///</summary>
		function setAndBindContainerFocus() {
			_containerJQuery.focus();
			bindContainerClickEvent();
		}

		///<summary>
		///	Slides up the DropDownlist when it is to be placed above the CB
		///</summary>
		function slideUp(newTop) {
			_dropDownListJQuery.animate(
					{
						height: "toggle",
						top: newTop
					},
					"fast",
					setDropDownListFocus);
		}

		///<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",
						top: newTop
					},
					"fast",
					setAndBindContainerFocus);
		}

		///<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 settings.animationType.
		///	This method also changes the button state
		///</summary>					
		function toggleDropDownList(isShown) {
			if (isShown) {
				if (_dropDownListJQuery.is(":hidden")) {
					// Remove the click event from the container because when the dropdown list is shown
					// and the container is clicked, the dropdownlist blur event is fired which hides the control
					// and the container click is fired after which will show the list again (error);
					unbindContainerClickEvent();

					// When the DropDown list is shown, highlist the current value in the list
					toggleItemHighlight(_lastItemSelectedJQuery, true);

					setDropDownButtonState(1);

					var dropdownListTop = getDropDownListTop();
					_dropDownListJQuery.css("top", dropdownListTop);
					//_dropDownListJQuery.css("left", _containerJQuery.offset().left);

					switch (settings.animationType) {
						case "slide":
							if (_downdownListPositionIsInverted) {
								var comboboxTop = _containerJQuery.position().top;
								var containerHeight = _containerJQuery.outerHeight();

								_dropDownListJQuery.css("top", (comboboxTop - containerHeight));

								slideUp(dropdownListTop);
							}
							else {
								_dropDownListJQuery.slideDown("fast", setDropDownListFocus);
							}
							break;

						case "fade":
							_dropDownListJQuery.fadeIn("fast", setDropDownListFocus);
							break;

						default:
							_dropDownListJQuery.show();
							setDropDownListFocus();
					}
				}
			}
			else {
				if (_dropDownListJQuery.is(":visible")) {
					setDropDownButtonState(0);

					switch (settings.animationType) {
						case "slide":
							if (_downdownListPositionIsInverted) {
								comboboxTop = _containerJQuery.position().top;
								dropdownListHeight = _dropDownListJQuery.height();

								slideDown(comboboxTop - _containerJQuery.outerHeight());
							}
							else {
								_dropDownListJQuery.slideUp("fast", setAndBindContainerFocus)
							}
							break;

						case "fade":
							_dropDownListJQuery.fadeOut("fast", setAndBindContainerFocus);
							break;

						default:
							_dropDownListJQuery.hide();
							setAndBindContainerFocus();
					}
				}
			}
		}

		///<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;
			}

			originalElement.selectedIndex = newIndex;
			setDisplayValue();
		}

		///<summary>
		///	Returns true if the DropDownList visible on screen, else false
		///</summary>
		function isDropDownVisible() {
			return _dropDownListJQuery.is(":visible");
		}

		//#endregion 'private' functions

		//#region 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>
		_originalElementJQuery.combobox.updateSelection =
				function() {
					setDisplayValue();
				};

		///<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>
		_originalElementJQuery.combobox.update =
				function() {
					buildDropDownList();
					updateDropDownListWidth();
					bindItemEvents();
					setDisplayValue();
				};

		//#endregion public methods

		//#region private events

		function container_onClick() {
			if (_dropDownListJQuery.is(":hidden")) {
				toggleDropDownList(true);
			}
			else {
				toggleDropDownList(false);
			}
		}

		function dropDownListJQuery_onBlur() {
			if (_dropDownListJQuery.is(":visible")) {
				toggleDropDownList(false);
			}
		}

		function container_onItemClick(itemJQuery) {
			_originalElementJQuery.val(itemJQuery[0].dataValue);

			setDisplayValue();

			toggleDropDownList(false);
		}

		function container_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(itemJQuery, true);
		}

		function container_onItemMouseOut(itemJQuery) {
			toggleItemHighlight(itemJQuery, false);
		}

		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
					$(window)[0].focus();

					break;
			}

			keyEvent.cancelBubble = true;
		}

		//#endregion private events

		initialiseControl();
	};
})(jQuery);

