/************************************************************************
 * typeahead.js
 *
 * AJAX type-ahead suggest box.  Needs to be used with net.js.  The
 * response format is specified in the book, AJAX in Action (First
 * Edition) on p. 369.
 *
 **
 * Log:
 * G. Krueger/BM		 9/06
 ************************************************************************/

/*======================================================================*/

/* Global Variables */
var arrOptions = new Array();
var strLastValue = "";
var bMadeRequest;
var theTextBox;
var objLastActive;
var currentValuesSelected = -1;
var bNoResults = false;
var isTiming = false;

/*======================================================================*/

/* Attaching the event handlers */
var isOpera = (navigator.userAgent.toLowerCase().indexOf("opera") != -1);

function AddHandler(objText)
{
    objText.onkeyup = GiveOptions;
    objText.onblur =
	function()
	{
	    if (this.obj.useTimeout) StartTimeout();
	}

    if (isOpera) objText.onkeypress = GiveOptions;
}

/*======================================================================*/

function SetProperties (
	xElem,              xHidden,             xRenderSpace,
	xserverCode,        xignoreCase,         xmatchAnywhere,
	xmatchTextBoxWidth, xshowNoMatchMessage, xnoMatchingDataMessage,
	xuseTimeout,        xtheVisibleTime
		       )
{
    var props =
	{
	    elem:		xElem,
	    hidden:		xHidden,
	    renderSpace:	xRenderSpace,
	    serverCode:		xserverCode,
	    regExFlags:		( (xignoreCase) ? "i" : "" ),
	    regExAny:		( (xmatchAnywhere) ? "" : "^" ),
	    matchAnywhere:	xmatchAnywhere,
	    matchTextBoxWidth:	xmatchTextBoxWidth,
	    theVisibleTime:	xtheVisibleTime,
	    showNoMatchMessage:	xshowNoMatchMessage,
	    noMatchingDataMessage:  xnoMatchingDataMessage,
	    useTimeout:		xuseTimeout
	};
    AddHandler(xElem);
    return props;
}

/*======================================================================*/

/* Detects the user's keypresses */
function GiveOptions(e)
{
    /* Detect the keypress */
    var intKey = -1;
    if ( window.event )
    {
	intKey = event.keyCode;
	theTextBox = event.srcElement;
    } else
    {
	intKey = e.which;
	theTextBox = e.target;
    }

    /* Reset the timer */
    if ( theTextBox.obj.useTimeout )
    {
	if (isTiming) EraseTimeout();
	StartTimeout();
    }

    /* Determine if the text exists */
    if ( theTextBox.value.length == 0 && ! isOpera )
    {
	arrOptions = new Array();
	HideTheBox();
	bNoResults = false;
	strLastValue = "";
	return false;
    }

    /* Determine function keys */
    if ( objLastActive == theTextBox )
    {
	if ( intKey == 13 )
	{
	    GrabHighlighted();
	    theTextBox.blur();
	    return false;
	} else if ( intKey == 38 )
	{
	    MoveHighlight(-1);
	    return false;
	} else if ( intKey == 40 )
	{
	    MoveHighlight(1);
	    return false;
	}
    }

    /* Handle keypress action */
    if ( objLastActive != theTextBox ||
	 theTextBox.value.indexOf(strLastValue) != 0 ||
	 (
	    (arrOptions.length == 0 || arrOptions.length == 15 ) &&
	    ! bNoResults
	 ) ||
	 (theTextBox.value.length <= strLastValue.length)
       )
    {
	objLastActive = theTextBox;
	bMadeRequest = true;
	TypeAhead(theTextBox.value);
    } else if ( ! bMadeRequest )
    {
	BuildList(theTextBox.value);
    }

    /* Save user input */
    strLastValue = theTextBox.value;
}

/*======================================================================*/

/* Ajax functionality to send data back to the server */
function TypeAhead(xStrText)
{
    var strParams =
	"q=" + xStrText + "&where=" + theTextBox.obj.matchAnywhere +
		"&name=" + theTextBox.name;

    var loader1 =
	new net.ContentLoader (
		theTextBox.obj.serverCode, BuildChoices, null, "POST",
		strParams
			      );
}

/*======================================================================*/

function handleError ( request )
/********************************************************************
 * handleError
 *
 * Calls specified error handler.
 *
 * this.component.handleError ( request )
 *
 * Input parameters:
 *	request		object		HTTP request object
 *
 * Output parameters:
 *		NONE
 **
 * Log:
 * G. Krueger/BM	 9/06
 ********************************************************************/
{
/*------------------------------------------------------------------*/
    if ( this.options.errorHandler )
	    this.options.errorHandler(request);
}

/*======================================================================*/

/* Transforming the responseText property to a JavaScript array */
function BuildChoices()
{
    var strText = this.req.responseText;
    eval(strText);
    BuildList(strLastValue);
    bMadeRequest = false;
}

/*======================================================================*/

/* Formatting the results into a displayable format */
function BuildList(theText)
{
    SetElementPosition(theTextBox);	/* Set the element position */
    var theMatches = MakeMatches(theText);	/* Format matches */
    theMatches = theMatches.join().replace(/\,/gi, "");

    if ( theText.length > 0 )
    {
	if ( theMatches.length > 0 )
	{
	    /* Show results */
	    document.getElementById("spanOutput").innerHTML = theMatches;
	    document.getElementById("OptionsList_0").className = 
		    "spanHighElement";
	    currentValueSelected = 0;
	    bNoResults = false;
	} else
	{
	    /* Show no matches */
	    currentValueSelected = -1;
	    bNoResults = true;
	    if ( theTextBox.obj.showNoMatchMessage )
		    document.getElementById("spanOutput").innerHTML =
			    "<span class='noMatchData'>" +
			    theTextBox.obj.noMatchingDataMessage +
			    "</span>";
	    else
		    HideTheBox();
	}
    } else
    {
	HideTheBox();
    }
}

/*======================================================================*/

/* Dynamically finding the position of a nonpositioned element */
function SetElementPosition (theTextBoxInt)
{
    var selectedPosX = 0;
    var selectedPosY = 0;
    var theElement = theTextBoxInt;
    if ( ! theElement ) return;
    var theElemHeight = theElement.offsetHeight;
    var theElemWidth = theElement.offsetWidth;

    while (theElement != null)
    {
	selectedPosX += theElement.offsetLeft;
	selectedPosY += theElement.offsetTop;
	theElement = theElement.offsetParent;
    }

    xPosElement = document.getElementById("spanOutput");
    xPosElement.style.left = selectedPosX + 'px';
    if (theTextBoxInt.obj.matchTextBoxWidth)
	xPosElement.style.width = theElemWidth;
    xPosElement.style.top = (selectedPosY + theElemHeight) + 'px';
    xPosElement.style.display = "block";

    if (theTextBoxInt.obj.useTimeout)
    {
	xPosElement.onmouseout = StartTimeout;
	xPosElement.onmouseover = EraseTimeout;
    } else
    {
	xPosElement.onmouseout = null;
	xPosElement.onmouseover = null;
    }
}

/*======================================================================*/

/* Using regular expressions to limit results */
var countForId = 0;
function MakeMatches ( xCompareStr )
{
    countForId = 0;
    var matchArray = new Array();
    var preppedCompareStr = xCompareStr;
    preppedCompareStr = preppedCompareStr.replace(/[^\w\s]/, '');
    var regExp =
	new RegExp ( theTextBox.obj.regExAny + preppedCompareStr,
		     theTextBox.obj.regExFlags
		   );

    for ( i = 0; i < arrOptions.length; i++ )
    {
	var theMatch = arrOptions[i][0].match(regExp);

	if ( theMatch )
	{
	    matchArray[matchArray.length] =
		CreateUnderline ( arrOptions[i][0], xCompareStr, i);
	}
    }

    return matchArray;
}

/*======================================================================*/

/* Performing String Manipulation */
var undeStart = "<span class='spanMatchText'>";
var undeEnd = "</span>";

var selectSpanStart =
	"<span style='width:100%;display;block;' " +
	"      class='spanNormalElement' " +
	"      onmouseover='SetHighColor(this)' ";
var selectSpanEnd = "<br></span>";

function CreateUnderline(xStr, xTextMatch, xVal)
{
    selectSpanMid =
	"onclick='SetText(" + xVal + ")' " +
	"id='OptionsList_" +
	countForId + "' theArrayNumber='" +
	xVal +
	"'>";
    var regExp =
	new RegExp (
		theTextBox.obj.regExAny + xTextMatch,
		theTextBox.obj.regExFlags
		   );
    var aStart = xStr.search(regExp);
    var matchedText =
	xStr.substring (aStart, aStart + xTextMatch.length);
    countForId++;
    return selectSpanStart +
	   selectSpanMid +
	   xStr.replace (regExp, undeStart + matchedText + undeEnd) +
	   selectSpanEnd;
}

/*======================================================================*/

/* Changing the CSS class names of elements */
function MoveHighlight (xDir)
{
    if ( currentValueSelected >= 0 )
    {
	newValue = parseInt (currentValueSelected) + parseInt (xDir);
	if ( newValue > -1 && newValue < countForId )
	{
	    currentValueSelected = newValue;
	    SetHighColor(null);
	}
    }
}

function SetHighColor(theTextBox)
{
    if (theTextBox)
    {
	currentValueSelected =
		theTextBox.id.slice (
			theTextBox.id.indexOf("_") + 1,
			theTextBox.id.length
				    );
    }

    for ( i = 0; i < countForId; i++ )
    {
	document.getElementById('OptionsList_' + i).className =
		'spanNormalElement';
    }

    document.getElementById('OptionsList_' + currentValueSelected).
	className = 'spanHighElement';
}

/*======================================================================*/

/* Handling the arrow keys and mouse click events */
function SetText (xVal)
{
    theTextBox.obj.renderSpace.innerHTML = arrOptions[xVal][0];	// render text value
    theTextBox.value = theTextBox.obj.renderSpace.innerHTML;	// set text value
    theTextBox.obj.hidden.value = arrOptions[xVal][1];
    document.getElementById("spanOutput").style.display = "none";
    currentValueSelected = -1;	// remove the selected index
}

function GrabHighlighted()
{
    if (currentValueSelected >= 0)
    {
	xVal = document.
		getElementById("OptionsList_" + currentValueSelected).
		getAttribute("theArrayNumber");
	SetText(xVal);
	HideTheBox();
    }
}

function HideTheBox()
{
    document.getElementById("spanOutput").style.display = "none";
    currentValueSelected = -1;
    EraseTimeout();
}

/*======================================================================*/

/* Attaching and removing timing events */
function EraseTimeout()
{
    clearTimeout (isTiming);
    isTiming = false;
}

function StartTimeout()
{
    isTiming =
	setTimeout("HideTheBox()", theTextBox.obj.theVisibleTime);
}

/*======================================================================*/

