(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: 2009-10-29
 * Rev:  1.2.10
 */
	$.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.10",
		setDefaults: function(options){
			$.extend(defaults, options);
		}
	};

	// set the defaults
	var defaults = {
		  minRows: 8                   // specify the minimum rows before creating a new column
		, maxRows: 25                  // specify the maximum rows in a column
		, targetColumnSize: 2          // 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: "slideDown"          // the fx to use for showing the root menu
		, openSpeed: 250               // the speed of the openFx
		, closeFx: "slideUp"           // the fx to use for hiding the root menu
		, closeSpeed: 250              // the speed of the closeFx
		, hoverOverDelay: 200          // the delay before opening a submenu
		, hoverOutDelay: 0             // 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
		, 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(":input");
		
		// 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", thismenu);

		// 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){
			// update the hidden value
			$hidden.val(value);
			// get the display name
			var name = displayString(value);
			
			// run the select callback (some keyboard entry methods will manage this callback manually)
			if( settings.select != null && skipCallback != true ) settings.select.apply(thismenu, [value, name]);
			
			// update the display value and return the jQuery object
			return $self[bInput ? "val" : "text"](name);
		};
		
		// set the default value (but don't run callback)
		if( bInput ) this.setValue($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(){
			var self = this;
			// the child menu
			var $ul = $("> ul", this);
			
			// 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
			$(this).parent().find('> li ul:visible').not($ul).parent().each(function(){
				hideBranch.apply(this);
			});
			
			// 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($(this));
	
			// move the menu to the correct position and show the menu || ((depth)*2)
			$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");
		
				$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 max rows
			var calculatedCols = Math.ceil(items/settings.maxRows);
			// 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
			var parentLIWidth = $el.parent("li").width();
			
			// 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;
			});
	
			// 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 hideResults = !(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, hideResults);
		};
		
		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, hideResults){
			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().css("visibility", (hideResults === true) ? "hidden" : "visible");
	
				// 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.outerHeight()) ) $keylist.height(maxHeight);
					
					// anchor the iframe behind the scrollable list
					if( !!$.fn.bgIframe ) anchorTo($divInput.parent(), $keylistiframe.css({height: $keylist.outerHeight(), width: $keylist.width()}, true).show())
				}
	
				// scroll the list into view
				if( hideResults !== true ) scrollToView($keylist);
			}
			
			// do not show the list on screen
			if( hideResults === true ){
				// hide the results and move them offscreen (so it doesn't hide the cursor in FF2)
				$keylist.css({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 && (hideResults != 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);
