PageSourceSearch

https://promex.cz/js/autocomplete/jquery.autocomplete.js

js promex.cz collected 2026-10-10 01:14:26 UTC 36,605 bytes, 1,152 lines download raw bytes

1/**
2 * @fileOverview jquery-autocomplete, the jQuery Autocompleter
3 * @author <a href="mailto:[email protected]">Dylan Verheul</a>
4 * @version 2.4.4
5 * @requires jQuery 1.6+
6 * @license MIT | GPL | Apache 2.0, see LICENSE.txt
7 * @see https://github.com/dyve/jquery-autocomplete
8 */
9(function($) {
10    "use strict";
11
12    /**
13     * jQuery autocomplete plugin
14     * @param {object|string} options
15     * @returns (object} jQuery object
16     */
17    $.fn.autocomplete = function(options) {
18        var url;
19        if (arguments.length > 1) {
20            url = options;
21            options = arguments[1];
22            options.url = url;
23        } else if (typeof options === 'string') {
24            url = options;
25            options = { url: url };
26        }
27        var opts = $.extend({}, $.fn.autocomplete.defaults, options);
28        return this.each(function() {
29            var $this = $(this);
30            $this.data('autocompleter', new $.Autocompleter(
31                $this,
32                $.meta ? $.extend({}, opts, $this.data()) : opts
33            ));
34        });
35    };
36
37    /**
38     * Store default options
39     * @type {object}
40     */
41    $.fn.autocomplete.defaults = {
42        inputClass: 'acInput',
43        loadingClass: 'acLoading',
44        resultsClass: 'acResults',
45        selectClass: 'acSelect',
46        queryParamName: 'q',
47        extraParams: {},
48        remoteDataType: false,
49        lineSeparator: '\n',
50        cellSeparator: '|',
51        minChars: 2,
52        maxItemsToShow: 10,
53        delay: 200,
54        useCache: false,
55        maxCacheLength: 10,
56        matchSubset: true,
57        matchCase: false,
58        matchInside: true,
59        mustMatch: false,
60        selectFirst: false,
61        selectOnly: false,
62        showResult: null,
63        preventDefaultReturn: 1,
64        preventDefaultTab: 0,
65        autoFill: false,
66        filterResults: true,
67        filter: true,
68        sortResults: true,
69        sortFunction: null,
70        onItemSelect: null,
71        onNoMatch: null,
72        onFinish: null,
73        matchStringConverter: null,
74        beforeUseConverter: null,
75        autoWidth: 'min-width',
76        useDelimiter: false,
77        delimiterChar: ',',
78        delimiterKeyCode: 188,
79        processData: null,
80        onError: null,
81        enabled: true
82    };
83
84    /**
85     * Sanitize result
86     * @param {Object} result
87     * @returns {Object} object with members value (String) and data (Object)
88     * @private
89     */
90    var sanitizeResult = function(result) {
91        var value, data;
92        var type = typeof result;
93        if (type === 'string') {
94            value = result;
95            data = {};
96        } else if ($.isArray(result)) {
97            value = result[0];
98            data = result.slice(1);
99        } else if (type === 'object') {
100            value = result.value;
101            data = result.data;
102        }
103        value = String(value);
104        if (typeof data !== 'object') {
105            data = {};
106        }
107        return {
108            value: value,
109            data: data
110        };
111    };
112
113    /**
114     * Sanitize integer
115     * @param {mixed} value
116     * @param {Object} options
117     * @returns {Number} integer
118     * @private
119     */
120    var sanitizeInteger = function(value, stdValue, options) {
121        var num = parseInt(value, 10);
122        options = options || {};
123        if (isNaN(num) || (options.min && num < options.min)) {
124            num = stdValue;
125        }
126        return num;
127    };
128
129    /**
130     * Create partial url for a name/value pair
131     */
132    var makeUrlParam = function(name, value) {
133        return [name, encodeURIComponent(value)].join('=');
134    };
135
136    /**
137     * Build an url
138     * @param {string} url Base url
139     * @param {object} [params] Dictionary of parameters
140     */
141    var makeUrl = function(url, params) {
142        var urlAppend = [];
143        $.each(params, function(index, value) {
144            urlAppend.push(makeUrlParam(index, value));
145        });
146        if (urlAppend.length) {
147            url += url.indexOf('?') === -1 ? '?' : '&';
148            url += urlAppend.join('&');
149        }
150        return url;
151    };
152
153    /**
154     * Default sort filter
155     * @param {object} a
156     * @param {object} b
157     * @param {boolean} matchCase
158     * @returns {number}
159     */
160    var sortValueAlpha = function(a, b, matchCase) {
161        a = String(a.value);
162        b = String(b.value);
163        if (!matchCase) {
164            a = a.toLowerCase();
165            b = b.toLowerCase();
166        }
167        if (a > b) {
168            return 1;
169        }
170        if (a < b) {
171            return -1;
172        }
173        return 0;
174    };
175
176    /**
177     * Parse data received in text format
178     * @param {string} text Plain text input
179     * @param {string} lineSeparator String that separates lines
180     * @param {string} cellSeparator String that separates cells
181     * @returns {array} Array of autocomplete data objects
182     */
183    var plainTextParser = function(text, lineSeparator, cellSeparator) {
184        var results = [];
185        var i, j, data, line, value, lines;
186        // Be nice, fix linebreaks before splitting on lineSeparator
187        lines = String(text).replace('\r\n', '\n').split(lineSeparator);
188        for (i = 0; i < lines.length; i++) {
189            line = lines[i].split(cellSeparator);
190            data = [];
191            for (j = 0; j < line.length; j++) {
192                data.push(decodeURIComponent(encodeURIComponent(line[j])));
193            }
194            value = data.shift();
195            results.push({ value: value, data: data });
196        }
197        return results;
198    };
199
200    /**
201     * Autocompleter class
202     * @param {object} $elem jQuery object with one input tag
203     * @param {object} options Settings
204     * @constructor
205     */
206    $.Autocompleter = function($elem, options) {
207
208        /**
209         * Assert parameters
210         */
211        if (!$elem || !($elem instanceof $) || $elem.length !== 1 || $elem.get(0).tagName.toUpperCase() !== 'INPUT') {
212            throw new Error('Invalid parameter for jquery.Autocompleter, jQuery object with one element with INPUT tag expected.');
213        }
214
215        /**
216         * @constant Link to this instance
217         * @type object
218         * @private
219         */
220        var self = this;
221
222        /**
223         * @property {object} Options for this instance
224         * @public
225         */
226        this.options = options;
227
228        /**
229         * @property object Cached data for this instance
230         * @private
231         */
232        this.cacheData_ = {};
233
234        /**
235         * @property {number} Number of cached data items
236         * @private
237         */
238        this.cacheLength_ = 0;
239
240        /**
241         * @property {string}
241 Class name to mark selected item
242         * @private
243         */
244        this.selectClass_ = 'jquery-autocomplete-selected-item';
245
246        /**
247         * @property {number} Handler to activation timeout
248         * @private
249         */
250        this.keyTimeout_ = null;
251
252        /**
253         * @property {number} Handler to finish timeout
254         * @private
255         */
256        this.finishTimeout_ = null;
257
258        /**
259         * @property {number} Last key pressed in the input field (store for behavior)
260         * @private
261         */
262        this.lastKeyPressed_ = null;
263
264        /**
265         * @property {string} Last value processed by the autocompleter
266         * @private
267         */
268        this.lastProcessedValue_ = null;
269
270        /**
271         * @property {string} Last value selected by the user
272         * @private
273         */
274        this.lastSelectedValue_ = null;
275
276        /**
277         * @property {boolean} Is this autocompleter active (showing results)?
278         * @see showResults
279         * @private
280         */
281        this.active_ = false;
282
283        /**
284         * @property {boolean} Is this autocompleter allowed to finish on blur?
285         * @private
286         */
287        this.finishOnBlur_ = true;
288
289        /**
290         * Sanitize options
291         */
292        this.options.minChars = sanitizeInteger(this.options.minChars, $.fn.autocomplete.defaults.minChars, { min: 0 });
293        this.options.maxItemsToShow = sanitizeInteger(this.options.maxItemsToShow, $.fn.autocomplete.defaults.maxItemsToShow, { min: 0 });
294        this.options.maxCacheLength = sanitizeInteger(this.options.maxCacheLength, $.fn.autocomplete.defaults.maxCacheLength, { min: 1 });
295        this.options.delay = sanitizeInteger(this.options.delay, $.fn.autocomplete.defaults.delay, { min: 0 });
296        if (this.options.preventDefaultReturn != 2) {
297            this.options.preventDefaultReturn = this.options.preventDefaultReturn ? 1 : 0;
298        }
299        if (this.options.preventDefaultTab != 2) {
300            this.options.preventDefaultTab = this.options.preventDefaultTab ? 1 : 0;
301        }
302
303        /**
304         * Init DOM elements repository
305         */
306        this.dom = {};
307
308        /**
309         * Store the input element we're attached to in the repository
310         */
311        this.dom.$elem = $elem;
312
313        /**
314         * Switch off the native autocomplete and add the input class
315         */
316        this.dom.$elem.attr('autocomplete', 'off').addClass(this.options.inputClass);
317
318        /**
319         * Create DOM element to hold results, and force absolute position
320         */
321        this.dom.$results = $('<div></div>').hide().addClass(this.options.resultsClass).css({
322            position: 'absolute'
323        });
324        $('body').append(this.dom.$results);
325
326        /**
327         * Attach keyboard monitoring to $elem
328         */
329        $elem.keydown(function(e) {
330            self.lastKeyPressed_ = e.keyCode;
331            switch(self.lastKeyPressed_) {
332
333                case self.options.delimiterKeyCode: // comma = 188
334                    if (self.options.useDelimiter && self.active_) {
335                        self.selectCurrent();
336                    }
337                    break;
338
339                // ignore navigational & special keys
340                case 35: // end
341                case 36: // home
342                case 16: // shift
343                case 17: // ctrl
344                case 18: // alt
345                case 37: // left
346                case 39: // right
347                    break;
348
349                case 38: // up
350                    e.preventDefault();
351                    if (self.active_) {
352                        self.focusPrev();
353                    } else {
354                        self.activate();
355                    }
356                    return false;
357
358                case 40: // down
359                    e.preventDefault();
360                    if (self.active_) {
361                        self.focusNext();
362                    } else {
363                        self.activate();
364                    }
365                    return false;
366
367                case 9: // tab
368                    if (self.active_) {
369                        self.selectCurrent();
370                        if (self.options.preventDefaultTab) {
371                            e.preventDefault();
372                            return false;
373                        }
374                    }
375                    if (self.options.preventDefaultTab === 2) {
376                        e.preventDefault();
377                        return false;
378                    }
379                break;
380
381                case 13: // return
382                    if (self.active_) {
383                        self.selectCurrent();
384                        if (self.options.preventDefaultReturn) {
385                            e.preventDefault();
386                            return false;
387                        }
388                    }
389                    if (self.options.preventDefaultReturn === 2) {
390                        e.preventDefault();
391                        return false;
392                    }
393                break;
394
395                case 27: // escape
396                    if (self.active_) {
397                        e.preventDefault();
398                        self.deactivate(true);
399                        return false;
400                    }
401                break;
402
403                default:
404                    self.activate();
405
406            }
407        });
408
409        /**
410         * Attach paste event listener because paste may occur much later then keydown or even without a keydown at all
411         */
412        $elem.on('paste', function() {
413            self.activate();
414        });
415
416        /**
417         * Finish on blur event
418         * Use a timeout because instant blur gives race conditions
419         */
420        var onBlurFunction = function() {
421            self.deactivate(true);
422        }
423        $elem.blur(function() {
424            if (self.finishOnBlur_) {
425                self.finishTimeout_ = setTimeout(onBlurFunction, 200);
426            }
427        });
428        /**
429         * Catch a race condition on form submit
430         */
431        $elem.parents('form').on('submit', onBlurFunction);
432
433    };
434
435    /**
436     * Position output DOM elements
437     * @private
438     */
439    $.Autocompleter.prototype.position = function() {
440        var offset = this.dom.$elem.offset();
441        var height = this.dom.$results.outerHeight();
442        var totalHeight = $(window).outerHeight();
443        var inputBottom = offset.top + this.dom.$elem.outerHeight();
444        var bottomIfDown = inputBottom + height;
445        // Set autocomplete results at the bottom of input
446        var position = {top: inputBottom, left: offset.left};
447        if (bottomIfDown > totalHeight) {
448            // Try to set autocomplete results at the top of input
449            var topIfUp = offset.top - height;
450            if (topIfUp >= 0) {
451                position.top = topIfUp;
452            }
453        }
454        this.dom.$results.css(position);
455    };
456
457    /**
458     * Read from cache
459     * @private
460     */
461    $.Autocompleter.prototype.cacheRead = function(filter) {
462        var filterLength, searchLength, search, maxPos, pos;
463        if (this.options.useCache) {
464            filter = String(filter);
465            filterLength = filter.length;
466            if (this.options.matchSubset) {
467                searchLength = 1;
468            } else {
469                searchLength = filterLength;
470            }
471            while (searchLength <= filterLength) {
472                if (this.options.matchInside) {
473                    maxPos = filterLength - searchLength;
474                } else {
475                    maxPos = 0;
476                }
477                pos = 0;
478                while (pos <= maxPos) {
479                    search = filter.substr(0, searchLength);
480                    if (this.cacheData_[search] !== undefined) {
481                        return this.cacheData_[search];
482                    }
483                    pos++;
484                }
485                searchLength++;
486            }
487        }
488        return false;
489    };
490
491    /**
492     * Write to cache
493     * @private
494     */
495    $.Autocompleter.prototype.cacheWrite = function(filter, data) {
496        if (this.options.useCache) {
497            if (this.cacheLength_ >= this.options.maxCacheLength) {
498                this.cacheFlush();
499            }
500            filter = String(filter);
501            if (this.cacheData_[filter] !== undefined) {
502                this.cacheLength_++;
503            }
504            this.cacheData_[filter] = data;
505            return this.cacheData_[filter];
506        }
507        return false;
508    };
509
510    /**
511     * Flush cache
512     * @public
513     */
514    $.Autocompleter.prototype.cacheFlush = function() {
515        this.cacheData_ = {};
516        this.cacheLength_ = 0;
517    };
518
519    /**
520     * Call hook
521     * Note that all called hooks are passed the autocompleter object
522     * @param {string} hook
523     * @param data
524     * @returns Result of called hook, false if hook is undefined
525     */
526    $.Autocompleter.prototype.callHook = function(hook, data) {
527        var f = this.options[hook];
528        if (f && $.isFunction(f)) {
529            return f(data, this);
530        }
531        return false;
532    };
533
534    /**
535     * Set timeout to activate autocompleter
536     */
537    $.Autocompleter.prototype.activate = function() {
538        if (!this.options.enabled) return;
539        var self = this;
540        if (this.keyTimeout_) {
541            clearTimeout(this.keyTimeout_);
542        }
543        this.keyTimeout_ = setTimeout(function() {
544            self.activateNow();
545        }, this.options.delay);
546    };
547
548    /**
549     * Activate autocompleter immediately
550     */
551    $.Autocompleter.prototype.activateNow = function() {
552        var value = this.beforeUseConverter(this.dom.$elem.val());
553        if (value !== this.lastProcessedValue_ && value !== this.lastSelectedValue_) {
554            this.fetchData(value);
555        }
556    };
557
558    /**
559     * Get autocomplete data for a given value
560     * @param {string} value Value to base autocompletion on
561     * @private
562     */
563    $.Autocompleter.prototype.fetchData = function(value) {
564        var self = this;
565        var processResults = function(results, filter) {
566            if (self.options.processData) {
567                results = self.options.processData(results);
568            }
569            self.showResults(self.filterResults(results, filter), filter);
570        };
571        this.lastProcessedValue_ = value;
572        if (value.length < this.options.minChars) {
573            processResults([], value);
574        } else if (this.options.data) {
575            processResults(this.options.data, value);
576        } else {
577            this.fetchRemoteData(value, function(remoteData) {
578                processResults(remoteData, value);
579            });
580        }
581    };
582
583    /**
584     * Get remote autocomplete data for a given value
585     * @param {string} filter The filter to base remote data on
586     * @param {function} callback The function to call after data retrieval
587     * @private
588     */
589    $.Autocompleter.prototype.fetchRemoteData = function(filter, callback) {
590        var data = this.cacheRead(filter);
591        if (data) {
592            callback(data);
593        } else {
594            var self = this;
595            var dataType = self.options.remoteDataType === 'json' ? 'json' : 'text';
596            var ajaxCallback = function(data) {
597                var parsed = false;
598                if (data !== false) {
599                    parsed = self.parseRemoteData(data);
600                    self.cacheWrite(filter, parsed);
601                }
602                self.dom.$elem.removeClass(self.options.loadingClass);
603                callback(parsed);
604            };
605            this.dom.$elem.addClass(this.options.loadingClass);
606            $.ajax({
607                url: this.makeUrl(filter),
608                success: ajaxCallback,
609                error: function(jqXHR, textStatus, errorThrown) {
610                    if($.isFunction(self.options.onError)) {
611                        self.options.onError(jqXHR, textStatus, errorThrown);
612                    } else {
613                      ajaxCallback(false);
614                    }
615                },
616                dataType: dataType
617            });
618        }
619    };
620
621    /**
622     * Create or update an extra parameter for the remote request
623     * @param {string} name Parameter name
624     * @param {string} value Parameter value
625     * @public
626     */
627    $.Autocompleter.prototype.setExtraParam = function(name, value) {
628        var index = $.trim(String(name));
629        if (index) {
630            if (!this.options.extraParams) {
631                this.options.extraParams = {};
632            }
633            if (this.options.extraParams[index] !== value) {
634                this.options.extraParams[index] = value;
635                this.cacheFlush();
636            }
637        }
638
639        return this;
640    };
641
642    /**
643     * Build the url for a remote request
644     * If options.queryParamName === false, append query to url instead of using a GET parameter
645     * @param {string} param The value parameter to pass to the backend
646     * @returns {string} The finished url with parameters
647     */
648    $.Autocompleter.prototype.makeUrl = function(param) {
649        var self = this;
650        var url = this.options.url;
651        var params = $.extend({}, this.options.extraParams);
652
653        if (this.options.queryParamName === false) {
654            url += encodeURIComponent(param);
655        } else {
656            params[this.options.queryParamName] = param;
657        }
658
659        return makeUrl(url, params);
660    };
661
662    /**
663     * Parse data received from server
664     * @param remoteData Data received from remote server
665     * @returns {array} Parsed data
666     */
667    $.Autocompleter.prototype.parseRemoteData = function(remoteData) {
668        var remoteDataType;
669        var data = remoteData;
670        if (this.options.remoteDataType === 'json') {
671            remoteDataType = typeof(remoteData);
672            switch (remoteDataType) {
673                case 'object':
674                    data = remoteData;
675                    break;
676                case 'string':
677                    data = $.parseJSON(remoteData);
678                    break;
679                default:
680                    throw new Error("Unexpected remote data type: " + remoteDataType);
681            }
682            return data;
683        }
684        return plainTextParser(data, this.options.lineSeparator, this.options.cellSeparator);
685    };
686
687    /**
688     * Default filter for results
689     * @param {Object} result
690     * @param {String} filter
691     * @returns {boolean} Include this result
692     * @private
693     */
694    $.Autocompleter.prototype.defaultFilter = function(result, filter) {
695        if (!result.value) {
696            return false;
697        }
698        if (this.options.filterResults) {
699            var pattern = this.matchStringConverter(filter);
700            var testValue = this.matchStringConverter(result.value);
701            if (!this.options.matchCase) {
702                pattern = pattern.toLowerCase();
703                testValue = testValue.toLowerCase();
704            }
705            var patternIndex = testValue.indexOf(pattern);
706            if (this.options.matchInside) {
707                return patternIndex > -1;
708            } else {
709                return patternIndex === 0;
710            }
711        }
712        return true;
713    };
714
715    /**
716     * Filter result
717     * @param {Object} result
718     * @param {String} filter
719     * @returns {boolean} Include this result
720     * @private
721     */
722    $.Autocompleter.prototype.filterResult = function(result, filter) {
723        // No filter
724        if (this.options.filter === false) {
725            return true;
726        }
727        // Custom filter
728        if ($.isFunction(this.options.filter)) {
729            return this.options.filter(result, filter);
730        }
731        // Default filter
732        return this.defaultFilter(result, filter);
733    };
734
735    /**
736     * Filter results
737     * @param results
738     * @param filter
739     */
740    $.Autocompleter.prototype.filterResults = function(results, filter) {
741        var filtered = [];
742        var i, result;
743
744        for (i = 0; i < results.length; i++) {
745            result = sanitizeResult(results[i]);
746            if (this.filterResult(result, filter)) {
747                filtered.push(result);
748            }
749        }
750        if (this.options.sortResults) {
751            filtered = this.sortResults(filtered, filter);
752        }
753        if (this.options.maxItemsToShow > 0 && this.options.maxItemsToShow < filtered.length) {
754            filtered.length = this.options.maxItemsToShow;
755        }
756        return filtered;
757    };
758
759    /**
760     * Sort results
761     * @param results
762     * @param filter
763     */
764    $.Autocompleter.prototype.sortResults = function(results, filter) {
765        var self = this;
766        var sortFunction = this.options.sortFunction;
767        if (!$.isFunction(sortFunction)) {
768            sortFunction = function(a, b, f) {
769                return sortValueAlpha(a, b, self.options.matchCase);
770            };
771        }
772        results.sort(function(a, b) {
773            return sortFunction(a, b, filter, self.options);
774        });
775        return results;
776    };
777
778    /**
779     * Convert string before matching
780     * @param s
781     * @param a
782     * @param b
783     */
784    $.Autocompleter.prototype.matchStringConverter = function(s, a, b) {
785        var converter = this.options.matchStringConverter;
786        if ($.isFunction(converter)) {
787            s = converter(s, a, b);
788        }
789        return s;
790    };
791
792    /**
793     * Convert string before use
794     * @param {String} s
795     */
796    $.Autocompleter.prototype.beforeUseConverter = function(s) {
797        s = this.getValue(s);
798        var converter = this.options.beforeUseConverter;
799        if ($.isFunction(converter)) {
800            s = converter(s);
801        }
802        return s;
803    };
804
805    /**
806     * Enable finish on blur event
807     */
808    $.Autocompleter.prototype.enableFinishOnBlur = function() {
809        this.finishOnBlur_ = true;
810    };
811
812    /**
813     * Disable finish on blur event
814     */
815    $.Autocompleter.prototype.disableFinishOnBlur = function() {
816        this.finishOnBlur_ = false;
817    };
818
819    /**
820     * Create a results item (LI element) from a result
821     * @param result
822     */
823    $.Autocompleter.prototype.createItemFromResult = function(result) {
824        var self = this;
825        var $li = $('<li/>');
826        $li.html(this.showResult(result.value, result.data));
827        $li.data({value: result.value, data: result.data})
828            .click(function() {
829                self.selectItem($li);
830            })
831            .mousedown(self.disableFinishOnBlur)
832            .mouseup(self.enableFinishOnBlur)
833        ;
834        return $li;
835    };
836
837    /**
838     * Get all items from the results list
839     * @param result
840     */
841    $.Autocompleter.prototype.getItems = function() {
842        return $('>ul>li', this.dom.$results);
843    };
844
845    /**
846     * Show all results
847     * @param results
848     * @param filter
849     */
850    $.Autocompleter.prototype.showResults = function(results, filter) {
851        var numResults = results.length;
852        var self = this;
853        var $ul = $('<ul></ul>');
854        var i, result, $li, autoWidth, first = false, $first = false;
855
856        if (numResults) {
857            for (i = 0; i < numResults; i++) {
858                result = results[i];
859                $li = this.createItemFromResult(result);
860                $ul.append($li);
861                if (first === false) {
862                    first = String(result.value);
863                    $first = $li;
864                    $li.addClass(this.options.firstItemClass);
865                }
866                if (i === numResults - 1) {
867                    $li.addClass(this.options.lastItemClass);
868                }
869            }
870
871            this.dom.$results.html($ul).show();
872
873            // Always recalculate position since window size or
874            // input element location may have changed.
875            this.position();
876            if (this.options.autoWidth) {
877                autoWidth = this.dom.$elem.outerWidth() - this.dom.$results.outerWidth() + this.dom.$results.width();
878                this.dom.$results.css(this.options.autoWidth, autoWidth);
879            }
880            this.getItems().hover(
881                function() { self.focusItem(this); },
882                function() { /* void */ }
883            );
884            if (this.autoFill(first, filter) || this.options.selectFirst || (this.options.selectOnly && numResults === 1)) {
885                this.focusItem($first);
886            }
887            this.active_ = true;
888        } else {
889            this.hideResults();
890            this.active_ = false;
891        }
892    };
893
894    $.Autocompleter.prototype.showResult = function(value, data) {
895        if ($.isFunction(this.options.showResult)) {
896            return this.options.showResult(value, data);
897        } else {
898            return $('<p></p>').text(value).html();
899        }
900    };
901
902    $.Autocompleter.prototype.autoFill = function(value, filter) {
903        var lcValue, lcFilter, valueLength, filterLength;
904        if (this.options.autoFill && this.lastKeyPressed_ !== 8) {
905            lcValue = String(value).toLowerCase();
906            lcFilter = String(filter).toLowerCase();
907            valueLength = value.length;
908            filterLength = filter.length;
909            if (lcValue.substr(0, filterLength) === lcFilter) {
910                var d = this.getDelimiterOffsets();
911                var pad = d.start ? ' ' : ''; // if there is a preceding delimiter
912                this.setValue( pad + value );
913                var start = filterLength + d.start + pad.length;
914                var end = valueLength + d.start + pad.length;
915                this.selectRange(start, end);
916                return true;
917            }
918        }
919        return false;
920    };
921
922    $.Autocompleter.prototype.focusNext = function() {
923        this.focusMove(+1);
924    };
925
926    $.Autocompleter.prototype.focusPrev = function() {
927        this.focusMove(-1);
928    };
929
930    $.Autocompleter.prototype.focusMove = function(modifier) {
931        var $items = this.getItems();
932        modifier = sanitizeInteger(modifier, 0);
933        if (modifier) {
934            for (var i = 0; i < $items.length; i++) {
935                if ($($items[i]).hasClass(this.selectClass_)) {
936                    this.focusItem(i + modifier);
937                    return;
938                }
939            }
940        }
941        this.focusItem(0);
942    };
943
944    $.Autocompleter.prototype.focusItem = function(item) {
945        var $item, $items = this.getItems();
946        if ($items.length) {
947            $items.removeClass(this.selectClass_).removeClass(this.options.selectClass);
948            if (typeof item === 'number') {
949                if (item < 0) {
950                    item = 0;
951                } else if (item >= $items.length) {
952                    item = $items.length - 1;
953                }
954                $item = $($items[item]);
955            } else {
956                $item = $(item);
957            }
958            if ($item) {
959                $item.addClass(this.selectClass_).addClass(this.options.selectClass);
960            }
961        }
962    };
963
964    $.Autocompleter.prototype.selectCurrent = function() {
965        var $item = $('li.' + this.selectClass_, this.dom.$results);
966        if ($item.length === 1) {
967            this.selectItem($item);
968        } else {
969            this.deactivate(false);
970        }
971    };
972
973    $.Autocompleter.prototype.selectItem = function($li) {
974        var value = $li.data('value');
975        var data = $li.data('data');
976        var displayValue = this.displayValue(value, data);
977        var processedDisplayValue = this.beforeUseConverter(displayValue);
978        this.lastProcessedValue_ = processedDisplayValue;
979        this.lastSelectedValue_ = processedDisplayValue;
980        var d = this.getDelimiterOffsets();
981        var delimiter = this.options.delimiterChar;
982        var elem = this.dom.$elem;
983        var extraCaretPos = 0;
984        if ( this.options.useDelimiter ) {
985            // if there is a preceding delimiter, add a space after the delimiter
986            if ( elem.val().substring(d.start-1, d.start) == delimiter && delimiter != ' ' ) {
987                displayValue = ' ' + displayValue;
988            }
989            // if there is not already a delimiter trailing this value, add it
990            if ( elem.val().substring(d.end, d.end+1) != delimiter && this.lastKeyPressed_ != this.options.delimiterKeyCode ) {
991                displayValue = displayValue + delimiter;
992            } else {
993                // move the cursor after the existing trailing delimiter
994                extraCaretPos = 1;
995            }
996        }
997        this.setValue(displayValue);
998        this.setCaret(d.start + displayValue.length + extraCaretPos);
999        this.callHook('onItemSelect', { value: value, data: data });
1000        this.deactivate(true);
1001        elem.focus();
1002    };
1003
1004    $.Autocompleter.prototype.displayValue = function(value, data) {
1005        if ($.isFunction(this.options.displayValue)) {
1006            return this.options.displayValue(value, data);
1007        }
1008        return value;
1009    };
1010
1011    $.Autocompleter.prototype.hideResults = function() {
1012        this.dom.$results.hide();
1013    };
1014
1015    $.Autocompleter.prototype.deactivate = function(finish) {
1016        if (this.finishTimeout_) {
1017            clearTimeout(this.finishTimeout_);
1018        }
1019        if (this.keyTimeout_) {
1020            clearTimeout(this.keyTimeout_);
1021        }
1022        if (finish) {
1023            if (this.lastProcessedValue_ !== this.lastSelectedValue_) {
1024                if (this.options.mustMatch) {
1025                    this.setValue('');
1026                }
1027                this.callHook('onNoMatch');
1028            }
1029            if (this.active_) {
1030                this.callHook('onFinish');
1031            }
1032            this.lastKeyPressed_ = null;
1033            this.lastProcessedValue_ = null;
1034            this.lastSelectedValue_ = null;
1035            this.active_ = false;
1036        }
1037        this.hideResults();
1038    };
1039
1040    $.Autocompleter.prototype.selectRange = function(start, end) {
1041        var input = this.dom.$elem.get(0);
1042        if (input.setSelectionRange) {
1043            input.focus();
1044            input.setSelectionRange(start, end);
1045        } else if (input.createTextRange) {
1046            var range = input.createTextRange();
1047            range.collapse(true);
1048            range.moveEnd('character', end);
1049            range.moveStart('character', start);
1050            range.select();
1051        }
1052    };
1053
1054    /**
1055     * Move caret to position
1056     * @param {Number} pos
1057     */
1058    $.Autocompleter.prototype.setCaret = function(pos) {
1059        this.selectRange(pos, pos);
1060    };
1061
1062    /**
1063     * Get caret position
1064     */
1065    $.Autocompleter.prototype.getCaret = function() {
1066        var $elem = this.dom.$elem;
1067        var elem = $elem[0];
1068        var val, selection, range, start, end, stored_range;
1069        if (elem.createTextRange) { // IE
1070            selection = document.selection;
1071            if (elem.tagName.toLowerCase() != 'textarea') {
1072                val = $elem.val();
1073                range = selection.createRange().duplicate();
1074                range.moveEnd('character', val.length);
1075                if (range.text === '') {
1076                    start = val.length;
1077                } else {
1078                    start = val.lastIndexOf(range.text);
1079                }
1080                range = selection.createRange().duplicate();
1081                range.moveStart('character', -val.length);
1082                end = range.text.length;
1083            } else {
1084                range = selection.createRange();
1085                stored_range = range.duplicate();
1086                stored_range.moveToElementText(elem);
1087                stored_range.setEndPoint('EndToEnd', range);
1088                start = stored_range.text.length - range.text.length;
1089                end = start + range.text.length;
1090            }
1091        } else {
1092            start = $elem[0].selectionStart;
1093            end = $elem[0].selectionEnd;
1094        }
1095        return {
1096            start: start,
1097            end: end
1098        };
1099    };
1100
1101    /**
1102     * Set the value that is currently being autocompleted
1103     * @param {String} value
1104     */
1105    $.Autocompleter.prototype.setValue = function(value) {
1106        if ( this.options.useDelimiter ) {
1107            // set the substring between the current delimiters
1108            var val = this.dom.$elem.val();
1109            var d = this.getDelimiterOffsets();
1110            var preVal = val.substring(0, d.start);
1111            var postVal = val.substring(d.end);
1112            value = preVal + value + postVal;
1113        }
1114        this.dom.$elem.val(value);
1115    };
1116
1117    /**
1118     * Get the value currently being autocompleted
1119     * @param {String} value
1120     */
1121    $.Autocompleter.prototype.getValue = function(value) {
1122        if ( this.options.useDelimiter ) {
1123            var d = this.getDelimiterOffsets();
1124            return value.substring(d.start, d.end).trim();
1125        } else {
1126            return value;
1127        }
1128    };
1129
1130    /**
1131     * Get the offsets of the value currently being autocompleted
1132     */
1133    $.Autocompleter.prototype.getDelimiterOffsets = function() {
1134        var val = this.dom.$elem.val();
1135        if ( this.options.useDelimiter ) {
1136            var preCaretVal = val.substring(0, this.getCaret().start);
1137            var start = preCaretVal.lastIndexOf(this.options.delimiterChar) + 1;
1138            var postCaretVal = val.substring(this.getCaret().start);
1139            var end = postCaretVal.indexOf(this.options.delimiterChar);
1140            if ( end == -1 ) end = val.length;
1141            end += this.getCaret().start;
1142        } else {
1143            start = 0;
1144            end = val.length;
1145        }
1146        return {
1147            start: start,
1148            end: end
1149        };
1150    };
1151
1152})(jQuery);

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.