扩展Jquery Auto complete插件

邴英毅
2023-12-01

JS代码:

(function ($) {

    /**
    * autor: Squeen
    * email: chuxue1342@qq.com
    * site: http://blog.csdn.net/chuxue1342/article/details/8077197
    * license: MIT & GPL
    * last update: 16.10.2012
    * version: 1.3.1
    */

    var ac = function (c, o) {
        this.cache = {}; // main chache {mask:[text]}
        this.store = {}; // secondary cache {mask:strind}
        this.pairs = {}; // cache of values {text:value}
        this.init(c, o);
    };

    ac.prototype = {

        // html elements
        ac: null, // main input
        ul: null, // autocomplete list
        img: null, // image
        container: null, // outer div

        // timeouts
        close: null, // ac hide
        timeout: null, // ac search

        // system definitons
        chars: 0, // previous search string lenght

        // user definitons
        url: null, // url for ajax request
        source: null, // <select/>, [], {} jQuery
        minchar: 0, // minchars
        delay: 50, // for search
        fillin: false, // pre fill-in
        type: 'xml', // ajax data type
        width: 200, // width
        top: false, // position top/bottom
        writable: true, // writeable/selectable
        values: false, // store values
        partial: false,

        // events, please use 'self' instead of 'this'
        onSelect: function () {
        },
        onSetup: function () {
        },
        onKeyPress: function () {
        },
        onSuggest: function () {
        },
        onError: function () {
        },
        onSuccess: function () {
        },
        onDisplay: function () {
        },

        init: function (ac, options) {
            var self = $.extend(this, options);
            self.container = $('<div/>')
                .css({ width: self.width })
                .addClass('ac_conteiner')
                .insertBefore(ac);
            self.ac = $(ac)
                .attr({ autocomplete: 'off' })
                .blur(function () {
                    clearTimeout(self.close);
                    self.close = setTimeout(function () {
                        self.ul.hide();
                    }, 300);
                }) // IE bug self.ul[.hide()] = undefined
                .css({ width: (self.width - 22).toString() }) // 18 img + 2 margin + 2 border
                .addClass('ac_input')
                .appendTo(self.container);
            self.img = $('<div/>')
                .click(function () {
                    clearTimeout(self.close);
                    self.scroll();
                    self.ul.toggle();
                    self.ac.focus();
                })
                .addClass('ac_img')
                .appendTo(self.container);
                self.ul = $('<div/>')
                .css({ width: self.width, marginTop: $(ac).outerHeight(true) })
                .addClass('ac_results')
                .appendTo(self.container)
                .click(function () {
                    self.select();
                    self.ac.focus();
                }).mousedown(function () {
                    setTimeout(function () {
                        clearTimeout(self.close);
                        self.ac.focus();
                    }, 50); // IE scroll bug
                });

            $(window).bind('resize load', function () {
                var c = self.container;
                self.ul.css({
                    width: self.width,
                    top: (self.top ?
                            c.offset().top - self.ul.height() - parseInt(c.css('border-top-width')) :
                            c.offset().top + c.height() + parseInt(c.css('border-bottom-width'))),
                    left: (c.offset().left + parseInt(c.css('border-left-width')))
                });
            });

            if ($.browser.mozilla)
                self.ac.bind('keyup', self, self.process);
            else
                self.ac.bind('keydown', self, self.process);

            self.onSetup.apply(self, arguments);

            if (self.fillin)
                self.suggest('hide');
        },

        process: function (e) {
            var self = e.data, len = self.ac.val().length;
            self.onKeyPress.apply(self, arguments);

            if (/^(27|38|40|13|9)$/.test(e.keyCode) && self.ul.is(':visible')) {
                switch (e.keyCode) {
                    case 27: // escape
                        self.ul.hide();
                        break;
                    case 38: // up
                        self.prev();
                        break;
                    case 40: // down
                        self.next();
                        break;
                    case 13: // return
                        e.preventDefault();
                    case 9:  // tab
                        self.select();
                        break;
                }
            } else if (!/^9$/.test(e.keyCode) && (len != self.chars || !len)) {
                self.chars = len;
                if (self.timeout)
                    clearTimeout(self.timeout);
                self.timeout = setTimeout(function () {
                    self.suggest('show');
                }, self.delay);
            }
        },

        get: function () {
            var self = this;
            return self.ul.children('.ac_over');
        },

        prev: function () {
            var self = this, current = self.get(), prev = current.prev();
            if (current.length) {
                current.removeClass('ac_over');
                if (prev.length)
                    prev.addClass('ac_over');
            }
            if (!current.length || !prev.length) {
                self.ul.children(':last').addClass('ac_over');
            }
            self.scroll();
        },

        next: function () {
            var self = this, current = self.get(), next = current.next();
            if (current.length) {
                current.removeClass('ac_over');
                if (next.length)
                    next.addClass('ac_over');
            }
            if (!current.length || !next.length)
                self.ul.children(':first').addClass('ac_over');
            self.scroll();
        },

        scroll: function () {
            var self = this, current = self.get();
            if (!current.length)
                return; // quick return
            var el = current.get(0), list = self.ul.get(0); // dont scroll after click on document :(
            if (el.offsetTop + el.offsetHeight > list.scrollTop + list.clientHeight)
                list.scrollTop = el.offsetTop + el.offsetHeight - list.clientHeight;
            else if (el.offsetTop < list.scrollTop)
                list.scrollTop = el.offsetTop;
        },

        select: function () {
            var self = this, current = self.get();
            if (current) {
                self.ac.val(current.text());
                self.ul.hide();
                self.onSelect.apply(self, arguments);
            }
        },

        suggest: function (show) {
            var self = this, mask = $.trim(self.ac.val());
            self.ul.hide();

            if (mask.length >= self.minchar) {
                self.onSuggest.apply(self, arguments);
                if (self.check(mask) && !$.isFunction(self.url))
                    self.prepare(self.grab(mask), mask)[show]();
                else if (self.url) // use ajax
                    $.ajax({ type: "GET", data: { mask: mask },
                        url: $.isFunction(self.url) ? self.url(self) : self.url,
                        success: function (xml) {
                            self.onSuccess.apply(self, arguments);
                            self.prepare(xml, mask)[show]();
                        },
                        error: function () {
                            self.onError.apply(self, arguments);
                        },
                        dataType: self.type
                    });
                else if (self.source) // use source
                    self.prepare(self.source, mask)[show]();
            } else {
                self.ul.empty();
            }
        },

        check: function (mask) {
            var self = this;
            if (self.cache[mask])
                return true; // quick return
            if (self.partial)
                return false;
            mask = mask.toLowerCase();
            for (var it in self.cache)
                if (it && !mask.indexOf(it.toLowerCase()))
                    return true;
            return false;
        },

        grab: function (mask) {
            var self = this, map = [], array = [];
            if (self.cache[mask])
                return self.cache[mask]; // quick return
            for (var it in self.cache)
                array.push(it);
            array = array.reverse();
            mask = mask.toLowerCase();
            for (var item in array)
                if (!mask.indexOf(array[item].toLowerCase())) {
                    for (var word in self.cache[array[item]])
                        if (!self.cache[array[item]][word].toLowerCase().indexOf(mask) > -1)
                            map.push(self.cache[array[item]][word]);
                    break;
                }
            return map;
        },

        prepare: function (xml, mask) {
            var self = this, select = $(xml);
            self.store[mask] = '';
            self.cache[mask] = [];
            if (!self.store[mask]) {
                if (select.context) // use selectbox or ajax result
                    select.find("option").each(function (i, n) {
                        var t = $(n).text();
                        self.cache[mask].push(t);
                        self.store[mask] += self.mark(t, mask);
                        if (self.values && !self.pairs[t]) {
                            self.pairs[t] = $(n).attr("value");
                        }
                    });
                else // use array or array-like object
                    $.each(xml, self.dataHandler(mask));
            }

            if (!self.writable && !self.cache[mask].length) {
                setTimeout(function () {
                    var val = self.ac.val();
                    self.ac.val(val.substring(0, val.length - 1));
                    self.chars--;
                }, 50);
                return self.ul;
            }
            return self.display(self.store[mask]);
        },

        dataHandler: function (mask) {
            var self = this;
            return function (i, n) {
                self.cache[mask].push(n);
                self.store[mask] += self.mark(n, mask);
                if (self.values && !self.pairs[n])
                    self.pairs[n] = i;
            };
        },

        mark: function (text, mask) {
            var i = text.toLowerCase().indexOf(mask.toLowerCase());
            return i > -1 ?
                '<div>' + text.replace(new RegExp(mask, 'ig'), function (mask) {
                    return '<span class="ac_match">' + mask + '</span>';
                }) + '</div>' : '';
        },

        display: function (list) {
            var self = this;
            self.onDisplay.apply(self, arguments);
            if (!list)
                return self.ul.empty().end().end(); // fake
            return self.ul.html(list).children('div').mouseover(function () {
                $(this).siblings().removeClass('ac_over').end().addClass('ac_over');
            }).eq(0).addClass('ac_over').end().end(); // ul
        }
    };

    $.fn.autocomplete = function (options) {
        this.each(function () {
            if (this.tagName == 'SELECT' && !this.multiple) {
                var select = $(this),
                    input = $("<input type='text'/>")
                        .addClass(select.attr('class'))
                        .attr('tabindex', select.attr('tabindex'))
                	.attr('id', 'ac_' + select.attr('id'));

                select.after(input).hide(); // add text input and hide the select

                $("label[for='" + select.attr('id') + "']")
                    .attr('for', 'ac_' + select.attr('id'));

                new ac(input, $.extend({source: this, values: true, fillin: true, writable: false, onSelect: function () { select.val(this.pairs[this.ac.val()]); } }, options)); } else if (this.tagName == 'INPUT') { new ac(this, options); } }); return this; };})(jQuery);


CSS样式
.ac_conteiner {
	border: 1px solid #abadb3;
	height: 20px;
}

.ac_input {
	text-indent:2px;
	font-size:1em;
	font-family:sans-serif;
	float: left;
	padding:1px;
	border: 0 !important;
	height: 16px;
	margin: 1px;
}

.ac_img {
	width: 18px;
	background: url('/Scripts/jqueryui/autocomplete/down.png');
	height: 20px;
	float:right;
	cursor: pointer;
}

.ac_results {
	border: 1px solid gray;
	background-color: white;
	padding: 0;
	margin: 0;
	list-style: none;
	position: absolute;
	z-index: 10000;
	display: none;
	overflow-x: hidden;
	overflow-y: auto;
	height: 100px;
	margin-left: -1px !important; /*all*/
	_margin-left:-3px !important; /*ie8*/
	_margin-left:-1px;            /*ie6*/
	_margin-top: -1px !important; /*ieX*/
}

.ac_results div {
	font-size:1em;
	font-family:sans-serif;
	padding-left: 2px;
	white-space: nowrap;
}

.ac_over {
	cursor: pointer;
	background-color: #3399ff;
	color: #fff;
}

.ac_match {
	text-decoration: underline;
	color: inherit;
	margin: 0;
	padding: 0;
}

html:first-child .ac_results {
/* opera <9.5 does not support css3 property overflow-y */
	overflow: auto;
}


用法参考:http://mabp.kiev.ua/content/2008/04/08/autocomplete/


 类似资料: