/*******************************************************

AutoSuggest - a javascript automatic text input completion component
Copyright (C) 2005 Joe Kepley, The Sling & Rock Design Group, Inc.

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

*******************************************************

Please send any useful modifications or improvements via 
email to joekepley at yahoo (dot) com

*******************************************************/

/********************************************************
 The AutoSuggest class binds to a text input field
 and creates an automatic suggestion dropdown in the style
 of the "IntelliSense" and "AutoComplete" features of some
 desktop apps. 
 Parameters: 
 elem: A DOM element for an INPUT TYPE="text" form field
 suggestions: an array of strings to be used as suggestions
              when someone's typing.

 Example usage: 
 
 Please enter the name of a fruit.
 <input type="text" id="fruit" name="fruit" />
 <script language="Javascript">
 var fruits=new Array("apple","orange","grape","kiwi","cumquat","banana");
 new AutoSuggest(document.getElementById("fruit"),fruits);
 </script>

 Requirements: 

 Unfortunately the AutoSuggest class doesn't seem to work 
 well with dynamically-created DIVs. So, somewhere in your 
 HTML, you'll need to add this: 
<iframe id=ifrmAutoSuggest name=ifrmAutoSuggest class=Popup style=\"DISPLAY:'none';\"></iframe>
<div id=divAutoSuggest name=divAutoSuggest class=Popup style=\"DISPLAY:'none';\"><ul></ul></div>

 Here's a default set of style rules that you'll also want to 
 add to your CSS: 

.suggestion_list
{
	background: white;
	border: 1px solid;
	padding: 4px;
	width: 400px
}

.suggestion_iframe
{
	width: 400px
}

.suggestion_list ul
{
	padding: 0;
	margin: 0;
	list-style-type: none;
}

.suggestion_list a
{
	text-decoration: none;
	color: black;
}

.suggestion_list .selected
{
	background: #FF5700;
	color: white;
}

.suggestion_list .selected a
{
	color: white;	
}

.suggestion_list .selected a:Hover {
	color: black;
}

#autosuggest
{
	display: none;
}

ul 
{
	margin-top: 0;
	margin-bottom: 0;
}
*********************************************************/
function AutoSuggest(elem, idelem, suggestions, elemValidationFunction, elemValidationFunctionParams, restrictData, styleClassName, resetIDField, callBack)
{
	//The 'me' variable allow you to access the AutoSuggest object
	//from the elem's event handlers defined below.
	var me = this;

	//A reference to the element we're binding the list to.
	this.elem = elem;
	this.elemValidationFunction = elemValidationFunction;
	this.elemValidationFunctionParams = elemValidationFunctionParams;
	
	//A place to store the ID of the lookup element.
	this.idelem = idelem;

	this.suggestions = suggestions;

	//Arrow to store a subset of eligible suggestions that match the user's input
	this.eligible = new Array();

	//The text input by the user.
	this.inputText = null;

	//A pointer to the index of the highlighted eligible item. -1 means nothing highlighted.
	//this.highlighted = -1;

	//A div to use to create the dropdown.
	this.iframe = document.getElementById("ifrmAutoSuggest");
	this.div = document.getElementById("divAutoSuggest");
	
	this.resetIDField = resetIDField;
	this.styleClassName = "";
	this.itemNotValidText = "Item not valid.";
	this.preOnBlurEvent = null;
	this.postOnBlurEvent = null;
	this.callBack = callBack;
	
	if (styleClassName != null) {
		this.styleClassName = styleClassName;
	}

	//Do you want to remember what keycode means what? Me neither.
	var TAB = 9;
	var ENTER = 13;
	var ESC = 27;
	var KEYUP = 38;
	var KEYDN = 40;
	
	
	var allowMultipleEntries = false;  //Allow multiple entries (using a ;) - won't work as using idelem!
	var restrictEntryToList = restrictData;    //Ensure users only enter something from the list
	
	var checkOnBlur = true;			   //Don't fire the onblur when the user clicks the DIV.
	var defaultHighlighted = 0;        //A pointer to the index of the highlighted eligible item. -1 means nothing highlighted.
	
	//if (restrictData) {
	//	defaultHighlighted = 0;      // if restrict the data to that in the list, then default to first item.
	//}
	
	this.highlighted = defaultHighlighted;
	

	//The browsers' own autocomplete feature can be problematic, since it will 
	//be making suggestions from the users' past input.
	//Setting this attribute should turn it off.
	elem.setAttribute("autocomplete","off");

	//We need to be able to reference the elem by id. If it doesn't have an id, set one.
	if(!elem.id)
	{
		var id = "autosuggest" + idCounter;
		idCounter++;

		elem.id = id;
	}
	
	this.validateElem = function() 
	{
		if (!checkOnBlur) {
			return;
		}
		
		if (me.idelem != null && me.resetIDField) {
			me.idelem.value = "";
		}
		var entryInList = false;

		var suggestion;
		var suggestionID;
		var code;
		var ouRef;
		
		for (i in me.suggestions) 
		{
			suggestion = me.suggestions[i][0];	
			suggestionID = me.suggestions[i][1];
			code = me.suggestions[i][2];
			ouRef = me.suggestions[i][5];
			if (suggestion.toLowerCase() == elem.value.toLowerCase())
			{
				entryInList = true;
				
				if (me.idelem != null) {
					me.idelem.value = suggestionID;
					break;
				}
			}
		}
		
		if (restrictEntryToList && !entryInList && elem.value.length > 0) {
			alert(this.itemNotValidText);
			setFocus(elem);
			return;
		}
		
		if (me.elemValidationFunction != null) {
			me.elemValidationFunction(me.elemValidationFunctionParams);
		}
		me.hideDiv();
		
		if (me.callBack != null) {
			me.callBack(new Array(suggestion, suggestionID, code, ouRef));
		}
	};

	elem.onblur = function()
	{
		if (me.preOnBlurEvent != null) {
			eval(me.preOnBlurEvent);
		}
		me.validateElem();
		if (me.postOnBlurEvent != null) {
			eval(me.postOnBlurEvent);
		}
	};


	/********************************************************
	onkeydown event handler for the input elem.
	Tab key = use the highlighted suggestion, if there is one.
	Esc key = get rid of the autosuggest dropdown
	Up/down arrows = Move the highlight up and down in the suggestions.
	********************************************************/
	elem.onkeydown = function(ev)
	{
		var key = me.getKeyCode(ev);

		switch(key)
		{
			case ENTER:
			case TAB:
				if (me.div.style.display == 'block') 
				{
					me.useSuggestion();
				}
				break;

			case ESC:
				me.hideDiv();
				break;

			case KEYUP:
				if (me.highlighted > 0)
				{
					me.highlighted--;
				}
				me.changeHighlight(key);
				break;

			case KEYDN:
				if (me.highlighted < (me.eligible.length - 1))
				{
					me.highlighted++;
				}
				me.changeHighlight(key);
				break;
		}
	};

	/********************************************************
	onkeyup handler for the elem
	If the text is of sufficient length, and has been changed, 
	then display a list of eligible suggestions.
	********************************************************/
	elem.onkeyup = function(ev) 
	{
		var key = me.getKeyCode(ev);
		//alert(key);
		switch(key)
		{
		//The control keys were already handled by onkeydown, so do nothing.
		case TAB:
		case ESC:
		case KEYUP:
		case KEYDN:
		case ENTER:
			return;
		default:
		
			//alert(key);
			//Do a search on the last typed in value after the last ";"
		
			var inputValue;
			
			if(this.value.indexOf(";") == 0 || allowMultipleEntries == false)
			{
				inputValue = this.value;
			}
			else
			{
				var tmpArray = this.value.split(";");
				inputValue = tmpArray[tmpArray.length -1];
			}

			if (inputValue != me.inputText)
			{
				me.inputText = inputValue;
				me.getEligible();
				
				
				if (me.highlighted >= me.eligible.length) {
					me.highlighted = me.eligible.length - 1;
				}
				
				if (me.eligible.length == 0 || inputValue.length == 0) {
					me.hideDiv();
				} else {
					me.createDiv();
					me.positionDiv();
					me.showDiv();
				}
			}
			//else
			//{
				//me.hideDiv();
			//}
		}
	};


	/********************************************************
	Insert the highlighted suggestion into the input box, and 
	remove the suggestion dropdown.
	********************************************************/
	this.useSuggestion = function()
	{
		if (this.highlighted > -1)
		{
			//this.elem.value = this.eligible[this.highlighted];
			
			//This code is used to add selected text to the text already in the text box
			
			if(this.elem.value.indexOf(";") <= 0 || allowMultipleEntries == false)
			{
				if (this.idelem != null) {
					this.idelem.value = this.eligible[this.highlighted][1];
				}
				this.elem.value = this.eligible[this.highlighted][0];				
			}
			else
			{
				var tmpArray = this.elem.value.split(";");
				
				this.elem.value = "";
				
				for(var i = 0; i < tmpArray.length-1; i++)
				{
					if(this.elem.value == "")
					{ 
						this.elem.value = tmpArray[i] + ";"
					}
					else
					{
						this.elem.value += tmpArray[i] + ";"
					}
				}
				
				this.elem.value += this.eligible[this.highlighted];
			}
			
			this.hideDiv();
			//It's impossible to cancel the Tab key's default behavior. 
			//So this undoes it by moving the focus back to our field right after
			//the event completes.
			//setTimeout("document.getElementById('" + this.elem.id + "').focus()",0);
		}
	};

	/********************************************************
	Display the dropdown. Pretty straightforward.
	********************************************************/
	this.showDiv = function()
	{
		this.iframe.style.display = 'block';
		this.div.style.display = 'block';
				
		this.iframe.style.height = this.div.offsetHeight;		
		this.iframe.style.width = this.div.offsetWidth;	
	};

	/********************************************************
	Hide the dropdown and clear any highlight.
	********************************************************/
	this.hideDiv = function()
	{
		checkOnBlur = true;
		this.div.style.display = 'none';
		this.iframe.style.display = 'none';
		
		//this.highlighted = -1;
		this.highlighted = defaultHighlighted;
	};

	/********************************************************
	Modify the HTML in the dropdown to move the highlight.
	********************************************************/
	this.changeHighlight = function()
	{
		var lis = this.div.getElementsByTagName('LI');
		for (i in lis)
		{
			var li = lis[i];

			if (this.highlighted == i)
			{
				li.className = "selected";
			}
			else
			{
				li.className = "";
			}
		}
	};

	/********************************************************
	Position the dropdown div below the input text field.
	********************************************************/
	this.positionDiv = function()
	{
		var el = this.elem;
		var x = 0;
		var y = el.offsetHeight;
	
//		//Walk up the DOM and add up all of the offset positions.
//		while (el.offsetParent && el.tagName.toUpperCase() != 'BODY')
//		{
//			x += el.offsetLeft;
//			y += el.offsetTop;
//			el = el.offsetParent;
//		}

//		x += el.offsetLeft;
//		y += el.offsetTop;

       y += $("#" + el.id).position().top;
       x += $("#" + el.id).position().left;

		this.div.style.left = x + 'px';
		this.div.style.top = y + 'px';
			
		this.iframe.style.left = this.div.style.left;
		this.iframe.style.top = this.div.style.top;			
		
	};

	this.testing = function(i) {
		alert(i);
	}

	/********************************************************
	Build the HTML for the dropdown div
	********************************************************/
	this.createDiv = function()
	{	
		var ul = document.createElement('ul');

		//Create an array of LI's for the words.
		for (i in this.eligible)
		{
			var word = this.eligible[i][3]; // use display suggestion
			var code = this.eligible[i][2];
	
			var displayText = "";
			if (code != null && code.length > 0) {
				displayText += code + " - ";
			}
			
			displayText += word;
	
			var li = document.createElement('li');	
			var a = document.createElement('a');
			a.href="javascript:false";
			//a.innerHTML = word;
			a.innerHTML = displayText;
			li.appendChild(a);
			if (me.highlighted == i)
			{
				li.className = "selected";
			}
			ul.appendChild(li);
		}
	
		this.div.replaceChild(ul,this.div.childNodes[0]);
	

		/********************************************************
		mouseover handler for the dropdown ul
		move the highlighted suggestion with the mouse
		********************************************************/
		ul.onmouseover = function(ev)
		{
			checkOnBlur = false;
	
			//Walk up from target until you find the LI.
			var target = me.getEventSource(ev);	
								
			if (typeof(target.tagName) != "undefined") 
			{
				while (target.parentNode && target.tagName.toUpperCase() != 'LI')
				{
					target = target.parentNode;
				}
			} 
			else 
			{
				target = target.parentNode.parentNode;
			}
		
			var lis = me.div.getElementsByTagName('LI');
			
			//for (i in lis)
			for (i=0; i<lis.length; i++) 
			{
				var li = lis[i];
				if(li == target)
				{
					me.highlighted = i;
					break;
				}
			}
			me.changeHighlight();
		};
		
		ul.onmouseout = function(ev) 
		{
			checkOnBlur = true;
		}

		/********************************************************
		click handler for the dropdown ul
		insert the clicked suggestion into the input
		********************************************************/
		ul.onclick = function(ev)
		{
			//Walk up from target until you find the LI. - need to ensure if the selection item is differnt than
			//that which the mouse is hovering over.
			var target = me.getEventSource(ev);
			if (typeof(target.tagName) != "undefined") 
			{
				while (target.parentNode && target.tagName.toUpperCase() != 'LI')
				{
					target = target.parentNode;
				}
			} 
			else 
			{
				target = target.parentNode.parentNode;
			}
		
			var lis = me.div.getElementsByTagName('LI');		
			//for (i in lis)
			
			for (i=0; i<lis.length; i++) 
			{
				var li = lis[i];
				if(li == target)
				{
					me.highlighted = i;
					break;
				}
			}
			
			me.useSuggestion();
			me.hideDiv();
			me.cancelEvent(ev);
			me.validateElem();
			return false;
		};
	
		this.div.className="suggestion" + this.styleClassName + "_list";
		this.iframe.className="suggestion" + this.styleClassName + "_iframe";
		
		
		this.div.style.position = 'absolute';
		this.iframe.style.position = 'absolute';

	};

	/********************************************************
	determine which of the suggestions matches the input
	********************************************************/
	this.getEligible = function()
	{	
		this.eligible = new Array();
		for (i in this.suggestions) 
		{
			//var suggestion = this.suggestions[i];
			var suggestion = this.suggestions[i][0];
			var suggestionID = this.suggestions[i][1];
			var code = this.suggestions[i][2];
			
			var displaySuggestion = this.suggestions[i][3];
			if (displaySuggestion == null) {
				displaySuggestion = suggestion;
			} 
								
			if(suggestion.toLowerCase().indexOf(this.inputText.toLowerCase()) == "0" ||
				(code != null && code.toLowerCase().indexOf(this.inputText.toLowerCase()) == "0"))
			{
				var tmpArray = new Array(suggestion, suggestionID, code, displaySuggestion);
				this.eligible[this.eligible.length]=tmpArray;
			}
		}
	};

	/********************************************************
	Helper function to determine the keycode pressed in a 
	browser-independent manner.
	********************************************************/
	this.getKeyCode = function(ev)
	{
		if(ev)			//Moz
		{
			return ev.keyCode;
		}
		if(window.event)	//IE
		{
			return window.event.keyCode;
		}
	};

	/********************************************************
	Helper function to determine the event source element in a 
	browser-independent manner.
	********************************************************/
	this.getEventSource = function(ev)
	{
		if(ev)			//Moz
		{		
			return ev.target;
		}
	
		if(window.event)	//IE
		{
			return window.event.srcElement;
		}
	};

	/********************************************************
	Helper function to cancel an event in a 
	browser-independent manner.
	(Returning false helps too).
	********************************************************/
	this.cancelEvent = function(ev)
	{
		if(ev)			//Moz
		{
			ev.preventDefault();
			ev.stopPropagation();
		}
		if(window.event)	//IE
		{
			window.event.returnValue = false;
		}
	};
}

//counter to help create unique ID's
var idCounter = 0;