diff --git a/readme.md b/readme.md index 035efba..b3ddf33 100644 --- a/readme.md +++ b/readme.md @@ -32,10 +32,11 @@ The standard jquery.autocomplete.js file is around 2.7KB when minified via Closu * `type`: Ajax request type to get suggestions. Default: `GET`. * `noCache`: Boolean value indicating whether to cache suggestion results. Default `false`. * `onSearchStart`: `function (query) {}` called before ajax request. `this` is bound to input element. - * `onSearchComplete`: `function (query) {}` called after ajax response is processed. `this` is bound to input element. + * `onSearchComplete`: `function (query, suggestions) {}` called after ajax response is processed. `this` is bound to input element. `suggestions` is an array containing the results. * `onSearchError`: `function (query, jqXHR, textStatus, errorThrown) {}` called if ajax request fails. `this` is bound to input element. * `onInvalidateSelection`: `function () {}` called when input is altered after selection has been made. `this` is bound to input element. * `triggerSelectOnValidInput`: Boolean value indicating if `select` should be triggered if it matches suggestion. Default `true`. + * `preventBadQueries`: Boolean value indicating if it shoud prevent future ajax requests for queries with the same root if no results were returned. E.g. if `Jam` returns no suggestions, it will not fire for any future query that starts with `Jam`. Default `true`. * `beforeRender`: `function (container) {}` called before displaying the suggestions. You may manipulate suggestions DOM before it is displayed. * `tabDisabled`: Default `false`. Set to true to leave the cursor in the input field after the user tabs to select a suggestion. * `paramName`: Default `query`. The name of the request parameter that contains the query. diff --git a/spec/autocompleteBehavior.js b/spec/autocompleteBehavior.js index d0aca94..80718cf 100644 --- a/spec/autocompleteBehavior.js +++ b/spec/autocompleteBehavior.js @@ -136,12 +136,15 @@ describe('Autocomplete', function () { it('Should execute onSearchComplete', function () { var input = document.createElement('input'), completeQuery, + mockupSuggestion = { value: 'A', data: 'A' }, + resultSuggestions, ajaxExecuted = false, url = '/test-completed', autocomplete = new $.Autocomplete(input, { serviceUrl: url, - onSearchComplete: function (query) { + onSearchComplete: function (query, suggestions) { completeQuery = query; + resultSuggestions = suggestions; } }); @@ -153,7 +156,7 @@ describe('Autocomplete', function () { var query = settings.data.query, response = { query: query, - suggestions: [] + suggestions: [mockupSuggestion] }; this.responseText = JSON.stringify(response); } @@ -169,6 +172,8 @@ describe('Autocomplete', function () { runs(function () { expect(ajaxExecuted).toBe(true); expect(completeQuery).toBe('A'); + expect(resultSuggestions[0].value).toBe('A'); + expect(resultSuggestions[0].data).toBe('A'); }); }); @@ -597,4 +602,56 @@ describe('Autocomplete', function () { expect(instance.suggestions.length).toBe(limit); }); -}); \ No newline at end of file + + it('Should prevent Ajax requests if previous query with matching root failed.', function () { + var input = $(''), + instance, + serviceUrl = '/autocomplete/prevent/ajax', + ajaxCount = 0; + + input.autocomplete({ + serviceUrl: serviceUrl + }); + + $.mockjax({ + url: serviceUrl, + responseTime: 5, + response: function (settings) { + ajaxCount++; + var response = { suggestions: [] }; + this.responseText = JSON.stringify(response); + } + }); + + input.val('Jam'); + instance = input.autocomplete(); + instance.onValueChange(); + + waits(10); + + runs(function (){ + expect(ajaxCount).toBe(1); + input.val('Jama'); + instance.onValueChange(); + }); + + waits(10); + + runs(function (){ + // Ajax call should not have bee made: + expect(ajaxCount).toBe(1); + + // Change setting and continue: + instance.setOptions({ preventBadQueries: false }); + input.val('Jamai'); + instance.onValueChange(); + }); + + waits(10); + + runs(function (){ + // Ajax call should have been made: + expect(ajaxCount).toBe(2); + }); + }); +}); diff --git a/src/jquery.autocomplete.js b/src/jquery.autocomplete.js index 88548d4..f2114bf 100644 --- a/src/jquery.autocomplete.js +++ b/src/jquery.autocomplete.js @@ -76,6 +76,7 @@ dataType: 'text', currentRequest: null, triggerSelectOnValidInput: true, + preventBadQueries: true, lookupFilter: function (suggestion, originalQuery, queryLowerCase) { return suggestion.value.toLowerCase().indexOf(queryLowerCase) !== -1; }, @@ -473,11 +474,11 @@ that = this, options = that.options, serviceUrl = options.serviceUrl, - data, + params, cacheKey; options.params[options.paramName] = q; - data = options.ignoreParams ? null : options.params; + params = options.ignoreParams ? null : options.params; if (that.isLocal) { response = that.getSuggestionsLocal(q); @@ -485,7 +486,7 @@ if ($.isFunction(serviceUrl)) { serviceUrl = serviceUrl.call(that.element, q); } - cacheKey = serviceUrl + '?' + $.param(data || {}); + cacheKey = serviceUrl + '?' + $.param(params || {}); response = that.cachedResponse[cacheKey]; } @@ -501,13 +502,15 @@ } that.currentRequest = $.ajax({ url: serviceUrl, - data: data, + data: params, type: options.type, dataType: options.dataType }).done(function (data) { + var result; that.currentRequest = null; - that.processResponse(data, q, cacheKey); - options.onSearchComplete.call(that.element, q); + result = options.transformResult(data); + that.processResponse(result, q, cacheKey); + options.onSearchComplete.call(that.element, q, result.suggestions); }).fail(function (jqXHR, textStatus, errorThrown) { options.onSearchError.call(that.element, q, jqXHR, textStatus, errorThrown); }); @@ -515,6 +518,10 @@ }, isBadQuery: function (q) { + if (!this.options.preventBadQueries){ + return false; + } + var badQueries = this.badQueries, i = badQueries.length; @@ -637,18 +644,17 @@ return suggestions; }, - processResponse: function (response, originalQuery, cacheKey) { + processResponse: function (result, originalQuery, cacheKey) { var that = this, - options = that.options, - result = options.transformResult(response, originalQuery); + options = that.options; result.suggestions = that.verifySuggestionsFormat(result.suggestions); // Cache results if cache is not disabled: if (!options.noCache) { that.cachedResponse[cacheKey] = result; - if (result.suggestions.length === 0) { - that.badQueries.push(cacheKey); + if (options.preventBadQueries && result.suggestions.length === 0) { + that.badQueries.push(originalQuery); } }