﻿// unable to find documentation online - limited documentation by Lanit
//
// usage : 
//      var data = [ 'Array', 'Of', 'Items' ]; // can also be a url to request from
//      $("#input").limittext(data, options);
//
// options : 
//      resultsClass - css class for the div containing the list of possible results
//          Default: "autosuggest"
//          ex : <div class="autosuggest">
//                  <ul>
//                      <li>Array</li><li>Of</li><li>Items</li>
//                  </ul>
//
//      minChars - minimum # of chars in the textbox before matching starts
//          Default : 1
//
//      matchCase - whether results are case sensitive
//          Default : 0
//
//      matchSubset - whether matches can use the cache for more specific queries.
//          Default : 1
//            ex : All matches of "Bat" a subset of the matches for "Ba"
//
//      matchContains - whether to look inside a string or from the start
//          Default : 0
//          ex : Does "he" match "The"
//
//      mustMatch - only allow results contained in the data.
//          Default : 0
//
//      selectFirst - if true, the first value in the data will be selected on tab/return
//          Default : 0
//
//      selectOnly - if true, if only one value is left in filtered data, it is selected
//          Default : 0
//
//      maxResults - max # of filtered results to display
//          Default : 10
//
//      onMatch - when a key is pressed, called with a boolean of whether or not a match is found
//          Default : null
//          ex :
//              function(found) {
//                  if (found) {
//                      console.log("right now, the text in the box matches one of the data items");
//                  }
//                  else {
//                      console.log("no match");
//                  }
//              }
//
//      onItemSelect - when an item is selected, called with the LI of the selected item
//          Default : null  
//          ex :
//                function($li) {
//                    console.log($li.html()) /
//                }
//
//      formatItem - when populating the list of data items, called with the value + index (starting @ 0) of data item to format. -- allows the match text to differ from the displayed text
//          Default : null
//          ex :
//                function(text, number) {
//                    return number + " " + text;
//                }
//              NOTE: will still match on "text", but "number text" will be displayed
var keycodes = {
    backspace: 8,
    tab: 9,
    enter: 13,
    shift: 16,
    ctrl: 17,
    alt: 18,
    caps: 20,
    esc: 27,
    leftarrow: 37,
    uparrow: 38,
    rightarrow: 39,
    downarrow: 40,
    del: 46
};

$.limittext = function(input, limitList, options) {
    var me = this;
    var $input = $(input).attr("autocomplete", "off");
    var results = document.createElement("div");
    var $results = $(results);
    var pos; setPos(); $("body").append(results);
    input.autocompleter = me;
    input.lastSelected = $input.val();
    var timeout = null;
    var active = -1;
    var keyb = false;
    var doKeyUp = true;
    var isFirstKeyPress = true;

    // added by Lanit - creates a "unautocomplete" event - can be triggered to remove all autocomplete features
    $input.bind("unautocomplete", function() {
        $results.remove();
        limitList = new Array();
    });

    // added by Lanit
    // Opera "KeyDown" is messed up - doesn't allow prevent default, doesn't allow special keys (enter/tab/up/down/etc)
    // bind to "keypress" for opera
    $input.bind(($.browser.opera ? "keypress" : "keydown"), function(e) {
        switch (e.keyCode) {
            case keycodes.uparrow: e.preventDefault();
                moveSelect(-1);
                doKeyUp = false;
                break;
            case keycodes.downarrow:
                e.preventDefault();
                moveSelect(1);
                doKeyUp = false;
                break;
            case keycodes.tab:
            case keycodes.enter:
                if (selectCurrent())
                    selectRange(input.value.length, input.value.length)
                doKeyUp = false;
                break;
            case keycodes.leftarrow:
            case keycodes.rightarrow:
            case keycodes.shift:
            case keycodes.caps:
                doKeyUp = false;
                break;
            default: active = -1;
                if (e.keyCode < 32 || (e.keyCode >= 33 && e.keyCode <= 46) || (e.keyCode >= 112 && e.keyCode <= 123)) {
                    if (e.keyCode == keycodes.backspace || e.keyCode == keycodes.del) {
                        if (timeout)
                            clearTimeout(timeout);
                        timeout = setTimeout(onCharDelete, options.timeout)
                    }
                }
                else {
                    if (timeout)
                        clearTimeout(timeout);
                    timeout = setTimeout(onChange, options.timeout)
                }
                break
        }
    }).focus(function() {
        setPos()
    }).blur(function() {
        hideResults()
    });

    hideResultsNow();

    function onChange() {
        var v = $input.val().toLowerCase();
        var a = v.length;
        filteredList = $.grep(limitList, function(i) {
            if (i != null) {
                var lc = i.toLowerCase();
                lc = lc.substr(0, 3);
                return (i.toLowerCase().indexOf(v) == 0)
            }
            return (false);
        });
        if (filteredList.length == 0) {
            if (options.onMatch)
                setTimeout(function() {
                    options.onMatch(false)
                }, 1);
            hideResultsNow();
            return
        }
        else {
            $input.val(filteredList[0]);
            if (options.onMatch)
                setTimeout(function() {
                    options.onMatch(true)
                }, 1)
        }
        if (v.length >= options.minChars) {
            buildList(filteredList);
            moveSelect(1);
            selectRange(a, filteredList[0].length)
        }
        else {
            $results.hide()
        }
    };

    function onCharDelete() {
        var v = $input.val().toLowerCase();
        filteredList = $.grep(limitList, function(i) {
            if (i != null)
                return (i.toLowerCase().indexOf(v) == 0)
            return false;
        });

        if (filteredList.length == 0) {
            hideResultsNow();
            return;
        } else {
            var a = false;
            if ($input.val() == filteredList[0] || (filteredList[0].indexOf($input.val()) == 0 && options.matchPartial)) {
                a = true;
            }
            if (options.onMatch) {
                setTimeout(function() { options.onMatch(a) }, 1);
            }
        }
        if (v.length >= options.minChars) {
            buildList(filteredList);
        } else {
            $results.hide();
        }
    };

    function selectRange(a, b) {
        if (input.createTextRange) {
            var r = input.createTextRange();
            r.moveStart('character', a);
            r.moveEnd('character', b);
            r.select()
        } else if (input.setSelectionRange) {
            input.setSelectionRange(a, b)
        }

        input.focus()
    };

    function getSel(a) {
        var l = -1;
        var s = 0;
        if (a.createTextRange) {
            var r = document.selection.createRange().duplicate();
            l = r.text.length;
            r.moveEnd("textedit", 1);
            s = a.value.length - l
        } else if (a.setSelectionRange) {
            l = a.selectionEnd - a.selectionStart;
            s = a.selectionStart;
        } else {
            s = -1
        }

        return { length: l, start: s }
    }

    function moveSelect(a) {
        var b = $("li", results);
        if (!b) {
            return;
        }

        active += a;

        if (active < 0) {
            active = 0
        } else if (active >= b.size()) {
            active = b.size() - 1;
        }

        b.removeClass("over");
        $(b[active]).addClass("over");
        $input.val(filteredList[active]);
        if (options.onMatch) {
            setTimeout(function() { options.onMatch(true) }, 1)
        }
    };

    function selectCurrent() {
        var a = $("li.over", results)[0];
        if (!a) {
            var b = $("li", results);
            if (options.selectOnly) {
                if (b.length == 1)
                    a = b[0];
            } else if (options.selectFirst) {
                a = b[0];
            }
        }

        if (a && $results.is(":visible")) {
            selectItem(a);
            return true;
        } else {
            return false
        }
    };

    function selectItem(a) {
        if (!a) {
            a = document.createElement("li");
            a.extra = [];
            a.selectValue = ""
        }
        var v = $.trim(a.selectValue ? a.selectValue : a.innerHTML);
        if (v.indexOf('<') == 0) {
            hideResultsNow();
            return;
        }

        input.lastSelected = v;
        prev = v;
        $results.html("");
        $input.val(v);
        hideResultsNow();
        if (options.onItemSelect)
            setTimeout(function() { options.onItemSelect(a) }, 1)
    };
    function hideResults() {
        if (timeout)
            clearTimeout(timeout);
        timeout = setTimeout(hideResultsNow, 200)
    };
    function hideResultsNow() {
        if (timeout) clearTimeout(timeout);
        if ($results.is(":visible")) {
            $results.hide()
        }
        if (options.mustMatch) {
            var v = $input.val();
            if (v != input.lastSelected) {
                selectItem(null)
            }
        }
    };
    function buildList(a) {
        if (a) {
            results.innerHTML = "";
            if ($.browser.msie) { }
            results.appendChild(listToDom(a));
            if ($results.is(":hidden")) {
                $results.fadeIn('fast')
            }
        }
        else { hideResultsNow() }
    };
    function listToDom(a) {
        var b = document.createElement("ul");
        for (var i = 0; i < a.length; i++) {
            if (i == options.maxResults) {
                break
            }
            var c = a[i];
            var d = document.createElement("li");
            if (options.formatItem) {
                d.innerHTML = options.formatItem(c, i);
                d.selectValue = c
            } else { d.innerHTML = c }
            b.appendChild(d);
            $(d).hover(function() {
                $("li", b).removeClass("over");
                $(this).addClass("over")
            }, function()
            { $(this).removeClass("over") }).click(function(e) {
                e.preventDefault();
                e.stopPropagation();
                selectItem(this)
            })
        }
        return b
    };
    function matchSubset(s, sub) {
        if (!options.matchCase) s = s.toLowerCase();
        var i = s.indexOf(sub); if (i == -1) return false; return i == 0 || options.matchContains;
    }; function findPos(obj) {
        curleft = (obj.offsetLeft || 0); curtop = (obj.offsetTop || 0);
        while (obj = obj.offsetParent) {
            curleft += obj.offsetLeft
            curtop += obj.offsetTop
        };
        return { x: curleft, y: curtop };
    };

    function setPos() {
        pos = findPos(input);
        $results.hide().addClass(options.resultsClass).css({
            position: "absolute",
            top: (pos.y + input.offsetHeight) + "px",
            left: pos.x + "px",
            width: parseInt($input.width()) + parseInt($input.css("padding-left")) + parseInt($input.css("padding-right")) + "px"
        });
    };
};

$.fn.limittext = function(limitList, options) {
    // Make sure options exists
    options = options || {};

    // Set default values for required options	
    options.resultsClass = options.resultsClass || "autosuggest";
    options.lineSeparator = options.lineSeparator || "\n";
    options.cellSeparator = options.cellSeparator || "|";
    options.minChars = options.minChars || 1;
    options.matchCase = options.matchCase || 0;
    options.matchSubset = options.matchSubset || 1;
    options.matchContains = options.matchContains || 0;
    options.cacheLength = options.cacheLength || 1;
    options.mustMatch = options.mustMatch || 0;
    options.selectFirst = options.selectFirst || false;
    options.selectOnly = options.selectOnly || false;
    options.limitToFilter = options.limitToFilter || false;
    options.maxResults = options.maxResults || 10;
    options.matchPartial = options.matchPartial || false;
    options.timeout = options.timeout || 0;

    this.each(function() {
        var input = this;
        options.input = this;   /// added by lanit so that we can say "this.input" in the event handler to get the current dom object
        new $.limittext(input, limitList, options);
    });

    // Don't break the chain
    return this;
};