/*** Script by Stefano N. Roncari - www.eggsist.com ***/
/***             v.2.0 [object-based]               ***/

function altMenu4Field(campo,sugg4what,theUrl)
{
	this.lastQueriedValue=""; // Servira' per decidere se fare o meno una nuova Query al DB.
	this.suggArray=""; // Array in cui verranno salvati i record tornati dal server
	this.optionSelectedNum=""; // ID dell'opzione attualmente selezionata

	/*** 1. Anzitutto si assegnano gli Event Handlers al campo indicato, per gestire il feedback alle azioni dell'utente ***/
	this.myField = campo;
	this.myField.actionUrl = theUrl;
	this.myField.parent=this;
	this.myField.sugg4what=sugg4what;
		this.myField.setEventHandlers = setEventHandlers;
			this.myField.setEventHandlers();
		this.myField.displaySuggestions = displaySuggestions;
		this.myField.feedback2user = feedback2user;
		this.myField.getSuggestions = getSuggestions;
		this.myField.makeSuggArray = makeSuggArray;
		this.myField.blockCursor = blockCursor;
		this.myField.findPos = findPos;
		this.myField.manageKeybSelection = manageKeybSelection;

	/*** 2. Poi, si crea l'oggetto DIV volto a contenere i suggerimenti di input ***/
	this.altMenu = document.createElement("div");
	this.altMenu.id = this.myField.id +"_menu";
	this.altMenu.className = "alternativeMenus";
	this.altMenu.parent=this;
		this.altMenu.hideMenu = hideMenu;
		this.altMenu.setMenuPosSize = setMenuPosSize;

	/*** 3. Poi, si crea l'oggetto Iframe che costringe i campi "Select" e altri Iframe a stare in secondo piano ***/
	this.altIframe = document.createElement("iframe");
	this.altIframe.className = "alternativeIframes";
	this.altIframe.style.filter = "alpha(opacity=0)";
	this.altIframe.style.MozOpacity = 0;
	this.altIframe.style.opacity = 0;
	this.altIframe.parent=this;
		this.altIframe.setMenuPosSize = setMenuPosSize;

	/*** Ora si aggiungono all'HTML gli oggetti creati ***/
	document.body.appendChild(this.altMenu);
	document.body.appendChild(this.altIframe);

	/*** 5. Infine, si utilizzano le proprieta' CSS per dimensionare il DIV e posizionarlo sotto al relativo campo del form ***/
	this.altMenu.setMenuPosSize();
	this.altIframe.setMenuPosSize();
}

function setEventHandlers()
{
	fieldObj = this;
	/** THIS e' l'Input Field **/
	addEvent(fieldObj,'click',feedback2user);
	addEvent(fieldObj,'keydown',blockCursor); // Serve a evitare che il cursore si muova avanti e indietro coi tasti UP e DOWN. Con onkeypress non funziona! onkeypress rileva qualsiasi tasto come keyCode=0!
	addEvent(fieldObj,'keyup',feedback2user); if (navigator.userAgent.toLowerCase().indexOf("opera")!= -1) {addEvent(this,'keypress',feedback2user);} // Opera non usa l'onKeyUp!
	addEvent(fieldObj,'blur',hideMenu);
}

function setMenuPosSize()
{
	/** THIS e' l'oggetto "altMenu" **/
	this.parent.myField.findPos();
	this.style.left = this.parent.myField.posLeft + "px";
	this.style.top = this.parent.myField.posTop + this.parent.myField.offsetHeight + "px";
	this.style.width = this.parent.myField.offsetWidth + "px";
}

function blockCursor(e)
{
	/*** Questa funzione serve ad evitare che i tasti UP e DOWN muovano il cursore avanti e indietro, nel FIELD compilato dall'utente ***/
	var myKey = -1;
	if (window.event) // IE
	{
		myKey = event.keyCode;
		functionName = "event.returnValue;"; // Proprieta' dell'oggetto EVENT che IE usa per bloccare la risposta di default all'azione dell'utente
	}
	else // Moz & Co.
	{
		myKey = e.which;
		functionName = "e.preventDefault()"; // Funzione di MOZ e altri per bloccare la risposta di default all'azione dell'utente
	}
	//alert(myKey);
	if (myKey==38 || myKey==40) { eval(functionName); }
}

function feedback2user(e)
{
	/** THIS e' l'Input Field **/

	/*** Questa e' la funzione principale, che fa essenzialmente 3 cose:
	1) Rileva l'input dell'utente nel campo di testo e capisce di quale campo si tratta;
	2) Lancia lo script per interagire col server in relazione all'input dell'utente;
	3) Lancia lo script per gestire la selezione delle diverse alternative che vengono tornate dal server ***/
	
	/*   keyCode 1 = mouseClick	||
	||   keyCode 13 = Enter			||
	||   keyCode 38 = UP arrow		||
	||   keyCode 40 = DOWN arrow  */
	var intKey = -1;
	if (window.event) // IE
	{
		intKey = event.keyCode;
	}
	else // Moz etc.
	{
		intKey = e.which;
	}
	//alert(intKey);

	if ((intKey<38 || intKey>40) && intKey!=13) // Se si preme qualcosa di diverso dalle frecce e dall'INVIO
	{
		this.parent.optionSelectedNum = -1; // Questo servira' in seguito, per capire quale opzione andra' evidenziata. Inizialmente, nessuna opzione e' evidenziata!
		LQValueCaseInsensitive = eval("/" +this.parent.lastQueriedValue+ "/i");

		if ( this.value!="" && (this.parent.lastQueriedValue=="" || this.value.search(LQValueCaseInsensitive)==-1) ) // Si invia la Query al DB solo se la stringa cercata NON contiene il carattere cercato in precedenza.
		{
			valueToSubmit = this.value.substring(0,1); // La query viene fatta utilizzando solo il primo carattere della stringa, anche qualora l'utente ne immetta una piu' lunga (es. con copy/paste). Cosi' si ottengono piu' risultati con un'unica Query!
			this.getSuggestions(valueToSubmit); // Fai una nuova Query al DB!
			this.parent.lastQueriedValue = valueToSubmit;
		}
		else if (this.value!="")
		{
			this.displaySuggestions(); // "displaySuggestions()" utilizzera' l'Array "suggArray", creato in precedenza.
		}
		else {this.parent.altMenu.hideMenu();}
	}

	this.manageKeybSelection(intKey);
}

function getSuggestions(valueToSubmit)
{
	/** THIS e' l'Input Field **/
	/*** Questa funzione si limita a fare la POST dei dati inseriti dall'utente, e a ricevere la risposta del server. Poi chiama lo script che gestisce la risposta del server. ***/
	myInputField = this;
	req = null;
	myURL = myInputField.actionUrl +'?subject=' +this.sugg4what;
	myString = myInputField.id +"="+ valueToSubmit;

	//this.altMenu.innerHTML="Submit effettuato. Attendo feedback dal server...";

	if (window.XMLHttpRequest) // Mozilla, Safari, ...
	{
		req = new XMLHttpRequest();
		if (req.overrideMimeType) { req.overrideMimeType('text/xml'); }
	}
	else if (window.ActiveXObject) // IE
	{
		try { req = new ActiveXObject("Msxml2.XMLHTTP"); } // Vers. 5.5 o inferiore
		catch (e)
		{
			try { req = new ActiveXObject("Microsoft.XMLHTTP"); } // Vers. 5.5 o superiore
			catch (e) {}
		}
	}

	if (!req) {
	   alert('Giving up :( Cannot create an XMLHTTP instance');
	   return false;
	}

	req.open("POST", myURL, true); // 1) POST or GET;  2) URL of the script to execute;  3) true for asynchronous (false for synchronous).
	req.setRequestHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
	req.send(myString); // Send data to the server. Data comes from "mydata" variable filled through the form by the user.

	req.onreadystatechange = function()
	{
		/** Ora THIS e' questa funzione! **/
		if (req.readyState == 4) // The 4 state means for the response is ready and sent by the server.
		{
			if (req.status == 200) // This status means "OK", otherwise some error code is returned, 404 for example.
			{
				//alert("Pagina trovata..."); myField.altMenu.innerHTML = "<font color='green'>Submit effettuata</font>.";

				/*** Following are the actions to be performed with the server response ***/
				respText = req.responseText;
				respXML = req.responseXML;
				//alert(respText);

				/** Sometimes (not always!) IE can't interpret the XML document produced by PHP. In that case, we have to re-build it from a String! **/
				if (respXML.childNodes.length!=0) { myInputField.makeSuggArray(respXML); }
				else
				{
					xmlDocument = new ActiveXObject('Microsoft.XMLDOM');
					xmlDocument.loadXML(respText); // Create an XML document starting from the given String ;-)
					myInputField.makeSuggArray(xmlDocument);
				}
			}
			else
			{
				//alert("La pagina che processa il form non c'e'!");
				myInputField.parent.altMenu.innerHTML="<font color='red'>Errore nell'invio dei dati al server!</font><br/>Codice: " + req.status + " " + req.statusText; // statusText è una proprietà dell'oggetto XMLHttpRequest che contiene il messaggio di errore.
			}
		}
	}
}

function makeSuggArray(rispServer) // "rispServer" e' di fatto una pagina XML
{
	myRoot = (rispServer.firstChild.nodeName == "xml") ? rispServer.childNodes[1] : rispServer.childNodes[0];  //alert("myRoot= "+myRoot.nodeName);
	/** THIS e' l'Input Field **/
	/*** Anzitutto, ricreiamo l'array ex-novo, ogni volta che interroghiamo il DB ***/
	this.parent.suggArray = new Array();

	for (a=0; a<myRoot.childNodes.length; a++) // firstChild è il nodo "padre" <dati></dati>
	{
		if (myRoot.childNodes[a].tagName == "item")
		{
			contTag = myRoot.childNodes[a].firstChild.data;
			this.parent.suggArray.push(contTag); // Aggiungiamo l'elemento all'Array dei suggerimenti
		}
	}
	this.displaySuggestions();
}

function displaySuggestions()
{
	/** THIS e' l'Input Field **/

	/*** Cominciamo riposizionando il DIV (puo' essere che l'utente abbia ridimensionato la finestra dopo l'onLoad!) con le alternative e azzerando il suo contenuto, in modo che si aggiorni completamente ogni volta che viene digitato un carattere ***/
	this.parent.altMenu.setMenuPosSize();
	this.parent.altMenu.innerHTML = "";

	optionCounter = 0;
	for (s=0; s<this.parent.suggArray.length; s++)
	{
		inputValueCaseInsensitive = eval("/" +this.value+ "/i");
		if (this.parent.suggArray[s].search(inputValueCaseInsensitive)!=-1 && this.parent.suggArray[s]!=this.value) // Vengono mostrate solo le alternative che matchano l'input dell'utente, E DIVERSE dal valore gia' digitato dall'utente!
		{
			contTagRegExp = underlineMatching(this,this.parent.suggArray[s]);

			/*** Creiamo l'oggetto SPAN, con i suoi attributi e i suoi metodi ***/
			spanID = this.id+ '_opt' +optionCounter;
			tempSpan = document.createElement("span");
			tempSpan.id = spanID;
			tempSpan.className = "altOptionOFF";
				tempSpan.setFieldValue = setFieldValue;
				tempSpan.refreshHighlight = refreshHighlight;

			addEvent(tempSpan,'mouseover',refreshHighlight);
			addEvent(tempSpan,'mousedown',setFieldValue);

			/*** Infine, inseriamo nell'HTML l'oggetto creato ***/
			this.parent.altMenu.appendChild(tempSpan);
			tempSpan.innerHTML=contTagRegExp;

			optionCounter++;
		}
	}

	if (optionCounter!=0)
	{
		this.parent.altMenu.style.display="block"; // Show DIV with suggestions
		this.parent.altIframe.style.height = this.parent.altMenu.offsetHeight + "px"; // Show support Iframe
		this.parent.altIframe.style.display="block";
	}
}

function underlineMatching(theField,resultString)
{
	contTagRegExp=resultString;
	myMatches=0;

	myRegExp = new RegExp(theField.value,"gi");
	myMatches = contTagRegExp.match(myRegExp); // Torna un array con tutte le stringe trovate
	if (myMatches) // La versione 1.1 (con caching) necessita di questo controllo aggiuntivo, perche' non e' detto che ogni elemento dell'array matchi la stringa corrente
	{
		for (p=0; p<myMatches.length; p++)
		{
			tempRegExp = new RegExp(eval("/" +myMatches[p]+ "(?!<)(?!>)/")); // Senza "g" (perche' le altre occorrenze le becca al prossimo giro del LOOP "for") e senza "i" - case insensitiveness
			contTagRegExp = contTagRegExp.replace(tempRegExp,"<b>"+myMatches[p]+"</b>");
			//alert(contTagRegExp);
		}
		return contTagRegExp;
	}
	else
	{
		return false;
	}
}

function setFieldValue()
{
	/** THIS e' l'oggetto SPAN **/
	/*** Questa funzione e' chiamata sia sull'onMouseClick, sia quando si preme ENTER ***/
	theAncestor = this.parentNode.parent; // this.parentNode e' l'oggetto DIV e, il suo parent, e' l'oggetto radice

	/*** Anzitutto, assegna al FIELD il valore selezionato, ripulito dai codici HTML utilizzati per evidenziare il match in bold underlined. ***/
	theAncestor.myField.value = delHTMLFromString(this.innerHTML);
	/*** Poi, nascondi il menu con le alternative ***/
	theAncestor.altMenu.hideMenu();
}

function manageKeybSelection(intKey)
{
	/** THIS e' l'Input Field **/
	/*** Questa funzione gestisce la selezione delle diverse alternative da parte dell'utente, TRAMITE TASTIERA (tasti UP/DOWN) ***/
	if (intKey==13 && this.parent.optionSelectedNum>-1) // Se l'utente preme INVIO. La 2^ condizione e' per evitare che la funzione sia chiamata quando l'utente preme invio senza aver selezionato scelta
	{
		/*** Si valorizza il campo col contenuto dello SPAN selezionato ***/
		selSpanID = this.id+ '_opt' +this.parent.optionSelectedNum;
		selSpan = document.getElementById(selSpanID);
		selSpan.setFieldValue();
	}
	else if (intKey==38 || intKey==40)
	{
		/*** 1. Definiamo la "selDirection" ***/
		if ( intKey==38 && this.parent.optionSelectedNum>0 ) {selDirection = -1;}
		else if ( intKey==40 && this.parent.optionSelectedNum<(this.parent.altMenu.childNodes.length-1) ) {selDirection = 1;}
		else {selDirection=0; /*alert("di qui non mi muovo!");*/ }
		
		/*** 2. Definiamo il nuovo span da selezionare ***/
		this.parent.optionSelectedNum += selDirection;

		//alert(this.parent.optionSelectedNum);
		selSpanID = this.id+ '_opt' +this.parent.optionSelectedNum;
		selSpan = document.getElementById(selSpanID);
		selSpan.refreshHighlight();
	}
}

function refreshHighlight()
{
	/** THIS e' l'oggetto "span" **/

	theMenu = this.parentNode;
	/*** Questa funzione e' chiamata sia dall'onMouseOver su ogni SPAN, sia dai tasti UP/DOWN. Cicla su tutti gli SPAN del DIV del menu, e attiva l'opzione corrispondente allo SPAN che chiama questo metodo! ***/
	for (k=0; k<theMenu.childNodes.length; k++)
	{
		tempChild = theMenu.childNodes[k];
		if (tempChild.nodeName == "SPAN")
		{
			if (tempChild == this) {tempChild.className="altOptionON";}
			else {tempChild.className="altOptionOFF";}
		}
	}
}

function hideMenu()
{
	/** THIS e' l'Input Field **/
	theMenu = this.parent.altMenu;
	theIframe = this.parent.altIframe;
	/*** Questa funzione e' chiamata sia quando si seleziona l'opzione di interesse, sia sull'onBlur del campo ***/
	// Anzitutto si nasconde il DIV; poi, si azzera la selezione delle opzioni (theMenu.parent.optionSelectedNum=-1)
	// NB: Le azioni sono ritardate di 150ms per dare il tempo al browser di assegnare al campo il valore selezionato, prima che il tasto si rialzi (le funzioni sono chiamate sull'onKeyUp)!
	menuHideTimeout = setTimeout('theMenu.style.display="none"; theIframe.style.display="none"; theMenu.parent.optionSelectedNum=-1;',150);
}

function delHTMLFromString(myString)
{
	/*** Questa regExp elimina tutti i tag HTML inseriti nello SPAN (per evidenziare il match in grassetto sottolineato) ***/
	regX = /<[^>]*?>/g; // this looks for "<", Then followed by any character that isn't ">", Then the final ">"
	myCleanString = myString.replace(regX,"");
	return myCleanString;
}
