$(document).ready(function() {
		
	// N A V  H O V E R  D E L A Y
	
	(function($){$.fn.hoverIntent=function(f,g){var cfg={sensitivity:7,interval:100,timeout:0};cfg=$.extend(cfg,g?{over:f,out:g}:f);var cX,cY,pX,pY;var track=function(ev){cX=ev.pageX;cY=ev.pageY;};var compare=function(ev,ob){ob.hoverIntent_t=clearTimeout(ob.hoverIntent_t);if((Math.abs(pX-cX)+Math.abs(pY-cY))<cfg.sensitivity){$(ob).unbind("mousemove",track);ob.hoverIntent_s=1;return cfg.over.apply(ob,[ev]);}else{pX=cX;pY=cY;ob.hoverIntent_t=setTimeout(function(){compare(ev,ob);},cfg.interval);}};var delay=function(ev,ob){ob.hoverIntent_t=clearTimeout(ob.hoverIntent_t);ob.hoverIntent_s=0;return cfg.out.apply(ob,[ev]);};var handleHover=function(e){var p=(e.type=="mouseover"?e.fromElement:e.toElement)||e.relatedTarget;while(p&&p!=this){try{p=p.parentNode;}catch(e){p=this;}}if(p==this){return false;}var ev=jQuery.extend({},e);var ob=this;if(ob.hoverIntent_t){ob.hoverIntent_t=clearTimeout(ob.hoverIntent_t);}if(e.type=="mouseover"){pX=ev.pageX;pY=ev.pageY;$(ob).bind("mousemove",track);if(ob.hoverIntent_s!=1){ob.hoverIntent_t=setTimeout(function(){compare(ev,ob);},cfg.interval);}}else{$(ob).unbind("mousemove",track);if(ob.hoverIntent_s==1){ob.hoverIntent_t=setTimeout(function(){delay(ev,ob);},cfg.timeout);}}};return this.mouseover(handleHover).mouseout(handleHover);};})(jQuery);
	
	//On Hover Over
	function megaHoverOver(){$(this).find(".sub_nav").stop().slideDown(350).css({'opacity':'1'}).show(); //Find sub ul and fade it in
	}
	
	//On Hover Out
	function megaHoverOut(){
	  $(this).find("ul").stop().fadeTo(300, 0, function() { //Fade to 0 opactiy
	      $(this).hide();  //after fading, hide it
	  });
	}
	
	//Set custom configurations
	var config = {
	     sensitivity: 3, // number = sensitivity threshold (must be 1 or higher)
	     interval: 50, // number = milliseconds for onMouseOver polling interval
	     over: megaHoverOver, // function = onMouseOver callback (REQUIRED)
	     timeout: 250, // number = milliseconds delay before onMouseOut
	     out: megaHoverOut // function = onMouseOut callback (REQUIRED)
	};
	
	$("ul#main_nav li ul.sub_nav").css({'opacity':'0'}).hide(); //Fade sub nav to 0 opacity on default
	$("ul#main_nav li.nav_parent").hoverIntent(config); //Trigger Hover intent with custom configurations
	$("ul#main_nav li ul.sub_nav").css('left', '-1px');
	
	// M A S O N R Y
	
	if( $('.masonry').length ) {
	
	$('.masonry').masonry({
		// options
		itemSelector : '.panel'
	});
	
	}
	
	// Q U I C K  L O O K
	
	$('ul.product_list li').each( function() {
		$link = $(this).find('a:first').attr('rel');
		$title = $(this).find('a:first').attr('title');
		$(this).append('<a class="quick_look" href="' + $link + '" title="' + $title +'" rel="lightbox[quick]">Quick Look</a>')
		$("a.quick_look").hide();
	})
	
	$('.catalogue ul.product_list li').hover(function() {$(this).find("a.quick_look").fadeIn("slow");},
	function() {$(this).find("a.quick_look").hide();});
	
	// I N C R E M E N T   B U T T O N S
	
	if($("#increments").length > 0) {$("#increments").css('display','inline-block');}
	
	$("#increments a").click(function() {
	    var $button = $(this);
	    var oldValue = $("#product_qty input").val();
	
	    if ($button.text() == "+") {
		  var newVal = parseFloat(oldValue) + 1;
		  // AJAX save would go here
		} else {
		  // Don't allow decrementing below zero
		  if (oldValue >= 2) {
		      var newVal = parseFloat(oldValue) - 1;
		      // AJAX save would go here
		  }
		}
		$("#product_qty input").val(newVal);
		return false;
	});
	
	if($("#select_size, #select_colour").length > 0) {
		$("#select_size, #select_size_label, #select_colour").show();
	}
	
	// L I G H T B O X
	if($('a[rel="lightbox"], a[rel="lightbox[quick]"], a[rel="lightbox[xmas]"]').length> 0) {
		$('a[rel="lightbox"], a[rel="lightbox[quick]"], a[rel="lightbox[xmas]"]').lightbox();
	}
	
	// H O M E P A G E .  .  .
	if($("#home").length > 0) {
	
		$(".promo").cycle({	timeout:   5000	});
		
		$("#register_member_form").hide();
		$("#create_account_link").click(function() {
			$(this)
				.parent().hide();
			$("#register_box form").show();
			return false;
		})
	}
	
	// T A B S
	
	if($(".tabs").length) {
	
		//$('.tab_box').addClass("js");
		
		var tabContainers = $('.tabs .tabs_content > div');
		$('.tabs .nav a').click(function () {
			//var scroll = $(window).scrollTop();
			tabContainers.hide().filter(this.hash).show();
		
			//$('.tabs .nav li').removeClass('active');
			$(this).parent().addClass('active').siblings("li").removeClass('active');
			
			//$(window).scrollTop(scroll);
			return false;
		}).filter(':first').click();
	
	}	
	
	// O R D E R  F O R M
	if($("#order_form").length > 0) {
		$("#add_to_order").click(function() {
			if($("#delivery_method").val() == "Please choose:") {
				$("#delivery_method").prev().text("Select a delivery method*:").css("color","#ff0000");
				$(window).scrollTop(600);
				return false;
			}else{$("#delivery_method").prev().css("color","#001E1A");}
			
			if($("#payment_method").val() == "Please choose:") {
				$("#payment_method").prev().text("Select a payment method*:").css("color","#ff0000");
				$(window).scrollTop(1000);
				return false;
			}else{$("#payment_method").prev().css("color","#001E1A");}
		});
		
		$("#delivery_method").change(function() {
			if($('#delivery_method :selected').val() == "Delivery") {
				$(".delivery_details").show();
				$(".collection_details").hide();
			}else if($('#delivery_method :selected').val() == "Collection"){
				$(".collection_details").show();
				$(".delivery_details").hide();
			}else{
				$(".collection_details").hide();
				$(".delivery_details").hide();
			}
		});
		
		$("#payment_method").change(function() {
			if($('#payment_method :selected').val() == "Other") {
				$(this).after("<p style='clear:both;'>If <b>Other</b>, please indicate your payment instructions in the <b>Additional Instructions</b> box below<p>")
			}else{$(this).next('p').hide();}
		});
		
		$(".delivery_details, .collection_details").toggle();
		
		
		// E D I T   D E T A I L S
		
		$(".save_details").hide().text("Save Details?");
		
		$(".editable, .delivery_details input").keyup(function() {
			$(this).parent().find(".save_details").show().removeClass("saving saved").text("Save Details?");
		})
		
		$(".save_details").click(function(ev) {
			$(this).text("Saving Details").addClass("saving");
			scope = $(this).parent().attr('class'); //for determing which button has been pressed
			link = $(this).attr("href");
			
			//window.open(link, 'Edit Profile','width=600,height=500,scrollbars=yes');
			//ev.preventDefault();
			$("#main").append("<div id='form_holder'></div>");
			$("#form_holder").hide().load(link + " #edit_profile_form", function() {
        		
        		//set new values into vars
        		var company_name = $("#order_form input[name='company_name']").val();
        		var vat_number = $("#order_form input[name='vat_number']").val();
        		var contact_number = $("#order_form input[name='contact_number']").val();
        		var address_1 = $("#order_form input[name='street1']").val();
        		var address_2 = $("#order_form input[name='street2']").val();
        		var town = $("#order_form input[name='town']").val();
        		var city = $("#order_form input[name='city']").val();
        		var county = $("#order_form input[name='county']").val();
        		var postalcode = $("#order_form input[name='postalcode']").val();
        		var country = $("#order_form input[name='country']").val();
        		
        		//set variables to newly loaded form
        		$(this).find("input[name='m_field_id_9']").val(company_name)
        			.end().find("input[name='m_field_id_10']").val(vat_number)
	        		.end().find("input[name='m_field_id_1']").val(contact_number)
	        		.end().find("input[name='m_field_id_2']").val(address_1)
	        		.end().find("input[name='m_field_id_3']").val(address_2)
	        		.end().find("input[name='m_field_id_4']").val(town)
	        		.end().find("input[name='m_field_id_5']").val(city)
	        		.end().find("input[name='m_field_id_6']").val(county)
	        		.end().find("input[name='m_field_id_7']").val(postalcode)
	        		.end().find("input[name='m_field_id_8']").val(country);
        		
        		$("#edit_profile_form").ajaxSubmit({
					success:function() {
						$("."+scope+" .save_details").toggleClass("saving saved").text("Details Saved");
						$(this).remove();
					}
				});
				
				/*$('#edit_profile_form').ajaxForm(function() { 
            		$(".save_details").text("Details Saved");
        		});*/
        		
			});
			
			return false;
		});
		
	}

	if($("#register_member_form").length > 0) {
		$("#register_member_form").validate({
			rules: {
				confirm_email: {
					equalTo: "#email"
				},
				password_confirm: {
					equalTo: "#password"
				}
			}
		});
	}
	
	
	// S C R O L L   C A T E G O R I E S
	
	var $scrollingDiv = $("body.catalogue #aside");
 	var $topMargin = 0;
 	
	$(window).scroll(function(){
	       
	       if($(window).scrollTop() > 223) { // sidebar is level with top of browser window
	       
		       	//if (($("html").height() - $(window).scrollTop()) > 750) { // catch if 750px from bottom of window to stop clash with footer
		 
		       		$topMargin = $(window).scrollTop();
		       
		       		$scrollingDiv
		              .addClass("scroll_fixed").css({"marginTop": 0}); 
		              
		       	/*}else{ //otherwise fix sidebar when scrolling
		       		$scrollingDiv
		              .removeClass("scroll_fixed").css({"marginTop" : ($topMargin - 223) + "px"});	              
		        }*/
		   }else{ //reset sidebar settings
	      		 $scrollingDiv
	              .removeClass("scroll_fixed").css({"marginTop": 0});
	       }
	       	       
	});
	
	// O R D E R   C A T A L O G U E
	
	$("#footer #order").click( function() {
		$("#footer").prepend("<div id='order_catalogue'></div>").find("#order").hide();
		$("#order_catalogue").hide().load('http://www.ljr-footwear.com//embeds/order_catalogue', function() {$(this).slideToggle("slow");})
		return false;
	});
	
	$("#jcart #cart_contents").css({"display" : "block"}).hide();
	
	$("#jcart a#my_order").live('click', function() {
		$(this).toggleClass('cart_open');
		$("#jcart #cart_contents").toggle();
		return false;
	});
	
	$("#account_links a.login_link").click(function() {
		$("#login_form").slideToggle();
		$("#login_form fieldset input[name='username']").focus();
		return false;
	});
	
	// F E E D B A C K   F O R M
	
	$(".feedback_btn").fadeIn();
	
	$(".feedback_btn").click(function() {
	
		if($(".feedback_form").length > 0) {showForm();}else{
			$("body").append("<div class='feedback_form'></div>");
			$(".feedback_form").load("http://www.ljr-footwear.com/embeds/form_feedback", function() {
				$(this).prepend("<a href='#' class='close_form'>(x) close</a>");
				showForm();				
			});
			
		}
						
		return false;
	});
	
	$('.close_form').live('click', function() {
		hideForm();
		return false;
	});
	
	
	 $(document).click(function(event) { 
	    if($(event.target).parents().index($('.feedback_form')) == -1) {hideForm();}        
	});
	
	function showForm() {
		$(".feedback_form").animate({
		   	marginRight: 0
		}, 500, function() {
		    $(".feedback_form textarea").focus();
		});
	}		
	
	function hideForm() {
		$(".feedback_form").animate({
		   	marginRight: '-470'
		}, 500, function() {});}

	$('#feedback_form').live('submit', function() {
		$(this).ajaxSubmit({
			beforeSubmit:function() {
				$("#feedback_form .action_btn").attr("value", "Sending...");
			},
			success:function(value) {
				if(value == "sent") {
					$(this).resetForm();
					$(".msg").html('<p>Feedback sent! <b>Thank you</b>, we will be in touch. <a href="#" class="close_form">Close this form</a></p>');
					
				}else{
					//there was an erro, so grab the UL in the content ID,
					//inside the hidden DIV, and put it into the Message Notification
					$(".msg").html("<p>*Please enter a problem</p>");
				}
				$("#feedback_form .action_btn").attr("value", "Send Feedback");
			}
		});
		return false; // <-- important!
	});

  	
  	// L I V E S E A R C H
  	
  	$('#search_box').livesearch("http://www.ljr-footwear.com/catalogue/livesearch/", { width: "200" });
	
	// N E W S   T I C K E R
	
	if($("#news_ticker").length > 0) {
		
		$("#news_ticker").css('display','block');
		
		var totalItems = $("#news_snippets li").length - 1;
		var currItem = 0;
		var timerOn = true;
		
		//Hide news items apart from first item
		$(this).find("#news_snippets li:nth-child(1n+2)").hide();
		
		//Remove list style as js is obviously enabled
		$("#news_ticker #news_snippets li").css({'list-style-type':'none', 'margin-left':'0'});
		
		//If more than 1 news item, add navigation and functions
		if(totalItems > 0) {
			$("#news_ticker").append('<ul id="news_nav"><li><a id="news_nav_prev" href="#">Prev</a></li><li><a id="news_nav_control" href="#">Control</a></li><li><a id="news_nav_next" href="#">Next</a></li></ul>');
		
			$("#news_nav li a").css('opacity','.5').hover(function() {
				$(this).animate({ opacity: 1.0 }, 350);
			},function() {
				$(this).animate({ opacity: .5 }, 350);
			});
			
			$("a#news_nav_control").click(function() {
				if(timerOn == true) {
					$(this).css('background-position','1px -70px');
					timerOn = false;
					window.clearInterval(timer);
				}else{
					$(this).css('background-position','1px -47px');
					timerOn = true;
					startTimer();
				}
				return false;
			})
			
			$("a#news_nav_next").click(function() {
				window.clearInterval(timer);
				timerOn = false;
				$("a#news_nav_control").css('background-position','1px -70px');
				if(currItem < totalItems) {toggleUp()}else{toggleToFirst()}
				return false;
			});
			
			$("a#news_nav_prev").click(function() {
				window.clearInterval(timer);
				timerOn = false;
				$("a#news_nav_control").css('background-position','1px -70px');
				if(currItem > 0) {toggleDown()}else{toggleToLast()}
				return false;
			});
		
			function timerToggle() {
				if(currItem == totalItems) {toggleToFirst()}else{toggleUp()}
			}
			
			function toggleUp() {
				$("#news_ticker #news_snippets li:eq(" + currItem + ")").toggle();
				currItem++;
				toggleItem();
			}
			
			function toggleDown() {
				$("#news_ticker #news_snippets li:eq(" + currItem + ")").toggle();
				currItem--;
				toggleItem();
			}
			
			function toggleToLast() {
				$("#news_ticker #news_snippets li:eq(" + currItem + ")").toggle();
				currItem = totalItems;
				toggleItem();
			}
			
			function toggleToFirst() {
				$("#news_ticker #news_snippets li:eq(" + currItem + ")").toggle();
				currItem = 0;
				toggleItem();
			}
			
			function toggleItem() {
				$("#news_ticker #news_snippets li:eq(" + currItem + ")").toggleFade({ speedIn : 1000, speedOut : 0 });
			}
			
			function startTimer() {
				timer=window.setInterval(timerToggle, 7500);
			}
			
			startTimer();
			
		}
		
	}

if($("#size_tabs").length > 0) {
	$("#size_tabs").show();
	$("#size_table_2").hide(); //hide second table
	$("#size_tabs li:first-child").addClass("on");
	$("#size_tabs li a:not(.on)").click(function() {
		//table = $(this).attr("href");
		table = $(this).parent().index();
		$(".size_table").hide();
		$(".size_table").eq(table).show();
		$("#size_tabs li").removeClass("on");
		$(this).parent().addClass("on");
		return false;		
	});
	
	$("#select_size_range").change(function() {
		var $size_index = $('#select_size_range :selected').index() - 1;
		
		if($size_index >= 0) {
			$(".size_table").hide();
			$("#size_tabs li").removeClass("on");
			$("#size_tabs li").eq($size_index).addClass("on");
			$(".size_table").eq($size_index).show();
			
		}
	});
}

$('#search_box[placeholder]')
	.placeholder()
	.css({"font-style" : "italic" , "color" : "#999"})
	.focus(function() {
		$(this).css({"font-style" : "normal" , "color" : "#162326"});
	});

});

(function($) {
	$.fn.toggleFade = function(settings)
	{
		if(settings==undefined) {
			settings={ speedIn : 'slow'};
		}
		
		settings = jQuery.extend(
				{
					speedIn: "normal",
					speedOut: settings.speedIn
				}, settings
		);
		return this.each(function()
				{
			var isHidden = jQuery(this).is(":hidden");
			jQuery(this)[ isHidden ? "fadeIn" : "fadeOut" ]( isHidden ? settings.speedIn : settings.speedOut);
				});
	};
})(jQuery);

(function($) {
	$.fn.toggleSlide = function(settings)
	{
		if(settings==undefined) {
			settings={ speedIn : 'slow'};
		}

		settings = jQuery.extend(
				{
					speedIn: "normal",
					speedOut: settings.speedIn
				}, settings
		);
		return this.each(function()
				{
			var isHidden = jQuery(this).is(":hidden");
			jQuery(this)[ isHidden ? "slideDown" : "slideUp" ]( isHidden ? settings.speedIn : settings.speedOut);
				});
	};
})(jQuery);

/*
* Placeholder plugin for jQuery
* @author Daniel Stocks (http://webcloud.se)
*/
(function($) {
    function Placeholder(input) {
        this.input = input;
        if (input.attr('type') == 'password') {this.handlePassword();}
        // Prevent placeholder values from submitting
        $(input[0].form).submit(function() {
            if (input.hasClass('placeholder')) {
                input[0].value = '';
            }
        });
    }
    Placeholder.prototype = {
        show : function(loading) {
            // FF and IE saves values when you refresh the page. If the user refreshes the page with
            // the placeholders showing they will be the default values and the input fields won't be empty.
            
            if (this.input[0].value === '' || (loading && this.valueIsPlaceholder())) {
                if (this.isPassword) {
                    try {
                        this.input[0].setAttribute('type', 'text');
                    } catch (e) {this.input.before(this.fakePassword.show()).hide();}
                }
                this.input[0].value = this.input.attr('placeholder');
                this.input.addClass('placeholder');
            }
        },
        hide : function() {
            if (this.valueIsPlaceholder() && this.input.hasClass('placeholder')) {
                if (this.isPassword) {
                    try {
                        this.input[0].setAttribute('type', 'password');
                    } catch (e) { }
                    // Restore focus for Opera and IE
                    this.input.show();
                    this.input[0].focus();
                }
                this.input[0].value = '';
                this.input.removeClass('placeholder');
            }
        },
        valueIsPlaceholder : function() {
            return this.input[0].value == this.input.attr('placeholder');
        },
        handlePassword: function() {
        
            var input = this.input;
            input.attr('realType', 'password');
            this.isPassword = true;
            // IE < 9 doesn't allow changing the type of password inputs
            if ($.browser.msie && input[0].outerHTML) {
                var fakeHTML = input[0].outerHTML.replace(/type=(['"])?password\1/gi, 'type=$1text$1');
                this.fakePassword = $(fakeHTML).val(input.attr('placeholder')).addClass('placeholder').focus(function() {
                    input.trigger('focus');
                    $(this).hide();
                });
            }
        }
    };
    var supported = !!("placeholder" in document.createElement( "input" ));
    $.fn.placeholder = function() {
        return supported ? this : this.each(function() {
            var input = $(this);
            var placeholder = new Placeholder(input);
            placeholder.show(true);
            input.focus(function() {placeholder.hide();});
            input.blur(function() {placeholder.show(false);});
            
            // On page refresh, IE doesn't re-populate user input
            // until the window.onload event is fired.
            if ($.browser.msie) {
                $(window).load(function() {
                    if(input.val()) {input.removeClass("placeholder");}
                    placeholder.show(true);
                });
            }
        });
    }
})(jQuery);

/*!
 * jQuery Form Plugin
 * version: 2.47 (04-SEP-2010)
 * @requires jQuery v1.3.2 or later
 *
 * Examples and documentation at: http://malsup.com/jquery/form/
 * Dual licensed under the MIT and GPL licenses:
 *   http://www.opensource.org/licenses/mit-license.php
 *   http://www.gnu.org/licenses/gpl.html
 */
;(function($) {

/*
	Usage Note:
	-----------
	Do not use both ajaxSubmit and ajaxForm on the same form.  These
	functions are intended to be exclusive.  Use ajaxSubmit if you want
	to bind your own submit handler to the form.  For example,

	$(document).ready(function() {
		$('#myForm').bind('submit', function() {
			$(this).ajaxSubmit({
				target: '#output'
			});
			return false; // <-- important!
		});
	});

	Use ajaxForm when you want the plugin to manage all the event binding
	for you.  For example,

	$(document).ready(function() {
		$('#myForm').ajaxForm({
			target: '#output'
		});
	});

	When using ajaxForm, the ajaxSubmit function will be invoked for you
	at the appropriate time.
*/

/**
 * ajaxSubmit() provides a mechanism for immediately submitting
 * an HTML form using AJAX.
 */
$.fn.ajaxSubmit = function(options) {
	// fast fail if nothing selected (http://dev.jquery.com/ticket/2752)
	if (!this.length) {
		log('ajaxSubmit: skipping submit process - no element selected');
		return this;
	}

	if (typeof options == 'function') {
		options = { success: options };
	}

	var url = $.trim(this.attr('action'));
	if (url) {
		// clean url (don't include hash vaue)
		url = (url.match(/^([^#]+)/)||[])[1];
	}
	url = url || window.location.href || '';

	options = $.extend(true, {
		url:  url,
		type: this.attr('method') || 'GET',
		iframeSrc: /^https/i.test(window.location.href || '') ? 'javascript:false' : 'about:blank'
	}, options);

	// hook for manipulating the form data before it is extracted;
	// convenient for use with rich editors like tinyMCE or FCKEditor
	var veto = {};
	this.trigger('form-pre-serialize', [this, options, veto]);
	if (veto.veto) {
		log('ajaxSubmit: submit vetoed via form-pre-serialize trigger');
		return this;
	}

	// provide opportunity to alter form data before it is serialized
	if (options.beforeSerialize && options.beforeSerialize(this, options) === false) {
		log('ajaxSubmit: submit aborted via beforeSerialize callback');
		return this;
	}

	var n,v,a = this.formToArray(options.semantic);
	if (options.data) {
		options.extraData = options.data;
		for (n in options.data) {
			if(options.data[n] instanceof Array) {
				for (var k in options.data[n]) {
					a.push( { name: n, value: options.data[n][k] } );
				}
			}
			else {
				v = options.data[n];
				v = $.isFunction(v) ? v() : v; // if value is fn, invoke it
				a.push( { name: n, value: v } );
			}
		}
	}

	// give pre-submit callback an opportunity to abort the submit
	if (options.beforeSubmit && options.beforeSubmit(a, this, options) === false) {
		log('ajaxSubmit: submit aborted via beforeSubmit callback');
		return this;
	}

	// fire vetoable 'validate' event
	this.trigger('form-submit-validate', [a, this, options, veto]);
	if (veto.veto) {
		log('ajaxSubmit: submit vetoed via form-submit-validate trigger');
		return this;
	}

	var q = $.param(a);

	if (options.type.toUpperCase() == 'GET') {
		options.url += (options.url.indexOf('?') >= 0 ? '&' : '?') + q;
		options.data = null;  // data is null for 'get'
	}
	else {
		options.data = q; // data is the query string for 'post'
	}

	var $form = this, callbacks = [];
	if (options.resetForm) {
		callbacks.push(function() {$form.resetForm();});
	}
	if (options.clearForm) {
		callbacks.push(function() {$form.clearForm();});
	}

	// perform a load on the target only if dataType is not provided
	if (!options.dataType && options.target) {
		var oldSuccess = options.success || function(){};
		callbacks.push(function(data) {
			var fn = options.replaceTarget ? 'replaceWith' : 'html';
			$(options.target)[fn](data).each(oldSuccess, arguments);
		});
	}
	else if (options.success) {callbacks.push(options.success);}

	options.success = function(data, status, xhr) { // jQuery 1.4+ passes xhr as 3rd arg
		var context = options.context || options;   // jQuery 1.4+ supports scope context 
		for (var i=0, max=callbacks.length; i < max; i++) {
			callbacks[i].apply(context, [data, status, xhr || $form, $form]);
		}
	};

	// are there files to upload?
	var fileInputs = $('input:file', this).length > 0;
	var mp = 'multipart/form-data';
	var multipart = ($form.attr('enctype') == mp || $form.attr('encoding') == mp);

	// options.iframe allows user to force iframe mode
	// 06-NOV-09: now defaulting to iframe mode if file input is detected
   if (options.iframe !== false && (fileInputs || options.iframe || multipart)) {
	   // hack to fix Safari hang (thanks to Tim Molendijk for this)
	   // see:  http://groups.google.com/group/jquery-dev/browse_thread/thread/36395b7ab510dd5d
	   if (options.closeKeepAlive) {
		   $.get(options.closeKeepAlive, fileUpload);
		}
	   else {fileUpload();}
   }
   else {$.ajax(options);}

	// fire 'notify' event
	this.trigger('form-submit-notify', [this, options]);
	return this;


	// private function for handling file uploads (hat tip to YAHOO!)
	function fileUpload() {
		var form = $form[0];

		if ($(':input[name=submit],:input[id=submit]', form).length) {
			// if there is an input with a name or id of 'submit' then we won't be
			// able to invoke the submit fn on the form (at least not x-browser)
			alert('Error: Form elements must not have name or id of "submit".');
			return;
		}
		
		var s = $.extend(true, {}, $.ajaxSettings, options);
		s.context = s.context || s;
		var id = 'jqFormIO' + (new Date().getTime()), fn = '_'+id;
		window[fn] = function() {
			var f = $io.data('form-plugin-onload');
			if (f) {
				f();
				window[fn] = undefined;
				try { delete window[fn]; } catch(e){}}
		}
		var $io = $('<iframe id="' + id + '" name="' + id + '" src="'+ s.iframeSrc +'" onload="window[\'_\'+this.id]()" />');
		var io = $io[0];

		$io.css({ position: 'absolute', top: '-1000px', left: '-1000px' });

		var xhr = { // mock object
			aborted: 0,
			responseText: null,
			responseXML: null,
			status: 0,
			statusText: 'n/a',
			getAllResponseHeaders: function() {},
			getResponseHeader: function() {},
			setRequestHeader: function() {},
			abort: function() {
				this.aborted = 1;
				$io.attr('src', s.iframeSrc); // abort op in progress
			}
		};

		var g = s.global;
		// trigger ajax global events so that activity/block indicators work like normal
		if (g && ! $.active++) {$.event.trigger("ajaxStart");}
		if (g) {
			$.event.trigger("ajaxSend", [xhr, s]);
		}

		if (s.beforeSend && s.beforeSend.call(s.context, xhr, s) === false) {
			if (s.global) {$.active--;}
			return;
		}
		if (xhr.aborted) {return;}

		var cbInvoked = false;
		var timedOut = 0;

		// add submitting element to data if we know it
		var sub = form.clk;
		if (sub) {
			var n = sub.name;
			if (n && !sub.disabled) {
				s.extraData = s.extraData || {};
				s.extraData[n] = sub.value;
				if (sub.type == "image") {
					s.extraData[n+'.x'] = form.clk_x;
					s.extraData[n+'.y'] = form.clk_y;
				}
			}
		}

		// take a breath so that pending repaints get some cpu time before the upload starts
		function doSubmit() {
			// make sure form attrs are set
			var t = $form.attr('target'), a = $form.attr('action');

			// update form attrs in IE friendly way
			form.setAttribute('target',id);
			if (form.getAttribute('method') != 'POST') {
				form.setAttribute('method', 'POST');
			}
			if (form.getAttribute('action') != s.url) {
				form.setAttribute('action', s.url);
			}

			// ie borks in some cases when setting encoding
			if (! s.skipEncodingOverride) {
				$form.attr({
					encoding: 'multipart/form-data',
					enctype:  'multipart/form-data'
				});
			}

			// support timout
			if (s.timeout) {
				setTimeout(function() { timedOut = true; cb(); }, s.timeout);
			}

			// add "extra" data to form if provided in options
			var extraInputs = [];
			try {
				if (s.extraData) {
					for (var n in s.extraData) {
						extraInputs.push(
							$('<input type="hidden" name="'+n+'" value="'+s.extraData[n]+'" />')
								.appendTo(form)[0]);
					}
				}

				// add iframe to doc and submit the form
				$io.appendTo('body');
				$io.data('form-plugin-onload', cb);
				form.submit();
			}
			finally {
				// reset attrs and remove "extra" input elements
				form.setAttribute('action',a);
				if(t) {
					form.setAttribute('target', t);
				} else {$form.removeAttr('target');}
				$(extraInputs).remove();
			}
		}

		if (s.forceSync) {doSubmit();}
		else {
			setTimeout(doSubmit, 10); // this lets dom updates render
		}
	
		var data, doc, domCheckCount = 50;

		function cb() {
			if (cbInvoked) {return;}

			$io.removeData('form-plugin-onload');
			
			var ok = true;
			try {
				if (timedOut) {
					throw 'timeout';
				}
				// extract the server response from the iframe
				doc = io.contentWindow ? io.contentWindow.document : io.contentDocument ? io.contentDocument : io.document;
				
				var isXml = s.dataType == 'xml' || doc.XMLDocument || $.isXMLDoc(doc);
				log('isXml='+isXml);
				if (!isXml && window.opera && (doc.body == null || doc.body.innerHTML == '')) {
					if (--domCheckCount) {
						// in some browsers (Opera) the iframe DOM is not always traversable when
						// the onload callback fires, so we loop a bit to accommodate
						log('requeing onLoad callback, DOM not available');
						setTimeout(cb, 250);
						return;
					}
					// let this fall through because server response could be an empty document
					//log('Could not access iframe DOM after mutiple tries.');
					//throw 'DOMException: not available';
				}

				//log('response detected');
				cbInvoked = true;
				xhr.responseText = doc.documentElement ? doc.documentElement.innerHTML : null; 
				xhr.responseXML = doc.XMLDocument ? doc.XMLDocument : doc;
				xhr.getResponseHeader = function(header){
					var headers = {'content-type': s.dataType};
					return headers[header];
				};

				var scr = /(json|script)/.test(s.dataType);
				if (scr || s.textarea) {
					// see if user embedded response in textarea
					var ta = doc.getElementsByTagName('textarea')[0];
					if (ta) {
						xhr.responseText = ta.value;
					}
					else if (scr) {
						// account for browsers injecting pre around json response
						var pre = doc.getElementsByTagName('pre')[0];
						if (pre) {
							xhr.responseText = pre.innerHTML;
						}
					}			  
				}
				else if (s.dataType == 'xml' && !xhr.responseXML && xhr.responseText != null) {
					xhr.responseXML = toXml(xhr.responseText);
				}
				data = $.httpData(xhr, s.dataType);
			}
			catch(e){
				log('error caught:',e);
				ok = false;
				xhr.error = e;
				$.handleError(s, xhr, 'error', e);
			}

			// ordering of these callbacks/triggers is odd, but that's how $.ajax does it
			if (ok) {
				s.success.call(s.context, data, 'success', xhr);
				if (g) {
					$.event.trigger("ajaxSuccess", [xhr, s]);
				}
			}
			if (g) {
				$.event.trigger("ajaxComplete", [xhr, s]);
			}
			if (g && ! --$.active) {$.event.trigger("ajaxStop");}
			if (s.complete) {
				s.complete.call(s.context, xhr, ok ? 'success' : 'error');
			}

			// clean up
			setTimeout(function() {
				$io.removeData('form-plugin-onload');
				$io.remove();
				xhr.responseXML = null;
			}, 100);
		}

		function toXml(s, doc) {
			if (window.ActiveXObject) {
				doc = new ActiveXObject('Microsoft.XMLDOM');
				doc.async = 'false';
				doc.loadXML(s);
			}
			else {
				doc = (new DOMParser()).parseFromString(s, 'text/xml');
			}
			return (doc && doc.documentElement && doc.documentElement.tagName != 'parsererror') ? doc : null;
		}
	}
};

/**
 * ajaxForm() provides a mechanism for fully automating form submission.
 *
 * The advantages of using this method instead of ajaxSubmit() are:
 *
 * 1: This method will include coordinates for <input type="image" /> elements (if the element
 *	is used to submit the form).
 * 2. This method will include the submit element's name/value data (for the element that was
 *	used to submit the form).
 * 3. This method binds the submit() method to the form for you.
 *
 * The options argument for ajaxForm works exactly as it does for ajaxSubmit.  ajaxForm merely
 * passes the options argument along after properly binding events for submit elements and
 * the form itself.
 */
$.fn.ajaxForm = function(options) {
	// in jQuery 1.3+ we can fix mistakes with the ready state
	if (this.length === 0) {
		var o = { s: this.selector, c: this.context };
		if (!$.isReady && o.s) {
			log('DOM not ready, queuing ajaxForm');
			$(function() {$(o.s,o.c).ajaxForm(options);});
			return this;
		}
		// is your DOM ready?  http://docs.jquery.com/Tutorials:Introducing_$(document).ready()
		log('terminating; zero elements found by selector' + ($.isReady ? '' : ' (DOM not ready)'));
		return this;
	}
	
	return this.ajaxFormUnbind().bind('submit.form-plugin', function(e) {
		if (!e.isDefaultPrevented()) { // if event has been canceled, don't proceed
			e.preventDefault();
			$(this).ajaxSubmit(options);
		}
	}).bind('click.form-plugin', function(e) {
		var target = e.target;
		var $el = $(target);
		if (!($el.is(":submit,input:image"))) {
			// is this a child element of the submit el?  (ex: a span within a button)
			var t = $el.closest(':submit');
			if (t.length == 0) {return;}
			target = t[0];
		}
		var form = this;
		form.clk = target;
		if (target.type == 'image') {
			if (e.offsetX != undefined) {
				form.clk_x = e.offsetX;
				form.clk_y = e.offsetY;
			} else if (typeof $.fn.offset == 'function') { // try to use dimensions plugin
				var offset = $el.offset();
				form.clk_x = e.pageX - offset.left;
				form.clk_y = e.pageY - offset.top;
			} else {
				form.clk_x = e.pageX - target.offsetLeft;
				form.clk_y = e.pageY - target.offsetTop;
			}
		}
		// clear form vars
		setTimeout(function() { form.clk = form.clk_x = form.clk_y = null; }, 100);
	});
};

// ajaxFormUnbind unbinds the event handlers that were bound by ajaxForm
$.fn.ajaxFormUnbind = function() {
	return this.unbind('submit.form-plugin click.form-plugin');
};

/**
 * formToArray() gathers form element data into an array of objects that can
 * be passed to any of the following ajax functions: $.get, $.post, or load.
 * Each object in the array has both a 'name' and 'value' property.  An example of
 * an array for a simple login form might be:
 *
 * [ { name: 'username', value: 'jresig' }, { name: 'password', value: 'secret' } ]
 *
 * It is this array that is passed to pre-submit callback functions provided to the
 * ajaxSubmit() and ajaxForm() methods.
 */
$.fn.formToArray = function(semantic) {
	var a = [];
	if (this.length === 0) {
		return a;
	}

	var form = this[0];
	var els = semantic ? form.getElementsByTagName('*') : form.elements;
	if (!els) {
		return a;
	}
	
	var i,j,n,v,el;
	for(i=0, max=els.length; i < max; i++) {
		el = els[i];
		n = el.name;
		if (!n) {continue;}

		if (semantic && form.clk && el.type == "image") {
			// handle image inputs on the fly when semantic == true
			if(!el.disabled && form.clk == el) {
				a.push({name: n, value: $(el).val()});
				a.push({name: n+'.x', value: form.clk_x}, {name: n+'.y', value: form.clk_y});
			}
			continue;
		}

		v = $.fieldValue(el, true);
		if (v && v.constructor == Array) {
			for(j=0, jmax=v.length; j < jmax; j++) {
				a.push({name: n, value: v[j]});
			}
		}
		else if (v !== null && typeof v != 'undefined') {
			a.push({name: n, value: v});
		}
	}

	if (!semantic && form.clk) {
		// input type=='image' are not found in elements array! handle it here
		var $input = $(form.clk), input = $input[0];
		n = input.name;
		if (n && !input.disabled && input.type == 'image') {
			a.push({name: n, value: $input.val()});
			a.push({name: n+'.x', value: form.clk_x}, {name: n+'.y', value: form.clk_y});
		}
	}
	return a;
};

/**
 * Serializes form data into a 'submittable' string. This method will return a string
 * in the format: name1=value1&amp;name2=value2
 */
$.fn.formSerialize = function(semantic) {
	//hand off to jQuery.param for proper encoding
	return $.param(this.formToArray(semantic));
};

/**
 * Serializes all field elements in the jQuery object into a query string.
 * This method will return a string in the format: name1=value1&amp;name2=value2
 */
$.fn.fieldSerialize = function(successful) {
	var a = [];
	this.each(function() {
		var n = this.name;
		if (!n) {return;}
		var v = $.fieldValue(this, successful);
		if (v && v.constructor == Array) {
			for (var i=0,max=v.length; i < max; i++) {
				a.push({name: n, value: v[i]});
			}
		}
		else if (v !== null && typeof v != 'undefined') {
			a.push({name: this.name, value: v});
		}
	});
	//hand off to jQuery.param for proper encoding
	return $.param(a);
};

/**
 * Returns the value(s) of the element in the matched set.  For example, consider the following form:
 *
 *  <form><fieldset>
 *	  <input name="A" type="text" />
 *	  <input name="A" type="text" />
 *	  <input name="B" type="checkbox" value="B1" />
 *	  <input name="B" type="checkbox" value="B2"/>
 *	  <input name="C" type="radio" value="C1" />
 *	  <input name="C" type="radio" value="C2" />
 *  </fieldset></form>
 *
 *  var v = $(':text').fieldValue();
 *  // if no values are entered into the text inputs
 *  v == ['','']
 *  // if values entered into the text inputs are 'foo' and 'bar'
 *  v == ['foo','bar']
 *
 *  var v = $(':checkbox').fieldValue();
 *  // if neither checkbox is checked
 *  v === undefined
 *  // if both checkboxes are checked
 *  v == ['B1', 'B2']
 *
 *  var v = $(':radio').fieldValue();
 *  // if neither radio is checked
 *  v === undefined
 *  // if first radio is checked
 *  v == ['C1']
 *
 * The successful argument controls whether or not the field element must be 'successful'
 * (per http://www.w3.org/TR/html4/interact/forms.html#successful-controls).
 * The default value of the successful argument is true.  If this value is false the value(s)
 * for each element is returned.
 *
 * Note: This method *always* returns an array.  If no valid value can be determined the
 *	   array will be empty, otherwise it will contain one or more values.
 */
$.fn.fieldValue = function(successful) {
	for (var val=[], i=0, max=this.length; i < max; i++) {
		var el = this[i];
		var v = $.fieldValue(el, successful);
		if (v === null || typeof v == 'undefined' || (v.constructor == Array && !v.length)) {continue;}
		v.constructor == Array ? $.merge(val, v) : val.push(v);
	}
	return val;
};

/**
 * Returns the value of the field element.
 */
$.fieldValue = function(el, successful) {
	var n = el.name, t = el.type, tag = el.tagName.toLowerCase();
	if (successful === undefined) {
		successful = true;
	}

	if (successful && (!n || el.disabled || t == 'reset' || t == 'button' ||
		(t == 'checkbox' || t == 'radio') && !el.checked ||
		(t == 'submit' || t == 'image') && el.form && el.form.clk != el ||
		tag == 'select' && el.selectedIndex == -1)) {
			return null;
	}

	if (tag == 'select') {
		var index = el.selectedIndex;
		if (index < 0) {
			return null;
		}
		var a = [], ops = el.options;
		var one = (t == 'select-one');
		var max = (one ? index+1 : ops.length);
		for(var i=(one ? index : 0); i < max; i++) {
			var op = ops[i];
			if (op.selected) {
				var v = op.value;
				if (!v) { // extra pain for IE...
					v = (op.attributes && op.attributes['value'] && !(op.attributes['value'].specified)) ? op.text : op.value;
				}
				if (one) {
					return v;
				}
				a.push(v);
			}
		}
		return a;
	}
	return $(el).val();
};

/**
 * Clears the form data.  Takes the following actions on the form's input fields:
 *  - input text fields will have their 'value' property set to the empty string
 *  - select elements will have their 'selectedIndex' property set to -1
 *  - checkbox and radio inputs will have their 'checked' property set to false
 *  - inputs of type submit, button, reset, and hidden will *not* be effected
 *  - button elements will *not* be effected
 */
$.fn.clearForm = function() {
	return this.each(function() {
		$('input,select,textarea', this).clearFields();
	});
};

/**
 * Clears the selected form elements.
 */
$.fn.clearFields = $.fn.clearInputs = function() {
	return this.each(function() {
		var t = this.type, tag = this.tagName.toLowerCase();
		if (t == 'text' || t == 'password' || tag == 'textarea') {
			this.value = '';
		}
		else if (t == 'checkbox' || t == 'radio') {
			this.checked = false;
		}
		else if (tag == 'select') {
			this.selectedIndex = -1;
		}
	});
};

/**
 * Resets the form data.  Causes all form elements to be reset to their original value.
 */
$.fn.resetForm = function() {
	return this.each(function() {
		// guard against an input with the name of 'reset'
		// note that IE reports the reset function as an 'object'
		if (typeof this.reset == 'function' || (typeof this.reset == 'object' && !this.reset.nodeType)) {this.reset();}
	});
};

/**
 * Enables or disables any matching elements.
 */
$.fn.enable = function(b) {
	if (b === undefined) {
		b = true;
	}
	return this.each(function() {
		this.disabled = !b;
	});
};

/**
 * Checks/unchecks any matching checkboxes or radio buttons and
 * selects/deselects and matching option elements.
 */
$.fn.selected = function(select) {
	if (select === undefined) {
		select = true;
	}
	return this.each(function() {
		var t = this.type;
		if (t == 'checkbox' || t == 'radio') {
			this.checked = select;
		}
		else if (this.tagName.toLowerCase() == 'option') {
			var $sel = $(this).parent('select');
			if (select && $sel[0] && $sel[0].type == 'select-one') {
				// deselect all other options
				$sel.find('option').selected(false);
			}
			this.selected = select;
		}
	});
};

// helper fn for console logging
// set $.fn.ajaxSubmit.debug to true to enable debug logging
function log() {
	if ($.fn.ajaxSubmit.debug) {
		var msg = '[jquery.form] ' + Array.prototype.join.call(arguments,'');
		if (window.console && window.console.log) {window.console.log(msg);}
		else if (window.opera && window.opera.postError) {window.opera.postError(msg);}
	}
};

})(jQuery);

/*
 * Livesearch Plugin
 *
 * @author	Pascal Kriete
 *
 * Built on the Autocomplete plugin:
 * @see http://bassistance.de/jquery-plugins/jquery-plugin-autocomplete/
 *
 */

// Original License:

/*
 * Autocomplete - jQuery plugin 1.0.2
 *
 * Copyright (c) 2007 Dylan Verheul, Dan G. Switzer, Anjesh Tuladhar, JÃ¶rn Zaefferer
 *
 * Dual licensed under the MIT and GPL licenses:
 *   http://www.opensource.org/licenses/mit-license.php
 *   http://www.gnu.org/licenses/gpl.html
 *
 * Revision: $Id: jquery.autocomplete.js 5747 2008-06-25 18:30:55Z joern.zaefferer $
 *
 */

;(function($) {

$.fn.extend({
	livesearch: function(url, options) {
		options = $.extend({
			center_results: false,
			
			parse: function(data) {
				return $.map(eval(data), function(row) {
					return {
						data: row,
						value: row.title,
						result: row.title + " <" + row.path + ">"
					};
				});
			},
			formatItem: function(link) {
				return "<a href='"+link.path+"'>"+link.title+ "</a>";
			}
		}, options);
		
		this.autocomplete(url, options);
	}
});
	
$.fn.extend({
	autocomplete: function(urlOrData, options) {
		var isUrl = typeof urlOrData == "string";
		options = $.extend({}, $.Autocompleter.defaults, {
			url: isUrl ? urlOrData : null,
			data: isUrl ? null : urlOrData,
			delay: isUrl ? $.Autocompleter.defaults.delay : 10,
			max: options && !options.scroll ? 10 : 150
		}, options);
		
		// if highlight is set to false, replace it with a do-nothing function
		options.highlight = options.highlight || function(value) { return value; };
		
		// if the formatMatch option is not specified, then use formatItem for backwards compatibility
		options.formatMatch = options.formatMatch || options.formatItem;
		
		return this.each(function() {
			new $.Autocompleter(this, options);
		});
	},
	result: function(handler) {
		return this.bind("result", handler);
	},
	search: function(handler) {
		return this.trigger("search", [handler]);
	},
	flushCache: function() {
		return this.trigger("flushCache");
	},
	setOptions: function(options){
		return this.trigger("setOptions", [options]);
	},
	unautocomplete: function() {
		return this.trigger("unautocomplete");
	}
});

$.Autocompleter = function(input, options) {

	var KEY = {
		UP: 38,
		DOWN: 40,
		DEL: 46,
		TAB: 9,
		RETURN: 13,
		ESC: 27,
		COMMA: 188,
		PAGEUP: 33,
		PAGEDOWN: 34,
		BACKSPACE: 8
	};

	// Create $ object for input element
	var $input = $(input).attr("autocomplete", "off").addClass(options.inputClass);

	var timeout;
	var previousValue = "";
	var cache = $.Autocompleter.Cache(options);
	var hasFocus = 0;
	var lastKeyPressCode;
	var config = {
		mouseDownOnSelect: false
	};
	var select = $.Autocompleter.Select(options, input, selectCurrent, config);
	
	var blockSubmit;
	
	// prevent form submit in opera when selecting with return key
	$.browser.opera && $(input.form).bind("submit.autocomplete", function() {
		if (blockSubmit) {
			blockSubmit = false;
			return false;
		}
	});
	
	// only opera doesn't trigger keydown multiple times while pressed, others don't work with keypress at all
	$input.bind(($.browser.opera ? "keypress" : "keydown") + ".autocomplete", function(event) {
		// track last key pressed
		lastKeyPressCode = event.keyCode;
		switch(event.keyCode) {
		
			case KEY.UP:
				event.preventDefault();
				if ( select.visible() ) {select.prev();} else {
					onChange(0, true);
				}
				break;
				
			case KEY.DOWN:
				event.preventDefault();
				if ( select.visible() ) {select.next();} else {
					onChange(0, true);
				}
				break;
				
			case KEY.PAGEUP:
				event.preventDefault();
				if ( select.visible() ) {select.pageUp();} else {
					onChange(0, true);
				}
				break;
				
			case KEY.PAGEDOWN:
				event.preventDefault();
				if ( select.visible() ) {select.pageDown();} else {
					onChange(0, true);
				}
				break;
			
			// matches also semicolon
			case options.multiple && $.trim(options.multipleSeparator) == "," && KEY.COMMA:
			case KEY.TAB:
			case KEY.RETURN:
				if( selectCurrent() ) {
					// stop default to prevent a form submit, Opera needs special handling
					event.preventDefault();
					blockSubmit = true;
					return false;
				}
				break;
				
			case KEY.ESC:
				select.hide();
				break;
				
			default:
				clearTimeout(timeout);
				timeout = setTimeout(onChange, options.delay);
				break;
		}
	}).focus(function(){
		// track whether the field has focus, we shouldn't process any
		// results if the field no longer has focus
		hasFocus++;
	}).blur(function() {
		hasFocus = 0;
		if (!config.mouseDownOnSelect) {hideResults();}
	}).click(function() {
		// show select when clicking in a focused field
		if ( hasFocus++ > 1 && !select.visible() ) {
			onChange(0, true);
		}
	}).bind("search", function() {
		// TODO why not just specifying both arguments?
		var fn = (arguments.length > 1) ? arguments[1] : null;
		function findValueCallback(q, data) {
			var result;
			if( data && data.length ) {
				for (var i=0; i < data.length; i++) {
					if( data[i].result.toLowerCase() == q.toLowerCase() ) {
						result = data[i];
						break;
					}
				}
			}
			if( typeof fn == "function" ) fn(result);
			else $input.trigger("result", result && [result.data, result.value]);
		}
		$.each(trimWords($input.val()), function(i, value) {
			request(value, findValueCallback, findValueCallback);
		});
	}).bind("flushCache", function() {cache.flush();}).bind("setOptions", function() {
		$.extend(options, arguments[1]);
		// if we've updated the data, repopulate
		if ( "data" in arguments[1] )
			cache.populate();
	}).bind("unautocomplete", function() {
		select.unbind();
		$input.unbind();
		$(input.form).unbind(".autocomplete");
	});
	
	
	function selectCurrent() {
		var selected = select.selected();
		if( !selected )
			return false;
		
		var v = selected.result;
		previousValue = v;
		
		// Follow link
		hideResultsNow();
		document.location = selected.data.path;
		return true;
	}
	
	function onChange(crap, skipPrevCheck) {
		if( lastKeyPressCode == KEY.DEL ) {
			select.hide();
			return;
		}
		
		var currentValue = $input.val();
		
		if ( !skipPrevCheck && currentValue == previousValue )
			return;
		
		previousValue = currentValue;
		
		currentValue = lastWord(currentValue);
		if ( currentValue.length >= options.minChars) {
			$input.addClass(options.loadingClass);
			if (!options.matchCase)
				currentValue = currentValue.toLowerCase();
			request(currentValue, receiveData, hideResultsNow);
		} else {
			stopLoading();
			select.hide();
		}
	};
	
	function trimWords(value) {
		if ( !value ) {
			return [""];
		}
		var words = value.split( options.multipleSeparator );
		var result = [];
		$.each(words, function(i, value) {
			if ( $.trim(value) )
				result[i] = $.trim(value);
		});
		return result;
	}
	
	function lastWord(value) {
		if ( !options.multiple )
			return value;
		var words = trimWords(value);
		return words[words.length - 1];
	}
	
	// fills in the input box w/the first match (assumed to be the best match)
	// q: the term entered
	// sValue: the first matching result
	function autoFill(q, sValue){
		// autofill in the complete box w/the first match as long as the user hasn't entered in more data
		// if the last user key pressed was backspace, don't autofill
		if( options.autoFill && (lastWord($input.val()).toLowerCase() == q.toLowerCase()) && lastKeyPressCode != KEY.BACKSPACE ) {
			// fill in the value (keep the case the user has typed)
			$input.val($input.val() + sValue.substring(lastWord(previousValue).length));
			// select the portion of the value not typed by the user (so the next character will erase)
			$.Autocompleter.Selection(input, previousValue.length, previousValue.length + sValue.length);
		}
	};

	function hideResults() {
		clearTimeout(timeout);
		timeout = setTimeout(hideResultsNow, 200);
	};

	function hideResultsNow() {
		var wasVisible = select.visible();
		select.hide();
		clearTimeout(timeout);
		stopLoading();
		if (options.mustMatch) {
			// call search and run callback
			$input.search(
				function (result){
					// if no value found, clear the input box
					if( !result ) {
						if (options.multiple) {
							var words = trimWords($input.val()).slice(0, -1);
							$input.val( words.join(options.multipleSeparator) + (words.length ? options.multipleSeparator : "") );
						}
						else
							$input.val( "" );
					}
				}
			);
		}
		if (wasVisible)
			// position cursor at end of input field
			$.Autocompleter.Selection(input, input.value.length, input.value.length);
	};

	function receiveData(q, data) {
		if ( data && data.length && hasFocus ) {
			stopLoading();
			select.display(data, q);
			autoFill(q, data[0].value);
			select.show();
		} else {hideResultsNow();}
	};

	function request(term, success, failure) {
		if (!options.matchCase)
			term = term.toLowerCase();
		var data = cache.load(term);
		// recieve the cached data
		if (data && data.length) {
			success(term, data);
		// if an AJAX url has been supplied, try loading the data now
		} else if( (typeof options.url == "string") && (options.url.length > 0) ){
			
			var extraParams = {
				timestamp: +new Date()
			};
			$.each(options.extraParams, function(key, param) {
				extraParams[key] = typeof param == "function" ? param() : param;
			});
			
			$.ajax({
				// try to leverage ajaxQueue plugin to abort previous requests
				mode: "abort",
				// limit abortion to this input
				port: "autocomplete" + input.name,
				dataType: options.dataType,
				url: options.url + input.value+"/",
				data: $.extend({
					q: lastWord(term),
					limit: options.max
				}, extraParams),
				success: function(data) {
					var parsed = options.parse && options.parse(data) || parse(data);
					cache.add(term, parsed);
					success(term, parsed);
				}
			});
		} else {
			// if we have a failure, we need to empty the list -- this prevents the the [TAB] key from selecting the last successful match
			select.emptyList();
			failure(term);
		}
	};
	
	function parse(data) {
		var parsed = [];
		var rows = data.split("\n");
		for (var i=0; i < rows.length; i++) {
			var row = $.trim(rows[i]);
			if (row) {
				row = row.split("|");
				parsed[parsed.length] = {
					data: row,
					value: row[0],
					result: options.formatResult && options.formatResult(row, row[0]) || row[0]
				};
			}
		}
		return parsed;
	};

	function stopLoading() {$input.removeClass(options.loadingClass);};

};

$.Autocompleter.defaults = {
	inputClass: "ac_input",
	resultsClass: "ac_results",
	loadingClass: "ac_loading",
	minChars: 1,
	delay: 400,
	matchCase: false,
	matchSubset: true,
	matchContains: false,
	cacheLength: 10,
	max: 100,
	mustMatch: false,
	extraParams: {},
	selectFirst: true,
	formatItem: function(row) { return row[0]; },
	formatMatch: null,
	autoFill: false,
	width: 0,
	multiple: false,
	multipleSeparator: ", ",
	highlight: function(value, term) {
		return value.replace(new RegExp("(?![^&;]+;)(?!<[^<>]*)(" + term.replace(/([\^\$\(\)\[\]\{\}\*\.\+\?\|\\])/gi, "\\$1") + ")(?![^<>]*>)(?![^&;]+;)", "gi"), "<strong>$1</strong>");
	},
    scroll: true,
    scrollHeight: 180
};

$.Autocompleter.Cache = function(options) {

	var data = {};
	var length = 0;
	
	function matchSubset(s, sub) {
		if (!options.matchCase) 
			s = s.toLowerCase();
		var i = s.indexOf(sub);
		if (i == -1) return false;
		return i == 0 || options.matchContains;
	};
	
	function add(q, value) {
		if (length > options.cacheLength){flush();}
		if (!data[q]){length++;}
		data[q] = value;
	}
	
	function populate(){
		if( !options.data ) return false;
		// track the matches
		var stMatchSets = {},
			nullData = 0;

		// no url was specified, we need to adjust the cache length to make sure it fits the local data store
		if( !options.url ) options.cacheLength = 1;
		
		// track all options for minChars = 0
		stMatchSets[""] = [];
		
		// loop through the array and create a lookup structure
		for ( var i = 0, ol = options.data.length; i < ol; i++ ) {
			var rawValue = options.data[i];
			// if rawValue is a string, make an array otherwise just reference the array
			rawValue = (typeof rawValue == "string") ? [rawValue] : rawValue;
			
			var value = options.formatMatch(rawValue, i+1, options.data.length);
			if ( value === false )
				continue;
				
			var firstChar = value.charAt(0).toLowerCase();
			// if no lookup array for this character exists, look it up now
			if( !stMatchSets[firstChar] ) 
				stMatchSets[firstChar] = [];

			// if the match is a string
			var row = {
				value: value,
				data: rawValue,
				result: options.formatResult && options.formatResult(rawValue) || value
			};
			
			// push the current match into the set list
			stMatchSets[firstChar].push(row);

			// keep track of minChars zero items
			if ( nullData++ < options.max ) {stMatchSets[""].push(row);}
		};

		// add the data items to the cache
		$.each(stMatchSets, function(i, value) {
			// increase the cache size
			options.cacheLength++;
			// add to the cache
			add(i, value);
		});
	}
	
	// populate any existing data
	setTimeout(populate, 25);
	
	function flush(){
		data = {};
		length = 0;
	}
	
	return {
		flush: flush,
		add: add,
		populate: populate,
		load: function(q) {
			if (!options.cacheLength || !length)
				return null;
			/* 
			 * if dealing w/local data and matchContains than we must make sure
			 * to loop through all the data collections looking for matches
			 */
			if( !options.url && options.matchContains ){
				// track all matches
				var csub = [];
				// loop through all the data grids for matches
				for( var k in data ){
					// don't search through the stMatchSets[""] (minChars: 0) cache
					// this prevents duplicates
					if( k.length > 0 ){
						var c = data[k];
						$.each(c, function(i, x) {
							// if we've got a match, add it to the array
							if (matchSubset(x.value, q)) {csub.push(x);}
						});
					}
				}				
				return csub;
			} else 
			// if the exact item exists, use it
			if (data[q]){
				return data[q];
			} else
			if (options.matchSubset) {
				for (var i = q.length - 1; i >= options.minChars; i--) {
					var c = data[q.substr(0, i)];
					if (c) {
						var csub = [];
						$.each(c, function(i, x) {
							if (matchSubset(x.value, q)) {
								csub[csub.length] = x;
							}
						});
						return csub;
					}
				}
			}
			return null;
		}
	};
};

$.Autocompleter.Select = function (options, input, select, config) {
	var CLASSES = {
		ACTIVE: "ac_over"
	};
	
	var listItems,
		active = -1,
		data,
		term = "",
		needsInit = true,
		element,
		list;
	
	// Create results
	function init() {
		if (!needsInit)
			return;
		element = $("<div/>")
		.hide()
		.addClass(options.resultsClass)
		.css("position", "absolute")
		.appendTo(document.body);
	
		list = $("<ul/>").appendTo(element).mouseover( function(event) {
			if(target(event).nodeName && target(event).nodeName.toUpperCase() == 'LI') {
	            active = $("li", list).removeClass(CLASSES.ACTIVE).index(target(event));
			    $(target(event)).addClass(CLASSES.ACTIVE);            
	        }
		}).click(function(event) {
			$(target(event)).addClass(CLASSES.ACTIVE);
			select();
			// TODO provide option to avoid setting focus again after selection? useful for cleanup-on-focus
			input.focus();
			return false;
		}).mousedown(function() {
			config.mouseDownOnSelect = true;
		}).mouseup(function() {
			config.mouseDownOnSelect = false;
		});
		
		if( options.width > 0 )
			element.css("width", options.width);
			
		needsInit = false;
	} 
	
	function target(event) {
		var element = event.target;
		while(element && element.tagName != "LI")
			element = element.parentNode;
		// more fun with IE, sometimes event.target is empty, just ignore it then
		if(!element)
			return [];
		return element;
	}

	function moveSelect(step) {
		listItems.slice(active, active + 1).removeClass(CLASSES.ACTIVE);
		movePosition(step);
        var activeItem = listItems.slice(active, active + 1).addClass(CLASSES.ACTIVE);
        if(options.scroll) {
            var offset = 0;
            listItems.slice(0, active).each(function() {
				offset += this.offsetHeight;
			});
            if((offset + activeItem[0].offsetHeight - list.scrollTop()) > list[0].clientHeight) {
                list.scrollTop(offset + activeItem[0].offsetHeight - list.innerHeight());
            } else if(offset < list.scrollTop()) {list.scrollTop(offset);}
        }
	};
	
	function movePosition(step) {
		active += step;
		if (active < 0) {
			active = listItems.size() - 1;
		} else if (active >= listItems.size()) {
			active = 0;
		}
	}
	
	function limitNumberOfItems(available) {
		return options.max && options.max < available
			? options.max
			: available;
	}
	
	function fillList() {
		list.empty();
		var max = limitNumberOfItems(data.length);
		for (var i=0; i < max; i++) {
			if (!data[i])
				continue;
			var formatted = options.formatItem(data[i].data, i+1, max, data[i].value, term);
			if ( formatted === false )
				continue;
			var li = $("<li/>").html( options.highlight(formatted, term) ).addClass(i%2 == 0 ? "ac_even" : "ac_odd").appendTo(list)[0];
			$.data(li, "ac_data", data[i]);
		}
		listItems = list.find("li");
		if ( options.selectFirst ) {
			listItems.slice(0, 1).addClass(CLASSES.ACTIVE);
			active = 0;
		}
		// apply bgiframe if available
		if ( $.fn.bgiframe )
			list.bgiframe();
	}
	
	return {
		display: function(d, q) {
			init();
			data = d;
			term = q;
			fillList();
		},
		next: function() {moveSelect(1);},
		prev: function() {moveSelect(-1);},
		pageUp: function() {
			if (active != 0 && active - 8 < 0) {
				moveSelect( -active );
			} else {moveSelect(-8);}
		},
		pageDown: function() {
			if (active != listItems.size() - 1 && active + 8 > listItems.size()) {
				moveSelect( listItems.size() - 1 - active );
			} else {moveSelect(8);}
		},
		hide: function() {
			element && element.hide();
			listItems && listItems.removeClass(CLASSES.ACTIVE);
			active = -1;
		},
		visible : function() {
			return element && element.is(":visible");
		},
		current: function() {
			return this.visible() && (listItems.filter("." + CLASSES.ACTIVE)[0] || options.selectFirst && listItems[0]);
		},
		show: function() {
			var offset = $(input).offset();
			
			var width = typeof options.width == "string" || options.width > 0 ? options.width : $(input).width();
			var left = offset.left
			
			// Added -pk
			if( options.center_results) {
				var input_width = $(input).width();
				var x_offset = (parseFloat(width) - parseFloat(input_width)) / 2;
				left = offset.left - x_offset
			}
					
			element.css({
				width: width,
				top: offset.top + input.offsetHeight,
				left: left
			}).show();
            if(options.scroll) {
                list.scrollTop(0);
                list.css({
					maxHeight: options.scrollHeight,
					overflow: 'auto'
				});
				
                if($.browser.msie && typeof document.body.style.maxHeight === "undefined") {
					var listHeight = 0;
					listItems.each(function() {
						listHeight += this.offsetHeight;
					});
					var scrollbarsVisible = listHeight > options.scrollHeight;
                    list.css('height', scrollbarsVisible ? options.scrollHeight : listHeight );
					if (!scrollbarsVisible) {
						// IE doesn't recalculate width when scrollbar disappears
						listItems.width( list.width() - parseInt(listItems.css("padding-left")) - parseInt(listItems.css("padding-right")) );
					}
                }
                
            }
		},
		selected: function() {
			var selected = listItems && listItems.filter("." + CLASSES.ACTIVE).removeClass(CLASSES.ACTIVE);
			return selected && selected.length && $.data(selected[0], "ac_data");
		},
		emptyList: function (){
			list && list.empty();
		},
		unbind: function() {
			element && element.remove();
		}
	};
};

$.Autocompleter.Selection = function(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();
};

})(jQuery);
