﻿/*
* Autocompletelite - jQuery plugin 0.1
*
* Copyright (c) 2009 Anthony Cooper
*
* Based on autocomplete plugin by Dylan Verheul, Dan G. Switzer, Anjesh Tuladhar, J�rn Zaefferer.
* this is a client side only implementation that does not support ajax calls to retrieve matches
* use for lists of up to 500 records, and is optimised to work better with older browsers (e.g. IE6).
* Special features:
* - show all functionality to be have like normal dropdown
* 	- on clicking show all, automatically scrolls to last selected item, or first item found depending on search text
* - uses regex to match list entries with options of contains, any and startswith
* - optional hidden field for storing value member
* - tab hit event firing
* - shows no match warning when no matches found
*/
// use $ as jQuery alias
(function ($) {

    // extend jQuery, create methods to be called from jQuery construct
    $.fn.extend({
        aclite: function (data, options) {
            options = $.extend({}, $.aclite.defaults, {
                data: data
            }, options);
            return this.each(function () {
                new $.aclite(this, options);
            });
        },
        selectChanged: function (handler) {
            return this.bind("selectChanged", handler);
        },
        showAll: function () {
            return this.trigger("showAll");
        },
        unaclite: function () {
            return this.trigger("unaclite");
        }
    });

    // aclite class 
    $.aclite = function (input, options) {
        var KEY = {
            UP: 38,
            DOWN: 40,
            DEL: 46,
            TAB: 9,
            RETURN: 13,
            ESC: 27,
            COMMA: 188,
            PAGEUP: 33,
            PAGEDOWN: 34,
            BACKSPACE: 8
        };

        // Create $ object for input element
        var $input = $(input).attr("autocomplete", "off").addClass(options.inputClass);
        var uiState = {
            mouseDownOnSelect: false,
            lastKeyPressCode: null
        };
        var selectedVal = $.aclite.SelectedValue(options, input);
        var noMatch = $.aclite.NoMatch(options, input);
        var isShowing = false;
        var select = $.aclite.Select(options, input, selectCurrent, uiState, selectedVal);
        select.initialize();

        var previousValue = "";
        var timeout;

        var blockSubmit;

        // prevent form submit in opera when selecting with return key
        $.browser.opera &&
        $(input.form).bind("submit.aclite", function () {
            if (blockSubmit) {
                blockSubmit = false;
                return false;
            }
        });

        $input.bind(($.browser.opera ? "keypress" : "keydown") + ".aclite", function (event) {
            // track last key pressed
            uiState.lastKeyPressCode = event.keyCode;
            switch (event.keyCode) {
                case KEY.UP:
                    event.preventDefault();
                    if (select.visible()) {
                        select.prev();
                    }
                    else {
                        onChange(true);
                    }
                    break;
                case KEY.DOWN:
                    event.preventDefault();
                    if (select.visible()) {
                        select.next();
                    }
                    else {
                        onChange(true);
                    }
                    break;

                case KEY.PAGEUP:
                    event.preventDefault();
                    if (select.visible()) {
                        select.pageUp();
                    }
                    else {
                        onChange(true);
                    }
                    break;

                case KEY.PAGEDOWN:
                    event.preventDefault();
                    if (select.visible()) {
                        select.pageDown();
                    }
                    else {
                        onChange(true);
                    }
                    break;
                case undefined:
                    event.preventDefault();
                    alert("undefined");
                    break;
                case KEY.TAB:
                case KEY.RETURN:
                    if (selectCurrent()) {
                        // stop default to prevent a form submit, Opera needs special handling
                        event.preventDefault();
                        blockSubmit = true;
                        return false;
                    }
                    break;
                case KEY.ESC:
                    hideResults();
                    break;
                default:
                    clearTimeout(timeout);
                    timeout = setTimeout(onChange, options.delay);
                    break;
            }
        }).bind("showAll", function () {
            if (!isShowing) {
                isShowing = true;
                startLoading();
                select.search("");
                select.show();
                $input.focus();
                stopLoading();
            } else {
                hideResults();
                isShowing = false;
            }
        }).blur(function () {
            if (!uiState.mouseDownOnSelect) {
                hideResults();
            }
            // isShowing = false; //  causing defect 2994 - [ww] 20100311
            noMatch.hide();
        }).bind("unaclite", function () {
            select.unbind();
            noMatch.unbind();
            $input.unbind();
            $(input.form).unbind(".aclite");
        });


        function selectCurrent() {
            var selected = select.selected();
            if (!selected)
                return false;

            noMatch.hide();

            var v = selected.result;
            previousValue = v;

            $input.val(v);

            selectedVal.set(selected.selectedValue);

            hideResults();
            $input.trigger("selectChanged", [selected.data, selected.value, uiState.lastKeyPressCode == KEY.TAB]);
            return true;
        }
        function hideResults() {
            stopLoading();
            var wasVisible = select.visible();
            select.hide();
            if (wasVisible)
            // position cursor at end of input field
                $.aclite.Selection(input, input.value.length, input.value.length);
            var selected_val = select.validateResult(input.value);
            if (selected_val) {
                $input.removeClass(options.warnClass);
                selectedVal.set(selected_val);
            }
            else {
                if (input.value.length >= options.minChars) {
                    $input.addClass(options.warnClass);
                }
                selectedVal.set("");
            }
        }
        function onChange(skipPrevCheck) {
            if (uiState.lastKeyPressCode == KEY.DEL) {
                hideResults();
                return;
            }

            var currentValue = $input.val();

            if (!skipPrevCheck && currentValue == previousValue)
                return;

            previousValue = currentValue;

            if (currentValue.length >= options.minChars) {
                startLoading();
                if (select.search(currentValue)) {
                    noMatch.hide();
                    select.show();
                    $input.removeClass(options.warnClass);
                    stopLoading();
                }
                else {
                    hideResults();
                    noMatch.show();
                }
            }
            else {
                hideResults();
                noMatch.hide();
            }
        }
        function stopLoading() {
            $input.removeClass(options.loadingClass);
        }
        function startLoading() {
            $input.addClass(options.loadingClass);
        };
    };

    $.aclite.defaults = {
        inputClass: "acl_input",
        loadingClass: "acl_loading",
        resultsClass: "acl_results",
        warnClass: "acl_warn",
        matchCase: false,
        matchContains: "contains",
        data: null,
        width: null,
        minChars: 2,
        regExIgnoreCharacters: ["(", ")", "*"],
        delay: 10,
        scroll: true,
        scrollHeight: 180,
        formatMatch: null,
        formatSelected: null,
        formatSelectedValue: null,
        resultValHolder: null,
        max: 200,
        noMatchText: "No Match",
        selectFirst: true,
        highlight: function (value, term) {
            return value.replace(new RegExp("(?![^&;]+;)(?!<[^<>]*)(" + term.replace(/([\^\$\(\)\[\]\{\}\*\.\+\?\|\\])/gi, "\\$1") + ")(?![^<>]*>)(?![^&;]+;)", "gi"), "<strong>$1</strong>");
        }
    };

    $.aclite.SelectedValue = function (options, input) {
        var valueHolder = options.resultValHolder ? $(options.resultValHolder) : null;
        return {
            get: function () {
                return valueHolder ? valueHolder.val() : null;
            },
            set: function (val) {
                valueHolder && valueHolder.val(val);
            },
            available: function () {
                return valueHolder != null;
            }
        };
    };

    $.aclite.Select = function (options, input, selectCurrent, uiState, selectedValControl) {
        var CLASSES = {
            ACTIVE: "acl_over",
            ODD: "acl_odd",
            EVEN: "acl_even",
            VISIBLE: "acl_visible"
        };
        var list, listItems, element, active = -1, lastSearchTerm, scrollTimeout;
        function init() {
            element = $("<div/>").attr("id", input.id + "_ddl").css("display", "none").addClass(options.resultsClass).css("position", "absolute").appendTo(document.body);

            list = $("<ul/>").appendTo(element).mouseover(function (event) {
                if (target(event).nodeName && target(event).nodeName.toUpperCase() == 'LI') {
                    active = $("li", list).removeClass(CLASSES.ACTIVE).index(target(event));
                    $(target(event)).addClass(CLASSES.ACTIVE);
                }
            }).click(function (event) {
                uiState.lastKeyPressCode = null;
                $(target(event)).addClass(CLASSES.ACTIVE);
                selectCurrent();
                input.focus();
                return false;
            }).mousedown(function () {
                uiState.mouseDownOnSelect = true;
            }).mouseup(function () {
                uiState.mouseDownOnSelect = false;
            });
            loadList(options.data);
            if (options.width && (typeof options.width == "string" || options.width > 0))
                element.css("width", options.width);

        }
        function loadList(data) {
            var max = data.length;
            for (var i = 0; i < max; i++) {
                if (!data[i])
                    continue;
                var formatted = options.formatMatch(data[i]);
                if (formatted === false)
                    continue;

                var li = $("<li/>").html(formatted).appendTo(list)[0];
                var resultValue = options.formatSelected && options.formatSelected(data[i]) || value;
                var row = {
                    value: formatted,
                    data: data[i],
                    result: resultValue,
                    selectedValue: options.formatSelectedValue && options.formatSelectedValue(data[i]) || resultValue
                };
                $.data(li, "ac_data", row);
                // apply bgiframe if available and browser is IE6
                if ($.fn.bgiframe && $.browser.msie && parseInt($.browser.version.charAt(0)) == 6) {
                    list.bgiframe();
                }

            }
        }

        function target(event) {
            var element = event.target;
            while (element && element.tagName != "LI")
                element = element.parentNode;
            // more fun with IE, sometimes event.target is empty, just ignore it then
            if (!element)
                return [];
            return element;
        }

        function search(term) {
            lastSearchTerm = term;
            active = -1;
            var counter = 1;
            list.children("li").each(function (i) {
                if (counter > options.max) {
                    return false;
                }
                $(this).attr("class", "");

                if ($(this).data("ac_data") != undefined) {
                    var subjectText = $(this).data("ac_data").value;

                    if (term == "") {
                        $(this).html(subjectText).addClass(counter % 2 == 0 ? CLASSES.EVEN : CLASSES.ODD).addClass(CLASSES.VISIBLE).show();
                        counter++;
                    }
                    else {
                        if (findMatch(term, subjectText)) {
                            $(this).html(options.highlight(subjectText, term)).addClass(counter % 2 == 0 ? CLASSES.EVEN : CLASSES.ODD).addClass(CLASSES.VISIBLE).show();
                            counter++;
                        }
                        else {
                            $(this).hide();
                        }
                    }
                }
            });
            listItems = list.find("li." + CLASSES.VISIBLE);
            if (options.selectFirst) {
                active = 0;
                listItems.slice(active, active + 1).addClass(CLASSES.ACTIVE);
            }
            return listItems.size() > 0;
        }
        function findMatch(query, subject) {
            //match = start/contains/any
            //replace " " with \s
            query = query.replace(/ /g, "\\s");
            for (i = 0; i < options.regExIgnoreCharacters.length; i++) {
                var ignoreRegex = new RegExp("\\" + options.regExIgnoreCharacters[i], "g");
                query = query.replace(ignoreRegex, "\\" + options.regExIgnoreCharacters[i]);
            }
            var qRegex;
            var matchCase = options.matchCase ? "" : "i";
            try {
                switch (options.matchContains) {
                    case "contains":
                        qRegex = new RegExp("\\b" + query, "g" + matchCase);
                        break;
                    case "any":
                        qRegex = new RegExp(query, "g" + matchCase);
                        break;
                    default:
                        //start
                        qRegex = new RegExp("^" + query, matchCase);
                        break;
                }
                return qRegex.test(subject);
            }
            catch (err) {
                return false;
            }

        }
        function moveSelect(step) {

            var currentItem = listItems.slice(active, active + 1);
            if (currentItem.length > 0)
                currentItem.removeClass(CLASSES.ACTIVE);
            movePosition(step);
            var activeItem = listItems.slice(active, active + 1).addClass(CLASSES.ACTIVE);
            if (options.scroll) {

                list.scrollTop(activeItem[0].offsetTop);
            }
        }
        function movePosition(step) {
            active += step;
            if (active < 0) {
                active = listItems.length - 1;
            }
            else
                if (active >= listItems.length) {
                    active = 0;
                }
        }

        function scrollToSelected() {
            //remove active items
            list.find("li." + CLASSES.ACTIVE).removeClass(CLASSES.ACTIVE);
            var activeItemBySearch;
            var activeItemBySearchIndex;

            var activeItemBySelectedVal;
            var activeItemBySelecteValIndex;

            var selectedStoredValue;
            if (selectedValControl.available()) {
                selectedStoredValue = selectedValControl.get();
            }

            var newActiveIndex;

            var qText = $(input).val();
            // find by input val
            // showAll has been called
            if (lastSearchTerm == "") {
                for (j = 0; j < listItems.length; j++) {
                    var itemData = $(listItems[j]).data("ac_data");
                    var subj = itemData.value;
                    var itemId = itemData.selectedValue;

                    if (qText != "" && !activeItemBySearch && findMatch(qText, subj)) {
                        activeItemBySearch = listItems[j];
                        activeItemBySearchIndex = j;
                    }
                    if (selectedStoredValue && !activeItemBySelectedVal && selectedStoredValue == itemId) {
                        activeItemBySelectedVal = listItems[j];
                        activeItemBySelecteValIndex = j;
                    }
                }
                newActiveIndex = activeItemBySearchIndex ? activeItemBySearchIndex :
									activeItemBySelecteValIndex ? activeItemBySelecteValIndex : undefined;
            }

            active = newActiveIndex ? newActiveIndex : active;

            moveSelect(0);
        }

        function show() {
            var offset = $(input).offset();
            element.css({
                width: typeof options.width == "string" || options.width > 0 ? options.width : $(input).width(),
                top: offset.top + input.offsetHeight,
                left: offset.left
            }).show();
            if (options.scroll) {
                list.css({
                    maxHeight: options.scrollHeight,
                    overflow: "auto"
                });
                clearTimeout(scrollTimeout);
                scrollTimeout = setTimeout(scrollToSelected, options.delay);
                if ($.browser.msie && typeof document.body.style.maxHeight === "undefined") {
                    var listHeight = 0;
                    listItems.each(function () {
                        listHeight += this.offsetHeight;
                    });
                    var scrollbarsVisible = listHeight > options.scrollHeight;
                    list.css('height', scrollbarsVisible ? options.scrollHeight : listHeight);
                    if (!scrollbarsVisible) {
                        // IE doesn't recalculate width when scrollbar disappears
                        listItems.width(list.width() - parseInt(listItems.css("padding-left")) - parseInt(listItems.css("padding-right")));
                    }
                }
            }
        }

        function resultIsValid(result) {
            var isValid = false;
            list.children("li").each(function (i) {
                var itemData = $(this).data("ac_data");
                if (itemData != undefined && itemData.result && result == itemData.result) {
                    isValid = itemData.selectedValue;
                    return false; //break out of each loop
                }
            });
            return isValid;
        }
        return {
            initialize: init,
            search: function (term) {
                return search(term);
            },
            show: function () {
                show();
            },
            hide: function () {
                element && element.hide();
            },
            visible: function () {
                return element && element.is(":visible");
            },
            next: function () {
                moveSelect(1);
            },
            prev: function () {
                moveSelect(-1);
            },
            pageUp: function () {
                if (active != 0 && active - 8 < 0) {
                    moveSelect(-active);
                }
                else {
                    moveSelect(-8);
                }
            },
            pageDown: function () {
                if (active != listItems.size() - 1 && active + 8 > listItems.size()) {
                    moveSelect(listItems.size() - 1 - active);
                }
                else {
                    moveSelect(8);
                }
            },
            selected: function () {
                var selected = listItems && listItems.filter("." + CLASSES.ACTIVE).removeClass(CLASSES.ACTIVE);
                return selected && selected.length && $.data(selected[0], "ac_data");
            },
            validateResult: function (result) {
                return resultIsValid(result);
            },
            unbind: function () {
                element && element.remove();
            }
        };
    };

    $.aclite.NoMatch = function (options, input) {
        var CLASSES = {
            NO_MATCH: "acl_nomatch"
        };
        var noMatch;

        // Create no match field
        noMatch = $("<div/>").css("display", "none").addClass(CLASSES.NO_MATCH).css("position", "absolute").html('<span>' + options.noMatchText + '</span>').appendTo(document.body);

        return {
            show: function () {
                var offset = $(input).offset();
                noMatch.css({
                    width: noMatch.css("width") == "" ? $(input).width() : noMatch.css("width"),
                    top: offset.top + input.offsetHeight,
                    left: offset.left
                }).show();
            },
            hide: function () {
                if (noMatch && noMatch.is(":visible"))
                    noMatch.hide();
            },
            unbind: function () {
                noMatch && noMatch.remove();
            }
        };
    };

    $.aclite.Selection = function (field, start, end) {
        if (field.createTextRange) {
            var selRange = field.createTextRange();
            selRange.collapse(true);
            selRange.moveStart("character", start);
            selRange.moveEnd("character", end);
            selRange.select();
        }
        else
            if (field.setSelectionRange) {
                field.setSelectionRange(start, end);
            }
            else {
                if (field.selectionStart) {
                    field.selectionStart = start;
                    field.selectionEnd = end;
                }
            }
        field.focus();
    };

})(jQuery);

