/**
 * Short description of the file
 *
 * Long description of the file
 * (Dit kan over meerdere regels en je kan zelfs HTML gebruiken)
 *
 * Na een witregel begint een tweede paragraaf van de lange 
 * beschrijving van het bestand
 *
 * @package    Occhio 
 * @subpackage Evt package naam (bv naam van klant)
 * @copyright  2007 Occhio Design
 * @filesource
 * @author    
 * @todo       Evt dingen die todo zijn.. todo-tag mag ook in class, functie of inline 
 */


/**
 * Suggest Class
 *
 * Gets suggestions for a given query from a database and
 * shows them in a UL applied to a given textfield
 *
 * @package   Occhio Design Ajax Library
 * @copyright 2007 Occhio Design
 * @author    Gijs Wilbrink
 * @version   0.1
 */

function IMM_AutoSuggest(id, oOptions)
{

	/**
 	 * Initialize function (JavaScript class constructor)
	 *
	 * Initialize the class: set all class properties and call the first necessary functions
	 * Tales one mandatory variable (id) and tons of optional variables
	 *
	 * @param	int    	id				The id of the textfield, on which the autosuggest tool should be applied
	 * @param	string 	url				Optional url of the server side script that fetches the results and returns them to JavaScript; default = "/lib/suggest/suggest.php"
	 * @param	bool   	enableAmount	Optional show the amount of results per suggestion (make sure you also enable this server-side); default = false
	 * @param	string	boxClass 		Optional CSS class of the suggestion box; default = "suggestions"
	 * @param	string	suggestionClass	Optional CSS class of eacht suggestion entry; = default = "suggestion"
	 * @param	int		idleTime		Optional waiting time before requesting suggestions, so that it only fires when user stops typing for a short while; default = 200
	 * @param	int		minLength		Optional minimum required amount of characters in the textfield before requesting suggestions; default = 2
	 * @return	bool	true
	 */ 

	this.init = function()
	{
		var OD_Suggest = this;
		
		this.id = id;
		this.iTimeoutId = null;
		this.oXmlHttp = false;
		this.oCurrentHighlight = false;
		this.textField = document.getElementById(this.id);
		this.includeJsFile("/static/lib/ajax/zxml.js");
		this.includeJsFile("/static/lib/ajax/json.js");
		this.includeJsFile("/static/lib/common/common.js");
		this.parentForm = this.findForm();
		
		this.parentForm.setAttribute("autocomplete", "off");
		
		this.oOptions = this.augment({
			url: "/lib/suggest/suggest.php",
			enableAmount: false,
			boxClass: "",
			suggestionClass: "",
			idleTime: 150,
			minLength: 2,
			autoSubmit: true
		}, oOptions);
		
		this.suggestionsBox = this.prepareSuggestionsBox();
		this.setBoxClass(false);
		this.setTextfieldEvents();
		
		document.body.onclick = function()
		{
		
			OD_Suggest.hideSuggestionsBox();
		};
		
		return true;
	};

	/**
 	 * Prepare Suggestions Box
	 *
	 * Creates a UL which can contain keyword suggestions
	 *
	 * @return	object	ulSuggestions
	 */ 
	 
	this.prepareSuggestionsBox = function()
	{
		var ulSuggestions = document.getElementById("OD_Suggestions");
		if(!ulSuggestions) {
    		ulSuggestions = document.createElement("ul");
    		ulSuggestions.id = "OD_Suggestions";
			ulSuggestions.className = "OD_Suggestions " + this.oOptions.boxClass;
			document.body.appendChild(ulSuggestions);
		}
		
		return ulSuggestions;
	};
	
	/**
 	 * Hide Suggestions Box
	 *
	 * Empty and hide the UL with suggestions	
	 * 	 
	 * @return	bool	true
	 */ 
	 
	this.hideSuggestionsBox = function()
	{
		this.suggestionsBox.innerHTML = "";
		this.setBoxClass(false);
		return true;
	};
	
	/**
 	 * Show Suggestions Box
	 *
	 * Show the UL with suggestions	
	 * 	 
	 * @return	bool	true
	 */ 
	 
	this.showSuggestionsBox = function()
	{
	
		this.setBoxClass(true);
		var extraMargin = 0;

    	if (this.textField.marginLeft) {
    		extraMargin = this.textField.marginLeft;
    	}
   		this.textField.parentNode.appendChild(this.suggestionsBox);
		this.suggestionsBox.style.left = this.textField.offsetLeft + extraMargin + "px";
		this.suggestionsBox.style.top = this.textField.offsetTop + extraMargin + this.textField.offsetHeight + "px";

		if(InternetExplorer()) {
			this.suggestionsBox.style.width = this.textField.offsetWidth + "px";
		} else {
			this.suggestionsBox.style.minWidth = this.textField.offsetWidth + "px";
		}
		return true;
	};
	
	
	/**
 	 * Set Box Class
	 *
	 * Sets the class of the Suggestions UL to "active" or "inactive" 
	 *
	 * @param	bool   	active  true = active, false = inactive	
	 * @return	bool	true
	 */ 
	 
	this.setBoxClass = function(active)
	{
		var activeClass = "OD_Suggestions-inactive ";
		
		if(active) {

			activeClass = "OD_Suggestions-active ";
		}
		
		
		this.suggestionsBox.className = activeClass + " OD_Suggestions " + this.oOptions.boxClass;
		
		return true;
	};
	
	/**
 	 * Set Textfield Events
	 *
	 * Sets the OnKeyUp Event Handler for the textfield
	 * The handler checks which key was pressed and calls a suited function
	 *
	 * @return	bool	true or false
	 */ 
	 
	this.setTextfieldEvents = function()
	{
		var OD_Suggest = this;
		
		$(OD_Suggest.textField).attr("autocomplete", "off");
		
		OD_Suggest.textField.onkeyup = function(e)
		{
			// Cancel all previous calls
			clearTimeout(OD_Suggest.iTimeoutId);
			
			// Get key code		
			key = OD_Suggest.getKeyCode(e);
			
			
			var returnValue = true;
			// Check key and react
			switch (key) {
				case 27: // escape: hide suggestions
					OD_Suggest.hideSuggestionsBox();
					returnValue = false;
				break;
				case 13: // enter: hide suggestions and continue normal browser behavior (i.e. send form)
					OD_Suggest.hideSuggestionsBox();
					returnValue = true;
				break;
				case 38: // up arrow: walk the list of suggestions upwards
					if (OD_Suggest.textField.value !== null && OD_Suggest.textField.value !== "") {
						OD_Suggest.walkList("up");
					}
					returnValue = false;
				break;
				case 40: // down arrow: walk the list of suggestions downwards
					if (OD_Suggest.textField.value !== null && OD_Suggest.textField.value !== "") {
						OD_Suggest.walkList("down");
					}
					returnValue = false;
				break;
				case 37:
				case 39:
					returnValue = true;
				break;
				default: //everything else: get suggestions from server
					if (OD_Suggest.textField.value.length < OD_Suggest.oOptions.minLength) {
						OD_Suggest.hideSuggestionsBox();		
					} else {					
						OD_Suggest.iTimeoutId = setTimeout(function () {
							OD_Suggest.getSuggestions();
						}, OD_Suggest.oOptions.idleTime);							
					}	
			}
			return returnValue;
		};
	};
	
	/**
 	 * Get Key Code
	 *
	 * Cross-browser way of checking which key was pressed and returning the key code in one format
	 * 	 
	 * @param	event	e			The onKeyUp event
	 * @return	string	keycode		The resulting key code of the event
	 */ 
	 
	this.getKeyCode = function(e)
	{
	    if (document.layers) {
	        return e.which;
	    } else if (document.all) {
	        return event.keyCode;
	    } else if (document.getElementById) {
	        return e.keyCode;
	    } else {
			return 0;
		}
	};
	
	/**
 	 * Walk List
	 *
	 * Walk through the list of suggestions, highlighting the selected suggestion	
	 * 	 
	 * @ param	string	direction	The direction of which the list should be walked through
	 * @return	bool	true
	 */ 
	  
	this.walkList = function(direction)
	{
		if (direction == "down") {
			if(!this.oCurrentHighlight) {
				this.oCurrentHighlight = this.suggestionsBox.firstChild;
			} else if(this.oCurrentHighlight.nextSibling) {
				this.oCurrentHighlight = this.oCurrentHighlight.nextSibling;
			} else {
				this.oCurrentHighlight = this.oCurrentHighlight;
			}
		}		
		else if(direction == "up") {			
			if(this.oCurrentHighlight.previousSibling) {
				this.oCurrentHighlight = this.oCurrentHighlight.previousSibling;
			} else {
				this.oCurrentHighlight = false;
			}	
		} 
		if(this.oCurrentHighlight) {
			this.highlightSuggestion(this.oCurrentHighlight.id);
			this.passSuggestion(this.oCurrentHighlight.id);
		} else {
			this.highlightNone();
		}
		
		return true;
	};

	/**
 	 * Get Suggestions
	 *
	 * Ajax Request to oOptions.url to get the suggstions matching the value of the textfield	
	 * 	 
	 * @return	void	Calls showSuggestions() if results were found
	 */ 
	 
	this.getSuggestions = function()
	{
			
		var OD_Suggest = this;
		var oXmlHttp = OD_Suggest.oXmlHttp;
		
		// Reset timer
	    if (OD_Suggest.iTimeoutId !== null) {
	        clearTimeout(OD_Suggest.iTimeoutId);
	        OD_Suggest.iTimeoutId = null;
	    }
	    
	    if (!oXmlHttp) {
	        oXmlHttp = zXmlHttp.createRequest();
	    } else if (oXmlHttp.readyState !== 0) {
	        oXmlHttp.abort();
	    }
	    
		// Get suggestions
	    var queryString = "query=" + encodeURIComponent(OD_Suggest.textField.value);
	    oXmlHttp.open("get", OD_Suggest.oOptions.url + "?" + queryString, true);
		
	    oXmlHttp.onreadystatechange = function () {               
	        
			if (oXmlHttp.readyState == 4 && oXmlHttp.status == 200) {
				OD_Suggest.hideSuggestionsBox();
				if(oXmlHttp.responseText != "false" && (OD_Suggest.textField.value.length >= OD_Suggest.oOptions.minLength)) {
					OD_Suggest.showSuggestions(oXmlHttp.responseText.parseJSON());	
				}
	        }
	    };
		oXmlHttp.setRequestHeader("X-Requested-With", "XMLHttpRequest");
		
		// Set timer
	    OD_Suggest.iTimeoutId = setTimeout(function () {
		
	        oXmlHttp.send(null);
	    }, OD_Suggest.oOptions.idleTime);
	};
	
	/**
 	 * Show Suggestions
	 *
	 * Fills the Suggestions UL Box with LI's containing the suggestions	
	 * 	 
	 * @return	void	Calls setBoxClass() function, setting the UL class to "active"
	 */ 
	 
	this.showSuggestions = function(aSuggestions)
	{
		var OD_Suggest = this;
	
		for(var i = 0; i<aSuggestions.length; i++) {
			var newSuggestion = document.createElement("li");
			newSuggestion.id = "suggestion-" + i;
			newSuggestion.className = "OD_Suggestion " + this.oOptions.suggestionClass;
			newSuggestion.innerHTML = aSuggestions[i].result;
			newSuggestion.onmouseover = function()
			{
				OD_Suggest.highlightSuggestion(this.id);
				OD_Suggest.oCurrentHighlight = this;			
			};
			
			newSuggestion.onclick = function()
			{			
				OD_Suggest.passSuggestion(this.id);
				OD_Suggest.hideSuggestionsBox();
				if(OD_Suggest.oOptions.autoSubmit) {
					$(OD_Suggest.parentForm).submit();
				}
			};
			
			this.suggestionsBox.appendChild(newSuggestion);
		}
		
		OD_Suggest.oCurrentHighlight = false;
		
		this.showSuggestionsBox();
	};
	
	
	
	/**
 	 * Pass Suggestion Suggestion
	 *
	 * Passes a suggestion to the textfield value	
	 * 	 
	 * @return	bool	true
	 */ 
	 
	this.passSuggestion = function(id)
	{
		var liSuggestion = document.getElementById(id);
		this.textField.value = liSuggestion.innerHTML;
		
		return true;
	};
	
	/**
 	 * Highlight Suggestion
	 *
	 * Give a suggestion a "hover" class	
	 * 
	 * @param	id		The ID of the LI that should be highlighted	 
	 * @return	bool	true
	 */ 
	 
	this.highlightSuggestion = function(id)
	{
		// First unhighlight everything
		this.highlightNone();
		
		// Then highlight the right LI
		var liSuggestion = document.getElementById(id);
		liSuggestion.className += " hover";
		
		return true;
	};
	
	/**
 	 * Highlight None
	 *
	 * Removes the "hover" class from eacht Suggestion LI	
	 * 	 
	 * @return	bool	true
	 */ 
	 
	this.highlightNone = function()
	{
		var aSuggestions = this.suggestionsBox.getElementsByTagName("li");
		for(var i = 0; i < aSuggestions.length; i++) {
			aSuggestions[i].className = aSuggestions[i].className.replace(/hover/gi, "");
		}	
	};
	
	/**
 	 * Find Form
	 *
	 * Find the parent form of the textfield	
	 * 	 
	 * @return	object	the form. returns "false" on failure
	 */ 
	 
	this.findForm = function()
	{
		return $(this.textField).parents("form")[0];
		
	};
		
	/**
 	 * Include JavaScript File
	 *
	 * Ads a <script> tag to the document to include an external .js file
	 *
	 * @param	string 	sFilename		URL of the external JavaScript
	 * @return	bool	true
	 */ 
	 
	this.includeJsFile = function(sFilename) 
	{		
		var includeScript = document.createElement("script");
		includeScript.setAttribute("src", sFilename);
		includeScript.setAttribute("type", "text/javascript");
		document.getElementsByTagName("head")[0].appendChild(includeScript);
		return true;	
	};
	
	/**
 	 * Include CSS File
	 *
	 * Ads a <style> tag to the document to include an external .css file
	 *
	 * @param	string 	sFilename		URL of the external Styleshoeet
	 * @return	bool	true
	 */ 
	 
	this.includeCssFile = function(sFilename) 
	{		
		var includeStyle = document.createElement("link");
		includeStyle.setAttribute("href", sFilename);
		includeStyle.setAttribute("type", "text/css");
		includeStyle.setAttribute("rel", "stylesheet");
		document.getElementsByTagName("head")[0].appendChild(includeStyle);
		return true;	
	};
	
	/**
 	 * Augment
	 *
	 * This enables optional variables to be passed to a function	
	 * 	 
	 * @param	object	oSelf			The default values of the object
	 * @param	string 	oOther			The optional variables set by the user
	 * @return	object	oSelf			All variables put together into one object
	 */ 
		
	this.augment = function (oSelf, oOther) 
	{
		if (oSelf === null) {
			oSelf = {};
		}
				
		for (var i = 1; i < arguments.length; i++) {
			var o = arguments[i];
			if (typeof(o) != 'undefined' && o !== null) {
				for (var j in o) {
					oSelf[j] = o[j];
				}
			}
		}
		return oSelf;
	};
	
	
	// Call initialize function - DO NOT REMOVE THIS FUNCTION CALL
	this.init();
}

var zXml={useActiveX:(typeof ActiveXObject!="undefined"),useDom:document.implementation&&document.implementation.createDocument,useXmlHttp:(typeof XMLHttpRequest!="undefined")};zXml.ARR_XMLHTTP_VERS=["MSXML2.XmlHttp.5.0","MSXML2.XmlHttp.4.0","MSXML2.XmlHttp.3.0","MSXML2.XmlHttp","Microsoft.XmlHttp"];zXml.ARR_DOM_VERS=["MSXML2.DOMDocument.5.0","MSXML2.DOMDocument.4.0","MSXML2.DOMDocument.3.0","MSXML2.DOMDocument","Microsoft.XmlDom"];;function zXmlHttp(){}zXmlHttp.createRequest=function(){if(zXml.useXmlHttp){return new XMLHttpRequest();}else if(zXml.useActiveX){if(!zXml.XMLHTTP_VER){for(var i=0;i<zXml.ARR_XMLHTTP_VERS.length;i++){try{new ActiveXObject(zXml.ARR_XMLHTTP_VERS[i]);zXml.XMLHTTP_VER=zXml.ARR_XMLHTTP_VERS[i];break;}catch(oError){;}}}if(zXml.XMLHTTP_VER){return new ActiveXObject(zXml.XMLHTTP_VER);}else{throw new Error("Could not create XML HTTP Request.");}}else{throw new Error("Your browser doesn't support an XML HTTP Request.");}};zXmlHttp.isSupported=function(){return zXml.useXmlHttp||zXml.useActiveX;};function zXmlDom(){}zXmlDom.createDocument=function(){if(zXml.useDom){var oXmlDom=document.implementation.createDocument("","",null);oXmlDom.parseError={valueOf:function(){return this.errorCode;},toString:function(){return this.errorCode.toString()}};oXmlDom.__initError__();oXmlDom.addEventListener("load",function(){this.__checkForErrors__();this.__changeReadyState__(4);},false);return oXmlDom;;}else if(zXml.useActiveX){if(!zXml.DOM_VER){for(var i=0;i<zXml.ARR_DOM_VERS.length;i++){try{new ActiveXObject(zXml.ARR_DOM_VERS[i]);zXml.DOM_VER=zXml.ARR_DOM_VERS[i];break;}catch(oError){;}}}if(zXml.DOM_VER){return new ActiveXObject(zXml.DOM_VER);}else{throw new Error("Could not create XML DOM document.");}}else{throw new Error("Your browser doesn't support an XML DOM document.");}};zXmlDom.isSupported=function(){return zXml.useDom||zXml.useActiveX;};var oMozDocument=null;if(typeof XMLDocument!="undefined"){oMozDocument=XMLDocument;}else if(typeof Document!="undefined"){oMozDocument=Document;}if(oMozDocument&&!window.opera){oMozDocument.prototype.readyState=0;oMozDocument.prototype.onreadystatechange=null;oMozDocument.prototype.__changeReadyState__=function(iReadyState){this.readyState=iReadyState;if(typeof this.onreadystatechange=="function"){this.onreadystatechange();}};oMozDocument.prototype.__initError__=function(){this.parseError.errorCode=0;this.parseError.filepos=-1;this.parseError.line=-1;this.parseError.linepos=-1;this.parseError.reason=null;this.parseError.srcText=null;this.parseError.url=null;};oMozDocument.prototype.__checkForErrors__=function(){if(this.documentElement.tagName=="parsererror"){var reError=/>([\s\S]*?)Location:([\s\S]*?)Line Number(\d+),Column(\d+):<sourcetext>([\s\S]*?)(?:\-*\^)/;reError.test(this.xml);this.parseError.errorCode=-999999;this.parseError.reason=RegExp.$1;this.parseError.url=RegExp.$2;this.parseError.line=parseInt(RegExp.$3);this.parseError.linepos=parseInt(RegExp.$4);this.parseError.srcText=RegExp.$5;}};oMozDocument.prototype.loadXML=function(sXml){this.__initError__();this.__changeReadyState__(1);var oParser=new DOMParser();var oXmlDom=oParser.parseFromString(sXml,"text/xml");while(this.firstChild){this.removeChild(this.firstChild);}for(var i=0;i<oXmlDom.childNodes.length;i++){var oNewNode=this.importNode(oXmlDom.childNodes[i],true);this.appendChild(oNewNode);}this.__checkForErrors__();this.__changeReadyState__(4);};oMozDocument.prototype.__load__=oMozDocument.prototype.load;oMozDocument.prototype.load=function(sURL){this.__initError__();this.__changeReadyState__(1);this.__load__(sURL);};Node.prototype.__defineGetter__("xml",function(){var oSerializer=new XMLSerializer();return oSerializer.serializeToString(this,"text/xml");});Node.prototype.__defineGetter__("text",function(){var sText="";for(var i=0;i<this.childNodes.length;i++){if(this.childNodes[i].hasChildNodes()){sText+=this.childNodes[i].text;}else{sText+=this.childNodes[i].nodeValue;}}return sText;});}function zXslt(){}zXslt.transformToText=function(oXml,oXslt){if(typeof XSLTProcessor!="undefined"){var oProcessor=new XSLTProcessor();oProcessor.importStylesheet(oXslt);var oResultDom=oProcessor.transformToDocument(oXml);var sResult=oResultDom.xml;if(sResult.indexOf("<transformiix:result")>-1){sResult=sResult.substring(sResult.indexOf(">")+1,sResult.lastIndexOf("<"));}return sResult;;}else if(zXml.useActiveX){return oXml.transformNode(oXslt);}else{throw new Error("No XSLT engine found.");}};function zXPath(){}zXPath.selectNodes=function(oRefNode,sXPath,sXmlNs){if(typeof XPathEvaluator!="undefined"){oXmlNs=oXmlNs||{};var nsResolver=function(sPrefix){return oXmlNs[sPrefix];};var oEvaluator=new XPathEvaluator();var oResult=oEvaluator.evaluate(sXPath,oRefNode,nsResolver,XPathResult.ORDERED_NODE_ITERATOR_TYPE,null);var aNodes=new Array;if(oResult!=null){var oElement=oResult.iterateNext();while(oElement){aNodes.push(oElement);oElement=oResult.iterateNext();}}return aNodes;}else if(zXml.useActiveX){if(oXmlNs){var sXmlNs="";for(var sProp in oXmlNs){sXmlNs+="xmlns:"+sProp+"="+oXmlNs[sProp]+" ";}oRefNode.ownerDocument.setProperty("SelectionNamespaces",sXmlNs);};return oRefNode.selectNodes(sXPath);}else{throw new Error("No XPath engine found.");}};zXPath.selectSingleNode=function(oRefNode,sXPath,oXmlNs){if(typeof XPathEvaluator!="undefined"){;oXmlNs=oXmlNs||{};var nsResolver=function(sPrefix){return oXmlNs[sPrefix];};var oEvaluator=new XPathEvaluator();var oResult=oEvaluator.evaluate(sXPath,oRefNode,nsResolver,XPathResult.FIRST_ORDERED_NODE_TYPE,null);if(oResult!=null){return oResult.singleNodeValue;}else{return null;};}else if(zXML.useActiveX){if(oXmlNs){var sXmlNs="";for(var sProp in oXmlNs){sXmlNs+="xmlns:"+sProp+"="+oXmlNs[sProp]+" ";}oRefNode.ownerDocument.setProperty("SelectionNamespaces",sXmlNs);};return oRefNode.selectSingleNode(sXPath);}else{throw new Error("No XPath engine found.");}};function zXMLSerializer(){}zXMLSerializer.prototype.serializeToString=function(oNode){var sXml="";switch(oNode.nodeType){case 1:sXml="<"+oNode.tagName;for(var i=0;i<oNode.attributes.length;i++){sXml+=" "+oNode.attributes[i].name+"=\""+oNode.attributes[i].value+"\"";}sXml+=">";for(var i=0;i<oNode.childNodes.length;i++){sXml+=this.serializeToString(oNode.childNodes[i]);}sXml+="</"+oNode.tagName+">";break;case 3:sXml=oNode.nodeValue;break;case 4:sXml="<![CDATA["+oNode.nodeValue+"]]>";break;case 7:sXml="<?"+oNode.nodevalue+"?>";break;case 8:sXml="<!--"+oNode.nodevalue+"-->";break;case 9:for(var i=0;i<oNode.childNodes.length;i++){sXml+=this.serializeToString(oNode.childNodes[i]);}break;};return sXml;};

