﻿// 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()) /
//                }
//
//      onMouseItemSelect - when an item is selected using the mouse, called with the LI of the selected item
//                --ADDED BY LANIT
//          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,
    pageup: 33,
    pagedown: 34,
    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
    // 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;
            // added by lanit - make pageup/pagedown scroll through items a bit faster 
            case keycodes.pageup:
                e.preventDefault();
                moveSelect(-5);
                doKeyUp = false;
                break;
            case keycodes.pagedown:
                e.preventDefault();
                moveSelect(5);
                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) {
                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]);  // this is fine when matching on the beginning of the string, 
            // but not when matching in the middle of the string
            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]); // this is fine when matching on the beginning of the string, 
        // but not when matching in the middle of the string
        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)
			    // added by lanit - handler for mouse selections (rather than tabbing w/ keyboard)
			    if (options.onMouseItemSelect) {
			        options.onMouseItemSelect(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"
        });
    };

    // added by Lanit - creates a "selectfirst" event - can be triggered to select the first item in the list
    $input.bind("selectfirst", function() {
        selectCurrent();
    });

    // added by Lanit - creates an "unautocomplete" event - can be triggered to remove all autocomplete features
    $input.bind("unautocomplete", function() {
        $results.remove();
        limitList = new Array();
    });
};

$.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;
};

//$.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;
//    $input.keydown(function(e) {
//        switch (e.keyCode) {
//            case 38: e.preventDefault();
//                moveSelect(-1);
//                doKeyUp = false;
//                break;
//            case 40: e.preventDefault();
//                moveSelect(1);
//                doKeyUp = false;
//                break;
//            case 9:
//            case 13: if (selectCurrent()) {
//                    selectRange(input.value.length, input.value.length)
//                }
//                doKeyUp = false;
//                break;
//            case 37:
//            case 39:
//            case 16:
//            case 20: 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 == 8 || e.keyCode == 46) {
//                        if (timeout)
//                            clearTimeout(timeout);
//                        timeout = setTimeout(onCharDelete, 0)
//                    }
//                }
//                else {
//                    if (timeout)
//                        clearTimeout(timeout);
//                    timeout = setTimeout(onChange, 0)
//                }
//                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, num); 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;

//    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;
//}

