PageSourceSearch

https://candidats.emploipublic.fr/resources/jquery-autocomplete/jquery.autocomplete.js

js emploipublic.fr collected 2026-10-02 10:15:17 UTC 33,196 bytes, 992 lines download raw bytes

1/**
2*  Ajax Autocomplete for jQuery, version 1.2.27
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.css('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            if (that.options.width === 'auto') {
336                styles.width = that.el.outerWidth() + 'px';
337            }
338
339            $container.css(styles);
340        },
341
342        enableKillerFn: function () {
343            var that = this;
344            $(document).on('click.autocomplete', that.killerFn);
345        },
346
347        disableKillerFn: function () {
348            var that = this;
349            $(document).off('click.autocomplete', that.killerFn);
350        },
351
352        killSuggestions: function () {
353            var that = this;
354            that.stopKillSuggestions();
355            that.intervalId = window.setInterval(function () {
356                if (that.visible) {
357                    // No need to restore value when 
358                    // preserveInput === true, 
359                    // because we did not change it
360                    if (!that.options.preserveInput) {
361                        that.el.val(that.currentValue);
362                    }
363
364                    that.hide();
365                }
366                
367                that.stopKillSuggestions();
368            }, 50);
369        },
370
371        stopKillSuggestions: function () {
372            window.clearInterval(this.intervalId);
373        },
374
375        isCursorAtEnd: function () {
376            var that = this,
377                valLength = that.el.val().length,
378                selectionStart = that.element.selectionStart,
379                range;
380
381            if (typeof selectionStart === 'number') {
382                return selectionStart === valLength;
383            }
384            if (document.selection) {
385                range = document.selection.createRange();
386                range.moveStart('character', -valLength);
387                return valLength === range.text.length;
388            }
389            return true;
390        },
391
392        onKeyPress: function (e) {
393            var that = this;
394
395            // If suggestions are hidden and user presses arrow down, display suggestions:
396            if (!that.disabled && !that.visible && e.which === keys.DOWN && that.currentValue) {
397                that.suggest();
398                return;
399            }
400
401            if (that.disabled || !that.visible) {
402                return;
403            }
404
405            switch (e.which) {
406                case keys.ESC:
407                    that.el.val(that.currentValue);
408                    that.hide();
409                    break;
410                case keys.RIGHT:
411                    if (that.hint && that.options.onHint && that.isCursorAtEnd()) {
412                        that.selectHint();
413                        break;
414                    }
415                    return;
416                case keys.TAB:
417                    if (that.hint && that.options.onHint) {
418                        that.selectHint();
419                        return;
420                    }
421                    if (that.selectedIndex === -1) {
422                        that.hide();
423                        return;
424                    }
425                    that.select(that.selectedIndex);
426                    if (that.options.tabDisabled === false) {
427                        return;
428                    }
429                    break;
430                case keys.RETURN:
431                    if (that.selectedIndex === -1) {
432                        that.hide();
433                        return;
434                    }
435                    that.select(that.selectedIndex);
436                    break;
437                case keys.UP:
438                    that.moveUp();
439                    break;
440                case keys.DOWN:
441                    that.moveDown();
442                    break;
443                default:
444                    return;
445            }
446
447            // Cancel event if function did not return:
448            e.stopImmediatePropagation();
449            e.preventDefault();
450        },
451
452        onKeyUp: function (e) {
453            var that = this;
454
455            if (that.disabled) {
456                return;
457            }
458
459            switch (e.which) {
460                case keys.UP:
461                case keys.DOWN:
462                    return;
463            }
464
465            clearInterval(that.onChangeInterval);
466
467            if (that.currentValue !== that.el.val()) {
468                that.findBestHint();
469                if (that.options.deferRequestBy > 0) {
470                    // Defer lookup in case when value changes very quickly:
471                    that.onChangeInterval = setInterval(function () {
472                        that.onValueChange();
473                    }, that.options.deferRequestBy);
474                } else {
475                    that.onValueChange();
476                }
477            }
478        },
479
480        onValueChange: function () {
481            var that = this,
482                options = that.options,
483                value = that.el.val(),
484                query = that.getQuery(value);
485
486            if (that.selection && that.currentValue !== query) {
487                that.selection = null;
488                (options.onInvalidateSelection || $.noop).call(that.element);
489            }
490
491            clearInterval(that.onChangeInterval);
492            that.currentValue = value;
493            that.selectedIndex = -1;
494
495            // Check existing suggestion for the match before proceeding:
496            if (options.triggerSelectOnValidInput && that.isExactMatch(query)) {
497                that.select(0);
498                return;
499            }
500
501            if (query.length < options.minChars) {
502                that.hide();
503            } else {
504                that.getSuggestions(query);
505            }
506        },
507
508        isExactMatch: function (query) {
509            var suggestions = this.suggestions;
510
511            return (suggestions.length === 1 && suggestions[0].value.toLowerCase() === query.toLowerCase());
512        },
513
514        getQuery: function (value) {
515            var delimiter = this.options.delimiter,
516                parts;
517
518            if (!delimiter) {
519                return value;
520            }
521            parts = value.split(delimiter);
522            return $.trim(parts[parts.length - 1]);
523        },
524
525        getSuggestionsLocal: function (query) {
526            var that = this,
527                options = that.options,
528                queryLowerCase = query.toLowerCase(),
529                filter = options.lookupFilter,
530                limit = parseInt(options.lookupLimit, 10),
531                data;
532
533            data = {
534                suggestions: $.grep(options.lookup, function (suggestion) {
535                    return filter(suggestion, query, queryLowerCase);
536                })
537            };
538
539            if (limit && data.suggestions.length > limit) {
540                data.suggestions = data.suggestions.slice(0, limit);
541            }
542
543            return data;
544        },
545
546        getSuggestions: function (q) {
547            var response,
548                that = this,
549                options = that.options,
550                serviceUrl = options.serviceUrl,
551                params,
552                cacheKey,
553                ajaxSettings;
554
555            options.params[options.paramName] = q;
556            params = options.ignoreParams ? null : options.params;
557
558            if (options.onSearchStart.call(that.element, options.params) === false) {
559                return;
560            }
561
562            if ($.isFunction(options.lookup)){
563                options.lookup(q, function (data) {
564                    that.suggestions = data.suggestions;
565                    that.suggest();
566                    options.onSearchComplete.call(that.element, q, data.suggestions);
567                });
568                return;
569            }
570
571            if (that.isLocal) {
572                response = that.getSuggestionsLocal(q);
573            } else {
574                if ($.isFunction(serviceUrl)) {
575                    serviceUrl = serviceUrl.call(that.element, q);
576                }
577                cacheKey = serviceUrl + '?' + $.param(params || {});
578                response = that.cachedResponse[cacheKey];
579            }
580
581            if (response && $.isArray(response.suggestions)) {
582                that.suggestions = response.suggestions;
583                that.suggest();
584                options.onSearchComplete.call(that.element, q, response.suggestions);
585            } else if (!that.isBadQuery(q)) {
586                that.abortAjax();
587
588                ajaxSettings = {
589                    url: serviceUrl,
590                    data: params,
591                    type: options.type,
592                    dataType: options.dataType
593                };
594
595                $.extend(ajaxSettings, options.ajaxSettings);
596
597                that.currentRequest = $.ajax(ajaxSettings).done(function (data) {
598                    var result;
599                    that.currentRequest = null;
600                    result = options.transformResult(data, q);
601                    that.processResponse(result, q, cacheKey);
602                    options.onSearchComplete.call(that.element, q, result.suggestions);
603                }).fail(function (jqXHR, textStatus, errorThrown) {
604                    options.onSearchError.call(that.element, q, jqXHR, textStatus, errorThrown);
605                });
606            } else {
607                options.onSearchComplete.call(that.element, q, []);
608            }
609        },
610
611        isBadQuery: function (q) {
612            if (!this.options.preventBadQueries){
613                return false;
614            }
615
616            var badQueries = this.badQueries,
617                i = badQueries.length;
618
619            while (i--) {
620                if (q.indexOf(badQueries[i]) === 0) {
621                    return true;
622                }
623            }
624
625            return false;
626        },
627
628        hide: function () {
629            var that = this,
630                container = $(that.suggestionsContainer);
631
632            if ($.isFunction(that.options.onHide) && that.visible) {
633                that.options.onHide.call(that.element, container);
634            }
635
636            that.visible = false;
637            that.selectedIndex = -1;
638            clearInterval(that.onChangeInterval);
639            $(that.suggestionsContainer).hide();
640            that.signalHint(null);
641        },
642
643        suggest: function () {
644            if (!this.suggestions.length) {
645                if (this.options.showNoSuggestionNotice) {
646                    this.noSuggestions();
647                } else {
648                    this.hide();
649                }
650                return;
651            }
652
653            var that = this,
654                options = that.options,
655                groupBy = options.groupBy,
656                formatResult = options.formatResult,
657                value = that.getQuery(that.currentValue),
658                className = that.classes.suggestion,
659                classSelected = that.classes.selected,
660                container = $(that.suggestionsContainer),
661                noSuggestionsContainer = $(that.noSuggestionsContainer),
662                beforeRender = options.beforeRender,
663                html = '',
664                category,
665                formatGroup = function (suggestion, index) {
666                        var currentCategory = suggestion.data[groupBy];
667
668                        if (category === currentCategory){
669                            return '';
670                        }
671
672                        category = currentCategory;
673
674                        return '<div class="autocomplete-group"><strong>' + category + '</strong></div>';
675                    };
676
677            if (options.triggerSelectOnValidInput && that.isExactMatch(value)) {
678                that.select(0);
679                return;
680            }
681
682            // Build suggestions inner HTML:
683            $.each(that.suggestions, function (i, suggestion) {
684                if (groupBy){
685                    html += formatGroup(suggestion, value, i);
686                }
687
688                html += '<div class="' + className + '" data-index="' + i + '">' + formatResult(suggestion, value, i) + '</div>';
689            });
690
691            this.adjustContainerWidth();
692
693            noSuggestionsContainer.detach();
694            container.html(html);
695
696            if ($.isFunction(beforeRender)) {
697                beforeRender.call(that.element, container, that.suggestions);
698            }
699
700            that.fixPosition();
701            container.show();
702
703            // Select first value by default:
704            if (options.autoSelectFirst) {
705                that.selectedIndex = 0;
706                container.scrollTop(0);
707                container.children('.' + className).first().addClass(classSelected);
708            }
709
710            that.visible = true;
711            that.findBestHint();
712        },
713
714        noSuggestions: function() {
715             var that = this,
716                 container = $(that.suggestionsContainer),
717                 noSuggestionsContainer = $(that.noSuggestionsContainer);
718
719            this.adjustContainerWidth();
720
721            // Some explicit steps. Be careful here as it easy to get
722            // noSuggestionsContainer removed from DOM if not detached properly.
723            noSuggestionsContainer.detach();
724            container.empty(); // clean suggestions if any
725            container.append(noSuggestionsContainer);
726
727            that.fixPosition();
728
729            container.show();
730            that.visible = true;
731        },
732
733        adjustContainerWidth: function() {
734            var that = this,
735                options = that.options,
736                width,
737                container = $(that.suggestionsContainer);
738
739            // If width is auto, adjust width before displaying suggestions,
740            // because if instance was created before input had width, it will be zero.
741            // Also it adjusts if input width has changed.
742            if (options.width === 'auto') {
743                width = that.el.outerWidth();
744                container.css('width', width > 0 ? width : 300);
745            }
746        },
747
748        findBestHint: function () {
749            var that = this,
750                value = that.el.val().toLowerCase(),
751                bestMatch = null;
752
753            if (!value) {
754                return;
755            }
756
757            $.each(that.suggestions, function (i, suggestion) {
758                var foundMatch = suggestion.value.toLowerCase().indexOf(value) === 0;
759                if (foundMatch) {
760                    bestMatch = suggestion;
761                }
762                return !foundMatch;
763            });
764
765            that.signalHint(bestMatch);
766        },
767
768        signalHint: function (suggestion) {
769            var hintValue = '',
770                that = this;
771            if (suggestion) {
772                hintValue = that.currentValue + suggestion.value.substr(that.currentValue.length);
773            }
774            if (that.hintValue !== hintValue) {
775                that.hintValue = hintValue;
776                that.hint = suggestion;
777                (this.options.onHint || $.noop)(hintValue);
778            }
779        },
780
781        verifySuggestionsFormat: function (suggestions) {
782            // If suggestions is string array, convert them to supported format:
783            if (suggestions.length && typeof suggestions[0] === 'string') {
784                return $.map(suggestions, function (value) {
785                    return { value: value, data: null };
786                });
787            }
788
789            return suggestions;
790        },
791
792        validateOrientation: function(orientation, fallback) {
793            orientation = $.trim(orientation || '').toLowerCase();
794
795            if($.inArray(orientation, ['auto', 'bottom', 'top']) === -1){
796                orientation = fallback;
797            }
798
799            return orientation;
800        },
801
802        processResponse: function (result, originalQuery, cacheKey) {
803            var that = this,
804                options = that.options;
805
806            result.suggestions = that.verifySuggestionsFormat(result.suggestions);
807
808            // Cache results if cache is not disabled:
809            if (!options.noCache) {
810                that.cachedResponse[cacheKey] = result;
811                if (options.preventBadQueries && !result.suggestions.length) {
812                    that.badQueries.push(originalQuery);
813                }
814            }
815
816            // Return if originalQuery is not matching current query:
817            if (originalQuery !== that.getQuery(that.currentValue)) {
818                return;
819            }
820
821            that.suggestions = result.suggestions;
822            that.suggest();
823        },
824
825        activate: function (index) {
826            var that = this,
827                activeItem,
828                selected = that.classes.selected,
829                container = $(that.suggestionsContainer),
830                children = container.find('.' + that.classes.suggestion);
831
832            container.find('.' + selected).removeClass(selected);
833
834            that.selectedIndex = index;
835
836            if (that.selectedIndex !== -1 && children.length > that.selectedIndex) {
837                activeItem = children.get(that.selectedIndex);
838                $(activeItem).addClass(selected);
839                return activeItem;
840            }
841
842            return null;
843        },
844
845        selectHint: function () {
846            var that = this,
847                i = $.inArray(that.hint, that.suggestions);
848
849            that.select(i);
850        },
851
852        select: function (i) {
853            var that = this;
854            that.hide();
855            that.onSelect(i);
856            that.disableKillerFn();
857        },
858
859        moveUp: function () {
860            var that = this;
861
862            if (that.selectedIndex === -1) {
863                return;
864            }
865
866            if (that.selectedIndex === 0) {
867                $(that.suggestionsContainer).children().first().removeClass(that.classes.selected);
868                that.selectedIndex = -1;
869                that.el.val(that.currentValue);
870                that.findBestHint();
871                return;
872            }
873
874            that.adjustScroll(that.selectedIndex - 1);
875        },
876
877        moveDown: function () {
878            var that = this;
879
880            if (that.selectedIndex === (that.suggestions.length - 1)) {
881                return;
882            }
883
884            that.adjustScroll(that.selectedIndex + 1);
885        },
886
887        adjustScroll: function (index) {
888            var that = this,
889                activeItem = that.activate(index);
890
891            if (!activeItem) {
892                return;
893            }
894
895            var offsetTop,
896                upperBound,
897                lowerBound,
898                heightDelta = $(activeItem).outerHeight();
899
900            offsetTop = activeItem.offsetTop;
901            upperBound = $(that.suggestionsContainer).scrollTop();
902            lowerBound = upperBound + that.options.maxHeight - heightDelta;
903
904            if (offsetTop < upperBound) {
905                $(that.suggestionsContainer).scrollTop(offsetTop);
906            } else if (offsetTop > lowerBound) {
907                $(that.suggestionsContainer).scrollTop(offsetTop - that.options.maxHeight + heightDelta);
908            }
909
910            if (!that.options.preserveInput) {
911                that.el.val(that.getValue(that.suggestions[index].value));
912            }
913            that.signalHint(null);
914        },
915
916        onSelect: function (index) {
917            var that = this,
918                onSelectCallback = that.options.onSelect,
919                suggestion = that.suggestions[index];
920
921            that.currentValue = that.getValue(suggestion.value);
922
923            if (that.currentValue !== that.el.val() && !that.options.preserveInput) {
924                that.el.val(that.currentValue);
925            }
926
927            that.signalHint(null);
928            that.suggestions = [];
929            that.selection = suggestion;
930
931            if ($.isFunction(onSelectCallback)) {
932                onSelectCallback.call(that.element, suggestion);
933            }
934        },
935
936        getValue: function (value) {
937            var that = this,
938                delimiter = that.options.delimiter,
939                currentValue,
940                parts;
941
942            if (!delimiter) {
943                return value;
944            }
945
946            currentValue = that.currentValue;
947            parts = currentValue.split(delimiter);
948
949            if (parts.length === 1) {
950                return value;
951            }
952
953            return currentValue.substr(0, currentValue.length - parts[parts.length - 1].length) + value;
954        },
955
956        dispose: function () {
957            var that = this;
958            that.el.off('.autocomplete').removeData('autocomplete');
959            that.disableKillerFn();
960            $(window).off('resize.autocomplete', that.fixPositionCapture);
961            $(that.suggestionsContainer).remove();
962        }
963    };
964
965    // Create chainable jQuery plugin:
966    $.fn.autocomplete = $.fn.devbridgeAutocomplete = function (options, args) {
967        var dataKey = 'autocomplete';
968        // If function invoked without argument return
969        // instance of the first matched element:
970        if (!arguments.length) {
971            return this.first().data(dataKey);
972        }
973
974        return this.each(function () {
975            var inputElement = $(this),
976                instance = inputElement.data(dataKey);
977
978            if (typeof options === 'string') {
979                if (instance && typeof instance[options] === 'function') {
980                    instance[options](args);
981                }
982            } else {
983                // If instance already exists, destroy it:
984                if (instance && instance.dispose) {
985                    instance.dispose();
986                }
987                instance = new Autocomplete(this, options);
988                inputElement.data(dataKey, instance);
989            }
990        });
991    };
992}));

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.