/**
Ajax4Suggest Framework Core 1.0 - Service Pack 1.0
Copyright (c) 2005 - Guillaume Leleu

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program 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 General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

@note from the author
You are not allowed to use this library (or part of it) into a proprietary program
 or applications which is not under the GNU GPL term (free software itself).
If you intend to do so, contact me and you will be charged for the usage rights.

@inspired from the inital library of Julian Robichaux, http://www.nsftools.com
@author Guillaume Leleu - antispam1@myweb-services.com

Supported Browsers: IE 6.0+, Mozilla based browsers
*/

// Begin of Work around for Mozilla browser that cannot cancel a ENTER event
function noEnter(e){
	// For IE
	if (!e) {
		e = window.event;
	}
	var key = e.keyCode;
	if(key == '13') {
		return false;
	}
}
// End of Work around for Mozilla browser that cannot cancel a ENTER event

/**
 * @class Ajax4Suggest (form object (input), string (URL), string (GET/POST), 
 *                      form object (form), backend response (JS/XML/SOAP), classStyle(CSS)
 * @access public
 * @see If you use POST, the whole form will be submitted not only the input field
 */
function Ajax4Suggest(queryFieldName, lookupURLPrefix, _reqprotocol, _formname, _btype) {
	
	// Key event definition
	var KEYUP = 38;
	var KEYDOWN = 40;
	var KEYENTER = 13;
	var KEYTAB = 9;
	var KEYESC = 27;
		
	// Create the cache Object
	var cache = new Object();
	// For Mozilla
	var evt = window.event;

	// Keyboard counter
	var kcounter = -1;
	// Delay time to avoid server overload
	var delaytime = 500;
	// Searh in progress flag
	var searching = false;
	// Value
	var val = null;	
	// last Value (query) done
	var lastVal = null;
	// Cache Results
	var cacheResult = null;
	// Div Results visible or not
	var isVisible = false;
	// Get the query field using DOM		
	var queryField = document.getElementById(queryFieldName);	
	
	// ResultsDiv
	var ResultsDivId = null;
	// Set the URL for the backend
	var lookupURL = lookupURLPrefix;
	// Set the req protocol (GET/POST) - GET by default
	var reqprotocol = _reqprotocol;
	if (reqprotocol != 'POST') {
		reqprotocol = 'GET';
	}
	// Get the form (used with POST)
	var formname = _formname;
	if ((!formname || formname == 'undefined') && reqprotocol == 'POST') {
		//alert('With POST protocol, you have to specify which form will be submitted.');
		return false;
	}	
	//Set the backend type (Javascript -JS-, XML or SOAP)
	var btype = _btype;
	// Control the params
	switch(btype) {
		case 'JS': break;
		case 'XML': break;
		case 'SOAP': break;
		default: btype = 'JS';
	}
	// Set the DIV results name	
	//var ResultsDivId =  createDiv4Results("ResultsDiv4" + queryFieldName);

	// onBlur remove the div visibility
	queryField.onblur = outOfList;
	// onBlur remove the div visibility
	queryField.onfocus = deleteField;	
	// Main page event - a key is pressed
	queryField.onkeyup = keypressUpHandler;
	// Main page event - a key is pressed
	queryField.onkeydown = keypressDownHandler;

	/**
	* @method keypressDownHandler(object)
	* @access private
	* @see Used to handle TAB and ENTER event
	* @return void
	*/		
	function keypressDownHandler(evt) {
		
		// For IE
		if (!evt) {
			evt = window.event;
		}
 		var key = evt.keyCode;
		//alert(queryField.value);
 		val = escape(queryField.value);
		
		// A value exists in the input and no search in progress
 		if (queryField.value.length > 0 && searching == false && isVisible == true) {
		
			if (key == KEYDOWN) {
				// get where the user is in the list (row in the list)
				getSelectedRowNum(ResultsDivId, 1);
			} else if (key == KEYUP) {
				// get where the user is in the list (row in the list)
				getSelectedRowNum(ResultsDivId, -1);
			}
		  // select the entry (row in the list)
		  var selSpan = setSelectedSpan(ResultsDivId, kcounter);
		  // A line is selected
		  if (selSpan) {
				// Tab key is used
			  if (key == KEYTAB) {
			      _selectResult(selSpan);
			      kcounter = -1;
			    	return false;
			  }	
				// Tab key is used
			  if (key == KEYENTER) {
			      _selectResult(selSpan);
			      kcounter = -1;
			      // Work for IE but not for Mozilla
			    	return false;
			  }			  
			}
		}
	}
	
	/**
	* @method keypressUpHandler(object)
	* @access private
	* @see Used to handle KEY DOWN, KEY UP and normal entry event
	* @return void
	*/		
	function keypressUpHandler(evt) {
		
		// For IE
		if (!evt) {
			evt = window.event;
		}
 		var key = evt.keyCode;
 		// Avoid to overload the server
 		//setTimeout(DoNothing, delaytime);
		
		
 		//val = GetInputVal;
 		val = encodeURIComponent(queryField.value);
		
 		// A value exists in the input and no search in progress
 		if (queryField.value.length > 0 && searching == false) {
			
 			// A "normal" key is entered
			if ((key != KEYUP) && (key != KEYDOWN) && (key != KEYENTER) && (key != KEYTAB) && (key != KEYESC)) {
				
				// The last key entered is different from the current one - NO cache
				if (lastVal != val) {
					
					//POST requested: we have to create a pair/value
					if (reqprotocol == 'POST') {
						val = getFormValues(formname);
					}
					
					doRemoteQuery();
			  	// Use the cache
				} else {
				  	cacheResult = cache[val];
				  	getCache(); 
				}
				// Set the last Val entry with the current one
				lastVal = val;
			}
		 	// A "ESC" key is used
			else if (key == KEYESC) {
				queryField.value = "";
				showDivResults(false);
			}
  		// End of a value exists in the input and no search in progress
	  }	  	
	  // Bug for IE - Solved it by removing the list
	  else if (queryField.value.length < 1) {
			showDivResults(false);
	 	}
	}	
	/**
	* @method showDivResults(boolean)
	* @access private
	* @see 	This either shows or hides the lookup div, depending on the value of
	*       the "show" parameter.
	* @return void
	*/
	function showDivResults(show)
	{
	  if (show) {
		  
	    ResultsDivId.style.visibility = "visible";
	    isVisible = true;
	  }
	  else {
	    ResultsDivId.style.visibility = "hidden";
	    isVisible = false;
	  }
	}
	/**
	* @method outOfList(void)
	* @access private
	* @see 
	* @return void
	*/	
	function outOfList() {
		// Hide the DIV
		showDivResults(false)
	}
	/**
	* @method deleteField(void)
	* @access private
	* @see 
	* @return void
	*/	
	function deleteField() {
		queryField.value ="";
		// Set the DIV results name	(IE problem with DOM - The page needs to be fully loaded
		ResultsDivId =  createDiv4Results("ResultsDiv4" + queryFieldName);
		showDivResults(false)		
	}

	/**
	* @method createXMLHTTP(void)
	* @access private
	* @see Set up the XMLHTTP object we're using for the dynamic lookups.
	* @return _xmlhttp(object)
	*/
	function createXMLHTTP() {
		var _xmlhttp = null;
	  
	  // Try IE with MSXML 2
	  try {
	  	_xmlhttp = new ActiveXObject("Msxml2.XMLHTTP");
	  }
	  catch(e) {
	  	// Try IE with XML HTTP
	    try {
	      _xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
	    }
	    catch(e1) {
	    	// Try XMLHTTPRequest()
	    	try {
	      	_xmlhttp = new XMLHttpRequest();
	    	}
	    	catch(e2) {
	    	 // alert("Sorry, your browser does not support XML over HTTP using Javascript.");
	    	}
	    }
	  }
	  return _xmlhttp;
	}
	/**
	* @method doRemoteQuery(string)
	* @access private
	* @see sends the lookup request (as a URL with a query string) to a server in the background
	* @return void
	*/
	function doRemoteQuery() {
				
			  /* xmlHTTP state
 		    	*0 (uninitialized) 
				*1 (loading) 
				*2 (loaded) 
				*3 (interactive) 
				*4 (complete)
				*/	
		// Create the XML over HTTP object
		
	  xmlHttp = createXMLHTTP();
	  if (xmlHttp){
	  	// Send the GET or POST request
		//alert("inside2--"+val+"---"+queryField.value);
	  	if (reqprotocol != 'POST') {
			//alert(lookupURL + val);
	    	xmlHttp.open("GET", lookupURL + val, true);
	    // Send the POST request		    	
	  	} else {
	  		xmlHttp.open("POST", lookupURL, true);
	  	}
	    // What do we do when the response comes back?
	    xmlHttp.onreadystatechange = function() {
	    	// OK Response is here and call successful
	      if (xmlHttp.readyState == 4 && xmlHttp.responseText) {
		    	searching = false;
	      	// Fine HTTP status OK
	      	if (xmlHttp.status == 200) {
	      		//window.status = "Call done successfully.";
	        	// We have a XML or SOAP response
	        	if (btype == 'XML' || btype == 'SOAP') {
					ResultsFromXML(xmlHttp.responseXML);
	        	// We have a javascript response
	        	} else {
	        		//alert(xmlHttp.responseText);
		    		eval(xmlHttp.responseText);
	        	}        	
    		//window.status = "";
			// Probably an error msg
			} else if (xmlHttp.status == 500) {
				//window.status = "Call done but application error found.";
				//alert("HTTP error: " + xmlHttp.status);
			// Anything else (404 - not found, 403/401 - not authenticated...)
			} else {
				//window.status = "Call cancelled. HTTP error.";
				//alert("HTTP error: " + xmlHttp.status);
			}
	      // Call in progress and response not yet received
	      } else if (xmlHttp.readyState == 1) {
	      	// window.status = "Call in progress!";
	      	 searching = true;
	      }
	    };
	  	// Send the GET request
	  	if (reqprotocol != 'POST') {
	    	xmlHttp.send(null);
	    // Send the POST request	
	  	} else {
	  		xmlHttp.setRequestHeader("Content-Type","application/x-www-form-urlencoded; charset=UTF-8");
	  		xmlHttp.send(val);
	  	}	    
	  } else {
	  	//alert("Sorry, I am not able to create the XML over HTTP object.");
	  }
	}
	/**
	* @method createDiv4Results(object: div)
	* @access private
	* @see 	Create the <DIV> we're using to display the lookup results
	* @return object (div)
	*/		
	function createDiv4Results(divId)
	{
		// Create  the DIV node
	    var newNode = document.createElement("div");
	    // Set the DIV id
	    newNode.setAttribute("id", divId);
		// Append the HTML page witht the DIV
		// Bug with IE!! the form is inside a table, you cannot set the positioning
		// but if not positionned inside the table...IE does not accept the append...argg
	    queryField.parentNode.appendChild(newNode);
	    // set the div reference
	    divId = document.getElementById(divId);
	    
	    // figure out where the top corner of the div should be, based on the
	    // bottom left corner of the input field
	    var x = queryField.offsetLeft;
	    var y = queryField.offsetTop + queryField.offsetHeight;
	    var parent = queryField;
	    while (parent.offsetParent) {
	      parent = parent.offsetParent;
	      x += parent.offsetLeft;
	      y += parent.offsetTop;
	    }
	    // Style by default
	    divId.setAttribute("class", "selection");
	    divId.className = "selection";
	    divId.style.position = "absolute";
	    divId.style.left = x + "px";
	    divId.style.top = y + "px";
	    divId.style.visibility = "hidden";
	    divId.style.zIndex = 10000;
	
	    return divId;
	}
	/**
	* @method showResultsJS(array(), array())
	* @access private
	* @see 	This is the function that should be returned by the XMLHTTP call. It will
	*        format and display the lookup results.
	* @return void
	*/	
	function showResultsJS(resultArray1, resultArray2)
	{
		//document.form1.myInput2.value=;
		// Show the results via the DIV
				showDivResults(true);
		// remove any results that are already there
		while (ResultsDivId.childNodes.length > 0) {
			ResultsDivId.removeChild(ResultsDivId.childNodes[0]);
		}
		// if we had at least one result
		if (resultArray1.length < 1) {
			resultArray1 = new Array("");
			resultArray2 = new Array("No results found.");
		} 
		// add an entry for each of the results in the resultArray
		for (var i = 0; i < resultArray1.length; i++)
		{
			// each result will be contained within its own div
			var result = document.createElement("div");

		  //Set the default design
		  _unhighlightResult(result);

		  // Handles event on the DIV
		  result.onmousedown = selectResult;
		  result.onmouseover = highlightResult;
		  result.onmouseout = unhighlightResult;
		  
			// SPAN for TEXT ELEMENT [0]
		  var result1 = document.createElement("span");
	    result1.setAttribute("id", "guid");		  
		  result1.style.textAlign = "left";
		  result1.style.fontWeight = "normal";
		  result1.innerHTML = resultArray1[i];
		 	// SPAN FOR TEXT ELEMENT [1]
		 	//var result2 = document.createElement("span");
		 	//result2.setAttribute("id", "description");		
		 	//result2.style.textAlign = "right";
		 	//result2.style.fontWeight = "bold";
		 	//result2.style.paddingLeft = "20px";
		 	//result2.innerHTML = resultArray2[i];
		  // Commit to the document
	  	result.appendChild(result1);
	  	//result.appendChild(result2);
	  	ResultsDivId.appendChild(result);
		}
	  	// if this resultset is not in our cache, add it
  		var isCached = cache[queryField.value];
  		if (!isCached) {
     		addToCache(queryField.value, resultArray1, resultArray2);
	  	}
	}
	/**
	* @method selectResult()
	* @access private
	* @see 		The user clicks one of the lookup results.
	*           It puts the value of the result in the queryField and hides the
	*           lookup div.
	* @return void
	*/	
	function selectResult()
	{
	  _selectResult(this);
	}
	/**
	* @method _selectResult(object)
	* @access private
	* @see 		This actually fills the field with the selected result and hides the div
	* @return void
	*/	
	function _selectResult(item)
	{
	  var spans = item.getElementsByTagName("span");
	  if (spans) {
	    for (var i = 0; i < spans.length; i++) {
	      if (spans[i].getAttribute("id") == "guid") {
	        queryField.value = spans[i].innerHTML;
	        showDivResults(false);
	      }
	    }
	  }	  
	}	
	/**
	* @method highlightResult()
	* @access private
	* @see This is called when a user mouses over a lookup result
	* @return void
	*/		
	function highlightResult()
	{
	  _highlightResult(this);
	}
	/**
	* @method _highlightResult()
	* @access private
	* @see This actually highlights the selected result
	* @return void
	*/
	function _highlightResult(item)
	{
		// IE
		item.className = "selectionover";
		// Firefox
		item.setAttribute("class", "selectionover");
	}
	/**
	* @method unhighlightResult()
	* @access private
	* @see This is called when a user mouses away from a lookup result
	* @return void
	*/	
	function unhighlightResult()
	{
	  _unhighlightResult(this);
	}
	/**
	* @method _unhighlightResult()
	* @access private
	* @see This actually unhighlights the selected result
	* @return void
	*/		
	function _unhighlightResult(item)
	{
		// IE
		item.className = "selection";
		// Firefox
		item.setAttribute("class", "selection");	
	}	
	/**
	* @method addToCache(string, array(), array())
	* @access private
	* @see Add to cache function
	* @return void
	*/			
	function addToCache(queryString, resultArray1, resultArray2) {
		cache[queryString] = new Array(resultArray1, resultArray2);
	}
	/**
	* @method getSelectedSpanNum(object)
	* @access private
	* @see Get the number of the result that's currently selected/highlighted
	*      (the first result is 0, the second is 1, etc.)
	* @return num
	*/		
	function getSelectedRowNum(div, _int)
	{
	  var spans = div.getElementsByTagName("div");
	  kcounter = kcounter + _int;
	  // First row reached
	  if (kcounter < 0) {
	  	kcounter = 0;
	  }
	  // Last row reached
	  if (kcounter == spans.length) {
	  	kcounter--;
	  }
	  // Keydown - Unhighlight the last row
	  if (kcounter > 0 && _int == 1) {
	  	_unhighlightResult(spans[kcounter-1]);
	  }
	  // Keyup - Unhighlight the last row	  
	  if (kcounter >= 0 && _int == -1) {
	  	_unhighlightResult(spans[kcounter+1]);
	  }
	  _highlightResult(spans[kcounter]);
	  
	}
	/**
	* @method setSelectedSpan(object, num)
	* @access private
	* @see Select/highlight the result at the given position
	* @return object
	*/	
	function setSelectedSpan(div, spanNum)
	{
	  var spans = div.getElementsByTagName("div");
	  var ret;
	  try{
		  ret=spans[spanNum];
	  }catch(e){
		  ret="";
	  }
	  return ret;
	}
	/**
	* @method getFormValues(object -form-, string -fct name-)
	* @access private
	* @see read all the form values and create a string with ?name=value&...
	* @return string
	*/	
	function getFormValues(_fobj) 
	{
	   var str = ""; 
	   var valueArr = null; 
	   var val = ""; 
	   var cmd = "";
	   var fobj = eval('document.forms.' + _fobj);
	   for(var i = 0;i < fobj.elements.length;i++) 
	   { 
	       switch(fobj.elements[i].type) 
	       { 
	           case "text": 
	                str += fobj.elements[i].name + 
	                 "=" + escape(fobj.elements[i].value) + "&"; 
	                 break;
	           case "hidden": 
	                str += fobj.elements[i].name + 
	                 "=" + escape(fobj.elements[i].value) + "&"; 
	                 break; 	                 
	           case "select-one": 
	                str += fobj.elements[i].name + 
	                "=" + fobj.elements[i].options[fobj.elements[i].selectedIndex].value + "&"; 
	                break; 
	       } 
	   } 
	   str = str.substr(0,(str.length - 1)); 
	   return str; 
	}
	/**
	* @method ArrayFromXML()
	* @access private
	* @see read XML and call the JS function
	* @return string
	*/	
	function ResultsFromXML(_xmlHttpResponse) 
	{
		// 2 arrays for the final results
    	var myArrayId = new Array();
		var myArrayDesc = new Array();
		// Create the XML response obj
		var XMLresponse = _xmlHttpResponse;	
		//alert(" TEST");
		//alert("-->"+XMLresponse.getElementsByTagName('result').length);
		//alert(" TEST2");
		// Put the elements in the Arrays
		for (i = 0; i < XMLresponse.getElementsByTagName('result').length && i<10; i++) {
			//alert("inside");
			//alert(XMLresponse.getElementsByTagName('result')[i].firstChild.nodeValue);
			myArrayId.push(XMLresponse.getElementsByTagName('result')[i].firstChild.nodeValue);
			myArrayDesc.push(XMLresponse.getElementsByTagName('result')[i].firstChild.nodeValue);
		}
		// Call the final fct
		showResultsJS(myArrayId, myArrayDesc);
	}
	
	function findPosX(obj)
	{
		var curleft = 0;
		if (obj.offsetParent)
		{
			while (obj.offsetParent)
			{
				curleft += obj.offsetLeft
				obj = obj.offsetParent;
			}
		}
		else if (obj.x)
			curleft += obj.x;
		return curleft;
	}
	
	function findPosY(obj)
	{
		var curtop = 0;
		if (obj.offsetParent)
		{
			while (obj.offsetParent)
			{
				curtop += obj.offsetTop
				obj = obj.offsetParent;
			}
		}
		else if (obj.y)
			curtop += obj.y;
		return curtop;
	}
	/**
	* @method getCache(void)
	* @access private
	* @see needed to able to use the timeout function
	* @return object
	*/		
	function getCache() {
		showResultsJS(cacheResult[0], cacheResult[1]);
	}
	/**
	* @method DoNothing(void)
	* @access private
	* @see needed to able to use the timeout function
	* @return string
	*/		
	function DoNothing() {
	}
}