(function ($){
/*!
 * mcDropdown jQuery Plug-in
 *
 * Copyright 2008 Giva, Inc. (http://www.givainc.com/labs/) 
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *  http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * Date: 2008-09-04
 * Rev:  1.2.07
 */
  $.fn.mcDropdown = function(list, options) {
    // track the dropdown object
    var dd;

    // create a dropdown for each match
    this.each(function() {
      dd = $.data(this, "mcDropdown");
      // we're already a dropdown, return a reference to myself
      if( dd ) return false;

      new $.mcDropDownMenu(this, list, options);
    });
    
    // return either the dropdown object or the jQuery object reference
    return dd || this;
  };

  // set default options
  $.mcDropdown = {
    version: "1.2.07",
    setDefaults: function(options){
      $.extend(defaults, options);
    }
  };

  // set the defaults
  var defaults = {
      minRows: 7                    // specify the minimum rows before creating a new column
    , maxRows: 30                   // specify the maximum rows in a column
    , targetColumnSize: 1           // specify the default target column size (it'll attempt to create this many columns by default, unless the min/max row rules are not being met)
    , openFx: "show"                // the fx to use for showing the root menu
    , openSpeed: 0                  // the speed of the openFx
    , closeFx: "hide"               // the fx to use for hiding the root menu
    , closeSpeed: 0                 // the speed of the closeFx
    , hoverOverDelay: 300           // the delay before opening a submenu
    , hoverOutDelay: 200            // the delay before closing a submenu
    , showFx: "show"                // the fx to use when showing a submenu
    , showSpeed: 0                  // the speed of the showFx
    , hideFx: "hide"                // the fx to use when closing a submenu
    , hideSpeed: 0                  // the speed of the hideFx
    , dropShadow: true              // determine whether drop shadows should be shown on the submenus
    , autoHeight: true              // always uses the lineHeight options (much faster than calculating height)
    , lineHeight: 19                // the base height of each list item (li) this is normally calculated automatically, but in some cases the value can't be determined and you'll need to set it manually
    , screenPadding: 10             // the padding to use around the border of the screen -- this is used to make sure items stay on the screen
    , allowParentSelect: false      // determines if parent items are allowed to be selected (by default only end nodes can be selected)
    , delim: " > "                  // the delimited to use when showing the display string
    , showACOnEmptyFocus: false     // show the autocomplete box on focus when input is empty
    , valueAttr: "rel"              // the attribute that contains the value to use in the hidden field
    , tooltip: false                // if true a tooltip is set for any text selected
    , click: null                   // callback that occurs when the user clicks on a menu item
    , select: null                  // callback that occurs when a value is selected
    , init: null                    // callback that occurs when the control is fully initialized
  };

  // check to see if the browser is IE6 
  var isIE6 = ($.browser.version && $.browser.version <= 6);

  $.mcDropDownMenu = function(el, list, options){
    var $self, thismenu = this, $list, $divInput, settings, typedText = "", matchesCache, oldCache, $keylist, $keylistiframe, bInput, bDisabled = false;
    
    // create a reference to the dropdown
    $self = $(el);
    
    // is the field and input element
    bInput = $self.is(":input");
    
    // get the settings for this instance
    settings = $.extend({}, defaults, options);
    
    // set the default click behavior
    if( settings.click == null ) {
      settings.click = function (e, dropdown, settings){
        dropdown.setValue(this.attr(settings.valueAttr));
      }
    }
  
    // attach window behaviors
    $(document)
      // Bind a click event to hide all visible menus when the document is clicked
      .bind("click", function(e){
        // get the target that was clicked
        var $target = $(e.target);
        var $ul = $target.parents().filter(function (){ return this === $list[0] || (!!$keylist && $keylist[0] === this); });
        // check to make sure the clicked element was inside the list
        if( $ul.length ){
          var bIsParent = $target.is(".mc_parent");
          
          // if we've clicked a parent item in the autocomplete box, we must adjust the current value
          if( bIsParent && $keylist && $ul[0] === $keylist[0] ){
            // prevent the blur code from running
            clearTimeout(iBlurTimeout);
            updateValue($target.find("> ul > li:first"), false);
            e.stopPropagation();
            return false;
          }
          // check to see if the user can click on parent items
          else if( !settings.allowParentSelect && bIsParent ) return false;

          // make sure to hide the parent branch if we're not the root
          if( $target.not(".mc_root") ) hideBranch.apply($target.parent().parent()[0], [e]);
          
          if( settings.click != null && settings.click.apply($target, [e, thismenu, settings]) == false ){
            return false;
          }
        }
        
        // close the menu
        thismenu.closeMenu();
      });
      
    // store a reference to the list, if it's not already a jQuery object make it one
    $list = (((typeof list == "object") && !!list.jquery)) ? list : $(list);
    
    // we need to calculate the visual width for each nested list
    $list
      // move list to body -- this allows us to always calculate the correct position & width of the elements
      .appendTo("body")
      // move the list way off screen
      .css({position: "absolute", top: -10000, left: -10000})
      // find all the ul tags
      .find("ul")
      // add the root ul tag to the array
      .andSelf()
      // make all the nodes visible
      .css("display", "block")
      // loop through each node
      .each(function (){
        var $el = $(this);
        // calculate the width of the element -- using clientWidth is 2x as fast as width()
        $el.data("width", $el[0].clientWidth);
      })
      // now that we've gotten the widths, hide all the lists and move them to x:0, y:0
      .css({top: 0, left: 0, display: "none"});
      
    // mark the root children items
    $list.find("> li").addClass("mc_root");
    // add parent class
    $("li > ul", $list).parent().addClass("mc_parent");

    // create the div to wrap everything in
    $divInput = $('<div class="mcdropdown"><a href="#" tabindex="-1"></a><input type="hidden" name="' + (el.name || el.id) + '" id="' + (el.id || el.name) + '" /></div>')
      .appendTo($('<div style="position: relative;"></div>'))
      .parent();
      
    // get a reference to the input element and remove it from the DOM        
    var $input = $self.replaceWith($divInput).attr({id: "", name: ""});
    // get a reference to the hidden form field
    var $hidden = $divInput.find(":hidden");
    
    // put the input element back in the div.mcdropdown layer
    $divInput = $divInput.find(".mcdropdown").prepend($input);
    
    // make a visible copy of the element so we can get the correct sizes, then delete it
    var $divInputClone = $divInput.clone().css({position: "absolute", top: -9999999, left: -999999, visibility: "visible"}).show().appendTo("body");
    var di = {width: $divInputClone.width() - $("a", $divInputClone).width(), height: $divInputClone.outerHeight()}
    $divInputClone.remove();
    
    // store a reference to this link select
    $.data($hidden[0], "mcDropdown", this);

    // update the height of the outer relative div, this allows us to
    // correctly anchor the dropdown
    $divInput.parent().height(di.height);

    // safari will not get the correct width until after everything has rendered    
    if( $.browser.safari ){
      setTimeout(function (){
        $self
        .width($divInput.width() - $("a", $divInput).width());
      }, 100);
    }
    
    // adjust the width of the new input element
    $self
      .width(di.width)
      // make sure we only attach the next events if we're in input element
      .filter(":input")
      // turn autocomplete off
      .attr("autocomplete", "off")
      // add key stroke bindings (IE6 requires keydown)
      .bind("keypress", checkKeypress)
      // prevent user from selecting text
      .bind("mousedown", function (e){ $(this).triggerHandler("focus"); e.stopPropagation(); return false; })
      // disable context menu
      .bind("contextmenu", function (){ return false; })
      // select the text when the cursor is placed in the field
      .bind("focus", onFocus)
      // when the user leaves the text field
      .bind("blur", onBlur);
      
    // IE6 doesn't register certain keypress events, so we must catch them during the keydown event
    if( $.browser.msie || $.browser.safari) $self.bind("keydown", function (e){
      // check to see if a key was pressed that IE6 doesn't trigger a keypress event for
      if( ",8,9,37,38,39,40,".indexOf("," + e.keyCode + ",") > -1 ) return checkKeypress(e);
    });
    
    // attach a click event to the anchor
    $("a", $divInput).bind("click", function (e){
      // if disabled, skip processing
      if( bDisabled ) return false;
      thismenu.openMenu(e);
      return false;
    });
    
    // set the value of the field
    this.setValue = function (value, skipCallback){
      // get the display name
      var name = displayString(value);
      
      // If no item was selected and we are not skipping the callback then do
      // not change the content and raise an event
      if ((!name) && (!skipCallback)) { return false; }
      
      // update the hidden value
      $hidden.val((value) ? value : '');
      
      // run the select callback (some keyboard entry methods will manage this callback manually)
      if( settings.select != null && skipCallback != true ) {
        settings.select.apply($self, [$hidden.attr('id'), value, name]);
      }
      
      // Add a tooltip to the DIV to show large names if configured
      if ( settings.tooltip ) {
        $self.attr('title', name);
      }
      
      if (name) {
        // update the display value and return the jQuery object
        return $self[bInput ? "val" : "text"](name);
      } else {
        // update the display value, set default text and return the jQuery object
        return this.setRawValue($self.attr('defaultValue'));
      }
    };
    
    // set a "raw" value in the field. Used for default text
    this.setRawValue = function (value) {
      // Add a tooltip to the DIV to show large names if configured
      if ( settings.tooltip ) {
        $self.attr('title', value);
      }
      
      // update the display value and return the jQuery object
      return $self.html(value);
    };
    
    // set the default value (but don't run callback)
    if( bInput ) {
      this.setValue($self.attr("defaultValue"), true);
    } else {
      this.setRawValue($self.attr("defaultValue"), true);
    }
  
    // get the value of the field (returns array)
    this.getValue = function (value){
      return [$hidden.val(), $self[bInput ? "val" : "text"]()];
    };
    
    // open the menu programmatically
    this.openMenu = function (e){
      // if the menu is open, kill processing
      if( $list.is(":visible") ){
        // on a mouse click, close the menu, otherwise just cancel
        return (!!e) ? thismenu.closeMenu() : false;
      }
      
      function open(){
        // columnize the root list
        columnizeList($list).hide();
        
        // add the bindings to the menu
        addBindings($list);
        
        // anchor the menu relative parent
        anchorTo($divInput.parent(), $list, true);
        
        // remove existing hover classes, which might exist from keyboard entry
        $list.find(".mc_hover").removeClass("mc_hover");

        // show the menu
        $list[settings.openFx](settings.openSpeed, function (){
          // scroll the list into view
          scrollToView($list);
        });

        // if the bgIframe exists, use the plug-in
        if( isIE6 && !!$.fn.bgIframe ) $list.bgIframe();
      }
      
      // if this is triggered via an event, just open the menu
      if( e ) open();
      // otherwise we need to open the menu asynchronously to avoid collision with $(document).click event
      else setTimeout(open, 1);
    };
    
    // close the menu programmatically
    this.closeMenu = function (e){
      // hide any open menus
      $list.find("ul:visible").parent().each(function (){
        hideBranch.apply(this);
      });
      
      // remove the bindings
      removeBindings($list);
      
      // close the menu
      $list[settings.closeFx](settings.closeSpeed);
    };
    
    // place focus in the input box
    this.focus = function (){
      $self.focus();
    };
  
    // disable the element
    this.disable = function (status){
      // change the disabled status
      bDisabled = !!status;
      
      $divInput[bDisabled ? "addClass" : "removeClass"]("mcdropdownDisabled");
      $input.attr("disabled", bDisabled ? "disabled" : "");
    };
    
    function getNodeText($el){
      // return either an empty string or the node's value
      return $el.contents()[0] ? $.trim($el.contents()[0].nodeValue) : "";
    };
    
    function getTreePath($li){
      if( $li.length == 0 ) return [];
  
      var name = [getNodeText($li)];
      // loop through the parents and get the value
      $li.parents().each(function (){
        var $el = $(this);
        // break when we get to the main list element
        if( this === $list[0] ) return false;
        else if( $el.is("li") ) name.push(getNodeText($el));
      });
  
      // return the display name
      return name.reverse();
    };
    
    function displayValue(value){
      // return the path as an array
      return getTreePath(getListItem(value));
    };
    
    function displayString(value){
      // return the display name
      return displayValue(value).join(settings.delim);
    };
    
    function parseTree($selector){
      var s = [], level = (arguments.length > 1) ? ++arguments[1] : 1;
  
      // loop through all the children and store information about the tree
      $("> li", $selector).each(
        function (){
          // get a reference to the current object
          var $self = $(this);
  
          // look for a ul tag as a direct child
          var $ul = $("> ul", this);
          
          // push a reference to the element to the tree array
          s.push({
            // get the name of the node
              name:     getNodeText($self)
            // store a reference to the current element
            , element:  this
            // parse and store any children items
            , children: ($ul.length) ? parseTree($ul, level) : []
          });
  
        }
      );
      
      return s;
    };
    
    function addBindings(el){
      removeBindings(el);
      $("> li", el)
        .bind("mouseover", hoverOver)
        .bind("mouseout", hoverOut);
    };
    
    function removeBindings(el){
      $("> li", el)
        .unbind("mouseover", hoverOver)
        .unbind("mouseout", hoverOut);
    };
    
    // scroll the current element into view
    function scrollToView($el){
      // get the current position
      var p = position($el, true);
      // get the screen dimensions
      var sd = getScreenDimensions();
      
      // if we're hidden off the bottom of the page, move up
      if( p.bottom > sd.y ){
        $("html,body").animate({"scrollTop": "+=" + ((p.bottom - sd.y) + settings.screenPadding) + "px" })
      }
    };
    
    function hoverOver(e){
      var self = this;
      var timer = $.data(self, "timer");
      
      // if the timer exists, clear it
      if( !isNaN(timer) ) clearTimeout(timer);
  
      // if IE6, add the hover class
      $(this).addClass("mc_hover");

      // show the branch
      $.data(self, "timer", setTimeout(function(){
          showBranch.apply(self);
        }, settings.hoverOverDelay)
      );
    };
    
    function hoverOut(e){
      var self = this;
      var timer = $.data(self, "timer");
      
      // if the timer exists, clear it
      if( !isNaN(timer) ) clearTimeout(timer);
  
      // if IE6, remove the hover class
      $(this).removeClass("mc_hover");
      
      // hide the branch
      $.data(self, "timer", setTimeout(function(){
          var $li = $(self);
          setTimeout(function (){
            // if no children selected, we must close the parent menus
            if( $li.parent().find("> li.mc_hover").length == 0 ){
              $li.parents("li").each(function (){
                var self = this;
                clearTimeout($.data(self, "timer"));
                hideBranch.apply(self);
                // check to see if we've hovered over a parent item
                if( $(this).siblings().filter(".mc_hover").length > 0 ) return false;
              });
          }
            
          }, settings.hoverOverDelay);

          hideBranch.apply(self);
        }, settings.hoverOutDelay)
      );
      
      // this will stop flickering in IE6, but it leaves mc_hover classes behind
      if( isIE6 ) e.stopPropagation();
    };
    
    function getShadow(depth){
      var shadows = $self.data("shadows");
      
      // if the shadows don't exist, create an object to track them
      if( !shadows ) 
        shadows = {};
      
      // if the shadow doesn't exist, create it
      if( !shadows[depth] ){
        // create shadow
        shadows[depth] = $('<div class="mcdropdown_shadow"></div>').appendTo('body');
        // if the bgIframe exists, use the plug-in
        if( !!$.fn.bgIframe ) shadows[depth].bgIframe();
        // update the shadows cache
        $self.data("shadows", shadows);
      }
      
      return shadows[depth];
    };
    
    function showBranch(ul){
      // the child menu
      var $ul = (ul == undefined) ? $("> ul", this) : ul;
      var self = (ul == undefined) ? this : ul;
      
      // if the menu is already visible or there is no submenu, cancel
      if( $ul.is(":visible") || ($ul.length == 0) ) return false;
      
      // hide any visible sibling menus
      $(self).parent().find('> li ul:visible').not($ul).parent().each(function(){
        hideBranch.apply(self);
      });
      
      // columnize the list
      columnizeList($ul);
      
      // add new bindings
      addBindings($ul);
  
      var depth = $ul.parents("ul").length;
      
      // get the screen dimensions
      var sd = getScreenDimensions();
      
      // get the coordinates for the menu item
      var li_coords = position($(self));
  
      // move the menu to the correct position and show the menu || ((depth)*2)
      if (depth == 0) {
        $ul.css({ top: li_coords.top, left: li_coords.left }).show();
      } else {
        $ul.css({top: li_coords.bottom, left: li_coords.marginLeft/*, zIndex: settings.baseZIndex + ((depth)*2)*/}).show();
      }
      
      // get the bottom of the menu
      var menuBottom = $ul.outerHeight() + $ul.offset().top;
      
      // if we're hidden off the bottom of the page, move up
      if( menuBottom > sd.y ){
        // adjust the menu by subtracting the bottom edge by the screen offset
        $ul.css("top", li_coords.bottom - (menuBottom - sd.y) - settings.screenPadding);
      }
      
      var showShadow = function (){
        // if using drop shadows, then show them    
        if( settings.dropShadow ){
          // get a reference to the current shadow
          var $shadow = getShadow(depth);
          // get the position of the parent element
          var pos = position($ul);
          
          // move the shadow to the correct visual & DOM position
          $shadow.css({
              top: pos.top + pos.marginTop
            , left: pos.left + pos.marginLeft
            , width: pos.width
            , height: pos.height
            /*, zIndex: settings.baseZIndex + ((2*depth)-1)*/
          }).insertAfter($ul).show();
          
          // store a reference to the shadow so we can hide it    
          $.data(self, "shadow", $shadow);
        }
      }
      
      // columnize the list and then show it using the defined effect
      // if the menu has a zero delay, just open it and then draw the
      // shadow, otherwise show the effect and the draw the shadow
      // after you're done.
      if( settings.showSpeed <= 0 ){
        showShadow();
      } else {
        $ul.hide()[settings.showFx](settings.showSpeed, showShadow);
      }
    };
    
    function hideBranch() {
      var $ul = $("> ul", this);
      
      // if the menu is already visible or there is no submenu, cancel
      if( $ul.is(":hidden") || ($ul.length == 0) ) return false;
      
      // if using drop shadows, then hide
      if( settings.dropShadow && $.data(this, "shadow") ) $.data(this, "shadow").hide();
  
      // if we're IE6, we need to set the visiblity to "hidden" so child
      // menus are correctly hidden and remove the .mc_hover class due to
      // the e.stopPropagation() call in the hoverOut() call
      if( isIE6 )
        $ul.css("visibility", "hidden").parent().removeClass("mc_hover");
  
      // hide the menu
      $ul.stop()[settings.hideFx](settings.hideSpeed);
    };
  
    function position($el, bUseOffset){
      var bHidden = false;
      // if the element is hidden we must make it visible to the DOM to get
      if ($el.is(":hidden")) {
        bHidden = !!$el.css("visibility", "hidden").show();
      }
      
      var pos = $.extend($el[bUseOffset === true ? "offset" : "position"](),{
          width: $el.outerWidth()
        , height: $el.outerHeight()
        , marginLeft: parseInt($.curCSS($el[0], "marginLeft", true), 10) || 0
        , marginRight: parseInt($.curCSS($el[0], "marginRight", true), 10) || 0
        , marginTop: parseInt($.curCSS($el[0], "marginTop", true), 10) || 0
        , marginBottom: parseInt($.curCSS($el[0], "marginBottom", true), 10) || 0
      });
      
      if( pos.marginTop < 0 ) pos.top += pos.marginTop;
      if( pos.marginLeft < 0 ) pos.left += pos.marginLeft;
      
      pos["bottom"] = pos.top + pos.height;
      pos["right"] = pos.left + pos.width;
      
      // hide the element again
      if( bHidden ) $el.hide().css("visibility", "visible");
  
      return pos;
    };
    
    function anchorTo($anchor, $target, bUseOffset){
      var pos = position($anchor, bUseOffset);
      
      $target.css({
          position: "absolute"
        , top: pos.bottom
        , left: pos.left
      });
      
      /*
       * we need to return the top edge of the core drop down menu, because
       * the top:0 starts at this point when repositioning items absolutely
       * this means we have to offset everything by the offset of the top menu
       */ 
      
      return pos.bottom;
    };
    
    function getScreenDimensions(){
      var d = {
          scrollLeft: $(window).scrollLeft()
        , scrollTop:  $(window).scrollTop()
        , width:      $(window).width()     // changed from innerWidth
        , height:     $(window).height()    // changed from innerHeight     
      };
      
      // calculate the correct x/y positions
      d.x = d.scrollLeft + d.width;
      d.y = d.scrollTop + d.height;
      
      return d;
    };
    
    function getPadding(el, name){
      var torl = name == 'height' ? 'Top'    : 'Left',  // top or left
          borr = name == 'height' ? 'Bottom' : 'Right'; // bottom or right
      
      return (
        // we add "0" to each string to make sure parseInt() returns a number
          parseInt("0"+$.curCSS(el, "border"+torl+"Width", true), 10)
        + parseInt("0"+$.curCSS(el, "border"+borr+"Width", true), 10)
        + parseInt("0"+$.curCSS(el, "padding"+torl, true), 10)
        + parseInt("0"+$.curCSS(el, "padding"+borr, true), 10)
        + parseInt("0"+$.curCSS(el, "margin"+torl, true), 10)
        + parseInt("0"+$.curCSS(el, "margin"+borr, true), 10)
      );
    };
    
    function getListDimensions($el, cols){
      if( !$el.data("dimensions") ){
        // get the width of the dropdown menu
        var ddWidth = $divInput.outerWidth();
        // if showing the root item, then try to make sure the width of the menu is sized to the drop down menu
        var width = ( ($el === $list) && ($el.data("width") * cols < ddWidth) ) ? Math.floor(ddWidth/cols) : $el.data("width");
        if (width == undefined) { width = ddWidth; }
        
        $el.data("dimensions", {
          // get the original width of the list item
          column: width
          // subtract the padding from the first list item from the width to get the width of the items
          , item: width - getPadding($el.children().eq(0)[0], "width")
          // get the original height
          , height: $el.height()
        });
      }
      
      return $el.data("dimensions");
    };
    
    function getHeight($el){
      // skip height calculation and use lineHeight
      if( settings.autoHeight === false ) return settings.lineHeight;
      // if we haven't cached our height, do so now
      if( !$el.data("height") ) $el.data("height", $el.outerHeight());
  
      // return the cached value
      return $el.data("height");
    };
    
    function columnizeList($el){
      // get the children items
      var $children = $el.find("> li");
      // get the total number of items
      var items = $children.length;
      
      // calculate how many columns we think we should have based on the available screen space
      var calculatedCols = Math.ceil(items/settings.maxRows); //Math.ceil(settings.lineHeight * items / window.innerHeight);
      
      // get the number of columns, don't columnize if we don't have enough rows
      // if the height of the column is bigger than the screen, we automatically try 
      // moving to a new column
      var cols = !!arguments[1] ? arguments[1] : ( items <= settings.minRows ) ? 1 : (calculatedCols > settings.targetColumnSize) ? calculatedCols : settings.targetColumnSize;
      
      // get the dimension of this element
      var widths = getListDimensions($el, cols);
      var prevColumn = 0;
      var columnHeight = 0;
      var maxColumnHeight = 0;
      var maxRows = Math.ceil(items/cols);
  
      // get the width of the parent item or the dropdown, if top-level
      var parentLIWidth = $el.parent("li").width();
      parentLIWidth = (parentLIWidth) ? parentLIWidth : $divInput.width();
      
      // if the parent list item is wider than the child menu, increase the size    
      if( parentLIWidth > (widths.column * cols) ){
        // get the difference in the widths so we can adjust
        var widthDiff = parentLIWidth - widths.column;
        widths.column += parentLIWidth;
        widths.item += widthDiff;
      }
      
      // we need to draw the list element, but hide it so we can correctly calculate it's information
      $el.css({"visibility": "hidden", "display": "block"});
      
      // loop through each child item
      $children.each(function (i){
        var currentItem = i+1;
        var nextItemColumn = Math.floor((currentItem/items) * cols);
        // calculate the column we're in
        var column = Math.floor((i/items) * cols);
        // reference the current item
        var $li = $(this);
        // variable to track margin-top
        var marginTop;
  
        // if we're in the same column
        if( prevColumn != column ){
          // move to the top of the next column
          marginTop = (columnHeight+1) * -1;
          // reset column height
          columnHeight = 0;
        // if we're in a new column
        } else {
          marginTop = 0;
        }
        
        // increase the column height based on it's current height (calculate this before adding classes)
        columnHeight += (getHeight($li) || settings.lineHeight);
        
        // update the css settings
        $li.css({
          "marginLeft": (widths.column * column)
          , "marginTop": marginTop
          , "width": widths.item
        })
          [((nextItemColumn > column) || (currentItem == items)) ? "addClass" : "removeClass"]("mc_endcol")
          [(marginTop != 0) ? "addClass" : "removeClass"]("mc_firstrow")
          ;
        // get the height of the longest column     
        if( columnHeight > maxColumnHeight ) maxColumnHeight = columnHeight;
  
        // update the previous column
        prevColumn = column;
      });
  
  /*    
   * this would work on resize the main column
      var s = ($el === $list) ? maxColumnHeight + $divInput.offset().top + $divInput.outerHeight() : maxColumnHeight;
  */
      
      // if the menu is too tall to fit on the screen, try adding another column
      if( ($el !== $list) && (maxColumnHeight + (settings.screenPadding*2) >= getScreenDimensions().height) ){
        return columnizeList($el, cols+1);
      }
  
      /*
       * set the height of the list to the max column height. this fixes
       * display problems in FF when the last column is not full. 
       * 
       * we also need to set the visiblity to "visible" to make sure that
       * the element will show up 
       */ 
      $el.css("visibility", "visible").height(maxColumnHeight);
      
      return $el;
    };
    
    function getListItem(value){
      return $list.find("li[" + settings.valueAttr + "="+ value +"]");
    };
    
    function getCurrentListItem(){
      return getListItem($hidden.val());
    };
    
    function onFocus(e){
      var $current = getCurrentListItem();
      var value = $self.val().toLowerCase();
      var treePath = value.toLowerCase().split(settings.delim);
      var currentNode = treePath.pop();
      var lastDelim = value.lastIndexOf(settings.delim) + 1;
      
      // reset the typed text
      typedText = treePath.join(settings.delim) + (treePath.length > 0 ? settings.delim : "");
  
      // we need to set the selection asynchronously so that when user TABs to field the pre-select isn't overwritten
      setTimeout(function (){
        // preselect the last child node
        setSelection($self[0], lastDelim, lastDelim+currentNode.length);
      }, 0);
      
      // create the keyboard hint list
      if( !$keylist ){
        $keylist = $('<ul class="mcdropdown_autocomplete"></ul>').appendTo("body");
        // if IE6 we need an iframe to hide the scrolling list
        if( isIE6 && !!$.fn.bgIframe ) $keylistiframe = $('<div></div>').bgIframe().appendTo("body");
      } 
      
      // should we show matches?
      var bShowHidden = (!settings.showACOnEmptyFocus && (typedText.length == 0));
    
      // get the siblings for the current item
      var $siblings = ($current.length == 0 || $current.hasClass("mc_root")) ? $list.find("> li") : $current.parent().find("> li");
      // show all matches
      showMatches($siblings, bShowHidden);
    };
    
    var iBlurTimeout;
    function onBlur(e){
      // we may need to cancel this blur event, so we run it asynchronously
      iBlurTimeout = setTimeout(function (){
        // get the current item
        var $current = getCurrentListItem();
        
        // if we must select a child item, then update to the first child we can find
        if( !settings.allowParentSelect && $current.is(".mc_parent") ){
          // grab the first end child item we can find for the current path
          var value = $current.find("li:not('.mc_parent'):first").attr(settings.valueAttr);
          // update the value
          thismenu.setValue(value, true);
        }
        
        // run the select callback
        if( settings.select != null ) settings.select.apply(thismenu, thismenu.getValue());
        
        // hide matches
        hideMatches();
      }, 200);
    };
    
    function showMatches($li, bShowHidden){
      var bCached = ($li === oldCache), $items = bCached ? $keylist.find("> li").removeClass("mc_hover mc_hover_parent mc_firstrow") : $li.clone().removeAttr("style").removeClass("mc_hover mc_hover_parent mc_firstrow mc_endcol").filter(":last").addClass("mc_endcol").end();
  
      // only do the following if we've updated the cache or the list is hidden
      if( !bCached || $keylist.is(":hidden") ){
        // update the matches
        $keylist.empty().append($items).width($divInput.outerWidth() - getPadding($keylist[0], "width")).css("height", "auto");
        
        // anchor the menu relative parent
        anchorTo($divInput.parent(), $keylist, true);

        // show hover on mouseover        
        $items.hover(function (){$keylist.find("> li").removeClass("mc_hover_parent mc_hover"); $(this).addClass("mc_hover")}, function (){$(this).removeClass("mc_hover")});

        // make sure the the ul's are hidden (so the li's are sized correctly)        
        $items.find("> ul").css("display", "none");
    
        // show the list
        $keylist.show();
  
        // if we're IE6, ensure we enforce the "max-height" CSS property      
        if( isIE6 ){
          var maxHeight = parseInt($keylist.css("max-height"), 10) || 0;
          if( (maxHeight > 0) && (maxHeight < $keylist.height()) ) $keylist.height(maxHeight);

          // anchor the iframe behind the scrollable list
          if( !!$.fn.bgIframe ) anchorTo($divInput.parent(), $keylistiframe.css({height: $keylist.height(), width: $keylist.width()}, true).show())
        }
  
        // scroll the list into view
        if( bShowHidden != true ) scrollToView($keylist);
      }
      
      // do not show the list on screen
      if( bShowHidden === true ){
        // hide the results and move them offscreen (so it doesn't hide the cursor in FF2)
        $keylist.css({visibility: "hidden", top: "-10000px", left: "-10000px"});
        // hiden the iframe overlay
        if( isIE6 && !!$.fn.bgIframe ) $keylistiframe.css("display", "none");
      }

      // get the currently selected item
      var $current = $keylist.find("li[" + settings.valueAttr + "="+ $hidden.val() +"]");
      
      // make sure the last match is still highlighted
      $current.addClass("mc_hover" + ($current.is(".mc_parent")? "_parent" : ""));
      
      // scroll the item into view
      if( $current.length > 0 && (bShowHidden != true) ) scrollIntoView($current);
      
      // update the cache
      oldCache = matchesCache = $li;
    };
    
    function hideMatches(){
      // hide the bgiframe
      if( isIE6 && !!$.fn.bgIframe && $keylistiframe ) $keylistiframe.hide();
      if( $keylist ) $keylist.hide();
    };
    
    // check the user's keypress
    function checkKeypress(e){
      var key = String.fromCharCode(e.keyCode || e.charCode).toLowerCase();
      var $current = getCurrentListItem();
      var $lis = ($current.length == 0 || $current.hasClass("mc_root")) ? $list.find("> li") : $current.parent().find("> li");
      var treePath = typedText.split(settings.delim);
      var currentNode = treePath.pop();
      var compare = currentNode + key;
      var selectedText = getSelection($self[0]).toLowerCase();
      var value = $self.val().toLowerCase();
      
      // if the up arrow was pressed
      if( e.keyCode == 38 ){
        moveMatch(-1);
        return false;
  
      // if the down arrow was pressed
      } else if( e.keyCode == 40 ){
        moveMatch(1);
        return false;
  
      // if the [ESC] was pressed
      } else if( e.keyCode == 27 ){
        // clear typedText
        typedText = "";
        // clear the value
        thismenu.setValue("");
        // show the root level
        showMatches($list.find("> li"));
      
        return false;
  
      // if user pressed [DEL] or [LEFT ARROW], go remove last typed character    
      } else if( e.keyCode == 8 || e.keyCode == 37 ){
        // if left arrow, go back to previous parent
        compare = (e.keyCode == 37) ? "" : currentNode.substring(0, currentNode.length - 1);
        
        // if all the text is highlighted we just came from a delete
        if( selectedText == currentNode ){
          currentNode = "";
        }
        // we're going backwards to the last parent, move backwards
        if( treePath.length > 0 && currentNode.length == 0){
          updateValue($current.parent().parent());
          return false;
        // if all the text is selected, remove everything
        } else if( selectedText == value ){
          typedText = "";
          thismenu.setValue("");
          return false;
        }
      // if the user pressed [ENTER], [TAB], [RIGHT ARROW] or the delimiter--go to next level
      } else if( e.keyCode == 9 || e.keyCode == 13 || e.keyCode == 39 || key == settings.delim ){
        // get the first child item if there is one
        var $first = $current.find("> ul > li:first");
  
        // update with the next child branch
        if( $first.length > 0 ){
          updateValue($first);
        // leave the field
        } else {
          // if IE6, we must deselect the selection
          if( $.browser.msie ) setSelection($self[0], 0, 0);
          if( e.keyCode == 9 ){
            // blur out of the field
            $self.triggerHandler("blur");
            // hide the matches
            hideMatches();
            // allow the tab
            return true;
          } else {
            // blur out of the field
            $self.trigger("blur");
            // hide the matches
            hideMatches();
          }
        }
  
        return false;
      // if all the text is highlighted then we need to delete everything
      } else if( selectedText == value ){
        typedText = "";
        compare = key;
      }
  
      // update the match cache with all the matches
      matchesCache = findMatches($lis, compare);
      
      // if we have some matches, populate autofill and show matches
      if( matchesCache.length > 0 ){
        // update the a reference to what the user's typed
        typedText = treePath.join(settings.delim) + (treePath.length > 0 ? settings.delim : "") + compare;
        updateValue(matchesCache.eq(0), true);
      } else {
        // find the previous compare string
        compare = compare.length ? compare.substring(0, compare.length-1) : "";
      
        // since we have no matches, get the previous matches
        matchesCache = findMatches($lis, compare);
  
        // if we have some matches, show them
        if( matchesCache.length > 0 )
          showMatches(matchesCache);
        // hide the matches
        else
          hideMatches();
      }

      // stop default behavior
      e.preventDefault();
      
      return false;
    };
    
    function moveMatch(step){
      // find the current item in the matches cache
      var $current = getCurrentListItem(), $next, pos = 0;
      
      // if nothing selected, look for the item with the hover class
      if( $current.length == 0 ) $current = matchesCache.filter(".mc_hover, .mc_hover_parent");
      // if still nothing, grab the first item in the cache
      if( $current.length == 0 || $keylist.is(":hidden") ){
        // grab the first item
        $current = matchesCache.eq(0);
        // since nothing is selected, don't step forward/back
        step = 0;
      } 
  
      // find the current position of the element   
      matchesCache.each(function (i){ 
        if( this === $current[0]){
          pos = i;
          return false;
        }
      });
  
      // if no matches, cancel
      if( !matchesCache || matchesCache.length == 0 || $current.length == 0 ) return false;
      
      // adjust by the step count
      pos = pos + step;
  
      // make sure pos is in valid bounds   
      if( pos < 0 ) pos = matchesCache.length-1;
      else if( pos >= matchesCache.length ) pos = 0;
      
      // get the next item
      $next = matchesCache.eq(pos);
      
      updateValue($next, true);
    };
    
    function findMatches($lis, compare){
      var matches = $([]); // $([]) = empty jquery object
      
      $lis.each(function (){
        // get the current list item and it's label
        var $li = $(this), label = getNodeText($li);
  
        // label matches what the user typed, add it to the queue
        if( label.substring(0, compare.length).toLowerCase() == compare ){
          // store a copy to this jQuery item
          matches = matches.add($li);
        }
      });
  
      // return the matches found   
      return matches;
    };
    
    function updateValue($li, keepTypedText){
      // grab all direct children items
      var $siblings = keepTypedText ? matchesCache : ($li.length == 0 || $li.hasClass("mc_root")) ? $list.find("> li") : $li.parent().find("> li");
      var treePath = getTreePath($li);
      var currentNode = treePath.pop().toLowerCase();
  
      // update the a reference to what the user's typed
      if( !keepTypedText ) typedText = treePath.join(settings.delim).toLowerCase() + (treePath.length > 0 ? settings.delim : "");
  
      // update form field and display with the updated value
      thismenu.setValue($li.attr(settings.valueAttr), true);
      
      // pre-select the last node
      setSelection($self[0], typedText.length, currentNode.length+typedText.length);
      
      // remove any currently selected items
      $siblings.filter(".mc_hover,.mc_hover_parent").removeClass("mc_hover mc_hover_parent");
      // add the hover class
      $li.addClass("mc_hover" + ($li.is(".mc_parent")? "_parent" : ""));
      
      // show all the matches
      showMatches($siblings);
    };
  
    // get the text currently selected by the user in a text field
    function getSelection(field){
      var text = "";
      if( field.setSelectionRange ){
        text = field.value.substring(field.selectionStart, field.selectionEnd);
      } else if( document.selection ){
        var range = document.selection.createRange();
        if( range.parentElement() == field ){
          text = range.text;
        }
      }
      return text;
    };
  
    // set the text selected in a text field
    function setSelection(field, start, end) {
      if( field.createTextRange ){
        var selRange = field.createTextRange();
        selRange.collapse(true);
        selRange.moveStart("character", start);
        selRange.moveEnd("character", end);
        selRange.select();
      } else if( field.setSelectionRange ){
        field.setSelectionRange(start, end);
      } else {
        if( field.selectionStart ){
          field.selectionStart = start;
          field.selectionEnd = end;
        }
      }
      field.focus();
    };
  
    function scrollIntoView($el, center){
      var el = $el[0];
      var scrollable = $keylist[0];
      // get the padding which is need to adjust the scrollTop
      var s = {pTop: parseInt($keylist.css("paddingTop"), 10)||0, pBottom: parseInt($keylist.css("paddingBottom"), 10)||0, bTop: parseInt($keylist.css("borderTopWidth"), 10)||0, bBottom: parseInt($keylist.css("borderBottomWidth"), 10)||0};
  
      // scrolling down
      if( (el.offsetTop + el.offsetHeight) > (scrollable.scrollTop + scrollable.clientHeight) ){
        scrollable.scrollTop = $el.offset().top + (scrollable.scrollTop - $keylist.offset().top) - ((scrollable.clientHeight/((center == true) ? 2 : 1)) - ($el.outerHeight() + s.pBottom));
      // scrolling up
      } else if( el.offsetTop - s.bTop - s.bBottom <= (scrollable.scrollTop + s.pTop + s.pBottom) ){
        scrollable.scrollTop = $el.offset().top + (scrollable.scrollTop - $keylist.offset().top) - s.pTop;
      }
    };
    
    // run the init callback (some keyboard entry methods will manage this callback manually)
    if( settings.init != null ) settings.init.apply(thismenu, [$input, $hidden, $list]);

  };
  
})(jQuery);
