PageSourceSearch

https://clue.ai/static/js/jquery.autocompleter.js

js clue.ai collected 2026-10-05 11:56:10 UTC 31,328 bytes, 888 lines download raw bytes

1/* 
2 * Autocompleter v0.1.2.1 - 2014-05-23 
3 * Simple, easy, customisable and with cache support. 
4 * http://github.com/ArtemFitiskin/jquery-autocompleter 
5 * 
6 * Copyright 2014 Artem Fitiskin; MIT Licensed 
7 */ 
8
9;(function ($, window) {
10    "use strict";
11
12    var guid = 0,
13        ignoredKeyCode = [9, 13, 17, 19, 20, 27, 33, 34, 35, 36, 37, 39, 44, 92, 113, 114, 115, 118, 119, 120, 122, 123, 144, 145],
14        allowOptions = ['source', 'empty', 'limit', 'cache', 'focusOpen', 'selectFirst', 'changeWhenSelect', 'highlightMatches', 'ignoredKeyCode', 'customLabel', 'customValue', 'template', 'offset', 'combine', 'callback'],
15        userAgent = (window.navigator.userAgent||window.navigator.vendor||window.opera),
16        isFirefox = /Firefox/i.test(userAgent),
17        isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(userAgent),
18        isFirefoxMobile = (isFirefox && isMobile),
19        $body = null,
20        localStorageKey = 'autocompleterCache',
21        supportLocalStorage = (function () {
22            var supported = typeof window.localStorage !== 'undefined';
23            if (supported) {
24                try {
25                    localStorage.setItem("autocompleter", "autocompleter");
26                    localStorage.removeItem("autocompleter");
27                } catch (e) {
28                    supported = false;
29                }
30            }
31            return supported;
32        })();
33
34    /**
35	 * @options
36	 * @param source [(string|object)] <null> "URL to the server or a local object"
37	 * @param empty [boolean] <true> "Launch if value is empty"
38	 * @param limit [int] <10> "Number of results to be displayed"
39	 * @param customClass [array] <[]> "Array with custom classes for autocompleter element"
40	 * @param cache [boolean] <true> "Save xhr data to localStorage to avoid the repetition of requests"
41	 * @param focusOpen [boolean] <true>
41 "Launch autocompleter when input gets focus"
42	 * @param hint [boolean] <false> "Add hint to input with first matched label, correct styles should be installed"
43	 * @param selectFirst [boolean] <false> "If set to true, first element in autocomplete list will be selected automatically, ignore if changeWhenSelect is on"
44	 * @param changeWhenSelect [boolean] <true> "Allows to change input value using arrow keys navigation in autocomplete list"
45	 * @param highlightMatches [boolean] <false> "This option defines <strong> tag wrap for matches in autocomplete results"
46	 * @param ignoredKeyCode [array] <[]> "Array with ignorable keycodes"
47	 * @param customLabel [boolean] <false> "The name of object's property which will be used as a label"
48	 * @param customValue [boolean] <false> "The name of object's property which will be used as a value"
49     * @param template [(string|boolean)] <false> "Custom template for list items"
50     * @param offset [(string|boolean)] <false> "Source response offset, for example: response.items.posts"
51	 * @param combine [function] <$.noop> "Returns an object which extends ajax data. Useful if you want to pass some additional server options"
52	 * @param callback [function] <$.noop> "Select value callback function. Arguments: value, index"
53	 */
54    var options = {
55        source: null,
56        empty: true,
57        limit: 10,
58        customClass: [],
59        cache: true,
60        focusOpen: true,
61        hint: false,
62        selectFirst: false,
63        changeWhenSelect: true,
64        highlightMatches: false,
65        ignoredKeyCode: [],
66        customLabel: false,
67        customValue: false,
68        template: false,
69        offset: false,
70        combine: $.noop,
71        callback: $.noop
72    };
73
74    var publics = {
75
76        /**
77         * @method
78         * @name defaults
79         * @description Sets default plugin options
80         * @param opts [object] <{}> "Options object"
81         * @example $.autocompleter("defaults", opts);
82         */
83        defaults: function (opts) {
84            options = $.extend(options, opts || {});
85            return $(this);
86        },
87
88        /**
89         * @method
90         * @name option
91         * @description Open autocompleter list
92         */
93        option: function (properties) {
94            return $(this).each(function(i, input) {
95                var data = $(input).next(".autocompleter").data("autocompleter");
96
97                for (var property in properties) {
98                    if ($.inArray(property, allowOptions) !== -1) {
99                        data[property] = properties[property];
100                    }
101                }
102            });
103        },
104
105        /**
106         * @method
107         * @name open
108         * @description Open autocompleter list
109         */
110        open: function () {
111            return $(this).each(function(i, input) {
112                var data = $(input).next(".autocompleter").data("autocompleter");
113
114                if (data) {
115                    _open(null, data);
116                }
117            });
118        },
119
120        /**
121         * @method
122         * @name close
123         * @description Close autocompleter list
124         */
125        close: function () {
126            return $(this).each(function(i, input) {
127                var data = $(input).next(".autocompleter").data("autocompleter");
128
129                if (data) {
130                    _close(null, data);
131                }
132            });
133        },
134
135        /**
136         * @method
137         * @name clearCache
138         * @description Remove localStorage cache
139         */
140        clearCache: function () {
141            _deleteCache();
142        },
143
144        /**
145         * @method
146         * @name destroy
147         * @description Removes instance of plugin
148         * @example $(".target").autocompleter("destroy");
149         */
150        destroy: function () {
151            return $(this).each(function (i, input) {
152                var data = $(input).next(".autocompleter").data("autocompleter");
153
154                if (data) {
155                    // Abort xhr
156                    if (data.jqxhr) {
157                        data.jqxhr.abort();
158                    }
159
160                    // If has selected item & open - confirm it
161                    if (data.$autocompleter.hasClass("open")) {
162                        data.$autocompleter.find(".autocompleter-selected")
163                                            .trigger("click.autocompleter");
164                    }
165
166                    // Restore original autocomplete attr
167                    if(!data.originalAutocomplete) {
168                        data.$node.removeAttr("autocomplete");
169                    } else {
170                        data.$node.attr("autocomplete", data.originalAutocomplete);
171                    }
172
173                    // Remove autocompleter & unbind events
174                    data.$node.off(".autocompleter")
175                               .removeClass("autocompleter-node");
176                    data.$autocompleter.off(".autocompleter")
177                                         .remove();
178                }
179            });
180        }
181    };
182
183    /**
184     * @method private
185     * @name _init
186     * @description Initializes plugin
187     * @param opts [object] "Initialization options"
188     */
189    function _init(opts) {
190        // Local options
191        opts = $.extend({}, options, opts || {});
192
193        // Check for Body
194        if ($body === null) {
195            $body = $("body");
196        }
197
198        // Apply to each element
199        var $items = $(this);
200        for (var i = 0, count = $items.length; i < count; i++) {
201            _build($items.eq(i), opts);
202        }
203
204        return $items;
205    }
206
207    /**
208	 * @method private
209	 * @name _build
210	 * @description Builds each instance
211	 * @param $node [jQuery object] "Target jQuery object"
212	 * @param opts [object] <{}> "Options object"
213	 */
214    function _build($node, opts) {
215        if (!$node.hasClass("autocompleter-node")) {
216            // Extend options
217            opts = $.extend({}, opts, $node.data("autocompleter-options"));
218
219            var html = '<div class="autocompleter '+opts.customClass.join(' ')+'" id="autocompleter-'+(guid+1)+'">';
220                if (opts.hint) {
221                    html += '<div class="autocompleter-hint"></div>';
222                }
223                html += '<ul class="autocompleter-list"></ul>';
224                html += '</div>';
225
226            $node.addClass("autocompleter-node")
227                 .after(html);
228
229            var $autocompleter = $node.next(".autocompleter").eq(0);
230
231            // Set autocomplete to off for warn overlay
232            var originalAutocomplete = $node.attr("autocomplete");
233            $node.attr("autocomplete", "off");
234
235            // Store plugin data
236            var data = $.extend({
237                $node: $node,
238                $autocompleter: $autocompleter,
239                $selected: null,
240                $list: null,
241                index: -1,
242                hintText: false,
243                source: false,
244                jqxhr: false,
245                response: null,
246                focused: false,
247                query: '',
248                originalAutocomplete: originalAutocomplete,
249                guid: guid++
250            }, opts);
251
252            // Bind autocompleter events
253            data.$autocompleter.on("mousedown.autocompleter", ".autocompleter-item", data, _select)
254                                .data("autocompleter", data);
255
256            // Bind node events
257            data.$node.on("keyup.autocompleter", data, _onKeyup)
258                      .on("keydown.autocompleter", data, _onKeydownHelper)
259                      .on("focus.autocompleter", data, _onFocus)
260                      .on("blur.autocompleter", data, _onBlur)
261                      .on("mousedown.autocompleter", data, _onMousedown);
262        }
263    }
264
265    /**
266     * @method private
267     * @name _search
268     * @description Local search function, return best collation
269     * @param query [string] "Query string"
270     * @param source [object] "Source data"
271     * @param limit [integer] "Results length"
272     */
273    function _search(query, source, limit) {
274        var response = [];
275        query = query.toUpperCase();
276
277        if (source.length) {
278            for (var i = 0; i < 2; i++) {
279                for (var item in source) {
280                    if (response.length < limit) {
281                        switch (i) {
282                            case 0:
283                                if (source[item].label.toUpperCase().search(query) === 0) {
284                                    response.push(source[item]);
285                                    delete source[item];
286                                }
287                            break;
288
289                            case 1:
290                                if (source[item].label.toUpperCase().search(query) !== -1) {
291                                    response.push(source[item]);
292                                    delete source[item];
293                                }
294                            break;
295                        }
296                    }
297                }
298            }
299        }
300
301        return response;
302    }
303
304    /**
305     * @method private
306     * @name _launch
307     * @description Use source locally or create xhr
308     * @param data [object] "Instance data"
309     */
310    function _launch(data) {
311        data.query = $.trim(data.$node.val());
312
313        if (!data.empty && data.query.length === 0) {
314            _clear(data);
315            return;
316        } else {
317            if (typeof data.source === 'object') {
318                _clear(data);
319
320                // Local search
321                var search = _search(data.query, _clone(data.source), data.limit);
322                if (search.length) {
323                    _response(search, data);
324                }
325            } else {
326                if (data.jqxhr) {
327                    data.jqxhr.abort();
328                }
329
330                var ajaxData = $.extend({
331                    limit: data.limit,
332                    query: data.query
333                }, data.combine());
334
335                data.jqxhr = $.ajax({
336                    url:        data.source,
337                    dataType:   "json",
338                    data:       ajaxData,
339                    beforeSend: function (xhr) {
340                        data.$autocompleter.addClass('autocompleter-ajax');
341                        _clear(data);
342                        if (data.cache) {
343                            var stored = _getCache(this.url);
344                            if (stored) {
345                                xhr.abort();
346                                _response(stored, data);
347                            }
348                        }
349                    }
350                })
351                .done(function (response) {
352                    // Get subobject from responce
353                    if (data.offset) {
354                        response = _grab(response, data.offset);
355                    }
356                    if (data.cache) {
357                        _setCache(this.url, response);
358                    }
359                    _response(response, data);
360                })
361                .always(function () {
362                    data.$autocompleter.removeClass('autocompleter-ajax');
363                });
364            }
365        }
366    }
367
368    /**
369     * @method private
370     * @name _clear
371     * @param data [object] "Instance data"
372     */
373    function _clear(data) {
374        // Clear data
375        data.response = null;
376        data.$list = null;
377        data.$selected = null;
378        data.index = 0;
379        data.$autocompleter.find(".autocompleter-list").empty();
380        data.$autocompleter.find('.autocompleter-hint').removeClass('autocompleter-hint-show').empty();
381        data.hintText = false;
382
383        _close(null, data);
384    }
385
386    /**
387     * @method private
388     * @name _response
389     * @description Main source response function
390     * @param response [object] "Source data"
391     * @param data [object] "Instance data"
392     */
393    function _response(response, data) {
394        _buildList(response, data);
395
396        if (data.$autocompleter.hasClass('autocompleter-focus')) {
397            _open(null, data);
398        }
399    }
400
401    /**
402     * @method private
403     * @name _buildList
404     * @description Generate autocompleter-list and update instance data by source
405     * @param list [object] "Source data"
406     * @param data [object] "Instance data"
407     */
408    function _buildList(list, data) {
409        var menu = '';
410
411        for (var item = 0, count = list.length; item < count; item++) {
412            var classes = ["autocompleter-item"];
413
414            if (data.selectFirst && item === 0 && !data.changeWhenSelect) {
415                classes.push("autocompleter-item-selected");
416            }
417
418            var highlightReg = new RegExp(data.query, "gi");
419            var label = (data.customLabel && list[item][data.customLabel]) ? list[item][data.customLabel] : list[item].label;
420
421            var clear = label;
422
423            label = data.highlightMatches ? label.replace(highlightReg, "<strong>$&</strong>") : label;
424
425            var value = (data.customValue && list[item][data.customValue]) ? list[item][data.customValue] : list[item].value;
426
427            // Apply custom template
428            if (data.template) {
429                var template = data.template.replace(/({{ label }})/gi, label);
430
431                for (var property in list[item]) {
432                    if (list[item].hasOwnProperty(property)) {
433                        var regex = new RegExp('{{ '+ property +' }}', 'gi');
434                        template = template.replace(regex, list[item][property]);
435                    }
436                }
437
438                label = template;
439            }
440
441            if (value) {
442                menu += '<li data-value="'+value+'" data-label="'+clear+'" class="'+classes.join(' ')+'">'+label+'</li>';
443            } else {
444                menu += '<li data-label="'+clear+'" class="'+classes.join(' ')+'">'+label+'</li>';
445            }
446        }
447
448        // Set hint
449        if (list.length && data.hint) {
450            var hint = ( list[0].label.substr(0, data.query.length).toUpperCase() === data.query.toUpperCase() ) ? list[0].label : false;
451            if (hint && (data.query !== list[0].label)) {
452                var hintReg = new RegExp(data.query, "i");
453                var hintText = hint.replace(hintReg, "<span>"+data.query+"</span>");
454                data.$autocompleter.find('.autocompleter-hint').addClass('autocompleter-hint-show').html(hintText);
455                data.hintText = hintText;
456            }
457        }
458
459        // Update data
460        data.response = list;
461        data.$autocompleter.find(".autocompleter-list").html(menu);
462        data.$selected = (data.$autocompleter.find(".autocompleter-item-selected").length) ? data.$autocompleter.find(".autocompleter-item-selected") : null;
463        data.$list = (list.length) ? data.$autocompleter.find(".autocompleter-item") : null;
464        data.index = data.$selected ? data.$list.index(data.$selected) : -1;
465        data.$autocompleter.find(".autocompleter-item").each(function (i, j) {
466            $(j).data(data.response[i]);
467        });
468    }
469
470    /**
471     * @method private
472     * @name _onKeyup
473     * @description Keyup events in node, up/down autocompleter-list navigation, typing and enter button callbacks
474     * @param e [object] "Event data"
475     */
476    function _onKeyup(e) {
477        var data = e.data;
478        var code = e.keyCode ? e.keyCode : e.which;
479
480        if ( (code === 40 || code === 38) && data.$autocompleter.hasClass('autocompleter-show') ) {
481            // Arrows up & down
482            var len = data.$list.length,
483                next,
484                prev;
485
486            if (len) {
487                // Determine new index
488                if (len > 1) {
489                    if (data.index === len - 1) {
490                        next = data.changeWhenSelect ? -1 : 0;
491                        prev = data.index - 1;
492                    } else if (data.index === 0) {
493                        next = data.index + 1;
494                        prev = data.changeWhenSelect ? -1 : len - 1;
495                    } else if (data.index === -1) {
496                        next = 0;
497                        prev = len - 1;
498                    } else {
499                        next = data.index + 1;
500                        prev = data.index - 1;
501                    }
502                } else if (data.index === -1) {
503                    next = 0;
504                    prev = 0;
505                } else {
506                    prev = -1;
507                    next = -1;
508                }
509                data.index = (code === 40) ? next : prev;
510
511                // Update HTML
512                data.$list.removeClass("autocompleter-item-selected");
513                if (data.index !== -1) {
514                    data.$list.eq(data.index).addClass("autocompleter-item-selected");
515                }
516                data.$selected = data.$autocompleter.find(".autocompleter-item-selected").length ? data.$autocompleter.find(".autocompleter-item-selected") : null;
517                if (data.changeWhenSelect) {
518                    _setValue(data);
519                }
520            }
521        } else if ($.inArray(code, ignoredKeyCode) === -1 && $.inArray(code, data.ignoredKeyCode) === -1) {
522            // Typing
523            _launch(data);
524        }
525    }
526
527    /**
528     * @method private
529     * @name _onKeydownHelper
530     * @description Keydown events in node, up/down for prevent cursor moving and right arrow for hint
531     * @param e [object] "Event data"
532     */
533    function _onKeydownHelper(e) {
534        var code = e.keyCode ? e.keyCode : e.which;
535        var data = e.data;
536
537        if (code === 40 || code === 38 ) {
538            e.preventDefault();
539            e.stopPropagation();
540        } else if (code === 39) {
541            // Right arrow
542            if (data.hint && data.hintText && data.$autocompleter.find('.autocompleter-hint').hasClass('autocompleter-hint-show')) {
543                e.preventDefault();
544                e.stopPropagation();
545
546                var hintOrigin = data.$autocompleter.find(".autocompleter-item").length ? data.$autocompleter.find(".autocompleter-item").eq(0).attr('data-label') : false;
547                if (hintOrigin) {
548                    data.query = hintOrigin;
549                    _setHint(data);
550                }
551            }
552        } else if (code === 13) {
553            // Enter
554            if (data.$autocompleter.hasClass('autocompleter-show') && data.$selected) {
555                _select(e);
556            }
557        }
558    }
559
560    /**
561     * @method private
562     * @name _onFocus
563     * @description Handles instance focus
564     * @param e [object] "Event data"
565     * @param internal [boolean] "Called by plugin"
566     */
567    function _onFocus(e, internal) {
568        if (!internal) {
569            var data = e.data;
570
571            data.$autocompleter.addClass("autocompleter-focus");
572
573            if (!data.$node.prop("disabled") && !data.$autocompleter.hasClass('autocompleter-show')) {
574                if (data.focusOpen) {
575                    _launch(data);
576                    data.focused = true;
577                    setTimeout(function () {
578                        data.focused = false;
579                    }, 500);
580                }
581            }
582        }
583    }
584
585    /**
586     * @method private
587     * @name _onBlur
588     * @description Handles instance blur
589     * @param e [object] "Event data"
590     * @param internal [boolean] "Called by plugin"
591     */
592    function _onBlur(e, internal) {
593        e.preventDefault();
594        e.stopPropagation();
595
596        var data = e.data;
597
598        if (!internal) {
599            data.$autocompleter.removeClass("autocompleter-focus");
600            _close(e);
601        }
602    }
603
604    /**
605     * @method private
606     * @name _onMousedown
607     * @description Handles mousedown to node
608     * @param e [object] "Event data"
609     */
610    function _onMousedown(e) {
611        // Disable middle & right mouse click
612        if (e.type === "mousedown" && $.inArray(e.which, [2, 3]) !== -1) { return; }
613
614        var data = e.data;
615        if (data.$list && !data.focused) {
616            if (!data.$node.is(":disabled")) {
617                if (isMobile && !isFirefoxMobile) {
618                    var el = data.$select[0];
619                    if (window.document.createEvent) { // All
620                        var evt = window.document.createEvent("MouseEvents");
621                        evt.initMouseEvent("mousedown", false, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
622                        el.dispatchEvent(evt);
623                    } else if (el.fireEvent) { // IE
624                        el.fireEvent("onmousedown");
625                    }
626                } else {
627                    // Delegate intent
628                    if (data.$autocompleter.hasClass("autocompleter-closed")) {
629                        _open(e);
630                    } else if (data.$autocompleter.hasClass("autocompleter-show")) {
631                        _close(e);
632                    }
633                }
634            }
635        }
636    }
637
638    /**
639     * @method private
640     * @name _open
641     * @description Opens option set
642     * @param e [object] "Event data"
643     * @param instanceData [object] "Instance data"
644     */
645    function _open(e, instanceData) {
646        var data = e ? e.data : instanceData;
647
648        if (!data.$node.prop("disabled") && !data.$autocompleter.hasClass("autocompleter-show") && data.$list && data.$list.length ) {
649            data.$autocompleter.removeClass("autocompleter-closed").addClass("autocompleter-show");
650            $body.on("click.autocompleter-" + data.guid, ":not(.autocompleter-item)", data, _closeHelper);
651        }
652    }
653
654    /**
655     * @method private
656     * @name _closeHelper
657     * @description Determines if event target is outside instance before closing
658     * @param e [object] "Event data"
659     */
660    function _closeHelper(e) {
661        if ( $(e.target).hasClass('autocompleter-node') ) {
662            return;
663        }
664
665        if ($(e.currentTarget).parents(".autocompleter").length === 0) {
666            _close(e);
667        }
668    }
669
670    /**
671     * @method private
672     * @name _close
673     * @description Closes option set
674     * @param e [object] "Event data"
675     * @param instanceData [object] "Instance data"
676     */
677    function _close(e, instanceData) {
678        var data = e ? e.data : instanceData;
679
680        if (data.$autocompleter.hasClass("autocompleter-show")) {
681            data.$autocompleter.removeClass("autocompleter-show").addClass("autocompleter-closed");
682            $body.off(".autocompleter-" + data.guid);
683        }
684    }
685
686    /**
687     * @method private
688     * @name _select
689     * @description Select item from .autocompleter-list
690     * @param e [object] "Event data"
691     */
692    function _select(e) {
693        // Disable middle & right mouse click
694        if (e.type === "mousedown" && $.inArray(e.which, [2, 3]) !== -1) { return; }
695
696        var data = e.data;
697
698        e.preventDefault();
699        e.stopPropagation();
700
701        if (e.type === "mousedown" && $(this).length) {
702            data.$selected = $(this);
703            data.index = data.$list.index(data.$selected);
704        }
705
706        if (!data.$node.prop("disabled")) {
707            _close(e);
708            _update(data);
709
710            if (e.type === "click") {
711                data.$node.trigger("focus", [true]);
712            }
713        }
714    }
715
716    /**
717     * @method private
718     * @name _setHint
719     * @description Set autocompleter by hint
720     * @param data [object] "Instance data"
721     */
722    function _setHint(data) {
723        _setValue(data);
724        _handleChange(data);
725        _launch(data);
726    }
727
728    /**
729     * @method private
730     * @name _setValue
731     * @description Set value for native field
732     * @param data [object] "Instance data"
733     */
734    function _setValue(data) {
735        if (data.$selected) {
736            if (data.hintText && data.$autocompleter.find('.autocompleter-hint').hasClass('autocompleter-hint-show')) {
737                data.$autocompleter.find('.autocompleter-hint').removeClass('autocompleter-hint-show');
738            }
739            var value = data.$selected.attr('data-value') ? data.$selected.attr('data-value') : data.$selected.attr('data-label');
740            data.$node.val(value);
741        } else {
742            if (data.hintText && !data.$autocompleter.find('.autocompleter-hint').hasClass('autocompleter-hint-show')) {
743                data.$autocompleter.find('.autocompleter-hint').addClass('autocompleter-hint-show');
744            }
745            data.$node.val(data.query);
746        }
747    }
748
749    /**
750     * @method private
751     * @name _update
752     * @param data [object] "Instance data"
753     */
754    function _update(data) {
755        _setValue(data);
756        _handleChange(data);
757        _clear(data);
758    }
759
760    /**
761     * @method private
762     * @name _handleChange
763     * @description Trigger node change event and call the callback function
764     * @param data [object] "Instance data"
765     */
766    function _handleChange(data) {
767        data.callback.call(data.$autocompleter, data.$node.val(), data.index, data.response[data.index]);
768        data.$node.trigger("change");
769    }
770
771    /**
772     * @method private
773     * @name _grab
774     * @description Grab sub-object from object
775     * @param response [object] "Native object"
776     * @param offset [string] "Offset string"
777     */
778    function _grab(response, offset) {
779        offset = offset.split('.');
780        while ( response && offset.length ) {
781            response = response[offset.shift()];
782        }
783        return response;
784    }
785
786    /**
787     * @method private
788     * @name _getCache
789     * @description Store AJAX response in plugin cache
790     * @param url [string] "AJAX get query string"
791     * @param data [object] "AJAX response data"
792     */
793    function _setCache(url, data) {
794        if (!supportLocalStorage) { return; }
795        if (url && data) {
796            cache[url] = {
797                value: data
798            };
799
800            // Proccess to localStorage
801            try {
802                  localStorage.setItem(localStorageKey, JSON.stringify(cache));
803            } catch (e) {
804                  var code = e.code || e.number || e.message;
805                  if (code === 22) {
806                    _deleteCache();
807                  } else {
808                    throw(e);
809                  }
810            }
811        }
812    }
813
814    /**
815     * @method private
816     * @name _getCache
817     * @description Get cached data by url if exist
818     * @param url [string] "AJAX get query string"
819     */
820    function _getCache(url) {
821        if (!url) { return; }
822        var response = (cache[url] && cache[url].value) ? cache[url].value : false;
823        return response;
824    }
825
826    /**
827     * @method private
828     * @name _loadCache
829     * @description Load all plugin cache from localStorage
830     */
831    function _loadCache() {
832        if (!supportLocalStorage) { return; }
833        var json = localStorage.getItem(localStorageKey) || '{}';
834        return JSON.parse(json);
835    }
836
837    /**
838	 * @method private
839     * @name _deleteCache
840     * @description Delete all plugin cache from localStorage
841     */
842    function _deleteCache() {
843        try {
844            localStorage.removeItem(localStorageKey);
845            cache = _loadCache();
846        } catch (e) {
847            throw(e);
848        }
849    }
850
851    /**
852     * @method private
853     * @name _clone
854     * @description Clone JavaScript object
855     */
856    function _clone(obj) {
857        if (null === obj || "object" !== typeof obj) {
858            return obj;
859        }
860        var copy = obj.constructor();
861        for (var attr in obj) {
862            if (obj.hasOwnProperty(attr)) {
863                copy[attr] = obj[attr];
864            }
865        }
866        return copy;
867    }
868
869    // Load cache
870    var cache = _loadCache();
871
872    $.fn.autocompleter = function (method) {
873        if (publics[method]) {
874            return publics[method].apply(this, Array.prototype.slice.call(arguments, 1));
875        } else if (typeof method === 'object' || !method) {
876            return _init.apply(this, arguments);
877        }
878        return this;
879    };
880
881    $.autocompleter = function (method) {
882        if (method === "defaults") {
883            publics.defaults.apply(this, Array.prototype.slice.call(arguments, 1));
884        } else if (method === "clearCache") {
885            publics.clearCache.apply(this, null);
886        }
887    };
888})(jQuery, window);

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.