mirror of
https://github.com/devbridge/jQuery-Autocomplete.git
synced 2024-12-23 03:18:55 +00:00
Add ability to select suggestion if it matches typed value. Fixes #112.
This commit is contained in:
parent
6e7bbf3ea5
commit
3542ada13f
@ -42,6 +42,7 @@
|
|||||||
<script type="text/javascript" src="scripts/jquery-1.8.2.min.js"></script>
|
<script type="text/javascript" src="scripts/jquery-1.8.2.min.js"></script>
|
||||||
<script type="text/javascript" src="scripts/jquery.mockjax.js"></script>
|
<script type="text/javascript" src="scripts/jquery.mockjax.js"></script>
|
||||||
<script type="text/javascript" src="src/jquery.autocomplete.js"></script>
|
<script type="text/javascript" src="src/jquery.autocomplete.js"></script>
|
||||||
|
<script type="text/javascript" src="scripts/countries.js"></script>
|
||||||
<script type="text/javascript" src="scripts/demo.js"></script>
|
<script type="text/javascript" src="scripts/demo.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -33,6 +33,8 @@ The standard jquery.autocomplete.js file is around 2.7KB when minified via Closu
|
|||||||
* `onSearchStart`: `function (query) {}` called before ajax request. `this` is bound to input element.
|
* `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) {}` called after ajax response is processed. `this` is bound to input element.
|
||||||
* `onSearchError`: `function (query, jqXHR, textStatus, errorThrown) {}` called if ajax request fails. `this` is bound to input element.
|
* `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`.
|
||||||
* `beforeRender`: `function (container) {}` called before displaying the suggestions. You may manipulate suggestions DOM before it is displayed.
|
* `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.
|
* `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.
|
* `paramName`: Default `query`. The name of the request parameter that contains the query.
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
{
|
var countries = {
|
||||||
"AD": "Andorra",
|
"AD": "Andorra",
|
||||||
"AE": "United Arab Emirates",
|
"AE": "United Arab Emirates",
|
||||||
"AF": "Afghanistan",
|
"AF": "Afghanistan",
|
126
scripts/demo.js
126
scripts/demo.js
@ -1,78 +1,68 @@
|
|||||||
/*jslint browser: true, white: true, plusplus: true */
|
/*jslint browser: true, white: true, plusplus: true */
|
||||||
/*global $: true */
|
/*global $, countries */
|
||||||
|
|
||||||
$(function () {
|
$(function () {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
// Load countries then initialize plugin:
|
var countriesArray = $.map(countries, function (value, key) { return { value: value, data: key }; });
|
||||||
$.ajax({
|
|
||||||
url: 'content/countries.txt',
|
|
||||||
dataType: 'json'
|
|
||||||
}).done(function (source) {
|
|
||||||
|
|
||||||
var countriesArray = $.map(source, function (value, key) { return { value: value, data: key }; }),
|
// Setup jQuery ajax mock:
|
||||||
countries = $.map(source, function (value) { return value; });
|
$.mockjax({
|
||||||
|
url: '*',
|
||||||
|
responseTime: 2000,
|
||||||
|
response: function (settings) {
|
||||||
|
var query = settings.data.query,
|
||||||
|
queryLowerCase = query.toLowerCase(),
|
||||||
|
re = new RegExp('\\b' + $.Autocomplete.utils.escapeRegExChars(queryLowerCase), 'gi'),
|
||||||
|
suggestions = $.grep(countriesArray, function (country) {
|
||||||
|
// return country.value.toLowerCase().indexOf(queryLowerCase) === 0;
|
||||||
|
return re.test(country.value);
|
||||||
|
}),
|
||||||
|
response = {
|
||||||
|
query: query,
|
||||||
|
suggestions: suggestions
|
||||||
|
};
|
||||||
|
|
||||||
// Setup jQuery ajax mock:
|
this.responseText = JSON.stringify(response);
|
||||||
$.mockjax({
|
}
|
||||||
url: '*',
|
|
||||||
responseTime: 2000,
|
|
||||||
response: function (settings) {
|
|
||||||
var query = settings.data.query,
|
|
||||||
queryLowerCase = query.toLowerCase(),
|
|
||||||
re = new RegExp('\\b' + $.Autocomplete.utils.escapeRegExChars(queryLowerCase), 'gi'),
|
|
||||||
suggestions = $.grep(countriesArray, function (country) {
|
|
||||||
// return country.value.toLowerCase().indexOf(queryLowerCase) === 0;
|
|
||||||
return re.test(country.value);
|
|
||||||
}),
|
|
||||||
response = {
|
|
||||||
query: query,
|
|
||||||
suggestions: suggestions
|
|
||||||
};
|
|
||||||
|
|
||||||
this.responseText = JSON.stringify(response);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Initialize ajax autocomplete:
|
|
||||||
$('#autocomplete-ajax').autocomplete({
|
|
||||||
// serviceUrl: '/autosuggest/service/url',
|
|
||||||
lookup: countriesArray,
|
|
||||||
lookupFilter: function(suggestion, originalQuery, queryLowerCase) {
|
|
||||||
var re = new RegExp('\\b' + $.Autocomplete.utils.escapeRegExChars(queryLowerCase), 'gi');
|
|
||||||
return re.test(suggestion.value);
|
|
||||||
},
|
|
||||||
onSelect: function(suggestion) {
|
|
||||||
$('#selction-ajax').html('You selected: ' + suggestion.value + ', ' + suggestion.data);
|
|
||||||
},
|
|
||||||
onHint: function (hint) {
|
|
||||||
$('#autocomplete-ajax-x').val(hint);
|
|
||||||
},
|
|
||||||
onInvalidateSelection: function() {
|
|
||||||
$('#selction-ajax').html('You selected: none');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Initialize autocomplete with local lookup:
|
|
||||||
$('#autocomplete').autocomplete({
|
|
||||||
lookup: countriesArray,
|
|
||||||
minChars: 0,
|
|
||||||
onSelect: function (suggestion) {
|
|
||||||
$('#selection').html('You selected: ' + suggestion.value + ', ' + suggestion.data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Initialize autocomplete with custom appendTo:
|
|
||||||
$('#autocomplete-custom-append').autocomplete({
|
|
||||||
lookup: countriesArray,
|
|
||||||
appendTo: '#suggestions-container'
|
|
||||||
});
|
|
||||||
|
|
||||||
// Initialize autocomplete with custom appendTo:
|
|
||||||
$('#autocomplete-dynamic').autocomplete({
|
|
||||||
lookup: countriesArray
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Initialize ajax autocomplete:
|
||||||
|
$('#autocomplete-ajax').autocomplete({
|
||||||
|
// serviceUrl: '/autosuggest/service/url',
|
||||||
|
lookup: countriesArray,
|
||||||
|
lookupFilter: function(suggestion, originalQuery, queryLowerCase) {
|
||||||
|
var re = new RegExp('\\b' + $.Autocomplete.utils.escapeRegExChars(queryLowerCase), 'gi');
|
||||||
|
return re.test(suggestion.value);
|
||||||
|
},
|
||||||
|
onSelect: function(suggestion) {
|
||||||
|
$('#selction-ajax').html('You selected: ' + suggestion.value + ', ' + suggestion.data);
|
||||||
|
},
|
||||||
|
onHint: function (hint) {
|
||||||
|
$('#autocomplete-ajax-x').val(hint);
|
||||||
|
},
|
||||||
|
onInvalidateSelection: function() {
|
||||||
|
$('#selction-ajax').html('You selected: none');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Initialize autocomplete with local lookup:
|
||||||
|
$('#autocomplete').autocomplete({
|
||||||
|
lookup: countriesArray,
|
||||||
|
minChars: 0,
|
||||||
|
onSelect: function (suggestion) {
|
||||||
|
$('#selection').html('You selected: ' + suggestion.value + ', ' + suggestion.data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Initialize autocomplete with custom appendTo:
|
||||||
|
$('#autocomplete-custom-append').autocomplete({
|
||||||
|
lookup: countriesArray,
|
||||||
|
appendTo: '#suggestions-container'
|
||||||
|
});
|
||||||
|
|
||||||
|
// Initialize autocomplete with custom appendTo:
|
||||||
|
$('#autocomplete-dynamic').autocomplete({
|
||||||
|
lookup: countriesArray
|
||||||
|
});
|
||||||
});
|
});
|
@ -66,14 +66,15 @@ describe('Autocomplete', function () {
|
|||||||
context,
|
context,
|
||||||
value,
|
value,
|
||||||
data,
|
data,
|
||||||
autocomplete = new $.Autocomplete(input, {
|
autocomplete = $(input).autocomplete({
|
||||||
lookup: [{ value: 'A', data: 'B' }],
|
lookup: [{ value: 'A', data: 'B' }],
|
||||||
|
triggerSelectOnValidInput: false,
|
||||||
onSelect: function (suggestion) {
|
onSelect: function (suggestion) {
|
||||||
context = this;
|
context = this;
|
||||||
value = suggestion.value;
|
value = suggestion.value;
|
||||||
data = suggestion.data;
|
data = suggestion.data;
|
||||||
}
|
}
|
||||||
});
|
}).autocomplete();
|
||||||
|
|
||||||
input.value = 'A';
|
input.value = 'A';
|
||||||
autocomplete.onValueChange();
|
autocomplete.onValueChange();
|
||||||
@ -260,7 +261,7 @@ describe('Autocomplete', function () {
|
|||||||
ajaxExecuted = true;
|
ajaxExecuted = true;
|
||||||
var response = {
|
var response = {
|
||||||
query: null,
|
query: null,
|
||||||
suggestions: ['A', 'B', 'C']
|
suggestions: ['Aa', 'Bb', 'Cc']
|
||||||
};
|
};
|
||||||
this.responseText = JSON.stringify(response);
|
this.responseText = JSON.stringify(response);
|
||||||
}
|
}
|
||||||
@ -276,7 +277,7 @@ describe('Autocomplete', function () {
|
|||||||
runs(function () {
|
runs(function () {
|
||||||
expect(ajaxExecuted).toBe(true);
|
expect(ajaxExecuted).toBe(true);
|
||||||
expect(autocomplete.suggestions.length).toBe(3);
|
expect(autocomplete.suggestions.length).toBe(3);
|
||||||
expect(autocomplete.suggestions[0].value).toBe('A');
|
expect(autocomplete.suggestions[0].value).toBe('Aa');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -496,4 +497,44 @@ describe('Autocomplete', function () {
|
|||||||
expect(context).toBe(element);
|
expect(context).toBe(element);
|
||||||
expect(elementCount).toBe(1);
|
expect(elementCount).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('Should trigger select when input value matches suggestion', function () {
|
||||||
|
var input = $('<input />'),
|
||||||
|
instance,
|
||||||
|
suggestionData = false;
|
||||||
|
|
||||||
|
input.autocomplete({
|
||||||
|
lookup: [{ value: 'Jamaica', data: 'J' }],
|
||||||
|
triggerSelectOnValidInput: true,
|
||||||
|
onSelect: function (suggestion) {
|
||||||
|
suggestionData = suggestion.data;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
input.val('Jamaica');
|
||||||
|
instance = input.autocomplete();
|
||||||
|
instance.onValueChange();
|
||||||
|
|
||||||
|
expect(suggestionData).toBe('J');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should NOT trigger select when input value matches suggestion', function () {
|
||||||
|
var input = $('<input />'),
|
||||||
|
instance,
|
||||||
|
suggestionData = null;
|
||||||
|
|
||||||
|
input.autocomplete({
|
||||||
|
lookup: [{ value: 'Jamaica', data: 'J' }],
|
||||||
|
triggerSelectOnValidInput: false,
|
||||||
|
onSelect: function (suggestion) {
|
||||||
|
suggestionData = suggestion.data;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
input.val('Jamaica');
|
||||||
|
instance = input.autocomplete();
|
||||||
|
instance.onValueChange();
|
||||||
|
|
||||||
|
expect(suggestionData).toBeNull();
|
||||||
|
});
|
||||||
});
|
});
|
@ -75,6 +75,7 @@
|
|||||||
tabDisabled: false,
|
tabDisabled: false,
|
||||||
dataType: 'text',
|
dataType: 'text',
|
||||||
currentRequest: null,
|
currentRequest: null,
|
||||||
|
triggerSelectOnValidInput: true,
|
||||||
lookupFilter: function (suggestion, originalQuery, queryLowerCase) {
|
lookupFilter: function (suggestion, originalQuery, queryLowerCase) {
|
||||||
return suggestion.value.toLowerCase().indexOf(queryLowerCase) !== -1;
|
return suggestion.value.toLowerCase().indexOf(queryLowerCase) !== -1;
|
||||||
},
|
},
|
||||||
@ -390,32 +391,57 @@
|
|||||||
|
|
||||||
onValueChange: function () {
|
onValueChange: function () {
|
||||||
var that = this,
|
var that = this,
|
||||||
q;
|
options = that.options,
|
||||||
|
value = that.el.val(),
|
||||||
|
query = that.getQuery(value),
|
||||||
|
index;
|
||||||
|
|
||||||
if (that.selection) {
|
if (that.selection) {
|
||||||
that.selection = null;
|
that.selection = null;
|
||||||
(that.options.onInvalidateSelection || $.noop)();
|
(options.onInvalidateSelection || $.noop).call(that.element);
|
||||||
}
|
}
|
||||||
|
|
||||||
clearInterval(that.onChangeInterval);
|
clearInterval(that.onChangeInterval);
|
||||||
that.currentValue = that.el.val();
|
that.currentValue = value;
|
||||||
|
|
||||||
q = that.getQuery(that.currentValue);
|
|
||||||
that.selectedIndex = -1;
|
that.selectedIndex = -1;
|
||||||
|
|
||||||
if (q.length < that.options.minChars) {
|
// Check existing suggestion for the match before proceeding:
|
||||||
|
if (options.triggerSelectOnValidInput) {
|
||||||
|
index = that.findSuggestionIndex(query);
|
||||||
|
if (index !== -1) {
|
||||||
|
that.select(index);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (query.length < options.minChars) {
|
||||||
that.hide();
|
that.hide();
|
||||||
} else {
|
} else {
|
||||||
that.getSuggestions(q);
|
that.getSuggestions(query);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
findSuggestionIndex: function (query) {
|
||||||
|
var that = this,
|
||||||
|
index = -1,
|
||||||
|
queryLowerCase = query.toLowerCase();
|
||||||
|
|
||||||
|
$.each(that.suggestions, function (i, suggestion) {
|
||||||
|
if (suggestion.value.toLowerCase() === queryLowerCase) {
|
||||||
|
index = i;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return index;
|
||||||
|
},
|
||||||
|
|
||||||
getQuery: function (value) {
|
getQuery: function (value) {
|
||||||
var delimiter = this.options.delimiter,
|
var delimiter = this.options.delimiter,
|
||||||
parts;
|
parts;
|
||||||
|
|
||||||
if (!delimiter) {
|
if (!delimiter) {
|
||||||
return $.trim(value);
|
return value;
|
||||||
}
|
}
|
||||||
parts = value.split(delimiter);
|
parts = value.split(delimiter);
|
||||||
return $.trim(parts[parts.length - 1]);
|
return $.trim(parts[parts.length - 1]);
|
||||||
@ -498,15 +524,25 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
var that = this,
|
var that = this,
|
||||||
formatResult = that.options.formatResult,
|
options = that.options,
|
||||||
|
formatResult = options.formatResult,
|
||||||
value = that.getQuery(that.currentValue),
|
value = that.getQuery(that.currentValue),
|
||||||
className = that.classes.suggestion,
|
className = that.classes.suggestion,
|
||||||
classSelected = that.classes.selected,
|
classSelected = that.classes.selected,
|
||||||
container = $(that.suggestionsContainer),
|
container = $(that.suggestionsContainer),
|
||||||
beforeRender = that.options.beforeRender,
|
beforeRender = options.beforeRender,
|
||||||
html = '',
|
html = '',
|
||||||
|
index,
|
||||||
width;
|
width;
|
||||||
|
|
||||||
|
if (options.triggerSelectOnValidInput) {
|
||||||
|
index = that.findSuggestionIndex(value);
|
||||||
|
if (index !== -1) {
|
||||||
|
that.select(index);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Build suggestions inner HTML:
|
// Build suggestions inner HTML:
|
||||||
$.each(that.suggestions, function (i, suggestion) {
|
$.each(that.suggestions, function (i, suggestion) {
|
||||||
html += '<div class="' + className + '" data-index="' + i + '">' + formatResult(suggestion, value) + '</div>';
|
html += '<div class="' + className + '" data-index="' + i + '">' + formatResult(suggestion, value) + '</div>';
|
||||||
@ -516,7 +552,7 @@
|
|||||||
// because if instance was created before input had width, it will be zero.
|
// because if instance was created before input had width, it will be zero.
|
||||||
// Also it adjusts if input width has changed.
|
// Also it adjusts if input width has changed.
|
||||||
// -2px to account for suggestions border.
|
// -2px to account for suggestions border.
|
||||||
if (that.options.width === 'auto') {
|
if (options.width === 'auto') {
|
||||||
width = that.el.outerWidth() - 2;
|
width = that.el.outerWidth() - 2;
|
||||||
container.width(width > 0 ? width : 300);
|
container.width(width > 0 ? width : 300);
|
||||||
}
|
}
|
||||||
@ -524,7 +560,7 @@
|
|||||||
container.html(html);
|
container.html(html);
|
||||||
|
|
||||||
// Select first value by default:
|
// Select first value by default:
|
||||||
if (that.options.autoSelectFirst) {
|
if (options.autoSelectFirst) {
|
||||||
that.selectedIndex = 0;
|
that.selectedIndex = 0;
|
||||||
container.children().first().addClass(classSelected);
|
container.children().first().addClass(classSelected);
|
||||||
}
|
}
|
||||||
@ -598,11 +634,13 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Display suggestions only if returned query matches current value:
|
// Return if originalQuery is not matching current query:
|
||||||
if (originalQuery === that.getQuery(that.currentValue)) {
|
if (originalQuery !== that.getQuery(that.currentValue)) {
|
||||||
that.suggestions = result.suggestions;
|
return;
|
||||||
that.suggest();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
that.suggestions = result.suggestions;
|
||||||
|
that.suggest();
|
||||||
},
|
},
|
||||||
|
|
||||||
activate: function (index) {
|
activate: function (index) {
|
||||||
|
Loading…
Reference in New Issue
Block a user