PageSourceSearch

https://les.media/js/jquery.autocomplete.js

js les.media collected 2026-09-24 09:15:34 UTC 33,033 bytes, 987 lines download raw bytes

1/**
2*  Ajax Autocomplete for jQuery, version %version%
3*  (c) 2015 Tomas Kirda
4*
5*  Ajax Autocomplete for jQuery is freely distributable under the terms of an MIT-style license.
6*  For details, see the web site: https://github.com/devbridge/jQuery-Autocomplete
7*/
8
9/*jslint  browser: true, white: true, single: true, this: true, multivar: true */
10/*global define, window, document, jQuery, exports, require */
11
12// Expose plugin as an AMD module if AMD loader is present:
13(function (factory) {
14    "use strict";
15    if (typeof define === 'function' && define.amd) {
16        // AMD. Register as an anonymous module.
17        define(['jquery'], factory);
18    } else if (typeof exports === 'object' && typeof require === 'function') {
19        // Browserify
20        factory(require('jquery'));
21    } else {
22        // Browser globals
23        factory(jQuery);
24    }
25}(function ($) {
26    'use strict';
27
28    var
29        utils = (function () {
30            return {
31                escapeRegExChars: function (value) {
32                    return value.replace(/[|\\{}()[\]^$+*?.]/g, "\\$&");
33                },
34                createNode: function (containerClass) {
35                    var div = document.createElement('div');
36                    div.className = containerClass;
37                    div.style.position = 'absolute';
38                    div.style.display = 'none';
39                    return div;
40                }
41            };
42        }()),
43
44        keys = {
45            ESC: 27,
46            TAB: 9,
47            RETURN: 13,
48            LEFT: 37,
49            UP: 38,
50            RIGHT: 39,
51            DOWN: 40
52        };
53
54    function Autocomplete(el, options) {
55        var noop = $.noop,
56            that = this,
57            defaults = {
58                ajaxSettings: {},
59                autoSelectFirst: false,
60                appendTo: document.body,
61                serviceUrl: null,
62                lookup: null,
63                onSelect: null,
64                width: 'auto',
65                minChars: 1,
66                maxHeight: 300,
67                deferRequestBy: 0,
68                params: {},
69                formatResult: Autocomplete.formatResult,
70                delimiter: null,
71                zIndex: 9999,
72                type: 'GET',
73                noCache: false,
74                onSearchStart: noop,
75                onSearchComplete: noop,
76                onSearchError: noop,
77                preserveInput: false,
78                containerClass: 'autocomplete-suggestions',
79                tabDisabled: false,
80                dataType: 'text',
81                currentRequest: null,
82                triggerSelectOnValidInput: true,
83                preventBadQueries: true,
84                lookupFilter: function (suggestion, originalQuery, queryLowerCase) {
85                    return suggestion.value.toLowerCase().indexOf(queryLowerCase) !== -1;
86                },
87                paramName: 'query',
88                transformResult: function (response) {
89                    return typeof response === 'string' ? $.parseJSON(response) : response;
90                },
91                showNoSuggestionNotice: false,
92                noSuggestionNotice: 'No results',
93                orientation: 'bottom',
94                forceFixPosition: false
95            };
96
97        // Shared variables:
98        that.element = el;
99        that.el = $(el);
100        that.suggestions = [];
101        that.badQueries = [];
102        that.selectedIndex = -1;
103        that.currentValue = that.element.value;
104        that.intervalId = 0;
105        that.cachedResponse = {};
106        that.onChangeInterval = null;
107        that.onChange = null;
108        that.isLocal = false;
109        that.suggestionsContainer = null;
110        that.noSuggestionsContainer = null;
111        that.options = $.extend({}, defaults, options);
112        that.classes = {
113            selected: 'autocomplete-selected',
114            suggestion: 'autocomplete-suggestion'
115        };
116        that.hint = null;
117        that.hintValue = '';
118        that.selection = null;
119
120        // Initialize and set options:
121        that.initialize();
122        that.setOptions(options);
123    }
124
125    Autocomplete.utils = utils;
126
127    $.Autocomplete = Autocomplete;
128
129    Autocomplete.formatResult = function (suggestion, currentValue) {
130        // Do not replace anything if there current value is empty
131        if (!currentValue) {
132            return suggestion.value;
133        }
134        
135        var pattern = '(' + utils.escapeRegExChars(currentValue) + ')';
136
137        return suggestion.value
138            .replace(new RegExp(pattern, 'gi'), '<strong>$1<\/strong>')
139            .replace(/&/g, '&amp;')
140            .replace(/</g, '&lt;')
141            .replace(/>/g, '&gt;')
142            .replace(/"/g, '&quot;')
143            .replace(/&lt;(\/?strong)&gt;/g, '<$1>');
144    };
145
146    Autocomplete.prototype = {
147
148        killerFn: null,
149
150        initialize: function () {
151            var that = this,
152                suggestionSelector = '.' + that.classes.suggestion,
153                selected = that.classes.selected,
154                options = that.options,
155                container;
156
157            // Remove autocomplete attribute to prevent native suggestions:
158            that.element.setAttribute('autocomplete', 'off');
159
160            that.killerFn = function (e) {
161                if (!$(e.target).closest('.' + that.options.containerClass).length) {
162                    that.killSuggestions();
163                    that.disableKillerFn();
164                }
165            };
166
167            // html() deals with many types: htmlString or Element or Array or jQuery
168            that.noSuggestionsContainer = $('<div class="autocomplete-no-suggestion"></div>')
169                                          .html(this.options.noSuggestionNotice).get(0);
170
171            that.suggestionsContainer = Autocomplete.utils.createNode(options.containerClass);
172
173            container = $(that.suggestionsContainer);
174
175            container.appendTo(options.appendTo);
176
177            // Only set width if it was provided:
178            if (options.width !== 'auto') {
179                container.width(options.width);
180            }
181
182            // Listen for mouse over event on suggestions list:
183            container.on('mouseover.autocomplete', suggestionSelector, function () {
184                that.activate($(this).data('index'));
185            });
186
187            // Deselect active element when mouse leaves suggestions container:
188            container.on('mouseout.autocomplete', function () {
189                that.selectedIndex = -1;
190                container.children('.' + selected).removeClass(selected);
191            });
192
193            // Listen for click event on suggestions list:
194            container.on('click.autocomplete', suggestionSelector, function () {
195                that.select($(this).data('index'));
196                return false;
197            });
198
199            that.fixPositionCapture = function () {
200                if (that.visible) {
201                    that.fixPosition();
202                }
203            };
204
205            $(window).on('resize.autocomplete', that.fixPositionCapture);
206
207            that.el.on('keydown.autocomplete', function (e) { that.onKeyPress(e); });
208            that.el.on('keyup.autocomplete', function (e) { that.onKeyUp(e); });
209            that.el.on('blur.autocomplete', function () { that.onBlur(); });
210            that.el.on('focus.autocomplete', function () { that.onFocus(); });
211            that.el.on('change.autocomplete', function (e) { that.onKeyUp(e); });
212            that.el.on('input.autocomplete', function (e) { that.onKeyUp(e); });
213        },
214
215        onFocus: function () {
216            var that = this;
217
218            that.fixPosition();
219
220            if (that.el.val().length >= that.options.minChars) {
221                that.onValueChange();
222            }
223        },
224
225        onBlur: function () {
226            this.enableKillerFn();
227        },
228        
229        abortAjax: function () {
230            var that = this;
231            if (that.currentRequest) {
232                that.currentRequest.abort();
233                that.currentRequest = null;
234            }
235        },
236
237        setOptions: function (suppliedOptions) {
238            var that = this,
239                options = that.options;
240
241            $.extend(options, suppliedOptions);
242
243            that.isLocal = $.isArray(options.lookup);
244
245            if (that.isLocal) {
246                options.lookup = that.verifySuggestionsFormat(options.lookup);
247            }
248
249            options.orientation = that.validateOrientation(options.orientation, 'bottom');
250
251            // Adjust height, width and z-index:
252            $(that.suggestionsContainer).css({
253                'max-height': options.maxHeight + 'px',
254                'width': options.width + 'px',
255                'z-index': options.zIndex
256            });
257        },
258
259
260        clearCache: function () {
261            this.cachedResponse = {};
262            this.badQueries = [];
263        },
264
265        clear: function () {
266            this.clearCache();
267            this.currentValue = '';
268            this.suggestions = [];
269        },
270
271        disable: function () {
272            var that = this;
273            that.disabled = true;
274            clearInterval(that.onChangeInterval);
275            that.abortAjax();
276        },
277
278        enable: function () {
279            this.disabled = false;
280        },
281
282        fixPosition: function () {
283            // Use only when container has already its content
284
285            var that = this,
286                $container = $(that.suggestionsContainer),
287                containerParent = $container.parent().get(0);
288            // Fix position automatically when appended to body.
289            // In other cases force parameter must be given.
290            if (containerParent !== document.body && !that.options.forceFixPosition) {
291                return;
292            }
293
294            // Choose orientation
295            var orientation = that.options.orientation,
296                containerHeight = $container.outerHeight(),
297                height = that.el.outerHeight(),
298                offset = that.el.offset(),
299                styles = { 'top': offset.top, 'left': offset.left };
300
301            if (orientation === 'auto') {
302                var viewPortHeight = $(window).height(),
303                    scrollTop = $(window).scrollTop(),
304                    topOverflow = -scrollTop + offset.top - containerHeight,
305                    bottomOverflow = scrollTop + viewPortHeight - (offset.top + height + containerHeight);
306
307                orientation = (Math.max(topOverflow, bottomOverflow) === topOverflow) ? 'top' : 'bottom';
308            }
309
310            if (orientation === 'top') {
311                styles.top += -containerHeight;
312            } else {
313                styles.top += height;
314            }
315
316            // If container is not positioned to body,
317            // correct its position using offset parent offset
318            if(containerParent !== document.body) {
319                var opacity = $container.css('opacity'),
320                    parentOffsetDiff;
321
322                    if (!that.visible){
323                        $container.css('opacity', 0).show();
324                    }
325
326                parentOffsetDiff = $container.offsetParent().offset();
327                styles.top -= parentOffsetDiff.top;
328                styles.left -= parentOffsetDiff.left;
329
330                if (!that.visible){
331                    $container.css('opacity', opacity).hide();
332                }
333            }
334
335            // -2px to account for suggestions border.
336            if (that.options.width === 'auto') {
337                styles.width = (that.el.outerWidth() - 2) + 'px';
338            }
339
340            $container.css(styles);
341        },
342
343        enableKillerFn: function () {
344            var that = this;
345            $(document).on('click.autocomplete', that.killerFn);
346        },
347
348        disableKillerFn: function () {
349            var that = this;
350            $(document).off('click.autocomplete', that.killerFn);
351        },
352
353        killSuggestions: function () {
354            var that = this;
355            that.stopKillSuggestions();
356            that.intervalId = window.setInterval(function () {
357                if (that.visible) {
358                    that.el.val(that.currentValue);
359                    that.hide();
360                }
361                
362                that.stopKillSuggestions();
363            }, 50);
364        },
365
366        stopKillSuggestions: function () {
367            window.clearInterval(this.intervalId);
368        },
369
370        isCursorAtEnd: function () {
371            var that = this,
372                valLength = that.el.val().length,
373                selectionStart = that.element.selectionStart,
374                range;
375
376            if (typeof selectionStart === 'number') {
377                return selectionStart === valLength;
378            }
379            if (document.selection) {
380                range = document.selection.createRange();
381                range.moveStart('character', -valLength);
382                return valLength === range.text.length;
383            }
384            return true;
385        },
386
387        onKeyPress: function (e) {
388            var that = this;
389
390            // If suggestions are hidden and user presses arrow down, display suggestions:
391            if (!that.disabled && !that.visible && e.which === keys.DOWN && that.currentValue) {
392                that.suggest();
393                return;
394            }
395
396            if (that.disabled || !that.visible) {
397                return;
398            }
399
400            switch (e.which) {
401                case keys.ESC:
402                    that.el.val(that.currentValue);
403                    that.hide();
404                    break;
405                case keys.RIGHT:
406                    if (that.hint && that.options.onHint && that.isCursorAtEnd()) {
407                        that.selectHint();
408                        break;
409                    }
410                    return;
411                case keys.TAB:
412                    if (that.hint && that.options.onHint) {
413                        that.selectHint();
414                        return;
415                    }
416                    if (that.selectedIndex === -1) {
417                        that.hide();
418                        return;
419                    }
420                    that.select(that.selectedIndex);
421                    if (that.options.tabDisabled === false) {
422                        return;
423                    }
424                    break;
425                case keys.RETURN:
426                    if (that.selectedIndex === -1) {
427                        that.hide();
428                        return;
429                    }
430                    that.select(that.selectedIndex);
431                    break;
432                case keys.UP:
433                    that.moveUp();
434                    break;
435                case keys.DOWN:
436                    that.moveDown();
437                    break;
438                default:
439                    return;
440            }
441
442            // Cancel event if function did not return:
443            e.stopImmediatePropagation();
444            e.preventDefault();
445        },
446
447        onKeyUp: function (e) {
448            var that = this;
449
450            if (that.disabled) {
451                return;
452            }
453
454            switch (e.which) {
455                case keys.UP:
456                case keys.DOWN:
457                    return;
458            }
459
460            clearInterval(that.onChangeInterval);
461
462            if (that.currentValue !== that.el.val()) {
463                that.findBestHint();
464                if (that.options.deferRequestBy > 0) {
465                    // Defer lookup in case when value changes very quickly:
466                    that.onChangeInterval = setInterval(function () {
467                        that.onValueChange();
468                    }, that.options.deferRequestBy);
469                } else {
470                    that.onValueChange();
471                }
472            }
473        },
474
475        onValueChange: function () {
476            var that = this,
477                options = that.options,
478                value = that.el.val(),
479                query = that.getQuery(value);
480
481            if (that.selection && that.currentValue !== query) {
482                that.selection = null;
483                (options.onInvalidateSelection || $.noop).call(that.element);
484            }
485
486            clearInterval(that.onChangeInterval);
487            that.currentValue = value;
488            that.selectedIndex = -1;
489
490            // Check existing suggestion for the match before proceeding:
491            if (options.triggerSelectOnValidInput && that.isExactMatch(query)) {
492                that.select(0);
493                return;
494            }
495
496            if (query.length < options.minChars) {
497                that.hide();
498            } else {
499                that.getSuggestions(query);
500            }
501        },
502
503        isExactMatch: function (query) {
504            var suggestions = this.suggestions;
505
506            return (suggestions.length === 1 && suggestions[0].value.toLowerCase() === query.toLowerCase());
507        },
508
509        getQuery: function (value) {
510            var delimiter = this.options.delimiter,
511                parts;
512
513            if (!delimiter) {
514                return value;
515            }
516            parts = value.split(delimiter);
517            return $.trim(parts[parts.length - 1]);
518        },
519
520        getSuggestionsLocal: function (query) {
521            var that = this,
522                options = that.options,
523                queryLowerCase = query.toLowerCase(),
524                filter = options.lookupFilter,
525                limit = parseInt(options.lookupLimit, 10),
526                data;
527
528            data = {
529                suggestions: $.grep(options.lookup, function (suggestion) {
530                    return filter(suggestion, query, queryLowerCase);
531                })
532            };
533
534            if (limit && data.suggestions.length > limit) {
535                data.suggestions = data.suggestions.slice(0, limit);
536            }
537
538            return data;
539        },
540
541        getSuggestions: function (q) {
542            var response,
543                that = this,
544                options = that.options,
545                serviceUrl = options.serviceUrl,
546                params,
547                cacheKey,
548                ajaxSettings;
549
550            options.params[options.paramName] = q;
551            params = options.ignoreParams ? null : options.params;
552
553            if (options.onSearchStart.call(that.element, options.params) === false) {
554                return;
555            }
556
557            if ($.isFunction(options.lookup)){
558                options.lookup(q, function (data) {
559                    that.suggestions = data.suggestions;
560                    that.suggest();
561                    options.onSearchComplete.call(that.element, q, data.suggestions);
562                });
563                return;
564            }
565
566            if (that.isLocal) {
567                response = that.getSuggestionsLocal(q);
568            } else {
569                if ($.isFunction(serviceUrl)) {
570                    serviceUrl = serviceUrl.call(that.element, q);
571                }
572                cacheKey = serviceUrl + '?' + $.param(params || {});
573                response = that.cachedResponse[cacheKey];
574            }
575
576            if (response && $.isArray(response.suggestions)) {
577                that.suggestions = response.suggestions;
578                that.suggest();
579                options.onSearchComplete.call(that.element, q, response.suggestions);
580            } else if (!that.isBadQuery(q)) {
581                that.abortAjax();
582
583                ajaxSettings = {
584                    url: serviceUrl,
585                    data: params,
586                    type: options.type,
587                    dataType: options.dataType
588                };
589
590                $.extend(ajaxSettings, options.ajaxSettings);
591
592                that.currentRequest = $.ajax(ajaxSettings).done(function (data) {
593                    var result;
594                    that.currentRequest = null;
595                    result = options.transformResult(data, q);
596                    that.processResponse(result, q, cacheKey);
597                    options.onSearchComplete.call(that.element, q, result.suggestions);
598                }).fail(function (jqXHR, textStatus, errorThrown) {
599                    options.onSearchError.call(that.element, q, jqXHR, textStatus, errorThrown);
600                });
601            } else {
602                options.onSearchComplete.call(that.element, q, []);
603            }
604        },
605
606        isBadQuery: function (q) {
607            if (!this.options.preventBadQueries){
608                return false;
609            }
610
611            var badQueries = this.badQueries,
612                i = badQueries.length;
613
614            while (i--) {
615                if (q.indexOf(badQueries[i]) === 0) {
616                    return true;
617                }
618            }
619
620            return false;
621        },
622
623        hide: function () {
624            var that = this,
625                container = $(that.suggestionsContainer);
626
627            if ($.isFunction(that.options.onHide) && that.visible) {
628                that.options.onHide.call(that.element, container);
629            }
630
631            that.visible = false;
632            that.selectedIndex = -1;
633            clearInterval(that.onChangeInterval);
634            $(that.suggestionsContainer).hide();
635            that.signalHint(null);
636        },
637
638        suggest: function () {
639            if (!this.suggestions.length) {
640                if (this.options.showNoSuggestionNotice) {
641                    this.noSuggestions();
642                } else {
643                    this.hide();
644                }
645                return;
646            }
647
648            var that = this,
649                options = that.options,
650                groupBy = options.groupBy,
651                formatResult = options.formatResult,
652                value = that.getQuery(that.currentValue),
653                className = that.classes.suggestion,
654                classSelected = that.classes.selected,
655                container = $(that.suggestionsContainer),
656                noSuggestionsContainer = $(that.noSuggestionsContainer),
657                beforeRender = options.beforeRender,
658                html = '',
659                category,
660                formatGroup = function (suggestion, index) {
661                        var currentCategory = suggestion.data[groupBy];
662
663                        if (category === currentCategory){
664                            return '';
665                        }
666
667                        category = currentCategory;
668
669                        return '<div class="autocomplete-group"><strong>' + category + '</strong></div>';
670                    };
671
672            if (options.triggerSelectOnValidInput && that.isExactMatch(value)) {
673                that.select(0);
674                return;
675            }
676
677            // Build suggestions inner HTML:
678            $.each(that.suggestions, function (i, suggestion) {
679                if (groupBy){
680                    html += formatGroup(suggestion, value, i);
681                }
682
683                html += '<div class="' + className + '" data-index="' + i + '">' + formatResult(suggestion, value, i) + '</div>';
684            });
685
686            this.adjustContainerWidth();
687
688            noSuggestionsContainer.detach();
689            container.html(html);
690
691            if ($.isFunction(beforeRender)) {
692                beforeRender.call(that.element, container, that.suggestions);
693            }
694
695            that.fixPosition();
696            container.show();
697
698            // Select first value by default:
699            if (options.autoSelectFirst) {
700                that.selectedIndex = 0;
701                container.scrollTop(0);
702                container.children('.' + className).first().addClass(classSelected);
703            }
704
705            that.visible = true;
706            that.findBestHint();
707        },
708
709        noSuggestions: function() {
710             var that = this,
711                 container = $(that.suggestionsContainer),
712                 noSuggestionsContainer = $(that.noSuggestionsContainer);
713
714            this.adjustContainerWidth();
715
716            // Some explicit steps. Be careful here as it easy to get
717            // noSuggestionsContainer removed from DOM if not detached properly.
718            noSuggestionsContainer.detach();
719            container.empty(); // clean suggestions if any
720            container.append(noSuggestionsContainer);
721
722            that.fixPosition();
723
724            container.show();
725            that.visible = true;
726        },
727
728        adjustContainerWidth: function() {
729            var that = this,
730                options = that.options,
731                width,
732                container = $(that.suggestionsContainer);
733
734            // If width is auto, adjust width before displaying suggestions,
735            // because if instance was created before input had width, it will be zero.
736            // Also it adjusts if input width has changed.
737            // -2px to account for suggestions border.
738            if (options.width === 'auto') {
739                width = that.el.outerWidth() - 2;
740                container.width(width > 0 ? width : 300);
741            }
742        },
743
744        findBestHint: function () {
745            var that = this,
746                value = that.el.val().toLowerCase(),
747                bestMatch = null;
748
749            if (!value) {
750                return;
751            }
752
753            $.each(that.suggestions, function (i, suggestion) {
754                var foundMatch = suggestion.value.toLowerCase().indexOf(value) === 0;
755                if (foundMatch) {
756                    bestMatch = suggestion;
757                }
758                return !foundMatch;
759            });
760
761            that.signalHint(bestMatch);
762        },
763
764        signalHint: function (suggestion) {
765            var hintValue = '',
766                that = this;
767            if (suggestion) {
768                hintValue = that.currentValue + suggestion.value.substr(that.currentValue.length);
769            }
770            if (that.hintValue !== hintValue) {
771                that.hintValue = hintValue;
772                that.hint = suggestion;
773                (this.options.onHint || $.noop)(hintValue);
774            }
775        },
776
777        verifySuggestionsFormat: function (suggestions) {
778            // If suggestions is string array, convert them to supported format:
779            if (suggestions.length && typeof suggestions[0] === 'string') {
780                return $.map(suggestions, function (value) {
781                    return { value: value, data: null };
782                });
783            }
784
785            return suggestions;
786        },
787
788        validateOrientation: function(orientation, fallback) {
789            orientation = $.trim(orientation || '').toLowerCase();
790
791            if($.inArray(orientation, ['auto', 'bottom', 'top']) === -1){
792                orientation = fallback;
793            }
794
795            return orientation;
796        },
797
798        processResponse: function (result, originalQuery, cacheKey) {
799            var that = this,
800                options = that.options;
801
802            result.suggestions = that.verifySuggestionsFormat(result.suggestions);
803
804            // Cache results if cache is not disabled:
805            if (!options.noCache) {
806                that.cachedResponse[cacheKey] = result;
807                if (options.preventBadQueries && !result.suggestions.length) {
808                    that.badQueries.push(originalQuery);
809                }
810            }
811
812            // Return if originalQuery is not matching current query:
813            if (originalQuery !== that.getQuery(that.currentValue)) {
814                return;
815            }
816
817            that.suggestions = result.suggestions;
818            that.suggest();
819        },
820
821        activate: function (index) {
822            var that = this,
823                activeItem,
824                selected = that.classes.selected,
825                container = $(that.suggestionsContainer),
826                children = container.find('.' + that.classes.suggestion);
827
828            container.find('.' + selected).removeClass(selected);
829
830            that.selectedIndex = index;
831
832            if (that.selectedIndex !== -1 && children.length > that.selectedIndex) {
833                activeItem = children.get(that.selectedIndex);
834                $(activeItem).addClass(selected);
835                return activeItem;
836            }
837
838            return null;
839        },
840
841        selectHint: function () {
842            var that = this,
843                i = $.inArray(that.hint, that.suggestions);
844
845            that.select(i);
846        },
847
848        select: function (i) {
849            var that = this;
850            that.hide();
851            that.onSelect(i);
852        },
853
854        moveUp: function () {
855            var that = this;
856
857            if (that.selectedIndex === -1) {
858                return;
859            }
860
861            if (that.selectedIndex === 0) {
862                $(that.suggestionsContainer).children().first().removeClass(that.classes.selected);
863                that.selectedIndex = -1;
864                that.el.val(that.currentValue);
865                that.findBestHint();
866                return;
867            }
868
869            that.adjustScroll(that.selectedIndex - 1);
870        },
871
872        moveDown: function () {
873            var that = this;
874
875            if (that.selectedIndex === (that.suggestions.length - 1)) {
876                return;
877            }
878
879            that.adjustScroll(that.selectedIndex + 1);
880        },
881
882        adjustScroll: function (index) {
883            var that = this,
884                activeItem = that.activate(index);
885
886            if (!activeItem) {
887                return;
888            }
889
890            var offsetTop,
891                upperBound,
892                lowerBound,
893                heightDelta = $(activeItem).outerHeight();
894
895            offsetTop = activeItem.offsetTop;
896            upperBound = $(that.suggestionsContainer).scrollTop();
897            lowerBound = upperBound + that.options.maxHeight - heightDelta;
898
899            if (offsetTop < upperBound) {
900                $(that.suggestionsContainer).scrollTop(offsetTop);
901            } else if (offsetTop > lowerBound) {
902                $(that.suggestionsContainer).scrollTop(offsetTop - that.options.maxHeight + heightDelta);
903            }
904
905            if (!that.options.preserveInput) {
906                that.el.val(that.getValue(that.suggestions[index].value));
907            }
908            that.signalHint(null);
909        },
910
911        onSelect: function (index) {
912            var that = this,
913                onSelectCallback = that.options.onSelect,
914                suggestion = that.suggestions[index];
915
916            that.currentValue = that.getValue(suggestion.value);
917
918            if (that.currentValue !== that.el.val() && !that.options.preserveInput) {
919                that.el.val(that.currentValue);
920            }
921
922            that.signalHint(null);
923            that.suggestions = [];
924            that.selection = suggestion;
925
926            if ($.isFunction(onSelectCallback)) {
927                onSelectCallback.call(that.element, suggestion);
928            }
929        },
930
931        getValue: function (value) {
932            var that = this,
933                delimiter = that.options.delimiter,
934                currentValue,
935                parts;
936
937            if (!delimiter) {
938                return value;
939            }
940
941            currentValue = that.currentValue;
942            parts = currentValue.split(delimiter);
943
944            if (parts.length === 1) {
945                return value;
946            }
947
948            return currentValue.substr(0, currentValue.length - parts[parts.length - 1].length) + value;
949        },
950
951        dispose: function () {
952            var that = this;
953            that.el.off('.autocomplete').removeData('autocomplete');
954            that.disableKillerFn();
955            $(window).off('resize.autocomplete', that.fixPositionCapture);
956            $(that.suggestionsContainer).remove();
957        }
958    };
959
960    // Create chainable jQuery plugin:
961    $.fn.autocomplete = $.fn.devbridgeAutocomplete = function (options, args) {
962        var dataKey = 'autocomplete';
963        // If function invoked without argument return
964        // instance of the first matched element:
965        if (!arguments.length) {
966            return this.first().data(dataKey);
967        }
968
969        return this.each(function () {
970            var inputElement = $(this),
971                instance = inputElement.data(dataKey);
972
973            if (typeof options === 'string') {
974                if (instance && typeof instance[options] === 'function') {
975                    instance[options](args);
976                }
977            } else {
978                // If instance already exists, destroy it:
979                if (instance && instance.dispose) {
980                    instance.dispose();
981                }
982                instance = new Autocomplete(this, options);
983                inputElement.data(dataKey, instance);
984            }
985        });
986    };
987}));

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.