function Suggest(inObjectName, inQueryField, inAutoCompleteDivName, inQueryUrl, inInitialQueryValue) {
    this.objectName = inObjectName;
    this.queryField = inQueryField;
    this.autocompleteDivName = inAutoCompleteDivName;
    this.queryUrl = inQueryUrl;
    this.latestServerQuery = inInitialQueryValue.toLowerCase();

    this.req;
    this.suggestionValues=[];
    this.resultsObj;
    this.selected = -1;
    this.THROTTLE_PERIOD = 500;

    this.startup();
}

/**
*
* It all starts here!
*
*/
Suggest.prototype.startup = function() {
    this.hideAutocompleteDiv();
    this.queryField.autocomplete = "off";
    var oThis = this;
    this.queryField.onkeydown = function (oEvent) {
        if (!oEvent) {
            oEvent = window.event;
        }
        oThis.handleKeyDown(oEvent);
    };
    this.queryField.onkeyup = function (oEvent) {
        if (!oEvent) {
            oEvent = window.event;
        }
        oThis.handleKeyUp(oEvent);
        oThis.requestLoop();
    };
    this.queryField.onblur = function (oEvent) {
        if (!oEvent) {
            oEvent = window.event;
        }
        oThis.handleBlur(oEvent);
        oThis.requestLoop();
    };
    //this.queryField.focus();
    this.requestLoop();
}

Suggest.prototype.requestLoop = function() {
    var keyword = this.query().toLowerCase();
    if ((keyword!=this.latestServerQuery) && (keyword != '')) {
        this.sendQuery(keyword.toLowerCase());
        this.latestServerQuery = this.query().toLowerCase();
    }
    if (keyword == '') {
        this.hideAutocompleteDiv();
        this.suggestionValues = [];
        this.latestServerQuery = null;
        this.selected = -1;
    }
    //setTimeout(this.requestLoop(), this.THROTTLE_PERIOD);
}

Suggest.prototype.query = function() {
    var textbox = this.queryField;
    if (textbox.createTextRange) {
        var fa = document.selection.createRange().duplicate();
        N = fa.text.length;
    } else if (textbox.setSelectionRange) {
        N = textbox.selectionEnd - textbox.selectionStart;
    }
    return textbox.value.substring(0, textbox.value.length-N);
}

Suggest.prototype.sendQuery = function(key) {
    this.initialize();
    var url = this.queryUrl + encodeURIComponent(key);
    if (this.req != null) {
        var oThis = this;
        this.req.onreadystatechange = function() {
            httpprocess(oThis);
        }
        this.req.open("GET", url, true);
        this.req.send(null);
    }
}

Suggest.prototype.initialize = function() {
    try {
        this.req = new ActiveXObject("Msxml2.XMLHTTP");
    }
    catch(e) {
        try {
            this.req = new ActiveXObject("Microsoft.XMLHTTP");
        }
        catch(oc) {
            this.req = null;
        }
    }
    
    if(!this.req && typeof XMLHttpRequest != "undefined") {
        this.req = new XMLHttpRequest();
    }
}
    
function httpprocess(othis) {
    if (othis.req.readyState == 4) {
        // only if "OK"
        if (othis.req.status == 200) {
            var resTxt = trim(othis.req.responseText);
            if (resTxt == "" || resTxt == "[]") {
                othis.hideAutocompleteDiv();
            }
            else {
                othis.showAutocompleteDiv();
                try {
                    //this.resultsObj = eval('(' + resTxt + ')');
                    othis.resultsObj = resTxt.parseJSON();
                    othis.htmlFormat();
                }
                catch(e) {
                    var msg = (typeof e == "string") ? e : ((e.message) ? e.message : "Unknown Error");
                }
            }
        }
        else {
            document.getElementById(othis.autocompleteDivName).innerHTML =
                "There was a problem retrieving data:<br>" + othis.req.statusText;
        }
    }
}

Suggest.prototype.htmlFormat = function() {
    var output = document.getElementById(this.autocompleteDivName);
    while (output.childNodes.length > 0) {
        output.removeChild(output.childNodes[0]);
    }
    
    this.suggestionValues = [];
    for (var i=0;i<this.resultsObj.length;i++) {
        if (this.resultsObj[i]) {
            this.suggestionValues.push(this.resultsObj[i]);
            
            var x1 = document.createElement("div");
            if (i==this.selected) {
                x1.className = "srs";
            } else {
                x1.className = "sr";
            }
            var onMouseFn = this.objectName + ".highlight(" + i + ");return false;";
            x1.onmousemove = new Function(onMouseFn);
            var onClickFn = this.objectName + ".setValues();" + this.objectName + ".hideAutocompleteDiv();return true;";
            x1.onmousedown = new Function(onClickFn);
            /*
            var oThis = this;
            x1.onmousemove = function (oEvent) {
                if (!oEvent) {
                    oEvent = window.event;
                }
                oThis.handleMouseMove(oEvent);
                oThis.requestLoop();
            };
            x1.onmousedown = function (oEvent) {
                if (!oEvent) {
                    oEvent = window.event;
                }
                oThis.handleMouseDown(oEvent);
                oThis.requestLoop();
            };
            */
            var x2 = document.createElement("span");
            x2.className = "srt";
            x2.appendChild(document.createTextNode(this.resultsObj[i]));
            x1.appendChild(x2);

            output.appendChild(x1);
        }
    }
    //this.requestSuggestions();
}

Suggest.prototype.handleKeyUp = function(evt) {
    var key = evt.keyCode;
    if (key < 32 || (key >= 33 && key <= 46) || (key >= 112 && key <= 123)) {
        //ignore
    } else {
        //request suggestions from the suggestion provider
        // Backspace key(8), Delete key (48)
        if (key != 8 && key != 48) {
            this.requestSuggestions();
        }
    }
    return false;
}

Suggest.prototype.handleKeyDown = function(evt) {
    // don't do anything if the div is hidden
    var div = document.getElementById(this.autocompleteDivName);
    if (div.style.display == "none") {
        return true;
    }
    
    var key = evt.keyCode;
    // if this key isn't one of the ones we care about, just return
    var KEYUP = 38;
    var KEYDOWN = 40;
    var KEYENTER = 13;
    var KEYTAB = 9;
    
    if ((key != KEYUP) && (key != KEYDOWN)  && (key != KEYENTER)) {
        return true;
    }

    if (key == KEYUP) {
        if ((this.selected-1) >= 0) {
            this.selected = this.selected - 1;
        } else {
            this.selected = this.resultsObj.length-1;
        }
        this.setValues();
        this.htmlFormat();
    }
    
    if (key == KEYDOWN) {
        if ((this.selected+1) < this.resultsObj.length) {
            this.selected = this.selected + 1;
        } else {
            this.selected = 0;
        }
        this.setValues();
        this.htmlFormat();
    }
    
    if (key == KEYENTER) {
        this.setValues();
        this.selectRange(99,99);
        this.hideAutocompleteDiv();
        this.suggestionValues = [];
        this.selected = -1;
        return false;
    }
    
    return true;
}

Suggest.prototype.handleBlur = function(evt) {
    this.hideAutocompleteDiv();
    this.suggestionValues = [];
    this.selected = -1;
    return false;
}

Suggest.prototype.highlight = function(i) {
    this.selected = i;
    this.htmlFormat();
}
    
Suggest.prototype.setValues = function() {
    if (this.resultsObj) {
        var result = this.resultsObj[this.selected];
        this.latestServerQuery = result.toLowerCase();
        this.queryField.value = result;
        this.selectRange(99,99);
    }
}
    
Suggest.prototype.showAutocompleteDiv = function() {
    if (document.layers) document.layers[this.autocompleteDivName].display="block";
    else document.getElementById(this.autocompleteDivName).style.display="block";
}
    
Suggest.prototype.hideAutocompleteDiv = function() {
    if (document.layers) document.layers[this.autocompleteDivName].display="none";
    else document.getElementById(this.autocompleteDivName).style.display="none";
}
    
Suggest.prototype.requestSuggestions = function() {
    var sTextboxValue =  this.query().toLowerCase();
    var aSuggestions = [];
    
    if (this.suggestionValues.length > 0){
        //search for matching states
        for (var i=0; i < this.suggestionValues.length; i++) {
            if (this.suggestionValues[i].toLowerCase().indexOf(sTextboxValue) == 0) {
                aSuggestions.push(this.suggestionValues[i]);
            }
        }
        this.autosuggest(aSuggestions);
    }
}
    
    
    
Suggest.prototype.autosuggest = function(aSuggestions) {
    //make sure there's at least one suggestion
    //commented out the 3 lines below to disable type ahead feature
    //if (aSuggestions.length > 0) {
    //    this.typeAhead(aSuggestions[0]);
    //}
}
    
Suggest.prototype.typeAhead = function(sSuggestion) {
    var textbox = this.queryField;
    //check for support of typeahead functionality
    if (textbox.createTextRange || textbox.setSelectionRange) {
        var iLen = this.query().length;
        textbox.value = sSuggestion;
        this.selectRange(iLen, sSuggestion.length);
    }
}
    
Suggest.prototype.selectRange = function(iStart, iLength) {
    var textbox = this.queryField;
    //use text ranges for Internet Explorer
    if (textbox.createTextRange) {
        var oRange = textbox.createTextRange();
        oRange.moveStart("character", iStart);
        oRange.moveEnd("character", iLength - textbox.value.length);
        oRange.select();
        //use setSelectionRange() for Mozilla
    } else if (textbox.setSelectionRange) {
        textbox.setSelectionRange(iStart, iLength);
    }
    
    //set focus back to the textbox
    textbox.focus();
}

function trim(str) {
    if (str) {
        var re=/[\r\n]/g;
        str = str.replace(re, '');
        re = /^[ ]+|[ ]+$/g;
        return str.replace(re, '');
    } else {
        return str;
    }
}
