/*******************************************************

AutoSuggest - a javascript automatic text input completion component
Copyright (C) 2005 Joe Kepley, The Sling & Rock Design Group, Inc.

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.

This library 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
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

*******************************************************

Please send any useful modifications or improvements via 
email to joekepley at yahoo (dot) com

*******************************************************/

/********************************************************
 The AutoSuggest class binds to a text input field
 and creates an automatic suggestion dropdown in the style
 of the "IntelliSense" and "AutoComplete" features of some
 desktop apps. 
 Parameters: 
 elem: A DOM element for an INPUT TYPE="text" form field
 suggestions: an array of strings to be used as suggestions
              when someone's typing.

 Example usage: 
 
 Please enter the name of a fruit.
 <input type="text" id="fruit" name="fruit" />
 <script language="Javascript">
 var fruits=new Array("apple","orange","grape","kiwi","cumquat","banana");
 new AutoSuggest(document.getElementById("fruit",fruits));
 </script>

 Requirements: 

 Unfortunately the AutoSuggest class doesn't seem to work 
 well with dynamically-created DIVs. So, somewhere in your 
 HTML, you'll need to add this: 
 <div id="autosuggest"><ul></ul></div>

 Here's a default set of style rules that you'll also want to 
 add to your CSS: 

 .suggestion_list
 {
 background: white;
 border: 1px solid;
 padding: 4px;
 }

 .suggestion_list ul
 {
 padding: 0;
 margin: 0;
 list-style-type: none;
 }

 .suggestion_list a
 {
 text-decoration: none;
 color: navy;
 }

 .suggestion_list .selected
 {
 background: navy;
 color: white;
 }

 .suggestion_list .selected a
 {
 color: white;
 }

 #autosuggest
 {
 display: none;
 }
*********************************************************/
function AutoSuggest(elem, suggestions)
{

    //The 'me' variable allow you to access the AutoSuggest object
    //from the elem's event handlers defined below.
    var me = this;

    //A reference to the element we're binding the list to.
    this.elem = elem;

    this.suggestions = suggestions;

    //Arrow to store a subset of eligible suggestions that match the user's input
    this.eligible = new Array();

    //The text input by the user.
    this.inputText = null;

    //A pointer to the index of the highlighted eligible item. -1 means nothing highlighted.
    this.highlighted = -1;

    //A div to use to create the dropdown.
    this.div = document.getElementById("autosuggest");


    //Do you want to remember what keycode means what? Me neither.
    var TAB = 9;
    var ESC = 27;
    var KEYUP = 38;
    var KEYDN = 40;
    var SHIFT = 16;
    

    //The browsers' own autocomplete feature can be problematic, since it will 
    //be making suggestions from the users' past input.
    //Setting this attribute should turn it off.
    elem.setAttribute("autocomplete","off");

    //We need to be able to reference the elem by id. If it doesn't have an id, set one.
    if(!elem.id)
    {
        var id = "autosuggest" + idCounter;
        idCounter++;

        elem.id = id;
    }


    /********************************************************
    onkeydown event handler for the input elem.
    Tab key = use the highlighted suggestion, if there is one.
    Esc key = get rid of the autosuggest dropdown
    Up/down arrows = Move the highlight up and down in the suggestions.
    ********************************************************/
    elem.onkeydown = function(ev)
    {
        var key = me.getKeyCode(ev);
        //alert(key);

        switch(key)
        {
            case 13:
                me.useSuggestion();
                break;
            case TAB:
            me.useSuggestion();
            break;

            case ESC:
            me.hideDiv();
            break;

            case KEYUP:
            if (me.highlighted > 0)
            {
                me.highlighted--;
            }
            me.changeHighlight(key);
            break;

            case KEYDN:
            if (me.highlighted < (me.eligible.length - 1))
            {
                me.highlighted++;
            }
            me.changeHighlight(key);
            break;
        }
    };

    /********************************************************
    onkeyup handler for the elem
    If the text is of sufficient length, and has been changed, 
    then display a list of eligible suggestions.
    ********************************************************/
    elem.onkeyup = function(ev) 
    {
        var key = me.getKeyCode(ev);
        switch(key)
        {
        //The control keys were already handled by onkeydown, so do nothing.
        case TAB:
        case ESC:
        case KEYUP:
        case KEYDN:
        case SHIFT:
            return;
        default:

            if (this.value != me.inputText && this.value.length > 0)
            {
                me.inputText = this.value;
                me.getEligible();
                me.createDiv();
                me.positionDiv();
                me.showDiv();
            }
            else
            {
                me.hideDiv();
            }
        }
    };


    /********************************************************
    Insert the highlighted suggestion into the input box, and 
    remove the suggestion dropdown.
    ********************************************************/
    this.useSuggestion = function()
    {
        if (this.highlighted > -1)
        {
            this.elem.value = this.eligible[this.highlighted];
            this.hideDiv();
            //It's impossible to cancel the Tab key's default behavior. 
            //So this undoes it by moving the focus back to our field right after
            //the event completes.
            setTimeout("document.getElementById('" + this.elem.id + "').focus()",0);
        }
    };

    /********************************************************
    Display the dropdown. Pretty straightforward.
    ********************************************************/
    this.showDiv = function()
    {
        this.div.style.display = 'block';
    };

    /********************************************************
    Hide the dropdown and clear any highlight.
    ********************************************************/
    this.hideDiv = function()
    {
        this.div.style.display = 'none';
        this.highlighted = -1;
    };

    /********************************************************
    Modify the HTML in the dropdown to move the highlight.
    ********************************************************/
    this.changeHighlight = function()
    {
        var lis = this.div.getElementsByTagName('LI');
        for (i in lis)
        {
            var li = lis[i];

            if (this.highlighted == i)
            {
                li.className = "selected";
            }
            else
            {
                li.className = "";
            }
        }
    };

    /********************************************************
    Position the dropdown div below the input text field.
    ********************************************************/
    this.positionDiv = function()
    {
        var el = this.elem;
        var x = 0;
        var y = el.offsetHeight;
    
        //Walk up the DOM and add up all of the offset positions.
        while (el.offsetParent && el.tagName.toUpperCase() != 'BODY')
        {
            x += el.offsetLeft;
            y += el.offsetTop;
            el = el.offsetParent;
        }

        x += el.offsetLeft;
        y += el.offsetTop;

        this.div.style.left = x + 'px';
        this.div.style.top = y + 'px';
    };

    /********************************************************
    Build the HTML for the dropdown div
    ********************************************************/
    this.createDiv = function()
    {
        var ul = document.createElement('ul');
    
        //Create an array of LI's for the words.
        for (i in this.eligible)
        {
            var word = this.eligible[i];
    
            var li = document.createElement('li');
            var a = document.createElement('a');
            a.href="javascript:false";
            a.innerHTML = word;
            li.appendChild(a);
    
            if (me.highlighted == i)
            {
                li.className = "selected";
            }
    
            ul.appendChild(li);
        }
    
        this.div.replaceChild(ul,this.div.childNodes[0]);
    

        /********************************************************
        mouseover handler for the dropdown ul
        move the highlighted suggestion with the mouse
        ********************************************************/
        ul.onmouseover = function(ev)
        {
            //Walk up from target until you find the LI.
            var target = me.getEventSource(ev);
            while (target.parentNode && target.tagName.toUpperCase() != 'LI')
            {
                target = target.parentNode;
            }
        
            var lis = me.div.getElementsByTagName('LI');
            
    
            for (i in lis)
            {
                var li = lis[i];
                if(li == target)
                {
                    me.highlighted = i;
                    break;
                }
            }
            me.changeHighlight();
        };

        /********************************************************
        click handler for the dropdown ul
        insert the clicked suggestion into the input
        ********************************************************/
        ul.onclick = function(ev)
        {
            me.useSuggestion();
            me.hideDiv();
            me.cancelEvent(ev);
            return false;
        };
    
        this.div.className="suggestion_list";
        this.div.style.position = 'absolute';

    };

    /********************************************************
    determine which of the suggestions matches the input
    ********************************************************/
    this.getEligible = function()
    {
        this.eligible = new Array();
        for (i in this.suggestions) 
        {
            var suggestion = this.suggestions[i];
            
            if(suggestion.toLowerCase().indexOf(this.inputText.toLowerCase()) == "0")
            {
                this.eligible[this.eligible.length]=suggestion;
            }
        }
    };

    /********************************************************
    Helper function to determine the keycode pressed in a 
    browser-independent manner.
    ********************************************************/
    this.getKeyCode = function(ev)
    {
        if(ev)          //Moz
        {
            return ev.keyCode;
        }
        if(window.event)    //IE
        {
            return window.event.keyCode;
        }
    };

    /********************************************************
    Helper function to determine the event source element in a 
    browser-independent manner.
    ********************************************************/
    this.getEventSource = function(ev)
    {
        if(ev)          //Moz
        {
            return ev.target;
        }
    
        if(window.event)    //IE
        {
            return window.event.srcElement;
        }
    };

    /********************************************************
    Helper function to cancel an event in a 
    browser-independent manner.
    (Returning false helps too).
    ********************************************************/
    this.cancelEvent = function(ev)
    {
        if(ev)          //Moz
        {
            ev.preventDefault();
            ev.stopPropagation();
        }
        if(window.event)    //IE
        {
            window.event.returnValue = false;
        }
    }
}

//counter to help create unique ID's
var idCounter = 0;
