PageSourceSearch

https://www.lareb.nl/js/jquery.autocomplete.js

js lareb.nl collected 2026-10-02 02:53:11 UTC 34,611 bytes, 1,001 lines download raw bytes

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

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.