|
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