 /*
 * AutoSuggest
 * Copyright 2009-2010 Drew Wilson
 * www.drewwilson.com
 * code.drewwilson.com/entry/autosuggest-jquery-plugin
 *
 * Version 1.4   -   Updated: Mar. 23, 2010
 *
 * This Plug-In will auto-complete or auto-suggest completed search queries
 * for you as you type. You can add multiple selections and remove them on
 * the fly. It supports keybord navigation (UP + DOWN + RETURN), as well
 * as multiple AutoSuggest fields on the same page.
 *
 * Inspied by the Autocomplete plugin by: Jörn Zaefferer
 * and the Facelist plugin by: Ian Tearle (iantearle.com)
 *
 * This AutoSuggest jQuery plug-in is dual licensed under the MIT and GPL licenses:
 *   http://www.opensource.org/licenses/mit-license.php
 *   http://www.gnu.org/licenses/gpl.html
 */

(function($){
	$.fn.autoSuggest = function(data, options) {
		var defaults = { 
			asHtmlID: false,
			startText: "Enter Name Here",
			startAnotherText: "You May Enter Another One",
			emptyText: "No Results Found",
			preFill: {},
			autoSugg: [], // show on focused empty line
			limitText: "No More Selections Are Allowed",
			selectedItemProp: "value", //name of object property
			selectedValuesProp: "value", //name of object property
			searchObjProps: "value", //comma separated list of object property names
			queryParam: "q",
			retrieveLimit: false, //number for 'limit' param on ajax request
			extraParams: "",
			matchCase: false,
			minChars: 1,
			keyDelay: 400,
			resultsHighlight: true,
			neverSubmit: false,
			selectionLimit: false,
			showResultList: true,
			
			start: function(){},
			selectionClick: function(elem){},
			selectionAdded: function(elem){},
			selectionShow: function(inp, prop){ return (inp.val().search(","+prop+",") == -1); },
			selectionFormat: function(data, prop){ return data[prop]; },
			selectionRemoved: function(elem){ elem.remove(); },
			formatList: false, //callback function
			beforeRetrieve: function(string){ return string; },
			retrieveComplete: function(data){ return data; },
			resultClick: function(data){},
			resultsComplete: function(){}
		};  
	 	var opts = $.extend(defaults, options);	 	
		var self = this;
		
		var d_type = "object";
		var d_count = 0;
		if(typeof data == "string") {
			d_type = "string";
			var req_string = data;
		} else {
			var org_data = data;
			for (k in data) if (data.hasOwnProperty(k)) d_count++;
		}
		if((d_type == "object" && d_count > 0) || d_type == "string"){
			return this.each(function(x){
				if(!opts.asHtmlID){
					x = x+""+Math.floor(Math.random()*100); //this ensures there will be unique IDs on the page if autoSuggest() is called multiple times
					var x_id = "as-input-"+x;
				} else {
					x = opts.asHtmlID;
					var x_id = x;
				}
				opts.start.call(this);
				var input = $(this);
				input.attr("autocomplete","off").addClass("as-input").attr("id",x_id);
				var input_focus = false;
				
				// Setup basic elements and render them to the DOM
				input.wrap('<ul class="as-selections" id="as-selections-'+x+'"></ul>').wrap('<li class="as-original" id="as-original-'+x+'"></li>');
				var selections_holder = $("#as-selections-"+x);
				var org_li = $("#as-original-"+x);				
				var results_holder = $('<div class="as-results" id="as-results-'+x+'"></div>').hide();
				var results_ul =  $('<ul class="as-list ui-drop-shadow ui-corner-all"></ul>');
				var values_input = $('<input type="hidden" value="," class="as-values" name="as_values_'+x+'" id="as-values-'+x+'" />');
				var prefill_value = "";
				if(typeof opts.preFill == "string"){
					var vals = opts.preFill.split(",");					
					for(var i=0; i < vals.length; i++){
						var v_data = {};
						v_data[opts.selectedValuesProp] = vals[i];
						if(vals[i] != ""){
							add_selected_item(v_data, "000"+i);	
						}		
					}
					prefill_value = opts.preFill;
				} else {
					prefill_value = "";
					var prefill_count = 0;
					for (k in opts.preFill) if (opts.preFill.hasOwnProperty(k)) prefill_count++;
					if(prefill_count > 0){
						for(var i=0; i < prefill_count; i++){
							var new_v = opts.preFill[i][opts.selectedValuesProp];
							if(new_v == undefined){ new_v = ""; }
							prefill_value = prefill_value+new_v+",";
							if(new_v != ""){
								add_selected_item(opts.preFill[i], "000"+i);	
							}		
						}
					}
				}
				if(prefill_value != ""){
					input.val("");
					var lastChar = prefill_value.substring(prefill_value.length-1);
					if(lastChar != ","){ prefill_value = prefill_value+","; }
					values_input.val(","+prefill_value);
					$("li.as-selection-item", selections_holder).addClass("blur").removeClass("selected");
				}
				input.after(values_input).val(values_input.val() == "," ? opts.startText : opts.startAnotherText);
				selections_holder.click(function(){
//$("#konzole")[0].innerHTML += 'sel_holder.click<br />';
					input_focus = true;
					input.focus();
				});
				if ($.browser.msie && parseInt($.browser.version) <= 8)
					selections_holder.mousedown(function(){ 
//$("#konzole")[0].innerHTML += 'false sel_holder.mousedown<br />';
						input_focus = false;
					});
				selections_holder.after(results_holder);

				var timeout = null;
				var prev = "";
				var totalSelections = 0;
				var tab_press = false;
				
				input.bind("add_custom_item", function(event, pData) {
					if (!pData.kod) pData.kod = 'x'+Math.floor(Math.random()*100);
					if($("#as-selection-"+pData.kod, selections_holder).length <= 0 && !tab_press) input.val("").focus();
					if(opts.selectionShow.call(this, values_input, pData[opts.selectedValuesProp])) {
						add_selected_item(pData, pData.kod); // pData[opts.selectedValuesProp]
					} else {
						$("#as-selection-"+pData.kod+" a.as-close").click();
					}
					this.blur();
					input_focus = false;
				});
				// Handle input field events
				if (!$.browser.msie)
					input.click(function(event){
						event.stopPropagation();
						input_focus = true;
//$("#konzole")[0].innerHTML += 'input.click<br />';
					}).mousedown(function(){
						input_focus = true;
//$("#konzole")[0].innerHTML += 'input.mousedown<br />';
					});
				input.focus(function(){			
					if($(this).val() == opts.startText || $(this).val() == opts.startAnotherText || $(this).val() == ''){
						$(this).val("");
						if (opts.autoSugg.length > 0 && input_focus) {
//							if (!$.browser.msie) {
								d_count = opts.autoSugg.length;
								processData(opts.autoSugg, '');
//							}
						}
					} else if(input_focus){
						$("li.as-selection-item", selections_holder).removeClass("blur");
						if($(this).val().length >= opts.minChars){
							results_ul.css("width",selections_holder.outerWidth());
							resultsHolderShowDelayed();
						}
					}
//					input_focus = true;
//$("#konzole")[0].innerHTML += 'input.focus<br />';
					return true;
				}).blur(function(){
					if($(this).val() == ""){ //  && prefill_value == ""
						if(values_input.val() == ",") $(this).val(opts.startText);
						else $(this).val(opts.startAnotherText);
					} 
					if(input_focus){
						$("li.as-selection-item", selections_holder).addClass("blur").removeClass("selected");
						results_holder.hide();
					}				
				}).keydown(function(e) {
					// track last key pressed
					lastKeyPressCode = e.keyCode;
					first_focus = false;
					switch(e.keyCode) {
						case 38: // up
							e.preventDefault();
							moveSelection("up");
							break;
						case 40: // down
							e.preventDefault();
							if (results_holder.is(':hidden')) results_holder.show()
							else moveSelection("down");
							break;
						case 8:  // delete
							if(input.val() == ""){							
								var last = values_input.val().split(",");
								last = last[last.length - 2];
								selections_holder.children().not(org_li.prev()).removeClass("selected");
								if(org_li.prev().hasClass("selected")){
									values_input.val(values_input.val().replace(","+last+",",","));
									opts.selectionRemoved.call(this, org_li.prev());
								} else {
									opts.selectionClick.call(this, org_li.prev());
									org_li.prev().addClass("selected");		
								}
							}
							if(input.val().length == 1){
								results_holder.hide();
								 prev = "";
							}
							if($(":visible",results_holder).length > 0){
								if (timeout){ clearTimeout(timeout); }
								timeout = setTimeout(function(){ keyChange(); }, opts.keyDelay);
							}
							break;
/*	Buggy, do not use it
						case 9: case 188:  // tab or comma
							tab_press = true;
							var i_input = input.val().replace(/(,)/g, "");
							if(i_input != "" && values_input.val().search(","+i_input+",") < 0 && i_input.length >= opts.minChars){	
								e.preventDefault();
								var n_data = {};
								n_data[opts.selectedItemProp] = i_input;
								n_data[opts.selectedValuesProp] = i_input;																				
								var lis = $("li", selections_holder).length;
								add_selected_item(n_data, "00"+(lis+1));
								input.val("");
							}*/
						case 13: // return
							tab_press = false;
							var active = $("li.active:first", results_holder);
							if(active.length > 0){
								active.click();
								results_holder.hide();
							}
							if(opts.neverSubmit || active.length > 0){
								e.preventDefault();
							}
							break;
						default:
							if(opts.showResultList){
								if(opts.selectionLimit && $("li.as-selection-item", selections_holder).length >= opts.selectionLimit){
									results_ul.html('<li class="as-message">'+opts.limitText+'</li>');
									resultsHolderShowDelayed();
								} else {
									if (timeout){ clearTimeout(timeout); }
									timeout = setTimeout(function(){ keyChange(); }, opts.keyDelay);
								}
							}
							break;
					}
				});
				
				function keyChange() {
					// ignore if the following keys are pressed: [del] [shift] [capslock]
					if( lastKeyPressCode == 46 || (lastKeyPressCode > 8 && lastKeyPressCode < 32) ){ return results_holder.hide(); }
					var string = input.val().replace(/[\\]+|[\/]+/g,"");
					if (string == prev) return;
					prev = string;
					if (string.length >= opts.minChars) {
						selections_holder.addClass("loading");
						if(d_type == "string"){
							var limit = "";
							if(opts.retrieveLimit){
								limit = "&limit="+encodeURIComponent(opts.retrieveLimit);
							}
							if(opts.beforeRetrieve){
								string = opts.beforeRetrieve.call(this, string);
							}
							$.getJSON(req_string+"?"+opts.queryParam+"="+encodeURIComponent(string)+limit+opts.extraParams, function(data){ 
								d_count = 0;
								var new_data = opts.retrieveComplete.call(this, data);
								for (k in new_data) if (new_data.hasOwnProperty(k)) d_count++;
								processData(new_data, string); 
							});
						} else {
							if(opts.beforeRetrieve){
								string = opts.beforeRetrieve.call(this, string);
							}
							processData(org_data, string);
						}
					} else {
						selections_holder.removeClass("loading");
						results_holder.hide();
					}
				}
				var num_count = 0;
				
				function processData(data, query){
					if (!opts.matchCase){ query = query.toLowerCase(); }
					var matchCount = 0;
					results_holder.html(results_ul.html("")).hide();
					for(var i=0;i<d_count;i++){				
						var num = i;
						num_count++;
						var forward = false;
						if(opts.searchObjProps == "value") {
							var str = data[num].value;
						} else {	
							var str = "";
							var names = opts.searchObjProps.split(",");
							for(var y=0;y<names.length;y++){
								var name = $.trim(names[y]);
								str = str+data[num][name]+" ";
							}
						}
						if(str){
							if (!opts.matchCase){ str = str.toLowerCase(); }
							// str.search(query) != -1 && 
							if(opts.selectionShow.call(this, values_input, data[num][opts.selectedValuesProp])){
								forward = true;
							}	
						}
						if(forward){
							var formatted = $('<li class="as-result-item" id="as-result-item-'+num+'"></li>').click(function(){
									var raw_data = $(this).data("data");
									var number = raw_data.attributes.kod || raw_data.num;
									if($("#as-selection-"+number, selections_holder).length <= 0 && !tab_press){
										var data = raw_data.attributes;
										input.val("").focus();
										prev = "";
										add_selected_item(data, number);
										opts.resultClick.call(this, raw_data);
										results_holder.hide();
									}
									tab_press = false;
								}).mousedown(function(){
//$("#konzole")[0].innerHTML += 'false li.mousedown<br />';
								input_focus = false; }).mouseover(function(){
									$("li", results_ul).removeClass("active");
									$(this).addClass("active");
								}).data("data",{attributes: data[num], num: num_count});
							var this_data = $.extend({},data[num]);
							
							if(opts.resultsHighlight && query != ''){
								var regx = new RegExp("(?![^&;]+;)(?!<[^<>]*)(" + query + ")(?![^<>]*>)(?![^&;]+;)", opts.matchCase ? "" : "i");
								this_data[opts.selectedItemProp] = this_data[opts.selectedItemProp].replace(regx,"<em>$1</em>");
							}
							if(!opts.formatList){
								formatted = formatted.html(this_data[opts.selectedItemProp]);
							} else {
								formatted = opts.formatList.call(this, this_data, formatted);	
							}
							//results_ul.css('opacity', 0.95).append(formatted);
							results_ul.append(formatted);
							delete this_data;
							matchCount++;
							if(opts.retrieveLimit && opts.retrieveLimit == matchCount ){ break; }
						}
					}
					selections_holder.removeClass("loading");
					if(matchCount <= 0 && query != ''){
						results_ul.html('<li class="as-message">'+opts.emptyText+'</li>');
					}
					results_ul.css("width", selections_holder.outerWidth());
//					if (!selections_holder.children().hasClass("selected"))
					resultsHolderShowDelayed();
					opts.resultsComplete.call(this);
				}
				
				function resultsHolderShowDelayed() {
					setTimeout(function(){ 
						if ($("div.linkselectContainer:visible").length==0)
							results_holder.show();
					}, 40);
				}
				
				function add_selected_item(data, num){
					values_input.val(values_input.val()+data[opts.selectedValuesProp]+",");
					var item = $('<li class="as-selection-item" id="as-selection-'+num+'"></li>').prop( 'kod', data[opts.selectedValuesProp] ).click(function(){
							opts.selectionClick.call(this, $(this));
							selections_holder.children().removeClass("selected");
							$(this).addClass("selected");
							results_holder.hide();
						}).mousedown(function(){
//$("#konzole")[0].innerHTML += 'sel_holder.mousedown<br />';
						input_focus = false; });
					var close = $('<a class="as-close">&times;</a>').click(function(){
							values_input.val(values_input.val().replace(","+data[opts.selectedValuesProp]+",",","));
							opts.selectionRemoved.call(this, item);
//$("#konzole")[0].innerHTML += 'sel_holder.mousedown<br />';
							input_focus = false;
							input.focus();
							return false;
						});
					org_li.before(item.html(opts.selectionFormat(data, opts.selectedItemProp)).append(close));
					opts.selectionAdded.call(this, org_li.prev(), data);
				}
				
				function moveSelection(direction){
					if($(":visible",results_holder).length > 0){
						var lis = $("li", results_holder);
						if(direction == "down"){
							var start = lis.eq(0);
						} else {
							var start = lis.filter(":last");
						}					
						var active = $("li.active:first", results_holder);
						if(active.length > 0){
							if(direction == "down"){
							start = active.next();
							} else {
								start = active.prev();
							}	
						}
						lis.removeClass("active");
						start.addClass("active");
					}
				}

				results_holder.parent().focusout(function (event) {
					setTimeout(function(){ 
						if (results_holder.is(':visible'))
							results_holder.hide();
					}, 400);
				});

			});
		}
	}
})(jQuery);  	

/*
Also, there is another, this time css issue showing its teeth in IE7 and most likely 6 (please someone test for 6, if you can and post your results).

See image sig_1.png in rar.

There are 2 ways to deal with that, depends on what you want to accomplish.

First of all go to new .js file shown after this post and find jp[#4], and compare it to previous version. You will see that data[opts.selectedItemProp] was wrapped in tag with a new class.

Now whole loc (line of code) looks like this:
if (!inArray(inDat, inArr)) org_li.before(item.html(''+data[opts.selectedItemProp]+'').prepend(close)); //jp[#4] allow to enter to input box only entries which are not there yet

If you use previous version, change it, or use this one. It is necessary for both ways outlined below.

way1:
Use it if you want to get same sized entries in input box for every item (see: sig_2.png).
Drawback:
Long entries will be truncated (cut).
See: “Michael Jordan in a slightly longer version than usually” is not shown as fully, but IE7 shows it correctly – (see: sig_3.png).

How to:
I. In css find: “ul.as-selections li.as-selection-item”
and add: overflow:hidden; width:200px;
Width is up to you. Whatever fits your design.
II. Add another entry (class):

ul.as-selections li.as-selection-item .as-selection-item-int {
float:left;
white-space:nowrap;
overflow:hidden;
width:179px;
}

Width here should be smaller than one in I. To accommodate “close x”.
Just give it about 20px less initially and adjust if needed.

way2:
Say, you do not want entries to be of same length, yet still you want IE7 to behave properly (more or less).
How to:
1. Go to: “ul.as-selections li.as-selection-item”
and add dot (.) in front of width
to make it look like this: overflow:hidden; .width:200px;
2. Go to “ul.as-selections li.as-selection-item .as-selection-item-int” and also add dot in front of width.
ul.as-selections li.as-selection-item .as-selection-item-int {
float:left;
white-space:nowrap;
overflow:hidden;
.width:179px;
}

You have just applied IE specific hack, which works decently (most of the time).
It will tell IE7 (and most likely 6) to show preset widths instead of using whole width of page, while other browsers will ignore this property and match wrapper size to entry width, so our “Michael Jordan in a slightly longer version than usually” will not truncate.
(see: sig_4.png)

Both widths must get dot (.) for this to work smoothly.

Again, use above at your own risk … and report problems.
Especially … someone please test it in IE6 and post results.

***************************

Hi, got a fix for IE7 (which has an issue with non-width floats within floats):

Replace the .prepend() function for the close-link with .append() instead and set float:none in the CSS. Not only does this work, it's better for screen readers (with the close link after the tag content) too :)

***************************

Hey guys, I did a small implementation to support auto select of first element at results list:

1 - Add this function to jquery.autoSuggest.js:

function setFirstItemSelected() {
	if ($(":visible", results_holder).length > 0) {
		var lis = $("li", results_holder);
		var start = lis.eq(0);
		var active = $("li.active:first", results_holder);

		if (active.length > 0) {
			start = active.next();
		}
		lis.removeClass("active");
		start.addClass("active");
	}
}

2 - Create a new propertie called selectFirstDataItem, and init it with false at jquery.autoSuggest.js, like:

resultClick: function (data) { }, resultsComplete: function () { }, selectFirstDataItem: false

3 - Go to function ProcessData, and at end of function before opts.resultsComplete.call(this); add this command:

if (opts.selectFirstDataItem) {setFirstItemSelected();}

4 - Ok guys, is it... Now you can init new propertie called selectFirstDataItem! Like:

$("#autosearch").autoSuggest(data.items, {asHtmlID: "12", selectedItemProp: "name", selectedValuesProp: "value", searchObjProps: "name", minChars: 2, matchCase: false, selectFirstDataItem: true, keyDelay: 0});



// bind the input element ID (that autosuggest is binded to) to pass in new data
var elementId = "#" + opts.asHtmlID;
//alert("What autosuggest ID to bind:" + elementId);
$(elementId).bind("assignTaskCustom", function(event, pData) {
    var id = opts.asHtmlID;
    if($("#as-selection-"+id, selections_holder).length <= 0 && !tab_press){
        input.val("").focus();
    }
    add_selected_item(pData, id);
});

 */

