Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
63 changes: 60 additions & 3 deletions spec/autocompleteBehavior.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
});

Expand All @@ -153,7 +156,7 @@ describe('Autocomplete', function () {
var query = settings.data.query,
response = {
query: query,
suggestions: []
suggestions: [mockupSuggestion]
};
this.responseText = JSON.stringify(response);
}
Expand All @@ -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');
});
});

Expand Down Expand Up @@ -597,4 +602,56 @@ describe('Autocomplete', function () {

expect(instance.suggestions.length).toBe(limit);
});
});

it('Should prevent Ajax requests if previous query with matching root failed.', function () {
var input = $('<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);
});
});
});
28 changes: 17 additions & 11 deletions src/jquery.autocomplete.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@
dataType: 'text',
currentRequest: null,
triggerSelectOnValidInput: true,
preventBadQueries: true,
lookupFilter: function (suggestion, originalQuery, queryLowerCase) {
return suggestion.value.toLowerCase().indexOf(queryLowerCase) !== -1;
},
Expand Down Expand Up @@ -473,19 +474,19 @@
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);
} else {
if ($.isFunction(serviceUrl)) {
serviceUrl = serviceUrl.call(that.element, q);
}
cacheKey = serviceUrl + '?' + $.param(data || {});
cacheKey = serviceUrl + '?' + $.param(params || {});
response = that.cachedResponse[cacheKey];
}

Expand All @@ -501,20 +502,26 @@
}
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);
});
}
},

isBadQuery: function (q) {
if (!this.options.preventBadQueries){
return false;
}

var badQueries = this.badQueries,
i = badQueries.length;

Expand Down Expand Up @@ -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);
}
}

Expand Down