/*
////////////////////////////////////////////////////////////
Module:     Autocomplete
Desc:       Provides the functionality to turn a normal 
            dropdown list box into a pseudo-autocomplete box

REVISION HISTORY
1.00            Kevin Werner            Initial development.


Copyright (c) 2001 Kevin Werner. All rights reserved.
////////////////////////////////////////////////////////////
*/

var DEBUG_MODE = false;

var KEY_PRESS = "keypress";
var BLUR = "blur";
var KEY_DOWN = "keydown";
var BACKSPACE_KEY = 8;
var TAB_KEY = 9;
var ESCAPE_KEY = 27;
var ENTER_KEY = 13;
var TIMEOUT = 1000;                     // milliseconds
var MAX_BUFFER_LENGTH = 20;
var EXTRA_WIDTH = 10;
var FONT_FAMILY = "Courier New";
var FONT_SIZE = "10pt";
var DUPLICATE_LIMIT = 2;
var DUPLICATE_BUFFER = 3;               // The number of characters after which we don't care if a user starts typing characters repeatedly

var g_strBuffer = "";
var g_strDuplicateBuffer = "";

var g_tmrBuffer;
var g_tmrDiv;

var g_objAc;

var g_intDuplicateCnt = 0;
var g_intSelectedIndex = -1;
var g_intStartIndex = 0;

g_tmrDiv = setTimeout("CreateDiv()", 500);

////////////////////////////////////////////////////////////////////
// Proc:    Gateway
// Desc:    Determines whether the object should be allowed to 
//          use autocomplete functionality.
//
// REVISION HISTORY
// 11/06/2001           Kevin Werner            Initial development.
////////////////////////////////////////////////////////////////////
function Gateway(arg_objElt, arg_strEvent)
{
    var blnContinue = false;
    
    if ("SELECT" == arg_objElt.tagName)
    {
        if (0 == arg_objElt.size)
        {
            blnContinue = true;
        }
    }
    
    if (blnContinue)
    {
        switch (arg_strEvent)
        {
            case KEY_PRESS :
                MyOnKeyPress(arg_objElt);
                break;
            case BLUR :
                MyOnBlur(arg_objElt);
                break;
            case KEY_DOWN :
                MyOnKeyDown(arg_objElt);
                break;
        }
    }
}

////////////////////////////////////////////////////////////////////
// Proc:    MyOnKeyPress
// Desc:    Handles the onKeyPress event.
//
// REVISION HISTORY
// 11/06/2001           Kevin Werner            Initial development.
////////////////////////////////////////////////////////////////////
function MyOnKeyPress(arg_objCombo)
{
    //
    // DECLARATIONS
    //
    var this_intKeyCode;
    var this_i;
    
    var this_strChar;
    var this_strMsg;
    
    var this_blnFoundMatch = false;
    var this_blnFoundDuplicate = false;
    var this_blnReturn = true;
    
    //
    // BODY
    //
    if (DEBUG_MODE)
    {
        window.status = "KeyPress: " + event.keyCode;
    }
    
    clearTimeout(g_tmrBuffer);
    
    this_intKeyCode = event.keyCode;
    
    switch (this_intKeyCode)
    {
        case TAB_KEY :
            break;
        case ENTER_KEY :
        case ESCAPE_KEY :
            ClearBuffer(arg_objCombo);
            break;
        default :
            this_strChar = String.fromCharCode(this_intKeyCode);
            
            this_blnFoundMatch = CheckForMatch(arg_objCombo, this_strChar)
            
            if (this_blnFoundMatch)
            {
                ToggleDiv(true, arg_objCombo);

                g_strBuffer += this_strChar;
                g_objAc.innerText = g_strBuffer;
                arg_objCombo.selectedIndex = g_intSelectedIndex;
                this_blnReturn = false;

                if (DEBUG_MODE)
                {
                    // window.status = g_strBuffer;
                }
            }
            else
            {
                this_blnFoundDuplicate = CheckForDuplication(this_strChar);

                if (this_blnFoundDuplicate)
                {
                    this_strMsg = "This is an autocomplete list box. Instead of typing a letter repeatedly \n";
                    this_strMsg += "to loop through all elements that start with that letter, you can \n";
                    this_strMsg += "simply type the word as normal.";
                    alert(this_strMsg);
                }
                else
                {
                    this_blnReturn = false;
                }
            }
            
            break;
    }
    
    g_tmrBuffer = setTimeout("ClearBuffer('" + arg_objCombo + "')", TIMEOUT);
    event.returnValue = this_blnReturn;
}

////////////////////////////////////////////////////////////////////
// Proc:    MyOnBlur
// Desc:    Handles the onBlur event.
//
// REVISION HISTORY
// 11/06/2001           Kevin Werner            Initial development.
////////////////////////////////////////////////////////////////////
function MyOnBlur(arg_objCombo)
{
    ClearBuffer(arg_objCombo)
}

////////////////////////////////////////////////////////////////////
// Proc:    MyOnKeyDown
// Desc:    Handles the onKeyDown, which is necessary for handling 
//          the backspace key.
//
// REVISION HISTORY
// 11/06/2001           Kevin Werner            Initial development.
////////////////////////////////////////////////////////////////////
function MyOnKeyDown(arg_objCombo)
{
    var this_intKeyCode;
    
    var this_blnFoundMatch = false;
    var this_blnReturn = true;

    clearTimeout(g_tmrBuffer);
    
    this_intKeyCode = event.keyCode;
    
    if (DEBUG_MODE)
    {
        window.status = "KeyDown: " + event.keyCode;
    }
    
    switch(this_intKeyCode)
    {
        case BACKSPACE_KEY :
            this_blnFoundMatch = CheckForMatch(arg_objCombo, "");
            //
            // Pretty much guaranteed to find a match
            //
            g_strBuffer = g_strBuffer.substr(0, g_strBuffer.length - 1);
            g_objAc.innerText = g_strBuffer;
            arg_objCombo.selectedIndex = g_intSelectedIndex;
            this_blnReturn = false;
            break;
        case TAB_KEY :
            ClearBuffer(arg_objCombo);
        default :
    }
    
    g_tmrBuffer = setTimeout("ClearBuffer('" + arg_objCombo + "')", TIMEOUT);
    event.returnValue = this_blnReturn;
}

////////////////////////////////////////////////////////////////////
// Proc:    ClearBuffer
// Desc:    Clears the text buffer and hides the 
//          autocomplete DIV
//
// REVISION HISTORY
// 11/06/2001           Kevin Werner            Initial development.
////////////////////////////////////////////////////////////////////
function ClearBuffer(arg_objCombo)
{
    clearTimeout(g_tmrBuffer);
    g_strBuffer = "";
    g_strDuplicateBuffer = "";
    g_intDuplicateCnt = 0;
    g_objAc.innerText = g_strBuffer;
    g_intStartIndex = 0;
    ToggleDiv(false, arg_objCombo);
}

////////////////////////////////////////////////////////////////////
// Proc:    CheckForMatch
// Desc:    Checks for the combo box for a match.
//
// REVISION HISTORY
// 11/06/2001           Kevin Werner            Initial development.
////////////////////////////////////////////////////////////////////
function CheckForMatch(arg_objCombo, arg_strNewChar)
{
    var this_intLength = arg_objCombo.length;
    var this_i;
    // var this_intLoVal;
    // var this_intHiVal;
    // var this_intMidVal;
    
    var this_strUcaseBuffer;
    var this_strUcaseOption;
    // var this_strMidVal;
    
    // var this_blnFoundMatch = false;
    // var this_blnFinished = false;
    
    //
    // An empty string is passed in as an 
    // indicator that the user pressed the 
    // backspace key
    //
    if ("" == arg_strNewChar)
    {
        g_intStartIndex = 0;
        this_strUcaseBuffer = (g_strBuffer.substr(0, g_strBuffer.length - 1)).toUpperCase();
    }
    else
    {
        this_strUcaseBuffer = (g_strBuffer + arg_strNewChar).toUpperCase();
    }
    
    //
    // If there aren't any characters 
    // in the buffer, (meaning the user has pressed 
    // the backspace key until there are no 
    // more characters in the buffer), set the index 
    // to 0 and exit the function.
    // 
    if (0 == this_strUcaseBuffer.length)
    {
        g_intStartIndex = 0;
        g_intSelectedIndex = 0;
        return true;
    }
    else
    {
    
        /*
        //
        // RESERVED FOR POSSIBLE FUTURE USE
        //
        
        this_intLoVal = 0;
        this_intHiVal = this_intLength;
    
        while (false == this_blnFoundMatch && false == this_blnFinished)
        {
            this_intMidVal = (this_intLoVal + this_intHiVal) / 2;
            
            if (this_intMidVal == this_intLoVal || this_intMidVal == this_intHiVal)
            {
                this_blnFinished = true;
                continue;
            }
            
            this_strMidVal = arg_objCombo.options[this_intMidVal].text;
            this_strUcaseOption = this_strMidVal.toUpperCase();
            if (this_strUcaseBuffer == this_strUcaseOption.substr(0, this_strUcaseBuffer.length))
            {
                this_blnFoundMatch = true;
                g_intSelectedIndex = this_intMidVal;
            }
            else
            {
                if (g_strBuffer < this_strMidVal)
                {
                    this_intHiVal = this_intMidVal;
                }
                else
                {
                    this_intLoVal = this_intMidVal;
                }
            }
        }
        */  // END RESERVED CODE
                
        for (this_i = g_intStartIndex; this_i < this_intLength; this_i++)
        {
            this_strUcaseOption = (arg_objCombo.options[this_i].text).toUpperCase();
            if (this_strUcaseBuffer == this_strUcaseOption.substr(0, this_strUcaseBuffer.length))
            {
                g_intStartIndex = this_i;
                g_intSelectedIndex = this_i;
                
                if (DEBUG_MODE)
                {
                    window.status = this_strUcaseBuffer + ", " + g_intSelectedIndex;
                }
                
                return true;
            }
        }
    }
    
    return false;
    //
    // return this_blnFoundMatch;
    //
}

////////////////////////////////////////////////////////////////////
// Proc:    CheckForDuplication
// Desc:    Detects whether the user may be trying to treat the 
//          combo box like a normal list box.
//
// REVISION HISTORY
// 11/06/2001           Kevin Werner            Initial development.
////////////////////////////////////////////////////////////////////
function CheckForDuplication(arg_strNewChar)
{
    var this_strChar;
    var this_strCharNext;

    var this_i;
    var this_intDuplicateLength;
    
    var this_blnFoundDuplicate = false;

    g_strDuplicateBuffer += arg_strNewChar;
    this_intDuplicateLength = g_strDuplicateBuffer.length;

    if (DEBUG_MODE)
    {
        g_objAc.innerText = "Duplicate: " + g_strDuplicateBuffer;
    }
    
    if (this_intDuplicateLength + g_strBuffer.length > DUPLICATE_BUFFER)
    {
        //
        // If the user has typed more characters than 
        // DUPLICATE_BUFFER, we don't need to test
        // for duplicates
        //
        return false;
    }
    else
    {
        if (DEBUG_MODE)
        {
            window.status = g_strDuplicateBuffer;
        }
        
        if (1 == g_strDuplicateBuffer.length)
        {
            //
            // If there's just one character in the duplicate 
            // buffer, let's see if that character matches
            // what's in the autocomplete buffer
            //
            if (g_strDuplicateBuffer.toUpperCase() == g_strBuffer.toUpperCase())
            {
                g_intDuplicateCnt++;
            }
            return false;
        }
        
        //
        // Loop through the characters in the duplicate 
        // buffer to see if they are all the same.
        // Basically, we'll only make it to this point 
        // if there are 2 or more characters in the duplicate 
        // buffer, and as many or fewer than DUPLICATE_BUFFER.
        // (1 < duplicate buffer length <= DUPLICATE_BUFFER)
        //
        for (this_i = 0; this_i < this_intDuplicateLength; this_i++)
        {
            this_strChar = g_strDuplicateBuffer.substr(this_i, 1).toUpperCase();
            this_strCharNext = g_strDuplicateBuffer.substr(this_i + 1, 1).toUpperCase();
            
            if (this_strChar == this_strCharNext)
            {
                g_intDuplicateCnt++;
            }
            
            if (DUPLICATE_LIMIT == g_intDuplicateCnt)
            {
                this_blnFoundDuplicate = true;
                break;
            }
        }
    }
    
    return this_blnFoundDuplicate;
}

////////////////////////////////////////////////////////////////////
// Proc:    CreateDiv
// Desc:    Creates the autocomplete DIV
//
// REVISION HISTORY
// 11/06/2001           Kevin Werner            Initial development.
////////////////////////////////////////////////////////////////////
function CreateDiv()
{
    //
    // Make sure the page is loaded before continuing!
    //
    if (!document.body)
    {
        g_tmrDiv = setTimeout("CreateDiv()", 500);
        return;
    }

    g_objAc = document.createElement("div");
    
    with (g_objAc.style)
    {
        posWidth = 200;
        posHeight = 20;
        backgroundColor = "white";
        fontFamily = FONT_FAMILY;
        fontSize = FONT_SIZE;
        border = "1px outset #efefef";
        paddingLeft = 5;
        position = "absolute";
        
        if (!DEBUG_MODE)
        {
            visibility = "hidden";
        }
    }

    document.body.appendChild(g_objAc);
}

////////////////////////////////////////////////////////////////////
// Proc:    ToggleDiv
// Desc:    Shows or hides the autocomplete DIV. Shows or hides any
//          any other <SELECT> object on the page.
//
// REVISION HISTORY
// 11/06/2001           Kevin Werner            Initial development.
////////////////////////////////////////////////////////////////////
function ToggleDiv(arg_blnShow, arg_objCombo)
{
    var this_strDivVisibility = (true == arg_blnShow)?"visible":"hidden";
    var this_strComboVisibility = (true == arg_blnShow)?"hidden":"visible";
    
    var this_intDocumentLength = document.all.length;
    var this_i;

    if (arg_blnShow && "visible" == g_objAc.style.visibility)
    {
        //
        // no need to step through this function
        //
        return;
    }
 
    if ("visible" == this_strDivVisibility)
    {
        PositionDiv(arg_objCombo);
    }
    
    g_objAc.style.visibility = this_strDivVisibility;
    
    for (this_i = 0; this_i < this_intDocumentLength; this_i++)
    {
        if ("SELECT" == document.all[this_i].tagName && document.all[this_i].name != arg_objCombo.name)
        {
            document.all[this_i].style.visibility = this_strComboVisibility;
        }
    }
}

////////////////////////////////////////////////////////////////////
// Proc:    PositionDiv
// Desc:    Positions the autocomplete div so it is either above or below
//          the associated combo box.
//
// REVISION HISTORY
// 11/07/2001           Kevin Werner            Initial development.
////////////////////////////////////////////////////////////////////
function PositionDiv(arg_objCombo)
{
    var this_intTop;
    var this_intLeft;
    var this_intScrollTop;
    
    var this_objParent;
    
    this_intTop = arg_objCombo.style.posTop + arg_objCombo.offsetTop;
    this_intLeft = arg_objCombo.style.posLeft + arg_objCombo.offsetLeft;
    
    this_intScrollTop = document.body.scrollTop;
    
    //
    // Crawl up the document structure to determine 
    // the true left and top of the combo
    //
    this_objParent = arg_objCombo.offsetParent;

    while ("BODY" != this_objParent.tagName)
    {
        this_intTop += this_objParent.offsetTop;
        this_intLeft += this_objParent.offsetLeft;
        
        this_objParent = this_objParent.offsetParent;
    }
    
    //
    // By default, position the DIV above the combo box
    //
    this_intTop -= 25;
     
    //
    // Check to make sure the DIV won't be off the screen
    //
    if (this_intTop <= this_intScrollTop)
    {
        this_intTop += 50;
    }
    
    g_objAc.style.posTop = this_intTop;
    g_objAc.style.posLeft = this_intLeft;
}
