/*
jquery.combobox
version 0.1.2.3 alpha

ahura mazda
copyright 2007
jquery.sanchezsalvador.com
*/
jQuery.fn.combobox = function(options)
{
   // Setting class
   var el = this;
   var settings =
   {
      comboboxContainerClass: null,
      comboboxValueContainerClass: null,
      comboboxValueContentClass: null,
      comboboxDropDownButtonClass: null,
      comboboxDropDownClass: null,
      comboboxDropDownItemClass: null,
      comboboxDropDownItemHoverClass: null,
      comboboxDropDownGroupItemHeaderClass: null,
      comboboxDropDownGroupItemContainerClass: null,
      animationType: "slide",
      width: "120px"
  };

   if (options)
   {
      jQuery.extend(settings, options);
   }

   //#region public events

   this.onChange =
      function()
      {
         //Intentionally left empty
      };

   myOnChange =
      function() {

      }
   //#endregion public events

   return this.each(
      function()
      {

         //#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(this);
         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 = _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()
         {
            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 = _containerJQuery.width();
            setInnerWidth(_valueDisplayContainerJQuery, controlWidth);

            var displayValueWidth = (_valueDisplayContainerJQuery.width() - _dropDownButtonJQuery.outerWidth());
            setInnerWidth(_valueDisplayJQuery, displayValueWidth);
            var dropDownButtonHeight = _dropDownButtonJQuery.outerHeight();
            setInnerHeight(_valueDisplayContainerJQuery, 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>
         /// 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)
               {
                 $(el).change();
                 // Check if the onChange event is being consumed, otherwise it will be undefined
                  if (_originalElementJQuery.combobox.onChange)
                  {
                     _originalElementJQuery.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 = 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", "#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 (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();
      });
}
