|
1 | 1 | /**
|
2 |
| -* Ajax Autocomplete for jQuery, version 1.3.0 |
| 2 | +* Ajax Autocomplete for jQuery, version 1.4.0 |
3 | 3 | * (c) 2017 Tomas Kirda
|
4 | 4 | *
|
5 | 5 | * Ajax Autocomplete for jQuery is freely distributable under the terms of an MIT-style license.
|
|
49 | 49 | UP: 38,
|
50 | 50 | RIGHT: 39,
|
51 | 51 | DOWN: 40
|
52 |
| - }; |
| 52 | + }, |
| 53 | + |
| 54 | + noop = $.noop; |
53 | 55 |
|
54 | 56 | function Autocomplete(el, options) {
|
55 |
| - var noop = $.noop, |
56 |
| - that = this, |
57 |
| - defaults = { |
58 |
| - ajaxSettings: {}, |
59 |
| - autoSelectFirst: false, |
60 |
| - appendTo: document.body, |
61 |
| - serviceUrl: null, |
62 |
| - lookup: null, |
63 |
| - onSelect: null, |
64 |
| - width: 'auto', |
65 |
| - minChars: 1, |
66 |
| - maxHeight: 300, |
67 |
| - deferRequestBy: 0, |
68 |
| - params: {}, |
69 |
| - formatResult: Autocomplete.formatResult, |
70 |
| - formatGroup: Autocomplete.formatGroup, |
71 |
| - delimiter: null, |
72 |
| - zIndex: 9999, |
73 |
| - type: 'GET', |
74 |
| - noCache: false, |
75 |
| - onSearchStart: noop, |
76 |
| - onSearchComplete: noop, |
77 |
| - onSearchError: noop, |
78 |
| - preserveInput: false, |
79 |
| - containerClass: 'autocomplete-suggestions', |
80 |
| - tabDisabled: false, |
81 |
| - dataType: 'text', |
82 |
| - currentRequest: null, |
83 |
| - triggerSelectOnValidInput: true, |
84 |
| - preventBadQueries: true, |
85 |
| - lookupFilter: function (suggestion, originalQuery, queryLowerCase) { |
86 |
| - return suggestion.value.toLowerCase().indexOf(queryLowerCase) !== -1; |
87 |
| - }, |
88 |
| - paramName: 'query', |
89 |
| - transformResult: function (response) { |
90 |
| - return typeof response === 'string' ? $.parseJSON(response) : response; |
91 |
| - }, |
92 |
| - showNoSuggestionNotice: false, |
93 |
| - noSuggestionNotice: 'No results', |
94 |
| - orientation: 'bottom', |
95 |
| - forceFixPosition: false |
96 |
| - }; |
| 57 | + var that = this; |
97 | 58 |
|
98 | 59 | // Shared variables:
|
99 | 60 | that.element = el;
|
|
102 | 63 | that.badQueries = [];
|
103 | 64 | that.selectedIndex = -1;
|
104 | 65 | that.currentValue = that.element.value;
|
105 |
| - that.intervalId = 0; |
| 66 | + that.timeoutId = null; |
106 | 67 | that.cachedResponse = {};
|
107 |
| - that.onChangeInterval = null; |
| 68 | + that.onChangeTimeout = null; |
108 | 69 | that.onChange = null;
|
109 | 70 | that.isLocal = false;
|
110 | 71 | that.suggestionsContainer = null;
|
111 | 72 | that.noSuggestionsContainer = null;
|
112 |
| - that.options = $.extend({}, defaults, options); |
| 73 | + that.options = $.extend({}, Autocomplete.defaults, options); |
113 | 74 | that.classes = {
|
114 | 75 | selected: 'autocomplete-selected',
|
115 | 76 | suggestion: 'autocomplete-suggestion'
|
|
127 | 88 |
|
128 | 89 | $.Autocomplete = Autocomplete;
|
129 | 90 |
|
130 |
| - Autocomplete.formatResult = function (suggestion, currentValue) { |
131 |
| - // Do not replace anything if there current value is empty |
| 91 | + Autocomplete.defaults = { |
| 92 | + ajaxSettings: {}, |
| 93 | + autoSelectFirst: false, |
| 94 | + appendTo: document.body, |
| 95 | + serviceUrl: null, |
| 96 | + lookup: null, |
| 97 | + onSelect: null, |
| 98 | + width: 'auto', |
| 99 | + minChars: 1, |
| 100 | + maxHeight: 300, |
| 101 | + deferRequestBy: 0, |
| 102 | + params: {}, |
| 103 | + formatResult: _formatResult, |
| 104 | + formatGroup: _formatGroup, |
| 105 | + delimiter: null, |
| 106 | + zIndex: 9999, |
| 107 | + type: 'GET', |
| 108 | + noCache: false, |
| 109 | + onSearchStart: noop, |
| 110 | + onSearchComplete: noop, |
| 111 | + onSearchError: noop, |
| 112 | + preserveInput: false, |
| 113 | + containerClass: 'autocomplete-suggestions', |
| 114 | + tabDisabled: false, |
| 115 | + dataType: 'text', |
| 116 | + currentRequest: null, |
| 117 | + triggerSelectOnValidInput: true, |
| 118 | + preventBadQueries: true, |
| 119 | + lookupFilter: _lookupFilter, |
| 120 | + paramName: 'query', |
| 121 | + transformResult: _transformResult, |
| 122 | + showNoSuggestionNotice: false, |
| 123 | + noSuggestionNotice: 'No results', |
| 124 | + orientation: 'bottom', |
| 125 | + forceFixPosition: false |
| 126 | + }; |
| 127 | + |
| 128 | + function _lookupFilter(suggestion, originalQuery, queryLowerCase) { |
| 129 | + return suggestion.value.toLowerCase().indexOf(queryLowerCase) !== -1; |
| 130 | + }; |
| 131 | + |
| 132 | + function _transformResult(response) { |
| 133 | + return typeof response === 'string' ? $.parseJSON(response) : response; |
| 134 | + }; |
| 135 | + |
| 136 | + function _formatResult(suggestion, currentValue) { |
| 137 | + // Do not replace anything if the current value is empty |
132 | 138 | if (!currentValue) {
|
133 | 139 | return suggestion.value;
|
134 | 140 | }
|
|
144 | 150 | .replace(/<(\/?strong)>/g, '<$1>');
|
145 | 151 | };
|
146 | 152 |
|
147 |
| - Autocomplete.formatGroup = function (suggestion, category) { |
148 |
| - return '<div class="autocomplete-group"><strong>' + category + '</strong></div>'; |
| 153 | + function _formatGroup(suggestion, category) { |
| 154 | + return '<div class="autocomplete-group">' + category + '</div>'; |
149 | 155 | };
|
150 | 156 |
|
151 | 157 | Autocomplete.prototype = {
|
152 | 158 |
|
153 |
| - killerFn: null, |
154 |
| - |
155 | 159 | initialize: function () {
|
156 | 160 | var that = this,
|
157 | 161 | suggestionSelector = '.' + that.classes.suggestion,
|
|
162 | 166 | // Remove autocomplete attribute to prevent native suggestions:
|
163 | 167 | that.element.setAttribute('autocomplete', 'off');
|
164 | 168 |
|
165 |
| - that.killerFn = function (e) { |
166 |
| - if (!$(e.target).closest('.' + that.options.containerClass).length) { |
167 |
| - that.killSuggestions(); |
168 |
| - that.disableKillerFn(); |
169 |
| - } |
170 |
| - }; |
171 |
| - |
172 | 169 | // html() deals with many types: htmlString or Element or Array or jQuery
|
173 | 170 | that.noSuggestionsContainer = $('<div class="autocomplete-no-suggestion"></div>')
|
174 | 171 | .html(this.options.noSuggestionNotice).get(0);
|
|
195 | 192 | container.children('.' + selected).removeClass(selected);
|
196 | 193 | });
|
197 | 194 |
|
| 195 | + |
198 | 196 | // Listen for click event on suggestions list:
|
199 | 197 | container.on('click.autocomplete', suggestionSelector, function () {
|
200 | 198 | that.select($(this).data('index'));
|
201 |
| - return false; |
202 | 199 | });
|
203 | 200 |
|
| 201 | + container.on('click.autocomplete', function () { |
| 202 | + clearTimeout(that.blurTimeoutId); |
| 203 | + }) |
| 204 | + |
204 | 205 | that.fixPositionCapture = function () {
|
205 | 206 | if (that.visible) {
|
206 | 207 | that.fixPosition();
|
|
228 | 229 | },
|
229 | 230 |
|
230 | 231 | onBlur: function () {
|
231 |
| - this.enableKillerFn(); |
| 232 | + var that = this; |
| 233 | + |
| 234 | + // If user clicked on a suggestion, hide() will |
| 235 | + // be canceled, otherwise close suggestions |
| 236 | + that.blurTimeoutId = setTimeout(function () { |
| 237 | + that.hide(); |
| 238 | + }, 200); |
232 | 239 | },
|
233 | 240 |
|
234 | 241 | abortAjax: function () {
|
|
276 | 283 | disable: function () {
|
277 | 284 | var that = this;
|
278 | 285 | that.disabled = true;
|
279 |
| - clearInterval(that.onChangeInterval); |
| 286 | + clearTimeout(that.onChangeTimeout); |
280 | 287 | that.abortAjax();
|
281 | 288 | },
|
282 | 289 |
|
|
344 | 351 | $container.css(styles);
|
345 | 352 | },
|
346 | 353 |
|
347 |
| - enableKillerFn: function () { |
348 |
| - var that = this; |
349 |
| - $(document).on('click.autocomplete', that.killerFn); |
350 |
| - }, |
351 |
| - |
352 |
| - disableKillerFn: function () { |
353 |
| - var that = this; |
354 |
| - $(document).off('click.autocomplete', that.killerFn); |
355 |
| - }, |
356 |
| - |
357 |
| - killSuggestions: function () { |
358 |
| - var that = this; |
359 |
| - that.stopKillSuggestions(); |
360 |
| - that.intervalId = window.setInterval(function () { |
361 |
| - if (that.visible) { |
362 |
| - // No need to restore value when |
363 |
| - // preserveInput === true, |
364 |
| - // because we did not change it |
365 |
| - if (!that.options.preserveInput) { |
366 |
| - that.el.val(that.currentValue); |
367 |
| - } |
368 |
| - |
369 |
| - that.hide(); |
370 |
| - } |
371 |
| - |
372 |
| - that.stopKillSuggestions(); |
373 |
| - }, 50); |
374 |
| - }, |
375 |
| - |
376 |
| - stopKillSuggestions: function () { |
377 |
| - window.clearInterval(this.intervalId); |
378 |
| - }, |
379 |
| - |
380 | 354 | isCursorAtEnd: function () {
|
381 | 355 | var that = this,
|
382 | 356 | valLength = that.el.val().length,
|
|
467 | 441 | return;
|
468 | 442 | }
|
469 | 443 |
|
470 |
| - clearInterval(that.onChangeInterval); |
| 444 | + clearTimeout(that.onChangeTimeout); |
471 | 445 |
|
472 | 446 | if (that.currentValue !== that.el.val()) {
|
473 | 447 | that.findBestHint();
|
474 | 448 | if (that.options.deferRequestBy > 0) {
|
475 | 449 | // Defer lookup in case when value changes very quickly:
|
476 |
| - that.onChangeInterval = setInterval(function () { |
| 450 | + that.onChangeTimeout = setTimeout(function () { |
477 | 451 | that.onValueChange();
|
478 | 452 | }, that.options.deferRequestBy);
|
479 | 453 | } else {
|
|
493 | 467 | (options.onInvalidateSelection || $.noop).call(that.element);
|
494 | 468 | }
|
495 | 469 |
|
496 |
| - clearInterval(that.onChangeInterval); |
| 470 | + clearTimeout(that.onChangeTimeout); |
497 | 471 | that.currentValue = value;
|
498 | 472 | that.selectedIndex = -1;
|
499 | 473 |
|
|
558 | 532 | ajaxSettings;
|
559 | 533 |
|
560 | 534 | options.params[options.paramName] = q;
|
561 |
| - params = options.ignoreParams ? null : options.params; |
562 | 535 |
|
563 | 536 | if (options.onSearchStart.call(that.element, options.params) === false) {
|
564 | 537 | return;
|
565 | 538 | }
|
566 | 539 |
|
| 540 | + params = options.ignoreParams ? null : options.params; |
| 541 | + |
567 | 542 | if ($.isFunction(options.lookup)){
|
568 | 543 | options.lookup(q, function (data) {
|
569 | 544 | that.suggestions = data.suggestions;
|
|
640 | 615 |
|
641 | 616 | that.visible = false;
|
642 | 617 | that.selectedIndex = -1;
|
643 |
| - clearInterval(that.onChangeInterval); |
| 618 | + clearTimeout(that.onChangeTimeout); |
644 | 619 | $(that.suggestionsContainer).hide();
|
645 | 620 | that.signalHint(null);
|
646 | 621 | },
|
|
718 | 693 |
|
719 | 694 | noSuggestions: function() {
|
720 | 695 | var that = this,
|
| 696 | + beforeRender = that.options.beforeRender, |
721 | 697 | container = $(that.suggestionsContainer),
|
722 | 698 | noSuggestionsContainer = $(that.noSuggestionsContainer);
|
723 | 699 |
|
|
726 | 702 | // Some explicit steps. Be careful here as it easy to get
|
727 | 703 | // noSuggestionsContainer removed from DOM if not detached properly.
|
728 | 704 | noSuggestionsContainer.detach();
|
729 |
| - container.empty(); // clean suggestions if any |
| 705 | + |
| 706 | + // clean suggestions if any |
| 707 | + container.empty(); |
730 | 708 | container.append(noSuggestionsContainer);
|
731 | 709 |
|
| 710 | + if ($.isFunction(beforeRender)) { |
| 711 | + beforeRender.call(that.element, container, that.suggestions); |
| 712 | + } |
| 713 | + |
732 | 714 | that.fixPosition();
|
733 | 715 |
|
734 | 716 | container.show();
|
|
862 | 844 | var that = this;
|
863 | 845 | that.hide();
|
864 | 846 | that.onSelect(i);
|
865 |
| - that.disableKillerFn(); |
866 | 847 | },
|
867 | 848 |
|
868 | 849 | moveUp: function () {
|
|
965 | 946 | dispose: function () {
|
966 | 947 | var that = this;
|
967 | 948 | that.el.off('.autocomplete').removeData('autocomplete');
|
968 |
| - that.disableKillerFn(); |
969 | 949 | $(window).off('resize.autocomplete', that.fixPositionCapture);
|
970 | 950 | $(that.suggestionsContainer).remove();
|
971 | 951 | }
|
972 | 952 | };
|
973 | 953 |
|
974 | 954 | // Create chainable jQuery plugin:
|
975 |
| - $.fn.autocomplete = $.fn.devbridgeAutocomplete = function (options, args) { |
| 955 | + $.fn.devbridgeAutocomplete = function (options, args) { |
976 | 956 | var dataKey = 'autocomplete';
|
977 | 957 | // If function invoked without argument return
|
978 | 958 | // instance of the first matched element:
|
|
998 | 978 | }
|
999 | 979 | });
|
1000 | 980 | };
|
| 981 | + |
| 982 | + // Don't overwrite if it already exists |
| 983 | + if (!$.fn.autocomplete) { |
| 984 | + $.fn.autocomplete = $.fn.devbridgeAutocomplete; |
| 985 | + } |
1001 | 986 | }));
|
0 commit comments